#!/usr/bin/env python3 # == Rapid Develop Macros in LibreOffice == # ~ This file is part of ZAZ. # ~ ZAZ is free software: you can redistribute it and/or modify # ~ it under the terms of the GNU General Public License as published by # ~ the Free Software Foundation, either version 3 of the License, or # ~ (at your option) any later version. # ~ ZAZ is distributed in the hope that it will be useful, # ~ but WITHOUT ANY WARRANTY; without even the implied warranty of # ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # ~ GNU General Public License for more details. # ~ You should have received a copy of the GNU General Public License # ~ along with ZAZ. If not, see . import datetime import getpass import logging import os import platform import socket import subprocess import sys import time from enum import IntEnum from functools import wraps from pathlib import Path from typing import Any import uno import unohelper from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS from com.sun.star.awt.MessageBoxResults import YES from com.sun.star.awt import Rectangle 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.awt import XActionListener from com.sun.star.lang import XEventListener from com.sun.star.awt import XMouseListener from com.sun.star.awt import XMouseMotionListener # ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1FontUnderline.html from com.sun.star.awt import FontUnderline from com.sun.star.style.VerticalAlignment import TOP, MIDDLE, BOTTOM try: from peewee import Database, DateTimeField, DateField, TimeField, \ __exception_wrapper__ except ImportError as e: print('Install peewee') peewee = None 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__) LEFT = 0 CENTER = 1 RIGHT = 2 CALC = 'calc' WRITER = 'writer' DRAW = 'draw' IMPRESS = 'impress' BASE = 'base' MATH = 'math' BASIC = 'basic' 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) # ~ 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 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) SECONDS_DAY = 60 * 60 * 24 CTX = uno.getComponentContext() SM = CTX.getServiceManager() def create_instance(name: str, with_context: bool=False, args: Any=None) -> Any: if with_context: instance = SM.createInstanceWithContext(name, CTX) elif args: instance = SM.createInstanceWithArguments(name, (args,)) else: instance = SM.createInstance(name) return instance def get_app_config(node_name, key=''): name = 'com.sun.star.configuration.ConfigurationProvider' service = 'com.sun.star.configuration.ConfigurationAccess' cp = create_instance(name, True) node = PropertyValue(Name='nodepath', Value=node_name) try: ca = cp.createInstanceWithArguments(service, (node,)) if ca and not key: return ca if ca and ca.hasByName(key): return ca.getPropertyValue(key) except Exception as e: error(e) return '' LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale') LANG = LANGUAGE.split('-')[0] NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName') VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion') INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{INFO_DEBUG}" node = '/org.openoffice.Office.Calc/Calculate/Other/Date' y = get_app_config(node, 'YY') m = get_app_config(node, 'MM') d = get_app_config(node, 'DD') DATE_OFFSET = datetime.date(y, m, d).toordinal() def debug(*args): data = [str(a) for a in args] log.debug('\t'.join(data)) return def error(info): log.error(info) return def catch_exception(f): @wraps(f) def func(*args, **kwargs): try: return f(*args, **kwargs) except Exception as e: name = f.__name__ if IS_WIN: debug(traceback.format_exc()) log.error(name, exc_info=True) return func def inspect(obj: Any) -> None: zaz = create_instance('net.elmau.zaz.inspect') if hasattr(obj, 'obj'): obj = obj.obj zaz.inspect(obj) return def now(only_time=False): now = datetime.datetime.now() if only_time: now = now.time() return now def today(): return datetime.date.today() 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 _path_url(path: str) -> str: if path.startswith('file://'): return path return uno.systemPathToFileUrl(path) def _path_system(path: str) -> str: if path.startswith('file://'): return str(Path(uno.fileUrlToSystemPath(path)).resolve()) return path def _get_dispatch() -> Any: return create_instance('com.sun.star.frame.DispatchHelper') def call_dispatch(frame: Any, url: str, args: dict={}) -> None: dispatch = _get_dispatch() opt = dict_to_property(args) dispatch.executeDispatch(frame, url, '', 0, opt) return def 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 class LOBaseObject(object): def __init__(self, obj): self._obj = obj def __setattr__(self, name, value): exists = hasattr(self, name) if not exists and name != '_obj': 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): def __init__(self, obj): self._obj = obj self._cc = self.obj.getCurrentController() def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): pass @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 _path_system(self.obj.URL) @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 selection(self): sel = self.obj.CurrentSelection return sel 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 paste(self): # ~ call_dispatch(self.frame, '.uno:Paste') # ~ return def paste(self): sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') transferable = sc.getContents() self._cc.insertTransferable(transferable) return def to_pdf(self, path: str='', args: dict={}): path_pdf = path if path: if is_dir(path): _, _, n, _ = get_info_path(self.path) path_pdf = join(path, '{}.{}'.format(n, EXT['pdf'])) else: path_pdf = replace_ext(self.path, EXT['pdf']) filter_name = '{}_pdf_Export'.format(self.type) filter_data = dict_to_property(args, True) args = { 'FilterName': filter_name, 'FilterData': filter_data, } opt = dict_to_property(args) try: self.obj.storeToURL(_path_url(path_pdf), opt) except Exception as e: error(e) path_pdf = '' return path_pdf def save(self, path: str='', args: dict={}) -> bool: result = True opt = dict_to_property(args) if path: try: self.obj.storeAsURL(_path_url(path), opt) except Exception as e: error(e) result = False else: self.obj.store() return result def close(self): self.obj.close(True) return 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 __len__(self): return self._sheets.Count @property def selection(self): sel = self.obj.CurrentSelection if sel.ImplementationName in TYPE_RANGES: sel = LOCalcRange(sel) return sel @property def active(self): return LOCalcSheet(self._cc.ActiveSheet) @property def db_ranges(self): # ~ return LOCalcDataBaseRanges(self.obj.DataBaseRanges) return self.obj.DatabaseRanges class LOChart(object): def __init__(self, name, obj, draw_page): self._name = name self._obj = obj self._eobj = self._obj.EmbeddedObject self._type = 'Column' self._cell = None self._shape = self._get_shape(draw_page) self._pos = self._shape.Position def __getitem__(self, index): return LOBaseObject(self.diagram.getDataRowProperties(index)) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): pass @property def obj(self): return self._obj @property def name(self): return self._name @property def diagram(self): return self._eobj.Diagram @property def type(self): return self._type @type.setter def type(self, value): self._type = value if value == 'Bar': self.diagram.Vertical = True return type_chart = f'com.sun.star.chart.{value}Diagram' self._eobj.setDiagram(self._eobj.createInstance(type_chart)) @property def cell(self): return self._cell @cell.setter def cell(self, value): self._cell = value self._shape.Anchor = value.obj @property def position(self): return self._pos @position.setter def position(self, value): self._pos = value self._shape.Position = value def _get_shape(self, draw_page): for shape in draw_page: if shape.PersistName == self.name: break return shape class LOSheetCharts(object): def __init__(self, obj, sheet): self._obj = obj self._sheet = sheet def __getitem__(self, index): return LOChart(index, self.obj[index], self._sheet.draw_page) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): pass def __contains__(self, item): return item in self.obj def __len__(self): return len(self.obj) @property def obj(self): return self._obj def new(self, name, pos_size, data): self.obj.addNewByName(name, pos_size, data, True, True) return LOChart(name, self.obj[name], self._sheet.draw_page) class LOFormControl(LOBaseObject): def __init__(self, obj): self._obj = obj self._control = self.doc.CurrentController.getControl(self.obj) def __setattr__(self, name, value): if name == '_control': self.__dict__[name] = value else: super().__setattr__(name, value) @property def doc(self): return self.obj.Parent.Parent.Parent @property def name(self): return self.obj.Name @property def label(self): return self.obj.Label def set_focus(self): self._control.setFocus() return class LOForm(object): def __init__(self, obj): self._obj = obj def __getitem__(self, index): return LOFormControl(self.obj[index]) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): pass def __contains__(self, item): return item in self.obj def __len__(self): return len(self.obj) @property def obj(self): return self._obj class LOSheetForms(object): def __init__(self, obj): self._obj = obj def __getitem__(self, index): return LOForm(self.obj[index]) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): pass def __contains__(self, item): return item in self.obj def __len__(self): return len(self.obj) @property def obj(self): return self._obj class 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 @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 draw_page(self): return self.obj.DrawPage @property def doc(self): return self.draw_page.Forms.Parent @property def charts(self): return LOSheetCharts(self.obj.Charts, self) @property def forms(self): return LOSheetForms(self.obj.DrawPage.Forms) def get_cursor(self, cell): return self.obj.createCursorByRange(cell) class LOCalcRange(object): def __init__(self, obj): self._obj = obj 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 @property def obj(self): return self._obj @property def sheet(self): return LOCalcSheet(self.obj.Spreadsheet) @property def doc(self): doc = self.obj.Spreadsheet.DrawPage.Forms.Parent return doc @property def name(self): return self.obj.AbsoluteName @property def columns(self): return self.obj.Columns.Count @property def rows(self): return self.obj.Rows.Count @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): if data[0] == '=': self.obj.setFormula(data) else: self.obj.setString(data) elif isinstance(data, (int, float, bool)): self.obj.setValue(data) elif isinstance(data, datetime.datetime): d = data.toordinal() t = (data - datetime.datetime.fromordinal(d)).seconds / SECONDS_DAY self.obj.setValue(d - DATE_OFFSET + t) elif isinstance(data, datetime.date): d = data.toordinal() self.obj.setValue(d - DATE_OFFSET) elif isinstance(data, datetime.time): d = (data.hour * 3600 + data.minute * 60 + data.second) / SECONDS_DAY self.obj.setValue(d) @property def data(self): return self.obj.getDataArray() @data.setter def data(self, values): self.obj.setDataArray(values) @property def formula(self): return self.obj.getFormulaArray() @formula.setter def formula(self, values): self.obj.setFormulaArray(values) @property def 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 def select(self): self.doc.CurrentController.select(self.obj) return def to_size(self, rows, cols): cursor = self.cursor cursor.collapseToSize(cols, rows) return LOCalcRange(self.sheet[cursor.AbsoluteName].obj) def copy_to(self, cell, formula=False): rango = cell.to_size(self.rows, self.columns) if formula: rango.formula = self.data else: rango.data = self.data return def auto_width(self): self.obj.Columns.OptimalWidth = True return 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 class LOWriter(LODocument): def __init__(self, obj): super().__init__(obj) self._type = WRITER class LODrawImpress(LODocument): def __init__(self, obj): super().__init__(obj) class LODraw(LODrawImpress): def __init__(self, obj): super().__init__(obj) self._type = DRAW class LOImpress(LODrawImpress): def __init__(self, obj): super().__init__(obj) self._type = IMPRESS class BaseDateField(DateField): def db_value(self, value): return _date_to_struct(value) def python_value(self, value): return _struct_to_date(value) class BaseTimeField(TimeField): def db_value(self, value): return _date_to_struct(value) def python_value(self, value): return _struct_to_date(value) class BaseDateTimeField(DateTimeField): def db_value(self, value): return _date_to_struct(value) def python_value(self, value): return _struct_to_date(value) class FirebirdDatabase(Database): field_types = {'BOOL': 'BOOLEAN', 'DATETIME': 'TIMESTAMP'} def __init__(self, database, **kwargs): super().__init__(database, **kwargs) self._db = database def _connect(self): return self._db def create_tables(self, models, **options): options['safe'] = False tables = self._db.tables models = [m for m in models if not m.__name__.lower() in tables] super().create_tables(models, **options) def execute_sql(self, sql, params=None, commit=True): with __exception_wrapper__: cursor = self._db.execute(sql, params) return cursor def last_insert_id(self, cursor, query_type=None): # ~ debug('LAST_ID', cursor) return 0 def rows_affected(self, cursor): return self._db.rows_affected @property def path(self): return self._db.path class BaseRow: pass class BaseQuery(object): PY_TYPES = { 'SQL_LONG': 'getLong', 'SQL_VARYING': 'getString', 'SQL_FLOAT': 'getFloat', 'SQL_BOOLEAN': 'getBoolean', 'SQL_TYPE_DATE': 'getDate', 'SQL_TYPE_TIME': 'getTime', 'SQL_TIMESTAMP': 'getTimestamp', } TYPES_DATE = ('SQL_TYPE_DATE', 'SQL_TYPE_TIME', 'SQL_TIMESTAMP') def __init__(self, query): self._query = query self._meta = query.MetaData self._cols = self._meta.ColumnCount self._names = query.Columns.ElementNames self._data = self._get_data() def __getitem__(self, index): return self._data[index] def __iter__(self): self._index = 0 return self def __next__(self): try: row = self._data[self._index] except IndexError: raise StopIteration self._index += 1 return row def _to_python(self, index): type_field = self._meta.getColumnTypeName(index) value = getattr(self._query, self.PY_TYPES[type_field])(index) if type_field in self.TYPES_DATE: value = _struct_to_date(value) return value def _get_row(self): row = BaseRow() for i in range(1, self._cols + 1): column_name = self._meta.getColumnName(i) value = self._to_python(i) setattr(row, column_name, value) return row def _get_data(self): data = [] while self._query.next(): row = self._get_row() data.append(row) return data @property def tuples(self): data = [tuple(r.__dict__.values()) for r in self._data] return tuple(data) @property def dicts(self): data = [r.__dict__ for r in self._data] return tuple(data) class LOBase(object): DB_TYPES = { str: 'setString', int: 'setInt', float: 'setFloat', bool: 'setBoolean', Date: 'setDate', Time: 'setTime', DateTime: 'setTimestamp', } # ~ setArray # ~ setBinaryStream # ~ setBlob # ~ setByte # ~ setBytes # ~ setCharacterStream # ~ setClob # ~ setNull # ~ setObject # ~ setObjectNull # ~ setObjectWithInfo # ~ setPropertyValue # ~ setRef def __init__(self, obj, args={}): self._obj = obj self._type = BASE self._path = args.get('path', '') self._dbc = create_instance('com.sun.star.sdb.DatabaseContext') self._rows_affected = 0 if self._path: self._name = Path(self._path).name path_url = _path_url(self._path) db = self._dbc.createInstance() db.URL = 'sdbc:embedded:firebird' db.DatabaseDocument.storeAsURL(path_url, ()) self.register() self._obj = db else: self._name = self._obj if Path(self._name).exists(): self._path = self._name self._name = Path(self._path).name if self.is_registered: db = self._dbc.getByName(self.name) self._path = _path_system(self._dbc.getDatabaseLocation(self.name)) self._obj = db else: path_url = _path_url(self._path) self._dbc.registerDatabaseLocation(self.name, path_url) db = self._dbc.getByName(self.name) 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 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: path_url = _path_url(self._path) self._dbc.registerDatabaseLocation(self.name, path_url) return def revoke(self, name): self._dbc.revokeDatabaseLocation(name) return True def save(self): self.obj.DatabaseDocument.store() self.refresh() return def close(self): self._con.close() return def refresh(self): self._con.getTables().refresh() return def initialize(self, database_proxy, tables): db = FirebirdDatabase(self) database_proxy.initialize(db) db.create_tables(tables) return def _validate_sql(self, sql, params): limit = ' LIMIT ' for p in params: sql = sql.replace('?', f"'{p}'", 1) if limit in sql: sql = sql.split(limit)[0] sql = sql.replace('SELECT', f'SELECT FIRST {params[-1]}') return sql def cursor(self, sql, params): if sql.startswith('SELECT'): sql = self._validate_sql(sql, params) cursor = self._con.prepareStatement(sql) return cursor if not params: cursor = self._con.createStatement() return cursor cursor = self._con.prepareStatement(sql) for i, v in enumerate(params, 1): t = type(v) if not t in self.DB_TYPES: error('Type not support') debug((i, t, v, self.DB_TYPES[t])) getattr(cursor, self.DB_TYPES[t])(i, v) return cursor def execute(self, sql, params): debug(sql, params) cursor = self.cursor(sql, params) if sql.startswith('SELECT'): result = cursor.executeQuery() elif params: result = cursor.executeUpdate() self._rows_affected = result self.save() else: result = cursor.execute(sql) self.save() return result def select(self, sql): debug('SELECT', sql) if not sql.startswith('SELECT'): return () cursor = self._con.prepareStatement(sql) query = cursor.executeQuery() return BaseQuery(query) def get_query(self, query): sql, args = query.sql() sql = self._validate_sql(sql, args) return self.select(sql) class LOMath(LODocument): def __init__(self, obj): super().__init__(obj) self._type = MATH class LOBasic(LODocument): def __init__(self, obj): super().__init__(obj) self._type = BASIC class LODocs(object): _desktop = None def __init__(self): self._desktop = get_desktop() LODocs._desktop = self._desktop def __getitem__(self, index): for i, doc in enumerate(self._desktop.Components): if isinstance(index, int) and i == index: return _get_class_doc(doc) elif isinstance(index, str) and doc.Title == index: return _get_class_doc(doc) def __contains__(self, item): doc = self[item] return not doc is None def __iter__(self): self._i = 0 return self def __next__(self): doc = self[self._i] if doc is None: raise StopIteration self._i += 1 return doc def __len__(self): for i, _ in enumerate(self._desktop.Components): pass return i + 1 @property def 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 = _path_url(path) opt = dict_to_property(args) doc = cls._desktop.loadComponentFromURL(path, '_default', 0, opt) if doc is None: return return _get_class_doc(doc) def connect(self, path): return LOBase(path) def _add_listeners(events, control, name=''): listeners = { 'addActionListener': EventsButton, 'addMouseListener': EventsMouse, # ~ 'addItemListener': EventsItem, # ~ 'addFocusListener': EventsFocus, # ~ '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' 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 UnoBaseObject(object): def __init__(self, obj): 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) @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 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 UnoCheck(UnoBaseObject): def __init__(self, obj): super().__init__(obj) @property def type(self): return 'check' @property def value(self): return self.model.State @value.setter def value(self, value): self.model.State = value @property def label(self): return self.model.Label @label.setter def label(self, value): self.model.Label = value @property def tri_state(self): return self.model.TriState @tri_state.setter def tri_state(self, value): self.model.TriState = value UNO_CLASSES = { 'label': UnoLabel, 'link': UnoLabelLink, 'button': UnoButton, 'radio': UnoRadio, 'check': UnoCheck, } class LODialog(object): MODELS = { 'label': 'com.sun.star.awt.UnoControlFixedTextModel', 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', 'button': 'com.sun.star.awt.UnoControlButtonModel', 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', 'check': 'com.sun.star.awt.UnoControlCheckBoxModel', # ~ 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', # ~ 'text': 'com.sun.star.awt.UnoControlEditModel', # ~ 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', # ~ 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', # ~ 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', # ~ 'tree': 'com.sun.star.awt.tree.TreeControlModel', # ~ 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', # ~ 'image': 'com.sun.star.awt.UnoControlImageControlModel', # ~ 'pages': 'com.sun.star.awt.UnoMultiPageModel', } def __init__(self, args): self._obj = self._create(args) self._model = self.obj.Model self._events = None self._modal = True self._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(_path_url(path)) return dlg if 'Location' in args: name = args['Name'] library = args.get('Library', 'Standard') location = args.get('Location', 'application') 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 @property def obj(self): return self._obj @property def model(self): return self._model @property def controls(self): return self._controls @property def visible(self): return self.obj.Visible @visible.setter def visible(self, value): self.obj.Visible = value @property def events(self): return self._events @events.setter def events(self, controllers): self._events = controllers(self) self._connect_listeners() def _connect_listeners(self): for control in self.obj.Controls: _add_listeners(self.events, control, control.Model.Name) return def _special_properties(self, tipo, args): columns = args.pop('Columns', ()) if tipo == 'link' and not 'Label' in args: args['Label'] = args['URL'] elif tipo == 'grid': args['ColumnModel'] = self._set_column_model(columns) elif tipo == 'button': if 'ImageURL' in args: args['ImageURL'] = self._set_image_url(args['ImageURL']) if not 'FocusOnClick' in args: args['FocusOnClick'] = False elif tipo == 'roadmap': if not 'Height' in args: args['Height'] = self.height if 'Title' in args: args['Text'] = args.pop('Title') elif tipo == 'tab': if not 'Width' in args: args['Width'] = self.width if not 'Height' in args: args['Height'] = self.height return args def add_control(self, args): tipo = args.pop('Type').lower() root = args.pop('Root', '') sheets = args.pop('Sheets', ()) 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 == 'tree' and root: control.root = root elif tipo == 'pages' and sheets: control.sheets = sheets control.events = self.events setattr(self, name, control) self._controls[name] = control return control def open(self, modal=True): self._modal = modal if modal: return self.obj.execute() else: self.visible = True return def close(self, value=0): if self._modal: value = self.obj.endDialog(value) else: self.visible = False self.obj.dispose() return value class LOSheets(object): def __init__(self): pass def __getitem__(self, index): return docs.active[index] def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): pass class LOCells(object): def __init__(self): pass def __getitem__(self, index): return docs.active.active[index] class Paths(object): @classmethod def exists(cls, path): return Path(path).exists() def __getattr__(name): if name == 'active': return docs.active if name == 'active_sheet': return docs.active.active if name == 'selection': return docs.active.selection if name in ('rectangle', 'pos_size'): return Rectangle() if name == 'paths': return Paths() if name == 'docs': return LODocs() raise AttributeError(f"module '{__name__}' has no attribute '{name}'") def create_dialog(args): return LODialog(args) def get_fonts(): toolkit = create_instance('com.sun.star.awt.Toolkit') device = toolkit.createScreenCompatibleDevice(0, 0) return device.FontDescriptors # ~ docs = LODocs() sheets = LOSheets() cells = LOCells() # ~ 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