diff --git a/CHANGELOG b/CHANGELOG index 2d67aa5..0ad42bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +v 0.9.0 [19-oct-2019] + - Add support for generate locales + - Start support for forms + + v 0.8.0 [13-oct-2019] - Generate manifest - Add support for execute dialogs in documents. diff --git a/VERSION b/VERSION index a3df0a6..ac39a10 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.0 +0.9.0 diff --git a/source/conf.py.example b/source/conf.py.example index 9be5aeb..892ab8c 100644 --- a/source/conf.py.example +++ b/source/conf.py.example @@ -206,6 +206,8 @@ PATHS = { 'soffice': ('soffice', PROGRAM, FILE_TEST), 'install': ('unopkg', 'add', '-v', '-f', '-s'), 'profile': '/home/mau/.config/libreoffice/4/user', + 'gettext': PATH_PYGETTEXT, + 'msgmerge': PATH_MSGMERGE, } diff --git a/source/easymacro.py b/source/easymacro.py index 218abbf..8f9f5b9 100644 --- a/source/easymacro.py +++ b/source/easymacro.py @@ -22,6 +22,7 @@ import csv import ctypes import datetime import errno +import gettext import getpass import hashlib import json @@ -46,6 +47,8 @@ from functools import wraps from operator import itemgetter from pathlib import Path, PurePath from pprint import pprint +from urllib.request import Request, urlopen +from urllib.error import URLError, HTTPError from string import Template from subprocess import PIPE @@ -75,6 +78,7 @@ from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK from com.sun.star.text.TextContentAnchorType import AS_CHARACTER +from com.sun.star.script import ScriptEventDescriptor from com.sun.star.lang import XEventListener from com.sun.star.awt import XActionListener from com.sun.star.awt import XMouseListener @@ -84,6 +88,9 @@ 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 +from com.sun.star.awt import XItemListener +from com.sun.star.awt import XFocusListener + try: from fernet import Fernet, InvalidToken @@ -91,6 +98,20 @@ except ImportError: pass +ID_EXTENSION = '' + +DIR = { + 'images': 'images', + 'locales': 'locales', +} + +KEY = { + 'enter': 1280, +} + +SEPARATION = 5 + + MSG_LANG = { 'es': { 'OK': 'Aceptar', @@ -114,6 +135,9 @@ LOG_NAME = 'ZAZ' CLIPBOARD_FORMAT_TEXT = 'text/plain;charset=utf-16' +PYTHON = 'python' +if IS_WIN: + PYTHON = 'python.exe' CALC = 'calc' WRITER = 'writer' OBJ_CELL = 'ScCellObj' @@ -130,9 +154,15 @@ TYPE_DOC = { 'base': 'com.sun.star.sdb.DocumentDataSource', 'math': 'com.sun.star.formula.FormulaProperties', 'basic': 'com.sun.star.script.BasicIDE', + 'main': 'com.sun.star.frame.StartModule', } NODE_MENUBAR = 'private:resource/menubar/menubar' +MENUS_MAIN = { + 'file': '.uno:PickList', + 'tools': '.uno:ToolsMenu', + 'help': '.uno:HelpMenu', +} MENUS_CALC = { 'file': '.uno:PickList', 'edit': '.uno:EditMenu', @@ -161,6 +191,7 @@ MENUS_WRITER = { } MENUS_APP = { + 'main': MENUS_MAIN, 'calc': MENUS_CALC, 'writer': MENUS_WRITER, } @@ -271,16 +302,17 @@ def info(data): return -def debug(info): +def debug(*info): if IS_WIN: doc = get_document(FILE_NAME_DEBUG) if doc is None: return doc = LogWin(doc.obj) - doc.write(info) + doc.write(str(info)) return - log.debug(str(info)) + data = [str(d) for d in info] + log.debug('\t'.join(data)) return @@ -498,6 +530,15 @@ class ObjectBase(object): def __getitem__(self, index): return self.obj[index] + def __getattr__(self, name): + a = None + if name == 'obj': + a = super().__getattr__(name) + else: + if hasattr(self.obj, name): + a = getattr(self.obj, name) + return a + @property def obj(self): return self._obj @@ -559,10 +600,10 @@ class LODocument(object): def _init_values(self): self._type_doc = get_type_doc(self.obj) - if self._type_doc == 'base': - self._cc = self.obj.DatabaseDocument.getCurrentController() - else: - self._cc = self.obj.getCurrentController() + # ~ if self._type_doc == 'base': + # ~ self._cc = self.obj.DatabaseDocument.getCurrentController() + # ~ else: + self._cc = self.obj.getCurrentController() return @property @@ -682,6 +723,164 @@ class LODocument(object): return path_pdf +class FormControlBase(object): + EVENTS = { + 'action': 'actionPerformed', + 'click': 'mousePressed', + } + TYPES = { + 'actionPerformed': 'XActionListener', + 'mousePressed': 'XMouseListener', + } + + def __init__(self, obj): + self._obj = obj + self._index = -1 + self._rules = {} + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self.obj.Name + + @property + def form(self): + return self.obj.getParent() + + @property + def index(self): + return self._index + @index.setter + def index(self, value): + self._index = value + + @property + def events(self): + return self.form.getScriptEvents(self.index) + + def remove_event(self, name=''): + for ev in self.events: + if name and \ + ev.EventMethod == self.EVENTS[name] and \ + ev.ListenerType == self.TYPES[ev.EventMethod]: + self.form.revokeScriptEvent(self.index, + ev.ListenerType, ev.EventMethod, ev.AddListenerParam) + break + else: + self.form.revokeScriptEvent(self.index, + ev.ListenerType, ev.EventMethod, ev.AddListenerParam) + return + + def add_event(self, name, macro): + if not 'name' in macro: + macro['name'] = '{}_{}'.format(self.name, name) + + event = ScriptEventDescriptor() + event.AddListenerParam = '' + event.EventMethod = self.EVENTS[name] + event.ListenerType = self.TYPES[event.EventMethod] + event.ScriptCode = _get_url_script(macro) + event.ScriptType = 'Script' + + for ev in self.events: + if ev.EventMethod == event.EventMethod and \ + ev.ListenerType == event.ListenerType: + self.form.revokeScriptEvent(self.index, + event.ListenerType, event.EventMethod, event.AddListenerParam) + break + + self.form.registerScriptEvent(self.index, event) + return + + +class FormButton(FormControlBase): + + def __init__(self, obj): + super().__init__(obj) + + + +class LOForm(ObjectBase): + + def __init__(self, obj): + super().__init__(obj) + self._init_controls() + + def __getitem__(self, index): + if isinstance(index, int): + return self._controls[index] + else: + return getattr(self, index) + + def _get_type_control(self, name): + types = { + # ~ 'stardiv.Toolkit.UnoFixedTextControl': 'label', + 'com.sun.star.form.OButtonModel': 'formbutton', + # ~ 'stardiv.Toolkit.UnoEditControl': 'text', + # ~ 'stardiv.Toolkit.UnoRoadmapControl': 'roadmap', + # ~ 'stardiv.Toolkit.UnoFixedHyperlinkControl': 'link', + # ~ 'stardiv.Toolkit.UnoListBoxControl': 'listbox', + } + return types[name] + + def _init_controls(self): + self._controls = [] + for i, c in enumerate(self.obj.ControlModels): + tipo = self._get_type_control(c.ImplementationName) + control = get_custom_class(tipo, c) + control.index = i + self._controls.append(control) + setattr(self, c.Name, control) + + @property + def name(self): + return self._obj.getName() + @name.setter + def name(self, value): + self._obj.setName(value) + + +class LOForms(ObjectBase): + + def __init__(self, obj, doc): + self._doc = doc + super().__init__(obj) + + def __getitem__(self, index): + form = super().__getitem__(index) + return LOForm(form) + + @property + def doc(self): + return self._doc + + @property + def count(self): + return self.obj.getCount() + + @property + def names(self): + return self.obj.getElementNames() + + def exists(self, name): + return name in self.names + + def insert(self, name): + form = self.doc.create_instance('com.sun.star.form.component.Form') + self.obj.insertByName(name, form) + return self[name] + + def remove(self, index): + if isinstance(index, int): + self.obj.removeByIndex(index) + else: + self.obj.removeByName(index) + return + + class LOCellStyle(LOObjectBase): def __init__(self, obj): @@ -978,6 +1177,8 @@ class LOCalcSheet(object): def _init_values(self): self._events = None + self._dp = self.obj.getDrawPage() + return @property def obj(self): @@ -1048,6 +1249,10 @@ class LOCalcSheet(object): def exists_chart(self, name): return name in self.obj.Charts.ElementNames + @property + def forms(self): + return LOForms(self._dp.getForms(), self.doc) + @property def events(self): return self._events @@ -1090,6 +1295,10 @@ class LOWriter(LODocument): def cursor(self): return self.text.createTextCursor() + @property + def paragraphs(self): + return [LOTextRange(p) for p in self.text] + @property def selection(self): sel = self.obj.getCurrentSelection() @@ -1141,16 +1350,40 @@ class LOWriter(LODocument): self.insert_content(cursor, image) return + def go_start(self): + cursor = self._cc.getViewCursor() + cursor.gotoStart(False) + return cursor + + def go_end(self): + cursor = self._cc.getViewCursor() + cursor.gotoEnd(False) + return cursor + + def select(self, text): + self._cc.select(text) + return + class LOTextRange(object): def __init__(self, obj): self._obj = obj + self._is_paragraph = self.obj.ImplementationName == 'SwXParagraph' + self._is_table = self.obj.ImplementationName == 'SwXTextTable' @property def obj(self): return self._obj + @property + def is_paragraph(self): + return self._is_paragraph + + @property + def is_table(self): + return self._is_table + @property def string(self): return self.obj.String @@ -1164,7 +1397,7 @@ class LOTextRange(object): return self.text.createTextCursorByRange(self.obj) -class LOBase(LODocument): +class LOBase(object): TYPES = { str: 'setString', int: 'setInt', @@ -1199,13 +1432,33 @@ class LOBase(LODocument): if not self.exists: self._dbc.registerDatabaseLocation(name, path_url) else: - db = self._dbc.getByName(name) - self.path = _path_system(self._dbc.getDatabaseLocation(name)) - super().__init__(db) - self._con = db.getConnection('', '') - msg = 'Connected to: {}'.format(name) + if name.startswith('odbc:'): + self._con = self._odbc(name, kwargs) + else: + db = self._dbc.getByName(name) + self.path = _path_system(self._dbc.getDatabaseLocation(name)) + self._con = db.getConnection('', '') + + if self._con is None: + msg = 'Not connected to: {}'.format(name) + else: + msg = 'Connected to: {}'.format(name) debug(msg) + def _odbc(self, name, kwargs): + dm = create_instance('com.sun.star.sdbc.DriverManager') + args = dict_to_property(kwargs) + try: + con = dm.getConnectionWithInfo('sdbc:{}'.format(name), args) + return con + except Exception as e: + error(str(e)) + return None + + @property + def obj(self): + return self._obj + @property def name(self): return self._name @@ -1231,6 +1484,10 @@ class LOBase(LODocument): self._dbc.registerDatabaseLocation(name, _path_url(path)) return + def revoke(self, name): + self._dbc.revokeDatabaseLocation(name) + return True + def save(self): # ~ self._db.connection.commit() # ~ self._db.connection.getTables().refresh() @@ -1269,8 +1526,8 @@ class LOBase(LODocument): else: cursor = self._con.createStatement() cursor.execute(sql) - # ~ executeQuery - # ~ executeUpdate + # ~ resulset = cursor.executeQuery(sql) + # ~ rows = cursor.executeUpdate(sql) self.save() return cursor @@ -1571,6 +1828,17 @@ class LOCellRange(object): img.setSize(Size(w, h)) return + def insert_shape(self, tipo, **kwargs): + s = self.obj.Size + w = kwargs.get('width', s.Width) + h = kwargs.get('Height', s.Height) + img = self.doc.create_instance('com.sun.star.drawing.{}Shape'.format(tipo)) + set_properties(img, kwargs) + self.draw_page.add(img) + img.Anchor = self.obj + img.setSize(Size(w, h)) + return + def select(self): self.doc._cc.select(self.obj) return @@ -1674,7 +1942,7 @@ class EventsMouse(EventsListenerBase, XMouseListener, XMouseMotionListener): def mousePressed(self, event): event_name = '{}_click'.format(self._name) if event.ClickCount == 2: - event_name = '{}_double_click'.format(name) + event_name = '{}_double_click'.format(self._name) if hasattr(self._controller, event_name): getattr(self._controller, event_name)(event) return @@ -1696,6 +1964,19 @@ class EventsMouse(EventsListenerBase, XMouseListener, XMouseMotionListener): pass +class EventsMouseLink(EventsMouse): + + def mouseEntered(self, event): + obj = event.Source.Model + obj.TextColor = get_color('blue') + return + + def mouseExited(self, event): + obj = event.Source.Model + obj.TextColor = 0 + return + + class EventsMouseGrid(EventsMouse): selected = False @@ -1714,13 +1995,13 @@ class EventsMouseGrid(EventsMouse): return def mouseReleased(self, event): - obj = event.Source - col = obj.getColumnAtPoint(event.X, event.Y) - row = obj.getRowAtPoint(event.X, event.Y) - if row == -1 and col > -1: - gdm = obj.Model.GridDataModel - for i in range(gdm.RowCount): - gdm.updateRowHeading(i, i + 1) + # ~ obj = event.Source + # ~ col = obj.getColumnAtPoint(event.X, event.Y) + # ~ row = obj.getRowAtPoint(event.X, event.Y) + # ~ if row == -1 and col > -1: + # ~ gdm = obj.Model.GridDataModel + # ~ for i in range(gdm.RowCount): + # ~ gdm.updateRowHeading(i, i + 1) return @@ -1736,6 +2017,43 @@ class EventsModify(EventsListenerBase, XModifyListener): return +class EventsItem(EventsListenerBase, XItemListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def disposing(self, event): + pass + + def itemStateChanged(self, event): + pass + + +class EventsItemRoadmap(EventsItem): + + def itemStateChanged(self, event): + dialog = event.Source.Context.Model + dialog.Step = event.ItemId + 1 + return + + +class EventsFocus(EventsListenerBase, XFocusListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def focusGained(self, event): + service = event.Source.Model.ImplementationName + if service == 'stardiv.Toolkit.UnoControlListBoxModel': + return + obj = event.Source.Model + obj.BackgroundColor = COLOR_ON_FOCUS + + def focusLost(self, event): + obj = event.Source.Model + obj.BackgroundColor = -1 + + class EventsKey(EventsListenerBase, XKeyListener): """ event.KeyChar @@ -1744,6 +2062,27 @@ class EventsKey(EventsListenerBase, XKeyListener): event.Modifiers """ + def __init__(self, controller, name): + super().__init__(controller, name) + + def keyPressed(self, event): + pass + + def keyReleased(self, event): + event_name = '{}_key_released'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + +class EventsKeyWindow(EventsListenerBase, XKeyListener): + """ + event.KeyChar + event.KeyCode + event.KeyFunc + event.Modifiers + """ + def __init__(self, cls): super().__init__(cls.events, cls.name) self._cls = cls @@ -1837,7 +2176,6 @@ class EventsMenu(EventsListenerBase, XMenuListener): def itemHighlighted(self, event): pass - @catch_exception def itemSelected(self, event): name = event.Source.getCommand(event.MenuId) if name.startswith('menu'): @@ -1936,6 +2274,20 @@ class UnoBaseObject(object): def tag(self, value): self.model.Tag = value + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.setVisible(value) + + @property + def enabled(self): + return self.model.Enabled + @enabled.setter + def enabled(self, value): + self.model.Enabled = value + @property def step(self): return self.model.Step @@ -1943,6 +2295,13 @@ class UnoBaseObject(object): def step(self, value): self.model.Step = value + @property + def back_color(self): + return self.model.BackgroundColor + @back_color.setter + def back_color(self, value): + self.model.BackgroundColor = value + @property def rules(self): return self._rules @@ -1967,16 +2326,17 @@ class UnoBaseObject(object): return def move(self, origin, x=0, y=5): - w = 0 - h = 0 if x: - w = origin.width + self.x = origin.x + origin.width + x if y: - h = origin.height - x = origin.x + x + w - y = origin.y + y + h - self.x = x - self.y = y + self.y = origin.y + origin.height + y + return + + def possize(self, origin): + self.x = origin.x + self.y = origin.y + self.width = origin.width + self.height = origin.height return @@ -1997,6 +2357,16 @@ class UnoLabel(UnoBaseObject): self.model.Label = value +class UnoLabelLink(UnoLabel): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'link' + + class UnoButton(UnoBaseObject): def __init__(self, obj): @@ -2039,7 +2409,6 @@ class UnoListBox(UnoBaseObject): def __init__(self, obj): super().__init__(obj) - self._data = [] @property def type(self): @@ -2049,13 +2418,49 @@ class UnoListBox(UnoBaseObject): def value(self): return self.obj.SelectedItem + @property + def count(self): + return len(self.data) + @property def data(self): - return self._data + return self.model.StringItemList @data.setter def data(self, values): - self._data = list(sorted(values)) - self.model.StringItemList = self.data + self.model.StringItemList = list(sorted(values)) + return + + def select(self, pos=0): + if isinstance(pos, str): + self.obj.selectItem(pos, True) + else: + self.obj.selectItemPos(pos, True) + return + + def clear(self): + self.obj.removeItems(0, self.count) + return + + def _set_image_url(self, image): + if exists_path(image): + return _path_url(image) + + if not ID_EXTENSION: + return '' + + path = get_path_extension(ID_EXTENSION) + path = join(path, DIR['images'], image) + return _path_url(path) + + def insert(self, value, path='', pos=-1, show=True): + if pos < 0: + pos = self.count + if path: + self.model.insertItem(pos, value, self._set_image_url(path)) + else: + self.model.insertItemText(pos, value) + if show: + self.select(pos) return @@ -2171,6 +2576,39 @@ class UnoGrid(UnoBaseObject): return +class UnoRoadmap(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._options = () + + @property + def options(self): + return self._options + @options.setter + def options(self, values): + self._options = values + for i, v in enumerate(values): + opt = self.model.createInstance() + opt.ID = i + opt.Label = v + self.model.insertByIndex(i, opt) + return + + @property + def enabled(self): + return True + @enabled.setter + def enabled(self, value): + for m in self.model: + m.Enabled = value + return + + def set_enabled(self, index, value): + self.model.getByIndex(index).Enabled = value + return + + def get_custom_class(tipo, obj): classes = { 'label': UnoLabel, @@ -2178,13 +2616,14 @@ def get_custom_class(tipo, obj): 'text': UnoText, 'listbox': UnoListBox, 'grid': UnoGrid, - # ~ 'link': UnoLink, + 'link': UnoLabelLink, + 'roadmap': UnoRoadmap, # ~ 'tab': UnoTab, - # ~ 'roadmap': UnoRoadmap, # ~ 'image': UnoImage, # ~ 'radio': UnoRadio, # ~ 'groupbox': UnoGroupBox, # ~ 'tree': UnoTree, + 'formbutton': FormButton, } return classes[tipo](obj) @@ -2193,16 +2632,29 @@ def add_listeners(events, control, name=''): listeners = { 'addActionListener': EventsButton, 'addMouseListener': EventsMouse, + 'addItemListener': EventsItem, + 'addFocusListener': EventsFocus, + 'addKeyListener': EventsKey, } if hasattr(control, 'obj'): control = contro.obj + # ~ debug(control.ImplementationName) is_grid = control.ImplementationName == 'stardiv.Toolkit.GridControl' + is_link = control.ImplementationName == 'stardiv.Toolkit.UnoFixedHyperlinkControl' + is_roadmap = control.ImplementationName == 'stardiv.Toolkit.UnoRoadmapControl' for key, value in listeners.items(): if hasattr(control, key): if is_grid and key == 'addMouseListener': - control.addMouseListener(EventsMouseGrid(events)) + control.addMouseListener(EventsMouseGrid(events, name)) continue + if is_link and key == 'addMouseListener': + control.addMouseListener(EventsMouseLink(events, name)) + continue + if is_roadmap and key == 'addItemListener': + control.addItemListener(EventsItemRoadmap(events, name)) + continue + getattr(control, key)(listeners[key](events, name)) return @@ -2654,7 +3106,9 @@ class LODialog(object): self._model = self._obj.Model self._init_controls() self._events = None - # ~ self._response = None + self._color_on_focus = -1 + self._id_extension = '' + self._images = 'images' return def _create(self, properties): @@ -2692,6 +3146,9 @@ class LODialog(object): 'stardiv.Toolkit.UnoFixedTextControl': 'label', 'stardiv.Toolkit.UnoButtonControl': 'button', 'stardiv.Toolkit.UnoEditControl': 'text', + 'stardiv.Toolkit.UnoRoadmapControl': 'roadmap', + 'stardiv.Toolkit.UnoFixedHyperlinkControl': 'link', + 'stardiv.Toolkit.UnoListBoxControl': 'listbox', } return types[name] @@ -2711,6 +3168,52 @@ class LODialog(object): def model(self): return self._model + @property + def id_extension(self): + return self._id_extension + @id_extension.setter + def id_extension(self, value): + global ID_EXTENSION + ID_EXTENSION = value + self._id_extension = value + + @property + def images(self): + return self._images + @images.setter + def images(self, value): + self._images = value + + @property + def height(self): + return self.model.Height + @height.setter + def height(self, value): + self.model.Height = value + + @property + def width(self): + return self.model.Width + @width.setter + def width(self, value): + self.model.Width = value + + @property + def color_on_focus(self): + return self._color_on_focus + @color_on_focus.setter + def color_on_focus(self, value): + global COLOR_ON_FOCUS + COLOR_ON_FOCUS = get_color(value) + self._color_on_focus = COLOR_ON_FOCUS + + @property + def step(self): + return self.model.Step + @step.setter + def step(self, value): + self.model.Step = value + @property def events(self): return self._events @@ -2732,46 +3235,57 @@ class LODialog(object): 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', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', } return services[control] 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 column_model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) - # ~ column_model.setDefaultColumns(len(columns)) for column in columns: grid_column = create_instance('com.sun.star.awt.grid.GridColumn', True) for k, v in column.items(): setattr(grid_column, k, v) column_model.addColumn(grid_column) - # ~ mri(grid_column) return column_model - def _set_image_url(self, path): - if exists_path(path): - return _path_url(path) - return '' + def _set_image_url(self, image): + if exists_path(image): + return _path_url(image) - def add_control(self, properties): - tipo = properties.pop('Type').lower() + if not self.id_extension: + return '' + path = get_path_extension(self.id_extension) + path = join(path, self.images, image) + return _path_url(path) + + def _special_properties(self, tipo, properties): columns = properties.pop('Columns', ()) if tipo == 'grid': properties['ColumnModel'] = self._set_column_model(columns) - if tipo == 'button' and 'ImageURL' in properties: + elif tipo == 'button' and 'ImageURL' in properties: properties['ImageURL'] = self._set_image_url(properties['ImageURL']) + elif tipo == 'roadmap': + if not 'Height' in properties: + properties['Height'] = self.height + if 'Title' in properties: + properties['Text'] = properties.pop('Title') + return properties + def add_control(self, properties): + tipo = properties.pop('Type').lower() + properties = self._special_properties(tipo, properties) model = self.model.createInstance(self._get_control_model(tipo)) set_properties(model, properties) name = properties['Name'] @@ -2782,6 +3296,32 @@ class LODialog(object): setattr(self, name, control) return + def center(self, control, x=0, y=0): + w = self.width + h = self.height + + if isinstance(control, tuple): + wt = SEPARATION * -1 + for c in control: + wt += c.width + SEPARATION + x = w / 2 - wt / 2 + for c in control: + c.x = x + x = c.x + c.width + SEPARATION + return + + if x < 0: + x = w + x - control.width + elif x == 0: + x = w / 2 - control.width / 2 + if y < 0: + y = h + y - control.height + elif y == 0: + y = h / 2 - control.height / 2 + control.x = x + control.y = y + return + class LOWindow(object): @@ -2910,7 +3450,7 @@ class LOWindow(object): controller = EventsWindow(self) self._window.addTopWindowListener(controller) self._window.addWindowListener(controller) - self._container.addKeyListener(EventsKey(self)) + self._container.addKeyListener(EventsKeyWindow(self)) return @property @@ -3029,6 +3569,11 @@ def get_config_path(name='Work'): return _path_system(getattr(path, name)) +def get_path_python(): + path = get_config_path('Module') + return join(path, PYTHON) + + # ~ Export ok def get_file(init_dir='', multiple=False, filters=()): """ @@ -3283,7 +3828,7 @@ def open_doc(path, **kwargs): """ path = _path_url(path) opt = dict_to_property(kwargs) - doc = get_desktop().loadComponentFromURL(path, '_blank', 0, opt) + doc = get_desktop().loadComponentFromURL(path, '_default', 0, opt) if doc is None: return @@ -3337,13 +3882,38 @@ def zip_content(path): return names +def popen(command, stdin=None): + try: + proc = subprocess.Popen(shlex.split(command), shell=IS_WIN, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + for line in proc.stdout: + yield line.decode().rstrip() + except Exception as e: + error(e) + yield (e.errno, e.strerror) + + +def url_open(url, options={}, json=False): + data = '' + req = Request(url) + try: + response = urlopen(req) + except HTTPError as e: + error(e) + except URLError as e: + error(e.reason) + else: + if json: + data = json_loads(response.read()) + else: + data = response.read() + + return data + + def run(command, wait=False): - # ~ debug(command) - # ~ debug(shlex.split(command)) try: if wait: - # ~ p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE) - # ~ p.wait() result = subprocess.check_output(command, shell=True) else: p = subprocess.Popen(shlex.split(command), stdin=None, @@ -3730,6 +4300,11 @@ def end(): # ~ Export ok # ~ https://en.wikipedia.org/wiki/Web_colors def get_color(*value): + if len(value) == 1 and isinstance(value[0], int): + return value[0] + if len(value) == 1 and isinstance(value[0], tuple): + value = value[0] + COLORS = { 'aliceblue': 15792383, 'antiquewhite': 16444375, @@ -3889,10 +4464,12 @@ def get_color(*value): color = (r << 16) + (g << 8) + b else: color = COLORS.get(value.lower(), -1) - return color +COLOR_ON_FOCUS = get_color('LightYellow') + + # ~ Export ok def render(template, data): s = Template(template) @@ -3951,6 +4528,24 @@ def format(template, data): return result +def _get_url_script(macro): + macro['language'] = macro.get('language', 'Python') + macro['location'] = macro.get('location', 'user') + data = macro.copy() + if data['language'] == 'Python': + data['module'] = '.py$' + elif data['language'] == 'Basic': + data['module'] = '.{}.'.format(macro['module']) + if macro['location'] == 'user': + data['location'] = 'application' + else: + data['module'] = '.' + + url = 'vnd.sun.star.script:{library}{module}{name}?language={language}&location={location}' + path = url.format(**data) + return path + + def _call_macro(macro): #~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification name = 'com.sun.star.script.provider.MasterScriptProviderFactory' @@ -3971,6 +4566,7 @@ def _call_macro(macro): args = macro.get('args', ()) url = 'vnd.sun.star.script:{library}{module}{name}?language={language}&location={location}' path = url.format(**data) + script = factory.createScriptProvider('').getScript(path) return script.invoke(args, None, None)[0] @@ -4197,6 +4793,7 @@ def import_csv(path, **kwargs): rows = tuple(csv.reader(f, **kwargs)) return rows + def export_csv(path, data, **kwargs): with open(path, 'w') as f: writer = csv.writer(f, **kwargs) @@ -4204,6 +4801,19 @@ def export_csv(path, data, **kwargs): return +def install_locales(path, domain='base', dir_locales=DIR['locales']): + p, *_ = get_info_path(path) + path_locales = join(p, dir_locales) + try: + lang = gettext.translation(domain, path_locales, languages=[LANG]) + lang.install() + _ = lang.gettext + except Exception as e: + from gettext import gettext as _ + error(e) + return _ + + class LIBOServer(object): HOST = 'localhost' PORT = '8100' @@ -4273,3 +4883,25 @@ class LIBOServer(object): else: instance = self._sm.createInstance(name) return instance + + + + +# ~ controls = { + # ~ 'CheckBox': 'com.sun.star.awt.UnoControlCheckBoxModel', + # ~ 'ComboBox': 'com.sun.star.awt.UnoControlComboBoxModel', + # ~ 'CurrencyField': 'com.sun.star.awt.UnoControlCurrencyFieldModel', + # ~ 'DateField': 'com.sun.star.awt.UnoControlDateFieldModel', + # ~ 'FileControl': 'com.sun.star.awt.UnoControlFileControlModel', + # ~ 'FormattedField': 'com.sun.star.awt.UnoControlFormattedFieldModel', + # ~ 'GroupBox': 'com.sun.star.awt.UnoControlGroupBoxModel', + # ~ 'ImageControl': 'com.sun.star.awt.UnoControlImageControlModel', + # ~ 'NumericField': 'com.sun.star.awt.UnoControlNumericFieldModel', + # ~ 'PatternField': 'com.sun.star.awt.UnoControlPatternFieldModel', + # ~ 'ProgressBar': 'com.sun.star.awt.UnoControlProgressBarModel', + # ~ 'ScrollBar': 'com.sun.star.awt.UnoControlScrollBarModel', + # ~ 'SimpleAnimation': 'com.sun.star.awt.UnoControlSimpleAnimationModel', + # ~ 'SpinButton': 'com.sun.star.awt.UnoControlSpinButtonModel', + # ~ 'Throbber': 'com.sun.star.awt.UnoControlThrobberModel', + # ~ 'TimeField': 'com.sun.star.awt.UnoControlTimeFieldModel', +# ~ } diff --git a/source/images/close.png b/source/images/close.png new file mode 100644 index 0000000..22a617b Binary files /dev/null and b/source/images/close.png differ diff --git a/source/images/console-24.png b/source/images/console-24.png new file mode 100644 index 0000000..abd99af Binary files /dev/null and b/source/images/console-24.png differ diff --git a/source/images/error.png b/source/images/error.png new file mode 100644 index 0000000..c76cd5c Binary files /dev/null and b/source/images/error.png differ diff --git a/source/images/install.png b/source/images/install.png new file mode 100644 index 0000000..26ac51f Binary files /dev/null and b/source/images/install.png differ diff --git a/source/images/ok.png b/source/images/ok.png new file mode 100644 index 0000000..9d99c0c Binary files /dev/null and b/source/images/ok.png differ diff --git a/source/images/python.png b/source/images/python.png new file mode 100644 index 0000000..2f0fce7 Binary files /dev/null and b/source/images/python.png differ diff --git a/source/images/python_48.png b/source/images/python_48.png new file mode 100644 index 0000000..d068a0d Binary files /dev/null and b/source/images/python_48.png differ diff --git a/source/images/question.png b/source/images/question.png new file mode 100644 index 0000000..a279b91 Binary files /dev/null and b/source/images/question.png differ diff --git a/source/images/search-24.png b/source/images/search-24.png new file mode 100644 index 0000000..12dea78 Binary files /dev/null and b/source/images/search-24.png differ diff --git a/source/images/search-48.png b/source/images/search-48.png new file mode 100644 index 0000000..18c6b3c Binary files /dev/null and b/source/images/search-48.png differ diff --git a/source/images/uninstalling-32.png b/source/images/uninstalling-32.png new file mode 100644 index 0000000..0af9b7e Binary files /dev/null and b/source/images/uninstalling-32.png differ diff --git a/source/zaz.py b/source/zaz.py index 867ac42..9cad0c8 100644 --- a/source/zaz.py +++ b/source/zaz.py @@ -33,6 +33,7 @@ from xml.dom.minidom import parseString from conf import ( DATA, DIRS, + DOMAIN, EXTENSION, FILES, INFO, @@ -50,6 +51,7 @@ class LiboXML(object): 'rdb': 'application/vnd.sun.star.uno-typelibrary;type=RDB', 'xcs': 'application/vnd.sun.star.configuration-schema', 'help': 'application/vnd.sun.star.help', + 'component': 'application/vnd.sun.star.uno-components', } NAME_SPACES = { 'manifest_version': '1.2', @@ -137,6 +139,19 @@ def _save(path, data): return +def _get_files(path, filters=''): + paths = [] + if filters in ('*', '*.*'): + filters = '' + for folder, _, files in os.walk(path): + if filters: + pattern = re.compile(r'\.(?:{})$'.format(filters), re.IGNORECASE) + paths += [_join(folder, f) for f in files if pattern.search(f)] + else: + paths += files + return paths + + def _compress_oxt(): log.info('Compress OXT extension...') @@ -434,8 +449,50 @@ def _embed(args): return +def _locales(args): + EASYMACRO = 'easymacro.py' + + if args.files: + files = args.files.split(',') + else: + files = _get_files(DIRS['source'], 'py') + paths = ' '.join([f for f in files if not EASYMACRO in f]) + path_pot = _join(DIRS['source'], DIRS['locales'], '{}.pot'.format(DOMAIN)) + call([PATHS['gettext'], '-o', path_pot, paths]) + log.info('POT generate successfully...') + return + + +def _update(): + path_locales = _join(DIRS['source'], DIRS['locales']) + path_pot = _join(DIRS['source'], DIRS['locales'], '{}.pot'.format(DOMAIN)) + if not _exists(path_pot): + log.error('Not exists file POT...') + return + + files = _get_files(path_locales, 'po') + if not files: + log.error('First, generate files PO...') + return + + for f in files: + call([PATHS['msgmerge'], '-U', f, path_pot]) + log.info('\tUpdate: {}'.format(f)) + + log.info('Locales update successfully...') + return + + def main(args): + if args.update: + _update() + return + + if args.locales: + _locales(args) + return + if args.embed: _embed(args) return @@ -468,6 +525,10 @@ def _process_command_line_arguments(): default=False, required=False) parser.add_argument('-d', '--document', dest='document', default='') parser.add_argument('-f', '--files', dest='files', default='') + parser.add_argument('-l', '--locales', dest='locales', action='store_true', + default=False, required=False) + parser.add_argument('-u', '--update', dest='update', action='store_true', + default=False, required=False) return parser.parse_args()