zaz/source/easymacro.py

1001 lines
24 KiB
Python
Raw Normal View History

2019-09-07 00:02:59 -05:00
#!/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
2019-09-09 22:13:01 -05:00
import os
2019-09-07 00:02:59 -05:00
import platform
import sys
2019-09-08 00:25:30 -05:00
import tempfile
2019-09-07 00:02:59 -05:00
import threading
import time
from functools import wraps
import uno
2019-09-09 22:13:01 -05:00
import unohelper
2019-09-07 00:02:59 -05:00
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
2019-09-08 00:25:30 -05:00
from com.sun.star.awt.PosSize import POSSIZE, SIZE
2019-09-09 22:13:01 -05:00
from com.sun.star.awt import Size, Point
2019-09-08 00:25:30 -05:00
from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA
2019-09-07 00:02:59 -05:00
2019-09-09 22:13:01 -05:00
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
2019-09-07 00:02:59 -05:00
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)
2019-09-08 00:25:30 -05:00
LEVEL_DEBUG = logging.getLevelName(logging.DEBUG)
2019-09-07 00:02:59 -05:00
LEVEL_INFO = logging.getLevelName(logging.INFO)
logging.addLevelName(logging.ERROR, f'\033[1;41m{LEVEL_ERROR}\033[1;0m')
2019-09-08 00:25:30 -05:00
logging.addLevelName(logging.DEBUG, f'\x1b[33m{LEVEL_DEBUG}\033[1;0m')
2019-09-07 00:02:59 -05:00
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__)
2019-09-08 00:25:30 -05:00
OBJ_CELL = 'ScCellObj'
OBJ_RANGE = 'ScCellRangeObj'
OBJ_RANGES = 'ScCellRangesObj'
2019-09-09 22:13:01 -05:00
OBJ_TYPE_RANGES = (OBJ_CELL, OBJ_RANGE, OBJ_RANGES)
2019-09-08 00:25:30 -05:00
2019-09-07 00:02:59 -05:00
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')
2019-09-09 22:13:01 -05:00
# ~ DESKTOP = create_instance('com.sun.star.frame.Desktop', True)
2019-09-07 00:02:59 -05:00
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
2019-09-08 00:25:30 -05:00
def debug(info):
log.debug(info)
return
def error(info):
log.error(info)
return
2019-09-07 00:02:59 -05:00
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')
2019-09-08 00:25:30 -05:00
def errorbox(message, title=TITLE):
2019-09-07 00:02:59 -05:00
return msgbox(message, title, type_msg='errorbox')
2019-09-08 00:25:30 -05:00
2019-09-09 22:13:01 -05:00
def get_desktop():
return create_instance('com.sun.star.frame.Desktop', True)
2019-09-08 00:25:30 -05:00
def get_temp_file():
return tempfile.NamedTemporaryFile()
def _path_url(path):
if path.startswith('file://'):
return path
return uno.systemPathToFileUrl(path)
2019-09-09 22:13:01 -05:00
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 ''
2019-09-08 00:25:30 -05:00
# ~ Custom classes
2019-09-09 22:13:01 -05:00
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):
2019-09-08 00:25:30 -05:00
def __init__(self, obj):
2019-09-09 22:13:01 -05:00
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):
2019-09-08 00:25:30 -05:00
self._obj = obj
2019-09-09 22:13:01 -05:00
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
2019-09-08 00:25:30 -05:00
self._init_values()
def __enter__(self):
return self
def __exit__(self, *args):
pass
2019-09-09 22:13:01 -05:00
def __getitem__(self, index):
return LOCellRange(self.obj[index], self.doc)
2019-09-08 00:25:30 -05:00
def _init_values(self):
2019-09-09 22:13:01 -05:00
self._type_obj = self.obj.ImplementationName
2019-09-08 00:25:30 -05:00
self._type_content = EMPTY
if self._type_obj == OBJ_CELL:
2019-09-09 22:13:01 -05:00
self._type_content = self.obj.getType()
2019-09-08 00:25:30 -05:00
return
@property
def obj(self):
return self._obj
2019-09-09 22:13:01 -05:00
@property
def doc(self):
return self._doc
@property
def type(self):
return self._type_obj
2019-09-08 00:25:30 -05:00
@property
def type_content(self):
return self._type_content
2019-09-09 22:13:01 -05:00
@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
2019-09-08 00:25:30 -05:00
@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)
2019-09-09 22:13:01 -05:00
@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)
2019-09-08 00:25:30 -05:00
def offset(self, col=1, row=0):
a = self.address
col = a.Column + col
row = a.Row + row
2019-09-09 22:13:01 -05:00
return LOCellRange(self.sheet[row,col], self.doc)
2019-09-08 00:25:30 -05:00
@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
2019-09-09 22:13:01 -05:00
@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):
2019-09-08 00:25:30 -05:00
s = self.obj.Size
w = kwargs.get('width', s.Width)
h = kwargs.get('Height', s.Height)
2019-09-09 22:13:01 -05:00
img = self.doc.create_instance('com.sun.star.drawing.GraphicObjectShape')
2019-09-08 00:25:30 -05:00
img.GraphicURL = _path_url(path)
self.draw_page.add(img)
img.Anchor = self.obj
img.setSize(Size(w, h))
return
2019-09-09 22:13:01 -05:00
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
2019-09-08 00:25:30 -05:00
# ~ Python >= 3.7
# ~ def __getattr__(name):
2019-09-09 22:13:01 -05:00
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)
2019-09-08 00:25:30 -05:00
def get_document():
doc = None
2019-09-09 22:13:01 -05:00
desktop = get_desktop()
2019-09-08 00:25:30 -05:00
try:
2019-09-09 22:13:01 -05:00
doc = _get_class_doc(desktop.getCurrentComponent())
2019-09-08 00:25:30 -05:00
except Exception as e:
log.error(e)
return doc
def get_selection():
2019-09-09 22:13:01 -05:00
return get_document().selection
2019-09-08 00:25:30 -05:00
def get_cell(*args):
2019-09-09 22:13:01 -05:00
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