diff --git a/source/easymacro.py b/source/easymacro.py index 5079666..595ebaa 100644 --- a/source/easymacro.py +++ b/source/easymacro.py @@ -60,11 +60,14 @@ import mailbox import uno import unohelper from com.sun.star.util import Time, Date, DateTime -from com.sun.star.beans import PropertyValue +from com.sun.star.beans import PropertyValue, NamedValue 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, Point +from com.sun.star.awt import Rectangle +from com.sun.star.awt import KeyEvent +from com.sun.star.awt.KeyFunction import QUIT from com.sun.star.datatransfer import XTransferable, DataFlavor from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA @@ -73,7 +76,12 @@ 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 +from com.sun.star.awt import XMouseMotionListener from com.sun.star.util import XModifyListener +from com.sun.star.awt import XTopWindowListener +from com.sun.star.awt import XWindowListener +from com.sun.star.awt import XMenuListener +from com.sun.star.awt import XKeyListener try: from fernet import Fernet, InvalidToken @@ -696,6 +704,12 @@ class LOCalc(LODocument): index = [s.Name for s in self._sheets if s.CodeName == index][0] or index return LOCalcSheet(self._sheets[index], self) + def __setitem__(self, key, value): + self._sheets[key] = value + + def __contains__(self, item): + return item in self.obj.Sheets + @property def active(self): return LOCalcSheet(self._cc.getActiveSheet(), self) @@ -729,7 +743,12 @@ class LOCalc(LODocument): obj = self.obj.getStyleFamilies()['CellStyles'] return LOCellStyles(obj) - def insert(self, name, pos): + def create(self): + return self.obj.createInstance('com.sun.star.sheet.Spreadsheet') + + def insert(self, name, pos=-1): + # ~ sheet = obj.createInstance('com.sun.star.sheet.Spreadsheet') + # ~ obj.Sheets['New'] = sheet index = pos if pos < 0: index = self._sheets.Count + pos + 1 @@ -741,6 +760,12 @@ class LOCalc(LODocument): name = n return LOCalcSheet(self._sheets[name], self) + def move(self, name, pos=-1): + return self.sheets.move(name, pos) + + def remove(self, name): + return self.sheets.remove(name) + def copy(self, source='', target='', pos=-1): index = pos if pos < 0: @@ -763,7 +788,7 @@ class LOCalc(LODocument): return LOCalcSheet(self._sheets[index], self) - def copy_from(self, doc, source, target, pos): + def copy_from(self, doc, source='', target='', pos=-1): index = pos if pos < 0: index = self._sheets.Count + pos + 1 @@ -876,6 +901,12 @@ class LOCalcSheet(object): def __getitem__(self, index): return LOCellRange(self.obj[index], self.doc) + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + def _init_values(self): self._events = None @@ -905,6 +936,31 @@ class LOCalcSheet(object): self.doc.activate(self.obj) return + @property + def visible(self): + return self.obj.IsVisible + @visible.setter + def visible(self, value): + self.obj.IsVisible = value + + @property + def password(self): + return '' + @visible.setter + def password(self, value): + self.obj.protect(value) + + def unprotect(self, value): + try: + self.obj.unprotect(value) + return True + except: + pass + return False + + def get_cursor(self, cell): + return self.obj.createCursorByRange(cell) + @property def events(self): return self._events @@ -1171,7 +1227,7 @@ class LOCellRange(object): def __enter__(self): return self - def __exit__(self, *args): + def __exit__(self, exc_type, exc_value, traceback): pass def __getitem__(self, index): @@ -1240,19 +1296,72 @@ class LOCellRange(object): values = tuple(values) self.obj.setDataArray(values) + @property + def formula(self): + return self.obj.getFormulaArray() + @formula.setter + def formula(self, values): + if isinstance(values, list): + values = tuple(values) + self.obj.setFormulaArray(values) + + @property + def columns(self): + return self._obj.Columns.Count + + @property + def rows(self): + return self._obj.Rows.Count + + def to_size(self, rows, cols): + cursor = self.sheet.get_cursor(self.obj[0,0]) + cursor.collapseToSize(cols, rows) + return LOCellRange(self.sheet[cursor.AbsoluteName].obj, self.doc) + + def copy_from(self, rango): + data = rango + if isinstance(rango, LOCellRange): + data = rango.data + rows = len(data) + cols = len(data[0]) + self.to_size(rows, cols).data = data + return + + def copy_to(self, cell, formula=False): + rango = cell.to_size(self.rows, self.columns) + if formula: + rango.formula = self.data + else: + rango.data = self.data + return + def offset(self, row=1, col=0): ra = self.obj.getRangeAddress() col = ra.EndColumn + col row = ra.EndRow + row - return LOCellRange(self.sheet[row, col], self.doc) + return LOCellRange(self.sheet[row, col].obj, self.doc) + + @property + def next_cell(self): + a = self.current_region.address + if hasattr(a, 'StartColumn'): + col = a.StartColumn + else: + col = a.Column + if hasattr(a, 'EndRow'): + row = a.EndRow + 1 + else: + row = a.Row + 1 + + return LOCellRange(self.sheet[row, col].obj, self.doc) @property def sheet(self): - return self.obj.Spreadsheet + return LOCalcSheet(self.obj.Spreadsheet, self.doc) @property def draw_page(self): - return self.sheet.getDrawPage() + return self.sheet.obj.getDrawPage() @property def name(self): @@ -1270,9 +1379,23 @@ class LOCellRange(object): @property def current_region(self): - cursor = self.sheet.createCursorByRange(self.obj[0,0]) + cursor = self.sheet.get_cursor(self.obj[0,0]) cursor.collapseToCurrentRegion() - return LOCellRange(self.sheet[cursor.AbsoluteName], self.doc) + return LOCellRange(self.sheet[cursor.AbsoluteName].obj, self.doc) + + @property + def visible(self): + cursor = self.sheet.get_cursor(self.obj) + rangos = [LOCellRange(self.sheet[r.AbsoluteName].obj, self.doc) + for r in cursor.queryVisibleCells()] + return tuple(rangos) + + @property + def empty(self): + cursor = self.sheet.get_cursor(self.obj) + rangos = [LOCellRange(self.sheet[r.AbsoluteName].obj, self.doc) + for r in cursor.queryEmptyCells()] + return tuple(rangos) @property def cell_style(self): @@ -1308,7 +1431,7 @@ class LOCellRange(object): address = rango.address else: address = rango.getRangeAddress() - cursor = self.sheet.createCursorByRange(self.obj) + cursor = self.sheet.get_cursor(self.obj) result = cursor.queryIntersection(address) return bool(result.Count) @@ -1316,11 +1439,16 @@ class LOCellRange(object): self.obj.fillAuto(0, source) return + def clear(self, what=31): + self.obj.clearContents(what) + return + class EventsListenerBase(unohelper.Base, XEventListener): - def __init__(self, controller, window=None): + def __init__(self, controller, name, window=None): self._controller = controller + self._name = name self._window = window def disposing(self, event): @@ -1331,25 +1459,23 @@ class EventsListenerBase(unohelper.Base, XEventListener): class EventsButton(EventsListenerBase, XActionListener): - def __init__(self, controller): - super().__init__(controller) + def __init__(self, controller, name): + super().__init__(controller, name) def actionPerformed(self, event): - name = event.Source.Model.Name - event_name = '{}_action'.format(name) + event_name = '{}_action'.format(self._name) if hasattr(self._controller, event_name): getattr(self._controller, event_name)(event) return -class EventsMouse(EventsListenerBase, XMouseListener): +class EventsMouse(EventsListenerBase, XMouseListener, XMouseMotionListener): - def __init__(self, controller): - super().__init__(controller) + def __init__(self, controller, name): + super().__init__(controller, name) def mousePressed(self, event): - name = event.Source.Model.Name - event_name = '{}_click'.format(name) + event_name = '{}_click'.format(self._name) if event.ClickCount == 2: event_name = '{}_double_click'.format(name) if hasattr(self._controller, event_name): @@ -1365,6 +1491,13 @@ class EventsMouse(EventsListenerBase, XMouseListener): def mouseExited(self, event): pass + # ~ XMouseMotionListener + def mouseMoved(self, event): + pass + + def mouseDragged(self, event): + pass + class EventsMouseGrid(EventsMouse): selected = False @@ -1406,6 +1539,125 @@ class EventsModify(EventsListenerBase, XModifyListener): return +class EventsKey(EventsListenerBase, XKeyListener): + """ + event.KeyChar + event.KeyCode + event.KeyFunc + event.Modifiers + """ + + def __init__(self, cls): + super().__init__(cls.events, cls.name) + self._cls = cls + + def keyPressed(self, event): + pass + + def keyReleased(self, event): + event_name = '{}_key_released'.format(self._cls.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + else: + if event.KeyFunc == QUIT and hasattr(self._cls, 'close'): + self._cls.close() + return + + +class EventsWindow(EventsListenerBase, XTopWindowListener, XWindowListener): + + def __init__(self, cls): + self._cls = cls + super().__init__(cls.events, cls.name, cls._window) + + def windowOpened(self, event): + event_name = '{}_opened'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def windowActivated(self, event): + control_name = '{}_activated'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + def windowDeactivated(self, event): + control_name = '{}_deactivated'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + def windowMinimized(self, event): + pass + + def windowNormalized(self, event): + pass + + def windowClosing(self, event): + if self._window: + control_name = 'window_closing' + else: + control_name = '{}_closing'.format(event.Source.Model.Name) + + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + # ~ else: + # ~ if not self._modal and not self._block: + # ~ event.Source.Visible = False + return + + def windowClosed(self, event): + control_name = '{}_closed'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + # ~ XWindowListener + def windowResized(self, event): + # ~ sb = self._container.getControl('subcontainer') + # ~ sb.setPosSize(0, 0, event.Width, event.Height, SIZE) + event_name = '{}_resized'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def windowMoved(self, event): + pass + + def windowShown(self, event): + pass + + def windowHidden(self, event): + pass + + +class EventsMenu(EventsListenerBase, XMenuListener): + + def __init__(self, controller): + super().__init__(controller, '') + + def itemHighlighted(self, event): + pass + + @catch_exception + def itemSelected(self, event): + name = event.Source.getCommand(event.MenuId) + if name.startswith('menu'): + event_name = '{}_selected'.format(name) + else: + event_name = 'menu_{}_selected'.format(name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def itemActivated(self, event): + return + + def itemDeactivated(self, event): + return + + class UnoBaseObject(object): def __init__(self, obj): @@ -1429,19 +1681,39 @@ class UnoBaseObject(object): def parent(self): return self.obj.getContext() + def _get_possize(self, name): + ps = self.obj.getPosSize() + return getattr(ps, name) + + def _set_possize(self, name, value): + ps = self.obj.getPosSize() + setattr(ps, name, value) + self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, POSSIZE) + return + @property def x(self): - return self.model.PositionX + if hasattr(self.model, 'PositionX'): + return self.model.PositionX + return self._get_possize('X') @x.setter def x(self, value): - self.model.PositionX = value + if hasattr(self.model, 'PositionX'): + self.model.PositionX = value + else: + self._set_possize('X', value) @property def y(self): - return self.model.PositionY + if hasattr(self.model, 'PositionY'): + return self.model.PositionY + return self._get_possize('Y') @y.setter def y(self, value): - self.model.PositionY = value + if hasattr(self.model, 'PositionY'): + self.model.PositionY = value + else: + self._set_possize('Y', value) @property def width(self): @@ -1452,7 +1724,10 @@ class UnoBaseObject(object): @property def height(self): - return self._model.Height + if hasattr(self._model, 'Height'): + return self._model.Height + ps = self.obj.getPosSize() + return ps.Height @height.setter def height(self, value): self._model.Height = value @@ -1699,9 +1974,45 @@ class UnoGrid(UnoBaseObject): return +def get_custom_class(tipo, obj): + classes = { + 'label': UnoLabel, + 'button': UnoButton, + 'text': UnoText, + 'listbox': UnoListBox, + 'grid': UnoGrid, + # ~ 'link': UnoLink, + # ~ 'tab': UnoTab, + # ~ 'roadmap': UnoRoadmap, + # ~ 'image': UnoImage, + # ~ 'radio': UnoRadio, + # ~ 'groupbox': UnoGroupBox, + # ~ 'tree': UnoTree, + } + return classes[tipo](obj) + + +def add_listeners(events, control, name=''): + listeners = { + 'addActionListener': EventsButton, + 'addMouseListener': EventsMouse, + } + if hasattr(control, 'obj'): + control = contro.obj + is_grid = control.ImplementationName == 'stardiv.Toolkit.GridControl' + + for key, value in listeners.items(): + if hasattr(control, key): + if is_grid and key == 'addMouseListener': + control.addMouseListener(EventsMouseGrid(events)) + continue + getattr(control, key)(listeners[key](events, name)) + return + + class LODialog(object): - def __init__(self, properties): + def __init__(self, **properties): self._obj = self._create(properties) self._init_values() @@ -1799,13 +2110,13 @@ class LODialog(object): } return services[control] - def _get_custom_class(self, tipo, obj): - classes = { - 'label': UnoLabel, - 'button': UnoButton, - 'text': UnoText, - 'listbox': UnoListBox, - 'grid': UnoGrid, + # ~ def _get_custom_class(self, tipo, obj): + # ~ classes = { + # ~ 'label': UnoLabel, + # ~ 'button': UnoButton, + # ~ 'text': UnoText, + # ~ 'listbox': UnoListBox, + # ~ 'grid': UnoGrid, # ~ 'link': UnoLink, # ~ 'tab': UnoTab, # ~ 'roadmap': UnoRoadmap, @@ -1813,8 +2124,8 @@ class LODialog(object): # ~ 'radio': UnoRadio, # ~ 'groupbox': UnoGroupBox, # ~ 'tree': UnoTree, - } - return classes[tipo](obj) + # ~ } + # ~ return classes[tipo](obj) def _set_column_model(self, columns): #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html @@ -1846,12 +2157,174 @@ class LODialog(object): 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) + control = self.obj.getControl(name) + add_listeners(self.events, control, name) + control = get_custom_class(tipo, control) setattr(self, name, control) return +class LOWindow(object): + + def __init__(self, **kwargs): + self._events = None + self._menu = None + self._container = None + self._obj = self._create(kwargs) + + def _create(self, properties): + ps = ( + properties.get('X', 0), + properties.get('Y', 0), + properties.get('Width', 500), + properties.get('Height', 500), + ) + self._title = properties.get('Title', TITLE) + self._create_frame(ps) + self._create_container(ps) + # ~ self._create_splitter(ps) + return + + def _create_frame(self, ps): + service = 'com.sun.star.frame.TaskCreator' + tc = create_instance(service, True) + self._frame = tc.createInstanceWithArguments(( + NamedValue('FrameName', 'EasyMacroWin'), + NamedValue('PosSize', Rectangle(*ps)), + )) + self._window = self._frame.getContainerWindow() + self._toolkit = self._window.getToolkit() + desktop = get_desktop() + self._frame.setCreator(desktop) + desktop.getFrames().append(self._frame) + self._frame.Title = self._title + return + + def _create_container(self, ps): + # ~ toolkit = self._window.getToolkit() + service = 'com.sun.star.awt.UnoControlContainer' + self._container = create_instance(service, True) + service = 'com.sun.star.awt.UnoControlContainerModel' + model = create_instance(service, True) + model.BackgroundColor = get_color(225, 225, 225) + self._container.setModel(model) + self._container.createPeer(self._toolkit, self._window) + self._container.setPosSize(*ps, POSSIZE) + self._frame.setComponent(self._container, None) + return + + def _get_base_control(self, tipo): + services = { + 'label': 'com.sun.star.awt.UnoControlFixedText', + 'button': 'com.sun.star.awt.UnoControlButton', + 'text': 'com.sun.star.awt.UnoControlEdit', + 'listbox': 'com.sun.star.awt.UnoControlListBox', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlink', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmap', + 'image': 'com.sun.star.awt.UnoControlImageControl', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBox', + 'radio': 'com.sun.star.awt.UnoControlRadioButton', + 'tree': 'com.sun.star.awt.tree.TreeControl', + 'grid': 'com.sun.star.awt.grid.UnoControlGrid', + } + return services[tipo] + + def add_control(self, properties): + tipo = properties.pop('Type').lower() + base = self._get_base_control(tipo) + obj = create_instance(base, True) + model = create_instance('{}Model'.format(base), True) + set_properties(model, properties) + obj.setModel(model) + x = properties.get('X', 5) + y = properties.get('Y', 5) + w = properties.get('Width', 200) + h = properties.get('Height', 25) + obj.setPosSize(x, y, w, h, POSSIZE) + name = properties['Name'] + self._container.addControl(name, obj) + add_listeners(self.events, obj, name) + control = get_custom_class(tipo, obj) + setattr(self, name, control) + return + + def _create_popupmenu(self, menus): + menu = create_instance('com.sun.star.awt.PopupMenu', True) + for i, m in enumerate(menus): + label = m['label'] + cmd = m.get('event', '') + if not cmd: + cmd = label.lower().replace(' ', '_') + if label == '-': + menu.insertSeparator(i) + else: + menu.insertItem(i, label, m.get('style', 0), i) + menu.setCommand(i, cmd) + # ~ menu.setItemImage(i, path?, True) + menu.addMenuListener(EventsMenu(self.events)) + return menu + + def _create_menu(self, menus): + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMenu.html + #~ nItemId specifies the ID of the menu item to be inserted. + #~ aText specifies the label of the menu item. + #~ nItemStyle 0 = Standard, CHECKABLE = 1, RADIOCHECK = 2, AUTOCHECK = 4 + #~ nItemPos specifies the position where the menu item will be inserted. + self._menu = create_instance('com.sun.star.awt.MenuBar', True) + for i, m in enumerate(menus): + self._menu.insertItem(i, m['label'], m.get('style', 0), i) + cmd = m['label'].lower().replace(' ', '_') + self._menu.setCommand(i, cmd) + submenu = self._create_popupmenu(m['submenu']) + self._menu.setPopupMenu(i, submenu) + + self._window.setMenuBar(self._menu) + return + + def add_menu(self, menus): + self._create_menu(menus) + return + + def _add_listeners(self, control=None): + if self.events is None: + return + controller = EventsWindow(self) + self._window.addTopWindowListener(controller) + self._window.addWindowListener(controller) + self._container.addKeyListener(EventsKey(self)) + return + + @property + def name(self): + return self._title.lower().replace(' ', '_') + + @property + def events(self): + return self._events + @events.setter + def events(self, value): + self._events = value + self._add_listeners() + + @property + def width(self): + return self._container.Size.Width + + @property + def height(self): + return self._container.Size.Height + + def open(self): + self._window.setVisible(True) + return + + def close(self): + self._window.setMenuBar(None) + self._window.dispose() + self._frame.close(True) + return + + # ~ Python >= 3.7 # ~ def __getattr__(name): @@ -1935,7 +2408,7 @@ def active_cell(): def create_dialog(properties): - return LODialog(properties) + return LODialog(**properties) # ~ Export ok @@ -2095,7 +2568,7 @@ def inputbox(message, default='', title=TITLE, echochar=''): 'Width': 200, 'Height': 80, } - dlg = LODialog(args) + dlg = LODialog(**args) dlg.events = ControllersInput(dlg) args = { @@ -2974,7 +3447,7 @@ class SmtpServer(object): def __enter__(self): return self - def __exit__(self, *args): + def __exit__(self, exc_type, exc_value, traceback): self.close() @property @@ -3109,6 +3582,9 @@ def server_smtp_test(config): return server.error +def create_window(kwargs): + return LOWindow(**kwargs) + class LIBOServer(object): HOST = 'localhost'