1790 lines
47 KiB
Python
1790 lines
47 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# == Rapid Develop Macros in LibreOffice ==
|
|
|
|
# ~ https://git.cuates.net/elmau/easymacro
|
|
|
|
# ~ easymacro 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.
|
|
|
|
# ~ easymacro 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 easymacro. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
import io
|
|
import logging
|
|
import os
|
|
import platform
|
|
import re
|
|
import socket
|
|
import ssl
|
|
|
|
from socket import timeout
|
|
from urllib import parse
|
|
from urllib.request import Request, urlopen
|
|
from urllib.error import URLError, HTTPError
|
|
|
|
from com.sun.star.awt import Rectangle, Size, Point
|
|
from com.sun.star.awt import Key, KeyEvent, KeyModifier
|
|
|
|
from com.sun.star.datatransfer import XTransferable, DataFlavor
|
|
from com.sun.star.io import IOException, XOutputStream
|
|
from com.sun.star.sheet import XRangeSelectionListener
|
|
from com.sun.star.container import NoSuchElementException
|
|
|
|
|
|
SALT = b'00a1bfb05353bb3fd8e7aa7fe5efdccc'
|
|
DIRS = {}
|
|
|
|
|
|
class ClipBoard(object):
|
|
SERVICE = 'com.sun.star.datatransfer.clipboard.SystemClipboard'
|
|
CLIPBOARD_FORMAT_TEXT = 'text/plain;charset=utf-16'
|
|
|
|
class TextTransferable(unohelper.Base, XTransferable):
|
|
|
|
def __init__(self, text):
|
|
df = DataFlavor()
|
|
df.MimeType = ClipBoard.CLIPBOARD_FORMAT_TEXT
|
|
df.HumanPresentableName = 'encoded text utf-16'
|
|
self.flavors = (df,)
|
|
self._data = text
|
|
|
|
def getTransferData(self, flavor):
|
|
return self._data
|
|
|
|
def getTransferDataFlavors(self):
|
|
return self.flavors
|
|
|
|
@classmethod
|
|
def set(cls, value):
|
|
ts = cls.TextTransferable(value)
|
|
sc = create_instance(cls.SERVICE)
|
|
sc.setContents(ts, None)
|
|
return
|
|
|
|
@classproperty
|
|
def contents(cls):
|
|
df = None
|
|
text = ''
|
|
sc = create_instance(cls.SERVICE)
|
|
transferable = sc.getContents()
|
|
data = transferable.getTransferDataFlavors()
|
|
for df in data:
|
|
if df.MimeType == cls.CLIPBOARD_FORMAT_TEXT:
|
|
break
|
|
if df:
|
|
text = transferable.getTransferData(df)
|
|
return text
|
|
|
|
|
|
class IOStream(object):
|
|
"""Classe for input/output stream"""
|
|
|
|
class OutputStream(unohelper.Base, XOutputStream):
|
|
|
|
def __init__(self):
|
|
self._buffer = b''
|
|
self.closed = 0
|
|
|
|
@property
|
|
def buffer(self):
|
|
return self._buffer
|
|
|
|
def closeOutput(self):
|
|
self.closed = 1
|
|
|
|
def writeBytes(self, seq):
|
|
if seq.value:
|
|
self._buffer = seq.value
|
|
|
|
def flush(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def buffer(cls):
|
|
return io.BytesIO()
|
|
|
|
@classmethod
|
|
def input(cls, buffer):
|
|
service = 'com.sun.star.io.SequenceInputStream'
|
|
stream = create_instance(service, True)
|
|
stream.initialize((uno.ByteSequence(buffer.getvalue()),))
|
|
return stream
|
|
|
|
@classmethod
|
|
def output(cls):
|
|
return cls.OutputStream()
|
|
|
|
|
|
class EventsRangeSelectionListener(EventsListenerBase, XRangeSelectionListener):
|
|
|
|
def __init__(self, controller):
|
|
super().__init__(controller, '')
|
|
|
|
def done(self, event):
|
|
range_selection = event.RangeDescriptor
|
|
event_name = 'range_selection_done'
|
|
if hasattr(self._controller, event_name):
|
|
getattr(self._controller, event_name)(range_selection)
|
|
return
|
|
|
|
def aborted(self, event):
|
|
range_selection = event.RangeDescriptor
|
|
event_name = 'range_selection_aborted'
|
|
if hasattr(self._controller, event_name):
|
|
getattr(self._controller, event_name)()
|
|
return
|
|
|
|
|
|
class LOShapes(object):
|
|
_type = 'ShapeCollection'
|
|
|
|
def __init__(self, obj):
|
|
self._obj = obj
|
|
|
|
def __len__(self):
|
|
return self.obj.Count
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
pass
|
|
|
|
def __iter__(self):
|
|
self._index = 0
|
|
return self
|
|
|
|
def __next__(self):
|
|
try:
|
|
s = self.obj[self._index]
|
|
shape = LOShape(s)
|
|
except IndexError:
|
|
raise StopIteration
|
|
|
|
self._index += 1
|
|
return shape
|
|
|
|
def __str__(self):
|
|
return 'Shapes'
|
|
|
|
@property
|
|
def obj(self):
|
|
return self._obj
|
|
|
|
|
|
class LOShape(object):
|
|
IMAGE = 'com.sun.star.drawing.GraphicObjectShape'
|
|
|
|
def __init__(self, obj):
|
|
self._obj = obj
|
|
|
|
def __str__(self):
|
|
return f'Shape: {self.name}'
|
|
|
|
@property
|
|
def obj(self):
|
|
return self._obj
|
|
|
|
@property
|
|
def properties(self):
|
|
# ~ properties = self.obj.PropertySetInfo.Properties
|
|
# ~ data = {p.Name: getattr(self.obj, p.Name) for p in properties}
|
|
data = self.obj.PropertySetInfo.Properties
|
|
keys = [p.Name for p in data]
|
|
values = self.obj.getPropertyValues(keys)
|
|
data = dict(zip(keys, values))
|
|
return data
|
|
@properties.setter
|
|
def properties(self, values):
|
|
_set_properties(self.obj, values)
|
|
|
|
@property
|
|
def shape_type(self):
|
|
return self.obj.ShapeType
|
|
|
|
@property
|
|
def name(self):
|
|
return self.obj.Name
|
|
@name.setter
|
|
def name(self, value):
|
|
self.obj.Name = value
|
|
|
|
@property
|
|
def is_image(self):
|
|
return self.shape_type == self.IMAGE
|
|
|
|
@property
|
|
def is_shape(self):
|
|
return self.shape_type != self.IMAGE
|
|
|
|
@property
|
|
def size(self):
|
|
s = self.obj.Size
|
|
return s
|
|
@size.setter
|
|
def size(self, value):
|
|
self.obj.Size = value
|
|
|
|
@property
|
|
def width(self):
|
|
s = self.obj.Size
|
|
return s.Width
|
|
@width.setter
|
|
def width(self, value):
|
|
s = self.size
|
|
s.Width = value
|
|
self.size = s
|
|
|
|
@property
|
|
def height(self):
|
|
s = self.obj.Size
|
|
return s.Height
|
|
@height.setter
|
|
def height(self, value):
|
|
s = self.size
|
|
s.Height = value
|
|
self.size = s
|
|
|
|
@property
|
|
def position(self):
|
|
return self.obj.Position
|
|
@property
|
|
def x(self):
|
|
return self.position.X
|
|
@property
|
|
def y(self):
|
|
return self.position.Y
|
|
|
|
@property
|
|
def string(self):
|
|
return self.obj.String
|
|
@string.setter
|
|
def string(self, value):
|
|
self.obj.String = value
|
|
|
|
@property
|
|
def title(self):
|
|
return self.obj.Title
|
|
@title.setter
|
|
def title(self, value):
|
|
self.obj.Title = value
|
|
|
|
@property
|
|
def description(self):
|
|
return self.obj.Description
|
|
@description.setter
|
|
def description(self, value):
|
|
self.obj.Description = value
|
|
|
|
|
|
|
|
class LOShortCuts(object):
|
|
"""Classe for manager shortcuts"""
|
|
KEYS = {getattr(Key, k): k for k in dir(Key)}
|
|
MODIFIERS = {
|
|
'shift': KeyModifier.SHIFT,
|
|
'ctrl': KeyModifier.MOD1,
|
|
'alt': KeyModifier.MOD2,
|
|
'ctrlmac': KeyModifier.MOD3,
|
|
}
|
|
COMBINATIONS = {
|
|
0: '',
|
|
1: 'shift',
|
|
2: 'ctrl',
|
|
4: 'alt',
|
|
8: 'ctrlmac',
|
|
3: 'shift+ctrl',
|
|
5: 'shift+alt',
|
|
9: 'shift+ctrlmac',
|
|
6: 'ctrl+alt',
|
|
10: 'ctrl+ctrlmac',
|
|
12: 'alt+ctrlmac',
|
|
7: 'shift+ctrl+alt',
|
|
11: 'shift+ctrl+ctrlmac',
|
|
13: 'shift+alt+ctrlmac',
|
|
14: 'ctrl+alt+ctrlmac',
|
|
15: 'shift+ctrl+alt+ctrlmac',
|
|
}
|
|
|
|
def __init__(self, app: str=''):
|
|
self._app = app
|
|
service = 'com.sun.star.ui.GlobalAcceleratorConfiguration'
|
|
if app:
|
|
service = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier'
|
|
type_app = LODocuments.TYPES[app]
|
|
manager = create_instance(service, True)
|
|
uicm = manager.getUIConfigurationManager(type_app)
|
|
self._config = uicm.ShortCutManager
|
|
else:
|
|
self._config = create_instance(service)
|
|
|
|
def __getitem__(self, index):
|
|
return LOShortCuts(index)
|
|
|
|
def __contains__(self, item):
|
|
cmd = self.get_by_shortcut(item)
|
|
return bool(cmd)
|
|
|
|
def __iter__(self):
|
|
self._i = -1
|
|
return self
|
|
|
|
def __next__(self):
|
|
self._i += 1
|
|
try:
|
|
event = self._config.AllKeyEvents[self._i]
|
|
event = self._get_info(event)
|
|
except IndexError:
|
|
raise StopIteration
|
|
|
|
return event
|
|
|
|
@classmethod
|
|
def to_key_event(cls, shortcut: str):
|
|
"""Convert from string shortcut (Shift+Ctrl+Alt+LETTER) to KeyEvent"""
|
|
key_event = KeyEvent()
|
|
keys = shortcut.split('+')
|
|
try:
|
|
for m in keys[:-1]:
|
|
key_event.Modifiers += cls.MODIFIERS[m.lower()]
|
|
key_event.KeyCode = getattr(Key, keys[-1].upper())
|
|
except Exception as e:
|
|
error(e)
|
|
key_event = None
|
|
return key_event
|
|
|
|
@classmethod
|
|
def get_url_script(cls, command: Union[str, dict]) -> str:
|
|
"""Get uno command or url for macro"""
|
|
url = command
|
|
if isinstance(url, str) and not url.startswith('.uno:'):
|
|
url = f'.uno:{command}'
|
|
elif isinstance(url, dict):
|
|
url = Macro.get_url_script(command)
|
|
return url
|
|
|
|
def _get_shortcut(self, k):
|
|
"""Get shortcut for key event"""
|
|
# ~ print(k.KeyCode, str(k.KeyChar), k.KeyFunc, k.Modifiers)
|
|
shortcut = f'{self.COMBINATIONS[k.Modifiers]}+{self.KEYS[k.KeyCode]}'
|
|
return shortcut
|
|
|
|
def _get_info(self, key):
|
|
"""Get shortcut and command"""
|
|
cmd = self._config.getCommandByKeyEvent(key)
|
|
shortcut = self._get_shortcut(key)
|
|
return shortcut, cmd
|
|
|
|
def get_all(self):
|
|
"""Get all events key"""
|
|
events = [(self._get_info(k)) for k in self._config.AllKeyEvents]
|
|
return events
|
|
|
|
def get_by_command(self, command: Union[str, dict]):
|
|
"""Get shortcuts by command"""
|
|
url = LOShortCuts.get_url_script(command)
|
|
key_events = self._config.getKeyEventsByCommand(url)
|
|
shortcuts = [self._get_shortcut(k) for k in key_events]
|
|
return shortcuts
|
|
|
|
def get_by_shortcut(self, shortcut: str):
|
|
"""Get command by shortcut"""
|
|
command = ''
|
|
key_event = LOShortCuts.to_key_event(shortcut)
|
|
if key_event:
|
|
command = self._config.getCommandByKeyEvent(key_event)
|
|
return command
|
|
|
|
def set(self, shortcut: str, command: Union[str, dict]) -> bool:
|
|
"""Set shortcut to command
|
|
|
|
:param shortcut: Shortcut like Shift+Ctrl+Alt+LETTER
|
|
:type shortcut: str
|
|
:param command: Command tu assign, 'UNOCOMMAND' or dict with macro info
|
|
:type command: str or dict
|
|
:return: True if set sucesfully
|
|
:rtype: bool
|
|
"""
|
|
result = True
|
|
url = LOShortCuts.get_url_script(command)
|
|
key_event = LOShortCuts.to_key_event(shortcut)
|
|
try:
|
|
self._config.setKeyEvent(key_event, url)
|
|
self._config.store()
|
|
except Exception as e:
|
|
error(e)
|
|
result = False
|
|
|
|
return result
|
|
|
|
def remove_by_shortcut(self, shortcut: str):
|
|
"""Remove by shortcut"""
|
|
key_event = LOShortCuts.to_key_event(shortcut)
|
|
try:
|
|
self._config.removeKeyEvent(key_event)
|
|
result = True
|
|
except NoSuchElementException:
|
|
debug(f'No exists: {shortcut}')
|
|
result = False
|
|
return result
|
|
|
|
def remove_by_command(self, command: Union[str, dict]):
|
|
"""Remove by shortcut"""
|
|
url = LOShortCuts.get_url_script(command)
|
|
self._config.removeCommandFromAllKeyEvents(url)
|
|
return
|
|
|
|
def reset(self):
|
|
"""Reset configuration"""
|
|
self._config.reset()
|
|
self._config.store()
|
|
return
|
|
|
|
|
|
class LOMenuDebug():
|
|
"""Classe for debug info menu"""
|
|
|
|
@classmethod
|
|
def _get_info(cls, menu, index):
|
|
"""Get every option menu"""
|
|
line = f"({index}) {menu.get('CommandURL', '----------')}"
|
|
submenu = menu.get('ItemDescriptorContainer', None)
|
|
if not submenu is None:
|
|
line += cls._get_submenus(submenu)
|
|
return line
|
|
|
|
@classmethod
|
|
def _get_submenus(cls, menu, level=1):
|
|
"""Get submenus"""
|
|
line = ''
|
|
for i, v in enumerate(menu):
|
|
data = data_to_dict(v)
|
|
cmd = data.get('CommandURL', '----------')
|
|
line += f'\n{" " * level}āā ({i}) {cmd}'
|
|
submenu = data.get('ItemDescriptorContainer', None)
|
|
if not submenu is None:
|
|
line += cls._get_submenus(submenu, level + 1)
|
|
return line
|
|
|
|
def __call__(cls, menu):
|
|
for i, m in enumerate(menu):
|
|
data = data_to_dict(m)
|
|
print(cls._get_info(data, i))
|
|
return
|
|
|
|
|
|
class LOMenuBase():
|
|
"""Classe base for menus"""
|
|
NODE = 'private:resource/menubar/menubar'
|
|
config = None
|
|
menus = None
|
|
app = ''
|
|
|
|
@classmethod
|
|
def _get_index(cls, parent: Any, name: Union[int, str]=''):
|
|
"""Get index menu from name
|
|
|
|
:param parent: Menu parent
|
|
:type parent: pyUno
|
|
:param name: Menu name for search if is str
|
|
:type name: int or str
|
|
:return: Index of menu
|
|
:rtype: int
|
|
"""
|
|
index = None
|
|
if isinstance(name, str) and name:
|
|
for i, m in enumerate(parent):
|
|
menu = data_to_dict(m)
|
|
if menu.get('CommandURL', '') == name:
|
|
index = i
|
|
break
|
|
elif isinstance(name, str):
|
|
index = len(parent) - 1
|
|
elif isinstance(name, int):
|
|
index = name
|
|
return index
|
|
|
|
@classmethod
|
|
def _get_command_url(cls, menu: dict):
|
|
"""Get url from command and set shortcut
|
|
|
|
:param menu: Menu data
|
|
:type menu: dict
|
|
:return: URL command
|
|
:rtype: str
|
|
"""
|
|
shortcut = menu.pop('ShortCut', '')
|
|
command = menu['CommandURL']
|
|
url = LOShortCuts.get_url_script(command)
|
|
if shortcut:
|
|
LOShortCuts(cls.app).set(shortcut, command)
|
|
return url
|
|
|
|
@classmethod
|
|
def _save(cls, parent: Any, menu: dict, index: int):
|
|
"""Insert menu
|
|
|
|
:param parent: Menu parent
|
|
:type parent: pyUno
|
|
:param menu: New menu data
|
|
:type menu: dict
|
|
:param index: Position to insert
|
|
:type index: int
|
|
"""
|
|
# ~ Some day
|
|
# ~ self._menus.insertByIndex(index, new_menu)
|
|
properties = dict_to_property(menu, True)
|
|
uno.invoke(parent, 'insertByIndex', (index, properties))
|
|
cls.config.replaceSettings(cls.NODE, cls.menus)
|
|
return
|
|
|
|
@classmethod
|
|
def _insert_submenu(cls, parent: Any, menus: list):
|
|
"""Insert submenus recursively
|
|
|
|
:param parent: Menu parent
|
|
:type parent: pyUno
|
|
:param menus: List of menus
|
|
:type menus: list
|
|
"""
|
|
for i, menu in enumerate(menus):
|
|
submenu = menu.pop('Submenu', False)
|
|
if submenu:
|
|
idc = cls.config.createSettings()
|
|
menu['ItemDescriptorContainer'] = idc
|
|
menu['Type'] = 0
|
|
if menu['Label'][0] == '-':
|
|
menu['Type'] = 1
|
|
else:
|
|
menu['CommandURL'] = cls._get_command_url(menu)
|
|
cls._save(parent, menu, i)
|
|
if submenu:
|
|
cls._insert_submenu(idc, submenu)
|
|
return
|
|
|
|
@classmethod
|
|
def _get_first_command(cls, command):
|
|
url = command
|
|
if isinstance(command, dict):
|
|
url = Macro.get_url_script(command)
|
|
return url
|
|
|
|
@classmethod
|
|
def insert(cls, parent: Any, menu: dict, after: Union[int, str]=''):
|
|
"""Insert new menu
|
|
|
|
:param parent: Menu parent
|
|
:type parent: pyUno
|
|
:param menu: New menu data
|
|
:type menu: dict
|
|
:param after: After menu insert
|
|
:type after: int or str
|
|
"""
|
|
index = cls._get_index(parent, after) + 1
|
|
submenu = menu.pop('Submenu', False)
|
|
menu['Type'] = 0
|
|
idc = cls.config.createSettings()
|
|
menu['ItemDescriptorContainer'] = idc
|
|
menu['CommandURL'] = cls._get_first_command(menu['CommandURL'])
|
|
cls._save(parent, menu, index)
|
|
if submenu:
|
|
cls._insert_submenu(idc, submenu)
|
|
return
|
|
|
|
@classmethod
|
|
def remove(cls, parent: Any, name: Union[str, dict]):
|
|
"""Remove name in parent
|
|
|
|
:param parent: Menu parent
|
|
:type parent: pyUno
|
|
:param menu: Menu name
|
|
:type menu: str
|
|
"""
|
|
if isinstance(name, dict):
|
|
name = Macro.get_url_script(name)
|
|
index = cls._get_index(parent, name)
|
|
if index is None:
|
|
debug(f'Not found: {name}')
|
|
return
|
|
uno.invoke(parent, 'removeByIndex', (index,))
|
|
cls.config.replaceSettings(cls.NODE, cls.menus)
|
|
cls.config.store()
|
|
return
|
|
|
|
|
|
class LOMenu(object):
|
|
"""Classe for individual menu"""
|
|
|
|
def __init__(self, config: Any, menus: Any, app: str, menu: Any):
|
|
"""
|
|
:param config: Configuration Mananer
|
|
:type config: pyUno
|
|
:param menus: Menu bar main
|
|
:type menus: pyUno
|
|
:param app: Name LibreOffice module
|
|
:type app: str
|
|
:para menu: Particular menu
|
|
:type menu: pyUno
|
|
"""
|
|
self._config = config
|
|
self._menus = menus
|
|
self._app = app
|
|
self._parent = menu
|
|
|
|
def __contains__(self, name):
|
|
"""If exists name in menu"""
|
|
exists = False
|
|
for m in self._parent:
|
|
menu = data_to_dict(m)
|
|
cmd = menu.get('CommandURL', '')
|
|
if name == cmd:
|
|
exists = True
|
|
break
|
|
return exists
|
|
|
|
def __getitem__(self, index):
|
|
"""Index access"""
|
|
if isinstance(index, int):
|
|
menu = data_to_dict(self._parent[index])
|
|
else:
|
|
for m in self._parent:
|
|
menu = data_to_dict(m)
|
|
cmd = menu.get('CommandURL', '')
|
|
if cmd == index:
|
|
break
|
|
|
|
obj = LOMenu(self._config, self._menus, self._app,
|
|
menu['ItemDescriptorContainer'])
|
|
return obj
|
|
|
|
def debug(self):
|
|
"""Debug menu"""
|
|
LOMenuDebug()(self._parent)
|
|
return
|
|
|
|
def insert(self, menu: dict, after: Union[int, str]='', save: bool=True):
|
|
"""Insert new menu
|
|
|
|
:param menu: New menu data
|
|
:type menu: dict
|
|
:param after: Insert in after menu
|
|
:type after: int or str
|
|
:param save: For persistente save
|
|
:type save: bool
|
|
"""
|
|
LOMenuBase.config = self._config
|
|
LOMenuBase.menus = self._menus
|
|
LOMenuBase.app = self._app
|
|
LOMenuBase.insert(self._parent, menu, after)
|
|
if save:
|
|
self._config.store()
|
|
return
|
|
|
|
def remove(self, menu: str):
|
|
"""Remove menu
|
|
|
|
:param menu: Menu name
|
|
:type menu: str
|
|
"""
|
|
LOMenuBase.config = self._config
|
|
LOMenuBase.menus = self._menus
|
|
LOMenuBase.remove(self._parent, menu)
|
|
return
|
|
|
|
|
|
class LOMenuApp(object):
|
|
"""Classe for manager menu by LibreOffice module"""
|
|
NODE = 'private:resource/menubar/menubar'
|
|
MENUS = {
|
|
'file': '.uno:PickList',
|
|
'picklist': '.uno:PickList',
|
|
'tools': '.uno:ToolsMenu',
|
|
'help': '.uno:HelpMenu',
|
|
'window': '.uno:WindowList',
|
|
'edit': '.uno:EditMenu',
|
|
'view': '.uno:ViewMenu',
|
|
'insert': '.uno:InsertMenu',
|
|
'format': '.uno:FormatMenu',
|
|
'styles': '.uno:FormatStylesMenu',
|
|
'formatstyles': '.uno:FormatStylesMenu',
|
|
'sheet': '.uno:SheetMenu',
|
|
'data': '.uno:DataMenu',
|
|
'table': '.uno:TableMenu',
|
|
'formatform': '.uno:FormatFormMenu',
|
|
'page': '.uno:PageMenu',
|
|
'shape': '.uno:ShapeMenu',
|
|
'slide': '.uno:SlideMenu',
|
|
'slideshow': '.uno:SlideShowMenu',
|
|
}
|
|
|
|
def __init__(self, app: str):
|
|
"""
|
|
:param app: LibreOffice Module: calc, writer, draw, impress, math, main
|
|
:type app: str
|
|
"""
|
|
self._app = app
|
|
self._config = self._get_config()
|
|
self._menus = self._config.getSettings(self.NODE, True)
|
|
|
|
def _get_config(self):
|
|
"""Get config manager"""
|
|
service = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier'
|
|
type_app = LODocuments.TYPES[self._app]
|
|
manager = create_instance(service, True)
|
|
config = manager.getUIConfigurationManager(type_app)
|
|
return config
|
|
|
|
def debug(self):
|
|
"""Debug menu"""
|
|
LOMenuDebug()(self._menus)
|
|
return
|
|
|
|
def __contains__(self, name):
|
|
"""If exists name in menu"""
|
|
exists = False
|
|
for m in self._menus:
|
|
menu = data_to_dict(m)
|
|
cmd = menu.get('CommandURL', '')
|
|
if name == cmd:
|
|
exists = True
|
|
break
|
|
return exists
|
|
|
|
def __getitem__(self, index):
|
|
"""Index access"""
|
|
if isinstance(index, int):
|
|
menu = data_to_dict(self._menus[index])
|
|
else:
|
|
for m in self._menus:
|
|
menu = data_to_dict(m)
|
|
cmd = menu.get('CommandURL', '')
|
|
if cmd == index or cmd == self.MENUS[index.lower()]:
|
|
break
|
|
|
|
obj = LOMenu(self._config, self._menus, self._app,
|
|
menu['ItemDescriptorContainer'])
|
|
return obj
|
|
|
|
def insert(self, menu: dict, after: Union[int, str]='', save: bool=True):
|
|
"""Insert new menu
|
|
|
|
:param menu: New menu data
|
|
:type menu: dict
|
|
:param after: Insert in after menu
|
|
:type after: int or str
|
|
:param save: For persistente save
|
|
:type save: bool
|
|
"""
|
|
LOMenuBase.config = self._config
|
|
LOMenuBase.menus = self._menus
|
|
LOMenuBase.app = self._app
|
|
LOMenuBase.insert(self._menus, menu, after)
|
|
if save:
|
|
self._config.store()
|
|
return
|
|
|
|
def remove(self, menu: str):
|
|
"""Remove menu
|
|
|
|
:param menu: Menu name
|
|
:type menu: str
|
|
"""
|
|
LOMenuBase.config = self._config
|
|
LOMenuBase.menus = self._menus
|
|
LOMenuBase.remove(self._menus, menu)
|
|
return
|
|
|
|
|
|
class LOMenus(object):
|
|
"""Classe for manager menus"""
|
|
|
|
def __getitem__(self, index):
|
|
"""Index access"""
|
|
return LOMenuApp(index)
|
|
|
|
|
|
class LOEvents():
|
|
|
|
def __init__(self, obj):
|
|
self._obj = obj
|
|
|
|
def __contains__(self, item):
|
|
return self.obj.hasByName(item)
|
|
|
|
def __getitem__(self, index):
|
|
"""Index access"""
|
|
return self.obj.getByName(index)
|
|
|
|
def __setitem__(self, name: str, macro: dict):
|
|
"""Set macro to event
|
|
|
|
:param name: Event name
|
|
:type name: str
|
|
:param macro: Macro execute in event
|
|
:type name: dict
|
|
"""
|
|
pv = '[]com.sun.star.beans.PropertyValue'
|
|
args = ()
|
|
if macro:
|
|
url = Macro.get_url_script(macro)
|
|
args = dict_to_property(dict(EventType='Script', Script=url))
|
|
uno.invoke(self.obj, 'replaceByName', (name, uno.Any(pv, args)))
|
|
|
|
@property
|
|
def obj(self):
|
|
return self._obj
|
|
|
|
@property
|
|
def names(self):
|
|
return self.obj.ElementNames
|
|
|
|
def remove(self, name):
|
|
pv = '[]com.sun.star.beans.PropertyValue'
|
|
uno.invoke(self.obj, 'replaceByName', (name, uno.Any(pv, ())))
|
|
return
|
|
|
|
|
|
class LOMain():
|
|
"""Classe for LibreOffice"""
|
|
|
|
class commands():
|
|
"""Class for disable and enable commands
|
|
|
|
`See DispatchCommands <https://wiki.documentfoundation.org/Development/DispatchCommands>`_
|
|
"""
|
|
@classmethod
|
|
def _set_app_command(cls, command: str, disable: bool):
|
|
"""Disable or enabled UNO command
|
|
|
|
:param command: UNO command to disable or enabled
|
|
:type command: str
|
|
:param disable: True if disable, False if active
|
|
:type disable: bool
|
|
:return: True if correctly update, False if not.
|
|
:rtype: bool
|
|
"""
|
|
NEW_NODE_NAME = f'zaz_disable_command_{command.lower()}'
|
|
name = 'com.sun.star.configuration.ConfigurationProvider'
|
|
service = 'com.sun.star.configuration.ConfigurationUpdateAccess'
|
|
node_name = '/org.openoffice.Office.Commands/Execute/Disabled'
|
|
|
|
cp = create_instance(name, True)
|
|
node = PropertyValue(Name='nodepath', Value=node_name)
|
|
update = cp.createInstanceWithArguments(service, (node,))
|
|
|
|
result = True
|
|
try:
|
|
if disable:
|
|
new_node = update.createInstanceWithArguments(())
|
|
new_node.setPropertyValue('Command', command)
|
|
update.insertByName(NEW_NODE_NAME, new_node)
|
|
else:
|
|
update.removeByName(NEW_NODE_NAME)
|
|
update.commitChanges()
|
|
except Exception as e:
|
|
result = False
|
|
|
|
return result
|
|
|
|
@classmethod
|
|
def disable(cls, command: str):
|
|
"""Disable UNO command
|
|
|
|
:param command: UNO command to disable
|
|
:type command: str
|
|
:return: True if correctly disable, False if not.
|
|
:rtype: bool
|
|
"""
|
|
return cls._set_app_command(command, True)
|
|
|
|
@classmethod
|
|
def enabled(cls, command):
|
|
"""Enabled UNO command
|
|
|
|
:param command: UNO command to enabled
|
|
:type command: str
|
|
:return: True if correctly disable, False if not.
|
|
:rtype: bool
|
|
"""
|
|
return cls._set_app_command(command, False)
|
|
|
|
@classproperty
|
|
def cmd(cls):
|
|
"""Disable or enable commands"""
|
|
return cls.commands
|
|
|
|
@classproperty
|
|
def desktop(cls):
|
|
"""Create desktop instance
|
|
|
|
:return: Desktop instance
|
|
:rtype: pyUno
|
|
"""
|
|
obj = create_instance('com.sun.star.frame.Desktop', True)
|
|
return obj
|
|
|
|
|
|
class LODocument():
|
|
|
|
def __init__(self, obj):
|
|
self._obj = obj
|
|
self._cc = obj.getCurrentController()
|
|
self._undo = True
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
self.close()
|
|
|
|
@property
|
|
def obj(self):
|
|
"""Return original pyUno object"""
|
|
return self._obj
|
|
|
|
@property
|
|
def type(self):
|
|
"""Get type document"""
|
|
return self._type
|
|
|
|
@property
|
|
def title(self):
|
|
"""Get title document"""
|
|
return self.obj.getTitle()
|
|
@title.setter
|
|
def title(self, value):
|
|
self.obj.setTitle(value)
|
|
|
|
@property
|
|
def uid(self):
|
|
"""Get Runtime UID"""
|
|
return self.obj.RuntimeUID
|
|
|
|
@property
|
|
def is_saved(self):
|
|
"""Get is saved"""
|
|
return self.obj.hasLocation()
|
|
|
|
@property
|
|
def is_modified(self):
|
|
"""Get is modified"""
|
|
return self.obj.isModified()
|
|
|
|
@property
|
|
def is_read_only(self):
|
|
"""Get is read only"""
|
|
return self.obj.isReadonly()
|
|
|
|
@property
|
|
def path(self):
|
|
"""Get path in system files"""
|
|
return Paths.to_system(self.obj.URL)
|
|
|
|
@property
|
|
def dir(self):
|
|
"""Get directory from path"""
|
|
return Paths(self.path).path
|
|
|
|
@property
|
|
def file_name(self):
|
|
"""Get only file name"""
|
|
return Paths(self.path).file_name
|
|
|
|
@property
|
|
def name(self):
|
|
"""Get name without extension"""
|
|
return Paths(self.path).name
|
|
|
|
@property
|
|
def visible(self):
|
|
"""Get windows visible"""
|
|
w = self.frame.ContainerWindow
|
|
return w.isVisible()
|
|
@visible.setter
|
|
def visible(self, value):
|
|
w = self.frame.ContainerWindow
|
|
w.setVisible(value)
|
|
|
|
@property
|
|
def zoom(self):
|
|
"""Get current zoom value"""
|
|
return self._cc.ZoomValue
|
|
@zoom.setter
|
|
def zoom(self, value):
|
|
self._cc.ZoomValue = value
|
|
|
|
@property
|
|
def status_bar(self):
|
|
"""Get status bar"""
|
|
bar = self._cc.getStatusIndicator()
|
|
return bar
|
|
|
|
@property
|
|
def selection(self):
|
|
"""Get current selecction"""
|
|
sel = self.obj.CurrentSelection
|
|
return sel
|
|
|
|
@property
|
|
def table_auto_formats(self):
|
|
taf = create_instance('com.sun.star.sheet.TableAutoFormats')
|
|
return taf.ElementNames
|
|
|
|
def save(self, path: str='', args: dict={}) -> bool:
|
|
"""Save document
|
|
|
|
:param path: Path to save document
|
|
:type path: str
|
|
:param args: Optional: Extra argument for save
|
|
:type args: dict
|
|
:return: True if save correctly, False if not
|
|
:rtype: bool
|
|
"""
|
|
if not path:
|
|
self.obj.store()
|
|
return True
|
|
|
|
path_save = Paths.to_url(path)
|
|
opt = dict_to_property(args)
|
|
|
|
try:
|
|
self.obj.storeAsURL(path_save, opt)
|
|
except Exception as e:
|
|
error(e)
|
|
return False
|
|
|
|
return True
|
|
|
|
def close(self):
|
|
"""Close document"""
|
|
self.obj.close(True)
|
|
return
|
|
|
|
def to_pdf(self, path: str='', args: dict={}):
|
|
"""Export to PDF
|
|
|
|
:param path: Path to export document
|
|
:type path: str
|
|
:param args: Optional: Extra argument for export
|
|
:type args: dict
|
|
:return: None if path or stream in memory
|
|
:rtype: bytes or None
|
|
|
|
`See PDF Export <https://wiki.documentfoundation.org/Macros/Python_Guide/PDF_export_filter_data>`_
|
|
"""
|
|
stream = None
|
|
path_pdf = 'private:stream'
|
|
|
|
filter_name = f'{self.type}_pdf_Export'
|
|
filter_data = dict_to_property(args, True)
|
|
filters = {
|
|
'FilterName': filter_name,
|
|
'FilterData': filter_data,
|
|
}
|
|
if path:
|
|
path_pdf = Paths.to_url(path)
|
|
else:
|
|
stream = IOStream.output()
|
|
filters['OutputStream'] = stream
|
|
|
|
opt = dict_to_property(filters)
|
|
try:
|
|
self.obj.storeToURL(path_pdf, opt)
|
|
except Exception as e:
|
|
error(e)
|
|
|
|
if not stream is None:
|
|
stream = stream.buffer
|
|
|
|
return stream
|
|
|
|
def export(self, path: str='', filter_name: str='', args: dict={}):
|
|
"""Export to others formats
|
|
|
|
:param path: Path to export document
|
|
:type path: str
|
|
:param filter_name: Filter name to export
|
|
:type filter_name: str
|
|
:param args: Optional: Extra argument for export
|
|
:type args: dict
|
|
:return: None if path or stream in memory
|
|
:rtype: bytes or None
|
|
"""
|
|
FILTERS = {
|
|
'xlsx': 'Calc MS Excel 2007 XML',
|
|
'xls': 'MS Excel 97',
|
|
'docx': 'MS Word 2007 XML',
|
|
'doc': 'MS Word 97',
|
|
'rtf': 'Rich Text Format',
|
|
}
|
|
|
|
stream = None
|
|
path_target = 'private:stream'
|
|
|
|
filter_name = FILTERS.get(filter_name, filter_name)
|
|
filter_data = dict_to_property(args, True)
|
|
filters = {
|
|
'FilterName': filter_name,
|
|
'FilterData': filter_data,
|
|
}
|
|
if path:
|
|
path_target = Paths.to_url(path)
|
|
else:
|
|
stream = IOStream.output()
|
|
filters['OutputStream'] = stream
|
|
|
|
opt = dict_to_property(filters)
|
|
try:
|
|
self.obj.storeToURL(path_target, opt)
|
|
except Exception as e:
|
|
error(e)
|
|
|
|
if not stream is None:
|
|
stream = stream.buffer
|
|
|
|
return stream
|
|
|
|
def set_focus(self):
|
|
"""Send focus to windows"""
|
|
w = self.frame.ComponentWindow
|
|
w.setFocus()
|
|
return
|
|
|
|
def copy(self):
|
|
"""Copy current selection"""
|
|
LOMain.dispatch(self.frame, 'Copy')
|
|
return
|
|
|
|
def paste(self):
|
|
"""Paste current content in clipboard"""
|
|
sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard')
|
|
transferable = sc.getContents()
|
|
self._cc.insertTransferable(transferable)
|
|
return
|
|
|
|
def paste_special(self):
|
|
"""Insert contents, show dialog box Paste Special"""
|
|
LOMain.dispatch(self.frame, 'InsertContents')
|
|
return
|
|
|
|
def paste_values(self):
|
|
"""Paste only values"""
|
|
args = {
|
|
'Flags': 'SVDT',
|
|
# ~ 'FormulaCommand': 0,
|
|
# ~ 'SkipEmptyCells': False,
|
|
# ~ 'Transpose': False,
|
|
# ~ 'AsLink': False,
|
|
# ~ 'MoveMode': 4,
|
|
}
|
|
LOMain.dispatch(self.frame, 'InsertContents', args)
|
|
return
|
|
|
|
def clear_undo(self):
|
|
"""Clear history undo"""
|
|
self.obj.getUndoManager().clear()
|
|
return
|
|
|
|
|
|
class LODocMain():
|
|
"""Classe for start module"""
|
|
_type = 'main'
|
|
|
|
def __init__(self, obj):
|
|
self._obj = obj
|
|
|
|
@property
|
|
def obj(self):
|
|
return self._obj
|
|
|
|
@property
|
|
def type(self):
|
|
return self._type
|
|
|
|
|
|
class LOCellStyle():
|
|
|
|
def __init__(self, obj):
|
|
self._obj = obj
|
|
|
|
def __str__(self):
|
|
return f'CellStyle: {self.name}'
|
|
|
|
@property
|
|
def obj(self):
|
|
return self._obj
|
|
|
|
@property
|
|
def name(self):
|
|
return self.obj.Name
|
|
|
|
@property
|
|
def properties(self):
|
|
properties = self.obj.PropertySetInfo.Properties
|
|
data = {p.Name: getattr(self.obj, p.Name) for p in properties}
|
|
return data
|
|
@properties.setter
|
|
def properties(self, values):
|
|
_set_properties(self.obj, values)
|
|
|
|
|
|
class LOCellStyles():
|
|
|
|
def __init__(self, obj, doc):
|
|
self._obj = obj
|
|
self._doc = doc
|
|
|
|
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 new(self, name: str):
|
|
obj = self._doc.create_instance('com.sun.star.style.CellStyle')
|
|
self.obj[name] = obj
|
|
return LOCellStyle(obj)
|
|
|
|
|
|
class LODocCalc(LODocument):
|
|
"""Classe for Calc module"""
|
|
TYPE_RANGES = ('ScCellObj', 'ScCellRangeObj')
|
|
RANGES = 'ScCellRangesObj'
|
|
SHAPE = 'com.sun.star.drawing.SvxShapeCollection'
|
|
_type = 'calc'
|
|
|
|
def __init__(self, obj):
|
|
super().__init__(obj)
|
|
self._sheets = obj.Sheets
|
|
self._listener_range_selection = None
|
|
|
|
def __getitem__(self, index):
|
|
"""Index access"""
|
|
return LOCalcSheet(self._sheets[index])
|
|
|
|
def __setitem__(self, key: str, value: Any):
|
|
"""Insert new sheet"""
|
|
self._sheets[key] = value
|
|
|
|
def __len__(self):
|
|
return self._sheets.Count
|
|
|
|
def __contains__(self, item):
|
|
return item in self._sheets
|
|
|
|
def __iter__(self):
|
|
self._i = 0
|
|
return self
|
|
|
|
def __next__(self):
|
|
try:
|
|
sheet = LOCalcSheet(self._sheets[self._i])
|
|
except Exception as e:
|
|
raise StopIteration
|
|
self._i += 1
|
|
return sheet
|
|
|
|
def __str__(self):
|
|
return f'Calc: {self.title}'
|
|
|
|
@property
|
|
def selection(self):
|
|
sel = self.obj.CurrentSelection
|
|
type_obj = sel.ImplementationName
|
|
if type_obj in self.TYPE_RANGES:
|
|
sel = LOCalcRange(sel)
|
|
elif type_obj == self.RANGES:
|
|
sel = LOCalcRanges(sel)
|
|
elif type_obj == self.SHAPE:
|
|
if len(sel) == 1:
|
|
sel = LOShape(sel[0])
|
|
else:
|
|
sel = LOShapes(sel)
|
|
else:
|
|
debug(type_obj)
|
|
return sel
|
|
|
|
@property
|
|
def headers(self):
|
|
"""Get true if is visible columns/rows headers"""
|
|
return self._cc.ColumnRowHeaders
|
|
@headers.setter
|
|
def headers(self, value):
|
|
"""Set visible columns/rows headers"""
|
|
self._cc.ColumnRowHeaders = value
|
|
|
|
@property
|
|
def tabs(self):
|
|
"""Get true if is visible tab sheets"""
|
|
return self._cc.SheetTabs
|
|
@tabs.setter
|
|
def tabs(self, value):
|
|
"""Set visible tab sheets"""
|
|
self._cc.SheetTabs = value
|
|
|
|
@property
|
|
def names(self):
|
|
"""Get all sheet names"""
|
|
names = self.obj.Sheets.ElementNames
|
|
return names
|
|
|
|
@property
|
|
def active(self):
|
|
"""Get active sheet"""
|
|
return LOCalcSheet(self._cc.ActiveSheet)
|
|
|
|
@property
|
|
def new_sheet(self):
|
|
sheet = self._create_instance('com.sun.star.sheet.Spreadsheet')
|
|
return sheet
|
|
|
|
@property
|
|
def events(self):
|
|
return LOEvents(self.obj.Events)
|
|
|
|
@property
|
|
def cs(self):
|
|
return self.cell_styles
|
|
@property
|
|
def cell_styles(self):
|
|
obj = self.obj.StyleFamilies['CellStyles']
|
|
return LOCellStyles(obj, self)
|
|
|
|
def activate(self, sheet: Any):
|
|
"""Activate sheet
|
|
|
|
:param sheet: Sheet to activate
|
|
:type sheet: str, pyUno or LOCalcSheet
|
|
"""
|
|
obj = sheet
|
|
if isinstance(sheet, LOCalcSheet):
|
|
obj = sheet.obj
|
|
elif isinstance(sheet, str):
|
|
obj = self._sheets[sheet]
|
|
self._cc.setActiveSheet(obj)
|
|
return
|
|
|
|
def remove(self, name: str):
|
|
"""Remove sheet by name
|
|
|
|
:param name: Name sheet will remove
|
|
:type name: str
|
|
"""
|
|
if isinstance(name, LOCalcSheet):
|
|
name = name.name
|
|
self._sheets.removeByName(name)
|
|
return
|
|
|
|
def move(self, name:str, pos: int=-1):
|
|
"""Move sheet name to position
|
|
|
|
:param name: Name sheet to move
|
|
:type name: str
|
|
:param pos: New position, if pos=-1 move to end
|
|
:type pos: int
|
|
"""
|
|
index = pos
|
|
if pos < 0:
|
|
index = len(self)
|
|
if isinstance(name, LOCalcSheet):
|
|
name = name.name
|
|
self._sheets.moveByName(name, index)
|
|
return
|
|
|
|
def _get_new_name_sheet(self, name):
|
|
i = 1
|
|
new_name = f'{name}_{i}'
|
|
while new_name in self:
|
|
i += 1
|
|
new_name = f'{name}_{i}'
|
|
return new_name
|
|
|
|
def copy_sheet(self, name: Any, new_name: str='', pos: int=-1):
|
|
"""Copy sheet by name
|
|
|
|
"""
|
|
if isinstance(name, LOCalcSheet):
|
|
name = name.name
|
|
index = pos
|
|
if pos < 0:
|
|
index = len(self)
|
|
if not new_name:
|
|
new_name = self._get_new_name_sheet(name)
|
|
self._sheets.copyByName(name, new_name, index)
|
|
return LOCalcSheet(self._sheets[new_name])
|
|
|
|
def copy_from(self, doc: Any, source: Any=None, target: Any=None, pos: int=-1):
|
|
"""Copy sheet from document
|
|
|
|
"""
|
|
index = pos
|
|
if pos < 0:
|
|
index = len(self)
|
|
|
|
names = source
|
|
if not source:
|
|
names = doc.names
|
|
elif isinstance(source, str):
|
|
names = (source,)
|
|
elif isinstance(source, LOCalcSheet):
|
|
names = (source.name,)
|
|
|
|
new_names = target
|
|
if not target:
|
|
new_names = names
|
|
elif isinstance(target, str):
|
|
new_names = (target,)
|
|
|
|
for i, name in enumerate(names):
|
|
self._sheets.importSheet(doc.obj, name, index + i)
|
|
self[index + i].name = new_names[i]
|
|
|
|
return LOCalcSheet(self._sheets[index])
|
|
|
|
def sort(self, reverse=False):
|
|
"""Sort sheets by name
|
|
|
|
:param reverse: For order in reverse
|
|
:type reverse: bool
|
|
"""
|
|
names = sorted(self.names, reverse=reverse)
|
|
for i, n in enumerate(names):
|
|
self.move(n, i)
|
|
return
|
|
|
|
@run_in_thread
|
|
def start_range_selection(self, controllers: Any, args: dict={}):
|
|
"""Start select range selection by user
|
|
|
|
`See Api RangeSelectionArguments <https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1sheet_1_1RangeSelectionArguments.html>`_
|
|
"""
|
|
if args:
|
|
args['CloseOnMouseRelease'] = args.get('CloseOnMouseRelease', True)
|
|
else:
|
|
args = dict(
|
|
Title = 'Please select a range',
|
|
CloseOnMouseRelease = True)
|
|
properties = dict_to_property(args)
|
|
|
|
self._listener_range_selection = EventsRangeSelectionListener(controllers(self))
|
|
self._cc.addRangeSelectionListener(self._listener_range_selection)
|
|
self._cc.startRangeSelection(properties)
|
|
return
|
|
|
|
def remove_range_selection_listener(self):
|
|
if not self._listener_range_selection is None:
|
|
self._cc.removeRangeSelectionListener(self._listener_range_selection)
|
|
return
|
|
|
|
def select(self, rango: Any):
|
|
obj = rango
|
|
if hasattr(rango, 'obj'):
|
|
obj = rango.obj
|
|
self._cc.select(obj)
|
|
return
|
|
|
|
@property
|
|
def ranges(self):
|
|
obj = self._create_instance('com.sun.star.sheet.SheetCellRanges')
|
|
return LOCalcRanges(obj)
|
|
|
|
def get_ranges(self, address: str):
|
|
ranges = self.ranges
|
|
ranges.add([sheet[address] for sheet in self])
|
|
return ranges
|
|
|
|
|
|
|
|
|
|
class LOCalcRanges(object):
|
|
|
|
def __init__(self, obj):
|
|
self._obj = obj
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
pass
|
|
|
|
def __len__(self):
|
|
return self._obj.Count
|
|
|
|
def __iter__(self):
|
|
self._index = 0
|
|
return self
|
|
|
|
def __next__(self):
|
|
try:
|
|
r = self.obj[self._index]
|
|
rango = LOCalcRange(r)
|
|
except IndexError:
|
|
raise StopIteration
|
|
|
|
self._index += 1
|
|
return rango
|
|
|
|
def __contains__(self, item):
|
|
return self._obj.hasByName(item.name)
|
|
|
|
def __getitem__(self, index):
|
|
r = self.obj[index]
|
|
rango = LOCalcRange(r)
|
|
return rango
|
|
|
|
def __str__(self):
|
|
s = f'Ranges: {self.names}'
|
|
return s
|
|
|
|
@property
|
|
def obj(self):
|
|
return self._obj
|
|
|
|
@property
|
|
def names(self):
|
|
return self.obj.ElementNames
|
|
|
|
@property
|
|
def data(self):
|
|
rows = [r.data for r in self]
|
|
return rows
|
|
@data.setter
|
|
def data(self, values):
|
|
for i, data in enumerate(values):
|
|
self[i].data = data
|
|
|
|
@property
|
|
def style(self):
|
|
return ''
|
|
@style.setter
|
|
def style(self, value):
|
|
for r in self:
|
|
r.style = value
|
|
|
|
def add(self, rangos: Any):
|
|
if isinstance(rangos, LOCalcRange):
|
|
rangos = (rangos,)
|
|
for r in rangos:
|
|
self.obj.addRangeAddress(r.range_address, False)
|
|
return
|
|
|
|
def remove(self, rangos: Any):
|
|
if isinstance(rangos, LOCalcRange):
|
|
rangos = (rangos,)
|
|
for r in rangos:
|
|
self.obj.removeRangeAddress(r.range_address)
|
|
return
|
|
|
|
|
|
class LOWriterTextRange(object):
|
|
|
|
def __init__(self, obj, doc):
|
|
self._obj = obj
|
|
self._doc = doc
|
|
|
|
@property
|
|
def obj(self):
|
|
return self._obj
|
|
|
|
@property
|
|
def text(self):
|
|
return self.obj.Text
|
|
|
|
@property
|
|
def cursor(self):
|
|
return self.text.createTextCursorByRange(self.obj)
|
|
|
|
def insert_comment(self, content: str, author: str='', dt: Any=None):
|
|
# ~ range.Text.insertTextContent(cursor, comment, False)
|
|
comment = self._doc._create_instance('com.sun.star.text.textfield.Annotation')
|
|
comment.Content = content
|
|
comment.Author = author
|
|
comment.attach(self.cursor.End)
|
|
return
|
|
|
|
|
|
class LODocWriter(LODocument):
|
|
_type = 'writer'
|
|
TEXT_RANGES = 'SwXTextRanges'
|
|
|
|
def __init__(self, obj):
|
|
super().__init__(obj)
|
|
self._view_settings = self._cc.ViewSettings
|
|
|
|
@property
|
|
def selection(self):
|
|
sel = self.obj.CurrentSelection
|
|
type_obj = sel.ImplementationName
|
|
if type_obj == self.TEXT_RANGES:
|
|
if len(sel) == 1:
|
|
sel = LOWriterTextRange(sel[0], self)
|
|
|
|
return sel
|
|
|
|
@property
|
|
def zoom(self):
|
|
return self._view_settings.ZoomValue
|
|
@zoom.setter
|
|
def zoom(self, value):
|
|
self._view_settings.ZoomValue = value
|
|
|
|
|
|
class LODocDrawImpress(LODocument):
|
|
|
|
def __init__(self, obj):
|
|
super().__init__(obj)
|
|
|
|
|
|
class LODocDraw(LODocDrawImpress):
|
|
_type = 'draw'
|
|
|
|
def __init__(self, obj):
|
|
super().__init__(obj)
|
|
|
|
|
|
class LODocImpress(LODocDrawImpress):
|
|
_type = 'impress'
|
|
|
|
def __init__(self, obj):
|
|
super().__init__(obj)
|
|
|
|
|
|
class LODocMath(LODocDrawImpress):
|
|
_type = 'math'
|
|
|
|
def __init__(self, obj):
|
|
super().__init__(obj)
|
|
|
|
|
|
class LODocBase(LODocument):
|
|
_type = 'base'
|
|
|
|
def __init__(self, obj):
|
|
super().__init__(obj)
|
|
|
|
|
|
class LODocIDE(LODocument):
|
|
_type = 'basicide'
|
|
|
|
def __init__(self, obj):
|
|
super().__init__(obj)
|
|
|
|
|
|
def __getattr__(name):
|
|
classes = {
|
|
'io': IOStream,
|
|
'clipboard': ClipBoard,
|
|
'shortcuts': LOShortCuts(),
|
|
'menus': LOMenus(),
|
|
'lo': LOMain,
|
|
'command': LOMain.cmd,
|
|
'docs': LODocuments(),
|
|
}
|
|
if name in classes:
|
|
return classes[name]
|
|
|
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
|
|
|
|
class LOServer(object):
|
|
"""Started LibeOffice like server
|
|
"""
|
|
HOST = 'localhost'
|
|
PORT = '8100'
|
|
ARG = f'socket,host={HOST},port={PORT};urp;StarOffice.ComponentContext'
|
|
CMD = ['soffice',
|
|
'-env:SingleAppInstance=false',
|
|
'-env:UserInstallation=file:///tmp/LO_Process8100',
|
|
'--headless', '--norestore', '--invisible',
|
|
f'--accept={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):
|
|
"""Stop server
|
|
"""
|
|
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
|