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