easymacro/source/easymacro/easymain.py

356 lines
10 KiB
Python

#!/usr/bin/env python3
import datetime
import getpass
import logging
import os
import platform
import sys
from typing import Any, Union
import uno
import unohelper
from com.sun.star.beans import PropertyValue, NamedValue
from com.sun.star.datatransfer import XTransferable, DataFlavor
__all__ = [
'DESKTOP',
'INFO_DEBUG',
'IS_MAC',
'IS_WIN',
'LANG',
'LANGUAGE',
'NAME',
'OS',
'PC',
'USER',
'VERSION',
'ClipBoard',
'LOMain',
# ~ 'create_instance',
'data_to_dict',
'dict_to_property',
'get_app_config',
]
CTX = uno.getComponentContext()
SM = CTX.getServiceManager()
# Global variables
OS = platform.system()
DESKTOP = os.environ.get('DESKTOP_SESSION', '')
PC = platform.node()
USER = getpass.getuser()
IS_WIN = OS == 'Windows'
IS_MAC = OS == 'Darwin'
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
LOG_DATE = '%d/%m/%Y %H:%M:%S'
if IS_WIN:
logging.addLevelName(logging.ERROR, 'ERROR')
logging.addLevelName(logging.DEBUG, 'DEBUG')
logging.addLevelName(logging.INFO, 'INFO')
else:
logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m')
logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m')
logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m')
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE)
log = logging.getLogger(__name__)
def create_instance(name: str, with_context: bool=False, arguments: Any=None) -> Any:
"""Create a service instance
:param name: Name of service
:type name: str
:param with_context: If used context
:type with_context: bool
:param argument: If needed some argument
:type argument: Any
:return: PyUno instance
:rtype: PyUno Object
"""
if with_context:
instance = SM.createInstanceWithContext(name, CTX)
elif arguments:
instance = SM.createInstanceWithArguments(name, (arguments,))
else:
instance = SM.createInstance(name)
return instance
def get_app_config(node_name: str, key: str='') -> Any:
"""Get any key from any node from LibreOffice configuration.
:param node_name: Name of node
:type name: str
:param key: Name of key
:type key: str
:return: Any value
:rtype: Any
`See Api ConfigurationProvider <https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1configuration_1_1ConfigurationProvider.html>`_
"""
name = 'com.sun.star.configuration.ConfigurationProvider'
service = 'com.sun.star.configuration.ConfigurationAccess'
cp = create_instance(name, True)
node = PropertyValue(Name='nodepath', Value=node_name)
value = ''
try:
value = cp.createInstanceWithArguments(service, (node,))
if value and value.hasByName(key):
value = value.getPropertyValue(key)
except Exception as e:
log.error(e)
value = ''
return value
# Get info LibO
NAME = TITLE = get_app_config('/org.openoffice.Setup/Product', 'ooName')
VERSION = get_app_config('/org.openoffice.Setup/Product','ooSetupVersion')
LANGUAGE = get_app_config('/org.openoffice.Setup/L10N/', 'ooLocale')
LANG = LANGUAGE.split('-')[0]
# Get start date from Calc configuration
node = '/org.openoffice.Office.Calc/Calculate/Other/Date'
year = get_app_config(node, 'YY')
month = get_app_config(node, 'MM')
day = get_app_config(node, 'DD')
DATE_OFFSET = datetime.date(year, month, day).toordinal()
_info_debug = f"Python: {sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path)
INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{_info_debug}"
def dict_to_property(values: dict, uno_any: bool=False):
"""Convert dictionary to array of PropertyValue
:param values: Dictionary of values
:type values: dict
:param uno_any: If return like array uno.Any
:type uno_any: bool
:return: Tuple of PropertyValue or array uno.Any
:rtype: tuples or uno.Any
"""
ps = tuple([PropertyValue(Name=n, Value=v) for n, v in values.items()])
if uno_any:
ps = uno.Any('[]com.sun.star.beans.PropertyValue', ps)
return ps
def data_to_dict(data: Union[tuple, list]) -> dict:
"""Convert tuples, list, PropertyValue, NamedValue to dictionary
:param data: Iterator of values
:type data: array of tuples, list, PropertyValue or NamedValue
:return: Dictionary
:rtype: dict
"""
d = {}
if isinstance(data[0], (tuple, list)):
d = {r[0]: r[1] for r in data}
elif isinstance(data[0], (PropertyValue, NamedValue)):
d = {r.Name: r.Value for r in data}
return d
# ~ https://github.com/django/django/blob/main/django/utils/functional.py#L61
class classproperty:
def __init__(self, method=None):
self.fget = method
def __get__(self, instance, cls=None):
return self.fget(cls)
def getter(self, method):
self.fget = method
return self
class BaseObject():
def __init__(self, obj):
self._obj = obj
def __enter__(self):
return self
@property
def obj(self):
"""Return original pyUno object"""
return self._obj
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) -> 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) -> bool:
"""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) -> bool:
"""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)
@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
@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
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
@classmethod
def get(cls):
return cls.contents