diff --git a/source/easymacro.py b/source/easymacro.py index 689270c..ef7af45 100644 --- a/source/easymacro.py +++ b/source/easymacro.py @@ -4,6 +4,8 @@ # ~ This file is part of ZAZ. +# ~ https://git.elmau.net/elmau/zaz + # ~ ZAZ is free software: you can redistribute it and/or modify # ~ it under the terms of the GNU General Public License as published by # ~ the Free Software Foundation, either version 3 of the License, or @@ -19,11 +21,9 @@ import base64 import csv -import ctypes import datetime -import errno -import gettext import getpass +import gettext import hashlib import json import logging @@ -33,8 +33,8 @@ import re import shlex import shutil import socket -import subprocess import ssl +import subprocess import sys import tempfile import threading @@ -42,14 +42,19 @@ 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, PurePath +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 -from string import Template -from subprocess import PIPE +import imaplib import smtplib from smtplib import SMTPException, SMTPAuthenticationError from email.mime.multipart import MIMEMultipart @@ -61,150 +66,53 @@ import mailbox import uno import unohelper -from com.sun.star.util import Time, Date, DateTime -from com.sun.star.beans import PropertyValue, NamedValue 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 Size, Point -from com.sun.star.awt import Rectangle -from com.sun.star.awt import KeyEvent -from com.sun.star.awt.KeyFunction import QUIT +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.script import ScriptEventDescriptor -from com.sun.star.lang import XEventListener 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.util import XModifyListener -from com.sun.star.awt import XTopWindowListener -from com.sun.star.awt import XWindowListener -from com.sun.star.awt import XMenuListener +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 XFocusListener 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 fernet import Fernet, InvalidToken -except ImportError: - pass + 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') -ID_EXTENSION = '' - -DIR = { - 'images': 'images', - 'locales': 'locales', -} - -KEY = { - 'enter': 1280, -} - -SEPARATION = 5 - -MSG_LANG = { - 'es': { - 'OK': 'Aceptar', - 'Cancel': 'Cancelar', - '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', - } -} - -OS = platform.system() -USER = getpass.getuser() -PC = platform.node() -DESKTOP = os.environ.get('DESKTOP_SESSION', '') -INFO_DEBUG = '{}\n\n{}\n\n{}'.format(sys.version, platform.platform(), '\n'.join(sys.path)) - -IS_WIN = OS == 'Windows' -IS_MAC = OS == 'Darwin' - -LOG_NAME = 'ZAZ' -CLIPBOARD_FORMAT_TEXT = 'text/plain;charset=utf-16' - -PYTHON = 'python' -if IS_WIN: - PYTHON = 'python.exe' - -CALC = 'calc' -WRITER = 'writer' - -OBJ_CELL = 'ScCellObj' -OBJ_RANGE = 'ScCellRangeObj' -OBJ_RANGES = 'ScCellRangesObj' -OBJ_TYPE_RANGES = (OBJ_CELL, OBJ_RANGE, OBJ_RANGES) - -TEXT_RANGE = 'SwXTextRange' -TEXT_RANGES = 'SwXTextRanges' -TEXT_TYPE_RANGES = (TEXT_RANGE, TEXT_RANGES) - -TYPE_DOC = { - 'calc': 'com.sun.star.sheet.SpreadsheetDocument', - 'writer': 'com.sun.star.text.TextDocument', - 'impress': 'com.sun.star.presentation.PresentationDocument', - 'draw': 'com.sun.star.drawing.DrawingDocument', - 'base': 'com.sun.star.sdb.DocumentDataSource', - 'math': 'com.sun.star.formula.FormulaProperties', - 'basic': 'com.sun.star.script.BasicIDE', - 'main': 'com.sun.star.frame.StartModule', -} - -NODE_MENUBAR = 'private:resource/menubar/menubar' -MENUS_MAIN = { - 'file': '.uno:PickList', - 'tools': '.uno:ToolsMenu', - 'help': '.uno:HelpMenu', -} -MENUS_CALC = { - 'file': '.uno:PickList', - 'edit': '.uno:EditMenu', - 'view': '.uno:ViewMenu', - 'insert': '.uno:InsertMenu', - 'format': '.uno:FormatMenu', - 'styles': '.uno:FormatStylesMenu', - 'sheet': '.uno:SheetMenu', - 'data': '.uno:DataMenu', - 'tools': '.uno:ToolsMenu', - 'windows': '.uno:WindowList', - 'help': '.uno:HelpMenu', -} -MENUS_WRITER = { - 'file': '.uno:PickList', - 'edit': '.uno:EditMenu', - 'view': '.uno:ViewMenu', - 'insert': '.uno:InsertMenu', - 'format': '.uno:FormatMenu', - 'styles': '.uno:FormatStylesMenu', - 'sheet': '.uno:TableMenu', - 'data': '.uno:FormatFormMenu', - 'tools': '.uno:ToolsMenu', - 'windows': '.uno:WindowList', - 'help': '.uno:HelpMenu', -} -MENUS_APP = { - 'main': MENUS_MAIN, - 'calc': MENUS_CALC, - 'writer': MENUS_WRITER, -} - -EXT = { - 'pdf': 'pdf', -} - -FILE_NAME_DEBUG = 'debug.odt' -FILE_NAME_CONFIG = 'zaz-{}.json' LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' LOG_DATE = '%d/%m/%Y %H:%M:%S' logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m') @@ -214,19 +122,168 @@ logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE) log = logging.getLogger(__name__) -_start = 0 -_stop_thread = {} +# ~ You can get custom salt +# ~ codecs.encode(os.urandom(16), 'hex') +SALT = b'c9548699d4e432dfd2b46adddafbb06d' + TIMEOUT = 10 +LOG_NAME = 'ZAZ' +FILE_NAME_CONFIG = 'zaz-{}.json' + +LEFT = 0 +CENTER = 1 +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, with_context=False): +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 @@ -248,33 +305,41 @@ def get_app_config(node_name, key=''): return '' -# ~ FILTER_PDF = '/org.openoffice.Office.Common/Filter/PDF/Export/' 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') -nd = '/org.openoffice.Office.Calc/Calculate/Other/Date' -d = get_app_config(nd, 'DD') -m = get_app_config(nd, 'MM') -y = get_app_config(nd, 'YY') +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 mri(obj): - m = create_instance('mytools.Mri') - if m is None: - msg = 'Extension MRI not found' - error(msg) - return - - m.inspect(obj) +def error(info): + log.error(info) return -def inspect(obj): - zaz = create_instance('net.elmau.zaz.inspect') - zaz.inspect(obj) +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 @@ -286,52 +351,27 @@ def catch_exception(f): except Exception as e: name = f.__name__ if IS_WIN: - debug(traceback.format_exc()) + msgbox(traceback.format_exc()) log.error(name, exc_info=True) return func -class LogWin(object): +def inspect(obj: Any) -> None: + zaz = create_instance('net.elmau.zaz.inspect') + if hasattr(obj, 'obj'): + obj = obj.obj + zaz.inspect(obj) + return - def __init__(self, doc): - self.doc = doc - def write(self, info): - text = self.doc.Text - cursor = text.createTextCursor() - cursor.gotoEnd(False) - text.insertString(cursor, str(info) + '\n\n', 0) +def mri(obj): + m = create_instance('mytools.Mri') + if m is None: + msg = 'Extension MRI not found' + error(msg) return - -def info(data): - log.info(data) - return - - -def debug(*info): - if IS_WIN: - doc = get_document(FILE_NAME_DEBUG) - if doc is None: - return - doc = LogWin(doc.obj) - doc.write(str(info)) - return - - data = [str(d) for d in info] - log.debug('\t'.join(data)) - return - - -def error(info): - log.error(info) - return - - -def save_log(path, data): - with open(path, 'a') as out: - out.write('{} -{}- '.format(str(now())[:19], LOG_NAME)) - pprint(data, stream=out) + m.inspect(obj) return @@ -346,7 +386,7 @@ def run_in_thread(fn): def now(only_time=False): now = datetime.datetime.now() if only_time: - return now.time() + now = now.time() return now @@ -354,64 +394,14 @@ def today(): return datetime.date.today() -def get_date(year, month, day, hour=-1, minute=-1, second=-1): - if hour > -1 or minute > -1 or second > -1: - h = hour - m = minute - s = second - if h == -1: - h = 0 - if m == -1: - m = 0 - if s == -1: - s = 0 - d = datetime.datetime(year, month, day, h, m, s) - else: - d = datetime.date(year, month, day) - return d - - -def get_config(key='', default=None, prefix='config'): - path_json = FILE_NAME_CONFIG.format(prefix) - values = None - path = join(get_config_path('UserConfig'), path_json) - if not exists_path(path): - return default - - with open(path, 'r', encoding='utf-8') as fh: - data = fh.read() - values = json.loads(data) - - if key: - return values.get(key, default) - - return values - - -def set_config(key, value, prefix='config'): - path_json = FILE_NAME_CONFIG.format(prefix) - path = join(get_config_path('UserConfig'), path_json) - values = get_config(default={}, prefix=prefix) - values[key] = value - with open(path, 'w', encoding='utf-8') as fh: - json.dump(values, fh, ensure_ascii=False, sort_keys=True, indent=4) - return True - - -def sleep(seconds): - time.sleep(seconds) - return - - def _(msg): - L = LANGUAGE.split('-')[0] - if L == 'en': + if LANG == 'en': return msg - if not L in MSG_LANG: + if not LANG in MESSAGES: return msg - return MSG_LANG[L][msg] + return MESSAGES[LANG][msg] def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infobox'): @@ -421,13 +411,13 @@ def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infob """ toolkit = create_instance('com.sun.star.awt.Toolkit') parent = toolkit.getDesktopWindow() - mb = toolkit.createMessageBox(parent, type_msg, buttons, title, str(message)) - return mb.execute() + box = toolkit.createMessageBox(parent, type_msg, buttons, title, str(message)) + return box.execute() def question(message, title=TITLE): - res = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, 'querybox') - return res == YES + result = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, 'querybox') + return result == YES def warning(message, title=TITLE): @@ -438,181 +428,612 @@ def errorbox(message, title=TITLE): return msgbox(message, title, type_msg='errorbox') -def get_desktop(): - return create_instance('com.sun.star.frame.Desktop', True) - - -def get_dispatch(): - return create_instance('com.sun.star.frame.DispatchHelper') - - -def call_dispatch(doc=None, url='', args=()): - if doc is None: - frame = get_document().frame - else: - frame = doc.frame - dispatch = get_dispatch() - dispatch.executeDispatch(frame, url, '', 0, args) - return - - -def get_temp_file(only_name=False): - delete = True - if IS_WIN: - delete = False - tmp = tempfile.NamedTemporaryFile(delete=delete) - if only_name: - tmp = tmp.name - return tmp - -def _path_url(path): - if path.startswith('file://'): - return path - return uno.systemPathToFileUrl(path) - - -def _path_system(path): - if path.startswith('file://'): - return os.path.abspath(uno.fileUrlToSystemPath(path)) - return path - - -def exists_app(name): - try: - dn = subprocess.DEVNULL - subprocess.Popen([name, ''], stdout=dn, stderr=dn).terminate() - except OSError as e: - if e.errno == errno.ENOENT: - return False - return True - - -def exists_path(path): - return Path(path).exists() - - -def get_type_doc(obj): +def get_type_doc(obj: Any) -> str: for k, v in TYPE_DOC.items(): if obj.supportsService(v): return k return '' -def dict_to_property(values, uno_any=False): +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 dict_to_named(values): - ps = tuple([NamedValue(n, v) for n, v in values.items()]) - return ps - - -def property_to_dict(values): - d = {i.Name: i.Value for i in values} +def _array_to_dict(values): + d = {v[0]: v[1] for v in values} return d -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) +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 -# ~ Custom classes -class ObjectBase(object): +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 - def __getitem__(self, index): - return self.obj[index] - - def __getattr__(self, name): - a = None - if name == 'obj': - a = super().__getattr__(name) - else: - if hasattr(self.obj, name): - a = getattr(self.obj, name) - return a - - @property - def obj(self): - return self._obj - @obj.setter - def obj(self, value): - self._obj = value - - -class LOObjectBase(object): - - def __init__(self, obj): - self.__dict__['_obj'] = obj - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - return True - - def __setattr__(self, name, value): - print('BASE__setattr__', name) - if name == '_obj': - super().__setattr__(name, value) - else: - self.obj.setPropertyValue(name, value) - - # ~ def _try_for_method(self, name): - # ~ a = None - # ~ m = 'get{}'.format(name) - # ~ if hasattr(self.obj, m): - # ~ a = getattr(self.obj, m)() - # ~ else: - # ~ a = getattr(self.obj, name) - # ~ return a - - def __getattr__(self, name): - print('BASE__getattr__', name) - if name == 'obj': - a = super().__getattr__(name) - else: - a = self.obj.getPropertyValue(name) - # ~ Bug - if a is None: - msg = 'Error get: {} - {}'.format(self.obj.ImplementationName, name) - error(msg) - raise Exception(msg) - return a - @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._init_values() - - def _init_values(self): - self._type_doc = get_type_doc(self.obj) self._cc = self.obj.getCurrentController() - return + self._undo = True + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() @property def obj(self): @@ -626,12 +1047,12 @@ class LODocument(object): self.obj.setTitle(value) @property - def uid(self): - return self.obj.RuntimeUID + def type(self): + return self._type @property - def type(self): - return self._type_doc + def uid(self): + return self.obj.RuntimeUID @property def frame(self): @@ -651,19 +1072,31 @@ class LODocument(object): @property def path(self): - return _path_system(self.obj.getURL()) + return _P.to_system(self.obj.URL) @property - def statusbar(self): + 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._cc.getFrame().getContainerWindow() + w = self.frame.ContainerWindow return w.isVisible() @visible.setter def visible(self, value): - w = self._cc.getFrame().getContainerWindow() + w = self.frame.ContainerWindow w.setVisible(value) @property @@ -673,6 +1106,31 @@ class LODocument(object): 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') @@ -682,59 +1140,386 @@ class LODocument(object): obj = self.obj.createInstance(name) return obj - def save(self, path='', **kwargs): - # ~ opt = _properties(kwargs) - opt = dict_to_property(kwargs) - if path: - self._obj.storeAsURL(_path_url(path), opt) - else: - self._obj.store() - return True - - def close(self): - self.obj.close(True) + def set_focus(self): + w = self.frame.ComponentWindow + w.setFocus() return - def focus(self): - w = self._cc.getFrame().getComponentWindow() - w.setFocus() + 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 self.obj.getCurrentSelection() + return - def to_pdf(self, path, **kwargs): + def select(self, obj): + self._cc.select(obj) + return + + def to_pdf(self, path: str='', args: dict={}): path_pdf = path - if path: - if is_dir(path): - _, _, n, _ = get_info_path(self.path) - path_pdf = join(path, '{}.{}'.format(n, EXT['pdf'])) - else: - path_pdf = replace_ext(self.path, EXT['pdf']) - filter_name = '{}_pdf_Export'.format(self.type) - filter_data = dict_to_property(kwargs, True) + filter_data = dict_to_property(args, True) args = { 'FilterName': filter_name, 'FilterData': filter_data, } - args = dict_to_property(args) + opt = dict_to_property(args) try: - self.obj.storeToURL(_path_url(path_pdf), args) + self.obj.storeToURL(_P.to_url(path), opt) except Exception as e: error(e) path_pdf = '' - return path_pdf + return _P.exists(path_pdf) - # ~ If location="document" Then - # ~ sp = ThisComponent.getScriptProvider() + 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 FormControlBase(object): +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', @@ -744,22 +1529,43 @@ class FormControlBase(object): 'mousePressed': 'XMouseListener', } - def __init__(self, obj): - self._obj = obj + def __init__(self, obj, view, form): + super().__init__(obj) + self._view = view + self._form = form + self._m = view.Model self._index = -1 - self._rules = {} - @property - def obj(self): - return self._obj + def __setattr__(self, name, value): + if name in ('_form', '_view', '_m', '_index'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) - @property - def name(self): - return self.obj.Name + def __str__(self): + return f'{self.name} ({self.type}) {[self.index]}' @property def form(self): - return self.obj.getParent() + 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): @@ -768,23 +1574,16 @@ class FormControlBase(object): 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 remove_event(self, name=''): - for ev in self.events: - if name and \ - ev.EventMethod == self.EVENTS[name] and \ - ev.ListenerType == self.TYPES[ev.EventMethod]: - self.form.revokeScriptEvent(self.index, - ev.ListenerType, ev.EventMethod, ev.AddListenerParam) - break - else: - self.form.revokeScriptEvent(self.index, - ev.ListenerType, ev.EventMethod, ev.AddListenerParam) - return - def add_event(self, name, macro): if not 'name' in macro: macro['name'] = '{}_{}'.format(self.name, name) @@ -806,83 +1605,227 @@ class FormControlBase(object): self.form.registerScriptEvent(self.index, event) return - -class FormButton(FormControlBase): - - def __init__(self, obj): - super().__init__(obj) + def set_focus(self): + self._view.setFocus() + return +class LOFormControlLabel(LOFormControl): -class LOForm(ObjectBase): + def __init__(self, obj, view, form): + super().__init__(obj, view, form) - def __init__(self, obj): - super().__init__(obj) + @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): - if isinstance(index, int): - return self._controls[index] - else: - return getattr(self, index) + control = self.obj[index] + return self._controls[control.Name] - def _get_type_control(self, name): - types = { - # ~ 'stardiv.Toolkit.UnoFixedTextControl': 'label', - 'com.sun.star.form.OButtonModel': 'formbutton', - # ~ 'stardiv.Toolkit.UnoEditControl': 'text', - # ~ 'stardiv.Toolkit.UnoRoadmapControl': 'roadmap', - # ~ 'stardiv.Toolkit.UnoFixedHyperlinkControl': 'link', - # ~ 'stardiv.Toolkit.UnoListBoxControl': 'listbox', - } - return types[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): - self._controls = [] - for i, c in enumerate(self.obj.ControlModels): - tipo = self._get_type_control(c.ImplementationName) - control = get_custom_class(tipo, c) + 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 - self._controls.append(control) - setattr(self, c.Name, control) + setattr(self, name, control) + self._controls[name] = control + return + + @property + def obj(self): + return self._obj @property def name(self): - return self._obj.getName() + return self.obj.Name @name.setter def name(self, value): - self._obj.setName(value) + self.obj.Name = value + @property + def source(self): + return self.obj.DataSourceName + @source.setter + def source(self, value): + self.obj.DataSourceName = value -class LOForms(ObjectBase): + @property + def type(self): + return self.obj.CommandType + @type.setter + def type(self, value): + self.obj.CommandType = value - def __init__(self, obj, doc): - self._doc = doc - super().__init__(obj) - - def __getitem__(self, index): - form = super().__getitem__(index) - return LOForm(form) + @property + def command(self): + return self.obj.Command + @command.setter + def command(self, value): + self.obj.Command = value @property def doc(self): - return self._doc + 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 self.obj.getCount() + return len(self) @property def names(self): - return self.obj.getElementNames() - - def exists(self, name): - return name in self.names + return self.obj.ElementNames def insert(self, name): - form = self.doc.create_instance('com.sun.star.form.component.Form') + form = self.doc.createInstance('com.sun.star.form.component.Form') self.obj.insertByName(name, form) - return self[name] + return LOForm(form, self._dp) def remove(self, index): if isinstance(index, int): @@ -892,380 +1835,128 @@ class LOForms(ObjectBase): return -class LOCellStyle(LOObjectBase): +# ~ IsFiltered, +# ~ IsManualPageBreak, +# ~ IsStartOfNewPage +class LOSheetRows(object): - def __init__(self, obj): - super().__init__(obj) - - @property - def name(self): - return self.obj.Name - - def apply(self, properties): - set_properties(self.obj, properties) - return - - -class LOCellStyles(object): - - def __init__(self, obj): + 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 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 apply(self, style, properties): - set_properties(style, properties) - return - - -class LOImage(object): - TYPES = { - 'image/png': 'png', - 'image/jpeg': 'jpg', - } - - def __init__(self, obj): - self._obj = obj - - @property - def address(self): - return self.obj.Anchor.AbsoluteName - - @property - def url(self): - return _path_system(self.obj.URL) - @url.setter - def url(self, value): - self.obj.URL = _path_url(value) - - @property - def path(self): - return _path_system(self.obj.GraphicURL) - @path.setter - def path(self, value): - self.obj.GraphicURL = _path_url(value) - - @property - def visible(self): - return self.obj.Visible - @visible.setter - def visible(self, value): - self.obj.Visible = value - - def save(self, path): - if is_dir(path): - p = path - n = self.name - else: - p, fn, n, e = get_info_path(path) - ext = self.TYPES[self.mimetype] - path = join(p, '{}.{}'.format(n, ext)) - size = len(self.obj.Bitmap.DIB) - data = self.obj.GraphicStream.readBytes((), size) - data = data[-1].value - save_file(path, 'wb', data) - return path - - -class LOCalc(LODocument): - - def __init__(self, obj): - super().__init__(obj) - self._sheets = obj.getSheets() - - def __getitem__(self, index): - if isinstance(index, str): - code_name = [s.Name for s in self._sheets if s.CodeName == index] - if code_name: - index = code_name[0] - return LOCalcSheet(self._sheets[index], self) - - def __setitem__(self, key, value): - self._sheets[key] = value - - def __contains__(self, item): - return item in self.obj.Sheets - - @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 active(self): - return LOCalcSheet(self._cc.getActiveSheet(), self) - - def activate(self, sheet): - obj = sheet - if isinstance(sheet, LOCalcSheet): - obj = sheet.obj - elif isinstance(sheet, str): - obj = self[sheet].obj - self._cc.setActiveSheet(obj) - return - - @property - def selection(self): - sel = self.obj.getCurrentSelection() - if sel.ImplementationName in OBJ_TYPE_RANGES: - sel = LOCellRange(sel, self) - return sel - - @property - def sheets(self): - return LOCalcSheets(self._sheets, self) - - @property - def names(self): - return self.sheets.names - - @property - def cell_style(self): - obj = self.obj.getStyleFamilies()['CellStyles'] - return LOCellStyles(obj) - - def create(self): - return self.obj.createInstance('com.sun.star.sheet.Spreadsheet') - - def insert(self, name, pos=-1): - # ~ sheet = obj.createInstance('com.sun.star.sheet.Spreadsheet') - # ~ obj.Sheets['New'] = sheet - index = pos - if pos < 0: - index = self._sheets.Count + pos + 1 - if isinstance(name, str): - self._sheets.insertNewByName(name, index) - else: - for n in name: - self._sheets.insertNewByName(n, index) - name = n - return LOCalcSheet(self._sheets[name], self) - - def move(self, name, pos=-1): - return self.sheets.move(name, pos) - - def remove(self, name): - return self.sheets.remove(name) - - def copy(self, source='', target='', pos=-1): - index = pos - if pos < 0: - index = self._sheets.Count + pos + 1 - - names = source - if not names: - names = self.names - elif isinstance(source, str): - names = (source,) - - new_names = target - if not target: - new_names = [n + '_2' for n in names] - elif isinstance(target, str): - new_names = (target,) - - for i, ns in enumerate(names): - self.sheets.copy(ns, new_names[i], index + i) - - return LOCalcSheet(self._sheets[index], self) - - def copy_from(self, doc, source='', target='', pos=-1): - index = pos - if pos < 0: - index = self._sheets.Count + pos + 1 - - names = source - if not names: - 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, n in enumerate(names): - self._sheets.importSheet(doc.obj, n, index + i) - self.sheets[index + i].name = new_names[i] - - # ~ doc.getCurrentController().setActiveSheet(sheet) - # ~ For controls in sheet - # ~ doc.getCurrentController().setFormDesignMode(False) - - return LOCalcSheet(self._sheets[index], self) - - def sort(self, reverse=False): - names = sorted(self.names, reverse=reverse) - for i, n in enumerate(names): - self.sheets.move(n, i) - return - - def get_cell(self, index=None): - """ - index is str 'A1' - index is tuple (row, col) - """ - if index is None: - cell = self.selection.first - else: - cell = LOCellRange(self.active[index].obj, self) - return cell - - def select(self, rango): - r = rango - if hasattr(rango, 'obj'): - r = rango.obj - elif isinstance(rango, str): - r = self.get_cell(rango).obj - self._cc.select(r) - return - - def create_cell_style(self, name=''): - obj = self.create_instance('com.sun.star.style.CellStyle') - if name: - self.cell_style[name] = obj - return LOCellStyle(obj) - - def clear_undo(self): - self.obj.getUndoManager().clear() - return - - def filter_by_color(self, cell=None): - if cell is None: - cell = self.selection.first - cr = cell.current_region - col = cell.column - cr.column - rangos = cell.get_column(col).visible - for r in rangos: - for row in range(r.rows): - c = r[row, 0] - if c.back_color != cell.back_color: - c.rows_visible = False - return - - def _set_cell(self, k='', v=None, cell=None, value=False): - if k: - self._sd.setSearchString(k) - ranges = self._search.findAll(self._sd) - if ranges: - ranges = ranges.getRangeAddressesAsString().split(';') - for r in ranges: - for c in r.split(','): - cell = self._sheet.getCellRangeByName(c) - if v is None: - return cell - if cell.getImplementationName() == 'ScCellObj': - pattern = re.compile(k, re.IGNORECASE) - nv = pattern.sub(v, cell.getString()) - if value: - cell.setValue(nv) - else: - cell.setString(nv) - return cell - if cell: - if cell.getImplementationName() == 'ScCellObj': - ca = cell.getCellAddress() - new_cell = self._sheet.getCellByPosition(ca.Column, ca.Row + 1) - if value: - new_cell.setValue(v) - else: - new_cell.setString(v) - return new_cell - - def render(self, data, sheet=0): - self.set_search_in(sheet) - - for k, v in data.items(): - self._render_cells(k, v) - return - - -class LOCalcSheets(object): - - def __init__(self, obj, doc): - self._obj = obj - self._doc = doc - - def __getitem__(self, index): - return LOCalcSheet(self.obj[index], self.doc) - - @property - def obj(self): - return self._obj - - @property - def doc(self): - return self._doc - - @property - def count(self): return self.obj.Count @property - def names(self): - return self.obj.ElementNames + def obj(self): + return self._obj - def copy(self, name, new_name, pos): - self.obj.copyByName(name, new_name, pos) + @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 move(self, name, pos): - index = pos - if pos < 0: - index = self.count + pos + 1 - sheet = self.obj[name] - self.obj.moveByName(sheet.Name, index) + def insert(self, index, count): + self.obj.insertByIndex(index, count) return - def remove(self, name): - sheet = self.obj[name] - self.obj.removeByName(sheet.Name) + 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, doc): + def __init__(self, obj): self._obj = obj - self._doc = doc - self._init_values() def __getitem__(self, index): - return LOCellRange(self.obj[index], self.doc) + return LOCalcRange(self.obj[index]) def __enter__(self): return self @@ -1273,23 +1964,13 @@ class LOCalcSheet(object): def __exit__(self, exc_type, exc_value, traceback): pass - def _init_values(self): - self._events = None - self._dp = self.obj.getDrawPage() - self._images = {i.Name: LOImage(i) for i in self._dp} + def __str__(self): + return f'easymacro.LOCalcSheet: {self.name}' @property def obj(self): return self._obj - @property - def doc(self): - return self._doc - - @property - def images(self): - return self._images - @property def name(self): return self._obj.Name @@ -1304,27 +1985,12 @@ class LOCalcSheet(object): def code_name(self, value): self._obj.CodeName = value - @property - def color(self): - return self._obj.TabColor - @color.setter - def color(self, value): - self._obj.TabColor = get_color(value) - - @property - def active(self): - return self.doc.selection.first - - def activate(self): - self.doc.activate(self.obj) - return - @property def visible(self): - return self.obj.IsVisible + return self._obj.IsVisible @visible.setter def visible(self, value): - self.obj.IsVisible = value + self._obj.IsVisible = value @property def is_protected(self): @@ -1345,157 +2011,985 @@ class LOCalcSheet(object): pass return False - def get_cursor(self, cell): - return self.obj.createCursorByRange(cell) + @property + def color(self): + return self._obj.TabColor + @color.setter + def color(self, value): + self._obj.TabColor = get_color(value) - def exists_chart(self, name): - return name in self.obj.Charts.ElementNames + @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 LOForms(self._dp.getForms(), self.doc) + return LOSheetForms(self.obj.DrawPage) @property def events(self): - return self._events + 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, controllers): - self._events = controllers - self._connect_listeners() + 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))) - def _connect_listeners(self): - if self.events is None: - return + @property + def search_descriptor(self): + return self.obj.createSearchDescriptor() - listeners = { - 'addModifyListener': EventsModify, - } - for key, value in listeners.items(): - getattr(self.obj, key)(listeners[key](self.events)) - print('add_listener') + @property + def replace_descriptor(self): + return self.obj.createReplaceDescriptor() + + def activate(self): + self.doc.activate(self.obj) return - def get_range_by_address(self, address): - row_s = address.StartRow - row_e = address.EndRow + 1 - col_s = address.StartColumn - col_e = address.EndColumn + 1 - return LOCellRange(self.obj[row_s:row_e,col_s:col_e], self.doc) + def clean(self): + doc = self.doc + sheet = doc.create_instance('com.sun.star.sheet.Spreadsheet') + doc._sheets.replaceByName(self.name, sheet) + return - def render(self, data, rango=None, clean=False): + 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: - ra = self.obj.getPrintAreas()[0] - rango = self.get_range_by_address(ra) - - rango.render(data, clean) - return rango + rango = self.used_area + return rango.render(data, clean) def find(self, search_string, rango=None): if rango is None: - ra = self.obj.getPrintAreas()[0] - rango = self.get_range_by_address(ra) + rango = self.used_area + return rango.find(search_string) - cell = 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 -class LOWriter(LODocument): + 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): - return self._obj.getText().String + 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.getText() + return self.obj.Text @property def cursor(self): - return self.text.createTextCursor() + return self.text.createTextCursorByRange(self.obj) @property - def paragraphs(self): - return [LOTextRange(p) for p in self.text] + def dp(self): + return self._doc.dp - @property - def selection(self): - sel = self.obj.getCurrentSelection() - if sel.ImplementationName == TEXT_RANGES: - return LOTextRange(sel[0]) - elif sel.ImplementationName == TEXT_RANGE: - return LOTextRange(sel) - return sel + def offset(self): + cursor = self.cursor.getEnd() + return LOWriterTextRange(cursor, self._doc) - def write(self, data, cursor=None): - cursor = cursor or self.selection.cursor.getEnd() - if data.startswith('\n'): - c = data.split('\n') - for i in range(len(c)-1): - self.text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False) - else: - self.text.insertString(cursor, data, False) - return - - def insert_table(self, data, cursor=None): - cursor = cursor or self.selection.cursor.getEnd() - table = self.obj.createInstance('com.sun.star.text.TextTable') - rows = len(data) - cols = len(data[0]) - table.initialize(rows, cols) - self.insert_content(cursor, table) - table.DataArray = data - return WriterTable(table) - - def create_chart(self, tipo, cursor=None): - cursor = cursor or self.selection.cursor.getEnd() - chart = LOChart(None, tipo) - chart.cursor = cursor - chart.doc = self - return chart - - def insert_content(self, cursor, data, replace=False): + def insert_content(self, data, cursor=None, replace=False): + if cursor is None: + cursor = self.cursor self.text.insertTextContent(cursor, data, replace) return - # ~ f = doc.createInstance('com.sun.star.text.TextFrame') - # ~ f.setSize(Size(10000, 500)) + 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_image(self, path, **kwargs): - cursor = kwargs.get('cursor', self.selection.cursor.getEnd()) - w = kwargs.get('width', 1000) - h = kwargs.get('Height', 1000) - image = self.create_instance('com.sun.star.text.GraphicObject') - image.GraphicURL = _path_url(path) + 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(cursor, image) + 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 - def go_start(self): - cursor = self._cc.getViewCursor() - cursor.gotoStart(False) - return cursor - def go_end(self): - cursor = self._cc.getViewCursor() - cursor.gotoEnd(False) - return cursor +class LOWriter(LODocument): - def select(self, text): - self._cc.select(text) - return + def __init__(self, obj): + super().__init__(obj) + self._type = WRITER - def search(self, options): - descriptor = self.obj.createSearchDescriptor() + @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) @@ -1507,15 +3001,20 @@ class LOWriter(LODocument): if hasattr(descriptor, 'SearchType') and 'Type' in options: descriptor.SearchType = options['Type'] - if options.get('First', False): - found = self.obj.findFirst(descriptor) - else: + 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 found + return result def replace(self, options): - descriptor = self.obj.createReplaceDescriptor() + descriptor = self.replace_descriptor descriptor.setSearchString(options['Search']) descriptor.setReplaceString(options['Replace']) descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) @@ -1530,41 +3029,446 @@ class LOWriter(LODocument): found = self.obj.replaceAll(descriptor) return found + def select(self, text): + if hasattr(text, 'obj'): + text = text.obj + self._cc.select(text) + return -class LOTextRange(object): - def __init__(self, obj): - self._obj = obj - self._is_paragraph = self.obj.ImplementationName == 'SwXParagraph' - self._is_table = self.obj.ImplementationName == 'SwXTextTable' +class LOShape(LOBaseObject): + IMAGE = 'com.sun.star.drawing.GraphicObjectShape' + + def __init__(self, obj, index): + self._index = index + super().__init__(obj) @property - def obj(self): - return self._obj + def type(self): + t = self.shape_type[21:] + if self.is_image: + t = 'image' + return t @property - def is_paragraph(self): - return self._is_paragraph + def shape_type(self): + return self.obj.ShapeType @property - def is_table(self): - return self._is_table + 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 text(self): - return self.obj.getText() + def description(self): + return self.obj.Description + @description.setter + def description(self, value): + self.obj.Description = value @property - def cursor(self): - return self.text.createTextCursorByRange(self.obj) + 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): - TYPES = { + DB_TYPES = { str: 'setString', int: 'setInt', float: 'setFloat', @@ -1586,40 +3490,29 @@ class LOBase(object): # ~ setObjectWithInfo # ~ setPropertyValue # ~ setRef - def __init__(self, name, path='', **kwargs): - self._name = name - self._path = path + + def __init__(self, obj, args={}): + self._obj = obj + self._type = BASE self._dbc = create_instance('com.sun.star.sdb.DatabaseContext') - if path: - path_url = _path_url(path) + 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(path_url, ()) - if not self.exists: - self._dbc.registerDatabaseLocation(name, path_url) - else: - if name.startswith('odbc:'): - self._con = self._odbc(name, kwargs) - else: - db = self._dbc.getByName(name) - self.path = _path_system(self._dbc.getDatabaseLocation(name)) - self._con = db.getConnection('', '') + db.DatabaseDocument.storeAsURL(self._path.url, ()) + self.register() + self._obj = db + self._con = db.getConnection('', '') - if self._con is None: - msg = 'Not connected to: {}'.format(name) - else: - msg = 'Connected to: {}'.format(name) - debug(msg) - - def _odbc(self, name, kwargs): - dm = create_instance('com.sun.star.sdbc.DriverManager') - args = dict_to_property(kwargs) - try: - con = dm.getConnectionWithInfo('sdbc:{}'.format(name), args) - return con - except Exception as e: - error(str(e)) - return None + def __contains__(self, item): + return item in self.tables @property def obj(self): @@ -1629,25 +3522,26 @@ class LOBase(object): def name(self): return self._name - @property - def connection(self): - return self._con - @property def path(self): - return self._path - @path.setter - def path(self, value): - self._path = value + return str(self._path) @property - def exists(self): + def is_registered(self): return self._dbc.hasRegisteredDatabase(self.name) - @classmethod - def register(self, path, name): - if not self._dbc.hasRegisteredDatabase(name): - self._dbc.registerDatabaseLocation(name, _path_url(path)) + @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): @@ -1655,10 +3549,7 @@ class LOBase(object): return True def save(self): - # ~ self._db.connection.commit() - # ~ self._db.connection.getTables().refresh() - # ~ oDisp.executeDispatch(oFrame,".uno:DBRefreshTables", "", 0, Array()) - self._obj.DatabaseDocument.store() + self.obj.DatabaseDocument.store() self.refresh() return @@ -1670,648 +3561,211 @@ class LOBase(object): self._con.getTables().refresh() return - def get_tables(self): - tables = self._con.getTables() - tables = [tables.getByIndex(i) for i in range(tables.Count)] - return tables + 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): - if not type(v) in self.TYPES: + t = type(v) + if not t in self.DB_TYPES: error('Type not support') - debug((i, type(v), v, self.TYPES[type(v)])) - getattr(cursor, self.TYPES[type(v)])(i, v) + 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) - if params: - cursor = self.cursor(sql, params) - cursor.execute() + 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: - cursor = self._con.createStatement() - cursor.execute(sql) - # ~ resulset = cursor.executeQuery(sql) - # ~ rows = cursor.executeUpdate(sql) - self.save() - return cursor + result = cursor.execute(sql) + self.save() + return result -class LODrawImpress(LODocument): + def select(self, sql): + debug('SELECT', sql) + if not sql.startswith('SELECT'): + return () - def __init__(self, obj): - super().__init__(obj) + cursor = self._con.prepareStatement(sql) + query = cursor.executeQuery() + return BaseQuery(query) - @property - def draw_page(self): - return self._cc.getCurrentPage() - - def insert_image(self, path, **kwargs): - w = kwargs.get('width', 3000) - h = kwargs.get('Height', 3000) - x = kwargs.get('X', 1000) - y = kwargs.get('Y', 1000) - - image = self.create_instance('com.sun.star.drawing.GraphicObjectShape') - image.GraphicURL = _path_url(path) - image.Size = Size(w, h) - image.Position = Point(x, y) - self.draw_page.add(image) - return - - -class LOImpress(LODrawImpress): - - def __init__(self, obj): - super().__init__(obj) - - -class LODraw(LODrawImpress): - - def __init__(self, obj): - super().__init__(obj) - - @property - def selection(self): - sel = self.obj.getCurrentSelection() - return sel + 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 LOBasicIde(LODocument): +class LOBasic(LODocument): def __init__(self, obj): super().__init__(obj) - - @property - def selection(self): - sel = self._cc.getSelection() - return sel + self._type = BASIC -class LOCellRange(object): +class LODocs(object): + _desktop = None - def __init__(self, obj, doc): - self._obj = obj - self._doc = doc - self._init_values() - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - pass + def __init__(self): + self._desktop = get_desktop() + LODocs._desktop = self._desktop def __getitem__(self, index): - return LOCellRange(self.obj[index], self.doc) + 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): - return item.in_range(self) + doc = self[item] + return not doc is None - def _init_values(self): - self._sd = None - self._type_obj = self.obj.ImplementationName - self._type_content = EMPTY + def __iter__(self): + self._i = -1 + return self - if self._type_obj == OBJ_CELL: - self._type_content = self.obj.getType() - return - - @property - def obj(self): - return self._obj - - @property - def doc(self): - return self._doc - - @property - def type(self): - return self._type_obj - - @property - def type_content(self): - return self._type_content - - @property - def first(self): - if self.type == OBJ_RANGES: - obj = LOCellRange(self.obj[0][0,0], self.doc) + def __next__(self): + self._i += 1 + doc = self[self._i] + if doc is None: + raise StopIteration else: - obj = LOCellRange(self.obj[0,0], self.doc) - return obj + return doc - @property - def value(self): - v = None - if self._type_content == VALUE: - v = self.obj.getValue() - elif self._type_content == TEXT: - v = self.obj.getString() - elif self._type_content == FORMULA: - v = self.obj.getFormula() - return v - @value.setter - def value(self, data): - if isinstance(data, str): - if data.startswith('='): - self.obj.setFormula(data) - else: - self.obj.setString(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 data(self): - return self.obj.getDataArray() - @data.setter - def data(self, values): - self.obj.setDataArray(values) - - @property - def formula(self): - return self.obj.getFormulaArray() - @formula.setter - def formula(self, values): - self.obj.setFormulaArray(values) - - @property - def column(self): - a = self.address - if hasattr(a, 'Column'): - c = a.Column - else: - c = a.StartColumn - return c - - @property - def columns(self): - return self._obj.Columns.Count - - @property - def row(self): - a = self.address - if hasattr(a, 'Row'): - r = a.Row - else: - r = a.StartRow - return r - - @property - def rows(self): - return self._obj.Rows.Count - - def to_size(self, rows, cols): - cursor = self.sheet.get_cursor(self.obj[0,0]) - cursor.collapseToSize(cols, rows) - return LOCellRange(self.sheet[cursor.AbsoluteName].obj, self.doc) - - def copy_from(self, rango, formula=False): - data = rango - if isinstance(rango, LOCellRange): - 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 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(self, source): - self.sheet.obj.copyRange(self.address, source.range_address) - return - - def transpose(self, formula=False): - data = self.data - if formula: - data = self.formula - data = tuple(zip(*data)) - self.clear(1023) - self[0,0].copy_from(data, formula=formula) - return - - def transpose2(self): - # ~ 'Flags': 'A', - # ~ 'FormulaCommand': 0, - # ~ 'SkipEmptyCells': False, - # ~ 'AsLink': False, - # ~ 'MoveMode': 4, - args = { - 'Transpose': True, - } - args = dict_to_property(args) - self.select() - copy() - self.clear(1023) - self[0,0].select() - call_dispatch(self._doc, '.uno:InsertContents', args) - set_clipboard('') - return - - def offset(self, row=1, col=0): - ra = self.obj.getRangeAddress() - col = ra.EndColumn + col - row = ra.EndRow + row - return LOCellRange(self.sheet[row, col].obj, self.doc) - - @property - def next_cell(self): - a = self.current_region.address - if hasattr(a, 'StartColumn'): - col = a.StartColumn - else: - col = a.Column - if hasattr(a, 'EndRow'): - row = a.EndRow + 1 - else: - row = a.Row + 1 - - return LOCellRange(self.sheet[row, col].obj, self.doc) - - @property - def sheet(self): - return LOCalcSheet(self.obj.Spreadsheet, self.doc) - - @property - def charts(self): - return self.obj.Spreadsheet.Charts - - @property - def ps(self): - ps = Rectangle() - s = self.obj.Size - p = self.obj.Position - ps.X = p.X - ps.Y = p.Y - ps.Width = s.Width - ps.Height = s.Height - return ps - - @property - def draw_page(self): - return self.sheet.obj.getDrawPage() - - @property - def name(self): - return self.obj.AbsoluteName - - @property - def address(self): - if self._type_obj == OBJ_CELL: - a = self.obj.getCellAddress() - elif self._type_obj == OBJ_RANGE: - a = self.obj.getRangeAddress() - else: - a = self.obj.getRangeAddressesAsString() - return a - - @property - def range_address(self): - return self.obj.getRangeAddress() - - @property - def current_region(self): - cursor = self.sheet.get_cursor(self.obj[0,0]) - cursor.collapseToCurrentRegion() - return LOCellRange(self.sheet[cursor.AbsoluteName].obj, self.doc) - - @property - def merged_area(self): - cursor = self.sheet.get_cursor(self.obj[0,0]) - cursor.collapseToMergedArea() - return LOCellRange(self.sheet[cursor.AbsoluteName].obj, self.doc) - - @property - def visible(self): - cursor = self.sheet.get_cursor(self.obj) - rangos = [LOCellRange(self.sheet[r.AbsoluteName].obj, self.doc) - for r in cursor.queryVisibleCells()] - return tuple(rangos) - - @property - def empty(self): - cursor = self.sheet.get_cursor(self.obj) - rangos = [LOCellRange(self.sheet[r.AbsoluteName].obj, self.doc) - for r in cursor.queryEmptyCells()] - return tuple(rangos) - - @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 cell_style(self): - return self.obj.CellStyle - @cell_style.setter - def cell_style(self, value): - self.obj.CellStyle = value - - @property - def auto_format(self): - return self.obj.CellStyle - @auto_format.setter - def auto_format(self, value): - self.obj.autoFormat(value) - - def auto_width(self): - self.obj.Columns.OptimalWidth = True - return - - def insert_image(self, path, **kwargs): - s = self.obj.Size - w = kwargs.get('Width', s.Width) - h = kwargs.get('Height', s.Height) - data = kwargs.get('Data', '') - img = self.doc.create_instance('com.sun.star.drawing.GraphicObjectShape') - img.GraphicURL = _path_url(path) - # ~ img.ResizeWithCell = True - self.draw_page.add(img) - img.setSize(Size(w, h)) - img.Anchor = self.obj - return - - def insert_shape(self, tipo, **kwargs): - s = self.obj.Size - w = kwargs.get('width', s.Width) - h = kwargs.get('Height', s.Height) - img = self.doc.create_instance('com.sun.star.drawing.{}Shape'.format(tipo)) - set_properties(img, kwargs) - self.draw_page.add(img) - img.Anchor = self.obj - img.setSize(Size(w, h)) - return - - def select(self): - self.doc._cc.select(self.obj) - return - - def in_range(self, rango): - if isinstance(rango, LOCellRange): - address = rango.address - else: - address = rango.getRangeAddress() - cursor = self.sheet.get_cursor(self.obj) - result = cursor.queryIntersection(address) - return bool(result.Count) - - def fill(self, source=1): - self.obj.fillAuto(0, source) - return - - def clear(self, what=31): - # ~ http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html - self.obj.clearContents(what) - return - - @property - def rows_visible(self): - return self._obj.getRows().IsVisible - @rows_visible.setter - def rows_visible(self, value): - self._obj.getRows().IsVisible = value - - @property - def columns_visible(self): - return self._obj.getColumns().IsVisible - @columns_visible.setter - def columns_visible(self, value): - self._obj.getColumns().IsVisible = value - - def get_column(self, index=0, first=False): - ca = self.address - ra = self.current_region.address - if hasattr(ca, 'Column'): - col = ca.Column - else: - col = ca.StartColumn + index - start = 1 - if first: - start = 0 - if hasattr(ra, 'Row'): - row_start = ra.Row + start - row_end = ra.Row + 1 - else: - row_start = ra.StartRow + start - row_end = ra.EndRow + 1 - return LOCellRange(self.sheet[row_start:row_end, col:col+1].obj, self.doc) - - def import_csv(self, path, **kwargs): - data = import_csv(path, **kwargs) - self.copy_from(data) - return - - def export_csv(self, path, **kwargs): - data = self.current_region.data - export_csv(path, data, **kwargs) - return - - def create_chart(self, tipo): - chart = LOChart(None, tipo) - chart.cell = self - return chart - - def search(self, options): - descriptor = self.obj.Spreadsheet.createSearchDescriptor() - descriptor.setSearchString(options.get('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 options.get('First', False): - found = self.obj.findFirst(descriptor) - else: - found = self.obj.findAll(descriptor) - - return found - - def replace(self, options): - descriptor = self.obj.Spreadsheet.createReplaceDescriptor() - 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'] - found = self.obj.replaceAll(descriptor) - return found - - @property - def validation(self): - return self.obj.Validation - @validation.setter - def validation(self, values): - is_list = False - current = self.validation - for k, v in values.items(): - if k == 'Type' and v == 6: - is_list = True - if k == 'Formula1' and is_list: - if isinstance(v, (tuple, list)): - v = ';'.join(['"{}"'.format(i) for i in v]) - setattr(current, k, v) - self.obj.Validation = current - - 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: - cell.Value = value - return - - def rows_insert(self, count=0): - if not count: - count = self.rows - self.obj.Rows.insertByIndex(0, count) - return - - def rows_merge(self): - for r in range(self.rows): - self[r].obj.merge(True) - return - - def copy_format_from(self, rango): - rango.select() - copy(self._doc) - self.select() - args = { - 'Flags': 'T', - 'MoveMode': 4, - } - args = dict_to_property(args) - url = '.uno:InsertContents' - call_dispatch(self._doc, url, args) - return - - def _render_list(self, key, values): - rango = None - total_rows = len(values) - try: - rango = self.sheet[key] - if total_rows > 1: - cells = rango[0,0].offset(1).to_size(total_rows - 1, 1) - name = cells.name - cells.rows_insert() - cells = self.sheet[name] - cells.copy_format_from(rango) - except Exception as e: - print(key, e) - - if rango is None: + def __len__(self): + for i, _ in enumerate(self._desktop.Components): pass - else: - headers = list(rango.data[0]) - positions = {} - for i, v in enumerate(headers): - if not v: - continue - k = v[1:-1] - if k in values[0]: - positions[k] = i + return i + 1 - data = [] - for row in values: - new_row = headers[:] - for k, v in positions.items(): - new_row[v] = row[k] - data.append(new_row) + @property + def active(self): + return _get_class_doc(self._desktop.getCurrentComponent()) - cell = rango[0,0] - cell.copy_from(data) - return + @classmethod + def new(cls, type_doc=CALC, args={}): + if type_doc == BASE: + return LOBase(None, args) - def _render_value(self, key, value, parent=''): - if isinstance(value, dict): - for k, v in value.items(): - self._render_value(k, v, key) - return - elif isinstance(value, (list, tuple)): - self._render_list(key, value) + 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 - search = f'{{{key}}}' - if parent: - search = f'{{{parent}.{key}}}' - ranges = self.find_all(search) + return _get_class_doc(doc) - for cell in ranges or range(0): - self._set_new_value(cell, search, value) - return + def connect(self, path): + return LOBase(None, {'path': path}) - def clean_render(self, template='\{(\w.+)\}'): - self._sd.SearchRegularExpression = True - self._sd.setSearchString(template) - self.obj.replaceAll(self._sd) - return - def find(self, search_string): - if self._sd is None: - self._sd = self.sheet.obj.createSearchDescriptor() - self._sd.SearchCaseSensitive = False +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' - self._sd.setSearchString(search_string) - cell = self.obj.findFirst(self._sd) - return LOCellRange(self.sheet[cell.AbsoluteName].obj, self.doc) + 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 - def find_all(self, search_string): - if self._sd is None: - self._sd = self.sheet.obj.createSearchDescriptor() - self._sd.SearchCaseSensitive = False + getattr(control, key)(listeners[key](events, name)) - self._sd.setSearchString(search_string) - ranges = self.obj.findAll(self._sd) - return ranges + if is_grid: + controllers = EventsGrid(events, name) + control.addSelectionListener(controllers) + control.Model.GridDataModel.addGridDataListener(controllers) + return - def render(self, data, clean=False): - self._sd = self.sheet.obj.createSearchDescriptor() - self._sd.SearchCaseSensitive = False - for k, v in data.items(): - self._render_value(k, v) - - if clean: - self.clean_render() - 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): @@ -2331,18 +3785,6 @@ class EventsListenerBase(unohelper.Base, XEventListener): self._window.setMenuBar(None) -class EventsButton(EventsListenerBase, XActionListener): - - def __init__(self, controller, name): - super().__init__(controller, name) - - def actionPerformed(self, event): - event_name = '{}_action'.format(self._name) - if hasattr(self._controller, event_name): - getattr(self._controller, event_name)(event) - return - - class EventsMouse(EventsListenerBase, XMouseListener, XMouseMotionListener): def __init__(self, controller, name): @@ -2375,14 +3817,129 @@ class EventsMouse(EventsListenerBase, XMouseListener, XMouseMotionListener): class EventsMouseLink(EventsMouse): + def __init__(self, controller, name): + super().__init__(controller, name) + self._text_color = 0 + def mouseEntered(self, event): - obj = event.Source.Model - obj.TextColor = get_color('blue') + model = event.Source.Model + self._text_color = model.TextColor or 0 + model.TextColor = get_color('blue') return def mouseExited(self, event): - obj = event.Source.Model - obj.TextColor = 0 + 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 @@ -2414,79 +3971,6 @@ class EventsMouseGrid(EventsMouse): return -class EventsModify(EventsListenerBase, XModifyListener): - - def __init__(self, controller): - super().__init__(controller) - - def modified(self, event): - event_name = '{}_modified'.format(event.Source.Name) - if hasattr(self._controller, event_name): - getattr(self._controller, event_name)(event) - 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 EventsFocus(EventsListenerBase, XFocusListener): - - def __init__(self, controller, name): - super().__init__(controller, name) - - def focusGained(self, event): - service = event.Source.Model.ImplementationName - if service == 'stardiv.Toolkit.UnoControlListBoxModel': - return - obj = event.Source.Model - obj.BackgroundColor = COLOR_ON_FOCUS - - def focusLost(self, event): - obj = event.Source.Model - obj.BackgroundColor = -1 - - -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) - return - - class EventsTab(EventsListenerBase, XTabListener): def __init__(self, controller, name): @@ -2499,55 +3983,28 @@ class EventsTab(EventsListenerBase, XTabListener): return -class EventsGrid(EventsListenerBase, XGridDataListener, XGridSelectionListener): +class EventsMenu(EventsListenerBase, XMenuListener): - def __init__(self, controller, name): - super().__init__(controller, name) + def __init__(self, controller): + super().__init__(controller, '') - 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): + def itemHighlighted(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 EventsKeyWindow(EventsListenerBase, XKeyListener): - """ - event.KeyChar - event.KeyCode - event.KeyFunc - event.Modifiers - """ - - def __init__(self, cls): - super().__init__(cls.events, cls.name) - self._cls = cls - - def keyPressed(self, event): - pass - - def keyReleased(self, event): - event_name = '{}_key_released'.format(self._cls.name) - if hasattr(self._controller, event_name): - getattr(self._controller, event_name)(event) + def itemSelected(self, event): + name = event.Source.getCommand(event.MenuId) + if name.startswith('menu'): + event_name = '{}_selected'.format(name) else: - if event.KeyFunc == QUIT and hasattr(self._cls, 'close'): - self._cls.close() + 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 @@ -2619,37 +4076,27 @@ class EventsWindow(EventsListenerBase, XTopWindowListener, XWindowListener): pass -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 - - +# ~ BorderColor = ? +# ~ FontStyleName = ? +# ~ HelpURL = ? class UnoBaseObject(object): - def __init__(self, obj): + def __init__(self, obj, path=''): self._obj = obj - self._model = self.obj.Model - self._rules = {} + 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): @@ -2658,6 +4105,16 @@ class UnoBaseObject(object): @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): @@ -2665,8 +4122,127 @@ class UnoBaseObject(object): @property def parent(self): - ps = self.obj.getContext().PosSize - return self.obj.getContext() + 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() @@ -2703,90 +4279,35 @@ class UnoBaseObject(object): self._set_possize('Y', value) @property - def width(self): - return self._model.Width - @width.setter - def width(self, value): - self.model.Width = value + def tab_index(self): + return self._model.TabIndex + @tab_index.setter + def tab_index(self, value): + self.model.TabIndex = value @property - def ps_width(self): - return self._get_possize('Width') - @ps_width.setter - def ps_width(self, value): - self._set_possize('Width', value) + def tab_stop(self): + return self._model.Tabstop + @tab_stop.setter + def tab_stop(self, value): + self.model.Tabstop = value @property - def height(self): - return self.model.Height - @height.setter - def height(self, value): - self.model.Height = value - - @property - def ps_height(self): - return self._get_possize('Height') - @ps_height.setter - def ps_height(self, value): - self._set_possize('Height', value) - - @property - def size(self): + def ps(self): ps = self.obj.getPosSize() - return (ps.Width, ps.Height) - @size.setter - def size(self, value): - ps = self.obj.getPosSize() - ps.Width = value[0] - ps.Height = value[1] - self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, SIZE) - - @property - def tag(self): - 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 back_color(self): - return self.model.BackgroundColor - @back_color.setter - def back_color(self, value): - self.model.BackgroundColor = value - - @property - def rules(self): - return self._rules - @rules.setter - def rules(self, value): - self._rules = value + 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 @@ -2799,7 +4320,7 @@ class UnoBaseObject(object): self.y = y return - def move(self, origin, x=0, y=5): + def move(self, origin, x=0, y=5, center=False): if x: self.x = origin.x + origin.width + x else: @@ -2808,13 +4329,9 @@ class UnoBaseObject(object): self.y = origin.y + origin.height + y else: self.y = origin.y - return - def possize(self, origin): - self.x = origin.x - self.y = origin.y - self.width = origin.width - self.height = origin.height + if center: + self.center() return @@ -2862,6 +4379,55 @@ class UnoButton(UnoBaseObject): 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): @@ -2879,14 +4445,45 @@ class UnoText(UnoBaseObject): 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): @@ -2906,7 +4503,13 @@ class UnoListBox(UnoBaseObject): @data.setter def data(self, values): self.model.StringItemList = list(sorted(values)) - return + + @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) @@ -2924,15 +4527,11 @@ class UnoListBox(UnoBaseObject): return def _set_image_url(self, image): - if exists_path(image): - return _path_url(image) + if _P.exists(image): + return _P.to_url(image) - if not ID_EXTENSION: - return '' - - path = get_path_extension(ID_EXTENSION) - path = join(path, DIR['images'], image) - return _path_url(path) + 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: @@ -2946,130 +4545,18 @@ class UnoListBox(UnoBaseObject): return -class UnoGrid(UnoBaseObject): - - def __init__(self, obj): - super().__init__(obj) - self._gdm = self._model.GridDataModel - # ~ self._data = [] - self._columns = {} - # ~ self._format_columns = () - - def __getitem__(self, index): - value = self._gdm.getCellData(index[0], index[1]) - return value - - @property - def type(self): - return 'grid' - - 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 value(self): - return self[self.column, self.row] - - @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 row(self): - return self.obj.CurrentRow - - @property - def rows(self): - return self._gdm.RowCount - - @property - def column(self): - return self.obj.CurrentColumn - - @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 clear(self): - self._gdm.removeAllRows() - return - - 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 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 @@ -3104,16 +4591,41 @@ class UnoTree(UnoBaseObject): 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): - return self.obj.Selection + 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) @@ -3125,9 +4637,15 @@ class UnoTree(UnoBaseObject): tdm.setRoot(root) self.model.DataModel = tdm self._tdm = self.model.DataModel - self._add_data() 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 @@ -3151,61 +4669,297 @@ class UnoTree(UnoBaseObject): return -class UnoTab(UnoBaseObject): +# ~ 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): - return self.get_sheet(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.getActiveTabID() + return self.obj.ActiveTabID @property def active(self): return self.current - def get_sheet(self, id): - if isinstance(id, int): - sheet = self.obj.Controls[id-1] - else: - sheet = self.obj.getControl(id.lower()) - return sheet - @property def sheets(self): return self._sheets @sheets.setter def sheets(self, values): - i = len(self.obj.Controls) - for title in values: - i += 1 - sheet = self.model.createInstance('com.sun.star.awt.UnoPageModel') + self._sheets = values + for i, title in enumerate(values): + sheet = self.m.createInstance('com.sun.star.awt.UnoPageModel') sheet.Title = title - self.model.insertByName('sheet{}'.format(i), sheet) - return - - def insert(self, title): - id = len(self.obj.Controls) + 1 - sheet = self.model.createInstance('com.sun.star.awt.UnoPageModel') - sheet.Title = title - self.model.insertByName('sheet{}'.format(id), sheet) - return id - - def remove(self, id): - sheet = self.get_sheet(id) - for control in sheet.getControls(): - sheet.Model.removeByName(control.Model.Name) - sheet.removeControl(control) - # ~ self._model.removeByName('page_{}'.format(ID)) - - self.obj.removeTab(id) - return - - def activate(self, id): - self.obj.activateTab(id) + self.m.insertByName(f'sheet{i + 1}', sheet) return @property @@ -3215,644 +4969,144 @@ class UnoTab(UnoBaseObject): def events(self, controllers): self._events = controllers - def _special_properties(self, tipo, properties): - columns = properties.pop('Columns', ()) - if tipo == 'grid': - properties['ColumnModel'] = _set_column_model(columns) - if not 'Width' in properties: - properties['Width'] = self.width - if not 'Height' in properties: - properties['Height'] = self.height - elif tipo == 'button' and 'ImageURL' in properties: - properties['ImageURL'] = self._set_image_url(properties['ImageURL']) - elif tipo == 'roadmap': - if not 'Height' in properties: - properties['Height'] = self.height - if 'Title' in properties: - properties['Text'] = properties.pop('Title') - elif tipo == 'pages': - if not 'Width' in properties: - properties['Width'] = self.width - if not 'Height' in properties: - properties['Height'] = self.height + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value - return properties + 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 add_control(self, id, properties): - tipo = properties.pop('Type').lower() - root = properties.pop('Root', '') - sheets = properties.pop('Sheets', ()) - properties = self._special_properties(tipo, properties) + def remove(self, id): + self.obj.removeTab(id) + return - sheet = self.get_sheet(id) - sheet_model = sheet.getModel() - model = sheet_model.createInstance(get_control_model(tipo)) - set_properties(model, properties) - name = properties['Name'] - sheet_model.insertByName(name, model) - - control = sheet.getControl(name) - add_listeners(self.events, control, name) - control = get_custom_class(tipo, control) - - if tipo == 'tree' and root: - control.root = root - elif tipo == 'pages' and sheets: - control.sheets = sheets - - setattr(self, name, control) + def activate(self, id): + self.obj.activateTab(id) return -def get_custom_class(tipo, obj): - classes = { - 'label': UnoLabel, - 'button': UnoButton, - 'text': UnoText, - 'listbox': UnoListBox, - 'grid': UnoGrid, - 'link': UnoLabelLink, - 'roadmap': UnoRoadmap, - 'tree': UnoTree, - 'tab': UnoTab, - # ~ 'image': UnoImage, - # ~ 'radio': UnoRadio, - # ~ 'groupbox': UnoGroupBox, - 'formbutton': FormButton, - } - return classes[tipo](obj) - - -def get_control_model(control): - services = { - 'label': 'com.sun.star.awt.UnoControlFixedTextModel', - 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', - 'text': 'com.sun.star.awt.UnoControlEditModel', - 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', - 'button': 'com.sun.star.awt.UnoControlButtonModel', - 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', - 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', - 'tree': 'com.sun.star.awt.tree.TreeControlModel', - 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', - 'image': 'com.sun.star.awt.UnoControlImageControlModel', - 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', - 'tab': 'com.sun.star.awt.UnoMultiPageModel', - } - return services[control] - - -def add_listeners(events, control, name=''): - listeners = { - 'addActionListener': EventsButton, - 'addMouseListener': EventsMouse, - 'addItemListener': EventsItem, - 'addFocusListener': EventsFocus, - 'addKeyListener': EventsKey, - 'addTabListener': EventsTab, - } - if hasattr(control, 'obj'): - control = contro.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' - - 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 - - -class WriterTable(ObjectBase): - - def __init__(self, obj): - super().__init__(obj) - - def __getitem__(self, key): - obj = super().__getitem__(key) - return WriterTableRange(obj, key, self.name) - - @property - def name(self): - return self.obj.Name - @name.setter - def name(self, value): - self.obj.Name = value - - -class WriterTableRange(ObjectBase): - - def __init__(self, obj, index, table_name): - self._index = index - self._table_name = table_name - super().__init__(obj) - self._is_cell = hasattr(self.obj, 'CellName') - - def __getitem__(self, key): - obj = super().__getitem__(key) - return WriterTableRange(obj, key, self._table_name) - - @property - def value(self): - return self.obj.String - @value.setter - def value(self, value): - self.obj.String = value - - @property - def data(self): - return self.obj.getDataArray() - @data.setter - def data(self, values): - if isinstance(values, list): - values = tuple(values) - self.obj.setDataArray(values) - - @property - def rows(self): - return len(self.data) - - @property - def columns(self): - return len(self.data[0]) - - @property - def name(self): - if self._is_cell: - name = '{}.{}'.format(self._table_name, self.obj.CellName) - elif isinstance(self._index, str): - name = '{}.{}'.format(self._table_name, self._index) - else: - c1 = self.obj[0,0].CellName - c2 = self.obj[self.rows-1,self.columns-1].CellName - name = '{}.{}:{}'.format(self._table_name, c1, c2) - return name - - def get_cell(self, *index): - return self[index] - - def get_column(self, index=0, start=1): - return self[start:self.rows,index:index+1] - - def get_series(self): - class Serie(): - pass - series = [] - for i in range(self.columns): - serie = Serie() - serie.label = self.get_cell(0,i).name - serie.data = self.get_column(i).data - serie.values = self.get_column(i).name - series.append(serie) - return series - - -class ChartFormat(object): - - def __call__(self, obj): - for k, v in self.__dict__.items(): - if hasattr(obj, k): - setattr(obj, k, v) - - -class LOChart(object): - BASE = 'com.sun.star.chart.{}Diagram' - - def __init__(self, obj, tipo=''): - self._obj = obj - self._type = tipo - self._name = '' - self._table = None - self._data = () - self._data_series = () - self._cell = None - self._cursor = None - self._doc = None - self._title = ChartFormat() - self._subtitle = ChartFormat() - self._legend = ChartFormat() - self._xaxistitle = ChartFormat() - self._yaxistitle = ChartFormat() - self._xaxis = ChartFormat() - self._yaxis = ChartFormat() - self._xmaingrid = ChartFormat() - self._ymaingrid = ChartFormat() - self._xhelpgrid = ChartFormat() - self._yhelpgrid = ChartFormat() - self._area = ChartFormat() - self._wall = ChartFormat() - self._dim3d = False - self._series = () - self._labels = () - return - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - self.insert() - - @property - def obj(self): - return self._obj - @obj.setter - def obj(self, value): - self._obj = value - - @property - def name(self): - return self._name - @name.setter - def name(self, value): - self._name = value - - @property - def type(self): - return self._type - @type.setter - def type(self, value): - self._type = value - - @property - def table(self): - return self._table - @table.setter - def table(self, value): - self._table = value - - @property - def data(self): - return self._data - @data.setter - def data(self, value): - self._data = value - - @property - def cell(self): - return self._cell - @cell.setter - def cell(self, value): - self._cell = value - self.doc = value.doc - - @property - def cursor(self): - return self._cursor - @cursor.setter - def cursor(self, value): - self._cursor = value - - @property - def doc(self): - return self._doc - @doc.setter - def doc(self, value): - self._doc = value - - @property - def width(self): - return self._width - @width.setter - def width(self, value): - self._width = value - - @property - def height(self): - return self._height - @height.setter - def height(self, value): - self._height = value - - @property - def title(self): - return self._title - - @property - def subtitle(self): - return self._subtitle - - @property - def legend(self): - return self._legend - - @property - def xaxistitle(self): - return self._xaxistitle - - @property - def yaxistitle(self): - return self._yaxistitle - - @property - def xaxis(self): - return self._xaxis - - @property - def yaxis(self): - return self._yaxis - - @property - def xmaingrid(self): - return self._xmaingrid - - @property - def ymaingrid(self): - return self._ymaingrid - - @property - def xhelpgrid(self): - return self._xhelpgrid - - @property - def yhelpgrid(self): - return self._yhelpgrid - - @property - def area(self): - return self._area - - @property - def wall(self): - return self._wall - - @property - def dim3d(self): - return self._dim3d - @dim3d.setter - def dim3d(self, value): - self._dim3d = value - - @property - def series(self): - return self._series - @series.setter - def series(self, value): - self._series = value - - @property - def data_series(self): - return self._series - @data_series.setter - def data_series(self, value): - self._data_series = value - - @property - def labels(self): - return self._labels - @labels.setter - def labels(self, value): - self._labels = value - - def _add_series_writer(self, chart): - dp = self.doc.create_instance('com.sun.star.chart2.data.DataProvider') - chart.attachDataProvider(dp) - chart_type = chart.getFirstDiagram().getCoordinateSystems()[0].getChartTypes()[0] - self._data_series = self.table[self.data].get_series() - series = [self._create_serie(dp, s) for s in self._data_series[1:]] - chart_type.setDataSeries(tuple(series)) - chart_data = chart.getData() - chart_data.ComplexRowDescriptions = self._data_series[0].data - return - - def _get_series(self): - rango = self._data_series - class Serie(): - pass - series = [] - for i in range(0, rango.columns, 2): - serie = Serie() - serie.label = rango[0, i+1].name - serie.xvalues = rango.get_column(i).name - serie.values = rango.get_column(i+1).name - series.append(serie) - return series - - def _add_series_calc(self, chart): - dp = self.doc.create_instance('com.sun.star.chart2.data.DataProvider') - chart.attachDataProvider(dp) - chart_type = chart.getFirstDiagram().getCoordinateSystems()[0].getChartTypes()[0] - series = self._get_series() - series = [self._create_serie(dp, s) for s in series] - chart_type.setDataSeries(tuple(series)) - return - - def _create_serie(self, dp, data): - serie = create_instance('com.sun.star.chart2.DataSeries') - rango = data.values - is_x = hasattr(data, 'xvalues') - if is_x: - xrango = data.xvalues - rango_label = data.label - - lds = create_instance('com.sun.star.chart2.data.LabeledDataSequence') - values = self._create_data(dp, rango, 'values-y') - lds.setValues(values) - if data.label: - label = self._create_data(dp, rango_label, '') - lds.setLabel(label) - - xlds = () - if is_x: - xlds = create_instance('com.sun.star.chart2.data.LabeledDataSequence') - values = self._create_data(dp, xrango, 'values-x') - xlds.setValues(values) - - if is_x: - serie.setData((lds, xlds)) - else: - serie.setData((lds,)) - - return serie - - def _create_data(self, dp, rango, role): - data = dp.createDataSequenceByRangeRepresentation(rango) - if not data is None: - data.Role = role - return data - - def _from_calc(self): - ps = self.cell.ps - ps.Width = self.width - ps.Height = self.height - charts = self.cell.charts - data = () - if self.data: - data = (self.data.address,) - charts.addNewByName(self.name, ps, data, True, True) - self.obj = charts.getByName(self.name) - chart = self.obj.getEmbeddedObject() - chart.setDiagram(chart.createInstance(self.BASE.format(self.type))) - if not self.data: - self._add_series_calc(chart) - return chart - - def _from_writer(self): - obj = self.doc.create_instance('com.sun.star.text.TextEmbeddedObject') - obj.setPropertyValue('CLSID', '12DCAE26-281F-416F-a234-c3086127382e') - obj.Name = self.name - obj.setSize(Size(self.width, self.height)) - self.doc.insert_content(self.cursor, obj) - self.obj = obj - chart = obj.getEmbeddedObject() - tipo = self.type - if self.type == 'Column': - tipo = 'Bar' - chart.Diagram.Vertical = True - chart.setDiagram(chart.createInstance(self.BASE.format(tipo))) - chart.DataSourceLabelsInFirstColumn = True - if isinstance(self.data, str): - self._add_series_writer(chart) - else: - chart_data = chart.getData() - labels = [r[0] for r in self.data] - data = [(r[1],) for r in self.data] - chart_data.setData(data) - chart_data.RowDescriptions = labels - - # ~ Bug - if tipo == 'Pie': - chart.setDiagram(chart.createInstance(self.BASE.format('Bar'))) - chart.setDiagram(chart.createInstance(self.BASE.format('Pie'))) - - return chart - - def insert(self): - if not self.cell is None: - chart = self._from_calc() - elif not self.cursor is None: - chart = self._from_writer() - - diagram = chart.Diagram - - if self.type == 'Bar': - diagram.Vertical = True - - if hasattr(self.title, 'String'): - chart.HasMainTitle = True - self.title(chart.Title) - - if hasattr(self.subtitle, 'String'): - chart.HasSubTitle = True - self.subtitle(chart.SubTitle) - - if self.legend.__dict__: - chart.HasLegend = True - self.legend(chart.Legend) - - if self.xaxistitle.__dict__: - diagram.HasXAxisTitle = True - self.xaxistitle(diagram.XAxisTitle) - - if self.yaxistitle.__dict__: - diagram.HasYAxisTitle = True - self.yaxistitle(diagram.YAxisTitle) - - if self.dim3d: - diagram.Dim3D = True - - if self.series: - data_series = chart.getFirstDiagram( - ).getCoordinateSystems( - )[0].getChartTypes()[0].DataSeries - for i, serie in enumerate(data_series): - for k, v in self.series[i].items(): - if hasattr(serie, k): - setattr(serie, k, v) - return self - - -def _set_column_model(columns): - #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html - column_model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) - for column in columns: - grid_column = create_instance('com.sun.star.awt.grid.GridColumn', True) - for k, v in column.items(): - setattr(grid_column, k, v) - column_model.addColumn(grid_column) - return column_model - - -def _set_image_url(image, id_extension=''): - if exists_path(image): - return _path_url(image) - - if not id_extension: - return '' - - path = get_path_extension(id_extension) - path = join(path, DIR['images'], image) - return _path_url(path) +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, **properties): - self._obj = self._create(properties) - self._init_values() - - def _init_values(self): - self._model = self._obj.Model - self._init_controls() + def __init__(self, args): + self._obj = self._create(args) + self._model = self.obj.Model self._events = None - self._color_on_focus = -1 - self._id_extension = '' - self._images = 'images' - return + self._modal = True + self._controls = {} + self._color_on_focus = COLOR_ON_FOCUS + self._id = '' + self._path = '' + self._init_controls() - def _create(self, properties): - path = properties.pop('Path', '') + def _create(self, args): + service = 'com.sun.star.awt.DialogProvider' + path = args.pop('Path', '') if path: - dp = create_instance('com.sun.star.awt.DialogProvider', True) - return dp.createDialog(_path_url(path)) + dp = create_instance(service, True) + dlg = dp.createDialog(_P.to_url(path)) + return dlg - if 'Location' in properties: - location = properties.get('Location', 'application') - library = properties.get('Library', 'Standard') + if 'Location' in args: + name = args['Name'] + library = args.get('Library', 'Standard') + location = args.get('Location', 'application').lower() if location == 'user': location = 'application' - dp = create_instance('com.sun.star.awt.DialogProvider', True) - path = 'vnd.sun.star.script:{}.{}?location={}'.format( - library, properties['Name'], location) + url = f'vnd.sun.star.script:{library}.{name}?location={location}' if location == 'document': - uid = get_document().uid - path = 'vnd.sun.star.tdoc:/{}/Dialogs/{}/{}.xml'.format( - uid, library, properties['Name']) - return dp.createDialog(path) + 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, properties) + _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 = { - 'stardiv.Toolkit.UnoFixedTextControl': 'label', - 'stardiv.Toolkit.UnoFixedHyperlinkControl': 'link', - 'stardiv.Toolkit.UnoEditControl': 'text', - 'stardiv.Toolkit.UnoButtonControl': 'button', - 'stardiv.Toolkit.UnoListBoxControl': 'listbox', - 'stardiv.Toolkit.UnoRoadmapControl': 'roadmap', - 'stardiv.Toolkit.UnoMultiPageControl': 'pages', + 'UnoFixedTextControl': 'label', + 'UnoEditControl': 'text', + 'UnoButtonControl': 'button', } return types[name] @@ -3860,7 +5114,7 @@ class LODialog(object): for control in self.obj.getControls(): tipo = self._get_type_control(control.ImplementationName) name = control.Model.Name - control = get_custom_class(tipo, control) + control = UNO_CLASSES[tipo](control) setattr(self, name, control) return @@ -3873,20 +5127,19 @@ class LODialog(object): return self._model @property - def id_extension(self): - return self._id_extension - @id_extension.setter - def id_extension(self, value): - global ID_EXTENSION - ID_EXTENSION = value - self._id_extension = value + def controls(self): + return self._controls @property - def images(self): - return self._images - @images.setter - def images(self, value): - self._images = value + 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): @@ -3903,13 +5156,11 @@ class LODialog(object): self.model.Width = value @property - def color_on_focus(self): - return self._color_on_focus - @color_on_focus.setter - def color_on_focus(self, value): - global COLOR_ON_FOCUS - COLOR_ON_FOCUS = get_color(value) - self._color_on_focus = COLOR_ON_FOCUS + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value @property def step(self): @@ -3923,112 +5174,101 @@ class LODialog(object): return self._events @events.setter def events(self, controllers): - self._events = 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.getControls(): - add_listeners(self._events, control, control.Model.Name) + for control in self.obj.Controls: + _add_listeners(self.events, control, control.Model.Name) return - def open(self): - return self.obj.execute() - - def close(self, value=0): - return self.obj.endDialog(value) - - def _get_control_model(self, control): - services = { - 'label': 'com.sun.star.awt.UnoControlFixedTextModel', - 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', - 'text': 'com.sun.star.awt.UnoControlEditModel', - 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', - 'button': 'com.sun.star.awt.UnoControlButtonModel', - 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', - 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', - 'tree': 'com.sun.star.awt.tree.TreeControlModel', - 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', - 'image': 'com.sun.star.awt.UnoControlImageControlModel', - 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', - 'pages': 'com.sun.star.awt.UnoMultiPageModel', - } - return services[control] - - def _set_column_model(self, columns): - #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html - column_model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) - for column in columns: - grid_column = create_instance('com.sun.star.awt.grid.GridColumn', True) - for k, v in column.items(): - setattr(grid_column, k, v) - column_model.addColumn(grid_column) - return column_model - def _set_image_url(self, image): - if exists_path(image): - return _path_url(image) + if _P.exists(image): + return _P.to_url(image) - if not self.id_extension: - return '' + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) - path = get_path_extension(self.id_extension) - path = join(path, self.images, image) - return _path_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 - def _special_properties(self, tipo, properties): - columns = properties.pop('Columns', ()) if tipo == 'grid': - properties['ColumnModel'] = self._set_column_model(columns) - elif tipo == 'button' and 'ImageURL' in properties: - properties['ImageURL'] = self._set_image_url(properties['ImageURL']) - elif tipo == 'roadmap': - if not 'Height' in properties: - properties['Height'] = self.height - if 'Title' in properties: - properties['Text'] = properties.pop('Title') - elif tipo == 'tab': - if not 'Width' in properties: - properties['Width'] = self.width - if not 'Height' in properties: - properties['Height'] = self.height + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args - return properties + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) - def add_control(self, properties): - tipo = properties.pop('Type').lower() - root = properties.pop('Root', '') - sheets = properties.pop('Sheets', ()) + return args - properties = self._special_properties(tipo, properties) - model = self.model.createInstance(self._get_control_model(tipo)) - set_properties(model, properties) - name = properties['Name'] + 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 = get_custom_class(tipo, control) + _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 + 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 = SEPARATION * -1 + wt = self.SEPARATION * -1 for c in control: - wt += c.width + SEPARATION + wt += c.width + self.SEPARATION x = w / 2 - wt / 2 for c in control: c.x = x - x = c.x + c.width + SEPARATION + x = c.x + c.width + self.SEPARATION return if x < 0: @@ -4043,27 +5283,302 @@ class LODialog(object): 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 = b""" + 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, **kwargs): + def __init__(self, args): self._events = None self._menu = None self._container = None - self._id_extension = '' - self._obj = self._create(kwargs) - - @property - def id_extension(self): - return self._id_extension - @id_extension.setter - def id_extension(self, value): - global ID_EXTENSION - ID_EXTENSION = value - self._id_extension = value + self._model = None + self._id = '' + self._path = '' + self._obj = self._create(args) def _create(self, properties): ps = ( @@ -4095,12 +5610,11 @@ class LOWindow(object): return def _create_container(self, ps): - # ~ toolkit = self._window.getToolkit() 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) + model.BackgroundColor = get_color((225, 225, 225)) self._container.setModel(model) self._container.createPeer(self._toolkit, self._window) self._container.setPosSize(*ps, POSSIZE) @@ -4110,86 +5624,17 @@ class LOWindow(object): def _create_subcontainer(self, ps): service = 'com.sun.star.awt.ContainerWindowProvider' cwp = create_instance(service, True) - with get_temp_file() as f: - f.write(self.EMPTY) - f.flush() - subcont = cwp.createContainerWindow( - _path_url(f.name), '', self._container.getPeer(), None) - # ~ service = 'com.sun.star.awt.UnoControlDialog' - # ~ subcont2 = create_instance(service, True) - # ~ service = 'com.sun.star.awt.UnoControlDialogModel' - # ~ model = create_instance(service, True) - # ~ service = 'com.sun.star.awt.UnoControlContainer' - # ~ context = create_instance(service, True) - # ~ subcont2.setModel(model) - # ~ subcont2.setContext(context) - # ~ subcont2.createPeer(self._toolkit, self._container.getPeer()) + 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 - return - - def _get_base_control(self, tipo): - services = { - 'label': 'com.sun.star.awt.UnoControlFixedText', - 'button': 'com.sun.star.awt.UnoControlButton', - 'text': 'com.sun.star.awt.UnoControlEdit', - 'listbox': 'com.sun.star.awt.UnoControlListBox', - 'link': 'com.sun.star.awt.UnoControlFixedHyperlink', - 'roadmap': 'com.sun.star.awt.UnoControlRoadmap', - 'image': 'com.sun.star.awt.UnoControlImageControl', - 'groupbox': 'com.sun.star.awt.UnoControlGroupBox', - 'radio': 'com.sun.star.awt.UnoControlRadioButton', - 'tree': 'com.sun.star.awt.tree.TreeControl', - 'grid': 'com.sun.star.awt.grid.UnoControlGrid', - 'tab': 'com.sun.star.awt.tab.UnoControlTabPage', - } - return services[tipo] - - def _special_properties(self, tipo, properties): - columns = properties.pop('Columns', ()) - if tipo == 'grid': - properties['ColumnModel'] = self._set_column_model(columns) - elif tipo == 'button' and 'ImageURL' in properties: - properties['ImageURL'] = _set_image_url( - properties['ImageURL'], self.id_extension) - elif tipo == 'roadmap': - if not 'Height' in properties: - properties['Height'] = self.height - if 'Title' in properties: - properties['Text'] = properties.pop('Title') - elif tipo == 'tab': - if not 'Width' in properties: - properties['Width'] = self.width - 20 - if not 'Height' in properties: - properties['Height'] = self.height - 20 - - return properties - - def add_control(self, properties): - tipo = properties.pop('Type').lower() - root = properties.pop('Root', '') - sheets = properties.pop('Sheets', ()) - - properties = self._special_properties(tipo, properties) - model = self._subcont.Model.createInstance(get_control_model(tipo)) - set_properties(model, properties) - name = properties['Name'] - self._subcont.Model.insertByName(name, model) - control = self._subcont.getControl(name) - add_listeners(self.events, control, name) - control = get_custom_class(tipo, control) - - if tipo == 'tree' and root: - control.root = root - elif tipo == 'tab' and sheets: - control.sheets = sheets - control.events = self.events - - setattr(self, name, control) + self._model = subcont.Model return def _create_popupmenu(self, menus): @@ -4225,31 +5670,94 @@ class LOWindow(object): self._window.setMenuBar(self._menu) return - def add_menu(self, menus): - self._create_menu(menus) - 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)) + # ~ self._container.addKeyListener(EventsKeyWindow(self)) return - @property - def name(self): - return self._title.lower().replace(' ', '_') + 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, value): - self._events = value + 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 @@ -4258,6 +5766,14 @@ class LOWindow(object): 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 @@ -4269,264 +5785,478 @@ class LOWindow(object): return -# ~ Python >= 3.7 -# ~ def __getattr__(name): +def create_window(args): + return LOWindow(args) -def _get_class_doc(obj): - classes = { - 'calc': LOCalc, - 'writer': LOWriter, - 'base': LOBase, - 'impress': LOImpress, - 'draw': LODraw, - 'math': LOMath, - 'basic': LOBasicIde, - } - type_doc = get_type_doc(obj) - return classes[type_doc](obj) +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 -# ~ Export ok -def get_document(title=''): - doc = None - desktop = get_desktop() - if not title: - doc = _get_class_doc(desktop.getCurrentComponent()) - return doc +class ClipBoard(object): + SERVICE = 'com.sun.star.datatransfer.clipboard.SystemClipboard' + CLIPBOARD_FORMAT_TEXT = 'text/plain;charset=utf-16' - for d in desktop.getComponents(): - if hasattr(d, 'Title') and d.Title == title: - doc = d - break + class TextTransferable(unohelper.Base, XTransferable): - if doc is None: + 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 - return _get_class_doc(doc) + @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 -def get_documents(custom=True): - docs = [] - desktop = get_desktop() - for doc in desktop.getComponents(): - if custom: - docs.append(_get_class_doc(doc)) +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: - docs.append(doc) - return docs + path = sys.executable + return path + @classmethod + def dir_tmp(self, only_name=False): + dt = tempfile.TemporaryDirectory() + if only_name: + dt = dt.name + return dt -def get_selection(): - return get_document().selection + @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 -def get_cell(*args): - if args: - index = args - if len(index) == 1: - index = args[0] - cell = get_document().get_cell(index) - else: - cell = get_selection().first - return cell + @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]) -def active_cell(): - return get_cell() + 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) -def active_sheet(): - return get_document().active + 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) -def create_dialog(properties): - return LODialog(**properties) + 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]) -def create_window(kwargs): - return LOWindow(**kwargs) + 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 -# ~ Export ok -def get_config_path(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 _path_system(getattr(path, name)) + @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)) -def get_path_python(): - return sys.executable - - # ~ path = get_config_path('Module') - # ~ if IS_MAC: - # ~ path = join(path, '..', 'Resources', PYTHON) - # ~ else: - # ~ path = join(path, PYTHON) - - # ~ cmd = '"{}" -V'.format(path) - # ~ if run(cmd, True): - # ~ return path - - # ~ path = PYTHON - # ~ cmd = '"{}" -V'.format(path) - # ~ result = run(cmd, True) - - # ~ if 'Python 3' in result: - # ~ return path - - # ~ path = PYTHON + '3' - # ~ cmd = '"{}" -V'.format(path) - # ~ result = run(cmd, True) - - # ~ if 'Python 3' in result: - # ~ return path - - # ~ return '' - - -# ~ Export ok -def get_file(init_dir='', multiple=False, filters=()): - """ - init_folder: folder default open - multiple: True for multiple selected - filters: Example - ( - ('XML', '*.xml'), - ('TXT', '*.txt'), - ) - """ - if not init_dir: - init_dir = get_config_path() - init_dir = _path_url(init_dir) - file_picker = create_instance('com.sun.star.ui.dialogs.FilePicker') - file_picker.setTitle(_('Select file')) - file_picker.setDisplayDirectory(init_dir) - file_picker.setMultiSelectionMode(multiple) - - path = '' - if filters: - file_picker.setCurrentFilter(filters[0][0]) - for f in filters: - file_picker.appendFilter(f[0], f[1]) - - if file_picker.execute(): - path = _path_system(file_picker.getSelectedFiles()[0]) - if multiple: - path = [_path_system(f) for f in file_picker.getSelectedFiles()] - - return path - - -# ~ Export ok -def get_path(init_dir='', filters=()): - """ - Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html - filters: Example - ( - ('XML', '*.xml'), - ('TXT', '*.txt'), - ) - """ - if not init_dir: - init_dir = get_config_path() - init_dir = _path_url(init_dir) - file_picker = create_instance('com.sun.star.ui.dialogs.FilePicker') - file_picker.setTitle(_('Select file')) - file_picker.setDisplayDirectory(init_dir) - file_picker.initialize((2,)) - if filters: - file_picker.setCurrentFilter(filters[0][0]) - for f in filters: - file_picker.appendFilter(f[0], f[1]) - - path = '' - if file_picker.execute(): - path = _path_system(file_picker.getSelectedFiles()[0]) - return path - - -# ~ Export ok -def get_dir(init_dir=''): - folder_picker = create_instance('com.sun.star.ui.dialogs.FolderPicker') - if not init_dir: - init_dir = get_config_path() - init_dir = _path_url(init_dir) - folder_picker.setDisplayDirectory(init_dir) - - path = '' - if folder_picker.execute(): - path = _path_system(folder_picker.getDirectory()) - return path - - -# ~ Export ok -def get_info_path(path): - path, filename = os.path.split(path) - name, extension = os.path.splitext(filename) - return (path, filename, name, extension) - - -# ~ Export ok -def read_file(path, mode='r', array=False): - data = '' - with open(path, mode) as f: - if array: - data = tuple(f.read().splitlines()) + @classmethod + def open(cls, path): + if IS_WIN: + os.startfile(path) else: - data = f.read() - return data + 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 -# ~ Export ok -def save_file(path, mode='w', data=None): - with open(path, mode) as f: - f.write(data) - return +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}'") -# ~ Export ok -def to_json(path, data): - with open(path, 'w') as f: - f.write(json.dumps(data, indent=4, sort_keys=True)) - return +def create_dialog(args): + return LODialog(args) -# ~ Export ok -def from_json(path): - with open(path) as f: - data = json.loads(f.read()) - return data - - -# ~ Export ok -def json_dumps(data): - return json.dumps(data, indent=4, sort_keys=True) - - -# ~ Export ok -def json_loads(data): - return json.loads(data) - - -def get_path_extension(id): - path = '' - pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') - try: - path = _path_system(pip.getPackageLocation(id)) - except Exception as e: - error(e) - return path - - -def get_home(): - return Path.home() - - -# ~ Export ok def inputbox(message, default='', title=TITLE, echochar=''): class ControllersInput(object): @@ -4543,8 +6273,8 @@ def inputbox(message, default='', title=TITLE, echochar=''): 'Width': 200, 'Height': 80, } - dlg = LODialog(**args) - dlg.events = ControllersInput(dlg) + dlg = LODialog(args) + dlg.events = ControllersInput args = { 'Type': 'Label', @@ -4600,545 +6330,56 @@ def inputbox(message, default='', title=TITLE, echochar=''): return '' -# ~ Export ok -def new_doc(type_doc=CALC, **kwargs): - path = 'private:factory/s{}'.format(type_doc) - opt = dict_to_property(kwargs) - doc = get_desktop().loadComponentFromURL(path, '_default', 0, opt) - return _get_class_doc(doc) +def get_fonts(): + toolkit = create_instance('com.sun.star.awt.Toolkit') + device = toolkit.createScreenCompatibleDevice(0, 0) + return device.FontDescriptors -# ~ Export ok -def new_db(path, name=''): - p, fn, n, e = get_info_path(path) - if not name: - name = n - return LOBase(name, path) +# ~ 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) -# ~ Todo -def exists_db(name): - dbc = create_instance('com.sun.star.sdb.DatabaseContext') - return dbc.hasRegisteredDatabase(name) + 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] -# ~ Todo -def register_db(name, path): - dbc = create_instance('com.sun.star.sdb.DatabaseContext') - dbc.registerDatabaseLocation(name, _path_url(path)) - return + def __delitem__(self, key): + del self._store[key.lower()] + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) -# ~ Todo -def get_db(name): - return LOBase(name) + 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 -# ~ Export ok -def open_doc(path, **kwargs): - """ 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 + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) - http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1frame_1_1XComponentLoader.html - http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1document_1_1MediaDescriptor.html - """ - path = _path_url(path) - opt = dict_to_property(kwargs) - doc = get_desktop().loadComponentFromURL(path, '_default', 0, opt) - if doc is None: - return + def __repr__(self): + return str(dict(self.items())) - return _get_class_doc(doc) - -# ~ Export ok -def open_file(path): - if IS_WIN: - os.startfile(path) - else: - pid = subprocess.Popen(['xdg-open', path]).pid - return - - -# ~ Export ok -def join(*paths): - return os.path.join(*paths) - - -# ~ Export ok -def is_dir(path): - return Path(path).is_dir() - - -# ~ Export ok -def is_file(path): - return Path(path).is_file() - - -# ~ Export ok -def get_file_size(path): - return Path(path).stat().st_size - - -# ~ Export ok -def is_created(path): - return is_file(path) and bool(get_file_size(path)) - - -# ~ Export ok -def replace_ext(path, extension): - path, _, name, _ = get_info_path(path) - return '{}/{}.{}'.format(path, name, extension) - - -# ~ Export ok -def zip_content(path): - with zipfile.ZipFile(path) as z: - names = z.namelist() - return names - - -def popen(command, stdin=None): - try: - proc = subprocess.Popen(shlex.split(command), shell=IS_WIN, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - for line in proc.stdout: - yield line.decode().rstrip() - except Exception as e: - error(e) - yield (e.errno, e.strerror) - - -def url_open(url, options={}, verify=True, json=False): - data = '' - err = '' - req = Request(url) - try: - if verify: - response = urlopen(req) - else: - context = ssl._create_unverified_context() - response = urlopen(req, context=context) - except HTTPError as e: - error(e) - err = str(e) - except URLError as e: - error(e.reason) - err = str(e.reason) - else: - if json: - data = json_loads(response.read()) - else: - data = response.read() - - return data, err - - -def run(command, wait=False): - try: - if wait: - result = subprocess.check_output(command, shell=True) - else: - p = subprocess.Popen(shlex.split(command), stdin=None, - stdout=None, stderr=None, close_fds=True) - result, er = p.communicate() - except subprocess.CalledProcessError as e: - msg = ("%s\nrun [ERROR]: output = %s, error code = %s\n" - % (command, e.output, e.returncode)) - error(msg) - return False - - if result is None: - return True - - return result.decode() - - -def _zippwd(source, target, pwd): - if IS_WIN: - return False - if not exists_app('zip'): - return False - - cmd = 'zip' - opt = '-j ' - args = "{} --password {} ".format(cmd, pwd) - - if isinstance(source, (tuple, list)): - if not target: - return False - args += opt + target + ' ' + ' '.join(source) - else: - if is_file(source) and not target: - target = replace_ext(source, 'zip') - elif is_dir(source) and not target: - target = join(PurePath(source).parent, - '{}.zip'.format(PurePath(source).name)) - opt = '-r ' - args += opt + target + ' ' + source - - result = run(args, True) - if not result: - return False - - return is_created(target) - - -# ~ Export ok -def zip_files(source, target='', mode='w', pwd=''): - if pwd: - return _zippwd(source, target, pwd) - - if isinstance(source, (tuple, list)): - if not target: - return False - - with zipfile.ZipFile(target, mode, compression=zipfile.ZIP_DEFLATED) as z: - for path in source: - _, name, _, _ = get_info_path(path) - z.write(path, name) - - return is_created(target) - - if is_file(source): - if not target: - target = replace_ext(source, 'zip') - z = zipfile.ZipFile(target, mode, compression=zipfile.ZIP_DEFLATED) - _, name, _, _ = get_info_path(source) - z.write(source, name) - z.close() - return is_created(target) - - if not target: - target = join( - PurePath(source).parent, - '{}.zip'.format(PurePath(source).name)) - z = zipfile.ZipFile(target, mode, compression=zipfile.ZIP_DEFLATED) - root_len = len(os.path.abspath(source)) - for root, dirs, files in os.walk(source): - relative = os.path.abspath(root)[root_len:] - for f in files: - fullpath = join(root, f) - file_name = join(relative, f) - z.write(fullpath, file_name) - z.close() - - return is_created(target) - - -# ~ Export ok -def unzip(source, path='', members=None, pwd=None): - if not path: - path, _, _, _ = get_info_path(source) - 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 - - -# ~ Export ok -def merge_zip(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 - - -# ~ Export ok -def kill(path): - p = Path(path) - try: - if p.is_file(): - p.unlink() - elif p.is_dir(): - shutil.rmtree(path) - except OSError as e: - log.error(e) - return - - -def get_size_screen(): - if IS_WIN: - user32 = ctypes.windll.user32 - res = '{}x{}'.format(user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)) - else: - args = 'xrandr | grep "*" | cut -d " " -f4' - res = run(args, True) - return res.strip() - - -def get_clipboard(): - df = None - text = '' - sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') - transferable = sc.getContents() - data = transferable.getTransferDataFlavors() - for df in data: - if df.MimeType == CLIPBOARD_FORMAT_TEXT: - break - if df: - text = transferable.getTransferData(df) - return text - - -class TextTransferable(unohelper.Base, XTransferable): - """Keep clipboard data and provide them.""" - - def __init__(self, text): - df = DataFlavor() - df.MimeType = CLIPBOARD_FORMAT_TEXT - df.HumanPresentableName = "encoded text utf-16" - self.flavors = [df] - self.data = [text] - - def getTransferData(self, flavor): - if not flavor: - return - for i, f in enumerate(self.flavors): - if flavor.MimeType == f.MimeType: - return self.data[i] - return - - def getTransferDataFlavors(self): - return tuple(self.flavors) - - def isDataFlavorSupported(self, flavor): - if not flavor: - return False - mtype = flavor.MimeType - for f in self.flavors: - if mtype == f.MimeType: - return True - return False - - -# ~ Export ok -def set_clipboard(value): - ts = TextTransferable(value) - sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') - sc.setContents(ts, None) - return - - -# ~ Export ok -def copy(doc): - call_dispatch(doc, '.uno:Copy') - return - - -def paste(doc): - call_dispatch(doc, '.uno:Paste') - return - - -# ~ Export ok -def get_epoch(): - n = now() - return int(time.mktime(n.timetuple())) - - -# ~ Export ok -def file_copy(source, target='', name=''): - p, f, n, e = get_info_path(source) - if target: - p = target - if name: - e = '' - n = name - path_new = join(p, '{}{}'.format(n, e)) - shutil.copy(source, path_new) - return path_new - - -def get_path_content(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 += [join(folder, f) for f in files if pattern.search(f)] - else: - paths += files - return paths - - -def _get_menu(type_doc, name_menu): - instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' - service = TYPE_DOC[type_doc] - manager = create_instance(instance, True) - ui = manager.getUIConfigurationManager(service) - menus = ui.getSettings(NODE_MENUBAR, True) - command = MENUS_APP[type_doc][name_menu] - for menu in menus: - data = property_to_dict(menu) - if data.get('CommandURL', '') == command: - idc = data.get('ItemDescriptorContainer', None) - return ui, menus, idc - return None, None, None - - -def _get_index_menu(menu, command): - for i, m in enumerate(menu): - data = property_to_dict(m) - cmd = data.get('CommandURL', '') - if cmd == command: - return i - # ~ submenu = data.get('ItemDescriptorContainer', None) - # ~ if not submenu is None: - # ~ get_index_menu(submenu, command, count + 1) - return 0 - - -def _store_menu(ui, menus, menu, index, data=(), remove=False): - if remove: - uno.invoke(menu, 'removeByIndex', (index,)) - else: - properties = dict_to_property(data, True) - uno.invoke(menu, 'insertByIndex', (index + 1, properties)) - ui.replaceSettings(NODE_MENUBAR, menus) - ui.store() - return - - -def insert_menu(type_doc, name_menu, **kwargs): - ui, menus, menu = _get_menu(type_doc, name_menu.lower()) - if menu is None: - return 0 - - label = kwargs.get('Label', '-') - separator = False - if label == '-': - separator = True - command = kwargs.get('CommandURL', '') - index = kwargs.get('Index', 0) - if not index: - index = _get_index_menu(menu, kwargs['After']) - if separator: - data = {'Type': 1} - _store_menu(ui, menus, menu, index, data) - return index + 1 - - index_menu = _get_index_menu(menu, command) - if index_menu: - msg = 'Exists: %s' % command - debug(msg) - return 0 - - sub_menu = kwargs.get('Submenu', ()) - idc = None - if sub_menu: - idc = ui.createSettings() - - data = { - 'CommandURL': command, - 'Label': label, - 'Style': 0, - 'Type': 0, - 'ItemDescriptorContainer': idc - } - _store_menu(ui, menus, menu, index, data) - if sub_menu: - _add_sub_menus(ui, menus, idc, sub_menu) - return True - - -def _add_sub_menus(ui, menus, menu, sub_menu): - for i, sm in enumerate(sub_menu): - submenu = sm.pop('Submenu', ()) - sm['Type'] = 0 - if submenu: - idc = ui.createSettings() - sm['ItemDescriptorContainer'] = idc - if sm['Label'] == '-': - sm = {'Type': 1} - _store_menu(ui, menus, menu, i - 1, sm) - if submenu: - _add_sub_menus(ui, menus, idc, submenu) - return - - -def remove_menu(type_doc, name_menu, command): - ui, menus, menu = _get_menu(type_doc, name_menu.lower()) - if menu is None: - return False - - index = _get_index_menu(menu, command) - if not index: - debug('Not exists: %s' % command) - return False - - _store_menu(ui, menus, menu, index, remove=True) - return True - - -def _get_app_submenus(menus, count=0): - for i, menu in enumerate(menus): - data = property_to_dict(menu) - cmd = data.get('CommandURL', '') - msg = ' ' * count + '├─' + cmd - debug(msg) - submenu = data.get('ItemDescriptorContainer', None) - if not submenu is None: - _get_app_submenus(submenu, count + 1) - return - - -def get_app_menus(name_app, index=-1): - instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' - service = TYPE_DOC[name_app] - manager = create_instance(instance, True) - ui = manager.getUIConfigurationManager(service) - menus = ui.getSettings(NODE_MENUBAR, True) - if index == -1: - for menu in menus: - data = property_to_dict(menu) - debug(data.get('CommandURL', '')) - else: - menus = property_to_dict(menus[index])['ItemDescriptorContainer'] - _get_app_submenus(menus) - return menus - - -# ~ Export ok -def start(): - global _start - _start = now() - log.info(_start) - return - - -# ~ Export ok -def end(): - global _start - e = now() - return str(e - _start).split('.')[0] - - -# ~ Export ok # ~ https://en.wikipedia.org/wiki/Web_colors -def get_color(*value): - if len(value) == 1 and isinstance(value[0], int): - return value[0] - if len(value) == 1 and isinstance(value[0], tuple): - value = value[0] - +def get_color(value): COLORS = { 'aliceblue': 15792383, 'antiquewhite': 16444375, @@ -5289,10 +6530,9 @@ def get_color(*value): 'yellowgreen': 10145074, } - if len(value) == 3: + if isinstance(value, tuple): color = (value[0] << 16) + (value[1] << 8) + value[2] else: - value = value[0] if value[0] == '#': r, g, b = bytes.fromhex(value[1:]) color = (r << 16) + (g << 8) + b @@ -5304,359 +6544,15 @@ def get_color(*value): COLOR_ON_FOCUS = get_color('LightYellow') -# ~ Export ok -def render(template, data): - s = Template(template) - return s.safe_substitute(**data) - - -def _to_date(value): - new_value = value - if isinstance(value, Time): - new_value = datetime.time(value.Hours, value.Minutes, value.Seconds) - elif isinstance(value, Date): - new_value = datetime.date(value.Year, value.Month, value.Day) - elif isinstance(value, DateTime): - new_value = datetime.datetime( - value.Year, value.Month, value.Day, - value.Hours, value.Minutes, value.Seconds) - return new_value - - -def date_to_struct(value): - # ~ print(type(value)) - if isinstance(value, datetime.datetime): - d = DateTime() - d.Seconds = value.second - d.Minutes = value.minute - d.Hours = value.hour - d.Day = value.day - d.Month = value.month - d.Year = value.year - elif isinstance(value, datetime.date): - d = Date() - d.Day = value.day - d.Month = value.month - d.Year = value.year - return d - - -# ~ Export ok -def format(template, data): - """ - https://pyformat.info/ - """ - if isinstance(data, (str, int, float)): - # ~ print(template.format(data)) - return template.format(data) - - if isinstance(data, (Time, Date, DateTime)): - return template.format(_to_date(data)) - - if isinstance(data, tuple) and isinstance(data[0], tuple): - data = {r[0]: _to_date(r[1]) for r in data} - return template.format(**data) - - data = [_to_date(v) for v in data] - result = template.format(*data) - return result - - -def _get_url_script(macro): - macro['language'] = macro.get('language', 'Python') - macro['location'] = macro.get('location', 'user') - data = macro.copy() - if data['language'] == 'Python': - data['module'] = '.py$' - elif data['language'] == 'Basic': - data['module'] = '.{}.'.format(macro['module']) - if macro['location'] == 'user': - data['location'] = 'application' - else: - data['module'] = '.' - - url = 'vnd.sun.star.script:{library}{module}{name}?language={language}&location={location}' - path = url.format(**data) - return path - - -def _call_macro(macro): - #~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification - name = 'com.sun.star.script.provider.MasterScriptProviderFactory' - factory = create_instance(name, False) - - macro['language'] = macro.get('language', 'Python') - macro['location'] = macro.get('location', 'user') - data = macro.copy() - if data['language'] == 'Python': - data['module'] = '.py$' - elif data['language'] == 'Basic': - data['module'] = '.{}.'.format(macro['module']) - if macro['location'] == 'user': - data['location'] = 'application' - else: - data['module'] = '.' - - args = macro.get('args', ()) - url = 'vnd.sun.star.script:{library}{module}{name}?language={language}&location={location}' - path = url.format(**data) - - script = factory.createScriptProvider('').getScript(path) - return script.invoke(args, None, None)[0] - - -# ~ Export ok -def call_macro(macro): - in_thread = macro.pop('thread') - if in_thread: - t = threading.Thread(target=_call_macro, args=(macro,)) - t.start() - return - - return _call_macro(macro) - - -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 - - -# ~ Export ok -def timer(name, seconds, macro): - global _stop_thread - _stop_thread[name] = threading.Event() - thread = TimerThread(_stop_thread[name], seconds, macro) - thread.start() - return - - -# ~ Export ok -def stop_timer(name): - global _stop_thread - _stop_thread[name].set() - del _stop_thread[name] - return - - -def _get_key(password): - digest = hashlib.sha256(password.encode()).digest() - key = base64.urlsafe_b64encode(digest) - return key - - -# ~ Export ok -def encrypt(data, password): - f = Fernet(_get_key(password)) - token = f.encrypt(data).decode() - return token - - -# ~ Export ok -def decrypt(token, password): - data = '' - f = Fernet(_get_key(password)) - try: - data = f.decrypt(token.encode()).decode() - except InvalidToken as e: - error('Invalid Token') - return data - - -class SmtpServer(object): - - def __init__(self, config): - self._server = None - self._error = '' - self._sender = '' - self._is_connect = self._login(config) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - self.close() - - @property - def is_connect(self): - return self._is_connect - - @property - def error(self): - return self._error - - def _login(self, config): - name = config['server'] - port = config['port'] - is_ssl = config['ssl'] - self._sender = config['user'] - hosts = ('gmail' in name or 'outlook' in name) - try: - if is_ssl and hosts: - self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) - self._server.ehlo() - self._server.starttls() - self._server.ehlo() - elif is_ssl: - self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT) - self._server.ehlo() - else: - self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) - - self._server.login(self._sender, config['pass']) - 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, _, _ = get_info_path(path) - part = MIMEBase('application', 'octet-stream') - part.set_payload(read_file(path, 'rb')) - encoders.encode_base64(part) - part.add_header('Content-Disposition', file_name.format(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 - - -def server_smtp_test(config): - with SmtpServer(config) as server: - if server.error: - error(server.error) - return server.error - - -def import_csv(path, **kwargs): - """ - See https://docs.python.org/3.5/library/csv.html#csv.reader - """ - with open(path) as f: - rows = tuple(csv.reader(f, **kwargs)) - return rows - - -def export_csv(path, data, **kwargs): - with open(path, 'w') as f: - writer = csv.writer(f, **kwargs) - writer.writerows(data) - return - - -def install_locales(path, domain='base', dir_locales=DIR['locales']): - p, *_ = get_info_path(path) - path_locales = join(p, 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 _ - - -class LIBOServer(object): +class LOServer(object): HOST = 'localhost' PORT = '8100' - ARG = 'socket,host={},port={};urp;StarOffice.ComponentContext'.format(HOST, PORT) + ARG = f'socket,host={HOST},port={PORT};urp;StarOffice.ComponentContext' CMD = ['soffice', '-env:SingleAppInstance=false', - '-env:UserInstallation=file:///tmp/LIBO_Process8100', + '-env:UserInstallation=file:///tmp/LO_Process8100', '--headless', '--norestore', '--invisible', - '--accept={}'.format(ARG)] + f'--accept={ARG}'] def __init__(self): self._server = None diff --git a/source/easymacro.py.bk b/source/easymacro.py.bk new file mode 100644 index 0000000..689270c --- /dev/null +++ b/source/easymacro.py.bk @@ -0,0 +1,5719 @@ +#!/usr/bin/env python3 + +# == Rapid Develop Macros in LibreOffice == + +# ~ This file is part of 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 ctypes +import datetime +import errno +import gettext +import getpass +import hashlib +import json +import logging +import os +import platform +import re +import shlex +import shutil +import socket +import subprocess +import ssl +import sys +import tempfile +import threading +import time +import traceback +import zipfile + +from functools import wraps +from pathlib import Path, PurePath +from pprint import pprint +from urllib.request import Request, urlopen +from urllib.error import URLError, HTTPError +from string import Template +from subprocess import PIPE + +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.util import Time, Date, DateTime +from com.sun.star.beans import PropertyValue, NamedValue +from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS +from com.sun.star.awt.MessageBoxResults import YES +from com.sun.star.awt.PosSize import POSSIZE, SIZE +from com.sun.star.awt import Size, Point +from com.sun.star.awt import Rectangle +from com.sun.star.awt import KeyEvent +from com.sun.star.awt.KeyFunction import QUIT +from com.sun.star.datatransfer import XTransferable, DataFlavor +from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA + +from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK +from com.sun.star.text.TextContentAnchorType import AS_CHARACTER + +from com.sun.star.script import ScriptEventDescriptor +from com.sun.star.lang import XEventListener +from com.sun.star.awt import XActionListener +from com.sun.star.awt import XMouseListener +from com.sun.star.awt import XMouseMotionListener +from com.sun.star.util import XModifyListener +from com.sun.star.awt import XTopWindowListener +from com.sun.star.awt import XWindowListener +from com.sun.star.awt import XMenuListener +from com.sun.star.awt import XKeyListener +from com.sun.star.awt import XItemListener +from com.sun.star.awt import XFocusListener +from com.sun.star.awt import XTabListener +from com.sun.star.awt.grid import XGridDataListener +from com.sun.star.awt.grid import XGridSelectionListener + + +try: + from fernet import Fernet, InvalidToken +except ImportError: + pass + + +ID_EXTENSION = '' + +DIR = { + 'images': 'images', + 'locales': 'locales', +} + +KEY = { + 'enter': 1280, +} + +SEPARATION = 5 + +MSG_LANG = { + 'es': { + 'OK': 'Aceptar', + 'Cancel': 'Cancelar', + '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', + } +} + +OS = platform.system() +USER = getpass.getuser() +PC = platform.node() +DESKTOP = os.environ.get('DESKTOP_SESSION', '') +INFO_DEBUG = '{}\n\n{}\n\n{}'.format(sys.version, platform.platform(), '\n'.join(sys.path)) + +IS_WIN = OS == 'Windows' +IS_MAC = OS == 'Darwin' + +LOG_NAME = 'ZAZ' +CLIPBOARD_FORMAT_TEXT = 'text/plain;charset=utf-16' + +PYTHON = 'python' +if IS_WIN: + PYTHON = 'python.exe' + +CALC = 'calc' +WRITER = 'writer' + +OBJ_CELL = 'ScCellObj' +OBJ_RANGE = 'ScCellRangeObj' +OBJ_RANGES = 'ScCellRangesObj' +OBJ_TYPE_RANGES = (OBJ_CELL, OBJ_RANGE, OBJ_RANGES) + +TEXT_RANGE = 'SwXTextRange' +TEXT_RANGES = 'SwXTextRanges' +TEXT_TYPE_RANGES = (TEXT_RANGE, TEXT_RANGES) + +TYPE_DOC = { + 'calc': 'com.sun.star.sheet.SpreadsheetDocument', + 'writer': 'com.sun.star.text.TextDocument', + 'impress': 'com.sun.star.presentation.PresentationDocument', + 'draw': 'com.sun.star.drawing.DrawingDocument', + 'base': 'com.sun.star.sdb.DocumentDataSource', + 'math': 'com.sun.star.formula.FormulaProperties', + 'basic': 'com.sun.star.script.BasicIDE', + 'main': 'com.sun.star.frame.StartModule', +} + +NODE_MENUBAR = 'private:resource/menubar/menubar' +MENUS_MAIN = { + 'file': '.uno:PickList', + 'tools': '.uno:ToolsMenu', + 'help': '.uno:HelpMenu', +} +MENUS_CALC = { + 'file': '.uno:PickList', + 'edit': '.uno:EditMenu', + 'view': '.uno:ViewMenu', + 'insert': '.uno:InsertMenu', + 'format': '.uno:FormatMenu', + 'styles': '.uno:FormatStylesMenu', + 'sheet': '.uno:SheetMenu', + 'data': '.uno:DataMenu', + 'tools': '.uno:ToolsMenu', + 'windows': '.uno:WindowList', + 'help': '.uno:HelpMenu', +} +MENUS_WRITER = { + 'file': '.uno:PickList', + 'edit': '.uno:EditMenu', + 'view': '.uno:ViewMenu', + 'insert': '.uno:InsertMenu', + 'format': '.uno:FormatMenu', + 'styles': '.uno:FormatStylesMenu', + 'sheet': '.uno:TableMenu', + 'data': '.uno:FormatFormMenu', + 'tools': '.uno:ToolsMenu', + 'windows': '.uno:WindowList', + 'help': '.uno:HelpMenu', +} +MENUS_APP = { + 'main': MENUS_MAIN, + 'calc': MENUS_CALC, + 'writer': MENUS_WRITER, +} + +EXT = { + 'pdf': 'pdf', +} + +FILE_NAME_DEBUG = 'debug.odt' +FILE_NAME_CONFIG = 'zaz-{}.json' +LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' +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__) + + +_start = 0 +_stop_thread = {} +TIMEOUT = 10 +SECONDS_DAY = 60 * 60 * 24 + + +CTX = uno.getComponentContext() +SM = CTX.getServiceManager() + + +def create_instance(name, with_context=False): + if with_context: + instance = SM.createInstanceWithContext(name, CTX) + 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 '' + + +# ~ FILTER_PDF = '/org.openoffice.Office.Common/Filter/PDF/Export/' +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') + +nd = '/org.openoffice.Office.Calc/Calculate/Other/Date' +d = get_app_config(nd, 'DD') +m = get_app_config(nd, 'MM') +y = get_app_config(nd, 'YY') +DATE_OFFSET = datetime.date(y, m, d).toordinal() + + +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 inspect(obj): + zaz = create_instance('net.elmau.zaz.inspect') + zaz.inspect(obj) + 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: + debug(traceback.format_exc()) + log.error(name, exc_info=True) + return func + + +class LogWin(object): + + def __init__(self, doc): + self.doc = doc + + def write(self, info): + text = self.doc.Text + cursor = text.createTextCursor() + cursor.gotoEnd(False) + text.insertString(cursor, str(info) + '\n\n', 0) + return + + +def info(data): + log.info(data) + return + + +def debug(*info): + if IS_WIN: + doc = get_document(FILE_NAME_DEBUG) + if doc is None: + return + doc = LogWin(doc.obj) + doc.write(str(info)) + return + + data = [str(d) for d in info] + log.debug('\t'.join(data)) + return + + +def error(info): + log.error(info) + return + + +def save_log(path, data): + with open(path, 'a') as out: + out.write('{} -{}- '.format(str(now())[:19], LOG_NAME)) + pprint(data, stream=out) + 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: + return now.time() + return now + + +def today(): + return datetime.date.today() + + +def get_date(year, month, day, hour=-1, minute=-1, second=-1): + if hour > -1 or minute > -1 or second > -1: + h = hour + m = minute + s = second + if h == -1: + h = 0 + if m == -1: + m = 0 + if s == -1: + s = 0 + d = datetime.datetime(year, month, day, h, m, s) + else: + d = datetime.date(year, month, day) + return d + + +def get_config(key='', default=None, prefix='config'): + path_json = FILE_NAME_CONFIG.format(prefix) + values = None + path = join(get_config_path('UserConfig'), path_json) + if not exists_path(path): + return default + + with open(path, 'r', encoding='utf-8') as fh: + data = fh.read() + values = json.loads(data) + + if key: + return values.get(key, default) + + return values + + +def set_config(key, value, prefix='config'): + path_json = FILE_NAME_CONFIG.format(prefix) + path = join(get_config_path('UserConfig'), path_json) + values = get_config(default={}, prefix=prefix) + values[key] = value + with open(path, 'w', encoding='utf-8') as fh: + json.dump(values, fh, ensure_ascii=False, sort_keys=True, indent=4) + return True + + +def sleep(seconds): + time.sleep(seconds) + return + + +def _(msg): + L = LANGUAGE.split('-')[0] + if L == 'en': + return msg + + if not L in MSG_LANG: + return msg + + return MSG_LANG[L][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() + mb = toolkit.createMessageBox(parent, type_msg, buttons, title, str(message)) + return mb.execute() + + +def question(message, title=TITLE): + res = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, 'querybox') + return res == 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_desktop(): + return create_instance('com.sun.star.frame.Desktop', True) + + +def get_dispatch(): + return create_instance('com.sun.star.frame.DispatchHelper') + + +def call_dispatch(doc=None, url='', args=()): + if doc is None: + frame = get_document().frame + else: + frame = doc.frame + dispatch = get_dispatch() + dispatch.executeDispatch(frame, url, '', 0, args) + return + + +def get_temp_file(only_name=False): + delete = True + if IS_WIN: + delete = False + tmp = tempfile.NamedTemporaryFile(delete=delete) + if only_name: + tmp = tmp.name + return tmp + +def _path_url(path): + if path.startswith('file://'): + return path + return uno.systemPathToFileUrl(path) + + +def _path_system(path): + if path.startswith('file://'): + return os.path.abspath(uno.fileUrlToSystemPath(path)) + return path + + +def exists_app(name): + try: + dn = subprocess.DEVNULL + subprocess.Popen([name, ''], stdout=dn, stderr=dn).terminate() + except OSError as e: + if e.errno == errno.ENOENT: + return False + return True + + +def exists_path(path): + return Path(path).exists() + + +def get_type_doc(obj): + for k, v in TYPE_DOC.items(): + if obj.supportsService(v): + return k + return '' + + +def dict_to_property(values, uno_any=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 dict_to_named(values): + ps = tuple([NamedValue(n, v) for n, v in values.items()]) + return ps + + +def property_to_dict(values): + d = {i.Name: i.Value for i in values} + return d + + +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 + + +# ~ Custom classes +class ObjectBase(object): + + def __init__(self, obj): + self._obj = obj + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __getitem__(self, index): + return self.obj[index] + + def __getattr__(self, name): + a = None + if name == 'obj': + a = super().__getattr__(name) + else: + if hasattr(self.obj, name): + a = getattr(self.obj, name) + return a + + @property + def obj(self): + return self._obj + @obj.setter + def obj(self, value): + self._obj = value + + +class LOObjectBase(object): + + def __init__(self, obj): + self.__dict__['_obj'] = obj + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + return True + + def __setattr__(self, name, value): + print('BASE__setattr__', name) + if name == '_obj': + super().__setattr__(name, value) + else: + self.obj.setPropertyValue(name, value) + + # ~ def _try_for_method(self, name): + # ~ a = None + # ~ m = 'get{}'.format(name) + # ~ if hasattr(self.obj, m): + # ~ a = getattr(self.obj, m)() + # ~ else: + # ~ a = getattr(self.obj, name) + # ~ return a + + def __getattr__(self, name): + print('BASE__getattr__', name) + if name == 'obj': + a = super().__getattr__(name) + else: + a = self.obj.getPropertyValue(name) + # ~ Bug + if a is None: + msg = 'Error get: {} - {}'.format(self.obj.ImplementationName, name) + error(msg) + raise Exception(msg) + return a + + @property + def obj(self): + return self._obj + + +class LODocument(object): + + def __init__(self, obj): + self._obj = obj + self._init_values() + + def _init_values(self): + self._type_doc = get_type_doc(self.obj) + self._cc = self.obj.getCurrentController() + return + + @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 uid(self): + return self.obj.RuntimeUID + + @property + def type(self): + return self._type_doc + + @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 _path_system(self.obj.getURL()) + + @property + def statusbar(self): + return self._cc.getStatusIndicator() + + @property + def visible(self): + w = self._cc.getFrame().getContainerWindow() + return w.isVisible() + @visible.setter + def visible(self, value): + w = self._cc.getFrame().getContainerWindow() + w.setVisible(value) + + @property + def zoom(self): + return self._cc.ZoomValue + @zoom.setter + def zoom(self, value): + self._cc.ZoomValue = value + + @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 save(self, path='', **kwargs): + # ~ opt = _properties(kwargs) + opt = dict_to_property(kwargs) + if path: + self._obj.storeAsURL(_path_url(path), opt) + else: + self._obj.store() + return True + + def close(self): + self.obj.close(True) + return + + def focus(self): + w = self._cc.getFrame().getComponentWindow() + w.setFocus() + return + + def paste(self): + sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') + transferable = sc.getContents() + self._cc.insertTransferable(transferable) + return self.obj.getCurrentSelection() + + def to_pdf(self, path, **kwargs): + path_pdf = path + if path: + if is_dir(path): + _, _, n, _ = get_info_path(self.path) + path_pdf = join(path, '{}.{}'.format(n, EXT['pdf'])) + else: + path_pdf = replace_ext(self.path, EXT['pdf']) + + filter_name = '{}_pdf_Export'.format(self.type) + filter_data = dict_to_property(kwargs, True) + args = { + 'FilterName': filter_name, + 'FilterData': filter_data, + } + args = dict_to_property(args) + try: + self.obj.storeToURL(_path_url(path_pdf), args) + except Exception as e: + error(e) + path_pdf = '' + + return path_pdf + + # ~ If location="document" Then + # ~ sp = ThisComponent.getScriptProvider() + + +class FormControlBase(object): + EVENTS = { + 'action': 'actionPerformed', + 'click': 'mousePressed', + } + TYPES = { + 'actionPerformed': 'XActionListener', + 'mousePressed': 'XMouseListener', + } + + def __init__(self, obj): + self._obj = obj + self._index = -1 + self._rules = {} + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self.obj.Name + + @property + def form(self): + return self.obj.getParent() + + @property + def index(self): + return self._index + @index.setter + def index(self, value): + self._index = value + + @property + def events(self): + return self.form.getScriptEvents(self.index) + + def remove_event(self, name=''): + for ev in self.events: + if name and \ + ev.EventMethod == self.EVENTS[name] and \ + ev.ListenerType == self.TYPES[ev.EventMethod]: + self.form.revokeScriptEvent(self.index, + ev.ListenerType, ev.EventMethod, ev.AddListenerParam) + break + else: + self.form.revokeScriptEvent(self.index, + ev.ListenerType, ev.EventMethod, ev.AddListenerParam) + return + + 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 + + +class FormButton(FormControlBase): + + def __init__(self, obj): + super().__init__(obj) + + + +class LOForm(ObjectBase): + + def __init__(self, obj): + super().__init__(obj) + self._init_controls() + + def __getitem__(self, index): + if isinstance(index, int): + return self._controls[index] + else: + return getattr(self, index) + + def _get_type_control(self, name): + types = { + # ~ 'stardiv.Toolkit.UnoFixedTextControl': 'label', + 'com.sun.star.form.OButtonModel': 'formbutton', + # ~ 'stardiv.Toolkit.UnoEditControl': 'text', + # ~ 'stardiv.Toolkit.UnoRoadmapControl': 'roadmap', + # ~ 'stardiv.Toolkit.UnoFixedHyperlinkControl': 'link', + # ~ 'stardiv.Toolkit.UnoListBoxControl': 'listbox', + } + return types[name] + + def _init_controls(self): + self._controls = [] + for i, c in enumerate(self.obj.ControlModels): + tipo = self._get_type_control(c.ImplementationName) + control = get_custom_class(tipo, c) + control.index = i + self._controls.append(control) + setattr(self, c.Name, control) + + @property + def name(self): + return self._obj.getName() + @name.setter + def name(self, value): + self._obj.setName(value) + + +class LOForms(ObjectBase): + + def __init__(self, obj, doc): + self._doc = doc + super().__init__(obj) + + def __getitem__(self, index): + form = super().__getitem__(index) + return LOForm(form) + + @property + def doc(self): + return self._doc + + @property + def count(self): + return self.obj.getCount() + + @property + def names(self): + return self.obj.getElementNames() + + def exists(self, name): + return name in self.names + + def insert(self, name): + form = self.doc.create_instance('com.sun.star.form.component.Form') + self.obj.insertByName(name, form) + return self[name] + + def remove(self, index): + if isinstance(index, int): + self.obj.removeByIndex(index) + else: + self.obj.removeByName(index) + return + + +class LOCellStyle(LOObjectBase): + + def __init__(self, obj): + super().__init__(obj) + + @property + def name(self): + return self.obj.Name + + def apply(self, properties): + set_properties(self.obj, properties) + return + + +class LOCellStyles(object): + + def __init__(self, obj): + self._obj = obj + + 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 apply(self, style, properties): + set_properties(style, properties) + return + + +class LOImage(object): + TYPES = { + 'image/png': 'png', + 'image/jpeg': 'jpg', + } + + def __init__(self, obj): + self._obj = obj + + @property + def address(self): + return self.obj.Anchor.AbsoluteName + + @property + def url(self): + return _path_system(self.obj.URL) + @url.setter + def url(self, value): + self.obj.URL = _path_url(value) + + @property + def path(self): + return _path_system(self.obj.GraphicURL) + @path.setter + def path(self, value): + self.obj.GraphicURL = _path_url(value) + + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value + + def save(self, path): + if is_dir(path): + p = path + n = self.name + else: + p, fn, n, e = get_info_path(path) + ext = self.TYPES[self.mimetype] + path = join(p, '{}.{}'.format(n, ext)) + size = len(self.obj.Bitmap.DIB) + data = self.obj.GraphicStream.readBytes((), size) + data = data[-1].value + save_file(path, 'wb', data) + return path + + +class LOCalc(LODocument): + + def __init__(self, obj): + super().__init__(obj) + self._sheets = obj.getSheets() + + def __getitem__(self, index): + if isinstance(index, str): + code_name = [s.Name for s in self._sheets if s.CodeName == index] + if code_name: + index = code_name[0] + return LOCalcSheet(self._sheets[index], self) + + def __setitem__(self, key, value): + self._sheets[key] = value + + def __contains__(self, item): + return item in self.obj.Sheets + + @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 active(self): + return LOCalcSheet(self._cc.getActiveSheet(), self) + + def activate(self, sheet): + obj = sheet + if isinstance(sheet, LOCalcSheet): + obj = sheet.obj + elif isinstance(sheet, str): + obj = self[sheet].obj + self._cc.setActiveSheet(obj) + return + + @property + def selection(self): + sel = self.obj.getCurrentSelection() + if sel.ImplementationName in OBJ_TYPE_RANGES: + sel = LOCellRange(sel, self) + return sel + + @property + def sheets(self): + return LOCalcSheets(self._sheets, self) + + @property + def names(self): + return self.sheets.names + + @property + def cell_style(self): + obj = self.obj.getStyleFamilies()['CellStyles'] + return LOCellStyles(obj) + + def create(self): + return self.obj.createInstance('com.sun.star.sheet.Spreadsheet') + + def insert(self, name, pos=-1): + # ~ sheet = obj.createInstance('com.sun.star.sheet.Spreadsheet') + # ~ obj.Sheets['New'] = sheet + index = pos + if pos < 0: + index = self._sheets.Count + pos + 1 + if isinstance(name, str): + self._sheets.insertNewByName(name, index) + else: + for n in name: + self._sheets.insertNewByName(n, index) + name = n + return LOCalcSheet(self._sheets[name], self) + + def move(self, name, pos=-1): + return self.sheets.move(name, pos) + + def remove(self, name): + return self.sheets.remove(name) + + def copy(self, source='', target='', pos=-1): + index = pos + if pos < 0: + index = self._sheets.Count + pos + 1 + + names = source + if not names: + names = self.names + elif isinstance(source, str): + names = (source,) + + new_names = target + if not target: + new_names = [n + '_2' for n in names] + elif isinstance(target, str): + new_names = (target,) + + for i, ns in enumerate(names): + self.sheets.copy(ns, new_names[i], index + i) + + return LOCalcSheet(self._sheets[index], self) + + def copy_from(self, doc, source='', target='', pos=-1): + index = pos + if pos < 0: + index = self._sheets.Count + pos + 1 + + names = source + if not names: + 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, n in enumerate(names): + self._sheets.importSheet(doc.obj, n, index + i) + self.sheets[index + i].name = new_names[i] + + # ~ doc.getCurrentController().setActiveSheet(sheet) + # ~ For controls in sheet + # ~ doc.getCurrentController().setFormDesignMode(False) + + return LOCalcSheet(self._sheets[index], self) + + def sort(self, reverse=False): + names = sorted(self.names, reverse=reverse) + for i, n in enumerate(names): + self.sheets.move(n, i) + return + + def get_cell(self, index=None): + """ + index is str 'A1' + index is tuple (row, col) + """ + if index is None: + cell = self.selection.first + else: + cell = LOCellRange(self.active[index].obj, self) + return cell + + def select(self, rango): + r = rango + if hasattr(rango, 'obj'): + r = rango.obj + elif isinstance(rango, str): + r = self.get_cell(rango).obj + self._cc.select(r) + return + + def create_cell_style(self, name=''): + obj = self.create_instance('com.sun.star.style.CellStyle') + if name: + self.cell_style[name] = obj + return LOCellStyle(obj) + + def clear_undo(self): + self.obj.getUndoManager().clear() + return + + def filter_by_color(self, cell=None): + if cell is None: + cell = self.selection.first + cr = cell.current_region + col = cell.column - cr.column + rangos = cell.get_column(col).visible + for r in rangos: + for row in range(r.rows): + c = r[row, 0] + if c.back_color != cell.back_color: + c.rows_visible = False + return + + def _set_cell(self, k='', v=None, cell=None, value=False): + if k: + self._sd.setSearchString(k) + ranges = self._search.findAll(self._sd) + if ranges: + ranges = ranges.getRangeAddressesAsString().split(';') + for r in ranges: + for c in r.split(','): + cell = self._sheet.getCellRangeByName(c) + if v is None: + return cell + if cell.getImplementationName() == 'ScCellObj': + pattern = re.compile(k, re.IGNORECASE) + nv = pattern.sub(v, cell.getString()) + if value: + cell.setValue(nv) + else: + cell.setString(nv) + return cell + if cell: + if cell.getImplementationName() == 'ScCellObj': + ca = cell.getCellAddress() + new_cell = self._sheet.getCellByPosition(ca.Column, ca.Row + 1) + if value: + new_cell.setValue(v) + else: + new_cell.setString(v) + return new_cell + + def render(self, data, sheet=0): + self.set_search_in(sheet) + + for k, v in data.items(): + self._render_cells(k, v) + return + + +class LOCalcSheets(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + + def __getitem__(self, index): + return LOCalcSheet(self.obj[index], self.doc) + + @property + def obj(self): + return self._obj + + @property + def doc(self): + return self._doc + + @property + def count(self): + return self.obj.Count + + @property + def names(self): + return self.obj.ElementNames + + def copy(self, name, new_name, pos): + self.obj.copyByName(name, new_name, pos) + return + + def move(self, name, pos): + index = pos + if pos < 0: + index = self.count + pos + 1 + sheet = self.obj[name] + self.obj.moveByName(sheet.Name, index) + return + + def remove(self, name): + sheet = self.obj[name] + self.obj.removeByName(sheet.Name) + return + + +class LOCalcSheet(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + self._init_values() + + def __getitem__(self, index): + return LOCellRange(self.obj[index], self.doc) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def _init_values(self): + self._events = None + self._dp = self.obj.getDrawPage() + self._images = {i.Name: LOImage(i) for i in self._dp} + + @property + def obj(self): + return self._obj + + @property + def doc(self): + return self._doc + + @property + def images(self): + return self._images + + @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 color(self): + return self._obj.TabColor + @color.setter + def color(self, value): + self._obj.TabColor = get_color(value) + + @property + def active(self): + return self.doc.selection.first + + def activate(self): + self.doc.activate(self.obj) + return + + @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 + + def get_cursor(self, cell): + return self.obj.createCursorByRange(cell) + + def exists_chart(self, name): + return name in self.obj.Charts.ElementNames + + @property + def forms(self): + return LOForms(self._dp.getForms(), self.doc) + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers + self._connect_listeners() + + def _connect_listeners(self): + if self.events is None: + return + + listeners = { + 'addModifyListener': EventsModify, + } + for key, value in listeners.items(): + getattr(self.obj, key)(listeners[key](self.events)) + print('add_listener') + return + + def get_range_by_address(self, address): + row_s = address.StartRow + row_e = address.EndRow + 1 + col_s = address.StartColumn + col_e = address.EndColumn + 1 + return LOCellRange(self.obj[row_s:row_e,col_s:col_e], self.doc) + + def render(self, data, rango=None, clean=False): + if rango is None: + ra = self.obj.getPrintAreas()[0] + rango = self.get_range_by_address(ra) + + rango.render(data, clean) + return rango + + def find(self, search_string, rango=None): + if rango is None: + ra = self.obj.getPrintAreas()[0] + rango = self.get_range_by_address(ra) + + cell = rango.find(search_string) + + return cell + + +class LOWriter(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + @property + def obj(self): + return self._obj + + @property + def string(self): + return self._obj.getText().String + + @property + def text(self): + return self._obj.getText() + + @property + def cursor(self): + return self.text.createTextCursor() + + @property + def paragraphs(self): + return [LOTextRange(p) for p in self.text] + + @property + def selection(self): + sel = self.obj.getCurrentSelection() + if sel.ImplementationName == TEXT_RANGES: + return LOTextRange(sel[0]) + elif sel.ImplementationName == TEXT_RANGE: + return LOTextRange(sel) + return sel + + def write(self, data, cursor=None): + cursor = cursor or self.selection.cursor.getEnd() + if data.startswith('\n'): + c = data.split('\n') + for i in range(len(c)-1): + self.text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False) + else: + self.text.insertString(cursor, data, False) + return + + def insert_table(self, data, cursor=None): + cursor = cursor or self.selection.cursor.getEnd() + table = self.obj.createInstance('com.sun.star.text.TextTable') + rows = len(data) + cols = len(data[0]) + table.initialize(rows, cols) + self.insert_content(cursor, table) + table.DataArray = data + return WriterTable(table) + + def create_chart(self, tipo, cursor=None): + cursor = cursor or self.selection.cursor.getEnd() + chart = LOChart(None, tipo) + chart.cursor = cursor + chart.doc = self + return chart + + def insert_content(self, cursor, data, replace=False): + self.text.insertTextContent(cursor, data, replace) + return + + # ~ f = doc.createInstance('com.sun.star.text.TextFrame') + # ~ f.setSize(Size(10000, 500)) + + def insert_image(self, path, **kwargs): + cursor = kwargs.get('cursor', self.selection.cursor.getEnd()) + w = kwargs.get('width', 1000) + h = kwargs.get('Height', 1000) + image = self.create_instance('com.sun.star.text.GraphicObject') + image.GraphicURL = _path_url(path) + image.AnchorType = AS_CHARACTER + image.Width = w + image.Height = h + self.insert_content(cursor, image) + return + + def go_start(self): + cursor = self._cc.getViewCursor() + cursor.gotoStart(False) + return cursor + + def go_end(self): + cursor = self._cc.getViewCursor() + cursor.gotoEnd(False) + return cursor + + def select(self, text): + self._cc.select(text) + return + + def search(self, options): + descriptor = self.obj.createSearchDescriptor() + 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'] + + if options.get('First', False): + found = self.obj.findFirst(descriptor) + else: + found = self.obj.findAll(descriptor) + + return found + + def replace(self, options): + descriptor = self.obj.createReplaceDescriptor() + 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 + + +class LOTextRange(object): + + def __init__(self, obj): + self._obj = obj + self._is_paragraph = self.obj.ImplementationName == 'SwXParagraph' + self._is_table = self.obj.ImplementationName == 'SwXTextTable' + + @property + def obj(self): + return self._obj + + @property + def is_paragraph(self): + return self._is_paragraph + + @property + def is_table(self): + return self._is_table + + @property + def string(self): + return self.obj.String + + @property + def text(self): + return self.obj.getText() + + @property + def cursor(self): + return self.text.createTextCursorByRange(self.obj) + + +class LOBase(object): + 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, name, path='', **kwargs): + self._name = name + self._path = path + self._dbc = create_instance('com.sun.star.sdb.DatabaseContext') + if path: + path_url = _path_url(path) + db = self._dbc.createInstance() + db.URL = 'sdbc:embedded:firebird' + db.DatabaseDocument.storeAsURL(path_url, ()) + if not self.exists: + self._dbc.registerDatabaseLocation(name, path_url) + else: + if name.startswith('odbc:'): + self._con = self._odbc(name, kwargs) + else: + db = self._dbc.getByName(name) + self.path = _path_system(self._dbc.getDatabaseLocation(name)) + self._con = db.getConnection('', '') + + if self._con is None: + msg = 'Not connected to: {}'.format(name) + else: + msg = 'Connected to: {}'.format(name) + debug(msg) + + def _odbc(self, name, kwargs): + dm = create_instance('com.sun.star.sdbc.DriverManager') + args = dict_to_property(kwargs) + try: + con = dm.getConnectionWithInfo('sdbc:{}'.format(name), args) + return con + except Exception as e: + error(str(e)) + return None + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self._name + + @property + def connection(self): + return self._con + + @property + def path(self): + return self._path + @path.setter + def path(self, value): + self._path = value + + @property + def exists(self): + return self._dbc.hasRegisteredDatabase(self.name) + + @classmethod + def register(self, path, name): + if not self._dbc.hasRegisteredDatabase(name): + self._dbc.registerDatabaseLocation(name, _path_url(path)) + return + + def revoke(self, name): + self._dbc.revokeDatabaseLocation(name) + return True + + def save(self): + # ~ self._db.connection.commit() + # ~ self._db.connection.getTables().refresh() + # ~ oDisp.executeDispatch(oFrame,".uno:DBRefreshTables", "", 0, Array()) + self._obj.DatabaseDocument.store() + self.refresh() + return + + def close(self): + self._con.close() + return + + def refresh(self): + self._con.getTables().refresh() + return + + def get_tables(self): + tables = self._con.getTables() + tables = [tables.getByIndex(i) for i in range(tables.Count)] + return tables + + def cursor(self, sql, params): + cursor = self._con.prepareStatement(sql) + for i, v in enumerate(params, 1): + if not type(v) in self.TYPES: + error('Type not support') + debug((i, type(v), v, self.TYPES[type(v)])) + getattr(cursor, self.TYPES[type(v)])(i, v) + return cursor + + def execute(self, sql, params): + debug(sql) + if params: + cursor = self.cursor(sql, params) + cursor.execute() + else: + cursor = self._con.createStatement() + cursor.execute(sql) + # ~ resulset = cursor.executeQuery(sql) + # ~ rows = cursor.executeUpdate(sql) + self.save() + return cursor + + +class LODrawImpress(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + @property + def draw_page(self): + return self._cc.getCurrentPage() + + def insert_image(self, path, **kwargs): + w = kwargs.get('width', 3000) + h = kwargs.get('Height', 3000) + x = kwargs.get('X', 1000) + y = kwargs.get('Y', 1000) + + image = self.create_instance('com.sun.star.drawing.GraphicObjectShape') + image.GraphicURL = _path_url(path) + image.Size = Size(w, h) + image.Position = Point(x, y) + self.draw_page.add(image) + return + + +class LOImpress(LODrawImpress): + + def __init__(self, obj): + super().__init__(obj) + + +class LODraw(LODrawImpress): + + def __init__(self, obj): + super().__init__(obj) + + @property + def selection(self): + sel = self.obj.getCurrentSelection() + return sel + + +class LOMath(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + +class LOBasicIde(LODocument): + + def __init__(self, obj): + super().__init__(obj) + + @property + def selection(self): + sel = self._cc.getSelection() + return sel + + +class LOCellRange(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + self._init_values() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __getitem__(self, index): + return LOCellRange(self.obj[index], self.doc) + + def __contains__(self, item): + return item.in_range(self) + + def _init_values(self): + self._sd = None + self._type_obj = self.obj.ImplementationName + self._type_content = EMPTY + + if self._type_obj == OBJ_CELL: + self._type_content = self.obj.getType() + return + + @property + def obj(self): + return self._obj + + @property + def doc(self): + return self._doc + + @property + def type(self): + return self._type_obj + + @property + def type_content(self): + return self._type_content + + @property + def first(self): + if self.type == OBJ_RANGES: + obj = LOCellRange(self.obj[0][0,0], self.doc) + else: + obj = LOCellRange(self.obj[0,0], self.doc) + return obj + + @property + def value(self): + v = None + if self._type_content == VALUE: + v = self.obj.getValue() + elif self._type_content == TEXT: + v = self.obj.getString() + elif self._type_content == FORMULA: + v = self.obj.getFormula() + return v + @value.setter + def value(self, data): + if isinstance(data, str): + if data.startswith('='): + self.obj.setFormula(data) + else: + self.obj.setString(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 data(self): + return self.obj.getDataArray() + @data.setter + def data(self, values): + self.obj.setDataArray(values) + + @property + def formula(self): + return self.obj.getFormulaArray() + @formula.setter + def formula(self, values): + self.obj.setFormulaArray(values) + + @property + def column(self): + a = self.address + if hasattr(a, 'Column'): + c = a.Column + else: + c = a.StartColumn + return c + + @property + def columns(self): + return self._obj.Columns.Count + + @property + def row(self): + a = self.address + if hasattr(a, 'Row'): + r = a.Row + else: + r = a.StartRow + return r + + @property + def rows(self): + return self._obj.Rows.Count + + def to_size(self, rows, cols): + cursor = self.sheet.get_cursor(self.obj[0,0]) + cursor.collapseToSize(cols, rows) + return LOCellRange(self.sheet[cursor.AbsoluteName].obj, self.doc) + + def copy_from(self, rango, formula=False): + data = rango + if isinstance(rango, LOCellRange): + 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 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(self, source): + self.sheet.obj.copyRange(self.address, source.range_address) + return + + def transpose(self, formula=False): + data = self.data + if formula: + data = self.formula + data = tuple(zip(*data)) + self.clear(1023) + self[0,0].copy_from(data, formula=formula) + return + + def transpose2(self): + # ~ 'Flags': 'A', + # ~ 'FormulaCommand': 0, + # ~ 'SkipEmptyCells': False, + # ~ 'AsLink': False, + # ~ 'MoveMode': 4, + args = { + 'Transpose': True, + } + args = dict_to_property(args) + self.select() + copy() + self.clear(1023) + self[0,0].select() + call_dispatch(self._doc, '.uno:InsertContents', args) + set_clipboard('') + return + + def offset(self, row=1, col=0): + ra = self.obj.getRangeAddress() + col = ra.EndColumn + col + row = ra.EndRow + row + return LOCellRange(self.sheet[row, col].obj, self.doc) + + @property + def next_cell(self): + a = self.current_region.address + if hasattr(a, 'StartColumn'): + col = a.StartColumn + else: + col = a.Column + if hasattr(a, 'EndRow'): + row = a.EndRow + 1 + else: + row = a.Row + 1 + + return LOCellRange(self.sheet[row, col].obj, self.doc) + + @property + def sheet(self): + return LOCalcSheet(self.obj.Spreadsheet, self.doc) + + @property + def charts(self): + return self.obj.Spreadsheet.Charts + + @property + def ps(self): + ps = Rectangle() + s = self.obj.Size + p = self.obj.Position + ps.X = p.X + ps.Y = p.Y + ps.Width = s.Width + ps.Height = s.Height + return ps + + @property + def draw_page(self): + return self.sheet.obj.getDrawPage() + + @property + def name(self): + return self.obj.AbsoluteName + + @property + def address(self): + if self._type_obj == OBJ_CELL: + a = self.obj.getCellAddress() + elif self._type_obj == OBJ_RANGE: + a = self.obj.getRangeAddress() + else: + a = self.obj.getRangeAddressesAsString() + return a + + @property + def range_address(self): + return self.obj.getRangeAddress() + + @property + def current_region(self): + cursor = self.sheet.get_cursor(self.obj[0,0]) + cursor.collapseToCurrentRegion() + return LOCellRange(self.sheet[cursor.AbsoluteName].obj, self.doc) + + @property + def merged_area(self): + cursor = self.sheet.get_cursor(self.obj[0,0]) + cursor.collapseToMergedArea() + return LOCellRange(self.sheet[cursor.AbsoluteName].obj, self.doc) + + @property + def visible(self): + cursor = self.sheet.get_cursor(self.obj) + rangos = [LOCellRange(self.sheet[r.AbsoluteName].obj, self.doc) + for r in cursor.queryVisibleCells()] + return tuple(rangos) + + @property + def empty(self): + cursor = self.sheet.get_cursor(self.obj) + rangos = [LOCellRange(self.sheet[r.AbsoluteName].obj, self.doc) + for r in cursor.queryEmptyCells()] + return tuple(rangos) + + @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 cell_style(self): + return self.obj.CellStyle + @cell_style.setter + def cell_style(self, value): + self.obj.CellStyle = value + + @property + def auto_format(self): + return self.obj.CellStyle + @auto_format.setter + def auto_format(self, value): + self.obj.autoFormat(value) + + def auto_width(self): + self.obj.Columns.OptimalWidth = True + return + + def insert_image(self, path, **kwargs): + s = self.obj.Size + w = kwargs.get('Width', s.Width) + h = kwargs.get('Height', s.Height) + data = kwargs.get('Data', '') + img = self.doc.create_instance('com.sun.star.drawing.GraphicObjectShape') + img.GraphicURL = _path_url(path) + # ~ img.ResizeWithCell = True + self.draw_page.add(img) + img.setSize(Size(w, h)) + img.Anchor = self.obj + return + + def insert_shape(self, tipo, **kwargs): + s = self.obj.Size + w = kwargs.get('width', s.Width) + h = kwargs.get('Height', s.Height) + img = self.doc.create_instance('com.sun.star.drawing.{}Shape'.format(tipo)) + set_properties(img, kwargs) + self.draw_page.add(img) + img.Anchor = self.obj + img.setSize(Size(w, h)) + return + + def select(self): + self.doc._cc.select(self.obj) + return + + def in_range(self, rango): + if isinstance(rango, LOCellRange): + address = rango.address + else: + address = rango.getRangeAddress() + cursor = self.sheet.get_cursor(self.obj) + result = cursor.queryIntersection(address) + return bool(result.Count) + + def fill(self, source=1): + self.obj.fillAuto(0, source) + return + + def clear(self, what=31): + # ~ http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html + self.obj.clearContents(what) + return + + @property + def rows_visible(self): + return self._obj.getRows().IsVisible + @rows_visible.setter + def rows_visible(self, value): + self._obj.getRows().IsVisible = value + + @property + def columns_visible(self): + return self._obj.getColumns().IsVisible + @columns_visible.setter + def columns_visible(self, value): + self._obj.getColumns().IsVisible = value + + def get_column(self, index=0, first=False): + ca = self.address + ra = self.current_region.address + if hasattr(ca, 'Column'): + col = ca.Column + else: + col = ca.StartColumn + index + start = 1 + if first: + start = 0 + if hasattr(ra, 'Row'): + row_start = ra.Row + start + row_end = ra.Row + 1 + else: + row_start = ra.StartRow + start + row_end = ra.EndRow + 1 + return LOCellRange(self.sheet[row_start:row_end, col:col+1].obj, self.doc) + + def import_csv(self, path, **kwargs): + data = import_csv(path, **kwargs) + self.copy_from(data) + return + + def export_csv(self, path, **kwargs): + data = self.current_region.data + export_csv(path, data, **kwargs) + return + + def create_chart(self, tipo): + chart = LOChart(None, tipo) + chart.cell = self + return chart + + def search(self, options): + descriptor = self.obj.Spreadsheet.createSearchDescriptor() + descriptor.setSearchString(options.get('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 options.get('First', False): + found = self.obj.findFirst(descriptor) + else: + found = self.obj.findAll(descriptor) + + return found + + def replace(self, options): + descriptor = self.obj.Spreadsheet.createReplaceDescriptor() + 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'] + found = self.obj.replaceAll(descriptor) + return found + + @property + def validation(self): + return self.obj.Validation + @validation.setter + def validation(self, values): + is_list = False + current = self.validation + for k, v in values.items(): + if k == 'Type' and v == 6: + is_list = True + if k == 'Formula1' and is_list: + if isinstance(v, (tuple, list)): + v = ';'.join(['"{}"'.format(i) for i in v]) + setattr(current, k, v) + self.obj.Validation = current + + 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: + cell.Value = value + return + + def rows_insert(self, count=0): + if not count: + count = self.rows + self.obj.Rows.insertByIndex(0, count) + return + + def rows_merge(self): + for r in range(self.rows): + self[r].obj.merge(True) + return + + def copy_format_from(self, rango): + rango.select() + copy(self._doc) + self.select() + args = { + 'Flags': 'T', + 'MoveMode': 4, + } + args = dict_to_property(args) + url = '.uno:InsertContents' + call_dispatch(self._doc, url, args) + return + + def _render_list(self, key, values): + rango = None + total_rows = len(values) + try: + rango = self.sheet[key] + if total_rows > 1: + cells = rango[0,0].offset(1).to_size(total_rows - 1, 1) + name = cells.name + cells.rows_insert() + cells = self.sheet[name] + cells.copy_format_from(rango) + except Exception as e: + print(key, e) + + if rango is None: + pass + else: + headers = list(rango.data[0]) + positions = {} + for i, v in enumerate(headers): + if not v: + continue + k = v[1:-1] + if k in values[0]: + positions[k] = i + + data = [] + for row in values: + new_row = headers[:] + for k, v in positions.items(): + new_row[v] = row[k] + data.append(new_row) + + cell = rango[0,0] + cell.copy_from(data) + return + + def _render_value(self, key, value, parent=''): + if isinstance(value, dict): + for k, v in value.items(): + self._render_value(k, v, key) + return + 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 + + def clean_render(self, template='\{(\w.+)\}'): + self._sd.SearchRegularExpression = True + self._sd.setSearchString(template) + self.obj.replaceAll(self._sd) + 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) + return LOCellRange(self.sheet[cell.AbsoluteName].obj, self.doc) + + 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 render(self, data, clean=False): + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + + for k, v in data.items(): + self._render_value(k, v) + + if clean: + self.clean_render() + 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 EventsButton(EventsListenerBase, XActionListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def actionPerformed(self, event): + event_name = '{}_action'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + +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 mouseEntered(self, event): + obj = event.Source.Model + obj.TextColor = get_color('blue') + return + + def mouseExited(self, event): + obj = event.Source.Model + obj.TextColor = 0 + 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 EventsModify(EventsListenerBase, XModifyListener): + + def __init__(self, controller): + super().__init__(controller) + + def modified(self, event): + event_name = '{}_modified'.format(event.Source.Name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + 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 EventsFocus(EventsListenerBase, XFocusListener): + + def __init__(self, controller, name): + super().__init__(controller, name) + + def focusGained(self, event): + service = event.Source.Model.ImplementationName + if service == 'stardiv.Toolkit.UnoControlListBoxModel': + return + obj = event.Source.Model + obj.BackgroundColor = COLOR_ON_FOCUS + + def focusLost(self, event): + obj = event.Source.Model + obj.BackgroundColor = -1 + + +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) + 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 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 EventsKeyWindow(EventsListenerBase, XKeyListener): + """ + event.KeyChar + event.KeyCode + event.KeyFunc + event.Modifiers + """ + + def __init__(self, cls): + super().__init__(cls.events, cls.name) + self._cls = cls + + def keyPressed(self, event): + pass + + def keyReleased(self, event): + event_name = '{}_key_released'.format(self._cls.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 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 + + +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 UnoBaseObject(object): + + def __init__(self, obj): + self._obj = obj + self._model = self.obj.Model + self._rules = {} + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._model + + @property + def name(self): + return self.model.Name + + @property + def parent(self): + ps = self.obj.getContext().PosSize + return self.obj.getContext() + + 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 width(self): + return self._model.Width + @width.setter + def width(self, value): + self.model.Width = value + + @property + def ps_width(self): + return self._get_possize('Width') + @ps_width.setter + def ps_width(self, value): + self._set_possize('Width', value) + + @property + def height(self): + return self.model.Height + @height.setter + def height(self, value): + self.model.Height = value + + @property + def ps_height(self): + return self._get_possize('Height') + @ps_height.setter + def ps_height(self, value): + self._set_possize('Height', value) + + @property + def size(self): + ps = self.obj.getPosSize() + return (ps.Width, ps.Height) + @size.setter + def size(self, value): + ps = self.obj.getPosSize() + ps.Width = value[0] + ps.Height = value[1] + self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, SIZE) + + @property + def tag(self): + 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 back_color(self): + return self.model.BackgroundColor + @back_color.setter + def back_color(self, value): + self.model.BackgroundColor = value + + @property + def rules(self): + return self._rules + @rules.setter + def rules(self, value): + self._rules = value + + def set_focus(self): + self.obj.setFocus() + 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): + if x: + self.x = origin.x + origin.width + x + else: + self.x = origin.x + if y: + self.y = origin.y + origin.height + y + else: + self.y = origin.y + return + + def possize(self, origin): + self.x = origin.x + self.y = origin.y + self.width = origin.width + self.height = origin.height + 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 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 UnoListBox(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @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)) + return + + 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 exists_path(image): + return _path_url(image) + + if not ID_EXTENSION: + return '' + + path = get_path_extension(ID_EXTENSION) + path = join(path, DIR['images'], image) + return _path_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 UnoGrid(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._gdm = self._model.GridDataModel + # ~ self._data = [] + self._columns = {} + # ~ self._format_columns = () + + def __getitem__(self, index): + value = self._gdm.getCellData(index[0], index[1]) + return value + + @property + def type(self): + return 'grid' + + 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 value(self): + return self[self.column, self.row] + + @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 row(self): + return self.obj.CurrentRow + + @property + def rows(self): + return self._gdm.RowCount + + @property + def column(self): + return self.obj.CurrentColumn + + @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 clear(self): + self._gdm.removeAllRows() + return + + 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 UnoRoadmap(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._options = () + + @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 = [] + + @property + def selection(self): + return self.obj.Selection + + @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 + self._add_data() + return + + @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 + + +class UnoTab(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._events = None + + def __getitem__(self, index): + return self.get_sheet(index) + + @property + def current(self): + return self.obj.getActiveTabID() + @property + def active(self): + return self.current + + def get_sheet(self, id): + if isinstance(id, int): + sheet = self.obj.Controls[id-1] + else: + sheet = self.obj.getControl(id.lower()) + return sheet + + @property + def sheets(self): + return self._sheets + @sheets.setter + def sheets(self, values): + i = len(self.obj.Controls) + for title in values: + i += 1 + sheet = self.model.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.model.insertByName('sheet{}'.format(i), sheet) + return + + def insert(self, title): + id = len(self.obj.Controls) + 1 + sheet = self.model.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.model.insertByName('sheet{}'.format(id), sheet) + return id + + def remove(self, id): + sheet = self.get_sheet(id) + for control in sheet.getControls(): + sheet.Model.removeByName(control.Model.Name) + sheet.removeControl(control) + # ~ self._model.removeByName('page_{}'.format(ID)) + + self.obj.removeTab(id) + return + + def activate(self, id): + self.obj.activateTab(id) + return + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers + + def _special_properties(self, tipo, properties): + columns = properties.pop('Columns', ()) + if tipo == 'grid': + properties['ColumnModel'] = _set_column_model(columns) + if not 'Width' in properties: + properties['Width'] = self.width + if not 'Height' in properties: + properties['Height'] = self.height + elif tipo == 'button' and 'ImageURL' in properties: + properties['ImageURL'] = self._set_image_url(properties['ImageURL']) + elif tipo == 'roadmap': + if not 'Height' in properties: + properties['Height'] = self.height + if 'Title' in properties: + properties['Text'] = properties.pop('Title') + elif tipo == 'pages': + if not 'Width' in properties: + properties['Width'] = self.width + if not 'Height' in properties: + properties['Height'] = self.height + + return properties + + def add_control(self, id, properties): + tipo = properties.pop('Type').lower() + root = properties.pop('Root', '') + sheets = properties.pop('Sheets', ()) + properties = self._special_properties(tipo, properties) + + sheet = self.get_sheet(id) + sheet_model = sheet.getModel() + model = sheet_model.createInstance(get_control_model(tipo)) + set_properties(model, properties) + name = properties['Name'] + sheet_model.insertByName(name, model) + + control = sheet.getControl(name) + add_listeners(self.events, control, name) + control = get_custom_class(tipo, control) + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'pages' and sheets: + control.sheets = sheets + + setattr(self, name, control) + return + + +def get_custom_class(tipo, obj): + classes = { + 'label': UnoLabel, + 'button': UnoButton, + 'text': UnoText, + 'listbox': UnoListBox, + 'grid': UnoGrid, + 'link': UnoLabelLink, + 'roadmap': UnoRoadmap, + 'tree': UnoTree, + 'tab': UnoTab, + # ~ 'image': UnoImage, + # ~ 'radio': UnoRadio, + # ~ 'groupbox': UnoGroupBox, + 'formbutton': FormButton, + } + return classes[tipo](obj) + + +def get_control_model(control): + services = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', + 'tab': 'com.sun.star.awt.UnoMultiPageModel', + } + return services[control] + + +def add_listeners(events, control, name=''): + listeners = { + 'addActionListener': EventsButton, + 'addMouseListener': EventsMouse, + 'addItemListener': EventsItem, + 'addFocusListener': EventsFocus, + 'addKeyListener': EventsKey, + 'addTabListener': EventsTab, + } + if hasattr(control, 'obj'): + control = contro.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' + + 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 + + +class WriterTable(ObjectBase): + + def __init__(self, obj): + super().__init__(obj) + + def __getitem__(self, key): + obj = super().__getitem__(key) + return WriterTableRange(obj, key, self.name) + + @property + def name(self): + return self.obj.Name + @name.setter + def name(self, value): + self.obj.Name = value + + +class WriterTableRange(ObjectBase): + + def __init__(self, obj, index, table_name): + self._index = index + self._table_name = table_name + super().__init__(obj) + self._is_cell = hasattr(self.obj, 'CellName') + + def __getitem__(self, key): + obj = super().__getitem__(key) + return WriterTableRange(obj, key, self._table_name) + + @property + def value(self): + return self.obj.String + @value.setter + def value(self, value): + self.obj.String = value + + @property + def data(self): + return self.obj.getDataArray() + @data.setter + def data(self, values): + if isinstance(values, list): + values = tuple(values) + self.obj.setDataArray(values) + + @property + def rows(self): + return len(self.data) + + @property + def columns(self): + return len(self.data[0]) + + @property + def name(self): + if self._is_cell: + name = '{}.{}'.format(self._table_name, self.obj.CellName) + elif isinstance(self._index, str): + name = '{}.{}'.format(self._table_name, self._index) + else: + c1 = self.obj[0,0].CellName + c2 = self.obj[self.rows-1,self.columns-1].CellName + name = '{}.{}:{}'.format(self._table_name, c1, c2) + return name + + def get_cell(self, *index): + return self[index] + + def get_column(self, index=0, start=1): + return self[start:self.rows,index:index+1] + + def get_series(self): + class Serie(): + pass + series = [] + for i in range(self.columns): + serie = Serie() + serie.label = self.get_cell(0,i).name + serie.data = self.get_column(i).data + serie.values = self.get_column(i).name + series.append(serie) + return series + + +class ChartFormat(object): + + def __call__(self, obj): + for k, v in self.__dict__.items(): + if hasattr(obj, k): + setattr(obj, k, v) + + +class LOChart(object): + BASE = 'com.sun.star.chart.{}Diagram' + + def __init__(self, obj, tipo=''): + self._obj = obj + self._type = tipo + self._name = '' + self._table = None + self._data = () + self._data_series = () + self._cell = None + self._cursor = None + self._doc = None + self._title = ChartFormat() + self._subtitle = ChartFormat() + self._legend = ChartFormat() + self._xaxistitle = ChartFormat() + self._yaxistitle = ChartFormat() + self._xaxis = ChartFormat() + self._yaxis = ChartFormat() + self._xmaingrid = ChartFormat() + self._ymaingrid = ChartFormat() + self._xhelpgrid = ChartFormat() + self._yhelpgrid = ChartFormat() + self._area = ChartFormat() + self._wall = ChartFormat() + self._dim3d = False + self._series = () + self._labels = () + return + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.insert() + + @property + def obj(self): + return self._obj + @obj.setter + def obj(self, value): + self._obj = value + + @property + def name(self): + return self._name + @name.setter + def name(self, value): + self._name = value + + @property + def type(self): + return self._type + @type.setter + def type(self, value): + self._type = value + + @property + def table(self): + return self._table + @table.setter + def table(self, value): + self._table = value + + @property + def data(self): + return self._data + @data.setter + def data(self, value): + self._data = value + + @property + def cell(self): + return self._cell + @cell.setter + def cell(self, value): + self._cell = value + self.doc = value.doc + + @property + def cursor(self): + return self._cursor + @cursor.setter + def cursor(self, value): + self._cursor = value + + @property + def doc(self): + return self._doc + @doc.setter + def doc(self, value): + self._doc = value + + @property + def width(self): + return self._width + @width.setter + def width(self, value): + self._width = value + + @property + def height(self): + return self._height + @height.setter + def height(self, value): + self._height = value + + @property + def title(self): + return self._title + + @property + def subtitle(self): + return self._subtitle + + @property + def legend(self): + return self._legend + + @property + def xaxistitle(self): + return self._xaxistitle + + @property + def yaxistitle(self): + return self._yaxistitle + + @property + def xaxis(self): + return self._xaxis + + @property + def yaxis(self): + return self._yaxis + + @property + def xmaingrid(self): + return self._xmaingrid + + @property + def ymaingrid(self): + return self._ymaingrid + + @property + def xhelpgrid(self): + return self._xhelpgrid + + @property + def yhelpgrid(self): + return self._yhelpgrid + + @property + def area(self): + return self._area + + @property + def wall(self): + return self._wall + + @property + def dim3d(self): + return self._dim3d + @dim3d.setter + def dim3d(self, value): + self._dim3d = value + + @property + def series(self): + return self._series + @series.setter + def series(self, value): + self._series = value + + @property + def data_series(self): + return self._series + @data_series.setter + def data_series(self, value): + self._data_series = value + + @property + def labels(self): + return self._labels + @labels.setter + def labels(self, value): + self._labels = value + + def _add_series_writer(self, chart): + dp = self.doc.create_instance('com.sun.star.chart2.data.DataProvider') + chart.attachDataProvider(dp) + chart_type = chart.getFirstDiagram().getCoordinateSystems()[0].getChartTypes()[0] + self._data_series = self.table[self.data].get_series() + series = [self._create_serie(dp, s) for s in self._data_series[1:]] + chart_type.setDataSeries(tuple(series)) + chart_data = chart.getData() + chart_data.ComplexRowDescriptions = self._data_series[0].data + return + + def _get_series(self): + rango = self._data_series + class Serie(): + pass + series = [] + for i in range(0, rango.columns, 2): + serie = Serie() + serie.label = rango[0, i+1].name + serie.xvalues = rango.get_column(i).name + serie.values = rango.get_column(i+1).name + series.append(serie) + return series + + def _add_series_calc(self, chart): + dp = self.doc.create_instance('com.sun.star.chart2.data.DataProvider') + chart.attachDataProvider(dp) + chart_type = chart.getFirstDiagram().getCoordinateSystems()[0].getChartTypes()[0] + series = self._get_series() + series = [self._create_serie(dp, s) for s in series] + chart_type.setDataSeries(tuple(series)) + return + + def _create_serie(self, dp, data): + serie = create_instance('com.sun.star.chart2.DataSeries') + rango = data.values + is_x = hasattr(data, 'xvalues') + if is_x: + xrango = data.xvalues + rango_label = data.label + + lds = create_instance('com.sun.star.chart2.data.LabeledDataSequence') + values = self._create_data(dp, rango, 'values-y') + lds.setValues(values) + if data.label: + label = self._create_data(dp, rango_label, '') + lds.setLabel(label) + + xlds = () + if is_x: + xlds = create_instance('com.sun.star.chart2.data.LabeledDataSequence') + values = self._create_data(dp, xrango, 'values-x') + xlds.setValues(values) + + if is_x: + serie.setData((lds, xlds)) + else: + serie.setData((lds,)) + + return serie + + def _create_data(self, dp, rango, role): + data = dp.createDataSequenceByRangeRepresentation(rango) + if not data is None: + data.Role = role + return data + + def _from_calc(self): + ps = self.cell.ps + ps.Width = self.width + ps.Height = self.height + charts = self.cell.charts + data = () + if self.data: + data = (self.data.address,) + charts.addNewByName(self.name, ps, data, True, True) + self.obj = charts.getByName(self.name) + chart = self.obj.getEmbeddedObject() + chart.setDiagram(chart.createInstance(self.BASE.format(self.type))) + if not self.data: + self._add_series_calc(chart) + return chart + + def _from_writer(self): + obj = self.doc.create_instance('com.sun.star.text.TextEmbeddedObject') + obj.setPropertyValue('CLSID', '12DCAE26-281F-416F-a234-c3086127382e') + obj.Name = self.name + obj.setSize(Size(self.width, self.height)) + self.doc.insert_content(self.cursor, obj) + self.obj = obj + chart = obj.getEmbeddedObject() + tipo = self.type + if self.type == 'Column': + tipo = 'Bar' + chart.Diagram.Vertical = True + chart.setDiagram(chart.createInstance(self.BASE.format(tipo))) + chart.DataSourceLabelsInFirstColumn = True + if isinstance(self.data, str): + self._add_series_writer(chart) + else: + chart_data = chart.getData() + labels = [r[0] for r in self.data] + data = [(r[1],) for r in self.data] + chart_data.setData(data) + chart_data.RowDescriptions = labels + + # ~ Bug + if tipo == 'Pie': + chart.setDiagram(chart.createInstance(self.BASE.format('Bar'))) + chart.setDiagram(chart.createInstance(self.BASE.format('Pie'))) + + return chart + + def insert(self): + if not self.cell is None: + chart = self._from_calc() + elif not self.cursor is None: + chart = self._from_writer() + + diagram = chart.Diagram + + if self.type == 'Bar': + diagram.Vertical = True + + if hasattr(self.title, 'String'): + chart.HasMainTitle = True + self.title(chart.Title) + + if hasattr(self.subtitle, 'String'): + chart.HasSubTitle = True + self.subtitle(chart.SubTitle) + + if self.legend.__dict__: + chart.HasLegend = True + self.legend(chart.Legend) + + if self.xaxistitle.__dict__: + diagram.HasXAxisTitle = True + self.xaxistitle(diagram.XAxisTitle) + + if self.yaxistitle.__dict__: + diagram.HasYAxisTitle = True + self.yaxistitle(diagram.YAxisTitle) + + if self.dim3d: + diagram.Dim3D = True + + if self.series: + data_series = chart.getFirstDiagram( + ).getCoordinateSystems( + )[0].getChartTypes()[0].DataSeries + for i, serie in enumerate(data_series): + for k, v in self.series[i].items(): + if hasattr(serie, k): + setattr(serie, k, v) + return self + + +def _set_column_model(columns): + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html + column_model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) + for column in columns: + grid_column = create_instance('com.sun.star.awt.grid.GridColumn', True) + for k, v in column.items(): + setattr(grid_column, k, v) + column_model.addColumn(grid_column) + return column_model + + +def _set_image_url(image, id_extension=''): + if exists_path(image): + return _path_url(image) + + if not id_extension: + return '' + + path = get_path_extension(id_extension) + path = join(path, DIR['images'], image) + return _path_url(path) + + +class LODialog(object): + + def __init__(self, **properties): + self._obj = self._create(properties) + self._init_values() + + def _init_values(self): + self._model = self._obj.Model + self._init_controls() + self._events = None + self._color_on_focus = -1 + self._id_extension = '' + self._images = 'images' + return + + def _create(self, properties): + path = properties.pop('Path', '') + if path: + dp = create_instance('com.sun.star.awt.DialogProvider', True) + return dp.createDialog(_path_url(path)) + + if 'Location' in properties: + location = properties.get('Location', 'application') + library = properties.get('Library', 'Standard') + if location == 'user': + location = 'application' + dp = create_instance('com.sun.star.awt.DialogProvider', True) + path = 'vnd.sun.star.script:{}.{}?location={}'.format( + library, properties['Name'], location) + if location == 'document': + uid = get_document().uid + path = 'vnd.sun.star.tdoc:/{}/Dialogs/{}/{}.xml'.format( + uid, library, properties['Name']) + return dp.createDialog(path) + + 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, properties) + dlg.setModel(model) + dlg.setVisible(False) + dlg.createPeer(toolkit, None) + + return dlg + + def _get_type_control(self, name): + types = { + 'stardiv.Toolkit.UnoFixedTextControl': 'label', + 'stardiv.Toolkit.UnoFixedHyperlinkControl': 'link', + 'stardiv.Toolkit.UnoEditControl': 'text', + 'stardiv.Toolkit.UnoButtonControl': 'button', + 'stardiv.Toolkit.UnoListBoxControl': 'listbox', + 'stardiv.Toolkit.UnoRoadmapControl': 'roadmap', + 'stardiv.Toolkit.UnoMultiPageControl': 'pages', + } + 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 = get_custom_class(tipo, control) + setattr(self, name, control) + return + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._model + + @property + def id_extension(self): + return self._id_extension + @id_extension.setter + def id_extension(self, value): + global ID_EXTENSION + ID_EXTENSION = value + self._id_extension = value + + @property + def images(self): + return self._images + @images.setter + def images(self, value): + self._images = 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 color_on_focus(self): + return self._color_on_focus + @color_on_focus.setter + def color_on_focus(self, value): + global COLOR_ON_FOCUS + COLOR_ON_FOCUS = get_color(value) + self._color_on_focus = COLOR_ON_FOCUS + + @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._connect_listeners() + + def _connect_listeners(self): + for control in self.obj.getControls(): + add_listeners(self._events, control, control.Model.Name) + return + + def open(self): + return self.obj.execute() + + def close(self, value=0): + return self.obj.endDialog(value) + + def _get_control_model(self, control): + services = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + } + return services[control] + + def _set_column_model(self, columns): + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html + column_model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) + for column in columns: + grid_column = create_instance('com.sun.star.awt.grid.GridColumn', True) + for k, v in column.items(): + setattr(grid_column, k, v) + column_model.addColumn(grid_column) + return column_model + + def _set_image_url(self, image): + if exists_path(image): + return _path_url(image) + + if not self.id_extension: + return '' + + path = get_path_extension(self.id_extension) + path = join(path, self.images, image) + return _path_url(path) + + def _special_properties(self, tipo, properties): + columns = properties.pop('Columns', ()) + if tipo == 'grid': + properties['ColumnModel'] = self._set_column_model(columns) + elif tipo == 'button' and 'ImageURL' in properties: + properties['ImageURL'] = self._set_image_url(properties['ImageURL']) + elif tipo == 'roadmap': + if not 'Height' in properties: + properties['Height'] = self.height + if 'Title' in properties: + properties['Text'] = properties.pop('Title') + elif tipo == 'tab': + if not 'Width' in properties: + properties['Width'] = self.width + if not 'Height' in properties: + properties['Height'] = self.height + + return properties + + def add_control(self, properties): + tipo = properties.pop('Type').lower() + root = properties.pop('Root', '') + sheets = properties.pop('Sheets', ()) + + properties = self._special_properties(tipo, properties) + model = self.model.createInstance(self._get_control_model(tipo)) + set_properties(model, properties) + name = properties['Name'] + self.model.insertByName(name, model) + control = self.obj.getControl(name) + add_listeners(self.events, control, name) + control = get_custom_class(tipo, control) + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + return + + def center(self, control, x=0, y=0): + w = self.width + h = self.height + + if isinstance(control, tuple): + wt = SEPARATION * -1 + for c in control: + wt += c.width + SEPARATION + x = w / 2 - wt / 2 + for c in control: + c.x = x + x = c.x + c.width + 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 + + +class LOWindow(object): + EMPTY = b""" + +""" + + def __init__(self, **kwargs): + self._events = None + self._menu = None + self._container = None + self._id_extension = '' + self._obj = self._create(kwargs) + + @property + def id_extension(self): + return self._id_extension + @id_extension.setter + def id_extension(self, value): + global ID_EXTENSION + ID_EXTENSION = value + self._id_extension = value + + 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): + # ~ toolkit = self._window.getToolkit() + 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) + with get_temp_file() as f: + f.write(self.EMPTY) + f.flush() + subcont = cwp.createContainerWindow( + _path_url(f.name), '', self._container.getPeer(), None) + + # ~ service = 'com.sun.star.awt.UnoControlDialog' + # ~ subcont2 = create_instance(service, True) + # ~ service = 'com.sun.star.awt.UnoControlDialogModel' + # ~ model = create_instance(service, True) + # ~ service = 'com.sun.star.awt.UnoControlContainer' + # ~ context = create_instance(service, True) + # ~ subcont2.setModel(model) + # ~ subcont2.setContext(context) + # ~ subcont2.createPeer(self._toolkit, self._container.getPeer()) + + subcont.setPosSize(0, 0, 500, 500, POSSIZE) + subcont.setVisible(True) + self._container.addControl('subcont', subcont) + self._subcont = subcont + return + + def _get_base_control(self, tipo): + services = { + 'label': 'com.sun.star.awt.UnoControlFixedText', + 'button': 'com.sun.star.awt.UnoControlButton', + 'text': 'com.sun.star.awt.UnoControlEdit', + 'listbox': 'com.sun.star.awt.UnoControlListBox', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlink', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmap', + 'image': 'com.sun.star.awt.UnoControlImageControl', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBox', + 'radio': 'com.sun.star.awt.UnoControlRadioButton', + 'tree': 'com.sun.star.awt.tree.TreeControl', + 'grid': 'com.sun.star.awt.grid.UnoControlGrid', + 'tab': 'com.sun.star.awt.tab.UnoControlTabPage', + } + return services[tipo] + + def _special_properties(self, tipo, properties): + columns = properties.pop('Columns', ()) + if tipo == 'grid': + properties['ColumnModel'] = self._set_column_model(columns) + elif tipo == 'button' and 'ImageURL' in properties: + properties['ImageURL'] = _set_image_url( + properties['ImageURL'], self.id_extension) + elif tipo == 'roadmap': + if not 'Height' in properties: + properties['Height'] = self.height + if 'Title' in properties: + properties['Text'] = properties.pop('Title') + elif tipo == 'tab': + if not 'Width' in properties: + properties['Width'] = self.width - 20 + if not 'Height' in properties: + properties['Height'] = self.height - 20 + + return properties + + def add_control(self, properties): + tipo = properties.pop('Type').lower() + root = properties.pop('Root', '') + sheets = properties.pop('Sheets', ()) + + properties = self._special_properties(tipo, properties) + model = self._subcont.Model.createInstance(get_control_model(tipo)) + set_properties(model, properties) + name = properties['Name'] + self._subcont.Model.insertByName(name, model) + control = self._subcont.getControl(name) + add_listeners(self.events, control, name) + control = get_custom_class(tipo, control) + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'tab' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + 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_menu(self, menus): + self._create_menu(menus) + 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 + + @property + def name(self): + return self._title.lower().replace(' ', '_') + + @property + def events(self): + return self._events + @events.setter + def events(self, value): + self._events = value + self._add_listeners() + + @property + def width(self): + return self._container.Size.Width + + @property + def height(self): + return self._container.Size.Height + + def open(self): + self._window.setVisible(True) + return + + def close(self): + self._window.setMenuBar(None) + self._window.dispose() + self._frame.close(True) + return + + +# ~ Python >= 3.7 +# ~ def __getattr__(name): + + +def _get_class_doc(obj): + classes = { + 'calc': LOCalc, + 'writer': LOWriter, + 'base': LOBase, + 'impress': LOImpress, + 'draw': LODraw, + 'math': LOMath, + 'basic': LOBasicIde, + } + type_doc = get_type_doc(obj) + return classes[type_doc](obj) + + +# ~ Export ok +def get_document(title=''): + doc = None + desktop = get_desktop() + if not title: + doc = _get_class_doc(desktop.getCurrentComponent()) + return doc + + for d in desktop.getComponents(): + if hasattr(d, 'Title') and d.Title == title: + doc = d + break + + if doc is None: + return + + return _get_class_doc(doc) + + +def get_documents(custom=True): + docs = [] + desktop = get_desktop() + for doc in desktop.getComponents(): + if custom: + docs.append(_get_class_doc(doc)) + else: + docs.append(doc) + return docs + + +def get_selection(): + return get_document().selection + + +def get_cell(*args): + if args: + index = args + if len(index) == 1: + index = args[0] + cell = get_document().get_cell(index) + else: + cell = get_selection().first + return cell + + +def active_cell(): + return get_cell() + + +def active_sheet(): + return get_document().active + + +def create_dialog(properties): + return LODialog(**properties) + + +def create_window(kwargs): + return LOWindow(**kwargs) + + +# ~ Export ok +def get_config_path(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 _path_system(getattr(path, name)) + + +def get_path_python(): + return sys.executable + + # ~ path = get_config_path('Module') + # ~ if IS_MAC: + # ~ path = join(path, '..', 'Resources', PYTHON) + # ~ else: + # ~ path = join(path, PYTHON) + + # ~ cmd = '"{}" -V'.format(path) + # ~ if run(cmd, True): + # ~ return path + + # ~ path = PYTHON + # ~ cmd = '"{}" -V'.format(path) + # ~ result = run(cmd, True) + + # ~ if 'Python 3' in result: + # ~ return path + + # ~ path = PYTHON + '3' + # ~ cmd = '"{}" -V'.format(path) + # ~ result = run(cmd, True) + + # ~ if 'Python 3' in result: + # ~ return path + + # ~ return '' + + +# ~ Export ok +def get_file(init_dir='', multiple=False, filters=()): + """ + init_folder: folder default open + multiple: True for multiple selected + filters: Example + ( + ('XML', '*.xml'), + ('TXT', '*.txt'), + ) + """ + if not init_dir: + init_dir = get_config_path() + init_dir = _path_url(init_dir) + file_picker = create_instance('com.sun.star.ui.dialogs.FilePicker') + file_picker.setTitle(_('Select file')) + file_picker.setDisplayDirectory(init_dir) + file_picker.setMultiSelectionMode(multiple) + + path = '' + if filters: + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + if file_picker.execute(): + path = _path_system(file_picker.getSelectedFiles()[0]) + if multiple: + path = [_path_system(f) for f in file_picker.getSelectedFiles()] + + return path + + +# ~ Export ok +def get_path(init_dir='', filters=()): + """ + Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html + filters: Example + ( + ('XML', '*.xml'), + ('TXT', '*.txt'), + ) + """ + if not init_dir: + init_dir = get_config_path() + init_dir = _path_url(init_dir) + file_picker = create_instance('com.sun.star.ui.dialogs.FilePicker') + file_picker.setTitle(_('Select file')) + file_picker.setDisplayDirectory(init_dir) + file_picker.initialize((2,)) + if filters: + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + path = '' + if file_picker.execute(): + path = _path_system(file_picker.getSelectedFiles()[0]) + return path + + +# ~ Export ok +def get_dir(init_dir=''): + folder_picker = create_instance('com.sun.star.ui.dialogs.FolderPicker') + if not init_dir: + init_dir = get_config_path() + init_dir = _path_url(init_dir) + folder_picker.setDisplayDirectory(init_dir) + + path = '' + if folder_picker.execute(): + path = _path_system(folder_picker.getDirectory()) + return path + + +# ~ Export ok +def get_info_path(path): + path, filename = os.path.split(path) + name, extension = os.path.splitext(filename) + return (path, filename, name, extension) + + +# ~ Export ok +def read_file(path, mode='r', array=False): + data = '' + with open(path, mode) as f: + if array: + data = tuple(f.read().splitlines()) + else: + data = f.read() + return data + + +# ~ Export ok +def save_file(path, mode='w', data=None): + with open(path, mode) as f: + f.write(data) + return + + +# ~ Export ok +def to_json(path, data): + with open(path, 'w') as f: + f.write(json.dumps(data, indent=4, sort_keys=True)) + return + + +# ~ Export ok +def from_json(path): + with open(path) as f: + data = json.loads(f.read()) + return data + + +# ~ Export ok +def json_dumps(data): + return json.dumps(data, indent=4, sort_keys=True) + + +# ~ Export ok +def json_loads(data): + return json.loads(data) + + +def get_path_extension(id): + path = '' + pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') + try: + path = _path_system(pip.getPackageLocation(id)) + except Exception as e: + error(e) + return path + + +def get_home(): + return Path.home() + + +# ~ Export ok +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(dlg) + + 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 '' + + +# ~ Export ok +def new_doc(type_doc=CALC, **kwargs): + path = 'private:factory/s{}'.format(type_doc) + opt = dict_to_property(kwargs) + doc = get_desktop().loadComponentFromURL(path, '_default', 0, opt) + return _get_class_doc(doc) + + +# ~ Export ok +def new_db(path, name=''): + p, fn, n, e = get_info_path(path) + if not name: + name = n + return LOBase(name, path) + + +# ~ Todo +def exists_db(name): + dbc = create_instance('com.sun.star.sdb.DatabaseContext') + return dbc.hasRegisteredDatabase(name) + + +# ~ Todo +def register_db(name, path): + dbc = create_instance('com.sun.star.sdb.DatabaseContext') + dbc.registerDatabaseLocation(name, _path_url(path)) + return + + +# ~ Todo +def get_db(name): + return LOBase(name) + + +# ~ Export ok +def open_doc(path, **kwargs): + """ 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 = _path_url(path) + opt = dict_to_property(kwargs) + doc = get_desktop().loadComponentFromURL(path, '_default', 0, opt) + if doc is None: + return + + return _get_class_doc(doc) + + +# ~ Export ok +def open_file(path): + if IS_WIN: + os.startfile(path) + else: + pid = subprocess.Popen(['xdg-open', path]).pid + return + + +# ~ Export ok +def join(*paths): + return os.path.join(*paths) + + +# ~ Export ok +def is_dir(path): + return Path(path).is_dir() + + +# ~ Export ok +def is_file(path): + return Path(path).is_file() + + +# ~ Export ok +def get_file_size(path): + return Path(path).stat().st_size + + +# ~ Export ok +def is_created(path): + return is_file(path) and bool(get_file_size(path)) + + +# ~ Export ok +def replace_ext(path, extension): + path, _, name, _ = get_info_path(path) + return '{}/{}.{}'.format(path, name, extension) + + +# ~ Export ok +def zip_content(path): + with zipfile.ZipFile(path) as z: + names = z.namelist() + return names + + +def popen(command, stdin=None): + try: + proc = subprocess.Popen(shlex.split(command), shell=IS_WIN, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + for line in proc.stdout: + yield line.decode().rstrip() + except Exception as e: + error(e) + yield (e.errno, e.strerror) + + +def url_open(url, options={}, verify=True, json=False): + data = '' + err = '' + req = Request(url) + try: + if verify: + response = urlopen(req) + else: + context = ssl._create_unverified_context() + response = urlopen(req, context=context) + except HTTPError as e: + error(e) + err = str(e) + except URLError as e: + error(e.reason) + err = str(e.reason) + else: + if json: + data = json_loads(response.read()) + else: + data = response.read() + + return data, err + + +def run(command, wait=False): + try: + if wait: + result = subprocess.check_output(command, shell=True) + else: + p = subprocess.Popen(shlex.split(command), stdin=None, + stdout=None, stderr=None, close_fds=True) + result, er = p.communicate() + except subprocess.CalledProcessError as e: + msg = ("%s\nrun [ERROR]: output = %s, error code = %s\n" + % (command, e.output, e.returncode)) + error(msg) + return False + + if result is None: + return True + + return result.decode() + + +def _zippwd(source, target, pwd): + if IS_WIN: + return False + if not exists_app('zip'): + return False + + cmd = 'zip' + opt = '-j ' + args = "{} --password {} ".format(cmd, pwd) + + if isinstance(source, (tuple, list)): + if not target: + return False + args += opt + target + ' ' + ' '.join(source) + else: + if is_file(source) and not target: + target = replace_ext(source, 'zip') + elif is_dir(source) and not target: + target = join(PurePath(source).parent, + '{}.zip'.format(PurePath(source).name)) + opt = '-r ' + args += opt + target + ' ' + source + + result = run(args, True) + if not result: + return False + + return is_created(target) + + +# ~ Export ok +def zip_files(source, target='', mode='w', pwd=''): + if pwd: + return _zippwd(source, target, pwd) + + if isinstance(source, (tuple, list)): + if not target: + return False + + with zipfile.ZipFile(target, mode, compression=zipfile.ZIP_DEFLATED) as z: + for path in source: + _, name, _, _ = get_info_path(path) + z.write(path, name) + + return is_created(target) + + if is_file(source): + if not target: + target = replace_ext(source, 'zip') + z = zipfile.ZipFile(target, mode, compression=zipfile.ZIP_DEFLATED) + _, name, _, _ = get_info_path(source) + z.write(source, name) + z.close() + return is_created(target) + + if not target: + target = join( + PurePath(source).parent, + '{}.zip'.format(PurePath(source).name)) + z = zipfile.ZipFile(target, mode, compression=zipfile.ZIP_DEFLATED) + root_len = len(os.path.abspath(source)) + for root, dirs, files in os.walk(source): + relative = os.path.abspath(root)[root_len:] + for f in files: + fullpath = join(root, f) + file_name = join(relative, f) + z.write(fullpath, file_name) + z.close() + + return is_created(target) + + +# ~ Export ok +def unzip(source, path='', members=None, pwd=None): + if not path: + path, _, _, _ = get_info_path(source) + 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 + + +# ~ Export ok +def merge_zip(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 + + +# ~ Export ok +def kill(path): + p = Path(path) + try: + if p.is_file(): + p.unlink() + elif p.is_dir(): + shutil.rmtree(path) + except OSError as e: + log.error(e) + return + + +def get_size_screen(): + if IS_WIN: + user32 = ctypes.windll.user32 + res = '{}x{}'.format(user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)) + else: + args = 'xrandr | grep "*" | cut -d " " -f4' + res = run(args, True) + return res.strip() + + +def get_clipboard(): + df = None + text = '' + sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') + transferable = sc.getContents() + data = transferable.getTransferDataFlavors() + for df in data: + if df.MimeType == CLIPBOARD_FORMAT_TEXT: + break + if df: + text = transferable.getTransferData(df) + return text + + +class TextTransferable(unohelper.Base, XTransferable): + """Keep clipboard data and provide them.""" + + def __init__(self, text): + df = DataFlavor() + df.MimeType = CLIPBOARD_FORMAT_TEXT + df.HumanPresentableName = "encoded text utf-16" + self.flavors = [df] + self.data = [text] + + def getTransferData(self, flavor): + if not flavor: + return + for i, f in enumerate(self.flavors): + if flavor.MimeType == f.MimeType: + return self.data[i] + return + + def getTransferDataFlavors(self): + return tuple(self.flavors) + + def isDataFlavorSupported(self, flavor): + if not flavor: + return False + mtype = flavor.MimeType + for f in self.flavors: + if mtype == f.MimeType: + return True + return False + + +# ~ Export ok +def set_clipboard(value): + ts = TextTransferable(value) + sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') + sc.setContents(ts, None) + return + + +# ~ Export ok +def copy(doc): + call_dispatch(doc, '.uno:Copy') + return + + +def paste(doc): + call_dispatch(doc, '.uno:Paste') + return + + +# ~ Export ok +def get_epoch(): + n = now() + return int(time.mktime(n.timetuple())) + + +# ~ Export ok +def file_copy(source, target='', name=''): + p, f, n, e = get_info_path(source) + if target: + p = target + if name: + e = '' + n = name + path_new = join(p, '{}{}'.format(n, e)) + shutil.copy(source, path_new) + return path_new + + +def get_path_content(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 += [join(folder, f) for f in files if pattern.search(f)] + else: + paths += files + return paths + + +def _get_menu(type_doc, name_menu): + instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' + service = TYPE_DOC[type_doc] + manager = create_instance(instance, True) + ui = manager.getUIConfigurationManager(service) + menus = ui.getSettings(NODE_MENUBAR, True) + command = MENUS_APP[type_doc][name_menu] + for menu in menus: + data = property_to_dict(menu) + if data.get('CommandURL', '') == command: + idc = data.get('ItemDescriptorContainer', None) + return ui, menus, idc + return None, None, None + + +def _get_index_menu(menu, command): + for i, m in enumerate(menu): + data = property_to_dict(m) + cmd = data.get('CommandURL', '') + if cmd == command: + return i + # ~ submenu = data.get('ItemDescriptorContainer', None) + # ~ if not submenu is None: + # ~ get_index_menu(submenu, command, count + 1) + return 0 + + +def _store_menu(ui, menus, menu, index, data=(), remove=False): + if remove: + uno.invoke(menu, 'removeByIndex', (index,)) + else: + properties = dict_to_property(data, True) + uno.invoke(menu, 'insertByIndex', (index + 1, properties)) + ui.replaceSettings(NODE_MENUBAR, menus) + ui.store() + return + + +def insert_menu(type_doc, name_menu, **kwargs): + ui, menus, menu = _get_menu(type_doc, name_menu.lower()) + if menu is None: + return 0 + + label = kwargs.get('Label', '-') + separator = False + if label == '-': + separator = True + command = kwargs.get('CommandURL', '') + index = kwargs.get('Index', 0) + if not index: + index = _get_index_menu(menu, kwargs['After']) + if separator: + data = {'Type': 1} + _store_menu(ui, menus, menu, index, data) + return index + 1 + + index_menu = _get_index_menu(menu, command) + if index_menu: + msg = 'Exists: %s' % command + debug(msg) + return 0 + + sub_menu = kwargs.get('Submenu', ()) + idc = None + if sub_menu: + idc = ui.createSettings() + + data = { + 'CommandURL': command, + 'Label': label, + 'Style': 0, + 'Type': 0, + 'ItemDescriptorContainer': idc + } + _store_menu(ui, menus, menu, index, data) + if sub_menu: + _add_sub_menus(ui, menus, idc, sub_menu) + return True + + +def _add_sub_menus(ui, menus, menu, sub_menu): + for i, sm in enumerate(sub_menu): + submenu = sm.pop('Submenu', ()) + sm['Type'] = 0 + if submenu: + idc = ui.createSettings() + sm['ItemDescriptorContainer'] = idc + if sm['Label'] == '-': + sm = {'Type': 1} + _store_menu(ui, menus, menu, i - 1, sm) + if submenu: + _add_sub_menus(ui, menus, idc, submenu) + return + + +def remove_menu(type_doc, name_menu, command): + ui, menus, menu = _get_menu(type_doc, name_menu.lower()) + if menu is None: + return False + + index = _get_index_menu(menu, command) + if not index: + debug('Not exists: %s' % command) + return False + + _store_menu(ui, menus, menu, index, remove=True) + return True + + +def _get_app_submenus(menus, count=0): + for i, menu in enumerate(menus): + data = property_to_dict(menu) + cmd = data.get('CommandURL', '') + msg = ' ' * count + '├─' + cmd + debug(msg) + submenu = data.get('ItemDescriptorContainer', None) + if not submenu is None: + _get_app_submenus(submenu, count + 1) + return + + +def get_app_menus(name_app, index=-1): + instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' + service = TYPE_DOC[name_app] + manager = create_instance(instance, True) + ui = manager.getUIConfigurationManager(service) + menus = ui.getSettings(NODE_MENUBAR, True) + if index == -1: + for menu in menus: + data = property_to_dict(menu) + debug(data.get('CommandURL', '')) + else: + menus = property_to_dict(menus[index])['ItemDescriptorContainer'] + _get_app_submenus(menus) + return menus + + +# ~ Export ok +def start(): + global _start + _start = now() + log.info(_start) + return + + +# ~ Export ok +def end(): + global _start + e = now() + return str(e - _start).split('.')[0] + + +# ~ Export ok +# ~ https://en.wikipedia.org/wiki/Web_colors +def get_color(*value): + if len(value) == 1 and isinstance(value[0], int): + return value[0] + if len(value) == 1 and isinstance(value[0], tuple): + value = value[0] + + 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 len(value) == 3: + color = (value[0] << 16) + (value[1] << 8) + value[2] + else: + value = value[0] + 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') + + +# ~ Export ok +def render(template, data): + s = Template(template) + return s.safe_substitute(**data) + + +def _to_date(value): + new_value = value + if isinstance(value, Time): + new_value = datetime.time(value.Hours, value.Minutes, value.Seconds) + elif isinstance(value, Date): + new_value = datetime.date(value.Year, value.Month, value.Day) + elif isinstance(value, DateTime): + new_value = datetime.datetime( + value.Year, value.Month, value.Day, + value.Hours, value.Minutes, value.Seconds) + return new_value + + +def date_to_struct(value): + # ~ print(type(value)) + if isinstance(value, datetime.datetime): + d = DateTime() + d.Seconds = value.second + d.Minutes = value.minute + d.Hours = value.hour + d.Day = value.day + d.Month = value.month + d.Year = value.year + elif isinstance(value, datetime.date): + d = Date() + d.Day = value.day + d.Month = value.month + d.Year = value.year + return d + + +# ~ Export ok +def format(template, data): + """ + https://pyformat.info/ + """ + if isinstance(data, (str, int, float)): + # ~ print(template.format(data)) + return template.format(data) + + if isinstance(data, (Time, Date, DateTime)): + return template.format(_to_date(data)) + + if isinstance(data, tuple) and isinstance(data[0], tuple): + data = {r[0]: _to_date(r[1]) for r in data} + return template.format(**data) + + data = [_to_date(v) for v in data] + result = template.format(*data) + return result + + +def _get_url_script(macro): + macro['language'] = macro.get('language', 'Python') + macro['location'] = macro.get('location', 'user') + data = macro.copy() + if data['language'] == 'Python': + data['module'] = '.py$' + elif data['language'] == 'Basic': + data['module'] = '.{}.'.format(macro['module']) + if macro['location'] == 'user': + data['location'] = 'application' + else: + data['module'] = '.' + + url = 'vnd.sun.star.script:{library}{module}{name}?language={language}&location={location}' + path = url.format(**data) + return path + + +def _call_macro(macro): + #~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification + name = 'com.sun.star.script.provider.MasterScriptProviderFactory' + factory = create_instance(name, False) + + macro['language'] = macro.get('language', 'Python') + macro['location'] = macro.get('location', 'user') + data = macro.copy() + if data['language'] == 'Python': + data['module'] = '.py$' + elif data['language'] == 'Basic': + data['module'] = '.{}.'.format(macro['module']) + if macro['location'] == 'user': + data['location'] = 'application' + else: + data['module'] = '.' + + args = macro.get('args', ()) + url = 'vnd.sun.star.script:{library}{module}{name}?language={language}&location={location}' + path = url.format(**data) + + script = factory.createScriptProvider('').getScript(path) + return script.invoke(args, None, None)[0] + + +# ~ Export ok +def call_macro(macro): + in_thread = macro.pop('thread') + if in_thread: + t = threading.Thread(target=_call_macro, args=(macro,)) + t.start() + return + + return _call_macro(macro) + + +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 + + +# ~ Export ok +def timer(name, seconds, macro): + global _stop_thread + _stop_thread[name] = threading.Event() + thread = TimerThread(_stop_thread[name], seconds, macro) + thread.start() + return + + +# ~ Export ok +def stop_timer(name): + global _stop_thread + _stop_thread[name].set() + del _stop_thread[name] + return + + +def _get_key(password): + digest = hashlib.sha256(password.encode()).digest() + key = base64.urlsafe_b64encode(digest) + return key + + +# ~ Export ok +def encrypt(data, password): + f = Fernet(_get_key(password)) + token = f.encrypt(data).decode() + return token + + +# ~ Export ok +def decrypt(token, password): + data = '' + f = Fernet(_get_key(password)) + try: + data = f.decrypt(token.encode()).decode() + except InvalidToken as e: + error('Invalid Token') + return data + + +class SmtpServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._sender = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + name = config['server'] + port = config['port'] + is_ssl = config['ssl'] + self._sender = config['user'] + hosts = ('gmail' in name or 'outlook' in name) + try: + if is_ssl and hosts: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + self._server.ehlo() + self._server.starttls() + self._server.ehlo() + elif is_ssl: + self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT) + self._server.ehlo() + else: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + + self._server.login(self._sender, config['pass']) + 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, _, _ = get_info_path(path) + part = MIMEBase('application', 'octet-stream') + part.set_payload(read_file(path, 'rb')) + encoders.encode_base64(part) + part.add_header('Content-Disposition', file_name.format(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 + + +def server_smtp_test(config): + with SmtpServer(config) as server: + if server.error: + error(server.error) + return server.error + + +def import_csv(path, **kwargs): + """ + See https://docs.python.org/3.5/library/csv.html#csv.reader + """ + with open(path) as f: + rows = tuple(csv.reader(f, **kwargs)) + return rows + + +def export_csv(path, data, **kwargs): + with open(path, 'w') as f: + writer = csv.writer(f, **kwargs) + writer.writerows(data) + return + + +def install_locales(path, domain='base', dir_locales=DIR['locales']): + p, *_ = get_info_path(path) + path_locales = join(p, 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 _ + + +class LIBOServer(object): + HOST = 'localhost' + PORT = '8100' + ARG = 'socket,host={},port={};urp;StarOffice.ComponentContext'.format(HOST, PORT) + CMD = ['soffice', + '-env:SingleAppInstance=false', + '-env:UserInstallation=file:///tmp/LIBO_Process8100', + '--headless', '--norestore', '--invisible', + '--accept={}'.format(ARG)] + + def __init__(self): + self._server = None + self._ctx = None + self._sm = None + self._start_server() + self._init_values() + + def _init_values(self): + global CTX + global SM + + if not self.is_running: + return + + ctx = uno.getComponentContext() + service = 'com.sun.star.bridge.UnoUrlResolver' + resolver = ctx.ServiceManager.createInstanceWithContext(service, ctx) + self._ctx = resolver.resolve('uno:{}'.format(self.ARG)) + self._sm = self._ctx.getServiceManager() + CTX = self._ctx + SM = self._sm + return + + @property + def is_running(self): + try: + s = socket.create_connection((self.HOST, self.PORT), 5.0) + s.close() + debug('LibreOffice is running...') + return True + except ConnectionRefusedError: + return False + + def _start_server(self): + if self.is_running: + return + + for i in range(3): + self._server = subprocess.Popen(self.CMD, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + time.sleep(3) + if self.is_running: + break + return + + def stop(self): + if self._server is None: + print('Search pgrep soffice') + else: + self._server.terminate() + debug('LibreOffice is stop...') + return + + def create_instance(self, name, with_context=True): + if with_context: + instance = self._sm.createInstanceWithContext(name, self._ctx) + else: + instance = self._sm.createInstance(name) + return instance diff --git a/source/easymacro2.py b/source/easymacro2.py deleted file mode 100644 index ef7af45..0000000 --- a/source/easymacro2.py +++ /dev/null @@ -1,6615 +0,0 @@ -#!/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') -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