diff --git a/CHANGELOG b/CHANGELOG index e5090d9..8acdd9c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +v 0.8.0 [13-jun-2021] + - Update easymacro.py + - Add proxy config + +v 0.7.0 [05-jan-2020] + - Disable search + +v 0.6.0 [18-nov-2020] + - Add install with requirements.txt + v 0.5.0 [09-jul-2020] - Test install pandas @@ -5,16 +15,12 @@ v 0.4.0 [10-mar-2020] - Update easymacro.py - Fix: in OSx Catalina - v 0.3.0 [12-nov-2019] - Update easymacro.py - Test install numpy - v 0.2.0 [18-oct-2019] - Add spanish - v 0.1.0 [18-oct-2019] - Initial version - diff --git a/README.md b/README.md index 28f175c..85888d9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Extension for install and admin Python Pip in LibreOffice. Thanks! -https://gitlab.com/mauriciobaeza/zaz +https://git.cuates.net/elmau/zaz ### Software libre, no gratis diff --git a/VERSION b/VERSION index bcaffe1..8adc70f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.0 \ No newline at end of file +0.8.0 \ No newline at end of file diff --git a/conf.py b/conf.py index 42e616c..5b3edec 100644 --- a/conf.py +++ b/conf.py @@ -26,7 +26,7 @@ import logging TYPE_EXTENSION = 1 # ~ https://semver.org/ -VERSION = '0.7.0' +VERSION = '0.8.0' # ~ Your great extension name, not used spaces NAME = 'ZAZPip' @@ -46,9 +46,10 @@ PATH_MSGMERGE = 'msgmerge' # ~ Show in extension manager +URL_GIT = 'https://git.cuates.net/elmau/zaz-pip' PUBLISHER = { - 'en': {'text': 'El Mau', 'link': 'https://gitlab.com/mauriciobaeza'}, - 'es': {'text': 'El Mau', 'link': 'https://gitlab.com/mauriciobaeza'}, + 'en': {'text': 'El Mau', 'link': URL_GIT}, + 'es': {'text': 'El Mau', 'link': URL_GIT}, } # ~ Name in this folder for copy diff --git a/easymacro.py b/easymacro.py index 0745d96..cbb26a0 100644 --- a/easymacro.py +++ b/easymacro.py @@ -21,6 +21,7 @@ import base64 import csv +import ctypes import datetime import getpass import gettext @@ -82,8 +83,9 @@ from com.sun.star.util import Time, Date, DateTime from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK from com.sun.star.text.TextContentAnchorType import AS_CHARACTER -from com.sun.star.awt import XActionListener +from com.sun.star.lang import Locale from com.sun.star.lang import XEventListener +from com.sun.star.awt import XActionListener from com.sun.star.awt import XMenuListener from com.sun.star.awt import XMouseListener from com.sun.star.awt import XMouseMotionListener @@ -203,6 +205,12 @@ class ConditionOperator(): CO = ConditionOperator +class DataPilotFieldOrientation(): + from com.sun.star.sheet.DataPilotFieldOrientation \ + import HIDDEN, COLUMN, ROW, PAGE, DATA +DPFO = DataPilotFieldOrientation + + OS = platform.system() IS_WIN = OS == 'Windows' IS_MAC = OS == 'Darwin' @@ -308,6 +316,11 @@ def get_app_config(node_name: str, key: str=''): LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale') LANG = LANGUAGE.split('-')[0] +try: + COUNTRY = LANGUAGE.split('-')[1] +except: + COUNTRY = '' +LOCALE = Locale(LANG, COUNTRY, '') NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName') VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion') @@ -372,6 +385,8 @@ def mri(obj: Any) -> None: error(msg) return + if hasattr(obj, 'obj'): + obj = obj.obj m.inspect(obj) return @@ -1166,6 +1181,9 @@ class LODocument(object): return def to_pdf(self, path: str='', args: dict={}): + """ + https://wiki.documentfoundation.org/Macros/Python_Guide/PDF_export_filter_data + """ path_pdf = path filter_name = '{}_pdf_Export'.format(self.type) filter_data = dict_to_property(args, True) @@ -1520,6 +1538,142 @@ class LOSheetCharts(object): return LOChart(name, self.obj[name], self._sheet.draw_page) +class LOSheetTableField(object): + + def __init__(self, obj): + self._obj = obj + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self.obj.Name + + @property + def orientation(self): + return self.obj.Orientation + @orientation.setter + def orientation(self, value): + self.obj.Orientation = value + + +# ~ com.sun.star.sheet.DataPilotFieldOrientation.ROW +class LOSheetTable(object): + + def __init__(self, obj): + self._obj = obj + self._source = None + + def __getitem__(self, index): + field = self.obj.DataPilotFields[index] + return LOSheetTableField(field) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def filter(self): + return self.obj.ShowFilterButton + @filter.setter + def filter(self, value): + self.obj.ShowFilterButton = value + + @property + def source(self): + return self._source + @source.setter + def source(self, value): + self._source = value + self.obj.SourceRange = value.range_address + + @property + def rows(self): + return self.obj.RowFields + @rows.setter + def rows(self, values): + if not isinstance(values, tuple): + values = (values,) + for v in values: + with self[v] as f: + f.orientation = DPFO.ROW + @property + def columns(self): + return self.obj.ColumnFields + @columns.setter + def columns(self, values): + if not isinstance(values, tuple): + values = (values,) + for v in values: + with self[v] as f: + f.orientation = DPFO.COLUMN + + @property + def data(self): + return self.obj.DataFields + @data.setter + def data(self, values): + if not isinstance(values, tuple): + values = (values,) + for v in values: + with self[v] as f: + f.orientation = DPFO.DATA + + +class LOSheetTables(object): + + def __init__(self, obj, sheet): + self._obj = obj + self._sheet = sheet + + def __getitem__(self, index): + return LOSheetTable(self.obj[index]) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + @property + def obj(self): + return self._obj + + @property + def count(self): + return self.obj.Count + + @property + def names(self): + return self.obj.ElementNames + + def new(self, name, target): + table = self.obj.createDataPilotDescriptor() + self.obj.insertNewByName(name, target.address, table) + return LOSheetTable(self.obj[name]) + + def remove(self, name): + self.obj.removeByName(name) + return + + class LOFormControl(LOBaseObject): EVENTS = { 'action': 'actionPerformed', @@ -2045,6 +2199,10 @@ class LOCalcSheet(object): def charts(self): return LOSheetCharts(self.obj.Charts, self) + @property + def tables(self): + return LOSheetTables(self.obj.DataPilotTables, self) + @property def rows(self): return LOSheetRows(self, self.obj.Rows) @@ -2558,6 +2716,7 @@ class LOCalcRange(object): cell = None if isinstance(value, dict): for k, v in value.items(): + # ~ print(1, 'RENDER', k, v) cell = self._render_value(k, v, key) return cell elif isinstance(value, (list, tuple)): @@ -2569,7 +2728,11 @@ class LOCalcRange(object): search = f'{{{parent}.{key}}}' ranges = self.find_all(search) - for cell in ranges or range(0): + if ranges is None: + return + + # ~ for cell in ranges or range(0): + for cell in ranges: self._set_new_value(cell, search, value) return LOCalcRange(cell) @@ -2718,6 +2881,72 @@ class LOCalcRange(object): self.obj.fillAuto(0, source) return + def _cast(self, t, v): + if not t: + return v + + if t == datetime.date: + nv = datetime.date.fromordinal(int(v) + DATE_OFFSET) + else: + nv = t(v) + return nv + + def get_data(self, types): + values = [ + [self._cast(types[i], v) for i, v in enumerate(row)] + for row in self.data + ] + return values + + +class LOWriterStyles(object): + + def __init__(self, styles): + self._styles = styles + + @property + def names(self): + return {s.DisplayName: s.Name for s in self._styles} + + def __str__(self): + return '\n'.join(tuple(self.names.values())) + + +class LOWriterStylesFamilies(object): + + def __init__(self, styles): + self._styles = styles + + def __getitem__(self, index): + styles = { + 'Character': 'CharacterStyles', + 'Paragraph': 'ParagraphStyles', + 'Page': 'PageStyles', + 'Frame': 'FrameStyles', + 'Numbering': 'NumberingStyles', + 'Table': 'TableStyles', + 'Cell': 'CellStyles', + } + name = styles.get(index, index) + return LOWriterStyles(self._styles[name]) + + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + obj = LOWriterStyles(self._styles[self._index]) + self._index += 1 + return obj + # ~ raise StopIteration + + @property + def names(self): + return self._styles.ElementNames + + def __str__(self): + return '\n'.join(self.names) + class LOWriterPageStyle(LOBaseObject): @@ -2755,18 +2984,23 @@ class LOWriterTextRange(object): self._doc = doc self._is_paragraph = self.obj.ImplementationName == 'SwXParagraph' self._is_table = self.obj.ImplementationName == 'SwXTextTable' + self._is_text = self.obj.ImplementationName == 'SwXTextPortion' + self._parts = [] + if self._is_paragraph: + self._parts = [LOWriterTextRange(p, doc) for p in obj] def __iter__(self): self._index = 0 return self def __next__(self): - for i, p in enumerate(self.obj): - if i == self._index: - obj = LOWriterTextRange(p, self._doc) - self._index += 1 - return obj - raise StopIteration + try: + obj = self._parts[self._index] + except IndexError: + raise StopIteration + + self._index += 1 + return obj @property def obj(self): @@ -2785,11 +3019,37 @@ class LOWriterTextRange(object): @property def value(self): return self.string + @value.setter + def value(self, value): + self.string = value + + @property + def style(self): + s = '' + if self.is_paragraph: + s = self.obj.ParaStyleName + elif self.is_text: + s = self.obj.CharStyleName + return s + @style.setter + def style(self, value): + if self.is_paragraph: + self.obj.ParaStyleName = value + elif self.is_text: + self.obj.CharStyleName = value + + @property + def is_paragraph(self): + return self._is_paragraph @property def is_table(self): return self._is_table + @property + def is_text(self): + return self._is_text + @property def text(self): return self.obj.Text @@ -2802,6 +3062,13 @@ class LOWriterTextRange(object): def dp(self): return self._doc.dp + def delete(self): + cursor = self.cursor + cursor.gotoStartOfParagraph(False) + cursor.gotoNextParagraph(True) + cursor.String = '' + return + def offset(self): cursor = self.cursor.getEnd() return LOWriterTextRange(cursor, self._doc) @@ -2846,25 +3113,26 @@ class LOWriterTextRanges(object): def __init__(self, obj, doc): self._obj = obj self._doc = doc + self._paragraphs = [LOWriterTextRange(p, doc) for p in obj] + + def __len__(self): + return len(self._paragraphs) def __getitem__(self, index): - for i, p in enumerate(self.obj): - if i == index: - obj = LOWriterTextRange(p, self._doc) - break - return obj + return self._paragraphs[index] def __iter__(self): self._index = 0 return self def __next__(self): - for i, p in enumerate(self.obj): - if i == self._index: - obj = LOWriterTextRange(p, self._doc) - self._index += 1 - return obj - raise StopIteration + try: + obj = self._paragraphs[self._index] + except IndexError: + raise StopIteration + + self._index += 1 + return obj @property def obj(self): @@ -2887,10 +3155,17 @@ class LOWriterTextTable(object): @property def data(self): - return self._obj.DataArray + return self.obj.DataArray @data.setter def data(self, values): - self._obj.DataArray = values + self.obj.DataArray = values + + @property + def style(self): + return self.obj.TableTemplateName + @style.setter + def style(self, value): + self.obj.autoFormat(value) class LOWriterTextTables(object): @@ -2920,7 +3195,7 @@ class LOWriter(LODocument): @property def text(self): - return LOWriterTextRange(self.obj.Text, self) + return self.paragraphs @property def paragraphs(self): @@ -2973,6 +3248,10 @@ class LOWriter(LODocument): ps = self.obj.StyleFamilies['PageStyles'] return LOWriterPageStyles(ps) + @property + def styles(self): + return LOWriterStylesFamilies(self.obj.StyleFamilies) + @property def search_descriptor(self): return self.obj.createSearchDescriptor() @@ -3403,15 +3682,19 @@ class BaseRow: class BaseQuery(object): PY_TYPES = { - 'SQL_LONG': 'getLong', - 'SQL_VARYING': 'getString', - 'SQL_FLOAT': 'getFloat', - 'SQL_BOOLEAN': 'getBoolean', - 'SQL_TYPE_DATE': 'getDate', - 'SQL_TYPE_TIME': 'getTime', - 'SQL_TIMESTAMP': 'getTimestamp', + 'VARCHAR': 'getString', + 'INTEGER': 'getLong', + 'DATE': 'getDate', + # ~ 'SQL_LONG': 'getLong', + # ~ 'SQL_VARYING': 'getString', + # ~ 'SQL_FLOAT': 'getFloat', + # ~ 'SQL_BOOLEAN': 'getBoolean', + # ~ 'SQL_TYPE_DATE': 'getDate', + # ~ 'SQL_TYPE_TIME': 'getTime', + # ~ 'SQL_TIMESTAMP': 'getTimestamp', } - TYPES_DATE = ('SQL_TYPE_DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') + # ~ TYPES_DATE = ('SQL_TYPE_DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') + TYPES_DATE = ('DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') def __init__(self, query): self._query = query @@ -3437,6 +3720,7 @@ class BaseQuery(object): def _to_python(self, index): type_field = self._meta.getColumnTypeName(index) + # ~ print('TF', type_field) value = getattr(self._query, self.PY_TYPES[type_field])(index) if type_field in self.TYPES_DATE: value = _struct_to_date(value) @@ -3562,10 +3846,11 @@ class LOBase(object): self._con.getTables().refresh() return - def initialize(self, database_proxy, tables): + def initialize(self, database_proxy, tables=[]): db = FirebirdDatabase(self) database_proxy.initialize(db) - db.create_tables(tables) + if tables: + db.create_tables(tables) return def _validate_sql(self, sql, params): @@ -3676,6 +3961,7 @@ class LODocs(object): return doc def __len__(self): + # ~ len(self._desktop.Components) for i, _ in enumerate(self._desktop.Components): pass return i + 1 @@ -4445,6 +4731,13 @@ class UnoText(UnoBaseObject): def value(self, value): self.model.Text = value + @property + def echochar(self): + return chr(self.model.EchoChar) + @echochar.setter + def echochar(self, value): + self.model.EchoChar = ord(value[0]) + def validate(self): return @@ -5285,6 +5578,11 @@ class LODialog(object): self.obj.dispose() return value + def set_values(self, data): + for k, v in data.items(): + self._controls[k].value = v + return + class LOSheets(object): @@ -5832,6 +6130,7 @@ _CB = ClipBoard class Paths(object): FILE_PICKER = 'com.sun.star.ui.dialogs.FilePicker' + FOLDER_PICKER = 'com.sun.star.ui.dialogs.FolderPicker' def __init__(self, path=''): if path.startswith('file://'): @@ -5941,7 +6240,7 @@ class Paths(object): @classmethod def get_dir(cls, init_dir=''): - folder_picker = create_instance(cls.FILE_PICKER) + folder_picker = create_instance(cls.FOLDER_PICKER) if not init_dir: init_dir = cls.documents init_dir = cls.to_url(init_dir) @@ -5950,7 +6249,7 @@ class Paths(object): path = '' if folder_picker.execute(): - path = cls.to_system(folder_picker.getDisplayDirectory()) + path = cls.to_system(folder_picker.getDirectory()) return path @classmethod @@ -6219,6 +6518,50 @@ class Paths(object): _P = Paths +class Dates(object): + + @classmethod + def date(cls, year, month, day): + d = datetime.date(year, month, day) + return d + + +class SpellChecker(object): + + def __init__(self): + service = 'com.sun.star.linguistic2.SpellChecker' + self._spellchecker = create_instance(service, True) + self._locale = LOCALE + + @property + def locale(self): + slocal = f'{self._locale.Language}-{self._locale.Country}' + return slocale + @locale.setter + def locale(self, value): + lang = value.split('-') + self._locale = Locale(lang[0], lang[1], '') + + def is_valid(self, word): + result = self._spellchecker.isValid(word, self._locale, ()) + return result + + def spell(self, word): + result = self._spellchecker.spell(word, self._locale, ()) + if result: + result = result.getAlternatives() + if not isinstance(result, tuple): + result = () + return result + + +def spell(word, locale=''): + sc = SpellChecker() + if locale: + sc.locale = locale + return sc.spell(word) + + def __getattr__(name): if name == 'active': return LODocs().active @@ -6244,6 +6587,8 @@ def __getattr__(name): return LOShortCuts() if name == 'clipboard': return ClipBoard + if name == 'dates': + return Dates raise AttributeError(f"module '{__name__}' has no attribute '{name}'") @@ -6330,48 +6675,6 @@ def get_fonts(): return device.FontDescriptors -# ~ From request -# ~ https://github.com/psf/requests/blob/master/requests/structures.py#L15 -class CaseInsensitiveDict(MutableMapping): - - def __init__(self, data=None, **kwargs): - self._store = OrderedDict() - if data is None: - data = {} - self.update(data, **kwargs) - - def __setitem__(self, key, value): - # Use the lowercased key for lookups, but store the actual - # key alongside the value. - self._store[key.lower()] = (key, value) - - def __getitem__(self, key): - return self._store[key.lower()][1] - - def __delitem__(self, key): - del self._store[key.lower()] - - def __iter__(self): - return (casedkey for casedkey, mappedvalue in self._store.values()) - - def __len__(self): - return len(self._store) - - def lower_items(self): - """Like iteritems(), but with all lowercase keys.""" - values = ( - (lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items() - ) - return values - - # Copy is required - def copy(self): - return CaseInsensitiveDict(self._store.values()) - - def __repr__(self): - return str(dict(self.items())) - - # ~ https://en.wikipedia.org/wiki/Web_colors def get_color(value): COLORS = { diff --git a/files/ZAZPip_v0.7.0.oxt b/files/ZAZPip_v0.7.0.oxt deleted file mode 100644 index 1b86d6a..0000000 Binary files a/files/ZAZPip_v0.7.0.oxt and /dev/null differ diff --git a/files/ZAZPip_v0.8.0.oxt b/files/ZAZPip_v0.8.0.oxt new file mode 100644 index 0000000..d134196 Binary files /dev/null and b/files/ZAZPip_v0.8.0.oxt differ diff --git a/source/description.xml b/source/description.xml index 655fa38..437e820 100644 --- a/source/description.xml +++ b/source/description.xml @@ -1,7 +1,7 @@ - + ZAZ Pip ZAZ Pip @@ -14,8 +14,8 @@ - El Mau - El Mau + El Mau + El Mau diff --git a/source/images/delete.svg b/source/images/delete.svg new file mode 100755 index 0000000..534460e --- /dev/null +++ b/source/images/delete.svg @@ -0,0 +1,4 @@ + + + + diff --git a/source/images/proxy.svg b/source/images/proxy.svg new file mode 100644 index 0000000..b8eecc3 --- /dev/null +++ b/source/images/proxy.svg @@ -0,0 +1,54 @@ + +image/svg+xml diff --git a/source/images/python.svg b/source/images/python.svg index 9d3262f..6866891 100644 --- a/source/images/python.svg +++ b/source/images/python.svg @@ -1,23 +1,23 @@ image/svg+xml + + + + + + + diff --git a/source/pythonpath/easymacro.py b/source/pythonpath/easymacro.py index 0745d96..cbb26a0 100644 --- a/source/pythonpath/easymacro.py +++ b/source/pythonpath/easymacro.py @@ -21,6 +21,7 @@ import base64 import csv +import ctypes import datetime import getpass import gettext @@ -82,8 +83,9 @@ from com.sun.star.util import Time, Date, DateTime from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK from com.sun.star.text.TextContentAnchorType import AS_CHARACTER -from com.sun.star.awt import XActionListener +from com.sun.star.lang import Locale from com.sun.star.lang import XEventListener +from com.sun.star.awt import XActionListener from com.sun.star.awt import XMenuListener from com.sun.star.awt import XMouseListener from com.sun.star.awt import XMouseMotionListener @@ -203,6 +205,12 @@ class ConditionOperator(): CO = ConditionOperator +class DataPilotFieldOrientation(): + from com.sun.star.sheet.DataPilotFieldOrientation \ + import HIDDEN, COLUMN, ROW, PAGE, DATA +DPFO = DataPilotFieldOrientation + + OS = platform.system() IS_WIN = OS == 'Windows' IS_MAC = OS == 'Darwin' @@ -308,6 +316,11 @@ def get_app_config(node_name: str, key: str=''): LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale') LANG = LANGUAGE.split('-')[0] +try: + COUNTRY = LANGUAGE.split('-')[1] +except: + COUNTRY = '' +LOCALE = Locale(LANG, COUNTRY, '') NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName') VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion') @@ -372,6 +385,8 @@ def mri(obj: Any) -> None: error(msg) return + if hasattr(obj, 'obj'): + obj = obj.obj m.inspect(obj) return @@ -1166,6 +1181,9 @@ class LODocument(object): return def to_pdf(self, path: str='', args: dict={}): + """ + https://wiki.documentfoundation.org/Macros/Python_Guide/PDF_export_filter_data + """ path_pdf = path filter_name = '{}_pdf_Export'.format(self.type) filter_data = dict_to_property(args, True) @@ -1520,6 +1538,142 @@ class LOSheetCharts(object): return LOChart(name, self.obj[name], self._sheet.draw_page) +class LOSheetTableField(object): + + def __init__(self, obj): + self._obj = obj + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self.obj.Name + + @property + def orientation(self): + return self.obj.Orientation + @orientation.setter + def orientation(self, value): + self.obj.Orientation = value + + +# ~ com.sun.star.sheet.DataPilotFieldOrientation.ROW +class LOSheetTable(object): + + def __init__(self, obj): + self._obj = obj + self._source = None + + def __getitem__(self, index): + field = self.obj.DataPilotFields[index] + return LOSheetTableField(field) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def filter(self): + return self.obj.ShowFilterButton + @filter.setter + def filter(self, value): + self.obj.ShowFilterButton = value + + @property + def source(self): + return self._source + @source.setter + def source(self, value): + self._source = value + self.obj.SourceRange = value.range_address + + @property + def rows(self): + return self.obj.RowFields + @rows.setter + def rows(self, values): + if not isinstance(values, tuple): + values = (values,) + for v in values: + with self[v] as f: + f.orientation = DPFO.ROW + @property + def columns(self): + return self.obj.ColumnFields + @columns.setter + def columns(self, values): + if not isinstance(values, tuple): + values = (values,) + for v in values: + with self[v] as f: + f.orientation = DPFO.COLUMN + + @property + def data(self): + return self.obj.DataFields + @data.setter + def data(self, values): + if not isinstance(values, tuple): + values = (values,) + for v in values: + with self[v] as f: + f.orientation = DPFO.DATA + + +class LOSheetTables(object): + + def __init__(self, obj, sheet): + self._obj = obj + self._sheet = sheet + + def __getitem__(self, index): + return LOSheetTable(self.obj[index]) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + @property + def obj(self): + return self._obj + + @property + def count(self): + return self.obj.Count + + @property + def names(self): + return self.obj.ElementNames + + def new(self, name, target): + table = self.obj.createDataPilotDescriptor() + self.obj.insertNewByName(name, target.address, table) + return LOSheetTable(self.obj[name]) + + def remove(self, name): + self.obj.removeByName(name) + return + + class LOFormControl(LOBaseObject): EVENTS = { 'action': 'actionPerformed', @@ -2045,6 +2199,10 @@ class LOCalcSheet(object): def charts(self): return LOSheetCharts(self.obj.Charts, self) + @property + def tables(self): + return LOSheetTables(self.obj.DataPilotTables, self) + @property def rows(self): return LOSheetRows(self, self.obj.Rows) @@ -2558,6 +2716,7 @@ class LOCalcRange(object): cell = None if isinstance(value, dict): for k, v in value.items(): + # ~ print(1, 'RENDER', k, v) cell = self._render_value(k, v, key) return cell elif isinstance(value, (list, tuple)): @@ -2569,7 +2728,11 @@ class LOCalcRange(object): search = f'{{{parent}.{key}}}' ranges = self.find_all(search) - for cell in ranges or range(0): + if ranges is None: + return + + # ~ for cell in ranges or range(0): + for cell in ranges: self._set_new_value(cell, search, value) return LOCalcRange(cell) @@ -2718,6 +2881,72 @@ class LOCalcRange(object): self.obj.fillAuto(0, source) return + def _cast(self, t, v): + if not t: + return v + + if t == datetime.date: + nv = datetime.date.fromordinal(int(v) + DATE_OFFSET) + else: + nv = t(v) + return nv + + def get_data(self, types): + values = [ + [self._cast(types[i], v) for i, v in enumerate(row)] + for row in self.data + ] + return values + + +class LOWriterStyles(object): + + def __init__(self, styles): + self._styles = styles + + @property + def names(self): + return {s.DisplayName: s.Name for s in self._styles} + + def __str__(self): + return '\n'.join(tuple(self.names.values())) + + +class LOWriterStylesFamilies(object): + + def __init__(self, styles): + self._styles = styles + + def __getitem__(self, index): + styles = { + 'Character': 'CharacterStyles', + 'Paragraph': 'ParagraphStyles', + 'Page': 'PageStyles', + 'Frame': 'FrameStyles', + 'Numbering': 'NumberingStyles', + 'Table': 'TableStyles', + 'Cell': 'CellStyles', + } + name = styles.get(index, index) + return LOWriterStyles(self._styles[name]) + + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + obj = LOWriterStyles(self._styles[self._index]) + self._index += 1 + return obj + # ~ raise StopIteration + + @property + def names(self): + return self._styles.ElementNames + + def __str__(self): + return '\n'.join(self.names) + class LOWriterPageStyle(LOBaseObject): @@ -2755,18 +2984,23 @@ class LOWriterTextRange(object): self._doc = doc self._is_paragraph = self.obj.ImplementationName == 'SwXParagraph' self._is_table = self.obj.ImplementationName == 'SwXTextTable' + self._is_text = self.obj.ImplementationName == 'SwXTextPortion' + self._parts = [] + if self._is_paragraph: + self._parts = [LOWriterTextRange(p, doc) for p in obj] def __iter__(self): self._index = 0 return self def __next__(self): - for i, p in enumerate(self.obj): - if i == self._index: - obj = LOWriterTextRange(p, self._doc) - self._index += 1 - return obj - raise StopIteration + try: + obj = self._parts[self._index] + except IndexError: + raise StopIteration + + self._index += 1 + return obj @property def obj(self): @@ -2785,11 +3019,37 @@ class LOWriterTextRange(object): @property def value(self): return self.string + @value.setter + def value(self, value): + self.string = value + + @property + def style(self): + s = '' + if self.is_paragraph: + s = self.obj.ParaStyleName + elif self.is_text: + s = self.obj.CharStyleName + return s + @style.setter + def style(self, value): + if self.is_paragraph: + self.obj.ParaStyleName = value + elif self.is_text: + self.obj.CharStyleName = value + + @property + def is_paragraph(self): + return self._is_paragraph @property def is_table(self): return self._is_table + @property + def is_text(self): + return self._is_text + @property def text(self): return self.obj.Text @@ -2802,6 +3062,13 @@ class LOWriterTextRange(object): def dp(self): return self._doc.dp + def delete(self): + cursor = self.cursor + cursor.gotoStartOfParagraph(False) + cursor.gotoNextParagraph(True) + cursor.String = '' + return + def offset(self): cursor = self.cursor.getEnd() return LOWriterTextRange(cursor, self._doc) @@ -2846,25 +3113,26 @@ class LOWriterTextRanges(object): def __init__(self, obj, doc): self._obj = obj self._doc = doc + self._paragraphs = [LOWriterTextRange(p, doc) for p in obj] + + def __len__(self): + return len(self._paragraphs) def __getitem__(self, index): - for i, p in enumerate(self.obj): - if i == index: - obj = LOWriterTextRange(p, self._doc) - break - return obj + return self._paragraphs[index] def __iter__(self): self._index = 0 return self def __next__(self): - for i, p in enumerate(self.obj): - if i == self._index: - obj = LOWriterTextRange(p, self._doc) - self._index += 1 - return obj - raise StopIteration + try: + obj = self._paragraphs[self._index] + except IndexError: + raise StopIteration + + self._index += 1 + return obj @property def obj(self): @@ -2887,10 +3155,17 @@ class LOWriterTextTable(object): @property def data(self): - return self._obj.DataArray + return self.obj.DataArray @data.setter def data(self, values): - self._obj.DataArray = values + self.obj.DataArray = values + + @property + def style(self): + return self.obj.TableTemplateName + @style.setter + def style(self, value): + self.obj.autoFormat(value) class LOWriterTextTables(object): @@ -2920,7 +3195,7 @@ class LOWriter(LODocument): @property def text(self): - return LOWriterTextRange(self.obj.Text, self) + return self.paragraphs @property def paragraphs(self): @@ -2973,6 +3248,10 @@ class LOWriter(LODocument): ps = self.obj.StyleFamilies['PageStyles'] return LOWriterPageStyles(ps) + @property + def styles(self): + return LOWriterStylesFamilies(self.obj.StyleFamilies) + @property def search_descriptor(self): return self.obj.createSearchDescriptor() @@ -3403,15 +3682,19 @@ class BaseRow: class BaseQuery(object): PY_TYPES = { - 'SQL_LONG': 'getLong', - 'SQL_VARYING': 'getString', - 'SQL_FLOAT': 'getFloat', - 'SQL_BOOLEAN': 'getBoolean', - 'SQL_TYPE_DATE': 'getDate', - 'SQL_TYPE_TIME': 'getTime', - 'SQL_TIMESTAMP': 'getTimestamp', + 'VARCHAR': 'getString', + 'INTEGER': 'getLong', + 'DATE': 'getDate', + # ~ 'SQL_LONG': 'getLong', + # ~ 'SQL_VARYING': 'getString', + # ~ 'SQL_FLOAT': 'getFloat', + # ~ 'SQL_BOOLEAN': 'getBoolean', + # ~ 'SQL_TYPE_DATE': 'getDate', + # ~ 'SQL_TYPE_TIME': 'getTime', + # ~ 'SQL_TIMESTAMP': 'getTimestamp', } - TYPES_DATE = ('SQL_TYPE_DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') + # ~ TYPES_DATE = ('SQL_TYPE_DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') + TYPES_DATE = ('DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') def __init__(self, query): self._query = query @@ -3437,6 +3720,7 @@ class BaseQuery(object): def _to_python(self, index): type_field = self._meta.getColumnTypeName(index) + # ~ print('TF', type_field) value = getattr(self._query, self.PY_TYPES[type_field])(index) if type_field in self.TYPES_DATE: value = _struct_to_date(value) @@ -3562,10 +3846,11 @@ class LOBase(object): self._con.getTables().refresh() return - def initialize(self, database_proxy, tables): + def initialize(self, database_proxy, tables=[]): db = FirebirdDatabase(self) database_proxy.initialize(db) - db.create_tables(tables) + if tables: + db.create_tables(tables) return def _validate_sql(self, sql, params): @@ -3676,6 +3961,7 @@ class LODocs(object): return doc def __len__(self): + # ~ len(self._desktop.Components) for i, _ in enumerate(self._desktop.Components): pass return i + 1 @@ -4445,6 +4731,13 @@ class UnoText(UnoBaseObject): def value(self, value): self.model.Text = value + @property + def echochar(self): + return chr(self.model.EchoChar) + @echochar.setter + def echochar(self, value): + self.model.EchoChar = ord(value[0]) + def validate(self): return @@ -5285,6 +5578,11 @@ class LODialog(object): self.obj.dispose() return value + def set_values(self, data): + for k, v in data.items(): + self._controls[k].value = v + return + class LOSheets(object): @@ -5832,6 +6130,7 @@ _CB = ClipBoard class Paths(object): FILE_PICKER = 'com.sun.star.ui.dialogs.FilePicker' + FOLDER_PICKER = 'com.sun.star.ui.dialogs.FolderPicker' def __init__(self, path=''): if path.startswith('file://'): @@ -5941,7 +6240,7 @@ class Paths(object): @classmethod def get_dir(cls, init_dir=''): - folder_picker = create_instance(cls.FILE_PICKER) + folder_picker = create_instance(cls.FOLDER_PICKER) if not init_dir: init_dir = cls.documents init_dir = cls.to_url(init_dir) @@ -5950,7 +6249,7 @@ class Paths(object): path = '' if folder_picker.execute(): - path = cls.to_system(folder_picker.getDisplayDirectory()) + path = cls.to_system(folder_picker.getDirectory()) return path @classmethod @@ -6219,6 +6518,50 @@ class Paths(object): _P = Paths +class Dates(object): + + @classmethod + def date(cls, year, month, day): + d = datetime.date(year, month, day) + return d + + +class SpellChecker(object): + + def __init__(self): + service = 'com.sun.star.linguistic2.SpellChecker' + self._spellchecker = create_instance(service, True) + self._locale = LOCALE + + @property + def locale(self): + slocal = f'{self._locale.Language}-{self._locale.Country}' + return slocale + @locale.setter + def locale(self, value): + lang = value.split('-') + self._locale = Locale(lang[0], lang[1], '') + + def is_valid(self, word): + result = self._spellchecker.isValid(word, self._locale, ()) + return result + + def spell(self, word): + result = self._spellchecker.spell(word, self._locale, ()) + if result: + result = result.getAlternatives() + if not isinstance(result, tuple): + result = () + return result + + +def spell(word, locale=''): + sc = SpellChecker() + if locale: + sc.locale = locale + return sc.spell(word) + + def __getattr__(name): if name == 'active': return LODocs().active @@ -6244,6 +6587,8 @@ def __getattr__(name): return LOShortCuts() if name == 'clipboard': return ClipBoard + if name == 'dates': + return Dates raise AttributeError(f"module '{__name__}' has no attribute '{name}'") @@ -6330,48 +6675,6 @@ def get_fonts(): return device.FontDescriptors -# ~ From request -# ~ https://github.com/psf/requests/blob/master/requests/structures.py#L15 -class CaseInsensitiveDict(MutableMapping): - - def __init__(self, data=None, **kwargs): - self._store = OrderedDict() - if data is None: - data = {} - self.update(data, **kwargs) - - def __setitem__(self, key, value): - # Use the lowercased key for lookups, but store the actual - # key alongside the value. - self._store[key.lower()] = (key, value) - - def __getitem__(self, key): - return self._store[key.lower()][1] - - def __delitem__(self, key): - del self._store[key.lower()] - - def __iter__(self): - return (casedkey for casedkey, mappedvalue in self._store.values()) - - def __len__(self): - return len(self._store) - - def lower_items(self): - """Like iteritems(), but with all lowercase keys.""" - values = ( - (lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items() - ) - return values - - # Copy is required - def copy(self): - return CaseInsensitiveDict(self._store.values()) - - def __repr__(self): - return str(dict(self.items())) - - # ~ https://en.wikipedia.org/wiki/Web_colors def get_color(value): COLORS = { diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py index 5df7702..16b0069 100644 --- a/source/pythonpath/main.py +++ b/source/pythonpath/main.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import os import easymacro as app @@ -9,8 +10,9 @@ _ = None TITLE = 'ZAZ-PIP' URL_PIP = 'https://bootstrap.pypa.io/get-pip.py' +URL_TEST = 'http://duckduckgo.com' PIP = 'pip' -URL_GIT = 'https://git.elmau.net/elmau' +URL_GIT = 'https://git.cuates.net/elmau/zaz-pip' ICON_OK = 'ok.svg' ICON_QUESTION = 'question.svg' @@ -31,6 +33,17 @@ PACKAGES = { def open_dialog_pip(): dialog = _create_dialog() + + proxy = app.get_config('proxy', {}, TITLE) + if proxy: + app.debug(proxy) + HOST = proxy['txt_proxy_host'] + PORT = proxy['txt_proxy_port'] + USER = proxy['txt_proxy_user'] + PASS = proxy['txt_proxy_pass'] + os.environ['http_proxy'] = f'http://{USER}:{PASS}@{HOST}:{PORT}' + os.environ['https_proxy'] = f'http://{USER}:{PASS}@{HOST}:{PORT}' + dialog.open() return @@ -298,6 +311,49 @@ class Controllers(object): self._install(path=path) return + def cmd_proxy_action(self, event): + dialog = _create_dialog_proxy() + data = app.get_config('proxy', {}, TITLE) + dialog.set_values(data) + dialog.open() + return + + +class ControllersProxy(object): + + def __init__(self, dialog): + self.d = dialog + + def cmd_proxy_save_action(self, event): + data = dict( + txt_proxy_host = self.d.txt_proxy_host.value, + txt_proxy_port = self.d.txt_proxy_port.value, + txt_proxy_user = self.d.txt_proxy_user.value, + txt_proxy_pass = self.d.txt_proxy_pass.value, + ) + msg = _('Are you shure save data?') + if not app.question(msg): + return + + app.set_config('proxy', data, TITLE) + + msg = _('Save data sucesfully') + app.msgbox(msg) + self.d.close() + return + + def cmd_proxy_delete_action(self, event): + msg = _("Are you shure delete data?") + if not app.question(msg): + return + + app.set_config('proxy', {}, TITLE) + + msg = _('Delete data sucesfully') + app.msgbox(msg) + self.d.close() + return + def _create_dialog(): BUTTON_WH = 20 @@ -408,6 +464,17 @@ def _create_dialog(): dialog.add_control(args) dialog.center(dialog.link_proyect, y=-5) + args = { + 'Type': 'Button', + 'Name': 'cmd_proxy', + 'Width': 15, + 'Height': 15, + 'Step': 10, + 'ImageURL': 'proxy.svg', + 'FocusOnClick': False, + } + dialog.add_control(args) + args = { 'Type': 'Listbox', 'Name': 'lst_log', @@ -534,6 +601,10 @@ def _create_dialog(): } dialog.add_control(args) + dialog.cmd_proxy.move(dialog.link_proyect, x=5, y=0) + controls = (dialog.link_proyect, dialog.cmd_proxy) + dialog.center(controls) + controls = (dialog.cmd_home, dialog.cmd_search, dialog.cmd_install, dialog.cmd_uninstall, dialog.cmd_shell) dialog.lbl_package.move(dialog.cmd_home) @@ -544,12 +615,150 @@ def _create_dialog(): dialog.lst_package.center() dialog.txt_search.center() dialog.center(controls) - dialog.center((dialog.txt_search, dialog.cmd_explore)) + controls = (dialog.txt_search, dialog.cmd_explore) + dialog.center(controls) dialog.step = 10 - dialog.cmd_install_pip.visible = not exists_pip dialog.cmd_admin_pip.visible = exists_pip dialog.lst_log.visible = False return dialog + + +def _create_dialog_proxy(): + args= { + 'Name': 'dlg_proxy', + 'Title': 'Zaz-Pip - Config Proxy', + 'Width': 150, + 'Height': 100, + } + dialog = app.create_dialog(args) + dialog.id = ID_EXTENSION + dialog.events = ControllersProxy + + args = { + 'Type': 'Label', + 'Name': 'lbl_proxy_host', + 'Label': 'Host: ', + 'Width': 40, + 'Height': 12, + 'Border': 1, + 'Align': 2, + 'VerticalAlign': 1, + 'X': 5, + 'Y': 5, + } + dialog.add_control(args) + + args = { + 'Type': 'Text', + 'Name': 'txt_proxy_host', + 'Width': 95, + 'Height': 12, + 'Border': 0, + } + dialog.add_control(args) + + args = { + 'Type': 'Label', + 'Name': 'lbl_proxy_port', + 'Label': 'Port: ', + 'Width': 40, + 'Height': 12, + 'Border': 1, + 'Align': 2, + 'VerticalAlign': 1, + } + dialog.add_control(args) + + args = { + 'Type': 'Text', + 'Name': 'txt_proxy_port', + 'Width': 95, + 'Height': 12, + 'Border': 0, + } + dialog.add_control(args) + + args = { + 'Type': 'Label', + 'Name': 'lbl_proxy_user', + 'Label': 'User: ', + 'Width': 40, + 'Height': 12, + 'Border': 1, + 'Align': 2, + 'VerticalAlign': 1, + } + dialog.add_control(args) + + args = { + 'Type': 'Text', + 'Name': 'txt_proxy_user', + 'Width': 95, + 'Height': 12, + 'Border': 0, + } + dialog.add_control(args) + + args = { + 'Type': 'Label', + 'Name': 'lbl_proxy_pass', + 'Label': 'Password: ', + 'Width': 40, + 'Height': 12, + 'Border': 1, + 'Align': 2, + 'VerticalAlign': 1, + } + dialog.add_control(args) + + args = { + 'Type': 'Text', + 'Name': 'txt_proxy_pass', + 'Width': 95, + 'Height': 12, + 'Border': 0, + } + txt = dialog.add_control(args) + txt.echochar = '*' + + args = { + 'Type': 'Button', + 'Name': 'cmd_proxy_save', + 'Label': _('Save'), + 'Width': 50, + 'Height': 15, + 'ImageURL': 'save.svg', + 'ImagePosition': 1, + 'FocusOnClick': False, + } + dialog.add_control(args) + + args = { + 'Type': 'Button', + 'Name': 'cmd_proxy_delete', + 'Label': _('Delete'), + 'Width': 50, + 'Height': 15, + 'ImageURL': 'delete.svg', + 'ImagePosition': 1, + 'FocusOnClick': False, + } + dialog.add_control(args) + + dialog.txt_proxy_host.move(dialog.lbl_proxy_host, x=5, y=0) + dialog.lbl_proxy_port.move(dialog.lbl_proxy_host) + dialog.txt_proxy_port.move(dialog.lbl_proxy_port, x=5, y=0) + dialog.lbl_proxy_user.move(dialog.lbl_proxy_port) + dialog.txt_proxy_user.move(dialog.lbl_proxy_user, x=5, y=0) + dialog.lbl_proxy_pass.move(dialog.lbl_proxy_user) + dialog.txt_proxy_pass.move(dialog.lbl_proxy_pass, x=5, y=0) + + dialog.cmd_proxy_save.move(dialog.lbl_proxy_pass, y=10) + dialog.cmd_proxy_delete.move(dialog.lbl_proxy_pass, y=10) + controls = (dialog.cmd_proxy_save, dialog.cmd_proxy_delete) + dialog.center(controls) + + return dialog