Extension for install and admin Python Pip in LibreOffice.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

6912 lines
181 KiB

#!/usr/bin/env python3
# == Rapid Develop Macros in LibreOffice ==
# ~ This file is part of ZAZ.
# ~ https://git.cuates.net/elmau/zaz
# ~ ZAZ 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.
# ~ ZAZ 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 ZAZ. If not, see <https://www.gnu.org/licenses/>.
import base64
import csv
import ctypes
import datetime
import getpass
import gettext
import hashlib
import json
import logging
import os
import platform
import re
import shlex
import shutil
import socket
import ssl
import subprocess
import sys
import tempfile
import threading
import time
import traceback
import zipfile
from collections import OrderedDict
from collections.abc import MutableMapping
from decimal import Decimal
from enum import IntEnum
from functools import wraps
from pathlib import Path
from pprint import pprint
from string import Template
from typing import Any, Union
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
import imaplib
import smtplib
from smtplib import SMTPException, SMTPAuthenticationError
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import formatdate
from email import encoders
import mailbox
import uno
import unohelper
from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS
from com.sun.star.awt.MessageBoxResults import YES
from com.sun.star.awt import Rectangle, Size, Point
from com.sun.star.awt.PosSize import POSSIZE, SIZE
from com.sun.star.awt import Key, KeyModifier, KeyEvent
from com.sun.star.container import NoSuchElementException
from com.sun.star.datatransfer import XTransferable, DataFlavor
from com.sun.star.beans import PropertyValue, NamedValue
from com.sun.star.sheet import TableFilterField
from com.sun.star.table.CellContentType import EMPTY, VALUE, TEXT, FORMULA
from com.sun.star.util import Time, Date, DateTime
from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK
from com.sun.star.text.TextContentAnchorType import AS_CHARACTER
from com.sun.star.lang import Locale
from com.sun.star.lang import XEventListener
from com.sun.star.awt import XActionListener
from com.sun.star.awt import XMenuListener
from com.sun.star.awt import XMouseListener
from com.sun.star.awt import XMouseMotionListener
from com.sun.star.awt import XFocusListener
from com.sun.star.awt import XKeyListener
from com.sun.star.awt import XItemListener
from com.sun.star.awt import XTabListener
from com.sun.star.awt import XWindowListener
from com.sun.star.awt import XTopWindowListener
from com.sun.star.awt.grid import XGridDataListener
from com.sun.star.awt.grid import XGridSelectionListener
from com.sun.star.script import ScriptEventDescriptor
# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1FontUnderline.html
from com.sun.star.awt import FontUnderline
from com.sun.star.style.VerticalAlignment import TOP, MIDDLE, BOTTOM
from com.sun.star.view.SelectionType import SINGLE, MULTI, RANGE
from com.sun.star.sdb.CommandType import TABLE, QUERY, COMMAND
try:
from peewee import Database, DateTimeField, DateField, TimeField, \
__exception_wrapper__
except ImportError as e:
Database = DateField = TimeField = DateTimeField = object
print('You need install peewee, only if you will develop with Base')
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
LOG_DATE = '%d/%m/%Y %H:%M:%S'
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__)
# ~ You can get custom salt
# ~ codecs.encode(os.urandom(16), 'hex')
# ~ but, not modify this file, modify in import file
SALT = b'c9548699d4e432dfd2b46adddafbb06d'
TIMEOUT = 10
LOG_NAME = 'ZAZ'
FILE_NAME_CONFIG = 'zaz-{}.json'
LEFT = 0
CENTER = 1
RIGHT = 2
CALC = 'calc'
WRITER = 'writer'
DRAW = 'draw'
IMPRESS = 'impress'
BASE = 'base'
MATH = 'math'
BASIC = 'basic'
MAIN = 'main'
TYPE_DOC = {
CALC: 'com.sun.star.sheet.SpreadsheetDocument',
WRITER: 'com.sun.star.text.TextDocument',
DRAW: 'com.sun.star.drawing.DrawingDocument',
IMPRESS: 'com.sun.star.presentation.PresentationDocument',
BASE: 'com.sun.star.sdb.DocumentDataSource',
MATH: 'com.sun.star.formula.FormulaProperties',
BASIC: 'com.sun.star.script.BasicIDE',
MAIN: 'com.sun.star.frame.StartModule',
}
OBJ_CELL = 'ScCellObj'
OBJ_RANGE = 'ScCellRangeObj'
OBJ_RANGES = 'ScCellRangesObj'
TYPE_RANGES = (OBJ_CELL, OBJ_RANGE, OBJ_RANGES)
OBJ_SHAPES = 'com.sun.star.drawing.SvxShapeCollection'
OBJ_SHAPE = 'com.sun.star.comp.sc.ScShapeObj'
OBJ_GRAPHIC = 'SwXTextGraphicObject'
OBJ_TEXTS = 'SwXTextRanges'
OBJ_TEXT = 'SwXTextRange'
# ~ from com.sun.star.sheet.FilterOperator import EMPTY, NO_EMPTY, EQUAL, NOT_EQUAL
class FilterOperator(IntEnum):
EMPTY = 0
NO_EMPTY = 1
EQUAL = 2
NOT_EQUAL = 3
# ~ https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1awt_1_1UnoControlEditModel.html#a54d3ff280d892218d71e667f81ce99d4
class Border(IntEnum):
NO_BORDER = 0
BORDER = 1
SIMPLE = 2
# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet.html#aa5aa6dbecaeb5e18a476b0a58279c57a
class ValidationType():
from com.sun.star.sheet.ValidationType \
import ANY, WHOLE, DECIMAL, DATE, TIME, TEXT_LEN, LIST, CUSTOM
VT = ValidationType
# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet.html#aecf58149730f4c8c5c18c70f3c7c5db7
class ValidationAlertStyle():
from com.sun.star.sheet.ValidationAlertStyle \
import STOP, WARNING, INFO, MACRO
VAS = ValidationAlertStyle
# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1ConditionOperator2.html
class ConditionOperator():
from com.sun.star.sheet.ConditionOperator2 \
import NONE, EQUAL, NOT_EQUAL, GREATER, GREATER_EQUAL, LESS, \
LESS_EQUAL, BETWEEN, NOT_BETWEEN, FORMULA, DUPLICATE, NOT_DUPLICATE
CO = ConditionOperator
class DataPilotFieldOrientation():
from com.sun.star.sheet.DataPilotFieldOrientation \
import HIDDEN, COLUMN, ROW, PAGE, DATA
DPFO = DataPilotFieldOrientation
OS = platform.system()
IS_WIN = OS == 'Windows'
IS_MAC = OS == 'Darwin'
USER = getpass.getuser()
PC = platform.node()
DESKTOP = os.environ.get('DESKTOP_SESSION', '')
INFO_DEBUG = f"{sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path)
PYTHON = 'python'
if IS_WIN:
PYTHON = 'python.exe'
_MACROS = {}
_start = 0
SECONDS_DAY = 60 * 60 * 24
DIR = {
'images': 'images',
'locales': 'locales',
}
KEY = {
'enter': 1280,
}
MODIFIERS = {
'shift': KeyModifier.SHIFT,
'ctrl': KeyModifier.MOD1,
'alt': KeyModifier.MOD2,
'ctrlmac': KeyModifier.MOD3,
}
# ~ Menus
NODE_MENUBAR = 'private:resource/menubar/menubar'
MENUS = {
'file': '.uno:PickList',
'tools': '.uno:ToolsMenu',
'help': '.uno:HelpMenu',
'windows': '.uno:WindowList',
'edit': '.uno:EditMenu',
'view': '.uno:ViewMenu',
'insert': '.uno:InsertMenu',
'format': '.uno:FormatMenu',
'styles': '.uno:FormatStylesMenu',
'sheet': '.uno:SheetMenu',
'data': '.uno:DataMenu',
'table': '.uno:TableMenu',
'form': '.uno:FormatFormMenu',
'page': '.uno:PageMenu',
'shape': '.uno:ShapeMenu',
'slide': '.uno:SlideMenu',
'show': '.uno:SlideShowMenu',
}
DEFAULT_MIME_TYPE = 'png'
MIME_TYPE = {
'png': 'image/png',
'jpg': 'image/jpeg',
}
MESSAGES = {
'es': {
'OK': 'Aceptar',
'Cancel': 'Cancelar',
'Select path': 'Seleccionar ruta',
'Select directory': 'Seleccionar directorio',
'Select file': 'Seleccionar archivo',
'Incorrect user or password': 'Nombre de usuario o contraseña inválidos',
'Allow less secure apps in GMail': 'Activa: Permitir aplicaciones menos segura en GMail',
}
}
CTX = uno.getComponentContext()
SM = CTX.getServiceManager()
def create_instance(name: str, with_context: bool=False, args: Any=None) -> Any:
if with_context:
instance = SM.createInstanceWithContext(name, CTX)
elif args:
instance = SM.createInstanceWithArguments(name, (args,))
else:
instance = SM.createInstance(name)
return instance
def get_app_config(node_name: str, key: str=''):
name = 'com.sun.star.configuration.ConfigurationProvider'
service = 'com.sun.star.configuration.ConfigurationAccess'
cp = create_instance(name, True)
node = PropertyValue(Name='nodepath', Value=node_name)
try:
ca = cp.createInstanceWithArguments(service, (node,))
if ca and not key:
return ca
if ca and ca.hasByName(key):
return ca.getPropertyValue(key)
except Exception as e:
error(e)
return ''
LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale')
LANG = LANGUAGE.split('-')[0]
try:
COUNTRY = LANGUAGE.split('-')[1]
except:
COUNTRY = ''
LOCALE = Locale(LANG, COUNTRY, '')
NAME = TITLE = get_app_config('org.openoffice.Setup/Product', 'ooName')
VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion')
INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{INFO_DEBUG}"
node = '/org.openoffice.Office.Calc/Calculate/Other/Date'
y = get_app_config(node, 'YY')
m = get_app_config(node, 'MM')
d = get_app_config(node, 'DD')
DATE_OFFSET = datetime.date(y, m, d).toordinal()
def error(info):
log.error(info)
return
def debug(*args):
data = [str(a) for a in args]
log.debug('\t'.join(data))
return
def info(*args):
data = [str(a) for a in args]
log.info('\t'.join(data))
return
def save_log(path: str, data):
with open(path, 'a') as f:
f.write(f'{str(now())[:19]} -{LOG_NAME}- ')
pprint(data, stream=f)
return
def catch_exception(f):
@wraps(f)
def func(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
name = f.__name__
if IS_WIN:
msgbox(traceback.format_exc())
log.error(name, exc_info=True)
return func
def inspect(obj: Any) -> None:
zaz = create_instance('net.elmau.zaz.inspect')
if hasattr(obj, 'obj'):
obj = obj.obj
zaz.inspect(obj)
return
def mri(obj: Any) -> None:
m = create_instance('mytools.Mri')
if m is None:
msg = 'Extension MRI not found'
error(msg)
return
if hasattr(obj, 'obj'):
obj = obj.obj
m.inspect(obj)
return
def run_in_thread(fn):
def run(*k, **kw):
t = threading.Thread(target=fn, args=k, kwargs=kw)
t.start()
return t
return run
def now(only_time: bool=False):
now = datetime.datetime.now()
if only_time:
now = now.time()
return now
def today():
return datetime.date.today()
def _(msg):
if LANG == 'en':
return msg
if not LANG in MESSAGES:
return msg
return MESSAGES[LANG][msg]
def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infobox'):
""" Create message box
type_msg: infobox, warningbox, errorbox, querybox, messbox
http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMessageBoxFactory.html
"""
toolkit = create_instance('com.sun.star.awt.Toolkit')
parent = toolkit.getDesktopWindow()
box = toolkit.createMessageBox(parent, type_msg, buttons, title, str(message))
return box.execute()
def question(message, title=TITLE):
result = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, 'querybox')
return result == YES
def warning(message, title=TITLE):
return msgbox(message, title, type_msg='warningbox')
def errorbox(message, title=TITLE):
return msgbox(message, title, type_msg='errorbox')
def get_type_doc(obj: Any) -> str:
for k, v in TYPE_DOC.items():
if obj.supportsService(v):
return k
return ''
def _get_class_doc(obj: Any) -> Any:
classes = {
CALC: LOCalc,
WRITER: LOWriter,
DRAW: LODraw,
IMPRESS: LOImpress,
BASE: LOBase,
MATH: LOMath,
BASIC: LOBasic,
}
type_doc = get_type_doc(obj)
return classes[type_doc](obj)
def dict_to_property(values: dict, uno_any: bool=False):
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 _array_to_dict(values):
d = {v[0]: v[1] for v in values}
return d
def _property_to_dict(values):
d = {v.Name: v.Value for v in values}
return d
def json_dumps(data):
return json.dumps(data, indent=4, sort_keys=True)
def json_loads(data):
return json.loads(data)
def data_to_dict(data):
if isinstance(data, tuple) and isinstance(data[0], tuple):
return _array_to_dict(data)
if isinstance(data, tuple) and isinstance(data[0], (PropertyValue, NamedValue)):
return _property_to_dict(data)
return {}
def _get_dispatch() -> Any:
return create_instance('com.sun.star.frame.DispatchHelper')
# ~ https://wiki.documentfoundation.org/Development/DispatchCommands
# ~ Used only if not exists in API
def call_dispatch(frame: Any, url: str, args: dict={}) -> None:
dispatch = _get_dispatch()
opt = dict_to_property(args)
dispatch.executeDispatch(frame, url, '', 0, opt)
return
def get_desktop():
return create_instance('com.sun.star.frame.Desktop', True)
def _date_to_struct(value):
if isinstance(value, datetime.datetime):
d = DateTime()
d.Year = value.year
d.Month = value.month
d.Day = value.day
d.Hours = value.hour
d.Minutes = value.minute
d.Seconds = value.second
elif isinstance(value, datetime.date):
d = Date()
d.Day = value.day
d.Month = value.month
d.Year = value.year
elif isinstance(value, datetime.time):
d = Time()
d.Hours = value.hour
d.Minutes = value.minute
d.Seconds = value.second
return d
def _struct_to_date(value):
d = None
if isinstance(value, Time):
d = datetime.time(value.Hours, value.Minutes, value.Seconds)
elif isinstance(value, Date):
if value != Date():
d = datetime.date(value.Year, value.Month, value.Day)
elif isinstance(value, DateTime):
if value.Year > 0:
d = datetime.datetime(
value.Year, value.Month, value.Day,
value.Hours, value.Minutes, value.Seconds)
return d
def _get_url_script(args: dict):
library = args['library']
module = '.'
name = args['name']
language = args.get('language', 'Python')
location = args.get('location', 'user')
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
def _call_macro(args: dict):
#~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification
url = _get_url_script(args)
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
def call_macro(args, in_thread=False):
result = None
if in_thread:
t = threading.Thread(target=_call_macro, args=(args,))
t.start()
else:
result = _call_macro(args)
return result
def run(command, capture=False, split=True):
if not split:
return subprocess.check_output(command, shell=True).decode()
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
return result
def popen(command):
try:
proc = subprocess.Popen(shlex.split(command), shell=IS_WIN,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in proc.stdout:
yield line.decode().rstrip()
except Exception as e:
error(e)
yield (e.errno, e.strerror)
def sleep(seconds):
time.sleep(seconds)
return
class TimerThread(threading.Thread):
def __init__(self, event, seconds, macro):
threading.Thread.__init__(self)
self.stopped = event
self.seconds = seconds
self.macro = macro
def run(self):
info('Timer started... {}'.format(self.macro['name']))
while not self.stopped.wait(self.seconds):
_call_macro(self.macro)
info('Timer stopped... {}'.format(self.macro['name']))
return
def start_timer(name, seconds, macro):
global _MACROS
_MACROS[name] = threading.Event()
thread = TimerThread(_MACROS[name], seconds, macro)
thread.start()
return
def stop_timer(name):
global _MACROS
_MACROS[name].set()
del _MACROS[name]
return
def install_locales(path: str, domain: str='base', dir_locales=DIR['locales']):
path_locales = _P.join(_P(path).path, dir_locales)
try:
lang = gettext.translation(domain, path_locales, languages=[LANG])
lang.install()
_ = lang.gettext
except Exception as e:
from gettext import gettext as _
error(e)
return _
def _export_image(obj, args):
name = 'com.sun.star.drawing.GraphicExportFilter'
exporter = create_instance(name)
path = _P.to_system(args['URL'])
args = dict_to_property(args)
exporter.setSourceDocument(obj)
exporter.filter(args)
return _P.exists(path)
def sha256(data):
result = hashlib.sha256(data.encode()).hexdigest()
return result
def sha512(data):
result = hashlib.sha512(data.encode()).hexdigest()
return result
def get_config(key='', default={}, prefix='conf'):
name_file = FILE_NAME_CONFIG.format(prefix)
values = None
path = _P.join(_P.config('UserConfig'), name_file)
if not _P.exists(path):
return default
values = _P.from_json(path)
if key:
values = values.get(key, default)
return values
def set_config(key, value, prefix='conf'):
name_file = FILE_NAME_CONFIG.format(prefix)
path = _P.join(_P.config('UserConfig'), name_file)
values = get_config(default={}, prefix=prefix)
values[key] = value
result = _P.to_json(path, values)
return result
def start():
global _start
_start = now()
info(_start)
return
def end(get_seconds: bool=False):
global _start
e = now()
td = e - _start
result = str(td)
if get_seconds:
result = td.total_seconds()
return result
def get_epoch():
n = now()
return int(time.mktime(n.timetuple()))
def render(template, data):
s = Template(template)
return s.safe_substitute(**data)
def get_size_screen():
if IS_WIN:
user32 = ctypes.windll.user32
res = f'{user32.GetSystemMetrics(0)}x{user32.GetSystemMetrics(1)}'
else:
args = 'xrandr | grep "*" | cut -d " " -f4'
res = run(args, split=False)
return res.strip()
def url_open(url, data=None, headers={}, verify=True, get_json=False):
err = ''
req = Request(url)
for k, v in headers.items():
req.add_header(k, v)
try:
# ~ debug(url)
if verify:
if not data is None and isinstance(data, str):
data = data.encode()
response = urlopen(req, data=data)
else:
context = ssl._create_unverified_context()
response = urlopen(req, context=context)
except HTTPError as e:
error(e)
err = str(e)
except URLError as e:
error(e.reason)
err = str(e.reason)
else:
headers = dict(response.info())
result = response.read()
if get_json:
result = json.loads(result)
return result, headers, err
def _get_key(password):
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=SALT,
iterations=100000)
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
return key
def encrypt(data, password):
from cryptography.fernet import Fernet
f = Fernet(_get_key(password))
if isinstance(data, str):
data = data.encode()
token = f.encrypt(data).decode()
return token
def decrypt(token, password):
from cryptography.fernet import Fernet, InvalidToken
data = ''
f = Fernet(_get_key(password))
try:
data = f.decrypt(token.encode()).decode()
except InvalidToken as e:
error('Invalid Token')
return data
def switch_design_mode(doc):
call_dispatch(doc.frame, '.uno:SwitchControlDesignMode')
return
class SmtpServer(object):
def __init__(self, config):
self._server = None
self._error = ''
self._sender = ''
self._is_connect = self._login(config)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
@property
def is_connect(self):
return self._is_connect
@property
def error(self):
return self._error
def _login(self, config):
name = config['server']
port = config['port']
is_ssl = config['ssl']
self._sender = config['user']
hosts = ('gmail' in name or 'outlook' in name)
try:
if is_ssl and hosts:
self._server = smtplib.SMTP(name, port, timeout=TIMEOUT)
self._server.ehlo()
self._server.starttls()
self._server.ehlo()
elif is_ssl:
self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT)
self._server.ehlo()
else:
self._server = smtplib.SMTP(name, port, timeout=TIMEOUT)
self._server.login(self._sender, config['password'])
msg = 'Connect to: {}'.format(name)
debug(msg)
return True
except smtplib.SMTPAuthenticationError as e:
if '535' in str(e):
self._error = _('Incorrect user or password')
return False
if '534' in str(e) and 'gmail' in name:
self._error = _('Allow less secure apps in GMail')
return False
except smtplib.SMTPException as e:
self._error = str(e)
return False
except Exception as e:
self._error = str(e)
return False
return False
def _body(self, msg):
body = msg.replace('\\n', '<BR>')
return body
def send(self, message):
file_name = 'attachment; filename={}'
email = MIMEMultipart()
email['From'] = self._sender
email['To'] = message['to']
email['Cc'] = message.get('cc', '')
email['Subject'] = message['subject']
email['Date'] = formatdate(localtime=True)
if message.get('confirm', False):
email['Disposition-Notification-To'] = email['From']
email.attach(MIMEText(self._body(message['body']), 'html'))
for path in message.get('files', ()):
fn = _P(path).file_name
part = MIMEBase('application', 'octet-stream')
part.set_payload(_P.read_bin(path))
encoders.encode_base64(part)
part.add_header('Content-Disposition', f'attachment; filename={fn}')
email.attach(part)
receivers = (
email['To'].split(',') +
email['CC'].split(',') +
message.get('bcc', '').split(','))
try:
self._server.sendmail(self._sender, receivers, email.as_string())
msg = 'Email sent...'
debug(msg)
if message.get('path', ''):
self.save_message(email, message['path'])
return True
except Exception as e:
self._error = str(e)
return False
return False
def save_message(self, email, path):
mbox = mailbox.mbox(path, create=True)
mbox.lock()
try:
msg = mailbox.mboxMessage(email)
mbox.add(msg)
mbox.flush()
finally:
mbox.unlock()
return
def close(self):
try:
self._server.quit()
msg = 'Close connection...'
debug(msg)
except:
pass
return
def _send_email(server, messages):
with SmtpServer(server) as server:
if server.is_connect:
for msg in messages:
server.send(msg)
else:
error(server.error)
return server.error
def send_email(server, message):
messages = message
if isinstance(message, dict):
messages = (message,)
t = threading.Thread(target=_send_email, args=(server, messages))
t.start()
return
class ImapServer(object):
def __init__(self, config):
self._server = None
self._error = ''
self._is_connect = self._login(config)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
@property
def is_connect(self):
return self._is_connect
@property
def error(self):
return self._error
def _login(self, config):
try:
# ~ hosts = 'gmail' in config['server']
if config['ssl']:
self._server = imaplib.IMAP4_SSL(config['server'], config['port'])
else:
self._server = imaplib.IMAP4(config['server'], config['port'])
self._server.login(config['user'], config['password'])
self._server.select()
return True
except imaplib.IMAP4.error as e:
self._error = str(e)
return False
except Exception as e:
self._error = str(e)
return False
return False
def get_folders(self, exclude=()):
folders = {}
result, subdir = self._server.list()
for s in subdir:
print(s.decode('utf-8'))
return folders
def close(self):
try:
self._server.close()
self._server.logout()
msg = 'Close connection...'
debug(msg)
except:
pass
return
# ~ Classes
class LOBaseObject(object):
def __init__(self, obj):
self._obj = obj
def __setattr__(self, name, value):
exists = hasattr(self, name)
if not exists and not name in ('_obj', '_index', '_view'):
setattr(self._obj, name, value)
else:
super().__setattr__(name, value)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
@property
def obj(self):
return self._obj
class LODocument(object):
FILTERS = {
'doc': 'MS Word 97',
'docx': 'MS Word 2007 XML',
}
def __init__(self, obj):
self._obj = obj
self._cc = self.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 self._obj
@property
def title(self):
return self.obj.getTitle()
@title.setter
def title(self, value):
self.obj.setTitle(value)
@property
def type(self):
return self._type
@property
def uid(self):
return self.obj.RuntimeUID
@property
def frame(self):
return self._cc.getFrame()
@property
def is_saved(self):
return self.obj.hasLocation()
@property
def is_modified(self):
return self.obj.isModified()
@property
def is_read_only(self):
return self.obj.isReadOnly()
@property
def path(self):
return _P.to_system(self.obj.URL)
@property
def dir(self):
return _P(self.path).path
@property
def file_name(self):
return _P(self.path).file_name
@property
def name(self):
return _P(self.path).name
@property
def status_bar(self):
return self._cc.getStatusIndicator()
@property
def visible(self):
w = self.frame.ContainerWindow
return w.isVisible()
@visible.setter
def visible(self, value):
w = self.frame.ContainerWindow
w.setVisible(value)
@property
def zoom(self):
return self._cc.ZoomValue
@zoom.setter
def zoom(self, value):
self._cc.ZoomValue = value
@property
def undo(self):
return self._undo
@undo.setter
def undo(self, value):
self._undo = value
um = self.obj.UndoManager
if value:
try:
um.leaveUndoContext()
except:
pass
else:
um.enterHiddenUndoContext()
def clear_undo(self):
self.obj.getUndoManager().clear()
return
@property
def selection(self):
sel = self.obj.CurrentSelection
# ~ return _get_class_uno(sel)
return sel
@property
def table_auto_formats(self):
taf = create_instance('com.sun.star.sheet.TableAutoFormats')
return taf.ElementNames
def create_instance(self, name):
obj = self.obj.createInstance(name)
return obj
def set_focus(self):
w = self.frame.ComponentWindow
w.setFocus()
return
def copy(self):
call_dispatch(self.frame, '.uno:Copy')
return
def insert_contents(self, args={}):
call_dispatch(self.frame, '.uno:InsertContents', args)
return
def paste(self):
sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard')
transferable = sc.getContents()
self._cc.insertTransferable(transferable)
# ~ return self.obj.getCurrentSelection()
return
def select(self, obj):
self._cc.select(obj)
return
def to_pdf(self, path: str='', args: dict={}):
"""
https://wiki.documentfoundation.org/Macros/Python_Guide/PDF_export_filter_data
"""
path_pdf = path
filter_name = '{}_pdf_Export'.format(self.type)
filter_data = dict_to_property(args, True)
args = {
'FilterName': filter_name,
'FilterData': filter_data,
}
opt = dict_to_property(args)
try:
self.obj.storeToURL(_P.to_url(path), opt)
except Exception as e:
error(e)
path_pdf = ''
return _P.exists(path_pdf)
def export(self, path: str, ext: str='', args: dict={}):
if not ext:
ext = _P(path).ext
filter_name = self.FILTERS[ext]
filter_data = dict_to_property(args, True)
args = {
'FilterName': filter_name,
'FilterData': filter_data,
}
opt = dict_to_property(args)
try:
self.obj.storeToURL(_P.to_url(path), opt)
except Exception as e:
error(e)
path = ''
return _P.exists(path)
def save(self, path: str='', args: dict={}) -> bool:
result = True
opt = dict_to_property(args)
if path:
try:
self.obj.storeAsURL(_P.to_url(path), opt)
except Exception as e:
error(e)
result = False
else:
self.obj.store()
return result
def close(self):
self.obj.close(True)
return
class LOCellStyle(LOBaseObject):
def __init__(self, obj):
super().__init__(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(object):
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')
if name:
self.obj[name] = obj
obj = LOCellStyle(obj)
return obj
class LOCalc(LODocument):
def __init__(self, obj):
super().__init__(obj)
self._type = CALC
self._sheets = obj.Sheets
def __getitem__(self, index):
return LOCalcSheet(self._sheets[index])
def __setitem__(self, key, value):
self._sheets[key] = value
def __len__(self):
return self._sheets.Count
def __contains__(self, item):
return item in self._sheets
@property
def names(self):
names = self.obj.Sheets.ElementNames
return names
@property
def selection(self):
sel = self.obj.CurrentSelection
if sel.ImplementationName in TYPE_RANGES:
sel = LOCalcRange(sel)
elif sel.ImplementationName == OBJ_SHAPES:
if len(sel) == 1:
sel = sel[0]
sel = LODrawPage(sel.Parent)[sel.Name]
else:
debug(sel.ImplementationName)
return sel
@property
def active(self):
return LOCalcSheet(self._cc.ActiveSheet)
@property
def headers(self):
return self._cc.ColumnRowHeaders
@headers.setter
def headers(self, value):
self._cc.ColumnRowHeaders = value
@property
def tabs(self):
return self._cc.SheetTabs
@tabs.setter
def tabs(self, value):
self._cc.SheetTabs = value
@property
def cs(self):
return self.cell_styles
@property
def cell_styles(self):
obj = self.obj.StyleFamilies['CellStyles']
return LOCellStyles(obj, self)
@property
def db_ranges(self):
# ~ return LOCalcDataBaseRanges(self.obj.DataBaseRanges)
return self.obj.DatabaseRanges
def activate(self, sheet):
obj = sheet
if isinstance(sheet, LOCalcSheet):
obj = sheet.obj
elif isinstance(sheet, str):
obj = self._sheets[sheet]
self._cc.setActiveSheet(obj)
return
def new_sheet(self):
s = self.create_instance('com.sun.star.sheet.Spreadsheet')
return s
def insert(self, name):
names = name
if isinstance(name, str):
names = (name,)
for n in names:
self._sheets[n] = self.new_sheet()
return LOCalcSheet(self._sheets[n])
def move(self, name, pos=-1):
index = pos
if pos < 0:
index = len(self)
if isinstance(name, LOCalcSheet):
name = name.name
self._sheets.moveByName(name, index)
return
def remove(self, name):
if isinstance(name, LOCalcSheet):
name = name.name
self._sheets.removeByName(name)
return
def copy(self, name, new_name='', pos=-1):
if isinstance(name, LOCalcSheet):
name = name.name
index = pos
if pos < 0:
index = len(self)
self._sheets.copyByName(name, new_name, index)
return LOCalcSheet(self._sheets[new_name])
def copy_from(self, doc, source='', target='', pos=-1):
index = pos
if pos < 0:
index = len(self)
names = source
if not source:
names = doc.names
elif isinstance(source, str):
names = (source,)
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):
names = sorted(self.names, reverse=reverse)
for i, n in enumerate(names):
self.move(n, i)
return
def render(self, data, sheet=None, clean=True):
if sheet is None:
sheet = self.active
return sheet.render(data, clean=clean)
class LOChart(object):
def __init__(self, name, obj, draw_page):
self._name = name
self._obj = obj
self._eobj = self._obj.EmbeddedObject
self._type = 'Column'
self._cell = None
self._shape = self._get_shape(draw_page)
self._pos = self._shape.Position
def __getitem__(self, index):
return LOBaseObject(self.diagram.getDataRowProperties(index))
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
@property
def obj(self):
return self._obj
@property
def name(self):
return self._name
@property
def diagram(self):
return self._eobj.Diagram
@property
def type(self):
return self._type
@type.setter
def type(self, value):
self._type = value
if value == 'Bar':
self.diagram.Vertical = True
return
type_chart = f'com.sun.star.chart.{value}Diagram'
self._eobj.setDiagram(self._eobj.createInstance(type_chart))
@property
def cell(self):
return self._cell
@cell.setter
def cell(self, value):
self._cell = value
self._shape.Anchor = value.obj
@property
def position(self):
return self._pos
@position.setter
def position(self, value):
self._pos = value
self._shape.Position = value
def _get_shape(self, draw_page):
for shape in draw_page:
if shape.PersistName == self.name:
break
return shape
class LOSheetCharts(object):
def __init__(self, obj, sheet):
self._obj = obj
self._sheet = sheet
def __getitem__(self, index):
return LOChart(index, self.obj[index], self._sheet.draw_page)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def __contains__(self, item):
return item in self.obj
def __len__(self):
return len(self.obj)
@property
def obj(self):
return self._obj
def new(self, name, pos_size, data):
self.obj.addNewByName(name, pos_size, data, True, True)
return LOChart(name, self.obj[name], self._sheet.draw_page)
class LOSheetTableField(object):
def __init__(self, obj):
self._obj = obj
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
@property
def obj(self):
return self._obj
@property
def name(self):
return self.obj.Name
@property
def orientation(self):
return self.obj.Orientation
@orientation.setter
def orientation(self, value):
self.obj.Orientation = value
# ~ com.sun.star.sheet.DataPilotFieldOrientation.ROW
class LOSheetTable(object):
def __init__(self, obj):
self._obj = obj
self._source = None
def __getitem__(self, index):
field = self.obj.DataPilotFields[index]
return LOSheetTableField(field)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
@property
def obj(self):
return self._obj
@property
def filter(self):
return self.obj.ShowFilterButton
@filter.setter
def filter(self, value):
self.obj.ShowFilterButton = value
@property
def source(self):
return self._source
@source.setter
def source(self, value):
self._source = value
self.obj.SourceRange = value.range_address
@property
def rows(self):
return self.obj.RowFields
@rows.setter
def rows(self, values):
if not isinstance(values, tuple):
values = (values,)
for v in values:
with self[v] as f:
f.orientation = DPFO.ROW
@property
def columns(self):
return self.obj.ColumnFields
@columns.setter
def columns(self, values):
if not isinstance(values, tuple):
values = (values,)
for v in values:
with self[v] as f:
f.orientation = DPFO.COLUMN
@property
def data(self):
return self.obj.DataFields
@data.setter
def data(self, values):
if not isinstance(values, tuple):
values = (values,)
for v in values:
with self[v] as f:
f.orientation = DPFO.DATA
class LOSheetTables(object):
def __init__(self, obj, sheet):
self._obj = obj
self._sheet = sheet
def __getitem__(self, index):
return LOSheetTable(self.obj[index])
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def __contains__(self, item):
return item in self.obj
@property
def obj(self):
return self._obj
@property
def count(self):
return self.obj.Count
@property
def names(self):
return self.obj.ElementNames
def new(self, name, target):
table = self.obj.createDataPilotDescriptor()
self.obj.insertNewByName(name, target.address, table)
return LOSheetTable(self.obj[name])
def remove(self, name):
self.obj.removeByName(name)
return
class LOFormControl(LOBaseObject):
EVENTS = {
'action': 'actionPerformed',
'click': 'mousePressed',
}
TYPES = {
'actionPerformed': 'XActionListener',
'mousePressed': 'XMouseListener',
}
def __init__(self, obj, view, form):
super().__init__(obj)
self._view = view
self._form = form
self._m = view.Model
self._index = -1
def __setattr__(self, name, value):
if name in ('_form', '_view', '_m', '_index'):
self.__dict__[name] = value
else:
super().__setattr__(name, value)
def __str__(self):
return f'{self.name} ({self.type}) {[self.index]}'
@property
def form(self):
return self._form
@property
def doc(self):
return self.obj.Parent.Forms.Parent
@property
def name(self):
return self._m.Name
@name.setter
def name(self, value):
self._m.Name = value
@property
def tag(self):
return self._m.Tag
@tag.setter
def tag(self, value):
self._m.Tag = value
@property
def index(self):
return self._index
@index.setter
def index(self, value):
self._index = value
@property
def enabled(self):
return self._m.Enabled
@enabled.setter
def enabled(self, value):
self._m.Enabled = value
@property
def events(self):
return self.form.getScriptEvents(self.index)
def add_event(self, name, macro):
if not 'name' in macro:
macro['name'] = '{}_{}'.format(self.name, name)
event = ScriptEventDescriptor()
event.AddListenerParam = ''
event.EventMethod = self.EVENTS[name]
event.ListenerType = self.TYPES[event.EventMethod]
event.ScriptCode = _get_url_script(macro)
event.ScriptType = 'Script'
for ev in self.events:
if ev.EventMethod == event.EventMethod and \
ev.ListenerType == event.ListenerType:
self.form.revokeScriptEvent(self.index,
event.ListenerType, event.EventMethod, event.AddListenerParam)
break
self.form.registerScriptEvent(self.index, event)
return
def set_focus(self):
self._view.setFocus()
return
class LOFormControlLabel(LOFormControl):
def __init__(self, obj, view, form):
super().__init__(obj, view, form)
@property
def type(self):
return 'label'
@property
def value(self):
return self._m.Label
@value.setter
def value(self, value):
self._m.Label = value
class LOFormControlText(LOFormControl):
def __init__(self, obj, view, form):
super().__init__(obj, view, form)
@property
def type(self):
return 'text'
@property
def value(self):
return self._m.Text
@value.setter
def value(self, value):
self._m.Text = value
class LOFormControlButton(LOFormControl):
def __init__(self, obj, view, form):
super().__init__(obj, view, form)
@property
def type(self):
return 'button'
@property
def value(self):
return self._m.Label
@value.setter
def value(self, value):
self._m.Text = Label
FORM_CONTROL_CLASS = {
'label': LOFormControlLabel,
'text': LOFormControlText,
'button': LOFormControlButton,
}
class LOForm(object):
MODELS = {
'label': 'com.sun.star.form.component.FixedText',
'text': 'com.sun.star.form.component.TextField',
'button': 'com.sun.star.form.component.CommandButton',
}
def __init__(self, obj, draw_page):
self._obj = obj
self._dp = draw_page
self._controls = {}
self._init_controls()
def __getitem__(self, index):
control = self.obj[index]
return self._controls[control.Name]
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def __contains__(self, item):
return item in self.obj
def __len__(self):
return len(self.obj)
def __str__(self):
return f'Form: {self.name}'
def _init_controls(self):
types = {
'com.sun.star.form.OFixedTextModel': 'label',
'com.sun.star.form.OEditModel': 'text',
'com.sun.star.form.OButtonModel': 'button',
}
for i, control in enumerate(self.obj):
name = control.Name
tipo = types[control.ImplementationName]
view = self.doc.CurrentController.getControl(control)
control = FORM_CONTROL_CLASS[tipo](control, view)
control.index = i
setattr(self, name, control)
self._controls[name] = control
return
@property
def obj(self):
return self._obj
@property
def name(self):
return self.obj.Name
@name.setter
def name(self, value):
self.obj.Name = value
@property
def source(self):
return self.obj.DataSourceName
@source.setter
def source(self, value):
self.obj.DataSourceName = value
@property
def type(self):
return self.obj.CommandType
@type.setter
def type(self, value):
self.obj.CommandType = value
@property
def command(self):
return self.obj.Command
@command.setter
def command(self, value):
self.obj.Command = value
@property
def doc(self):
return self.obj.Parent.Parent
def _special_properties(self, tipo, args):
if tipo == 'button':
# ~ if 'ImageURL' in args:
# ~ args['ImageURL'] = self._set_image_url(args['ImageURL'])
args['FocusOnClick'] = args.get('FocusOnClick', False)
return args
return args
def add(self, args):
name = args['Name']
tipo = args.pop('Type').lower()
w = args.pop('Width')
h = args.pop('Height')
x = args.pop('X', 0)
y = args.pop('Y', 0)
control = self.doc.createInstance('com.sun.star.drawing.ControlShape')
control.setSize(Size(w, h))
control.setPosition(Point(x, y))
model = self.doc.createInstance(self.MODELS[tipo])
args = self._special_properties(tipo, args)
_set_properties(model, args)
control.Control = model
index = len(self)
self.obj.insertByIndex(index, model)
self._dp.add(control)
view = self.doc.CurrentController.getControl(self.obj.getByName(name))
control = FORM_CONTROL_CLASS[tipo](control, view, self.obj)
control.index = index
setattr(self, name, control)
self._controls[name] = control
return control
class LOSheetForms(object):
def __init__(self, draw_page):
self._dp = draw_page
self._obj = draw_page.Forms
def __getitem__(self, index):
return LOForm(self.obj[index], self._dp)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def __contains__(self, item):
return item in self.obj
def __len__(self):
return len(self.obj)
@property
def obj(self):
return self._obj
@property
def doc(self):
return self.obj.Parent
@property
def count(self):
return len(self)
@property
def names(self):
return self.obj.ElementNames
def insert(self, name):
form = self.doc.createInstance('com.sun.star.form.component.Form')
self.obj.insertByName(name, form)
return LOForm(form, self._dp)
def remove(self, index):
if isinstance(index, int):
self.obj.removeByIndex(index)
else:
self.obj.removeByName(index)
return
# ~ IsFiltered,
# ~ IsManualPageBreak,
# ~ IsStartOfNewPage
class LOSheetRows(object):
def __init__(self, sheet, obj):
self._sheet = sheet
self._obj = obj
def __getitem__(self, index):
if isinstance(index, int):
rows = LOSheetRows(self._sheet, self.obj[index])
else:
rango = self._sheet[index.start:index.stop,0:]
rows = LOSheetRows(self._sheet, rango.obj.Rows)
return rows
def __len__(self):
return self.obj.Count
@property
def obj(self):
return self._obj
@property
def visible(self):
return self._obj.IsVisible
@visible.setter
def visible(self, value):
self._obj.IsVisible = value
@property
def color(self):
return self.obj.CellBackColor
@color.setter
def color(self, value):
self.obj.CellBackColor = value
@property
def is_transparent(self):
return self.obj.IsCellBackgroundTransparent
@is_transparent.setter
def is_transparent(self, value):
self.obj.IsCellBackgroundTransparent = value
@property
def height(self):
return self.obj.Height
@height.setter
def height(self, value):
self.obj.Height = value
def optimal(self):
self.obj.OptimalHeight = True
return
def insert(self, index, count):
self.obj.insertByIndex(index, count)
return
def remove(self, index, count):
self.obj.removeByIndex(index, count)
return
# ~ IsManualPageBreak,
# ~ IsStartOfNewPage
class LOSheetColumns(object):
def __init__(self, sheet, obj):
self._sheet = sheet
self._obj = obj
def __getitem__(self, index):
if isinstance(index, (int, str)):
rows = LOSheetColumns(self._sheet, self.obj[index])
else:
rango = self._sheet[0,index.start:index.stop]
rows = LOSheetColumns(self._sheet, rango.obj.Columns)
return rows
def __len__(self):
return self.obj.Count
@property
def obj(self):
return self._obj
@property
def visible(self):
return self._obj.IsVisible
@visible.setter
def visible(self, value):
self._obj.IsVisible = value
@property
def width(self):
return self.obj.Width
@width.setter
def width(self, value):
self.obj.Width = value
def optimal(self):
self.obj.OptimalWidth = True
return
def insert(self, index, count):
self.obj.insertByIndex(index, count)
return
def remove(self, index, count):
self.obj.removeByIndex(index, count)
return
class LOCalcSheet(object):
def __init__(self, obj):
self._obj = obj
def __getitem__(self, index):
return LOCalcRange(self.obj[index])
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def __str__(self):
return f'easymacro.LOCalcSheet: {self.name}'
@property
def obj(self):
return self._obj
@property
def name(self):
return self._obj.Name
@name.setter
def name(self, value):
self._obj.Name = value
@property
def code_name(self):
return self._obj.CodeName
@code_name.setter
def code_name(self, value):
self._obj.CodeName = value
@property
def visible(self):
return self._obj.IsVisible
@visible.setter
def visible(self, value):
self._obj.IsVisible = value
@property
def is_protected(self):
return self._obj.isProtected()
@property
def password(self):
return ''
@visible.setter
def password(self, value):
self.obj.protect(value)
def unprotect(self, value):
try:
self.obj.unprotect(value)
return True
except:
pass
return False
@property
def color(self):
return self._obj.TabColor
@color.setter
def color(self, value):
self._obj.TabColor = get_color(value)
@property
def used_area(self):
cursor = self.get_cursor()
cursor.gotoEndOfUsedArea(True)
return LOCalcRange(self[cursor.AbsoluteName].obj)
@property
def draw_page(self):
return LODrawPage(self.obj.DrawPage)
@property
def dp(self):
return self.draw_page
@property
def shapes(self):
return self.draw_page
@property
def doc(self):
return LOCalc(self.obj.DrawPage.Forms.Parent)
@property
def charts(self):
return LOSheetCharts(self.obj.Charts, self)
@property
def tables(self):
return LOSheetTables(self.obj.DataPilotTables, self)
@property
def rows(self):
return LOSheetRows(self, self.obj.Rows)
@property
def columns(self):
return LOSheetColumns(self, self.obj.Columns)
@property
def forms(self):
return LOSheetForms(self.obj.DrawPage)
@property
def events(self):
names = ('OnFocus', 'OnUnfocus', 'OnSelect', 'OnDoubleClick',
'OnRightClick', 'OnChange', 'OnCalculate')
evs = self.obj.Events
events = {n: _property_to_dict(evs.getByName(n)) for n in names
if evs.getByName(n)}
return events
@events.setter
def events(self, values):
pv = '[]com.sun.star.beans.PropertyValue'
ev = self.obj.Events
for name, v in values.items():
url = _get_url_script(v)
args = dict_to_property(dict(EventType='Script', Script=url))
# ~ e.replaceByName(k, args)
uno.invoke(ev, 'replaceByName', (name, uno.Any(pv, args)))
@property
def search_descriptor(self):
return self.obj.createSearchDescriptor()
@property
def replace_descriptor(self):
return self.obj.createReplaceDescriptor()
def activate(self):
self.doc.activate(self.obj)
return
def clean(self):
doc = self.doc
sheet = doc.create_instance('com.sun.star.sheet.Spreadsheet')
doc._sheets.replaceByName(self.name, sheet)
return
def move(self, pos=-1):
index = pos
if pos < 0:
index = len(self.doc)
self.doc._sheets.moveByName(self.name, index)
return
def remove(self):
self.doc._sheets.removeByName(self.name)
return
def copy(self, new_name='', pos=-1):
index = pos
if pos < 0:
index = len(self.doc)
self.doc._sheets.copyByName(self.name, new_name, index)
return LOCalcSheet(self.doc._sheets[new_name])
def copy_to(self, doc, target='', pos=-1):
index = pos
if pos < 0:
index = len(doc)
name = self.name
if not target:
new_name = name
doc._sheets.importSheet(self.doc.obj, name, index)
sheet = doc[name]
sheet.name = new_name
return sheet
def get_cursor(self, cell=None):
if cell is None:
cursor = self.obj.createCursor()
else:
cursor = self.obj.createCursorByRange(cell)
return cursor
def render(self, data, rango=None, clean=True):
if rango is None:
rango = self.used_area
return rango.render(data, clean)
def find(self, search_string, rango=None):
if rango is None:
rango = self.used_area
return rango.find(search_string)
class LOCalcRange(object):
def __init__(self, obj):
self._obj = obj
self._sd = None
self._is_cell = obj.ImplementationName == OBJ_CELL
def __getitem__(self, index):
return LOCalcRange(self.obj[index])
def __iter__(self):
self._r = 0
self._c = 0
return self
def __next__(self):
try:
rango = self[self._r, self._c]
except Exception as e:
raise StopIteration
self._c += 1
if self._c == self.columns:
self._c = 0
self._r +=1
return rango
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
def __contains__(self, item):
return item.in_range(self)
def __str__(self):
if self.is_none:
s = 'Range: None'
else:
s = f'Range: {self.name}'
return s
@property
def obj(self):
return self._obj
@property
def is_none(self):
return self.obj is None
@property
def is_cell(self):
return self._is_cell
@property
def back_color(self):
return self._obj.CellBackColor
@back_color.setter
def back_color(self, value):
self._obj.CellBackColor = get_color(value)
@property
def dp(self):
return self.sheet.dp
@property
def sheet(self):
return LOCalcSheet(self.obj.Spreadsheet)
@property
def doc(self):
doc = self.obj.Spreadsheet.DrawPage.Forms.Parent
return LODocument(doc)
@property
def name(self):
return self.obj.AbsoluteName
@property
def code_name(self):
name = self.name.replace('$', '').replace('.', '_').replace(':', '')
return name
@property
def columns(self):
return self.obj.Columns.Count
@property
def column(self):
c1 = self.address.Column
c2 = c1 + 1
ra = self.current_region.range_address
r1 = ra.StartRow
r2 = ra.EndRow + 1
return LOCalcRange(self.sheet[r1:r2, c1:c2].obj)
@property
def rows(self):
return LOSheetRows(self.sheet, self.obj.Rows)
@property
def row(self):
r1 = self.address.Row
r2 = r1 + 1
ra = self.current_region.range_address
c1 = ra.StartColumn
c2 = ra.EndColumn + 1
return LOCalcRange(self.sheet[r1:r2, c1:c2].obj)
@property
def type(self):
return self.obj.Type
@property
def value(self):
v = None
if self.type == VALUE:
v = self.obj.getValue()
elif self.type == TEXT:
v = self.obj.getString()
elif self.type == FORMULA:
v = self.obj.getFormula()
return v
@value.setter
def value(self, data):
if isinstance(data, str):
# ~ print(isinstance(data, str), data[0])
if data[0] in '=':
self.obj.setFormula(data)
# ~ print('Set Formula')
else:
self.obj.setString(data)
elif isinstance(data, Decimal):
self.obj.setValue(float(data))
elif isinstance(data, (int, float, bool)):
self.obj.setValue(data)
elif isinstance(data, datetime.datetime):
d = data.toordinal()
t = (data - datetime.datetime.fromordinal(d)).seconds / SECONDS_DAY
self.obj.setValue(d - DATE_OFFSET + t)
elif isinstance(data, datetime.date):
d = data.toordinal()
self.obj.setValue(d - DATE_OFFSET)
elif isinstance(data, datetime.time):
d = (data.hour * 3600 + data.minute * 60 + data.second) / SECONDS_DAY