From 8c659c8af9e23c0b7bf941ebae9c765241f97566 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Thu, 17 Dec 2020 17:32:21 -0600 Subject: [PATCH 01/13] Replace easymacro2 for easymacro --- .gitignore | 2 + README.md | 2 +- easymacro.py | 6616 +++++++++++++++++ easymacro2.py | 1 - files/ZAZLaTex2SVG_v0.1.0.oxt | Bin 58610 -> 64442 bytes source/ZAZLaTex2SVG.py | 2 +- .../{easymacro2.py => easymacro.py} | 1149 ++- zaz.py | 56 +- 8 files changed, 7758 insertions(+), 70 deletions(-) create mode 100644 easymacro.py delete mode 120000 easymacro2.py rename source/pythonpath/{easymacro2.py => easymacro.py} (76%) diff --git a/.gitignore b/.gitignore index 13d1490..fd613cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ # ---> Python +*.po~ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/README.md b/README.md index 3879342..b0e72f6 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Requirements: #### but, don't make the mistake of many of *thinking only in gratis software* that so much damage has done to **Free Software**. -This extension have a cost of maintenance of 5 euros every year. +This extension have a cost of maintenance of 1 euro every year. BCH: `qztd3l00xle5tffdqvh2snvadkuau2ml0uqm4n875d` diff --git a/easymacro.py b/easymacro.py new file mode 100644 index 0000000..d91c6af --- /dev/null +++ b/easymacro.py @@ -0,0 +1,6616 @@ +#!/usr/bin/env python3 + +# == Rapid Develop Macros in LibreOffice == + +# ~ This file is part of ZAZ. + +# ~ https://git.elmau.net/elmau/zaz + +# ~ ZAZ is free software: you can redistribute it and/or modify +# ~ it under the terms of the GNU General Public License as published by +# ~ the Free Software Foundation, either version 3 of the License, or +# ~ (at your option) any later version. + +# ~ ZAZ is distributed in the hope that it will be useful, +# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of +# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# ~ GNU General Public License for more details. + +# ~ You should have received a copy of the GNU General Public License +# ~ along with ZAZ. If not, see . + +import base64 +import csv +import datetime +import getpass +import gettext +import hashlib +import json +import logging +import os +import platform +import re +import shlex +import shutil +import socket +import ssl +import subprocess +import sys +import tempfile +import threading +import time +import traceback +import zipfile + +from collections import OrderedDict +from collections.abc import MutableMapping +from decimal import Decimal +from enum import IntEnum +from functools import wraps +from pathlib import Path +from pprint import pprint +from string import Template +from typing import Any, Union +from urllib.request import Request, urlopen +from urllib.error import URLError, HTTPError + +import imaplib +import smtplib +from smtplib import SMTPException, SMTPAuthenticationError +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email.mime.text import MIMEText +from email.utils import formatdate +from email import encoders +import mailbox + +import uno +import unohelper +from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS +from com.sun.star.awt.MessageBoxResults import YES +from com.sun.star.awt import Rectangle, Size, Point +from com.sun.star.awt.PosSize import POSSIZE, SIZE +from com.sun.star.awt import Key, KeyModifier, KeyEvent +from com.sun.star.container import NoSuchElementException +from com.sun.star.datatransfer import XTransferable, DataFlavor + +from com.sun.star.beans import PropertyValue, NamedValue +from com.sun.star.sheet import TableFilterField +from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA +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 XEventListener +from com.sun.star.awt import XMenuListener +from com.sun.star.awt import XMouseListener +from com.sun.star.awt import XMouseMotionListener +from com.sun.star.awt import XFocusListener +from com.sun.star.awt import XKeyListener +from com.sun.star.awt import XItemListener +from com.sun.star.awt import XTabListener +from com.sun.star.awt import XWindowListener +from com.sun.star.awt import XTopWindowListener +from com.sun.star.awt.grid import XGridDataListener +from com.sun.star.awt.grid import XGridSelectionListener +from com.sun.star.script import ScriptEventDescriptor + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1FontUnderline.html +from com.sun.star.awt import FontUnderline +from com.sun.star.style.VerticalAlignment import TOP, MIDDLE, BOTTOM + +from com.sun.star.view.SelectionType import SINGLE, MULTI, RANGE + +from com.sun.star.sdb.CommandType import TABLE, QUERY, COMMAND + +try: + from peewee import Database, DateTimeField, DateField, TimeField, \ + __exception_wrapper__ +except ImportError as e: + Database = DateField = TimeField = DateTimeField = object + print('You need install peewee, only if you will develop with Base') + + +LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' +LOG_DATE = '%d/%m/%Y %H:%M:%S' +logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m') +logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m') +logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m') +logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE) +log = logging.getLogger(__name__) + + +# ~ You can get custom salt +# ~ codecs.encode(os.urandom(16), 'hex') +# ~ but, not modify this file, modify in import file +SALT = b'c9548699d4e432dfd2b46adddafbb06d' + +TIMEOUT = 10 +LOG_NAME = 'ZAZ' +FILE_NAME_CONFIG = 'zaz-{}.json' + +LEFT = 0 +CENTER = 1 +RIGHT = 2 + +CALC = 'calc' +WRITER = 'writer' +DRAW = 'draw' +IMPRESS = 'impress' +BASE = 'base' +MATH = 'math' +BASIC = 'basic' +MAIN = 'main' +TYPE_DOC = { + CALC: 'com.sun.star.sheet.SpreadsheetDocument', + WRITER: 'com.sun.star.text.TextDocument', + DRAW: 'com.sun.star.drawing.DrawingDocument', + IMPRESS: 'com.sun.star.presentation.PresentationDocument', + BASE: 'com.sun.star.sdb.DocumentDataSource', + MATH: 'com.sun.star.formula.FormulaProperties', + BASIC: 'com.sun.star.script.BasicIDE', + MAIN: 'com.sun.star.frame.StartModule', +} + +OBJ_CELL = 'ScCellObj' +OBJ_RANGE = 'ScCellRangeObj' +OBJ_RANGES = 'ScCellRangesObj' +TYPE_RANGES = (OBJ_CELL, OBJ_RANGE, OBJ_RANGES) + +OBJ_SHAPES = 'com.sun.star.drawing.SvxShapeCollection' +OBJ_SHAPE = 'com.sun.star.comp.sc.ScShapeObj' +OBJ_GRAPHIC = 'SwXTextGraphicObject' + +OBJ_TEXTS = 'SwXTextRanges' +OBJ_TEXT = 'SwXTextRange' + + +# ~ from com.sun.star.sheet.FilterOperator import EMPTY, NO_EMPTY, EQUAL, NOT_EQUAL +class FilterOperator(IntEnum): + EMPTY = 0 + NO_EMPTY = 1 + EQUAL = 2 + NOT_EQUAL = 3 + +# ~ https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1awt_1_1UnoControlEditModel.html#a54d3ff280d892218d71e667f81ce99d4 +class Border(IntEnum): + NO_BORDER = 0 + BORDER = 1 + SIMPLE = 2 + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet.html#aa5aa6dbecaeb5e18a476b0a58279c57a +class ValidationType(): + from com.sun.star.sheet.ValidationType \ + import ANY, WHOLE, DECIMAL, DATE, TIME, TEXT_LEN, LIST, CUSTOM +VT = ValidationType + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet.html#aecf58149730f4c8c5c18c70f3c7c5db7 +class ValidationAlertStyle(): + from com.sun.star.sheet.ValidationAlertStyle \ + import STOP, WARNING, INFO, MACRO +VAS = ValidationAlertStyle + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1ConditionOperator2.html +class ConditionOperator(): + from com.sun.star.sheet.ConditionOperator2 \ + import NONE, EQUAL, NOT_EQUAL, GREATER, GREATER_EQUAL, LESS, \ + LESS_EQUAL, BETWEEN, NOT_BETWEEN, FORMULA, DUPLICATE, NOT_DUPLICATE +CO = ConditionOperator + + +OS = platform.system() +IS_WIN = OS == 'Windows' +IS_MAC = OS == 'Darwin' +USER = getpass.getuser() +PC = platform.node() +DESKTOP = os.environ.get('DESKTOP_SESSION', '') +INFO_DEBUG = f"{sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path) + +PYTHON = 'python' +if IS_WIN: + PYTHON = 'python.exe' + +_MACROS = {} +_start = 0 + +SECONDS_DAY = 60 * 60 * 24 +DIR = { + 'images': 'images', + 'locales': 'locales', +} + +KEY = { + 'enter': 1280, +} + +MODIFIERS = { + 'shift': KeyModifier.SHIFT, + 'ctrl': KeyModifier.MOD1, + 'alt': KeyModifier.MOD2, + 'ctrlmac': KeyModifier.MOD3, +} + +# ~ Menus +NODE_MENUBAR = 'private:resource/menubar/menubar' +MENUS = { + 'file': '.uno:PickList', + 'tools': '.uno:ToolsMenu', + 'help': '.uno:HelpMenu', + 'windows': '.uno:WindowList', + 'edit': '.uno:EditMenu', + 'view': '.uno:ViewMenu', + 'insert': '.uno:InsertMenu', + 'format': '.uno:FormatMenu', + 'styles': '.uno:FormatStylesMenu', + 'sheet': '.uno:SheetMenu', + 'data': '.uno:DataMenu', + 'table': '.uno:TableMenu', + 'form': '.uno:FormatFormMenu', + 'page': '.uno:PageMenu', + 'shape': '.uno:ShapeMenu', + 'slide': '.uno:SlideMenu', + 'show': '.uno:SlideShowMenu', +} + +DEFAULT_MIME_TYPE = 'png' +MIME_TYPE = { + 'png': 'image/png', + 'jpg': 'image/jpeg', +} + +MESSAGES = { + 'es': { + 'OK': 'Aceptar', + 'Cancel': 'Cancelar', + 'Select path': 'Seleccionar ruta', + 'Select directory': 'Seleccionar directorio', + 'Select file': 'Seleccionar archivo', + 'Incorrect user or password': 'Nombre de usuario o contraseña inválidos', + 'Allow less secure apps in GMail': 'Activa: Permitir aplicaciones menos segura en GMail', + } +} + + +CTX = uno.getComponentContext() +SM = CTX.getServiceManager() + + +def create_instance(name: str, with_context: bool=False, args: Any=None) -> Any: + if with_context: + instance = SM.createInstanceWithContext(name, CTX) + elif args: + instance = SM.createInstanceWithArguments(name, (args,)) + else: + instance = SM.createInstance(name) + return instance + + +def get_app_config(node_name, key=''): + name = 'com.sun.star.configuration.ConfigurationProvider' + service = 'com.sun.star.configuration.ConfigurationAccess' + cp = create_instance(name, True) + node = PropertyValue(Name='nodepath', Value=node_name) + try: + ca = cp.createInstanceWithArguments(service, (node,)) + if ca and not key: + return ca + if ca and ca.hasByName(key): + return ca.getPropertyValue(key) + except Exception as e: + error(e) + return '' + + +LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale') +LANG = LANGUAGE.split('-')[0] +NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName') +VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion') + +INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{INFO_DEBUG}" + +node = '/org.openoffice.Office.Calc/Calculate/Other/Date' +y = get_app_config(node, 'YY') +m = get_app_config(node, 'MM') +d = get_app_config(node, 'DD') +DATE_OFFSET = datetime.date(y, m, d).toordinal() + + +def error(info): + log.error(info) + return + + +def debug(*args): + data = [str(a) for a in args] + log.debug('\t'.join(data)) + return + + +def info(*args): + data = [str(a) for a in args] + log.info('\t'.join(data)) + return + + +def save_log(path, data): + with open(path, 'a') as f: + f.write(f'{str(now())[:19]} -{LOG_NAME}- ') + pprint(data, stream=f) + return + + +def catch_exception(f): + @wraps(f) + def func(*args, **kwargs): + try: + return f(*args, **kwargs) + except Exception as e: + name = f.__name__ + if IS_WIN: + msgbox(traceback.format_exc()) + log.error(name, exc_info=True) + return func + + +def inspect(obj: Any) -> None: + zaz = create_instance('net.elmau.zaz.inspect') + if hasattr(obj, 'obj'): + obj = obj.obj + zaz.inspect(obj) + return + + +def mri(obj): + m = create_instance('mytools.Mri') + if m is None: + msg = 'Extension MRI not found' + error(msg) + return + + m.inspect(obj) + return + + +def run_in_thread(fn): + def run(*k, **kw): + t = threading.Thread(target=fn, args=k, kwargs=kw) + t.start() + return t + return run + + +def now(only_time=False): + now = datetime.datetime.now() + if only_time: + now = now.time() + return now + + +def today(): + return datetime.date.today() + + +def _(msg): + if LANG == 'en': + return msg + + if not LANG in MESSAGES: + return msg + + return MESSAGES[LANG][msg] + + +def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infobox'): + """ Create message box + type_msg: infobox, warningbox, errorbox, querybox, messbox + http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMessageBoxFactory.html + """ + toolkit = create_instance('com.sun.star.awt.Toolkit') + parent = toolkit.getDesktopWindow() + box = toolkit.createMessageBox(parent, type_msg, buttons, title, str(message)) + return box.execute() + + +def question(message, title=TITLE): + result = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, 'querybox') + return result == YES + + +def warning(message, title=TITLE): + return msgbox(message, title, type_msg='warningbox') + + +def errorbox(message, title=TITLE): + return msgbox(message, title, type_msg='errorbox') + + +def get_type_doc(obj: Any) -> str: + for k, v in TYPE_DOC.items(): + if obj.supportsService(v): + return k + return '' + + +def _get_class_doc(obj: Any) -> Any: + classes = { + CALC: LOCalc, + WRITER: LOWriter, + DRAW: LODraw, + IMPRESS: LOImpress, + BASE: LOBase, + MATH: LOMath, + BASIC: LOBasic, + } + type_doc = get_type_doc(obj) + return classes[type_doc](obj) + + +def dict_to_property(values: dict, uno_any: bool=False): + ps = tuple([PropertyValue(Name=n, Value=v) for n, v in values.items()]) + if uno_any: + ps = uno.Any('[]com.sun.star.beans.PropertyValue', ps) + return ps + + +def _array_to_dict(values): + d = {v[0]: v[1] for v in values} + return d + + +def _property_to_dict(values): + d = {v.Name: v.Value for v in values} + return d + + +def json_dumps(data): + return json.dumps(data, indent=4, sort_keys=True) + + +def json_loads(data): + return json.loads(data) + + +def data_to_dict(data): + if isinstance(data, tuple) and isinstance(data[0], tuple): + return _array_to_dict(data) + + if isinstance(data, tuple) and isinstance(data[0], (PropertyValue, NamedValue)): + return _property_to_dict(data) + return {} + + +def _get_dispatch() -> Any: + return create_instance('com.sun.star.frame.DispatchHelper') + + +# ~ https://wiki.documentfoundation.org/Development/DispatchCommands +# ~ Used only if not exists in API +def call_dispatch(frame: Any, url: str, args: dict={}) -> None: + dispatch = _get_dispatch() + opt = dict_to_property(args) + dispatch.executeDispatch(frame, url, '', 0, opt) + return + + +def get_desktop(): + return create_instance('com.sun.star.frame.Desktop', True) + + +def _date_to_struct(value): + if isinstance(value, datetime.datetime): + d = DateTime() + d.Year = value.year + d.Month = value.month + d.Day = value.day + d.Hours = value.hour + d.Minutes = value.minute + d.Seconds = value.second + elif isinstance(value, datetime.date): + d = Date() + d.Day = value.day + d.Month = value.month + d.Year = value.year + elif isinstance(value, datetime.time): + d = Time() + d.Hours = value.hour + d.Minutes = value.minute + d.Seconds = value.second + return d + + +def _struct_to_date(value): + d = None + if isinstance(value, Time): + d = datetime.time(value.Hours, value.Minutes, value.Seconds) + elif isinstance(value, Date): + if value != Date(): + d = datetime.date(value.Year, value.Month, value.Day) + elif isinstance(value, DateTime): + if value.Year > 0: + d = datetime.datetime( + value.Year, value.Month, value.Day, + value.Hours, value.Minutes, value.Seconds) + return d + + +def _get_url_script(args): + library = args['library'] + module = '.' + name = args['name'] + language = args.get('language', 'Python') + location = args.get('location', 'user') + + if language == 'Python': + module = '.py$' + elif language == 'Basic': + module = f".{module}." + if location == 'user': + location = 'application' + + url = 'vnd.sun.star.script' + url = f'{url}:{library}{module}{name}?language={language}&location={location}' + return url + + +def _call_macro(args): + #~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification + + url = _get_url_script(args) + args = args.get('args', ()) + + service = 'com.sun.star.script.provider.MasterScriptProviderFactory' + factory = create_instance(service) + script = factory.createScriptProvider('').getScript(url) + result = script.invoke(args, None, None)[0] + + return result + + +def call_macro(args, in_thread=False): + result = None + if in_thread: + t = threading.Thread(target=_call_macro, args=(args,)) + t.start() + else: + result = _call_macro(args) + return result + + +def run(command, capture=False, split=True): + if not split: + return subprocess.check_output(command, shell=True).decode() + + cmd = shlex.split(command) + result = subprocess.run(cmd, capture_output=capture, text=True, shell=IS_WIN) + if capture: + result = result.stdout + else: + result = result.returncode + return result + + +def popen(command): + 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 sleep(seconds): + time.sleep(seconds) + return + + +class TimerThread(threading.Thread): + + def __init__(self, event, seconds, macro): + threading.Thread.__init__(self) + self.stopped = event + self.seconds = seconds + self.macro = macro + + def run(self): + info('Timer started... {}'.format(self.macro['name'])) + while not self.stopped.wait(self.seconds): + _call_macro(self.macro) + info('Timer stopped... {}'.format(self.macro['name'])) + return + + +def start_timer(name, seconds, macro): + global _MACROS + _MACROS[name] = threading.Event() + thread = TimerThread(_MACROS[name], seconds, macro) + thread.start() + return + + +def stop_timer(name): + global _MACROS + _MACROS[name].set() + del _MACROS[name] + return + + +def install_locales(path, domain='base', dir_locales=DIR['locales']): + path_locales = _P.join(_P(path).path, 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 _ + + +def _export_image(obj, args): + name = 'com.sun.star.drawing.GraphicExportFilter' + exporter = create_instance(name) + path = _P.to_system(args['URL']) + args = dict_to_property(args) + exporter.setSourceDocument(obj) + exporter.filter(args) + return _P.exists(path) + + +def sha256(data): + result = hashlib.sha256(data.encode()).hexdigest() + return result + +def sha512(data): + result = hashlib.sha512(data.encode()).hexdigest() + return result + + +def get_config(key='', default={}, prefix='conf'): + name_file = FILE_NAME_CONFIG.format(prefix) + values = None + path = _P.join(_P.config('UserConfig'), name_file) + if not _P.exists(path): + return default + + values = _P.from_json(path) + if key: + values = values.get(key, default) + + return values + + +def set_config(key, value, prefix='conf'): + name_file = FILE_NAME_CONFIG.format(prefix) + path = _P.join(_P.config('UserConfig'), name_file) + values = get_config(default={}, prefix=prefix) + values[key] = value + result = _P.to_json(path, values) + return result + + +def start(): + global _start + _start = now() + info(_start) + return + + +def end(get_seconds=False): + global _start + e = now() + td = e - _start + result = str(td) + if get_seconds: + result = td.total_seconds() + return result + + +def get_epoch(): + n = now() + return int(time.mktime(n.timetuple())) + + +def render(template, data): + s = Template(template) + return s.safe_substitute(**data) + + +def get_size_screen(): + if IS_WIN: + user32 = ctypes.windll.user32 + res = f'{user32.GetSystemMetrics(0)}x{user32.GetSystemMetrics(1)}' + else: + args = 'xrandr | grep "*" | cut -d " " -f4' + res = run(args, split=False) + return res.strip() + + +def url_open(url, data=None, headers={}, verify=True, get_json=False): + err = '' + req = Request(url) + for k, v in headers.items(): + req.add_header(k, v) + try: + # ~ debug(url) + if verify: + if not data is None and isinstance(data, str): + data = data.encode() + response = urlopen(req, data=data) + 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: + headers = dict(response.info()) + result = response.read() + if get_json: + result = json.loads(result) + + return result, headers, err + + +def _get_key(password): + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + + kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=SALT, + iterations=100000) + key = base64.urlsafe_b64encode(kdf.derive(password.encode())) + return key + + +def encrypt(data, password): + from cryptography.fernet import Fernet + + f = Fernet(_get_key(password)) + if isinstance(data, str): + data = data.encode() + token = f.encrypt(data).decode() + return token + + +def decrypt(token, password): + from cryptography.fernet import Fernet, InvalidToken + + data = '' + f = Fernet(_get_key(password)) + try: + data = f.decrypt(token.encode()).decode() + except InvalidToken as e: + error('Invalid Token') + return data + + +def switch_design_mode(doc): + call_dispatch(doc.frame, '.uno:SwitchControlDesignMode') + return + + +class SmtpServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._sender = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + name = config['server'] + port = config['port'] + is_ssl = config['ssl'] + self._sender = config['user'] + hosts = ('gmail' in name or 'outlook' in name) + try: + if is_ssl and hosts: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + self._server.ehlo() + self._server.starttls() + self._server.ehlo() + elif is_ssl: + self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT) + self._server.ehlo() + else: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + + self._server.login(self._sender, config['password']) + msg = 'Connect to: {}'.format(name) + debug(msg) + return True + except smtplib.SMTPAuthenticationError as e: + if '535' in str(e): + self._error = _('Incorrect user or password') + return False + if '534' in str(e) and 'gmail' in name: + self._error = _('Allow less secure apps in GMail') + return False + except smtplib.SMTPException as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def _body(self, msg): + body = msg.replace('\\n', '
') + return body + + def send(self, message): + file_name = 'attachment; filename={}' + email = MIMEMultipart() + email['From'] = self._sender + email['To'] = message['to'] + email['Cc'] = message.get('cc', '') + email['Subject'] = message['subject'] + email['Date'] = formatdate(localtime=True) + if message.get('confirm', False): + email['Disposition-Notification-To'] = email['From'] + email.attach(MIMEText(self._body(message['body']), 'html')) + + for path in message.get('files', ()): + fn = _P(path).file_name + part = MIMEBase('application', 'octet-stream') + part.set_payload(_P.read_bin(path)) + encoders.encode_base64(part) + part.add_header('Content-Disposition', f'attachment; filename={fn}') + email.attach(part) + + receivers = ( + email['To'].split(',') + + email['CC'].split(',') + + message.get('bcc', '').split(',')) + try: + self._server.sendmail(self._sender, receivers, email.as_string()) + msg = 'Email sent...' + debug(msg) + if message.get('path', ''): + self.save_message(email, message['path']) + return True + except Exception as e: + self._error = str(e) + return False + return False + + def save_message(self, email, path): + mbox = mailbox.mbox(path, create=True) + mbox.lock() + try: + msg = mailbox.mboxMessage(email) + mbox.add(msg) + mbox.flush() + finally: + mbox.unlock() + return + + def close(self): + try: + self._server.quit() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + +def _send_email(server, messages): + with SmtpServer(server) as server: + if server.is_connect: + for msg in messages: + server.send(msg) + else: + error(server.error) + return server.error + + +def send_email(server, message): + messages = message + if isinstance(message, dict): + messages = (message,) + t = threading.Thread(target=_send_email, args=(server, messages)) + t.start() + return + + +class ImapServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + try: + # ~ hosts = 'gmail' in config['server'] + if config['ssl']: + self._server = imaplib.IMAP4_SSL(config['server'], config['port']) + else: + self._server = imaplib.IMAP4(config['server'], config['port']) + self._server.login(config['user'], config['password']) + self._server.select() + return True + except imaplib.IMAP4.error as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def get_folders(self, exclude=()): + folders = {} + result, subdir = self._server.list() + for s in subdir: + print(s.decode('utf-8')) + return folders + + def close(self): + try: + self._server.close() + self._server.logout() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + +# ~ Classes + +class LOBaseObject(object): + + def __init__(self, obj): + self._obj = obj + + def __setattr__(self, name, value): + exists = hasattr(self, name) + if not exists and not name in ('_obj', '_index', '_view'): + setattr(self._obj, name, value) + else: + super().__setattr__(name, value) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + +class LODocument(object): + FILTERS = { + 'doc': 'MS Word 97', + 'docx': 'MS Word 2007 XML', + } + + def __init__(self, obj): + self._obj = obj + self._cc = self.obj.getCurrentController() + self._undo = True + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def obj(self): + return self._obj + + @property + def title(self): + return self.obj.getTitle() + @title.setter + def title(self, value): + self.obj.setTitle(value) + + @property + def type(self): + return self._type + + @property + def uid(self): + return self.obj.RuntimeUID + + @property + def frame(self): + return self._cc.getFrame() + + @property + def is_saved(self): + return self.obj.hasLocation() + + @property + def is_modified(self): + return self.obj.isModified() + + @property + def is_read_only(self): + return self.obj.isReadOnly() + + @property + def path(self): + return _P.to_system(self.obj.URL) + + @property + def dir(self): + return _P(self.path).path + + @property + def file_name(self): + return _P(self.path).file_name + + @property + def name(self): + return _P(self.path).name + + @property + def status_bar(self): + return self._cc.getStatusIndicator() + + @property + def visible(self): + w = self.frame.ContainerWindow + return w.isVisible() + @visible.setter + def visible(self, value): + w = self.frame.ContainerWindow + w.setVisible(value) + + @property + def zoom(self): + return self._cc.ZoomValue + @zoom.setter + def zoom(self, value): + self._cc.ZoomValue = value + + @property + def undo(self): + return self._undo + @undo.setter + def undo(self, value): + self._undo = value + um = self.obj.UndoManager + if value: + try: + um.leaveUndoContext() + except: + pass + else: + um.enterHiddenUndoContext() + + def clear_undo(self): + self.obj.getUndoManager().clear() + return + + @property + def selection(self): + sel = self.obj.CurrentSelection + # ~ return _get_class_uno(sel) + return sel + + @property + def table_auto_formats(self): + taf = create_instance('com.sun.star.sheet.TableAutoFormats') + return taf.ElementNames + + def create_instance(self, name): + obj = self.obj.createInstance(name) + return obj + + def set_focus(self): + w = self.frame.ComponentWindow + w.setFocus() + return + + def copy(self): + call_dispatch(self.frame, '.uno:Copy') + return + + def insert_contents(self, args={}): + call_dispatch(self.frame, '.uno:InsertContents', args) + return + + def paste(self): + sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') + transferable = sc.getContents() + self._cc.insertTransferable(transferable) + # ~ return self.obj.getCurrentSelection() + return + + def select(self, obj): + self._cc.select(obj) + return + + def to_pdf(self, path: str='', args: dict={}): + path_pdf = path + filter_name = '{}_pdf_Export'.format(self.type) + filter_data = dict_to_property(args, True) + args = { + 'FilterName': filter_name, + 'FilterData': filter_data, + } + opt = dict_to_property(args) + try: + self.obj.storeToURL(_P.to_url(path), opt) + except Exception as e: + error(e) + path_pdf = '' + + return _P.exists(path_pdf) + + def export(self, path: str, ext: str='', args: dict={}): + if not ext: + ext = _P(path).ext + filter_name = self.FILTERS[ext] + filter_data = dict_to_property(args, True) + args = { + 'FilterName': filter_name, + 'FilterData': filter_data, + } + opt = dict_to_property(args) + try: + self.obj.storeToURL(_P.to_url(path), opt) + except Exception as e: + error(e) + path = '' + return _P.exists(path) + + def save(self, path: str='', args: dict={}) -> bool: + result = True + opt = dict_to_property(args) + if path: + try: + self.obj.storeAsURL(_P.to_url(path), opt) + except Exception as e: + error(e) + result = False + else: + self.obj.store() + return result + + def close(self): + self.obj.close(True) + return + + +class LOCellStyle(LOBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def name(self): + return self.obj.Name + + @property + def properties(self): + properties = self.obj.PropertySetInfo.Properties + data = {p.Name: getattr(self.obj, p.Name) for p in properties} + return data + @properties.setter + def properties(self, values): + _set_properties(self.obj, values) + + +class LOCellStyles(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + + def __len__(self): + return len(self.obj) + + def __getitem__(self, index): + return LOCellStyle(self.obj[index]) + + def __setitem__(self, key, value): + self.obj[key] = value + + def __delitem__(self, key): + if not isinstance(key, str): + key = key.Name + del self.obj[key] + + def __contains__(self, item): + return item in self.obj + + @property + def obj(self): + return self._obj + + @property + def names(self): + return self.obj.ElementNames + + def new(self, name: str=''): + obj = self._doc.create_instance('com.sun.star.style.CellStyle') + if name: + self.obj[name] = obj + obj = LOCellStyle(obj) + return obj + + +class LOCalc(LODocument): + + def __init__(self, obj): + super().__init__(obj) + self._type = CALC + self._sheets = obj.Sheets + + def __getitem__(self, index): + return LOCalcSheet(self._sheets[index]) + + def __setitem__(self, key, value): + self._sheets[key] = value + + def __len__(self): + return self._sheets.Count + + def __contains__(self, item): + return item in self._sheets + + @property + def names(self): + names = self.obj.Sheets.ElementNames + return names + + @property + def selection(self): + sel = self.obj.CurrentSelection + if sel.ImplementationName in TYPE_RANGES: + sel = LOCalcRange(sel) + elif sel.ImplementationName == OBJ_SHAPES: + if len(sel) == 1: + sel = sel[0] + sel = LODrawPage(sel.Parent)[sel.Name] + else: + debug(sel.ImplementationName) + return sel + + @property + def active(self): + return LOCalcSheet(self._cc.ActiveSheet) + + @property + def headers(self): + return self._cc.ColumnRowHeaders + @headers.setter + def headers(self, value): + self._cc.ColumnRowHeaders = value + + @property + def tabs(self): + return self._cc.SheetTabs + @tabs.setter + def tabs(self, value): + self._cc.SheetTabs = value + + @property + def cs(self): + return self.cell_styles + @property + def cell_styles(self): + obj = self.obj.StyleFamilies['CellStyles'] + return LOCellStyles(obj, self) + + @property + def db_ranges(self): + # ~ return LOCalcDataBaseRanges(self.obj.DataBaseRanges) + return self.obj.DatabaseRanges + + def activate(self, sheet): + obj = sheet + if isinstance(sheet, LOCalcSheet): + obj = sheet.obj + elif isinstance(sheet, str): + obj = self._sheets[sheet] + self._cc.setActiveSheet(obj) + return + + def new_sheet(self): + s = self.create_instance('com.sun.star.sheet.Spreadsheet') + return s + + def insert(self, name): + names = name + if isinstance(name, str): + names = (name,) + for n in names: + self._sheets[n] = self.new_sheet() + return LOCalcSheet(self._sheets[n]) + + def move(self, name, pos=-1): + index = pos + if pos < 0: + index = len(self) + if isinstance(name, LOCalcSheet): + name = name.name + self._sheets.moveByName(name, index) + return + + def remove(self, name): + if isinstance(name, LOCalcSheet): + name = name.name + self._sheets.removeByName(name) + return + + def copy(self, name, new_name='', pos=-1): + if isinstance(name, LOCalcSheet): + name = name.name + index = pos + if pos < 0: + index = len(self) + self._sheets.copyByName(name, new_name, index) + return LOCalcSheet(self._sheets[new_name]) + + def copy_from(self, doc, source='', target='', pos=-1): + index = pos + if pos < 0: + index = len(self) + + names = source + if not source: + names = doc.names + elif isinstance(source, str): + names = (source,) + + new_names = target + if not target: + new_names = names + elif isinstance(target, str): + new_names = (target,) + + for i, name in enumerate(names): + self._sheets.importSheet(doc.obj, name, index + i) + self[index + i].name = new_names[i] + + return LOCalcSheet(self._sheets[index]) + + def sort(self, reverse=False): + names = sorted(self.names, reverse=reverse) + for i, n in enumerate(names): + self.move(n, i) + return + + def render(self, data, sheet=None, clean=True): + if sheet is None: + sheet = self.active + return sheet.render(data, clean=clean) + + +class LOChart(object): + + def __init__(self, name, obj, draw_page): + self._name = name + self._obj = obj + self._eobj = self._obj.EmbeddedObject + self._type = 'Column' + self._cell = None + self._shape = self._get_shape(draw_page) + self._pos = self._shape.Position + + def __getitem__(self, index): + return LOBaseObject(self.diagram.getDataRowProperties(index)) + + 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._name + + @property + def diagram(self): + return self._eobj.Diagram + + @property + def type(self): + return self._type + @type.setter + def type(self, value): + self._type = value + if value == 'Bar': + self.diagram.Vertical = True + return + type_chart = f'com.sun.star.chart.{value}Diagram' + self._eobj.setDiagram(self._eobj.createInstance(type_chart)) + + @property + def cell(self): + return self._cell + @cell.setter + def cell(self, value): + self._cell = value + self._shape.Anchor = value.obj + + @property + def position(self): + return self._pos + @position.setter + def position(self, value): + self._pos = value + self._shape.Position = value + + def _get_shape(self, draw_page): + for shape in draw_page: + if shape.PersistName == self.name: + break + return shape + + +class LOSheetCharts(object): + + def __init__(self, obj, sheet): + self._obj = obj + self._sheet = sheet + + def __getitem__(self, index): + return LOChart(index, self.obj[index], self._sheet.draw_page) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + @property + def obj(self): + return self._obj + + def new(self, name, pos_size, data): + self.obj.addNewByName(name, pos_size, data, True, True) + return LOChart(name, self.obj[name], self._sheet.draw_page) + + +class LOFormControl(LOBaseObject): + EVENTS = { + 'action': 'actionPerformed', + 'click': 'mousePressed', + } + TYPES = { + 'actionPerformed': 'XActionListener', + 'mousePressed': 'XMouseListener', + } + + def __init__(self, obj, view, form): + super().__init__(obj) + self._view = view + self._form = form + self._m = view.Model + self._index = -1 + + def __setattr__(self, name, value): + if name in ('_form', '_view', '_m', '_index'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __str__(self): + return f'{self.name} ({self.type}) {[self.index]}' + + @property + def form(self): + return self._form + + @property + def doc(self): + return self.obj.Parent.Forms.Parent + + @property + def name(self): + return self._m.Name + @name.setter + def name(self, value): + self._m.Name = value + + @property + def tag(self): + return self._m.Tag + @tag.setter + def tag(self, value): + self._m.Tag = value + + @property + def index(self): + return self._index + @index.setter + def index(self, value): + self._index = value + + @property + def enabled(self): + return self._m.Enabled + @enabled.setter + def enabled(self, value): + self._m.Enabled = value + + @property + def events(self): + return self.form.getScriptEvents(self.index) + 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 + + def set_focus(self): + self._view.setFocus() + return + + +class LOFormControlLabel(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'label' + + @property + def value(self): + return self._m.Label + @value.setter + def value(self, value): + self._m.Label = value + + +class LOFormControlText(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'text' + + @property + def value(self): + return self._m.Text + @value.setter + def value(self, value): + self._m.Text = value + + +class LOFormControlButton(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'button' + + @property + def value(self): + return self._m.Label + @value.setter + def value(self, value): + self._m.Text = Label + + +FORM_CONTROL_CLASS = { + 'label': LOFormControlLabel, + 'text': LOFormControlText, + 'button': LOFormControlButton, +} + + +class LOForm(object): + MODELS = { + 'label': 'com.sun.star.form.component.FixedText', + 'text': 'com.sun.star.form.component.TextField', + 'button': 'com.sun.star.form.component.CommandButton', + } + + def __init__(self, obj, draw_page): + self._obj = obj + self._dp = draw_page + self._controls = {} + self._init_controls() + + def __getitem__(self, index): + control = self.obj[index] + return self._controls[control.Name] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + def __str__(self): + return f'Form: {self.name}' + + def _init_controls(self): + types = { + 'com.sun.star.form.OFixedTextModel': 'label', + 'com.sun.star.form.OEditModel': 'text', + 'com.sun.star.form.OButtonModel': 'button', + } + for i, control in enumerate(self.obj): + name = control.Name + tipo = types[control.ImplementationName] + view = self.doc.CurrentController.getControl(control) + control = FORM_CONTROL_CLASS[tipo](control, view) + control.index = i + setattr(self, name, control) + self._controls[name] = control + return + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self.obj.Name + @name.setter + def name(self, value): + self.obj.Name = value + + @property + def source(self): + return self.obj.DataSourceName + @source.setter + def source(self, value): + self.obj.DataSourceName = value + + @property + def type(self): + return self.obj.CommandType + @type.setter + def type(self, value): + self.obj.CommandType = value + + @property + def command(self): + return self.obj.Command + @command.setter + def command(self, value): + self.obj.Command = value + + @property + def doc(self): + return self.obj.Parent.Parent + + def _special_properties(self, tipo, args): + if tipo == 'button': + # ~ if 'ImageURL' in args: + # ~ args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + return args + + def add(self, args): + name = args['Name'] + tipo = args.pop('Type').lower() + w = args.pop('Width') + h = args.pop('Height') + x = args.pop('X', 0) + y = args.pop('Y', 0) + control = self.doc.createInstance('com.sun.star.drawing.ControlShape') + control.setSize(Size(w, h)) + control.setPosition(Point(x, y)) + model = self.doc.createInstance(self.MODELS[tipo]) + args = self._special_properties(tipo, args) + _set_properties(model, args) + control.Control = model + index = len(self) + self.obj.insertByIndex(index, model) + self._dp.add(control) + view = self.doc.CurrentController.getControl(self.obj.getByName(name)) + control = FORM_CONTROL_CLASS[tipo](control, view, self.obj) + control.index = index + setattr(self, name, control) + self._controls[name] = control + return control + + +class LOSheetForms(object): + + def __init__(self, draw_page): + self._dp = draw_page + self._obj = draw_page.Forms + + def __getitem__(self, index): + return LOForm(self.obj[index], self._dp) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + @property + def obj(self): + return self._obj + + @property + def doc(self): + return self.obj.Parent + + @property + def count(self): + return len(self) + + @property + def names(self): + return self.obj.ElementNames + + def insert(self, name): + form = self.doc.createInstance('com.sun.star.form.component.Form') + self.obj.insertByName(name, form) + return LOForm(form, self._dp) + + def remove(self, index): + if isinstance(index, int): + self.obj.removeByIndex(index) + else: + self.obj.removeByName(index) + return + + +# ~ IsFiltered, +# ~ IsManualPageBreak, +# ~ IsStartOfNewPage +class LOSheetRows(object): + + def __init__(self, sheet, obj): + self._sheet = sheet + self._obj = obj + + def __getitem__(self, index): + if isinstance(index, int): + rows = LOSheetRows(self._sheet, self.obj[index]) + else: + rango = self._sheet[index.start:index.stop,0:] + rows = LOSheetRows(self._sheet, rango.obj.Rows) + return rows + + def __len__(self): + return self.obj.Count + + @property + def obj(self): + return self._obj + + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def color(self): + return self.obj.CellBackColor + @color.setter + def color(self, value): + self.obj.CellBackColor = value + + @property + def is_transparent(self): + return self.obj.IsCellBackgroundTransparent + @is_transparent.setter + def is_transparent(self, value): + self.obj.IsCellBackgroundTransparent = value + + @property + def height(self): + return self.obj.Height + @height.setter + def height(self, value): + self.obj.Height = value + + def optimal(self): + self.obj.OptimalHeight = True + return + + def insert(self, index, count): + self.obj.insertByIndex(index, count) + return + + def remove(self, index, count): + self.obj.removeByIndex(index, count) + return + + +# ~ IsManualPageBreak, +# ~ IsStartOfNewPage +class LOSheetColumns(object): + + def __init__(self, sheet, obj): + self._sheet = sheet + self._obj = obj + + def __getitem__(self, index): + if isinstance(index, (int, str)): + rows = LOSheetColumns(self._sheet, self.obj[index]) + else: + rango = self._sheet[0,index.start:index.stop] + rows = LOSheetColumns(self._sheet, rango.obj.Columns) + return rows + + def __len__(self): + return self.obj.Count + + @property + def obj(self): + return self._obj + + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def width(self): + return self.obj.Width + @width.setter + def width(self, value): + self.obj.Width = value + + def optimal(self): + self.obj.OptimalWidth = True + return + + def insert(self, index, count): + self.obj.insertByIndex(index, count) + return + + def remove(self, index, count): + self.obj.removeByIndex(index, count) + return + + +class LOCalcSheet(object): + + def __init__(self, obj): + self._obj = obj + + def __getitem__(self, index): + return LOCalcRange(self.obj[index]) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __str__(self): + return f'easymacro.LOCalcSheet: {self.name}' + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self._obj.Name + @name.setter + def name(self, value): + self._obj.Name = value + + @property + def code_name(self): + return self._obj.CodeName + @code_name.setter + def code_name(self, value): + self._obj.CodeName = value + + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def is_protected(self): + return self._obj.isProtected() + + @property + def password(self): + return '' + @visible.setter + def password(self, value): + self.obj.protect(value) + + def unprotect(self, value): + try: + self.obj.unprotect(value) + return True + except: + pass + return False + + @property + def color(self): + return self._obj.TabColor + @color.setter + def color(self, value): + self._obj.TabColor = get_color(value) + + @property + def used_area(self): + cursor = self.get_cursor() + cursor.gotoEndOfUsedArea(True) + return LOCalcRange(self[cursor.AbsoluteName].obj) + + @property + def draw_page(self): + return LODrawPage(self.obj.DrawPage) + + @property + def dp(self): + return self.draw_page + + @property + def shapes(self): + return self.draw_page + + @property + def doc(self): + return LOCalc(self.obj.DrawPage.Forms.Parent) + + @property + def charts(self): + return LOSheetCharts(self.obj.Charts, self) + + @property + def rows(self): + return LOSheetRows(self, self.obj.Rows) + + @property + def columns(self): + return LOSheetColumns(self, self.obj.Columns) + + @property + def forms(self): + return LOSheetForms(self.obj.DrawPage) + + @property + def events(self): + names = ('OnFocus', 'OnUnfocus', 'OnSelect', 'OnDoubleClick', + 'OnRightClick', 'OnChange', 'OnCalculate') + evs = self.obj.Events + events = {n: _property_to_dict(evs.getByName(n)) for n in names + if evs.getByName(n)} + return events + @events.setter + def events(self, values): + pv = '[]com.sun.star.beans.PropertyValue' + ev = self.obj.Events + for name, v in values.items(): + url = _get_url_script(v) + args = dict_to_property(dict(EventType='Script', Script=url)) + # ~ e.replaceByName(k, args) + uno.invoke(ev, 'replaceByName', (name, uno.Any(pv, args))) + + @property + def search_descriptor(self): + return self.obj.createSearchDescriptor() + + @property + def replace_descriptor(self): + return self.obj.createReplaceDescriptor() + + def activate(self): + self.doc.activate(self.obj) + return + + def clean(self): + doc = self.doc + sheet = doc.create_instance('com.sun.star.sheet.Spreadsheet') + doc._sheets.replaceByName(self.name, sheet) + return + + def move(self, pos=-1): + index = pos + if pos < 0: + index = len(self.doc) + self.doc._sheets.moveByName(self.name, index) + return + + def remove(self): + self.doc._sheets.removeByName(self.name) + return + + def copy(self, new_name='', pos=-1): + index = pos + if pos < 0: + index = len(self.doc) + self.doc._sheets.copyByName(self.name, new_name, index) + return LOCalcSheet(self.doc._sheets[new_name]) + + def copy_to(self, doc, target='', pos=-1): + index = pos + if pos < 0: + index = len(doc) + name = self.name + if not target: + new_name = name + + doc._sheets.importSheet(self.doc.obj, name, index) + sheet = doc[name] + sheet.name = new_name + return sheet + + def get_cursor(self, cell=None): + if cell is None: + cursor = self.obj.createCursor() + else: + cursor = self.obj.createCursorByRange(cell) + return cursor + + def render(self, data, rango=None, clean=True): + if rango is None: + rango = self.used_area + return rango.render(data, clean) + + def find(self, search_string, rango=None): + if rango is None: + rango = self.used_area + return rango.find(search_string) + + +class LOCalcRange(object): + + def __init__(self, obj): + self._obj = obj + self._sd = None + self._is_cell = obj.ImplementationName == OBJ_CELL + + def __getitem__(self, index): + return LOCalcRange(self.obj[index]) + + def __iter__(self): + self._r = 0 + self._c = 0 + return self + + def __next__(self): + try: + rango = self[self._r, self._c] + except Exception as e: + raise StopIteration + self._c += 1 + if self._c == self.columns: + self._c = 0 + self._r +=1 + return rango + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item.in_range(self) + + def __str__(self): + if self.is_none: + s = 'Range: None' + else: + s = f'Range: {self.name}' + return s + + @property + def obj(self): + return self._obj + + @property + def is_none(self): + return self.obj is None + + @property + def is_cell(self): + return self._is_cell + + @property + def back_color(self): + return self._obj.CellBackColor + @back_color.setter + def back_color(self, value): + self._obj.CellBackColor = get_color(value) + + @property + def dp(self): + return self.sheet.dp + + @property + def sheet(self): + return LOCalcSheet(self.obj.Spreadsheet) + + @property + def doc(self): + doc = self.obj.Spreadsheet.DrawPage.Forms.Parent + return LODocument(doc) + + @property + def name(self): + return self.obj.AbsoluteName + + @property + def code_name(self): + name = self.name.replace('$', '').replace('.', '_').replace(':', '') + return name + + @property + def columns(self): + return self.obj.Columns.Count + + @property + def column(self): + c1 = self.address.Column + c2 = c1 + 1 + ra = self.current_region.range_address + r1 = ra.StartRow + r2 = ra.EndRow + 1 + return LOCalcRange(self.sheet[r1:r2, c1:c2].obj) + + @property + 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) + + @property + def type(self): + return self.obj.Type + + @property + def value(self): + v = None + if self.type == VALUE: + v = self.obj.getValue() + elif self.type == TEXT: + v = self.obj.getString() + elif self.type == FORMULA: + v = self.obj.getFormula() + return v + @value.setter + def value(self, data): + if isinstance(data, str): + # ~ print(isinstance(data, str), data[0]) + if data[0] in '=': + self.obj.setFormula(data) + # ~ print('Set Formula') + else: + self.obj.setString(data) + elif isinstance(data, Decimal): + self.obj.setValue(float(data)) + elif isinstance(data, (int, float, bool)): + self.obj.setValue(data) + elif isinstance(data, datetime.datetime): + d = data.toordinal() + t = (data - datetime.datetime.fromordinal(d)).seconds / SECONDS_DAY + self.obj.setValue(d - DATE_OFFSET + t) + elif isinstance(data, datetime.date): + d = data.toordinal() + self.obj.setValue(d - DATE_OFFSET) + elif isinstance(data, datetime.time): + d = (data.hour * 3600 + data.minute * 60 + data.second) / SECONDS_DAY + self.obj.setValue(d) + + @property + def date(self): + value = int(self.obj.Value) + date = datetime.date.fromordinal(value + DATE_OFFSET) + return date + + @property + def time(self): + seconds = self.obj.Value * SECONDS_DAY + time_delta = datetime.timedelta(seconds=seconds) + time = (datetime.datetime.min + time_delta).time() + return time + + @property + def datetime(self): + return datetime.datetime.combine(self.date, self.time) + + @property + def data(self): + return self.obj.getDataArray() + @data.setter + def data(self, values): + if self._is_cell: + self.to_size(len(values), len(values[0])).data = values + else: + self.obj.setDataArray(values) + + @property + def dict(self): + rows = self.data + k = rows[0] + data = [dict(zip(k, r)) for r in rows[1:]] + return data + @dict.setter + def dict(self, values): + data = [tuple(values[0].keys())] + data += [tuple(d.values()) for d in values] + self.data = data + + @property + def formula(self): + return self.obj.getFormulaArray() + @formula.setter + def formula(self, values): + self.obj.setFormulaArray(values) + + @property + def array_formula(self): + return self.obj.ArrayFormula + @array_formula.setter + def array_formula(self, value): + self.obj.ArrayFormula = value + + @property + def address(self): + return self.obj.CellAddress + + @property + def range_address(self): + return self.obj.RangeAddress + + @property + def cursor(self): + cursor = self.obj.Spreadsheet.createCursorByRange(self.obj) + return cursor + + @property + def current_region(self): + cursor = self.cursor + cursor.collapseToCurrentRegion() + return LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + + @property + def next_cell(self): + a = self.current_region.range_address + col = a.StartColumn + row = a.EndRow + 1 + return LOCalcRange(self.sheet[row, col].obj) + + @property + def position(self): + return self.obj.Position + + @property + def size(self): + return self.obj.Size + + @property + def possize(self): + data = { + 'Width': self.size.Width, + 'Height': self.size.Height, + 'X': self.position.X, + 'Y': self.position.Y, + } + return data + + @property + def visible(self): + cursor = self.cursor + rangos = cursor.queryVisibleCells() + rangos = [LOCalcRange(self.sheet[r.AbsoluteName].obj) for r in rangos] + return tuple(rangos) + + @property + def merged_area(self): + cursor = self.cursor + cursor.collapseToMergedArea() + rango = LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + return rango + + @property + def empty(self): + cursor = self.sheet.get_cursor(self.obj) + cursor = self.cursor + rangos = cursor.queryEmptyCells() + rangos = [LOCalcRange(self.sheet[r.AbsoluteName].obj) for r in rangos] + return tuple(rangos) + + @property + def merge(self): + return self.obj.IsMerged + @merge.setter + def merge(self, value): + self.obj.merge(value) + + @property + def style(self): + return self.obj.CellStyle + @style.setter + def style(self, value): + self.obj.CellStyle = value + + @property + def auto_format(self): + return '' + @auto_format.setter + def auto_format(self, value): + self.obj.autoFormat(value) + + @property + def validation(self): + return self.obj.Validation + @validation.setter + def validation(self, values): + current = self.validation + if not values: + current.Type = ValidationType.ANY + current.ShowInputMessage = False + else: + is_list = False + for k, v in values.items(): + if k == 'Type' and v == VT.LIST: + 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 + + def select(self): + self.doc.select(self.obj) + return + + def search(self, options, find_all=True): + rangos = None + + descriptor = self.sheet.search_descriptor + descriptor.setSearchString(options['Search']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + + if find_all: + found = self.obj.findAll(descriptor) + else: + found = self.obj.findFirst(descriptor) + + if found: + if found.ImplementationName == OBJ_CELL: + rangos = LOCalcRange(found) + else: + rangos = [LOCalcRange(f) for f in found] + + return rangos + + def replace(self, options): + descriptor = self.sheet.replace_descriptor + descriptor.setSearchString(options['Search']) + descriptor.setReplaceString(options['Replace']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + count = self.obj.replaceAll(descriptor) + return count + + def in_range(self, rango): + if isinstance(rango, LOCalcRange): + address = rango.range_address + else: + address = rango.RangeAddress + result = self.cursor.queryIntersection(address) + return bool(result.Count) + + def offset(self, rows=0, cols=1): + ra = self.range_address + col = ra.EndColumn + cols + row = ra.EndRow + rows + return LOCalcRange(self.sheet[row, col].obj) + + def to_size(self, rows, cols): + cursor = self.cursor + cursor.collapseToSize(cols, rows) + return LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + + def copy(self, source): + self.sheet.obj.copyRange(self.address, source.range_address) + return + + def copy_to(self, cell, formula=False): + rango = cell.to_size(self.rows, self.columns) + if formula: + rango.formula = self.data + else: + rango.data = self.data + return + + def copy_from(self, rango, formula=False): + data = rango + if isinstance(rango, LOCalcRange): + if formula: + data = rango.formula + else: + data = rango.data + rows = len(data) + cols = len(data[0]) + if formula: + self.to_size(rows, cols).formula = data + else: + self.to_size(rows, cols).data = data + return + + def optimal_width(self): + self.obj.Columns.OptimalWidth = True + return + + def clean_render(self, template='\{(\w.+)\}'): + self._sd.SearchRegularExpression = True + self._sd.setSearchString(template) + self.obj.replaceAll(self._sd) + return + + def render(self, data, clean=True): + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + for k, v in data.items(): + cell = self._render_value(k, v) + return cell + + def _render_value(self, key, value, parent=''): + cell = None + if isinstance(value, dict): + for k, v in value.items(): + cell = self._render_value(k, v, key) + return cell + elif isinstance(value, (list, tuple)): + self._render_list(key, value) + return + + search = f'{{{key}}}' + if parent: + search = f'{{{parent}.{key}}}' + ranges = self.find_all(search) + + for cell in ranges or range(0): + self._set_new_value(cell, search, value) + return LOCalcRange(cell) + + def _set_new_value(self, cell, search, value): + if not cell.ImplementationName == 'ScCellObj': + return + + if isinstance(value, str): + pattern = re.compile(search, re.IGNORECASE) + new_value = pattern.sub(value, cell.String) + cell.String = new_value + else: + LOCalcRange(cell).value = value + return + + def _render_list(self, key, rows): + for row in rows: + for k, v in row.items(): + self._render_value(k, v) + return + + def find(self, search_string): + if self._sd is None: + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + + self._sd.setSearchString(search_string) + cell = self.obj.findFirst(self._sd) + if cell: + cell = LOCalcRange(cell) + return cell + + def find_all(self, search_string): + if self._sd is None: + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + + self._sd.setSearchString(search_string) + ranges = self.obj.findAll(self._sd) + return ranges + + def filter(self, args, with_headers=True): + ff = TableFilterField() + ff.Field = args['Field'] + ff.Operator = args['Operator'] + if isinstance(args['Value'], str): + ff.IsNumeric = False + ff.StringValue = args['Value'] + else: + ff.IsNumeric = True + ff.NumericValue = args['Value'] + + fd = self.obj.createFilterDescriptor(True) + fd.ContainsHeader = with_headers + fd.FilterFields = ((ff,)) + # ~ self.obj.AutoFilter = True + self.obj.filter(fd) + return + + def copy_format_from(self, rango): + rango.select() + self.doc.copy() + self.select() + args = { + 'Flags': 'T', + 'MoveMode': 4, + } + url = '.uno:InsertContents' + call_dispatch(self.doc.frame, url, args) + return + + def to_image(self): + self.select() + self.doc.copy() + args = {'SelectedFormat': 141} + url = '.uno:ClipboardFormatItems' + call_dispatch(self.doc.frame, url, args) + return self.sheet.shapes[-1] + + def insert_image(self, path, args={}): + ps = self.possize + args['Width'] = args.get('Width', ps['Width']) + args['Height'] = args.get('Height', ps['Height']) + args['X'] = args.get('X', ps['X']) + args['Y'] = args.get('Y', ps['Y']) + # ~ img.ResizeWithCell = True + img = self.sheet.dp.insert_image(path, args) + img.anchor = self.obj + args.clear() + return img + + def insert_shape(self, tipo, args={}): + ps = self.possize + args['Width'] = args.get('Width', ps['Width']) + args['Height'] = args.get('Height', ps['Height']) + args['X'] = args.get('X', ps['X']) + args['Y'] = args.get('Y', ps['Y']) + + shape = self.sheet.dp.add(tipo, args) + shape.anchor = self.obj + args.clear() + return + + def filter_by_color(self, cell): + rangos = cell.column[1:,:].visible + for r in rangos: + for c in r: + if c.back_color != cell.back_color: + c.rows.visible = False + return + + def clear(self, what=1023): + # ~ http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html + self.obj.clearContents(what) + return + + def transpose(self): + # ~ 'Flags': 'A', + # ~ 'FormulaCommand': 0, + # ~ 'SkipEmptyCells': False, + # ~ 'AsLink': False, + # ~ 'MoveMode': 4, + self.select() + self.doc.copy() + self.clear(1023) + self[0,0].select() + self.doc.insert_contents({'Transpose': True}) + _CB.set('') + return + + def transpose_data(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 merge_by_row(self): + for r in range(len(self.rows)): + self[r].merge = True + return + + def fill(self, source=1): + self.obj.fillAuto(0, source) + return + + +class LOWriterPageStyle(LOBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + def __str__(self): + return f'Page Style: {self.name}' + + @property + def name(self): + return self._obj.Name + + +class LOWriterPageStyles(object): + + def __init__(self, styles): + self._styles = styles + + def __getitem__(self, index): + return LOWriterPageStyle(self._styles[index]) + + @property + def names(self): + return self._styles.ElementNames + + def __str__(self): + return '\n'.join(self.names) + + +class LOWriterTextRange(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + self._is_paragraph = self.obj.ImplementationName == 'SwXParagraph' + self._is_table = self.obj.ImplementationName == 'SwXTextTable' + + 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 + + @property + def obj(self): + return self._obj + + @property + def string(self): + s = '' + if self._is_paragraph: + s = self.obj.String + return s + @string.setter + def string(self, value): + self.obj.String = value + + @property + def value(self): + return self.string + + @property + def is_table(self): + return self._is_table + + @property + def text(self): + return self.obj.Text + + @property + def cursor(self): + return self.text.createTextCursorByRange(self.obj) + + @property + def dp(self): + return self._doc.dp + + def offset(self): + cursor = self.cursor.getEnd() + return LOWriterTextRange(cursor, self._doc) + + def insert_content(self, data, cursor=None, replace=False): + if cursor is None: + cursor = self.cursor + self.text.insertTextContent(cursor, data, replace) + return + + def new_line(self, count=1): + cursor = self.cursor + for i in range(count): + self.text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False) + return self._doc.selection + + def insert_table(self, data): + table = self._doc.create_instance('com.sun.star.text.TextTable') + rows = len(data) + cols = len(data[0]) + table.initialize(rows, cols) + self.insert_content(table) + table.DataArray = data + name = table.Name + table = LOWriterTextTable(self._doc.tables[name], self._doc) + return table + + def insert_image(self, path, args={}): + w = args.get('Width', 1000) + h = args.get('Height', 1000) + image = self._doc.create_instance('com.sun.star.text.GraphicObject') + image.GraphicURL = _P.to_url(path) + image.AnchorType = AS_CHARACTER + image.Width = w + image.Height = h + self.insert_content(image) + return self._doc.dp.last + + +class LOWriterTextRanges(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + + def __getitem__(self, index): + for i, p in enumerate(self.obj): + if i == index: + obj = LOWriterTextRange(p, self._doc) + break + return 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 + + @property + def obj(self): + return self._obj + + +class LOWriterTextTable(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self._obj.Name + + @property + def data(self): + return self._obj.DataArray + @data.setter + def data(self, values): + self._obj.DataArray = values + + +class LOWriterTextTables(object): + + def __init__(self, doc): + self._doc = doc + self._obj = doc.obj.TextTables + + def __getitem__(self, key): + return LOWriterTextTable(self._obj[key], self._doc) + + def __len__(self): + return self._obj.Count + + def insert(self, data, text_range=None): + if text_range is None: + text_range = self._doc.selection + text_range.insert_table(data) + return + + +class LOWriter(LODocument): + + def __init__(self, obj): + super().__init__(obj) + self._type = WRITER + + @property + def text(self): + return LOWriterTextRange(self.obj.Text, self) + + @property + def paragraphs(self): + return LOWriterTextRanges(self.obj.Text, self) + + @property + def tables(self): + return LOWriterTextTables(self) + + @property + def selection(self): + sel = self.obj.CurrentSelection + if sel.ImplementationName == OBJ_TEXTS: + if len(sel) == 1: + sel = LOWriterTextRanges(sel, self)[0] + else: + sel = LOWriterTextRanges(sel, self) + return sel + + if sel.ImplementationName == OBJ_SHAPES: + if len(sel) == 1: + sel = sel[0] + sel = LODrawPage(sel.Parent)[sel.Name] + return sel + + if sel.ImplementationName == OBJ_GRAPHIC: + sel = self.dp[sel.Name] + else: + debug(sel.ImplementationName) + + return sel + + @property + def dp(self): + return self.draw_page + @property + def draw_page(self): + return LODrawPage(self.obj.DrawPage) + + @property + def view_cursor(self): + return self._cc.ViewCursor + + @property + def cursor(self): + return self.obj.Text.createTextCursor() + + @property + def page_styles(self): + ps = self.obj.StyleFamilies['PageStyles'] + return LOWriterPageStyles(ps) + + @property + def search_descriptor(self): + return self.obj.createSearchDescriptor() + + @property + def replace_descriptor(self): + return self.obj.createReplaceDescriptor() + + def goto_start(self): + self.view_cursor.gotoStart(False) + return self.selection + + def goto_end(self): + self.view_cursor.gotoEnd(False) + return self.selection + + def search(self, options, find_all=True): + descriptor = self.search_descriptor + descriptor.setSearchString(options.get('Search', '')) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if 'Attributes' in options: + attr = dict_to_property(options['Attributes']) + descriptor.setSearchAttributes(attr) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + + result = False + if find_all: + found = self.obj.findAll(descriptor) + if len(found): + result = [LOWriterTextRange(f, self) for f in found] + else: + found = self.obj.findFirst(descriptor) + if found: + result = LOWriterTextRange(found, self) + + return result + + def replace(self, options): + descriptor = self.replace_descriptor + descriptor.setSearchString(options['Search']) + descriptor.setReplaceString(options['Replace']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if 'Attributes' in options: + attr = dict_to_property(options['Attributes']) + descriptor.setSearchAttributes(attr) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + found = self.obj.replaceAll(descriptor) + return found + + def select(self, text): + if hasattr(text, 'obj'): + text = text.obj + self._cc.select(text) + return + + +class LOShape(LOBaseObject): + IMAGE = 'com.sun.star.drawing.GraphicObjectShape' + + def __init__(self, obj, index): + self._index = index + super().__init__(obj) + + @property + def type(self): + t = self.shape_type[21:] + if self.is_image: + t = 'image' + return t + + @property + def shape_type(self): + return self.obj.ShapeType + + @property + def is_image(self): + return self.shape_type == self.IMAGE + + @property + def name(self): + return self.obj.Name or f'{self.type}{self.index}' + @name.setter + def name(self, value): + self.obj.Name = value + + @property + def index(self): + return self._index + + @property + def size(self): + s = self.obj.Size + a = dict(Width=s.Width, Height=s.Height) + return a + + @property + def string(self): + return self.obj.String + @string.setter + def string(self, value): + self.obj.String = value + + @property + def description(self): + return self.obj.Description + @description.setter + def description(self, value): + self.obj.Description = value + + @property + def cell(self): + return self.anchor + + @property + def anchor(self): + obj = self.obj.Anchor + if obj.ImplementationName == OBJ_CELL: + obj = LOCalcRange(obj) + elif obj.ImplementationName == OBJ_TEXT: + obj = LOWriterTextRange(obj, LODocs().active) + else: + debug('Anchor', obj.ImplementationName) + return obj + @anchor.setter + def anchor(self, value): + if hasattr(value, 'obj'): + value = value.obj + self.obj.Anchor = value + + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value + + @property + def path(self): + return self.url + @property + def url(self): + url = '' + if self.is_image: + url = _P.to_system(self.obj.GraphicURL.OriginURL) + return url + + @property + def mimetype(self): + mt = '' + if self.is_image: + mt = self.obj.GraphicURL.MimeType + return mt + + @property + def linked(self): + l = False + if self.is_image: + l = self.obj.GraphicURL.Linked + return l + + def delete(self): + self.remove() + return + def remove(self): + self.obj.Parent.remove(self.obj) + return + + def save(self, path: str, mimetype=DEFAULT_MIME_TYPE): + if _P.is_dir(path): + name = self.name + ext = mimetype.lower() + else: + p = _P(path) + path = p.path + name = p.name + ext = p.ext.lower() + + path = _P.join(path, f'{name}.{ext}') + args = dict( + URL = _P.to_url(path), + MimeType = MIME_TYPE[ext], + ) + if not _export_image(self.obj, args): + path = '' + return path + + # ~ def save2(self, path: str): + # ~ size = len(self.obj.Bitmap.DIB) + # ~ data = self.obj.GraphicStream.readBytes((), size) + # ~ data = data[-1].value + # ~ path = _P.join(path, f'{self.name}.png') + # ~ _P.save_bin(path, b'') + # ~ return + + +class LODrawPage(LOBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + def __getitem__(self, index): + if isinstance(index, int): + shape = LOShape(self.obj[index], index) + else: + for i, o in enumerate(self.obj): + shape = self.obj[i] + name = shape.Name or f'shape{i}' + if name == index: + shape = LOShape(shape, i) + break + return shape + + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + if self._index == self.count: + raise StopIteration + shape = self[self._index] + self._index += 1 + return shape + + + @property + def name(self): + return self.obj.Name + + @property + def doc(self): + return self.obj.Forms.Parent + + @property + def width(self): + return self.obj.Width + + @property + def height(self): + return self.obj.Height + + @property + def count(self): + return self.obj.Count + + @property + def last(self): + return self[self.count - 1] + + def create_instance(self, name): + return self.doc.createInstance(name) + + def add(self, type_shape, args={}): + """Insert a shape in page, type shapes: + Line + Rectangle + Ellipse + Text + """ + index = self.count + w = args.get('Width', 3000) + h = args.get('Height', 3000) + x = args.get('X', 1000) + y = args.get('Y', 1000) + name = args.get('Name', f'{type_shape.lower()}{index}') + + service = f'com.sun.star.drawing.{type_shape}Shape' + shape = self.create_instance(service) + shape.Size = Size(w, h) + shape.Position = Point(x, y) + shape.Name = name + self.obj.add(shape) + return LOShape(self.obj[index], index) + + def remove(self, shape): + if hasattr(shape, 'obj'): + shape = shape.obj + return self.obj.remove(shape) + + def remove_all(self): + while self.count: + self.obj.remove(self.obj[0]) + return + + def insert_image(self, path, args={}): + index = self.count + w = args.get('Width', 3000) + h = args.get('Height', 3000) + x = args.get('X', 1000) + y = args.get('Y', 1000) + name = args.get('Name', f'image{index}') + + image = self.create_instance('com.sun.star.drawing.GraphicObjectShape') + image.GraphicURL = _P.to_url(path) + image.Size = Size(w, h) + image.Position = Point(x, y) + image.Name = name + self.obj.add(image) + return LOShape(self.obj[index], index) + + +class LODrawImpress(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + def __getitem__(self, index): + if isinstance(index, int): + page = self.obj.DrawPages[index] + else: + page = self.obj.DrawPages.getByName(index) + return LODrawPage(page) + + @property + def selection(self): + sel = self.obj.CurrentSelection[0] + # ~ return _get_class_uno(sel) + return sel + + @property + def current_page(self): + return LODrawPage(self._cc.getCurrentPage()) + + def paste(self): + call_dispatch(self.frame, '.uno:Paste') + return self.current_page[-1] + + def add(self, type_shape, args={}): + return self.current_page.add(type_shape, args) + + def insert_image(self, path, args={}): + self.current_page.insert_image(path, args) + return + + # ~ def export(self, path, mimetype='png'): + # ~ args = dict( + # ~ URL = _P.to_url(path), + # ~ MimeType = MIME_TYPE[mimetype], + # ~ ) + # ~ result = _export_image(self.obj, args) + # ~ return result + + +class LODraw(LODrawImpress): + + def __init__(self, obj): + super().__init__(obj) + self._type = DRAW + + +class LOImpress(LODrawImpress): + + def __init__(self, obj): + super().__init__(obj) + self._type = IMPRESS + + +class BaseDateField(DateField): + + def db_value(self, value): + return _date_to_struct(value) + + def python_value(self, value): + return _struct_to_date(value) + + +class BaseTimeField(TimeField): + + def db_value(self, value): + return _date_to_struct(value) + + def python_value(self, value): + return _struct_to_date(value) + + +class BaseDateTimeField(DateTimeField): + + def db_value(self, value): + return _date_to_struct(value) + + def python_value(self, value): + return _struct_to_date(value) + + +class FirebirdDatabase(Database): + field_types = {'BOOL': 'BOOLEAN', 'DATETIME': 'TIMESTAMP'} + + def __init__(self, database, **kwargs): + super().__init__(database, **kwargs) + self._db = database + + def _connect(self): + return self._db + + def create_tables(self, models, **options): + options['safe'] = False + tables = self._db.tables + models = [m for m in models if not m.__name__.lower() in tables] + super().create_tables(models, **options) + + def execute_sql(self, sql, params=None, commit=True): + with __exception_wrapper__: + cursor = self._db.execute(sql, params) + return cursor + + def last_insert_id(self, cursor, query_type=None): + # ~ debug('LAST_ID', cursor) + return 0 + + def rows_affected(self, cursor): + return self._db.rows_affected + + @property + def path(self): + return self._db.path + + +class BaseRow: + pass + + +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', + } + TYPES_DATE = ('SQL_TYPE_DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') + + def __init__(self, query): + self._query = query + self._meta = query.MetaData + self._cols = self._meta.ColumnCount + self._names = query.Columns.ElementNames + self._data = self._get_data() + + def __getitem__(self, index): + return self._data[index] + + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + try: + row = self._data[self._index] + except IndexError: + raise StopIteration + self._index += 1 + return row + + def _to_python(self, index): + type_field = self._meta.getColumnTypeName(index) + value = getattr(self._query, self.PY_TYPES[type_field])(index) + if type_field in self.TYPES_DATE: + value = _struct_to_date(value) + return value + + def _get_row(self): + row = BaseRow() + for i in range(1, self._cols + 1): + column_name = self._meta.getColumnName(i) + value = self._to_python(i) + setattr(row, column_name, value) + return row + + def _get_data(self): + data = [] + while self._query.next(): + row = self._get_row() + data.append(row) + return data + + @property + def tuples(self): + data = [tuple(r.__dict__.values()) for r in self._data] + return tuple(data) + + @property + def dicts(self): + data = [r.__dict__ for r in self._data] + return tuple(data) + + +class LOBase(object): + DB_TYPES = { + str: 'setString', + int: 'setInt', + float: 'setFloat', + bool: 'setBoolean', + Date: 'setDate', + Time: 'setTime', + DateTime: 'setTimestamp', + } + # ~ setArray + # ~ setBinaryStream + # ~ setBlob + # ~ setByte + # ~ setBytes + # ~ setCharacterStream + # ~ setClob + # ~ setNull + # ~ setObject + # ~ setObjectNull + # ~ setObjectWithInfo + # ~ setPropertyValue + # ~ setRef + + def __init__(self, obj, args={}): + self._obj = obj + self._type = BASE + self._dbc = create_instance('com.sun.star.sdb.DatabaseContext') + self._rows_affected = 0 + path = args.get('path', '') + self._path = _P(path) + self._name = self._path.name + if _P.exists(path): + if not self.is_registered: + self.register() + db = self._dbc.getByName(self.name) + else: + db = self._dbc.createInstance() + db.URL = 'sdbc:embedded:firebird' + db.DatabaseDocument.storeAsURL(self._path.url, ()) + self.register() + self._obj = db + self._con = db.getConnection('', '') + + def __contains__(self, item): + return item in self.tables + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self._name + + @property + def path(self): + return str(self._path) + + @property + def is_registered(self): + return self._dbc.hasRegisteredDatabase(self.name) + + @property + def tables(self): + tables = [t.Name.lower() for t in self._con.getTables()] + return tables + + @property + def rows_affected(self): + return self._rows_affected + + def register(self): + if not self.is_registered: + self._dbc.registerDatabaseLocation(self.name, self._path.url) + return + + def revoke(self, name): + self._dbc.revokeDatabaseLocation(name) + return True + + def save(self): + self.obj.DatabaseDocument.store() + self.refresh() + return + + def close(self): + self._con.close() + return + + def refresh(self): + self._con.getTables().refresh() + return + + def initialize(self, database_proxy, tables): + db = FirebirdDatabase(self) + database_proxy.initialize(db) + db.create_tables(tables) + return + + def _validate_sql(self, sql, params): + limit = ' LIMIT ' + for p in params: + sql = sql.replace('?', f"'{p}'", 1) + if limit in sql: + sql = sql.split(limit)[0] + sql = sql.replace('SELECT', f'SELECT FIRST {params[-1]}') + return sql + + def cursor(self, sql, params): + if sql.startswith('SELECT'): + sql = self._validate_sql(sql, params) + cursor = self._con.prepareStatement(sql) + return cursor + + if not params: + cursor = self._con.createStatement() + return cursor + + cursor = self._con.prepareStatement(sql) + for i, v in enumerate(params, 1): + t = type(v) + if not t in self.DB_TYPES: + error('Type not support') + debug((i, t, v, self.DB_TYPES[t])) + getattr(cursor, self.DB_TYPES[t])(i, v) + return cursor + + def execute(self, sql, params): + debug(sql, params) + cursor = self.cursor(sql, params) + + if sql.startswith('SELECT'): + result = cursor.executeQuery() + elif params: + result = cursor.executeUpdate() + self._rows_affected = result + self.save() + else: + result = cursor.execute(sql) + self.save() + + return result + + def select(self, sql): + debug('SELECT', sql) + if not sql.startswith('SELECT'): + return () + + cursor = self._con.prepareStatement(sql) + query = cursor.executeQuery() + return BaseQuery(query) + + def get_query(self, query): + sql, args = query.sql() + sql = self._validate_sql(sql, args) + return self.select(sql) + + +class LOMath(LODocument): + + def __init__(self, obj): + super().__init__(obj) + self._type = MATH + + +class LOBasic(LODocument): + + def __init__(self, obj): + super().__init__(obj) + self._type = BASIC + + +class LODocs(object): + _desktop = None + + def __init__(self): + self._desktop = get_desktop() + LODocs._desktop = self._desktop + + def __getitem__(self, index): + document = None + for i, doc in enumerate(self._desktop.Components): + if isinstance(index, int) and i == index: + document = _get_class_doc(doc) + break + elif isinstance(index, str) and doc.Title == index: + document = _get_class_doc(doc) + break + return document + + def __contains__(self, item): + doc = self[item] + return not doc is None + + def __iter__(self): + self._i = -1 + return self + + def __next__(self): + self._i += 1 + doc = self[self._i] + if doc is None: + raise StopIteration + else: + return doc + + def __len__(self): + for i, _ in enumerate(self._desktop.Components): + pass + return i + 1 + + @property + def active(self): + return _get_class_doc(self._desktop.getCurrentComponent()) + + @classmethod + def new(cls, type_doc=CALC, args={}): + if type_doc == BASE: + return LOBase(None, args) + + path = f'private:factory/s{type_doc}' + opt = dict_to_property(args) + doc = cls._desktop.loadComponentFromURL(path, '_default', 0, opt) + return _get_class_doc(doc) + + @classmethod + def open(cls, path, args={}): + """ Open document in path + Usually options: + Hidden: True or False + AsTemplate: True or False + ReadOnly: True or False + Password: super_secret + MacroExecutionMode: 4 = Activate macros + Preview: True or False + + http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1frame_1_1XComponentLoader.html + http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1document_1_1MediaDescriptor.html + """ + path = _P.to_url(path) + opt = dict_to_property(args) + doc = cls._desktop.loadComponentFromURL(path, '_default', 0, opt) + if doc is None: + return + + return _get_class_doc(doc) + + def connect(self, path): + return LOBase(None, {'path': path}) + + +def _add_listeners(events, control, name=''): + listeners = { + 'addActionListener': EventsButton, + 'addMouseListener': EventsMouse, + 'addFocusListener': EventsFocus, + 'addItemListener': EventsItem, + 'addKeyListener': EventsKey, + 'addTabListener': EventsTab, + } + if hasattr(control, 'obj'): + control = control.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' + is_pages = control.ImplementationName == 'stardiv.Toolkit.UnoMultiPageControl' + + for key, value in listeners.items(): + if hasattr(control, key): + if is_grid and key == 'addMouseListener': + 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)) + + if is_grid: + controllers = EventsGrid(events, name) + control.addSelectionListener(controllers) + control.Model.GridDataModel.addGridDataListener(controllers) + return + + +def _set_properties(model, properties): + if 'X' in properties: + properties['PositionX'] = properties.pop('X') + if 'Y' in properties: + properties['PositionY'] = properties.pop('Y') + keys = tuple(properties.keys()) + values = tuple(properties.values()) + model.setPropertyValues(keys, values) + return + + +class EventsListenerBase(unohelper.Base, XEventListener): + + def __init__(self, controller, name, window=None): + self._controller = controller + self._name = name + self._window = window + + @property + def name(self): + return self._name + + def disposing(self, event): + self._controller = None + if not self._window is None: + self._window.setMenuBar(None) + + +class EventsMouse(EventsListenerBase, XMouseListener, XMouseMotionListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def mousePressed(self, event): + event_name = '{}_click'.format(self._name) + if event.ClickCount == 2: + event_name = '{}_double_click'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def mouseReleased(self, event): + pass + + def mouseEntered(self, event): + pass + + def mouseExited(self, event): + pass + + # ~ XMouseMotionListener + def mouseMoved(self, event): + pass + + def mouseDragged(self, event): + pass + + +class EventsMouseLink(EventsMouse): + + def __init__(self, controller, name): + super().__init__(controller, name) + self._text_color = 0 + + def mouseEntered(self, event): + model = event.Source.Model + self._text_color = model.TextColor or 0 + model.TextColor = get_color('blue') + return + + def mouseExited(self, event): + model = event.Source.Model + model.TextColor = self._text_color + return + + +class EventsButton(EventsListenerBase, XActionListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def actionPerformed(self, event): + event_name = f'{self.name}_action' + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + +class EventsFocus(EventsListenerBase, XFocusListener): + CONTROLS = ( + 'stardiv.Toolkit.UnoControlEditModel', + ) + + def __init__(self, controller, name): + super().__init__(controller, name) + + def focusGained(self, event): + service = event.Source.Model.ImplementationName + # ~ print('Focus enter', service) + if service in self.CONTROLS: + obj = event.Source.Model + obj.BackgroundColor = COLOR_ON_FOCUS + return + + def focusLost(self, event): + service = event.Source.Model.ImplementationName + if service in self.CONTROLS: + obj = event.Source.Model + obj.BackgroundColor = -1 + return + + +class EventsKey(EventsListenerBase, XKeyListener): + """ + event.KeyChar + event.KeyCode + event.KeyFunc + 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) + # ~ else: + # ~ if event.KeyFunc == QUIT and hasattr(self._cls, 'close'): + # ~ self._cls.close() + return + + +class EventsItem(EventsListenerBase, XItemListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def disposing(self, event): + pass + + def itemStateChanged(self, event): + event_name = '{}_item_changed'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + +class EventsItemRoadmap(EventsItem): + + def itemStateChanged(self, event): + dialog = event.Source.Context.Model + dialog.Step = event.ItemId + 1 + return + + +class EventsGrid(EventsListenerBase, XGridDataListener, XGridSelectionListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def dataChanged(self, event): + event_name = '{}_data_changed'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def rowHeadingChanged(self, event): + pass + + def rowsInserted(self, event): + pass + + def rowsRemoved(self, evemt): + pass + + def selectionChanged(self, event): + event_name = '{}_selection_changed'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + +class EventsMouseGrid(EventsMouse): + selected = False + + def mousePressed(self, event): + super().mousePressed(event) + # ~ obj = event.Source + # ~ col = obj.getColumnAtPoint(event.X, event.Y) + # ~ row = obj.getRowAtPoint(event.X, event.Y) + # ~ print(col, row) + # ~ if col == -1 and row == -1: + # ~ if self.selected: + # ~ obj.deselectAllRows() + # ~ else: + # ~ obj.selectAllRows() + # ~ self.selected = not self.selected + 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) + return + + +class EventsTab(EventsListenerBase, XTabListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def activated(self, id): + event_name = '{}_activated'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(id) + return + + +class EventsMenu(EventsListenerBase, XMenuListener): + + def __init__(self, controller): + super().__init__(controller, '') + + def itemHighlighted(self, event): + pass + + def itemSelected(self, event): + name = event.Source.getCommand(event.MenuId) + if name.startswith('menu'): + event_name = '{}_selected'.format(name) + else: + event_name = 'menu_{}_selected'.format(name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def itemActivated(self, event): + return + + def itemDeactivated(self, event): + return + + +class EventsWindow(EventsListenerBase, XTopWindowListener, XWindowListener): + + def __init__(self, cls): + self._cls = cls + super().__init__(cls.events, cls.name, cls._window) + + def windowOpened(self, event): + event_name = '{}_opened'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def windowActivated(self, event): + control_name = '{}_activated'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + def windowDeactivated(self, event): + control_name = '{}_deactivated'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + def windowMinimized(self, event): + pass + + def windowNormalized(self, event): + pass + + def windowClosing(self, event): + if self._window: + control_name = 'window_closing' + else: + control_name = '{}_closing'.format(event.Source.Model.Name) + + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + # ~ else: + # ~ if not self._modal and not self._block: + # ~ event.Source.Visible = False + return + + def windowClosed(self, event): + control_name = '{}_closed'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + # ~ XWindowListener + def windowResized(self, event): + sb = self._cls._subcont + sb.setPosSize(0, 0, event.Width, event.Height, SIZE) + event_name = '{}_resized'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def windowMoved(self, event): + pass + + def windowShown(self, event): + pass + + def windowHidden(self, event): + pass + + +# ~ BorderColor = ? +# ~ FontStyleName = ? +# ~ HelpURL = ? +class UnoBaseObject(object): + + def __init__(self, obj, path=''): + self._obj = obj + self._model = obj.Model + + def __setattr__(self, name, value): + exists = hasattr(self, name) + if not exists and not name in ('_obj', '_model'): + setattr(self._model, name, value) + else: + super().__setattr__(name, value) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._model + @property + def m(self): + return self._model + + @property + def properties(self): + return {} + @properties.setter + def properties(self, values): + _set_properties(self.model, values) + + @property + def name(self): + return self.model.Name + + @property + def parent(self): + return self.obj.Context + + @property + def tag(self): + return self.model.Tag + @tag.setter + 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 + @step.setter + def step(self, value): + self.model.Step = value + + @property + def align(self): + return self.model.Align + @align.setter + def align(self, value): + self.model.Align = value + + @property + def valign(self): + return self.model.VerticalAlign + @valign.setter + def valign(self, value): + self.model.VerticalAlign = value + + @property + def font_weight(self): + return self.model.FontWeight + @font_weight.setter + def font_weight(self, value): + self.model.FontWeight = value + + @property + def font_height(self): + return self.model.FontHeight + @font_height.setter + def font_height(self, value): + self.model.FontHeight = value + + @property + def font_name(self): + return self.model.FontName + @font_name.setter + def font_name(self, value): + self.model.FontName = value + + @property + def font_underline(self): + return self.model.FontUnderline + @font_underline.setter + def font_underline(self, value): + self.model.FontUnderline = value + + @property + def text_color(self): + return self.model.TextColor + @text_color.setter + def text_color(self, value): + self.model.TextColor = value + + @property + def back_color(self): + return self.model.BackgroundColor + @back_color.setter + def back_color(self, value): + self.model.BackgroundColor = value + + @property + def multi_line(self): + return self.model.MultiLine + @multi_line.setter + def multi_line(self, value): + self.model.MultiLine = value + + @property + def help_text(self): + return self.model.HelpText + @help_text.setter + def help_text(self, value): + self.model.HelpText = value + + @property + def border(self): + return self.model.Border + @border.setter + def border(self, value): + # ~ Bug for report + self.model.Border = value + + @property + def width(self): + return self._model.Width + @width.setter + def width(self, value): + self.model.Width = value + + @property + def height(self): + return self.model.Height + @height.setter + def height(self, value): + self.model.Height = value + + def _get_possize(self, name): + ps = self.obj.getPosSize() + return getattr(ps, name) + + def _set_possize(self, name, value): + ps = self.obj.getPosSize() + setattr(ps, name, value) + self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, POSSIZE) + return + + @property + def x(self): + if hasattr(self.model, 'PositionX'): + return self.model.PositionX + return self._get_possize('X') + @x.setter + def x(self, value): + if hasattr(self.model, 'PositionX'): + self.model.PositionX = value + else: + self._set_possize('X', value) + + @property + def y(self): + if hasattr(self.model, 'PositionY'): + return self.model.PositionY + return self._get_possize('Y') + @y.setter + def y(self, value): + if hasattr(self.model, 'PositionY'): + self.model.PositionY = value + else: + self._set_possize('Y', value) + + @property + def tab_index(self): + return self._model.TabIndex + @tab_index.setter + def tab_index(self, value): + self.model.TabIndex = value + + @property + def tab_stop(self): + return self._model.Tabstop + @tab_stop.setter + def tab_stop(self, value): + self.model.Tabstop = value + + @property + def ps(self): + ps = self.obj.getPosSize() + return ps + @ps.setter + def ps(self, ps): + self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, POSSIZE) + + def set_focus(self): + self.obj.setFocus() + return + + def ps_from(self, source): + self.ps = source.ps + return + + def center(self, horizontal=True, vertical=False): + p = self.parent.Model + w = p.Width + h = p.Height + if horizontal: + x = w / 2 - self.width / 2 + self.x = x + if vertical: + y = h / 2 - self.height / 2 + self.y = y + return + + def move(self, origin, x=0, y=5, center=False): + 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 + + if center: + self.center() + return + + +class UnoLabel(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'label' + + @property + def value(self): + return self.model.Label + @value.setter + def value(self, value): + 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): + super().__init__(obj) + + @property + def type(self): + return 'button' + + @property + def value(self): + return self.model.Label + @value.setter + def value(self, value): + self.model.Label = value + + +class UnoRadio(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'radio' + + @property + def value(self): + return self.model.Label + @value.setter + def value(self, value): + self.model.Label = value + + +class UnoCheckBox(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'checkbox' + + @property + def value(self): + return self.model.State + @value.setter + def value(self, value): + self.model.State = value + + @property + def label(self): + return self.model.Label + @label.setter + def label(self, value): + self.model.Label = value + + @property + def tri_state(self): + return self.model.TriState + @tri_state.setter + def tri_state(self, value): + self.model.TriState = value + + +# ~ https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1awt_1_1UnoControlEditModel.html +class UnoText(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'text' + + @property + def value(self): + return self.model.Text + @value.setter + def value(self, value): + self.model.Text = value + + def validate(self): + return + + +class UnoImage(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'image' + + @property + def value(self): + return self.url + @value.setter + def value(self, value): + self.url = value + + @property + def url(self): + return self.m.ImageURL + @url.setter + def url(self, value): + self.m.ImageURL = None + self.m.ImageURL = _P.to_url(value) + + +class UnoListBox(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._path = '' + + def __setattr__(self, name, value): + if name in ('_path',): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def type(self): + return 'listbox' + + @property + def value(self): + return self.obj.getSelectedItem() + + @property + def count(self): + return len(self.data) + + @property + def data(self): + return self.model.StringItemList + @data.setter + def data(self, values): + self.model.StringItemList = list(sorted(values)) + + @property + def path(self): + return self._path + @path.setter + def path(self, value): + self._path = value + + def unselect(self): + self.obj.selectItem(self.value, False) + 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.model.removeAllItems() + return + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_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 + + +class UnoRoadmap(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._options = () + + def __setattr__(self, name, value): + if name in ('_options',): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @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 + + +class UnoTree(UnoBaseObject): + + def __init__(self, obj, ): + super().__init__(obj) + self._tdm = None + self._data = [] + + def __setattr__(self, name, value): + if name in ('_tdm', '_data'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def selection(self): + sel = self.obj.Selection + return sel.DataValue, sel.DisplayValue + + @property + def parent(self): + parent = self.obj.Selection.Parent + if parent is None: + return () + return parent.DataValue, parent.DisplayValue + + def _get_parents(self, node): + value = (node.DisplayValue,) + parent = node.Parent + if parent is None: + return value + return self._get_parents(parent) + value + + @property + def parents(self): + values = self._get_parents(self.obj.Selection) + return values + + @property + def root(self): + if self._tdm is None: + return '' + return self._tdm.Root.DisplayValue + @root.setter + def root(self, value): + self._add_data_model(value) + + def _add_data_model(self, name): + tdm = create_instance('com.sun.star.awt.tree.MutableTreeDataModel') + root = tdm.createNode(name, True) + root.DataValue = 0 + tdm.setRoot(root) + self.model.DataModel = tdm + self._tdm = self.model.DataModel + return + + @property + def path(self): + return self.root + @path.setter + def path(self, value): + self.data = _P.walk_dir(value, True) + + @property + def data(self): + return self._data + @data.setter + def data(self, values): + self._data = list(values) + self._add_data() + + def _add_data(self): + if not self.data: + return + + parents = {} + for node in self.data: + parent = parents.get(node[1], self._tdm.Root) + child = self._tdm.createNode(node[2], False) + child.DataValue = node[0] + parent.appendChild(child) + parents[node[0]] = child + self.obj.expandNode(self._tdm.Root) + return + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1grid.html +class UnoGrid(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._gdm = self.model.GridDataModel + self._columns = [] + self._data = [] + # ~ self._format_columns = () + + def __setattr__(self, name, value): + if name in ('_gdm', '_columns', '_data'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __getitem__(self, key): + value = self._gdm.getCellData(key[0], key[1]) + return value + + def __setitem__(self, key, value): + self._gdm.updateCellData(key[0], key[1], value) + return + + @property + def type(self): + return 'grid' + + @property + def columns(self): + return self._columns + @columns.setter + def columns(self, values): + self._columns = values + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html + model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) + for properties in values: + column = create_instance('com.sun.star.awt.grid.GridColumn', True) + for k, v in properties.items(): + setattr(column, k, v) + model.addColumn(column) + self.model.ColumnModel = model + return + + @property + def data(self): + return self._data + @data.setter + def data(self, values): + self._data = values + self.clear() + headings = tuple(range(1, len(values) + 1)) + self._gdm.addRows(headings, values) + # ~ rows = range(grid_dm.RowCount) + # ~ colors = [COLORS['GRAY'] if r % 2 else COLORS['WHITE'] for r in rows] + # ~ grid.Model.RowBackgroundColors = tuple(colors) + return + + @property + def value(self): + if self.column == -1 or self.row == -1: + return '' + return self[self.column, self.row] + @value.setter + def value(self, value): + if self.column > -1 and self.row > -1: + self[self.column, self.row] = value + + @property + def row(self): + return self.obj.CurrentRow + + @property + def column(self): + return self.obj.CurrentColumn + + def clear(self): + self._gdm.removeAllRows() + return + + # UP + def _format_cols(self): + rows = tuple(tuple( + self._format_columns[i].format(r) for i, r in enumerate(row)) for row in self._data + ) + return rows + + # ~ @property + # ~ def format_columns(self): + # ~ return self._format_columns + # ~ @format_columns.setter + # ~ def format_columns(self, value): + # ~ self._format_columns = value + + # ~ @property + # ~ def rows(self): + # ~ return self._gdm.RowCount + + # ~ @property + # ~ def columns(self): + # ~ return self._gdm.ColumnCount + + def set_cell_tooltip(self, col, row, value): + self._gdm.updateCellToolTip(col, row, value) + return + + def get_cell_tooltip(self, col, row): + value = self._gdm.getCellToolTip(col, row) + return value + + def _validate_column(self, data): + row = [] + for i, d in enumerate(data): + if i in self._columns: + if 'image' in self._columns[i]: + row.append(self._columns[i]['image']) + else: + row.append(d) + return tuple(row) + + def add_row(self, data): + # ~ self._data.append(data) + data = self._validate_column(data) + self._gdm.addRow(self.rows + 1, data) + return + + def remove_row(self, row): + self._gdm.removeRow(row) + # ~ del self._data[row] + self.update_row_heading() + return + + def update_row_heading(self): + for i in range(self.rows): + self._gdm.updateRowHeading(i, i + 1) + return + + def sort(self, column, asc=True): + self._gdm.sortByColumn(column, asc) + self.update_row_heading() + return + + def set_column_image(self, column, path): + gp = create_instance('com.sun.star.graphic.GraphicProvider') + data = dict_to_property({'URL': _path_url(path)}) + image = gp.queryGraphic(data) + if not column in self._columns: + self._columns[column] = {} + self._columns[column]['image'] = image + return + + +class UnoPage(object): + + def __init__(self, obj): + self._obj = obj + self._events = None + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._obj.Model + + # ~ @property + # ~ def id(self): + # ~ return self.m.TabPageID + + @property + def parent(self): + return self.obj.Context + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(UNO_MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self.obj.getControl(name) + _add_listeners(self._events, control, name) + control = UNO_CLASSES[tipo](control) + + if tipo in ('listbox',): + control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + return control + + +class UnoPages(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._sheets = [] + self._events = None + + def __setattr__(self, name, value): + if name in ('_sheets', '_events'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __getitem__(self, index): + name = index + if isinstance(index, int): + name = f'sheet{index}' + sheet = self.obj.getControl(name) + page = UnoPage(sheet) + page._events = self._events + return page + + @property + def type(self): + return 'pages' + + @property + def current(self): + return self.obj.ActiveTabID + @property + def active(self): + return self.current + + @property + def sheets(self): + return self._sheets + @sheets.setter + def sheets(self, values): + self._sheets = values + for i, title in enumerate(values): + sheet = self.m.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.m.insertByName(f'sheet{i + 1}', sheet) + return + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers + + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value + + def insert(self, title): + self._sheets.append(title) + id = len(self._sheets) + sheet = self.m.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.m.insertByName(f'sheet{id}', sheet) + return self[id] + + def remove(self, id): + self.obj.removeTab(id) + return + + def activate(self, id): + self.obj.activateTab(id) + return + + +UNO_CLASSES = { + 'label': UnoLabel, + 'link': UnoLabelLink, + 'button': UnoButton, + 'radio': UnoRadio, + 'checkbox': UnoCheckBox, + 'text': UnoText, + 'image': UnoImage, + 'listbox': UnoListBox, + 'roadmap': UnoRoadmap, + 'tree': UnoTree, + 'grid': UnoGrid, + 'pages': UnoPages, +} + +UNO_MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + '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', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + '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', +# ~ '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', + + +class LODialog(object): + SEPARATION = 5 + MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + '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', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', + } + + def __init__(self, args): + self._obj = self._create(args) + self._model = self.obj.Model + self._events = None + self._modal = True + self._controls = {} + self._color_on_focus = COLOR_ON_FOCUS + self._id = '' + self._path = '' + self._init_controls() + + def _create(self, args): + service = 'com.sun.star.awt.DialogProvider' + path = args.pop('Path', '') + if path: + dp = create_instance(service, True) + dlg = dp.createDialog(_P.to_url(path)) + return dlg + + if 'Location' in args: + name = args['Name'] + library = args.get('Library', 'Standard') + location = args.get('Location', 'application').lower() + if location == 'user': + location = 'application' + url = f'vnd.sun.star.script:{library}.{name}?location={location}' + if location == 'document': + dp = create_instance(service, args=docs.active.obj) + else: + dp = create_instance(service, True) + # ~ uid = docs.active.uid + # ~ url = f'vnd.sun.star.tdoc:/{uid}/Dialogs/{library}/{name}.xml' + dlg = dp.createDialog(url) + return dlg + + dlg = create_instance('com.sun.star.awt.UnoControlDialog', True) + model = create_instance('com.sun.star.awt.UnoControlDialogModel', True) + toolkit = create_instance('com.sun.star.awt.Toolkit', True) + _set_properties(model, args) + dlg.setModel(model) + dlg.setVisible(False) + dlg.createPeer(toolkit, None) + return dlg + + def _get_type_control(self, name): + name = name.split('.')[2] + types = { + 'UnoFixedTextControl': 'label', + 'UnoEditControl': 'text', + 'UnoButtonControl': 'button', + } + return types[name] + + def _init_controls(self): + for control in self.obj.getControls(): + tipo = self._get_type_control(control.ImplementationName) + name = control.Model.Name + control = UNO_CLASSES[tipo](control) + setattr(self, name, control) + return + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._model + + @property + def controls(self): + return self._controls + + @property + def path(self): + return self._path + @property + def id(self): + return self._id + @id.setter + def id(self, value): + self._id = value + self._path = _P.from_id(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 visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value + + @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 + @events.setter + def events(self, controllers): + self._events = controllers(self) + self._connect_listeners() + + @property + def color_on_focus(self): + return self._color_on_focus + @color_on_focus.setter + def color_on_focus(self, value): + self._color_on_focus = get_color(value) + + def _connect_listeners(self): + for control in self.obj.Controls: + _add_listeners(self.events, control, control.Model.Name) + return + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(self.MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self.obj.getControl(name) + _add_listeners(self.events, control, name) + control = UNO_CLASSES[tipo](control) + + if tipo in ('listbox',): + control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + self._controls[name] = control + return control + + def center(self, control, x=0, y=0): + w = self.width + h = self.height + + if isinstance(control, tuple): + wt = self.SEPARATION * -1 + for c in control: + wt += c.width + self.SEPARATION + x = w / 2 - wt / 2 + for c in control: + c.x = x + x = c.x + c.width + self.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 + + def open(self, modal=True): + self._modal = modal + if modal: + return self.obj.execute() + else: + self.visible = True + return + + def close(self, value=0): + if self._modal: + value = self.obj.endDialog(value) + else: + self.visible = False + self.obj.dispose() + return value + + +class LOSheets(object): + + def __getitem__(self, index): + return LODocs().active[index] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + +class LOCells(object): + + def __getitem__(self, index): + return LODocs().active.active[index] + + +class LOShortCut(object): +# ~ getKeyEventsByCommand + + def __init__(self, app): + self._app = app + self._scm = None + self._init_values() + + def _init_values(self): + name = 'com.sun.star.ui.GlobalAcceleratorConfiguration' + instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' + service = TYPE_DOC[self._app] + manager = create_instance(instance, True) + uicm = manager.getUIConfigurationManager(service) + self._scm = uicm.ShortCutManager + return + + def __contains__(self, item): + cmd = self._get_command(item) + return bool(cmd) + + def _get_key_event(self, command): + events = self._scm.AllKeyEvents + for event in events: + cmd = self._scm.getCommandByKeyEvent(event) + if cmd == command: + break + return event + + def _to_key_event(self, shortcut): + key_event = KeyEvent() + keys = shortcut.split('+') + for v in keys[:-1]: + key_event.Modifiers += MODIFIERS[v.lower()] + key_event.KeyCode = getattr(Key, keys[-1].upper()) + return key_event + + def _get_command(self, shortcut): + command = '' + key_event = self._to_key_event(shortcut) + try: + command = self._scm.getCommandByKeyEvent(key_event) + except NoSuchElementException: + debug(f'No exists: {shortcut}') + return command + + def add(self, shortcut, command): + if isinstance(command, dict): + command = _get_url_script(command) + key_event = self._to_key_event(shortcut) + self._scm.setKeyEvent(key_event, command) + self._scm.store() + return + + def reset(self): + self._scm.reset() + self._scm.store() + return + + def remove(self, shortcut): + key_event = self._to_key_event(shortcut) + try: + self._scm.removeKeyEvent(key_event) + self._scm.store() + except NoSuchElementException: + debug(f'No exists: {shortcut}') + return + + def remove_by_command(self, command): + if isinstance(command, dict): + command = _get_url_script(command) + try: + self._scm.removeCommandFromAllKeyEvents(command) + self._scm.store() + except NoSuchElementException: + debug(f'No exists: {command}') + return + + +class LOShortCuts(object): + + def __getitem__(self, index): + return LOShortCut(index) + + +class LOMenu(object): + + def __init__(self, app): + self._app = app + self._ui = None + self._pymenus = None + self._menu = None + self._menus = self._get_menus() + + def __getitem__(self, index): + if isinstance(index, int): + self._menu = self._menus[index] + else: + for menu in self._menus: + cmd = menu.get('CommandURL', '') + if MENUS[index.lower()] == cmd: + self._menu = menu + break + line = self._menu.get('CommandURL', '') + line += self._get_submenus(self._menu['ItemDescriptorContainer']) + return line + + def _get_menus(self): + instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' + service = TYPE_DOC[self._app] + manager = create_instance(instance, True) + self._ui = manager.getUIConfigurationManager(service) + self._pymenus = self._ui.getSettings(NODE_MENUBAR, True) + data = [] + for menu in self._pymenus: + data.append(data_to_dict(menu)) + return data + + def _get_info(self, menu): + line = menu.get('CommandURL', '') + line += self._get_submenus(menu['ItemDescriptorContainer']) + return line + + def _get_submenus(self, menu, level=1): + line = '' + for i, v in enumerate(menu): + data = data_to_dict(v) + cmd = data.get('CommandURL', '----------') + line += f'\n{" " * level}├─ ({i}) {cmd}' + submenu = data.get('ItemDescriptorContainer', None) + if not submenu is None: + line += self._get_submenus(submenu, level + 1) + return line + + def __str__(self): + info = '\n'.join([self._get_info(m) for m in self._menus]) + return info + + def _get_index_menu(self, menu, command): + index = -1 + for i, v in enumerate(menu): + data = data_to_dict(v) + cmd = data.get('CommandURL', '') + if cmd == command: + index = i + break + return index + + def insert(self, name, args): + idc = None + replace = False + command = args['CommandURL'] + label = args['Label'] + + self[name] + menu = self._menu['ItemDescriptorContainer'] + submenu = args.get('Submenu', False) + if submenu: + idc = self._ui.createSettings() + + index = self._get_index_menu(menu, command) + if index == -1: + if 'Index' in args: + index = args['Index'] + else: + index = self._get_index_menu(menu, args['After']) + 1 + else: + replace = True + + data = dict ( + CommandURL = command, + Label = label, + Style = 0, + Type = 0, + ItemDescriptorContainer = idc, + ) + self._save(menu, data, index, replace) + self._insert_submenu(idc, submenu) + return + + def _get_command(self, args): + shortcut = args.get('ShortCut', '') + cmd = args['CommandURL'] + if isinstance(cmd, dict): + cmd = _get_url_script(cmd) + if shortcut: + LOShortCut(self._app).add(shortcut, cmd) + return cmd + + def _insert_submenu(self, parent, menus): + for i, v in enumerate(menus): + submenu = v.pop('Submenu', False) + if submenu: + idc = self._ui.createSettings() + v['ItemDescriptorContainer'] = idc + v['Type'] = 0 + if v['Label'] == '-': + v['Type'] = 1 + else: + v['CommandURL'] = self._get_command(v) + self._save(parent, v, i) + if submenu: + self._insert_submenu(idc, submenu) + return + + def remove(self, name, command): + self[name] + menu = self._menu['ItemDescriptorContainer'] + index = self._get_index_menu(menu, command) + if index > -1: + uno.invoke(menu, 'removeByIndex', (index,)) + self._ui.replaceSettings(NODE_MENUBAR, self._pymenus) + self._ui.store() + return + + def _save(self, menu, properties, index, replace=False): + properties = dict_to_property(properties, True) + if replace: + uno.invoke(menu, 'replaceByIndex', (index, properties)) + else: + uno.invoke(menu, 'insertByIndex', (index, properties)) + self._ui.replaceSettings(NODE_MENUBAR, self._pymenus) + self._ui.store() + return + + +class LOMenus(object): + + def __getitem__(self, index): + return LOMenu(index) + + +class LOWindow(object): + EMPTY = """ + +""" + MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + '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', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', + } + + def __init__(self, args): + self._events = None + self._menu = None + self._container = None + self._model = None + self._id = '' + self._path = '' + self._obj = self._create(args) + + def _create(self, properties): + ps = ( + properties.get('X', 0), + properties.get('Y', 0), + properties.get('Width', 500), + properties.get('Height', 500), + ) + self._title = properties.get('Title', TITLE) + self._create_frame(ps) + self._create_container(ps) + self._create_subcontainer(ps) + # ~ self._create_splitter(ps) + return + + def _create_frame(self, ps): + service = 'com.sun.star.frame.TaskCreator' + tc = create_instance(service, True) + self._frame = tc.createInstanceWithArguments(( + NamedValue('FrameName', 'EasyMacroWin'), + NamedValue('PosSize', Rectangle(*ps)), + )) + self._window = self._frame.getContainerWindow() + self._toolkit = self._window.getToolkit() + desktop = get_desktop() + self._frame.setCreator(desktop) + desktop.getFrames().append(self._frame) + self._frame.Title = self._title + return + + def _create_container(self, ps): + service = 'com.sun.star.awt.UnoControlContainer' + self._container = create_instance(service, True) + service = 'com.sun.star.awt.UnoControlContainerModel' + model = create_instance(service, True) + model.BackgroundColor = get_color((225, 225, 225)) + self._container.setModel(model) + self._container.createPeer(self._toolkit, self._window) + self._container.setPosSize(*ps, POSSIZE) + self._frame.setComponent(self._container, None) + return + + def _create_subcontainer(self, ps): + service = 'com.sun.star.awt.ContainerWindowProvider' + cwp = create_instance(service, True) + + path_tmp = _P.save_tmp(self.EMPTY) + subcont = cwp.createContainerWindow( + _P.to_url(path_tmp), '', self._container.getPeer(), None) + _P.kill(path_tmp) + + subcont.setPosSize(0, 0, 500, 500, POSSIZE) + subcont.setVisible(True) + self._container.addControl('subcont', subcont) + self._subcont = subcont + self._model = subcont.Model + return + + def _create_popupmenu(self, menus): + menu = create_instance('com.sun.star.awt.PopupMenu', True) + for i, m in enumerate(menus): + label = m['label'] + cmd = m.get('event', '') + if not cmd: + cmd = label.lower().replace(' ', '_') + if label == '-': + menu.insertSeparator(i) + else: + menu.insertItem(i, label, m.get('style', 0), i) + menu.setCommand(i, cmd) + # ~ menu.setItemImage(i, path?, True) + menu.addMenuListener(EventsMenu(self.events)) + return menu + + def _create_menu(self, menus): + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMenu.html + #~ nItemId specifies the ID of the menu item to be inserted. + #~ aText specifies the label of the menu item. + #~ nItemStyle 0 = Standard, CHECKABLE = 1, RADIOCHECK = 2, AUTOCHECK = 4 + #~ nItemPos specifies the position where the menu item will be inserted. + self._menu = create_instance('com.sun.star.awt.MenuBar', True) + for i, m in enumerate(menus): + self._menu.insertItem(i, m['label'], m.get('style', 0), i) + cmd = m['label'].lower().replace(' ', '_') + self._menu.setCommand(i, cmd) + submenu = self._create_popupmenu(m['submenu']) + self._menu.setPopupMenu(i, submenu) + + self._window.setMenuBar(self._menu) + return + + def _add_listeners(self, control=None): + if self.events is None: + return + controller = EventsWindow(self) + self._window.addTopWindowListener(controller) + self._window.addWindowListener(controller) + # ~ self._container.addKeyListener(EventsKeyWindow(self)) + return + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(self.MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self._subcont.getControl(name) + _add_listeners(self.events, control, name) + control = UNO_CLASSES[tipo](control) + + # ~ if tipo in ('listbox',): + # ~ control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + return control + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers(self) + self._add_listeners() + + @property + def model(self): + return self._model + + @property + def width(self): + return self._container.Size.Width + + @property + def height(self): + return self._container.Size.Height + + @property + def name(self): + return self._title.lower().replace(' ', '_') + + def add_menu(self, menus): + self._create_menu(menus) + return + + def open(self): + self._window.setVisible(True) + return + + def close(self): + self._window.setMenuBar(None) + self._window.dispose() + self._frame.close(True) + return + + +def create_window(args): + return LOWindow(args) + + +class classproperty: + def __init__(self, method=None): + self.fget = method + + def __get__(self, instance, cls=None): + return self.fget(cls) + + def getter(self, method): + self.fget = method + return self + + +class ClipBoard(object): + SERVICE = 'com.sun.star.datatransfer.clipboard.SystemClipboard' + CLIPBOARD_FORMAT_TEXT = 'text/plain;charset=utf-16' + + class TextTransferable(unohelper.Base, XTransferable): + + def __init__(self, text): + df = DataFlavor() + df.MimeType = ClipBoard.CLIPBOARD_FORMAT_TEXT + df.HumanPresentableName = "encoded text utf-16" + self.flavors = (df,) + self._data = text + + def getTransferData(self, flavor): + return self._data + + def getTransferDataFlavors(self): + return self.flavors + + + @classmethod + def set(cls, value): + ts = cls.TextTransferable(value) + sc = create_instance(cls.SERVICE) + sc.setContents(ts, None) + return + + @classproperty + def contents(cls): + df = None + text = '' + sc = create_instance(cls.SERVICE) + transferable = sc.getContents() + data = transferable.getTransferDataFlavors() + for df in data: + if df.MimeType == cls.CLIPBOARD_FORMAT_TEXT: + break + if df: + text = transferable.getTransferData(df) + return text +_CB = ClipBoard + + +class Paths(object): + FILE_PICKER = 'com.sun.star.ui.dialogs.FilePicker' + + def __init__(self, path=''): + if path.startswith('file://'): + path = str(Path(uno.fileUrlToSystemPath(path)).resolve()) + self._path = Path(path) + + @property + def path(self): + return str(self._path.parent) + + @property + def file_name(self): + return self._path.name + + @property + def name(self): + return self._path.stem + + @property + def ext(self): + return self._path.suffix[1:] + + @property + def info(self): + return self.path, self.file_name, self.name, self.ext + + @property + def url(self): + return self._path.as_uri() + + @property + def size(self): + return self._path.stat().st_size + + @classproperty + def home(self): + return str(Path.home()) + + @classproperty + def documents(self): + return self.config() + + @classproperty + def temp_dir(self): + return tempfile.gettempdir() + + @classproperty + def python(self): + if IS_WIN: + path = self.join(self.config('Module'), PYTHON) + elif IS_MAC: + path = self.join(self.config('Module'), '..', 'Resources', PYTHON) + else: + path = sys.executable + return path + + @classmethod + def dir_tmp(self, only_name=False): + dt = tempfile.TemporaryDirectory() + if only_name: + dt = dt.name + return dt + + @classmethod + def tmp(cls, ext=''): + tmp = tempfile.NamedTemporaryFile(suffix=ext) + return tmp.name + + @classmethod + def save_tmp(cls, data): + path_tmp = cls.tmp() + cls.save(path_tmp, data) + return path_tmp + + @classmethod + def config(cls, name='Work'): + """ + Return de path name in config + http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1util_1_1XPathSettings.html + """ + path = create_instance('com.sun.star.util.PathSettings') + return cls.to_system(getattr(path, name)) + + @classmethod + def get(cls, init_dir='', filters: str=''): + """ + Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html + filters: 'xml' or 'txt,xml' + """ + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + file_picker = create_instance(cls.FILE_PICKER) + file_picker.setTitle(_('Select path')) + file_picker.setDisplayDirectory(init_dir) + file_picker.initialize((2,)) + if filters: + filters = [(f.upper(), f'*.{f.lower()}') for f in filters.split(',')] + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + path = '' + if file_picker.execute(): + path = cls.to_system(file_picker.getSelectedFiles()[0]) + return path + + @classmethod + def get_dir(cls, init_dir=''): + folder_picker = create_instance(cls.FILE_PICKER) + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + folder_picker.setTitle(_('Select directory')) + folder_picker.setDisplayDirectory(init_dir) + + path = '' + if folder_picker.execute(): + path = cls.to_system(folder_picker.getDisplayDirectory()) + return path + + @classmethod + def get_file(cls, init_dir: str='', filters: str='', multiple: bool=False): + """ + init_folder: folder default open + multiple: True for multiple selected + filters: 'xml' or 'xml,txt' + """ + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + + file_picker = create_instance(cls.FILE_PICKER) + file_picker.setTitle(_('Select file')) + file_picker.setDisplayDirectory(init_dir) + file_picker.setMultiSelectionMode(multiple) + + if filters: + filters = [(f.upper(), f'*.{f.lower()}') for f in filters.split(',')] + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + path = '' + if file_picker.execute(): + files = file_picker.getSelectedFiles() + path = [cls.to_system(f) for f in files] + if not multiple: + path = path[0] + return path + + @classmethod + def replace_ext(cls, path, new_ext): + p = Paths(path) + name = f'{p.name}.{new_ext}' + path = cls.join(p.path, name) + return path + + @classmethod + def exists(cls, path): + result = False + if path: + path = cls.to_system(path) + result = Path(path).exists() + return result + + @classmethod + def exists_app(cls, name_app): + return bool(shutil.which(name_app)) + + @classmethod + def open(cls, path): + if IS_WIN: + os.startfile(path) + else: + pid = subprocess.Popen(['xdg-open', path]).pid + return + + @classmethod + def is_dir(cls, path): + return Path(path).is_dir() + + @classmethod + def is_file(cls, path): + return Path(path).is_file() + + @classmethod + def join(cls, *paths): + return str(Path(paths[0]).joinpath(*paths[1:])) + + @classmethod + def save(cls, path, data, encoding='utf-8'): + result = bool(Path(path).write_text(data, encoding=encoding)) + return result + + @classmethod + def save_bin(cls, path, data): + result = bool(Path(path).write_bytes(data)) + return result + + @classmethod + def read(cls, path, encoding='utf-8'): + data = Path(path).read_text(encoding=encoding) + return data + + @classmethod + def read_bin(cls, path): + data = Path(path).read_bytes() + return data + + @classmethod + def to_url(cls, path): + if not path.startswith('file://'): + path = Path(path).as_uri() + return path + + @classmethod + def to_system(cls, path): + if path.startswith('file://'): + path = str(Path(uno.fileUrlToSystemPath(path)).resolve()) + return path + + @classmethod + def kill(cls, path): + result = True + p = Path(path) + + try: + if p.is_file(): + p.unlink() + elif p.is_dir(): + shutil.rmtree(path) + except OSError as e: + log.error(e) + result = False + + return result + + @classmethod + def files(cls, path, pattern='*'): + files = [str(p) for p in Path(path).glob(pattern) if p.is_file()] + return files + + @classmethod + def dirs(cls, path): + dirs = [str(p) for p in Path(path).iterdir() if p.is_dir()] + return dirs + + @classmethod + def walk(cls, 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 += [cls.join(folder, f) for f in files if pattern.search(f)] + else: + paths += [cls.join(folder, f) for f in files] + return paths + + @classmethod + def walk_dir(cls, path, tree=False): + folders = [] + if tree: + i = 0 + p = 0 + parents = {path: 0} + for root, dirs, _ in os.walk(path): + for name in dirs: + i += 1 + rn = cls.join(root, name) + if not rn in parents: + parents[rn] = i + folders.append((i, parents[root], name)) + else: + for root, dirs, _ in os.walk(path): + folders += [cls.join(root, name) for name in dirs] + return folders + + @classmethod + def from_id(cls, id_ext): + pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') + path = _P.to_system(pip.getPackageLocation(id_ext)) + return path + + @classmethod + def from_json(cls, path): + data = json.loads(cls.read(path)) + return data + + @classmethod + def to_json(cls, path, data): + data = json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True) + return cls.save(path, data) + + @classmethod + def from_csv(cls, path, args={}): + # ~ See https://docs.python.org/3.7/library/csv.html#csv.reader + with open(path) as f: + rows = tuple(csv.reader(f, **args)) + return rows + + @classmethod + def to_csv(cls, path, data, args={}): + with open(path, 'w') as f: + writer = csv.writer(f, **args) + writer.writerows(data) + return + + @classmethod + def zip(cls, source, target='', pwd=''): + path_zip = target + if not isinstance(source, (tuple, list)): + path, _, name, _ = _P(source).info + start = len(path) + 1 + if not target: + path_zip = f'{path}/{name}.zip' + + if isinstance(source, (tuple, list)): + files = [(f, f[len(_P(f).path)+1:]) for f in source] + elif _P.is_file(source): + files = ((source, source[start:]),) + else: + files = [(f, f[start:]) for f in _P.walk(source)] + + compression = zipfile.ZIP_DEFLATED + with zipfile.ZipFile(path_zip, 'w', compression=compression) as z: + for f in files: + z.write(f[0], f[1]) + return + + @classmethod + def zip_content(cls, path): + with zipfile.ZipFile(path) as z: + names = z.namelist() + return names + + @classmethod + def unzip(cls, source, target='', members=None, pwd=None): + path = target + if not target: + path = _P(source).path + with zipfile.ZipFile(source) as z: + if not pwd is None: + pwd = pwd.encode() + if isinstance(members, str): + members = (members,) + z.extractall(path, members=members, pwd=pwd) + return True + + @classmethod + def merge_zip(cls, target, zips): + try: + with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as t: + for path in zips: + with zipfile.ZipFile(path, compression=zipfile.ZIP_DEFLATED) as s: + for name in s.namelist(): + t.writestr(name, s.open(name).read()) + except Exception as e: + error(e) + return False + + return True + + @classmethod + def copy(cls, source, target='', name=''): + p, f, n, e = _P(source).info + if target: + p = target + if name: + e = '' + n = name + path_new = cls.join(p, f'{n}{e}') + shutil.copy(source, path_new) + return path_new +_P = Paths + + +def __getattr__(name): + if name == 'active': + return LODocs().active + if name == 'active_sheet': + return LODocs().active.active + if name == 'selection': + return LODocs().active.selection + if name == 'current_region': + return LODocs().active.selection.current_region + if name in ('rectangle', 'pos_size'): + return Rectangle() + if name == 'paths': + return Paths + if name == 'docs': + return LODocs() + if name == 'sheets': + return LOSheets() + if name == 'cells': + return LOCells() + if name == 'menus': + return LOMenus() + if name == 'shortcuts': + return LOShortCuts() + if name == 'clipboard': + return ClipBoard + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + + +def create_dialog(args): + return LODialog(args) + + +def inputbox(message, default='', title=TITLE, echochar=''): + + class ControllersInput(object): + + def __init__(self, dlg): + self.d = dlg + + def cmd_ok_action(self, event): + self.d.close(1) + return + + args = { + 'Title': title, + 'Width': 200, + 'Height': 80, + } + dlg = LODialog(args) + dlg.events = ControllersInput + + args = { + 'Type': 'Label', + 'Name': 'lbl_msg', + 'Label': message, + 'Width': 140, + 'Height': 50, + 'X': 5, + 'Y': 5, + 'MultiLine': True, + 'Border': 1, + } + dlg.add_control(args) + + args = { + 'Type': 'Text', + 'Name': 'txt_value', + 'Text': default, + 'Width': 190, + 'Height': 15, + } + if echochar: + args['EchoChar'] = ord(echochar[0]) + dlg.add_control(args) + dlg.txt_value.move(dlg.lbl_msg) + + args = { + 'Type': 'button', + 'Name': 'cmd_ok', + 'Label': _('OK'), + 'Width': 40, + 'Height': 15, + 'DefaultButton': True, + 'PushButtonType': 1, + } + dlg.add_control(args) + dlg.cmd_ok.move(dlg.lbl_msg, 10, 0) + + args = { + 'Type': 'button', + 'Name': 'cmd_cancel', + 'Label': _('Cancel'), + 'Width': 40, + 'Height': 15, + 'PushButtonType': 2, + } + dlg.add_control(args) + dlg.cmd_cancel.move(dlg.cmd_ok) + + if dlg.open(): + return dlg.txt_value.value + + return '' + + +def get_fonts(): + toolkit = create_instance('com.sun.star.awt.Toolkit') + device = toolkit.createScreenCompatibleDevice(0, 0) + 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 = { + 'aliceblue': 15792383, + 'antiquewhite': 16444375, + 'aqua': 65535, + 'aquamarine': 8388564, + 'azure': 15794175, + 'beige': 16119260, + 'bisque': 16770244, + 'black': 0, + 'blanchedalmond': 16772045, + 'blue': 255, + 'blueviolet': 9055202, + 'brown': 10824234, + 'burlywood': 14596231, + 'cadetblue': 6266528, + 'chartreuse': 8388352, + 'chocolate': 13789470, + 'coral': 16744272, + 'cornflowerblue': 6591981, + 'cornsilk': 16775388, + 'crimson': 14423100, + 'cyan': 65535, + 'darkblue': 139, + 'darkcyan': 35723, + 'darkgoldenrod': 12092939, + 'darkgray': 11119017, + 'darkgreen': 25600, + 'darkgrey': 11119017, + 'darkkhaki': 12433259, + 'darkmagenta': 9109643, + 'darkolivegreen': 5597999, + 'darkorange': 16747520, + 'darkorchid': 10040012, + 'darkred': 9109504, + 'darksalmon': 15308410, + 'darkseagreen': 9419919, + 'darkslateblue': 4734347, + 'darkslategray': 3100495, + 'darkslategrey': 3100495, + 'darkturquoise': 52945, + 'darkviolet': 9699539, + 'deeppink': 16716947, + 'deepskyblue': 49151, + 'dimgray': 6908265, + 'dimgrey': 6908265, + 'dodgerblue': 2003199, + 'firebrick': 11674146, + 'floralwhite': 16775920, + 'forestgreen': 2263842, + 'fuchsia': 16711935, + 'gainsboro': 14474460, + 'ghostwhite': 16316671, + 'gold': 16766720, + 'goldenrod': 14329120, + 'gray': 8421504, + 'grey': 8421504, + 'green': 32768, + 'greenyellow': 11403055, + 'honeydew': 15794160, + 'hotpink': 16738740, + 'indianred': 13458524, + 'indigo': 4915330, + 'ivory': 16777200, + 'khaki': 15787660, + 'lavender': 15132410, + 'lavenderblush': 16773365, + 'lawngreen': 8190976, + 'lemonchiffon': 16775885, + 'lightblue': 11393254, + 'lightcoral': 15761536, + 'lightcyan': 14745599, + 'lightgoldenrodyellow': 16448210, + 'lightgray': 13882323, + 'lightgreen': 9498256, + 'lightgrey': 13882323, + 'lightpink': 16758465, + 'lightsalmon': 16752762, + 'lightseagreen': 2142890, + 'lightskyblue': 8900346, + 'lightslategray': 7833753, + 'lightslategrey': 7833753, + 'lightsteelblue': 11584734, + 'lightyellow': 16777184, + 'lime': 65280, + 'limegreen': 3329330, + 'linen': 16445670, + 'magenta': 16711935, + 'maroon': 8388608, + 'mediumaquamarine': 6737322, + 'mediumblue': 205, + 'mediumorchid': 12211667, + 'mediumpurple': 9662683, + 'mediumseagreen': 3978097, + 'mediumslateblue': 8087790, + 'mediumspringgreen': 64154, + 'mediumturquoise': 4772300, + 'mediumvioletred': 13047173, + 'midnightblue': 1644912, + 'mintcream': 16121850, + 'mistyrose': 16770273, + 'moccasin': 16770229, + 'navajowhite': 16768685, + 'navy': 128, + 'oldlace': 16643558, + 'olive': 8421376, + 'olivedrab': 7048739, + 'orange': 16753920, + 'orangered': 16729344, + 'orchid': 14315734, + 'palegoldenrod': 15657130, + 'palegreen': 10025880, + 'paleturquoise': 11529966, + 'palevioletred': 14381203, + 'papayawhip': 16773077, + 'peachpuff': 16767673, + 'peru': 13468991, + 'pink': 16761035, + 'plum': 14524637, + 'powderblue': 11591910, + 'purple': 8388736, + 'red': 16711680, + 'rosybrown': 12357519, + 'royalblue': 4286945, + 'saddlebrown': 9127187, + 'salmon': 16416882, + 'sandybrown': 16032864, + 'seagreen': 3050327, + 'seashell': 16774638, + 'sienna': 10506797, + 'silver': 12632256, + 'skyblue': 8900331, + 'slateblue': 6970061, + 'slategray': 7372944, + 'slategrey': 7372944, + 'snow': 16775930, + 'springgreen': 65407, + 'steelblue': 4620980, + 'tan': 13808780, + 'teal': 32896, + 'thistle': 14204888, + 'tomato': 16737095, + 'turquoise': 4251856, + 'violet': 15631086, + 'wheat': 16113331, + 'white': 16777215, + 'whitesmoke': 16119285, + 'yellow': 16776960, + 'yellowgreen': 10145074, + } + + if isinstance(value, tuple): + color = (value[0] << 16) + (value[1] << 8) + value[2] + else: + if value[0] == '#': + r, g, b = bytes.fromhex(value[1:]) + color = (r << 16) + (g << 8) + b + else: + color = COLORS.get(value.lower(), -1) + return color + + +COLOR_ON_FOCUS = get_color('LightYellow') + + +class LOServer(object): + HOST = 'localhost' + PORT = '8100' + ARG = f'socket,host={HOST},port={PORT};urp;StarOffice.ComponentContext' + CMD = ['soffice', + '-env:SingleAppInstance=false', + '-env:UserInstallation=file:///tmp/LO_Process8100', + '--headless', '--norestore', '--invisible', + f'--accept={ARG}'] + + def __init__(self): + self._server = None + self._ctx = None + self._sm = None + self._start_server() + self._init_values() + + def _init_values(self): + global CTX + global SM + + if not self.is_running: + return + + ctx = uno.getComponentContext() + service = 'com.sun.star.bridge.UnoUrlResolver' + resolver = ctx.ServiceManager.createInstanceWithContext(service, ctx) + self._ctx = resolver.resolve('uno:{}'.format(self.ARG)) + self._sm = self._ctx.getServiceManager() + CTX = self._ctx + SM = self._sm + return + + @property + def is_running(self): + try: + s = socket.create_connection((self.HOST, self.PORT), 5.0) + s.close() + debug('LibreOffice is running...') + return True + except ConnectionRefusedError: + return False + + def _start_server(self): + if self.is_running: + return + + for i in range(3): + self._server = subprocess.Popen(self.CMD, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + time.sleep(3) + if self.is_running: + break + return + + def stop(self): + if self._server is None: + print('Search pgrep soffice') + else: + self._server.terminate() + debug('LibreOffice is stop...') + return + + def create_instance(self, name, with_context=True): + if with_context: + instance = self._sm.createInstanceWithContext(name, self._ctx) + else: + instance = self._sm.createInstance(name) + return instance diff --git a/easymacro2.py b/easymacro2.py deleted file mode 120000 index d2de111..0000000 --- a/easymacro2.py +++ /dev/null @@ -1 +0,0 @@ -/home/mau/Projects/libre_office/zaz/source/easymacro2.py \ No newline at end of file diff --git a/files/ZAZLaTex2SVG_v0.1.0.oxt b/files/ZAZLaTex2SVG_v0.1.0.oxt index caf2286ed379fb0311ae891fb8dc7eaf70f01cb5..5f91c1df9159bb8a1d5cb5c9be7cc8be226ebf07 100644 GIT binary patch delta 29917 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

