From 643970525eaea72108963ec38f557e0d597ea747 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Mon, 9 Sep 2019 22:13:01 -0500 Subject: [PATCH] Add support for context --- source/conf.py.example | 31 +- source/easymacro.py | 740 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 737 insertions(+), 34 deletions(-) diff --git a/source/conf.py.example b/source/conf.py.example index 768330a..a58f314 100644 --- a/source/conf.py.example +++ b/source/conf.py.example @@ -48,8 +48,6 @@ ICON_EXT = f'{NAME.lower()}.png' # ~ DEPENDENCIES_MINIMAL = '6.0' DEPENDENCIES_MINIMAL = '' -LICENSE_ACCEPT_BY = 'user' # or admin -LICENSE_SUPPRESS_ON_UPDATE = True # ~ Change for you favorite license LICENSE_EN = f"""This file is part of {NAME}. @@ -84,6 +82,12 @@ INFO = { CONTEXT = { 'calc': 'com.sun.star.sheet.SpreadsheetDocument', + 'writer': 'com.sun.star.text.TextDocument', + 'impress': 'com.sun.star.presentation.PresentationDocument', + 'draw': 'com.sun.star.drawing.DrawingDocument', + 'base': 'com.sun.star.sdb.OfficeDatabaseDocument', + 'math': 'com.sun.star.formula.FormulaProperties', + 'basic': 'com.sun.star.script.BasicIDE', } @@ -101,7 +105,7 @@ MENUS = ( { 'title': {'en': 'Option 1', 'es': 'OpciĆ³n 1'}, 'argument': 'option1', - 'context': '', + 'context': 'calc,writer', 'icon': 'icon', 'toolbar': True, }, @@ -165,11 +169,6 @@ URL_XML_UPDATE = '' URL_OXT = '' -# ~ If used user profile for develop -# ~ PATH_DEV = '-env:UserInstallation=file:///home/mau/.temp/develop' -# ~ unopkg not support (or I not know how) other user profile -PATH_DEV = '' - # ~ Default program for test: --calc, --writer, --draw PROGRAM = '--calc' # ~ Path to file for test @@ -305,7 +304,7 @@ FILE_DESCRIPTION = f""" {NODE_PUBLISHER} - + {NODE_LICENSE} {NODE_DEPENDENCIES_MINIMAL}{NODE_UPDATE} @@ -334,6 +333,14 @@ opt = 'fuse' if PARENT == 'OfficeMenuBar': opt = 'replace' + +def _get_context(args): + c = [] + for v in args.split(','): + c.append(CONTEXT[v]) + return ','.join(c) + + menus = [] toolbar = [] tmp = ' {}' @@ -345,7 +352,7 @@ for i, m in enumerate(MENUS): 'opt': opt, 'titles': '\n'.join(titles), 'argument': m['argument'], - 'context': m['context'], + 'context': _get_context(m['context']), 'folder': DIRS['images'], 'icon': m['icon'], } @@ -549,3 +556,7 @@ DATA = { 'idl': FILE_IDL, 'addin': FILE_ADDIN, } + + +# ~ LICENSE_ACCEPT_BY = 'user' # or admin +# ~ LICENSE_SUPPRESS_ON_UPDATE = True diff --git a/source/easymacro.py b/source/easymacro.py index aa607f0..76de6cf 100644 --- a/source/easymacro.py +++ b/source/easymacro.py @@ -20,6 +20,7 @@ import getpass import logging +import os import platform import sys import tempfile @@ -28,14 +29,20 @@ import time from functools import wraps import uno - +import unohelper from com.sun.star.beans import PropertyValue from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS from com.sun.star.awt.MessageBoxResults import YES from com.sun.star.awt.PosSize import POSSIZE, SIZE -from com.sun.star.awt import Size +from com.sun.star.awt import Size, Point from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA +from com.sun.star.text.TextContentAnchorType import AS_CHARACTER + +from com.sun.star.lang import XEventListener +from com.sun.star.awt import XActionListener +from com.sun.star.awt import XMouseListener + FILE_NAME_DEBUG = 'debug.log' MSG_LANG = { @@ -60,7 +67,7 @@ log = logging.getLogger(__name__) OBJ_CELL = 'ScCellObj' OBJ_RANGE = 'ScCellRangeObj' OBJ_RANGES = 'ScCellRangesObj' - +OBJ_TYPE_RANGES = (OBJ_CELL, OBJ_RANGE, OBJ_RANGES) CTX = uno.getComponentContext() SM = CTX.getServiceManager() @@ -96,7 +103,7 @@ PC = platform.node() LANGUAGE = _get_config('ooLocale', 'org.openoffice.Setup/L10N/') NAME = TITLE = _get_config('ooName', 'org.openoffice.Setup/Product') VERSION = _get_config('ooSetupVersion', 'org.openoffice.Setup/Product') -DESKTOP = create_instance('com.sun.star.frame.Desktop', True) +# ~ DESKTOP = create_instance('com.sun.star.frame.Desktop', True) INFO_DEBUG = '{}\n\n{}\n\n{}'.format( sys.version, platform.platform(), '\n'.join(sys.path)) @@ -176,6 +183,10 @@ def errorbox(message, title=TITLE): return msgbox(message, title, type_msg='errorbox') +def get_desktop(): + return create_instance('com.sun.star.frame.Desktop', True) + + def get_temp_file(): return tempfile.NamedTemporaryFile() @@ -186,35 +197,322 @@ def _path_url(path): return uno.systemPathToFileUrl(path) +def _path_system(path): + if path.startswith('file://'): + return os.path.abspath(uno.fileUrlToSystemPath(path)) + return path + + +def get_type_doc(obj): + services = { + 'calc': 'com.sun.star.sheet.SpreadsheetDocument', + 'writer': 'com.sun.star.text.TextDocument', + 'impress': 'com.sun.star.presentation.PresentationDocument', + 'draw': 'com.sun.star.drawing.DrawingDocument', + 'base': 'com.sun.star.sdb.OfficeDatabaseDocument', + 'math': 'com.sun.star.formula.FormulaProperties', + 'basic': 'com.sun.star.script.BasicIDE', + } + for k, v in services.items(): + if obj.supportsService(v): + return k + return '' + + # ~ Custom classes -class LOCellRange(object): + + +class LODocument(object): def __init__(self, obj): self._obj = obj self._init_values() + def _init_values(self): + self._type_doc = get_type_doc(self.obj) + self._cc = self.obj.getCurrentController() + return + + @property + def obj(self): + return self._obj + + @property + def type(self): + return self._type_doc + + @property + def title(self): + return self.obj.getTitle() + + @property + def is_saved(self): + return self.obj.hasLocation() + + @property + def is_modified(self): + return self.obj.isModified() + + @property + def is_read_only(self): + return self.obj.isReadOnly() + + @property + def path(self): + return _path_system(self.obj.getURL()) + + @property + def visible(self): + w = self._cc.getFrame().getContainerWindow() + return w.Visible + @visible.setter + def visible(self, value): + w = self._cc.getFrame().getContainerWindow() + w.setVisible(value) + + @property + def zoom(self): + return self._cc.ZoomValue + @zoom.setter + def zoom(self, value): + self._cc.ZoomValue = value + + def create_instance(self, name): + obj = self.obj.createInstance(name) + return obj + + +class LOCalc(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + @property + def obj(self): + return self._obj + + @property + def active(self): + return LOCalcSheet(self._cc.getActiveSheet(), self) + + @property + def selection(self): + sel = self.obj.getCurrentSelection() + if sel.ImplementationName in OBJ_TYPE_RANGES: + sel = LOCellRange(sel, self) + return sel + + def get_cell(self, index=None): + """ + index is str 'A1' + index is tuple (row, col) + """ + if index is None: + cell = self.selection.first + else: + cell = LOCellRange(self.active[index].obj, self) + return cell + + # ~ def create_instance(self, name): + # ~ obj = self.obj.createInstance(name) + # ~ return obj + + +class LOCalcSheet(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + self._init_values() + + def __getitem__(self, index): + return LOCellRange(self.obj[index], self.doc) + + def _init_values(self): + return + + @property + def obj(self): + return self._obj + + @property + def doc(self): + return self._doc + + +class LOWriter(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + @property + def obj(self): + return self._obj + + @property + def string(self): + return self._obj.getText().String + + @property + def text(self): + return self._obj.getText() + + @property + def cursor(self): + return self.text.createTextCursor() + + @property + def selection(self): + sel = self._cc.getSelection() + return LOTextRange(sel[0]) + + def insert_content(self, cursor, data, replace=False): + self.text.insertTextContent(cursor, data, replace) + return + + # ~ tt = doc.createInstance('com.sun.star.text.TextTable') + # ~ tt.initialize(5, 2) + + # ~ f = doc.createInstance('com.sun.star.text.TextFrame') + # ~ f.setSize(Size(10000, 500)) + + def insert_image(self, path, **kwargs): + cursor = kwargs.get('cursor', self.selection.cursor.getEnd()) + w = kwargs.get('width', 1000) + h = kwargs.get('Height', 1000) + image = self.create_instance('com.sun.star.text.GraphicObject') + image.GraphicURL = _path_url(path) + image.AnchorType = AS_CHARACTER + image.Width = w + image.Height = h + self.insert_content(cursor, image) + return + + +class LOTextRange(object): + + def __init__(self, obj): + self._obj = obj + + @property + def obj(self): + return self._obj + + @property + def string(self): + return self.obj.String + + @property + def text(self): + return self.obj.getText() + + @property + def cursor(self): + return self.text.createTextCursorByRange(self.obj) + + +class LOBase(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + +class LODrawImpress(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + @property + def draw_page(self): + return self._cc.getCurrentPage() + + @catch_exception + def insert_image(self, path, **kwargs): + w = kwargs.get('width', 3000) + h = kwargs.get('Height', 1000) + x = kwargs.get('X', 1000) + y = kwargs.get('Y', 1000) + + image = self.create_instance('com.sun.star.drawing.GraphicObjectShape') + image.GraphicURL = _path_url(path) + image.Size = Size(w, h) + image.Position = Point(x, y) + self.draw_page.add(image) + return + + +class LOImpress(LODrawImpress): + + def __init__(self, obj): + super().__init__(obj) + + +class LODraw(LODrawImpress): + + def __init__(self, obj): + super().__init__(obj) + + +class LOMath(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + +class LOBasicIde(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + +class LOCellRange(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + self._init_values() + def __enter__(self): return self def __exit__(self, *args): pass + def __getitem__(self, index): + return LOCellRange(self.obj[index], self.doc) + def _init_values(self): - self._type_obj = self._obj.ImplementationName + self._type_obj = self.obj.ImplementationName self._type_content = EMPTY if self._type_obj == OBJ_CELL: - self._type_content = self._obj.getType() + self._type_content = self.obj.getType() return @property def obj(self): return self._obj + @property + def doc(self): + return self._doc + + @property + def type(self): + return self._type_obj + @property def type_content(self): return self._type_content + @property + def first(self): + if self.type == OBJ_RANGES: + obj = LOCellRange(self.obj[0][0,0], self.doc) + else: + obj = LOCellRange(self.obj[0,0], self.doc) + return obj + @property def value(self): v = None @@ -235,11 +533,20 @@ class LOCellRange(object): elif isinstance(data, (int, float)): self.obj.setValue(data) + @property + def data(self): + return self.obj.getDataArray() + @data.setter + def data(self, values): + if isinstance(values, list): + values = tuple(values) + self.obj.setDataArray(values) + def offset(self, col=1, row=0): a = self.address col = a.Column + col row = a.Row + row - return LOCellRange(self.obj.Spreadsheet[row,col]) + return LOCellRange(self.sheet[row,col], self.doc) @property def sheet(self): @@ -263,12 +570,17 @@ class LOCellRange(object): a = self.obj.getRangeAddressesAsString() return a - def add_image(self, path, **kwargs): + @property + def current_region(self): + cursor = self.sheet.createCursorByRange(self.obj[0,0]) + cursor.collapseToCurrentRegion() + return LOCellRange(self.sheet[cursor.AbsoluteName], self.doc) + + def insert_image(self, path, **kwargs): s = self.obj.Size w = kwargs.get('width', s.Width) h = kwargs.get('Height', s.Height) - doc = get_document() - img = doc.createInstance('com.sun.star.drawing.GraphicObjectShape') + img = self.doc.create_instance('com.sun.star.drawing.GraphicObjectShape') img.GraphicURL = _path_url(path) self.draw_page.add(img) img.Anchor = self.obj @@ -276,33 +588,413 @@ class LOCellRange(object): return +class EventsListenerBase(unohelper.Base, XEventListener): + + def __init__(self, controller, window=None): + self._controller = controller + self._window = window + + def disposing(self, event): + self._controller = None + if not self._window is None: + self._window.setMenuBar(None) + + +class EventsButton(EventsListenerBase, XActionListener): + + def __init__(self, controller): + super().__init__(controller) + + def actionPerformed(self, event): + name = event.Source.Model.Name + event_name = '{}_action'.format(name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + +class EventsMouse(EventsListenerBase, XMouseListener): + + def __init__(self, controller): + super().__init__(controller) + + def mousePressed(self, event): + name = event.Source.Model.Name + event_name = '{}_click'.format(name) + if event.ClickCount == 2: + event_name = '{}_double_click'.format(name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def mouseReleased(self, event): + pass + + def mouseEntered(self, event): + pass + + def mouseExited(self, event): + pass + + +class UnoBaseObject(object): + + def __init__(self, obj): + self._obj = obj + self._model = self.obj.Model + self._rules = {} + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._model + + @property + def name(self): + return self.model.Name + + @property + def parent(self): + return self.obj.getContext() + + @property + def x(self): + return self.model.PositionX + @x.setter + def x(self, value): + self.model.PositionX = value + + @property + def y(self): + return self.model.PositionY + @y.setter + def y(self, value): + self.model.PositionY = value + + @property + def width(self): + return self._model.Width + @width.setter + def width(self, value): + self._model.Width = value + + @property + def height(self): + return self._model.Height + @height.setter + def height(self, value): + self._model.Height = value + + @property + def tag(self): + return self.model.Tag + @tag.setter + def tag(self, value): + self.model.Tag = value + + @property + def step(self): + return self.model.Step + @step.setter + def step(self, value): + self.model.Step = value + + @property + def rules(self): + return self._rules + @rules.setter + def rules(self, value): + self._rules = value + + def set_focus(self): + self.obj.setFocus() + return + + def center(self, horizontal=True, vertical=False): + p = self.parent.Model + w = p.Width + h = p.Height + if horizontal: + x = w / 2 - self.width / 2 + self.x = x + if vertical: + y = h / 2 - self.height / 2 + self.y = y + return + + def move(self, origin, x=0, y=5): + w = 0 + h = 0 + if x: + w = origin.width + if y: + h = origin.height + x = origin.x + x + w + y = origin.y + y + h + self.x = x + self.y = y + return + + +class UnoLabel(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def value(self): + return self.model.Label + @value.setter + def value(self, value): + self.model.Label = value + + +class UnoButton(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + # ~ self._set_icon() + + def _set_icon(self): + icon_name = self.tag.strip() + if icon_name: + path_icon = _file_url('{}/img/{}'.format(CURRENT_PATH, icon_name)) + self._model.ImageURL = path_icon + if self.value: + self._model.ImageAlign = 0 + return + + @property + def value(self): + return self.model.Label + @value.setter + def value(self, value): + self.model.Label = value + + +class UnoText(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def value(self): + return self.model.Text + @value.setter + def value(self, value): + self.model.Text = value + + def validate(self): + + return + + +class UnoListBox(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._data = [] + + @property + def value(self): + return self.obj.SelectedItem + + @property + def data(self): + return self._data + @data.setter + def data(self, values): + self._data = list(sorted(values)) + self.model.StringItemList = self.data + return + + +class LODialog(object): + + def __init__(self, properties): + self._obj = self._create(properties) + self._init_values() + + def _init_values(self): + self._model = self._obj.Model + self._init_controls() + self._events = None + # ~ self._response = None + return + + def _create(self, properties): + path = properties.pop('Path', '') + if path: + dp = create_instance('com.sun.star.awt.DialogProvider2', True) + return dp.createDialog(_path_url(path)) + + if 'Library' in properties: + location = properties['Location'] + if location == 'user': + location = 'application' + dp = create_instance('com.sun.star.awt.DialogProvider2', True) + path = 'vnd.sun.star.script:{}.{}?location={}'.format( + properties['Library'], properties['Name'], location) + return dp.createDialog(path) + + dlg = create_instance('com.sun.star.awt.UnoControlDialog', True) + model = create_instance('com.sun.star.awt.UnoControlDialogModel', True) + toolkit = create_instance('com.sun.star.awt.Toolkit', True) + set_properties(model, properties) + dlg.setModel(model) + dlg.setVisible(False) + dlg.createPeer(toolkit, None) + + return dlg + + def _init_controls(self): + + return + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._model + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers + self._connect_listeners() + + def _connect_listeners(self): + + return + + def _add_listeners(self, control): + if self.events is None: + return + + listeners = { + 'addActionListener': EventsButton, + 'addMouseListener': EventsMouse, + } + for key, value in listeners.items(): + if hasattr(control.obj, key): + getattr(control.obj, key)(listeners[key](self.events)) + return + + def open(self): + return self.obj.execute() + + def close(self, value=0): + return self.obj.endDialog(value) + + def _get_control_model(self, control): + services = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + } + return services[control] + + def _get_custom_class(self, tipo, obj): + classes = { + 'label': UnoLabel, + 'button': UnoButton, + 'text': UnoText, + 'listbox': UnoListBox, + # ~ 'link': UnoLink, + # ~ 'tab': UnoTab, + # ~ 'roadmap': UnoRoadmap, + # ~ 'image': UnoImage, + # ~ 'radio': UnoRadio, + # ~ 'groupbox': UnoGroupBox, + # ~ 'tree': UnoTree, + # ~ 'grid': UnoGrid, + } + return classes[tipo](obj) + + def add_control(self, properties): + tipo = properties.pop('Type').lower() + model = self.model.createInstance(self._get_control_model(tipo)) + set_properties(model, properties) + name = properties['Name'] + self.model.insertByName(name, model) + control = self._get_custom_class(tipo, self.obj.getControl(name)) + self._add_listeners(control) + setattr(self, name, control) + return + # ~ Python >= 3.7 # ~ def __getattr__(name): +def _get_class_doc(obj): + classes = { + 'calc': LOCalc, + 'writer': LOWriter, + 'base': LOBase, + 'impress': LOImpress, + 'draw': LODraw, + 'math': LOMath, + 'basic': LOBasicIde, + } + type_doc = get_type_doc(obj) + return classes[type_doc](obj) + + def get_document(): doc = None + desktop = get_desktop() try: - doc = DESKTOP.getCurrentComponent() + doc = _get_class_doc(desktop.getCurrentComponent()) except Exception as e: log.error(e) return doc def get_selection(): - obj = None - try: - obj = get_document().getCurrentSelection() - except Exception as e: - log.error(e) - return obj + return get_document().selection def get_cell(*args): - sel = get_selection() - if sel.ImplementationName == OBJ_RANGE: - sel = sel[0,0] - elif sel.ImplementationName == OBJ_RANGES: - sel = sel[0][0,0] - return LOCellRange(sel) + if args: + index = args + if len(index) == 1: + index = args[0] + cell = get_document().get_cell(index) + else: + cell = get_selection().first + return cell + + +def active_cell(): + return get_cell() + + +def create_dialog(properties): + return LODialog(properties) + + +def set_properties(model, properties): + if 'X' in properties: + properties['PositionX'] = properties.pop('X') + if 'Y' in properties: + properties['PositionY'] = properties.pop('Y') + keys = tuple(properties.keys()) + values = tuple(properties.values()) + model.setPropertyValues(keys, values) + return