zaz-barcode/easymacro.py

1001 lines
24 KiB
Python

#!/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 <https://www.gnu.org/licenses/>.
import getpass
import logging
import os
import platform
import sys
import tempfile
import threading
import time
from functools import wraps
import uno
import unohelper
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.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
FILE_NAME_DEBUG = 'debug.log'
MSG_LANG = {
'es': {
'OK': 'Aceptar',
'Cancel': 'Cancelar',
}
}
FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
DATE = '%d/%m/%Y %H:%M:%S'
LEVEL_ERROR = logging.getLevelName(logging.ERROR)
LEVEL_DEBUG = logging.getLevelName(logging.DEBUG)
LEVEL_INFO = logging.getLevelName(logging.INFO)
logging.addLevelName(logging.ERROR, f'\033[1;41m{LEVEL_ERROR}\033[1;0m')
logging.addLevelName(logging.DEBUG, f'\x1b[33m{LEVEL_DEBUG}\033[1;0m')
logging.addLevelName(logging.INFO, f'\x1b[32m{LEVEL_INFO}\033[1;0m')
logging.basicConfig(level=logging.DEBUG, format=FORMAT, datefmt=DATE)
log = logging.getLogger(__name__)
OBJ_CELL = 'ScCellObj'
OBJ_RANGE = 'ScCellRangeObj'
OBJ_RANGES = 'ScCellRangesObj'
OBJ_TYPE_RANGES = (OBJ_CELL, OBJ_RANGE, OBJ_RANGES)
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_config(key, node_name):
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 (ca.hasByName(key)):
data = ca.getPropertyValue(key)
return data
except Exception as e:
log.error(e)
return ''
OS = sys.platform
USER = getpass.getuser()
PC = platform.node()
LANGUAGE = _get_config('ooLocale', 'org.openoffice.Setup/L10N/')
NAME = TITLE = _get_config('ooName', 'org.openoffice.Setup/Product')
VERSION = _get_config('ooSetupVersion', 'org.openoffice.Setup/Product')
# ~ DESKTOP = create_instance('com.sun.star.frame.Desktop', True)
INFO_DEBUG = '{}\n\n{}\n\n{}'.format(
sys.version, platform.platform(), '\n'.join(sys.path))
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 debug(info):
log.debug(info)
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:
log.error(f.__name__, exc_info=True)
return func
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 _(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_temp_file():
return tempfile.NamedTemporaryFile()
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 get_type_doc(obj):
services = {
'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',
'math': 'com.sun.star.formula.FormulaProperties',
'basic': 'com.sun.star.script.BasicIDE',
}
for k, v in services.items():
if obj.supportsService(v):
return k
return ''
# ~ Custom classes
class LODocument(object):
def __init__(self, obj):
self._obj = obj
self._init_values()
def _init_values(self):
self._type_doc = get_type_doc(self.obj)
self._cc = self.obj.getCurrentController()
return
@property
def obj(self):
return self._obj
@property
def type(self):
return self._type_doc
@property
def title(self):
return self.obj.getTitle()
@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 visible(self):
w = self._cc.getFrame().getContainerWindow()
return w.Visible
@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
def create_instance(self, name):
obj = self.obj.createInstance(name)
return obj
class LOCalc(LODocument):
def __init__(self, obj):
super().__init__(obj)
@property
def obj(self):
return self._obj
@property
def active(self):
return LOCalcSheet(self._cc.getActiveSheet(), self)
@property
def selection(self):
sel = self.obj.getCurrentSelection()
if sel.ImplementationName in OBJ_TYPE_RANGES:
sel = LOCellRange(sel, self)
return sel
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 create_instance(self, name):
# ~ obj = self.obj.createInstance(name)
# ~ return obj
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):
return
@property
def obj(self):
return self._obj
@property
def doc(self):
return self._doc
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()
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):
def __init__(self, obj):
super().__init__(obj)
class LODrawImpress(LODocument):
def __init__(self, obj):
super().__init__(obj)
@property
def draw_page(self):
return self._cc.getCurrentPage()
@catch_exception
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)
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 isinstance(values, list):
values = tuple(values)
self.obj.setDataArray(values)
def offset(self, col=1, row=0):
a = self.address
col = a.Column + col
row = a.Row + 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)
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
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 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 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)
# ~ self._set_icon()
def _set_icon(self):
icon_name = self.tag.strip()
if icon_name:
path_icon = _file_url('{}/img/{}'.format(CURRENT_PATH, icon_name))
self._model.ImageURL = path_icon
if self.value:
self._model.ImageAlign = 0
return
@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 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 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 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):
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,
# ~ 'link': UnoLink,
# ~ 'tab': UnoTab,
# ~ 'roadmap': UnoRoadmap,
# ~ 'image': UnoImage,
# ~ 'radio': UnoRadio,
# ~ 'groupbox': UnoGroupBox,
# ~ 'tree': UnoTree,
# ~ 'grid': UnoGrid,
}
return classes[tipo](obj)
def add_control(self, properties):
tipo = properties.pop('Type').lower()
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)
def get_document():
doc = None
desktop = get_desktop()
try:
doc = _get_class_doc(desktop.getCurrentComponent())
except Exception as e:
log.error(e)
return doc
def get_selection():
return get_document().selection
def get_cell(*args):
if args:
index = args
if len(index) == 1:
index = args[0]
cell = get_document().get_cell(index)
else:
cell = get_selection().first
return cell
def active_cell():
return get_cell()
def create_dialog(properties):
return LODialog(properties)
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