Add support for forms buttons

This commit is contained in:
Mauricio Baeza 2019-10-19 20:01:04 -05:00
parent ac4aac9b89
commit d1aad0293b
12 changed files with 382 additions and 40 deletions

View File

@ -22,6 +22,7 @@ import csv
import ctypes
import datetime
import errno
import gettext
import getpass
import hashlib
import json
@ -46,6 +47,8 @@ from functools import wraps
from operator import itemgetter
from pathlib import Path, PurePath
from pprint import pprint
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
from string import Template
from subprocess import PIPE
@ -75,6 +78,7 @@ from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA
from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK
from com.sun.star.text.TextContentAnchorType import AS_CHARACTER
from com.sun.star.script import ScriptEventDescriptor
from com.sun.star.lang import XEventListener
from com.sun.star.awt import XActionListener
from com.sun.star.awt import XMouseListener
@ -94,6 +98,20 @@ except ImportError:
pass
ID_EXTENSION = ''
DIR = {
'images': 'images',
'locales': 'locales',
}
KEY = {
'enter': 1280,
}
SEPARATION = 5
MSG_LANG = {
'es': {
'OK': 'Aceptar',
@ -117,6 +135,9 @@ LOG_NAME = 'ZAZ'
CLIPBOARD_FORMAT_TEXT = 'text/plain;charset=utf-16'
PYTHON = 'python'
if IS_WIN:
PYTHON = 'python.exe'
CALC = 'calc'
WRITER = 'writer'
OBJ_CELL = 'ScCellObj'
@ -133,9 +154,15 @@ TYPE_DOC = {
'base': 'com.sun.star.sdb.DocumentDataSource',
'math': 'com.sun.star.formula.FormulaProperties',
'basic': 'com.sun.star.script.BasicIDE',
'main': 'com.sun.star.frame.StartModule',
}
NODE_MENUBAR = 'private:resource/menubar/menubar'
MENUS_MAIN = {
'file': '.uno:PickList',
'tools': '.uno:ToolsMenu',
'help': '.uno:HelpMenu',
}
MENUS_CALC = {
'file': '.uno:PickList',
'edit': '.uno:EditMenu',
@ -164,6 +191,7 @@ MENUS_WRITER = {
}
MENUS_APP = {
'main': MENUS_MAIN,
'calc': MENUS_CALC,
'writer': MENUS_WRITER,
}
@ -274,16 +302,17 @@ def info(data):
return
def debug(info):
def debug(*info):
if IS_WIN:
doc = get_document(FILE_NAME_DEBUG)
if doc is None:
return
doc = LogWin(doc.obj)
doc.write(info)
doc.write(str(info))
return
log.debug(str(info))
data = [str(d) for d in info]
log.debug('\t'.join(data))
return
@ -694,10 +723,117 @@ class LODocument(object):
return path_pdf
class FormControlBase(object):
EVENTS = {
'action': 'actionPerformed',
'click': 'mousePressed',
}
TYPES = {
'actionPerformed': 'XActionListener',
'mousePressed': 'XMouseListener',
}
def __init__(self, obj):
self._obj = obj
self._index = -1
self._rules = {}
@property
def obj(self):
return self._obj
@property
def name(self):
return self.obj.Name
@property
def form(self):
return self.obj.getParent()
@property
def index(self):
return self._index
@index.setter
def index(self, value):
self._index = value
@property
def events(self):
return self.form.getScriptEvents(self.index)
def remove_event(self, name=''):
for ev in self.events:
if name and \
ev.EventMethod == self.EVENTS[name] and \
ev.ListenerType == self.TYPES[ev.EventMethod]:
self.form.revokeScriptEvent(self.index,
ev.ListenerType, ev.EventMethod, ev.AddListenerParam)
break
else:
self.form.revokeScriptEvent(self.index,
ev.ListenerType, ev.EventMethod, ev.AddListenerParam)
return
def add_event(self, name, macro):
if not 'name' in macro:
macro['name'] = '{}_{}'.format(self.name, name)
event = ScriptEventDescriptor()
event.AddListenerParam = ''
event.EventMethod = self.EVENTS[name]
event.ListenerType = self.TYPES[event.EventMethod]
event.ScriptCode = _get_url_script(macro)
event.ScriptType = 'Script'
for ev in self.events:
if ev.EventMethod == event.EventMethod and \
ev.ListenerType == event.ListenerType:
self.form.revokeScriptEvent(self.index,
event.ListenerType, event.EventMethod, event.AddListenerParam)
break
self.form.registerScriptEvent(self.index, event)
return
class FormButton(FormControlBase):
def __init__(self, obj):
super().__init__(obj)
class LOForm(ObjectBase):
def __init__(self, obj):
super().__init__(obj)
self._init_controls()
def __getitem__(self, index):
if isinstance(index, int):
return self._controls[index]
else:
return getattr(self, index)
def _get_type_control(self, name):
types = {
# ~ 'stardiv.Toolkit.UnoFixedTextControl': 'label',
'com.sun.star.form.OButtonModel': 'formbutton',
# ~ 'stardiv.Toolkit.UnoEditControl': 'text',
# ~ 'stardiv.Toolkit.UnoRoadmapControl': 'roadmap',
# ~ 'stardiv.Toolkit.UnoFixedHyperlinkControl': 'link',
# ~ 'stardiv.Toolkit.UnoListBoxControl': 'listbox',
}
return types[name]
def _init_controls(self):
self._controls = []
for i, c in enumerate(self.obj.ControlModels):
tipo = self._get_type_control(c.ImplementationName)
control = get_custom_class(tipo, c)
control.index = i
self._controls.append(control)
setattr(self, c.Name, control)
@property
def name(self):
@ -1806,7 +1942,7 @@ class EventsMouse(EventsListenerBase, XMouseListener, XMouseMotionListener):
def mousePressed(self, event):
event_name = '{}_click'.format(self._name)
if event.ClickCount == 2:
event_name = '{}_double_click'.format(name)
event_name = '{}_double_click'.format(self._name)
if hasattr(self._controller, event_name):
getattr(self._controller, event_name)(event)
return
@ -1859,13 +1995,13 @@ class EventsMouseGrid(EventsMouse):
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)
# ~ 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
@ -1907,6 +2043,9 @@ class EventsFocus(EventsListenerBase, XFocusListener):
super().__init__(controller, name)
def focusGained(self, event):
service = event.Source.Model.ImplementationName
if service == 'stardiv.Toolkit.UnoControlListBoxModel':
return
obj = event.Source.Model
obj.BackgroundColor = COLOR_ON_FOCUS
@ -1923,6 +2062,27 @@ class EventsKey(EventsListenerBase, XKeyListener):
event.Modifiers
"""
def __init__(self, controller, name):
super().__init__(controller, name)
def keyPressed(self, event):
pass
def keyReleased(self, event):
event_name = '{}_key_released'.format(self._name)
if hasattr(self._controller, event_name):
getattr(self._controller, event_name)(event)
return
class EventsKeyWindow(EventsListenerBase, XKeyListener):
"""
event.KeyChar
event.KeyCode
event.KeyFunc
event.Modifiers
"""
def __init__(self, cls):
super().__init__(cls.events, cls.name)
self._cls = cls
@ -2016,7 +2176,6 @@ class EventsMenu(EventsListenerBase, XMenuListener):
def itemHighlighted(self, event):
pass
@catch_exception
def itemSelected(self, event):
name = event.Source.getCommand(event.MenuId)
if name.startswith('menu'):
@ -2115,6 +2274,20 @@ class UnoBaseObject(object):
def tag(self, value):
self.model.Tag = value
@property
def visible(self):
return self.obj.Visible
@visible.setter
def visible(self, value):
self.obj.setVisible(value)
@property
def enabled(self):
return self.model.Enabled
@enabled.setter
def enabled(self, value):
self.model.Enabled = value
@property
def step(self):
return self.model.Step
@ -2153,16 +2326,17 @@ class UnoBaseObject(object):
return
def move(self, origin, x=0, y=5):
w = 0
h = 0
if x:
w = origin.width
self.x = origin.x + origin.width + x
if y:
h = origin.height
x = origin.x + x + w
y = origin.y + y + h
self.x = x
self.y = y
self.y = origin.y + origin.height + y
return
def possize(self, origin):
self.x = origin.x
self.y = origin.y
self.width = origin.width
self.height = origin.height
return
@ -2235,7 +2409,6 @@ class UnoListBox(UnoBaseObject):
def __init__(self, obj):
super().__init__(obj)
self._data = []
@property
def type(self):
@ -2245,13 +2418,49 @@ class UnoListBox(UnoBaseObject):
def value(self):
return self.obj.SelectedItem
@property
def count(self):
return len(self.data)
@property
def data(self):
return self._data
return self.model.StringItemList
@data.setter
def data(self, values):
self._data = list(sorted(values))
self.model.StringItemList = self.data
self.model.StringItemList = list(sorted(values))
return
def select(self, pos=0):
if isinstance(pos, str):
self.obj.selectItem(pos, True)
else:
self.obj.selectItemPos(pos, True)
return
def clear(self):
self.obj.removeItems(0, self.count)
return
def _set_image_url(self, image):
if exists_path(image):
return _path_url(image)
if not ID_EXTENSION:
return ''
path = get_path_extension(ID_EXTENSION)
path = join(path, DIR['images'], image)
return _path_url(path)
def insert(self, value, path='', pos=-1, show=True):
if pos < 0:
pos = self.count
if path:
self.model.insertItem(pos, value, self._set_image_url(path))
else:
self.model.insertItemText(pos, value)
if show:
self.select(pos)
return
@ -2386,6 +2595,15 @@ class UnoRoadmap(UnoBaseObject):
self.model.insertByIndex(i, opt)
return
@property
def enabled(self):
return True
@enabled.setter
def enabled(self, value):
for m in self.model:
m.Enabled = value
return
def set_enabled(self, index, value):
self.model.getByIndex(index).Enabled = value
return
@ -2405,6 +2623,7 @@ def get_custom_class(tipo, obj):
# ~ 'radio': UnoRadio,
# ~ 'groupbox': UnoGroupBox,
# ~ 'tree': UnoTree,
'formbutton': FormButton,
}
return classes[tipo](obj)
@ -2415,6 +2634,7 @@ def add_listeners(events, control, name=''):
'addMouseListener': EventsMouse,
'addItemListener': EventsItem,
'addFocusListener': EventsFocus,
'addKeyListener': EventsKey,
}
if hasattr(control, 'obj'):
control = contro.obj
@ -2426,7 +2646,7 @@ def add_listeners(events, control, name=''):
for key, value in listeners.items():
if hasattr(control, key):
if is_grid and key == 'addMouseListener':
control.addMouseListener(EventsMouseGrid(events))
control.addMouseListener(EventsMouseGrid(events, name))
continue
if is_link and key == 'addMouseListener':
control.addMouseListener(EventsMouseLink(events, name))
@ -2434,6 +2654,7 @@ def add_listeners(events, control, name=''):
if is_roadmap and key == 'addItemListener':
control.addItemListener(EventsItemRoadmap(events, name))
continue
getattr(control, key)(listeners[key](events, name))
return
@ -2886,6 +3107,8 @@ class LODialog(object):
self._init_controls()
self._events = None
self._color_on_focus = -1
self._id_extension = ''
self._images = 'images'
return
def _create(self, properties):
@ -2925,6 +3148,7 @@ class LODialog(object):
'stardiv.Toolkit.UnoEditControl': 'text',
'stardiv.Toolkit.UnoRoadmapControl': 'roadmap',
'stardiv.Toolkit.UnoFixedHyperlinkControl': 'link',
'stardiv.Toolkit.UnoListBoxControl': 'listbox',
}
return types[name]
@ -2944,6 +3168,22 @@ class LODialog(object):
def model(self):
return self._model
@property
def id_extension(self):
return self._id_extension
@id_extension.setter
def id_extension(self, value):
global ID_EXTENSION
ID_EXTENSION = value
self._id_extension = value
@property
def images(self):
return self._images
@images.setter
def images(self, value):
self._images = value
@property
def height(self):
return self.model.Height
@ -2951,6 +3191,13 @@ class LODialog(object):
def height(self, value):
self.model.Height = value
@property
def width(self):
return self.model.Width
@width.setter
def width(self, value):
self.model.Width = value
@property
def color_on_focus(self):
return self._color_on_focus
@ -3012,10 +3259,16 @@ class LODialog(object):
column_model.addColumn(grid_column)
return column_model
def _set_image_url(self, path):
if exists_path(path):
return _path_url(path)
return ''
def _set_image_url(self, image):
if exists_path(image):
return _path_url(image)
if not self.id_extension:
return ''
path = get_path_extension(self.id_extension)
path = join(path, self.images, image)
return _path_url(path)
def _special_properties(self, tipo, properties):
columns = properties.pop('Columns', ())
@ -3023,8 +3276,11 @@ class LODialog(object):
properties['ColumnModel'] = self._set_column_model(columns)
elif tipo == 'button' and 'ImageURL' in properties:
properties['ImageURL'] = self._set_image_url(properties['ImageURL'])
elif tipo == 'roadmap' and not 'Height' in properties:
properties['Height'] = self.height
elif tipo == 'roadmap':
if not 'Height' in properties:
properties['Height'] = self.height
if 'Title' in properties:
properties['Text'] = properties.pop('Title')
return properties
def add_control(self, properties):
@ -3040,6 +3296,32 @@ class LODialog(object):
setattr(self, name, control)
return
def center(self, control, x=0, y=0):
w = self.width
h = self.height
if isinstance(control, tuple):
wt = SEPARATION * -1
for c in control:
wt += c.width + SEPARATION
x = w / 2 - wt / 2
for c in control:
c.x = x
x = c.x + c.width + SEPARATION
return
if x < 0:
x = w + x - control.width
elif x == 0:
x = w / 2 - control.width / 2
if y < 0:
y = h + y - control.height
elif y == 0:
y = h / 2 - control.height / 2
control.x = x
control.y = y
return
class LOWindow(object):
@ -3168,7 +3450,7 @@ class LOWindow(object):
controller = EventsWindow(self)
self._window.addTopWindowListener(controller)
self._window.addWindowListener(controller)
self._container.addKeyListener(EventsKey(self))
self._container.addKeyListener(EventsKeyWindow(self))
return
@property
@ -3287,6 +3569,11 @@ def get_config_path(name='Work'):
return _path_system(getattr(path, name))
def get_path_python():
path = get_config_path('Module')
return join(path, PYTHON)
# ~ Export ok
def get_file(init_dir='', multiple=False, filters=()):
"""
@ -3541,7 +3828,7 @@ def open_doc(path, **kwargs):
"""
path = _path_url(path)
opt = dict_to_property(kwargs)
doc = get_desktop().loadComponentFromURL(path, '_blank', 0, opt)
doc = get_desktop().loadComponentFromURL(path, '_default', 0, opt)
if doc is None:
return
@ -3595,13 +3882,38 @@ def zip_content(path):
return names
def popen(command, stdin=None):
try:
proc = subprocess.Popen(shlex.split(command), shell=IS_WIN,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in proc.stdout:
yield line.decode().rstrip()
except Exception as e:
error(e)
yield (e.errno, e.strerror)
def url_open(url, options={}, json=False):
data = ''
req = Request(url)
try:
response = urlopen(req)
except HTTPError as e:
error(e)
except URLError as e:
error(e.reason)
else:
if json:
data = json_loads(response.read())
else:
data = response.read()
return data
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,
@ -4216,6 +4528,24 @@ def format(template, data):
return result
def _get_url_script(macro):
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'] = '.'
url = 'vnd.sun.star.script:{library}{module}{name}?language={language}&location={location}'
path = url.format(**data)
return path
def _call_macro(macro):
#~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification
name = 'com.sun.star.script.provider.MasterScriptProviderFactory'
@ -4236,6 +4566,7 @@ def _call_macro(macro):
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]
@ -4462,6 +4793,7 @@ def import_csv(path, **kwargs):
rows = tuple(csv.reader(f, **kwargs))
return rows
def export_csv(path, data, **kwargs):
with open(path, 'w') as f:
writer = csv.writer(f, **kwargs)
@ -4469,6 +4801,19 @@ def export_csv(path, data, **kwargs):
return
def install_locales(path, domain='base', dir_locales=DIR['locales']):
p, *_ = get_info_path(path)
path_locales = join(p, dir_locales)
try:
lang = gettext.translation(domain, path_locales, languages=[LANG])
lang.install()
_ = lang.gettext
except Exception as e:
from gettext import gettext as _
error(e)
return _
class LIBOServer(object):
HOST = 'localhost'
PORT = '8100'
@ -4548,12 +4893,9 @@ class LIBOServer(object):
# ~ 'CurrencyField': 'com.sun.star.awt.UnoControlCurrencyFieldModel',
# ~ 'DateField': 'com.sun.star.awt.UnoControlDateFieldModel',
# ~ 'FileControl': 'com.sun.star.awt.UnoControlFileControlModel',
# ~ 'FixedLine': 'com.sun.star.awt.UnoControlFixedLineModel',
# ~ 'FixedText': 'com.sun.star.awt.UnoControlFixedTextModel',
# ~ 'FormattedField': 'com.sun.star.awt.UnoControlFormattedFieldModel',
# ~ 'GroupBox': 'com.sun.star.awt.UnoControlGroupBoxModel',
# ~ 'ImageControl': 'com.sun.star.awt.UnoControlImageControlModel',
# ~ 'ListBox': 'com.sun.star.awt.UnoControlListBoxModel',
# ~ 'NumericField': 'com.sun.star.awt.UnoControlNumericFieldModel',
# ~ 'PatternField': 'com.sun.star.awt.UnoControlPatternFieldModel',
# ~ 'ProgressBar': 'com.sun.star.awt.UnoControlProgressBarModel',

BIN
source/images/close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

BIN
source/images/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

BIN
source/images/install.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

BIN
source/images/ok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

BIN
source/images/python.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
source/images/python_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
source/images/question.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

BIN
source/images/search-24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

BIN
source/images/search-48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B