From f8c4e3c3ee056d14757e0bc5b62b958e8c0f5720 Mon Sep 17 00:00:00 2001 From: El Mau Date: Wed, 23 Feb 2022 14:48:14 -0600 Subject: [PATCH] Add global variables --- easymacro.py | 6808 +------------------------------- files/ZAZEasyMacro_v0.1.0.oxt | Bin 70083 -> 16146 bytes images/logo.png | Bin 26700 -> 9189 bytes source/XZAZEasyMacro.idl | 12 +- source/XZAZEasyMacro.rdb | Bin 18432 -> 20480 bytes source/images/zazeasymacro.png | Bin 26700 -> 9189 bytes source/pythonpath/easymacro.py | 6808 +------------------------------- source/pythonpath/libo.py | 12 +- 8 files changed, 112 insertions(+), 13528 deletions(-) diff --git a/easymacro.py b/easymacro.py index cbb26a0..2c1b5c8 100644 --- a/easymacro.py +++ b/easymacro.py @@ -2,117 +2,38 @@ # == Rapid Develop Macros in LibreOffice == -# ~ This file is part of ZAZ. +# ~ https://git.cuates.net/elmau/easymacro -# ~ https://git.cuates.net/elmau/zaz - -# ~ ZAZ is free software: you can redistribute it and/or modify +# ~ easymacro 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, +# ~ easymacro 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 . +# ~ along with easymacro. If not, see . + -import base64 -import csv -import ctypes -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 +from typing import Any + 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.lang import Locale -from com.sun.star.lang import XEventListener -from com.sun.star.awt import XActionListener -from com.sun.star.awt import XMenuListener -from com.sun.star.awt import XMouseListener -from com.sun.star.awt import XMouseMotionListener -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') +from com.sun.star.beans import PropertyValue LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' @@ -124,170 +45,48 @@ 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 - - -class DataPilotFieldOrientation(): - from com.sun.star.sheet.DataPilotFieldOrientation \ - import HIDDEN, COLUMN, ROW, PAGE, DATA -DPFO = DataPilotFieldOrientation - - +# Global variables OS = platform.system() +DESKTOP = os.environ.get('DESKTOP_SESSION', '') +PC = platform.node() +USER = getpass.getuser() 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) +_info_debug = f"Python: {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', - } -} +SALT = b'00a1bfb05353bb3fd8e7aa7fe5efdccc' CTX = uno.getComponentContext() SM = CTX.getServiceManager() +def debug(*args): + """ + Show messages debug + + Parameters: + args (list): iterable messages for show + """ + + data = [str(a) for a in args] + log.debug('\t'.join(data)) + return + + +def error(message: Any): + """ + Show message error + + Parameters: + message (Any): message show error + """ + + log.error(message) + return + + def create_instance(name: str, with_context: bool=False, args: Any=None) -> Any: if with_context: instance = SM.createInstanceWithContext(name, CTX) @@ -298,9 +97,11 @@ def create_instance(name: str, with_context: bool=False, args: Any=None) -> Any: return instance -def get_app_config(node_name: str, key: str=''): +def get_app_config(node_name: str, key: str='', update: bool=False): name = 'com.sun.star.configuration.ConfigurationProvider' service = 'com.sun.star.configuration.ConfigurationAccess' + if update: + service = 'com.sun.star.configuration.ConfigurationUpdateAccess' cp = create_instance(name, True) node = PropertyValue(Name='nodepath', Value=node_name) try: @@ -314,6531 +115,14 @@ def get_app_config(node_name: str, key: str=''): return '' +# Get info LibO +NAME = get_app_config('org.openoffice.Setup/Product', 'ooName') +VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion') LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale') LANG = LANGUAGE.split('-')[0] -try: - COUNTRY = LANGUAGE.split('-')[1] -except: - COUNTRY = '' -LOCALE = Locale(LANG, COUNTRY, '') -NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName') -VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion') -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: str, 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: Any) -> None: - m = create_instance('mytools.Mri') - if m is None: - msg = 'Extension MRI not found' - error(msg) - return - - if hasattr(obj, 'obj'): - obj = obj.obj - 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: bool=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: dict): - 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: dict): - #~ 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: str, domain: str='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: bool=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={}): - """ - https://wiki.documentfoundation.org/Macros/Python_Guide/PDF_export_filter_data - """ - path_pdf = path - filter_name = '{}_pdf_Export'.format(self.type) - filter_data = dict_to_property(args, True) - 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 LOSheetTableField(object): - - def __init__(self, obj): - self._obj = obj - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - pass - - @property - def obj(self): - return self._obj - - @property - def name(self): - return self.obj.Name - - @property - def orientation(self): - return self.obj.Orientation - @orientation.setter - def orientation(self, value): - self.obj.Orientation = value - - -# ~ com.sun.star.sheet.DataPilotFieldOrientation.ROW -class LOSheetTable(object): - - def __init__(self, obj): - self._obj = obj - self._source = None - - def __getitem__(self, index): - field = self.obj.DataPilotFields[index] - return LOSheetTableField(field) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - pass - - @property - def obj(self): - return self._obj - - @property - def filter(self): - return self.obj.ShowFilterButton - @filter.setter - def filter(self, value): - self.obj.ShowFilterButton = value - - @property - def source(self): - return self._source - @source.setter - def source(self, value): - self._source = value - self.obj.SourceRange = value.range_address - - @property - def rows(self): - return self.obj.RowFields - @rows.setter - def rows(self, values): - if not isinstance(values, tuple): - values = (values,) - for v in values: - with self[v] as f: - f.orientation = DPFO.ROW - @property - def columns(self): - return self.obj.ColumnFields - @columns.setter - def columns(self, values): - if not isinstance(values, tuple): - values = (values,) - for v in values: - with self[v] as f: - f.orientation = DPFO.COLUMN - - @property - def data(self): - return self.obj.DataFields - @data.setter - def data(self, values): - if not isinstance(values, tuple): - values = (values,) - for v in values: - with self[v] as f: - f.orientation = DPFO.DATA - - -class LOSheetTables(object): - - def __init__(self, obj, sheet): - self._obj = obj - self._sheet = sheet - - def __getitem__(self, index): - return LOSheetTable(self.obj[index]) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - pass - - def __contains__(self, item): - return item in self.obj - - @property - def obj(self): - return self._obj - - @property - def count(self): - return self.obj.Count - - @property - def names(self): - return self.obj.ElementNames - - def new(self, name, target): - table = self.obj.createDataPilotDescriptor() - self.obj.insertNewByName(name, target.address, table) - return LOSheetTable(self.obj[name]) - - def remove(self, name): - self.obj.removeByName(name) - return - - -class LOFormControl(LOBaseObject): - EVENTS = { - 'action': 'actionPerformed', - '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 tables(self): - return LOSheetTables(self.obj.DataPilotTables, 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(): - # ~ print(1, 'RENDER', k, v) - 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) - - if ranges is None: - return - - # ~ for cell in ranges or range(0): - for cell in ranges: - self._set_new_value(cell, search, value) - return LOCalcRange(cell) - - 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 - - def _cast(self, t, v): - if not t: - return v - - if t == datetime.date: - nv = datetime.date.fromordinal(int(v) + DATE_OFFSET) - else: - nv = t(v) - return nv - - def get_data(self, types): - values = [ - [self._cast(types[i], v) for i, v in enumerate(row)] - for row in self.data - ] - return values - - -class LOWriterStyles(object): - - def __init__(self, styles): - self._styles = styles - - @property - def names(self): - return {s.DisplayName: s.Name for s in self._styles} - - def __str__(self): - return '\n'.join(tuple(self.names.values())) - - -class LOWriterStylesFamilies(object): - - def __init__(self, styles): - self._styles = styles - - def __getitem__(self, index): - styles = { - 'Character': 'CharacterStyles', - 'Paragraph': 'ParagraphStyles', - 'Page': 'PageStyles', - 'Frame': 'FrameStyles', - 'Numbering': 'NumberingStyles', - 'Table': 'TableStyles', - 'Cell': 'CellStyles', - } - name = styles.get(index, index) - return LOWriterStyles(self._styles[name]) - - def __iter__(self): - self._index = 0 - return self - - def __next__(self): - obj = LOWriterStyles(self._styles[self._index]) - self._index += 1 - return obj - # ~ raise StopIteration - - @property - def names(self): - return self._styles.ElementNames - - def __str__(self): - return '\n'.join(self.names) - - -class LOWriterPageStyle(LOBaseObject): - - 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' - self._is_text = self.obj.ImplementationName == 'SwXTextPortion' - self._parts = [] - if self._is_paragraph: - self._parts = [LOWriterTextRange(p, doc) for p in obj] - - def __iter__(self): - self._index = 0 - return self - - def __next__(self): - try: - obj = self._parts[self._index] - except IndexError: - raise StopIteration - - self._index += 1 - return obj - - @property - def obj(self): - return self._obj - - @property - def string(self): - s = '' - if not self._is_table: - s = self.obj.String - return s - @string.setter - def string(self, value): - self.obj.String = value - - @property - def value(self): - return self.string - @value.setter - def value(self, value): - self.string = value - - @property - def style(self): - s = '' - if self.is_paragraph: - s = self.obj.ParaStyleName - elif self.is_text: - s = self.obj.CharStyleName - return s - @style.setter - def style(self, value): - if self.is_paragraph: - self.obj.ParaStyleName = value - elif self.is_text: - self.obj.CharStyleName = value - - @property - def is_paragraph(self): - return self._is_paragraph - - @property - def is_table(self): - return self._is_table - - @property - def is_text(self): - return self._is_text - - @property - def text(self): - return self.obj.Text - - @property - def cursor(self): - return self.text.createTextCursorByRange(self.obj) - - @property - def dp(self): - return self._doc.dp - - def delete(self): - cursor = self.cursor - cursor.gotoStartOfParagraph(False) - cursor.gotoNextParagraph(True) - cursor.String = '' - return - - def offset(self): - cursor = self.cursor.getEnd() - return LOWriterTextRange(cursor, self._doc) - - 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 - self._paragraphs = [LOWriterTextRange(p, doc) for p in obj] - - def __len__(self): - return len(self._paragraphs) - - def __getitem__(self, index): - return self._paragraphs[index] - - def __iter__(self): - self._index = 0 - return self - - def __next__(self): - try: - obj = self._paragraphs[self._index] - except IndexError: - raise StopIteration - - self._index += 1 - return obj - - @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 - - @property - def style(self): - return self.obj.TableTemplateName - @style.setter - def style(self, value): - self.obj.autoFormat(value) - - -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 self.paragraphs - - @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 styles(self): - return LOWriterStylesFamilies(self.obj.StyleFamilies) - - @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 = { - 'VARCHAR': 'getString', - 'INTEGER': 'getLong', - 'DATE': 'getDate', - # ~ 'SQL_LONG': 'getLong', - # ~ 'SQL_VARYING': 'getString', - # ~ 'SQL_FLOAT': 'getFloat', - # ~ 'SQL_BOOLEAN': 'getBoolean', - # ~ 'SQL_TYPE_DATE': 'getDate', - # ~ 'SQL_TYPE_TIME': 'getTime', - # ~ 'SQL_TIMESTAMP': 'getTimestamp', - } - # ~ TYPES_DATE = ('SQL_TYPE_DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') - TYPES_DATE = ('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) - # ~ print('TF', type_field) - value = getattr(self._query, self.PY_TYPES[type_field])(index) - if type_field in self.TYPES_DATE: - value = _struct_to_date(value) - 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) - if tables: - 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): - # ~ len(self._desktop.Components) - 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 - - @property - def echochar(self): - return chr(self.model.EchoChar) - @echochar.setter - def echochar(self, value): - self.model.EchoChar = ord(value[0]) - - def validate(self): - return - - -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._data = [] - self._formats = () - - def __setattr__(self, name, value): - if name in ('_gdm', '_data', '_formats'): - 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 {} - @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 row_count(self): - return self._gdm.RowCount - - @property - def column(self): - return self.obj.CurrentColumn - - @property - def column(self): - return self.obj.CurrentColumn - - @property - def is_valid(self): - return not (self.row == -1 or self.column == -1) - - @property - def formats(self): - return self._formats - @formats.setter - def formats(self, values): - self._formats = values - - def clear(self): - self._gdm.removeAllRows() - return - - def _format_columns(self, data): - row = data - if self.formats: - for i, f in enumerate(formats): - if f: - row[i] = f.format(data[i]) - return row - - def add_row(self, data): - self._data.append(data) - row = self._format_columns(data) - self._gdm.addRow(self.row_count + 1, row) - return - - 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 sort(self, column, asc=True): - self._gdm.sortByColumn(column, asc) - self.update_row_heading() - return - - def update_row_heading(self): - for i in range(self.row_count): - self._gdm.updateRowHeading(i, i + 1) - return - - def remove_row(self, row): - self._gdm.removeRow(row) - del self._data[row] - self.update_row_heading() - 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 - - def set_values(self, data): - for k, v in data.items(): - self._controls[k].value = v - return - - -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 self._menu - - 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' - FOLDER_PICKER = 'com.sun.star.ui.dialogs.FolderPicker' - - 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.FOLDER_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.getDirectory()) - 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 image(cls, path): - gp = create_instance('com.sun.star.graphic.GraphicProvider') - image = gp.queryGraphic(( - PropertyValue(Name='URL', Value=cls.to_url(path)), - )) - return image - - @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 - - -class Dates(object): - - @classmethod - def date(cls, year, month, day): - d = datetime.date(year, month, day) - return d - - -class SpellChecker(object): - - def __init__(self): - service = 'com.sun.star.linguistic2.SpellChecker' - self._spellchecker = create_instance(service, True) - self._locale = LOCALE - - @property - def locale(self): - slocal = f'{self._locale.Language}-{self._locale.Country}' - return slocale - @locale.setter - def locale(self, value): - lang = value.split('-') - self._locale = Locale(lang[0], lang[1], '') - - def is_valid(self, word): - result = self._spellchecker.isValid(word, self._locale, ()) - return result - - def spell(self, word): - result = self._spellchecker.spell(word, self._locale, ()) - if result: - result = result.getAlternatives() - if not isinstance(result, tuple): - result = () - return result - - -def spell(word, locale=''): - sc = SpellChecker() - if locale: - sc.locale = locale - return sc.spell(word) - - -def __getattr__(name): - if name == 'active': - return LODocs().active - 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 - if name == 'dates': - return Dates - 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 - - -# ~ 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') +INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{_info_debug}" class LOServer(object): diff --git a/files/ZAZEasyMacro_v0.1.0.oxt b/files/ZAZEasyMacro_v0.1.0.oxt index 48fefe666225850f842a5cc7e41d25079a801791..efef550e246ff13270260e206d34b5110e318086 100644 GIT binary patch delta 13611 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 delta 67947 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. +# ~ along with easymacro. If not, see . + -import base64 -import csv -import ctypes -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 +from typing import Any + 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.lang import Locale -from com.sun.star.lang import XEventListener -from com.sun.star.awt import XActionListener -from com.sun.star.awt import XMenuListener -from com.sun.star.awt import XMouseListener -from com.sun.star.awt import XMouseMotionListener -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') +from com.sun.star.beans import PropertyValue LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' @@ -124,170 +45,48 @@ 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 - - -class DataPilotFieldOrientation(): - from com.sun.star.sheet.DataPilotFieldOrientation \ - import HIDDEN, COLUMN, ROW, PAGE, DATA -DPFO = DataPilotFieldOrientation - - +# Global variables OS = platform.system() +DESKTOP = os.environ.get('DESKTOP_SESSION', '') +PC = platform.node() +USER = getpass.getuser() 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) +_info_debug = f"Python: {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', - } -} +SALT = b'00a1bfb05353bb3fd8e7aa7fe5efdccc' CTX = uno.getComponentContext() SM = CTX.getServiceManager() +def debug(*args): + """ + Show messages debug + + Parameters: + args (list): iterable messages for show + """ + + data = [str(a) for a in args] + log.debug('\t'.join(data)) + return + + +def error(message: Any): + """ + Show message error + + Parameters: + message (Any): message show error + """ + + log.error(message) + return + + def create_instance(name: str, with_context: bool=False, args: Any=None) -> Any: if with_context: instance = SM.createInstanceWithContext(name, CTX) @@ -298,9 +97,11 @@ def create_instance(name: str, with_context: bool=False, args: Any=None) -> Any: return instance -def get_app_config(node_name: str, key: str=''): +def get_app_config(node_name: str, key: str='', update: bool=False): name = 'com.sun.star.configuration.ConfigurationProvider' service = 'com.sun.star.configuration.ConfigurationAccess' + if update: + service = 'com.sun.star.configuration.ConfigurationUpdateAccess' cp = create_instance(name, True) node = PropertyValue(Name='nodepath', Value=node_name) try: @@ -314,6531 +115,14 @@ def get_app_config(node_name: str, key: str=''): return '' +# Get info LibO +NAME = get_app_config('org.openoffice.Setup/Product', 'ooName') +VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion') LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale') LANG = LANGUAGE.split('-')[0] -try: - COUNTRY = LANGUAGE.split('-')[1] -except: - COUNTRY = '' -LOCALE = Locale(LANG, COUNTRY, '') -NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName') -VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion') -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: str, 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: Any) -> None: - m = create_instance('mytools.Mri') - if m is None: - msg = 'Extension MRI not found' - error(msg) - return - - if hasattr(obj, 'obj'): - obj = obj.obj - 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: bool=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: dict): - 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: dict): - #~ 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: str, domain: str='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: bool=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={}): - """ - https://wiki.documentfoundation.org/Macros/Python_Guide/PDF_export_filter_data - """ - path_pdf = path - filter_name = '{}_pdf_Export'.format(self.type) - filter_data = dict_to_property(args, True) - 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 LOSheetTableField(object): - - def __init__(self, obj): - self._obj = obj - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - pass - - @property - def obj(self): - return self._obj - - @property - def name(self): - return self.obj.Name - - @property - def orientation(self): - return self.obj.Orientation - @orientation.setter - def orientation(self, value): - self.obj.Orientation = value - - -# ~ com.sun.star.sheet.DataPilotFieldOrientation.ROW -class LOSheetTable(object): - - def __init__(self, obj): - self._obj = obj - self._source = None - - def __getitem__(self, index): - field = self.obj.DataPilotFields[index] - return LOSheetTableField(field) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - pass - - @property - def obj(self): - return self._obj - - @property - def filter(self): - return self.obj.ShowFilterButton - @filter.setter - def filter(self, value): - self.obj.ShowFilterButton = value - - @property - def source(self): - return self._source - @source.setter - def source(self, value): - self._source = value - self.obj.SourceRange = value.range_address - - @property - def rows(self): - return self.obj.RowFields - @rows.setter - def rows(self, values): - if not isinstance(values, tuple): - values = (values,) - for v in values: - with self[v] as f: - f.orientation = DPFO.ROW - @property - def columns(self): - return self.obj.ColumnFields - @columns.setter - def columns(self, values): - if not isinstance(values, tuple): - values = (values,) - for v in values: - with self[v] as f: - f.orientation = DPFO.COLUMN - - @property - def data(self): - return self.obj.DataFields - @data.setter - def data(self, values): - if not isinstance(values, tuple): - values = (values,) - for v in values: - with self[v] as f: - f.orientation = DPFO.DATA - - -class LOSheetTables(object): - - def __init__(self, obj, sheet): - self._obj = obj - self._sheet = sheet - - def __getitem__(self, index): - return LOSheetTable(self.obj[index]) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - pass - - def __contains__(self, item): - return item in self.obj - - @property - def obj(self): - return self._obj - - @property - def count(self): - return self.obj.Count - - @property - def names(self): - return self.obj.ElementNames - - def new(self, name, target): - table = self.obj.createDataPilotDescriptor() - self.obj.insertNewByName(name, target.address, table) - return LOSheetTable(self.obj[name]) - - def remove(self, name): - self.obj.removeByName(name) - return - - -class LOFormControl(LOBaseObject): - EVENTS = { - 'action': 'actionPerformed', - '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 tables(self): - return LOSheetTables(self.obj.DataPilotTables, 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(): - # ~ print(1, 'RENDER', k, v) - 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) - - if ranges is None: - return - - # ~ for cell in ranges or range(0): - for cell in ranges: - self._set_new_value(cell, search, value) - return LOCalcRange(cell) - - 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 - - def _cast(self, t, v): - if not t: - return v - - if t == datetime.date: - nv = datetime.date.fromordinal(int(v) + DATE_OFFSET) - else: - nv = t(v) - return nv - - def get_data(self, types): - values = [ - [self._cast(types[i], v) for i, v in enumerate(row)] - for row in self.data - ] - return values - - -class LOWriterStyles(object): - - def __init__(self, styles): - self._styles = styles - - @property - def names(self): - return {s.DisplayName: s.Name for s in self._styles} - - def __str__(self): - return '\n'.join(tuple(self.names.values())) - - -class LOWriterStylesFamilies(object): - - def __init__(self, styles): - self._styles = styles - - def __getitem__(self, index): - styles = { - 'Character': 'CharacterStyles', - 'Paragraph': 'ParagraphStyles', - 'Page': 'PageStyles', - 'Frame': 'FrameStyles', - 'Numbering': 'NumberingStyles', - 'Table': 'TableStyles', - 'Cell': 'CellStyles', - } - name = styles.get(index, index) - return LOWriterStyles(self._styles[name]) - - def __iter__(self): - self._index = 0 - return self - - def __next__(self): - obj = LOWriterStyles(self._styles[self._index]) - self._index += 1 - return obj - # ~ raise StopIteration - - @property - def names(self): - return self._styles.ElementNames - - def __str__(self): - return '\n'.join(self.names) - - -class LOWriterPageStyle(LOBaseObject): - - 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' - self._is_text = self.obj.ImplementationName == 'SwXTextPortion' - self._parts = [] - if self._is_paragraph: - self._parts = [LOWriterTextRange(p, doc) for p in obj] - - def __iter__(self): - self._index = 0 - return self - - def __next__(self): - try: - obj = self._parts[self._index] - except IndexError: - raise StopIteration - - self._index += 1 - return obj - - @property - def obj(self): - return self._obj - - @property - def string(self): - s = '' - if not self._is_table: - s = self.obj.String - return s - @string.setter - def string(self, value): - self.obj.String = value - - @property - def value(self): - return self.string - @value.setter - def value(self, value): - self.string = value - - @property - def style(self): - s = '' - if self.is_paragraph: - s = self.obj.ParaStyleName - elif self.is_text: - s = self.obj.CharStyleName - return s - @style.setter - def style(self, value): - if self.is_paragraph: - self.obj.ParaStyleName = value - elif self.is_text: - self.obj.CharStyleName = value - - @property - def is_paragraph(self): - return self._is_paragraph - - @property - def is_table(self): - return self._is_table - - @property - def is_text(self): - return self._is_text - - @property - def text(self): - return self.obj.Text - - @property - def cursor(self): - return self.text.createTextCursorByRange(self.obj) - - @property - def dp(self): - return self._doc.dp - - def delete(self): - cursor = self.cursor - cursor.gotoStartOfParagraph(False) - cursor.gotoNextParagraph(True) - cursor.String = '' - return - - def offset(self): - cursor = self.cursor.getEnd() - return LOWriterTextRange(cursor, self._doc) - - 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 - self._paragraphs = [LOWriterTextRange(p, doc) for p in obj] - - def __len__(self): - return len(self._paragraphs) - - def __getitem__(self, index): - return self._paragraphs[index] - - def __iter__(self): - self._index = 0 - return self - - def __next__(self): - try: - obj = self._paragraphs[self._index] - except IndexError: - raise StopIteration - - self._index += 1 - return obj - - @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 - - @property - def style(self): - return self.obj.TableTemplateName - @style.setter - def style(self, value): - self.obj.autoFormat(value) - - -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 self.paragraphs - - @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 styles(self): - return LOWriterStylesFamilies(self.obj.StyleFamilies) - - @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 = { - 'VARCHAR': 'getString', - 'INTEGER': 'getLong', - 'DATE': 'getDate', - # ~ 'SQL_LONG': 'getLong', - # ~ 'SQL_VARYING': 'getString', - # ~ 'SQL_FLOAT': 'getFloat', - # ~ 'SQL_BOOLEAN': 'getBoolean', - # ~ 'SQL_TYPE_DATE': 'getDate', - # ~ 'SQL_TYPE_TIME': 'getTime', - # ~ 'SQL_TIMESTAMP': 'getTimestamp', - } - # ~ TYPES_DATE = ('SQL_TYPE_DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') - TYPES_DATE = ('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) - # ~ print('TF', type_field) - value = getattr(self._query, self.PY_TYPES[type_field])(index) - if type_field in self.TYPES_DATE: - value = _struct_to_date(value) - 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) - if tables: - 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): - # ~ len(self._desktop.Components) - 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 - - @property - def echochar(self): - return chr(self.model.EchoChar) - @echochar.setter - def echochar(self, value): - self.model.EchoChar = ord(value[0]) - - def validate(self): - return - - -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._data = [] - self._formats = () - - def __setattr__(self, name, value): - if name in ('_gdm', '_data', '_formats'): - 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 {} - @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 row_count(self): - return self._gdm.RowCount - - @property - def column(self): - return self.obj.CurrentColumn - - @property - def column(self): - return self.obj.CurrentColumn - - @property - def is_valid(self): - return not (self.row == -1 or self.column == -1) - - @property - def formats(self): - return self._formats - @formats.setter - def formats(self, values): - self._formats = values - - def clear(self): - self._gdm.removeAllRows() - return - - def _format_columns(self, data): - row = data - if self.formats: - for i, f in enumerate(formats): - if f: - row[i] = f.format(data[i]) - return row - - def add_row(self, data): - self._data.append(data) - row = self._format_columns(data) - self._gdm.addRow(self.row_count + 1, row) - return - - 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 sort(self, column, asc=True): - self._gdm.sortByColumn(column, asc) - self.update_row_heading() - return - - def update_row_heading(self): - for i in range(self.row_count): - self._gdm.updateRowHeading(i, i + 1) - return - - def remove_row(self, row): - self._gdm.removeRow(row) - del self._data[row] - self.update_row_heading() - 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 - - def set_values(self, data): - for k, v in data.items(): - self._controls[k].value = v - return - - -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 self._menu - - 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' - FOLDER_PICKER = 'com.sun.star.ui.dialogs.FolderPicker' - - 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.FOLDER_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.getDirectory()) - 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 image(cls, path): - gp = create_instance('com.sun.star.graphic.GraphicProvider') - image = gp.queryGraphic(( - PropertyValue(Name='URL', Value=cls.to_url(path)), - )) - return image - - @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 - - -class Dates(object): - - @classmethod - def date(cls, year, month, day): - d = datetime.date(year, month, day) - return d - - -class SpellChecker(object): - - def __init__(self): - service = 'com.sun.star.linguistic2.SpellChecker' - self._spellchecker = create_instance(service, True) - self._locale = LOCALE - - @property - def locale(self): - slocal = f'{self._locale.Language}-{self._locale.Country}' - return slocale - @locale.setter - def locale(self, value): - lang = value.split('-') - self._locale = Locale(lang[0], lang[1], '') - - def is_valid(self, word): - result = self._spellchecker.isValid(word, self._locale, ()) - return result - - def spell(self, word): - result = self._spellchecker.spell(word, self._locale, ()) - if result: - result = result.getAlternatives() - if not isinstance(result, tuple): - result = () - return result - - -def spell(word, locale=''): - sc = SpellChecker() - if locale: - sc.locale = locale - return sc.spell(word) - - -def __getattr__(name): - if name == 'active': - return LODocs().active - 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 - if name == 'dates': - return Dates - 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 - - -# ~ 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') +INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{_info_debug}" class LOServer(object): diff --git a/source/pythonpath/libo.py b/source/pythonpath/libo.py index 6c2fc99..4566f60 100644 --- a/source/pythonpath/libo.py +++ b/source/pythonpath/libo.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -from net.elmau.zaz.EasyMacro import XDebug +from net.elmau.zaz.EasyMacro import XZAZDebug import easymacro as app -class Debug(XDebug): +class Debug(XZAZDebug): def info(self, message): app.info(message) @@ -25,5 +25,13 @@ class Debug(XDebug): class LIBO(Debug): OS = app.OS + DESKTOP = app.DESKTOP + PC = app.PC USER = app.USER + IS_WIN = app.IS_WIN + IS_MAC = app.IS_MAC + NAME = app.NAME + VERSION = app.VERSION + LANGUAGE = app.LANGUAGE + LANG = app.LANG INFO_DEBUG = app.INFO_DEBUG