Add styles

This commit is contained in:
El Mau 2023-12-17 23:57:30 -06:00
parent d0c5f3a8ed
commit 5a02f50cc6
21 changed files with 882 additions and 77 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ build/
*.lock *.lock
bk/ bk/
site/ site/
update.sh

View File

@ -1,3 +1,7 @@
v 0.6.0 [17-Dec-2023]
---------------------
- Add control styles.
v 0.5.0 [07-Dec-2023] v 0.5.0 [07-Dec-2023]
--------------------- ---------------------
- Add acctions to controls. - Add acctions to controls.

View File

@ -1 +1 @@
0.5.0 0.6.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,177 @@
!!! tip "Atención"
La fecha inicial en Calc y en Python son diferentes.
<br>
### **today**
Obtener la fecha actual.
```py
d = app.dates
app.msgbox(d.today)
```
<br>
### **now**
Obtener la fecha y hora actuales.
```py
d = app.dates
app.msgbox(d.now)
```
<br>
### **time**
Obtener la hora actual.
```py
d = app.dates
app.msgbox(d.now.time())
```
<br>
### **epoch**
Obtener el [tiempo Unix][1]
```py
d = app.dates
app.msgbox(d.epoch)
```
<br>
### **date**
Devolver una fecha
```py
d = app.dates
date = d.date(1974, 1, 15)
app.msgbox(date)
```
<br>
### **time**
Devolver una hora
```py
d = app.dates
time = d.time(10, 20, 15)
app.msgbox(time)
```
<br>
### **datetime**
Devolver fecha y hora
```py
d = app.dates
dt = d.datetime(1974, 1, 15, 10, 11, 12)
app.msgbox(dt)
```
<br>
### **str_to_date**
Convertir una cadena en fecha. Mira este [excelente recurso][2]
```py
d = app.dates
cadena = '1974-01-15'
plantilla = '%Y-%m-%d'
fecha = d.str_to_date(cadena, plantilla)
app.msgbox(fecha)
app.msgbox(type(fecha))
```
Para obtener un valor válido para establecer en una celda de Calc.
```py
d = app.dates
cadena = '1974-01-15'
plantilla = '%Y-%m-%d'
fecha = d.str_to_date(cadena, plantilla, True)
app.msgbox(fecha)
app.msgbox(type(fecha))
```
<br>
### **calc_to_date**
Convierte el valor de una celda en una fecha Python, por ejemplo, la fecha inicial configurada en Calc.
```py
d = app.dates
valor_en_celda = 0
fecha = d.calc_to_date(valor_en_celda)
app.msgbox(fecha)
app.msgbox(type(fecha))
```
<br>
### **sleep**
Pausar la ejecución por X segundos.
!!! tip inline end "Atención"
La pausa es bloqueante.
```py
d = app.dates
app.sleep(3)
app.msgbox('Fin')
```
<br>
### **start** y **end**
Medir tiempo en segundos.
```py
d = app.dates
d.start()
app.sleep(5)
seconds = d.end()
app.msgbox(seconds)
```
Regresar timedelta en vez de segundos.
```py
d = app.dates
d.start()
app.sleep(5)
td = d.end(False)
app.msgbox(td)
```
[1]: https://es.wikipedia.org/wiki/Tiempo_Unix
[2]: https://strftime.org

123
docs/en/docs/tools/index.md Normal file
View File

