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
bk/
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]
---------------------
- 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
- Install: install.md
- Debug: debug.md
- Tools:
- tools/index.md
- Messages: tools/messages.md
- Dates and time: tools/datetime.md
theme:
name: material
locale: en
@ -33,8 +37,8 @@ markdown_extensions:
extra:
alternate:
- name: English
link: /
link: /easymacro
lang: en
- name: Español
link: /langs/es
link: /easymacro/langs/es
lang: es

View File

@ -69,8 +69,8 @@ markdown_extensions:
extra:
alternate:
- name: English
link: /
link: /easymacro
lang: en
- name: Español
link: /langs/es
link: /easymacro/langs/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 .easydrawpage import LODrawPage
from .easyforms import LOForms
from .easystyles import LOStyleFamilies
SECONDS_DAY = 60 * 60 * 24
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,
# ~ IsManualPageBreak,
# ~ IsStartOfNewPage
@ -939,6 +877,11 @@ class LOCalc(LODocument):
"""Get class events"""
return LOEvents(self.obj.Events)
@property
def styles(self):
ci = self.obj.createInstance
return LOStyleFamilies(self.obj.StyleFamilies, ci)
def ranges(self):
"""Create ranges container"""
obj = self._create_instance('com.sun.star.sheet.SheetCellRanges')
@ -1111,5 +1054,4 @@ class LOCalc(LODocument):
return self.cell_styles
@property
def cell_styles(self):
obj = self.obj.StyleFamilies['CellStyles']
return LOCellStyles(obj, self)
return self.styles['CellStyles']

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.ui.dialogs import TemplateDescription
from .constants import ALL
from .messages import MESSAGES
@ -31,6 +32,8 @@ __all__ = [
'ALL',
'DESKTOP',
'INFO_DEBUG',
'IS_APPIMAGE',
'IS_FLATPAK',
'IS_MAC',
'IS_WIN',
'LANG',
@ -63,13 +66,12 @@ PC = platform.node()
USER = getpass.getuser()
IS_WIN = OS == 'Windows'
IS_MAC = OS == 'Darwin'
ALL = 1023
IS_FLATPAK = bool(os.getenv("FLATPAK_ID", ""))
IS_APPIMAGE = bool(os.getenv("APPIMAGE", ""))
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:
logging.addLevelName(logging.ERROR, 'ERROR')
logging.addLevelName(logging.DEBUG, 'DEBUG')
@ -149,7 +151,6 @@ day = get_app_config(node, 'DD')
DATE_OFFSET = datetime.date(year, month, day).toordinal()
_info_debug = f"Python: {sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path)
# ~ doc
INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{_info_debug}"
@ -221,6 +222,12 @@ def set_properties(model, properties):
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
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
from .easymain import log, BaseObject
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):
_type = 'writer'
TEXT_RANGES = 'SwXTextRanges'
_type = 'writer'
def __init__(self, obj):
super().__init__(obj)
@ -17,3 +129,25 @@ class LOWriter(LODocument):
@zoom.setter
def zoom(self, 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_MAC = False
LIBO_VERSION = '7.4'
LIBO_VERSION = '7.6'
LANGUAGE = 'en-US'
LANG = 'en'