#!/usr/bin/env python3 # == Rapid Develop Macros in LibreOffice == # ~ This file is part of ZAZ. # ~ ZAZ is free software: you can redistribute it and/or modify # ~ it under the terms of the GNU General Public License as published by # ~ the Free Software Foundation, either version 3 of the License, or # ~ (at your option) any later version. # ~ ZAZ is distributed in the hope that it will be useful, # ~ but WITHOUT ANY WARRANTY; without even the implied warranty of # ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # ~ GNU General Public License for more details. # ~ You should have received a copy of the GNU General Public License # ~ along with ZAZ. If not, see . import base64 import ctypes import datetime import errno import getpass import hashlib import json import logging import os import platform import re import shlex import shutil import socket import subprocess import sys import tempfile import threading import time import traceback import zipfile from collections import OrderedDict from collections.abc import MutableMapping from functools import wraps from operator import itemgetter from pathlib import Path, PurePath from pprint import pprint from string import Template from subprocess import PIPE import smtplib from smtplib import SMTPException, SMTPAuthenticationError from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase from email.mime.text import MIMEText from email.utils import formatdate from email import encoders import mailbox import uno import unohelper from com.sun.star.util import Time, Date, DateTime from com.sun.star.beans import PropertyValue from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS from com.sun.star.awt.MessageBoxResults import YES from com.sun.star.awt.PosSize import POSSIZE, SIZE from com.sun.star.awt import Size, Point from com.sun.star.datatransfer import XTransferable, DataFlavor from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA from com.sun.star.text.TextContentAnchorType import AS_CHARACTER from com.sun.star.lang import XEventListener from com.sun.star.awt import XActionListener from com.sun.star.awt import XMouseListener from com.sun.star.util import XModifyListener try: from fernet import Fernet, InvalidToken except ImportError: pass MSG_LANG = { 'es': { 'OK': 'Aceptar', 'Cancel': 'Cancelar', 'Select file': 'Seleccionar archivo', 'Incorrect user or password': 'Nombre de usuario o contraseña inválidos', 'Allow less secure apps in GMail': 'Activa: Permitir aplicaciones menos segura en GMail', } } OS = platform.system() USER = getpass.getuser() PC = platform.node() DESKTOP = os.environ.get('DESKTOP_SESSION', '') INFO_DEBUG = '{}\n\n{}\n\n{}'.format(sys.version, platform.platform(), '\n'.join(sys.path)) IS_WIN = OS == 'Windows' LOG_NAME = 'ZAZ' CLIPBOARD_FORMAT_TEXT = 'text/plain;charset=utf-16' CALC = 'calc' WRITER = 'writer' OBJ_CELL = 'ScCellObj' OBJ_RANGE = 'ScCellRangeObj' OBJ_RANGES = 'ScCellRangesObj' OBJ_TYPE_RANGES = (OBJ_CELL, OBJ_RANGE, OBJ_RANGES) TYPE_DOC = { 'calc': 'com.sun.star.sheet.SpreadsheetDocument', 'writer': 'com.sun.star.text.TextDocument', 'impress': 'com.sun.star.presentation.PresentationDocument', 'draw': 'com.sun.star.drawing.DrawingDocument', # ~ 'base': 'com.sun.star.sdb.OfficeDatabaseDocument', 'base': 'com.sun.star.sdb.DocumentDataSource', 'math': 'com.sun.star.formula.FormulaProperties', 'basic': 'com.sun.star.script.BasicIDE', } NODE_MENUBAR = 'private:resource/menubar/menubar' MENUS_CALC = { 'file': '.uno:PickList', 'edit': '.uno:EditMenu', 'view': '.uno:ViewMenu', 'insert': '.uno:InsertMenu', 'format': '.uno:FormatMenu', 'styles': '.uno:FormatStylesMenu', 'sheet': '.uno:SheetMenu', 'data': '.uno:DataMenu', 'tools': '.uno:ToolsMenu', 'windows': '.uno:WindowList', 'help': '.uno:HelpMenu', } MENUS_WRITER = { 'file': '.uno:PickList', 'edit': '.uno:EditMenu', 'view': '.uno:ViewMenu', 'insert': '.uno:InsertMenu', 'format': '.uno:FormatMenu', 'styles': '.uno:FormatStylesMenu', 'sheet': '.uno:TableMenu', 'data': '.uno:FormatFormMenu', 'tools': '.uno:ToolsMenu', 'windows': '.uno:WindowList', 'help': '.uno:HelpMenu', } MENUS_APP = { 'calc': MENUS_CALC, 'writer': MENUS_WRITER, } EXT = { 'pdf': 'pdf', } FILE_NAME_DEBUG = 'debug.odt' FILE_NAME_CONFIG = 'zaz-{}.json' LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' LOG_DATE = '%d/%m/%Y %H:%M:%S' logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m') logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m') logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m') logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE) log = logging.getLogger(__name__) _start = 0 _stop_thread = {} TIMEOUT = 10 CTX = uno.getComponentContext() SM = CTX.getServiceManager() def create_instance(name, with_context=False): if with_context: instance = SM.createInstanceWithContext(name, CTX) else: instance = SM.createInstance(name) return instance def _get_app_config(node_name, key=''): name = 'com.sun.star.configuration.ConfigurationProvider' service = 'com.sun.star.configuration.ConfigurationAccess' cp = create_instance(name, True) node = PropertyValue(Name='nodepath', Value=node_name) try: ca = cp.createInstanceWithArguments(service, (node,)) if ca and not key: return ca if ca and ca.hasByName(key): return ca.getPropertyValue(key) except Exception as e: error(e) return '' # ~ FILTER_PDF = '/org.openoffice.Office.Common/Filter/PDF/Export/' LANGUAGE = _get_app_config('org.openoffice.Setup/L10N/', 'ooLocale') LANG = LANGUAGE.split('-')[0] NAME = TITLE = _get_app_config('org.openoffice.Setup/Product', 'ooName') VERSION = _get_app_config('org.openoffice.Setup/Product','ooSetupVersion') def mri(obj): m = create_instance('mytools.Mri') if m is None: msg = 'Extension MRI not found' error(msg) return m.inspect(obj) return def catch_exception(f): @wraps(f) def func(*args, **kwargs): try: return f(*args, **kwargs) except Exception as e: name = f.__name__ if IS_WIN: debug(traceback.format_exc()) log.error(name, exc_info=True) return func class LogWin(object): def __init__(self, doc): self.doc = doc def write(self, info): text = self.doc.Text cursor = text.createTextCursor() cursor.gotoEnd(False) text.insertString(cursor, str(info) + '\n\n', 0) return def info(data): log.info(data) return def debug(info): if IS_WIN: doc = get_document(FILE_NAME_DEBUG) if doc is None: return doc = LogWin(doc.obj) doc.write(info) return log.debug(str(info)) return def error(info): log.error(info) return def save_log(path, data): with open(path, 'a') as out: out.write('{} -{}- '.format(str(now())[:19], LOG_NAME)) pprint(data, stream=out) return def run_in_thread(fn): def run(*k, **kw): t = threading.Thread(target=fn, args=k, kwargs=kw) t.start() return t return run def now(): return datetime.datetime.now() def today(): return datetime.date.today() def get_date(year, month, day, hour=-1, minute=-1, second=-1): if hour > -1 or minute > -1 or second > -1: h = hour m = minute s = second if h == -1: h = 0 if m == -1: m = 0 if s == -1: s = 0 d = datetime.datetime(year, month, day, h, m, s) else: d = datetime.date(year, month, day) return d def get_config(key='', default=None, prefix='config'): path_json = FILE_NAME_CONFIG.format(prefix) values = None path = join(get_config_path('UserConfig'), path_json) if not exists_path(path): return default with open(path, 'r', encoding='utf-8') as fh: data = fh.read() values = json.loads(data) if key: return values.get(key, default) return values def set_config(key, value, prefix='config'): path_json = FILE_NAME_CONFIG.format(prefix) path = join(get_config_path('UserConfig'), path_json) values = get_config(default={}, prefix=prefix) values[key] = value with open(path, 'w', encoding='utf-8') as fh: json.dump(values, fh, ensure_ascii=False, sort_keys=True, indent=4) return True def sleep(seconds): time.sleep(seconds) return def _(msg): L = LANGUAGE.split('-')[0] if L == 'en': return msg if not L in MSG_LANG: return msg return MSG_LANG[L][msg] def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infobox'): """ Create message box type_msg: infobox, warningbox, errorbox, querybox, messbox http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMessageBoxFactory.html """ toolkit = create_instance('com.sun.star.awt.Toolkit') parent = toolkit.getDesktopWindow() mb = toolkit.createMessageBox(parent, type_msg, buttons, title, str(message)) return mb.execute() def question(message, title=TITLE): res = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, 'querybox') return res == YES def warning(message, title=TITLE): return msgbox(message, title, type_msg='warningbox') def errorbox(message, title=TITLE): return msgbox(message, title, type_msg='errorbox') def get_desktop(): return create_instance('com.sun.star.frame.Desktop', True) def get_dispatch(): return create_instance('com.sun.star.frame.DispatchHelper') def call_dispatch(url, args=()): frame = get_document().frame dispatch = get_dispatch() dispatch.executeDispatch(frame, url, '', 0, args) return def get_temp_file(): delete = True if IS_WIN: delete = False return tempfile.NamedTemporaryFile(delete=delete) def _path_url(path): if path.startswith('file://'): return path return uno.systemPathToFileUrl(path) def _path_system(path): if path.startswith('file://'): return os.path.abspath(uno.fileUrlToSystemPath(path)) return path def exists_app(name): try: dn = subprocess.DEVNULL subprocess.Popen([name, ''], stdout=dn, stderr=dn).terminate() except OSError as e: if e.errno == errno.ENOENT: return False return True def exists_path(path): return Path(path).exists() def get_type_doc(obj): for k, v in TYPE_DOC.items(): if obj.supportsService(v): return k return '' def dict_to_property(values, uno_any=False): ps = tuple([PropertyValue(Name=n, Value=v) for n, v in values.items()]) if uno_any: ps = uno.Any('[]com.sun.star.beans.PropertyValue', ps) return ps def property_to_dict(values): d = {i.Name: i.Value for i in values} return d def set_properties(model, properties): if 'X' in properties: properties['PositionX'] = properties.pop('X') if 'Y' in properties: properties['PositionY'] = properties.pop('Y') keys = tuple(properties.keys()) values = tuple(properties.values()) model.setPropertyValues(keys, values) return def array_to_dict(values): d = {r[0]: r[1] for r in values} return d # ~ Custom classes class LOObjectBase(object): def __init__(self, obj): self.__dict__['_obj'] = obj def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): return True def __setattr__(self, name, value): print('BASE__setattr__', name) if name == '_obj': super().__setattr__(name, value) else: self.obj.setPropertyValue(name, value) # ~ def _try_for_method(self, name): # ~ a = None # ~ m = 'get{}'.format(name) # ~ if hasattr(self.obj, m): # ~ a = getattr(self.obj, m)() # ~ else: # ~ a = getattr(self.obj, name) # ~ return a def __getattr__(self, name): print('BASE__getattr__', name) if name == 'obj': a = super().__getattr__(name) else: a = self.obj.getPropertyValue(name) # ~ Bug if a is None: msg = 'Error get: {} - {}'.format(self.obj.ImplementationName, name) error(msg) raise Exception(msg) return a @property def obj(self): return self._obj class LODocument(object): def __init__(self, obj): self._obj = obj self._init_values() def _init_values(self): self._type_doc = get_type_doc(self.obj) if self._type_doc == 'base': self._cc = self.obj.DatabaseDocument.getCurrentController() else: self._cc = self.obj.getCurrentController() return @property def obj(self): return self._obj @property def title(self): return self.obj.getTitle() @title.setter def title(self, value): self.obj.setTitle(value) @property def type(self): return self._type_doc @property def frame(self): return self._cc.getFrame() @property def is_saved(self): return self.obj.hasLocation() @property def is_modified(self): return self.obj.isModified() @property def is_read_only(self): return self.obj.isReadOnly() @property def path(self): return _path_system(self.obj.getURL()) @property def statusbar(self): return self._cc.getStatusIndicator() @property def visible(self): w = self._cc.getFrame().getContainerWindow() return w.isVisible() @visible.setter def visible(self, value): w = self._cc.getFrame().getContainerWindow() w.setVisible(value) @property def zoom(self): return self._cc.ZoomValue @zoom.setter def zoom(self, value): self._cc.ZoomValue = value @property def table_auto_formats(self): taf = create_instance('com.sun.star.sheet.TableAutoFormats') return taf.ElementNames def create_instance(self, name): obj = self.obj.createInstance(name) return obj def save(self, path='', **kwargs): # ~ opt = _properties(kwargs) opt = dict_to_property(kwargs) if path: self._obj.storeAsURL(_path_url(path), opt) else: self._obj.store() return True def close(self): self.obj.close(True) return def focus(self): w = self._cc.getFrame().getComponentWindow() w.setFocus() return def paste(self): sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') transferable = sc.getContents() self._cc.insertTransferable(transferable) return self.obj.getCurrentSelection() def to_pdf(self, path, **kwargs): path_pdf = path if path: if is_dir(path): _, _, n, _ = get_info_path(self.path) path_pdf = join(path, '{}.{}'.format(n, EXT['pdf'])) else: path_pdf = replace_ext(self.path, EXT['pdf']) filter_name = '{}_pdf_Export'.format(self.type) filter_data = dict_to_property(kwargs, True) args = { 'FilterName': filter_name, 'FilterData': filter_data, } args = dict_to_property(args) try: self.obj.storeToURL(_path_url(path_pdf), args) except Exception as e: error(e) path_pdf = '' return path_pdf class LOCellStyle(LOObjectBase): def __init__(self, obj): super().__init__(obj) @property def name(self): return self.obj.Name def apply(self, properties): set_properties(self.obj, properties) return class LOCellStyles(object): def __init__(self, obj): self._obj = obj def __len__(self): return len(self.obj) def __getitem__(self, index): return LOCellStyle(self.obj[index]) def __setitem__(self, key, value): self.obj[key] = value def __delitem__(self, key): if not isinstance(key, str): key = key.Name del self.obj[key] def __contains__(self, item): return item in self.obj @property def obj(self): return self._obj @property def names(self): return self.obj.ElementNames def apply(self, style, properties): set_properties(style, properties) return class LOCalc(LODocument): def __init__(self, obj): super().__init__(obj) self._sheets = obj.getSheets() def __getitem__(self, index): if isinstance(index, str): index = [s.Name for s in self._sheets if s.CodeName == index][0] or index return LOCalcSheet(self._sheets[index], self) @property def active(self): return LOCalcSheet(self._cc.getActiveSheet(), self) def activate(self, sheet): obj = sheet if isinstance(sheet, LOCalcSheet): obj = sheet.obj elif isinstance(sheet, str): obj = self[sheet].obj self._cc.setActiveSheet(obj) return @property def selection(self): sel = self.obj.getCurrentSelection() if sel.ImplementationName in OBJ_TYPE_RANGES: sel = LOCellRange(sel, self) return sel @property def sheets(self): return LOCalcSheets(self._sheets, self) @property def names(self): return self.sheets.names @property def cell_style(self): obj = self.obj.getStyleFamilies()['CellStyles'] return LOCellStyles(obj) def insert(self, name, pos): index = pos if pos < 0: index = self._sheets.Count + pos + 1 if isinstance(name, str): self._sheets.insertNewByName(name, index) else: for n in name: self._sheets.insertNewByName(n, index) name = n return LOCalcSheet(self._sheets[name], self) def copy(self, source='', target='', pos=-1): index = pos if pos < 0: index = self._sheets.Count + pos + 1 names = source if not names: names = self.names elif isinstance(source, str): names = (source,) new_names = target if not target: new_names = [n + '_2' for n in names] elif isinstance(target, str): new_names = (target,) for i, ns in enumerate(names): self.sheets.copy(ns, new_names[i], index + i) return LOCalcSheet(self._sheets[index], self) def copy_from(self, doc, source, target, pos): index = pos if pos < 0: index = self._sheets.Count + pos + 1 names = source if not names: names = doc.names elif isinstance(source, str): names = (source,) new_names = target if not target: new_names = names elif isinstance(target, str): new_names = (target,) for i, n in enumerate(names): self._sheets.importSheet(doc.obj, n, index + i) self.sheets[index + i].name = new_names[i] # ~ doc.getCurrentController().setActiveSheet(sheet) # ~ For controls in sheet # ~ doc.getCurrentController().setFormDesignMode(False) return LOCalcSheet(self._sheets[index], self) def sort(self, reverse=False): names = sorted(self.names, reverse=reverse) for i, n in enumerate(names): self.sheets.move(n, i) return def get_cell(self, index=None): """ index is str 'A1' index is tuple (row, col) """ if index is None: cell = self.selection.first else: cell = LOCellRange(self.active[index].obj, self) return cell def select(self, rango): r = rango if hasattr(rango, 'obj'): r = rango.obj elif isinstance(rango, str): r = self.get_cell(rango).obj self._cc.select(r) return def create_cell_style(self, name=''): obj = self.create_instance('com.sun.star.style.CellStyle') if name: self.cell_style[name] = obj return LOCellStyle(obj) class LOCalcSheets(object): def __init__(self, obj, doc): self._obj = obj self._doc = doc def __getitem__(self, index): return LOCalcSheet(self.obj[index], self.doc) @property def obj(self): return self._obj @property def doc(self): return self._doc @property def count(self): return self.obj.Count @property def names(self): return self.obj.ElementNames def copy(self, name, new_name, pos): self.obj.copyByName(name, new_name, pos) return def move(self, name, pos): index = pos if pos < 0: index = self.count + pos + 1 sheet = self.obj[name] self.obj.moveByName(sheet.Name, index) return def remove(self, name): sheet = self.obj[name] self.obj.removeByName(sheet.Name) return class LOCalcSheet(object): def __init__(self, obj, doc): self._obj = obj self._doc = doc self._init_values() def __getitem__(self, index): return LOCellRange(self.obj[index], self.doc) def _init_values(self): self._events = None @property def obj(self): return self._obj @property def doc(self): return self._doc @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 def activate(self): self.doc.activate(self.obj) return @property def events(self): return self._events @events.setter def events(self, controllers): self._events = controllers self._connect_listeners() def _connect_listeners(self): if self.events is None: return listeners = { 'addModifyListener': EventsModify, } for key, value in listeners.items(): getattr(self.obj, key)(listeners[key](self.events)) print('add_listener') return class LOWriter(LODocument): def __init__(self, obj): super().__init__(obj) @property def obj(self): return self._obj @property def string(self): return self._obj.getText().String @property def text(self): return self._obj.getText() @property def cursor(self): return self.text.createTextCursor() @property def selection(self): # ~ sel = self._cc.getSelection() sel = self.obj.getCurrentSelection() return LOTextRange(sel[0]) def insert_content(self, cursor, data, replace=False): self.text.insertTextContent(cursor, data, replace) return # ~ tt = doc.createInstance('com.sun.star.text.TextTable') # ~ tt.initialize(5, 2) # ~ f = doc.createInstance('com.sun.star.text.TextFrame') # ~ f.setSize(Size(10000, 500)) def insert_image(self, path, **kwargs): cursor = kwargs.get('cursor', self.selection.cursor.getEnd()) w = kwargs.get('width', 1000) h = kwargs.get('Height', 1000) image = self.create_instance('com.sun.star.text.GraphicObject') image.GraphicURL = _path_url(path) image.AnchorType = AS_CHARACTER image.Width = w image.Height = h self.insert_content(cursor, image) return class LOTextRange(object): def __init__(self, obj): self._obj = obj @property def obj(self): return self._obj @property def string(self): return self.obj.String @property def text(self): return self.obj.getText() @property def cursor(self): return self.text.createTextCursorByRange(self.obj) class LOBase(LODocument): TYPES = { str: 'setString', int: 'setInt', float: 'setFloat', bool: 'setBoolean', Date: 'setDate', Time: 'setTime', DateTime: 'setTimestamp', } # ~ setArray # ~ setBinaryStream # ~ setBlob # ~ setByte # ~ setBytes # ~ setCharacterStream # ~ setClob # ~ setNull # ~ setObject # ~ setObjectNull # ~ setObjectWithInfo # ~ setPropertyValue # ~ setRef def __init__(self, name, path='', **kwargs): self._name = name self._path = path self._dbc = create_instance('com.sun.star.sdb.DatabaseContext') if path: path_url = _path_url(path) db = self._dbc.createInstance() db.URL = 'sdbc:embedded:firebird' db.DatabaseDocument.storeAsURL(path_url, ()) if not self.exists: self._dbc.registerDatabaseLocation(name, path_url) else: db = self._dbc.getByName(name) self.path = _path_system(self._dbc.getDatabaseLocation(name)) super().__init__(db) self._con = db.getConnection('', '') msg = 'Connected to: {}'.format(name) debug(msg) @property def name(self): return self._name @property def connection(self): return self._con @property def path(self): return self._path @path.setter def path(self, value): self._path = value @property def exists(self): return self._dbc.hasRegisteredDatabase(self.name) @classmethod def register(self, path, name): if not self._dbc.hasRegisteredDatabase(name): self._dbc.registerDatabaseLocation(name, _path_url(path)) return def save(self): # ~ self._db.connection.commit() # ~ self._db.connection.getTables().refresh() # ~ oDisp.executeDispatch(oFrame,".uno:DBRefreshTables", "", 0, Array()) self._obj.DatabaseDocument.store() self.refresh() return def close(self): self._con.close() return def refresh(self): self._con.getTables().refresh() return def get_tables(self): tables = self._con.getTables() tables = [tables.getByIndex(i) for i in range(tables.Count)] return tables def cursor(self, sql, params): cursor = self._con.prepareStatement(sql) for i, v in enumerate(params, 1): if not type(v) in self.TYPES: error('Type not support') debug((i, type(v), v, self.TYPES[type(v)])) getattr(cursor, self.TYPES[type(v)])(i, v) return cursor def execute(self, sql, params): debug(sql) if params: cursor = self.cursor(sql, params) cursor.execute() else: cursor = self._con.createStatement() cursor.execute(sql) # ~ executeQuery # ~ executeUpdate self.save() return cursor class LODrawImpress(LODocument): def __init__(self, obj): super().__init__(obj) @property def draw_page(self): return self._cc.getCurrentPage() def insert_image(self, path, **kwargs): w = kwargs.get('width', 3000) h = kwargs.get('Height', 1000) x = kwargs.get('X', 1000) y = kwargs.get('Y', 1000) image = self.create_instance('com.sun.star.drawing.GraphicObjectShape') image.GraphicURL = _path_url(path) image.Size = Size(w, h) image.Position = Point(x, y) self.draw_page.add(image) return class LOImpress(LODrawImpress): def __init__(self, obj): super().__init__(obj) class LODraw(LODrawImpress): def __init__(self, obj): super().__init__(obj) class LOMath(LODocument): def __init__(self, obj): super().__init__(obj) class LOBasicIde(LODocument): def __init__(self, obj): super().__init__(obj) @property def selection(self): sel = self._cc.getSelection() return sel class LOCellRange(object): def __init__(self, obj, doc): self._obj = obj self._doc = doc self._init_values() def __enter__(self): return self def __exit__(self, *args): pass def __getitem__(self, index): return LOCellRange(self.obj[index], self.doc) def _init_values(self): self._type_obj = self.obj.ImplementationName self._type_content = EMPTY if self._type_obj == OBJ_CELL: self._type_content = self.obj.getType() return @property def obj(self): return self._obj @property def doc(self): return self._doc @property def type(self): return self._type_obj @property def type_content(self): return self._type_content @property def first(self): if self.type == OBJ_RANGES: obj = LOCellRange(self.obj[0][0,0], self.doc) else: obj = LOCellRange(self.obj[0,0], self.doc) return obj @property def value(self): v = None if self._type_content == VALUE: v = self.obj.getValue() elif self._type_content == TEXT: v = self.obj.getString() elif self._type_content == FORMULA: v = self.obj.getFormula() return v @value.setter def value(self, data): if isinstance(data, str): if data.startswith('='): self.obj.setFormula(data) else: self.obj.setString(data) elif isinstance(data, (int, float)): self.obj.setValue(data) @property def data(self): return self.obj.getDataArray() @data.setter def data(self, values): # ~ if not isinstance(data[0], tuple): # ~ data = tuple(zip(data)) if isinstance(values, list): values = tuple(values) self.obj.setDataArray(values) def offset(self, row=1, col=0): ra = self.obj.getRangeAddress() col = ra.EndColumn + col row = ra.EndRow + row return LOCellRange(self.sheet[row, col], self.doc) @property def sheet(self): return self.obj.Spreadsheet @property def draw_page(self): return self.sheet.getDrawPage() @property def name(self): return self.obj.AbsoluteName @property def address(self): if self._type_obj == OBJ_CELL: a = self.obj.getCellAddress() elif self._type_obj == OBJ_RANGE: a = self.obj.getRangeAddress() else: a = self.obj.getRangeAddressesAsString() return a @property def current_region(self): cursor = self.sheet.createCursorByRange(self.obj[0,0]) cursor.collapseToCurrentRegion() return LOCellRange(self.sheet[cursor.AbsoluteName], self.doc) @property def cell_style(self): return self.obj.CellStyle @cell_style.setter def cell_style(self, value): self.obj.CellStyle = value @property def auto_format(self): return self.obj.CellStyle @auto_format.setter def auto_format(self, value): self.obj.autoFormat(value) def insert_image(self, path, **kwargs): s = self.obj.Size w = kwargs.get('width', s.Width) h = kwargs.get('Height', s.Height) img = self.doc.create_instance('com.sun.star.drawing.GraphicObjectShape') img.GraphicURL = _path_url(path) self.draw_page.add(img) img.Anchor = self.obj img.setSize(Size(w, h)) return def select(self): self.doc._cc.select(self.obj) return def in_range(self, rango): if isinstance(rango, LOCellRange): address = rango.address else: address = rango.getRangeAddress() cursor = self.sheet.createCursorByRange(self.obj) result = cursor.queryIntersection(address) return bool(result.Count) def fill(self, source=1): self.obj.fillAuto(0, source) return class EventsListenerBase(unohelper.Base, XEventListener): def __init__(self, controller, window=None): self._controller = controller self._window = window def disposing(self, event): self._controller = None if not self._window is None: self._window.setMenuBar(None) class EventsButton(EventsListenerBase, XActionListener): def __init__(self, controller): super().__init__(controller) def actionPerformed(self, event): name = event.Source.Model.Name event_name = '{}_action'.format(name) if hasattr(self._controller, event_name): getattr(self._controller, event_name)(event) return class EventsMouse(EventsListenerBase, XMouseListener): def __init__(self, controller): super().__init__(controller) def mousePressed(self, event): name = event.Source.Model.Name event_name = '{}_click'.format(name) if event.ClickCount == 2: event_name = '{}_double_click'.format(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 class EventsMouseGrid(EventsMouse): selected = False def mousePressed(self, event): super().mousePressed(event) # ~ obj = event.Source # ~ col = obj.getColumnAtPoint(event.X, event.Y) # ~ row = obj.getRowAtPoint(event.X, event.Y) # ~ print(col, row) # ~ if col == -1 and row == -1: # ~ if self.selected: # ~ obj.deselectAllRows() # ~ else: # ~ obj.selectAllRows() # ~ self.selected = not self.selected return def mouseReleased(self, event): obj = event.Source col = obj.getColumnAtPoint(event.X, event.Y) row = obj.getRowAtPoint(event.X, event.Y) if row == -1 and col > -1: gdm = obj.Model.GridDataModel for i in range(gdm.RowCount): gdm.updateRowHeading(i, i + 1) return class EventsModify(EventsListenerBase, XModifyListener): def __init__(self, controller): super().__init__(controller) def modified(self, event): event_name = '{}_modified'.format(event.Source.Name) if hasattr(self._controller, event_name): getattr(self._controller, event_name)(event) return class UnoBaseObject(object): def __init__(self, obj): self._obj = obj self._model = self.obj.Model self._rules = {} @property def obj(self): return self._obj @property def model(self): return self._model @property def name(self): return self.model.Name @property def parent(self): return self.obj.getContext() @property def x(self): return self.model.PositionX @x.setter def x(self, value): self.model.PositionX = value @property def y(self): return self.model.PositionY @y.setter def y(self, value): self.model.PositionY = 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 @property def tag(self): return self.model.Tag @tag.setter def tag(self, value): self.model.Tag = value @property def step(self): return self.model.Step @step.setter def step(self, value): self.model.Step = value @property def rules(self): return self._rules @rules.setter def rules(self, value): self._rules = value def set_focus(self): self.obj.setFocus() return def center(self, horizontal=True, vertical=False): p = self.parent.Model w = p.Width h = p.Height if horizontal: x = w / 2 - self.width / 2 self.x = x if vertical: y = h / 2 - self.height / 2 self.y = y return def move(self, origin, x=0, y=5): w = 0 h = 0 if x: w = origin.width if y: h = origin.height x = origin.x + x + w y = origin.y + y + h self.x = x self.y = y 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 UnoButton(UnoBaseObject): def __init__(self, obj): super().__init__(obj) @property def type(self): return 'button' @property def value(self): return self.model.Label @value.setter def value(self, value): self.model.Label = value class UnoText(UnoBaseObject): def __init__(self, obj): super().__init__(obj) @property def type(self): return 'text' @property def value(self): return self.model.Text @value.setter def value(self, value): self.model.Text = value def validate(self): return class UnoListBox(UnoBaseObject): def __init__(self, obj): super().__init__(obj) self._data = [] @property def type(self): return 'listbox' @property def value(self): return self.obj.SelectedItem @property def data(self): return self._data @data.setter def data(self, values): self._data = list(sorted(values)) self.model.StringItemList = self.data return class UnoGrid(UnoBaseObject): def __init__(self, obj): super().__init__(obj) self._gdm = self._model.GridDataModel # ~ self._data = [] self._columns = {} # ~ self._format_columns = () def __getitem__(self, index): value = self._gdm.getCellData(index[0], index[1]) return value @property def type(self): return 'grid' def _format_cols(self): rows = tuple(tuple( self._format_columns[i].format(r) for i, r in enumerate(row)) for row in self._data ) return rows # ~ @property # ~ def format_columns(self): # ~ return self._format_columns # ~ @format_columns.setter # ~ def format_columns(self, value): # ~ self._format_columns = value @property def data(self): return self._data @data.setter def data(self, values): # ~ self._data = values self._gdm.removeAllRows() headings = tuple(range(1, len(values) + 1)) self._gdm.addRows(headings, values) # ~ rows = range(grid_dm.RowCount) # ~ colors = [COLORS['GRAY'] if r % 2 else COLORS['WHITE'] for r in rows] # ~ grid.Model.RowBackgroundColors = tuple(colors) return @property def row(self): return self.obj.CurrentRow @property def rows(self): return self._gdm.RowCount @property def column(self): return self.obj.CurrentColumn @property def columns(self): return self._gdm.ColumnCount def set_cell_tooltip(self, col, row, value): self._gdm.updateCellToolTip(col, row, value) return def get_cell_tooltip(self, col, row): value = self._gdm.getCellToolTip(col, row) return value def _validate_column(self, data): row = [] for i, d in enumerate(data): if i in self._columns: if 'image' in self._columns[i]: row.append(self._columns[i]['image']) else: row.append(d) return tuple(row) def add_row(self, data): # ~ self._data.append(data) data = self._validate_column(data) self._gdm.addRow(self.rows + 1, data) return def remove_row(self, row): self._gdm.removeRow(row) # ~ del self._data[row] self._update_row_heading() return def _update_row_heading(self): for i in range(self.rows): self._gdm.updateRowHeading(i, i + 1) return def sort(self, column, asc=True): self._gdm.sortByColumn(column, asc) # ~ self._data.sort(key=itemgetter(column), reverse=not asc) self._update_row_heading() return def set_column_image(self, column, path): gp = create_instance('com.sun.star.graphic.GraphicProvider') data = dict_to_property({'URL': _path_url(path)}) image = gp.queryGraphic(data) if not column in self._columns: self._columns[column] = {} self._columns[column]['image'] = image return class LODialog(object): def __init__(self, properties): self._obj = self._create(properties) self._init_values() def _init_values(self): self._model = self._obj.Model self._init_controls() self._events = None # ~ self._response = None return def _create(self, properties): path = properties.pop('Path', '') if path: dp = create_instance('com.sun.star.awt.DialogProvider2', True) return dp.createDialog(_path_url(path)) if 'Library' in properties: location = properties['Location'] if location == 'user': location = 'application' dp = create_instance('com.sun.star.awt.DialogProvider2', True) path = 'vnd.sun.star.script:{}.{}?location={}'.format( properties['Library'], properties['Name'], location) return dp.createDialog(path) dlg = create_instance('com.sun.star.awt.UnoControlDialog', True) model = create_instance('com.sun.star.awt.UnoControlDialogModel', True) toolkit = create_instance('com.sun.star.awt.Toolkit', True) set_properties(model, properties) dlg.setModel(model) dlg.setVisible(False) dlg.createPeer(toolkit, None) return dlg def _init_controls(self): return @property def obj(self): return self._obj @property def model(self): return self._model @property def events(self): return self._events @events.setter def events(self, controllers): self._events = controllers self._connect_listeners() def _connect_listeners(self): return def _add_listeners(self, control): if self.events is None: return listeners = { 'addActionListener': EventsButton, 'addMouseListener': EventsMouse, } for key, value in listeners.items(): if hasattr(control.obj, key): if control.type == 'grid' and key == 'addMouseListener': control.obj.addMouseListener(EventsMouseGrid(self.events)) continue getattr(control.obj, key)(listeners[key](self.events)) return def open(self): return self.obj.execute() def close(self, value=0): return self.obj.endDialog(value) def _get_control_model(self, control): services = { 'label': 'com.sun.star.awt.UnoControlFixedTextModel', 'button': 'com.sun.star.awt.UnoControlButtonModel', 'text': 'com.sun.star.awt.UnoControlEditModel', 'listbox': 'com.sun.star.awt.UnoControlListBoxModel', 'link': 'com.sun.star.awt.UnoControlFixedHyperlinkModel', 'roadmap': 'com.sun.star.awt.UnoControlRoadmapModel', 'image': 'com.sun.star.awt.UnoControlImageControlModel', 'groupbox': 'com.sun.star.awt.UnoControlGroupBoxModel', 'radio': 'com.sun.star.awt.UnoControlRadioButtonModel', 'tree': 'com.sun.star.awt.tree.TreeControlModel', 'grid': 'com.sun.star.awt.grid.UnoControlGridModel', } return services[control] def _get_custom_class(self, tipo, obj): classes = { 'label': UnoLabel, 'button': UnoButton, 'text': UnoText, 'listbox': UnoListBox, 'grid': UnoGrid, # ~ 'link': UnoLink, # ~ 'tab': UnoTab, # ~ 'roadmap': UnoRoadmap, # ~ 'image': UnoImage, # ~ 'radio': UnoRadio, # ~ 'groupbox': UnoGroupBox, # ~ 'tree': UnoTree, } return classes[tipo](obj) def _set_column_model(self, columns): #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html column_model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) # ~ column_model.setDefaultColumns(len(columns)) for column in columns: grid_column = create_instance('com.sun.star.awt.grid.GridColumn', True) for k, v in column.items(): setattr(grid_column, k, v) column_model.addColumn(grid_column) # ~ mri(grid_column) return column_model def _set_image_url(self, path): if exists_path(path): return _path_url(path) return '' def add_control(self, properties): tipo = properties.pop('Type').lower() columns = properties.pop('Columns', ()) if tipo == 'grid': properties['ColumnModel'] = self._set_column_model(columns) if tipo == 'button' and 'ImageURL' in properties: properties['ImageURL'] = self._set_image_url(properties['ImageURL']) model = self.model.createInstance(self._get_control_model(tipo)) set_properties(model, properties) name = properties['Name'] self.model.insertByName(name, model) control = self._get_custom_class(tipo, self.obj.getControl(name)) self._add_listeners(control) setattr(self, name, control) return # ~ Python >= 3.7 # ~ def __getattr__(name): def _get_class_doc(obj): classes = { 'calc': LOCalc, 'writer': LOWriter, 'base': LOBase, 'impress': LOImpress, 'draw': LODraw, 'math': LOMath, 'basic': LOBasicIde, } type_doc = get_type_doc(obj) return classes[type_doc](obj) # ~ Export ok def get_document(title=''): doc = None desktop = get_desktop() if not title: doc = _get_class_doc(desktop.getCurrentComponent()) return doc for d in desktop.getComponents(): if d.Title == title: doc = d break if doc is None: return return _get_class_doc(doc) def get_documents(custom=True): docs = [] desktop = get_desktop() for doc in desktop.getComponents(): if custom: docs.append(_get_class_doc(doc)) else: docs.append(doc) return docs def get_selection(): return get_document().selection def get_cell(*args): if args: index = args if len(index) == 1: index = args[0] cell = get_document().get_cell(index) else: cell = get_selection().first return cell # ~ def get_range(self, obj, address): # ~ if not obj.ImplementationName == 'ScTableSheetObj': # ~ obj = obj.getCurrentController().getActiveSheet() # ~ if isinstance(address, tuple): # ~ if len(address) == 1: # ~ address = (slice(0, None), address[0]) # ~ elif len(address) == 4: # ~ address = ( # ~ slice(address[0], address[1]), # ~ slice(address[2], address[3]) # ~ ) # ~ return obj[address] def active_cell(): return get_cell() def create_dialog(properties): return LODialog(properties) # ~ Export ok def get_config_path(name='Work'): """ Return de path name in config http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1util_1_1XPathSettings.html """ path = create_instance('com.sun.star.util.PathSettings') return _path_system(getattr(path, name)) # ~ Export ok def get_file(init_dir='', multiple=False, filters=()): """ init_folder: folder default open multiple: True for multiple selected filters: Example ( ('XML', '*.xml'), ('TXT', '*.txt'), ) """ if not init_dir: init_dir = get_config_path() init_dir = _path_url(init_dir) file_picker = create_instance('com.sun.star.ui.dialogs.FilePicker') file_picker.setTitle(_('Select file')) file_picker.setDisplayDirectory(init_dir) file_picker.setMultiSelectionMode(multiple) path = '' if filters: file_picker.setCurrentFilter(filters[0][0]) for f in filters: file_picker.appendFilter(f[0], f[1]) if file_picker.execute(): path = _path_system(file_picker.getSelectedFiles()[0]) if multiple: path = [_path_system(f) for f in file_picker.getSelectedFiles()] return path # ~ Export ok def get_path(init_dir='', filters=()): """ Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html filters: Example ( ('XML', '*.xml'), ('TXT', '*.txt'), ) """ if not init_dir: init_dir = get_config_path() init_dir = _path_url(init_dir) file_picker = create_instance('com.sun.star.ui.dialogs.FilePicker') file_picker.setTitle(_('Select file')) file_picker.setDisplayDirectory(init_dir) file_picker.initialize((2,)) if filters: file_picker.setCurrentFilter(filters[0][0]) for f in filters: file_picker.appendFilter(f[0], f[1]) path = '' if file_picker.execute(): path = _path_system(file_picker.getSelectedFiles()[0]) return path # ~ Export ok def get_dir(init_dir=''): folder_picker = create_instance('com.sun.star.ui.dialogs.FolderPicker') if not init_dir: init_dir = get_config_path() init_dir = _path_url(init_dir) folder_picker.setDisplayDirectory(init_dir) path = '' if folder_picker.execute(): path = _path_system(folder_picker.getDirectory()) return path # ~ Export ok def get_info_path(path): path, filename = os.path.split(path) name, extension = os.path.splitext(filename) return (path, filename, name, extension) # ~ Export ok def read_file(path, mode='r', array=False): data = '' with open(path, mode) as f: if array: data = tuple(f.read().splitlines()) else: data = f.read() return data # ~ Export ok def save_file(path, mode='w', data=None): with open(path, mode) as f: f.write(data) return # ~ Export ok def to_json(path, data): with open(path, 'w') as f: f.write(json.dumps(data, indent=4, sort_keys=True)) return # ~ Export ok def from_json(path): with open(path) as f: data = json.loads(f.read()) return data # ~ Export ok def json_dumps(data): return json.dumps(data, indent=4, sort_keys=True) # ~ Export ok def json_loads(data): return json.loads(data) def get_path_extension(id): pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') path = _path_system(pip.getPackageLocation(id)) return path # ~ Export ok def inputbox(message, default='', title=TITLE, echochar=''): class ControllersInput(object): def __init__(self, dlg): self.d = dlg def cmd_ok_action(self, event): self.d.close(1) return args = { 'Title': title, 'Width': 200, 'Height': 80, } dlg = LODialog(args) dlg.events = ControllersInput(dlg) args = { 'Type': 'Label', 'Name': 'lbl_msg', 'Label': message, 'Width': 140, 'Height': 50, 'X': 5, 'Y': 5, 'MultiLine': True, 'Border': 1, } dlg.add_control(args) args = { 'Type': 'Text', 'Name': 'txt_value', 'Text': default, 'Width': 190, 'Height': 15, } if echochar: args['EchoChar'] = ord(echochar[0]) dlg.add_control(args) dlg.txt_value.move(dlg.lbl_msg) args = { 'Type': 'button', 'Name': 'cmd_ok', 'Label': _('OK'), 'Width': 40, 'Height': 15, 'DefaultButton': True, 'PushButtonType': 1, } dlg.add_control(args) dlg.cmd_ok.move(dlg.lbl_msg, 10, 0) args = { 'Type': 'button', 'Name': 'cmd_cancel', 'Label': _('Cancel'), 'Width': 40, 'Height': 15, 'PushButtonType': 2, } dlg.add_control(args) dlg.cmd_cancel.move(dlg.cmd_ok) if dlg.open(): return dlg.txt_value.value return '' # ~ Export ok def new_doc(type_doc=CALC, **kwargs): path = 'private:factory/s{}'.format(type_doc) opt = dict_to_property(kwargs) doc = get_desktop().loadComponentFromURL(path, '_default', 0, opt) return _get_class_doc(doc) # ~ Export ok def new_db(path, name=''): p, fn, n, e = get_info_path(path) if not name: name = n return LOBase(name, path) # ~ Todo def exists_db(name): dbc = create_instance('com.sun.star.sdb.DatabaseContext') return dbc.hasRegisteredDatabase(name) # ~ Todo def register_db(name, path): dbc = create_instance('com.sun.star.sdb.DatabaseContext') dbc.registerDatabaseLocation(name, _path_url(path)) return # ~ Todo def get_db(name): return LOBase(name) # ~ Export ok def open_doc(path, **kwargs): """ Open document in path Usually options: Hidden: True or False AsTemplate: True or False ReadOnly: True or False Password: super_secret MacroExecutionMode: 4 = Activate macros Preview: True or False http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1frame_1_1XComponentLoader.html http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1document_1_1MediaDescriptor.html """ path = _path_url(path) opt = dict_to_property(kwargs) doc = get_desktop().loadComponentFromURL(path, '_blank', 0, opt) if doc is None: return return _get_class_doc(doc) # ~ Export ok def open_file(path): if IS_WIN: os.startfile(path) else: subprocess.Popen(['xdg-open', path]) return # ~ Export ok def join(*paths): return os.path.join(*paths) # ~ Export ok def is_dir(path): return Path(path).is_dir() # ~ Export ok def is_file(path): return Path(path).is_file() # ~ Export ok def get_file_size(path): return Path(path).stat().st_size # ~ Export ok def is_created(path): return is_file(path) and bool(get_file_size(path)) # ~ Export ok def replace_ext(path, extension): path, _, name, _ = get_info_path(path) return '{}/{}.{}'.format(path, name, extension) # ~ Export ok def zip_content(path): with zipfile.ZipFile(path) as z: names = z.namelist() return names def run(command, wait=False): # ~ debug(command) # ~ debug(shlex.split(command)) try: if wait: # ~ p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE) # ~ p.wait() result = subprocess.check_output(command, shell=True) else: p = subprocess.Popen(shlex.split(command), stdin=None, stdout=None, stderr=None, close_fds=True) result, er = p.communicate() except subprocess.CalledProcessError as e: msg = ("run [ERROR]: output = %s, error code = %s\n" % (e.output, e.returncode)) error(msg) return False if result is None: return True return result.decode() def _zippwd(source, target, pwd): if IS_WIN: return False if not exists_app('zip'): return False cmd = 'zip' opt = '-j ' args = "{} --password {} ".format(cmd, pwd) if isinstance(source, (tuple, list)): if not target: return False args += opt + target + ' ' + ' '.join(source) else: if is_file(source) and not target: target = replace_ext(source, 'zip') elif is_dir(source) and not target: target = join(PurePath(source).parent, '{}.zip'.format(PurePath(source).name)) opt = '-r ' args += opt + target + ' ' + source result = run(args, True) if not result: return False return is_created(target) # ~ Export ok def zip(source, target='', mode='w', pwd=''): if pwd: return _zippwd(source, target, pwd) if isinstance(source, (tuple, list)): if not target: return False with zipfile.ZipFile(target, mode, compression=zipfile.ZIP_DEFLATED) as z: for path in source: _, name, _, _ = get_info_path(path) z.write(path, name) return is_created(target) if is_file(source): if not target: target = replace_ext(source, 'zip') z = zipfile.ZipFile(target, mode, compression=zipfile.ZIP_DEFLATED) _, name, _, _ = get_info_path(source) z.write(source, name) z.close() return is_created(target) if not target: target = join( PurePath(source).parent, '{}.zip'.format(PurePath(source).name)) z = zipfile.ZipFile(target, mode, compression=zipfile.ZIP_DEFLATED) root_len = len(os.path.abspath(source)) for root, dirs, files in os.walk(source): relative = os.path.abspath(root)[root_len:] for f in files: fullpath = join(root, f) file_name = join(relative, f) z.write(fullpath, file_name) z.close() return is_created(target) # ~ Export ok def unzip(source, path='', members=None, pwd=None): if not path: path, _, _, _ = get_info_path(source) with zipfile.ZipFile(source) as z: if not pwd is None: pwd = pwd.encode() if isinstance(members, str): members = (members,) z.extractall(path, members=members, pwd=pwd) return True # ~ Export ok def merge_zip(target, zips): try: with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as t: for path in zips: with zipfile.ZipFile(path, compression=zipfile.ZIP_DEFLATED) as s: for name in s.namelist(): t.writestr(name, s.open(name).read()) except Exception as e: error(e) return False return True # ~ Export ok def kill(path): p = Path(path) try: if p.is_file(): p.unlink() elif p.is_dir(): shutil.rmtree(path) except OSError as e: log.error(e) return def get_size_screen(): if IS_WIN: user32 = ctypes.windll.user32 res = '{}x{}'.format(user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)) else: args = 'xrandr | grep "*" | cut -d " " -f4' res = run(args, True) return res.strip() # ~ Export ok def get_clipboard(): df = None text = '' sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') transferable = sc.getContents() data = transferable.getTransferDataFlavors() for df in data: if df.MimeType == CLIPBOARD_FORMAT_TEXT: break if df: text = transferable.getTransferData(df) return text class TextTransferable(unohelper.Base, XTransferable): """Keep clipboard data and provide them.""" def __init__(self, text): df = DataFlavor() df.MimeType = CLIPBOARD_FORMAT_TEXT df.HumanPresentableName = "encoded text utf-16" self.flavors = [df] self.data = [text] def getTransferData(self, flavor): if not flavor: return for i, f in enumerate(self.flavors): if flavor.MimeType == f.MimeType: return self.data[i] return def getTransferDataFlavors(self): return tuple(self.flavors) def isDataFlavorSupported(self, flavor): if not flavor: return False mtype = flavor.MimeType for f in self.flavors: if mtype == f.MimeType: return True return False # ~ Export ok def set_clipboard(value): ts = TextTransferable(value) sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') sc.setContents(ts, None) return # ~ Export ok def copy(): call_dispatch('.uno:Copy') return # ~ Export ok def get_epoch(): n = now() return int(time.mktime(n.timetuple())) # ~ Export ok def file_copy(source, target='', name=''): p, f, n, e = get_info_path(source) if target: p = target if name: e = '' n = name path_new = join(p, '{}{}'.format(n, e)) shutil.copy(source, path_new) return path_new def get_path_content(path, filters=''): paths = [] if filters in ('*', '*.*'): filters = '' for folder, _, files in os.walk(path): if filters: pattern = re.compile(r'\.(?:{})$'.format(filters), re.IGNORECASE) paths += [join(folder, f) for f in files if pattern.search(f)] else: paths += files return paths def _get_menu(type_doc, name_menu): instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' service = TYPE_DOC[type_doc] manager = create_instance(instance, True) ui = manager.getUIConfigurationManager(service) menus = ui.getSettings(NODE_MENUBAR, True) command = MENUS_APP[type_doc][name_menu] for menu in menus: data = property_to_dict(menu) if data.get('CommandURL', '') == command: idc = data.get('ItemDescriptorContainer', None) return ui, menus, idc return None, None, None def _get_index_menu(menu, command): for i, m in enumerate(menu): data = property_to_dict(m) cmd = data.get('CommandURL', '') if cmd == command: return i # ~ submenu = data.get('ItemDescriptorContainer', None) # ~ if not submenu is None: # ~ get_index_menu(submenu, command, count + 1) return 0 def _store_menu(ui, menus, menu, index, data=(), remove=False): if remove: uno.invoke(menu, 'removeByIndex', (index,)) else: properties = dict_to_property(data, True) uno.invoke(menu, 'insertByIndex', (index + 1, properties)) ui.replaceSettings(NODE_MENUBAR, menus) ui.store() return def insert_menu(type_doc, name_menu, **kwargs): ui, menus, menu = _get_menu(type_doc, name_menu.lower()) if menu is None: return 0 label = kwargs.get('Label', '-') separator = False if label == '-': separator = True command = kwargs.get('CommandURL', '') index = kwargs.get('Index', 0) if not index: index = _get_index_menu(menu, kwargs['After']) if separator: data = {'Type': 1} _store_menu(ui, menus, menu, index, data) return index + 1 index_menu = _get_index_menu(menu, command) if index_menu: msg = 'Exists: %s' % command debug(msg) return 0 sub_menu = kwargs.get('Submenu', ()) idc = None if sub_menu: idc = ui.createSettings() data = { 'CommandURL': command, 'Label': label, 'Style': 0, 'Type': 0, 'ItemDescriptorContainer': idc } _store_menu(ui, menus, menu, index, data) if sub_menu: _add_sub_menus(ui, menus, idc, sub_menu) return True def _add_sub_menus(ui, menus, menu, sub_menu): for i, sm in enumerate(sub_menu): submenu = sm.pop('Submenu', ()) sm['Type'] = 0 if submenu: idc = ui.createSettings() sm['ItemDescriptorContainer'] = idc if sm['Label'] == '-': sm = {'Type': 1} _store_menu(ui, menus, menu, i - 1, sm) if submenu: _add_sub_menus(ui, menus, idc, submenu) return def remove_menu(type_doc, name_menu, command): ui, menus, menu = _get_menu(type_doc, name_menu.lower()) if menu is None: return False index = _get_index_menu(menu, command) if not index: debug('Not exists: %s' % command) return False _store_menu(ui, menus, menu, index, remove=True) return True def _get_app_submenus(menus, count=0): for i, menu in enumerate(menus): data = property_to_dict(menu) cmd = data.get('CommandURL', '') msg = ' ' * count + '├─' + cmd debug(msg) submenu = data.get('ItemDescriptorContainer', None) if not submenu is None: _get_app_submenus(submenu, count + 1) return def get_app_menus(name_app, index=-1): instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier' service = TYPE_DOC[name_app] manager = create_instance(instance, True) ui = manager.getUIConfigurationManager(service) menus = ui.getSettings(NODE_MENUBAR, True) if index == -1: for menu in menus: data = property_to_dict(menu) debug(data.get('CommandURL', '')) else: menus = property_to_dict(menus[index])['ItemDescriptorContainer'] _get_app_submenus(menus) return menus # ~ Export ok def start(): global _start _start = now() log.info(_start) return # ~ Export ok def end(): global _start e = now() return str(e - _start).split('.')[0] # ~ Export ok # ~ https://en.wikipedia.org/wiki/Web_colors def get_color(*value): COLORS = { 'aliceblue': 15792383, 'antiquewhite': 16444375, 'aqua': 65535, 'aquamarine': 8388564, 'azure': 15794175, 'beige': 16119260, 'bisque': 16770244, 'black': 0, 'blanchedalmond': 16772045, 'blue': 255, 'blueviolet': 9055202, 'brown': 10824234, 'burlywood': 14596231, 'cadetblue': 6266528, 'chartreuse': 8388352, 'chocolate': 13789470, 'coral': 16744272, 'cornflowerblue': 6591981, 'cornsilk': 16775388, 'crimson': 14423100, 'cyan': 65535, 'darkblue': 139, 'darkcyan': 35723, 'darkgoldenrod': 12092939, 'darkgray': 11119017, 'darkgreen': 25600, 'darkgrey': 11119017, 'darkkhaki': 12433259, 'darkmagenta': 9109643, 'darkolivegreen': 5597999, 'darkorange': 16747520, 'darkorchid': 10040012, 'darkred': 9109504, 'darksalmon': 15308410, 'darkseagreen': 9419919, 'darkslateblue': 4734347, 'darkslategray': 3100495, 'darkslategrey': 3100495, 'darkturquoise': 52945, 'darkviolet': 9699539, 'deeppink': 16716947, 'deepskyblue': 49151, 'dimgray': 6908265, 'dimgrey': 6908265, 'dodgerblue': 2003199, 'firebrick': 11674146, 'floralwhite': 16775920, 'forestgreen': 2263842, 'fuchsia': 16711935, 'gainsboro': 14474460, 'ghostwhite': 16316671, 'gold': 16766720, 'goldenrod': 14329120, 'gray': 8421504, 'grey': 8421504, 'green': 32768, 'greenyellow': 11403055, 'honeydew': 15794160, 'hotpink': 16738740, 'indianred': 13458524, 'indigo': 4915330, 'ivory': 16777200, 'khaki': 15787660, 'lavender': 15132410, 'lavenderblush': 16773365, 'lawngreen': 8190976, 'lemonchiffon': 16775885, 'lightblue': 11393254, 'lightcoral': 15761536, 'lightcyan': 14745599, 'lightgoldenrodyellow': 16448210, 'lightgray': 13882323, 'lightgreen': 9498256, 'lightgrey': 13882323, 'lightpink': 16758465, 'lightsalmon': 16752762, 'lightseagreen': 2142890, 'lightskyblue': 8900346, 'lightslategray': 7833753, 'lightslategrey': 7833753, 'lightsteelblue': 11584734, 'lightyellow': 16777184, 'lime': 65280, 'limegreen': 3329330, 'linen': 16445670, 'magenta': 16711935, 'maroon': 8388608, 'mediumaquamarine': 6737322, 'mediumblue': 205, 'mediumorchid': 12211667, 'mediumpurple': 9662683, 'mediumseagreen': 3978097, 'mediumslateblue': 8087790, 'mediumspringgreen': 64154, 'mediumturquoise': 4772300, 'mediumvioletred': 13047173, 'midnightblue': 1644912, 'mintcream': 16121850, 'mistyrose': 16770273, 'moccasin': 16770229, 'navajowhite': 16768685, 'navy': 128, 'oldlace': 16643558, 'olive': 8421376, 'olivedrab': 7048739, 'orange': 16753920, 'orangered': 16729344, 'orchid': 14315734, 'palegoldenrod': 15657130, 'palegreen': 10025880, 'paleturquoise': 11529966, 'palevioletred': 14381203, 'papayawhip': 16773077, 'peachpuff': 16767673, 'peru': 13468991, 'pink': 16761035, 'plum': 14524637, 'powderblue': 11591910, 'purple': 8388736, 'red': 16711680, 'rosybrown': 12357519, 'royalblue': 4286945, 'saddlebrown': 9127187, 'salmon': 16416882, 'sandybrown': 16032864, 'seagreen': 3050327, 'seashell': 16774638, 'sienna': 10506797, 'silver': 12632256, 'skyblue': 8900331, 'slateblue': 6970061, 'slategray': 7372944, 'slategrey': 7372944, 'snow': 16775930, 'springgreen': 65407, 'steelblue': 4620980, 'tan': 13808780, 'teal': 32896, 'thistle': 14204888, 'tomato': 16737095, 'turquoise': 4251856, 'violet': 15631086, 'wheat': 16113331, 'white': 16777215, 'whitesmoke': 16119285, 'yellow': 16776960, 'yellowgreen': 10145074, } if len(value) == 3: color = (value[0] << 16) + (value[1] << 8) + value[2] else: value = value[0] if value[0] == '#': r, g, b = bytes.fromhex(value[1:]) color = (r << 16) + (g << 8) + b else: color = COLORS.get(value.lower(), -1) return color # ~ Export ok def render(template, data): s = Template(template) return s.safe_substitute(**data) def _to_date(value): new_value = value if isinstance(value, Time): new_value = datetime.time(value.Hours, value.Minutes, value.Seconds) elif isinstance(value, Date): new_value = datetime.date(value.Year, value.Month, value.Day) elif isinstance(value, DateTime): new_value = datetime.datetime( value.Year, value.Month, value.Day, value.Hours, value.Minutes, value.Seconds) return new_value def date_to_struct(value): # ~ print(type(value)) if isinstance(value, datetime.datetime): d = DateTime() d.Seconds = value.second d.Minutes = value.minute d.Hours = value.hour d.Day = value.day d.Month = value.month d.Year = value.year elif isinstance(value, datetime.date): d = Date() d.Day = value.day d.Month = value.month d.Year = value.year return d # ~ Export ok def format(template, data): """ https://pyformat.info/ """ if isinstance(data, (str, int, float)): # ~ print(template.format(data)) return template.format(data) if isinstance(data, (Time, Date, DateTime)): return template.format(_to_date(data)) if isinstance(data, tuple) and isinstance(data[0], tuple): data = {r[0]: _to_date(r[1]) for r in data} return template.format(**data) data = [_to_date(v) for v in data] result = template.format(*data) return result def _call_macro(macro): #~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification name = 'com.sun.star.script.provider.MasterScriptProviderFactory' factory = create_instance(name, False) macro['language'] = macro.get('language', 'Python') macro['location'] = macro.get('location', 'user') data = macro.copy() if data['language'] == 'Python': data['module'] = '.py$' elif data['language'] == 'Basic': data['module'] = '.{}.'.format(macro['module']) if macro['location'] == 'user': data['location'] = 'application' else: data['module'] = '.' args = macro.get('args', ()) url = 'vnd.sun.star.script:{library}{module}{name}?language={language}&location={location}' path = url.format(**data) script = factory.createScriptProvider('').getScript(path) return script.invoke(args, None, None)[0] # ~ Export ok def call_macro(macro): in_thread = macro.pop('thread') if in_thread: t = threading.Thread(target=_call_macro, args=(macro,)) t.start() return return _call_macro(macro) class TimerThread(threading.Thread): def __init__(self, event, seconds, macro): threading.Thread.__init__(self) self.stopped = event self.seconds = seconds self.macro = macro def run(self): info('Timer started... {}'.format(self.macro['name'])) while not self.stopped.wait(self.seconds): _call_macro(self.macro) info('Timer stopped... {}'.format(self.macro['name'])) return # ~ Export ok def timer(name, seconds, macro): global _stop_thread _stop_thread[name] = threading.Event() thread = TimerThread(_stop_thread[name], seconds, macro) thread.start() return # ~ Export ok def stop_timer(name): global _stop_thread _stop_thread[name].set() del _stop_thread[name] return def _get_key(password): digest = hashlib.sha256(password.encode()).digest() key = base64.urlsafe_b64encode(digest) return key # ~ Export ok def encrypt(data, password): f = Fernet(_get_key(password)) token = f.encrypt(data).decode() return token # ~ Export ok def decrypt(token, password): data = '' f = Fernet(_get_key(password)) try: data = f.decrypt(token.encode()).decode() except InvalidToken as e: error('Invalid Token') return data class SmtpServer(object): def __init__(self, config): self._server = None self._error = '' self._sender = '' self._is_connect = self._login(config) def __enter__(self): return self def __exit__(self, *args): self.close() @property def is_connect(self): return self._is_connect @property def error(self): return self._error def _login(self, config): name = config['server'] port = config['port'] is_ssl = config['ssl'] self._sender = config['user'] hosts = ('gmail' in name or 'outlook' in name) try: if is_ssl and hosts: self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) self._server.ehlo() self._server.starttls() self._server.ehlo() elif is_ssl: self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT) self._server.ehlo() else: self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) self._server.login(self._sender, config['pass']) msg = 'Connect to: {}'.format(name) debug(msg) return True except smtplib.SMTPAuthenticationError as e: if '535' in str(e): self._error = _('Incorrect user or password') return False if '534' in str(e) and 'gmail' in name: self._error = _('Allow less secure apps in GMail') return False except smtplib.SMTPException as e: self._error = str(e) return False except Exception as e: self._error = str(e) return False return False def _body(self, msg): body = msg.replace('\\n', '
') return body def send(self, message): file_name = 'attachment; filename={}' email = MIMEMultipart() email['From'] = self._sender email['To'] = message['to'] email['Cc'] = message.get('cc', '') email['Subject'] = message['subject'] email['Date'] = formatdate(localtime=True) if message.get('confirm', False): email['Disposition-Notification-To'] = email['From'] email.attach(MIMEText(self._body(message['body']), 'html')) for path in message.get('files', ()): _, fn, _, _ = get_info_path(path) part = MIMEBase('application', 'octet-stream') part.set_payload(read_file(path, 'rb')) encoders.encode_base64(part) part.add_header('Content-Disposition', file_name.format(fn)) email.attach(part) receivers = ( email['To'].split(',') + email['CC'].split(',') + message.get('bcc', '').split(',')) try: self._server.sendmail(self._sender, receivers, email.as_string()) msg = 'Email sent...' debug(msg) if message.get('path', ''): self.save_message(email, message['path']) return True except Exception as e: self._error = str(e) return False return False def save_message(self, email, path): mbox = mailbox.mbox(path, create=True) mbox.lock() try: msg = mailbox.mboxMessage(email) mbox.add(msg) mbox.flush() finally: mbox.unlock() return def close(self): try: self._server.quit() msg = 'Close connection...' debug(msg) except: pass return def _send_email(server, messages): with SmtpServer(server) as server: if server.is_connect: for msg in messages: server.send(msg) else: error(server.error) return server.error def send_email(server, message): messages = message if isinstance(message, dict): messages = (message,) t = threading.Thread(target=_send_email, args=(server, messages)) t.start() return def server_smtp_test(config): with SmtpServer(config) as server: if server.error: error(server.error) return server.error class LIBOServer(object): HOST = 'localhost' PORT = '8100' ARG = 'socket,host={},port={};urp;StarOffice.ComponentContext'.format(HOST, PORT) CMD = ['soffice', '-env:SingleAppInstance=false', '-env:UserInstallation=file:///tmp/LIBO_Process8100', '--headless', '--norestore', '--invisible', '--accept={}'.format(ARG)] def __init__(self): self._server = None self._ctx = None self._sm = None self._start_server() self._init_values() def _init_values(self): global CTX global SM if not self.is_running: return ctx = uno.getComponentContext() service = 'com.sun.star.bridge.UnoUrlResolver' resolver = ctx.ServiceManager.createInstanceWithContext(service, ctx) self._ctx = resolver.resolve('uno:{}'.format(self.ARG)) self._sm = self._ctx.getServiceManager() CTX = self._ctx SM = self._sm return @property def is_running(self): try: s = socket.create_connection((self.HOST, self.PORT), 5.0) s.close() debug('LibreOffice is running...') return True except ConnectionRefusedError: return False def _start_server(self): if self.is_running: return for i in range(3): self._server = subprocess.Popen(self.CMD, stdout=subprocess.PIPE, stderr=subprocess.PIPE) time.sleep(3) if self.is_running: break return def stop(self): if self._server is None: print('Search pgrep soffice') else: self._server.terminate() debug('LibreOffice is stop...') return def create_instance(self, name, with_context=True): if with_context: instance = self._sm.createInstanceWithContext(name, self._ctx) else: instance = self._sm.createInstance(name) return instance