@ -0,0 +1,123 @@
---
title: Information
---
Remember, import first the library.
```py
import easymacro as app
```
<br>
## About PC
<br>
### **OS**
Get Operate System.
```py
app.msgbox(app.OS)
```
<br>
### **DESKTOP**
Get desktop type, only GNU/Linux.
```py
app.msgbox(app.DESKTOP)
```
<br>
### **PC**
Get PC name.
```py
app.msgbox(app.PC)
```
<br>
### **USER**
Get user name.
```py
app.msgbox(app.USER)
```
<br>
### **IS_WIN**
If OS is Windows.
```py
app.msgbox(app.IS_WIN)
```
<br>
### **IS_MAC**
IF OS is MAC
```py
app.msgbox(app.IS_MAC)
```
<br>
## About LibreOffice
### **NAME**
Application name.
```py
app.msgbox(app.NAME)
```
<br>
### **VERSION**
Version.
```py
app.msgbox(app.VERSION)
```
<br>
### **LANG**
Language
```py
app.msgbox(app.LANG)
```
<br>
### **LANGUAGE**
Language with variant.
```py
app.msgbox(app.LANGUAGE)
```
<br>
### **IS_APPIMAGE**
If LibreOffice use by AppImage.
```py
app.msgbox(app.IS_APPIMAGE)
```
<br>
### **IS_FLATPAK**
If LibreOffice is use by FlatPak.
```py
app.msgbox(app.IS_FLATPAK)
```

View File

@ -0,0 +1,75 @@
## Message Box
### **msgbox**
Show standard message.
```py
message = 'Fucking World'
title = 'My Macro'
app.msgbox(message, title)
```
![msgbox](../img/tools_msg_01.png)
<br>
### **warning**
Show message with warning icon.
```py
message = 'Caution, this action is dangerous'
title = 'My Macro'
app.warning(message, title)
```
![warning](../img/tools_msg_02.png)
<br>
### **errorbox**
Show message with error icon.
```py
message = 'ERROR: contact support'
title = 'My Macro'
app.errorbox(message, title)
```
![error](../img/tools_msg_03.png)
<br>
### **question**
Ask a question by showing the interrogation icon and displaying the command buttons `Yes` and `No`. The answer is always True if user select `yes` and False otherwise.
```py
message = 'Python is easy?'
title = 'My Macro'
result = app.question(message, title)
app.msgbox(result)
```
![question](../img/tools_msg_04.png)
<br>
### **inputbox**
Shows a message to user, allowing to capture an answer.
```py
message = 'Capture your name'
name = app.inputbox(message)
app.msgbox(name)
```
![inputbox](../img/tools_msg_05.png)
To hide on screen what the user typing, util for request passwords.
```py
message = 'Type your password'
echochar = '*'
password = app.inputbox(message, echochar=echochar)
app.msgbox(password)
```
![inputbox](../img/tools_msg_06.png)

View File

@ -5,6 +5,10 @@ nav:
- Home: index.md - Home: index.md
- Install: install.md - Install: install.md
- Debug: debug.md - Debug: debug.md
- Tools:
- tools/index.md
- Messages: tools/messages.md
- Dates and time: tools/datetime.md
theme: theme:
name: material name: material
locale: en locale: en
@ -33,8 +37,8 @@ markdown_extensions:
extra: extra:
alternate: alternate:
- name: English - name: English
link: / link: /easymacro
lang: en lang: en
- name: Español - name: Español
link: /langs/es link: /easymacro/langs/es
lang: es lang: es

View File

@ -69,8 +69,8 @@ markdown_extensions:
extra: extra:
alternate: alternate:
- name: English - name: English
link: / link: /easymacro
lang: en lang: en
- name: Español - name: Español
link: /langs/es link: /easymacro/langs/es
lang: es lang: es

View File

@ -0,0 +1,10 @@
#!/usr/bin/env python
# ~ Is de sum of:
# ~ https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html
from com.sun.star.sheet import CellFlags
ONLY_DATA = 31
ALL = 1023

View File

