easymacro/source/easymacro.py

2470 lines
66 KiB
Python
Raw Normal View History

2022-02-21 23:43:58 -06:00
#!/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/>.
2022-08-15 23:57:31 -05:00
2022-02-21 23:43:58 -06:00
import getpass
2022-02-26 22:22:11 -06:00
import hashlib
2022-03-03 23:32:55 -06:00
import io
2022-08-15 23:57:31 -05:00
2022-02-21 23:43:58 -06:00
import logging
import os
import platform
2022-03-02 14:44:58 -06:00
import re
2022-02-26 22:22:11 -06:00
import shlex
2022-02-21 23:43:58 -06:00
import socket
2022-02-27 23:28:06 -06:00
import ssl
2022-08-15 23:57:31 -05:00
2022-08-16 13:21:30 -05:00
2022-08-11 22:33:41 -05:00
2022-02-27 23:28:06 -06:00
from string import Template
2022-08-11 22:33:41 -05:00
2022-02-27 23:28:06 -06:00
from socket import timeout
from urllib import parse
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
2022-02-21 23:43:58 -06:00
from com.sun.star.awt import Rectangle, Size, Point
2022-03-04 22:08:16 -06:00
from com.sun.star.awt import Key, KeyEvent, KeyModifier
2022-08-11 22:33:41 -05:00
2022-03-03 23:45:19 -06:00
from com.sun.star.datatransfer import XTransferable, DataFlavor
2022-03-03 23:32:55 -06:00
from com.sun.star.io import IOException, XOutputStream
2022-08-15 23:57:31 -05:00
2022-03-09 13:18:40 -06:00
from com.sun.star.sheet import XRangeSelectionListener
2022-08-15 23:57:31 -05:00
2022-03-04 22:08:16 -06:00
from com.sun.star.container import NoSuchElementException
2022-02-24 23:59:04 -06:00
2022-02-23 22:49:43 -06:00
2022-02-21 23:43:58 -06:00
SALT = b'00a1bfb05353bb3fd8e7aa7fe5efdccc'
2022-02-26 22:22:11 -06:00
_EVENTS = {}
2022-02-27 23:28:06 -06:00
FILES = {
'CONFIG': 'zaz-{}.json',
}
DIRS = {}
2022-02-24 23:59:04 -06:00
2022-02-26 00:11:41 -06:00
def run_in_thread(fn):
"""Run any function in thread
:param fn: Any Python function (macro)
:type fn: Function instance
"""
def run(*k, **kw):
t = threading.Thread(target=fn, args=k, kwargs=kw)
t.start()
return t
return run
def _property_to_dict(values):
d = {v.Name: v.Value for v in values}
return d
def data_to_dict(data) -> dict:
"""Convert tuples, list, PropertyValue, NamedValue to dictionary
:param data: Dictionary of values
:type data: array of tuples, list, PropertyValue or NamedValue
:return: Dictionary
:rtype: dict
"""
d = {}
if not isinstance(data, (tuple, list)):
return d
if isinstance(data[0], (tuple, list)):
d = {r[0]: r[1] for r in data}
elif isinstance(data[0], (PropertyValue, NamedValue)):
d = _property_to_dict(data)
return d
2022-02-27 23:28:06 -06:00
def render(template, data):
s = Template(template)
return s.safe_substitute(**data)
# Classes
2022-03-08 18:23:54 -06:00
2022-02-26 00:11:41 -06:00
class Json(object):
"""Class for json data
"""
@classmethod
def dumps(cls, data: Any) -> str:
"""Dumps
:param data: Any data
:type data: Any
:return: Return string json
:rtype: str
"""
return json.dumps(data, indent=4, sort_keys=True)
@classmethod
def loads(cls, data: str) -> Any:
"""Loads
:param data: String data
:type data: str
:return: Return any object
:rtype: Any
"""
return json.loads(data)
class Macro(object):
"""Class for call macro
`See Scripting Framework <https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification>`_
"""
@classmethod
def call(cls, args: dict, in_thread: bool=False):
"""Call any macro
:param args: Dictionary with macro location
:type args: dict
:param in_thread: If execute in thread
:type in_thread: bool
:return: Return None or result of call macro
:rtype: Any
"""
result = None
if in_thread:
t = threading.Thread(target=cls._call, args=(args,))
t.start()
else:
result = cls._call(args)
return result
@classmethod
2022-03-04 22:08:16 -06:00
def get_url_script(cls, args: dict):
2022-02-26 00:11:41 -06:00
library = args['library']
name = args['name']
language = args.get('language', 'Python')
location = args.get('location', 'user')
module = args.get('module', '.')
if language == 'Python':
module = '.py$'
elif language == 'Basic':
module = f".{module}."
if location == 'user':
location = 'application'
url = 'vnd.sun.star.script'
url = f'{url}:{library}{module}{name}?language={language}&location={location}'
return url
@classmethod
def _call(cls, args: dict):
2022-03-04 22:08:16 -06:00
url = cls.get_url_script(args)
2022-02-26 00:11:41 -06:00
args = args.get('args', ())
service = 'com.sun.star.script.provider.MasterScriptProviderFactory'
factory = create_instance(service)
script = factory.createScriptProvider('').getScript(url)
result = script.invoke(args, None, None)[0]
return result
2022-02-26 22:22:11 -06:00
class Shell(object):
"""Class for subprocess
`See Subprocess <https://docs.python.org/3.7/library/subprocess.html>`_
"""
@classmethod
def run(cls, command, capture=False, split=False):
"""Execute commands
:param command: Command to run
:type command: str
:param capture: If capture result of command
:type capture: bool
:param split: Some commands need split.
:type split: bool
:return: Result of command
:rtype: Any
"""
if split:
cmd = shlex.split(command)
result = subprocess.run(cmd, capture_output=capture, text=True, shell=IS_WIN)
if capture:
result = result.stdout
else:
result = result.returncode
else:
if capture:
result = subprocess.check_output(command, shell=True).decode()
else:
result = subprocess.Popen(command)
return result
@classmethod
def popen(cls, command):
"""Execute commands and return line by line
:param command: Command to run
:type command: str
:return: Result of command
:rtype: Any
"""
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)
class Timer(object):
"""Class for timer thread"""
class TimerThread(threading.Thread):
def __init__(self, event, seconds, macro):
threading.Thread.__init__(self)
self._event = event
self._seconds = seconds
self._macro = macro
def run(self):
while not self._event.wait(self._seconds):
Macro.call(self._macro)
info('\tTimer stopped... ')
return
@classmethod
def exists(cls, name):
"""Validate in timer **name** exists
:param name: Timer name, it must be unique
:type name: str
:return: True if exists timer name
:rtype: bool
"""
global _EVENTS
return name in _EVENTS
@classmethod
def start(cls, name: str, seconds: float, macro: dict):
"""Start timer **name** every **seconds** and execute **macro**
:param name: Timer name, it must be unique
:type name: str
:param seconds: Seconds for wait
:type seconds: float
:param macro: Macro for execute
:type macro: dict
"""
global _EVENTS
_EVENTS[name] = threading.Event()
info(f"Timer '{name}' started, execute macro: '{macro['name']}'")
thread = cls.TimerThread(_EVENTS[name], seconds, macro)
thread.start()
return
@classmethod
def stop(cls, name: str):
"""Stop timer **name**
:param name: Timer name
:type name: str
"""
global _EVENTS
_EVENTS[name].set()
del _EVENTS[name]
return
@classmethod
def once(cls, name: str, seconds: float, macro: dict):
"""Start timer **name** only once in **seconds** and execute **macro**
:param name: Timer name, it must be unique
:type name: str
:param seconds: Seconds for wait before execute macro
:type seconds: float
:param macro: Macro for execute
:type macro: dict
"""
global _EVENTS
_EVENTS[name] = threading.Timer(seconds, Macro.call, (macro,))
_EVENTS[name].start()
info(f'Event: "{name}", started... execute in {seconds} seconds')
return
@classmethod
def cancel(cls, name: str):
"""Cancel timer **name** only once events.
:param name: Timer name, it must be unique
:type name: str
"""
global _EVENTS
if name in _EVENTS:
try:
_EVENTS[name].cancel()
del _EVENTS[name]
info(f'Cancel event: "{name}", ok...')
except Exception as e:
error(e)
else:
debug(f'Cancel event: "{name}", not exists...')
return
class Hash(object):
"""Class for hash
"""
@classmethod
def digest(cls, method: str, data: str, in_hex: bool=True):
"""Get digest from data with method
:param method: Digest method: md5, sha1, sha256, sha512, etc...
:type method: str
:param data: Data for get digest
:type data: str
:param in_hex: If True, get digest in hexadecimal, if False, get bytes
:type in_hex: bool
:return: bytes or hex digest
:rtype: bytes or str
"""
result = ''
obj = getattr(hashlib, method)(data.encode())
if in_hex:
result = obj.hexdigest()
else:
result = obj.digest()
return result
2022-02-27 23:28:06 -06:00
class Config(object):
"""Class for set and get configurations
"""
@classmethod
def set(cls, prefix: str, value: Any, key: str='') -> bool:
"""Save data config in user config like json
:param prefix: Unique prefix for this data
:type prefix: str
:param value: Value for save
:type value: Any
:param key: Key for value
:type key: str
:return: True if save correctly
:rtype: bool
"""
name_file = FILES['CONFIG'].format(prefix)
path = Paths.join(Paths.user_config, name_file)
data = value
if key:
data = cls.get(prefix)
data[key] = value
result = Paths.to_json(path, data)
return result
@classmethod
def get(cls, prefix: str, key: str='', default: Any={}) -> Any:
"""Get data config from user config like json
:param prefix: Unique prefix for this data
:type prefix: str
:param key: Key for value
:type key: str
:param default: Get if not exists key
:type default: Any
:return: data
:rtype: Any
"""
data = {}
name_file = FILES['CONFIG'].format(prefix)
path = Paths.join(Paths.user_config, name_file)
if not Paths.exists(path):
return data
data = Paths.from_json(path)
if key:
data = data.get(key, default)
return data
class Url(object):
"""Class for simple url open
"""
@classmethod
def _open(cls, url: str, data: Any=None, headers: dict={}, verify: bool=True, \
json: bool=False, timeout: int=TIMEOUT, method: str='GET') -> tuple:
"""URL Open"""
debug(url)
result = None
context = None
rheaders = {}
err = ''
if verify:
2022-08-03 22:22:27 -05:00
if not data is None:
if isinstance(data, str):
data = data.encode()
elif isinstance(data, dict):
data = parse.urlencode(data).encode('ascii')
2022-02-27 23:28:06 -06:00
else:
context = ssl._create_unverified_context()
try:
req = Request(url, data=data, headers=headers, method=method)
response = urlopen(req, timeout=timeout, context=context)
except HTTPError as e:
error(e)
err = str(e)
except URLError as e:
error(e.reason)
err = str(e.reason)
# ToDo
# ~ except timeout:
# ~ err = 'timeout'
# ~ error(err)
else:
rheaders = dict(response.info())
result = response.read().decode()
if json:
result = Json.loads(result)
return result, rheaders, err
@classmethod
def get(cls, url: str, data: Any=None, headers: dict={}, verify: bool=True, \
json: bool=False, timeout: int=TIMEOUT) -> tuple:
"""Method GET
:param url: Url to open
:type url: str
:return: result, headers and error
:rtype: tuple
"""
return cls._open(url, data, headers, verify, json, timeout)
# ToDo
@classmethod
2022-08-03 22:22:27 -05:00
def post(cls, url: str, data: Any=None, headers: dict={}, verify: bool=True, \
2022-02-27 23:28:06 -06:00
json: bool=False, timeout: int=TIMEOUT) -> tuple:
"""Method POST
"""
data = parse.urlencode(data).encode('ascii')
return cls._open(url, data, headers, verify, json, timeout, 'POST')
class Color(object):
"""Class for colors
`See Web Colors <https://en.wikipedia.org/wiki/Web_colors>`_
"""
COLORS = {
'aliceblue': 15792383,
'antiquewhite': 16444375,
'aqua': 65535,
'aquamarine': 8388564,
'azure': 15794175,
'beige': 16119260,
'bisque': 16770244,
'black': 0,
'blanchedalmond': 16772045,
'blue': 255,
'blueviolet': 9055202,
'brown': 10824234,
'burlywood': 14596231,
'cadetblue': 6266528,
'chartreuse': 8388352,
'chocolate': 13789470,
'coral': 16744272,
'cornflowerblue': 6591981,
'cornsilk': 16775388,
'crimson': 14423100,
'cyan': 65535,
'darkblue': 139,
'darkcyan': 35723,
'darkgoldenrod': 12092939,
'darkgray': 11119017,
'darkgreen': 25600,
'darkgrey': 11119017,
'darkkhaki': 12433259,
'darkmagenta': 9109643,
'darkolivegreen': 5597999,
'darkorange': 16747520,
'darkorchid': 10040012,
'darkred': 9109504,
'darksalmon': 15308410,
'darkseagreen': 9419919,
'darkslateblue': 4734347,
'darkslategray': 3100495,
'darkslategrey': 3100495,
'darkturquoise': 52945,
'darkviolet': 9699539,
'deeppink': 16716947,
'deepskyblue': 49151,
'dimgray': 6908265,
'dimgrey': 6908265,
'dodgerblue': 2003199,
'firebrick': 11674146,
'floralwhite': 16775920,
'forestgreen': 2263842,
'fuchsia': 16711935,
'gainsboro': 14474460,
'ghostwhite': 16316671,
'gold': 16766720,
'goldenrod': 14329120,
'gray': 8421504,
'grey': 8421504,
'green': 32768,
'greenyellow': 11403055,
'honeydew': 15794160,
'hotpink': 16738740,
'indianred': 13458524,
'indigo': 4915330,
'ivory': 16777200,
'khaki': 15787660,
'lavender': 15132410,
'lavenderblush': 16773365,
'lawngreen': 8190976,
'lemonchiffon': 16775885,
'lightblue': 11393254,
'lightcoral': 15761536,
'lightcyan': 14745599,
'lightgoldenrodyellow': 16448210,
'lightgray': 13882323,
'lightgreen': 9498256,
'lightgrey': 13882323,
'lightpink': 16758465,
'lightsalmon': 16752762,
'lightseagreen': 2142890,
'lightskyblue': 8900346,
'lightslategray': 7833753,
'lightslategrey': 7833753,
'lightsteelblue': 11584734,
'lightyellow': 16777184,
'lime': 65280,
'limegreen': 3329330,
'linen': 16445670,
'magenta': 16711935,
'maroon': 8388608,
'mediumaquamarine': 6737322,
'mediumblue': 205,
'mediumorchid': 12211667,
'mediumpurple': 9662683,
'mediumseagreen': 3978097,
'mediumslateblue': 8087790,
'mediumspringgreen': 64154,
'mediumturquoise': 4772300,
'mediumvioletred': 13047173,
'midnightblue': 1644912,
'mintcream': 16121850,
'mistyrose': 16770273,
'moccasin': 16770229,
'navajowhite': 16768685,
'navy': 128,
'oldlace': 16643558,
'olive': 8421376,
'olivedrab': 7048739,
'orange': 16753920,
'orangered': 16729344,
'orchid': 14315734,
'palegoldenrod': 15657130,
'palegreen': 10025880,
'paleturquoise': 11529966,
'palevioletred': 14381203,
'papayawhip': 16773077,
'peachpuff': 16767673,
'peru': 13468991,
'pink': 16761035,
'plum': 14524637,
'powderblue': 11591910,
'purple': 8388736,
'red': 16711680,
'rosybrown': 12357519,
'royalblue': 4286945,
'saddlebrown': 9127187,
'salmon': 16416882,
'sandybrown': 16032864,
'seagreen': 3050327,
'seashell': 16774638,
'sienna': 10506797,
'silver': 12632256,
'skyblue': 8900331,
'slateblue': 6970061,
'slategray': 7372944,
'slategrey': 7372944,
'snow': 16775930,
'springgreen': 65407,
'steelblue': 4620980,
'tan': 13808780,
'teal': 32896,
'thistle': 14204888,
'tomato': 16737095,
'turquoise': 4251856,
'violet': 15631086,
'wheat': 16113331,
'white': 16777215,
'whitesmoke': 16119285,
'yellow': 16776960,
'yellowgreen': 10145074,
}
def _get_color(self, index):
if isinstance(index, tuple):
color = (index[0] << 16) + (index[1] << 8) + index[2]
else:
if index[0] == '#':
r, g, b = bytes.fromhex(index[1:])
color = (r << 16) + (g << 8) + b
else:
color = self.COLORS.get(index.lower(), -1)
return color
def __call__(self, index):
return self._get_color(index)
def __getitem__(self, index):
return self._get_color(index)
COLOR_ON_FOCUS = Color()('LightYellow')
2022-02-26 22:22:11 -06:00
2022-03-03 23:45:19 -06:00
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
2022-03-03 23:32:55 -06:00
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()
2022-03-09 13:18:40 -06:00
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):
2022-04-09 22:23:13 -05:00
# ~ 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
2022-03-04 22:08:16 -06:00
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
2022-03-06 22:19:26 -06:00
class LOMenuDebug():
"""Classe for debug info menu"""
2022-03-05 23:17:18 -06:00
@classmethod
2022-03-06 22:19:26 -06:00
def _get_info(cls, menu, index):
"""Get every option menu"""
line = f"({index}) {menu.get('CommandURL', '----------')}"
2022-03-05 23:17:18 -06:00
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):
2022-03-06 22:19:26 -06:00
"""Get submenus"""
2022-03-05 23:17:18 -06:00
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):
2022-03-06 22:19:26 -06:00
for i, m in enumerate(menu):
2022-03-05 23:17:18 -06:00
data = data_to_dict(m)
2022-03-06 22:19:26 -06:00
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()
2022-03-05 23:17:18 -06:00
return
2022-03-04 22:08:16 -06:00
class LOMenu(object):
2022-03-06 22:19:26 -06:00
"""Classe for individual menu"""
2022-03-05 13:20:21 -06:00
2022-03-06 22:19:26 -06:00
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
"""
2022-03-05 13:20:21 -06:00
self._config = config
2022-03-06 22:19:26 -06:00
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
2022-03-05 13:20:21 -06:00
2022-03-05 23:17:18 -06:00
def debug(self):
2022-03-06 22:19:26 -06:00
"""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)
2022-03-05 23:17:18 -06:00
return
2022-03-05 13:20:21 -06:00
class LOMenuApp(object):
2022-03-06 22:19:26 -06:00
"""Classe for manager menu by LibreOffice module"""
2022-03-04 22:08:16 -06:00
NODE = 'private:resource/menubar/menubar'
MENUS = {
'file': '.uno:PickList',
2022-03-05 13:20:21 -06:00
'picklist': '.uno:PickList',
2022-03-04 22:08:16 -06:00
'tools': '.uno:ToolsMenu',
'help': '.uno:HelpMenu',
2022-03-05 13:20:21 -06:00
'window': '.uno:WindowList',
2022-03-04 22:08:16 -06:00
'edit': '.uno:EditMenu',
'view': '.uno:ViewMenu',
'insert': '.uno:InsertMenu',
'format': '.uno:FormatMenu',
'styles': '.uno:FormatStylesMenu',
2022-03-05 13:20:21 -06:00
'formatstyles': '.uno:FormatStylesMenu',
2022-03-04 22:08:16 -06:00
'sheet': '.uno:SheetMenu',
'data': '.uno:DataMenu',
'table': '.uno:TableMenu',
2022-03-05 13:20:21 -06:00
'formatform': '.uno:FormatFormMenu',
2022-03-04 22:08:16 -06:00
'page': '.uno:PageMenu',
'shape': '.uno:ShapeMenu',
'slide': '.uno:SlideMenu',
2022-03-05 13:20:21 -06:00
'slideshow': '.uno:SlideShowMenu',
2022-03-04 22:08:16 -06:00
}
2022-03-06 22:19:26 -06:00
def __init__(self, app: str):
"""
:param app: LibreOffice Module: calc, writer, draw, impress, math, main
:type app: str
"""
2022-03-04 22:08:16 -06:00
self._app = app
self._config = self._get_config()
self._menus = self._config.getSettings(self.NODE, True)
def _get_config(self):
2022-03-06 22:19:26 -06:00
"""Get config manager"""
2022-03-04 22:08:16 -06:00
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):
2022-03-06 22:19:26 -06:00
"""Debug menu"""
LOMenuDebug()(self._menus)
2022-03-05 13:20:21 -06:00
return
def __contains__(self, name):
2022-03-06 22:19:26 -06:00
"""If exists name in menu"""
2022-03-05 13:20:21 -06:00
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):
2022-03-06 22:19:26 -06:00
"""Index access"""
2022-03-05 13:20:21 -06:00
if isinstance(index, int):
2022-03-05 23:17:18 -06:00
menu = data_to_dict(self._menus[index])
2022-03-05 13:20:21 -06:00
else:
for m in self._menus:
menu = data_to_dict(m)
cmd = menu.get('CommandURL', '')
2022-03-06 22:19:26 -06:00
if cmd == index or cmd == self.MENUS[index.lower()]:
2022-03-05 13:20:21 -06:00
break
2022-03-06 22:19:26 -06:00
obj = LOMenu(self._config, self._menus, self._app,
menu['ItemDescriptorContainer'])
return obj
2022-03-05 13:20:21 -06:00
2022-03-06 22:19:26 -06:00
def insert(self, menu: dict, after: Union[int, str]='', save: bool=True):
"""Insert new menu
2022-03-04 22:08:16 -06:00
2022-03-06 22:19:26 -06:00
: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()
2022-03-05 13:20:21 -06:00
return
2022-03-06 22:19:26 -06:00
def remove(self, menu: str):
"""Remove menu
2022-03-05 13:20:21 -06:00
2022-03-06 22:19:26 -06:00
:param menu: Menu name
:type menu: str
"""
LOMenuBase.config = self._config
LOMenuBase.menus = self._menus
LOMenuBase.remove(self._menus, menu)
2022-03-04 22:08:16 -06:00
return
class LOMenus(object):
"""Classe for manager menus"""
def __getitem__(self, index):
2022-03-06 22:19:26 -06:00
"""Index access"""
2022-03-05 13:20:21 -06:00
return LOMenuApp(index)
2022-03-04 22:08:16 -06:00
2022-03-09 13:18:40 -06:00
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
2022-03-03 23:32:55 -06:00
class LOMain():
"""Classe for LibreOffice"""
2022-03-04 22:08:16 -06:00
class commands():
2022-03-02 22:14:23 -06:00
"""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)
2022-03-03 23:32:55 -06:00
@classmethod
2022-03-02 22:14:23 -06:00
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)
2022-03-03 23:45:19 -06:00
@classproperty
2022-03-02 22:14:23 -06:00
def cmd(cls):
2022-03-03 23:32:55 -06:00
"""Disable or enable commands"""
2022-03-04 22:08:16 -06:00
return cls.commands
2022-03-02 22:14:23 -06:00
2022-03-03 23:45:19 -06:00
@classproperty
2022-03-03 23:32:55 -06:00
def desktop(cls):
2022-03-02 22:14:23 -06:00
"""Create desktop instance
:return: Desktop instance
:rtype: pyUno
"""
obj = create_instance('com.sun.star.frame.Desktop', True)
return obj
@classmethod
def dispatch(cls, frame: Any, command: str, args: dict={}) -> None:
"""Call dispatch, used only if not exists directly in API
:param frame: doc or frame instance
:type frame: pyUno
:param command: Command to execute
:type command: str
:param args: Extra argument for command
:type args: dict
`See DispatchCommands <`See DispatchCommands <https://wiki.documentfoundation.org/Development/DispatchCommands>`_>`_
"""
dispatch = create_instance('com.sun.star.frame.DispatchHelper')
if hasattr(frame, 'frame'):
frame = frame.frame
url = command
if not command.startswith('.uno:'):
url = f'.uno:{command}'
opt = dict_to_property(args)
dispatch.executeDispatch(frame, url, '', 0, opt)
return
@classmethod
def fonts(cls):
"""Get all font visibles in LibreOffice
:return: tuple of FontDescriptors
:rtype: tuple
`See API FontDescriptor <https://api.libreoffice.org/docs/idl/ref/structcom_1_1sun_1_1star_1_1awt_1_1FontDescriptor.html>`_
"""
toolkit = create_instance('com.sun.star.awt.Toolkit')
device = toolkit.createScreenCompatibleDevice(0, 0)
return device.FontDescriptors
@classmethod
def filters(cls):
"""Get all support filters
`See Help ConvertFilters <https://help.libreoffice.org/latest/en-US/text/shared/guide/convertfilters.html>`_
`See API FilterFactory <https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1document_1_1FilterFactory.html>`_
"""
factory = create_instance('com.sun.star.document.FilterFactory')
rows = [data_to_dict(factory[name]) for name in factory]
for row in rows:
row['UINames'] = data_to_dict(row['UINames'])
return rows
2022-03-03 23:32:55 -06:00
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 frame(self):
"""Get frame document"""
return self._cc.getFrame()
@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():
2022-03-04 22:08:16 -06:00
"""Classe for start module"""
2022-03-03 23:32:55 -06:00
_type = 'main'
def __init__(self, obj):
self._obj = obj
@property
def obj(self):
return self._obj
@property
def type(self):
return self._type
2022-04-13 22:59:40 -05:00
class LOCellStyle():
2022-04-09 22:23:13 -05:00
def __init__(self, obj):
2022-04-13 22:59:40 -05:00
self._obj = obj
2022-08-03 22:22:27 -05:00
def __str__(self):
return f'CellStyle: {self.name}'
2022-04-13 22:59:40 -05:00
@property
def obj(self):
return self._obj
2022-04-09 22:23:13 -05:00
@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)
2022-04-13 22:59:40 -05:00
class LOCellStyles():
2022-04-09 22:23:13 -05:00
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)
2022-03-03 23:32:55 -06:00
class LODocCalc(LODocument):
2022-03-07 21:36:41 -06:00
"""Classe for Calc module"""
2022-03-15 00:44:35 -06:00
TYPE_RANGES = ('ScCellObj', 'ScCellRangeObj')
RANGES = 'ScCellRangesObj'
SHAPE = 'com.sun.star.drawing.SvxShapeCollection'
2022-03-03 23:32:55 -06:00
_type = 'calc'
def __init__(self, obj):
super().__init__(obj)
2022-03-07 21:36:41 -06:00
self._sheets = obj.Sheets
2022-03-09 13:18:40 -06:00
self._listener_range_selection = None
2022-03-07 21:36:41 -06:00
def __getitem__(self, index):
2022-03-07 22:45:36 -06:00
"""Index access"""
2022-03-07 21:36:41 -06:00
return LOCalcSheet(self._sheets[index])
2022-03-08 22:24:10 -06:00
def __setitem__(self, key: str, value: Any):
"""Insert new sheet"""
self._sheets[key] = value
2022-03-07 21:36:41 -06:00
def __len__(self):
return self._sheets.Count
def __contains__(self, item):
return item in self._sheets
2022-03-15 00:44:35 -06:00
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
2022-04-09 22:23:13 -05:00
def __str__(self):
return f'Calc: {self.title}'
2022-03-15 00:44:35 -06:00
@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)
2022-03-15 00:44:35 -06:00
else:
debug(type_obj)
return sel
2022-03-07 21:36:41 -06:00
@property
def headers(self):
2022-03-07 22:45:36 -06:00
"""Get true if is visible columns/rows headers"""
2022-03-07 21:36:41 -06:00
return self._cc.ColumnRowHeaders
@headers.setter
def headers(self, value):
2022-03-07 22:45:36 -06:00
"""Set visible columns/rows headers"""
2022-03-07 21:36:41 -06:00
self._cc.ColumnRowHeaders = value
@property
def tabs(self):
2022-03-07 22:45:36 -06:00
"""Get true if is visible tab sheets"""
2022-03-07 21:36:41 -06:00
return self._cc.SheetTabs
@tabs.setter
def tabs(self, value):
2022-03-07 22:45:36 -06:00
"""Set visible tab sheets"""
2022-03-07 21:36:41 -06:00
self._cc.SheetTabs = value
@property
def names(self):
2022-03-07 22:45:36 -06:00
"""Get all sheet names"""
2022-03-07 21:36:41 -06:00
names = self.obj.Sheets.ElementNames
return names
@property
def active(self):
2022-03-07 22:45:36 -06:00
"""Get active sheet"""
2022-03-07 21:36:41 -06:00
return LOCalcSheet(self._cc.ActiveSheet)
2022-03-03 23:32:55 -06:00
2022-03-08 22:24:10 -06:00
@property
def new_sheet(self):
sheet = self._create_instance('com.sun.star.sheet.Spreadsheet')
return sheet
2022-03-09 13:18:40 -06:00
@property
def events(self):
return LOEvents(self.obj.Events)
2022-04-09 22:23:13 -05:00
@property
def cs(self):
return self.cell_styles
@property
def cell_styles(self):
obj = self.obj.StyleFamilies['CellStyles']
return LOCellStyles(obj, self)
2022-03-08 22:24:10 -06:00
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
2022-03-09 13:18:40 -06:00
@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>`_
"""
2022-03-13 22:45:15 -06:00
if args:
args['CloseOnMouseRelease'] = args.get('CloseOnMouseRelease', True)
else:
2022-03-09 13:18:40 -06:00
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
2022-03-15 00:44:35 -06:00
def select(self, rango: Any):
obj = rango
if hasattr(rango, 'obj'):
obj = rango.obj
self._cc.select(obj)
return
@property
def ranges(self):
2022-03-15 20:39:12 -06:00
obj = self._create_instance('com.sun.star.sheet.SheetCellRanges')
2022-03-15 00:44:35 -06:00
return LOCalcRanges(obj)
def get_ranges(self, address: str):
ranges = self.ranges
ranges.add([sheet[address] for sheet in self])
return ranges
2022-03-03 23:32:55 -06:00
2022-03-15 00:44:35 -06:00
class LOCalcRanges(object):
def __init__(self, obj):
self._obj = obj
2022-03-15 20:39:12 -06:00
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
2022-03-15 00:44:35 -06:00
@property
def obj(self):
return self._obj
@property
def names(self):
return self.obj.ElementNames
2022-03-15 20:39:12 -06:00
@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):
2022-03-15 00:44:35 -06:00
if isinstance(rangos, LOCalcRange):
rangos = (rangos,)
for r in rangos:
self.obj.addRangeAddress(r.range_address, False)
return
2022-03-15 20:39:12 -06:00
def remove(self, rangos: Any):
if isinstance(rangos, LOCalcRange):
rangos = (rangos,)
for r in rangos:
self.obj.removeRangeAddress(r.range_address)
return
2022-03-04 22:08:16 -06:00
2022-04-09 22:23:13 -05:00
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
2022-03-03 23:32:55 -06:00
class LODocWriter(LODocument):
_type = 'writer'
2022-04-09 22:23:13 -05:00
TEXT_RANGES = 'SwXTextRanges'
2022-03-03 23:32:55 -06:00
def __init__(self, obj):
super().__init__(obj)
self._view_settings = self._cc.ViewSettings
2022-04-09 22:23:13 -05:00
@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
2022-03-03 23:32:55 -06:00
@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)
2022-02-25 22:43:37 -06:00
def __getattr__(name):
classes = {
2022-02-26 00:11:41 -06:00
'json': Json,
'macro': Macro,
2022-02-26 22:22:11 -06:00
'shell': Shell,
'timer': Timer,
'hash': Hash,
2022-02-27 23:28:06 -06:00
'config': Config,
'url': Url,
'color': Color(),
2022-03-03 23:32:55 -06:00
'io': IOStream,
2022-03-03 23:45:19 -06:00
'clipboard': ClipBoard,
2022-03-04 22:08:16 -06:00
'shortcuts': LOShortCuts(),
2022-03-05 13:20:21 -06:00
'menus': LOMenus(),
2022-03-03 23:32:55 -06:00
'lo': LOMain,
'command': LOMain.cmd,
'docs': LODocuments(),
2022-02-25 22:43:37 -06:00
}
if name in classes:
return classes[name]
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
2022-02-24 23:59:04 -06:00
2022-02-21 23:43:58 -06:00
class LOServer(object):
2022-02-25 22:43:37 -06:00
"""Started LibeOffice like server
"""
2022-02-21 23:43:58 -06:00
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):
2022-02-25 22:43:37 -06:00
"""Stop server
"""
2022-02-21 23:43:58 -06:00
if self._server is None:
print('Search pgrep soffice')
else:
self._server.terminate()
debug('LibreOffice is stop...')
return
2022-02-25 22:43:37 -06:00
def _create_instance(self, name, with_context=True):
2022-02-21 23:43:58 -06:00
if with_context:
instance = self._sm.createInstanceWithContext(name, self._ctx)
else:
instance = self._sm.createInstance(name)
return instance