From 233a7c1fbf83856787c218e5f34c8e15ff804ac8 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Thu, 12 Nov 2020 15:33:31 -0600 Subject: [PATCH 01/12] Update zaz and easymacro --- CHANGELOG | 6 +- conf.py | 2 +- easymacro.py | 7555 ++++++++++++++++++++-------------------------- source/ZAZPip.py | 8 +- zaz.py | 79 +- 5 files changed, 3385 insertions(+), 4265 deletions(-) mode change 100644 => 100755 zaz.py diff --git a/CHANGELOG b/CHANGELOG index d1e9ecc..e5090d9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,5 @@ -v 0.5.0 [0-mar-2020] - - +v 0.5.0 [09-jul-2020] + - Test install pandas v 0.4.0 [10-mar-2020] - Update easymacro.py @@ -12,11 +12,9 @@ v 0.3.0 [12-nov-2019] v 0.2.0 [18-oct-2019] ---------------------- - Add spanish v 0.1.0 [18-oct-2019] ---------------------- - Initial version diff --git a/conf.py b/conf.py index bf0095a..b3dca5c 100644 --- a/conf.py +++ b/conf.py @@ -36,7 +36,7 @@ ID = 'net.elmau.zaz.pip' # ~ If you extension will be multilanguage set: True # ~ This feature used gettext, set pythonpath and easymacro in True -# ~ Yu can used PoEdit for edit PO files and generate MO files. +# ~ You can used PoEdit for edit PO files and generate MO files. # ~ https://poedit.net/ USE_LOCALES = True DOMAIN = 'base' diff --git a/easymacro.py b/easymacro.py index 30ea714..cdef714 100644 --- a/easymacro.py +++ b/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,22 +33,25 @@ import re import shlex import shutil import socket -import subprocess 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, PurePath +from pathlib import Path from pprint import pprint +from string import Template +from typing import Any from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError -from string import Template -from subprocess import PIPE import smtplib from smtplib import SMTPException, SMTPAuthenticationError @@ -61,150 +64,38 @@ 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.awt import Rectangle, Size, Point +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.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA -from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK +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.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 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 +# ~ 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 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('Install peewee') -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 +105,135 @@ 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 + +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) + +_MACROS = {} +_start = 0 + SECONDS_DAY = 60 * 60 * 24 +DIR = { + 'images': 'images', + 'locales': 'locales', +} +DEFAULT_MIME_TYPE = 'png' +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', +} + +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 +255,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 @@ -291,47 +306,22 @@ def catch_exception(f): 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 +336,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 +344,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 +361,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,183 +378,608 @@ 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(url, args=()): - frame = get_document().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 _get_class_uno(obj: Any) -> Any: + # ~ classes = dict( + # ~ SwXTextGraphicObject = LOImage, + # ~ SvxShapeText = LOImage, + # ~ ) + # ~ name = obj.ImplementationName + # ~ print(f'ImplementationName = {name}') + # ~ instance = obj + # ~ if name in classes: + # ~ instance = classes[name](obj) + # ~ return instance + + +def dict_to_property(values: dict, uno_any: bool=False): ps = tuple([PropertyValue(Name=n, Value=v) for n, v in values.items()]) if uno_any: 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 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 _path_url(path: str) -> str: + if path.startswith('file://'): + return path + return uno.systemPathToFileUrl(path) + + +def _get_dispatch() -> Any: + return create_instance('com.sun.star.frame.DispatchHelper') + + +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 array_to_dict(values): - d = {r[0]: r[1] for r in values} +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 -# ~ Custom classes -class ObjectBase(object): +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) + if capture: + result = result.stdout + else: + result = result.returncode + return result + + +# ~ def popen(command, stdin=None): + # ~ try: + # ~ proc = subprocess.Popen(shlex.split(command), shell=IS_WIN, + # ~ stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + # ~ for line in proc.stdout: + # ~ yield line.decode().rstrip() + # ~ except Exception as e: + # ~ error(e) + # ~ yield (e.errno, e.strerror) + + +def 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 + + +class SmtpServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._sender = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + name = config['server'] + port = config['port'] + is_ssl = config['ssl'] + self._sender = config['user'] + hosts = ('gmail' in name or 'outlook' in name) + try: + if is_ssl and hosts: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + self._server.ehlo() + self._server.starttls() + self._server.ehlo() + elif is_ssl: + self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT) + self._server.ehlo() + else: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + + self._server.login(self._sender, config['password']) + msg = 'Connect to: {}'.format(name) + debug(msg) + return True + except smtplib.SMTPAuthenticationError as e: + if '535' in str(e): + self._error = _('Incorrect user or password') + return False + if '534' in str(e) and 'gmail' in name: + self._error = _('Allow less secure apps in GMail') + return False + except smtplib.SMTPException as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def _body(self, msg): + body = msg.replace('\\n', '
') + return body + + def send(self, message): + file_name = 'attachment; filename={}' + email = MIMEMultipart() + email['From'] = self._sender + email['To'] = message['to'] + email['Cc'] = message.get('cc', '') + email['Subject'] = message['subject'] + email['Date'] = formatdate(localtime=True) + if message.get('confirm', False): + email['Disposition-Notification-To'] = email['From'] + email.attach(MIMEText(self._body(message['body']), 'html')) + + for path in message.get('files', ()): + fn = _P(path).file_name + part = MIMEBase('application', 'octet-stream') + part.set_payload(_P.read_bin(path)) + encoders.encode_base64(part) + part.add_header('Content-Disposition', f'attachment; filename={fn}') + email.attach(part) + + receivers = ( + email['To'].split(',') + + email['CC'].split(',') + + message.get('bcc', '').split(',')) + try: + self._server.sendmail(self._sender, receivers, email.as_string()) + msg = 'Email sent...' + debug(msg) + if message.get('path', ''): + self.save_message(email, message['path']) + return True + except Exception as e: + self._error = str(e) + return False + return False + + def save_message(self, email, path): + mbox = mailbox.mbox(path, create=True) + mbox.lock() + try: + msg = mailbox.mboxMessage(email) + mbox.add(msg) + mbox.flush() + finally: + mbox.unlock() + return + + def close(self): + try: + self._server.quit() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + +def _send_email(server, messages): + with SmtpServer(server) as server: + if server.is_connect: + for msg in messages: + server.send(msg) + else: + error(server.error) + return server.error + + +def send_email(server, message): + messages = message + if isinstance(message, dict): + messages = (message,) + t = threading.Thread(target=_send_email, args=(server, messages)) + t.start() + return + + +# ~ Classes + +class LOBaseObject(object): 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'): + 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] + @property + def obj(self): + return self._obj - 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 + +class LOImage(object): + TYPE = { + 'png': 'image/png', + 'jpg': 'image/jpeg', + } + + def __init__(self, obj): + self._obj = obj @property def obj(self): return self._obj - @obj.setter - def obj(self, value): - self._obj = value + + @property + def name(self): + return self.obj.Name or 'img' + + @property + def mimetype(self): + return self.obj.Bitmap.MimeType + + def save(self, path, mimetype=DEFAULT_MIME_TYPE): + p = _P(path) + if _P.is_dir(path): + name = self.name + else: + path = p.path + name = p.name + + path = _P.join(path, f'{name}.{mimetype.lower()}') + args = dict( + URL = _P.to_url(path), + MimeType = self.TYPE[mimetype], + ) + if not _export_image(self.obj, args): + path = '' + + # ~ size = len(self.obj.Bitmap.DIB) + # ~ data = self.obj.GraphicStream.readBytes((), size) + # ~ data = data[-1].value + + # ~ data = self.obj.Bitmap.DIB.value + # ~ data = self.obj.Graphic.DIB.value + + # ~ _P.save_bin(path, data) + return path -class LOObjectBase(object): +class LODocument(object): + FILTERS = { + 'doc': 'MS Word 97', + 'docx': 'MS Word 2007 XML', + } def __init__(self, obj): - self.__dict__['_obj'] = 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): - 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 + self.close() @property def obj(self): @@ -628,12 +993,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): @@ -653,19 +1018,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 @@ -675,6 +1052,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') @@ -684,344 +1086,118 @@ 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 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() - - -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', + 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, } - return types[name] + 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 _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) + def save(self, path: str='', args: dict={}) -> bool: + result = True + opt = dict_to_property(args) + if path: + try: + self.obj.storeAsURL(_path_url(path), opt) + except Exception as e: + error(e) + result = False else: - self.obj.removeByName(index) + self.obj.store() + return result + + def close(self): + self.obj.close(True) 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 obj(self): - return self._obj - - @property - def address(self): - return self.obj.Anchor.AbsoluteName - - @property - def name(self): - return self.obj.Name - - @property - def mimetype(self): - return self.obj.Bitmap.MimeType - - @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() + self._type = CALC + self._sheets = obj.Sheets 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) + 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.obj.Sheets + 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): @@ -1038,90 +1214,62 @@ class LOCalc(LODocument): self._cc.SheetTabs = value @property - def active(self): - return LOCalcSheet(self._cc.getActiveSheet(), self) + 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[sheet].obj + obj = self._sheets[sheet] 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 + def new_sheet(self): + s = self.create_instance('com.sun.star.sheet.Spreadsheet') + return s - @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 + def insert(self, name): + names = name 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) + names = (name,) + for n in names: + self._sheets[n] = self.new_sheet() + return LOCalcSheet(self._sheets[n]) 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 + index = len(self) + if isinstance(name, LOCalcSheet): + name = name.name + self._sheets.moveByName(name, index) + return - names = source - if not names: - names = self.names - elif isinstance(source, str): - names = (source,) + def remove(self, name): + if isinstance(name, LOCalcSheet): + name = name.name + self._sheets.removeByName(name) + return - 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(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 = self._sheets.Count + pos + 1 + index = len(self) names = source - if not names: + if not source: names = doc.names elif isinstance(source, str): names = (source,) @@ -1132,118 +1280,37 @@ class LOCalc(LODocument): 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] + for i, name in enumerate(names): + self._sheets.importSheet(doc.obj, name, index + i) + self[index + i].name = new_names[i] - # ~ doc.getCurrentController().setActiveSheet(sheet) - # ~ For controls in sheet - # ~ doc.getCurrentController().setFormDesignMode(False) - - return LOCalcSheet(self._sheets[index], self) + return LOCalcSheet(self._sheets[index]) def sort(self, reverse=False): names = sorted(self.names, reverse=reverse) for i, n in enumerate(names): - self.sheets.move(n, i) + self.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 render(self, data, sheet=None, clean=True): + if sheet is None: + sheet = self.active + return sheet.render(data, clean=clean) -class LOCalcSheets(object): +class LOChart(object): - def __init__(self, obj, doc): + def __init__(self, name, obj, draw_page): + self._name = name self._obj = obj - self._doc = doc + 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 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) + return LOBaseObject(self.diagram.getDataRowProperties(index)) def __enter__(self): return self @@ -1251,22 +1318,201 @@ 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} - @property def obj(self): return self._obj @property - def doc(self): - return self._doc + def name(self): + return self._name @property - def images(self): - return self._images + 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): + + def __init__(self, obj): + self._obj = obj + self._control = self.doc.CurrentController.getControl(self.obj) + + def __setattr__(self, name, value): + if name == '_control': + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def doc(self): + return self.obj.Parent.Parent.Parent + + @property + def name(self): + return self.obj.Name + + @property + def label(self): + return self.obj.Label + + def set_focus(self): + self._control.setFocus() + return + + +class LOForm(object): + + def __init__(self, obj): + self._obj = obj + + def __getitem__(self, index): + return LOFormControl(self.obj[index]) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + @property + def obj(self): + return self._obj + + +class LOSheetForms(object): + + def __init__(self, obj): + self._obj = obj + + def __getitem__(self, index): + return LOForm(self.obj[index]) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + @property + def obj(self): + return self._obj + + +class LOSheetRows(object): + + def __init__(self, sheet): + self._sheet = sheet + self._obj = sheet.obj.Rows + + def __getitem__(self, index): + return LOSheetRows(self.obj[index]) + + @property + def obj(self): + return self._obj + + def insert(self, index, count): + self.obj.insertByIndex(index, count) + end = index + count + return self._sheet[index:end,0:] + + +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): @@ -1282,27 +1528,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): @@ -1323,172 +1554,575 @@ 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) @property def forms(self): - return LOForms(self._dp.getForms(), self.doc) + return LOSheetForms(self.obj.DrawPage.Forms) - @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') + 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 -class LOWriter(LODocument): + 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) + + +class LOCalcRows(object): def __init__(self, obj): - super().__init__(obj) + self._obj = obj + + def __len__(self): + return self.obj.Count + + def __str__(self): + return 'Rows' @property def obj(self): return self._obj @property - def string(self): - return self._obj.getText().String + def count(self): + return len(self) @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 + def visible(self): + return self.obj.IsVisible + @visible.setter + def visible(self, value): + self.obj.IsVisible = value -class LOTextRange(object): +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 __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 LOCalcRows(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) + + def select(self): + self.doc.select(self.obj) + return + + 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_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 auto_width(self): + self.obj.Columns.OptimalWidth = True + return + + def clean_render(self, template='\{(\w.+)\}'): + self._sd.SearchRegularExpression = True + self._sd.setSearchString(template) + self.obj.replaceAll(self._sd) + return + + def render(self, data, clean=True): + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + 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_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 filter_by_color(self, cell): + rangos = cell.column[1:,:].visible + for r in rangos: + for c in r: + if c.back_color != cell.back_color: + c.rows.visible = False + return + + def clear(self, what=1023): + # ~ http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html + self.obj.clearContents(what) + return + + +class LOWriterPageStyle(LOBaseObject): + + 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' @@ -1497,17 +2131,20 @@ class LOTextRange(object): return self._obj @property - def is_paragraph(self): - return self._is_paragraph + def string(self): + return self.obj.String + @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 string(self): - return self.obj.String - @property def text(self): return self.obj.getText() @@ -1516,9 +2153,444 @@ class LOTextRange(object): 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 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): + return LOWriterTextRange(self.obj[index], self._doc) + + @property + def obj(self): + return self._obj + + +class LOWriter(LODocument): + + def __init__(self, obj): + super().__init__(obj) + self._type = WRITER + + @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) + + +class LOShape(LOBaseObject): + + def __init__(self, obj, index): + self._index = index + super().__init__(obj) + + @property + def type(self): + return 'shape' + + @property + def name(self): + return self.obj.Name or f'shape{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 + + def remove(self): + self.obj.Parent.remove(self.obj) + 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 + + @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 + """ + w = args.get('Width', 3000) + h = args.get('Height', 3000) + x = args.get('X', 1000) + y = args.get('Y', 1000) + + service = f'com.sun.star.drawing.{type_shape}Shape' + shape = self.create_instance(service) + shape.Size = Size(w, h) + shape.Position = Point(x, y) + index = self.count + shape.Name = f'{type_shape.lower()}{index}' + 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 insert_image(self, path, args={}): + w = args.get('Width', 3000) + h = args.get('Height', 3000) + x = args.get('X', 1000) + y = args.get('Y', 1000) + + image = self.create_instance('com.sun.star.drawing.GraphicObjectShape') + image.GraphicURL = _P.to_url(path) + image.Size = Size(w, h) + image.Position = Point(x, y) + index = self.count + image.Name = f'image{index}' + 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.selection + + 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', @@ -1540,40 +2612,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): @@ -1583,25 +2644,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): @@ -1609,10 +2671,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 @@ -1624,508 +2683,209 @@ 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) + 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) + doc = None + for i, doc in enumerate(self._desktop.Components): + if isinstance(index, int) and i == index: + doc = _get_class_doc(doc) + break + elif isinstance(index, str) and doc.Title == index: + doc = _get_class_doc(doc) + break + return doc def __contains__(self, item): - return item.in_range(self) + doc = self[item] + return not doc is None - def _init_values(self): - self._type_obj = self.obj.ImplementationName - self._type_content = EMPTY + def __iter__(self): + self._i = 0 + return self - if self._type_obj == OBJ_CELL: - self._type_content = self.obj.getType() - return + def __next__(self): + doc = self[self._i] + if doc is None: + raise StopIteration + self._i += 1 + return doc + + def __len__(self): + for i, _ in enumerate(self._desktop.Components): + pass + return i + 1 @property - def obj(self): - return self._obj + def active(self): + return _get_class_doc(self._desktop.getCurrentComponent()) - @property - def doc(self): - return self._doc + @classmethod + def new(cls, type_doc=CALC, args={}): + if type_doc == BASE: + return LOBase(None, args) - @property - def type(self): - return self._type_obj + 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) - @property - def type_content(self): - return self._type_content + @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 - @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 + 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(args) + doc = cls._desktop.loadComponentFromURL(path, '_default', 0, opt) + if doc is None: + return - @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) + return _get_class_doc(doc) - @property - def data(self): - return self.obj.getDataArray() - @data.setter - def data(self, values): - self.obj.setDataArray(values) + def connect(self, path): + return LOBase(None, {'path': path}) - @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 +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' - @property - def columns(self): - return self._obj.Columns.Count + 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 - @property - def row(self): - a = self.address - if hasattr(a, 'Row'): - r = a.Row - else: - r = a.StartRow - return r + getattr(control, key)(listeners[key](events, name)) - @property - def rows(self): - return self._obj.Rows.Count + # ~ if is_grid: + # ~ controllers = EventsGrid(events, name) + # ~ control.addSelectionListener(controllers) + # ~ control.Model.GridDataModel.addGridDataListener(controllers) + return - 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('.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 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) - img = self.doc.create_instance('com.sun.star.drawing.GraphicObjectShape') - img.GraphicURL = _path_url(path) - self.draw_page.add(img) - img.Anchor = self.obj - img.setSize(Size(w, h)) - 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_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): @@ -2145,18 +2905,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): @@ -2189,281 +2937,73 @@ 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 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): +class EventsButton(EventsListenerBase, XActionListener): 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) + def actionPerformed(self, event): + event_name = f'{self.name}_action' 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): + CONTROLS = ( + 'stardiv.Toolkit.UnoControlEditModel', + ) 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 + # ~ print('Focus enter', service) + if service in self.CONTROLS: + obj = event.Source.Model + obj.BackgroundColor = COLOR_ON_FOCUS + return 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): + service = event.Source.Model.ImplementationName + if service in self.CONTROLS: + obj = event.Source.Model + obj.BackgroundColor = -1 return +# ~ BorderColor = ? +# ~ FontStyleName = ? +# ~ HelpURL = ? class UnoBaseObject(object): def __init__(self, obj): 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) @property def obj(self): @@ -2472,6 +3012,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): @@ -2479,8 +3029,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() @@ -2517,89 +3186,18 @@ 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) - - @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 tab_stop(self): + return self._model.Tabstop + @tab_stop.setter + def tab_stop(self, value): + self.model.Tabstop = value def center(self, horizontal=True, vertical=False): p = self.parent.Model @@ -2613,7 +3211,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: @@ -2622,13 +3220,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 @@ -2676,6 +3270,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 UnoCheck(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'check' + + @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): @@ -2692,992 +3335,104 @@ class UnoText(UnoBaseObject): def value(self, value): self.model.Text = value - def validate(self): - return - - -class UnoListBox(UnoBaseObject): +class UnoImage(UnoBaseObject): def __init__(self, obj): super().__init__(obj) @property def type(self): - return 'listbox' + return 'image' @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 + return self.url @value.setter def value(self, value): - self.obj.String = value + self.url = 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) + def url(self): + return self.m.ImageURL + @url.setter + def url(self, value): + self.m.ImageURL = None + self.m.ImageURL = _P.to_url(value) + + +UNO_CLASSES = { + 'label': UnoLabel, + 'link': UnoLabelLink, + 'button': UnoButton, + 'radio': UnoRadio, + 'check': UnoCheck, + 'text': UnoText, + 'image': UnoImage, +} 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', + 'check': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + # ~ 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + # ~ 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + # ~ 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + # ~ 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + # ~ 'tree': 'com.sun.star.awt.tree.TreeControlModel', + # ~ 'pages': 'com.sun.star.awt.UnoMultiPageModel', + } - 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 = '' - 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(_path_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') 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): - 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 @@ -3687,20 +3442,16 @@ 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 id(self): + return self._id + @id.setter + def id(self, value): + self._id = value + self._path = _P.from_id(value) @property def height(self): @@ -3717,110 +3468,77 @@ 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 - - @property - def step(self): - return self.model.Step - @step.setter - def step(self, value): - self.model.Step = value + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value @property def events(self): 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): + columns = args.pop('Columns', ()) - 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']) + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + elif tipo == 'grid': + args['ColumnModel'] = self._set_column_model(columns) + elif tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + if not 'FocusOnClick' in args: + args['FocusOnClick'] = False elif tipo == 'roadmap': - if not 'Height' in properties: - properties['Height'] = self.height - if 'Title' in properties: - properties['Text'] = properties.pop('Title') + if not 'Height' in args: + args['Height'] = self.height + if 'Title' in args: + args['Text'] = args.pop('Title') elif tipo == 'tab': - if not 'Width' in properties: - properties['Width'] = self.width - if not 'Height' in properties: - properties['Height'] = self.height + if not 'Width' in args: + args['Width'] = self.width + if not 'Height' in args: + args['Height'] = self.height - return properties + return args - def add_control(self, properties): - tipo = properties.pop('Type').lower() - root = properties.pop('Root', '') - sheets = properties.pop('Sheets', ()) + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) - properties = self._special_properties(tipo, properties) - model = self.model.createInstance(self._get_control_model(tipo)) - set_properties(model, properties) - name = properties['Name'] + 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 == 'tree' and root: control.root = root @@ -3829,20 +3547,21 @@ class LODialog(object): 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: @@ -3857,488 +3576,691 @@ 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 -class LOWindow(object): - EMPTY = b""" - -""" + def close(self, value=0): + if self._modal: + value = self.obj.endDialog(value) + else: + self.visible = False + self.obj.dispose() + return value - def __init__(self, **kwargs): - self._events = None + +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._container = None - self._id_extension = '' - self._obj = self._create(kwargs) + 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 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 + + +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 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 path(self): + return str(self._path.parent) - 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 file_name(self): + return self._path.name @property def name(self): - return self._title.lower().replace(' ', '_') + return self._path.stem @property - def events(self): - return self._events - @events.setter - def events(self, value): - self._events = value - self._add_listeners() + def ext(self): + return self._path.suffix[1:] @property - def width(self): - return self._container.Size.Width + def info(self): + return self.path, self.file_name, self.name, self.ext @property - def height(self): - return self._container.Size.Height + def url(self): + return self._path.as_uri() - def open(self): - self._window.setVisible(True) - return + @property + def size(self): + return self._path.stat().st_size - def close(self): - self._window.setMenuBar(None) - self._window.dispose() - self._frame.close(True) - return + @classproperty + def home(self): + return str(Path.home()) + @classproperty + def documents(self): + return self.config() -# ~ Python >= 3.7 -# ~ def __getattr__(name): + @classproperty + def temp_dir(self): + return tempfile.gettempdir() + @classproperty + def python(self): + return sys.executable -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) + @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 -# ~ Export ok -def get_document(title=''): - doc = None - desktop = get_desktop() - if not title: - doc = _get_class_doc(desktop.getCurrentComponent()) - return doc + @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)) - for d in desktop.getComponents(): - if hasattr(d, 'Title') and d.Title == title: - doc = d - break + @classmethod + def get(cls, init_dir='', filters=()): + """ + Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html + filters: Example + ( + ('XML', '*.xml'), + ('TXT', '*.txt'), + ) + """ + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + file_picker = create_instance(cls.FILE_PICKER) + file_picker.setTitle(_('Select path')) + file_picker.setDisplayDirectory(init_dir) + file_picker.initialize((2,)) + if filters: + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) - if doc is None: - return + path = '' + if file_picker.execute(): + path = cls.to_system(file_picker.getSelectedFiles()[0]) + return path - return _get_class_doc(doc) + @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 -def get_documents(custom=True): - docs = [] - desktop = get_desktop() - for doc in desktop.getComponents(): - if custom: - docs.append(_get_class_doc(doc)) + @classmethod + def get_file(cls, init_dir='', filters=(), multiple=False): + """ + init_folder: folder default open + multiple: True for multiple selected + filters: Example + ( + ('XML', '*.xml'), + ('TXT', '*.txt'), + ) + """ + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + + file_picker = create_instance(cls.FILE_PICKER) + file_picker.setTitle(_('Select file')) + file_picker.setDisplayDirectory(init_dir) + file_picker.setMultiSelectionMode(multiple) + + if filters: + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + path = '' + if file_picker.execute(): + files = file_picker.getSelectedFiles() + path = [cls.to_system(f) for f in files] + if not multiple: + path = path[0] + return path + + @classmethod + def replace_ext(cls, path, new_ext): + p = Paths(path) + 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: - docs.append(doc) - return docs + pid = subprocess.Popen(['xdg-open', path]).pid + return + @classmethod + def is_dir(cls, path): + return Path(path).is_dir() -def get_selection(): - return get_document().selection + @classmethod + def is_file(cls, path): + return Path(path).is_file() + @classmethod + def join(cls, *paths): + return str(Path(paths[0]).joinpath(*paths[1:])) -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 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 -def active_cell(): - return get_cell() + @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 -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(): - 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): + @classmethod + def to_url(cls, path): + if not path.startswith('file://'): + path = Path(path).as_uri() return path - path = PYTHON - cmd = '"{}" -V'.format(path) - result = run(cmd, True) - - if 'Python 3' in result: + @classmethod + def to_system(cls, path): + if path.startswith('file://'): + path = str(Path(uno.fileUrlToSystemPath(path)).resolve()) return path - path = PYTHON + '3' - cmd = '"{}" -V'.format(path) - result = run(cmd, True) + @classmethod + def kill(cls, path): + result = True + p = Path(path) - if 'Python 3' in result: + 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 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 from_id(cls, id_ext): + pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') + path = _P.to_system(pip.getPackageLocation(id_ext)) return path - return '' + @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) -# ~ 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) + @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 - path = '' - if filters: - file_picker.setCurrentFilter(filters[0][0]) - for f in filters: - file_picker.appendFilter(f[0], f[1]) + @classmethod + def to_csv(cls, path, data, args={}): + with open(path, 'w') as f: + writer = csv.writer(f, **args) + writer.writerows(data) + return - if file_picker.execute(): - path = _path_system(file_picker.getSelectedFiles()[0]) - if multiple: - path = [_path_system(f) for f in file_picker.getSelectedFiles()] + @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' - 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()) + 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: - data = f.read() - return data + files = [(f, f[start:]) for f in _P.walk(source)] + + compression = zipfile.ZIP_DEFLATED + with zipfile.ZipFile(path_zip, 'w', compression=compression) as z: + for f in files: + z.write(f[0], f[1]) + return + + @classmethod + def zip_content(cls, path): + with zipfile.ZipFile(path) as z: + names = z.namelist() + return names + + @classmethod + def unzip(cls, source, target='', members=None, pwd=None): + path = target + if not target: + path = _P(source).path + with zipfile.ZipFile(source) as z: + if not pwd is None: + pwd = pwd.encode() + if isinstance(members, str): + members = (members,) + z.extractall(path, members=members, pwd=pwd) + return True + + @classmethod + def copy(cls, source, target='', name=''): + p, f, n, e = _P(source).info + 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): @@ -4355,8 +4277,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', @@ -4412,540 +4334,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(): - call_dispatch('.uno:Copy') - 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, @@ -5096,10 +4534,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 @@ -5111,359 +4548,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 @@ -5524,23 +4617,3 @@ class LIBOServer(object): else: instance = self._sm.createInstance(name) return instance - - -# ~ controls = { - # ~ 'CheckBox': 'com.sun.star.awt.UnoControlCheckBoxModel', - # ~ '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', - # ~ 'GroupBox': 'com.sun.star.awt.UnoControlGroupBoxModel', - # ~ 'ImageControl': 'com.sun.star.awt.UnoControlImageControlModel', - # ~ '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', -# ~ } diff --git a/source/ZAZPip.py b/source/ZAZPip.py index d8dfc16..4f98bc9 100644 --- a/source/ZAZPip.py +++ b/source/ZAZPip.py @@ -13,11 +13,17 @@ PIP = 'pip' PACKAGES = { + 'cffi': 'ok.png', + 'cryptography': 'ok.png', + 'httpx': 'ok.png', + 'lxml': 'ok.png', + 'numpy': 'ok.png', + 'pandas': 'ok.png', 'psycopg2-binary': 'ok.png', 'peewee': 'ok.png', - 'numpy': 'ok.png', 'pillow': 'ok.png', 'pytesseract': 'ok.png', + 'sounddevice': 'ok.png', } diff --git a/zaz.py b/zaz.py old mode 100644 new mode 100755 index 17a0ea5..667fe97 --- a/zaz.py +++ b/zaz.py @@ -4,6 +4,8 @@ # ~ This file is part of ZAZ. +# ~ https://git.elmau.net/elmau/zaz + # ~ ZAZ is free software: you can redistribute it and/or modify # ~ it under the terms of the GNU General Public License as published by # ~ the Free Software Foundation, either version 3 of the License, or @@ -19,6 +21,7 @@ import argparse import os +import py_compile import re import sys import zipfile @@ -43,6 +46,10 @@ from conf import ( log) +EASYMACRO_TMP = 'easymacro2.py' +EASYMACRO = 'easymacro.py' + + class LiboXML(object): CONTEXT = { 'calc': 'com.sun.star.sheet.SpreadsheetDocument', @@ -55,6 +62,7 @@ class LiboXML(object): } TYPES = { 'py': 'application/vnd.sun.star.uno-component;type=Python', + 'pyc': 'application/binary', 'zip': 'application/binary', 'xcu': 'application/vnd.sun.star.configuration-data', 'rdb': 'application/vnd.sun.star.uno-typelibrary;type=RDB', @@ -121,8 +129,8 @@ class LiboXML(object): def parse_manifest(self, data): ET.register_namespace('manifest', self.NS_MANIFEST['manifest']) self._manifest = ET.fromstring(data) - data = {'xmlns:loext': self.NS_MANIFEST['xmlns:loext']} - self._manifest.attrib.update(**data) + attr = {'xmlns:loext': self.NS_MANIFEST['xmlns:loext']} + self._manifest.attrib.update(**attr) self._clean('manifest', self._manifest) return @@ -547,7 +555,7 @@ def _update_files(): copyfile(source, target) if FILES['easymacro']: - source = 'easymacro.py' + source = EASYMACRO target = _join(path_source, 'pythonpath', source) copyfile(source, target) @@ -593,7 +601,7 @@ def _update_files(): return -def _new(): +def _create(): if not _validate_new(): return @@ -615,7 +623,7 @@ def _get_info_path(path): def _zip_embed(source, files): PATH = 'Scripts/python/' - EASYMACRO = 'easymacro.' + FILE_PYC = 'easymacro.pyc' p, f, name, e = _get_info_path(source) now = datetime.now().strftime('_%Y%m%d_%H%M%S') @@ -623,12 +631,10 @@ def _zip_embed(source, files): copyfile(source, path_source) target = source - with zipfile.PyZipFile(EASYMACRO + 'zip', mode='w') as zf: - zf.writepy(EASYMACRO + 'py') - + py_compile.compile(EASYMACRO, FILE_PYC) xml = LiboXML() - path_easymacro = PATH + EASYMACRO + 'zip' + path_easymacro = PATH + FILE_PYC names = [f[1] for f in files] + [path_easymacro] nodes = [] with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as zt: @@ -647,14 +653,14 @@ def _zip_embed(source, files): data.append(name) zt.write(path, name) - zt.write(EASYMACRO + 'zip', path_easymacro) + zt.write(FILE_PYC, path_easymacro) data.append(path_easymacro) xml.parse_manifest(xml_manifest) xml_manifest = xml.add_data_manifest(data) zt.writestr(path_manifest, xml_manifest) - os.unlink(EASYMACRO + 'zip') + os.unlink(FILE_PYC) return @@ -692,8 +698,6 @@ def _embed(args): def _locales(args): - EASYMACRO = 'easymacro.py' - if args.files: files = args.files.split(',') else: @@ -725,8 +729,39 @@ def _update(): return +def _new(args): + if not args.target: + msg = 'Add argument target: -t PATH_TARGET' + log.error(msg) + return + + if not args.name: + msg = 'Add argument name: -n name-new-extension' + log.error(msg) + return + + path = _join(args.target, args.name) + _mkdir(path) + _mkdir(_join(path, 'files')) + _mkdir(_join(path, 'images')) + path_logo = 'images/pymacros.png' + copyfile(path_logo, _join(path, 'images/logo.png')) + copyfile('zaz.py', _join(path, 'zaz.py')) + copyfile(EASYMACRO_TMP, _join(path, 'easymacro.py')) + copyfile('conf.py.example', _join(path, 'conf.py')) + + msg = 'Folders and files copy successfully for new extension.' + log.info(msg) + msg = f'Change to folder: {path}' + log.info(msg) + return + def main(args): + if args.new: + _new(args) + return + if args.update: _update() return @@ -739,14 +774,16 @@ def main(args): _embed(args) return - if args.new: - _new() + if args.create: + _create() return if not _validate_update(): return - _update_files() + if not args.only_compress: + _update_files() + _compress_oxt() if args.install: @@ -759,9 +796,13 @@ def main(args): def _process_command_line_arguments(): parser = argparse.ArgumentParser( description='Make LibreOffice extensions') - parser.add_argument('-i', '--install', dest='install', action='store_true', + parser.add_argument('-new', '--new', dest='new', action='store_true', default=False, required=False) - parser.add_argument('-n', '--new', dest='new', action='store_true', + parser.add_argument('-t', '--target', dest='target', default='') + parser.add_argument('-n', '--name', dest='name', default='', required=False) + parser.add_argument('-c', '--create', dest='create', action='store_true', + default=False, required=False) + parser.add_argument('-i', '--install', dest='install', action='store_true', default=False, required=False) parser.add_argument('-e', '--embed', dest='embed', action='store_true', default=False, required=False) @@ -771,6 +812,8 @@ def _process_command_line_arguments(): default=False, required=False) parser.add_argument('-u', '--update', dest='update', action='store_true', default=False, required=False) + parser.add_argument('-oc', '--only_compress', dest='only_compress', + action='store_true', default=False, required=False) return parser.parse_args() From 41681330d3d5c556535ed06d2fdacc10f3871407 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Thu, 12 Nov 2020 19:35:29 -0600 Subject: [PATCH 02/12] Add install button --- VERSION | 2 +- conf.py | 4 +- easymacro.py | 140 +- .../{ZAZPip_v0.5.0.oxt => ZAZPip_v0.6.0.oxt} | Bin 81277 -> 77469 bytes source/Addons.xcu | 2 +- source/Office/Accelerators.xcu | 2 +- source/ZAZPip.py | 492 +- source/description.xml | 2 +- source/pythonpath/easymacro.py | 7535 ++++++++--------- source/pythonpath/main.py | 517 ++ 10 files changed, 3993 insertions(+), 4703 deletions(-) rename files/{ZAZPip_v0.5.0.oxt => ZAZPip_v0.6.0.oxt} (50%) create mode 100644 source/pythonpath/main.py diff --git a/VERSION b/VERSION index 79a2734..09a3acf 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.0 \ No newline at end of file +0.6.0 \ No newline at end of file diff --git a/conf.py b/conf.py index b3dca5c..ae67910 100644 --- a/conf.py +++ b/conf.py @@ -26,7 +26,7 @@ import logging TYPE_EXTENSION = 1 # ~ https://semver.org/ -VERSION = '0.5.0' +VERSION = '0.6.0' # ~ Your great extension name, not used spaces NAME = 'ZAZPip' @@ -112,7 +112,7 @@ MENU_MAIN = {} MENUS = ( { 'title': {'en': 'Open Pip', 'es': 'Abrir Pip'}, - 'argument': 'open', + 'argument': 'open_dialog_pip', 'context': '', 'icon': 'icon', 'toolbar': False, diff --git a/easymacro.py b/easymacro.py index cdef714..29d4a17 100644 --- a/easymacro.py +++ b/easymacro.py @@ -67,6 +67,7 @@ import unohelper from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS from com.sun.star.awt.MessageBoxResults import YES from com.sun.star.awt import Rectangle, Size, Point +from com.sun.star.awt.PosSize import POSSIZE from com.sun.star.awt import Key, KeyModifier, KeyEvent from com.sun.star.container import NoSuchElementException from com.sun.star.datatransfer import XTransferable, DataFlavor @@ -399,19 +400,6 @@ def _get_class_doc(obj: Any) -> Any: return classes[type_doc](obj) -# ~ def _get_class_uno(obj: Any) -> Any: - # ~ classes = dict( - # ~ SwXTextGraphicObject = LOImage, - # ~ SvxShapeText = LOImage, - # ~ ) - # ~ name = obj.ImplementationName - # ~ print(f'ImplementationName = {name}') - # ~ instance = obj - # ~ if name in classes: - # ~ instance = classes[name](obj) - # ~ return instance - - def dict_to_property(values: dict, uno_any: bool=False): ps = tuple([PropertyValue(Name=n, Value=v) for n, v in values.items()]) if uno_any: @@ -429,6 +417,14 @@ def _property_to_dict(values): return d +def json_dumps(data): + return json.dumps(data, indent=4, sort_keys=True) + + +def json_loads(data): + return json.loads(data) + + def data_to_dict(data): if isinstance(data, tuple) and isinstance(data[0], tuple): return _array_to_dict(data) @@ -438,12 +434,6 @@ def data_to_dict(data): return {} -def _path_url(path: str) -> str: - if path.startswith('file://'): - return path - return uno.systemPathToFileUrl(path) - - def _get_dispatch() -> Any: return create_instance('com.sun.star.frame.DispatchHelper') @@ -1145,7 +1135,7 @@ class LODocument(object): opt = dict_to_property(args) if path: try: - self.obj.storeAsURL(_path_url(path), opt) + self.obj.storeAsURL(_P.to_url(path), opt) except Exception as e: error(e) result = False @@ -2828,7 +2818,7 @@ class LODocs(object): http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1frame_1_1XComponentLoader.html http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1document_1_1MediaDescriptor.html """ - path = _path_url(path) + path = _P.to_url(path) opt = dict_to_property(args) doc = cls._desktop.loadComponentFromURL(path, '_default', 0, opt) if doc is None: @@ -2994,9 +2984,10 @@ class EventsFocus(EventsListenerBase, XFocusListener): # ~ HelpURL = ? class UnoBaseObject(object): - def __init__(self, obj): + def __init__(self, obj, path=''): self._obj = obj self._model = obj.Model + # ~ self._path = path def __setattr__(self, name, value): exists = hasattr(self, name) @@ -3199,6 +3190,22 @@ class UnoBaseObject(object): def tab_stop(self, value): self.model.Tabstop = value + @property + def ps(self): + ps = self.obj.getPosSize() + return ps + @ps.setter + def ps(self, ps): + self.obj.setPosSize(ps.X, ps.Y, ps.Width, ps.Height, POSSIZE) + + def set_focus(self): + self.obj.setFocus() + return + + def ps_from(self, source): + self.ps = source.ps + return + def center(self, horizontal=True, vertical=False): p = self.parent.Model w = p.Width @@ -3361,6 +3368,78 @@ class UnoImage(UnoBaseObject): self.m.ImageURL = _P.to_url(value) +class UnoListBox(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._path = '' + + def __setattr__(self, name, value): + if name in ('_path',): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def type(self): + return 'listbox' + + @property + def value(self): + return self.obj.getSelectedItem() + + @property + def count(self): + return len(self.data) + + @property + def data(self): + return self.model.StringItemList + @data.setter + def data(self, values): + self.model.StringItemList = list(sorted(values)) + + @property + def path(self): + return self._path + @path.setter + def path(self, value): + self._path = value + + def unselect(self): + self.obj.selectItem(self.value, False) + return + + def select(self, pos=0): + if isinstance(pos, str): + self.obj.selectItem(pos, True) + else: + self.obj.selectItemPos(pos, True) + return + + def clear(self): + self.model.removeAllItems() + return + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def insert(self, value, path='', pos=-1, show=True): + if pos < 0: + pos = self.count + if path: + self.model.insertItem(pos, value, self._set_image_url(path)) + else: + self.model.insertItemText(pos, value) + if show: + self.select(pos) + return + + UNO_CLASSES = { 'label': UnoLabel, 'link': UnoLabelLink, @@ -3369,6 +3448,7 @@ UNO_CLASSES = { 'check': UnoCheck, 'text': UnoText, 'image': UnoImage, + 'listbox': UnoListBox, } @@ -3382,9 +3462,9 @@ class LODialog(object): 'check': 'com.sun.star.awt.UnoControlCheckBoxModel', 'text': 'com.sun.star.awt.UnoControlEditModel', 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', # ~ 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', # ~ 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', - # ~ 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', # ~ 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', # ~ 'tree': 'com.sun.star.awt.tree.TreeControlModel', # ~ 'pages': 'com.sun.star.awt.UnoMultiPageModel', @@ -3405,7 +3485,7 @@ class LODialog(object): path = args.pop('Path', '') if path: dp = create_instance(service, True) - dlg = dp.createDialog(_path_url(path)) + dlg = dp.createDialog(_P.to_url(path)) return dlg if 'Location' in args: @@ -3446,6 +3526,9 @@ class LODialog(object): return self._controls @property + def path(self): + return self._path + @property def id(self): return self._id @id.setter @@ -3474,6 +3557,13 @@ class LODialog(object): def visible(self, value): self.obj.Visible = value + @property + def step(self): + return self.model.Step + @step.setter + def step(self, value): + self.model.Step = value + @property def events(self): return self._events @@ -3539,6 +3629,8 @@ class LODialog(object): control = self.obj.getControl(name) _add_listeners(self.events, control, name) control = UNO_CLASSES[tipo](control) + if tipo in ('listbox',): + control.path = self.path if tipo == 'tree' and root: control.root = root diff --git a/files/ZAZPip_v0.5.0.oxt b/files/ZAZPip_v0.6.0.oxt similarity index 50% rename from files/ZAZPip_v0.5.0.oxt rename to files/ZAZPip_v0.6.0.oxt index b1410964df62f365d058b748f5bebd6417e57327..08a54e7687c03cca60224acdc21e8b453cbc354f 100644 GIT binary patch delta 33767 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 delta 37628 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