@ -16,75 +16,13 @@ from .easyevents import EventsRangeSelectionListener, LOEvents
from .easyshape import LOShapes, LOShape from .easyshape import LOShapes, LOShape
from .easydrawpage import LODrawPage from .easydrawpage import LODrawPage
from .easyforms import LOForms from .easyforms import LOForms
from .easystyles import LOStyleFamilies
SECONDS_DAY = 60 * 60 * 24 SECONDS_DAY = 60 * 60 * 24
ONLY_VALUES = CellFlags.VALUE + CellFlags.DATETIME + CellFlags.STRING ONLY_VALUES = CellFlags.VALUE + CellFlags.DATETIME + CellFlags.STRING
class LOCellStyle():
def __init__(self, obj):
self._obj = obj
def __str__(self):
return f'CellStyle: {self.name}'
@property
def obj(self):
return self._obj
@property
def name(self):
return self.obj.Name
@property
def properties(self):
properties = self.obj.PropertySetInfo.Properties
data = {p.Name: getattr(self.obj, p.Name) for p in properties}
return data
@properties.setter
def properties(self, values):
set_properties(self.obj, values)
class LOCellStyles():
def __init__(self, obj, doc):
self._obj = obj
self._doc = doc
def __len__(self):
return len(self.obj)
def __getitem__(self, index):
return LOCellStyle(self.obj[index])
def __setitem__(self, key, value):
self.obj[key] = value
def __delitem__(self, key):
if not isinstance(key, str):
key = key.Name
del self.obj[key]
def __contains__(self, item):
return item in self.obj
@property
def obj(self):
return self._obj
@property
def names(self):
return self.obj.ElementNames
def new(self, name: str):
obj = self._doc.create_instance('com.sun.star.style.CellStyle')
self.obj[name] = obj
return LOCellStyle(obj)
# ~ IsFiltered, # ~ IsFiltered,
# ~ IsManualPageBreak, # ~ IsManualPageBreak,
# ~ IsStartOfNewPage # ~ IsStartOfNewPage
@ -939,6 +877,11 @@ class LOCalc(LODocument):
"""Get class events""" """Get class events"""
return LOEvents(self.obj.Events) return LOEvents(self.obj.Events)
@property
def styles(self):
ci = self.obj.createInstance
return LOStyleFamilies(self.obj.StyleFamilies, ci)
def ranges(self): def ranges(self):
"""Create ranges container""" """Create ranges container"""
obj = self._create_instance('com.sun.star.sheet.SheetCellRanges') obj = self._create_instance('com.sun.star.sheet.SheetCellRanges')
@ -1111,5 +1054,4 @@ class LOCalc(LODocument):
return self.cell_styles return self.cell_styles
@property @property
def cell_styles(self): def cell_styles(self):
obj = self.obj.StyleFamilies['CellStyles'] return self.styles['CellStyles']
return LOCellStyles(obj, self)

View File

