diff --git a/source/conf.py.example b/source/conf.py.example
index 768330a..a58f314 100644
--- a/source/conf.py.example
+++ b/source/conf.py.example
@@ -48,8 +48,6 @@ ICON_EXT = f'{NAME.lower()}.png'
# ~ DEPENDENCIES_MINIMAL = '6.0'
DEPENDENCIES_MINIMAL = ''
-LICENSE_ACCEPT_BY = 'user' # or admin
-LICENSE_SUPPRESS_ON_UPDATE = True
# ~ Change for you favorite license
LICENSE_EN = f"""This file is part of {NAME}.
@@ -84,6 +82,12 @@ INFO = {
CONTEXT = {
'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',
}
@@ -101,7 +105,7 @@ MENUS = (
{
'title': {'en': 'Option 1', 'es': 'OpciĆ³n 1'},
'argument': 'option1',
- 'context': '',
+ 'context': 'calc,writer',
'icon': 'icon',
'toolbar': True,
},
@@ -165,11 +169,6 @@ URL_XML_UPDATE = ''
URL_OXT = ''
-# ~ If used user profile for develop
-# ~ PATH_DEV = '-env:UserInstallation=file:///home/mau/.temp/develop'
-# ~ unopkg not support (or I not know how) other user profile
-PATH_DEV = ''
-
# ~ Default program for test: --calc, --writer, --draw
PROGRAM = '--calc'
# ~ Path to file for test
@@ -305,7 +304,7 @@ FILE_DESCRIPTION = f"""
{NODE_PUBLISHER}
-
+
{NODE_LICENSE}
{NODE_DEPENDENCIES_MINIMAL}{NODE_UPDATE}
@@ -334,6 +333,14 @@ opt = 'fuse'
if PARENT == 'OfficeMenuBar':
opt = 'replace'
+
+def _get_context(args):
+ c = []
+ for v in args.split(','):
+ c.append(CONTEXT[v])
+ return ','.join(c)
+
+
menus = []
toolbar = []
tmp = ' {}'
@@ -345,7 +352,7 @@ for i, m in enumerate(MENUS):
'opt': opt,
'titles': '\n'.join(titles),
'argument': m['argument'],
- 'context': m['context'],
+ 'context': _get_context(m['context']),
'folder': DIRS['images'],
'icon': m['icon'],
}
@@ -549,3 +556,7 @@ DATA = {
'idl': FILE_IDL,
'addin': FILE_ADDIN,
}
+
+
+# ~ LICENSE_ACCEPT_BY = 'user' # or admin
+# ~ LICENSE_SUPPRESS_ON_UPDATE = True
diff --git a/source/easymacro.py b/source/easymacro.py
index aa607f0..76de6cf 100644
--- a/source/easymacro.py
+++ b/source/easymacro.py
@@ -20,6 +20,7 @@
import getpass
import logging
+import os
import platform
import sys
import tempfile
@@ -28,14 +29,20 @@ 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
+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 = {
@@ -60,7 +67,7 @@ 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()
@@ -96,7 +103,7 @@ 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)
+# ~ DESKTOP = create_instance('com.sun.star.frame.Desktop', True)
INFO_DEBUG = '{}\n\n{}\n\n{}'.format(
sys.version, platform.platform(), '\n'.join(sys.path))
@@ -176,6 +183,10 @@ 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()
@@ -186,35 +197,322 @@ def _path_url(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 LOCellRange(object):
+
+
+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_obj = self.obj.ImplementationName
self._type_content = EMPTY
if self._type_obj == OBJ_CELL:
- self._type_content = self._obj.getType()
+ 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
@@ -235,11 +533,20 @@ class LOCellRange(object):
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.obj.Spreadsheet[row,col])
+ return LOCellRange(self.sheet[row,col], self.doc)
@property
def sheet(self):
@@ -263,12 +570,17 @@ class LOCellRange(object):
a = self.obj.getRangeAddressesAsString()
return a
- def add_image(self, path, **kwargs):
+ @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)
- doc = get_document()
- img = doc.createInstance('com.sun.star.drawing.GraphicObjectShape')
+ img = self.doc.create_instance('com.sun.star.drawing.GraphicObjectShape')
img.GraphicURL = _path_url(path)
self.draw_page.add(img)
img.Anchor = self.obj
@@ -276,33 +588,413 @@ class LOCellRange(object):
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 = DESKTOP.getCurrentComponent()
+ doc = _get_class_doc(desktop.getCurrentComponent())
except Exception as e:
log.error(e)
return doc
def get_selection():
- obj = None
- try:
- obj = get_document().getCurrentSelection()
- except Exception as e:
- log.error(e)
- return obj
+ return get_document().selection
def get_cell(*args):
- sel = get_selection()
- if sel.ImplementationName == OBJ_RANGE:
- sel = sel[0,0]
- elif sel.ImplementationName == OBJ_RANGES:
- sel = sel[0][0,0]
- return LOCellRange(sel)
+ 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