From d787c5e8dc4e6f9329dedd1110adb55ebdab894a Mon Sep 17 00:00:00 2001 From: el Mau Date: Thu, 3 Nov 2022 23:31:29 -0600 Subject: [PATCH] Initial support for Galleries --- VERSION | 2 +- source/easymacro/__init__.py | 3 + source/easymacro/easycalc.py | 19 ++ source/easymacro/easydialog.py | 8 +- source/easymacro/easydoc.py | 5 +- source/easymacro/easydrawpage.py | 103 +++++++- source/easymacro/easyforms.py | 369 +++++++++++++++++++++++++++ source/easymacro/easymain.py | 9 +- source/easymacro/easytools.py | 1 + source/tests/__init_.py | 2 + source/{ => tests}/test_config.py | 2 +- source/tests/test_easydocs.py | 41 +++ source/{ => tests}/test_easymacro.py | 20 +- 13 files changed, 561 insertions(+), 23 deletions(-) create mode 100644 source/easymacro/easyforms.py create mode 100644 source/tests/__init_.py rename source/{ => tests}/test_config.py (89%) create mode 100644 source/tests/test_easydocs.py rename source/{ => tests}/test_easymacro.py (89%) diff --git a/VERSION b/VERSION index 6e8bf73..0ea3a94 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0 +0.2.0 diff --git a/source/easymacro/__init__.py b/source/easymacro/__init__.py index 560698b..88ef035 100644 --- a/source/easymacro/__init__.py +++ b/source/easymacro/__init__.py @@ -5,6 +5,8 @@ from .easydialog import * from .easytools import * from .easydocs import LODocuments +from .easydrawpage import LOGalleries + def __getattr__(name): classes = { @@ -33,6 +35,7 @@ def __getattr__(name): 'shell': Shell, 'shortcuts': LOShortCuts(), 'timer': Timer, + 'galleries': LOGalleries() } if name in classes: diff --git a/source/easymacro/easycalc.py b/source/easymacro/easycalc.py index 205ddab..5b85e3c 100644 --- a/source/easymacro/easycalc.py +++ b/source/easymacro/easycalc.py @@ -15,6 +15,7 @@ from .easydoc import LODocument from .easyevents import EventsRangeSelectionListener, LOEvents from .easyshape import LOShapes, LOShape from .easydrawpage import LODrawPage +from .easyforms import LOForms SECONDS_DAY = 60 * 60 * 24 @@ -309,6 +310,11 @@ class LOCalcRange(): """Return the range address like string""" return self.obj.AbsoluteName + @property + def name2(self): + """Return the range address like string""" + return self.name.replace('$', '') + @property def address(self): """Return com.sun.star.table.CellAddress""" @@ -570,6 +576,15 @@ class LOCalcRange(): def rows(self): return LOSheetRows(self.sheet, self.obj.Rows) + @property + def row(self): + r1 = self.address.Row + r2 = r1 + 1 + ra = self.current_region.range_address + c1 = ra.StartColumn + c2 = ra.EndColumn + 1 + return LOCalcRange(self.sheet[r1:r2, c1:c2].obj) + def clear(self, what: int=ONLY_VALUES): """Clear contents""" # ~ http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html @@ -665,6 +680,10 @@ class LOCalcSheet(BaseObject): def shapes(self): return self.draw_page + @property + def forms(self): + return LOForms(self.obj.DrawPage) + @property def events(self): return LOEvents(self.obj.Events) diff --git a/source/easymacro/easydialog.py b/source/easymacro/easydialog.py index f0d8032..e1cd473 100644 --- a/source/easymacro/easydialog.py +++ b/source/easymacro/easydialog.py @@ -6,7 +6,9 @@ from com.sun.star.view.SelectionType import SINGLE, MULTI, RANGE from .easyevents import * from .easydocs import LODocuments -from .easymain import log, TITLE, Color, BaseObject, create_instance, set_properties +from .easymain import (log, TITLE, + BaseObject, Color, + create_instance, set_properties) from .easytools import _, LOInspect, Paths, Services @@ -19,12 +21,12 @@ __all__ = [ COLOR_ON_FOCUS = Color()('LightYellow') SEPARATION = 5 MODELS = { - 'button': 'com.sun.star.awt.UnoControlButtonModel', 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', 'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel', - 'text': 'com.sun.star.awt.UnoControlEditModel', 'image': 'com.sun.star.awt.UnoControlImageControlModel', 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', diff --git a/source/easymacro/easydoc.py b/source/easymacro/easydoc.py index 61dd753..01cb6de 100644 --- a/source/easymacro/easydoc.py +++ b/source/easymacro/easydoc.py @@ -4,7 +4,7 @@ import unohelper from com.sun.star.io import IOException, XOutputStream from .easymain import (log, BaseObject, LOMain, Paths, - create_instance, dict_to_property + dict_to_property, create_instance ) @@ -168,11 +168,12 @@ class LODocument(BaseObject): path_save = Paths.to_url(path) opt = dict_to_property(args) + print(opt) try: self.obj.storeAsURL(path_save, opt) except Exception as e: - error(e) + log.error(e) return False return True diff --git a/source/easymacro/easydrawpage.py b/source/easymacro/easydrawpage.py index e9f7830..ef62b04 100644 --- a/source/easymacro/easydrawpage.py +++ b/source/easymacro/easydrawpage.py @@ -2,7 +2,7 @@ from com.sun.star.awt import Size, Point -from .easymain import BaseObject, set_properties +from .easymain import BaseObject, create_instance, set_properties from .easyshape import LOShape @@ -231,3 +231,104 @@ class LODrawPage(BaseObject): image.Position = Point(x, y) image.Name = name return LOShape(self.obj[index], index) + + +class LOGalleryItem(BaseObject): + + def __init__(self, obj): + super().__init__(obj) + + def __str__(self): + return f'Gallery Item: {self.name}' + + @property + def name(self): + return self.obj.Title + + @property + def url(self): + return self.obj.URL + + @property + def type(self): + """https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1gallery_1_1GalleryItemType.html""" + item_type = {0: 'empty', 1: 'graphic', 2: 'media', 3: 'drawing'} + return item_type[self.obj.GalleryItemType] + + +class LOGallery(BaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._names = [i.Title for i in obj] + + def __str__(self): + return f'Gallery: {self.name}' + + def __len__(self): + return len(self.obj) + + def __getitem__(self, index): + """Index access""" + if isinstance(index, str): + index = self._names.index(index) + return LOGalleryItem(self.obj[index]) + + def __iter__(self): + self._iter = iter(range(len(self))) + return self + + def __next__(self): + """Interation items""" + try: + item = LOGalleryItem(self.obj[next(self._iter)]) + except Exception as e: + raise StopIteration + return item + + @property + def name(self): + return self.obj.Name + + @property + def names(self): + return self._names + + +class LOGalleries(BaseObject): + + def __init__(self): + service_name = 'com.sun.star.gallery.GalleryThemeProvider' + service = create_instance(service_name) + super().__init__(service) + + def __getitem__(self, index): + """Index access""" + name = index + if isinstance(index, int): + name = self.names[index] + return LOGallery(self.obj[name]) + + def __len__(self): + return len(self.obj) + + def __contains__(self, item): + """Contains""" + return item in self.obj + + def __iter__(self): + self._iter = iter(self.names) + return self + + def __next__(self): + """Interation galleries""" + try: + gallery = LOGallery(self.obj[next(self._iter)]) + except Exception as e: + raise StopIteration + return gallery + + @property + def names(self): + return self.obj.ElementNames + diff --git a/source/easymacro/easyforms.py b/source/easymacro/easyforms.py new file mode 100644 index 0000000..cb8b25c --- /dev/null +++ b/source/easymacro/easyforms.py @@ -0,0 +1,369 @@ +#!/usr/bin/env python + +from typing import Any + +from com.sun.star.awt import Size, Point +from com.sun.star.beans import NamedValue + +from .easymain import log, BaseObject + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1form_1_1component.html +MODELS = { + 'hidden': 'com.sun.star.form.component.HiddenControl', + 'label': 'com.sun.star.form.component.FixedText', + 'text': 'com.sun.star.form.component.TextField', + 'checkbox': 'com.sun.star.form.component.CheckBox', +} + +TYPE_CONTROLS = { + 'com.sun.star.form.OHiddenModel': 'hidden', + 'stardiv.Toolkit.UnoFixedTextControl': 'label', + 'com.sun.star.form.OEditControl': 'text', + 'com.sun.star.form.OCheckBoxControl': 'checkbox', +} + + +def _set_properties(model, properties): + keys = tuple(properties.keys()) + values = tuple(properties.values()) + model.setPropertyValues(keys, values) + return + + +class FormBaseControl(BaseObject): + + def __init__(self, obj: Any, shape: Any): + super().__init__(obj) + self._shape = shape + self._doc = obj.Parent.Parent.Parent + try: + self._view = self._doc.CurrentController.getControl(self.obj) + except Exception as e: + self._view = None + + @property + def name(self): + return self.obj.Name + + @property + def properties(self): + from .easytools import LOInspect + data = LOInspect(self.obj).properties + return data + @properties.setter + def properties(self, values: dict): + _set_properties(self.obj, values) + + @property + def border(self): + return self.obj.Border + @border.setter + def border(self, value): + # ~ Not work + self.obj.Border = value + + @property + def enabled(self): + return self.obj.Enabled + @enabled.setter + def enabled(self, value): + self.obj.Enabled = value + + @property + def visible(self): + return self._view.Visible + @visible.setter + def visible(self, value): + self._view.Visible = value + + @property + def cell(self): + return self.obj.ValueBinding.BoundCell + @cell.setter + def cell(self, value): + SERVICE = 'com.sun.star.table.CellValueBinding' + options = (NamedValue(Name='BoundCell', Value=value.address),) + value_binding = self._doc.createInstanceWithArguments(SERVICE, options) + self.obj.ValueBinding = value_binding + + @property + def anchor(self): + return self._shape.Anchor + @anchor.setter + def anchor(self, value): + self._shape.Anchor = value.obj + self._shape.ResizeWithCell = True + + @property + def width(self): + return self._shape.Size.Width + @width.setter + def width(self, value): + self._shape.Size = Size(value, self.height) + + @property + def height(self): + return self._shape.Size.Height + @height.setter + def height(self, value): + self._shape.Size = Size(self.width, value) + + @property + def position(self): + return self._shape.Position + @position.setter + def position(self, value): + self._shape.Position = value + + def center(self): + cell = self.anchor + size = cell.Size + pos = cell.Position + pos.X += (size.Width / 2) - (self.width / 2) + self.position = pos + return + + def set_focus(self): + self._view.setFocus() + return + + +class FormLabel(FormBaseControl): + + def __init__(self, obj: Any, shape: Any): + super().__init__(obj, shape) + + def __str__(self): + return f'{self.type}: {self.name}' + + @property + def type(self): + return 'label' + + @property + def value(self): + return self.obj.Label + @value.setter + def value(self, value): + self.obj.Label = value + + +class FormText(FormBaseControl): + + def __init__(self, obj: Any, shape: Any): + super().__init__(obj, shape) + + def __str__(self): + return f'{self.type}: {self.name}' + + @property + def type(self): + return 'text' + + @property + def value(self): + return self.obj.Text + @value.setter + def value(self, value): + self.obj.Text = value + + +class FormCheckBox(FormBaseControl): + + def __init__(self, obj: Any, shape: Any): + super().__init__(obj, shape) + + def __str__(self): + return f'{self.type}: {self.name}' + + @property + def type(self): + return 'checkbox' + + @property + def value(self): + return self.obj.State + @value.setter + def value(self, value): + self.obj.State = value + + +class FormHidden(FormBaseControl): + + def __init__(self, obj: Any, shape: Any): + super().__init__(obj, shape) + + def __str__(self): + return f'{self.type}: {self.name}' + + @property + def type(self): + return 'hidden' + + @property + def value(self): + return self.obj.HiddenValue + @value.setter + def value(self, value): + self.obj.HiddenValue = value + + +FORM_CLASSES = { + 'hidden': FormHidden, + 'label': FormLabel, + 'text': FormText, + 'checkbox': FormCheckBox, +} + + +class LOForm(BaseObject): + CONTROL_SHAPE = 'com.sun.star.drawing.ControlShape' + CONTROL_HIDDEN = 'com.sun.star.form.OHiddenModel' + + def __init__(self, obj, draw_page): + self._dp = draw_page + self._doc = obj.Parent.Parent + super().__init__(obj) + self._init_controls() + + def _init_controls(self): + for model in self.obj: + if model.ImplementationName == self.CONTROL_HIDDEN: + view = None + tipo = model.ImplementationName + else: + view = self._doc.CurrentController.getControl(model) + tipo = view.ImplementationName + + if not tipo in TYPE_CONTROLS: + log.error(f'For add form control: {tipo}') + continue + + control = FORM_CLASSES[TYPE_CONTROLS[tipo]](model, view) + setattr(self, model.Name, control) + return + + def __str__(self): + return f'Form: {self.name}' + + def __contains__(self, item): + """Contains""" + return item in self.obj.ElementNames + + def __iter__(self): + self._i = 0 + return self + + def __next__(self): + """Interation form""" + try: + control = self.obj[self._i] + control = getattr(self, control.Name) + except Exception as e: + raise StopIteration + self._i += 1 + return control + + def _create_instance(self, name): + return self._doc.createInstance(name) + + @property + def name(self): + return self.obj.Name + @name.setter + def name(self, value: str): + self.obj.setName(value) + + def _default_properties(self, tipo: str, properties: dict, shape: Any): + WIDTH = 5000 + HEIGHT = 500 + + width = properties.pop('Width', WIDTH) + height = properties.pop('Height', HEIGHT) + shape.Size = Size(width, height) + + x = properties.pop('X', 0) + y = properties.pop('Y', 0) + shape.Position = Point(x, y) + + return properties + + def insert(self, properties: dict): + return self.add_control(properties) + + def add_control(self, properties: dict): + tipo = properties.pop('Type').lower() + name = properties['Name'] + + shape = self._create_instance(self.CONTROL_SHAPE) + self._default_properties(tipo, properties, shape) + + model = self._create_instance(MODELS[tipo]) + _set_properties(model, properties) + + if tipo == 'hidden': + shape = None + else: + shape.Control = model + + self.obj.insertByIndex(self.obj.Count, model) + if not shape is None: + self._dp.add(shape) + control = FORM_CLASSES[tipo](model, shape) + setattr(self, name, control) + + return control + + +class LOForms(BaseObject): + + def __init__(self, obj): + self._dp = obj + super().__init__(obj.Forms) + + def __getitem__(self, index): + """Index access""" + return LOForm(self.obj[index], self._dp) + + def __setitem__(self, key: str, value: Any): + """Insert new form""" + self.obj[key] = value + + def __len__(self): + """Count forms""" + return len(self.obj) + + def __iter__(self): + self._i = 0 + return self + + def __next__(self): + """Interation forms""" + try: + form = LOForm(self.obj[self._i], self._dp) + except Exception as e: + raise StopIteration + self._i += 1 + return form + + def __contains__(self, item): + """Contains""" + return item in self.obj + + def new_form(self): + """Create new instance form""" + form = self.obj.Parent.createInstance('com.sun.star.form.component.Form') + return form + + def insert(self, name: str): + """Insert new form + + :param name: Name new form. + :type name: str + :return: New instance LOForm. + :rtype: LOForm + """ + form = self.new_form() + self.obj.insertByName(name, form) + return LOForm(form, self._dp) diff --git a/source/easymacro/easymain.py b/source/easymacro/easymain.py index 65a895a..805e45f 100644 --- a/source/easymacro/easymain.py +++ b/source/easymacro/easymain.py @@ -841,7 +841,8 @@ class Paths(object): return tempfile.TemporaryDirectory(ignore_cleanup_errors=True) @classmethod - def get(cls, init_dir: str='', filters: str='', multiple: bool=False) -> Union[str, list]: + def get(cls, init_dir: str='', filters: str='', + multiple: bool=False, filter_group: tuple=()) -> Union[str, list]: """Get path for open :param init_dir: Initial default path @@ -868,6 +869,12 @@ class Paths(object): if filters: for f in filters.split(','): file_picker.appendFilter(f.upper(), f'*.{f.lower()}') + if filter_group: + for t, v in filter_group: + sp = StringPair() + sp.First = t + sp.Second = v + file_picker.appendFilterGroup(t, ((sp),)) if file_picker.execute(): paths = [cls.to_system(p) for p in file_picker.getSelectedFiles()] diff --git a/source/easymacro/easytools.py b/source/easymacro/easytools.py index 1bb3d04..e8da6ed 100644 --- a/source/easymacro/easytools.py +++ b/source/easymacro/easytools.py @@ -73,6 +73,7 @@ __all__ = [ 'catch_exception', 'debug', 'error', + 'errorbox', 'info', 'mri', 'msgbox', diff --git a/source/tests/__init_.py b/source/tests/__init_.py new file mode 100644 index 0000000..cf529d7 --- /dev/null +++ b/source/tests/__init_.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python + diff --git a/source/test_config.py b/source/tests/test_config.py similarity index 89% rename from source/test_config.py rename to source/tests/test_config.py index 1ab64b5..2734f82 100644 --- a/source/test_config.py +++ b/source/tests/test_config.py @@ -9,6 +9,6 @@ USER = 'mau' IS_WIN = False IS_MAC = False -LIBO_VERSION = '7.3' +LIBO_VERSION = '7.4' LANGUAGE = 'en-US' LANG = 'en' diff --git a/source/tests/test_easydocs.py b/source/tests/test_easydocs.py new file mode 100644 index 0000000..bde8025 --- /dev/null +++ b/source/tests/test_easydocs.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# coding: utf-8 + +import sys +from pathlib import Path +p = str(Path(__file__).resolve().parent.parent) +sys.path.insert(0, p) + +import unittest +from tempfile import NamedTemporaryFile +import easymacro as app + + +class BaseTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + pass + + @classmethod + def tearDownClass(cls): + pass + + def setUp(self): + msg = f'In method: {self._testMethodName}' + app.debug(msg) + + def tearDown(self): + pass + + +class TestDocuments(BaseTest): + + def test_doc_new(self): + result = app.docs.new() + self.assertIsNotNone(result) + result.close() + + +if __name__ == '__main__': + unittest.main(exit=False) diff --git a/source/test_easymacro.py b/source/tests/test_easymacro.py similarity index 89% rename from source/test_easymacro.py rename to source/tests/test_easymacro.py index 77badcc..fb6e25b 100644 --- a/source/test_easymacro.py +++ b/source/tests/test_easymacro.py @@ -1,6 +1,12 @@ #!/usr/bin/env python3 # coding: utf-8 +import sys +from pathlib import Path +p = str(Path(__file__).resolve().parent.parent) +sys.path.insert(0, p) + + import unittest from tempfile import NamedTemporaryFile import easymacro as app @@ -29,20 +35,6 @@ class BaseTest(unittest.TestCase): pass -# ~ class TestDocuments(BaseTest): - - # ~ def test_new_doc(self): - # ~ result = app.new_doc() - # ~ self.assertIsInstance(result, app.LOCalc) - # ~ result.close() - - # ~ def test_get_type_doc(self): - # ~ expected = 'calc' - # ~ result = app.new_doc() - # ~ self.assertEqual(result.type, expected) - # ~ result.close() - - class TestVars(BaseTest): def test_01_os(self):