@ -24,6 +24,7 @@ from com.sun.star.beans import PropertyValue, NamedValue, StringPair
from com.sun.star.datatransfer import XTransferable, DataFlavor from com.sun.star.datatransfer import XTransferable, DataFlavor
from com.sun.star.ui.dialogs import TemplateDescription from com.sun.star.ui.dialogs import TemplateDescription
from .constants import ALL
from .messages import MESSAGES from .messages import MESSAGES
@ -31,6 +32,8 @@ __all__ = [
'ALL', 'ALL',
'DESKTOP', 'DESKTOP',
'INFO_DEBUG', 'INFO_DEBUG',
'IS_APPIMAGE',
'IS_FLATPAK',
'IS_MAC', 'IS_MAC',
'IS_WIN', 'IS_WIN',
'LANG', 'LANG',
@ -63,13 +66,12 @@ PC = platform.node()
USER = getpass.getuser() USER = getpass.getuser()
IS_WIN = OS == 'Windows' IS_WIN = OS == 'Windows'
IS_MAC = OS == 'Darwin' IS_MAC = OS == 'Darwin'
IS_FLATPAK = bool(os.getenv("FLATPAK_ID", ""))
IS_APPIMAGE = bool(os.getenv("APPIMAGE", ""))
ALL = 1023
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
LOG_DATE = '%d/%m/%Y %H:%M:%S' LOG_DATE = '%Y/%m/%d %H:%M:%S'
if IS_WIN: if IS_WIN:
logging.addLevelName(logging.ERROR, 'ERROR') logging.addLevelName(logging.ERROR, 'ERROR')
logging.addLevelName(logging.DEBUG, 'DEBUG') logging.addLevelName(logging.DEBUG, 'DEBUG')
@ -149,7 +151,6 @@ day = get_app_config(node, 'DD')
DATE_OFFSET = datetime.date(year, month, day).toordinal() DATE_OFFSET = datetime.date(year, month, day).toordinal()
_info_debug = f"Python: {sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path) _info_debug = f"Python: {sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path)
# ~ doc
INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{_info_debug}" INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{_info_debug}"
@ -221,6 +222,12 @@ def set_properties(model, properties):
return return
def get_properties(obj):
properties = obj.PropertySetInfo.Properties
values = {p.Name: getattr(obj, p.Name) for p in properties}
return values
# ~ https://github.com/django/django/blob/main/django/utils/functional.py#L61 # ~ https://github.com/django/django/blob/main/django/utils/functional.py#L61
class classproperty: class classproperty:

View File

@ -0,0 +1,143 @@
#!/usr/bin/env python
from .easymain import log, BaseObject, set_properties, get_properties
STYLE_FAMILIES = 'StyleFamilies'
class LOBaseStyles(BaseObject):
def __init__(self, obj, create_instance=None):
super().__init__(obj)
self._create_intance = create_instance
def __len__(self):
return self.obj.Count
def __contains__(self, item):
return self.obj.hasByName(item)
def __getitem__(self, index):
if index in self:
style = self.obj.getByName(index)
else:
raise IndexError
if self.NAME == STYLE_FAMILIES:
s = LOStyles(style, index, self._create_intance)
else:
s = LOStyle(style)
return s
def __iter__(self):
self._index = 0
return self
def __next__(self):
if self._index < self.obj.Count:
style = self[self.names[self._index]]
else:
raise StopIteration
self._index += 1
return style
@property
def names(self):
return self.obj.ElementNames
class LOStyle():
NAME = 'Style'
def __init__(self, obj):
self._obj = obj
def __str__(self):
return f'Style: {self.name}'
def __contains__(self, item):
return hasattr(self.obj, item)
def __setattr__(self, name, value):
if name != '_obj':
self.obj.setPropertyValue(name, value)
else:
super().__setattr__(name, value)
def __getattr__(self, name):
return self.obj.getPropertyValue(name)
@property
def obj(self):
return self._obj
@property
def name(self):
return self.obj.Name
@property
def is_in_use(self):
return self.obj.isInUse()
@property
def is_user_defined(self):
return self.obj.isUserDefined()
@property
def properties(self):
return get_properties(self.obj)
@properties.setter
def properties(self, values):
set_properties(self.obj, values)
class LOStyles(LOBaseStyles):
NAME = 'Styles'
def __init__(self, obj, type_style, create_instance):
super().__init__(obj)
self._type_style = type_style
self._create_instance = create_instance
def __str__(self):
return f'Styles: {self._type_style}'
def __setitem__(self, key, value):
if key in self:
style = self.obj.getByName(key)
else:
name = f'com.sun.star.style.{self._type_style[:-1]}'
style = self._create_instance(name)
self.obj.insertByName(key, style)
set_properties(style, value)
def __delitem__(self, key):
self.obj.removeByName(key)
class LOStyleFamilies(LOBaseStyles):
NAME = STYLE_FAMILIES
def __init__(self, obj, create_instance):
super().__init__(obj, create_instance)
def __str__(self):
return f'Style Families: {self.names}'
def _validate_name(self, name):
if not name.endswith('Styles'):
name = f'{name}Styles'
return name
def __contains__(self, item):
return self.obj.hasByName(self._validate_name(item))
def __getitem__(self, index):
if isinstance(index, int):
index = self.names[index]
else:
index = self._validate_name(index)
return super().__getitem__(index)

View File

@ -1,11 +1,123 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from .easymain import log, BaseObject
from .easydoc import LODocument from .easydoc import LODocument
class LOWriterTextPortion(BaseObject):
def __init__(self, obj):
super().__init__(obj)
def __str__(self):
return 'Writer: TextPortion'
@property
def string(self):
return self.obj.String
class LOWriterParagraph(BaseObject):
TEXT_PORTION = 'SwXTextPortion'
def __init__(self, obj):
super().__init__(obj)
def __str__(self):
return 'Writer: Paragraph'
def __iter__(self):
self._iter = iter(self.obj)
return self
def __next__(self):
obj = next(self._iter)
type_obj = obj.ImplementationName
if type_obj == self.TEXT_PORTION:
obj = LOWriterTextPortion(obj)
return obj
@property
def string(self):
return self.obj.String
@property
def cursor(self):
return self.obj.Text.createTextCursorByRange(self.obj)
class LOWriterTextRange(BaseObject):
PARAGRAPH = 'SwXParagraph'
def __init__(self, obj):
super().__init__(obj)
def __str__(self):
return 'Writer: TextRange'
def __getitem__(self, index):
for i, v in enumerate(self):
if index == i:
return v
if index > i:
raise IndexError
def __iter__(self):
self._enum = self.obj.createEnumeration()
return self
def __next__(self):
if self._enum.hasMoreElements():
obj = self._enum.nextElement()
type_obj = obj.ImplementationName
if type_obj == self.PARAGRAPH:
obj = LOWriterParagraph(obj)
else:
raise StopIteration
return obj
@property
def string(self):
return self.obj.String
@property
def cursor(self):
return self.obj.Text.createTextCursorByRange(self.obj)
class LOWriterTextRanges(BaseObject):
def __init__(self, obj):
super().__init__(obj)
# ~ self._doc = doc
# ~ self._paragraphs = [LOWriterTextRange(p, doc) for p in obj]
def __str__(self):
return 'Writer: TextRanges'
def __len__(self):
return self.obj.Count
def __getitem__(self, index):
return LOWriterTextRange(self.obj[index])
def __iter__(self):
self._index = 0
return self
def __next__(self):
try:
obj = LOWriterTextRange(self.obj[self._index])
except IndexError:
raise StopIteration
self._index += 1
return obj
class LOWriter(LODocument): class LOWriter(LODocument):
_type = 'writer'
TEXT_RANGES = 'SwXTextRanges' TEXT_RANGES = 'SwXTextRanges'
_type = 'writer'
def __init__(self, obj): def __init__(self, obj):
super().__init__(obj) super().__init__(obj)
@ -17,3 +129,25 @@ class LOWriter(LODocument):
@zoom.setter @zoom.setter
def zoom(self, value): def zoom(self, value):
self._view_settings.ZoomValue = value self._view_settings.ZoomValue = value
@property
def selection(self):
"""Get current seleccion"""
sel = None
selection = self.obj.CurrentSelection
type_obj = selection.ImplementationName
if type_obj == self.TEXT_RANGES:
sel = LOWriterTextRanges(selection)
if len(sel) == 1:
sel = sel[0]
else:
log.debug(type_obj)
log.debug(selection)
sel = selection
return sel
@property
def string(self):
return self._obj.Text.String

185
source/easymacro/utils.py Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env python
# ~ https://github.com/pyca/cryptography/blob/main/src/cryptography/fernet.py#L27
import base64
import binascii
import os
import time
import typing
_MAX_CLOCK_SKEW = 60
class InvalidSignature(Exception):
pass
class InvalidToken(Exception):
pass
class Fernet:
def __init__(
self,
key: bytes | str,
backend: typing.Any = None,
) -> None:
try:
key = base64.urlsafe_b64decode(key)
except binascii.Error as exc:
raise ValueError(
"Fernet key must be 32 url-safe base64-encoded bytes."
) from exc
if len(key) != 32:
raise ValueError(
"Fernet key must be 32 url-safe base64-encoded bytes."
)
self._signing_key = key[:16]
self._encryption_key = key[16:]
@classmethod
def generate_key(cls) -> bytes:
return base64.urlsafe_b64encode(os.urandom(32))
def encrypt(self, data: bytes) -> bytes:
return self.encrypt_at_time(data, int(time.time()))
def encrypt_at_time(self, data: bytes, current_time: int) -> bytes:
iv = os.urandom(16)
return self._encrypt_from_parts(data, current_time, iv)
def _encrypt_from_parts(
self, data: bytes, current_time: int, iv: bytes
) -> bytes:
utils._check_bytes("data", data)
padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_data = padder.update(data) + padder.finalize()
encryptor = Cipher(
algorithms.AES(self._encryption_key),
modes.CBC(iv),
).encryptor()
ciphertext = encryptor.update(padded_data) + encryptor.finalize()
basic_parts = (
b"\x80"
+ current_time.to_bytes(length=8, byteorder="big")
+ iv
+ ciphertext
)
h = HMAC(self._signing_key, hashes.SHA256())
h.update(basic_parts)
hmac = h.finalize()
return base64.urlsafe_b64encode(basic_parts + hmac)
def decrypt(self, token: bytes | str, ttl: int | None = None) -> bytes:
timestamp, data = Fernet._get_unverified_token_data(token)
if ttl is None:
time_info = None
else:
time_info = (ttl, int(time.time()))
return self._decrypt_data(data, timestamp, time_info)
def decrypt_at_time(
self, token: bytes | str, ttl: int, current_time: int
) -> bytes:
if ttl is None:
raise ValueError(
"decrypt_at_time() can only be used with a non-None ttl"
)
timestamp, data = Fernet._get_unverified_token_data(token)
return self._decrypt_data(data, timestamp, (ttl, current_time))
def extract_timestamp(self, token: bytes | str) -> int:
timestamp, data = Fernet._get_unverified_token_data(token)
# Verify the token was not tampered with.
self._verify_signature(data)
return timestamp
@staticmethod
def _get_unverified_token_data(token: bytes | str) -> tuple[int, bytes]:
if not isinstance(token, (str, bytes)):
raise TypeError("token must be bytes or str")
try:
data = base64.urlsafe_b64decode(token)
except (TypeError, binascii.Error):
raise InvalidToken
if not data or data[0] != 0x80:
raise InvalidToken
if len(data) < 9:
raise InvalidToken
timestamp = int.from_bytes(data[1:9], byteorder="big")
return timestamp, data
def _verify_signature(self, data: bytes) -> None:
h = HMAC(self._signing_key, hashes.SHA256())
h.update(data[:-32])
try:
h.verify(data[-32:])
except InvalidSignature:
raise InvalidToken
def _decrypt_data(
self,
data: bytes,
timestamp: int,
time_info: tuple[int, int] | None,
) -> bytes:
if time_info is not None:
ttl, current_time = time_info
if timestamp + ttl < current_time:
raise InvalidToken
if current_time + _MAX_CLOCK_SKEW < timestamp:
raise InvalidToken
self._verify_signature(data)
iv = data[9:25]
ciphertext = data[25:-32]
decryptor = Cipher(
algorithms.AES(self._encryption_key), modes.CBC(iv)
).decryptor()
plaintext_padded = decryptor.update(ciphertext)
try:
plaintext_padded += decryptor.finalize()
except ValueError:
raise InvalidToken
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
unpadded = unpadder.update(plaintext_padded)
try:
unpadded += unpadder.finalize()
except ValueError:
raise InvalidToken
return unpadded

View File

@ -9,6 +9,6 @@ USER = 'elmau'
IS_WIN = False IS_WIN = False
IS_MAC = False IS_MAC = False
LIBO_VERSION = '7.4' LIBO_VERSION = '7.6'
LANGUAGE = 'en-US' LANGUAGE = 'en-US'
LANG = 'en' LANG = 'en'