Usr9oC&M$`48@MESs6oZJuk*9rE_9S3rEr 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 VQnJ4bvtHhqD*(W-#O1_*{{R^n^+Nyv diff --git a/source/Addons.xcu b/source/Addons.xcu index 691020b..dca21e5 100644 --- a/source/Addons.xcu +++ b/source/Addons.xcu @@ -11,7 +11,7 @@ - service:net.elmau.zaz.pip?open + service:net.elmau.zaz.pip?open_dialog_pip _self diff --git a/source/Office/Accelerators.xcu b/source/Office/Accelerators.xcu index c5ac0c1..92fcc4c 100644 --- a/source/Office/Accelerators.xcu +++ b/source/Office/Accelerators.xcu @@ -4,7 +4,7 @@ - service:net.elmau.zaz.pip?open + service:net.elmau.zaz.pip?open_dialog_pip diff --git a/source/ZAZPip.py b/source/ZAZPip.py index 4f98bc9..79382e1 100644 --- a/source/ZAZPip.py +++ b/source/ZAZPip.py @@ -1,507 +1,23 @@ import uno import unohelper from com.sun.star.task import XJobExecutor -import easymacro as app +import main + ID_EXTENSION = 'net.elmau.zaz.pip' SERVICE = ('com.sun.star.task.Job',) -TITLE = 'ZAZ-PIP' -URL_PIP = 'https://bootstrap.pypa.io/get-pip.py' -PIP = 'pip' - - -PACKAGES = { - 'cffi': 'ok.png', - 'cryptography': 'ok.png', - 'httpx': 'ok.png', - 'lxml': 'ok.png', - 'numpy': 'ok.png', - 'pandas': 'ok.png', - 'psycopg2-binary': 'ok.png', - 'peewee': 'ok.png', - 'pillow': 'ok.png', - 'pytesseract': 'ok.png', - 'sounddevice': 'ok.png', -} - - -_ = app.install_locales(__file__) - - -class Controllers(object): - OK1 = 'Successfully installed' - OK2 = 'Requirement already' - OK3 = 'Successfully uninstalled' - - def __init__(self, dialog): - self.d = dialog - self.path_python = app.get_path_python() - self._states = { - 'list': False, - 'install': False, - 'search': False, - 'versions': False, - } - - def _set_state(self, state): - for k in self._states.keys(): - self._states[k] = False - self._states[state] = True - return - - def cmd_install_pip_action(self, event): - msg = _('Do you want install PIP?') - if not app.question(msg, 'ZAZ-Pip'): - return - - self._install_pip() - return - - @app.run_in_thread - def _install_pip(self): - self.d.link_proyect.visible = False - self.d.lst_log.visible = True - path_pip = app.get_temp_file(True) - - self.d.lst_log.insert('Download PIP...') - data, err = app.url_open(URL_PIP, verify=False) - if err: - msg = _('Do you have internet connection?') - app.errorbox('{}\n\n{}'.format(msg, err)) - return - - app.save_file(path_pip, 'wb', data) - if not app.is_created(path_pip): - msg = _('File PIP not save') - app.errorbox(msg) - return - self.d.lst_log.insert(_('PIP save correctly...')) - - try: - - self.d.lst_log.insert(_('Start installing PIP...')) - cmd = '"{}" "{}" --user'.format(self.path_python, path_pip) - for line in app.popen(cmd): - if isinstance(line, tuple): - app.errorbox(line) - break - self.d.lst_log.insert(line) - - cmd = self._cmd_pip('-V') - label = app.run(cmd, True) - if label: - self.d.lbl_pip.value = label - self.d.cmd_install_pip.visible = False - self.d.cmd_admin_pip.visible = True - msg = _('PIP installed sucesfully') - app.msgbox(msg) - else: - msg = _('PIP not installed, see log') - app.warning(msg) - except Exception as e: - app.errorbox(e) - - return - - def _cmd_pip(self, args): - cmd = '"{}" -m pip {}'.format(self.path_python, args) - return cmd - - def cmd_admin_pip_action(self, event): - self.d.lst_log.possize(self.d.lst_package) - self.d.lst_log.step = 1 - self.d.step = 1 - self.cmd_home_action(None) - return - - def cmd_close_action(self, event): - self.d.close() - return - - def cmd_home_action(self, event): - self.d.txt_search.value = '' - self._list() - return - - def txt_search_key_released(self, event): - if event.KeyCode == app.KEY['enter']: - self.cmd_search_action(None) - return - - if not self.d.txt_search.value.strip(): - self.cmd_home_action(None) - return - - @app.run_in_thread - def _list(self): - self._set_state('list') - self.d.lst_log.visible = False - self.d.lst_package.visible = True - - cmd = self._cmd_pip(' list --format=json') - self.d.lst_package.clear() - result = app.run(cmd, True) - packages = app.json_loads(result) - - for p in packages: - t = '{} - ({})'.format(p['name'], p['version']) - self.d.lst_package.insert(t, 'ok.png') - self.d.lst_package.select() - self.d.txt_search.set_focus() - return - - @app.run_in_thread - def _search(self, value): - self._set_state('search') - line = '' - cmd = self._cmd_pip(' search {}'.format(value)) - self.d.lst_package.clear() - for line in app.popen(cmd): - parts = line.split(')') - name = parts[0].strip() + ')' - description = parts[-1].strip() - parts = name.split('(') - name_verify = parts[0].strip() - package = '{} {}'.format(name, description) - image = PACKAGES.get(name_verify, 'question.png') - self.d.lst_package.insert(package, image) - - if line: - self.d.lst_package.select() - else: - self.d.lst_package.insert(_('Not found...'), 'error.png', show=False) - return - - def cmd_search_action(self, event): - search = self.d.txt_search.value.strip() - if not search: - self.d.txt_search.set_focus() - return - - self._search(search) - return - - @app.run_in_thread - def _install(self, value): - self._set_state('install') - self.d.lst_package.visible = False - self.d.lst_log.visible = True - - line = '' - name = value.split(' ')[0].strip() - cmd = self._cmd_pip(' install --upgrade --user {}'.format(name)) - self.d.lst_log.clear() - for line in app.popen(cmd): - if self.OK1 in line or self.OK2 in line: - self.d.lst_log.insert(line, 'ok.png') - else: - self.d.lst_log.insert(line) - return - - @app.catch_exception - def lst_package_double_click(self, event): - opt = 'install' - if self._states['list']: - opt = 'upgrade' - - name = self.d.lst_package.value - msg = _('Do you want {}:\n\n{} ?').format(opt, name) - if not app.question(msg, TITLE): - return - - self._install(name) - return - - @app.run_in_thread - def _uninstall(self, value): - self._set_state('install') - self.d.lst_package.visible = False - self.d.lst_log.visible = True - - line = '' - name = value.split(' ')[0].strip() - cmd = self._cmd_pip(' uninstall -y {}'.format(name)) - self.d.lst_log.clear() - for line in app.popen(cmd): - if self.OK3 in line: - self.d.lst_log.insert(line, 'ok.png') - else: - self.d.lst_log.insert(line) - return - - def cmd_uninstall_action(self, event): - if not self._states['list']: - msg = _('Select installed package') - app.warning(msg) - return - - name = self.d.lst_package.value - msg = _('Do you want uninstall:\n\n{} ?').format(name) - if not app.question(msg): - return - - self._uninstall(name) - return - - @app.catch_exception - def cmd_shell_action(self, name): - if app.IS_WIN: - cmd = '"{}"'.format(self.path_python) - app.open_file(cmd) - else: - cmd = 'exec "{}"' - if app.IS_MAC: - cmd = 'open "{}"' - elif app.DESKTOP == 'gnome': - cmd = 'gnome-terminal -- {}' - - cmd = cmd.format(self.path_python) - app.run(cmd) - return - - class ZAZPip(unohelper.Base, XJobExecutor): def __init__(self, ctx): self.ctx = ctx def trigger(self, args): - dialog = self._create_dialog() - dialog.open() + main.ID_EXTENSION = ID_EXTENSION + main.run(args, __file__) return - def _create_dialog(self): - args= { - 'Name': 'dialog', - 'Title': 'Zaz-Pip', - 'Width': 200, - 'Height': 220, - } - dialog = app.create_dialog(args) - dialog.id_extension = ID_EXTENSION - dialog.events = Controllers(dialog) - - lbl_title = '{} {} - {}'.format(app.NAME, app.VERSION, app.OS) - args = { - 'Type': 'Label', - 'Name': 'lbl_title', - 'Label': lbl_title, - 'Width': 100, - 'Height': 15, - 'Border': 1, - 'Align': 1, - 'VerticalAlign': 1, - 'Step': 10, - 'FontHeight': 12, - } - dialog.add_control(args) - dialog.center(dialog.lbl_title, y=5) - - path_python = app.get_path_python() - cmd = '"{}" -V'.format(path_python) - label = app.run(cmd, True) - - args = { - 'Type': 'Label', - 'Name': 'lbl_python', - 'Label': str(label), - 'Width': 100, - 'Height': 15, - 'Border': 1, - 'Align': 1, - 'VerticalAlign': 1, - 'Step': 10, - 'FontHeight': 11, - } - dialog.add_control(args) - dialog.center(dialog.lbl_python, y=25) - - cmd = '"{}" -m pip -V'.format(path_python) - label = app.run(cmd, True) - exists_pip = True - if not label: - exists_pip = False - label = _('PIP not installed') - args = { - 'Type': 'Label', - 'Name': 'lbl_pip', - 'Label': label, - 'Width': 160, - 'Height': 30, - 'Border': 1, - 'Align': 1, - 'VerticalAlign': 1, - 'Step': 10, - 'MultiLine': True, - } - dialog.add_control(args) - dialog.center(dialog.lbl_pip, y=45) - - args = { - 'Type': 'Button', - 'Name': 'cmd_admin_pip', - 'Label': _('Admin PIP'), - 'Width': 60, - 'Height': 18, - 'Step': 10, - 'ImageURL': 'python.png', - 'ImagePosition': 1, - } - dialog.add_control(args) - dialog.center(dialog.cmd_admin_pip, y=80) - - args = { - 'Type': 'Button', - 'Name': 'cmd_install_pip', - 'Label': _('Install PIP'), - 'Width': 60, - 'Height': 18, - 'Step': 10, - 'ImageURL': 'install.png', - 'ImagePosition': 1, - } - dialog.add_control(args) - dialog.center(dialog.cmd_install_pip, y=80) - - args = { - 'Type': 'Link', - 'Name': 'link_proyect', - 'URL': 'https://gitlab.com/mauriciobaeza/', - 'Label': 'https://gitlab.com/mauriciobaeza/', - 'Border': 1, - 'Width': 130, - 'Height': 15, - 'Align': 1, - 'VerticalAlign': 1, - 'Step': 10, - } - dialog.add_control(args) - dialog.center(dialog.link_proyect, y=-5) - - args = { - 'Type': 'Listbox', - 'Name': 'lst_log', - 'Width': 160, - 'Height': 105, - 'Step': 10, - } - dialog.add_control(args) - dialog.center(dialog.lst_log, y=105) - - args = { - 'Type': 'Label', - 'Name': 'lbl_package', - 'Label': _('Packages'), - 'Width': 100, - 'Height': 15, - 'Border': 1, - 'Align': 1, - 'VerticalAlign': 1, - 'Step': 1, - } - dialog.add_control(args) - - args = { - 'Type': 'Text', - 'Name': 'txt_search', - 'Width': 180, - 'Height': 12, - 'Step': 1, - 'Border': 0, - } - dialog.add_control(args) - - args = { - 'Type': 'Listbox', - 'Name': 'lst_package', - 'Width': 180, - 'Height': 128, - 'Step': 1, - } - dialog.add_control(args) - - args = { - 'Type': 'Button', - 'Name': 'cmd_close', - 'Label': _('~Close'), - 'Width': 60, - 'Height': 18, - 'Step': 1, - 'ImageURL': 'close.png', - 'ImagePosition': 1, - # ~ 'PushButtonType': 2, - } - dialog.add_control(args) - dialog.center(dialog.cmd_close, y=-5) - - args = { - 'Type': 'Button', - 'Name': 'cmd_home', - 'Width': 18, - 'Height': 18, - 'Step': 1, - 'ImageURL': 'home.png', - 'FocusOnClick': False, - 'Y': 2, - } - dialog.add_control(args) - - args = { - 'Type': 'Button', - 'Name': 'cmd_search', - 'Width': 18, - 'Height': 18, - 'Step': 1, - 'ImageURL': 'search.png', - 'FocusOnClick': False, - 'Y': 2, - } - dialog.add_control(args) - - args = { - 'Type': 'Button', - 'Name': 'cmd_uninstall', - 'Width': 18, - 'Height': 18, - 'Step': 1, - 'ImageURL': 'uninstall.png', - 'FocusOnClick': False, - 'Y': 2, - } - dialog.add_control(args) - - args = { - 'Type': 'Button', - 'Name': 'cmd_shell', - 'Width': 18, - 'Height': 18, - 'Step': 1, - 'ImageURL': 'shell.png', - 'FocusOnClick': False, - 'Y': 2, - } - dialog.add_control(args) - - controls = (dialog.cmd_home, dialog.cmd_search, - dialog.cmd_uninstall, dialog.cmd_shell) - dialog.lbl_package.move(dialog.cmd_home) - dialog.txt_search.move(dialog.lbl_package) - dialog.lst_package.move(dialog.txt_search) - dialog.lbl_package.center() - dialog.lst_package.center() - dialog.txt_search.center() - dialog.center(controls) - - dialog.step = 10 - - dialog.cmd_install_pip.visible = not exists_pip - dialog.cmd_admin_pip.visible = exists_pip - dialog.lst_log.visible = False - - return dialog - g_ImplementationHelper = unohelper.ImplementationHelper() g_ImplementationHelper.addImplementation(ZAZPip, ID_EXTENSION, SERVICE) diff --git a/source/description.xml b/source/description.xml index 3b7128f..f2e52ef 100644 --- a/source/description.xml +++ b/source/description.xml @@ -1,7 +1,7 @@ - + ZAZ Pip ZAZ Pip diff --git a/source/pythonpath/easymacro.py b/source/pythonpath/easymacro.py index 30ea714..29d4a17 100644 --- a/source/pythonpath/easymacro.py +++ b/source/pythonpath/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,22 +33,25 @@ import re import shlex import shutil import socket -import subprocess 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, PurePath +from pathlib import Path from pprint import pprint +from string import Template +from typing import Any from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError -from string import Template -from subprocess import PIPE import smtplib from smtplib import SMTPException, SMTPAuthenticationError @@ -61,150 +64,39 @@ 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.awt import Rectangle, Size, Point +from com.sun.star.awt.PosSize import POSSIZE +from com.sun.star.awt import Key, KeyModifier, KeyEvent +from com.sun.star.container import NoSuchElementException from com.sun.star.datatransfer import XTransferable, DataFlavor -from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA -from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK +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.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 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 +# ~ 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 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('Install peewee') -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 +106,135 @@ 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 + +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) + +_MACROS = {} +_start = 0 + SECONDS_DAY = 60 * 60 * 24 +DIR = { + 'images': 'images', + 'locales': 'locales', +} +DEFAULT_MIME_TYPE = 'png' +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', +} + +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 +256,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 @@ -291,47 +307,22 @@ def catch_exception(f): 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 +337,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 +345,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 +362,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,183 +379,597 @@ 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(url, args=()): - frame = get_document().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') + + +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 array_to_dict(values): - d = {r[0]: r[1] for r in values} +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 -# ~ Custom classes -class ObjectBase(object): +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) + if capture: + result = result.stdout + else: + result = result.returncode + return result + + +# ~ def popen(command, stdin=None): + # ~ try: + # ~ proc = subprocess.Popen(shlex.split(command), shell=IS_WIN, + # ~ stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + # ~ for line in proc.stdout: + # ~ yield line.decode().rstrip() + # ~ except Exception as e: + # ~ error(e) + # ~ yield (e.errno, e.strerror) + + +def 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 + + +class SmtpServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._sender = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + name = config['server'] + port = config['port'] + is_ssl = config['ssl'] + self._sender = config['user'] + hosts = ('gmail' in name or 'outlook' in name) + try: + if is_ssl and hosts: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + self._server.ehlo() + self._server.starttls() + self._server.ehlo() + elif is_ssl: + self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT) + self._server.ehlo() + else: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + + self._server.login(self._sender, config['password']) + msg = 'Connect to: {}'.format(name) + debug(msg) + return True + except smtplib.SMTPAuthenticationError as e: + if '535' in str(e): + self._error = _('Incorrect user or password') + return False + if '534' in str(e) and 'gmail' in name: + self._error = _('Allow less secure apps in GMail') + return False + except smtplib.SMTPException as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def _body(self, msg): + body = msg.replace('\\n', '
') + return body + + def send(self, message): + file_name = 'attachment; filename={}' + email = MIMEMultipart() + email['From'] = self._sender + email['To'] = message['to'] + email['Cc'] = message.get('cc', '') + email['Subject'] = message['subject'] + email['Date'] = formatdate(localtime=True) + if message.get('confirm', False): + email['Disposition-Notification-To'] = email['From'] + email.attach(MIMEText(self._body(message['body']), 'html')) + + for path in message.get('files', ()): + fn = _P(path).file_name + part = MIMEBase('application', 'octet-stream') + part.set_payload(_P.read_bin(path)) + encoders.encode_base64(part) + part.add_header('Content-Disposition', f'attachment; filename={fn}') + email.attach(part) + + receivers = ( + email['To'].split(',') + + email['CC'].split(',') + + message.get('bcc', '').split(',')) + try: + self._server.sendmail(self._sender, receivers, email.as_string()) + msg = 'Email sent...' + debug(msg) + if message.get('path', ''): + self.save_message(email, message['path']) + return True + except Exception as e: + self._error = str(e) + return False + return False + + def save_message(self, email, path): + mbox = mailbox.mbox(path, create=True) + mbox.lock() + try: + msg = mailbox.mboxMessage(email) + mbox.add(msg) + mbox.flush() + finally: + mbox.unlock() + return + + def close(self): + try: + self._server.quit() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + +def _send_email(server, messages): + with SmtpServer(server) as server: + if server.is_connect: + for msg in messages: + server.send(msg) + else: + error(server.error) + return server.error + + +def send_email(server, message): + messages = message + if isinstance(message, dict): + messages = (message,) + t = threading.Thread(target=_send_email, args=(server, messages)) + t.start() + return + + +# ~ Classes + +class LOBaseObject(object): 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'): + 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] + @property + def obj(self): + return self._obj - 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 + +class LOImage(object): + TYPE = { + 'png': 'image/png', + 'jpg': 'image/jpeg', + } + + def __init__(self, obj): + self._obj = obj @property def obj(self): return self._obj - @obj.setter - def obj(self, value): - self._obj = value + + @property + def name(self): + return self.obj.Name or 'img' + + @property + def mimetype(self): + return self.obj.Bitmap.MimeType + + def save(self, path, mimetype=DEFAULT_MIME_TYPE): + p = _P(path) + if _P.is_dir(path): + name = self.name + else: + path = p.path + name = p.name + + path = _P.join(path, f'{name}.{mimetype.lower()}') + args = dict( + URL = _P.to_url(path), + MimeType = self.TYPE[mimetype], + ) + if not _export_image(self.obj, args): + path = '' + + # ~ size = len(self.obj.Bitmap.DIB) + # ~ data = self.obj.GraphicStream.readBytes((), size) + # ~ data = data[-1].value + + # ~ data = self.obj.Bitmap.DIB.value + # ~ data = self.obj.Graphic.DIB.value + + # ~ _P.save_bin(path, data) + return path -class LOObjectBase(object): +class LODocument(object): + FILTERS = { + 'doc': 'MS Word 97', + 'docx': 'MS Word 2007 XML', + } def __init__(self, obj): - self.__dict__['_obj'] = 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): - 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 + self.close() @property def obj(self): @@ -628,12 +983,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): @@ -653,19 +1008,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 @@ -675,6 +1042,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') @@ -684,344 +1076,118 @@ 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 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() - - -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', + 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, } - return types[name] + 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 _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) + 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.removeByName(index) + self.obj.store() + return result + + def close(self): + self.obj.close(True) 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 obj(self): - return self._obj - - @property - def address(self): - return self.obj.Anchor.AbsoluteName - - @property - def name(self): - return self.obj.Name - - @property - def mimetype(self): - return self.obj.Bitmap.MimeType - - @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() + self._type = CALC + self._sheets = obj.Sheets 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) + 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.obj.Sheets + 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): @@ -1038,90 +1204,62 @@ class LOCalc(LODocument): self._cc.SheetTabs = value @property - def active(self): - return LOCalcSheet(self._cc.getActiveSheet(), self) + 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[sheet].obj + obj = self._sheets[sheet] 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 + def new_sheet(self): + s = self.create_instance('com.sun.star.sheet.Spreadsheet') + return s - @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 + def insert(self, name): + names = name 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) + names = (name,) + for n in names: + self._sheets[n] = self.new_sheet() + return LOCalcSheet(self._sheets[n]) 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 + index = len(self) + if isinstance(name, LOCalcSheet): + name = name.name + self._sheets.moveByName(name, index) + return - names = source - if not names: - names = self.names - elif isinstance(source, str): - names = (source,) + def remove(self, name): + if isinstance(name, LOCalcSheet): + name = name.name + self._sheets.removeByName(name) + return - 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(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 = self._sheets.Count + pos + 1 + index = len(self) names = source - if not names: + if not source: names = doc.names elif isinstance(source, str): names = (source,) @@ -1132,118 +1270,37 @@ class LOCalc(LODocument): 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] + for i, name in enumerate(names): + self._sheets.importSheet(doc.obj, name, index + i) + self[index + i].name = new_names[i] - # ~ doc.getCurrentController().setActiveSheet(sheet) - # ~ For controls in sheet - # ~ doc.getCurrentController().setFormDesignMode(False) - - return LOCalcSheet(self._sheets[index], self) + return LOCalcSheet(self._sheets[index]) def sort(self, reverse=False): names = sorted(self.names, reverse=reverse) for i, n in enumerate(names): - self.sheets.move(n, i) + self.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 render(self, data, sheet=None, clean=True): + if sheet is None: + sheet = self.active + return sheet.render(data, clean=clean) -class LOCalcSheets(object): +class LOChart(object): - def __init__(self, obj, doc): + def __init__(self, name, obj, draw_page): + self._name = name self._obj = obj - self._doc = doc + 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 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) + return LOBaseObject(self.diagram.getDataRowProperties(index)) def __enter__(self): return self @@ -1251,22 +1308,201 @@ 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} - @property def obj(self): return self._obj @property - def doc(self): - return self._doc + def name(self): + return self._name @property - def images(self): - return self._images + 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): + + def __init__(self, obj): + self._obj = obj + self._control = self.doc.CurrentController.getControl(self.obj) + + def __setattr__(self, name, value): + if name == '_control': + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def doc(self): + return self.obj.Parent.Parent.Parent + + @property + def name(self): + return self.obj.Name + + @property + def label(self): + return self.obj.Label + + def set_focus(self): + self._control.setFocus() + return + + +class LOForm(object): + + def __init__(self, obj): + self._obj = obj + + def __getitem__(self, index): + return LOFormControl(self.obj[index]) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + @property + def obj(self): + return self._obj + + +class LOSheetForms(object): + + def __init__(self, obj): + self._obj = obj + + def __getitem__(self, index): + return LOForm(self.obj[index]) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + @property + def obj(self): + return self._obj + + +class LOSheetRows(object): + + def __init__(self, sheet): + self._sheet = sheet + self._obj = sheet.obj.Rows + + def __getitem__(self, index): + return LOSheetRows(self.obj[index]) + + @property + def obj(self): + return self._obj + + def insert(self, index, count): + self.obj.insertByIndex(index, count) + end = index + count + return self._sheet[index:end,0:] + + +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): @@ -1282,27 +1518,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): @@ -1323,172 +1544,575 @@ 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) @property def forms(self): - return LOForms(self._dp.getForms(), self.doc) + return LOSheetForms(self.obj.DrawPage.Forms) - @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') + 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 -class LOWriter(LODocument): + 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) + + +class LOCalcRows(object): def __init__(self, obj): - super().__init__(obj) + self._obj = obj + + def __len__(self): + return self.obj.Count + + def __str__(self): + return 'Rows' @property def obj(self): return self._obj @property - def string(self): - return self._obj.getText().String + def count(self): + return len(self) @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 + def visible(self): + return self.obj.IsVisible + @visible.setter + def visible(self, value): + self.obj.IsVisible = value -class LOTextRange(object): +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 __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 LOCalcRows(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) + + def select(self): + self.doc.select(self.obj) + return + + 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_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 auto_width(self): + self.obj.Columns.OptimalWidth = True + return + + def clean_render(self, template='\{(\w.+)\}'): + self._sd.SearchRegularExpression = True + self._sd.setSearchString(template) + self.obj.replaceAll(self._sd) + return + + def render(self, data, clean=True): + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + 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_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 filter_by_color(self, cell): + rangos = cell.column[1:,:].visible + for r in rangos: + for c in r: + if c.back_color != cell.back_color: + c.rows.visible = False + return + + def clear(self, what=1023): + # ~ http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html + self.obj.clearContents(what) + return + + +class LOWriterPageStyle(LOBaseObject): + + 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' @@ -1497,17 +2121,20 @@ class LOTextRange(object): return self._obj @property - def is_paragraph(self): - return self._is_paragraph + def string(self): + return self.obj.String + @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 string(self): - return self.obj.String - @property def text(self): return self.obj.getText() @@ -1516,9 +2143,444 @@ class LOTextRange(object): 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 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): + return LOWriterTextRange(self.obj[index], self._doc) + + @property + def obj(self): + return self._obj + + +class LOWriter(LODocument): + + def __init__(self, obj): + super().__init__(obj) + self._type = WRITER + + @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) + + +class LOShape(LOBaseObject): + + def __init__(self, obj, index): + self._index = index + super().__init__(obj) + + @property + def type(self): + return 'shape' + + @property + def name(self): + return self.obj.Name or f'shape{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 + + def remove(self): + self.obj.Parent.remove(self.obj) + 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 + + @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 + """ + w = args.get('Width', 3000) + h = args.get('Height', 3000) + x = args.get('X', 1000) + y = args.get('Y', 1000) + + service = f'com.sun.star.drawing.{type_shape}Shape' + shape = self.create_instance(service) + shape.Size = Size(w, h) + shape.Position = Point(x, y) + index = self.count + shape.Name = f'{type_shape.lower()}{index}' + 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 insert_image(self, path, args={}): + w = args.get('Width', 3000) + h = args.get('Height', 3000) + x = args.get('X', 1000) + y = args.get('Y', 1000) + + image = self.create_instance('com.sun.star.drawing.GraphicObjectShape') + image.GraphicURL = _P.to_url(path) + image.Size = Size(w, h) + image.Position = Point(x, y) + index = self.count + image.Name = f'image{index}' + 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.selection + + 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', @@ -1540,40 +2602,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): @@ -1583,25 +2634,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): @@ -1609,10 +2661,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 @@ -1624,508 +2673,209 @@ 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) + 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) + doc = None + for i, doc in enumerate(self._desktop.Components): + if isinstance(index, int) and i == index: + doc = _get_class_doc(doc) + break + elif isinstance(index, str) and doc.Title == index: + doc = _get_class_doc(doc) + break + return doc def __contains__(self, item): - return item.in_range(self) + doc = self[item] + return not doc is None - def _init_values(self): - self._type_obj = self.obj.ImplementationName - self._type_content = EMPTY + def __iter__(self): + self._i = 0 + return self - if self._type_obj == OBJ_CELL: - self._type_content = self.obj.getType() - return + def __next__(self): + doc = self[self._i] + if doc is None: + raise StopIteration + self._i += 1 + return doc + + def __len__(self): + for i, _ in enumerate(self._desktop.Components): + pass + return i + 1 @property - def obj(self): - return self._obj + def active(self): + return _get_class_doc(self._desktop.getCurrentComponent()) - @property - def doc(self): - return self._doc + @classmethod + def new(cls, type_doc=CALC, args={}): + if type_doc == BASE: + return LOBase(None, args) - @property - def type(self): - return self._type_obj + 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) - @property - def type_content(self): - return self._type_content + @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 - @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 + 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 - @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) + return _get_class_doc(doc) - @property - def data(self): - return self.obj.getDataArray() - @data.setter - def data(self, values): - self.obj.setDataArray(values) + def connect(self, path): + return LOBase(None, {'path': path}) - @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 +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' - @property - def columns(self): - return self._obj.Columns.Count + 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 - @property - def row(self): - a = self.address - if hasattr(a, 'Row'): - r = a.Row - else: - r = a.StartRow - return r + getattr(control, key)(listeners[key](events, name)) - @property - def rows(self): - return self._obj.Rows.Count + # ~ if is_grid: + # ~ controllers = EventsGrid(events, name) + # ~ control.addSelectionListener(controllers) + # ~ control.Model.GridDataModel.addGridDataListener(controllers) + return - 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('.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 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) - img = self.doc.create_instance('com.sun.star.drawing.GraphicObjectShape') - img.GraphicURL = _path_url(path) - self.draw_page.add(img) - img.Anchor = self.obj - img.setSize(Size(w, h)) - 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_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): @@ -2145,18 +2895,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): @@ -2189,281 +2927,74 @@ 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 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): +class EventsButton(EventsListenerBase, XActionListener): 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) + def actionPerformed(self, event): + event_name = f'{self.name}_action' 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): + CONTROLS = ( + 'stardiv.Toolkit.UnoControlEditModel', + ) 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 + # ~ print('Focus enter', service) + if service in self.CONTROLS: + obj = event.Source.Model + obj.BackgroundColor = COLOR_ON_FOCUS + return 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): + service = event.Source.Model.ImplementationName + if service in self.CONTROLS: + obj = event.Source.Model + obj.BackgroundColor = -1 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 + # ~ self._path = path + + 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) @property def obj(self): @@ -2472,6 +3003,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): @@ -2479,8 +3020,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() @@ -2517,90 +3177,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 @@ -2613,7 +3218,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: @@ -2622,13 +3227,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 @@ -2676,6 +3277,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 UnoCheck(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def type(self): + return 'check' + + @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): @@ -2692,15 +3342,43 @@ class UnoText(UnoBaseObject): 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): @@ -2720,7 +3398,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) @@ -2738,15 +3422,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: @@ -2760,924 +3440,79 @@ 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 = () - - @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) +UNO_CLASSES = { + 'label': UnoLabel, + 'link': UnoLabelLink, + 'button': UnoButton, + 'radio': UnoRadio, + 'check': UnoCheck, + 'text': UnoText, + 'image': UnoImage, + 'listbox': UnoListBox, +} 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', + 'check': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + # ~ 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + # ~ 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + # ~ 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + # ~ 'tree': 'com.sun.star.awt.tree.TreeControlModel', + # ~ 'pages': 'com.sun.star.awt.UnoMultiPageModel', + } - 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 = '' - 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') 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): - 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 @@ -3687,20 +3522,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): @@ -3717,13 +3551,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): @@ -3737,90 +3569,68 @@ 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): + columns = args.pop('Columns', ()) - 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']) + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + elif tipo == 'grid': + args['ColumnModel'] = self._set_column_model(columns) + elif tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + if not 'FocusOnClick' in args: + args['FocusOnClick'] = False elif tipo == 'roadmap': - if not 'Height' in properties: - properties['Height'] = self.height - if 'Title' in properties: - properties['Text'] = properties.pop('Title') + if not 'Height' in args: + args['Height'] = self.height + if 'Title' in args: + args['Text'] = args.pop('Title') elif tipo == 'tab': - if not 'Width' in properties: - properties['Width'] = self.width - if not 'Height' in properties: - properties['Height'] = self.height + if not 'Width' in args: + args['Width'] = self.width + if not 'Height' in args: + args['Height'] = self.height - return properties + return args - def add_control(self, properties): - tipo = properties.pop('Type').lower() - root = properties.pop('Root', '') - sheets = properties.pop('Sheets', ()) + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) - properties = self._special_properties(tipo, properties) - model = self.model.createInstance(self._get_control_model(tipo)) - set_properties(model, properties) - name = properties['Name'] + 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 @@ -3829,20 +3639,21 @@ class LODialog(object): 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: @@ -3857,488 +3668,691 @@ 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 -class LOWindow(object): - EMPTY = b""" - -""" + def close(self, value=0): + if self._modal: + value = self.obj.endDialog(value) + else: + self.visible = False + self.obj.dispose() + return value - def __init__(self, **kwargs): - self._events = None + +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._container = None - self._id_extension = '' - self._obj = self._create(kwargs) + 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 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 + + +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 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 path(self): + return str(self._path.parent) - 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 file_name(self): + return self._path.name @property def name(self): - return self._title.lower().replace(' ', '_') + return self._path.stem @property - def events(self): - return self._events - @events.setter - def events(self, value): - self._events = value - self._add_listeners() + def ext(self): + return self._path.suffix[1:] @property - def width(self): - return self._container.Size.Width + def info(self): + return self.path, self.file_name, self.name, self.ext @property - def height(self): - return self._container.Size.Height + def url(self): + return self._path.as_uri() - def open(self): - self._window.setVisible(True) - return + @property + def size(self): + return self._path.stat().st_size - def close(self): - self._window.setMenuBar(None) - self._window.dispose() - self._frame.close(True) - return + @classproperty + def home(self): + return str(Path.home()) + @classproperty + def documents(self): + return self.config() -# ~ Python >= 3.7 -# ~ def __getattr__(name): + @classproperty + def temp_dir(self): + return tempfile.gettempdir() + @classproperty + def python(self): + return sys.executable -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) + @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 -# ~ Export ok -def get_document(title=''): - doc = None - desktop = get_desktop() - if not title: - doc = _get_class_doc(desktop.getCurrentComponent()) - return doc + @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)) - for d in desktop.getComponents(): - if hasattr(d, 'Title') and d.Title == title: - doc = d - break + @classmethod + def get(cls, init_dir='', filters=()): + """ + Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html + filters: Example + ( + ('XML', '*.xml'), + ('TXT', '*.txt'), + ) + """ + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + file_picker = create_instance(cls.FILE_PICKER) + file_picker.setTitle(_('Select path')) + file_picker.setDisplayDirectory(init_dir) + file_picker.initialize((2,)) + if filters: + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) - if doc is None: - return + path = '' + if file_picker.execute(): + path = cls.to_system(file_picker.getSelectedFiles()[0]) + return path - return _get_class_doc(doc) + @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 -def get_documents(custom=True): - docs = [] - desktop = get_desktop() - for doc in desktop.getComponents(): - if custom: - docs.append(_get_class_doc(doc)) + @classmethod + def get_file(cls, init_dir='', filters=(), multiple=False): + """ + init_folder: folder default open + multiple: True for multiple selected + filters: Example + ( + ('XML', '*.xml'), + ('TXT', '*.txt'), + ) + """ + if not init_dir: + init_dir = cls.documents + init_dir = cls.to_url(init_dir) + + file_picker = create_instance(cls.FILE_PICKER) + file_picker.setTitle(_('Select file')) + file_picker.setDisplayDirectory(init_dir) + file_picker.setMultiSelectionMode(multiple) + + if filters: + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + path = '' + if file_picker.execute(): + files = file_picker.getSelectedFiles() + path = [cls.to_system(f) for f in files] + if not multiple: + path = path[0] + return path + + @classmethod + def replace_ext(cls, path, new_ext): + p = Paths(path) + 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: - docs.append(doc) - return docs + pid = subprocess.Popen(['xdg-open', path]).pid + return + @classmethod + def is_dir(cls, path): + return Path(path).is_dir() -def get_selection(): - return get_document().selection + @classmethod + def is_file(cls, path): + return Path(path).is_file() + @classmethod + def join(cls, *paths): + return str(Path(paths[0]).joinpath(*paths[1:])) -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 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 -def active_cell(): - return get_cell() + @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 -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(): - 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): + @classmethod + def to_url(cls, path): + if not path.startswith('file://'): + path = Path(path).as_uri() return path - path = PYTHON - cmd = '"{}" -V'.format(path) - result = run(cmd, True) - - if 'Python 3' in result: + @classmethod + def to_system(cls, path): + if path.startswith('file://'): + path = str(Path(uno.fileUrlToSystemPath(path)).resolve()) return path - path = PYTHON + '3' - cmd = '"{}" -V'.format(path) - result = run(cmd, True) + @classmethod + def kill(cls, path): + result = True + p = Path(path) - if 'Python 3' in result: + 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 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 from_id(cls, id_ext): + pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') + path = _P.to_system(pip.getPackageLocation(id_ext)) return path - return '' + @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) -# ~ 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) + @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 - path = '' - if filters: - file_picker.setCurrentFilter(filters[0][0]) - for f in filters: - file_picker.appendFilter(f[0], f[1]) + @classmethod + def to_csv(cls, path, data, args={}): + with open(path, 'w') as f: + writer = csv.writer(f, **args) + writer.writerows(data) + return - if file_picker.execute(): - path = _path_system(file_picker.getSelectedFiles()[0]) - if multiple: - path = [_path_system(f) for f in file_picker.getSelectedFiles()] + @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' - 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()) + 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: - data = f.read() - return data + files = [(f, f[start:]) for f in _P.walk(source)] + + compression = zipfile.ZIP_DEFLATED + with zipfile.ZipFile(path_zip, 'w', compression=compression) as z: + for f in files: + z.write(f[0], f[1]) + return + + @classmethod + def zip_content(cls, path): + with zipfile.ZipFile(path) as z: + names = z.namelist() + return names + + @classmethod + def unzip(cls, source, target='', members=None, pwd=None): + path = target + if not target: + path = _P(source).path + with zipfile.ZipFile(source) as z: + if not pwd is None: + pwd = pwd.encode() + if isinstance(members, str): + members = (members,) + z.extractall(path, members=members, pwd=pwd) + return True + + @classmethod + def copy(cls, source, target='', name=''): + p, f, n, e = _P(source).info + 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): @@ -4355,8 +4369,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', @@ -4412,540 +4426,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(): - call_dispatch('.uno:Copy') - 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, @@ -5096,10 +4626,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 @@ -5111,359 +4640,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 @@ -5524,23 +4709,3 @@ class LIBOServer(object): else: instance = self._sm.createInstance(name) return instance - - -# ~ controls = { - # ~ 'CheckBox': 'com.sun.star.awt.UnoControlCheckBoxModel', - # ~ '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', - # ~ 'GroupBox': 'com.sun.star.awt.UnoControlGroupBoxModel', - # ~ 'ImageControl': 'com.sun.star.awt.UnoControlImageControlModel', - # ~ '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', -# ~ } diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py new file mode 100644 index 0000000..8b290b8 --- /dev/null +++ b/source/pythonpath/main.py @@ -0,0 +1,517 @@ +#!/usr/bin/env python3 + +import easymacro as app + + +ID_EXTENSION = '' + + +TITLE = 'ZAZ-PIP' +URL_PIP = 'https://bootstrap.pypa.io/get-pip.py' +PIP = 'pip' +URL_GIT = 'https://git.elmau.net/elmau' + + +PACKAGES = { + 'cffi': 'ok.png', + 'cryptography': 'ok.png', + 'httpx': 'ok.png', + 'lxml': 'ok.png', + 'numpy': 'ok.png', + 'pandas': 'ok.png', + 'psycopg2-binary': 'ok.png', + 'peewee': 'ok.png', + 'pillow': 'ok.png', + 'pytesseract': 'ok.png', + 'sounddevice': 'ok.png', +} + + +def open_dialog_pip(): + dialog = _create_dialog() + dialog.open() + return + + +def run(args, path_locales): + app.install_locales(path_locales) + globals()[args]() + return + + +@app.catch_exception +class Controllers(object): + OK1 = 'Successfully installed' + OK2 = 'Requirement already' + OK3 = 'Successfully uninstalled' + + def __init__(self, dialog): + self.d = dialog + self.path_python = app.paths.python + self._states = { + 'list': False, + 'install': False, + 'search': False, + 'versions': False, + } + + def _set_state(self, state): + for k in self._states.keys(): + self._states[k] = False + self._states[state] = True + return + + def cmd_install_pip_action(self, event): + msg = _('Do you want install PIP?') + if not app.question(msg, 'ZAZ-Pip'): + return + + self._install_pip() + return + + @app.run_in_thread + def _install_pip(self): + self.d.link_proyect.visible = False + self.d.lst_log.visible = True + path_pip = app.get_temp_file(True) + + self.d.lst_log.insert('Download PIP...') + data, err = app.url_open(URL_PIP, verify=False) + if err: + msg = _('Do you have internet connection?') + app.errorbox('{}\n\n{}'.format(msg, err)) + return + + app.save_file(path_pip, 'wb', data) + if not app.is_created(path_pip): + msg = _('File PIP not save') + app.errorbox(msg) + return + self.d.lst_log.insert(_('PIP save correctly...')) + + try: + + self.d.lst_log.insert(_('Start installing PIP...')) + cmd = '"{}" "{}" --user'.format(self.path_python, path_pip) + for line in app.popen(cmd): + if isinstance(line, tuple): + app.errorbox(line) + break + self.d.lst_log.insert(line) + + cmd = self._cmd_pip('-V') + label = app.run(cmd, True) + if label: + self.d.lbl_pip.value = label + self.d.cmd_install_pip.visible = False + self.d.cmd_admin_pip.visible = True + msg = _('PIP installed sucesfully') + app.msgbox(msg) + else: + msg = _('PIP not installed, see log') + app.warning(msg) + except Exception as e: + app.errorbox(e) + + return + + def _cmd_pip(self, args): + cmd = '"{}" -m pip {}'.format(self.path_python, args) + return cmd + + @app.catch_exception + def cmd_admin_pip_action(self, event): + self.d.lst_log.ps_from(self.d.lst_package) + self.d.lst_log.step = 1 + self.d.step = 1 + self.cmd_home_action(None) + return + + def cmd_close_action(self, event): + self.d.close() + return + + def cmd_home_action(self, event): + self.d.txt_search.value = '' + self._list() + return + + def txt_search_key_released(self, event): + if event.KeyCode == app.KEY['enter']: + self.cmd_search_action(None) + return + + if not self.d.txt_search.value.strip(): + self.cmd_home_action(None) + return + + @app.run_in_thread + def _list(self): + self._set_state('list') + self.d.lst_log.visible = False + self.d.lst_package.visible = True + + cmd = self._cmd_pip(' list --format=json') + self.d.lst_package.clear() + result = app.run(cmd, True) + packages = app.json_loads(result) + + for p in packages: + t = '{} - ({})'.format(p['name'], p['version']) + self.d.lst_package.insert(t, 'ok.png') + self.d.lst_package.select() + self.d.txt_search.set_focus() + return + + @app.run_in_thread + def _search(self, value): + self._set_state('search') + line = '' + cmd = self._cmd_pip(' search {}'.format(value)) + self.d.lst_package.clear() + for line in app.popen(cmd): + parts = line.split(')') + name = parts[0].strip() + ')' + description = parts[-1].strip() + parts = name.split('(') + name_verify = parts[0].strip() + package = '{} {}'.format(name, description) + image = PACKAGES.get(name_verify, 'question.png') + self.d.lst_package.insert(package, image) + + if line: + self.d.lst_package.select() + else: + self.d.lst_package.insert(_('Not found...'), 'error.png', show=False) + return + + def cmd_search_action(self, event): + search = self.d.txt_search.value.strip() + if not search: + self.d.txt_search.set_focus() + return + + self._search(search) + return + + @app.run_in_thread + def _install(self, value): + self._set_state('install') + self.d.lst_package.visible = False + self.d.lst_log.visible = True + + line = '' + name = value.split(' ')[0].strip() + cmd = self._cmd_pip(' install --upgrade --user {}'.format(name)) + self.d.lst_log.clear() + for line in app.popen(cmd): + if self.OK1 in line or self.OK2 in line: + self.d.lst_log.insert(line, 'ok.png') + else: + self.d.lst_log.insert(line) + return + + @app.catch_exception + def lst_package_double_click(self, event): + opt = 'install' + if self._states['list']: + opt = 'upgrade' + + name = self.d.lst_package.value + msg = _('Do you want {}:\n\n{} ?').format(opt, name) + if not app.question(msg, TITLE): + return + + self._install(name) + return + + @app.run_in_thread + def _uninstall(self, value): + self._set_state('install') + self.d.lst_package.visible = False + self.d.lst_log.visible = True + + line = '' + name = value.split(' ')[0].strip() + cmd = self._cmd_pip(' uninstall -y {}'.format(name)) + self.d.lst_log.clear() + for line in app.popen(cmd): + if self.OK3 in line: + self.d.lst_log.insert(line, 'ok.png') + else: + self.d.lst_log.insert(line) + return + + def cmd_uninstall_action(self, event): + if not self._states['list']: + msg = _('Select installed package') + app.warning(msg) + return + + name = self.d.lst_package.value + msg = _('Do you want uninstall:\n\n{} ?').format(name) + if not app.question(msg): + return + + self._uninstall(name) + return + + @app.catch_exception + def cmd_shell_action(self, name): + if app.IS_WIN: + cmd = '"{}"'.format(self.path_python) + app.open_file(cmd) + else: + cmd = 'exec "{}"' + if app.IS_MAC: + cmd = 'open "{}"' + elif app.DESKTOP == 'gnome': + cmd = 'gnome-terminal -- {}' + + cmd = cmd.format(self.path_python) + app.run(cmd) + return + + +@app.catch_exception +def _create_dialog(): + args= { + 'Name': 'dialog', + 'Title': 'Zaz-Pip', + 'Width': 200, + 'Height': 220, + } + dialog = app.create_dialog(args) + dialog.id = ID_EXTENSION + dialog.events = Controllers + + lbl_title = '{} {} - {}'.format(app.NAME, app.VERSION, app.OS) + args = { + 'Type': 'Label', + 'Name': 'lbl_title', + 'Label': lbl_title, + 'Width': 100, + 'Height': 15, + 'Border': 1, + 'Align': 1, + 'VerticalAlign': 1, + 'Step': 10, + 'FontHeight': 12, + } + dialog.add_control(args) + dialog.center(dialog.lbl_title, y=5) + + path_python = app.paths.python + cmd = '"{}" -V'.format(path_python) + label = app.run(cmd, True) + + args = { + 'Type': 'Label', + 'Name': 'lbl_python', + 'Label': str(label), + 'Width': 100, + 'Height': 15, + 'Border': 1, + 'Align': 1, + 'VerticalAlign': 1, + 'Step': 10, + 'FontHeight': 11, + } + dialog.add_control(args) + dialog.center(dialog.lbl_python, y=25) + + cmd = '"{}" -m pip -V'.format(path_python) + label = app.run(cmd, True) + exists_pip = True + if not label: + exists_pip = False + label = _('PIP not installed') + args = { + 'Type': 'Label', + 'Name': 'lbl_pip', + 'Label': label, + 'Width': 160, + 'Height': 30, + 'Border': 1, + 'Align': 1, + 'VerticalAlign': 1, + 'Step': 10, + 'MultiLine': True, + } + dialog.add_control(args) + dialog.center(dialog.lbl_pip, y=45) + + args = { + 'Type': 'Button', + 'Name': 'cmd_admin_pip', + 'Label': _('Admin PIP'), + 'Width': 60, + 'Height': 18, + 'Step': 10, + 'ImageURL': 'python.png', + 'ImagePosition': 1, + } + dialog.add_control(args) + dialog.center(dialog.cmd_admin_pip, y=80) + + args = { + 'Type': 'Button', + 'Name': 'cmd_install_pip', + 'Label': _('Install PIP'), + 'Width': 60, + 'Height': 18, + 'Step': 10, + 'ImageURL': 'install.png', + 'ImagePosition': 1, + } + dialog.add_control(args) + dialog.center(dialog.cmd_install_pip, y=80) + + args = { + 'Type': 'Link', + 'Name': 'link_proyect', + 'URL': URL_GIT, + 'Label': URL_GIT, + 'Border': 1, + 'Width': 130, + 'Height': 15, + 'Align': 1, + 'VerticalAlign': 1, + 'Step': 10, + } + dialog.add_control(args) + dialog.center(dialog.link_proyect, y=-5) + + args = { + 'Type': 'Listbox', + 'Name': 'lst_log', + 'Width': 160, + 'Height': 105, + 'Step': 10, + } + dialog.add_control(args) + dialog.center(dialog.lst_log, y=105) + + args = { + 'Type': 'Label', + 'Name': 'lbl_package', + 'Label': _('Packages'), + 'Width': 100, + 'Height': 15, + 'Border': 1, + 'Align': 1, + 'VerticalAlign': 1, + 'Step': 1, + } + lbl = dialog.add_control(args) + + args = { + 'Type': 'Text', + 'Name': 'txt_search', + 'Width': 180, + 'Height': 12, + 'Step': 1, + 'Border': 0, + } + dialog.add_control(args) + + args = { + 'Type': 'Listbox', + 'Name': 'lst_package', + 'Width': 180, + 'Height': 128, + 'Step': 1, + } + dialog.add_control(args) + + args = { + 'Type': 'Button', + 'Name': 'cmd_close', + 'Label': _('~Close'), + 'Width': 60, + 'Height': 18, + 'Step': 1, + 'ImageURL': 'close.png', + 'ImagePosition': 1, + # ~ 'PushButtonType': 2, + } + dialog.add_control(args) + dialog.center(dialog.cmd_close, y=-5) + + args = { + 'Type': 'Button', + 'Name': 'cmd_home', + 'Width': 18, + 'Height': 18, + 'Step': 1, + 'ImageURL': 'home.png', + 'FocusOnClick': False, + 'Y': 2, + } + dialog.add_control(args) + + args = { + 'Type': 'Button', + 'Name': 'cmd_search', + 'Width': 18, + 'Height': 18, + 'Step': 1, + 'ImageURL': 'search.png', + 'FocusOnClick': False, + 'Y': 2, + } + dialog.add_control(args) + + args = { + 'Type': 'Button', + 'Name': 'cmd_uninstall', + 'Width': 18, + 'Height': 18, + 'Step': 1, + 'ImageURL': 'uninstall.png', + 'FocusOnClick': False, + 'Y': 2, + } + dialog.add_control(args) + + args = { + 'Type': 'Button', + 'Name': 'cmd_install', + 'Width': 18, + 'Height': 18, + 'Step': 1, + 'ImageURL': 'install.png', + 'FocusOnClick': False, + 'Y': 2, + } + dialog.add_control(args) + + args = { + 'Type': 'Button', + 'Name': 'cmd_shell', + 'Width': 18, + 'Height': 18, + 'Step': 1, + 'ImageURL': 'shell.png', + 'FocusOnClick': False, + 'Y': 2, + } + dialog.add_control(args) + + controls = (dialog.cmd_home, dialog.cmd_search, + dialog.cmd_install, dialog.cmd_uninstall, dialog.cmd_shell) + dialog.lbl_package.move(dialog.cmd_home) + dialog.txt_search.move(dialog.lbl_package) + dialog.lst_package.move(dialog.txt_search) + dialog.lbl_package.center() + dialog.lst_package.center() + dialog.txt_search.center() + dialog.center(controls) + + dialog.step = 10 + + dialog.cmd_install_pip.visible = not exists_pip + dialog.cmd_admin_pip.visible = exists_pip + dialog.lst_log.visible = False + + return dialog From 0099908afc08b874daaae5ae060893f4d0661b2a Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Thu, 12 Nov 2020 23:10:42 -0600 Subject: [PATCH 03/12] Validate install PIP in Windows --- easymacro.py | 35 +++++++++++------- files/ZAZPip_v0.6.0.oxt | Bin 77469 -> 0 bytes source/pythonpath/easymacro.py | 36 ++++++++++++------- source/pythonpath/main.py | 63 +++++++++++++++++---------------- 4 files changed, 79 insertions(+), 55 deletions(-) delete mode 100644 files/ZAZPip_v0.6.0.oxt diff --git a/easymacro.py b/easymacro.py index 29d4a17..16715df 100644 --- a/easymacro.py +++ b/easymacro.py @@ -39,6 +39,7 @@ import sys import tempfile import threading import time +import traceback import zipfile from collections import OrderedDict @@ -170,6 +171,10 @@ 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 @@ -302,7 +307,7 @@ def catch_exception(f): except Exception as e: name = f.__name__ if IS_WIN: - debug(traceback.format_exc()) + msgbox(traceback.format_exc()) log.error(name, exc_info=True) return func @@ -535,7 +540,7 @@ def run(command, capture=False, split=True): return subprocess.check_output(command, shell=True).decode() cmd = shlex.split(command) - result = subprocess.run(cmd, capture_output=capture, text=True) + result = subprocess.run(cmd, capture_output=capture, text=True, shell=IS_WIN) if capture: result = result.stdout else: @@ -543,15 +548,15 @@ def run(command, capture=False, split=True): return result -# ~ def popen(command, stdin=None): - # ~ try: - # ~ proc = subprocess.Popen(shlex.split(command), shell=IS_WIN, - # ~ stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - # ~ for line in proc.stdout: - # ~ yield line.decode().rstrip() - # ~ except Exception as e: - # ~ error(e) - # ~ yield (e.errno, e.strerror) +def popen(command): + try: + proc = subprocess.Popen(shlex.split(command), shell=IS_WIN, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + for line in proc.stdout: + yield line.decode().rstrip() + except Exception as e: + error(e) + yield (e.errno, e.strerror) def sleep(seconds): @@ -4039,7 +4044,13 @@ class Paths(object): @classproperty def python(self): - return sys.executable + 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): diff --git a/files/ZAZPip_v0.6.0.oxt b/files/ZAZPip_v0.6.0.oxt deleted file mode 100644 index 08a54e7687c03cca60224acdc21e8b453cbc354f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77469 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%0AYgYI7eQs5dqK@Y<7iRMchEd%u1&dI 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 Npn*`w2&6OU{{Y^cKS2Nh diff --git a/source/pythonpath/easymacro.py b/source/pythonpath/easymacro.py index 29d4a17..c2e81bd 100644 --- a/source/pythonpath/easymacro.py +++ b/source/pythonpath/easymacro.py @@ -39,6 +39,7 @@ import sys import tempfile import threading import time +import traceback import zipfile from collections import OrderedDict @@ -170,6 +171,10 @@ 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 @@ -302,7 +307,7 @@ def catch_exception(f): except Exception as e: name = f.__name__ if IS_WIN: - debug(traceback.format_exc()) + msgbox(traceback.format_exc()) log.error(name, exc_info=True) return func @@ -535,7 +540,7 @@ def run(command, capture=False, split=True): return subprocess.check_output(command, shell=True).decode() cmd = shlex.split(command) - result = subprocess.run(cmd, capture_output=capture, text=True) + result = subprocess.run(cmd, capture_output=capture, text=True, shell=IS_WIN) if capture: result = result.stdout else: @@ -543,15 +548,16 @@ def run(command, capture=False, split=True): return result -# ~ def popen(command, stdin=None): - # ~ try: - # ~ proc = subprocess.Popen(shlex.split(command), shell=IS_WIN, - # ~ stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - # ~ for line in proc.stdout: - # ~ yield line.decode().rstrip() - # ~ except Exception as e: - # ~ error(e) - # ~ yield (e.errno, e.strerror) +def popen(command): + try: + proc = subprocess.Popen(shlex.split(command), shell=IS_WIN, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + for line in proc.stdout: + yield line.decode().rstrip() + except Exception as e: + error(e) + msg = f'Error No: {e.errno} - {e.strerror}' + yield msg def sleep(seconds): @@ -4039,7 +4045,13 @@ class Paths(object): @classproperty def python(self): - return sys.executable + 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): diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py index 8b290b8..ebc9bd3 100644 --- a/source/pythonpath/main.py +++ b/source/pythonpath/main.py @@ -4,6 +4,7 @@ import easymacro as app ID_EXTENSION = '' +_ = None TITLE = 'ZAZ-PIP' @@ -34,12 +35,12 @@ def open_dialog_pip(): def run(args, path_locales): - app.install_locales(path_locales) + global _ + _ = app.install_locales(path_locales) globals()[args]() return -@app.catch_exception class Controllers(object): OK1 = 'Successfully installed' OK2 = 'Requirement already' @@ -48,22 +49,20 @@ class Controllers(object): def __init__(self, dialog): self.d = dialog self.path_python = app.paths.python + + def _set_state(self, state): self._states = { 'list': False, 'install': False, 'search': False, 'versions': False, } - - def _set_state(self, state): - for k in self._states.keys(): - self._states[k] = False self._states[state] = True return def cmd_install_pip_action(self, event): msg = _('Do you want install PIP?') - if not app.question(msg, 'ZAZ-Pip'): + if not app.question(msg, TITLE): return self._install_pip() @@ -73,26 +72,25 @@ class Controllers(object): def _install_pip(self): self.d.link_proyect.visible = False self.d.lst_log.visible = True - path_pip = app.get_temp_file(True) - - self.d.lst_log.insert('Download PIP...') - data, err = app.url_open(URL_PIP, verify=False) + path_pip = app.paths.tmp() + self.d.lst_log.insert(_('Download PIP...')) + data, h, err = app.url_open(URL_PIP, verify=False) + app.msgbox(path_pip) if err: msg = _('Do you have internet connection?') app.errorbox('{}\n\n{}'.format(msg, err)) return - app.save_file(path_pip, 'wb', data) - if not app.is_created(path_pip): + app.paths.save_bin(path_pip, data) + if not app.paths.exists(path_pip): msg = _('File PIP not save') app.errorbox(msg) return self.d.lst_log.insert(_('PIP save correctly...')) try: - self.d.lst_log.insert(_('Start installing PIP...')) - cmd = '"{}" "{}" --user'.format(self.path_python, path_pip) + cmd = f'"{self.path_python}" "{path_pip}" --user' for line in app.popen(cmd): if isinstance(line, tuple): app.errorbox(line) @@ -119,7 +117,6 @@ class Controllers(object): cmd = '"{}" -m pip {}'.format(self.path_python, args) return cmd - @app.catch_exception def cmd_admin_pip_action(self, event): self.d.lst_log.ps_from(self.d.lst_package) self.d.lst_log.step = 1 @@ -275,6 +272,7 @@ class Controllers(object): @app.catch_exception def _create_dialog(): + BUTTON_WH = 20 args= { 'Name': 'dialog', 'Title': 'Zaz-Pip', @@ -303,6 +301,7 @@ def _create_dialog(): path_python = app.paths.python cmd = '"{}" -V'.format(path_python) + app.msgbox(cmd) label = app.run(cmd, True) args = { @@ -321,7 +320,9 @@ def _create_dialog(): dialog.center(dialog.lbl_python, y=25) cmd = '"{}" -m pip -V'.format(path_python) + app.msgbox(cmd) label = app.run(cmd, True) + app.msgbox(label) exists_pip = True if not label: exists_pip = False @@ -345,8 +346,8 @@ def _create_dialog(): 'Type': 'Button', 'Name': 'cmd_admin_pip', 'Label': _('Admin PIP'), - 'Width': 60, - 'Height': 18, + 'Width': 70, + 'Height': BUTTON_WH, 'Step': 10, 'ImageURL': 'python.png', 'ImagePosition': 1, @@ -359,7 +360,7 @@ def _create_dialog(): 'Name': 'cmd_install_pip', 'Label': _('Install PIP'), 'Width': 60, - 'Height': 18, + 'Height': BUTTON_WH, 'Step': 10, 'ImageURL': 'install.png', 'ImagePosition': 1, @@ -428,8 +429,8 @@ def _create_dialog(): 'Type': 'Button', 'Name': 'cmd_close', 'Label': _('~Close'), - 'Width': 60, - 'Height': 18, + 'Width': 70, + 'Height': BUTTON_WH, 'Step': 1, 'ImageURL': 'close.png', 'ImagePosition': 1, @@ -441,8 +442,8 @@ def _create_dialog(): args = { 'Type': 'Button', 'Name': 'cmd_home', - 'Width': 18, - 'Height': 18, + 'Width': BUTTON_WH, + 'Height': BUTTON_WH, 'Step': 1, 'ImageURL': 'home.png', 'FocusOnClick': False, @@ -453,8 +454,8 @@ def _create_dialog(): args = { 'Type': 'Button', 'Name': 'cmd_search', - 'Width': 18, - 'Height': 18, + 'Width': BUTTON_WH, + 'Height': BUTTON_WH, 'Step': 1, 'ImageURL': 'search.png', 'FocusOnClick': False, @@ -465,8 +466,8 @@ def _create_dialog(): args = { 'Type': 'Button', 'Name': 'cmd_uninstall', - 'Width': 18, - 'Height': 18, + 'Width': BUTTON_WH, + 'Height': BUTTON_WH, 'Step': 1, 'ImageURL': 'uninstall.png', 'FocusOnClick': False, @@ -477,8 +478,8 @@ def _create_dialog(): args = { 'Type': 'Button', 'Name': 'cmd_install', - 'Width': 18, - 'Height': 18, + 'Width': BUTTON_WH, + 'Height': BUTTON_WH, 'Step': 1, 'ImageURL': 'install.png', 'FocusOnClick': False, @@ -489,8 +490,8 @@ def _create_dialog(): args = { 'Type': 'Button', 'Name': 'cmd_shell', - 'Width': 18, - 'Height': 18, + 'Width': BUTTON_WH, + 'Height': BUTTON_WH, 'Step': 1, 'ImageURL': 'shell.png', 'FocusOnClick': False, From 0804c922f1df8b4ba2b472e271b4b5c30df3c48a Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Tue, 17 Nov 2020 22:54:10 -0600 Subject: [PATCH 04/12] Replace icons png for svg --- .gitignore | 3 +- easymacro.py | 139 +++++++++++++++++++++++++++++++- files/ZAZPip_v0.6.0.oxt | Bin 0 -> 77560 bytes source/images/close.svg | 52 ++++++++++++ source/images/home.svg | 37 +++++++++ source/images/icon_16.bmp | Bin 0 -> 1782 bytes source/images/install.svg | 62 ++++++++++++++ source/images/ok.svg | 50 ++++++++++++ source/images/python.svg | 57 +++++++++++++ source/images/question.svg | 27 +++++++ source/images/search.svg | 41 ++++++++++ source/images/shell.svg | 48 +++++++++++ source/images/uninstall.svg | 63 +++++++++++++++ source/images/zazpip.png | Bin 0 -> 26700 bytes source/pythonpath/easymacro.py | 142 +++++++++++++++++++++++++++++++-- source/pythonpath/main.py | 62 ++++++++------ 16 files changed, 748 insertions(+), 35 deletions(-) create mode 100644 files/ZAZPip_v0.6.0.oxt create mode 100644 source/images/close.svg create mode 100644 source/images/home.svg create mode 100644 source/images/icon_16.bmp create mode 100644 source/images/install.svg create mode 100644 source/images/ok.svg create mode 100644 source/images/python.svg create mode 100644 source/images/question.svg create mode 100644 source/images/search.svg create mode 100644 source/images/shell.svg create mode 100644 source/images/uninstall.svg create mode 100644 source/images/zazpip.png diff --git a/.gitignore b/.gitignore index 399bdd7..0030b5e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,7 @@ __pycache__/ *.po~ *.log -images/ - +/images docs/ # Virtualenv diff --git a/easymacro.py b/easymacro.py index 16715df..16563ad 100644 --- a/easymacro.py +++ b/easymacro.py @@ -85,6 +85,7 @@ from com.sun.star.lang import XEventListener 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 # ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1FontUnderline.html from com.sun.star.awt import FontUnderline @@ -184,6 +185,11 @@ DIR = { 'locales': 'locales', } DEFAULT_MIME_TYPE = 'png' + +KEY = { + 'enter': 1280, +} + MODIFIERS = { 'shift': KeyModifier.SHIFT, 'ctrl': KeyModifier.MOD1, @@ -1153,6 +1159,64 @@ class LODocument(object): return +class LOCellStyle(LOBaseObject): + + def __init__(self, obj): + super().__init__(obj) + + @property + def name(self): + return self.obj.Name + + @property + def properties(self): + properties = self.obj.PropertySetInfo.Properties + data = {p.Name: getattr(self.obj, p.Name) for p in properties} + return data + @properties.setter + def properties(self, values): + _set_properties(self.obj, values) + + +class LOCellStyles(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + + def __len__(self): + return len(self.obj) + + def __getitem__(self, index): + return LOCellStyle(self.obj[index]) + + def __setitem__(self, key, value): + self.obj[key] = value + + def __delitem__(self, key): + if not isinstance(key, str): + key = key.Name + del self.obj[key] + + def __contains__(self, item): + return item in self.obj + + @property + def obj(self): + return self._obj + + @property + def names(self): + return self.obj.ElementNames + + def new(self, name: str=''): + obj = self._doc.create_instance('com.sun.star.style.CellStyle') + if name: + self.obj[name] = obj + obj = LOCellStyle(obj) + return obj + + class LOCalc(LODocument): def __init__(self, obj): @@ -1208,6 +1272,14 @@ class LOCalc(LODocument): 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) @@ -2121,6 +2193,18 @@ class LOWriterTextRange(object): self._is_paragraph = self.obj.ImplementationName == 'SwXParagraph' self._is_table = self.obj.ImplementationName == 'SwXTextTable' + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + for i, p in enumerate(self.obj): + if i == self._index: + obj = LOWriterTextRange(p, self._doc) + self._index += 1 + return obj + raise StopIteration + @property def obj(self): return self._obj @@ -2142,7 +2226,7 @@ class LOWriterTextRange(object): @property def text(self): - return self.obj.getText() + return self.obj.Text @property def cursor(self): @@ -2152,6 +2236,10 @@ class LOWriterTextRange(object): def dp(self): return self._doc.dp + @property + def is_table(self): + return self._is_table + def offset(self): cursor = self.cursor.getEnd() return LOWriterTextRange(cursor, self._doc) @@ -2181,7 +2269,23 @@ class LOWriterTextRanges(object): self._doc = doc def __getitem__(self, index): - return LOWriterTextRange(self.obj[index], self._doc) + for i, p in enumerate(self.obj): + if i == index: + obj = LOWriterTextRange(p, self._doc) + break + return obj + + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + for i, p in enumerate(self.obj): + if i == self._index: + obj = LOWriterTextRange(p, self._doc) + self._index += 1 + return obj + raise StopIteration @property def obj(self): @@ -2194,6 +2298,14 @@ class LOWriter(LODocument): super().__init__(obj) self._type = WRITER + @property + def text(self): + return LOWriterTextRange(self.obj.Text, self) + + @property + def paragraphs(self): + return LOWriterTextRanges(self.obj.Text, self) + @property def selection(self): sel = self.obj.CurrentSelection @@ -2841,7 +2953,7 @@ def _add_listeners(events, control, name=''): 'addMouseListener': EventsMouse, 'addFocusListener': EventsFocus, # ~ 'addItemListener': EventsItem, - # ~ 'addKeyListener': EventsKey, + 'addKeyListener': EventsKey, # ~ 'addTabListener': EventsTab, } if hasattr(control, 'obj'): @@ -2984,6 +3096,27 @@ class EventsFocus(EventsListenerBase, XFocusListener): return +class EventsKey(EventsListenerBase, XKeyListener): + """ + event.KeyChar + event.KeyCode + event.KeyFunc + event.Modifiers + """ + + def __init__(self, controller, name): + super().__init__(controller, name) + + def keyPressed(self, event): + pass + + def keyReleased(self, event): + event_name = '{}_key_released'.format(self._name) + if hasattr(self._controller, event_name): + getattr(self._controller, event_name)(event) + return + + # ~ BorderColor = ? # ~ FontStyleName = ? # ~ HelpURL = ? diff --git a/files/ZAZPip_v0.6.0.oxt b/files/ZAZPip_v0.6.0.oxt new file mode 100644 index 0000000000000000000000000000000000000000..c62bcdbb594d3cb90e05499385963c9d43fa4822 GIT binary patch literal 77560 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�kJCN&YhHynJKzBFlA1!CNMThPfX@$IP>-b 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%0D@jTJKeHs|{>;dgiwF9?)0G2>$zY2D_9!5LN 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 a{u-v0WTC;z2G%$5Uo2RmmBBv+0R9Wr)RB__ literal 0 HcmV?d00001 diff --git a/source/images/close.svg b/source/images/close.svg new file mode 100644 index 0000000..8b7bc47 --- /dev/null +++ b/source/images/close.svg @@ -0,0 +1,52 @@ + + + + + + image/svg+xml + + 9.4 + + + + + 9.4 + Created with Sketch. + + + + + + + + diff --git a/source/images/home.svg b/source/images/home.svg new file mode 100644 index 0000000..517ac7d --- /dev/null +++ b/source/images/home.svg @@ -0,0 +1,37 @@ + +image/svg+xml diff --git a/source/images/icon_16.bmp b/source/images/icon_16.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8eaab006c82156f7b69b18eb90dbd2a7718382ed GIT binary patch literal 1782 zcmcJPJyL}*5XTpI0y7r29>EiM0DBK$zogOf1$}5bq?*? zI$;!NVn&}=mMcRJomli`XYOl49loISR9Vp6_3*i*bf~r~(dsWUqUk`YK=6yLxwMtQ zPWmz>0fJU#5y$u#^sRA>n_KCpQ3Gb0V3SixIhMZE6E$WY($A;||DsK8NoUtGhr?D4 zaIj5;8A{}EHJt;h+*x^iTn<|`imi5YRaN^eT1{62Uhl)QKUWNlFnrzxYRrGwf2&}+ A+W-In literal 0 HcmV?d00001 diff --git a/source/images/install.svg b/source/images/install.svg new file mode 100644 index 0000000..c878786 --- /dev/null +++ b/source/images/install.svg @@ -0,0 +1,62 @@ + +image/svg+xml diff --git a/source/images/ok.svg b/source/images/ok.svg new file mode 100644 index 0000000..f211040 --- /dev/null +++ b/source/images/ok.svg @@ -0,0 +1,50 @@ + +image/svg+xml diff --git a/source/images/python.svg b/source/images/python.svg new file mode 100644 index 0000000..9d3262f --- /dev/null +++ b/source/images/python.svg @@ -0,0 +1,57 @@ + +image/svg+xml diff --git a/source/images/question.svg b/source/images/question.svg new file mode 100644 index 0000000..2fc074d --- /dev/null +++ b/source/images/question.svg @@ -0,0 +1,27 @@ + +image/svg+xml diff --git a/source/images/search.svg b/source/images/search.svg new file mode 100644 index 0000000..d928e3b --- /dev/null +++ b/source/images/search.svg @@ -0,0 +1,41 @@ + +image/svg+xml diff --git a/source/images/shell.svg b/source/images/shell.svg new file mode 100644 index 0000000..2e4350a --- /dev/null +++ b/source/images/shell.svg @@ -0,0 +1,48 @@ + +image/svg+xml diff --git a/source/images/uninstall.svg b/source/images/uninstall.svg new file mode 100644 index 0000000..5a7c6bd --- /dev/null +++ b/source/images/uninstall.svg @@ -0,0 +1,63 @@ + +image/svg+xml diff --git a/source/images/zazpip.png b/source/images/zazpip.png new file mode 100644 index 0000000000000000000000000000000000000000..2f210eda94433623e613c91ad5675eb6631dd370 GIT binary patch literal 26700 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 Date: Tue, 17 Nov 2020 23:16:06 -0600 Subject: [PATCH 05/12] Add control for select file --- easymacro.py | 2 +- files/ZAZPip_v0.6.0.oxt | Bin 77560 -> 77668 bytes source/pythonpath/main.py | 26 +++++++++++++++++++++++--- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/easymacro.py b/easymacro.py index 16563ad..88dfe6a 100644 --- a/easymacro.py +++ b/easymacro.py @@ -4248,7 +4248,7 @@ class Paths(object): return path @classmethod - def get_file(cls, init_dir='', filters=(), multiple=False): + def get_file(cls, init_dir: str='', filters=(), multiple: bool=False): """ init_folder: folder default open multiple: True for multiple selected diff --git a/files/ZAZPip_v0.6.0.oxt b/files/ZAZPip_v0.6.0.oxt index c62bcdbb594d3cb90e05499385963c9d43fa4822..da50cd583aea3f6c05057335c8efc5583abb27bd 100644 GIT binary patch delta 3331 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 Date: Tue, 17 Nov 2020 23:24:57 -0600 Subject: [PATCH 06/12] Change filters --- easymacro.py | 11 ++++------- files/ZAZPip_v0.6.0.oxt | Bin 77668 -> 77712 bytes source/pythonpath/easymacro.py | 11 ++++------- source/pythonpath/main.py | 2 +- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/easymacro.py b/easymacro.py index 88dfe6a..c1d1363 100644 --- a/easymacro.py +++ b/easymacro.py @@ -50,7 +50,7 @@ from functools import wraps from pathlib import Path from pprint import pprint from string import Template -from typing import Any +from typing import Any, Union from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError @@ -4248,15 +4248,11 @@ class Paths(object): return path @classmethod - def get_file(cls, init_dir: str='', filters=(), multiple: bool=False): + def get_file(cls, init_dir: str='', filters: str='', multiple: bool=False): """ init_folder: folder default open multiple: True for multiple selected - filters: Example - ( - ('XML', '*.xml'), - ('TXT', '*.txt'), - ) + filters: 'xml' or 'xml,txt' """ if not init_dir: init_dir = cls.documents @@ -4268,6 +4264,7 @@ class Paths(object): file_picker.setMultiSelectionMode(multiple) if filters: + filters = [(f.upper(), f'*.{f.lower()}') for f in filters.split(',')] file_picker.setCurrentFilter(filters[0][0]) for f in filters: file_picker.appendFilter(f[0], f[1]) diff --git a/files/ZAZPip_v0.6.0.oxt b/files/ZAZPip_v0.6.0.oxt index da50cd583aea3f6c05057335c8efc5583abb27bd..d5e2e5525559976e29643df124b10627d3ccc577 100644 GIT binary patch delta 29688 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

6D$u)8EdM#kJW)q^= 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 diff --git a/source/pythonpath/easymacro.py b/source/pythonpath/easymacro.py index 16563ad..c1d1363 100644 --- a/source/pythonpath/easymacro.py +++ b/source/pythonpath/easymacro.py @@ -50,7 +50,7 @@ from functools import wraps from pathlib import Path from pprint import pprint from string import Template -from typing import Any +from typing import Any, Union from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError @@ -4248,15 +4248,11 @@ class Paths(object): return path @classmethod - def get_file(cls, init_dir='', filters=(), multiple=False): + def get_file(cls, init_dir: str='', filters: str='', multiple: bool=False): """ init_folder: folder default open multiple: True for multiple selected - filters: Example - ( - ('XML', '*.xml'), - ('TXT', '*.txt'), - ) + filters: 'xml' or 'xml,txt' """ if not init_dir: init_dir = cls.documents @@ -4268,6 +4264,7 @@ class Paths(object): file_picker.setMultiSelectionMode(multiple) if filters: + filters = [(f.upper(), f'*.{f.lower()}') for f in filters.split(',')] file_picker.setCurrentFilter(filters[0][0]) for f in filters: file_picker.appendFilter(f[0], f[1]) diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py index cd46fb8..9eb9b85 100644 --- a/source/pythonpath/main.py +++ b/source/pythonpath/main.py @@ -285,7 +285,7 @@ class Controllers(object): @app.catch_exception def cmd_explore_action(self, event): - file_name = app.paths.get_file(filters=(('TXT', '*.txt'),)) + file_name = app.paths.get_file(filters='txt') self.d.txt_search.value = file_name app.debug(file_name) return From 475840d7e8927b4134eed49c4f65fceb7792246a Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Wed, 18 Nov 2020 12:47:12 -0600 Subject: [PATCH 07/12] Install with file requeriments.txt --- easymacro.py | 9 +++------ files/ZAZPip_v0.6.0.oxt | Bin 77712 -> 77747 bytes source/pythonpath/easymacro.py | 9 +++------ source/pythonpath/main.py | 23 ++++++++++++++++------- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/easymacro.py b/easymacro.py index c1d1363..6ddace0 100644 --- a/easymacro.py +++ b/easymacro.py @@ -4207,14 +4207,10 @@ class Paths(object): return cls.to_system(getattr(path, name)) @classmethod - def get(cls, init_dir='', filters=()): + def get(cls, init_dir='', filters: str=''): """ Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html - filters: Example - ( - ('XML', '*.xml'), - ('TXT', '*.txt'), - ) + filters: 'xml' or 'txt,xml' """ if not init_dir: init_dir = cls.documents @@ -4224,6 +4220,7 @@ class Paths(object): file_picker.setDisplayDirectory(init_dir) file_picker.initialize((2,)) if filters: + filters = [(f.upper(), f'*.{f.lower()}') for f in filters.split(',')] file_picker.setCurrentFilter(filters[0][0]) for f in filters: file_picker.appendFilter(f[0], f[1]) diff --git a/files/ZAZPip_v0.6.0.oxt b/files/ZAZPip_v0.6.0.oxt index d5e2e5525559976e29643df124b10627d3ccc577..439c35e98b91c0e04123253418a5f325dcd066f8 100644 GIT binary patch delta 32540 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 Q_Wj3_77Bs->p~#^2PwM$761SM delta 32562 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∈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 Date: Wed, 18 Nov 2020 16:47:07 -0600 Subject: [PATCH 08/12] Validate in Windows --- files/ZAZPip_v0.6.0.oxt | Bin 77747 -> 77759 bytes source/pythonpath/main.py | 5 ++--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/files/ZAZPip_v0.6.0.oxt b/files/ZAZPip_v0.6.0.oxt index 439c35e98b91c0e04123253418a5f325dcd066f8..e6c732c18755ae0b583db32f86fed519182a80a7 100644 GIT binary patch delta 3399 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 Date: Wed, 18 Nov 2020 22:35:13 -0600 Subject: [PATCH 09/12] Update easymacro --- easymacro.py | 79 +++++++++++++++++++--------------------------------- 1 file changed, 29 insertions(+), 50 deletions(-) diff --git a/easymacro.py b/easymacro.py index 6ddace0..17d1253 100644 --- a/easymacro.py +++ b/easymacro.py @@ -184,7 +184,6 @@ DIR = { 'images': 'images', 'locales': 'locales', } -DEFAULT_MIME_TYPE = 'png' KEY = { 'enter': 1280, @@ -219,6 +218,7 @@ MENUS = { 'show': '.uno:SlideShowMenu', } +DEFAULT_MIME_TYPE = 'png' MIME_TYPE = { 'png': 'image/png', 'jpg': 'image/jpeg', @@ -917,54 +917,6 @@ class LOBaseObject(object): return self._obj -class LOImage(object): - TYPE = { - 'png': 'image/png', - 'jpg': 'image/jpeg', - } - - def __init__(self, obj): - self._obj = obj - - @property - def obj(self): - return self._obj - - @property - def name(self): - return self.obj.Name or 'img' - - @property - def mimetype(self): - return self.obj.Bitmap.MimeType - - def save(self, path, mimetype=DEFAULT_MIME_TYPE): - p = _P(path) - if _P.is_dir(path): - name = self.name - else: - path = p.path - name = p.name - - path = _P.join(path, f'{name}.{mimetype.lower()}') - args = dict( - URL = _P.to_url(path), - MimeType = self.TYPE[mimetype], - ) - if not _export_image(self.obj, args): - path = '' - - # ~ size = len(self.obj.Bitmap.DIB) - # ~ data = self.obj.GraphicStream.readBytes((), size) - # ~ data = data[-1].value - - # ~ data = self.obj.Bitmap.DIB.value - # ~ data = self.obj.Graphic.DIB.value - - # ~ _P.save_bin(path, data) - return path - - class LODocument(object): FILTERS = { 'doc': 'MS Word 97', @@ -2422,6 +2374,33 @@ class LOShape(LOBaseObject): 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): @@ -2533,7 +2512,7 @@ class LODrawImpress(LODocument): def paste(self): call_dispatch(self.frame, '.uno:Paste') - return self.selection + return self.current_page[-1] def add(self, type_shape, args={}): return self.current_page.add(type_shape, args) From 46b4855f6073c9bdb8bbca5f76c7a672e60da2e6 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 20 Dec 2020 21:44:13 -0600 Subject: [PATCH 10/12] Update easymacro.py --- easymacro.py | 2044 ++++++++++++++++++++++++++++++++++--- source/pythonpath/main.py | 1 - 2 files changed, 1916 insertions(+), 129 deletions(-) diff --git a/easymacro.py b/easymacro.py index 17d1253..d91c6af 100644 --- a/easymacro.py +++ b/easymacro.py @@ -54,6 +54,7 @@ from typing import Any, Union from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError +import imaplib import smtplib from smtplib import SMTPException, SMTPAuthenticationError from email.mime.multipart import MIMEMultipart @@ -68,7 +69,7 @@ import unohelper from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS from com.sun.star.awt.MessageBoxResults import YES from com.sun.star.awt import Rectangle, Size, Point -from com.sun.star.awt.PosSize import POSSIZE +from com.sun.star.awt.PosSize import POSSIZE, SIZE from com.sun.star.awt import Key, KeyModifier, KeyEvent from com.sun.star.container import NoSuchElementException from com.sun.star.datatransfer import XTransferable, DataFlavor @@ -78,25 +79,38 @@ from com.sun.star.sheet import TableFilterField from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA from com.sun.star.util import Time, Date, DateTime +from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK from com.sun.star.text.TextContentAnchorType import AS_CHARACTER from com.sun.star.awt import XActionListener from com.sun.star.lang import XEventListener +from com.sun.star.awt import XMenuListener from com.sun.star.awt import XMouseListener from com.sun.star.awt import XMouseMotionListener from com.sun.star.awt import XFocusListener from com.sun.star.awt import XKeyListener +from com.sun.star.awt import XItemListener +from com.sun.star.awt import XTabListener +from com.sun.star.awt import XWindowListener +from com.sun.star.awt import XTopWindowListener +from com.sun.star.awt.grid import XGridDataListener +from com.sun.star.awt.grid import XGridSelectionListener +from com.sun.star.script import ScriptEventDescriptor # ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1FontUnderline.html from com.sun.star.awt import FontUnderline from com.sun.star.style.VerticalAlignment import TOP, MIDDLE, BOTTOM +from com.sun.star.view.SelectionType import SINGLE, MULTI, RANGE + +from com.sun.star.sdb.CommandType import TABLE, QUERY, COMMAND + try: from peewee import Database, DateTimeField, DateField, TimeField, \ __exception_wrapper__ except ImportError as e: Database = DateField = TimeField = DateTimeField = object - print('Install peewee') + print('You need install peewee, only if you will develop with Base') LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' @@ -110,6 +124,7 @@ log = logging.getLogger(__name__) # ~ You can get custom salt # ~ codecs.encode(os.urandom(16), 'hex') +# ~ but, not modify this file, modify in import file SALT = b'c9548699d4e432dfd2b46adddafbb06d' TIMEOUT = 10 @@ -151,6 +166,7 @@ OBJ_GRAPHIC = 'SwXTextGraphicObject' OBJ_TEXTS = 'SwXTextRanges' OBJ_TEXT = 'SwXTextRange' + # ~ from com.sun.star.sheet.FilterOperator import EMPTY, NO_EMPTY, EQUAL, NOT_EQUAL class FilterOperator(IntEnum): EMPTY = 0 @@ -164,6 +180,29 @@ class Border(IntEnum): BORDER = 1 SIMPLE = 2 + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet.html#aa5aa6dbecaeb5e18a476b0a58279c57a +class ValidationType(): + from com.sun.star.sheet.ValidationType \ + import ANY, WHOLE, DECIMAL, DATE, TIME, TEXT_LEN, LIST, CUSTOM +VT = ValidationType + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet.html#aecf58149730f4c8c5c18c70f3c7c5db7 +class ValidationAlertStyle(): + from com.sun.star.sheet.ValidationAlertStyle \ + import STOP, WARNING, INFO, MACRO +VAS = ValidationAlertStyle + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1ConditionOperator2.html +class ConditionOperator(): + from com.sun.star.sheet.ConditionOperator2 \ + import NONE, EQUAL, NOT_EQUAL, GREATER, GREATER_EQUAL, LESS, \ + LESS_EQUAL, BETWEEN, NOT_BETWEEN, FORMULA, DUPLICATE, NOT_DUPLICATE +CO = ConditionOperator + + OS = platform.system() IS_WIN = OS == 'Windows' IS_MAC = OS == 'Darwin' @@ -449,6 +488,8 @@ def _get_dispatch() -> Any: return create_instance('com.sun.star.frame.DispatchHelper') +# ~ https://wiki.documentfoundation.org/Development/DispatchCommands +# ~ Used only if not exists in API def call_dispatch(frame: Any, url: str, args: dict={}) -> None: dispatch = _get_dispatch() opt = dict_to_property(args) @@ -513,7 +554,6 @@ def _get_url_script(args): url = 'vnd.sun.star.script' url = f'{url}:{library}{module}{name}?language={language}&location={location}' - return url @@ -753,6 +793,11 @@ def decrypt(token, password): return data +def switch_design_mode(doc): + call_dispatch(doc.frame, '.uno:SwitchControlDesignMode') + return + + class SmtpServer(object): def __init__(self, config): @@ -892,6 +937,63 @@ def send_email(server, message): return +class ImapServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + try: + # ~ hosts = 'gmail' in config['server'] + if config['ssl']: + self._server = imaplib.IMAP4_SSL(config['server'], config['port']) + else: + self._server = imaplib.IMAP4(config['server'], config['port']) + self._server.login(config['user'], config['password']) + self._server.select() + return True + except imaplib.IMAP4.error as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def get_folders(self, exclude=()): + folders = {} + result, subdir = self._server.list() + for s in subdir: + print(s.decode('utf-8')) + return folders + + def close(self): + try: + self._server.close() + self._server.logout() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + # ~ Classes class LOBaseObject(object): @@ -901,7 +1003,7 @@ class LOBaseObject(object): def __setattr__(self, name, value): exists = hasattr(self, name) - if not exists and not name in ('_obj', '_index'): + if not exists and not name in ('_obj', '_index', '_view'): setattr(self._obj, name, value) else: super().__setattr__(name, value) @@ -1048,6 +1150,10 @@ class LODocument(object): call_dispatch(self.frame, '.uno:Copy') return + def insert_contents(self, args={}): + call_dispatch(self.frame, '.uno:InsertContents', args) + return + def paste(self): sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') transferable = sc.getContents() @@ -1415,66 +1521,279 @@ class LOSheetCharts(object): class LOFormControl(LOBaseObject): + EVENTS = { + 'action': 'actionPerformed', + 'click': 'mousePressed', + } + TYPES = { + 'actionPerformed': 'XActionListener', + 'mousePressed': 'XMouseListener', + } - def __init__(self, obj): - self._obj = obj - self._control = self.doc.CurrentController.getControl(self.obj) + def __init__(self, obj, view, form): + super().__init__(obj) + self._view = view + self._form = form + self._m = view.Model + self._index = -1 def __setattr__(self, name, value): - if name == '_control': + if name in ('_form', '_view', '_m', '_index'): self.__dict__[name] = value else: super().__setattr__(name, value) + def __str__(self): + return f'{self.name} ({self.type}) {[self.index]}' + + @property + def form(self): + return self._form + @property def doc(self): - return self.obj.Parent.Parent.Parent + return self.obj.Parent.Forms.Parent + + @property + def name(self): + return self._m.Name + @name.setter + def name(self, value): + self._m.Name = value + + @property + def tag(self): + return self._m.Tag + @tag.setter + def tag(self, value): + self._m.Tag = value + + @property + def index(self): + return self._index + @index.setter + def index(self, value): + self._index = value + + @property + def enabled(self): + return self._m.Enabled + @enabled.setter + def enabled(self, value): + self._m.Enabled = value + + @property + def events(self): + return self.form.getScriptEvents(self.index) + def add_event(self, name, macro): + if not 'name' in macro: + macro['name'] = '{}_{}'.format(self.name, name) + + event = ScriptEventDescriptor() + event.AddListenerParam = '' + event.EventMethod = self.EVENTS[name] + event.ListenerType = self.TYPES[event.EventMethod] + event.ScriptCode = _get_url_script(macro) + event.ScriptType = 'Script' + + for ev in self.events: + if ev.EventMethod == event.EventMethod and \ + ev.ListenerType == event.ListenerType: + self.form.revokeScriptEvent(self.index, + event.ListenerType, event.EventMethod, event.AddListenerParam) + break + + self.form.registerScriptEvent(self.index, event) + return + + def set_focus(self): + self._view.setFocus() + return + + +class LOFormControlLabel(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'label' + + @property + def value(self): + return self._m.Label + @value.setter + def value(self, value): + self._m.Label = value + + +class LOFormControlText(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'text' + + @property + def value(self): + return self._m.Text + @value.setter + def value(self, value): + self._m.Text = value + + +class LOFormControlButton(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'button' + + @property + def value(self): + return self._m.Label + @value.setter + def value(self, value): + self._m.Text = Label + + +FORM_CONTROL_CLASS = { + 'label': LOFormControlLabel, + 'text': LOFormControlText, + 'button': LOFormControlButton, +} + + +class LOForm(object): + MODELS = { + 'label': 'com.sun.star.form.component.FixedText', + 'text': 'com.sun.star.form.component.TextField', + 'button': 'com.sun.star.form.component.CommandButton', + } + + def __init__(self, obj, draw_page): + self._obj = obj + self._dp = draw_page + self._controls = {} + self._init_controls() + + def __getitem__(self, index): + control = self.obj[index] + return self._controls[control.Name] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + def __str__(self): + return f'Form: {self.name}' + + def _init_controls(self): + types = { + 'com.sun.star.form.OFixedTextModel': 'label', + 'com.sun.star.form.OEditModel': 'text', + 'com.sun.star.form.OButtonModel': 'button', + } + for i, control in enumerate(self.obj): + name = control.Name + tipo = types[control.ImplementationName] + view = self.doc.CurrentController.getControl(control) + control = FORM_CONTROL_CLASS[tipo](control, view) + control.index = i + setattr(self, name, control) + self._controls[name] = control + return + + @property + def obj(self): + return self._obj @property def name(self): return self.obj.Name + @name.setter + def name(self, value): + self.obj.Name = value @property - def label(self): - return self.obj.Label - - def set_focus(self): - self._control.setFocus() - return - - -class LOForm(object): - - def __init__(self, obj): - self._obj = obj - - def __getitem__(self, index): - return LOFormControl(self.obj[index]) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - pass - - def __contains__(self, item): - return item in self.obj - - def __len__(self): - return len(self.obj) + def source(self): + return self.obj.DataSourceName + @source.setter + def source(self, value): + self.obj.DataSourceName = value @property - def obj(self): - return self._obj + def type(self): + return self.obj.CommandType + @type.setter + def type(self, value): + self.obj.CommandType = value + + @property + def command(self): + return self.obj.Command + @command.setter + def command(self, value): + self.obj.Command = value + + @property + def doc(self): + return self.obj.Parent.Parent + + def _special_properties(self, tipo, args): + if tipo == 'button': + # ~ if 'ImageURL' in args: + # ~ args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + return args + + def add(self, args): + name = args['Name'] + tipo = args.pop('Type').lower() + w = args.pop('Width') + h = args.pop('Height') + x = args.pop('X', 0) + y = args.pop('Y', 0) + control = self.doc.createInstance('com.sun.star.drawing.ControlShape') + control.setSize(Size(w, h)) + control.setPosition(Point(x, y)) + model = self.doc.createInstance(self.MODELS[tipo]) + args = self._special_properties(tipo, args) + _set_properties(model, args) + control.Control = model + index = len(self) + self.obj.insertByIndex(index, model) + self._dp.add(control) + view = self.doc.CurrentController.getControl(self.obj.getByName(name)) + control = FORM_CONTROL_CLASS[tipo](control, view, self.obj) + control.index = index + setattr(self, name, control) + self._controls[name] = control + return control class LOSheetForms(object): - def __init__(self, obj): - self._obj = obj + def __init__(self, draw_page): + self._dp = draw_page + self._obj = draw_page.Forms def __getitem__(self, index): - return LOForm(self.obj[index]) + return LOForm(self.obj[index], self._dp) def __enter__(self): return self @@ -1492,24 +1811,144 @@ class LOSheetForms(object): def obj(self): return self._obj + @property + def doc(self): + return self.obj.Parent + @property + def count(self): + return len(self) + + @property + def names(self): + return self.obj.ElementNames + + def insert(self, name): + form = self.doc.createInstance('com.sun.star.form.component.Form') + self.obj.insertByName(name, form) + return LOForm(form, self._dp) + + def remove(self, index): + if isinstance(index, int): + self.obj.removeByIndex(index) + else: + self.obj.removeByName(index) + return + + +# ~ IsFiltered, +# ~ IsManualPageBreak, +# ~ IsStartOfNewPage class LOSheetRows(object): - def __init__(self, sheet): + def __init__(self, sheet, obj): self._sheet = sheet - self._obj = sheet.obj.Rows + self._obj = obj def __getitem__(self, index): - return LOSheetRows(self.obj[index]) + if isinstance(index, int): + rows = LOSheetRows(self._sheet, self.obj[index]) + else: + rango = self._sheet[index.start:index.stop,0:] + rows = LOSheetRows(self._sheet, rango.obj.Rows) + return rows + + def __len__(self): + return self.obj.Count @property def obj(self): return self._obj + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def color(self): + return self.obj.CellBackColor + @color.setter + def color(self, value): + self.obj.CellBackColor = value + + @property + def is_transparent(self): + return self.obj.IsCellBackgroundTransparent + @is_transparent.setter + def is_transparent(self, value): + self.obj.IsCellBackgroundTransparent = value + + @property + def height(self): + return self.obj.Height + @height.setter + def height(self, value): + self.obj.Height = value + + def optimal(self): + self.obj.OptimalHeight = True + return + def insert(self, index, count): self.obj.insertByIndex(index, count) - end = index + count - return self._sheet[index:end,0:] + return + + def remove(self, index, count): + self.obj.removeByIndex(index, count) + return + + +# ~ IsManualPageBreak, +# ~ IsStartOfNewPage +class LOSheetColumns(object): + + def __init__(self, sheet, obj): + self._sheet = sheet + self._obj = obj + + def __getitem__(self, index): + if isinstance(index, (int, str)): + rows = LOSheetColumns(self._sheet, self.obj[index]) + else: + rango = self._sheet[0,index.start:index.stop] + rows = LOSheetColumns(self._sheet, rango.obj.Columns) + return rows + + def __len__(self): + return self.obj.Count + + @property + def obj(self): + return self._obj + + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def width(self): + return self.obj.Width + @width.setter + def width(self, value): + self.obj.Width = value + + def optimal(self): + self.obj.OptimalWidth = True + return + + def insert(self, index, count): + self.obj.insertByIndex(index, count) + return + + def remove(self, index, count): + self.obj.removeByIndex(index, count) + return class LOCalcSheet(object): @@ -1608,14 +2047,44 @@ class LOCalcSheet(object): @property def rows(self): - return LOSheetRows(self) + return LOSheetRows(self, self.obj.Rows) + + @property + def columns(self): + return LOSheetColumns(self, self.obj.Columns) @property def forms(self): - return LOSheetForms(self.obj.DrawPage.Forms) + return LOSheetForms(self.obj.DrawPage) + + @property + def events(self): + names = ('OnFocus', 'OnUnfocus', 'OnSelect', 'OnDoubleClick', + 'OnRightClick', 'OnChange', 'OnCalculate') + evs = self.obj.Events + events = {n: _property_to_dict(evs.getByName(n)) for n in names + if evs.getByName(n)} + return events + @events.setter + def events(self, values): + pv = '[]com.sun.star.beans.PropertyValue' + ev = self.obj.Events + for name, v in values.items(): + url = _get_url_script(v) + args = dict_to_property(dict(EventType='Script', Script=url)) + # ~ e.replaceByName(k, args) + uno.invoke(ev, 'replaceByName', (name, uno.Any(pv, args))) + + @property + def search_descriptor(self): + return self.obj.createSearchDescriptor() + + @property + def replace_descriptor(self): + return self.obj.createReplaceDescriptor() def activate(self): - self.doc.activate(self._obj) + self.doc.activate(self.obj) return def clean(self): @@ -1667,32 +2136,10 @@ class LOCalcSheet(object): rango = self.used_area return rango.render(data, clean) - -class LOCalcRows(object): - - def __init__(self, obj): - self._obj = obj - - def __len__(self): - return self.obj.Count - - def __str__(self): - return 'Rows' - - @property - def obj(self): - return self._obj - - @property - def count(self): - return len(self) - - @property - def visible(self): - return self.obj.IsVisible - @visible.setter - def visible(self, value): - self.obj.IsVisible = value + def find(self, search_string, rango=None): + if rango is None: + rango = self.used_area + return rango.find(search_string) class LOCalcRange(object): @@ -1727,6 +2174,9 @@ class LOCalcRange(object): def __exit__(self, exc_type, exc_value, traceback): pass + def __contains__(self, item): + return item.in_range(self) + def __str__(self): if self.is_none: s = 'Range: None' @@ -1790,7 +2240,7 @@ class LOCalcRange(object): @property def rows(self): - return LOCalcRows(self.obj.Rows) + return LOSheetRows(self.sheet, self.obj.Rows) @property def row(self): @@ -1943,10 +2393,112 @@ class LOCalcRange(object): rangos = [LOCalcRange(self.sheet[r.AbsoluteName].obj) for r in rangos] return tuple(rangos) + @property + def merged_area(self): + cursor = self.cursor + cursor.collapseToMergedArea() + rango = LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + return rango + + @property + def empty(self): + cursor = self.sheet.get_cursor(self.obj) + cursor = self.cursor + rangos = cursor.queryEmptyCells() + rangos = [LOCalcRange(self.sheet[r.AbsoluteName].obj) for r in rangos] + return tuple(rangos) + + @property + def merge(self): + return self.obj.IsMerged + @merge.setter + def merge(self, value): + self.obj.merge(value) + + @property + def style(self): + return self.obj.CellStyle + @style.setter + def style(self, value): + self.obj.CellStyle = value + + @property + def auto_format(self): + return '' + @auto_format.setter + def auto_format(self, value): + self.obj.autoFormat(value) + + @property + def validation(self): + return self.obj.Validation + @validation.setter + def validation(self, values): + current = self.validation + if not values: + current.Type = ValidationType.ANY + current.ShowInputMessage = False + else: + is_list = False + for k, v in values.items(): + if k == 'Type' and v == VT.LIST: + is_list = True + if k == 'Formula1' and is_list: + if isinstance(v, (tuple, list)): + v = ';'.join(['"{}"'.format(i) for i in v]) + setattr(current, k, v) + self.obj.Validation = current + def select(self): self.doc.select(self.obj) return + def search(self, options, find_all=True): + rangos = None + + descriptor = self.sheet.search_descriptor + descriptor.setSearchString(options['Search']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + + if find_all: + found = self.obj.findAll(descriptor) + else: + found = self.obj.findFirst(descriptor) + + if found: + if found.ImplementationName == OBJ_CELL: + rangos = LOCalcRange(found) + else: + rangos = [LOCalcRange(f) for f in found] + + return rangos + + def replace(self, options): + descriptor = self.sheet.replace_descriptor + descriptor.setSearchString(options['Search']) + descriptor.setReplaceString(options['Replace']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + count = self.obj.replaceAll(descriptor) + return count + + def in_range(self, rango): + if isinstance(rango, LOCalcRange): + address = rango.range_address + else: + address = rango.RangeAddress + result = self.cursor.queryIntersection(address) + return bool(result.Count) + def offset(self, rows=0, cols=1): ra = self.range_address col = ra.EndColumn + cols @@ -1958,6 +2510,10 @@ class LOCalcRange(object): cursor.collapseToSize(cols, rows) return LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + def copy(self, source): + self.sheet.obj.copyRange(self.address, source.range_address) + return + def copy_to(self, cell, formula=False): rango = cell.to_size(self.rows, self.columns) if formula: @@ -1981,7 +2537,7 @@ class LOCalcRange(object): self.to_size(rows, cols).data = data return - def auto_width(self): + def optimal_width(self): self.obj.Columns.OptimalWidth = True return @@ -2035,6 +2591,17 @@ class LOCalcRange(object): self._render_value(k, v) return + def find(self, search_string): + if self._sd is None: + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + + self._sd.setSearchString(search_string) + cell = self.obj.findFirst(self._sd) + if cell: + cell = LOCalcRange(cell) + return cell + def find_all(self, search_string): if self._sd is None: self._sd = self.sheet.obj.createSearchDescriptor() @@ -2090,10 +2657,22 @@ class LOCalcRange(object): args['Y'] = args.get('Y', ps['Y']) # ~ img.ResizeWithCell = True img = self.sheet.dp.insert_image(path, args) - img.Anchor = self.obj + img.anchor = self.obj args.clear() return img + def insert_shape(self, tipo, args={}): + ps = self.possize + args['Width'] = args.get('Width', ps['Width']) + args['Height'] = args.get('Height', ps['Height']) + args['X'] = args.get('X', ps['X']) + args['Y'] = args.get('Y', ps['Y']) + + shape = self.sheet.dp.add(tipo, args) + shape.anchor = self.obj + args.clear() + return + def filter_by_color(self, cell): rangos = cell.column[1:,:].visible for r in rangos: @@ -2107,6 +2686,38 @@ class LOCalcRange(object): self.obj.clearContents(what) return + def transpose(self): + # ~ 'Flags': 'A', + # ~ 'FormulaCommand': 0, + # ~ 'SkipEmptyCells': False, + # ~ 'AsLink': False, + # ~ 'MoveMode': 4, + self.select() + self.doc.copy() + self.clear(1023) + self[0,0].select() + self.doc.insert_contents({'Transpose': True}) + _CB.set('') + return + + def transpose_data(self, formula=False): + data = self.data + if formula: + data = self.formula + data = tuple(zip(*data)) + self.clear(1023) + self[0,0].copy_from(data, formula=formula) + return + + def merge_by_row(self): + for r in range(len(self.rows)): + self[r].merge = True + return + + def fill(self, source=1): + self.obj.fillAuto(0, source) + return + class LOWriterPageStyle(LOBaseObject): @@ -2163,7 +2774,10 @@ class LOWriterTextRange(object): @property def string(self): - return self.obj.String + s = '' + if self._is_paragraph: + s = self.obj.String + return s @string.setter def string(self, value): self.obj.String = value @@ -2188,10 +2802,6 @@ class LOWriterTextRange(object): def dp(self): return self._doc.dp - @property - def is_table(self): - return self._is_table - def offset(self): cursor = self.cursor.getEnd() return LOWriterTextRange(cursor, self._doc) @@ -2202,6 +2812,23 @@ class LOWriterTextRange(object): self.text.insertTextContent(cursor, data, replace) return + def new_line(self, count=1): + cursor = self.cursor + for i in range(count): + self.text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False) + return self._doc.selection + + def insert_table(self, data): + table = self._doc.create_instance('com.sun.star.text.TextTable') + rows = len(data) + cols = len(data[0]) + table.initialize(rows, cols) + self.insert_content(table) + table.DataArray = data + name = table.Name + table = LOWriterTextTable(self._doc.tables[name], self._doc) + return table + def insert_image(self, path, args={}): w = args.get('Width', 1000) h = args.get('Height', 1000) @@ -2244,6 +2871,47 @@ class LOWriterTextRanges(object): return self._obj +class LOWriterTextTable(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self._obj.Name + + @property + def data(self): + return self._obj.DataArray + @data.setter + def data(self, values): + self._obj.DataArray = values + + +class LOWriterTextTables(object): + + def __init__(self, doc): + self._doc = doc + self._obj = doc.obj.TextTables + + def __getitem__(self, key): + return LOWriterTextTable(self._obj[key], self._doc) + + def __len__(self): + return self._obj.Count + + def insert(self, data, text_range=None): + if text_range is None: + text_range = self._doc.selection + text_range.insert_table(data) + return + + class LOWriter(LODocument): def __init__(self, obj): @@ -2258,6 +2926,10 @@ class LOWriter(LODocument): def paragraphs(self): return LOWriterTextRanges(self.obj.Text, self) + @property + def tables(self): + return LOWriterTextTables(self) + @property def selection(self): sel = self.obj.CurrentSelection @@ -2301,8 +2973,72 @@ class LOWriter(LODocument): ps = self.obj.StyleFamilies['PageStyles'] return LOWriterPageStyles(ps) + @property + def search_descriptor(self): + return self.obj.createSearchDescriptor() + + @property + def replace_descriptor(self): + return self.obj.createReplaceDescriptor() + + def goto_start(self): + self.view_cursor.gotoStart(False) + return self.selection + + def goto_end(self): + self.view_cursor.gotoEnd(False) + return self.selection + + def search(self, options, find_all=True): + descriptor = self.search_descriptor + descriptor.setSearchString(options.get('Search', '')) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if 'Attributes' in options: + attr = dict_to_property(options['Attributes']) + descriptor.setSearchAttributes(attr) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + + result = False + if find_all: + found = self.obj.findAll(descriptor) + if len(found): + result = [LOWriterTextRange(f, self) for f in found] + else: + found = self.obj.findFirst(descriptor) + if found: + result = LOWriterTextRange(found, self) + + return result + + def replace(self, options): + descriptor = self.replace_descriptor + descriptor.setSearchString(options['Search']) + descriptor.setReplaceString(options['Replace']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if 'Attributes' in options: + attr = dict_to_property(options['Attributes']) + descriptor.setSearchAttributes(attr) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + found = self.obj.replaceAll(descriptor) + return found + + def select(self, text): + if hasattr(text, 'obj'): + text = text.obj + self._cc.select(text) + return + class LOShape(LOBaseObject): + IMAGE = 'com.sun.star.drawing.GraphicObjectShape' def __init__(self, obj, index): self._index = index @@ -2310,11 +3046,22 @@ class LOShape(LOBaseObject): @property def type(self): - return 'shape' + t = self.shape_type[21:] + if self.is_image: + t = 'image' + return t + + @property + def shape_type(self): + return self.obj.ShapeType + + @property + def is_image(self): + return self.shape_type == self.IMAGE @property def name(self): - return self.obj.Name or f'shape{self.index}' + return self.obj.Name or f'{self.type}{self.index}' @name.setter def name(self, value): self.obj.Name = value @@ -2370,6 +3117,33 @@ class LOShape(LOBaseObject): def visible(self, value): self.obj.Visible = value + @property + def path(self): + return self.url + @property + def url(self): + url = '' + if self.is_image: + url = _P.to_system(self.obj.GraphicURL.OriginURL) + return url + + @property + def mimetype(self): + mt = '' + if self.is_image: + mt = self.obj.GraphicURL.MimeType + return mt + + @property + def linked(self): + l = False + if self.is_image: + l = self.obj.GraphicURL.Linked + return l + + def delete(self): + self.remove() + return def remove(self): self.obj.Parent.remove(self.obj) return @@ -2419,6 +3193,18 @@ class LODrawPage(LOBaseObject): break return shape + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + if self._index == self.count: + raise StopIteration + shape = self[self._index] + self._index += 1 + return shape + + @property def name(self): return self.obj.Name @@ -2453,17 +3239,18 @@ class LODrawPage(LOBaseObject): Ellipse Text """ + index = self.count w = args.get('Width', 3000) h = args.get('Height', 3000) x = args.get('X', 1000) y = args.get('Y', 1000) + name = args.get('Name', f'{type_shape.lower()}{index}') service = f'com.sun.star.drawing.{type_shape}Shape' shape = self.create_instance(service) shape.Size = Size(w, h) shape.Position = Point(x, y) - index = self.count - shape.Name = f'{type_shape.lower()}{index}' + shape.Name = name self.obj.add(shape) return LOShape(self.obj[index], index) @@ -2472,18 +3259,24 @@ class LODrawPage(LOBaseObject): shape = shape.obj return self.obj.remove(shape) + def remove_all(self): + while self.count: + self.obj.remove(self.obj[0]) + return + def insert_image(self, path, args={}): + index = self.count w = args.get('Width', 3000) h = args.get('Height', 3000) x = args.get('X', 1000) y = args.get('Y', 1000) + name = args.get('Name', f'image{index}') image = self.create_instance('com.sun.star.drawing.GraphicObjectShape') image.GraphicURL = _P.to_url(path) image.Size = Size(w, h) image.Position = Point(x, y) - index = self.count - image.Name = f'image{index}' + image.Name = name self.obj.add(image) return LOShape(self.obj[index], index) @@ -2856,30 +3649,31 @@ class LODocs(object): LODocs._desktop = self._desktop def __getitem__(self, index): - doc = None + document = None for i, doc in enumerate(self._desktop.Components): if isinstance(index, int) and i == index: - doc = _get_class_doc(doc) + document = _get_class_doc(doc) break elif isinstance(index, str) and doc.Title == index: - doc = _get_class_doc(doc) + document = _get_class_doc(doc) break - return doc + return document def __contains__(self, item): doc = self[item] return not doc is None def __iter__(self): - self._i = 0 + self._i = -1 return self def __next__(self): + self._i += 1 doc = self[self._i] if doc is None: raise StopIteration - self._i += 1 - return doc + else: + return doc def __len__(self): for i, _ in enumerate(self._desktop.Components): @@ -2931,9 +3725,9 @@ def _add_listeners(events, control, name=''): 'addActionListener': EventsButton, 'addMouseListener': EventsMouse, 'addFocusListener': EventsFocus, - # ~ 'addItemListener': EventsItem, + 'addItemListener': EventsItem, 'addKeyListener': EventsKey, - # ~ 'addTabListener': EventsTab, + 'addTabListener': EventsTab, } if hasattr(control, 'obj'): control = control.obj @@ -2941,6 +3735,7 @@ def _add_listeners(events, control, name=''): is_grid = control.ImplementationName == 'stardiv.Toolkit.GridControl' is_link = control.ImplementationName == 'stardiv.Toolkit.UnoFixedHyperlinkControl' is_roadmap = control.ImplementationName == 'stardiv.Toolkit.UnoRoadmapControl' + is_pages = control.ImplementationName == 'stardiv.Toolkit.UnoMultiPageControl' for key, value in listeners.items(): if hasattr(control, key): @@ -2956,10 +3751,10 @@ def _add_listeners(events, control, name=''): getattr(control, key)(listeners[key](events, name)) - # ~ if is_grid: - # ~ controllers = EventsGrid(events, name) - # ~ control.addSelectionListener(controllers) - # ~ control.Model.GridDataModel.addGridDataListener(controllers) + if is_grid: + controllers = EventsGrid(events, name) + control.addSelectionListener(controllers) + control.Model.GridDataModel.addGridDataListener(controllers) return @@ -3093,9 +3888,195 @@ class EventsKey(EventsListenerBase, XKeyListener): 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 = ? @@ -3104,7 +4085,6 @@ class UnoBaseObject(object): def __init__(self, obj, path=''): self._obj = obj self._model = obj.Model - # ~ self._path = path def __setattr__(self, name, value): exists = hasattr(self, name) @@ -3113,6 +4093,12 @@ class UnoBaseObject(object): else: super().__setattr__(name, value) + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + @property def obj(self): return self._obj @@ -3411,14 +4397,14 @@ class UnoRadio(UnoBaseObject): self.model.Label = value -class UnoCheck(UnoBaseObject): +class UnoCheckBox(UnoBaseObject): def __init__(self, obj): super().__init__(obj) @property def type(self): - return 'check' + return 'checkbox' @property def value(self): @@ -3459,6 +4445,9 @@ class UnoText(UnoBaseObject): def value(self, value): self.model.Text = value + def validate(self): + return + class UnoImage(UnoBaseObject): @@ -3557,17 +4546,498 @@ class UnoListBox(UnoBaseObject): return +class UnoRoadmap(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._options = () + + def __setattr__(self, name, value): + if name in ('_options',): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def options(self): + return self._options + @options.setter + def options(self, values): + self._options = values + for i, v in enumerate(values): + opt = self.model.createInstance() + opt.ID = i + opt.Label = v + self.model.insertByIndex(i, opt) + return + + @property + def enabled(self): + return True + @enabled.setter + def enabled(self, value): + for m in self.model: + m.Enabled = value + return + + def set_enabled(self, index, value): + self.model.getByIndex(index).Enabled = value + return + + +class UnoTree(UnoBaseObject): + + def __init__(self, obj, ): + super().__init__(obj) + self._tdm = None + self._data = [] + + def __setattr__(self, name, value): + if name in ('_tdm', '_data'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def selection(self): + sel = self.obj.Selection + return sel.DataValue, sel.DisplayValue + + @property + def parent(self): + parent = self.obj.Selection.Parent + if parent is None: + return () + return parent.DataValue, parent.DisplayValue + + def _get_parents(self, node): + value = (node.DisplayValue,) + parent = node.Parent + if parent is None: + return value + return self._get_parents(parent) + value + + @property + def parents(self): + values = self._get_parents(self.obj.Selection) + return values + + @property + def root(self): + if self._tdm is None: + return '' + return self._tdm.Root.DisplayValue + @root.setter + def root(self, value): + self._add_data_model(value) + + def _add_data_model(self, name): + tdm = create_instance('com.sun.star.awt.tree.MutableTreeDataModel') + root = tdm.createNode(name, True) + root.DataValue = 0 + tdm.setRoot(root) + self.model.DataModel = tdm + self._tdm = self.model.DataModel + return + + @property + def path(self): + return self.root + @path.setter + def path(self, value): + self.data = _P.walk_dir(value, True) + + @property + def data(self): + return self._data + @data.setter + def data(self, values): + self._data = list(values) + self._add_data() + + def _add_data(self): + if not self.data: + return + + parents = {} + for node in self.data: + parent = parents.get(node[1], self._tdm.Root) + child = self._tdm.createNode(node[2], False) + child.DataValue = node[0] + parent.appendChild(child) + parents[node[0]] = child + self.obj.expandNode(self._tdm.Root) + return + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1grid.html +class UnoGrid(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._gdm = self.model.GridDataModel + self._columns = [] + self._data = [] + # ~ self._format_columns = () + + def __setattr__(self, name, value): + if name in ('_gdm', '_columns', '_data'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __getitem__(self, key): + value = self._gdm.getCellData(key[0], key[1]) + return value + + def __setitem__(self, key, value): + self._gdm.updateCellData(key[0], key[1], value) + return + + @property + def type(self): + return 'grid' + + @property + def columns(self): + return self._columns + @columns.setter + def columns(self, values): + self._columns = values + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html + model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) + for properties in values: + column = create_instance('com.sun.star.awt.grid.GridColumn', True) + for k, v in properties.items(): + setattr(column, k, v) + model.addColumn(column) + self.model.ColumnModel = model + return + + @property + def data(self): + return self._data + @data.setter + def data(self, values): + self._data = values + self.clear() + headings = tuple(range(1, len(values) + 1)) + self._gdm.addRows(headings, values) + # ~ rows = range(grid_dm.RowCount) + # ~ colors = [COLORS['GRAY'] if r % 2 else COLORS['WHITE'] for r in rows] + # ~ grid.Model.RowBackgroundColors = tuple(colors) + return + + @property + def value(self): + if self.column == -1 or self.row == -1: + return '' + return self[self.column, self.row] + @value.setter + def value(self, value): + if self.column > -1 and self.row > -1: + self[self.column, self.row] = value + + @property + def row(self): + return self.obj.CurrentRow + + @property + def column(self): + return self.obj.CurrentColumn + + def clear(self): + self._gdm.removeAllRows() + return + + # UP + def _format_cols(self): + rows = tuple(tuple( + self._format_columns[i].format(r) for i, r in enumerate(row)) for row in self._data + ) + return rows + + # ~ @property + # ~ def format_columns(self): + # ~ return self._format_columns + # ~ @format_columns.setter + # ~ def format_columns(self, value): + # ~ self._format_columns = value + + # ~ @property + # ~ def rows(self): + # ~ return self._gdm.RowCount + + # ~ @property + # ~ def columns(self): + # ~ return self._gdm.ColumnCount + + def set_cell_tooltip(self, col, row, value): + self._gdm.updateCellToolTip(col, row, value) + return + + def get_cell_tooltip(self, col, row): + value = self._gdm.getCellToolTip(col, row) + return value + + def _validate_column(self, data): + row = [] + for i, d in enumerate(data): + if i in self._columns: + if 'image' in self._columns[i]: + row.append(self._columns[i]['image']) + else: + row.append(d) + return tuple(row) + + def add_row(self, data): + # ~ self._data.append(data) + data = self._validate_column(data) + self._gdm.addRow(self.rows + 1, data) + return + + def remove_row(self, row): + self._gdm.removeRow(row) + # ~ del self._data[row] + self.update_row_heading() + return + + def update_row_heading(self): + for i in range(self.rows): + self._gdm.updateRowHeading(i, i + 1) + return + + def sort(self, column, asc=True): + self._gdm.sortByColumn(column, asc) + self.update_row_heading() + return + + def set_column_image(self, column, path): + gp = create_instance('com.sun.star.graphic.GraphicProvider') + data = dict_to_property({'URL': _path_url(path)}) + image = gp.queryGraphic(data) + if not column in self._columns: + self._columns[column] = {} + self._columns[column]['image'] = image + return + + +class UnoPage(object): + + def __init__(self, obj): + self._obj = obj + self._events = None + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._obj.Model + + # ~ @property + # ~ def id(self): + # ~ return self.m.TabPageID + + @property + def parent(self): + return self.obj.Context + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(UNO_MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self.obj.getControl(name) + _add_listeners(self._events, control, name) + control = UNO_CLASSES[tipo](control) + + if tipo in ('listbox',): + control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + return control + + +class UnoPages(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._sheets = [] + self._events = None + + def __setattr__(self, name, value): + if name in ('_sheets', '_events'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __getitem__(self, index): + name = index + if isinstance(index, int): + name = f'sheet{index}' + sheet = self.obj.getControl(name) + page = UnoPage(sheet) + page._events = self._events + return page + + @property + def type(self): + return 'pages' + + @property + def current(self): + return self.obj.ActiveTabID + @property + def active(self): + return self.current + + @property + def sheets(self): + return self._sheets + @sheets.setter + def sheets(self, values): + self._sheets = values + for i, title in enumerate(values): + sheet = self.m.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.m.insertByName(f'sheet{i + 1}', sheet) + return + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers + + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value + + def insert(self, title): + self._sheets.append(title) + id = len(self._sheets) + sheet = self.m.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.m.insertByName(f'sheet{id}', sheet) + return self[id] + + def remove(self, id): + self.obj.removeTab(id) + return + + def activate(self, id): + self.obj.activateTab(id) + return + + UNO_CLASSES = { 'label': UnoLabel, 'link': UnoLabelLink, 'button': UnoButton, 'radio': UnoRadio, - 'check': UnoCheck, + 'checkbox': UnoCheckBox, 'text': UnoText, 'image': UnoImage, 'listbox': UnoListBox, + 'roadmap': UnoRoadmap, + 'tree': UnoTree, + 'grid': UnoGrid, + 'pages': UnoPages, } +UNO_MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', + 'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', +} +# ~ 'CurrencyField': 'com.sun.star.awt.UnoControlCurrencyFieldModel', +# ~ 'DateField': 'com.sun.star.awt.UnoControlDateFieldModel', +# ~ 'FileControl': 'com.sun.star.awt.UnoControlFileControlModel', +# ~ 'FormattedField': 'com.sun.star.awt.UnoControlFormattedFieldModel', +# ~ 'NumericField': 'com.sun.star.awt.UnoControlNumericFieldModel', +# ~ 'PatternField': 'com.sun.star.awt.UnoControlPatternFieldModel', +# ~ 'ProgressBar': 'com.sun.star.awt.UnoControlProgressBarModel', +# ~ 'ScrollBar': 'com.sun.star.awt.UnoControlScrollBarModel', +# ~ 'SimpleAnimation': 'com.sun.star.awt.UnoControlSimpleAnimationModel', +# ~ 'SpinButton': 'com.sun.star.awt.UnoControlSpinButtonModel', +# ~ 'Throbber': 'com.sun.star.awt.UnoControlThrobberModel', +# ~ 'TimeField': 'com.sun.star.awt.UnoControlTimeFieldModel', + class LODialog(object): SEPARATION = 5 @@ -3576,15 +5046,16 @@ class LODialog(object): 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', 'button': 'com.sun.star.awt.UnoControlButtonModel', 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', - 'check': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel', 'text': 'com.sun.star.awt.UnoControlEditModel', 'image': 'com.sun.star.awt.UnoControlImageControlModel', 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', - # ~ 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', - # ~ 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', - # ~ 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', - # ~ 'tree': 'com.sun.star.awt.tree.TreeControlModel', - # ~ 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', } def __init__(self, args): @@ -3596,6 +5067,7 @@ class LODialog(object): self._color_on_focus = COLOR_ON_FOCUS self._id = '' self._path = '' + self._init_controls() def _create(self, args): service = 'com.sun.star.awt.DialogProvider' @@ -3608,7 +5080,7 @@ class LODialog(object): if 'Location' in args: name = args['Name'] library = args.get('Library', 'Standard') - location = args.get('Location', 'application') + location = args.get('Location', 'application').lower() if location == 'user': location = 'application' url = f'vnd.sun.star.script:{library}.{name}?location={location}' @@ -3630,6 +5102,23 @@ class LODialog(object): dlg.createPeer(toolkit, None) return dlg + def _get_type_control(self, name): + name = name.split('.')[2] + types = { + 'UnoFixedTextControl': 'label', + 'UnoEditControl': 'text', + 'UnoButtonControl': 'button', + } + return types[name] + + def _init_controls(self): + for control in self.obj.getControls(): + tipo = self._get_type_control(control.ImplementationName) + name = control.Model.Name + control = UNO_CLASSES[tipo](control) + setattr(self, name, control) + return + @property def obj(self): return self._obj @@ -3709,27 +5198,33 @@ class LODialog(object): return _P.to_url(path) def _special_properties(self, tipo, args): - columns = args.pop('Columns', ()) - if tipo == 'link' and not 'Label' in args: args['Label'] = args['URL'] - elif tipo == 'grid': - args['ColumnModel'] = self._set_column_model(columns) - elif tipo == 'button': + return args + + if tipo == 'button': if 'ImageURL' in args: args['ImageURL'] = self._set_image_url(args['ImageURL']) - if not 'FocusOnClick' in args: - args['FocusOnClick'] = False - elif tipo == 'roadmap': - if not 'Height' in args: - args['Height'] = self.height + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) if 'Title' in args: args['Text'] = args.pop('Title') - elif tipo == 'tab': - if not 'Width' in args: - args['Width'] = self.width - if not 'Height' in args: - args['Height'] = self.height + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) return args @@ -3737,6 +5232,7 @@ class LODialog(object): tipo = args.pop('Type').lower() root = args.pop('Root', '') sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) args = self._special_properties(tipo, args) model = self.model.createInstance(self.MODELS[tipo]) @@ -3746,11 +5242,14 @@ class LODialog(object): control = self.obj.getControl(name) _add_listeners(self.events, control, name) control = UNO_CLASSES[tipo](control) + if tipo in ('listbox',): control.path = self.path if tipo == 'tree' and root: control.root = root + elif tipo == 'grid' and columns: + control.columns = columns elif tipo == 'pages' and sheets: control.sheets = sheets control.events = self.events @@ -4052,6 +5551,245 @@ class LOMenus(object): return LOMenu(index) +class LOWindow(object): + EMPTY = """ + +""" + MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', + 'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', + } + + def __init__(self, args): + self._events = None + self._menu = None + self._container = None + self._model = None + self._id = '' + self._path = '' + self._obj = self._create(args) + + def _create(self, properties): + ps = ( + properties.get('X', 0), + properties.get('Y', 0), + properties.get('Width', 500), + properties.get('Height', 500), + ) + self._title = properties.get('Title', TITLE) + self._create_frame(ps) + self._create_container(ps) + self._create_subcontainer(ps) + # ~ self._create_splitter(ps) + return + + def _create_frame(self, ps): + service = 'com.sun.star.frame.TaskCreator' + tc = create_instance(service, True) + self._frame = tc.createInstanceWithArguments(( + NamedValue('FrameName', 'EasyMacroWin'), + NamedValue('PosSize', Rectangle(*ps)), + )) + self._window = self._frame.getContainerWindow() + self._toolkit = self._window.getToolkit() + desktop = get_desktop() + self._frame.setCreator(desktop) + desktop.getFrames().append(self._frame) + self._frame.Title = self._title + return + + def _create_container(self, ps): + service = 'com.sun.star.awt.UnoControlContainer' + self._container = create_instance(service, True) + service = 'com.sun.star.awt.UnoControlContainerModel' + model = create_instance(service, True) + model.BackgroundColor = get_color((225, 225, 225)) + self._container.setModel(model) + self._container.createPeer(self._toolkit, self._window) + self._container.setPosSize(*ps, POSSIZE) + self._frame.setComponent(self._container, None) + return + + def _create_subcontainer(self, ps): + service = 'com.sun.star.awt.ContainerWindowProvider' + cwp = create_instance(service, True) + + path_tmp = _P.save_tmp(self.EMPTY) + subcont = cwp.createContainerWindow( + _P.to_url(path_tmp), '', self._container.getPeer(), None) + _P.kill(path_tmp) + + subcont.setPosSize(0, 0, 500, 500, POSSIZE) + subcont.setVisible(True) + self._container.addControl('subcont', subcont) + self._subcont = subcont + self._model = subcont.Model + return + + def _create_popupmenu(self, menus): + menu = create_instance('com.sun.star.awt.PopupMenu', True) + for i, m in enumerate(menus): + label = m['label'] + cmd = m.get('event', '') + if not cmd: + cmd = label.lower().replace(' ', '_') + if label == '-': + menu.insertSeparator(i) + else: + menu.insertItem(i, label, m.get('style', 0), i) + menu.setCommand(i, cmd) + # ~ menu.setItemImage(i, path?, True) + menu.addMenuListener(EventsMenu(self.events)) + return menu + + def _create_menu(self, menus): + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMenu.html + #~ nItemId specifies the ID of the menu item to be inserted. + #~ aText specifies the label of the menu item. + #~ nItemStyle 0 = Standard, CHECKABLE = 1, RADIOCHECK = 2, AUTOCHECK = 4 + #~ nItemPos specifies the position where the menu item will be inserted. + self._menu = create_instance('com.sun.star.awt.MenuBar', True) + for i, m in enumerate(menus): + self._menu.insertItem(i, m['label'], m.get('style', 0), i) + cmd = m['label'].lower().replace(' ', '_') + self._menu.setCommand(i, cmd) + submenu = self._create_popupmenu(m['submenu']) + self._menu.setPopupMenu(i, submenu) + + self._window.setMenuBar(self._menu) + return + + def _add_listeners(self, control=None): + if self.events is None: + return + controller = EventsWindow(self) + self._window.addTopWindowListener(controller) + self._window.addWindowListener(controller) + # ~ self._container.addKeyListener(EventsKeyWindow(self)) + return + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(self.MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self._subcont.getControl(name) + _add_listeners(self.events, control, name) + control = UNO_CLASSES[tipo](control) + + # ~ if tipo in ('listbox',): + # ~ control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + return control + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers(self) + self._add_listeners() + + @property + def model(self): + return self._model + + @property + def width(self): + return self._container.Size.Width + + @property + def height(self): + return self._container.Size.Height + + @property + def name(self): + return self._title.lower().replace(' ', '_') + + def add_menu(self, menus): + self._create_menu(menus) + return + + def open(self): + self._window.setVisible(True) + return + + def close(self): + self._window.setMenuBar(None) + self._window.dispose() + self._frame.close(True) + return + + +def create_window(args): + return LOWindow(args) + + class classproperty: def __init__(self, method=None): self.fget = method @@ -4104,6 +5842,7 @@ class ClipBoard(object): if df: text = transferable.getTransferData(df) return text +_CB = ClipBoard class Paths(object): @@ -4176,6 +5915,12 @@ class Paths(object): tmp = tempfile.NamedTemporaryFile(suffix=ext) return tmp.name + @classmethod + def save_tmp(cls, data): + path_tmp = cls.tmp() + cls.save(path_tmp, data) + return path_tmp + @classmethod def config(cls, name='Work'): """ @@ -4340,6 +6085,16 @@ class Paths(object): return result + @classmethod + def files(cls, path, pattern='*'): + files = [str(p) for p in Path(path).glob(pattern) if p.is_file()] + return files + + @classmethod + def dirs(cls, path): + dirs = [str(p) for p in Path(path).iterdir() if p.is_dir()] + return dirs + @classmethod def walk(cls, path, filters=''): paths = [] @@ -4353,6 +6108,25 @@ class Paths(object): paths += [cls.join(folder, f) for f in files] return paths + @classmethod + def walk_dir(cls, path, tree=False): + folders = [] + if tree: + i = 0 + p = 0 + parents = {path: 0} + for root, dirs, _ in os.walk(path): + for name in dirs: + i += 1 + rn = cls.join(root, name) + if not rn in parents: + parents[rn] = i + folders.append((i, parents[root], name)) + else: + for root, dirs, _ in os.walk(path): + folders += [cls.join(root, name) for name in dirs] + return folders + @classmethod def from_id(cls, id_ext): pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') @@ -4424,6 +6198,20 @@ class Paths(object): z.extractall(path, members=members, pwd=pwd) return True + @classmethod + def merge_zip(cls, target, zips): + try: + with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as t: + for path in zips: + with zipfile.ZipFile(path, compression=zipfile.ZIP_DEFLATED) as s: + for name in s.namelist(): + t.writestr(name, s.open(name).read()) + except Exception as e: + error(e) + return False + + return True + @classmethod def copy(cls, source, target='', name=''): p, f, n, e = _P(source).info diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py index 19b966a..547fed7 100644 --- a/source/pythonpath/main.py +++ b/source/pythonpath/main.py @@ -299,7 +299,6 @@ class Controllers(object): return -@app.catch_exception def _create_dialog(): BUTTON_WH = 20 args= { From d502f3631310111d578d7ab6a0a9c1a29d80f6a8 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 8 Jan 2021 21:14:15 -0600 Subject: [PATCH 11/12] Diable search --- easymacro.py | 137 +- files/ZAZPip_v0.6.0.oxt | Bin 77759 -> 86551 bytes source/pythonpath/easymacro.py | 2140 +++++++++++++++++++++++++++++--- source/pythonpath/main.py | 4 +- 4 files changed, 2016 insertions(+), 265 deletions(-) diff --git a/easymacro.py b/easymacro.py index d91c6af..0745d96 100644 --- a/easymacro.py +++ b/easymacro.py @@ -4,7 +4,7 @@ # ~ This file is part of ZAZ. -# ~ https://git.elmau.net/elmau/zaz +# ~ https://git.cuates.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 @@ -290,7 +290,7 @@ def create_instance(name: str, with_context: bool=False, args: Any=None) -> Any: return instance -def get_app_config(node_name, key=''): +def get_app_config(node_name: str, key: str=''): name = 'com.sun.star.configuration.ConfigurationProvider' service = 'com.sun.star.configuration.ConfigurationAccess' cp = create_instance(name, True) @@ -337,7 +337,7 @@ def info(*args): return -def save_log(path, data): +def save_log(path: str, data): with open(path, 'a') as f: f.write(f'{str(now())[:19]} -{LOG_NAME}- ') pprint(data, stream=f) @@ -365,7 +365,7 @@ def inspect(obj: Any) -> None: return -def mri(obj): +def mri(obj: Any) -> None: m = create_instance('mytools.Mri') if m is None: msg = 'Extension MRI not found' @@ -384,7 +384,7 @@ def run_in_thread(fn): return run -def now(only_time=False): +def now(only_time: bool=False): now = datetime.datetime.now() if only_time: now = now.time() @@ -538,7 +538,7 @@ def _struct_to_date(value): return d -def _get_url_script(args): +def _get_url_script(args: dict): library = args['library'] module = '.' name = args['name'] @@ -557,7 +557,7 @@ def _get_url_script(args): return url -def _call_macro(args): +def _call_macro(args: dict): #~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification url = _get_url_script(args) @@ -641,7 +641,7 @@ def stop_timer(name): return -def install_locales(path, domain='base', dir_locales=DIR['locales']): +def install_locales(path: str, domain: str='base', dir_locales=DIR['locales']): path_locales = _P.join(_P(path).path, dir_locales) try: lang = gettext.translation(domain, path_locales, languages=[LANG]) @@ -702,7 +702,7 @@ def start(): return -def end(get_seconds=False): +def end(get_seconds: bool=False): global _start e = now() td = e - _start @@ -2775,7 +2775,7 @@ class LOWriterTextRange(object): @property def string(self): s = '' - if self._is_paragraph: + if not self._is_table: s = self.obj.String return s @string.setter @@ -4676,12 +4676,11 @@ class UnoGrid(UnoBaseObject): def __init__(self, obj): super().__init__(obj) self._gdm = self.model.GridDataModel - self._columns = [] self._data = [] - # ~ self._format_columns = () + self._formats = () def __setattr__(self, name, value): - if name in ('_gdm', '_columns', '_data'): + if name in ('_gdm', '_data', '_formats'): self.__dict__[name] = value else: super().__setattr__(name, value) @@ -4700,10 +4699,10 @@ class UnoGrid(UnoBaseObject): @property def columns(self): - return self._columns + return {} @columns.setter def columns(self, values): - self._columns = 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: @@ -4742,35 +4741,46 @@ class UnoGrid(UnoBaseObject): def row(self): return self.obj.CurrentRow + @property + def row_count(self): + return self._gdm.RowCount + @property def column(self): return self.obj.CurrentColumn + @property + def column(self): + return self.obj.CurrentColumn + + @property + def is_valid(self): + return not (self.row == -1 or self.column == -1) + + @property + def formats(self): + return self._formats + @formats.setter + def formats(self, values): + self._formats = values + def clear(self): self._gdm.removeAllRows() return - # 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 + def _format_columns(self, data): + row = data + if self.formats: + for i, f in enumerate(formats): + if f: + row[i] = f.format(data[i]) + return row - # ~ @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 add_row(self, data): + self._data.append(data) + row = self._format_columns(data) + self._gdm.addRow(self.row_count + 1, row) + return def set_cell_tooltip(self, col, row, value): self._gdm.updateCellToolTip(col, row, value) @@ -4780,45 +4790,20 @@ class UnoGrid(UnoBaseObject): 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 + def update_row_heading(self): + for i in range(self.row_count): + self._gdm.updateRowHeading(i, i + 1) + return + + def remove_row(self, row): + self._gdm.removeRow(row) + del self._data[row] + self.update_row_heading() return @@ -5422,9 +5407,9 @@ class LOMenu(object): if MENUS[index.lower()] == cmd: self._menu = menu break - line = self._menu.get('CommandURL', '') - line += self._get_submenus(self._menu['ItemDescriptorContainer']) - return line + # ~ line = self._menu.get('CommandURL', '') + # ~ line += self._get_submenus(self._menu['ItemDescriptorContainer']) + return self._menu def _get_menus(self): instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' @@ -6212,6 +6197,14 @@ class Paths(object): return True + @classmethod + def image(cls, path): + gp = create_instance('com.sun.star.graphic.GraphicProvider') + image = gp.queryGraphic(( + PropertyValue(Name='URL', Value=cls.to_url(path)), + )) + return image + @classmethod def copy(cls, source, target='', name=''): p, f, n, e = _P(source).info diff --git a/files/ZAZPip_v0.6.0.oxt b/files/ZAZPip_v0.6.0.oxt index e6c732c18755ae0b583db32f86fed519182a80a7..4802a06c5a3f17bcbe4c877b9e9af39f344d3869 100644 GIT binary patch delta 41620 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 Hvw{8(a|;w? delta 32733 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 Any: return instance -def get_app_config(node_name, key=''): +def get_app_config(node_name: str, key: str=''): name = 'com.sun.star.configuration.ConfigurationProvider' service = 'com.sun.star.configuration.ConfigurationAccess' cp = create_instance(name, True) @@ -298,7 +337,7 @@ def info(*args): return -def save_log(path, data): +def save_log(path: str, data): with open(path, 'a') as f: f.write(f'{str(now())[:19]} -{LOG_NAME}- ') pprint(data, stream=f) @@ -326,7 +365,7 @@ def inspect(obj: Any) -> None: return -def mri(obj): +def mri(obj: Any) -> None: m = create_instance('mytools.Mri') if m is None: msg = 'Extension MRI not found' @@ -345,7 +384,7 @@ def run_in_thread(fn): return run -def now(only_time=False): +def now(only_time: bool=False): now = datetime.datetime.now() if only_time: now = now.time() @@ -449,6 +488,8 @@ def _get_dispatch() -> Any: return create_instance('com.sun.star.frame.DispatchHelper') +# ~ https://wiki.documentfoundation.org/Development/DispatchCommands +# ~ Used only if not exists in API def call_dispatch(frame: Any, url: str, args: dict={}) -> None: dispatch = _get_dispatch() opt = dict_to_property(args) @@ -497,7 +538,7 @@ def _struct_to_date(value): return d -def _get_url_script(args): +def _get_url_script(args: dict): library = args['library'] module = '.' name = args['name'] @@ -513,11 +554,10 @@ def _get_url_script(args): url = 'vnd.sun.star.script' url = f'{url}:{library}{module}{name}?language={language}&location={location}' - return url -def _call_macro(args): +def _call_macro(args: dict): #~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification url = _get_url_script(args) @@ -601,7 +641,7 @@ def stop_timer(name): return -def install_locales(path, domain='base', dir_locales=DIR['locales']): +def install_locales(path: str, domain: str='base', dir_locales=DIR['locales']): path_locales = _P.join(_P(path).path, dir_locales) try: lang = gettext.translation(domain, path_locales, languages=[LANG]) @@ -662,7 +702,7 @@ def start(): return -def end(get_seconds=False): +def end(get_seconds: bool=False): global _start e = now() td = e - _start @@ -753,6 +793,11 @@ def decrypt(token, password): return data +def switch_design_mode(doc): + call_dispatch(doc.frame, '.uno:SwitchControlDesignMode') + return + + class SmtpServer(object): def __init__(self, config): @@ -892,6 +937,63 @@ def send_email(server, message): return +class ImapServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + try: + # ~ hosts = 'gmail' in config['server'] + if config['ssl']: + self._server = imaplib.IMAP4_SSL(config['server'], config['port']) + else: + self._server = imaplib.IMAP4(config['server'], config['port']) + self._server.login(config['user'], config['password']) + self._server.select() + return True + except imaplib.IMAP4.error as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def get_folders(self, exclude=()): + folders = {} + result, subdir = self._server.list() + for s in subdir: + print(s.decode('utf-8')) + return folders + + def close(self): + try: + self._server.close() + self._server.logout() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + # ~ Classes class LOBaseObject(object): @@ -901,7 +1003,7 @@ class LOBaseObject(object): def __setattr__(self, name, value): exists = hasattr(self, name) - if not exists and not name in ('_obj', '_index'): + if not exists and not name in ('_obj', '_index', '_view'): setattr(self._obj, name, value) else: super().__setattr__(name, value) @@ -917,54 +1019,6 @@ class LOBaseObject(object): return self._obj -class LOImage(object): - TYPE = { - 'png': 'image/png', - 'jpg': 'image/jpeg', - } - - def __init__(self, obj): - self._obj = obj - - @property - def obj(self): - return self._obj - - @property - def name(self): - return self.obj.Name or 'img' - - @property - def mimetype(self): - return self.obj.Bitmap.MimeType - - def save(self, path, mimetype=DEFAULT_MIME_TYPE): - p = _P(path) - if _P.is_dir(path): - name = self.name - else: - path = p.path - name = p.name - - path = _P.join(path, f'{name}.{mimetype.lower()}') - args = dict( - URL = _P.to_url(path), - MimeType = self.TYPE[mimetype], - ) - if not _export_image(self.obj, args): - path = '' - - # ~ size = len(self.obj.Bitmap.DIB) - # ~ data = self.obj.GraphicStream.readBytes((), size) - # ~ data = data[-1].value - - # ~ data = self.obj.Bitmap.DIB.value - # ~ data = self.obj.Graphic.DIB.value - - # ~ _P.save_bin(path, data) - return path - - class LODocument(object): FILTERS = { 'doc': 'MS Word 97', @@ -1096,6 +1150,10 @@ class LODocument(object): call_dispatch(self.frame, '.uno:Copy') return + def insert_contents(self, args={}): + call_dispatch(self.frame, '.uno:InsertContents', args) + return + def paste(self): sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') transferable = sc.getContents() @@ -1463,66 +1521,279 @@ class LOSheetCharts(object): class LOFormControl(LOBaseObject): + EVENTS = { + 'action': 'actionPerformed', + 'click': 'mousePressed', + } + TYPES = { + 'actionPerformed': 'XActionListener', + 'mousePressed': 'XMouseListener', + } - def __init__(self, obj): - self._obj = obj - self._control = self.doc.CurrentController.getControl(self.obj) + def __init__(self, obj, view, form): + super().__init__(obj) + self._view = view + self._form = form + self._m = view.Model + self._index = -1 def __setattr__(self, name, value): - if name == '_control': + if name in ('_form', '_view', '_m', '_index'): self.__dict__[name] = value else: super().__setattr__(name, value) + def __str__(self): + return f'{self.name} ({self.type}) {[self.index]}' + + @property + def form(self): + return self._form + @property def doc(self): - return self.obj.Parent.Parent.Parent + return self.obj.Parent.Forms.Parent + + @property + def name(self): + return self._m.Name + @name.setter + def name(self, value): + self._m.Name = value + + @property + def tag(self): + return self._m.Tag + @tag.setter + def tag(self, value): + self._m.Tag = value + + @property + def index(self): + return self._index + @index.setter + def index(self, value): + self._index = value + + @property + def enabled(self): + return self._m.Enabled + @enabled.setter + def enabled(self, value): + self._m.Enabled = value + + @property + def events(self): + return self.form.getScriptEvents(self.index) + def add_event(self, name, macro): + if not 'name' in macro: + macro['name'] = '{}_{}'.format(self.name, name) + + event = ScriptEventDescriptor() + event.AddListenerParam = '' + event.EventMethod = self.EVENTS[name] + event.ListenerType = self.TYPES[event.EventMethod] + event.ScriptCode = _get_url_script(macro) + event.ScriptType = 'Script' + + for ev in self.events: + if ev.EventMethod == event.EventMethod and \ + ev.ListenerType == event.ListenerType: + self.form.revokeScriptEvent(self.index, + event.ListenerType, event.EventMethod, event.AddListenerParam) + break + + self.form.registerScriptEvent(self.index, event) + return + + def set_focus(self): + self._view.setFocus() + return + + +class LOFormControlLabel(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'label' + + @property + def value(self): + return self._m.Label + @value.setter + def value(self, value): + self._m.Label = value + + +class LOFormControlText(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'text' + + @property + def value(self): + return self._m.Text + @value.setter + def value(self, value): + self._m.Text = value + + +class LOFormControlButton(LOFormControl): + + def __init__(self, obj, view, form): + super().__init__(obj, view, form) + + @property + def type(self): + return 'button' + + @property + def value(self): + return self._m.Label + @value.setter + def value(self, value): + self._m.Text = Label + + +FORM_CONTROL_CLASS = { + 'label': LOFormControlLabel, + 'text': LOFormControlText, + 'button': LOFormControlButton, +} + + +class LOForm(object): + MODELS = { + 'label': 'com.sun.star.form.component.FixedText', + 'text': 'com.sun.star.form.component.TextField', + 'button': 'com.sun.star.form.component.CommandButton', + } + + def __init__(self, obj, draw_page): + self._obj = obj + self._dp = draw_page + self._controls = {} + self._init_controls() + + def __getitem__(self, index): + control = self.obj[index] + return self._controls[control.Name] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + def __contains__(self, item): + return item in self.obj + + def __len__(self): + return len(self.obj) + + def __str__(self): + return f'Form: {self.name}' + + def _init_controls(self): + types = { + 'com.sun.star.form.OFixedTextModel': 'label', + 'com.sun.star.form.OEditModel': 'text', + 'com.sun.star.form.OButtonModel': 'button', + } + for i, control in enumerate(self.obj): + name = control.Name + tipo = types[control.ImplementationName] + view = self.doc.CurrentController.getControl(control) + control = FORM_CONTROL_CLASS[tipo](control, view) + control.index = i + setattr(self, name, control) + self._controls[name] = control + return + + @property + def obj(self): + return self._obj @property def name(self): return self.obj.Name + @name.setter + def name(self, value): + self.obj.Name = value @property - def label(self): - return self.obj.Label - - def set_focus(self): - self._control.setFocus() - return - - -class LOForm(object): - - def __init__(self, obj): - self._obj = obj - - def __getitem__(self, index): - return LOFormControl(self.obj[index]) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - pass - - def __contains__(self, item): - return item in self.obj - - def __len__(self): - return len(self.obj) + def source(self): + return self.obj.DataSourceName + @source.setter + def source(self, value): + self.obj.DataSourceName = value @property - def obj(self): - return self._obj + def type(self): + return self.obj.CommandType + @type.setter + def type(self, value): + self.obj.CommandType = value + + @property + def command(self): + return self.obj.Command + @command.setter + def command(self, value): + self.obj.Command = value + + @property + def doc(self): + return self.obj.Parent.Parent + + def _special_properties(self, tipo, args): + if tipo == 'button': + # ~ if 'ImageURL' in args: + # ~ args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + return args + + def add(self, args): + name = args['Name'] + tipo = args.pop('Type').lower() + w = args.pop('Width') + h = args.pop('Height') + x = args.pop('X', 0) + y = args.pop('Y', 0) + control = self.doc.createInstance('com.sun.star.drawing.ControlShape') + control.setSize(Size(w, h)) + control.setPosition(Point(x, y)) + model = self.doc.createInstance(self.MODELS[tipo]) + args = self._special_properties(tipo, args) + _set_properties(model, args) + control.Control = model + index = len(self) + self.obj.insertByIndex(index, model) + self._dp.add(control) + view = self.doc.CurrentController.getControl(self.obj.getByName(name)) + control = FORM_CONTROL_CLASS[tipo](control, view, self.obj) + control.index = index + setattr(self, name, control) + self._controls[name] = control + return control class LOSheetForms(object): - def __init__(self, obj): - self._obj = obj + def __init__(self, draw_page): + self._dp = draw_page + self._obj = draw_page.Forms def __getitem__(self, index): - return LOForm(self.obj[index]) + return LOForm(self.obj[index], self._dp) def __enter__(self): return self @@ -1540,24 +1811,144 @@ class LOSheetForms(object): def obj(self): return self._obj + @property + def doc(self): + return self.obj.Parent + @property + def count(self): + return len(self) + + @property + def names(self): + return self.obj.ElementNames + + def insert(self, name): + form = self.doc.createInstance('com.sun.star.form.component.Form') + self.obj.insertByName(name, form) + return LOForm(form, self._dp) + + def remove(self, index): + if isinstance(index, int): + self.obj.removeByIndex(index) + else: + self.obj.removeByName(index) + return + + +# ~ IsFiltered, +# ~ IsManualPageBreak, +# ~ IsStartOfNewPage class LOSheetRows(object): - def __init__(self, sheet): + def __init__(self, sheet, obj): self._sheet = sheet - self._obj = sheet.obj.Rows + self._obj = obj def __getitem__(self, index): - return LOSheetRows(self.obj[index]) + if isinstance(index, int): + rows = LOSheetRows(self._sheet, self.obj[index]) + else: + rango = self._sheet[index.start:index.stop,0:] + rows = LOSheetRows(self._sheet, rango.obj.Rows) + return rows + + def __len__(self): + return self.obj.Count @property def obj(self): return self._obj + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def color(self): + return self.obj.CellBackColor + @color.setter + def color(self, value): + self.obj.CellBackColor = value + + @property + def is_transparent(self): + return self.obj.IsCellBackgroundTransparent + @is_transparent.setter + def is_transparent(self, value): + self.obj.IsCellBackgroundTransparent = value + + @property + def height(self): + return self.obj.Height + @height.setter + def height(self, value): + self.obj.Height = value + + def optimal(self): + self.obj.OptimalHeight = True + return + def insert(self, index, count): self.obj.insertByIndex(index, count) - end = index + count - return self._sheet[index:end,0:] + return + + def remove(self, index, count): + self.obj.removeByIndex(index, count) + return + + +# ~ IsManualPageBreak, +# ~ IsStartOfNewPage +class LOSheetColumns(object): + + def __init__(self, sheet, obj): + self._sheet = sheet + self._obj = obj + + def __getitem__(self, index): + if isinstance(index, (int, str)): + rows = LOSheetColumns(self._sheet, self.obj[index]) + else: + rango = self._sheet[0,index.start:index.stop] + rows = LOSheetColumns(self._sheet, rango.obj.Columns) + return rows + + def __len__(self): + return self.obj.Count + + @property + def obj(self): + return self._obj + + @property + def visible(self): + return self._obj.IsVisible + @visible.setter + def visible(self, value): + self._obj.IsVisible = value + + @property + def width(self): + return self.obj.Width + @width.setter + def width(self, value): + self.obj.Width = value + + def optimal(self): + self.obj.OptimalWidth = True + return + + def insert(self, index, count): + self.obj.insertByIndex(index, count) + return + + def remove(self, index, count): + self.obj.removeByIndex(index, count) + return class LOCalcSheet(object): @@ -1656,14 +2047,44 @@ class LOCalcSheet(object): @property def rows(self): - return LOSheetRows(self) + return LOSheetRows(self, self.obj.Rows) + + @property + def columns(self): + return LOSheetColumns(self, self.obj.Columns) @property def forms(self): - return LOSheetForms(self.obj.DrawPage.Forms) + return LOSheetForms(self.obj.DrawPage) + + @property + def events(self): + names = ('OnFocus', 'OnUnfocus', 'OnSelect', 'OnDoubleClick', + 'OnRightClick', 'OnChange', 'OnCalculate') + evs = self.obj.Events + events = {n: _property_to_dict(evs.getByName(n)) for n in names + if evs.getByName(n)} + return events + @events.setter + def events(self, values): + pv = '[]com.sun.star.beans.PropertyValue' + ev = self.obj.Events + for name, v in values.items(): + url = _get_url_script(v) + args = dict_to_property(dict(EventType='Script', Script=url)) + # ~ e.replaceByName(k, args) + uno.invoke(ev, 'replaceByName', (name, uno.Any(pv, args))) + + @property + def search_descriptor(self): + return self.obj.createSearchDescriptor() + + @property + def replace_descriptor(self): + return self.obj.createReplaceDescriptor() def activate(self): - self.doc.activate(self._obj) + self.doc.activate(self.obj) return def clean(self): @@ -1715,32 +2136,10 @@ class LOCalcSheet(object): rango = self.used_area return rango.render(data, clean) - -class LOCalcRows(object): - - def __init__(self, obj): - self._obj = obj - - def __len__(self): - return self.obj.Count - - def __str__(self): - return 'Rows' - - @property - def obj(self): - return self._obj - - @property - def count(self): - return len(self) - - @property - def visible(self): - return self.obj.IsVisible - @visible.setter - def visible(self, value): - self.obj.IsVisible = value + def find(self, search_string, rango=None): + if rango is None: + rango = self.used_area + return rango.find(search_string) class LOCalcRange(object): @@ -1775,6 +2174,9 @@ class LOCalcRange(object): def __exit__(self, exc_type, exc_value, traceback): pass + def __contains__(self, item): + return item.in_range(self) + def __str__(self): if self.is_none: s = 'Range: None' @@ -1838,7 +2240,7 @@ class LOCalcRange(object): @property def rows(self): - return LOCalcRows(self.obj.Rows) + return LOSheetRows(self.sheet, self.obj.Rows) @property def row(self): @@ -1991,10 +2393,112 @@ class LOCalcRange(object): rangos = [LOCalcRange(self.sheet[r.AbsoluteName].obj) for r in rangos] return tuple(rangos) + @property + def merged_area(self): + cursor = self.cursor + cursor.collapseToMergedArea() + rango = LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + return rango + + @property + def empty(self): + cursor = self.sheet.get_cursor(self.obj) + cursor = self.cursor + rangos = cursor.queryEmptyCells() + rangos = [LOCalcRange(self.sheet[r.AbsoluteName].obj) for r in rangos] + return tuple(rangos) + + @property + def merge(self): + return self.obj.IsMerged + @merge.setter + def merge(self, value): + self.obj.merge(value) + + @property + def style(self): + return self.obj.CellStyle + @style.setter + def style(self, value): + self.obj.CellStyle = value + + @property + def auto_format(self): + return '' + @auto_format.setter + def auto_format(self, value): + self.obj.autoFormat(value) + + @property + def validation(self): + return self.obj.Validation + @validation.setter + def validation(self, values): + current = self.validation + if not values: + current.Type = ValidationType.ANY + current.ShowInputMessage = False + else: + is_list = False + for k, v in values.items(): + if k == 'Type' and v == VT.LIST: + is_list = True + if k == 'Formula1' and is_list: + if isinstance(v, (tuple, list)): + v = ';'.join(['"{}"'.format(i) for i in v]) + setattr(current, k, v) + self.obj.Validation = current + def select(self): self.doc.select(self.obj) return + def search(self, options, find_all=True): + rangos = None + + descriptor = self.sheet.search_descriptor + descriptor.setSearchString(options['Search']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + + if find_all: + found = self.obj.findAll(descriptor) + else: + found = self.obj.findFirst(descriptor) + + if found: + if found.ImplementationName == OBJ_CELL: + rangos = LOCalcRange(found) + else: + rangos = [LOCalcRange(f) for f in found] + + return rangos + + def replace(self, options): + descriptor = self.sheet.replace_descriptor + descriptor.setSearchString(options['Search']) + descriptor.setReplaceString(options['Replace']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + count = self.obj.replaceAll(descriptor) + return count + + def in_range(self, rango): + if isinstance(rango, LOCalcRange): + address = rango.range_address + else: + address = rango.RangeAddress + result = self.cursor.queryIntersection(address) + return bool(result.Count) + def offset(self, rows=0, cols=1): ra = self.range_address col = ra.EndColumn + cols @@ -2006,6 +2510,10 @@ class LOCalcRange(object): cursor.collapseToSize(cols, rows) return LOCalcRange(self.sheet[cursor.AbsoluteName].obj) + def copy(self, source): + self.sheet.obj.copyRange(self.address, source.range_address) + return + def copy_to(self, cell, formula=False): rango = cell.to_size(self.rows, self.columns) if formula: @@ -2029,7 +2537,7 @@ class LOCalcRange(object): self.to_size(rows, cols).data = data return - def auto_width(self): + def optimal_width(self): self.obj.Columns.OptimalWidth = True return @@ -2083,6 +2591,17 @@ class LOCalcRange(object): self._render_value(k, v) return + def find(self, search_string): + if self._sd is None: + self._sd = self.sheet.obj.createSearchDescriptor() + self._sd.SearchCaseSensitive = False + + self._sd.setSearchString(search_string) + cell = self.obj.findFirst(self._sd) + if cell: + cell = LOCalcRange(cell) + return cell + def find_all(self, search_string): if self._sd is None: self._sd = self.sheet.obj.createSearchDescriptor() @@ -2138,10 +2657,22 @@ class LOCalcRange(object): args['Y'] = args.get('Y', ps['Y']) # ~ img.ResizeWithCell = True img = self.sheet.dp.insert_image(path, args) - img.Anchor = self.obj + img.anchor = self.obj args.clear() return img + def insert_shape(self, tipo, args={}): + ps = self.possize + args['Width'] = args.get('Width', ps['Width']) + args['Height'] = args.get('Height', ps['Height']) + args['X'] = args.get('X', ps['X']) + args['Y'] = args.get('Y', ps['Y']) + + shape = self.sheet.dp.add(tipo, args) + shape.anchor = self.obj + args.clear() + return + def filter_by_color(self, cell): rangos = cell.column[1:,:].visible for r in rangos: @@ -2155,6 +2686,38 @@ class LOCalcRange(object): self.obj.clearContents(what) return + def transpose(self): + # ~ 'Flags': 'A', + # ~ 'FormulaCommand': 0, + # ~ 'SkipEmptyCells': False, + # ~ 'AsLink': False, + # ~ 'MoveMode': 4, + self.select() + self.doc.copy() + self.clear(1023) + self[0,0].select() + self.doc.insert_contents({'Transpose': True}) + _CB.set('') + return + + def transpose_data(self, formula=False): + data = self.data + if formula: + data = self.formula + data = tuple(zip(*data)) + self.clear(1023) + self[0,0].copy_from(data, formula=formula) + return + + def merge_by_row(self): + for r in range(len(self.rows)): + self[r].merge = True + return + + def fill(self, source=1): + self.obj.fillAuto(0, source) + return + class LOWriterPageStyle(LOBaseObject): @@ -2211,7 +2774,10 @@ class LOWriterTextRange(object): @property def string(self): - return self.obj.String + s = '' + if not self._is_table: + s = self.obj.String + return s @string.setter def string(self, value): self.obj.String = value @@ -2236,10 +2802,6 @@ class LOWriterTextRange(object): def dp(self): return self._doc.dp - @property - def is_table(self): - return self._is_table - def offset(self): cursor = self.cursor.getEnd() return LOWriterTextRange(cursor, self._doc) @@ -2250,6 +2812,23 @@ class LOWriterTextRange(object): self.text.insertTextContent(cursor, data, replace) return + def new_line(self, count=1): + cursor = self.cursor + for i in range(count): + self.text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False) + return self._doc.selection + + def insert_table(self, data): + table = self._doc.create_instance('com.sun.star.text.TextTable') + rows = len(data) + cols = len(data[0]) + table.initialize(rows, cols) + self.insert_content(table) + table.DataArray = data + name = table.Name + table = LOWriterTextTable(self._doc.tables[name], self._doc) + return table + def insert_image(self, path, args={}): w = args.get('Width', 1000) h = args.get('Height', 1000) @@ -2292,6 +2871,47 @@ class LOWriterTextRanges(object): return self._obj +class LOWriterTextTable(object): + + def __init__(self, obj, doc): + self._obj = obj + self._doc = doc + + @property + def obj(self): + return self._obj + + @property + def name(self): + return self._obj.Name + + @property + def data(self): + return self._obj.DataArray + @data.setter + def data(self, values): + self._obj.DataArray = values + + +class LOWriterTextTables(object): + + def __init__(self, doc): + self._doc = doc + self._obj = doc.obj.TextTables + + def __getitem__(self, key): + return LOWriterTextTable(self._obj[key], self._doc) + + def __len__(self): + return self._obj.Count + + def insert(self, data, text_range=None): + if text_range is None: + text_range = self._doc.selection + text_range.insert_table(data) + return + + class LOWriter(LODocument): def __init__(self, obj): @@ -2306,6 +2926,10 @@ class LOWriter(LODocument): def paragraphs(self): return LOWriterTextRanges(self.obj.Text, self) + @property + def tables(self): + return LOWriterTextTables(self) + @property def selection(self): sel = self.obj.CurrentSelection @@ -2349,8 +2973,72 @@ class LOWriter(LODocument): ps = self.obj.StyleFamilies['PageStyles'] return LOWriterPageStyles(ps) + @property + def search_descriptor(self): + return self.obj.createSearchDescriptor() + + @property + def replace_descriptor(self): + return self.obj.createReplaceDescriptor() + + def goto_start(self): + self.view_cursor.gotoStart(False) + return self.selection + + def goto_end(self): + self.view_cursor.gotoEnd(False) + return self.selection + + def search(self, options, find_all=True): + descriptor = self.search_descriptor + descriptor.setSearchString(options.get('Search', '')) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if 'Attributes' in options: + attr = dict_to_property(options['Attributes']) + descriptor.setSearchAttributes(attr) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + + result = False + if find_all: + found = self.obj.findAll(descriptor) + if len(found): + result = [LOWriterTextRange(f, self) for f in found] + else: + found = self.obj.findFirst(descriptor) + if found: + result = LOWriterTextRange(found, self) + + return result + + def replace(self, options): + descriptor = self.replace_descriptor + descriptor.setSearchString(options['Search']) + descriptor.setReplaceString(options['Replace']) + descriptor.SearchCaseSensitive = options.get('CaseSensitive', False) + descriptor.SearchWords = options.get('Words', False) + if 'Attributes' in options: + attr = dict_to_property(options['Attributes']) + descriptor.setSearchAttributes(attr) + if hasattr(descriptor, 'SearchRegularExpression'): + descriptor.SearchRegularExpression = options.get('RegularExpression', False) + if hasattr(descriptor, 'SearchType') and 'Type' in options: + descriptor.SearchType = options['Type'] + found = self.obj.replaceAll(descriptor) + return found + + def select(self, text): + if hasattr(text, 'obj'): + text = text.obj + self._cc.select(text) + return + class LOShape(LOBaseObject): + IMAGE = 'com.sun.star.drawing.GraphicObjectShape' def __init__(self, obj, index): self._index = index @@ -2358,11 +3046,22 @@ class LOShape(LOBaseObject): @property def type(self): - return 'shape' + t = self.shape_type[21:] + if self.is_image: + t = 'image' + return t + + @property + def shape_type(self): + return self.obj.ShapeType + + @property + def is_image(self): + return self.shape_type == self.IMAGE @property def name(self): - return self.obj.Name or f'shape{self.index}' + return self.obj.Name or f'{self.type}{self.index}' @name.setter def name(self, value): self.obj.Name = value @@ -2418,10 +3117,64 @@ class LOShape(LOBaseObject): def visible(self, value): self.obj.Visible = value + @property + def path(self): + return self.url + @property + def url(self): + url = '' + if self.is_image: + url = _P.to_system(self.obj.GraphicURL.OriginURL) + return url + + @property + def mimetype(self): + mt = '' + if self.is_image: + mt = self.obj.GraphicURL.MimeType + return mt + + @property + def linked(self): + l = False + if self.is_image: + l = self.obj.GraphicURL.Linked + return l + + def delete(self): + self.remove() + return def remove(self): self.obj.Parent.remove(self.obj) return + def save(self, path: str, mimetype=DEFAULT_MIME_TYPE): + if _P.is_dir(path): + name = self.name + ext = mimetype.lower() + else: + p = _P(path) + path = p.path + name = p.name + ext = p.ext.lower() + + path = _P.join(path, f'{name}.{ext}') + args = dict( + URL = _P.to_url(path), + MimeType = MIME_TYPE[ext], + ) + if not _export_image(self.obj, args): + path = '' + return path + + # ~ def save2(self, path: str): + # ~ size = len(self.obj.Bitmap.DIB) + # ~ data = self.obj.GraphicStream.readBytes((), size) + # ~ data = data[-1].value + # ~ path = _P.join(path, f'{self.name}.png') + # ~ _P.save_bin(path, b'') + # ~ return + class LODrawPage(LOBaseObject): @@ -2440,6 +3193,18 @@ class LODrawPage(LOBaseObject): break return shape + def __iter__(self): + self._index = 0 + return self + + def __next__(self): + if self._index == self.count: + raise StopIteration + shape = self[self._index] + self._index += 1 + return shape + + @property def name(self): return self.obj.Name @@ -2474,17 +3239,18 @@ class LODrawPage(LOBaseObject): Ellipse Text """ + index = self.count w = args.get('Width', 3000) h = args.get('Height', 3000) x = args.get('X', 1000) y = args.get('Y', 1000) + name = args.get('Name', f'{type_shape.lower()}{index}') service = f'com.sun.star.drawing.{type_shape}Shape' shape = self.create_instance(service) shape.Size = Size(w, h) shape.Position = Point(x, y) - index = self.count - shape.Name = f'{type_shape.lower()}{index}' + shape.Name = name self.obj.add(shape) return LOShape(self.obj[index], index) @@ -2493,18 +3259,24 @@ class LODrawPage(LOBaseObject): shape = shape.obj return self.obj.remove(shape) + def remove_all(self): + while self.count: + self.obj.remove(self.obj[0]) + return + def insert_image(self, path, args={}): + index = self.count w = args.get('Width', 3000) h = args.get('Height', 3000) x = args.get('X', 1000) y = args.get('Y', 1000) + name = args.get('Name', f'image{index}') image = self.create_instance('com.sun.star.drawing.GraphicObjectShape') image.GraphicURL = _P.to_url(path) image.Size = Size(w, h) image.Position = Point(x, y) - index = self.count - image.Name = f'image{index}' + image.Name = name self.obj.add(image) return LOShape(self.obj[index], index) @@ -2533,7 +3305,7 @@ class LODrawImpress(LODocument): def paste(self): call_dispatch(self.frame, '.uno:Paste') - return self.selection + return self.current_page[-1] def add(self, type_shape, args={}): return self.current_page.add(type_shape, args) @@ -2877,30 +3649,31 @@ class LODocs(object): LODocs._desktop = self._desktop def __getitem__(self, index): - doc = None + document = None for i, doc in enumerate(self._desktop.Components): if isinstance(index, int) and i == index: - doc = _get_class_doc(doc) + document = _get_class_doc(doc) break elif isinstance(index, str) and doc.Title == index: - doc = _get_class_doc(doc) + document = _get_class_doc(doc) break - return doc + return document def __contains__(self, item): doc = self[item] return not doc is None def __iter__(self): - self._i = 0 + self._i = -1 return self def __next__(self): + self._i += 1 doc = self[self._i] if doc is None: raise StopIteration - self._i += 1 - return doc + else: + return doc def __len__(self): for i, _ in enumerate(self._desktop.Components): @@ -2952,9 +3725,9 @@ def _add_listeners(events, control, name=''): 'addActionListener': EventsButton, 'addMouseListener': EventsMouse, 'addFocusListener': EventsFocus, - # ~ 'addItemListener': EventsItem, + 'addItemListener': EventsItem, 'addKeyListener': EventsKey, - # ~ 'addTabListener': EventsTab, + 'addTabListener': EventsTab, } if hasattr(control, 'obj'): control = control.obj @@ -2962,6 +3735,7 @@ def _add_listeners(events, control, name=''): is_grid = control.ImplementationName == 'stardiv.Toolkit.GridControl' is_link = control.ImplementationName == 'stardiv.Toolkit.UnoFixedHyperlinkControl' is_roadmap = control.ImplementationName == 'stardiv.Toolkit.UnoRoadmapControl' + is_pages = control.ImplementationName == 'stardiv.Toolkit.UnoMultiPageControl' for key, value in listeners.items(): if hasattr(control, key): @@ -2977,10 +3751,10 @@ def _add_listeners(events, control, name=''): getattr(control, key)(listeners[key](events, name)) - # ~ if is_grid: - # ~ controllers = EventsGrid(events, name) - # ~ control.addSelectionListener(controllers) - # ~ control.Model.GridDataModel.addGridDataListener(controllers) + if is_grid: + controllers = EventsGrid(events, name) + control.addSelectionListener(controllers) + control.Model.GridDataModel.addGridDataListener(controllers) return @@ -3114,9 +3888,195 @@ class EventsKey(EventsListenerBase, XKeyListener): 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 = ? @@ -3125,7 +4085,6 @@ class UnoBaseObject(object): def __init__(self, obj, path=''): self._obj = obj self._model = obj.Model - # ~ self._path = path def __setattr__(self, name, value): exists = hasattr(self, name) @@ -3134,6 +4093,12 @@ class UnoBaseObject(object): else: super().__setattr__(name, value) + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + @property def obj(self): return self._obj @@ -3432,14 +4397,14 @@ class UnoRadio(UnoBaseObject): self.model.Label = value -class UnoCheck(UnoBaseObject): +class UnoCheckBox(UnoBaseObject): def __init__(self, obj): super().__init__(obj) @property def type(self): - return 'check' + return 'checkbox' @property def value(self): @@ -3480,6 +4445,9 @@ class UnoText(UnoBaseObject): def value(self, value): self.model.Text = value + def validate(self): + return + class UnoImage(UnoBaseObject): @@ -3578,17 +4546,483 @@ class UnoListBox(UnoBaseObject): return +class UnoRoadmap(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._options = () + + def __setattr__(self, name, value): + if name in ('_options',): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def options(self): + return self._options + @options.setter + def options(self, values): + self._options = values + for i, v in enumerate(values): + opt = self.model.createInstance() + opt.ID = i + opt.Label = v + self.model.insertByIndex(i, opt) + return + + @property + def enabled(self): + return True + @enabled.setter + def enabled(self, value): + for m in self.model: + m.Enabled = value + return + + def set_enabled(self, index, value): + self.model.getByIndex(index).Enabled = value + return + + +class UnoTree(UnoBaseObject): + + def __init__(self, obj, ): + super().__init__(obj) + self._tdm = None + self._data = [] + + def __setattr__(self, name, value): + if name in ('_tdm', '_data'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + @property + def selection(self): + sel = self.obj.Selection + return sel.DataValue, sel.DisplayValue + + @property + def parent(self): + parent = self.obj.Selection.Parent + if parent is None: + return () + return parent.DataValue, parent.DisplayValue + + def _get_parents(self, node): + value = (node.DisplayValue,) + parent = node.Parent + if parent is None: + return value + return self._get_parents(parent) + value + + @property + def parents(self): + values = self._get_parents(self.obj.Selection) + return values + + @property + def root(self): + if self._tdm is None: + return '' + return self._tdm.Root.DisplayValue + @root.setter + def root(self, value): + self._add_data_model(value) + + def _add_data_model(self, name): + tdm = create_instance('com.sun.star.awt.tree.MutableTreeDataModel') + root = tdm.createNode(name, True) + root.DataValue = 0 + tdm.setRoot(root) + self.model.DataModel = tdm + self._tdm = self.model.DataModel + return + + @property + def path(self): + return self.root + @path.setter + def path(self, value): + self.data = _P.walk_dir(value, True) + + @property + def data(self): + return self._data + @data.setter + def data(self, values): + self._data = list(values) + self._add_data() + + def _add_data(self): + if not self.data: + return + + parents = {} + for node in self.data: + parent = parents.get(node[1], self._tdm.Root) + child = self._tdm.createNode(node[2], False) + child.DataValue = node[0] + parent.appendChild(child) + parents[node[0]] = child + self.obj.expandNode(self._tdm.Root) + return + + +# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1grid.html +class UnoGrid(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._gdm = self.model.GridDataModel + self._data = [] + self._formats = () + + def __setattr__(self, name, value): + if name in ('_gdm', '_data', '_formats'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __getitem__(self, key): + value = self._gdm.getCellData(key[0], key[1]) + return value + + def __setitem__(self, key, value): + self._gdm.updateCellData(key[0], key[1], value) + return + + @property + def type(self): + return 'grid' + + @property + def columns(self): + return {} + @columns.setter + def columns(self, values): + # ~ self._columns = values + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html + model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) + for properties in values: + column = create_instance('com.sun.star.awt.grid.GridColumn', True) + for k, v in properties.items(): + setattr(column, k, v) + model.addColumn(column) + self.model.ColumnModel = model + return + + @property + def data(self): + return self._data + @data.setter + def data(self, values): + self._data = values + self.clear() + headings = tuple(range(1, len(values) + 1)) + self._gdm.addRows(headings, values) + # ~ rows = range(grid_dm.RowCount) + # ~ colors = [COLORS['GRAY'] if r % 2 else COLORS['WHITE'] for r in rows] + # ~ grid.Model.RowBackgroundColors = tuple(colors) + return + + @property + def value(self): + if self.column == -1 or self.row == -1: + return '' + return self[self.column, self.row] + @value.setter + def value(self, value): + if self.column > -1 and self.row > -1: + self[self.column, self.row] = value + + @property + def row(self): + return self.obj.CurrentRow + + @property + def row_count(self): + return self._gdm.RowCount + + @property + def column(self): + return self.obj.CurrentColumn + + @property + def column(self): + return self.obj.CurrentColumn + + @property + def is_valid(self): + return not (self.row == -1 or self.column == -1) + + @property + def formats(self): + return self._formats + @formats.setter + def formats(self, values): + self._formats = values + + def clear(self): + self._gdm.removeAllRows() + return + + def _format_columns(self, data): + row = data + if self.formats: + for i, f in enumerate(formats): + if f: + row[i] = f.format(data[i]) + return row + + def add_row(self, data): + self._data.append(data) + row = self._format_columns(data) + self._gdm.addRow(self.row_count + 1, row) + return + + def set_cell_tooltip(self, col, row, value): + self._gdm.updateCellToolTip(col, row, value) + return + + def get_cell_tooltip(self, col, row): + value = self._gdm.getCellToolTip(col, row) + return value + + def sort(self, column, asc=True): + self._gdm.sortByColumn(column, asc) + self.update_row_heading() + return + + def update_row_heading(self): + for i in range(self.row_count): + self._gdm.updateRowHeading(i, i + 1) + return + + def remove_row(self, row): + self._gdm.removeRow(row) + del self._data[row] + self.update_row_heading() + return + + +class UnoPage(object): + + def __init__(self, obj): + self._obj = obj + self._events = None + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + pass + + @property + def obj(self): + return self._obj + + @property + def model(self): + return self._obj.Model + + # ~ @property + # ~ def id(self): + # ~ return self.m.TabPageID + + @property + def parent(self): + return self.obj.Context + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(UNO_MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self.obj.getControl(name) + _add_listeners(self._events, control, name) + control = UNO_CLASSES[tipo](control) + + if tipo in ('listbox',): + control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + return control + + +class UnoPages(UnoBaseObject): + + def __init__(self, obj): + super().__init__(obj) + self._sheets = [] + self._events = None + + def __setattr__(self, name, value): + if name in ('_sheets', '_events'): + self.__dict__[name] = value + else: + super().__setattr__(name, value) + + def __getitem__(self, index): + name = index + if isinstance(index, int): + name = f'sheet{index}' + sheet = self.obj.getControl(name) + page = UnoPage(sheet) + page._events = self._events + return page + + @property + def type(self): + return 'pages' + + @property + def current(self): + return self.obj.ActiveTabID + @property + def active(self): + return self.current + + @property + def sheets(self): + return self._sheets + @sheets.setter + def sheets(self, values): + self._sheets = values + for i, title in enumerate(values): + sheet = self.m.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.m.insertByName(f'sheet{i + 1}', sheet) + return + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers + + @property + def visible(self): + return self.obj.Visible + @visible.setter + def visible(self, value): + self.obj.Visible = value + + def insert(self, title): + self._sheets.append(title) + id = len(self._sheets) + sheet = self.m.createInstance('com.sun.star.awt.UnoPageModel') + sheet.Title = title + self.m.insertByName(f'sheet{id}', sheet) + return self[id] + + def remove(self, id): + self.obj.removeTab(id) + return + + def activate(self, id): + self.obj.activateTab(id) + return + + UNO_CLASSES = { 'label': UnoLabel, 'link': UnoLabelLink, 'button': UnoButton, 'radio': UnoRadio, - 'check': UnoCheck, + 'checkbox': UnoCheckBox, 'text': UnoText, 'image': UnoImage, 'listbox': UnoListBox, + 'roadmap': UnoRoadmap, + 'tree': UnoTree, + 'grid': UnoGrid, + 'pages': UnoPages, } +UNO_MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', + 'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', +} +# ~ 'CurrencyField': 'com.sun.star.awt.UnoControlCurrencyFieldModel', +# ~ 'DateField': 'com.sun.star.awt.UnoControlDateFieldModel', +# ~ 'FileControl': 'com.sun.star.awt.UnoControlFileControlModel', +# ~ 'FormattedField': 'com.sun.star.awt.UnoControlFormattedFieldModel', +# ~ 'NumericField': 'com.sun.star.awt.UnoControlNumericFieldModel', +# ~ 'PatternField': 'com.sun.star.awt.UnoControlPatternFieldModel', +# ~ 'ProgressBar': 'com.sun.star.awt.UnoControlProgressBarModel', +# ~ 'ScrollBar': 'com.sun.star.awt.UnoControlScrollBarModel', +# ~ 'SimpleAnimation': 'com.sun.star.awt.UnoControlSimpleAnimationModel', +# ~ 'SpinButton': 'com.sun.star.awt.UnoControlSpinButtonModel', +# ~ 'Throbber': 'com.sun.star.awt.UnoControlThrobberModel', +# ~ 'TimeField': 'com.sun.star.awt.UnoControlTimeFieldModel', + class LODialog(object): SEPARATION = 5 @@ -3597,15 +5031,16 @@ class LODialog(object): 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', 'button': 'com.sun.star.awt.UnoControlButtonModel', 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', - 'check': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel', 'text': 'com.sun.star.awt.UnoControlEditModel', 'image': 'com.sun.star.awt.UnoControlImageControlModel', 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', - # ~ 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', - # ~ 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', - # ~ 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', - # ~ 'tree': 'com.sun.star.awt.tree.TreeControlModel', - # ~ 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', } def __init__(self, args): @@ -3617,6 +5052,7 @@ class LODialog(object): self._color_on_focus = COLOR_ON_FOCUS self._id = '' self._path = '' + self._init_controls() def _create(self, args): service = 'com.sun.star.awt.DialogProvider' @@ -3629,7 +5065,7 @@ class LODialog(object): if 'Location' in args: name = args['Name'] library = args.get('Library', 'Standard') - location = args.get('Location', 'application') + location = args.get('Location', 'application').lower() if location == 'user': location = 'application' url = f'vnd.sun.star.script:{library}.{name}?location={location}' @@ -3651,6 +5087,23 @@ class LODialog(object): dlg.createPeer(toolkit, None) return dlg + def _get_type_control(self, name): + name = name.split('.')[2] + types = { + 'UnoFixedTextControl': 'label', + 'UnoEditControl': 'text', + 'UnoButtonControl': 'button', + } + return types[name] + + def _init_controls(self): + for control in self.obj.getControls(): + tipo = self._get_type_control(control.ImplementationName) + name = control.Model.Name + control = UNO_CLASSES[tipo](control) + setattr(self, name, control) + return + @property def obj(self): return self._obj @@ -3730,27 +5183,33 @@ class LODialog(object): return _P.to_url(path) def _special_properties(self, tipo, args): - columns = args.pop('Columns', ()) - if tipo == 'link' and not 'Label' in args: args['Label'] = args['URL'] - elif tipo == 'grid': - args['ColumnModel'] = self._set_column_model(columns) - elif tipo == 'button': + return args + + if tipo == 'button': if 'ImageURL' in args: args['ImageURL'] = self._set_image_url(args['ImageURL']) - if not 'FocusOnClick' in args: - args['FocusOnClick'] = False - elif tipo == 'roadmap': - if not 'Height' in args: - args['Height'] = self.height + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) if 'Title' in args: args['Text'] = args.pop('Title') - elif tipo == 'tab': - if not 'Width' in args: - args['Width'] = self.width - if not 'Height' in args: - args['Height'] = self.height + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) return args @@ -3758,6 +5217,7 @@ class LODialog(object): tipo = args.pop('Type').lower() root = args.pop('Root', '') sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) args = self._special_properties(tipo, args) model = self.model.createInstance(self.MODELS[tipo]) @@ -3767,11 +5227,14 @@ class LODialog(object): control = self.obj.getControl(name) _add_listeners(self.events, control, name) control = UNO_CLASSES[tipo](control) + if tipo in ('listbox',): control.path = self.path if tipo == 'tree' and root: control.root = root + elif tipo == 'grid' and columns: + control.columns = columns elif tipo == 'pages' and sheets: control.sheets = sheets control.events = self.events @@ -3944,9 +5407,9 @@ class LOMenu(object): if MENUS[index.lower()] == cmd: self._menu = menu break - line = self._menu.get('CommandURL', '') - line += self._get_submenus(self._menu['ItemDescriptorContainer']) - return line + # ~ line = self._menu.get('CommandURL', '') + # ~ line += self._get_submenus(self._menu['ItemDescriptorContainer']) + return self._menu def _get_menus(self): instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' @@ -4073,6 +5536,245 @@ class LOMenus(object): return LOMenu(index) +class LOWindow(object): + EMPTY = """ + +""" + MODELS = { + 'label': 'com.sun.star.awt.UnoControlFixedTextModel', + 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', + 'button': 'com.sun.star.awt.UnoControlButtonModel', + 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', + 'checkbox': 'com.sun.star.awt.UnoControlCheckBoxModel', + 'text': 'com.sun.star.awt.UnoControlEditModel', + 'image': 'com.sun.star.awt.UnoControlImageControlModel', + 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', + 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', + 'tree': 'com.sun.star.awt.tree.TreeControlModel', + 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', + 'pages': 'com.sun.star.awt.UnoMultiPageModel', + 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', + 'combobox': 'com.sun.star.awt.UnoControlComboBoxModel', + } + + def __init__(self, args): + self._events = None + self._menu = None + self._container = None + self._model = None + self._id = '' + self._path = '' + self._obj = self._create(args) + + def _create(self, properties): + ps = ( + properties.get('X', 0), + properties.get('Y', 0), + properties.get('Width', 500), + properties.get('Height', 500), + ) + self._title = properties.get('Title', TITLE) + self._create_frame(ps) + self._create_container(ps) + self._create_subcontainer(ps) + # ~ self._create_splitter(ps) + return + + def _create_frame(self, ps): + service = 'com.sun.star.frame.TaskCreator' + tc = create_instance(service, True) + self._frame = tc.createInstanceWithArguments(( + NamedValue('FrameName', 'EasyMacroWin'), + NamedValue('PosSize', Rectangle(*ps)), + )) + self._window = self._frame.getContainerWindow() + self._toolkit = self._window.getToolkit() + desktop = get_desktop() + self._frame.setCreator(desktop) + desktop.getFrames().append(self._frame) + self._frame.Title = self._title + return + + def _create_container(self, ps): + service = 'com.sun.star.awt.UnoControlContainer' + self._container = create_instance(service, True) + service = 'com.sun.star.awt.UnoControlContainerModel' + model = create_instance(service, True) + model.BackgroundColor = get_color((225, 225, 225)) + self._container.setModel(model) + self._container.createPeer(self._toolkit, self._window) + self._container.setPosSize(*ps, POSSIZE) + self._frame.setComponent(self._container, None) + return + + def _create_subcontainer(self, ps): + service = 'com.sun.star.awt.ContainerWindowProvider' + cwp = create_instance(service, True) + + path_tmp = _P.save_tmp(self.EMPTY) + subcont = cwp.createContainerWindow( + _P.to_url(path_tmp), '', self._container.getPeer(), None) + _P.kill(path_tmp) + + subcont.setPosSize(0, 0, 500, 500, POSSIZE) + subcont.setVisible(True) + self._container.addControl('subcont', subcont) + self._subcont = subcont + self._model = subcont.Model + return + + def _create_popupmenu(self, menus): + menu = create_instance('com.sun.star.awt.PopupMenu', True) + for i, m in enumerate(menus): + label = m['label'] + cmd = m.get('event', '') + if not cmd: + cmd = label.lower().replace(' ', '_') + if label == '-': + menu.insertSeparator(i) + else: + menu.insertItem(i, label, m.get('style', 0), i) + menu.setCommand(i, cmd) + # ~ menu.setItemImage(i, path?, True) + menu.addMenuListener(EventsMenu(self.events)) + return menu + + def _create_menu(self, menus): + #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMenu.html + #~ nItemId specifies the ID of the menu item to be inserted. + #~ aText specifies the label of the menu item. + #~ nItemStyle 0 = Standard, CHECKABLE = 1, RADIOCHECK = 2, AUTOCHECK = 4 + #~ nItemPos specifies the position where the menu item will be inserted. + self._menu = create_instance('com.sun.star.awt.MenuBar', True) + for i, m in enumerate(menus): + self._menu.insertItem(i, m['label'], m.get('style', 0), i) + cmd = m['label'].lower().replace(' ', '_') + self._menu.setCommand(i, cmd) + submenu = self._create_popupmenu(m['submenu']) + self._menu.setPopupMenu(i, submenu) + + self._window.setMenuBar(self._menu) + return + + def _add_listeners(self, control=None): + if self.events is None: + return + controller = EventsWindow(self) + self._window.addTopWindowListener(controller) + self._window.addWindowListener(controller) + # ~ self._container.addKeyListener(EventsKeyWindow(self)) + return + + def _set_image_url(self, image): + if _P.exists(image): + return _P.to_url(image) + + path = _P.join(self._path, DIR['images'], image) + return _P.to_url(path) + + def _special_properties(self, tipo, args): + if tipo == 'link' and not 'Label' in args: + args['Label'] = args['URL'] + return args + + if tipo == 'button': + if 'ImageURL' in args: + args['ImageURL'] = self._set_image_url(args['ImageURL']) + args['FocusOnClick'] = args.get('FocusOnClick', False) + return args + + if tipo == 'roadmap': + args['Height'] = args.get('Height', self.height) + if 'Title' in args: + args['Text'] = args.pop('Title') + return args + + if tipo == 'tree': + args['SelectionType'] = args.get('SelectionType', SINGLE) + return args + + if tipo == 'grid': + args['ShowRowHeader'] = args.get('ShowRowHeader', True) + return args + + if tipo == 'pages': + args['Width'] = args.get('Width', self.width) + args['Height'] = args.get('Height', self.height) + + return args + + def add_control(self, args): + tipo = args.pop('Type').lower() + root = args.pop('Root', '') + sheets = args.pop('Sheets', ()) + columns = args.pop('Columns', ()) + + args = self._special_properties(tipo, args) + model = self.model.createInstance(self.MODELS[tipo]) + _set_properties(model, args) + name = args['Name'] + self.model.insertByName(name, model) + control = self._subcont.getControl(name) + _add_listeners(self.events, control, name) + control = UNO_CLASSES[tipo](control) + + # ~ if tipo in ('listbox',): + # ~ control.path = self.path + + if tipo == 'tree' and root: + control.root = root + elif tipo == 'grid' and columns: + control.columns = columns + elif tipo == 'pages' and sheets: + control.sheets = sheets + control.events = self.events + + setattr(self, name, control) + return control + + @property + def events(self): + return self._events + @events.setter + def events(self, controllers): + self._events = controllers(self) + self._add_listeners() + + @property + def model(self): + return self._model + + @property + def width(self): + return self._container.Size.Width + + @property + def height(self): + return self._container.Size.Height + + @property + def name(self): + return self._title.lower().replace(' ', '_') + + def add_menu(self, menus): + self._create_menu(menus) + return + + def open(self): + self._window.setVisible(True) + return + + def close(self): + self._window.setMenuBar(None) + self._window.dispose() + self._frame.close(True) + return + + +def create_window(args): + return LOWindow(args) + + class classproperty: def __init__(self, method=None): self.fget = method @@ -4125,6 +5827,7 @@ class ClipBoard(object): if df: text = transferable.getTransferData(df) return text +_CB = ClipBoard class Paths(object): @@ -4197,6 +5900,12 @@ class Paths(object): tmp = tempfile.NamedTemporaryFile(suffix=ext) return tmp.name + @classmethod + def save_tmp(cls, data): + path_tmp = cls.tmp() + cls.save(path_tmp, data) + return path_tmp + @classmethod def config(cls, name='Work'): """ @@ -4361,6 +6070,16 @@ class Paths(object): return result + @classmethod + def files(cls, path, pattern='*'): + files = [str(p) for p in Path(path).glob(pattern) if p.is_file()] + return files + + @classmethod + def dirs(cls, path): + dirs = [str(p) for p in Path(path).iterdir() if p.is_dir()] + return dirs + @classmethod def walk(cls, path, filters=''): paths = [] @@ -4374,6 +6093,25 @@ class Paths(object): paths += [cls.join(folder, f) for f in files] return paths + @classmethod + def walk_dir(cls, path, tree=False): + folders = [] + if tree: + i = 0 + p = 0 + parents = {path: 0} + for root, dirs, _ in os.walk(path): + for name in dirs: + i += 1 + rn = cls.join(root, name) + if not rn in parents: + parents[rn] = i + folders.append((i, parents[root], name)) + else: + for root, dirs, _ in os.walk(path): + folders += [cls.join(root, name) for name in dirs] + return folders + @classmethod def from_id(cls, id_ext): pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') @@ -4445,6 +6183,28 @@ class Paths(object): z.extractall(path, members=members, pwd=pwd) return True + @classmethod + def merge_zip(cls, target, zips): + try: + with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as t: + for path in zips: + with zipfile.ZipFile(path, compression=zipfile.ZIP_DEFLATED) as s: + for name in s.namelist(): + t.writestr(name, s.open(name).read()) + except Exception as e: + error(e) + return False + + return True + + @classmethod + def image(cls, path): + gp = create_instance('com.sun.star.graphic.GraphicProvider') + image = gp.queryGraphic(( + PropertyValue(Name='URL', Value=cls.to_url(path)), + )) + return image + @classmethod def copy(cls, source, target='', name=''): p, f, n, e = _P(source).info diff --git a/source/pythonpath/main.py b/source/pythonpath/main.py index 547fed7..5df7702 100644 --- a/source/pythonpath/main.py +++ b/source/pythonpath/main.py @@ -448,8 +448,6 @@ def _create_dialog(): 'Width': 12, 'Height': 12, 'Step': 1, - # ~ 'ImageURL': 'close.svg', - # ~ 'ImagePosition': 1, } dialog.add_control(args) @@ -471,7 +469,6 @@ def _create_dialog(): 'Step': 1, 'ImageURL': 'close.svg', 'ImagePosition': 1, - # ~ 'PushButtonType': 2, } dialog.add_control(args) dialog.center(dialog.cmd_close, y=-5) @@ -496,6 +493,7 @@ def _create_dialog(): 'Step': 1, 'ImageURL': 'search.svg', 'FocusOnClick': False, + 'Enabled': False, 'Y': 2, } dialog.add_control(args) From 3c21ae6a87a91e06911c92c30a0eae701a22019b Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 8 Jan 2021 21:15:38 -0600 Subject: [PATCH 12/12] Disable search --- VERSION | 2 +- conf.py | 2 +- .../{ZAZPip_v0.6.0.oxt => ZAZPip_v0.7.0.oxt} | Bin 86551 -> 86539 bytes source/description.xml | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename files/{ZAZPip_v0.6.0.oxt => ZAZPip_v0.7.0.oxt} (93%) diff --git a/VERSION b/VERSION index 09a3acf..bcaffe1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.0 \ No newline at end of file +0.7.0 \ No newline at end of file diff --git a/conf.py b/conf.py index ae67910..42e616c 100644 --- a/conf.py +++ b/conf.py @@ -26,7 +26,7 @@ import logging TYPE_EXTENSION = 1 # ~ https://semver.org/ -VERSION = '0.6.0' +VERSION = '0.7.0' # ~ Your great extension name, not used spaces NAME = 'ZAZPip' diff --git a/files/ZAZPip_v0.6.0.oxt b/files/ZAZPip_v0.7.0.oxt similarity index 93% rename from files/ZAZPip_v0.6.0.oxt rename to files/ZAZPip_v0.7.0.oxt index 4802a06c5a3f17bcbe4c877b9e9af39f344d3869..1b86d6a90630f2187cc4b3f64d57f7459facff57 100644 GIT binary patch delta 3375 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 diff --git a/source/description.xml b/source/description.xml index f2e52ef..655fa38 100644 --- a/source/description.xml +++ b/source/description.xml @@ -1,7 +1,7 @@ - + ZAZ Pip ZAZ Pip