E-lEoq<a#OTTeJ7zt|-cJB4FZqOMJK3EH)-T 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 L`0DrZpW6Qcl({X_ delta 24004 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 CBTb+H diff --git a/source/ZAZLaTex2SVG.py b/source/ZAZLaTex2SVG.py index a787c96..4072fe5 100644 --- a/source/ZAZLaTex2SVG.py +++ b/source/ZAZLaTex2SVG.py @@ -1,7 +1,7 @@ import uno import unohelper from com.sun.star.task import XJobExecutor -import easymacro2 as app +import easymacro as app ID_EXTENSION = 'net.elmau.zaz.latex2svg' diff --git a/source/pythonpath/easymacro2.py b/source/pythonpath/easymacro.py similarity index 76% rename from source/pythonpath/easymacro2.py rename to source/pythonpath/easymacro.py index d11c7e2..30f0e40 100644 --- a/source/pythonpath/easymacro2.py +++ b/source/pythonpath/easymacro.py @@ -4,7 +4,7 @@ # ~ This file is part of ZAZ. -# ~ https://gitlab.com/mauriciobaeza/zaz +# ~ https://git.elmau.net/elmau/zaz # ~ ZAZ is free software: you can redistribute it and/or modify # ~ it under the terms of the GNU General Public License as published by @@ -19,11 +19,13 @@ # ~ You should have received a copy of the GNU General Public License # ~ along with ZAZ. If not, see . - +import base64 +import csv import datetime import getpass import gettext import hashlib +import json import logging import os import platform @@ -31,11 +33,14 @@ import re import shlex import shutil import socket +import ssl import subprocess import sys import tempfile import threading import time +import traceback +import zipfile from collections import OrderedDict from collections.abc import MutableMapping @@ -44,15 +49,29 @@ from enum import IntEnum from functools import wraps from pathlib import Path from pprint import pprint +from string import Template from typing import Any +from urllib.request import Request, urlopen +from urllib.error import URLError, HTTPError + +import smtplib +from smtplib import SMTPException, SMTPAuthenticationError +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email.mime.text import MIMEText +from email.utils import formatdate +from email import encoders +import mailbox import uno import unohelper from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS from com.sun.star.awt.MessageBoxResults import YES from com.sun.star.awt import Rectangle, Size, Point +from com.sun.star.awt.PosSize import POSSIZE from com.sun.star.awt import Key, KeyModifier, KeyEvent from com.sun.star.container import NoSuchElementException +from com.sun.star.datatransfer import XTransferable, DataFlavor from com.sun.star.beans import PropertyValue, NamedValue from com.sun.star.sheet import TableFilterField @@ -87,7 +106,14 @@ logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m') logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE) log = logging.getLogger(__name__) + +# ~ You can get custom salt +# ~ codecs.encode(os.urandom(16), 'hex') +SALT = b'c9548699d4e432dfd2b46adddafbb06d' + +TIMEOUT = 10 LOG_NAME = 'ZAZ' +FILE_NAME_CONFIG = 'zaz-{}.json' LEFT = 0 CENTER = 1 @@ -145,7 +171,12 @@ PC = platform.node() DESKTOP = os.environ.get('DESKTOP_SESSION', '') INFO_DEBUG = f"{sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path) +PYTHON = 'python' +if IS_WIN: + PYTHON = 'python.exe' + _MACROS = {} +_start = 0 SECONDS_DAY = 60 * 60 * 24 DIR = { @@ -191,6 +222,8 @@ MESSAGES = { 'es': { 'OK': 'Aceptar', 'Cancel': 'Cancelar', + 'Select path': 'Seleccionar ruta', + 'Select directory': 'Seleccionar directorio', 'Select file': 'Seleccionar archivo', 'Incorrect user or password': 'Nombre de usuario o contraseña inválidos', 'Allow less secure apps in GMail': 'Activa: Permitir aplicaciones menos segura en GMail', @@ -274,7 +307,7 @@ def catch_exception(f): except Exception as e: name = f.__name__ if IS_WIN: - debug(traceback.format_exc()) + msgbox(traceback.format_exc()) log.error(name, exc_info=True) return func @@ -372,19 +405,6 @@ def _get_class_doc(obj: Any) -> Any: return classes[type_doc](obj) -def _get_class_uno(obj: Any) -> Any: - classes = dict( - SwXTextGraphicObject = LOImage, - SvxShapeText = LOImage, - ) - name = obj.ImplementationName - print(f'ImplementationName = {name}') - instance = obj - if name in classes: - instance = classes[name](obj) - return instance - - def dict_to_property(values: dict, uno_any: bool=False): ps = tuple([PropertyValue(Name=n, Value=v) for n, v in values.items()]) if uno_any: @@ -402,6 +422,14 @@ def _property_to_dict(values): return d +def json_dumps(data): + return json.dumps(data, indent=4, sort_keys=True) + + +def json_loads(data): + return json.loads(data) + + def data_to_dict(data): if isinstance(data, tuple) and isinstance(data[0], tuple): return _array_to_dict(data) @@ -411,18 +439,6 @@ def data_to_dict(data): return {} -def _path_url(path: str) -> str: - if path.startswith('file://'): - return path - return uno.systemPathToFileUrl(path) - - -def _path_system(path: str) -> str: - if path.startswith('file://'): - return str(Path(uno.fileUrlToSystemPath(path)).resolve()) - return path - - def _get_dispatch() -> Any: return create_instance('com.sun.star.frame.DispatchHelper') @@ -519,9 +535,12 @@ def call_macro(args, in_thread=False): return result -def run(command, capture=False): +def run(command, capture=False, split=True): + if not split: + return subprocess.check_output(command, shell=True).decode() + cmd = shlex.split(command) - result = subprocess.run(cmd, capture_output=capture, text=True) + result = subprocess.run(cmd, capture_output=capture, text=True, shell=IS_WIN) if capture: result = result.stdout else: @@ -529,15 +548,15 @@ def run(command, capture=False): return result -# ~ 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 popen(command): + 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 sleep(seconds): @@ -607,6 +626,266 @@ def sha512(data): return result +def get_config(key='', default={}, prefix='conf'): + name_file = FILE_NAME_CONFIG.format(prefix) + values = None + path = _P.join(_P.config('UserConfig'), name_file) + if not _P.exists(path): + return default + + values = _P.from_json(path) + if key: + values = values.get(key, default) + + return values + + +def set_config(key, value, prefix='conf'): + name_file = FILE_NAME_CONFIG.format(prefix) + path = _P.join(_P.config('UserConfig'), name_file) + values = get_config(default={}, prefix=prefix) + values[key] = value + result = _P.to_json(path, values) + return result + + +def start(): + global _start + _start = now() + info(_start) + return + + +def end(get_seconds=False): + global _start + e = now() + td = e - _start + result = str(td) + if get_seconds: + result = td.total_seconds() + return result + + +def get_epoch(): + n = now() + return int(time.mktime(n.timetuple())) + + +def render(template, data): + s = Template(template) + return s.safe_substitute(**data) + + +def get_size_screen(): + if IS_WIN: + user32 = ctypes.windll.user32 + res = f'{user32.GetSystemMetrics(0)}x{user32.GetSystemMetrics(1)}' + else: + args = 'xrandr | grep "*" | cut -d " " -f4' + res = run(args, split=False) + return res.strip() + + +def url_open(url, data=None, headers={}, verify=True, get_json=False): + err = '' + req = Request(url) + for k, v in headers.items(): + req.add_header(k, v) + try: + # ~ debug(url) + if verify: + if not data is None and isinstance(data, str): + data = data.encode() + response = urlopen(req, data=data) + 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: + headers = dict(response.info()) + result = response.read() + if get_json: + result = json.loads(result) + + return result, headers, err + + +def _get_key(password): + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + + kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=SALT, + iterations=100000) + key = base64.urlsafe_b64encode(kdf.derive(password.encode())) + return key + + +def encrypt(data, password): + from cryptography.fernet import Fernet + + f = Fernet(_get_key(password)) + if isinstance(data, str): + data = data.encode() + token = f.encrypt(data).decode() + return token + + +def decrypt(token, password): + from cryptography.fernet import Fernet, InvalidToken + + data = '' + f = Fernet(_get_key(password)) + try: + data = f.decrypt(token.encode()).decode() + except InvalidToken as e: + error('Invalid Token') + return data + + +class SmtpServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._sender = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + name = config['server'] + port = config['port'] + is_ssl = config['ssl'] + self._sender = config['user'] + hosts = ('gmail' in name or 'outlook' in name) + try: + if is_ssl and hosts: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + self._server.ehlo() + self._server.starttls() + self._server.ehlo() + elif is_ssl: + self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT) + self._server.ehlo() + else: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + + self._server.login(self._sender, config['password']) + msg = 'Connect to: {}'.format(name) + debug(msg) + return True + except smtplib.SMTPAuthenticationError as e: + if '535' in str(e): + self._error = _('Incorrect user or password') + return False + if '534' in str(e) and 'gmail' in name: + self._error = _('Allow less secure apps in GMail') + return False + except smtplib.SMTPException as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def _body(self, msg): + body = msg.replace('\\n', '
') + return body + + def send(self, message): + file_name = 'attachment; filename={}' + email = MIMEMultipart() + email['From'] = self._sender + email['To'] = message['to'] + email['Cc'] = message.get('cc', '') + email['Subject'] = message['subject'] + email['Date'] = formatdate(localtime=True) + if message.get('confirm', False): + email['Disposition-Notification-To'] = email['From'] + email.attach(MIMEText(self._body(message['body']), 'html')) + + for path in message.get('files', ()): + fn = _P(path).file_name + part = MIMEBase('application', 'octet-stream') + part.set_payload(_P.read_bin(path)) + encoders.encode_base64(part) + part.add_header('Content-Disposition', f'attachment; filename={fn}') + email.attach(part) + + receivers = ( + email['To'].split(',') + + email['CC'].split(',') + + message.get('bcc', '').split(',')) + try: + self._server.sendmail(self._sender, receivers, email.as_string()) + msg = 'Email sent...' + debug(msg) + if message.get('path', ''): + self.save_message(email, message['path']) + return True + except Exception as e: + self._error = str(e) + return False + return False + + def save_message(self, email, path): + mbox = mailbox.mbox(path, create=True) + mbox.lock() + try: + msg = mailbox.mboxMessage(email) + mbox.add(msg) + mbox.flush() + finally: + mbox.unlock() + return + + def close(self): + try: + self._server.quit() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + +def _send_email(server, messages): + with SmtpServer(server) as server: + if server.is_connect: + for msg in messages: + server.send(msg) + else: + error(server.error) + return server.error + + +def send_email(server, message): + messages = message + if isinstance(message, dict): + messages = (message,) + t = threading.Thread(target=_send_email, args=(server, messages)) + t.start() + return + + # ~ Classes class LOBaseObject(object): @@ -681,6 +960,10 @@ class LOImage(object): class LODocument(object): + FILTERS = { + 'doc': 'MS Word 97', + 'docx': 'MS Word 2007 XML', + } def __init__(self, obj): self._obj = obj @@ -730,7 +1013,19 @@ class LODocument(object): @property def path(self): - return _path_system(self.obj.URL) + return _P.to_system(self.obj.URL) + + @property + def dir(self): + return _P(self.path).path + + @property + def file_name(self): + return _P(self.path).file_name + + @property + def name(self): + return _P(self.path).name @property def status_bar(self): @@ -767,12 +1062,21 @@ class LODocument(object): else: um.enterHiddenUndoContext() + def clear_undo(self): + self.obj.getUndoManager().clear() + return + @property def selection(self): sel = self.obj.CurrentSelection # ~ return _get_class_uno(sel) return sel + @property + def table_auto_formats(self): + taf = create_instance('com.sun.star.sheet.TableAutoFormats') + return taf.ElementNames + def create_instance(self, name): obj = self.obj.createInstance(name) return obj @@ -790,6 +1094,7 @@ class LODocument(object): sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') transferable = sc.getContents() self._cc.insertTransferable(transferable) + # ~ return self.obj.getCurrentSelection() return def select(self, obj): @@ -813,12 +1118,29 @@ class LODocument(object): return _P.exists(path_pdf) + def export(self, path: str, ext: str='', args: dict={}): + if not ext: + ext = _P(path).ext + filter_name = self.FILTERS[ext] + filter_data = dict_to_property(args, True) + args = { + 'FilterName': filter_name, + 'FilterData': filter_data, + } + opt = dict_to_property(args) + try: + self.obj.storeToURL(_P.to_url(path), opt) + except Exception as e: + error(e) + path = '' + return _P.exists(path) + def save(self, path: str='', args: dict={}) -> bool: result = True opt = dict_to_property(args) if path: try: - self.obj.storeAsURL(_path_url(path), opt) + self.obj.storeAsURL(_P.to_url(path), opt) except Exception as e: error(e) result = False @@ -831,6 +1153,64 @@ class LODocument(object): return +class LOCellStyle(LOBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def name(self): + return self.obj.Name + + @property + def properties(self): + properties = self.obj.PropertySetInfo.Properties + data = {p.Name: getattr(self.obj, p.Name) for p in properties} + return data + @properties.setter + def properties(self, values): + _set_properties(self.obj, values) + + +class LOCellStyles(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + + def __len__(self): + return len(self.obj) + + def __getitem__(self, index): + return LOCellStyle(self.obj[index]) + + def __setitem__(self, key, value): + self.obj[key] = value + + def __delitem__(self, key): + if not isinstance(key, str): + key = key.Name + del self.obj[key] + + def __contains__(self, item): + return item in self.obj + + @property + def obj(self): + return self._obj + + @property + def names(self): + return self.obj.ElementNames + + def new(self, name: str=''): + obj = self._doc.create_instance('com.sun.star.style.CellStyle') + if name: + self.obj[name] = obj + obj = LOCellStyle(obj) + return obj + + class LOCalc(LODocument): def __init__(self, obj): @@ -841,9 +1221,20 @@ class LOCalc(LODocument): def __getitem__(self, index): return LOCalcSheet(self._sheets[index]) + def __setitem__(self, key, value): + self._sheets[key] = value + def __len__(self): return self._sheets.Count + def __contains__(self, item): + return item in self._sheets + + @property + def names(self): + names = self.obj.Sheets.ElementNames + return names + @property def selection(self): sel = self.obj.CurrentSelection @@ -861,11 +1252,107 @@ class LOCalc(LODocument): def active(self): return LOCalcSheet(self._cc.ActiveSheet) + @property + def headers(self): + return self._cc.ColumnRowHeaders + @headers.setter + def headers(self, value): + self._cc.ColumnRowHeaders = value + + @property + def tabs(self): + return self._cc.SheetTabs + @tabs.setter + def tabs(self, value): + self._cc.SheetTabs = value + + @property + def cs(self): + return self.cell_styles + @property + def cell_styles(self): + obj = self.obj.StyleFamilies['CellStyles'] + return LOCellStyles(obj, self) + @property def db_ranges(self): # ~ return LOCalcDataBaseRanges(self.obj.DataBaseRanges) return self.obj.DatabaseRanges + def activate(self, sheet): + obj = sheet + if isinstance(sheet, LOCalcSheet): + obj = sheet.obj + elif isinstance(sheet, str): + obj = self._sheets[sheet] + self._cc.setActiveSheet(obj) + return + + def new_sheet(self): + s = self.create_instance('com.sun.star.sheet.Spreadsheet') + return s + + def insert(self, name): + names = name + if isinstance(name, str): + names = (name,) + for n in names: + self._sheets[n] = self.new_sheet() + return LOCalcSheet(self._sheets[n]) + + def move(self, name, pos=-1): + index = pos + if pos < 0: + index = len(self) + if isinstance(name, LOCalcSheet): + name = name.name + self._sheets.moveByName(name, index) + return + + def remove(self, name): + if isinstance(name, LOCalcSheet): + name = name.name + self._sheets.removeByName(name) + return + + def copy(self, name, new_name='', pos=-1): + if isinstance(name, LOCalcSheet): + name = name.name + index = pos + if pos < 0: + index = len(self) + self._sheets.copyByName(name, new_name, index) + return LOCalcSheet(self._sheets[new_name]) + + def copy_from(self, doc, source='', target='', pos=-1): + index = pos + if pos < 0: + index = len(self) + + names = source + if not source: + names = doc.names + elif isinstance(source, str): + names = (source,) + + new_names = target + if not target: + new_names = names + elif isinstance(target, str): + new_names = (target,) + + for i, name in enumerate(names): + self._sheets.importSheet(doc.obj, name, index + i) + self[index + i].name = new_names[i] + + return LOCalcSheet(self._sheets[index]) + + def sort(self, reverse=False): + names = sorted(self.names, reverse=reverse) + for i, n in enumerate(names): + self.move(n, i) + return + def render(self, data, sheet=None, clean=True): if sheet is None: sheet = self.active @@ -1081,6 +1568,9 @@ class LOCalcSheet(object): def __exit__(self, exc_type, exc_value, traceback): pass + def __str__(self): + return f'easymacro.LOCalcSheet: {self.name}' + @property def obj(self): return self._obj @@ -1092,6 +1582,46 @@ class LOCalcSheet(object): def name(self, value): self._obj.Name = value + @property + def code_name(self): + return self._obj.CodeName + @code_name.setter + def code_name(self, value): + self._obj.CodeName = value + + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def is_protected(self): + return self._obj.isProtected() + + @property + def password(self): + return '' + @visible.setter + def password(self, value): + self.obj.protect(value) + + def unprotect(self, value): + try: + self.obj.unprotect(value) + return True + except: + pass + return False + + @property + def color(self): + return self._obj.TabColor + @color.setter + def color(self, value): + self._obj.TabColor = get_color(value) + @property def used_area(self): cursor = self.get_cursor() @@ -1112,7 +1642,7 @@ class LOCalcSheet(object): @property def doc(self): - return self.obj.DrawPage.Forms.Parent + return LOCalc(self.obj.DrawPage.Forms.Parent) @property def charts(self): @@ -1126,6 +1656,47 @@ class LOCalcSheet(object): def forms(self): return LOSheetForms(self.obj.DrawPage.Forms) + def activate(self): + self.doc.activate(self._obj) + return + + def clean(self): + doc = self.doc + sheet = doc.create_instance('com.sun.star.sheet.Spreadsheet') + doc._sheets.replaceByName(self.name, sheet) + return + + def move(self, pos=-1): + index = pos + if pos < 0: + index = len(self.doc) + self.doc._sheets.moveByName(self.name, index) + return + + def remove(self): + self.doc._sheets.removeByName(self.name) + return + + def copy(self, new_name='', pos=-1): + index = pos + if pos < 0: + index = len(self.doc) + self.doc._sheets.copyByName(self.name, new_name, index) + return LOCalcSheet(self.doc._sheets[new_name]) + + def copy_to(self, doc, target='', pos=-1): + index = pos + if pos < 0: + index = len(doc) + name = self.name + if not target: + new_name = name + + doc._sheets.importSheet(self.doc.obj, name, index) + sheet = doc[name] + sheet.name = new_name + return sheet + def get_cursor(self, cell=None): if cell is None: cursor = self.obj.createCursor() @@ -1139,11 +1710,39 @@ class LOCalcSheet(object): return rango.render(data, clean) +class LOCalcRows(object): + + def __init__(self, obj): + self._obj = obj + + def __len__(self): + return self.obj.Count + + def __str__(self): + return 'Rows' + + @property + def obj(self): + return self._obj + + @property + def count(self): + return len(self) + + @property + def visible(self): + return self.obj.IsVisible + @visible.setter + def visible(self, value): + self.obj.IsVisible = value + + class LOCalcRange(object): def __init__(self, obj): self._obj = obj self._sd = None + self._is_cell = obj.ImplementationName == OBJ_CELL def __getitem__(self, index): return LOCalcRange(self.obj[index]) @@ -1185,6 +1784,17 @@ class LOCalcRange(object): def is_none(self): return self.obj is None + @property + def is_cell(self): + return self._is_cell + + @property + def back_color(self): + return self._obj.CellBackColor + @back_color.setter + def back_color(self, value): + self._obj.CellBackColor = get_color(value) + @property def dp(self): return self.sheet.dp @@ -1211,9 +1821,18 @@ class LOCalcRange(object): def columns(self): return self.obj.Columns.Count + @property + def column(self): + c1 = self.address.Column + c2 = c1 + 1 + ra = self.current_region.range_address + r1 = ra.StartRow + r2 = ra.EndRow + 1 + return LOCalcRange(self.sheet[r1:r2, c1:c2].obj) + @property def rows(self): - return self.obj.Rows.Count + return LOCalcRows(self.obj.Rows) @property def row(self): @@ -1284,7 +1903,22 @@ class LOCalcRange(object): return self.obj.getDataArray() @data.setter def data(self, values): - self.obj.setDataArray(values) + if self._is_cell: + self.to_size(len(values), len(values[0])).data = values + else: + self.obj.setDataArray(values) + + @property + def dict(self): + rows = self.data + k = rows[0] + data = [dict(zip(k, r)) for r in rows[1:]] + return data + @dict.setter + def dict(self, values): + data = [tuple(values[0].keys())] + data += [tuple(d.values()) for d in values] + self.data = data @property def formula(self): @@ -1344,6 +1978,13 @@ class LOCalcRange(object): } return data + @property + def visible(self): + cursor = self.cursor + rangos = cursor.queryVisibleCells() + rangos = [LOCalcRange(self.sheet[r.AbsoluteName].obj) for r in rangos] + return tuple(rangos) + def select(self): self.doc.select(self.obj) return @@ -1367,10 +2008,31 @@ class LOCalcRange(object): rango.data = self.data return + def copy_from(self, rango, formula=False): + data = rango + if isinstance(rango, LOCalcRange): + if formula: + data = rango.formula + else: + data = rango.data + rows = len(data) + cols = len(data[0]) + if formula: + self.to_size(rows, cols).formula = data + else: + self.to_size(rows, cols).data = data + return + def auto_width(self): self.obj.Columns.OptimalWidth = True return + def clean_render(self, template='\{(\w.+)\}'): + self._sd.SearchRegularExpression = True + self._sd.setSearchString(template) + self.obj.replaceAll(self._sd) + return + def render(self, data, clean=True): self._sd = self.sheet.obj.createSearchDescriptor() self._sd.SearchCaseSensitive = False @@ -1474,6 +2136,19 @@ class LOCalcRange(object): args.clear() return img + def filter_by_color(self, cell): + rangos = cell.column[1:,:].visible + for r in rangos: + for c in r: + if c.back_color != cell.back_color: + c.rows.visible = False + return + + def clear(self, what=1023): + # ~ http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html + self.obj.clearContents(what) + return + class LOWriterPageStyle(LOBaseObject): @@ -1512,6 +2187,18 @@ class LOWriterTextRange(object): self._is_paragraph = self.obj.ImplementationName == 'SwXParagraph' self._is_table = self.obj.ImplementationName == 'SwXTextTable' + 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 + @property def obj(self): return self._obj @@ -1533,7 +2220,7 @@ class LOWriterTextRange(object): @property def text(self): - return self.obj.getText() + return self.obj.Text @property def cursor(self): @@ -1543,6 +2230,10 @@ class LOWriterTextRange(object): def dp(self): return self._doc.dp + @property + def is_table(self): + return self._is_table + def offset(self): cursor = self.cursor.getEnd() return LOWriterTextRange(cursor, self._doc) @@ -1572,7 +2263,23 @@ class LOWriterTextRanges(object): self._doc = doc def __getitem__(self, index): - return LOWriterTextRange(self.obj[index], self._doc) + for i, p in enumerate(self.obj): + if i == index: + obj = LOWriterTextRange(p, self._doc) + break + return 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 @property def obj(self): @@ -1585,6 +2292,14 @@ class LOWriter(LODocument): super().__init__(obj) self._type = WRITER + @property + def text(self): + return LOWriterTextRange(self.obj.Text, self) + + @property + def paragraphs(self): + return LOWriterTextRanges(self.obj.Text, self) + @property def selection(self): sel = self.obj.CurrentSelection @@ -1690,6 +2405,13 @@ class LOShape(LOBaseObject): value = value.obj self.obj.Anchor = value + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value + def remove(self): self.obj.Parent.remove(self.obj) return @@ -1796,7 +2518,8 @@ class LODrawImpress(LODocument): @property def selection(self): sel = self.obj.CurrentSelection[0] - return _get_class_uno(sel) + # ~ return _get_class_uno(sel) + return sel @property def current_page(self): @@ -2206,7 +2929,7 @@ class LODocs(object): http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1frame_1_1XComponentLoader.html http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1document_1_1MediaDescriptor.html """ - path = _path_url(path) + path = _P.to_url(path) opt = dict_to_property(args) doc = cls._desktop.loadComponentFromURL(path, '_default', 0, opt) if doc is None: @@ -2372,9 +3095,10 @@ class EventsFocus(EventsListenerBase, XFocusListener): # ~ HelpURL = ? class UnoBaseObject(object): - def __init__(self, obj): + def __init__(self, obj, path=''): self._obj = obj self._model = obj.Model + # ~ self._path = path def __setattr__(self, name, value): exists = hasattr(self, name) @@ -2577,6 +3301,22 @@ class UnoBaseObject(object): def tab_stop(self, value): self.model.Tabstop = value + @property + def ps(self): + ps = self.obj.getPosSize() + return ps + @ps.setter + def ps(self, ps): + self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, POSSIZE) + + def set_focus(self): + self.obj.setFocus() + return + + def ps_from(self, source): + self.ps = source.ps + return + def center(self, horizontal=True, vertical=False): p = self.parent.Model w = p.Width @@ -2739,6 +3479,78 @@ class UnoImage(UnoBaseObject): self.m.ImageURL = _P.to_url(value) +class UnoListBox(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._path = '' + + def __setattr__(self, name, value): + if name in ('_path',): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def type(self): + return 'listbox' + + @property + def value(self): + return self.obj.getSelectedItem() + + @property + def count(self): + return len(self.data) + + @property + def data(self): + return self.model.StringItemList + @data.setter + def data(self, values): + self.model.StringItemList = list(sorted(values)) + + @property + def path(self): + return self._path + @path.setter + def path(self, value): + self._path = value + + def unselect(self): + self.obj.selectItem(self.value, False) + 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.model.removeAllItems() + return + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_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 + + UNO_CLASSES = { 'label': UnoLabel, 'link': UnoLabelLink, @@ -2747,6 +3559,7 @@ UNO_CLASSES = { 'check': UnoCheck, 'text': UnoText, 'image': UnoImage, + 'listbox': UnoListBox, } @@ -2760,12 +3573,11 @@ class LODialog(object): 'check': 'com.sun.star.awt.UnoControlCheckBoxModel', 'text': 'com.sun.star.awt.UnoControlEditModel', 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', # ~ 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', # ~ 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', - # ~ 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', # ~ 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', # ~ 'tree': 'com.sun.star.awt.tree.TreeControlModel', - # ~ 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', # ~ 'pages': 'com.sun.star.awt.UnoMultiPageModel', } @@ -2784,7 +3596,7 @@ class LODialog(object): path = args.pop('Path', '') if path: dp = create_instance(service, True) - dlg = dp.createDialog(_path_url(path)) + dlg = dp.createDialog(_P.to_url(path)) return dlg if 'Location' in args: @@ -2825,6 +3637,9 @@ class LODialog(object): return self._controls @property + def path(self): + return self._path + @property def id(self): return self._id @id.setter @@ -2853,6 +3668,13 @@ class LODialog(object): def visible(self, value): self.obj.Visible = value + @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 @@ -2918,6 +3740,8 @@ class LODialog(object): control = self.obj.getControl(name) _add_listeners(self.events, control, name) control = UNO_CLASSES[tipo](control) + if tipo in ('listbox',): + control.path = self.path if tipo == 'tree' and root: control.root = root @@ -3234,7 +4058,50 @@ class classproperty: return self +class ClipBoard(object): + SERVICE = 'com.sun.star.datatransfer.clipboard.SystemClipboard' + CLIPBOARD_FORMAT_TEXT = 'text/plain;charset=utf-16' + + class TextTransferable(unohelper.Base, XTransferable): + + def __init__(self, text): + df = DataFlavor() + df.MimeType = ClipBoard.CLIPBOARD_FORMAT_TEXT + df.HumanPresentableName = "encoded text utf-16" + self.flavors = (df,) + self._data = text + + def getTransferData(self, flavor): + return self._data + + def getTransferDataFlavors(self): + return self.flavors + + + @classmethod + def set(cls, value): + ts = cls.TextTransferable(value) + sc = create_instance(cls.SERVICE) + sc.setContents(ts, None) + return + + @classproperty + def contents(cls): + df = None + text = '' + sc = create_instance(cls.SERVICE) + transferable = sc.getContents() + data = transferable.getTransferDataFlavors() + for df in data: + if df.MimeType == cls.CLIPBOARD_FORMAT_TEXT: + break + if df: + text = transferable.getTransferData(df) + return text + + class Paths(object): + FILE_PICKER = 'com.sun.star.ui.dialogs.FilePicker' def __init__(self, path=''): if path.startswith('file://'): @@ -3255,7 +4122,7 @@ class Paths(object): @property def ext(self): - return self._path.suffix + return self._path.suffix[1:] @property def info(self): @@ -3265,14 +4132,32 @@ class Paths(object): def url(self): return self._path.as_uri() + @property + def size(self): + return self._path.stat().st_size + @classproperty def home(self): return str(Path.home()) + @classproperty + def documents(self): + return self.config() + @classproperty def temp_dir(self): return tempfile.gettempdir() + @classproperty + def python(self): + if IS_WIN: + path = self.join(self.config('Module'), PYTHON) + elif IS_MAC: + path = self.join(self.config('Module'), '..', 'Resources', PYTHON) + else: + path = sys.executable + return path + @classmethod def dir_tmp(self, only_name=False): dt = tempfile.TemporaryDirectory() @@ -3285,10 +4170,6 @@ class Paths(object): tmp = tempfile.NamedTemporaryFile(suffix=ext) return tmp.name - @classproperty - def python(self): - return sys.executable - @classmethod def config(cls, name='Work'): """ @@ -3298,6 +4179,80 @@ class Paths(object): path = create_instance('com.sun.star.util.PathSettings') return cls.to_system(getattr(path, name)) + @classmethod + def get(cls, init_dir='', filters=()): + """ + Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html + filters: Example + ( + ('XML', '*.xml'), + ('TXT', '*.txt'), + ) + """ + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + file_picker = create_instance(cls.FILE_PICKER) + file_picker.setTitle(_('Select path')) + file_picker.setDisplayDirectory(init_dir) + file_picker.initialize((2,)) + if filters: + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + path = '' + if file_picker.execute(): + path = cls.to_system(file_picker.getSelectedFiles()[0]) + return path + + @classmethod + def get_dir(cls, init_dir=''): + folder_picker = create_instance(cls.FILE_PICKER) + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + folder_picker.setTitle(_('Select directory')) + folder_picker.setDisplayDirectory(init_dir) + + path = '' + if folder_picker.execute(): + path = cls.to_system(folder_picker.getDisplayDirectory()) + return path + + @classmethod + def get_file(cls, init_dir='', filters=(), multiple=False): + """ + init_folder: folder default open + multiple: True for multiple selected + filters: Example + ( + ('XML', '*.xml'), + ('TXT', '*.txt'), + ) + """ + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + + file_picker = create_instance(cls.FILE_PICKER) + file_picker.setTitle(_('Select file')) + file_picker.setDisplayDirectory(init_dir) + file_picker.setMultiSelectionMode(multiple) + + if filters: + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + path = '' + if file_picker.execute(): + files = file_picker.getSelectedFiles() + path = [cls.to_system(f) for f in files] + if not multiple: + path = path[0] + return path + @classmethod def replace_ext(cls, path, new_ext): p = Paths(path) @@ -3347,6 +4302,16 @@ class Paths(object): result = bool(Path(path).write_bytes(data)) return result + @classmethod + def read(cls, path, encoding='utf-8'): + data = Path(path).read_text(encoding=encoding) + return data + + @classmethod + def read_bin(cls, path): + data = Path(path).read_bytes() + return data + @classmethod def to_url(cls, path): if not path.startswith('file://'): @@ -3394,6 +4359,71 @@ class Paths(object): path = _P.to_system(pip.getPackageLocation(id_ext)) return path + @classmethod + def from_json(cls, path): + data = json.loads(cls.read(path)) + return data + + @classmethod + def to_json(cls, path, data): + data = json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True) + return cls.save(path, data) + + @classmethod + def from_csv(cls, path, args={}): + # ~ See https://docs.python.org/3.7/library/csv.html#csv.reader + with open(path) as f: + rows = tuple(csv.reader(f, **args)) + return rows + + @classmethod + def to_csv(cls, path, data, args={}): + with open(path, 'w') as f: + writer = csv.writer(f, **args) + writer.writerows(data) + return + + @classmethod + def zip(cls, source, target='', pwd=''): + path_zip = target + if not isinstance(source, (tuple, list)): + path, _, name, _ = _P(source).info + start = len(path) + 1 + if not target: + path_zip = f'{path}/{name}.zip' + + if isinstance(source, (tuple, list)): + files = [(f, f[len(_P(f).path)+1:]) for f in source] + elif _P.is_file(source): + files = ((source, source[start:]),) + else: + files = [(f, f[start:]) for f in _P.walk(source)] + + compression = zipfile.ZIP_DEFLATED + with zipfile.ZipFile(path_zip, 'w', compression=compression) as z: + for f in files: + z.write(f[0], f[1]) + return + + @classmethod + def zip_content(cls, path): + with zipfile.ZipFile(path) as z: + names = z.namelist() + return names + + @classmethod + def unzip(cls, source, target='', members=None, pwd=None): + path = target + if not target: + path = _P(source).path + with zipfile.ZipFile(source) as z: + if not pwd is None: + pwd = pwd.encode() + if isinstance(members, str): + members = (members,) + z.extractall(path, members=members, pwd=pwd) + return True + @classmethod def copy(cls, source, target='', name=''): p, f, n, e = _P(source).info @@ -3415,6 +4445,8 @@ def __getattr__(name): return LODocs().active.active if name == 'selection': return LODocs().active.selection + if name == 'current_region': + return LODocs().active.selection.current_region if name in ('rectangle', 'pos_size'): return Rectangle() if name == 'paths': @@ -3429,6 +4461,8 @@ def __getattr__(name): return LOMenus() if name == 'shortcuts': return LOShortCuts() + if name == 'clipboard': + return ClipBoard raise AttributeError(f"module '{__name__}' has no attribute '{name}'") @@ -3792,4 +4826,3 @@ class LOServer(object): else: instance = self._sm.createInstance(name) return instance - diff --git a/zaz.py b/zaz.py index 0953e17..667fe97 100755 --- a/zaz.py +++ b/zaz.py @@ -4,6 +4,8 @@ # ~ This file is part of ZAZ. +# ~ https://git.elmau.net/elmau/zaz + # ~ ZAZ is free software: you can redistribute it and/or modify # ~ it under the terms of the GNU General Public License as published by # ~ the Free Software Foundation, either version 3 of the License, or @@ -44,6 +46,10 @@ from conf import ( log) +EASYMACRO_TMP = 'easymacro2.py' +EASYMACRO = 'easymacro.py' + + class LiboXML(object): CONTEXT = { 'calc': 'com.sun.star.sheet.SpreadsheetDocument', @@ -549,7 +555,7 @@ def _update_files(): copyfile(source, target) if FILES['easymacro']: - source = 'easymacro2.py' + source = EASYMACRO target = _join(path_source, 'pythonpath', source) copyfile(source, target) @@ -595,7 +601,7 @@ def _update_files(): return -def _new(): +def _create(): if not _validate_new(): return @@ -617,7 +623,6 @@ def _get_info_path(path): def _zip_embed(source, files): PATH = 'Scripts/python/' - EASYMACRO = 'easymacro2.py' FILE_PYC = 'easymacro.pyc' p, f, name, e = _get_info_path(source) @@ -693,8 +698,6 @@ def _embed(args): def _locales(args): - EASYMACRO = 'easymacro2.py' - if args.files: files = args.files.split(',') else: @@ -726,8 +729,39 @@ def _update(): return +def _new(args): + if not args.target: + msg = 'Add argument target: -t PATH_TARGET' + log.error(msg) + return + + if not args.name: + msg = 'Add argument name: -n name-new-extension' + log.error(msg) + return + + path = _join(args.target, args.name) + _mkdir(path) + _mkdir(_join(path, 'files')) + _mkdir(_join(path, 'images')) + path_logo = 'images/pymacros.png' + copyfile(path_logo, _join(path, 'images/logo.png')) + copyfile('zaz.py', _join(path, 'zaz.py')) + copyfile(EASYMACRO_TMP, _join(path, 'easymacro.py')) + copyfile('conf.py.example', _join(path, 'conf.py')) + + msg = 'Folders and files copy successfully for new extension.' + log.info(msg) + msg = f'Change to folder: {path}' + log.info(msg) + return + def main(args): + if args.new: + _new(args) + return + if args.update: _update() return @@ -740,8 +774,8 @@ def main(args): _embed(args) return - if args.new: - _new() + if args.create: + _create() return if not _validate_update(): @@ -762,9 +796,13 @@ def main(args): def _process_command_line_arguments(): parser = argparse.ArgumentParser( description='Make LibreOffice extensions') - parser.add_argument('-i', '--install', dest='install', action='store_true', + parser.add_argument('-new', '--new', dest='new', action='store_true', default=False, required=False) - parser.add_argument('-n', '--new', dest='new', action='store_true', + parser.add_argument('-t', '--target', dest='target', default='') + parser.add_argument('-n', '--name', dest='name', default='', required=False) + parser.add_argument('-c', '--create', dest='create', action='store_true', + default=False, required=False) + parser.add_argument('-i', '--install', dest='install', action='store_true', default=False, required=False) parser.add_argument('-e', '--embed', dest='embed', action='store_true', default=False, required=False) From 76e327e52118cedca0d8f0e6a15ba6fd842db688 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Thu, 17 Dec 2020 18:01:02 -0600 Subject: [PATCH 02/13] Remove template --- source/ZAZLaTex2SVG.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/source/ZAZLaTex2SVG.py b/source/ZAZLaTex2SVG.py index 4072fe5..9ae5f29 100644 --- a/source/ZAZLaTex2SVG.py +++ b/source/ZAZLaTex2SVG.py @@ -27,27 +27,6 @@ TEMPLATE = """\documentclass{{article}} \endgroup \end{{document}} """ -# ~ TEMPLATE = """\documentclass{{standalone}} -# ~ \\usepackage[a5paper, landscape]{{geometry}} -# ~ \\usepackage{{xcolor}} -# ~ \\usepackage{{amssymb}} -# ~ \\usepackage{{amsmath}} -# ~ \\usepackage{{tikz}} -# ~ \pagestyle{{empty}} -# ~ \\begin{{document}} -# ~ \\begin{{tikzpicture}} - -# ~ \\node at (0, 0) {{ - # ~ \\begin{{equation*}} - -# ~ \[ {} \] - - # ~ \end{{equation*}} -# ~ }}; - -# ~ \end{{tikzpicture}} -# ~ \end{{document}} -# ~ """ class Controllers(object): From 801b4e7626ffaeeee912bcc59d526dec4306fa7e Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Thu, 17 Dec 2020 22:35:53 -0600 Subject: [PATCH 03/13] Start version 2 --- VERSION | 2 +- conf.py | 2 +- files/ZAZLaTex2SVG_v0.1.0.oxt | Bin 64442 -> 0 bytes files/ZAZLaTex2SVG_v0.2.0.oxt | Bin 0 -> 73343 bytes source/description.xml | 2 +- source/pythonpath/easymacro.py | 2172 +++++++++++++++++++++++++++++--- 6 files changed, 1983 insertions(+), 195 deletions(-) delete mode 100644 files/ZAZLaTex2SVG_v0.1.0.oxt create mode 100644 files/ZAZLaTex2SVG_v0.2.0.oxt diff --git a/VERSION b/VERSION index 6c6aa7c..341cf11 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0 \ No newline at end of file +0.2.0 \ No newline at end of file diff --git a/conf.py b/conf.py index 534a986..3e5f623 100644 --- a/conf.py +++ b/conf.py @@ -26,7 +26,7 @@ import logging TYPE_EXTENSION = 1 # ~ https://semver.org/ -VERSION = '0.1.0' +VERSION = '0.2.0' # ~ Your great extension name, not used spaces or "-_" NAME = 'ZAZLaTex2SVG' diff --git a/files/ZAZLaTex2SVG_v0.1.0.oxt b/files/ZAZLaTex2SVG_v0.1.0.oxt deleted file mode 100644 index 5f91c1df9159bb8a1d5cb5c9be7cc8be226ebf07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64442 zcmV)sK$yQ!O9KQH000080QGcmQS(t|iAn+h0E`U)015yA06}DAZ*Frgcw=?#m)~yM zFcik``xKF--mMO>n<)yQZfFt`)uz!-Q*S2EIRtBtZP^aczSbULFSe6}t^ooKUDca$ z6~{i`Ime&<<4-(XWinWRbwaB?>Cz4fKyjT2mG(*QXBY3taBvoDZ6mHTqZKHBkuc8! z6e<_3T%XLnH&GZamrJ^QOSMhIUZ>LyzgsU;g zK~YYz4Ep3UNwjj+jA8sIOK}nvw3gsg`5=R{Ac&PtU|W7OF4uNfE8!KW{5`YlnB6D^ zo`TF+PVd=0mCVDc=N4(zlQw-~!AQ1lV4cEPZE7j5g_lqX{GGvxm5XqvXmU-2vIUbl z6mCV56$b!{3~n!Pg9*FFqSHkhhh@lNk$XxMhedw}+GREc-?rbucL#FzbdfDvr}>Wz zo!?Bj6_a%ix%>}K?{)j5)mec10q&8A2D|rKJ_3immr^c%|7zB5o6@v}4_e_BYhi!w z2#`(caz&kImby9Mm7N+3EGfq$&2#kHzu5>Yicq{FdzKT+mROOFs%LW$)^Iq<3bW|v z+RH=uvt79PGC9ft2X=ulM|)@;9zN4H9kr#R)>JbYZ3~Vw{InY<=%Sbjut!xs*H)xL zorj`G;zGf->bC484hswqa@l=tF2@QF*i%Z6L9!CzHQggS(b(37m!>h1OP>%vRJiAVeY!r6N!oSX0Ux5x3r zC=Pf3PYurg08mQ<1QY-O00;o}bZ}9Yc_Ejn0RRA71ONaJ0001FWpiV4X>fFDZ*DGl zZET&BF>l*25QX>r3WB?MP6`yYK{@CUWND`s9kLX(bdrfu6u~2H#lOCkWyw+lqkuPo zeD59ayOVtG8p96gNw+rVk7C6c+M+GBt#ckjm3`sQ+Xq=96<)iLR4h3x<$NE4+W@o< zZCh2kK+*adunTDWFoKx}o>r^Rd~j|$qwSx`e>@(=@zdG+xL&W}d^<|Zd&+Dd7?Zk0 z8+4`7vx71bb8ayRG>wYlq)x)9fZY=vY7S@p1;Hp@i5|spRcdlZy=PW6*dL^${*xKi zGeXO^Z(rZOsh=tD$FEljH*|DMC*j`;a%wM|b$7`mUtCMGGp^0vW0mtciT?XHS`oT1 zw6S#3|D1GnO1SwPsf+1dB~~h$u*lVos*xZ8pLh#rN9HiPrHebGX^(!+Z64Hb0i`vl zbueltinf6?Nv{jt?i8LBpH1V%uOkq@IcrsH~ zM=o%AwOC7U+%^!t>sJu&LE3gLI0lRWE_`rOw{hXPM!ZeZ#DTz-xH20`R4K}_Eo^^# zXDCXdv=7IPvxkK=FV1|N2PvKvQfX4<(&OFZH4_C>-a^TY#4-!ZDi2FdRj6tCnOM2^ zzsvc_EsHBHRjbQrd6&^xNkU6Pi^B7~(_f;K_p_7p%hNy3$r15$rb8w&T7~!YJ`_~5 z+x_w;^}WlJH*Zg0o`6i?uPhqEV*YgE%}!@$DB^!O{y@%*^5xs#d{`#}Dy$gt9F&BJ zgp8?RWe`ORE?5*zKxTIG`r_<(h6Z+acCHc`R~gH7ENEHQHC39&f-RTc)m2rpg2ta| z%0A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 z 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 LEC6@^$FKhd)HZdw diff --git a/files/ZAZLaTex2SVG_v0.2.0.oxt b/files/ZAZLaTex2SVG_v0.2.0.oxt new file mode 100644 index 0000000000000000000000000000000000000000..d5e909aaee55334760559ca97935c5c4f79b31fb GIT binary patch literal 73343 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%0D@jTJKeHs|{ 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 XYDyriA^-ps*iRf*1^Tc*1pxjFxZQzX literal 0 HcmV?d00001 diff --git a/source/description.xml b/source/description.xml index 491b052..9659ffa 100644 --- a/source/description.xml +++ b/source/description.xml @@ -1,7 +1,7 @@ - + ZAZLaTex2SVG ZAZLaTex2SVG diff --git a/source/pythonpath/easymacro.py b/source/pythonpath/easymacro.py index 30f0e40..d91c6af 100644 --- a/source/pythonpath/easymacro.py +++ b/source/pythonpath/easymacro.py @@ -50,10 +50,11 @@ from functools import wraps from pathlib import Path from pprint import pprint from string import Template -from typing import Any +from typing import Any, Union from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError +import imaplib import smtplib from smtplib import SMTPException, SMTPAuthenticationError from email.mime.multipart import MIMEMultipart @@ -68,7 +69,7 @@ import unohelper from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS from com.sun.star.awt.MessageBoxResults import YES from com.sun.star.awt import Rectangle, Size, Point -from com.sun.star.awt.PosSize import POSSIZE +from com.sun.star.awt.PosSize import POSSIZE, SIZE from com.sun.star.awt import Key, KeyModifier, KeyEvent from com.sun.star.container import NoSuchElementException from com.sun.star.datatransfer import XTransferable, DataFlavor @@ -78,24 +79,38 @@ from com.sun.star.sheet import TableFilterField from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA 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 XEventListener +from com.sun.star.awt import XMenuListener from com.sun.star.awt import XMouseListener from com.sun.star.awt import XMouseMotionListener from com.sun.star.awt import XFocusListener +from com.sun.star.awt import XKeyListener +from com.sun.star.awt import XItemListener +from com.sun.star.awt import XTabListener +from com.sun.star.awt import XWindowListener +from com.sun.star.awt import XTopWindowListener +from com.sun.star.awt.grid import XGridDataListener +from com.sun.star.awt.grid import XGridSelectionListener +from com.sun.star.script import ScriptEventDescriptor # ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1FontUnderline.html from com.sun.star.awt import FontUnderline from com.sun.star.style.VerticalAlignment import TOP, MIDDLE, BOTTOM +from com.sun.star.view.SelectionType import SINGLE, MULTI, RANGE + +from com.sun.star.sdb.CommandType import TABLE, QUERY, COMMAND + try: from peewee import Database, DateTimeField, DateField, TimeField, \ __exception_wrapper__ except ImportError as e: Database = DateField = TimeField = DateTimeField = object - print('Install peewee') + print('You need install peewee, only if you will develop with Base') LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' @@ -109,6 +124,7 @@ log = logging.getLogger(__name__) # ~ You can get custom salt # ~ codecs.encode(os.urandom(16), 'hex') +# ~ but, not modify this file, modify in import file SALT = b'c9548699d4e432dfd2b46adddafbb06d' TIMEOUT = 10 @@ -150,6 +166,7 @@ OBJ_GRAPHIC = 'SwXTextGraphicObject' OBJ_TEXTS = 'SwXTextRanges' OBJ_TEXT = 'SwXTextRange' + # ~ from com.sun.star.sheet.FilterOperator import EMPTY, NO_EMPTY, EQUAL, NOT_EQUAL class FilterOperator(IntEnum): EMPTY = 0 @@ -163,6 +180,29 @@ class Border(IntEnum): BORDER = 1 SIMPLE = 2 + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet.html#aa5aa6dbecaeb5e18a476b0a58279c57a +class ValidationType(): + from com.sun.star.sheet.ValidationType \ + import ANY, WHOLE, DECIMAL, DATE, TIME, TEXT_LEN, LIST, CUSTOM +VT = ValidationType + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet.html#aecf58149730f4c8c5c18c70f3c7c5db7 +class ValidationAlertStyle(): + from com.sun.star.sheet.ValidationAlertStyle \ + import STOP, WARNING, INFO, MACRO +VAS = ValidationAlertStyle + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1ConditionOperator2.html +class ConditionOperator(): + from com.sun.star.sheet.ConditionOperator2 \ + import NONE, EQUAL, NOT_EQUAL, GREATER, GREATER_EQUAL, LESS, \ + LESS_EQUAL, BETWEEN, NOT_BETWEEN, FORMULA, DUPLICATE, NOT_DUPLICATE +CO = ConditionOperator + + OS = platform.system() IS_WIN = OS == 'Windows' IS_MAC = OS == 'Darwin' @@ -183,7 +223,11 @@ DIR = { 'images': 'images', 'locales': 'locales', } -DEFAULT_MIME_TYPE = 'png' + +KEY = { + 'enter': 1280, +} + MODIFIERS = { 'shift': KeyModifier.SHIFT, 'ctrl': KeyModifier.MOD1, @@ -213,6 +257,7 @@ MENUS = { 'show': '.uno:SlideShowMenu', } +DEFAULT_MIME_TYPE = 'png' MIME_TYPE = { 'png': 'image/png', 'jpg': 'image/jpeg', @@ -443,6 +488,8 @@ def _get_dispatch() -> Any: return create_instance('com.sun.star.frame.DispatchHelper') +# ~ https://wiki.documentfoundation.org/Development/DispatchCommands +# ~ Used only if not exists in API def call_dispatch(frame: Any, url: str, args: dict={}) -> None: dispatch = _get_dispatch() opt = dict_to_property(args) @@ -507,7 +554,6 @@ def _get_url_script(args): url = 'vnd.sun.star.script' url = f'{url}:{library}{module}{name}?language={language}&location={location}' - return url @@ -747,6 +793,11 @@ def decrypt(token, password): return data +def switch_design_mode(doc): + call_dispatch(doc.frame, '.uno:SwitchControlDesignMode') + return + + class SmtpServer(object): def __init__(self, config): @@ -886,6 +937,63 @@ def send_email(server, message): return +class ImapServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + try: + # ~ hosts = 'gmail' in config['server'] + if config['ssl']: + self._server = imaplib.IMAP4_SSL(config['server'], config['port']) + else: + self._server = imaplib.IMAP4(config['server'], config['port']) + self._server.login(config['user'], config['password']) + self._server.select() + return True + except imaplib.IMAP4.error as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def get_folders(self, exclude=()): + folders = {} + result, subdir = self._server.list() + for s in subdir: + print(s.decode('utf-8')) + return folders + + def close(self): + try: + self._server.close() + self._server.logout() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + # ~ Classes class LOBaseObject(object): @@ -895,7 +1003,7 @@ class LOBaseObject(object): def __setattr__(self, name, value): exists = hasattr(self, name) - if not exists and not name in ('_obj', '_index'): + if not exists and not name in ('_obj', '_index', '_view'): setattr(self._obj, name, value) else: super().__setattr__(name, value) @@ -911,54 +1019,6 @@ class LOBaseObject(object): return self._obj -class LOImage(object): - TYPE = { - 'png': 'image/png', - 'jpg': 'image/jpeg', - } - - def __init__(self, obj): - self._obj = obj - - @property - def obj(self): - return self._obj - - @property - def name(self): - return self.obj.Name or 'img' - - @property - def mimetype(self): - return self.obj.Bitmap.MimeType - - def save(self, path, mimetype=DEFAULT_MIME_TYPE): - p = _P(path) - if _P.is_dir(path): - name = self.name - else: - path = p.path - name = p.name - - path = _P.join(path, f'{name}.{mimetype.lower()}') - args = dict( - URL = _P.to_url(path), - MimeType = self.TYPE[mimetype], - ) - if not _export_image(self.obj, args): - path = '' - - # ~ size = len(self.obj.Bitmap.DIB) - # ~ data = self.obj.GraphicStream.readBytes((), size) - # ~ data = data[-1].value - - # ~ data = self.obj.Bitmap.DIB.value - # ~ data = self.obj.Graphic.DIB.value - - # ~ _P.save_bin(path, data) - return path - - class LODocument(object): FILTERS = { 'doc': 'MS Word 97', @@ -1090,6 +1150,10 @@ class LODocument(object): call_dispatch(self.frame, '.uno:Copy') return + def insert_contents(self, args={}): + call_dispatch(self.frame, '.uno:InsertContents', args) + return + def paste(self): sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') transferable = sc.getContents() @@ -1457,66 +1521,279 @@ class LOSheetCharts(object): class LOFormControl(LOBaseObject): + EVENTS = { + 'action': 'actionPerformed', + 'click': 'mousePressed', + } + TYPES = { + 'actionPerformed': 'XActionListener', + 'mousePressed': 'XMouseListener', + } - def __init__(self, obj): - self._obj = obj - self._control = self.doc.CurrentController.getControl(self.obj) + def __init__(self, obj, view, form): + super().__init__(obj) + self._view = view + self._form = form + self._m = view.Model + self._index = -1 def __setattr__(self, name, value): - if name == '_control': + if name in ('_form', '_view', '_m', '_index'): self.__dict__[name] = value else: super().__setattr__(name, value) + def __str__(self): + return f'{self.name} ({self.type}) {[self.index]}' + + @property + def form(self): + return self._form + @property def doc(self): - return self.obj.Parent.Parent.Parent + return self.obj.Parent.Forms.Parent + + @property + def name(self): + return self._m.Name + @name.setter + def name(self, value): + self._m.Name = value + + @property + def tag(self): + return self._m.Tag + @tag.setter + def tag(self, value): + self._m.Tag = value + + @property + def index(self): + return self._index + @index.setter + def index(self, value): + self._index = value + + @property + def enabled(self): + return self._m.Enabled + @enabled.setter + def enabled(self, value): + self._m.Enabled = value + + @property + def events(self): + return self.form.getScriptEvents(self.index) + 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 + + def set_focus(self): + self._view.setFocus() + return + + +class LOFormControlLabel(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'label' + + @property + def value(self): + return self._m.Label + @value.setter + def value(self, value): + self._m.Label = value + + +class LOFormControlText(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'text' + + @property + def value(self): + return self._m.Text + @value.setter + def value(self, value): + self._m.Text = value + + +class LOFormControlButton(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'button' + + @property + def value(self): + return self._m.Label + @value.setter + def value(self, value): + self._m.Text = Label + + +FORM_CONTROL_CLASS = { + 'label': LOFormControlLabel, + 'text': LOFormControlText, + 'button': LOFormControlButton, +} + + +class LOForm(object): + MODELS = { + 'label': 'com.sun.star.form.component.FixedText', + 'text': 'com.sun.star.form.component.TextField', + 'button': 'com.sun.star.form.component.CommandButton', + } + + def __init__(self, obj, draw_page): + self._obj = obj + self._dp = draw_page + self._controls = {} + self._init_controls() + + def __getitem__(self, index): + control = self.obj[index] + return self._controls[control.Name] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + def __str__(self): + return f'Form: {self.name}' + + def _init_controls(self): + types = { + 'com.sun.star.form.OFixedTextModel': 'label', + 'com.sun.star.form.OEditModel': 'text', + 'com.sun.star.form.OButtonModel': 'button', + } + for i, control in enumerate(self.obj): + name = control.Name + tipo = types[control.ImplementationName] + view = self.doc.CurrentController.getControl(control) + control = FORM_CONTROL_CLASS[tipo](control, view) + control.index = i + setattr(self, name, control) + self._controls[name] = control + return + + @property + def obj(self): + return self._obj @property def name(self): return self.obj.Name + @name.setter + def name(self, value): + self.obj.Name = value @property - def label(self): - return self.obj.Label - - def set_focus(self): - self._control.setFocus() - return - - -class LOForm(object): - - def __init__(self, obj): - self._obj = obj - - def __getitem__(self, index): - return LOFormControl(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 - - def __len__(self): - return len(self.obj) + def source(self): + return self.obj.DataSourceName + @source.setter + def source(self, value): + self.obj.DataSourceName = value @property - def obj(self): - return self._obj + def type(self): + return self.obj.CommandType + @type.setter + def type(self, value): + self.obj.CommandType = value + + @property + def command(self): + return self.obj.Command + @command.setter + def command(self, value): + self.obj.Command = value + + @property + def doc(self): + return self.obj.Parent.Parent + + def _special_properties(self, tipo, args): + if tipo == 'button': + # ~ if 'ImageURL' in args: + # ~ args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + return args + + def add(self, args): + name = args['Name'] + tipo = args.pop('Type').lower() + w = args.pop('Width') + h = args.pop('Height') + x = args.pop('X', 0) + y = args.pop('Y', 0) + control = self.doc.createInstance('com.sun.star.drawing.ControlShape') + control.setSize(Size(w, h)) + control.setPosition(Point(x, y)) + model = self.doc.createInstance(self.MODELS[tipo]) + args = self._special_properties(tipo, args) + _set_properties(model, args) + control.Control = model + index = len(self) + self.obj.insertByIndex(index, model) + self._dp.add(control) + view = self.doc.CurrentController.getControl(self.obj.getByName(name)) + control = FORM_CONTROL_CLASS[tipo](control, view, self.obj) + control.index = index + setattr(self, name, control) + self._controls[name] = control + return control class LOSheetForms(object): - def __init__(self, obj): - self._obj = obj + def __init__(self, draw_page): + self._dp = draw_page + self._obj = draw_page.Forms def __getitem__(self, index): - return LOForm(self.obj[index]) + return LOForm(self.obj[index], self._dp) def __enter__(self): return self @@ -1534,24 +1811,144 @@ class LOSheetForms(object): def obj(self): return self._obj + @property + def doc(self): + return self.obj.Parent + @property + def count(self): + return len(self) + + @property + def names(self): + return self.obj.ElementNames + + def insert(self, name): + form = self.doc.createInstance('com.sun.star.form.component.Form') + self.obj.insertByName(name, form) + return LOForm(form, self._dp) + + def remove(self, index): + if isinstance(index, int): + self.obj.removeByIndex(index) + else: + self.obj.removeByName(index) + return + + +# ~ IsFiltered, +# ~ IsManualPageBreak, +# ~ IsStartOfNewPage class LOSheetRows(object): - def __init__(self, sheet): + def __init__(self, sheet, obj): self._sheet = sheet - self._obj = sheet.obj.Rows + self._obj = obj def __getitem__(self, index): - return LOSheetRows(self.obj[index]) + if isinstance(index, int): + rows = LOSheetRows(self._sheet, self.obj[index]) + else: + rango = self._sheet[index.start:index.stop,0:] + rows = LOSheetRows(self._sheet, rango.obj.Rows) + return rows + + def __len__(self): + return self.obj.Count @property def obj(self): return self._obj + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def color(self): + return self.obj.CellBackColor + @color.setter + def color(self, value): + self.obj.CellBackColor = value + + @property + def is_transparent(self): + return self.obj.IsCellBackgroundTransparent + @is_transparent.setter + def is_transparent(self, value): + self.obj.IsCellBackgroundTransparent = value + + @property + def height(self): + return self.obj.Height + @height.setter + def height(self, value): + self.obj.Height = value + + def optimal(self): + self.obj.OptimalHeight = True + return + def insert(self, index, count): self.obj.insertByIndex(index, count) - end = index + count - return self._sheet[index:end,0:] + return + + def remove(self, index, count): + self.obj.removeByIndex(index, count) + return + + +# ~ IsManualPageBreak, +# ~ IsStartOfNewPage +class LOSheetColumns(object): + + def __init__(self, sheet, obj): + self._sheet = sheet + self._obj = obj + + def __getitem__(self, index): + if isinstance(index, (int, str)): + rows = LOSheetColumns(self._sheet, self.obj[index]) + else: + rango = self._sheet[0,index.start:index.stop] + rows = LOSheetColumns(self._sheet, rango.obj.Columns) + return rows + + def __len__(self): + return self.obj.Count + + @property + def obj(self): + return self._obj + + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def width(self): + return self.obj.Width + @width.setter + def width(self, value): + self.obj.Width = value + + def optimal(self): + self.obj.OptimalWidth = True + return + + def insert(self, index, count): + self.obj.insertByIndex(index, count) + return + + def remove(self, index, count): + self.obj.removeByIndex(index, count) + return class LOCalcSheet(object): @@ -1650,14 +2047,44 @@ class LOCalcSheet(object): @property def rows(self): - return LOSheetRows(self) + return LOSheetRows(self, self.obj.Rows) + + @property + def columns(self): + return LOSheetColumns(self, self.obj.Columns) @property def forms(self): - return LOSheetForms(self.obj.DrawPage.Forms) + return LOSheetForms(self.obj.DrawPage) + + @property + def events(self): + names = ('OnFocus', 'OnUnfocus', 'OnSelect', 'OnDoubleClick', + 'OnRightClick', 'OnChange', 'OnCalculate') + evs = self.obj.Events + events = {n: _property_to_dict(evs.getByName(n)) for n in names + if evs.getByName(n)} + return events + @events.setter + def events(self, values): + pv = '[]com.sun.star.beans.PropertyValue' + ev = self.obj.Events + for name, v in values.items(): + url = _get_url_script(v) + args = dict_to_property(dict(EventType='Script', Script=url)) + # ~ e.replaceByName(k, args) + uno.invoke(ev, 'replaceByName', (name, uno.Any(pv, args))) + + @property + def search_descriptor(self): + return self.obj.createSearchDescriptor() + + @property + def replace_descriptor(self): + return self.obj.createReplaceDescriptor() def activate(self): - self.doc.activate(self._obj) + self.doc.activate(self.obj) return def clean(self): @@ -1709,32 +2136,10 @@ class LOCalcSheet(object): rango = self.used_area return rango.render(data, clean) - -class LOCalcRows(object): - - def __init__(self, obj): - self._obj = obj - - def __len__(self): - return self.obj.Count - - def __str__(self): - return 'Rows' - - @property - def obj(self): - return self._obj - - @property - def count(self): - return len(self) - - @property - def visible(self): - return self.obj.IsVisible - @visible.setter - def visible(self, value): - self.obj.IsVisible = value + def find(self, search_string, rango=None): + if rango is None: + rango = self.used_area + return rango.find(search_string) class LOCalcRange(object): @@ -1769,6 +2174,9 @@ class LOCalcRange(object): def __exit__(self, exc_type, exc_value, traceback): pass + def __contains__(self, item): + return item.in_range(self) + def __str__(self): if self.is_none: s = 'Range: None' @@ -1832,7 +2240,7 @@ class LOCalcRange(object): @property def rows(self): - return LOCalcRows(self.obj.Rows) + return LOSheetRows(self.sheet, self.obj.Rows) @property def row(self): @@ -1985,10 +2393,112 @@ class LOCalcRange(object): rangos = [LOCalcRange(self.sheet[r.AbsoluteName].obj) for r in rangos] return tuple(rangos) + @property + def merged_area(self): + cursor = self.cursor + cursor.collapseToMergedArea() + rango = LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + return rango + + @property + def empty(self): + cursor = self.sheet.get_cursor(self.obj) + cursor = self.cursor + rangos = cursor.queryEmptyCells() + rangos = [LOCalcRange(self.sheet[r.AbsoluteName].obj) for r in rangos] + return tuple(rangos) + + @property + def merge(self): + return self.obj.IsMerged + @merge.setter + def merge(self, value): + self.obj.merge(value) + + @property + def style(self): + return self.obj.CellStyle + @style.setter + def style(self, value): + self.obj.CellStyle = value + + @property + def auto_format(self): + return '' + @auto_format.setter + def auto_format(self, value): + self.obj.autoFormat(value) + + @property + def validation(self): + return self.obj.Validation + @validation.setter + def validation(self, values): + current = self.validation + if not values: + current.Type = ValidationType.ANY + current.ShowInputMessage = False + else: + is_list = False + for k, v in values.items(): + if k == 'Type' and v == VT.LIST: + 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 + def select(self): self.doc.select(self.obj) return + def search(self, options, find_all=True): + rangos = None + + descriptor = self.sheet.search_descriptor + descriptor.setSearchString(options['Search']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + + if find_all: + found = self.obj.findAll(descriptor) + else: + found = self.obj.findFirst(descriptor) + + if found: + if found.ImplementationName == OBJ_CELL: + rangos = LOCalcRange(found) + else: + rangos = [LOCalcRange(f) for f in found] + + return rangos + + def replace(self, options): + descriptor = self.sheet.replace_descriptor + descriptor.setSearchString(options['Search']) + descriptor.setReplaceString(options['Replace']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + count = self.obj.replaceAll(descriptor) + return count + + def in_range(self, rango): + if isinstance(rango, LOCalcRange): + address = rango.range_address + else: + address = rango.RangeAddress + result = self.cursor.queryIntersection(address) + return bool(result.Count) + def offset(self, rows=0, cols=1): ra = self.range_address col = ra.EndColumn + cols @@ -2000,6 +2510,10 @@ class LOCalcRange(object): cursor.collapseToSize(cols, rows) return LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + def copy(self, source): + self.sheet.obj.copyRange(self.address, source.range_address) + return + def copy_to(self, cell, formula=False): rango = cell.to_size(self.rows, self.columns) if formula: @@ -2023,7 +2537,7 @@ class LOCalcRange(object): self.to_size(rows, cols).data = data return - def auto_width(self): + def optimal_width(self): self.obj.Columns.OptimalWidth = True return @@ -2077,6 +2591,17 @@ class LOCalcRange(object): self._render_value(k, v) return + def find(self, search_string): + if self._sd is None: + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + + self._sd.setSearchString(search_string) + cell = self.obj.findFirst(self._sd) + if cell: + cell = LOCalcRange(cell) + return cell + def find_all(self, search_string): if self._sd is None: self._sd = self.sheet.obj.createSearchDescriptor() @@ -2132,10 +2657,22 @@ class LOCalcRange(object): args['Y'] = args.get('Y', ps['Y']) # ~ img.ResizeWithCell = True img = self.sheet.dp.insert_image(path, args) - img.Anchor = self.obj + img.anchor = self.obj args.clear() return img + def insert_shape(self, tipo, args={}): + ps = self.possize + args['Width'] = args.get('Width', ps['Width']) + args['Height'] = args.get('Height', ps['Height']) + args['X'] = args.get('X', ps['X']) + args['Y'] = args.get('Y', ps['Y']) + + shape = self.sheet.dp.add(tipo, args) + shape.anchor = self.obj + args.clear() + return + def filter_by_color(self, cell): rangos = cell.column[1:,:].visible for r in rangos: @@ -2149,6 +2686,38 @@ class LOCalcRange(object): self.obj.clearContents(what) return + def transpose(self): + # ~ 'Flags': 'A', + # ~ 'FormulaCommand': 0, + # ~ 'SkipEmptyCells': False, + # ~ 'AsLink': False, + # ~ 'MoveMode': 4, + self.select() + self.doc.copy() + self.clear(1023) + self[0,0].select() + self.doc.insert_contents({'Transpose': True}) + _CB.set('') + return + + def transpose_data(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 merge_by_row(self): + for r in range(len(self.rows)): + self[r].merge = True + return + + def fill(self, source=1): + self.obj.fillAuto(0, source) + return + class LOWriterPageStyle(LOBaseObject): @@ -2205,7 +2774,10 @@ class LOWriterTextRange(object): @property def string(self): - return self.obj.String + s = '' + if self._is_paragraph: + s = self.obj.String + return s @string.setter def string(self, value): self.obj.String = value @@ -2230,10 +2802,6 @@ class LOWriterTextRange(object): def dp(self): return self._doc.dp - @property - def is_table(self): - return self._is_table - def offset(self): cursor = self.cursor.getEnd() return LOWriterTextRange(cursor, self._doc) @@ -2244,6 +2812,23 @@ class LOWriterTextRange(object): self.text.insertTextContent(cursor, data, replace) return + def new_line(self, count=1): + cursor = self.cursor + for i in range(count): + self.text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False) + return self._doc.selection + + def insert_table(self, data): + table = self._doc.create_instance('com.sun.star.text.TextTable') + rows = len(data) + cols = len(data[0]) + table.initialize(rows, cols) + self.insert_content(table) + table.DataArray = data + name = table.Name + table = LOWriterTextTable(self._doc.tables[name], self._doc) + return table + def insert_image(self, path, args={}): w = args.get('Width', 1000) h = args.get('Height', 1000) @@ -2286,6 +2871,47 @@ class LOWriterTextRanges(object): return self._obj +class LOWriterTextTable(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self._obj.Name + + @property + def data(self): + return self._obj.DataArray + @data.setter + def data(self, values): + self._obj.DataArray = values + + +class LOWriterTextTables(object): + + def __init__(self, doc): + self._doc = doc + self._obj = doc.obj.TextTables + + def __getitem__(self, key): + return LOWriterTextTable(self._obj[key], self._doc) + + def __len__(self): + return self._obj.Count + + def insert(self, data, text_range=None): + if text_range is None: + text_range = self._doc.selection + text_range.insert_table(data) + return + + class LOWriter(LODocument): def __init__(self, obj): @@ -2300,6 +2926,10 @@ class LOWriter(LODocument): def paragraphs(self): return LOWriterTextRanges(self.obj.Text, self) + @property + def tables(self): + return LOWriterTextTables(self) + @property def selection(self): sel = self.obj.CurrentSelection @@ -2343,8 +2973,72 @@ class LOWriter(LODocument): ps = self.obj.StyleFamilies['PageStyles'] return LOWriterPageStyles(ps) + @property + def search_descriptor(self): + return self.obj.createSearchDescriptor() + + @property + def replace_descriptor(self): + return self.obj.createReplaceDescriptor() + + def goto_start(self): + self.view_cursor.gotoStart(False) + return self.selection + + def goto_end(self): + self.view_cursor.gotoEnd(False) + return self.selection + + def search(self, options, find_all=True): + descriptor = self.search_descriptor + descriptor.setSearchString(options.get('Search', '')) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if 'Attributes' in options: + attr = dict_to_property(options['Attributes']) + descriptor.setSearchAttributes(attr) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + + result = False + if find_all: + found = self.obj.findAll(descriptor) + if len(found): + result = [LOWriterTextRange(f, self) for f in found] + else: + found = self.obj.findFirst(descriptor) + if found: + result = LOWriterTextRange(found, self) + + return result + + def replace(self, options): + descriptor = self.replace_descriptor + descriptor.setSearchString(options['Search']) + descriptor.setReplaceString(options['Replace']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if 'Attributes' in options: + attr = dict_to_property(options['Attributes']) + descriptor.setSearchAttributes(attr) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + found = self.obj.replaceAll(descriptor) + return found + + def select(self, text): + if hasattr(text, 'obj'): + text = text.obj + self._cc.select(text) + return + class LOShape(LOBaseObject): + IMAGE = 'com.sun.star.drawing.GraphicObjectShape' def __init__(self, obj, index): self._index = index @@ -2352,11 +3046,22 @@ class LOShape(LOBaseObject): @property def type(self): - return 'shape' + t = self.shape_type[21:] + if self.is_image: + t = 'image' + return t + + @property + def shape_type(self): + return self.obj.ShapeType + + @property + def is_image(self): + return self.shape_type == self.IMAGE @property def name(self): - return self.obj.Name or f'shape{self.index}' + return self.obj.Name or f'{self.type}{self.index}' @name.setter def name(self, value): self.obj.Name = value @@ -2412,10 +3117,64 @@ class LOShape(LOBaseObject): def visible(self, value): self.obj.Visible = value + @property + def path(self): + return self.url + @property + def url(self): + url = '' + if self.is_image: + url = _P.to_system(self.obj.GraphicURL.OriginURL) + return url + + @property + def mimetype(self): + mt = '' + if self.is_image: + mt = self.obj.GraphicURL.MimeType + return mt + + @property + def linked(self): + l = False + if self.is_image: + l = self.obj.GraphicURL.Linked + return l + + def delete(self): + self.remove() + return def remove(self): self.obj.Parent.remove(self.obj) return + def save(self, path: str, mimetype=DEFAULT_MIME_TYPE): + if _P.is_dir(path): + name = self.name + ext = mimetype.lower() + else: + p = _P(path) + path = p.path + name = p.name + ext = p.ext.lower() + + path = _P.join(path, f'{name}.{ext}') + args = dict( + URL = _P.to_url(path), + MimeType = MIME_TYPE[ext], + ) + if not _export_image(self.obj, args): + path = '' + return path + + # ~ def save2(self, path: str): + # ~ size = len(self.obj.Bitmap.DIB) + # ~ data = self.obj.GraphicStream.readBytes((), size) + # ~ data = data[-1].value + # ~ path = _P.join(path, f'{self.name}.png') + # ~ _P.save_bin(path, b'') + # ~ return + class LODrawPage(LOBaseObject): @@ -2434,6 +3193,18 @@ class LODrawPage(LOBaseObject): break return shape + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + if self._index == self.count: + raise StopIteration + shape = self[self._index] + self._index += 1 + return shape + + @property def name(self): return self.obj.Name @@ -2468,17 +3239,18 @@ class LODrawPage(LOBaseObject): Ellipse Text """ + index = self.count w = args.get('Width', 3000) h = args.get('Height', 3000) x = args.get('X', 1000) y = args.get('Y', 1000) + name = args.get('Name', f'{type_shape.lower()}{index}') service = f'com.sun.star.drawing.{type_shape}Shape' shape = self.create_instance(service) shape.Size = Size(w, h) shape.Position = Point(x, y) - index = self.count - shape.Name = f'{type_shape.lower()}{index}' + shape.Name = name self.obj.add(shape) return LOShape(self.obj[index], index) @@ -2487,18 +3259,24 @@ class LODrawPage(LOBaseObject): shape = shape.obj return self.obj.remove(shape) + def remove_all(self): + while self.count: + self.obj.remove(self.obj[0]) + return + def insert_image(self, path, args={}): + index = self.count w = args.get('Width', 3000) h = args.get('Height', 3000) x = args.get('X', 1000) y = args.get('Y', 1000) + name = args.get('Name', f'image{index}') image = self.create_instance('com.sun.star.drawing.GraphicObjectShape') image.GraphicURL = _P.to_url(path) image.Size = Size(w, h) image.Position = Point(x, y) - index = self.count - image.Name = f'image{index}' + image.Name = name self.obj.add(image) return LOShape(self.obj[index], index) @@ -2527,7 +3305,7 @@ class LODrawImpress(LODocument): def paste(self): call_dispatch(self.frame, '.uno:Paste') - return self.selection + return self.current_page[-1] def add(self, type_shape, args={}): return self.current_page.add(type_shape, args) @@ -2871,30 +3649,31 @@ class LODocs(object): LODocs._desktop = self._desktop def __getitem__(self, index): - doc = None + document = None for i, doc in enumerate(self._desktop.Components): if isinstance(index, int) and i == index: - doc = _get_class_doc(doc) + document = _get_class_doc(doc) break elif isinstance(index, str) and doc.Title == index: - doc = _get_class_doc(doc) + document = _get_class_doc(doc) break - return doc + return document def __contains__(self, item): doc = self[item] return not doc is None def __iter__(self): - self._i = 0 + self._i = -1 return self def __next__(self): + self._i += 1 doc = self[self._i] if doc is None: raise StopIteration - self._i += 1 - return doc + else: + return doc def __len__(self): for i, _ in enumerate(self._desktop.Components): @@ -2946,9 +3725,9 @@ def _add_listeners(events, control, name=''): 'addActionListener': EventsButton, 'addMouseListener': EventsMouse, 'addFocusListener': EventsFocus, - # ~ 'addItemListener': EventsItem, - # ~ 'addKeyListener': EventsKey, - # ~ 'addTabListener': EventsTab, + 'addItemListener': EventsItem, + 'addKeyListener': EventsKey, + 'addTabListener': EventsTab, } if hasattr(control, 'obj'): control = control.obj @@ -2956,6 +3735,7 @@ def _add_listeners(events, control, name=''): is_grid = control.ImplementationName == 'stardiv.Toolkit.GridControl' is_link = control.ImplementationName == 'stardiv.Toolkit.UnoFixedHyperlinkControl' is_roadmap = control.ImplementationName == 'stardiv.Toolkit.UnoRoadmapControl' + is_pages = control.ImplementationName == 'stardiv.Toolkit.UnoMultiPageControl' for key, value in listeners.items(): if hasattr(control, key): @@ -2971,10 +3751,10 @@ def _add_listeners(events, control, name=''): getattr(control, key)(listeners[key](events, name)) - # ~ if is_grid: - # ~ controllers = EventsGrid(events, name) - # ~ control.addSelectionListener(controllers) - # ~ control.Model.GridDataModel.addGridDataListener(controllers) + if is_grid: + controllers = EventsGrid(events, name) + control.addSelectionListener(controllers) + control.Model.GridDataModel.addGridDataListener(controllers) return @@ -3090,6 +3870,213 @@ class EventsFocus(EventsListenerBase, XFocusListener): return +class EventsKey(EventsListenerBase, XKeyListener): + """ + event.KeyChar + event.KeyCode + event.KeyFunc + 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) + # ~ else: + # ~ if event.KeyFunc == QUIT and hasattr(self._cls, 'close'): + # ~ self._cls.close() + return + + +class EventsItem(EventsListenerBase, XItemListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def disposing(self, event): + pass + + def itemStateChanged(self, event): + event_name = '{}_item_changed'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + +class EventsItemRoadmap(EventsItem): + + def itemStateChanged(self, event): + dialog = event.Source.Context.Model + dialog.Step = event.ItemId + 1 + return + + +class EventsGrid(EventsListenerBase, XGridDataListener, XGridSelectionListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def dataChanged(self, event): + event_name = '{}_data_changed'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def rowHeadingChanged(self, event): + pass + + def rowsInserted(self, event): + pass + + def rowsRemoved(self, evemt): + pass + + def selectionChanged(self, event): + event_name = '{}_selection_changed'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + +class EventsMouseGrid(EventsMouse): + selected = False + + def mousePressed(self, event): + super().mousePressed(event) + # ~ obj = event.Source + # ~ col = obj.getColumnAtPoint(event.X, event.Y) + # ~ row = obj.getRowAtPoint(event.X, event.Y) + # ~ print(col, row) + # ~ if col == -1 and row == -1: + # ~ if self.selected: + # ~ obj.deselectAllRows() + # ~ else: + # ~ obj.selectAllRows() + # ~ self.selected = not self.selected + 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) + return + + +class EventsTab(EventsListenerBase, XTabListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def activated(self, id): + event_name = '{}_activated'.format(self.name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(id) + return + + +class EventsMenu(EventsListenerBase, XMenuListener): + + def __init__(self, controller): + super().__init__(controller, '') + + def itemHighlighted(self, event): + pass + + def itemSelected(self, event): + name = event.Source.getCommand(event.MenuId) + if name.startswith('menu'): + event_name = '{}_selected'.format(name) + else: + event_name = 'menu_{}_selected'.format(name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def itemActivated(self, event): + return + + def itemDeactivated(self, event): + return + + +class EventsWindow(EventsListenerBase, XTopWindowListener, XWindowListener): + + def __init__(self, cls): + self._cls = cls + super().__init__(cls.events, cls.name, cls._window) + + def windowOpened(self, event): + event_name = '{}_opened'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def windowActivated(self, event): + control_name = '{}_activated'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + def windowDeactivated(self, event): + control_name = '{}_deactivated'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + def windowMinimized(self, event): + pass + + def windowNormalized(self, event): + pass + + def windowClosing(self, event): + if self._window: + control_name = 'window_closing' + else: + control_name = '{}_closing'.format(event.Source.Model.Name) + + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + # ~ else: + # ~ if not self._modal and not self._block: + # ~ event.Source.Visible = False + return + + def windowClosed(self, event): + control_name = '{}_closed'.format(event.Source.Model.Name) + if hasattr(self._controller, control_name): + getattr(self._controller, control_name)(event) + return + + # ~ XWindowListener + def windowResized(self, event): + sb = self._cls._subcont + sb.setPosSize(0, 0, event.Width, event.Height, SIZE) + event_name = '{}_resized'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + def windowMoved(self, event): + pass + + def windowShown(self, event): + pass + + def windowHidden(self, event): + pass + + # ~ BorderColor = ? # ~ FontStyleName = ? # ~ HelpURL = ? @@ -3098,7 +4085,6 @@ class UnoBaseObject(object): def __init__(self, obj, path=''): self._obj = obj self._model = obj.Model - # ~ self._path = path def __setattr__(self, name, value): exists = hasattr(self, name) @@ -3107,6 +4093,12 @@ class UnoBaseObject(object): else: super().__setattr__(name, value) + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + @property def obj(self): return self._obj @@ -3405,14 +4397,14 @@ class UnoRadio(UnoBaseObject): self.model.Label = value -class UnoCheck(UnoBaseObject): +class UnoCheckBox(UnoBaseObject): def __init__(self, obj): super().__init__(obj) @property def type(self): - return 'check' + return 'checkbox' @property def value(self): @@ -3453,6 +4445,9 @@ class UnoText(UnoBaseObject): def value(self, value): self.model.Text = value + def validate(self): + return + class UnoImage(UnoBaseObject): @@ -3551,17 +4546,498 @@ class UnoListBox(UnoBaseObject): return +class UnoRoadmap(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._options = () + + def __setattr__(self, name, value): + if name in ('_options',): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @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 + + +class UnoTree(UnoBaseObject): + + def __init__(self, obj, ): + super().__init__(obj) + self._tdm = None + self._data = [] + + def __setattr__(self, name, value): + if name in ('_tdm', '_data'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def selection(self): + sel = self.obj.Selection + return sel.DataValue, sel.DisplayValue + + @property + def parent(self): + parent = self.obj.Selection.Parent + if parent is None: + return () + return parent.DataValue, parent.DisplayValue + + def _get_parents(self, node): + value = (node.DisplayValue,) + parent = node.Parent + if parent is None: + return value + return self._get_parents(parent) + value + + @property + def parents(self): + values = self._get_parents(self.obj.Selection) + return values + + @property + def root(self): + if self._tdm is None: + return '' + return self._tdm.Root.DisplayValue + @root.setter + def root(self, value): + self._add_data_model(value) + + def _add_data_model(self, name): + tdm = create_instance('com.sun.star.awt.tree.MutableTreeDataModel') + root = tdm.createNode(name, True) + root.DataValue = 0 + tdm.setRoot(root) + self.model.DataModel = tdm + self._tdm = self.model.DataModel + return + + @property + def path(self): + return self.root + @path.setter + def path(self, value): + self.data = _P.walk_dir(value, True) + + @property + def data(self): + return self._data + @data.setter + def data(self, values): + self._data = list(values) + self._add_data() + + def _add_data(self): + if not self.data: + return + + parents = {} + for node in self.data: + parent = parents.get(node[1], self._tdm.Root) + child = self._tdm.createNode(node[2], False) + child.DataValue = node[0] + parent.appendChild(child) + parents[node[0]] = child + self.obj.expandNode(self._tdm.Root) + return + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1grid.html +class UnoGrid(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._gdm = self.model.GridDataModel + self._columns = [] + self._data = [] + # ~ self._format_columns = () + + def __setattr__(self, name, value): + if name in ('_gdm', '_columns', '_data'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __getitem__(self, key): + value = self._gdm.getCellData(key[0], key[1]) + return value + + def __setitem__(self, key, value): + self._gdm.updateCellData(key[0], key[1], value) + return + + @property + def type(self): + return 'grid' + + @property + def columns(self): + return self._columns + @columns.setter + def columns(self, values): + self._columns = values + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html + model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) + for properties in values: + column = create_instance('com.sun.star.awt.grid.GridColumn', True) + for k, v in properties.items(): + setattr(column, k, v) + model.addColumn(column) + self.model.ColumnModel = model + return + + @property + def data(self): + return self._data + @data.setter + def data(self, values): + self._data = values + self.clear() + headings = tuple(range(1, len(values) + 1)) + self._gdm.addRows(headings, values) + # ~ rows = range(grid_dm.RowCount) + # ~ colors = [COLORS['GRAY'] if r % 2 else COLORS['WHITE'] for r in rows] + # ~ grid.Model.RowBackgroundColors = tuple(colors) + return + + @property + def value(self): + if self.column == -1 or self.row == -1: + return '' + return self[self.column, self.row] + @value.setter + def value(self, value): + if self.column > -1 and self.row > -1: + self[self.column, self.row] = value + + @property + def row(self): + return self.obj.CurrentRow + + @property + def column(self): + return self.obj.CurrentColumn + + def clear(self): + self._gdm.removeAllRows() + return + + # UP + def _format_cols(self): + rows = tuple(tuple( + self._format_columns[i].format(r) for i, r in enumerate(row)) for row in self._data + ) + return rows + + # ~ @property + # ~ def format_columns(self): + # ~ return self._format_columns + # ~ @format_columns.setter + # ~ def format_columns(self, value): + # ~ self._format_columns = value + + # ~ @property + # ~ def rows(self): + # ~ return self._gdm.RowCount + + # ~ @property + # ~ def columns(self): + # ~ return self._gdm.ColumnCount + + def set_cell_tooltip(self, col, row, value): + self._gdm.updateCellToolTip(col, row, value) + return + + def get_cell_tooltip(self, col, row): + value = self._gdm.getCellToolTip(col, row) + return value + + def _validate_column(self, data): + row = [] + for i, d in enumerate(data): + if i in self._columns: + if 'image' in self._columns[i]: + row.append(self._columns[i]['image']) + else: + row.append(d) + return tuple(row) + + def add_row(self, data): + # ~ self._data.append(data) + data = self._validate_column(data) + self._gdm.addRow(self.rows + 1, data) + return + + def remove_row(self, row): + self._gdm.removeRow(row) + # ~ del self._data[row] + self.update_row_heading() + return + + def update_row_heading(self): + for i in range(self.rows): + self._gdm.updateRowHeading(i, i + 1) + return + + def sort(self, column, asc=True): + self._gdm.sortByColumn(column, asc) + self.update_row_heading() + return + + def set_column_image(self, column, path): + gp = create_instance('com.sun.star.graphic.GraphicProvider') + data = dict_to_property({'URL': _path_url(path)}) + image = gp.queryGraphic(data) + if not column in self._columns: + self._columns[column] = {} + self._columns[column]['image'] = image + return + + +class UnoPage(object): + + def __init__(self, obj): + self._obj = obj + self._events = None + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._obj.Model + + # ~ @property + # ~ def id(self): + # ~ return self.m.TabPageID + + @property + def parent(self): + return self.obj.Context + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(UNO_MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self.obj.getControl(name) + _add_listeners(self._events, control, name) + control = UNO_CLASSES[tipo](control) + + if tipo in ('listbox',): + control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + return control + + +class UnoPages(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._sheets = [] + self._events = None + + def __setattr__(self, name, value): + if name in ('_sheets', '_events'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __getitem__(self, index): + name = index + if isinstance(index, int): + name = f'sheet{index}' + sheet = self.obj.getControl(name) + page = UnoPage(sheet) + page._events = self._events + return page + + @property + def type(self): + return 'pages' + + @property + def current(self): + return self.obj.ActiveTabID + @property + def active(self): + return self.current + + @property + def sheets(self): + return self._sheets + @sheets.setter + def sheets(self, values): + self._sheets = values + for i, title in enumerate(values): + sheet = self.m.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.m.insertByName(f'sheet{i + 1}', sheet) + return + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers + + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value + + def insert(self, title): + self._sheets.append(title) + id = len(self._sheets) + sheet = self.m.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.m.insertByName(f'sheet{id}', sheet) + return self[id] + + def remove(self, id): + self.obj.removeTab(id) + return + + def activate(self, id): + self.obj.activateTab(id) + return + + UNO_CLASSES = { 'label': UnoLabel, 'link': UnoLabelLink, 'button': UnoButton, 'radio': UnoRadio, - 'check': UnoCheck, + 'checkbox': UnoCheckBox, 'text': UnoText, 'image': UnoImage, 'listbox': UnoListBox, + 'roadmap': UnoRoadmap, + 'tree': UnoTree, + 'grid': UnoGrid, + 'pages': UnoPages, } +UNO_MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + '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', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + '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', +# ~ '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', + class LODialog(object): SEPARATION = 5 @@ -3570,15 +5046,16 @@ class LODialog(object): 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', 'button': 'com.sun.star.awt.UnoControlButtonModel', 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', - 'check': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel', 'text': 'com.sun.star.awt.UnoControlEditModel', 'image': 'com.sun.star.awt.UnoControlImageControlModel', 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', - # ~ 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', - # ~ 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', - # ~ 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', - # ~ 'tree': 'com.sun.star.awt.tree.TreeControlModel', - # ~ 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', } def __init__(self, args): @@ -3590,6 +5067,7 @@ class LODialog(object): self._color_on_focus = COLOR_ON_FOCUS self._id = '' self._path = '' + self._init_controls() def _create(self, args): service = 'com.sun.star.awt.DialogProvider' @@ -3602,7 +5080,7 @@ class LODialog(object): if 'Location' in args: name = args['Name'] library = args.get('Library', 'Standard') - location = args.get('Location', 'application') + location = args.get('Location', 'application').lower() if location == 'user': location = 'application' url = f'vnd.sun.star.script:{library}.{name}?location={location}' @@ -3624,6 +5102,23 @@ class LODialog(object): dlg.createPeer(toolkit, None) return dlg + def _get_type_control(self, name): + name = name.split('.')[2] + types = { + 'UnoFixedTextControl': 'label', + 'UnoEditControl': 'text', + 'UnoButtonControl': 'button', + } + return types[name] + + def _init_controls(self): + for control in self.obj.getControls(): + tipo = self._get_type_control(control.ImplementationName) + name = control.Model.Name + control = UNO_CLASSES[tipo](control) + setattr(self, name, control) + return + @property def obj(self): return self._obj @@ -3703,27 +5198,33 @@ class LODialog(object): return _P.to_url(path) def _special_properties(self, tipo, args): - columns = args.pop('Columns', ()) - if tipo == 'link' and not 'Label' in args: args['Label'] = args['URL'] - elif tipo == 'grid': - args['ColumnModel'] = self._set_column_model(columns) - elif tipo == 'button': + return args + + if tipo == 'button': if 'ImageURL' in args: args['ImageURL'] = self._set_image_url(args['ImageURL']) - if not 'FocusOnClick' in args: - args['FocusOnClick'] = False - elif tipo == 'roadmap': - if not 'Height' in args: - args['Height'] = self.height + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) if 'Title' in args: args['Text'] = args.pop('Title') - elif tipo == 'tab': - if not 'Width' in args: - args['Width'] = self.width - if not 'Height' in args: - args['Height'] = self.height + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) return args @@ -3731,6 +5232,7 @@ class LODialog(object): tipo = args.pop('Type').lower() root = args.pop('Root', '') sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) args = self._special_properties(tipo, args) model = self.model.createInstance(self.MODELS[tipo]) @@ -3740,11 +5242,14 @@ class LODialog(object): control = self.obj.getControl(name) _add_listeners(self.events, control, name) control = UNO_CLASSES[tipo](control) + if tipo in ('listbox',): control.path = self.path if tipo == 'tree' and root: control.root = root + elif tipo == 'grid' and columns: + control.columns = columns elif tipo == 'pages' and sheets: control.sheets = sheets control.events = self.events @@ -4046,6 +5551,245 @@ class LOMenus(object): return LOMenu(index) +class LOWindow(object): + EMPTY = """ + +""" + MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + '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', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', + } + + def __init__(self, args): + self._events = None + self._menu = None + self._container = None + self._model = None + self._id = '' + self._path = '' + self._obj = self._create(args) + + def _create(self, properties): + ps = ( + properties.get('X', 0), + properties.get('Y', 0), + properties.get('Width', 500), + properties.get('Height', 500), + ) + self._title = properties.get('Title', TITLE) + self._create_frame(ps) + self._create_container(ps) + self._create_subcontainer(ps) + # ~ self._create_splitter(ps) + return + + def _create_frame(self, ps): + service = 'com.sun.star.frame.TaskCreator' + tc = create_instance(service, True) + self._frame = tc.createInstanceWithArguments(( + NamedValue('FrameName', 'EasyMacroWin'), + NamedValue('PosSize', Rectangle(*ps)), + )) + self._window = self._frame.getContainerWindow() + self._toolkit = self._window.getToolkit() + desktop = get_desktop() + self._frame.setCreator(desktop) + desktop.getFrames().append(self._frame) + self._frame.Title = self._title + return + + def _create_container(self, ps): + service = 'com.sun.star.awt.UnoControlContainer' + self._container = create_instance(service, True) + service = 'com.sun.star.awt.UnoControlContainerModel' + model = create_instance(service, True) + model.BackgroundColor = get_color((225, 225, 225)) + self._container.setModel(model) + self._container.createPeer(self._toolkit, self._window) + self._container.setPosSize(*ps, POSSIZE) + self._frame.setComponent(self._container, None) + return + + def _create_subcontainer(self, ps): + service = 'com.sun.star.awt.ContainerWindowProvider' + cwp = create_instance(service, True) + + path_tmp = _P.save_tmp(self.EMPTY) + subcont = cwp.createContainerWindow( + _P.to_url(path_tmp), '', self._container.getPeer(), None) + _P.kill(path_tmp) + + subcont.setPosSize(0, 0, 500, 500, POSSIZE) + subcont.setVisible(True) + self._container.addControl('subcont', subcont) + self._subcont = subcont + self._model = subcont.Model + return + + def _create_popupmenu(self, menus): + menu = create_instance('com.sun.star.awt.PopupMenu', True) + for i, m in enumerate(menus): + label = m['label'] + cmd = m.get('event', '') + if not cmd: + cmd = label.lower().replace(' ', '_') + if label == '-': + menu.insertSeparator(i) + else: + menu.insertItem(i, label, m.get('style', 0), i) + menu.setCommand(i, cmd) + # ~ menu.setItemImage(i, path?, True) + menu.addMenuListener(EventsMenu(self.events)) + return menu + + def _create_menu(self, menus): + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMenu.html + #~ nItemId specifies the ID of the menu item to be inserted. + #~ aText specifies the label of the menu item. + #~ nItemStyle 0 = Standard, CHECKABLE = 1, RADIOCHECK = 2, AUTOCHECK = 4 + #~ nItemPos specifies the position where the menu item will be inserted. + self._menu = create_instance('com.sun.star.awt.MenuBar', True) + for i, m in enumerate(menus): + self._menu.insertItem(i, m['label'], m.get('style', 0), i) + cmd = m['label'].lower().replace(' ', '_') + self._menu.setCommand(i, cmd) + submenu = self._create_popupmenu(m['submenu']) + self._menu.setPopupMenu(i, submenu) + + self._window.setMenuBar(self._menu) + return + + def _add_listeners(self, control=None): + if self.events is None: + return + controller = EventsWindow(self) + self._window.addTopWindowListener(controller) + self._window.addWindowListener(controller) + # ~ self._container.addKeyListener(EventsKeyWindow(self)) + return + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(self.MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self._subcont.getControl(name) + _add_listeners(self.events, control, name) + control = UNO_CLASSES[tipo](control) + + # ~ if tipo in ('listbox',): + # ~ control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + return control + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers(self) + self._add_listeners() + + @property + def model(self): + return self._model + + @property + def width(self): + return self._container.Size.Width + + @property + def height(self): + return self._container.Size.Height + + @property + def name(self): + return self._title.lower().replace(' ', '_') + + def add_menu(self, menus): + self._create_menu(menus) + return + + def open(self): + self._window.setVisible(True) + return + + def close(self): + self._window.setMenuBar(None) + self._window.dispose() + self._frame.close(True) + return + + +def create_window(args): + return LOWindow(args) + + class classproperty: def __init__(self, method=None): self.fget = method @@ -4098,6 +5842,7 @@ class ClipBoard(object): if df: text = transferable.getTransferData(df) return text +_CB = ClipBoard class Paths(object): @@ -4170,6 +5915,12 @@ class Paths(object): tmp = tempfile.NamedTemporaryFile(suffix=ext) return tmp.name + @classmethod + def save_tmp(cls, data): + path_tmp = cls.tmp() + cls.save(path_tmp, data) + return path_tmp + @classmethod def config(cls, name='Work'): """ @@ -4180,14 +5931,10 @@ class Paths(object): return cls.to_system(getattr(path, name)) @classmethod - def get(cls, init_dir='', filters=()): + def get(cls, init_dir='', filters: str=''): """ Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html - filters: Example - ( - ('XML', '*.xml'), - ('TXT', '*.txt'), - ) + filters: 'xml' or 'txt,xml' """ if not init_dir: init_dir = cls.documents @@ -4197,6 +5944,7 @@ class Paths(object): file_picker.setDisplayDirectory(init_dir) file_picker.initialize((2,)) if filters: + filters = [(f.upper(), f'*.{f.lower()}') for f in filters.split(',')] file_picker.setCurrentFilter(filters[0][0]) for f in filters: file_picker.appendFilter(f[0], f[1]) @@ -4221,15 +5969,11 @@ class Paths(object): return path @classmethod - def get_file(cls, init_dir='', filters=(), multiple=False): + def get_file(cls, init_dir: str='', filters: str='', multiple: bool=False): """ init_folder: folder default open multiple: True for multiple selected - filters: Example - ( - ('XML', '*.xml'), - ('TXT', '*.txt'), - ) + filters: 'xml' or 'xml,txt' """ if not init_dir: init_dir = cls.documents @@ -4241,6 +5985,7 @@ class Paths(object): file_picker.setMultiSelectionMode(multiple) if filters: + filters = [(f.upper(), f'*.{f.lower()}') for f in filters.split(',')] file_picker.setCurrentFilter(filters[0][0]) for f in filters: file_picker.appendFilter(f[0], f[1]) @@ -4340,6 +6085,16 @@ class Paths(object): return result + @classmethod + def files(cls, path, pattern='*'): + files = [str(p) for p in Path(path).glob(pattern) if p.is_file()] + return files + + @classmethod + def dirs(cls, path): + dirs = [str(p) for p in Path(path).iterdir() if p.is_dir()] + return dirs + @classmethod def walk(cls, path, filters=''): paths = [] @@ -4353,6 +6108,25 @@ class Paths(object): paths += [cls.join(folder, f) for f in files] return paths + @classmethod + def walk_dir(cls, path, tree=False): + folders = [] + if tree: + i = 0 + p = 0 + parents = {path: 0} + for root, dirs, _ in os.walk(path): + for name in dirs: + i += 1 + rn = cls.join(root, name) + if not rn in parents: + parents[rn] = i + folders.append((i, parents[root], name)) + else: + for root, dirs, _ in os.walk(path): + folders += [cls.join(root, name) for name in dirs] + return folders + @classmethod def from_id(cls, id_ext): pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') @@ -4424,6 +6198,20 @@ class Paths(object): z.extractall(path, members=members, pwd=pwd) return True + @classmethod + def merge_zip(cls, target, zips): + try: + with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as t: + for path in zips: + with zipfile.ZipFile(path, compression=zipfile.ZIP_DEFLATED) as s: + for name in s.namelist(): + t.writestr(name, s.open(name).read()) + except Exception as e: + error(e) + return False + + return True + @classmethod def copy(cls, source, target='', name=''): p, f, n, e = _P(source).info From dc61cfb45cac6663cd30d3f700a2fb737762e50b Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 18 Dec 2020 23:20:36 -0600 Subject: [PATCH 04/13] Separate files --- source/ZAZLaTex2SVG.py | 4 ++++ source/pythonpath/main.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 source/pythonpath/main.py diff --git a/source/ZAZLaTex2SVG.py b/source/ZAZLaTex2SVG.py index 9ae5f29..1ce2538 100644 --- a/source/ZAZLaTex2SVG.py +++ b/source/ZAZLaTex2SVG.py @@ -101,6 +101,10 @@ class ZAZLaTex2SVG(unohelper.Base, XJobExecutor): self.ctx = ctx def trigger(self, args=''): + main.ID_EXTENSION = ID_EXTENSION + main.run(args, __file__) + return + if args == 'app': self._app() return diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py new file mode 100644 index 0000000..c5ace66 --- /dev/null +++ b/source/pythonpath/main.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import easymacro as app + + +ID_EXTENSION = '' +_ = None + + +def run(args, path_locales): + global _ + _ = app.install_locales(path_locales) + globals()[args]() + return From 30a755a7489888b696e97ed7ef939aad227801d3 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 18 Dec 2020 23:23:29 -0600 Subject: [PATCH 05/13] Add function selection --- files/ZAZLaTex2SVG_v0.2.0.oxt | Bin 73343 -> 73673 bytes source/ZAZLaTex2SVG.py | 3 +++ source/pythonpath/main.py | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/files/ZAZLaTex2SVG_v0.2.0.oxt b/files/ZAZLaTex2SVG_v0.2.0.oxt index d5e909aaee55334760559ca97935c5c4f79b31fb..271833fad8cb570f2a428365de72ad1a9ed44981 100644 GIT binary patch delta 2425 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 ej^`CC^3LC%%h*ocgA(O!B?0)GL+jy(_n delta 2060 zcmZ9Nc{JPU8prcXtPx^Kkwik&(y?zv(NSA!5>-?QYO6a%TP>}vQWK@dR&5b}TBVjS zRIa5|VOvYF-q0eQbsHj?z#7zx$kq{^Zh)_InR0i`u28+?6O6q-N-Nz z6a)g1fJ8LsG)iBF!i3sHgN^_)CvN4q*St%bAc;XBlZtskB1*K0OB3d+{(*#PCzBb8 z@TAgXNj41smW0Tr(v>%LF@zy`{iaIC8w@Vv=iS7QZgj_9u=&C#aR;k)0pa-<59bB% z)Mbi*RnBH`{YW{2jGlw159DwiiTA4w+bgNNAmz;D2!m^H&ZbwVJ<#yjaQus*cl349 z7Cx};p}+r>D{ZI!o8eYzsa3g+_UYB^>sgk=t2>^PKi-I=uFWh=|9N2OW>*1Mf-cI> zu_zj-a+bf&+Txv*{<<(>3mZ~^q0ER(h}K|;f&8-?I6uDEGMg_qG*A~X zEq1mPY=+gJ9E$&BocvN2%CFt^K>5N4Ydp&+*Q+w{#ncjH2i;)X@|@^Ml!{It7-IV; zIw(;BFErzmTP3aBe<*n&sg(IbW7q}drO^f}YPm`L*+k_@&<`=1S?$dp zTx((nz5q<8Cu@4?d1l;Rl&B9QcNY9aXfB&)ErYcPWxi3o_Pwh1WHaoepwWXIQiTV+ zRd$`QyxLed>GwY4#H*LS-EQ9qeRyep=ap(&YIQDqrE3|FkfI;W-;DHRjudwvt=sKl za6@b>MDv4cFFWjMD9O<0Hyon$tX8qeuEdwPW z3M@Injcfff*Xad0j8;}fb@fv6;f8YyE}XZNM)O%QRBSK~%ONHf9IDjGVLI9(3urS3 z+j#e6ChN+qOvb?aSMj4p_Q}5;h~#nrTg9~(JMH$l&{oA1jhrs$C6 zryL)O>OS*4F4{Xc9w>Ifv(@f6G_BGFyz>~6-4886P(GV(%BT3q=o;K|FU}jh;-r?9 z-4()**T-+dGz?*GVIO8xRh#S`B4|bBzfok1nO7btzbxneSb4_|jbhwIDjmOYVST2@ z*4N^h#c{h@KQAWK9a!t$Jk@C&l}%>vNdY@OY}tZR^}ene(w%gG#AH84@Qw%rC?;_-Sf!%SJ+= zWrQfdt!io@t5Xo3(vb4gD6M4b?IQIob-;Ly_U5Xug>UG|*Xrc(B499Wjy3nM5%ij| z$Y-7&fg2mV}+&x zn-h;p`eAcDh6QA&C#M!-Y+FOcB;aq#R7vMC3%YpjbmM8%G6(tK2PH{E~O36`1l>pV92UbqHapX9org`>}?FCFiQK6 zgi*Y431ijH^i^K>9M*dIk!0e@#7Jp@!ZZA+O&9AO7aKKdj z3A$S;WXyu~kQ7}=d*2sxx#lSIh(pE6!Tero_Y|70y=ko})t9zt-o=x`7iB$%1ezA} zjf~d)sDg2Y!tJPQ=H2Wm(JiHk5D1mLqVBSx8R~Pxh57-TgEEMVp;rQVmUFt__a)_K6vo)ce*a&y_qt_>=Q;sem&Y>mYphThs{3VRg0;gZ>&z4jn%2X z-ZoNJQ1RJi-z&dDN9RBHQpMcImKCB29qJ22PJ3a4f6tQhR#RT$_O5P|w_zr%w`f%4 zwJujx{nmu_`_vk2OhQAdeDi;DtBXt2k`Eq znYgQ&VKum5Iq||@x$~CUr4f&Jjs*<&n?+av3v_@_ zv4GZouBHNDp`@#MkZ342raDZmviVM{~! z|2c(01E3uSPzEol0*L+d5>){#EapFD1+X3ih$^~~MTKq-fyfEto(Kdogcfdt{0#>3 Bni~KB diff --git a/source/ZAZLaTex2SVG.py b/source/ZAZLaTex2SVG.py index 1ce2538..d9fa6b2 100644 --- a/source/ZAZLaTex2SVG.py +++ b/source/ZAZLaTex2SVG.py @@ -1,6 +1,8 @@ import uno import unohelper from com.sun.star.task import XJobExecutor +import main + import easymacro as app @@ -100,6 +102,7 @@ class ZAZLaTex2SVG(unohelper.Base, XJobExecutor): def __init__(self, ctx): self.ctx = ctx + @app.catch_exception def trigger(self, args=''): main.ID_EXTENSION = ID_EXTENSION main.run(args, __file__) diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py index c5ace66..d7efc51 100644 --- a/source/pythonpath/main.py +++ b/source/pythonpath/main.py @@ -7,6 +7,11 @@ ID_EXTENSION = '' _ = None +def selection(): + app.debug('From selection') + return + + def run(args, path_locales): global _ _ = app.install_locales(path_locales) From a7501bad5f952febd1cb1b41f80905391544c2ef Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 18 Dec 2020 23:26:16 -0600 Subject: [PATCH 06/13] Add function from dialog --- conf.py | 6 +++--- files/ZAZLaTex2SVG_v0.2.0.oxt | Bin 73673 -> 73716 bytes source/Addons.xcu | 6 +++--- source/pythonpath/main.py | 12 +++++++++++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/conf.py b/conf.py index 3e5f623..72b99da 100644 --- a/conf.py +++ b/conf.py @@ -111,7 +111,7 @@ MENU_MAIN = { MENUS = ( { 'title': {'en': 'From selection', 'es': 'Desde selección'}, - 'argument': 'selection', + 'argument': 'from_selection', 'context': 'calc,writer,draw,impress', 'icon': 'icon1', 'toolbar': False, @@ -119,7 +119,7 @@ MENUS = ( }, { 'title': {'en': 'Insert...', 'es': 'Insertar...'}, - 'argument': 'dlg', + 'argument': 'from_dialog', 'context': 'calc,writer,draw,impress', 'icon': 'icon1', 'toolbar': False, @@ -127,7 +127,7 @@ MENUS = ( }, { 'title': {'en': 'Validate applications', 'es': 'Validar aplicaciones'}, - 'argument': 'app', + 'argument': 'validate_app', 'context': 'calc,writer,draw,impress', 'icon': 'icon2', 'toolbar': False, diff --git a/files/ZAZLaTex2SVG_v0.2.0.oxt b/files/ZAZLaTex2SVG_v0.2.0.oxt index 271833fad8cb570f2a428365de72ad1a9ed44981..6c2c6fbb4e0c08382336353af7e8a8c88e1c47ff 100644 GIT binary patch delta 1319 zcmX@PpXJMbmihp1W)=|!1_llW>)n$A+s?Tj31(toSj5Y~zy%a_Oi9VlE7q$>E`1w* zHt&{$z`oBS)0Ws~h`7(G#v+fhHW_niHX#fj<@f`0of z^WqZtX0v3P6^F>N_?eBnH-!D1w>?Jhs6LYRxaRIStu4(JNbC1Ul4B$uhzoXEZ6j=Ok4Hr(X87n zS`B-{cWgAWyC>qgu2EpxWtHY8UHgTuj1D^v@9wrv_gZiGLZ_d3y@@4{(fLNE^(Wpt z>^sAnpko`OF z@EQ)M4X)p0+Gpt*DoKVXZx%deu)~vk(~0S~9$ox%ROe33T)CqSjB^*y_-mUb>l$(6 z=fgwWJXfzZ$(m_;dU5Un&#wMGo*f~dI-2Y4v_8$eV)N|Lna8cdj||QvS`+cW#4ak_8)(>@&C1|Jxe){=Q|dx z2;xjnojx;pOQhw64*dr_4=?oCcW%+;{vWmE=dNSLYd-#8ed!-}=&qgX*3Wy^zxlxJ zzo(_j>X>)o5ba&>0#S^?AJ!grq*UIM1W=E>c~lJ%wij@gF|cv|28 z6VIobk3ogW!egrC0KD&En-}|Em2Sc34}a)oAC{M;hI-vJV7O z3?|OFm2&juCs)UHJnOBpj>U<`-_B{UcMgpE;*iPn&Ti4>6PNxUkH2d9qb0}MC&kFf zx&GdhO!M~ki9*)3-}Aswtp^CnE!c5HMU}U`b(gf)dH;VKR(bj8~^O$}rk8KAgT^hS8f@o`-R|AP=MbbbdKTp6M2{ zKuf>yPJa)ir{9xd6q?>4%P7UfDF70YoW56<(SV6r2*mMXoHX4^j?sfTQjBrBw-}=g zy1nb=fc7d#Og}5fs4NfiuszqNc{_owao+r=90}7ZLc}5xLRS;3(>FcE!S;YdpS=m4)3jyIx76t}ZX`o#U E0C2?-TmS$7 delta 1357 zcmeyepXKC!mihp1W)=|!1_lm>C%YyEehy6Q^kQOQ=;38x-~x&|rljQO73){p-@|dsFZ1q?^<7rOC#n28!snBDgmgfa znXrr;t60#(J$scM1N}Dg{CZ;Kxuxi8PvReS??NHbEz=$Z|5Rw+tk5ylFCn_lSnqmS zxb{QQ6@P3uzn*bdgLPhg)0qgr_cv{)o;kegxmf1a$!4`l2R*jsCCoh(k(;Htu623< z=byO;JEwLREYB$NZP~;+*-|9Wb&@DMLl;k;ett9O?qiECyGpgEo=BWn?IqIgTF-tf z)r|H2w)+Rm*^9oFOw^5D{)|8mN?CAU*z$~>EPB^+uo?~ar5vO-K@>k zJI=*_BiO6oag)$P)4R&n7jIl^oxaR)qHlbvI$!_O4c~9c)TIVJIB(A}!!YQ>r?Q>N zGu8-2ToEtoE6TVw|3S{{L*9ow-GBc-bhBD*&g}PJw;%s_Sc9+pzd!G}e+=+c#|%t% zo7Jx+B@C5r{ZFAo9FZy^UZNfFYexw zQR?-JcN>52MeAd?&PjKiRwN;F=e# z^F*dDG>uZJW;@J#XWJsBYfba#zN}-M=d*9Iz}$J6;#(cUfAdE0^oA^RajnZ+HB~d9 zan8KLvNd^I`Bz*q*AlxU|IGE1#O(P=hQAr%J_dQXyJY@ipoh2XNHE$n0vXeDB^lp< z=x8a%NDfd&eErJ$^z=k2M!D&?q!`V4QS#<=IcY|1kZNyfMj6JU=^4_DHufGY3=C0@ zQ9g+wsTD@SVeY^%3h-uR5@AMU?pCG@->?h@1|J|62ZkyPENOhnHJOiFa{3=>Mjm-= z;g3*RGF@GUQ4wUhmkgr}Q%K3=*(H+G8)X=^7*|bSBg1IRyp4--`adp4`RUxUj9ScJ zxWPQx=@xQ~eAAO<8ReKy@-j{rm@Y2BC;%*MAYe)3J$|sH#B@6WMz-niKt|jU z1Pg(U&|yj!nZ90*QDS!&j=BxPhA99S!Aik_TqYSf|B;)it wNk$26^pJ3BW-L+zTB!oWs3D;;ycom.sun.star.sheet.SpreadsheetDocument,com.sun.star.text.TextDocument,com.sun.star.drawing.DrawingDocument,com.sun.star.presentation.PresentationDocument - service:net.elmau.zaz.latex2svg?selection + service:net.elmau.zaz.latex2svg?from_selection _self @@ -38,7 +38,7 @@ com.sun.star.sheet.SpreadsheetDocument,com.sun.star.text.TextDocument,com.sun.star.drawing.DrawingDocument,com.sun.star.presentation.PresentationDocument - service:net.elmau.zaz.latex2svg?dlg + service:net.elmau.zaz.latex2svg?from_dialog _self @@ -56,7 +56,7 @@ com.sun.star.sheet.SpreadsheetDocument,com.sun.star.text.TextDocument,com.sun.star.drawing.DrawingDocument,com.sun.star.presentation.PresentationDocument - service:net.elmau.zaz.latex2svg?app + service:net.elmau.zaz.latex2svg?validate_app _self diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py index d7efc51..6c5c863 100644 --- a/source/pythonpath/main.py +++ b/source/pythonpath/main.py @@ -7,11 +7,21 @@ ID_EXTENSION = '' _ = None -def selection(): +def from_selection(): app.debug('From selection') return +def from_dialog(): + app.debug('From dialog') + return + + +def validate_app(): + app.debug('Validate app') + return + + def run(args, path_locales): global _ _ = app.install_locales(path_locales) From 4c1183102b77e5a5919ca859f19ba2e2e42302eb Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 18 Dec 2020 23:29:18 -0600 Subject: [PATCH 07/13] Move function validate applications --- files/ZAZLaTex2SVG_v0.2.0.oxt | Bin 73716 -> 73742 bytes source/ZAZLaTex2SVG.py | 18 ------------------ source/pythonpath/main.py | 22 +++++++++++++++++++++- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/files/ZAZLaTex2SVG_v0.2.0.oxt b/files/ZAZLaTex2SVG_v0.2.0.oxt index 6c2c6fbb4e0c08382336353af7e8a8c88e1c47ff..b19c02d98f028c4558850cfd275e00239f033977 100644 GIT binary patch delta 2444 zcmZuz3pCVQ7oWdzy=lB>Ff$l=CBl$*UYBb~GbD_>lNlEw*UWfLB(E!v@%RhXB+o*G zh!9Gmh?~4edR%ksl43EM;+y*JUH7|deP^wG_S$Fv&faV7ea`yr%|8Cz`~0GKXDGi6 z1OgF)l(dM(^8qET>3mZFJ-?el@FI73!2}?XYt4;6Yu4#@`o*E^$ zVE3g^@AdVWvkFerw5qml+ET-}?^fQcn)JaHaI=On97`x?8<(x$B_U$wsmp zSw(TSqIuwFxuUv^3N<&5{Z5sR<#_on%0H>p-*?G%uXjuD3zKP9X`$hOjEwc_AIa!j{8V$3lmt67PLskLRqfb{~N@OG9OK92y`hW5K#o z1N9=WsgF%AzNYK1Ke@$Pc%r*f7l2h*tX8d$A+pn?tBn;^jhsyuBYV2LaT5$Re6?2v8KTHo{qbvF ziPy4Fed`wJUAL~y*QK&9kD}`|{awElg*LyRKT`Fhma@RG2No3;P2ZKVc zHp=Y2)rQjhO~hEvHTAcYpQayxQ)Zec>xyi3ADHo9T5Ed0jk0K|-|_ktK4N9R_}WTB zCxe)&{kR;S^C!WJ`Dgv}M}!ue4h2Emz4K1h8p#f#+WnqOw!3}G%0%a9y|Y~{a~M(o z=bY8}E>)3`Lq%EWd{u8@L{2x#r~1B>*4E{??VL3FVxzp^*7dd=wWkj)f3#bKHJooJ zy;t~s=tetJ(QRhHOh5Nxw<-bWq3WF#jXL<$aV#~|m7Od;HGI;)SAUgYCo24oPo)e} zO-`C{`-yGhx>!9|1NeFpMou+sOcEW4Vl_I>iPA}u4MOfnS+!beIwJhp8#$=3Qh|Z5 zl{O#UqDjbpQo3ac?kpB*T4{1<=oeSwxNM4!vBWW#m+kQ;XeJGD&gOhMJ!U{eyged& zEyyPJ_DD-`MJ5g`wgQ6j`L8~G`AkSsmc#cCX&%-~?|Nfh!EPcFmtW-{(w_tv@KRVAgtZn319GUOD|$g-HT*~=fVybDVqC*nn)p(ornmLKY|j9y_3XE+xOB=89qmJyb5okcT+xf<)e zzkOa0QjrQ(YjgVrGuS&HQR=Ef;RY=N@pA+}9WXmgV_3m~Vg}q{XC!OZ_irou37*>b z%0Y)Z?$#sX$62YZtv%G}=U&o?ZJ>__tg@99kVpA{kwl@D;Li`Om#$gOSC-`ZWuE7Q!3P z+SdV`*Q16vHTQfavVSlpZZK|dF8~4Q(XgS_1ABA#?C+o8YMVZQOy>?VO0Vl$#-hy&$2eCwTZczmw$cB? zDy?5dvPtwj9-mAPT`j-^5QrTV0+HhJNfa%XbS5$?FqWhj5lD{YQA#zV-+Azm%+QCg z2UJmvWp)&c|4d}FvnAUGuSG75OTlW<9?!t`DE?is;Rb{@aQ#!#61V453{7A3<>_Jr zL$Yzz(oVb4VegtQmf1124BtmThYAZ0aINU0x2KZsQj>`?c{ejs7bJ15Qh5UkS(W+X z+QqJQ7u-Fy!c!nZO-Ni5cY?fUuUlW5Uvop)9#J3aBb_m+vmYav+$ z`inLR7u`#8O2BXgu*y?`Xh0f_Pyif- zN_b6r|IU0A3{(J6;G6<*nCF&K{H}0{fQ7`rrvk7@5ztfqcIg5i>F5a(1meOId0uD# zUR5c%B83v72=I#{Acp>bZ{Qy!0@P3fRCo!UlmH~(rP#vqSOl1&1ZV>vzy>A263P_; zz+Djl4T>TGZK$c(cMA!cpa2Ol7zv=D5)uF?De+$b43vifF!6uFV_@H%aL@_{2!Lzf zLLjBSAAJv@!`C1K-a-LzFc}5t0cXJ`6fbiLIEw<1fEl=r0@Qex5*k246A%EHi~!)` wcxQl52Kv|V^sS6YfDF>nfI9l`LaOuftMTODHF@w2Bn!^+HUYFE??XWT1HC*n761SM delta 2393 zcmY*bdpy%^AO8KelD0K-US?K3j8QREvJOr&LJ<`zb8ah#L~?kTLvlzCll>GavZS1a ziUu1~bFu*mQ`UFT6qCJw;SNt<>A=*7y?YV-{6EQ`Wb+AMSK3HaD0TeZ~d zqI2~hA0{Sx)YWj-Td~T@Z0(+$h#Bp5vt7JKSeh(cPSv~^*wpQ@Ri?QJF%r^Y zaA{;MzmwHXKTgl>N^26&E_ut$UK8~kjcOO!BBy47_ej@xX1 z7nzh`aSW^)kY}oI-^PGWJ`R;{y^%6q)W~A9wA*SlW+rVIEL|ajG$6!V(OP&Ksk=N_I7NLjHYs* z?<2(MSXgWn9*`P1EbNMAmHTD+qhk)&6enR;`B=tmblk2wGt`p}#R~#7rVG|A><@dP zfaZjZ%RO}8cegw*2+!$zZiU3-!adWLg!F5{Voj zr1i+PW56;1DM=|d-uHbH?p@x4WGd4v?`W3t+~O*xNITojTk=buy4eqV2fy7pl&n$6 zcFXTu$PSeb*;|59m`a`el6`b0wK(-n@=D>JwzIzHe$4AvX4Ndas@W9Ue9>uj^xt!1 z%?aB*X)<>9@e8JLtmu}3(cyg@W9M~q5;gX`xeEE=uihK{vNi<-$EMuLWf(*)X7mae zOQ2I~40x_U@i{ktsN>ti+_&3S=fs3fEV8efEGC8LDfc;>D%5)vHLT11u1=%T%4_Ni zHrMsfM52V~7Zv#YiW}4Bi#sIsPrjXX6q)*eDek&zcJMUDnsul-j<+F_K?KV(6y-LjbcHo3{;I8ep9ZgQMO(TW0*o>`N z>fJrwe7fH0Foei7QKBlyhs`f?;`I>6Ib*wBg{W7uQ;$@U&*I0nck_`d*>#=#dScjx zuE)oUrs7UKVUb57Lv?qBHX;|aIc>l<&c}DzGkV>Xutf8pxOBPcjOwGs^u}ItdC3I1 zaRPtBS1Bj?>S7S1RcoT^Gh3cm?MaO{f3J$|Druor&V+Ke$$dL`=fC?S)QrbyhhDyye4LYCI4t|_2M5-ipJ6MYd;(|n>`br zWaF(9+^NvxPTBkobGGQUosv0^QSI@xytoM~EPoL)G_K?7BZtIICe}G*^GK2L^Yxn> z+O77_9@Q>%IzqpyMQWccEv&=1P7T^5k|$eCMFW(nOTr@my)FmpSAhSkuBt1DBnH~B zj%}AWJ(u@NTvoAQ200MRI-6>IsznDCCtQ}--2EiFM>!bxc|ako1C8^ry6GZbg*We?;C|+-scuLf2YDN!q+f<}`}};j z(+Sx>B1DHT~UdjHOa^F9=7PhDM8*85TBj`;WB2vVR6>%l281Sx^eVtAnh9uvc6 z9JoUao;Yw9`uAFgN^zjJ_~<PgE9~e1!9oDqy^Cg$QBP; z!ROb3kP!yNLMnJr5$eK&O7OLqe<&45QU%n6!)5*z_@Tfxkb{^4*$_(ww17D&Kv60n zR>yB^P)Lxi6$q1q|GUkIzj&~%${h^?KmrT^ Date: Fri, 18 Dec 2020 23:32:27 -0600 Subject: [PATCH 08/13] Move function from selection --- files/ZAZLaTex2SVG_v0.2.0.oxt | Bin 73742 -> 74140 bytes source/ZAZLaTex2SVG.py | 17 --------- source/pythonpath/main.py | 67 ++++++++++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/files/ZAZLaTex2SVG_v0.2.0.oxt b/files/ZAZLaTex2SVG_v0.2.0.oxt index b19c02d98f028c4558850cfd275e00239f033977..5c353289382da2747ae8351e2a88a0f9479fb77a 100644 GIT binary patch delta 2819 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 mb_+@3SRsj`3Tk8jGF^ox?2#$}o>Ld Date: Fri, 18 Dec 2020 23:34:14 -0600 Subject: [PATCH 09/13] Clean --- source/ZAZLaTex2SVG.py | 57 --------------------------------------- source/pythonpath/main.py | 5 +--- 2 files changed, 1 insertion(+), 61 deletions(-) diff --git a/source/ZAZLaTex2SVG.py b/source/ZAZLaTex2SVG.py index 935d334..462ab86 100644 --- a/source/ZAZLaTex2SVG.py +++ b/source/ZAZLaTex2SVG.py @@ -8,27 +8,6 @@ import easymacro as app ID_EXTENSION = 'net.elmau.zaz.latex2svg' SERVICE = ('com.sun.star.task.Job',) -TITLE = 'ZAZ Latex2SVG' - - -_ = app.install_locales(__file__) - - -TEMPLATE = """\documentclass{{article}} -\\usepackage[a5paper, landscape]{{geometry}} -\\usepackage{{xcolor}} -\\usepackage{{amssymb}} -\\usepackage{{amsmath}} -\pagestyle{{empty}} -\\begin{{document}} -\\begingroup -\Huge - -\[ {} \] - -\endgroup -\end{{document}} -""" class Controllers(object): @@ -72,27 +51,6 @@ class Controllers(object): return -def _latex_to_svg(code): - NAME = 'temp' - data = TEMPLATE.format(code) - dt = app.paths.dir_tmp() - path_tex = app._P.join(dt.name, f'{NAME}.tex') - path_pdf = app._P.join(dt.name, f'{NAME}.pdf') - path_svg = app._P.join(app._P.temp_dir, f'{NAME}.svg') - - app.paths.save(path_tex, data) - cmd = f'pdflatex --interaction=batchmode -output-directory="{dt.name}" "{path_tex}"' - app.run(cmd) - cmd = f'pdfcrop "{path_pdf}" "{path_pdf}"' - app.run(cmd) - cmd = f'pdf2svg "{path_pdf}" "{path_svg}"' - app.run(cmd) - - if not app.paths.exists(path_svg): - path_svg = '' - dt.cleanup() - return path_svg - class ZAZLaTex2SVG(unohelper.Base, XJobExecutor): NAME = 'temp' @@ -105,21 +63,6 @@ class ZAZLaTex2SVG(unohelper.Base, XJobExecutor): main.run(args, __file__) return - def _from_selection(self): - doc = app.active - sel = doc.selection - - data = sel.value - path_svg = _latex_to_svg(data) - - sel = sel.offset() - args = {} - if doc.type == 'writer': - args = {'Width': 5000, 'Height': 2000} - image = sel.insert_image(path_svg, args) - image.description = data - return - def _dlg(self): dlg = self._create_dialog() sel = app.selection diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py index 3317c8e..d84e85a 100644 --- a/source/pythonpath/main.py +++ b/source/pythonpath/main.py @@ -4,7 +4,7 @@ import easymacro as app ID_EXTENSION = '' - +TITLE = 'ZAZ Latex2SVG' _ = None TEMPLATE = """\documentclass{{article}} @@ -92,9 +92,6 @@ def from_dialog(): return - - - def run(args, path_locales): global _ _ = app.install_locales(path_locales) From a797314ff3b539b52ccd280d291bc08d0844d15a Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 19 Dec 2020 21:53:55 -0600 Subject: [PATCH 10/13] Move create with dialog --- files/ZAZLaTex2SVG_v0.2.0.oxt | Bin 74140 -> 73594 bytes source/ZAZLaTex2SVG.py | 134 ---------------------------------- source/pythonpath/main.py | 132 ++++++++++++++++++++++++++++++++- 3 files changed, 130 insertions(+), 136 deletions(-) diff --git a/files/ZAZLaTex2SVG_v0.2.0.oxt b/files/ZAZLaTex2SVG_v0.2.0.oxt index 5c353289382da2747ae8351e2a88a0f9479fb77a..68ed380efc51ae9d54de9ab93c07d412b3b7f816 100644 GIT binary patch delta 2307 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 delta 2775 zcmY*bc{tSj7oP7J#=Z<{m<@47h{_t-%Q9p)mPA*!DU56lW)jL;mdu2~*spy{SwfPe zx>-huEG5boNyeHVp5J}$b3gyQ?{m(1&pDqz&U4OI8-&&g;ju7ffe3*>Aa)S5CAF3( z6wI=Z^|4?EK(1>^wakN()Vor8l)4rPkbB~5mV_M6xfxRnk6%T?(LD?0&wBZc4G7T%42 z>5U{`jP$&@xlSb18nCAId{GfLPG{&z(~dWc!%7{V&-s2GpRkLkNN%1#wUt3jhtM-N zX+otYQ=4ysvn*4r-}HZA`@NKc9nS{4QN!qBUzvH*pE@*XInz8REQhcO7G8d$Qn( zizj;4KW<4v`rC%7&SZY&X-#W}Ak zKFGi%OMGA#!EU3Sik`t_)}z*Y6Qjvmm6v&G7raG`GZW6n-`Nt!LGk_Xl;0-V;&oLE zYodZYkXh@7`47qr>R{8Fj+ysAvi5%L+Z8QKX=)C|j~3iH0vlx2I|G<7w(=X2`v|mV z(*`q^=BoS$J-Dw$4T+E7SO?06#+EEjpjboJ$lUX{#z=k-iVdbFakZIb10jbx3&gsM zUCC9Vz&{2U5L~efw7{GbV25kSoQ!AqU;f%UKQ%y6Y^r--qJ>3$#)QT<u1*Vt*REIy*zSiHwKNM3O8f z>W@OJL!q3lo+jlrDS-;fEM%gqsiFRRODh|*^@U8l`q&SkGenp2YmsMcMx1CU^kw)G zjMr{vgl4?9>n{o7_%hWZIFNc(B;D@LD{MB@_wus{Nl`h}cYLluzdq`8`Ciu1vMVbZ>EOO^CRHcwLuthxv8o`!^&$)(jFv zx%Wh$+XoqnQ(o_zbvTO@GzP@H1fmnFSWu$3mTc(XUg3II2aY+vl4QjBhQO21^-DT3a;UtU?B5T4H+o79!NcrQjDI3L ze654i+OaObQ_K(cc6Q>Epy5W@fT+S}qr`X1w~o!V`wk85!}ZgCgl& z6Iy39hxL>gdL+Zqq!=aaNM2{I(M~<1c6;wcy**$2v7W49?u)dR(M3Ve)o%0=wIeD6yf(q_UuxTGo=o_9vq$DPL7)G(sz*DU(|A=x zEHHGhGxcG?IjunZ4F|vQ-tg^<9|yOO{<{wj7eP5|tQYU{{vu${@~n_%0fAmY({z2| z=g;|L--Q5t8@Lx=p%4=()n_1aS+GzLYmL`85L z*cG28@~{e;G3<$n!X`tuHO-Y=i}+tgnUsp06)uC>Co2n(SX99|kuUD1>Tk$@MM9(! zUJe5tmrE0v$4y}|rrt&h_>`DwlIPG;_^RbmJ38LizfGW|BbjF+jtsVY*NqI(ap1{raz@@}_b zXDD?^*{b*Wg=THpXSv0*@9D?xJ#Ll64T0wW2plXccV@^NM05xahlY^D9X(1O)66pz z>0Umzxz(k{s#5d1#dRvS*NctVN>hBaG0JVYG z_lL&B=Dd{V%gZmAZI6FzFoOq=K_8=I(ZuqHaLh&7dD2$)SKf??@TSzO`H@j+)#Qkk zjMeG-I^PafC9bGOVazHyD({4n=(M$6XA_S(&qPvye|}ew=CEsck&W=^#Pi4Y9RZmU ztnT-gcI~*kQRuK`M(48J>8zDZ=Swaw=0>4h6(exxUjLIJPAmdIfDJjR-UvWS?7!m* z0>Sq0wtrq>WM2Ta3IT{6%K8sv(!pusfg)5W5|9N)a8ger0ZnimH2eNAv7y!Xyg#@T(qWkIHF#tbXC2~In=zkKd```co diff --git a/source/ZAZLaTex2SVG.py b/source/ZAZLaTex2SVG.py index 462ab86..5f67048 100644 --- a/source/ZAZLaTex2SVG.py +++ b/source/ZAZLaTex2SVG.py @@ -10,50 +10,7 @@ ID_EXTENSION = 'net.elmau.zaz.latex2svg' SERVICE = ('com.sun.star.task.Job',) -class Controllers(object): - - def __init__(self, dlg): - self.d = dlg - self._path = '' - - def cmd_close_action(self, event): - self.d.close() - return - - def cmd_preview_action(self, event): - code = self.d.text.value - if not code: - msg = _('Write some code') - app.errorbox(msg) - return - - self._path = _latex_to_svg(code) - self.d.image.url = self._path - return - - def cmd_insert_action(self, event): - if not self._path: - msg = _('First, generate preview') - app.errorbox(msg) - return - - attr = {} - sel = app.selection - if hasattr(sel, 'anchor'): - attr = sel.size - anchor = sel.anchor - anchor.dp.remove(sel) - sel = anchor - - image = sel.insert_image(self._path, attr) - image.description = self.d.text.value - self.d.close() - return - - - class ZAZLaTex2SVG(unohelper.Base, XJobExecutor): - NAME = 'temp' def __init__(self, ctx): self.ctx = ctx @@ -63,97 +20,6 @@ class ZAZLaTex2SVG(unohelper.Base, XJobExecutor): main.run(args, __file__) return - def _dlg(self): - dlg = self._create_dialog() - sel = app.selection - if hasattr(sel, 'description'): - dlg.text.value = sel.description - dlg.open() - return - - def _create_dialog(self): - args = { - 'Name': 'dialog', - 'Title': TITLE, - 'Width': 270, - 'Height': 250, - } - dlg = app.create_dialog(args) - dlg.id = ID_EXTENSION - dlg.events = Controllers - - args = { - 'Type': 'Label', - 'Name': 'lbl_code', - 'Label': _('Latex code'), - 'Width': 70, - 'Height': 10, - 'X': 10, - 'Y': 5, - 'VerticalAlign': 1, - } - dlg.add_control(args) - - args = { - 'Type': 'Text', - 'Name': 'text', - 'Width': 250, - 'Height': 75, - 'MultiLine': True, - 'VScroll': True, - } - dlg.add_control(args) - - args = { - 'Type': 'Button', - 'Name': 'cmd_preview', - 'Label': _('Preview'), - 'Width': 70, - 'Height': 15, - 'ImageURL': 'view.png', - 'ImagePosition': 1, - } - dlg.add_control(args) - - args = { - 'Type': 'Image', - 'Name': 'image', - 'Width': 250, - 'Height': 100, - } - dlg.add_control(args) - - args = { - 'Type': 'Button', - 'Name': 'cmd_insert', - 'Label': _('Insert'), - 'Width': 70, - 'Height': 15, - 'ImageURL': 'insert.png', - 'ImagePosition': 1, - } - dlg.add_control(args) - args = { - 'Type': 'Button', - 'Name': 'cmd_close', - 'Label': _('Close'), - 'Width': 70, - 'Height': 15, - 'ImageURL': 'close.png', - 'ImagePosition': 1, - } - dlg.add_control(args) - - dlg.text.move(dlg.lbl_code) - dlg.cmd_preview.move(dlg.text, center=True) - dlg.image.move(dlg.cmd_preview, center=True) - dlg.cmd_insert.move(dlg.image) - dlg.cmd_close.move(dlg.image) - controls = (dlg.cmd_insert, dlg.cmd_close) - dlg.center(controls) - - return dlg - g_ImplementationHelper = unohelper.ImplementationHelper() g_ImplementationHelper.addImplementation(ZAZLaTex2SVG, ID_EXTENSION, SERVICE) diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py index d84e85a..2bf31b1 100644 --- a/source/pythonpath/main.py +++ b/source/pythonpath/main.py @@ -6,7 +6,6 @@ import easymacro as app ID_EXTENSION = '' TITLE = 'ZAZ Latex2SVG' _ = None - TEMPLATE = """\documentclass{{article}} \\usepackage[a5paper, landscape]{{geometry}} \\usepackage{{xcolor}} @@ -24,6 +23,47 @@ TEMPLATE = """\documentclass{{article}} """ +class Controllers(object): + + def __init__(self, dlg): + self.d = dlg + self._path = '' + + def cmd_close_action(self, event): + self.d.close() + return + + def cmd_preview_action(self, event): + code = self.d.text.value + if not code: + msg = _('Write some code') + app.errorbox(msg) + return + + self._path = _latex_to_svg(code) + self.d.image.url = self._path + return + + def cmd_insert_action(self, event): + if not self._path: + msg = _('First, generate preview') + app.errorbox(msg) + return + + attr = {} + sel = app.selection + if hasattr(sel, 'anchor'): + attr = sel.size + anchor = sel.anchor + anchor.dp.remove(sel) + sel = anchor + + image = sel.insert_image(self._path, attr) + image.description = self.d.text.value + self.d.close() + return + + def validate_app(): msg1 = _('Not found') msg2 = _('Found') @@ -87,8 +127,96 @@ def from_selection(): return +def _create_dialog(): + args = { + 'Name': 'dialog', + 'Title': TITLE, + 'Width': 270, + 'Height': 250, + } + dlg = app.create_dialog(args) + dlg.id = ID_EXTENSION + dlg.events = Controllers + + args = { + 'Type': 'Label', + 'Name': 'lbl_code', + 'Label': _('Latex code'), + 'Width': 70, + 'Height': 10, + 'X': 10, + 'Y': 5, + 'VerticalAlign': 1, + } + dlg.add_control(args) + + args = { + 'Type': 'Text', + 'Name': 'text', + 'Width': 250, + 'Height': 75, + 'MultiLine': True, + 'VScroll': True, + } + dlg.add_control(args) + + args = { + 'Type': 'Button', + 'Name': 'cmd_preview', + 'Label': _('Preview'), + 'Width': 70, + 'Height': 15, + 'ImageURL': 'view.png', + 'ImagePosition': 1, + } + dlg.add_control(args) + + args = { + 'Type': 'Image', + 'Name': 'image', + 'Width': 250, + 'Height': 100, + } + dlg.add_control(args) + + args = { + 'Type': 'Button', + 'Name': 'cmd_insert', + 'Label': _('Insert'), + 'Width': 70, + 'Height': 15, + 'ImageURL': 'insert.png', + 'ImagePosition': 1, + } + dlg.add_control(args) + args = { + 'Type': 'Button', + 'Name': 'cmd_close', + 'Label': _('Close'), + 'Width': 70, + 'Height': 15, + 'ImageURL': 'close.png', + 'ImagePosition': 1, + } + dlg.add_control(args) + + dlg.text.move(dlg.lbl_code) + dlg.cmd_preview.move(dlg.text, center=True) + dlg.image.move(dlg.cmd_preview, center=True) + dlg.cmd_insert.move(dlg.image) + dlg.cmd_close.move(dlg.image) + controls = (dlg.cmd_insert, dlg.cmd_close) + dlg.center(controls) + + return dlg + + def from_dialog(): - app.debug('From dialog') + dlg = _create_dialog() + sel = app.selection + if hasattr(sel, 'description'): + dlg.text.value = sel.description + dlg.open() return From c72d5e58f4b0fe61e9a1ff12858249f163fea397 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 19 Dec 2020 22:06:07 -0600 Subject: [PATCH 11/13] Add icons in buttons --- files/ZAZLaTex2SVG_v0.2.0.oxt | Bin 73594 -> 77150 bytes source/images/close.svg | 52 ++++++++++++++++ source/images/insert.svg | 87 ++++++++++++++++++++++++++ source/images/preview.svg | 113 ++++++++++++++++++++++++++++++++++ source/pythonpath/main.py | 14 ++--- 5 files changed, 259 insertions(+), 7 deletions(-) create mode 100644 source/images/close.svg create mode 100644 source/images/insert.svg create mode 100644 source/images/preview.svg diff --git a/files/ZAZLaTex2SVG_v0.2.0.oxt b/files/ZAZLaTex2SVG_v0.2.0.oxt index 68ed380efc51ae9d54de9ab93c07d412b3b7f816..9010cbf021981dc500d8cdb54d8b782fc5b7972f 100644 GIT binary patch delta 5443 zcmZu#2Q*w=*Pa#pbQv(DaU-*wk{_TA5UJNCj1Hj#lg7B&?C0Kfy}RM!v%fYxVU>m1-<p-Z zQPwzdu0s{7{+MBi5oQ%ySC+cp8xgJ6U5JzWGb1E)K!Wa*4n|dA#?*IfqNLXIY#FvW zW;&uR8LvKs*Q0?vn(%oN{PFttCGU`kg^4&^!c-xCKc){KE;XMz*zjH%PjSDQDf2w$ zla@7!p-*6Tu;{Ss#6;5e8k>59N?bMG?p{#zR9(4=f9jDK(Rg$EkZ0fqVq<+F-@S>k zrRUDaSLhg?1SwJ=x!|XQ(%cWIlDuDLCD}#{iZ1F`LR&Nav#jIe|!2~9Ck|$!8!t-%^IwcW+ttt8|hCh@U5(H-O1_@+1XN3W^<*c>GTG^eX>JV zKWieV_x4bEgz{Kf{WmhRFVIzBORrb2q1Wqr)C%HzOp1_CiYW$fv@AX9#L?2sE)wI5 za78$&Xr@t1pEgDvuX2oh4yRdz&qF7{oZ2HCsVh^JPd%wKjG4?}m&g7vSvnp8NWq*Azl@svTt8y28Js z6=*ocr2I@D`zDw++OU-7Pg5y-vitsuS0PJz^!xXC)=@1#Exdky5gV=GWAl4bKKzbB zmY3(EU$njXRJ6UN;kd$5Ei{an4E`)Y<{=971i#vmRfgjb=a=MKe$ngSsQiW9MZ>kY zwI_u^(`G+RuIK0giFvPJ>A6fXbUt5ZF3%H^p3%vT-Re&Z z_hCaiOB-9m;*x@D^kk>UzYEoZ-Iw2jr`_K~RsDJ(9+IDq41)k0nl$m}Ao7 z?;&8-(z#^1IegAvv#&hse#I#%8Dm!%0KlGMDfZ?BDao1feNTub9x@QJm)aBL8NHhQ zMdF`zfIk8Dw;5TdBW@MM7vM}{L)jXz=uNDGNd!}0r#<+9<5ho%oeUR2D6#cH8o7-k z&XJu66QA$Tboro)%8J|sn{+&RI6H~Y0cykiQ?E4pMEuL?4~3Gz^;#}(t@FVyJD&@c zD4Da~;`31gGXk8nF`-OZ?4mLT8?5P{+4Qs>T-nqUOCPFo!QdJXqYA?#9U|n6#xOlS zh+e`E3-qc;Utk~|2i%w--a-ZKw;He=xn_7;k40DST_1^*tCe>3u)Y5x`mIlAe*jxe z<#%^i5qXOkjTs(SJYEfTF&;c6G(?st{)Cfw!8z0a0m$uhy`mAgYmdTTOi1z7QY=9z zQBko+3Dx8r8v#C1i`6XN6npv0wY4R0rgB(;yxcYI16E@3FnH|#&M#mYCvCsAqF1;C z9tB%VJ&f?sXmr)WUil?R)3OmBx|qlT?UTnY!JD2to0h!i{tRW&{(kn$_LnB>$?b=T zRSw%QGcjm`WmSYi`?~~}&Q|%w)WG@e-;inXuGzs@sDE3#ZJgafrRD1$$n@c57w1e~$j@mSruuJ?C4+D&4epv>htNbgD?I z_gR{jkeZLW>+aT1|Ijd6x%pR2k7r7slZm)+f7(5;@Uev^bbJjp+N*4V5yIm`b9X~n zJ1+L!B-A2Mp=WqSGod@LLU!M%G`i1%09wQchGQ*+29ed)&Zv9-KBRb4tU zAsib9sh7{FhdLv@zLgynMIUP>mSpyPLwrkqKV}xS|8VOE_^B2TIX>;n?cyab>AvD6 zP8_bh(DvS;3Eeh0fz4ymPvP*%`a*YZWf|E7BI2#GAmMw@#;q=Y#J0OTEw0~(SiMs0 za5Eh9-qAHZ-`HvaYr#nNA}VHAq2A^=TP5EOcJh!&a4DEtXog#!vpL&R!5DowU8d`u zw`$hqYvX^#JE}bH@^aVYshZ}=gt@KEQ>_ZEc9A*#JNb{{I@>=V)u>}hY;CI!N_3Pp zPDTn9t7W6->jm86-WFxZ$4kTtp)40SpbHzuJ1H(=FR;xzSDO5Hg;1(Br)98?WJ)>A zr>aawGTf?K^Q9QKgR9yZG`mkLymwmYTM8oN{Nnu^fEr$Q^m2Gdz+h@mBU<3sZm8)L zD}JCxdSWTO{kR^P5NIb}%%D%*Ir-S%*t%Xqxu16M2xFBXw<{9F3~eRoG}~Fk6EsT2 zW2Z(A^pvSH67^T5UA$XNBq{yGPma`1iZc2(1RM5oY8K9Ccpjme?4b4{fKvvVv@Y0S zhiT^E?a|Xre$9LFkw3(TXS#}i4N^#*XT?C^W<3C>(htuR?#j_nimWh?j^tkGw(%SE zv2Rhd0w(Cl6t$$TJ2L<3J9^PS2AWBdd@ zyiH#y=+%skG@j)VBh+fqhz`wk4J1{jF2DBjU1gDY&j}|x18y==ZIKFN_8^yOtc$o7 z4aIB{!;Y=a$smn?LIicy)_qI*u+^zZ;? zTzKS?n*Au?d5tc`DF^9cWFp)0c*f=Ysiyj(*0V`6%0q{T>l5l1Z}vgxPLse9P`CU~Cfe zBKYT|sLi)V#;Y7R;ftMAN&X-2PORO9Cy-$-dB496jJZcSkoD;wUUwTfbP79k1{~v` z0^{y^l#Xx)``hKNh8;+QCZj*?5pj|(Nq`*n`N}<2{*>;!rrPnIPe1-daz;&2^Ut3DY z_0LBIt96`ZxnoY0Uz&mhN?9YrQyUUxFe>wD;1mup1nkR<-E}7P{cCj3*R|OL1;OqV z;a3qoXDcg2tO~hdL_#q|k_Sz@VX+ZZjwm=9-LZyod_69MjYrv6eNc7h19Flp;3*Z`zxTF*W9^hw3r~A`2RlVRNY~V8 z)Ney}HVf_1_W3h&M7YJ$ONNe6{VA1vBBhKd@{-zy#4(@E8e;vGd zRoVU@DxZy9co-OzJV-3Y7Q~0*jekVAX?U`J$?$ZMq0;m!743KW80Zd@_n-w0NoiCw z8Ko$l+S5{Lba`iWXlv|iiJATsy~FK|dZbQVt~TjGjLSpcT>V6zEK+Ne*w})e&@iV; zXYw3>uVE#?&R*q@k+CETM z;Z1|&D7f#Krh4F!S~VnDLS&OcgY@Sl+vQz>kLSUG=e^fj2JG;Sx+iN1lLN<6ETFUuJ~w@Kw-rnI#&@-ne{iE(eeG!=&*6u7-fjz067 zcZKgwjO zGqoq($$GOfR^90ek&EZye#ocVaPaysrK52ibhyNkD{(x-QfQB%U;#_;Aj^A)78~`d z543%KeZtVN;&OB6fH&Q3K^yB{61ezPh~c0Dm5fCbs`!w&Cc5 z-++6ik|vaS#7$Tv&5}_#RnU~UE|ehWl(5aM-neJ`(i_GoGw}q3d~WyX{KC)Q{vpxf z*IAxOEOVMf(vMz>X|FE~ED}Tdjq98P9n#LI8x8Bm+Mq7jdr}J<@<-kB%kD$(Pp*(F zl7QvnY-(!J-5v^`rZjd8%a&GnsjqrVE(p~~TOe$6Zqts?>=rbKeimZf!V1W; zqq&2duy?hg=4=>$wp+1+``LHWEq*b(tVLh0ukCQ^4Xxu4y=W->&6*a^3$q=~TBsPZ z3wnV|y87en*Ohqi*3#@VAGYI-<2`&O_hDJZRo2MK?GLzl?QjVP{qPC8z*36tyLGc! z`h+3QS(b~Y@A)R`@<;iHcm#R41ESvxx)f>meSgVHwmFDFiE6#zr#YpI#(E(g8%^M# z4Aal|1iNvz=@Zv!EaQhaQ>4A@KQI-K{d5akgK#5x%V*my`_%}W6<`?{b zFAO60rXc)(bU@bFPEMj+?E3Il(lrFi}~+8>$VpW*(k8TjM= z#rMDN|7Z>V>Cg6m3K`|!-Nc^&2EnLGMj+emyN}*x&5uZ7_n}AOG65l=2x`Y|Qt^z@AZp%$_gg^j*_CHD9 zAO=Cgh3aAfa^8Av-g^D!Lfv8g+ZJF2GGUo<15vsRKsp)&ZJax|p42yY+Rdk`<-Ump F_#ari++F|x delta 1942 zcmZ9Nc{rQd7suZ>wn$=Ysfc}z5T$C2j!en4#*%7LwM;_MT4Qezv@&=w)|QmFwrGf2 zIt@k3FhX_FMUAD_nUSJ>Dz%QKJW5T>GoI%+zx&VUoO{l__uTWx_e@QSZu3M@P9zag zc>n<50GoRs6%T{4xy>0cO_6=dOJ)X1m2jEPk48sM{453lC}v=y0YfPTCboJbW&)$} zB4vcyF`8ygYUw$vB~}p4RQj^ip^eLoe#h~c!3w_YE#>tx89UQ9RqwKOXN~nQW^9!6 z1MOwzeqFvZwTQn4W{?a=v{v&>fxQEq5(-9RqjZ7g|?e zxOY#WYlP;9PTD2@plNenBD>X*nCkP_i9bRgKUJhx65{Z53_~C0_q^m0h9^fMITyLs zd*Hi<&YZg_L4?&Pf_&lC!`r+>B>=zRL^0OOL2z|+I&tnL#``%NpH*lDWd=+|U*L)ifY z!TE5jx_cT3L`?8tp_X{~Gc9r8@wCBe$j6CYa@3-u|u?22^j)Q};Dae8P zPi)_?GEazhD(iDg{~4cQwnM@BLVGvuehKngezUC4Q)23SrxN)g#SV_Or1#{u7ct+x za{8CM=*T>Zzv@?wM=qQX@wpxFb|`Pd8fCz4JrnFtI32??^_o}gcDD-I?Vhh^K1(3i zEvDG+F?w;Ro$aU!s}nW)yR-`1NPHI`>mYMA*1P=XT?y3g)lru9c@;}?H}!>SidTM1 z+M@yM#7M6AdvzsE^QpVTq$^-Z8&S(r{U<%^;0WGjT^)+W)cA`BmTBfnCzy5IkTJR) zDNNT{r}Hci@obiYEpw%bB!q8#{hjZwaYNr_ zb!&!;u$1185^{Qd$!aPlT;G=B;ww!$-*nWi^jOH|a+R+E{XS}v@o}aTZEda0$fn@j z2(0r5vN=IqVv|tpO*2@ToG*oXHykhK3Pi5Cb}dpD8vdNhBRe+S>*UySFPNcJ9q5c< zH~uFIR!|b5Yf~7j-BEtBF{o|T_lL3AvI$89KH|w|ugv2u>PgGdulCI2*>G-u>LWNVE2osZ_d_q2lX={xZQK&j7*3oDrwW zJwxq*ZS2DHBWn04Zs_A56V7Pln}m`W{3VNkr`@+Yb4|xn{iQdo@rsx2N$L*BCQ4D= z!~+6LAu0FLo6z-eC1sG1w^@B|w~e%=OkM8ms@amVemPvAppq6BU3f#~lP3c?@_tUP z@k-m<-o}rr3GNSPozV?D`o`0Dd=+y_S6`)X15^GUY2d8n;NDP>(U!-~lmV}XGPXFn zwfkU|$eQ)sp8pL=OOGumk=f?2G^F{_`&rel&t}kW4`0za9>y|pzl~oBoH|?iZKhRg zU*PkOm^D-}1L4*TUryeBw!&b6by2GnqjY@pcTQ!`Xq~Z*zS*zSiVv~ZucJYS1CXG+C*Z| zrrSDq=QBTZlPA}iIL7C9gxqp`?Dd`%s@PeJyKeh1jB>xR{dfo4^d{^myuK`>Ql8|J zUKelgb^C^%=TX! zJ>hJj#Ls`Ud<98kK{Y4?3x2&H>c)cR``p5U^nEt!gDOy-K8S?IgqF{L=E7kBI3ye* z2aA)83kQJ6cv?_Ic%&~a$T-Y5IQ(d2yb}q8$%`C}8>aIj6~@%O2>>hYFhKF(u#lW& zF&Z?415d%oa7fVrB)|d@5Zgdl9MTXx1WlqrS=2!~C%GEAA9H{uNI{E2id>fgAsz-q zLoSA(iAbj0Kb|VoWdKS;jfS8a3@r~07=pGizQR7y$HI@&=(PW@&84^5PZ|V(pI`u> zaPZhawcSyK{P3W*#(vwZ5KXLKgu6&#=6`?i0S#5-LEZh%_2EGk*oYGJ@n2ugNa&kj WAY&s?Q~Hpwa}%Nfkc|;u0sIZSjZPQ< diff --git a/source/images/close.svg b/source/images/close.svg new file mode 100644 index 0000000..8b7bc47 --- /dev/null +++ b/source/images/close.svg @@ -0,0 +1,52 @@ + + + + + + image/svg+xml + + 9.4 + + + + + 9.4 + Created with Sketch. + + + + + + + + diff --git a/source/images/insert.svg b/source/images/insert.svg new file mode 100644 index 0000000..21d461b --- /dev/null +++ b/source/images/insert.svg @@ -0,0 +1,87 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/images/preview.svg b/source/images/preview.svg new file mode 100644 index 0000000..8c4e378 --- /dev/null +++ b/source/images/preview.svg @@ -0,0 +1,113 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py index 2bf31b1..9991057 100644 --- a/source/pythonpath/main.py +++ b/source/pythonpath/main.py @@ -132,7 +132,7 @@ def _create_dialog(): 'Name': 'dialog', 'Title': TITLE, 'Width': 270, - 'Height': 250, + 'Height': 260, } dlg = app.create_dialog(args) dlg.id = ID_EXTENSION @@ -165,8 +165,8 @@ def _create_dialog(): 'Name': 'cmd_preview', 'Label': _('Preview'), 'Width': 70, - 'Height': 15, - 'ImageURL': 'view.png', + 'Height': 20, + 'ImageURL': 'preview.svg', 'ImagePosition': 1, } dlg.add_control(args) @@ -184,8 +184,8 @@ def _create_dialog(): 'Name': 'cmd_insert', 'Label': _('Insert'), 'Width': 70, - 'Height': 15, - 'ImageURL': 'insert.png', + 'Height': 20, + 'ImageURL': 'insert.svg', 'ImagePosition': 1, } dlg.add_control(args) @@ -194,8 +194,8 @@ def _create_dialog(): 'Name': 'cmd_close', 'Label': _('Close'), 'Width': 70, - 'Height': 15, - 'ImageURL': 'close.png', + 'Height': 20, + 'ImageURL': 'close.svg', 'ImagePosition': 1, } dlg.add_control(args) From 31c8142397dfab8af32d328004dff526756487d8 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 19 Dec 2020 22:08:02 -0600 Subject: [PATCH 12/13] Remove import --- source/ZAZLaTex2SVG.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/ZAZLaTex2SVG.py b/source/ZAZLaTex2SVG.py index 5f67048..48f77b4 100644 --- a/source/ZAZLaTex2SVG.py +++ b/source/ZAZLaTex2SVG.py @@ -3,8 +3,6 @@ import unohelper from com.sun.star.task import XJobExecutor import main -import easymacro as app - ID_EXTENSION = 'net.elmau.zaz.latex2svg' SERVICE = ('com.sun.star.task.Job',) From d7ef6f91c1bac23e74a9d0a3e14fdd5bd4767372 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 19 Dec 2020 22:08:25 -0600 Subject: [PATCH 13/13] Remove import --- files/ZAZLaTex2SVG_v0.2.0.oxt | Bin 77150 -> 77137 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/files/ZAZLaTex2SVG_v0.2.0.oxt b/files/ZAZLaTex2SVG_v0.2.0.oxt index 9010cbf021981dc500d8cdb54d8b782fc5b7972f..87e440f27528989c47a88e77b873a4b9f800e9fd 100644 GIT binary patch delta 750 zcmcb2i{;`i7M=iaW)=|!1_llWri~kUDw&v=Hg0Zby24ohV8i6VhRCJG!i)?I0!$1H z{6Ixfj!`~|A*mHc!C~%t1(l_NzTQU+1ol1;e=<$PiBGQ8_DA3rCG8ujk>7-ptk)i_ znW8dv)BbxQS=&R)`4o*S^PlbQG@AQa@cT7I)A;+2qdtPMD3!RPW>s3 zzw?yS`Q&^@8(sw+@4s5Q&IgXqe!TgPYSzyB1)II`L6x_%xW{y!S7x2n`6VwxZ#T`ixU@ad`9zcXttb0^^e&rT_--++&o)BnvIehH zitz#GH-C3*3KLm&IpKhtG|pcPq_^KxVYFuiGNwzZF}?xO9O{gb984Q02flt~eR?{dI-}h5 zc6CN`kkkQnMl%roL!Hr@7bPa9J7_TaF;1M`ufZq}Qn5jUQHJR^$Mka=jLwX0(}gq{ z?HEr^57Y$OEj7S(*(kMUuq9&sYW9f7;Ek;|W zM)BzjwSey6)@Br%eoKN;fE%H5|MUe~jNXh>roRDF%qwLWr|*+ul$ai*&1k^9SPsmS zoxWd-k#G72ZALjpqv_|h89kVODowwj%_w1zr&m%@65!3qB*F|!Zj%Fpj&Dxl1E#MI zVEPh;*#xAQG#;3qsKclNcDp^-rg=Mog4{rbsBUMRzC?#n4HODzbQonA-%o!Dxf?d}R5CHm-LScx=?Y{0taXzEKeTWfD=;!JXfiP{ z@BZTcvzlD&6!-Sn+-7cg$9D7wQjm z+_t&7J!!!%{%f-JJEfK;9&fo&pSfl(>z9Vu;}Y8MT{lLGo?cZGd5B5KxpDHP8=r08 z?L6dgcf&gWkYwwpXN z)|kz(>`H^cr4tG+uRKjHuOxP?H#oNNShD81{^y+{b8@1Kl-Xlni+j2j9kc%Ne811U z<;4qL`&PY}=x*2>qT*u4DARiHXzZr2DZ7G~oMhp-H0!v0@y7QjU#L3F`g{1vj@xHn zzx?^{)69D^$NEp)FyB0pwS*ZIr0kqZ6(Cx>asFZ;z5R>|qdg;#F`ZY9@ePRnt;QJ1 zF?Yk{z}K&=Pfur5XOx>>rOs#$lG?1!Xa=HRt20{jqD0GdV+}?>hk2|F44Ju!>8Zu~ z$vOGOsd~j_=>gu1Od`y%h@BkR%9P<7mchW_1H|IMn1q2PjU1fQ4{9(vGftk)s>x`_ zcx}3iCeUdC(_1td6%{}ZdYjEOS#b&j!-ri=Xj*j|CtEj4PQRzgsKwYcol}d^mbp)y zar!)QM)_&li~`duv>2tB&q#npWT$V^V$@+=IQ_mBqc`(58OG@wr5Giq-;-fvo9?a6 zsKvZN4lE=)eX2I24zs-?<8*&TMw#gkKyqx#V4nDN6&*%Fd61)mj&Dxl1E$OlV9FGQ zc?L)?X*@eUM2ArY6kb(2j55r8YK+t6)EFiBP(x(;exM}dpXqmW7}dZAf`VNQXn+b3 TqXtm;bVXf8Ew)YSK