diff --git a/.gitignore b/.gitignore index 05f63a9..75753ff 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ source/source/ .env/ virtual/ +source/VERSION + diff --git a/README.md b/README.md index f45ef3b..96bd964 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ For Python 3.6+ This extension have a cost of maintenance of 1 euro every year. -BCH: `1RPLWHJW34p7pMQV1ft4x7eWhAYw69Dsb` +BCH: `qztd3l00xle5tffdqvh2snvadkuau2ml0uqm4n875d` -BTC: `3Fe4JuADrAK8Qs7GDAxbSXR8E54avwZJLW` +BTC: `3FhiXcXmAesmQzrNEngjHFnvaJRhU1AGWV` PayPal :( donate ATT elmau DOT net diff --git a/VERSION b/VERSION index 78bc1ab..d9df1bb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.10.0 +0.11.0 diff --git a/source/conf.py.example b/source/conf.py.example index 892ab8c..7e481f8 100644 --- a/source/conf.py.example +++ b/source/conf.py.example @@ -633,5 +633,8 @@ DATA = { } +with open('VERSION', 'w') as f: + f.write(VERSION) + # ~ LICENSE_ACCEPT_BY = 'user' # or admin # ~ LICENSE_SUPPRESS_ON_UPDATE = True diff --git a/source/easymacro.py b/source/easymacro.py index 4762bb3..5a75695 100644 --- a/source/easymacro.py +++ b/source/easymacro.py @@ -34,6 +34,7 @@ import shlex import shutil import socket import subprocess +import ssl import sys import tempfile import threading @@ -41,13 +42,9 @@ import time import traceback import zipfile -# ~ from collections import OrderedDict -# ~ from collections.abc import MutableMapping from functools import wraps -from operator import itemgetter from pathlib import Path, PurePath from pprint import pprint -from enum import Enum, IntEnum from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError from string import Template @@ -96,15 +93,6 @@ from com.sun.star.awt.grid import XGridDataListener from com.sun.star.awt.grid import XGridSelectionListener -class FontSlant(IntEnum): - NONE = 0 - OBLIQUE = 1 - ITALIC = 2 - DONTKNOW = 3 - REVERSE_OBLIQUE = 4 - REVERSE_ITALIC = 5 - - try: from fernet import Fernet, InvalidToken except ImportError: @@ -124,7 +112,6 @@ KEY = { SEPARATION = 5 - MSG_LANG = { 'es': { 'OK': 'Aceptar', @@ -135,35 +122,36 @@ MSG_LANG = { } } - OS = platform.system() USER = getpass.getuser() PC = platform.node() DESKTOP = os.environ.get('DESKTOP_SESSION', '') INFO_DEBUG = '{}\n\n{}\n\n{}'.format(sys.version, platform.platform(), '\n'.join(sys.path)) - IS_WIN = OS == 'Windows' 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' OBJ_RANGE = 'ScCellRangeObj' OBJ_RANGES = 'ScCellRangesObj' OBJ_TYPE_RANGES = (OBJ_CELL, OBJ_RANGE, OBJ_RANGES) +TEXT_RANGE = 'SwXTextRange' +TEXT_RANGES = 'SwXTextRanges' +TEXT_TYPE_RANGES = (TEXT_RANGE, TEXT_RANGES) + TYPE_DOC = { 'calc': 'com.sun.star.sheet.SpreadsheetDocument', 'writer': 'com.sun.star.text.TextDocument', 'impress': 'com.sun.star.presentation.PresentationDocument', 'draw': 'com.sun.star.drawing.DrawingDocument', - # ~ 'base': 'com.sun.star.sdb.OfficeDatabaseDocument', 'base': 'com.sun.star.sdb.DocumentDataSource', 'math': 'com.sun.star.formula.FormulaProperties', 'basic': 'com.sun.star.script.BasicIDE', @@ -202,19 +190,16 @@ MENUS_WRITER = { 'windows': '.uno:WindowList', 'help': '.uno:HelpMenu', } - MENUS_APP = { 'main': MENUS_MAIN, 'calc': MENUS_CALC, 'writer': MENUS_WRITER, } - EXT = { 'pdf': 'pdf', } - FILE_NAME_DEBUG = 'debug.odt' FILE_NAME_CONFIG = 'zaz-{}.json' LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' @@ -284,6 +269,12 @@ def mri(obj): return +def inspect(obj): + zaz = create_instance('net.elmau.zaz.inspect') + zaz.inspect(obj) + return + + def catch_exception(f): @wraps(f) def func(*args, **kwargs): @@ -459,12 +450,14 @@ def call_dispatch(url, args=()): return -def get_temp_file(): +def get_temp_file(only_name=False): delete = True if IS_WIN: delete = False - return tempfile.NamedTemporaryFile(delete=delete) - + tmp = tempfile.NamedTemporaryFile(delete=delete) + if only_name: + tmp = tmp.name + return tmp def _path_url(path): if path.startswith('file://'): @@ -506,6 +499,11 @@ def dict_to_property(values, uno_any=False): return ps +def dict_to_named(values): + ps = tuple([NamedValue(n, v) for n, v in values.items()]) + return ps + + def property_to_dict(values): d = {i.Name: i.Value for i in values} return d @@ -1008,7 +1006,9 @@ class LOCalc(LODocument): def __getitem__(self, index): if isinstance(index, str): - index = [s.Name for s in self._sheets if s.CodeName == index][0] or index + code_name = [s.Name for s in self._sheets if s.CodeName == index] + if code_name: + index = code_name[0] return LOCalcSheet(self._sheets[index], self) def __setitem__(self, key, value): @@ -1376,7 +1376,11 @@ class LOWriter(LODocument): @property def selection(self): sel = self.obj.getCurrentSelection() - return LOTextRange(sel[0]) + if sel.ImplementationName == TEXT_RANGES: + return LOTextRange(sel[0]) + elif sel.ImplementationName == TEXT_RANGE: + return LOTextRange(sel) + return sel def write(self, data, cursor=None): cursor = cursor or self.selection.cursor.getEnd() @@ -1653,7 +1657,7 @@ class LODrawImpress(LODocument): def insert_image(self, path, **kwargs): w = kwargs.get('width', 3000) - h = kwargs.get('Height', 1000) + h = kwargs.get('Height', 3000) x = kwargs.get('X', 1000) y = kwargs.get('Y', 1000) @@ -1710,6 +1714,9 @@ class LOCellRange(object): def __getitem__(self, index): return LOCellRange(self.obj[index], self.doc) + def __contains__(self, item): + return item.in_range(self) + def _init_values(self): self._type_obj = self.obj.ImplementationName self._type_content = EMPTY @@ -1759,7 +1766,7 @@ class LOCellRange(object): self.obj.setFormula(data) else: self.obj.setString(data) - elif isinstance(data, (int, float)): + elif isinstance(data, (int, float, bool)): self.obj.setValue(data) elif isinstance(data, datetime.datetime): d = data.toordinal() @@ -1777,8 +1784,6 @@ class LOCellRange(object): return self.obj.getDataArray() @data.setter def data(self, values): - if isinstance(values, list): - values = tuple(values) self.obj.setDataArray(values) @property @@ -1786,8 +1791,6 @@ class LOCellRange(object): return self.obj.getFormulaArray() @formula.setter def formula(self, values): - if isinstance(values, list): - values = tuple(values) self.obj.setFormulaArray(values) @property @@ -1812,13 +1815,19 @@ class LOCellRange(object): cursor.collapseToSize(cols, rows) return LOCellRange(self.sheet[cursor.AbsoluteName].obj, self.doc) - def copy_from(self, rango): + def copy_from(self, rango, formula=False): data = rango if isinstance(rango, LOCellRange): - data = rango.data + if formula: + data = rango.formula + else: + data = rango.data rows = len(data) cols = len(data[0]) - self.to_size(rows, cols).data = data + if formula: + self.to_size(rows, cols).formula = data + else: + self.to_size(rows, cols).data = data return def copy_to(self, cell, formula=False): @@ -1833,6 +1842,33 @@ class LOCellRange(object): self.sheet.obj.copyRange(self.address, source.range_address) return + def transpose(self, formula=False): + data = self.data + if formula: + data = self.formula + data = tuple(zip(*data)) + self.clear(1023) + self[0,0].copy_from(data, formula=formula) + return + + def transpose2(self): + # ~ 'Flags': 'A', + # ~ 'FormulaCommand': 0, + # ~ 'SkipEmptyCells': False, + # ~ 'AsLink': False, + # ~ 'MoveMode': 4, + args = { + 'Transpose': True, + } + args = dict_to_property(args) + self.select() + copy() + self.clear(1023) + self[0,0].select() + call_dispatch('.uno:InsertContents', args) + set_clipboard('') + return + def offset(self, row=1, col=0): ra = self.obj.getRangeAddress() col = ra.EndColumn + col @@ -1935,6 +1971,10 @@ class LOCellRange(object): def auto_format(self, value): self.obj.autoFormat(value) + def auto_width(self): + self.obj.Columns.OptimalWidth = True + return + def insert_image(self, path, **kwargs): s = self.obj.Size w = kwargs.get('width', s.Width) @@ -2056,6 +2096,22 @@ class LOCellRange(object): found = self.obj.replaceAll(descriptor) return found + @property + def validation(self): + return self.obj.Validation + @validation.setter + def validation(self, values): + is_list = False + current = self.validation + for k, v in values.items(): + if k == 'Type' and v == 6: + is_list = True + if k == 'Formula1' and is_list: + if isinstance(v, (tuple, list)): + v = ';'.join(['"{}"'.format(i) for i in v]) + setattr(current, k, v) + self.obj.Validation = current + class EventsListenerBase(unohelper.Base, XEventListener): @@ -2178,7 +2234,10 @@ class EventsItem(EventsListenerBase, XItemListener): pass def itemStateChanged(self, event): - pass + event_name = '{}_item_changed'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return class EventsItemRoadmap(EventsItem): @@ -2447,23 +2506,39 @@ class UnoBaseObject(object): return self._model.Width @width.setter def width(self, value): - if hasattr(self.obj, 'PosSize'): - self._set_possize('Width', value) - else: - self._model.Width = value + self.model.Width = value + + @property + def ps_width(self): + return self._get_possize('Width') + @ps_width.setter + def ps_width(self, value): + self._set_possize('Width', value) @property def height(self): - if hasattr(self._model, 'Height'): - return self._model.Height - ps = self.obj.getPosSize() - return ps.Height + return self.model.Height @height.setter def height(self, value): - if hasattr(self.obj, 'PosSize'): - self._set_possize('Height', value) - else: - self._model.Height = value + self.model.Height = value + + @property + def ps_height(self): + return self._get_possize('Height') + @ps_height.setter + def ps_height(self, value): + self._set_possize('Height', value) + + @property + def size(self): + ps = self.obj.getPosSize() + return (ps.Width, ps.Height) + @size.setter + def size(self, value): + ps = self.obj.getPosSize() + ps.Width = value[0] + ps.Height = value[1] + self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, SIZE) @property def tag(self): @@ -2526,8 +2601,12 @@ class UnoBaseObject(object): def move(self, origin, x=0, y=5): if x: self.x = origin.x + origin.width + x + else: + self.x = origin.x if y: self.y = origin.y + origin.height + y + else: + self.y = origin.y return def possize(self, origin): @@ -2771,7 +2850,6 @@ class UnoGrid(UnoBaseObject): def sort(self, column, asc=True): self._gdm.sortByColumn(column, asc) - # ~ self._data.sort(key=itemgetter(column), reverse=not asc) self.update_row_heading() return @@ -3022,7 +3100,6 @@ def get_control_model(control): return services[control] -@catch_exception def add_listeners(events, control, name=''): listeners = { 'addActionListener': EventsButton, @@ -3891,7 +3968,6 @@ class LOWindow(object): return properties - @catch_exception def add_control(self, properties): tipo = properties.pop('Type').lower() root = properties.pop('Root', '') @@ -4207,8 +4283,12 @@ def json_loads(data): def get_path_extension(id): + path = '' pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') - path = _path_system(pip.getPackageLocation(id)) + try: + path = _path_system(pip.getPackageLocation(id)) + except Exception as e: + error(e) return path @@ -4405,23 +4485,29 @@ def popen(command, stdin=None): yield (e.errno, e.strerror) -def url_open(url, options={}, json=False): +def url_open(url, options={}, verify=True, json=False): data = '' + err = '' req = Request(url) try: - response = urlopen(req) - # ~ response.info() + if verify: + response = urlopen(req) + else: + context = ssl._create_unverified_context() + response = urlopen(req, context=context) except HTTPError as e: error(e) + err = str(e) except URLError as e: error(e.reason) + err = str(e.reason) else: if json: data = json_loads(response.read()) else: data = response.read() - return data + return data, err def run(command, wait=False): @@ -4475,7 +4561,7 @@ def _zippwd(source, target, pwd): # ~ Export ok -def zip(source, target='', mode='w', pwd=''): +def zip_files(source, target='', mode='w', pwd=''): if pwd: return _zippwd(source, target, pwd) @@ -5398,8 +5484,6 @@ class LIBOServer(object): return instance - - # ~ controls = { # ~ 'CheckBox': 'com.sun.star.awt.UnoControlCheckBoxModel', # ~ 'ComboBox': 'com.sun.star.awt.UnoControlComboBoxModel', diff --git a/source/zaz.py b/source/zaz.py index 9cad0c8..71fe021 100644 --- a/source/zaz.py +++ b/source/zaz.py @@ -53,18 +53,23 @@ class LiboXML(object): 'help': 'application/vnd.sun.star.help', 'component': 'application/vnd.sun.star.uno-components', } - NAME_SPACES = { + NS_MANIFEST = { 'manifest_version': '1.2', 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0', 'xmlns:loext': 'urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0', } + NS_DESCRIPTION = { + 'xmlns': 'http://openoffice.org/extensions/description/2006', + 'xmlns:xlink': 'http://www.w3.org/1999/xlink', + 'xmlns:d': 'http://openoffice.org/extensions/description/2006', + } def __init__(self): self._manifest = None self._paths = [] def _save_path(self, attr): - self._paths.append(attr['{{{}}}full-path'.format(self.NAME_SPACES['manifest'])]) + self._paths.append(attr['{{{}}}full-path'.format(self.NS_MANIFEST['manifest'])]) return def _clean(self, name, nodes): @@ -86,17 +91,17 @@ class LiboXML(object): def new_manifest(self, data): attr = { - 'manifest:version': self.NAME_SPACES['manifest_version'], - 'xmlns:manifest': self.NAME_SPACES['manifest'], - 'xmlns:loext': self.NAME_SPACES['xmlns:loext'], + 'manifest:version': self.NS_MANIFEST['manifest_version'], + 'xmlns:manifest': self.NS_MANIFEST['manifest'], + 'xmlns:loext': self.NS_MANIFEST['xmlns:loext'], } self._manifest = ET.Element('manifest:manifest', attr) return self.add_data_manifest(data) def parse_manifest(self, data): - ET.register_namespace('manifest', self.NAME_SPACES['manifest']) + ET.register_namespace('manifest', self.NS_MANIFEST['manifest']) self._manifest = ET.fromstring(data) - data = {'xmlns:loext': self.NAME_SPACES['xmlns:loext']} + data = {'xmlns:loext': self.NS_MANIFEST['xmlns:loext']} self._manifest.attrib.update(**data) self._clean('manifest', self._manifest) return @@ -116,6 +121,60 @@ class LiboXML(object): ET.SubElement(self._manifest, node_name, attr) return self._get_xml(self._manifest) + def new_description(self, data): + doc = ET.Element('description', self.NS_DESCRIPTION) + + key = 'identifier' + ET.SubElement(doc, key, data[key]) + + key = 'version' + ET.SubElement(doc, key, data[key]) + + key = 'display-name' + node = ET.SubElement(doc, key) + for k, v in data[key].items(): + sn = ET.SubElement(node, 'name', {'lang': k}) + sn.text = v + + node = ET.SubElement(doc, 'extension-description') + for k in data[key].keys(): + attr = { + 'lang': k, + 'xlink:href': f'description/desc_{k}.txt', + } + ET.SubElement(node, 'src', attr) + + key = 'icon' + node = ET.SubElement(doc, key) + attr = {'xlink:href': f"images/{data[key]}"} + ET.SubElement(node, 'default', attr) + + key = 'publisher' + node = ET.SubElement(doc, key) + for k, v in data[key].items(): + attr = { + 'xlink:href': v['link'], + 'lang': k, + } + sn = ET.SubElement(node, 'name', attr) + sn.text = v['text'] + + key = 'display-name' + node = ET.SubElement(doc, 'registration') + attr = { + 'accept-by': 'user', + 'suppress-on-update': 'true', + } + node = ET.SubElement(node, 'simple-license', attr) + for k in data[key].keys(): + attr = { + 'xlink:href': f"{DIRS['registration']}/license_{k}.txt", + 'lang': k + } + ET.SubElement(node, 'license-text', attr) + + return self._get_xml(doc) + def _get_xml(self, doc): xml = parseString(ET.tostring(doc, encoding='utf-8')) return xml.toprettyxml(indent=' ', encoding='utf-8').decode('utf-8') @@ -325,6 +384,13 @@ def _update_files(): data = xml.new_manifest(DATA['manifest']) _save(path, data) + path = _join(path_source, FILES['description']) + data = xml.new_description(DATA['description']) + _save(path, data) + + + + path = _join(path_source, DIRS['office']) _mkdir(path) path = _join(path_source, DIRS['office'], FILES['shortcut']) @@ -333,9 +399,6 @@ def _update_files(): path = _join(path_source, FILES['addons']) _save(path, DATA['addons']) - path = _join(path_source, FILES['description']) - _save(path, DATA['description']) - if TYPE_EXTENSION == 3: path = _join(path_source, FILES['addin']) _save(path, DATA['addin'])