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