diff --git a/CHANGELOG b/CHANGELOG
index 607d777..2332ccf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,22 +1,31 @@
+v 0.7.0 [27-sep-2019]
+ - Add support for InputBox and documents
+
+
v 0.6.0 [18-sep-2019]
- Add support for modify menus
+
v 0.5.0 [15-sep-2019]
---------------------
- Add support for shortcuts
+
v 0.4.0 [14-sep-2019]
---------------------
- Add support for locales
+
v 0.3.0 [10-sep-2019]
---------------------
- Add support for dialogs
+
v 0.2.0 [09-sep-2019]
---------------------
- Add support for context in menus
+
v 0.1.0 [06-sep-2019]
---------------------
- Initial version
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 45e2060..25ce94a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,3 +15,4 @@
**ETH**: `0x61a4f614a30ff686445751ed8328b82b77ecfc69`
+PayPal :( donate ATT elmau DOT net
diff --git a/README.md b/README.md
index 33a6766..6735a4b 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ Develop in pure Python, not need any dependence.
For Python 3.6+
-* See [documentation](https://gitlab.com/mauriciobaeza/zaz/wikis/home)
+* Look [documentation](https://gitlab.com/mauriciobaeza/zaz/wikis/home)
* Ver [documentación](https://gitlab.com/mauriciobaeza/zaz/wikis/inicio)
@@ -17,6 +17,8 @@ BCH: `1RPLWHJW34p7pMQV1ft4x7eWhAYw69Dsb`
BTC: `3Fe4JuADrAK8Qs7GDAxbSXR8E54avwZJLW`
-## Extension develop with ZAZ
+## Extensions develop with ZAZ
* https://gitlab.com/mauriciobaeza/zaz-barcode
+* https://gitlab.com/mauriciobaeza/zaz-favorite
+* https://gitlab.com/mauriciobaeza/zaz-easymacro
diff --git a/TODO.md b/TODO.md
index 3d28553..1db343e 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,6 +1,4 @@
-* Automatic update
* Help
-* Configuration
* Option panel
* Sub-menus
-* Panel lateral
+* Lateral panel
diff --git a/VERSION b/VERSION
index a918a2a..faef31a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.6.0
+0.7.0
diff --git a/source/conf.py.example b/source/conf.py.example
index 5daa760..9e4f591 100644
--- a/source/conf.py.example
+++ b/source/conf.py.example
@@ -393,7 +393,7 @@ NODE_MENUS = ''
if TYPE_EXTENSION == 1:
if PARENT == 'AddonMenu':
NODE_MENUS = '\n'.join(menus)
- else:
+ elif PARENT == 'OfficeMenuBar':
tmp = ' {}'
titles = '\n'.join([tmp.format(k, v) for k, v in MENU_MAIN.items()])
SUBMENUS = '\n ' + '\n'.join(menus) + '\n '
diff --git a/source/easymacro.py b/source/easymacro.py
index 8b41c24..0375c5b 100644
--- a/source/easymacro.py
+++ b/source/easymacro.py
@@ -17,11 +17,12 @@
# ~ You should have received a copy of the GNU General Public License
# ~ along with ZAZ. If not, see .
-
+import base64
import ctypes
import datetime
import errno
import getpass
+import hashlib
import json
import logging
import os
@@ -34,18 +35,30 @@ import sys
import tempfile
import threading
import time
+import traceback
import zipfile
from collections import OrderedDict
from collections.abc import MutableMapping
-from datetime import datetime
from functools import wraps
+from operator import itemgetter
from pathlib import Path, PurePath
from pprint import pprint
+from string import Template
from subprocess import PIPE
+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.util import Time, Date, DateTime
from com.sun.star.beans import PropertyValue
from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS
from com.sun.star.awt.MessageBoxResults import YES
@@ -60,12 +73,20 @@ from com.sun.star.lang import XEventListener
from com.sun.star.awt import XActionListener
from com.sun.star.awt import XMouseListener
+try:
+ from fernet import Fernet, InvalidToken
+ CRYPTO = True
+except ImportError:
+ CRYPTO = False
+
MSG_LANG = {
'es': {
'OK': 'Aceptar',
'Cancel': 'Cancelar',
'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',
}
}
@@ -94,7 +115,8 @@ TYPE_DOC = {
'writer': 'com.sun.star.text.TextDocument',
'impress': 'com.sun.star.presentation.PresentationDocument',
'draw': 'com.sun.star.drawing.DrawingDocument',
- 'base': 'com.sun.star.sdb.OfficeDatabaseDocument',
+ # ~ 'base': 'com.sun.star.sdb.OfficeDatabaseDocument',
+ 'base': 'com.sun.star.sdb.DocumentDataSource',
'math': 'com.sun.star.formula.FormulaProperties',
'basic': 'com.sun.star.script.BasicIDE',
}
@@ -113,14 +135,33 @@ MENUS_CALC = {
'windows': '.uno:WindowList',
'help': '.uno:HelpMenu',
}
+MENUS_WRITER = {
+ 'file': '.uno:PickList',
+ 'edit': '.uno:EditMenu',
+ 'view': '.uno:ViewMenu',
+ 'insert': '.uno:InsertMenu',
+ 'format': '.uno:FormatMenu',
+ 'styles': '.uno:FormatStylesMenu',
+ 'sheet': '.uno:TableMenu',
+ 'data': '.uno:FormatFormMenu',
+ 'tools': '.uno:ToolsMenu',
+ 'windows': '.uno:WindowList',
+ 'help': '.uno:HelpMenu',
+}
MENUS_APP = {
'calc': MENUS_CALC,
+ 'writer': MENUS_WRITER,
}
-FILE_NAME_DEBUG = 'zaz-debug.log'
-FILE_NAME_CONFIG = 'zaz-config.json'
+EXT = {
+ 'pdf': 'pdf',
+}
+
+
+FILE_NAME_DEBUG = 'debug.odt'
+FILE_NAME_CONFIG = 'zaz-{}.json'
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')
@@ -130,10 +171,16 @@ logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE)
log = logging.getLogger(__name__)
+_start = 0
+_stop_thread = {}
+TIMEOUT = 10
+
+
CTX = uno.getComponentContext()
SM = CTX.getServiceManager()
+# ~ Export ok
def create_instance(name, with_context=False):
if with_context:
instance = SM.createInstanceWithContext(name, CTX)
@@ -163,6 +210,7 @@ NAME = TITLE = _get_app_config('ooName', 'org.openoffice.Setup/Product')
VERSION = _get_app_config('ooSetupVersion', 'org.openoffice.Setup/Product')
+# ~ Export ok
def mri(obj):
m = create_instance('mytools.Mri')
if m is None:
@@ -180,7 +228,10 @@ def catch_exception(f):
try:
return f(*args, **kwargs)
except Exception as e:
- log.error(f.__name__, exc_info=True)
+ name = f.__name__
+ if IS_WIN:
+ debug(traceback.format_exc())
+ log.error(name, exc_info=True)
return func
@@ -188,30 +239,30 @@ class LogWin(object):
def __init__(self, doc):
self.doc = doc
+ self.doc.Title = FILE_NAME_DEBUG
def write(self, info):
text = self.doc.Text
cursor = text.createTextCursor()
cursor.gotoEnd(False)
- text.insertString(cursor, str(info), 0)
+ text.insertString(cursor, str(info) + '\n\n', 0)
return
+# ~ Export ok
def info(data):
log.info(data)
return
+# ~ Export ok
def debug(info):
if IS_WIN:
- # ~ app = LOApp(self.ctx, self.sm, self.desktop, self.toolkit)
- # ~ doc = app.getDoc(FILE_NAME_DEBUG)
- # ~ if not doc:
- # ~ doc = app.newDoc(WRITER)
- # ~ out = OutputDoc(doc)
- # ~ sys.stdout = out
- # ~ pprint(info)
- doc = LogWin(new_doc('writer').obj)
+ doc = get_document(FILE_NAME_DEBUG)
+ if doc is None:
+ # ~ doc = new_doc('writer')
+ return
+ doc = LogWin(doc.obj)
doc.write(info)
return
@@ -219,14 +270,16 @@ def debug(info):
return
+# ~ Export ok
def error(info):
log.error(info)
return
+# ~ Export ok
def save_log(path, data):
with open(path, 'a') as out:
- out.write('{} -{}- '.format(str(datetime.now())[:19], LOG_NAME))
+ out.write('{} -{}- '.format(str(now())[:19], LOG_NAME))
pprint(data, stream=out)
return
@@ -239,32 +292,40 @@ def run_in_thread(fn):
return run
-def get_config(key=''):
- values = {}
- path = join(get_config_path('UserConfig'), FILE_NAME_CONFIG)
+def now():
+ return datetime.datetime.now()
+
+
+# ~ Export ok
+def get_config(key='', default=None, prefix='config'):
+ path_json = FILE_NAME_CONFIG.format(prefix)
+ values = None
+ path = join(get_config_path('UserConfig'), path_json)
if not exists_path(path):
- return values
+ return default
with open(path, 'r', encoding='utf-8') as fh:
data = fh.read()
- if data:
- values = json.loads(data)
+ values = json.loads(data)
if key:
- return values.get(key, None)
+ return values.get(key, default)
return values
-def set_config(key, value):
- path = join(get_config_path('UserConfig'), FILE_NAME_CONFIG)
- values = get_config()
+# ~ Export ok
+def set_config(key, value, prefix='config'):
+ path_json = FILE_NAME_CONFIG.format(prefix)
+ path = join(get_config_path('UserConfig'), path_json)
+ values = get_config(default={}, prefix=prefix)
values[key] = value
with open(path, 'w', encoding='utf-8') as fh:
json.dump(values, fh, ensure_ascii=False, sort_keys=True, indent=4)
- return True
+ return
+# ~ Export ok
def sleep(seconds):
time.sleep(seconds)
return
@@ -281,6 +342,7 @@ def _(msg):
return MSG_LANG[L][msg]
+# ~ Export ok
def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infobox'):
""" Create message box
type_msg: infobox, warningbox, errorbox, querybox, messbox
@@ -292,15 +354,18 @@ def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infob
return mb.execute()
+# ~ Export ok
def question(message, title=TITLE):
res = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, 'querybox')
return res == YES
+# ~ Export ok
def warning(message, title=TITLE):
return msgbox(message, title, type_msg='warningbox')
+# ~ Export ok
def errorbox(message, title=TITLE):
return msgbox(message, title, type_msg='errorbox')
@@ -309,10 +374,20 @@ def get_desktop():
return create_instance('com.sun.star.frame.Desktop', True)
+# ~ Export ok
def get_dispatch():
return create_instance('com.sun.star.frame.DispatchHelper')
+# ~ Export ok
+def call_dispatch(url, args=()):
+ frame = get_document().frame
+ dispatch = get_dispatch()
+ dispatch.executeDispatch(frame, url, '', 0, args)
+ return
+
+
+# ~ Export ok
def get_temp_file():
delete = True
if IS_WIN:
@@ -332,6 +407,7 @@ def _path_system(path):
return path
+# ~ Export ok
def exists_app(name):
try:
dn = subprocess.DEVNULL
@@ -341,33 +417,20 @@ def exists_app(name):
return False
return True
-# ~ Delete
-def exists(path):
- return Path(path).exists()
+
+# ~ Export ok
def exists_path(path):
return Path(path).exists()
+# ~ Export ok
def get_type_doc(obj):
- # ~ services = {
- # ~ 'calc': 'com.sun.star.sheet.SpreadsheetDocument',
- # ~ 'writer': 'com.sun.star.text.TextDocument',
- # ~ 'impress': 'com.sun.star.presentation.PresentationDocument',
- # ~ 'draw': 'com.sun.star.drawing.DrawingDocument',
- # ~ 'base': 'com.sun.star.sdb.OfficeDatabaseDocument',
- # ~ 'math': 'com.sun.star.formula.FormulaProperties',
- # ~ 'basic': 'com.sun.star.script.BasicIDE',
- # ~ }
for k, v in TYPE_DOC.items():
if obj.supportsService(v):
return k
return ''
-# ~ def _properties(values):
- # ~ p = [PropertyValue(Name=n, Value=v) for n, v in values.items()]
- # ~ return tuple(p)
-
def dict_to_property(values, uno_any=False):
ps = tuple([PropertyValue(Name=n, Value=v) for n, v in values.items()])
if uno_any:
@@ -380,82 +443,12 @@ def property_to_dict(values):
return d
-# ~ Third classes
-
-
-# ~ https://github.com/psf/requests/blob/v2.22.0/requests/structures.py
-class CaseInsensitiveDict(MutableMapping):
- """A case-insensitive ``dict``-like object.
- Implements all methods and operations of
- ``MutableMapping`` as well as dict's ``copy``. Also
- provides ``lower_items``.
- All keys are expected to be strings. The structure remembers the
- case of the last key to be set, and ``iter(instance)``,
- ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
- will contain case-sensitive keys. However, querying and contains
- testing is case insensitive::
- cid = CaseInsensitiveDict()
- cid['Accept'] = 'application/json'
- cid['aCCEPT'] == 'application/json' # True
- list(cid) == ['Accept'] # True
- For example, ``headers['content-encoding']`` will return the
- value of a ``'Content-Encoding'`` response header, regardless
- of how the header name was originally stored.
- If the constructor, ``.update``, or equality comparison
- operations are given keys that have equal ``.lower()``s, the
- behavior is undefined.
- """
-
- def __init__(self, data=None, **kwargs):
- self._store = OrderedDict()
- if data is None:
- data = {}
- self.update(data, **kwargs)
-
- def __setitem__(self, key, value):
- # Use the lowercased key for lookups, but store the actual
- # key alongside the value.
- self._store[key.lower()] = (key, value)
-
- def __getitem__(self, key):
- return self._store[key.lower()][1]
-
- def __delitem__(self, key):
- del self._store[key.lower()]
-
- def __iter__(self):
- return (casedkey for casedkey, mappedvalue in self._store.values())
-
- def __len__(self):
- return len(self._store)
-
- def lower_items(self):
- """Like iteritems(), but with all lowercase keys."""
- return (
- (lowerkey, keyval[1])
- for (lowerkey, keyval)
- in self._store.items()
- )
-
- def __eq__(self, other):
- if isinstance(other, Mapping):
- other = CaseInsensitiveDict(other)
- else:
- return NotImplemented
- # Compare insensitively
- return dict(self.lower_items()) == dict(other.lower_items())
-
- # Copy is required
- def copy(self):
- return CaseInsensitiveDict(self._store.values())
-
- def __repr__(self):
- return str(dict(self.items()))
+def array_to_dict(values):
+ d = {r[0]: r[1] for r in values}
+ return d
# ~ Custom classes
-
-
class LODocument(object):
def __init__(self, obj):
@@ -464,7 +457,10 @@ class LODocument(object):
def _init_values(self):
self._type_doc = get_type_doc(self.obj)
- self._cc = self.obj.getCurrentController()
+ if self._type_doc == 'base':
+ self._cc = self.obj.DatabaseDocument.getCurrentController()
+ else:
+ self._cc = self.obj.getCurrentController()
return
@property
@@ -499,6 +495,10 @@ class LODocument(object):
def path(self):
return _path_system(self.obj.getURL())
+ @property
+ def statusbar(self):
+ return self._cc.getStatusIndicator()
+
@property
def visible(self):
w = self._cc.getFrame().getContainerWindow()
@@ -543,6 +543,31 @@ class LODocument(object):
self._cc.insertTransferable(transferable)
return self.obj.getCurrentSelection()
+ @catch_exception
+ def to_pdf(self, path, **kwargs):
+ path_pdf = path
+ if path:
+ if is_dir(path):
+ _, _, n, _ = get_info_path(self.path)
+ path_pdf = join(path, '{}.{}'.format(n, EXT['pdf']))
+ else:
+ path_pdf = replace_ext(self.path, EXT['pdf'])
+
+ filter_name = '{}_pdf_Export'.format(self.type)
+ filter_data = dict_to_property(kwargs, True)
+ args = {
+ 'FilterName': filter_name,
+ 'FilterData': filter_data,
+ }
+ args = dict_to_property(args)
+ try:
+ self.obj.storeToURL(_path_url(path_pdf), args)
+ except Exception as e:
+ error(e)
+ path_pdf = ''
+
+ return path_pdf
+
class LOCalc(LODocument):
@@ -913,6 +938,34 @@ class EventsMouse(EventsListenerBase, XMouseListener):
pass
+class EventsMouseGrid(EventsMouse):
+ selected = False
+
+ def mousePressed(self, event):
+ super().mousePressed(event)
+ # ~ obj = event.Source
+ # ~ col = obj.getColumnAtPoint(event.X, event.Y)
+ # ~ row = obj.getRowAtPoint(event.X, event.Y)
+ # ~ print(col, row)
+ # ~ if col == -1 and row == -1:
+ # ~ if self.selected:
+ # ~ obj.deselectAllRows()
+ # ~ else:
+ # ~ obj.selectAllRows()
+ # ~ self.selected = not self.selected
+ return
+
+ def mouseReleased(self, event):
+ obj = event.Source
+ col = obj.getColumnAtPoint(event.X, event.Y)
+ row = obj.getRowAtPoint(event.X, event.Y)
+ if row == -1 and col > -1:
+ gdm = obj.Model.GridDataModel
+ for i in range(gdm.RowCount):
+ gdm.updateRowHeading(i, i + 1)
+ return
+
+
class UnoBaseObject(object):
def __init__(self, obj):
@@ -1020,6 +1073,10 @@ class UnoLabel(UnoBaseObject):
def __init__(self, obj):
super().__init__(obj)
+ @property
+ def type(self):
+ return 'label'
+
@property
def value(self):
return self.model.Label
@@ -1032,16 +1089,10 @@ class UnoButton(UnoBaseObject):
def __init__(self, obj):
super().__init__(obj)
- # ~ self._set_icon()
- def _set_icon(self):
- icon_name = self.tag.strip()
- if icon_name:
- path_icon = _file_url('{}/img/{}'.format(CURRENT_PATH, icon_name))
- self._model.ImageURL = path_icon
- if self.value:
- self._model.ImageAlign = 0
- return
+ @property
+ def type(self):
+ return 'button'
@property
def value(self):
@@ -1056,6 +1107,10 @@ class UnoText(UnoBaseObject):
def __init__(self, obj):
super().__init__(obj)
+ @property
+ def type(self):
+ return 'text'
+
@property
def value(self):
return self.model.Text
@@ -1088,6 +1143,118 @@ class UnoListBox(UnoBaseObject):
return
+class UnoGrid(UnoBaseObject):
+
+ def __init__(self, obj):
+ super().__init__(obj)
+ self._gdm = self._model.GridDataModel
+ # ~ self._data = []
+ self._columns = {}
+ # ~ self._format_columns = ()
+
+ def __getitem__(self, index):
+ value = self._gdm.getCellData(index[0], index[1])
+ return value
+
+ @property
+ def type(self):
+ return 'grid'
+
+ def _format_cols(self):
+ rows = tuple(tuple(
+ self._format_columns[i].format(r) for i, r in enumerate(row)) for row in self._data
+ )
+ return rows
+
+ # ~ @property
+ # ~ def format_columns(self):
+ # ~ return self._format_columns
+ # ~ @format_columns.setter
+ # ~ def format_columns(self, value):
+ # ~ self._format_columns = value
+
+ @property
+ def data(self):
+ return self._data
+ @data.setter
+ def data(self, values):
+ # ~ self._data = values
+ self._gdm.removeAllRows()
+ headings = tuple(range(1, len(values) + 1))
+ self._gdm.addRows(headings, values)
+
+ # ~ rows = range(grid_dm.RowCount)
+ # ~ colors = [COLORS['GRAY'] if r % 2 else COLORS['WHITE'] for r in rows]
+ # ~ grid.Model.RowBackgroundColors = tuple(colors)
+ return
+
+ @property
+ def row(self):
+ return self.obj.CurrentRow
+
+ @property
+ def rows(self):
+ return self._gdm.RowCount
+
+ @property
+ def column(self):
+ return self.obj.CurrentColumn
+
+ @property
+ def columns(self):
+ return self._gdm.ColumnCount
+
+ def set_cell_tooltip(self, col, row, value):
+ self._gdm.updateCellToolTip(col, row, value)
+ return
+
+ def get_cell_tooltip(self, col, row):
+ value = self._gdm.getCellToolTip(col, row)
+ return value
+
+ def _validate_column(self, data):
+ row = []
+ for i, d in enumerate(data):
+ if i in self._columns:
+ if 'image' in self._columns[i]:
+ row.append(self._columns[i]['image'])
+ else:
+ row.append(d)
+ return tuple(row)
+
+ def add_row(self, data):
+ # ~ self._data.append(data)
+ data = self._validate_column(data)
+ self._gdm.addRow(self.rows + 1, data)
+ return
+
+ def remove_row(self, row):
+ self._gdm.removeRow(row)
+ # ~ del self._data[row]
+ self._update_row_heading()
+ return
+
+ def _update_row_heading(self):
+ for i in range(self.rows):
+ self._gdm.updateRowHeading(i, i + 1)
+ return
+
+ def sort(self, column, asc=True):
+ self._gdm.sortByColumn(column, asc)
+ # ~ self._data.sort(key=itemgetter(column), reverse=not asc)
+ self._update_row_heading()
+ return
+
+ def set_column_image(self, column, path):
+ gp = create_instance('com.sun.star.graphic.GraphicProvider')
+ data = dict_to_property({'URL': _path_url(path)})
+ image = gp.queryGraphic(data)
+ if not column in self._columns:
+ self._columns[column] = {}
+ self._columns[column]['image'] = image
+ return
+
+
class LODialog(object):
def __init__(self, properties):
@@ -1160,6 +1327,9 @@ class LODialog(object):
}
for key, value in listeners.items():
if hasattr(control.obj, key):
+ if control.type == 'grid' and key == 'addMouseListener':
+ control.obj.addMouseListener(EventsMouseGrid(self.events))
+ continue
getattr(control.obj, key)(listeners[key](self.events))
return
@@ -1191,6 +1361,7 @@ class LODialog(object):
'button': UnoButton,
'text': UnoText,
'listbox': UnoListBox,
+ 'grid': UnoGrid,
# ~ 'link': UnoLink,
# ~ 'tab': UnoTab,
# ~ 'roadmap': UnoRoadmap,
@@ -1198,13 +1369,36 @@ class LODialog(object):
# ~ 'radio': UnoRadio,
# ~ 'groupbox': UnoGroupBox,
# ~ 'tree': UnoTree,
- # ~ 'grid': UnoGrid,
}
return classes[tipo](obj)
+ def _set_column_model(self, columns):
+ #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html
+ column_model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True)
+ # ~ column_model.setDefaultColumns(len(columns))
+ for column in columns:
+ grid_column = create_instance('com.sun.star.awt.grid.GridColumn', True)
+ for k, v in column.items():
+ setattr(grid_column, k, v)
+ column_model.addColumn(grid_column)
+ # ~ mri(grid_column)
+ return column_model
+
+ def _set_image_url(self, path):
+ if exists_path(path):
+ return _path_url(path)
+ return ''
+
@catch_exception
def add_control(self, properties):
tipo = properties.pop('Type').lower()
+
+ columns = properties.pop('Columns', ())
+ if tipo == 'grid':
+ properties['ColumnModel'] = self._set_column_model(columns)
+ if tipo == 'button' and 'ImageURL' in properties:
+ properties['ImageURL'] = self._set_image_url(properties['ImageURL'])
+
model = self.model.createInstance(self._get_control_model(tipo))
set_properties(model, properties)
name = properties['Name']
@@ -1233,14 +1427,35 @@ def _get_class_doc(obj):
return classes[type_doc](obj)
-def get_document():
+# ~ Export ok
+def get_document(title=''):
doc = None
desktop = get_desktop()
- try:
+ if not title:
doc = _get_class_doc(desktop.getCurrentComponent())
- except Exception as e:
- log.error(e)
- return doc
+ return doc
+
+ for d in desktop.getComponents():
+ if d.Title == title:
+ doc = d
+ break
+
+ if doc is None:
+ return
+
+ return _get_class_doc(doc)
+
+
+# ~ Export ok
+def get_documents(custom=True):
+ docs = []
+ desktop = get_desktop()
+ for doc in desktop.getComponents():
+ if custom:
+ docs.append(_get_class_doc(doc))
+ else:
+ docs.append(doc)
+ return docs
def get_selection():
@@ -1277,6 +1492,7 @@ def set_properties(model, properties):
return
+# ~ Export ok
def get_config_path(name='Work'):
"""
Return de path name in config
@@ -1286,6 +1502,7 @@ def get_config_path(name='Work'):
return _path_system(getattr(path, name))
+# ~ Export ok
def get_file(init_dir='', multiple=False, filters=()):
"""
init_folder: folder default open
@@ -1298,31 +1515,115 @@ def get_file(init_dir='', multiple=False, filters=()):
"""
if not init_dir:
init_dir = get_config_path()
+ init_dir = _path_url(init_dir)
file_picker = create_instance('com.sun.star.ui.dialogs.FilePicker')
file_picker.setTitle(_('Select file'))
file_picker.setDisplayDirectory(init_dir)
file_picker.setMultiSelectionMode(multiple)
+ path = ''
if filters:
file_picker.setCurrentFilter(filters[0][0])
for f in filters:
file_picker.appendFilter(f[0], f[1])
if file_picker.execute():
+ path = _path_system(file_picker.getSelectedFiles()[0])
if multiple:
- return [_path_system(f) for f in file_picker.getSelectedFiles()]
- return _path_system(file_picker.getSelectedFiles()[0])
+ path = [_path_system(f) for f in file_picker.getSelectedFiles()]
- return ''
+ return path
+# ~ Export ok
+def get_path(init_dir='', filters=()):
+ """
+ Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html
+ filters: Example
+ (
+ ('XML', '*.xml'),
+ ('TXT', '*.txt'),
+ )
+ """
+ if not init_dir:
+ init_dir = get_config_path()
+ init_dir = _path_url(init_dir)
+ file_picker = create_instance('com.sun.star.ui.dialogs.FilePicker')
+ file_picker.setTitle(_('Select file'))
+ file_picker.setDisplayDirectory(init_dir)
+ file_picker.initialize((2,))
+ if filters:
+ file_picker.setCurrentFilter(filters[0][0])
+ for f in filters:
+ file_picker.appendFilter(f[0], f[1])
+
+ path = ''
+ if file_picker.execute():
+ path = _path_system(file_picker.getSelectedFiles()[0])
+ return path
+
+
+# ~ Export ok
+def get_dir(init_dir=''):
+ folder_picker = create_instance('com.sun.star.ui.dialogs.FolderPicker')
+ if not init_dir:
+ init_dir = get_config_path()
+ init_dir = _path_url(init_dir)
+ folder_picker.setDisplayDirectory(init_dir)
+
+ path = ''
+ if folder_picker.execute():
+ path = _path_system(folder_picker.getDirectory())
+ return path
+
+
+# ~ Export ok
def get_info_path(path):
path, filename = os.path.split(path)
name, extension = os.path.splitext(filename)
return (path, filename, name, extension)
-def inputbox(message, default='', title=TITLE):
+# ~ Export ok
+def read_file(path, mode='r', array=False):
+ data = ''
+ with open(path, mode) as f:
+ if array:
+ data = tuple(f.read().splitlines())
+ else:
+ data = f.read()
+ return data
+
+
+# ~ Export ok
+def save_file(path, mode='w', data=None):
+ with open(path, mode) as f:
+ f.write(data)
+ return
+
+
+# ~ Export ok
+def to_json(path, data):
+ with open(path, 'w') as f:
+ f.write(json.dumps(data, indent=4, sort_keys=True))
+ return
+
+
+# ~ Export ok
+def from_json(path):
+ with open(path) as f:
+ data = json.loads(f.read())
+ return data
+
+
+def get_path_extension(id):
+ pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider')
+ path = _path_system(pip.getPackageLocation(id))
+ return path
+
+
+# ~ Export ok
+def inputbox(message, default='', title=TITLE, echochar=''):
class ControllersInput(object):
@@ -1361,6 +1662,8 @@ def inputbox(message, default='', title=TITLE):
'Width': 190,
'Height': 15,
}
+ if echochar:
+ args['EchoChar'] = ord(echochar[0])
dlg.add_control(args)
dlg.txt_value.move(dlg.lbl_msg)
@@ -1393,12 +1696,24 @@ def inputbox(message, default='', title=TITLE):
return ''
-def new_doc(type_doc=CALC):
+# ~ Export ok
+def new_doc(type_doc=CALC, **kwargs):
path = 'private:factory/s{}'.format(type_doc)
- doc = get_desktop().loadComponentFromURL(path, '_default', 0, ())
+ opt = dict_to_property(kwargs)
+ doc = get_desktop().loadComponentFromURL(path, '_default', 0, opt)
return _get_class_doc(doc)
+# ~ Export ok
+def new_db(path):
+ dbc = create_instance('com.sun.star.sdb.DatabaseContext')
+ db = dbc.createInstance()
+ db.URL = 'sdbc:embedded:firebird' # hsqldb
+ db.DatabaseDocument.storeAsURL(_path_url(path), ())
+ return _get_class_doc(db)
+
+
+# ~ Export ok
def open_doc(path, **kwargs):
""" Open document in path
Usually options:
@@ -1413,7 +1728,6 @@ def open_doc(path, **kwargs):
http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1document_1_1MediaDescriptor.html
"""
path = _path_url(path)
- # ~ opt = _properties(kwargs)
opt = dict_to_property(kwargs)
doc = get_desktop().loadComponentFromURL(path, '_blank', 0, opt)
if doc is None:
@@ -1422,6 +1736,7 @@ def open_doc(path, **kwargs):
return _get_class_doc(doc)
+# ~ Export ok
def open_file(path):
if IS_WIN:
os.startfile(path)
@@ -1430,37 +1745,45 @@ def open_file(path):
return
+# ~ Export ok
def join(*paths):
return os.path.join(*paths)
+# ~ Export ok
def is_dir(path):
return Path(path).is_dir()
+# ~ Export ok
def is_file(path):
return Path(path).is_file()
+# ~ Export ok
def get_file_size(path):
return Path(path).stat().st_size
+# ~ Export ok
def is_created(path):
return is_file(path) and bool(get_file_size(path))
+# ~ Export ok
def replace_ext(path, extension):
path, _, name, _ = get_info_path(path)
return '{}/{}.{}'.format(path, name, extension)
-def zip_names(path):
+# ~ Export ok
+def zip_content(path):
with zipfile.ZipFile(path) as z:
names = z.namelist()
return names
+# ~ Export ok
def run(command, wait=False):
# ~ debug(command)
# ~ debug(shlex.split(command))
@@ -1515,6 +1838,7 @@ def _zippwd(source, target, pwd):
return is_created(target)
+# ~ Export ok
def zip(source, target='', mode='w', pwd=''):
if pwd:
return _zippwd(source, target, pwd)
@@ -1556,6 +1880,7 @@ def zip(source, target='', mode='w', pwd=''):
return is_created(target)
+# ~ Export ok
def unzip(source, path='', members=None, pwd=None):
if not path:
path, _, _, _ = get_info_path(source)
@@ -1568,6 +1893,7 @@ def unzip(source, path='', members=None, pwd=None):
return True
+# ~ Export ok
def merge_zip(target, zips):
try:
with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as t:
@@ -1582,18 +1908,20 @@ def merge_zip(target, zips):
return True
+# ~ Export ok
def kill(path):
p = Path(path)
- if p.is_file():
- try:
+ try:
+ if p.is_file():
p.unlink()
- except:
- pass
- elif p.is_dir():
- p.rmdir()
+ elif p.is_dir():
+ shutil.rmtree(path)
+ except OSError as e:
+ log.error(e)
return
+# ~ Export ok
def get_size_screen():
if IS_WIN:
user32 = ctypes.windll.user32
@@ -1604,6 +1932,7 @@ def get_size_screen():
return res.strip()
+# ~ Export ok
def get_clipboard():
df = None
text = ''
@@ -1649,6 +1978,7 @@ class TextTransferable(unohelper.Base, XTransferable):
return False
+# ~ Export ok
def set_clipboard(value):
ts = TextTransferable(value)
sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard')
@@ -1656,40 +1986,38 @@ def set_clipboard(value):
return
-def copy(doc=None):
- if doc is None:
- doc = get_document()
- if hasattr(doc, 'frame'):
- frame = doc.frame
- else:
- frame = doc.getCurrentController().getFrame()
- dispatch = get_dispatch()
- dispatch.executeDispatch(frame, '.uno:Copy', '', 0, ())
+# ~ Todo
+def copy():
+ call_dispatch('.uno:Copy')
return
+# ~ Export ok
def get_epoch():
- now = datetime.datetime.now()
- return int(time.mktime(now.timetuple()))
+ n = now()
+ return int(time.mktime(n.timetuple()))
+# ~ Export ok
def file_copy(source, target='', name=''):
p, f, n, e = get_info_path(source)
if target:
p = target
if name:
+ e = ''
n = name
path_new = join(p, '{}{}'.format(n, e))
shutil.copy(source, path_new)
- return
+ return path_new
-def get_files(path, ext='*'):
- docs = []
+# ~ Export ok
+def get_path_content(path, filters='*'):
+ paths = []
for folder, _, files in os.walk(path):
- pattern = re.compile(r'\.{}'.format(ext), re.IGNORECASE)
- docs += [join(folder, f) for f in files if pattern.search(f)]
- return docs
+ pattern = re.compile(r'\.(?:{})$'.format(filters), re.IGNORECASE)
+ paths += [join(folder, f) for f in files if pattern.search(f)]
+ return paths
def _get_menu(type_doc, name_menu):
@@ -1739,7 +2067,7 @@ def insert_menu(type_doc, name_menu, **kwargs):
separator = False
if label == '-':
separator = True
- command = kwargs.get('Command', '')
+ command = kwargs.get('CommandURL', '')
index = kwargs.get('Index', 0)
if not index:
index = _get_index_menu(menu, kwargs['After'])
@@ -1779,6 +2107,8 @@ def _add_sub_menus(ui, menus, menu, sub_menu):
if submenu:
idc = ui.createSettings()
sm['ItemDescriptorContainer'] = idc
+ if sm['Label'] == '-':
+ sm = {'Type': 1}
_store_menu(ui, menus, menu, i - 1, sm)
if submenu:
_add_sub_menus(ui, menus, idc, submenu)
@@ -1799,6 +2129,488 @@ def remove_menu(type_doc, name_menu, command):
return True
+def _get_app_submenus(menus, count=0):
+ for i, menu in enumerate(menus):
+ data = property_to_dict(menu)
+ cmd = data.get('CommandURL', '')
+ msg = ' ' * count + '├─' + cmd
+ debug(msg)
+ submenu = data.get('ItemDescriptorContainer', None)
+ if not submenu is None:
+ _get_app_submenus(submenu, count + 1)
+ return
+
+
+def get_app_menus(name_app, index=-1):
+ instance = 'com.sun.star.ui.ModuleUIConfigurationManagerSupplier'
+ service = TYPE_DOC[name_app]
+ manager = create_instance(instance, True)
+ ui = manager.getUIConfigurationManager(service)
+ menus = ui.getSettings(NODE_MENUBAR, True)
+ if index == -1:
+ for menu in menus:
+ data = property_to_dict(menu)
+ debug(data.get('CommandURL', ''))
+ else:
+ menus = property_to_dict(menus[index])['ItemDescriptorContainer']
+ _get_app_submenus(menus)
+ return menus
+
+
+# ~ Export ok
+def start():
+ global _start
+ _start = now()
+ log.info(_start)
+ return
+
+
+# ~ Export ok
+def end():
+ global _start
+ e = now()
+ return str(e - _start).split('.')[0]
+
+
+# ~ Export ok
+# ~ https://en.wikipedia.org/wiki/Web_colors
+def get_color(value):
+ 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,
+ }
+
+ if isinstance(value, tuple):
+ return (value[0] << 16) + (value[1] << 8) + value[2]
+
+ if isinstance(value, str) and value[0] == '#':
+ r, g, b = bytes.fromhex(value[1:])
+ return (r << 16) + (g << 8) + b
+
+ return COLORS.get(value.lower(), -1)
+
+
+# ~ Export ok
+def render(template, data):
+ s = Template(template)
+ return s.safe_substitute(**data)
+
+
+def _to_date(value):
+ new_value = value
+ if isinstance(value, Time):
+ new_value = datetime.time(value.Hours, value.Minutes, value.Seconds)
+ elif isinstance(value, Date):
+ new_value = datetime.date(value.Year, value.Month, value.Day)
+ elif isinstance(value, DateTime):
+ new_value = datetime.datetime(
+ value.Year, value.Month, value.Day,
+ value.Hours, value.Minutes, value.Seconds)
+ return new_value
+
+
+# ~ Export ok
+def format(template, data):
+ """
+ https://pyformat.info/
+ """
+ if isinstance(data, (str, int, float)):
+ # ~ print(template.format(data))
+ return template.format(data)
+
+ if isinstance(data, (Time, Date, DateTime)):
+ return template.format(_to_date(data))
+
+ if isinstance(data, tuple) and isinstance(data[0], tuple):
+ data = {r[0]: _to_date(r[1]) for r in data}
+ return template.format(**data)
+
+ data = [_to_date(v) for v in data]
+ result = template.format(*data)
+ return result
+
+
+def _call_macro(macro):
+ #~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification
+ name = 'com.sun.star.script.provider.MasterScriptProviderFactory'
+ factory = create_instance(name, False)
+
+ data = macro.copy()
+ if macro['language'] == 'Python':
+ data['module'] = '.py$'
+ elif macro['language'] == 'Basic':
+ data['module'] = '.{}.'.format(macro['module'])
+ if macro['location'] == 'user':
+ data['location'] = 'application'
+ else:
+ data['module'] = '.'
+
+ args = macro.get('args', ())
+ url = 'vnd.sun.star.script:{library}{module}{name}?language={language}&location={location}'
+ path = url.format(**data)
+ script = factory.createScriptProvider('').getScript(path)
+ return script.invoke(args, None, None)[0]
+
+
+# ~ Export ok
+def call_macro(macro):
+ in_thread = macro.pop('thread')
+ if in_thread:
+ t = threading.Thread(target=_call_macro, args=(macro,))
+ t.start()
+ return
+
+ return _call_macro(macro)
+
+
+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
+
+
+# ~ Export ok
+def timer(name, seconds, macro):
+ global _stop_thread
+ _stop_thread[name] = threading.Event()
+ thread = TimerThread(_stop_thread[name], seconds, macro)
+ thread.start()
+ return
+
+
+# ~ Export ok
+def stop_timer(name):
+ global _stop_thread
+ _stop_thread[name].set()
+ del _stop_thread[name]
+ return
+
+
+def _get_key(password):
+ digest = hashlib.sha256(password.encode()).digest()
+ key = base64.urlsafe_b64encode(digest)
+ return key
+
+
+# ~ Export ok
+def encrypt(data, password):
+ f = Fernet(_get_key(password))
+ token = f.encrypt(data).decode()
+ return token
+
+
+# ~ Export ok
+def decrypt(token, password):
+ data = ''
+ f = Fernet(_get_key(password))
+ try:
+ data = f.decrypt(token.encode()).decode()
+ except InvalidToken as e:
+ error('Invalid Token')
+ return data
+
+
+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, *args):
+ 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['pass'])
+ 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', '
')
+ 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, _, _ = get_info_path(path)
+ part = MIMEBase('application', 'octet-stream')
+ part.set_payload(read_file(path, 'rb'))
+ encoders.encode_base64(part)
+ part.add_header('Content-Disposition', file_name.format(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
+
+
+def server_smtp_test(config):
+ with SmtpServer(config) as server:
+ if server.error:
+ error(server.error)
+ return server.error
+
+
# ~ name = 'com.sun.star.configuration.ConfigurationProvider'
# ~ cp = create_instance(name, True)
# ~ node = PropertyValue(Name='nodepath', Value=NODE_SETTING)
diff --git a/source/images/add.png b/source/images/add.png
new file mode 100644
index 0000000..6cacfc5
Binary files /dev/null and b/source/images/add.png differ
diff --git a/source/images/barcode_16.bmp b/source/images/barcode_16.bmp
new file mode 100644
index 0000000..10246a3
Binary files /dev/null and b/source/images/barcode_16.bmp differ
diff --git a/source/images/delete.png b/source/images/delete.png
new file mode 100644
index 0000000..7652e03
Binary files /dev/null and b/source/images/delete.png differ
diff --git a/source/images/favorite.png b/source/images/favorite.png
new file mode 100644
index 0000000..2e6d2ea
Binary files /dev/null and b/source/images/favorite.png differ
diff --git a/source/images/favorite_26.bmp b/source/images/favorite_26.bmp
new file mode 100644
index 0000000..0da6992
Binary files /dev/null and b/source/images/favorite_26.bmp differ
diff --git a/source/images/save.png b/source/images/save.png
new file mode 100644
index 0000000..06d84fe
Binary files /dev/null and b/source/images/save.png differ
diff --git a/source/images/tool_16.bmp b/source/images/tool_16.bmp
new file mode 100644
index 0000000..0bd1d9b
Binary files /dev/null and b/source/images/tool_16.bmp differ