diff --git a/doc/docs/calc/cells.md b/doc/docs/calc/cells.md
new file mode 100644
index 0000000..9e405c0
--- /dev/null
+++ b/doc/docs/calc/cells.md
@@ -0,0 +1,601 @@
+
+## Trabajar con celdas y rangos
+
+---
+
+### **address**
+
+Referencia por dirección.
+
+```py
+ hoja = app.active.active
+ celda = hoja['A1']
+ rango = hoja['C10:D15']
+
+ app.debug(celda)
+ app.debug(rango)
+```
+
+
+
+### **doc**
+
+Devuelve el documento padre.
+
+```py
+ hoja = app.active.active
+ rango = hoja['A1:C5']
+
+ doc = rango.doc
+ app.debug(doc)
+```
+
+
+
+### **in**
+
+Verificar si un rango esta dentro de otro.
+
+```py
+ hoja = app.active.active
+
+ celda = hoja['C5']
+ rango = hoja['A1:E10']
+
+ resultado = celda in rango
+ app.debug(resultado)
+
+ celda = hoja['C50']
+ resultado = celda in rango
+ app.debug(resultado)
+```
+
+
+
+### **iter**
+
+Iterar cada celda de un rango.
+
+```py
+ hoja = app.active.active
+ rango = hoja['B10:C15']
+
+ for celda in rango:
+ app.debug(celda)
+```
+
+
+
+### **position**
+
+Referencia por posición.
+
+Para celdas: `HOJA[fila,columna]`
+
+Para rangos: `HOJA[fila_inicial:fila_final, columna_inicial:columna_final]`
+
+```py
+ hoja = app.active.active
+
+ # ~ Cell A10
+ celda = hoja[9,0]
+
+ # ~ Range A1:C10
+ rango = hoja[0:10,0:3]
+```
+
+!!! tip "Atención"
+
+ Recuerde que las filas y columnas empiezan en cero, por lo que `fila_final` y la `columna_final` deben de ser la fila y columna deseada más 1.
+
+
+
+### **selection**
+
+Referencia por selección actual.
+
+```py
+ seleccion = app.active.selection
+ app.debug(seleccion)
+```
+
+!!! tip "Atención"
+
+ Recuerde que la selección actual pueden ser varias cosas diferentes.
+
+
+
+## Propiedades
+
+---
+
+### **address**
+
+Devuelve la dirección de la celda como una estructura: `com.sun.star.table.CellAddress`
+
+```py
+ hoja = app.active.active
+
+ celda = hoja['A1']
+ if celda.is_cell:
+ app.debug(celda.address)
+```
+
+
+
+### **back_color**
+
+Devuelve o aplica el color de fondo del rango.
+
+```py
+ hoja = app.active.active
+
+ rango = hoja['A1:E10']
+ rango.back_color = 'red'
+```
+
+
+
+### **current_region**
+
+Devuelve la región actual.
+
+```py
+ hoja = app.active.active
+
+ celda = hoja['A1']
+ rango = celda.current_region
+ app.debug(rango)
+```
+
+
+
+### **data**
+
+Alias de `data_array` al obtener los datos. Al establecer los datos, si es un rango se comporta como `data_array`, pero si es una celda, se autoajusta al tamaño de los datos.
+
+```py
+ hoja = app.active.active
+ celda = hoja['A1']
+ datos = (
+ (1, 'Uno'),
+ (2, 'Dos'),
+ (3, 'Tres'),
+ )
+
+ celda.data = datos
+ app.debug(celda.current_region.data)
+```
+
+!!! warning "Cuidado"
+
+ Siempre valide que haya suficientes celdas libres para los datos para evitar sobreescribirlos.
+
+
+
+### **data_array**
+
+Devuelve o establece los datos de un rango. Es un alias de `DataArray`.
+
+```py
+ rango = app.active.selection
+
+ datos = rango.data_array
+ app.debug(datos)
+ rango.data_array = datos
+```
+
+!!! warning "Cuidado"
+
+ El tamaño de los datos, debe ser **exactamente** del tamaño del rango destino. De lo contrario obtendrá un error.
+
+
+
+### **date**
+
+Devuelve o establece el contenido de la celda como fecha.
+
+```py
+ hoja = app.active.active
+ celda = hoja['A1']
+ celda.date = app.dates.date(1974, 1, 15)
+ app.debug(type(celda.date), celda.date)
+```
+
+```
+20/08/2022 18:38:53 - DEBUG - 1974-01-15
+```
+
+
+
+### **datetime**
+
+Devuelve o establece el contenido de la celda como fecha y tiempo.
+
+```python
+ hoja = app.active.active
+ celda = hoja['A1']
+ celda.datetime = app.dates.datetime(1974, 1, 15, 10, 11, 12)
+ app.debug(type(celda.datetime), celda.datetime)
+```
+
+
+
+### **dict**
+
+Devuelve o establece los datos como diccionarios. Automáticamente ajusta el rango al tamaño de los datos.
+
+```py
+ hoja = app.active.active
+ celda = hoja['A1']
+
+ datos = (
+ {'No': 1, 'Nombre': 'Ingrid'},
+ {'No': 2, 'Nombre': 'Sophia'},
+ {'No': 3, 'Nombre': 'Scarlette'},
+ )
+
+ celda.dict = datos
+ app.debug(celda.current_region.dict)
+```
+
+!!! warning "Consejo"
+
+ Siempre valide que haya suficientes celdas libres para los datos para evitar sobreescribirlos, a menos que eso sea lo que realmente quiera.
+
+
+
+### **error**
+
+Si la celda tiene una formula con error, devuelve el número de error.
+
+```py
+ hoja = app.active.active
+ celda = hoja['A1']
+ celda.value = '=RAND()/0'
+ app.debug(celda.error)
+```
+
+
+
+### **float**
+
+Devuelve o establece el contenido de la celda como valor.
+
+```py
+ hoja = app.active.active
+ celda = hoja['A1']
+ celda.float = 12345
+ app.debug(celda.type, celda.float)
+```
+
+
+
+### **formula**
+
+Devuelve o establece la formula de la celda.
+
+```py
+ hoja = app.active.active
+ celda = hoja['A1']
+ celda.formula = '=RAND()'
+ app.debug(celda.type, celda.formula)
+```
+
+
+
+### **formula_array**
+
+Devuelve o establece los datos de un rango. Es un alias de `FormulaArray`.
+
+```py
+ rango = app.active.selection
+
+ datos = rango.data_array
+ app.debug(datos)
+ datos = rango.formula_array
+ app.debug(datos)
+ rango.formula_array = datos
+```
+
+!!! tip "Consejo"
+
+ **data_array** devolverá los resultados de las celdas con formulas. **formula_array** devolverá las formulas en dichas celdas.
+
+
+
+### **is_cell**
+
+Verdadero (True) si el objeto es de una celda.
+
+```py
+ hoja = app.active.active
+
+ celda = hoja['A1']
+ app.debug(celda.is_cell)
+
+ rango = hoja['A1:C5']
+ app.debug(rango.is_cell)
+```
+
+
+
+### **is_range**
+
+Verdadero (True) si el objeto es de un rango.
+
+```py
+ hoja = app.active.active
+
+ celda = hoja['A1']
+ app.debug(celda.is_range)
+
+ rango = hoja['A1:C5']
+ app.debug(rango.is_range)
+```
+
+
+
+### **len**
+
+Devolver el tamaño del rango en filas y columnas.
+
+```py
+ hoja = app.active.active
+
+ rango = hoja['A1:E100']
+ filas = len(rango)
+ columnas = rango.len_columns
+ app.debug(filas, columnas)
+```
+
+
+
+### **name**
+
+Devuelve la dirección de la celda o rango como texto.
+
+```py
+ hoja = app.active.active
+
+ celda = hoja['A1']
+ app.debug(celda.name)
+
+ rango = hoja['A1:C5']
+ app.debug(rango.name)
+```
+
+
+
+### **next_free**
+
+Devuelve la siguiente celda libre después de la región actual.
+
+```py
+ hoja = app.active.active
+ celda = hoja['A1']
+
+ celda_libre = celda.next_free
+ app.debug(celda_libre)
+```
+
+
+
+### **range_address**
+
+Devuelve la dirección del rango como una estructura: `com.sun.star.table.CellRangeAddress`
+
+```py
+ hoja = app.active.active
+
+ rango = hoja['A1:C5']
+ if rango.is_range:
+ app.debug(rango.range_address)
+```
+
+
+
+### **range_data**
+
+Devuelve la región actual del rango excepto la primer fila.
+
+```py
+ hoja = app.active.active
+
+ celda = hoja['A1']
+ rango = celda.range_data
+ app.debug(rango)
+```
+
+
+
+### **sheet**
+
+Devuelve la hoja padre.
+
+```py
+ hoja = app.active.active
+ rango = hoja['A1:C5']
+
+ hoja = rango.sheet
+ app.debug(hoja)
+```
+
+
+
+### **string**
+
+Devuelve o establece el contenido de la celda como texto.
+
+```py
+ hoja = app.active.active
+
+ celda = hoja['A1']
+ celda.string = 'Maldito Mundo'
+ app.debug(celda.type, celda.string)
+
+ celda = hoja['A2']
+ celda.string = 12345
+ app.debug(celda.type, celda.string)
+```
+
+
+
+### **style**
+
+Devuelve o aplica el estilo de celda.
+
+```py
+ hoja = app.active.active
+ rango = hoja['A1:C5']
+ rango.style = 'Good'
+```
+
+
+
+### **time**
+
+Devuelve o establece el contenido de la celda como tiempo.
+
+```py
+ hoja = app.active.active
+ celda = hoja['A1']
+ celda.time = app.dates.time(10, 11, 12)
+ app.debug(type(celda.time), celda.time)
+```
+
+
+
+### **type**
+
+Devuelve el tipo de contenido de la celda: texto, número o formula.
+
+```py
+ hoja = app.active.active
+ celda = hoja['A1']
+ app.debug(celda.type)
+```
+
+!!! tip "Consejo"
+
+ Asegurese de que la selección siempre sea una sola celda.
+
+
+
+### **value**
+
+Devuelve o establece el valor de la celda, estableciendo el tipo de dato automáticamente.
+
+```py
+ hoja = app.active.active
+
+ celda = hoja['A1']
+ celda.value = 'Soy Texto'
+ app.debug(celda.type, celda.value)
+
+ celda = hoja['A2']
+ celda.value = 12345
+ app.debug(celda.type, celda.value)
+
+ celda = hoja['A3']
+ celda.value = '=RAND()'
+ app.debug(celda.type, celda.value)
+
+ celda = hoja['A4']
+ celda.value = app.dates.date(1974, 1, 15)
+ app.debug(celda.type, celda.value)
+```
+
+```sh
+23/04/2023 19:49:05 - DEBUG - Soy Texto
+23/04/2023 19:49:05 - DEBUG - 12345.0
+23/04/2023 19:49:05 - DEBUG - =RAND()
+23/04/2023 19:49:05 - DEBUG - 27044.0
+```
+
+
+
+## Métodos
+
+---
+
+### **clear**
+
+Limpia el rango. Por default solo borra datos. Mire [API CellFlags][1] para más información.
+
+```python
+ rango.clear()
+```
+
+Para borrar todo.
+```python
+ rango.clear(app.ALL)
+```
+
+
+
+### **len**
+
+Devuelve el tamaño en filas y columnas del rango.
+
+```py
+ hoja = app.active.active
+
+ rango = hoja['A1:C100']
+ filas, columnas = rango.len()
+ app.debug(filas, columnas)
+```
+
+
+
+### **offset**
+
+Devuelve la celda inmediata a la derecha en la misma fila.
+
+```py
+ cell = app.active.active['A1']
+ app.debug(cell)
+ cell = cell.offset()
+ app.debug(cell)
+```
+
+```
+23/04/2023 21:22:15 - DEBUG - Cell: $Sheet1.$A$1
+23/04/2023 21:22:15 - DEBUG - Cell: $Sheet1.$B$1
+```
+
+Se puede establecer la distancia a la celda a devolver en filas y columnas.
+
+```py
+ cell = app.active.active['A1']
+ app.debug(cell)
+ cell = cell.offset(4,2)
+ app.debug(cell)
+```
+
+```
+23/04/2023 21:24:59 - DEBUG - Cell: $Sheet1.$A$1
+23/04/2023 21:24:59 - DEBUG - Cell: $Sheet1.$C$5
+```
+
+
+
+### **to_size**
+
+Espande el tamaño del rango actual tantas filas y columnas se pasen como argumentos.
+
+```py
+ cell = app.active.active['A1']
+ app.debug(cell)
+ cell = cell.to_size(10,5)
+ app.debug(cell)
+```
+
+```
+23/04/2023 21:30:56 - DEBUG - Cell: $Sheet1.$A$1
+23/04/2023 21:30:56 - DEBUG - Range: $Sheet1.$A$1:$E$10
+```
+
+
+
+[1]: https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1sheet_1_1CellFlags.html
\ No newline at end of file
diff --git a/doc/docs/calc/index.md b/doc/docs/calc/index.md
new file mode 100644
index 0000000..5c56c8f
--- /dev/null
+++ b/doc/docs/calc/index.md
@@ -0,0 +1,394 @@
+---
+title: Documento
+---
+
+Devolver el documento activo.
+
+### **active**
+
+```py
+ doc = app.active
+```
+
+
+
+## Propiedades
+
+---
+
+### **active**
+
+Devuelve la hoja activa.
+
+```py
+ doc = app.active
+ hoja = doc.active
+ app.debug(hoja)
+```
+
+
+
+### **headers**
+
+Mostrar u ocultar encabezados de filas y columnas.
+
+```py
+ doc = app.active
+ app.msgbox(doc.headers)
+ doc.headers = not doc.headers
+ app.msgbox(doc.headers)
+ doc.headers = not doc.headers
+```
+
+
+
+### **names**
+
+Devolver una tupla con los nombres de todas las hojas.
+
+```py
+ doc = app.active
+ nombres = doc.names
+ app.debug(nombres)
+```
+
+
+
+### **selection**
+
+Devolver la selección activa.
+
+!!! warning inline end "Cuidado"
+
+ La selección actual pueden ser muchas cosas diferentes.
+
+```py
+ doc = app.active
+ seleccion = doc.selection
+ app.debug(seleccion)
+```
+
+
+
+### **tabs**
+
+Mostrar u ocultar las pestañas de las hojas.
+
+```py
+ doc = app.active
+ app.msgbox(doc.tabs)
+ doc.tabs = not doc.tabs
+ app.msgbox(doc.tabs)
+ doc.tabs = not doc.tabs
+```
+
+
+
+## Métodos
+
+---
+
+### **activate**
+
+Activar hoja, argumento como objeto.
+
+```py
+ doc = app.active
+ hoja = doc[-1]
+ doc.activate(hoja)
+```
+
+Activar hoja por nombre.
+
+```py
+ doc = app.active
+ doc.activate('Hoja3')
+```
+
+
+
+### **copy**
+
+Copiar hoja dentro del mismo documento.
+
+```py
+ doc = app.active
+ hoja = doc[0]
+
+ doc.copy_sheet(hoja, 'Otra hoja')
+```
+
+Por nombre.
+
+```py
+ doc.copy_sheet('Hoja1', 'Nueva Hoja')
+```
+
+Si no se establece el nuevo nombre, se generá de forma automática: `nombre + índice`.
+
+```py
+ doc.copy_sheet(hoja)
+```
+
+
+
+### **copy_from**
+
+Copiar hojas de otro documento. Copiar con el mismo nombre.
+
+```py
+ doc = app.active
+ documento_origen = app.docs['Contactos.ods']
+ nombre_origen = 'Nombres'
+
+ doc.copy_from(documento_origen, nombre_origen)
+```
+
+Copiar con un nuevo nombre.
+
+```py
+ doc.copy_from(documento_origen, nombre_origen, 'NuevoNombre')
+```
+
+Si solo se establece el documento origen, se copian todas las hojas.
+
+
+```py
+ doc.copy_from(documento_origen)
+```
+
+!!! info "Información"
+
+ Si algún nombre de hoja en el origen existe en el destino, se copiará con un nuevo índice concatenado el nombre origen.
+
+
+
+### **insert**
+
+Inserta una nueva hoja, se devuelve la hoja insertada.
+
+```py
+ doc = app.active
+ hoja = doc.insert('HojaInsertada')
+```
+
+Insertar varias hojas. Devolverá la última insertada.
+
+```py
+ nombres = ('Enero', 'Febrero', 'Marzo')
+ hoja = doc.insert(nombres)
+ app.debug(hoja)
+```
+
+Creando una nueva instancia y asignandola.
+
+```py
+ doc = app.active
+ doc['NuevaHoja'] = doc.new_sheet()
+```
+
+!!! warning "Cuidado"
+
+ Si la hoja existe, obtendrá un error, verifique siempre que no exista primero.
+
+
+
+### **move**
+
+Mover hojas.
+
+Pasar la hoja como objeto, de forma predetermianda se mueve a la última posición.
+
+```py
+ doc = app.active
+ hoja = doc[0]
+ doc.move(hoja)
+```
+
+!!! info "Información"
+
+ Al mover una hoja, cambian sus índices.
+
+Pasar la hoja por nombre.
+
+```py
+ doc = app.active
+ doc.move('Hoja1')
+```
+
+Especificar la posición destino.
+
+```py
+ doc = app.active
+ hoja = doc[0]
+ doc.move(hoja, 2)
+```
+
+
+
+### **remove**
+
+!!! warning "Cuidado"
+
+ Siempre debe haber al menos una hoja en un documento.
+
+Eliminar hoja por índice.
+
+```py
+ doc = app.active
+ sheet = doc[0]
+
+ doc.remove(sheet)
+```
+
+Eliminar por nombre.
+
+```py
+ doc.remove('Hoja2')
+```
+
+
+
+### **start_range_selection**
+
+Permitir al usuario seleccionar un rango. Es necesario pasarle una clase con el nombre que prefiera (`Eventos` es recomendable) con dos métodos para procesar la captura como en el siguiente ejemplo:
+
+```py
+class Eventos():
+
+ def __init__(self, doc):
+ self.doc = doc
+
+ def range_selection_done(self, range_selection):
+ if range_selection:
+ app.debug(range_selection)
+ self.doc.remove_range_selection_listener()
+ return
+
+ def range_selection_aborted(self):
+ self.doc.remove_range_selection_listener()
+ return
+
+
+def main():
+ doc = app.active
+ doc.start_range_selection(Eventos)
+ return
+```
+
+
+
+### **select**
+
+Seleccionar una celda o rango.
+
+```py
+ doc = app.active
+ cell = doc[0]['A1']
+ doc.select(cell)
+```
+
+!!! tip "Consejo"
+
+ **No es necesario** seleccionar celdas o rangos para manipularlos.
+
+
+
+### **sort**
+
+Ordenar hojas en orden alfabetico.
+
+```py
+ doc = app.active
+ doc.sort()
+```
+
+Ordenar de forma inversa.
+
+```py
+ doc = app.active
+ doc.sort(True)
+```
+
+
+
+## Eventos
+
+---
+
+Obtener una tupla con los eventos soportados por el documento.
+
+```py
+ doc = app.active
+ nombres = doc.events.names
+ for nombre in nombres:
+ app.debug(nombre)
+```
+
+```sh
+23/04/2023 13:54:10 - DEBUG - OnStartApp
+23/04/2023 13:54:10 - DEBUG - OnCloseApp
+23/04/2023 13:54:10 - DEBUG - OnCreate
+23/04/2023 13:54:10 - DEBUG - OnNew
+23/04/2023 13:54:10 - DEBUG - OnLoadFinished
+23/04/2023 13:54:10 - DEBUG - OnLoad
+23/04/2023 13:54:10 - DEBUG - OnPrepareUnload
+23/04/2023 13:54:10 - DEBUG - OnUnload
+23/04/2023 13:54:10 - DEBUG - OnSave
+23/04/2023 13:54:10 - DEBUG - OnSaveDone
+23/04/2023 13:54:10 - DEBUG - OnSaveFailed
+23/04/2023 13:54:10 - DEBUG - OnSaveAs
+23/04/2023 13:54:10 - DEBUG - OnSaveAsDone
+23/04/2023 13:54:10 - DEBUG - OnSaveAsFailed
+23/04/2023 13:54:10 - DEBUG - OnCopyTo
+23/04/2023 13:54:10 - DEBUG - OnCopyToDone
+23/04/2023 13:54:10 - DEBUG - OnCopyToFailed
+23/04/2023 13:54:10 - DEBUG - OnFocus
+23/04/2023 13:54:10 - DEBUG - OnUnfocus
+23/04/2023 13:54:10 - DEBUG - OnPrint
+23/04/2023 13:54:10 - DEBUG - OnViewCreated
+23/04/2023 13:54:10 - DEBUG - OnPrepareViewClosing
+23/04/2023 13:54:10 - DEBUG - OnViewClosed
+23/04/2023 13:54:10 - DEBUG - OnModifyChanged
+23/04/2023 13:54:10 - DEBUG - OnTitleChanged
+23/04/2023 13:54:10 - DEBUG - OnVisAreaChanged
+23/04/2023 13:54:10 - DEBUG - OnModeChanged
+23/04/2023 13:54:10 - DEBUG - OnStorageChanged
+```
+
+
+
+### Asignar una macro a un evento.
+
+```py
+def doc_on_focus(event):
+ app.debug('Documento activado...')
+ return
+
+
+def main():
+ doc = app.active
+ events = doc.events
+ if 'OnFocus' in events:
+ macro = {'library': 'test', 'name': 'doc_on_focus'}
+ events['OnFocus'] = macro
+ return
+```
+
+
+
+### Eliminar la asignación del evento.
+
+```py
+ doc = app.active
+ doc.events['OnFocus'] = {}
+```
+
+O
+
+```python
+ doc = app.active
+ doc.events.remove('OnFocus')
+```
+
+
\ No newline at end of file
diff --git a/doc/docs/calc/ranges.md b/doc/docs/calc/ranges.md
new file mode 100644
index 0000000..6584231
--- /dev/null
+++ b/doc/docs/calc/ranges.md
@@ -0,0 +1,175 @@
+## Trabajar con conjuntos de rangos
+
+---
+
+### **address**
+
+Referencia a un rango por su dirección.
+
+```py
+ rango = rangos['Hoja1.A1:B5']
+ app.debug(rango)
+```
+
+
+
+### **in**
+
+Verificar si un rango esta en la colección.
+
+```py
+ doc = app.active
+ hoja = doc.active
+ rangos = doc.selection
+
+ resultado = hoja['D5:F10'] in rangos
+ app.debug(resultado)
+```
+
+
+
+### **index**
+
+Referencia a un rango por índice.
+
+```py
+ doc = app.active
+ rangos = doc.selection
+
+ rango = rangos[1]
+ app.debug(rango)
+```
+
+
+
+### **iter**
+
+Iterar entre los rangos.
+
+```py
+ doc = app.active
+ for rango in doc.selection:
+ app.debug(rango)
+```
+
+```sh
+20/08/2022 13:27:03 - DEBUG - Range: $Sheet1.$B$4:$D$7
+20/08/2022 13:27:03 - DEBUG - Range: $Sheet1.$G$10:$H$14
+```
+
+
+
+### **len**
+
+Contar los rangos en la colección.
+
+```py
+ doc = app.active
+ contar = len(doc.selection)
+ app.debug(contar)
+```
+
+
+
+### **selection**
+
+Obtener una referencia desde la selección actual. Deben de estar seleccionados más de un rango de celdas.
+
+```py
+ doc = app.active
+ seleccion = doc.selection
+ app.debug(seleccion)
+```
+
+```sh
+20/08/2022 13:21:17 - DEBUG - Ranges: ('Sheet1.A5:C8', 'Sheet1.E11:F14')
+```
+
+
+
+## Propiedades
+
+---
+
+### **data**
+
+Devolver y establecer datos.
+
+```py
+ doc = app.active
+ rangos = doc.selection
+
+ datos = rangos.data
+ app.debug(datos)
+ rangos.data = datos
+```
+
+!!! warning "Cuidado"
+
+ Al asignar esta propiedad, cada matriz de datos debe tener exactamente el mismo tamaño del rango destino.
+
+
+
+### **names**
+
+Devolver las direcciones de los rangos.
+
+```py
+ doc = app.active
+ rangos = doc.selection
+
+ nombres = rangos.names
+ app.debug(nombres)
+```
+
+
+
+### **style**
+
+Establecer el estilo de todos los rangos de la colección.
+
+```py
+ doc = app.active
+ rangos = doc.selection
+ rangos.style = 'Good'
+```
+
+
+
+## Métodos
+
+---
+
+### **add**
+
+```py
+ doc = app.active
+ hoja = doc.active
+
+ rangos = doc.ranges()
+ rangos.add(hoja['A1:B2'])
+ rangos.add(hoja['D5:F10'])
+ app.debug(rangos)
+```
+
+
+
+### **ranges**
+
+Crear un nuevo contender de rangos vacío.
+
+```py
+ doc = app.active
+ rangos = doc.ranges()
+ app.debug(rangos)
+```
+
+
+
+### **remove**
+
+```py
+ rangos.remove(hoja['A1:B2'])
+```
+
+
\ No newline at end of file
diff --git a/doc/docs/calc/sheets.md b/doc/docs/calc/sheets.md
new file mode 100644
index 0000000..095aab4
--- /dev/null
+++ b/doc/docs/calc/sheets.md
@@ -0,0 +1,354 @@
+---
+title: Hojas
+---
+
+## Trabajar con hojas
+
+---
+
+### Referencia por **índice**
+
+```py
+ doc = app.active
+ hoja = doc[0]
+ app.debug(hoja.name)
+```
+
+
+### Referencia por **nombre**
+
+```py
+ doc = app.active
+ hoja = doc['datos']
+ app.debug(hoja.name)
+```
+
+### **in**
+
+Verificar por nombre si una hoja existe.
+
+```py
+ doc = app.active
+ existe = 'Hoja2' in doc
+ app.debug(existe)
+```
+
+### **len**
+
+Contar la cantidad de hojas en el documento.
+
+```py
+ doc = app.active
+ contar = len(doc)
+ app.debug(contar)
+```
+
+### **iter**
+
+Recorrer todas las hojas.
+
+```py
+ doc = app.active
+ for hoja in doc:
+ app.debug(hoja)
+```
+
+
+
+## Propiedades
+
+---
+
+### **code_name**
+
+Nombre editable y accesible solo por código.
+
+```py
+ hoja = app.active.active
+
+ app.msgbox(hoja.code_name)
+ hoja.code_name = 'datos'
+ app.msgbox(hoja.code_name)
+```
+
+
+
+### **color**
+
+Color de la pestaña.
+
+```py
+ hoja = app.active.active
+ app.msgbox(hoja.color)
+
+ hoja.color = 'red'
+ app.msgbox(hoja.color)
+
+ # RGB
+ hoja.color = (125, 200, 10)
+ app.msgbox(hoja.color)
+```
+
+
+
+### **doc**
+
+Devuelve al documento Calc donde esta la hoja.
+
+```py
+ hoja = app.active.active
+
+ doc = hoja.doc
+ app.debug(doc.title)
+```
+
+
+
+### **is_protected**
+
+Devuelve verdadero (True) si la hoja esta protegida
+
+```py
+ hoja = app.active.active
+
+ esta_protegida = hoja.is_protected
+ app.debug(esta_protegida)
+```
+
+
+
+### **name**
+
+Nombre visible y editable por el usuario.
+
+```py
+ hoja = app.active.active
+
+ app.debug(hoja.name)
+ hoja.name = 'Nuevo Nombre'
+ app.debug(hoja.name)
+```
+
+
+
+### **password**
+
+Establecer una contraseña.
+
+!!! tip "Consejo"
+
+ Si la hoja ya esta protegida, aún sin contraseña, este método no tendrá ningún efecto.
+
+```py
+ hoja = app.active.active
+ hoja.password = 'siscaloburropanzon'
+ app.debug(hoja.is_protected)
+```
+
+
+
+### **used_area**
+
+Referencia al área de usuario actual.
+
+```py
+ hoja = app.active.active
+
+ rango = hoja.used_area
+ app.debug(rango)
+```
+
+
+
+### **visible**
+
+Muestra u oculta la hoja
+
+```py
+ hoja = app.active.active
+
+ app.msgbox(hoja.visible)
+ hoja.visible = not hoja.visible
+ app.msgbox(hoja.visible)
+ hoja.visible = not hoja.visible
+```
+
+!!! tip "Cuidado"
+
+ Solo funcionará con dos o más hojas, por que debe haber al menos una visible. Siempre puedes trabajar con todo el documento oculto.
+
+
+
+## Métodos
+
+---
+
+### **activate**
+
+Pasar el foco a la hoja.
+
+!!! tip inline end "Recuerde"
+
+ No es necesario activar una hoja para manipularse.
+
+```py
+ doc = app.active
+ hoja = doc[-1]
+ hoja.activate()
+```
+
+
+
+### **copy**
+
+!!! tip "Consejo"
+
+ Siempre valide que no exista el nuevo nombre.
+
+```py
+ doc = app.active
+ hoja = doc[0]
+
+ nuevo_nombre = f'{hoja.name}_2'
+ if not nuevo_nombre in doc:
+ hoja.copy(nuevo_nombre)
+```
+
+Si no se establece el nuevo nombre, se generá de forma automática: `nombre + índice`.
+
+```py
+ hoja.copy()
+```
+
+### **copy_to**
+
+Copiar la hoja a otro documento. Se usa el mismo nombre.
+
+```py
+ doc = app.active
+ hoja = doc.active
+
+ documento_nuevo = app.docs.new()
+ hoja.copy_to(documento_nuevo)
+```
+
+Usar un nuevo nombre.
+
+```python
+ hoja.copy_to(documento_nuevo, 'Nuevo nombre')
+```
+
+
+
+### **move**
+
+Mover a la última posición.
+
+```py
+ doc = app.active
+ hoja = doc[0]
+ hoja.move()
+```
+
+Mover a una posición especifica.
+
+```py
+ doc = app.active
+ hoja = doc[0]
+ hoja.move(3)
+```
+
+
+
+### **remove**
+
+Remover hoja.
+
+```py
+ sheet = app.active.active
+ sheet.remove()
+```
+
+!!! warning "Cuidado"
+
+ Siempre debe existir al menos una hoja.
+
+
+
+### **unprotect**
+
+Quitar contraseña.
+
+```py
+ hoja = app.active.active
+
+ hoja.password = 'siscaloburropanzon'
+ app.msgbox(hoja.is_protected)
+
+ hoja.unprotect('siscaloburropanzon')
+ app.msgbox(hoja.is_protected)
+```
+
+
+
+## Eventos
+
+---
+
+### **names**
+
+Obtener una tupla con los eventos soportados por la hoja.
+
+```py
+ hoja = app.active.active
+
+ for nombre in hoja.events.names:
+ app.debug(nombre)
+```
+
+```sh
+23/04/2023 16:59:26 - DEBUG - OnFocus
+23/04/2023 16:59:26 - DEBUG - OnUnfocus
+23/04/2023 16:59:26 - DEBUG - OnSelect
+23/04/2023 16:59:26 - DEBUG - OnDoubleClick
+23/04/2023 16:59:26 - DEBUG - OnRightClick
+23/04/2023 16:59:26 - DEBUG - OnChange
+23/04/2023 16:59:26 - DEBUG - OnCalculate
+```
+
+
+
+### Asignar una macro a un evento.
+
+```py
+def on_select(source):
+ app.debug(source.AbsoluteName)
+ return
+
+
+def main():
+ doc = app.active
+ hoja = doc.active
+
+ if 'OnSelect' in hoja.events:
+ macro = {'library': 'test', 'name': 'on_select'}
+ hoja.events['OnSelect'] = macro
+
+ return
+```
+
+
+
+### Eliminar la asignación del evento.
+
+```py
+ hoja.events['OnSelect'] = {}
+```
+
+O
+
+```py
+ hoja.events.remove('OnFocus')
+```
+
+
\ No newline at end of file
diff --git a/doc/docs/dp/drawpage.md b/doc/docs/dp/drawpage.md
new file mode 100644
index 0000000..e1f6846
--- /dev/null
+++ b/doc/docs/dp/drawpage.md
@@ -0,0 +1,109 @@
+---
+title: DrawPage
+---
+
+La página de dibujo es la capa gráfica donde se agregan las formas y las imágenes en los documentos. La forma de acceder es la misma, solo cambia el tipo de documento.
+
+## Hoja de Calc
+
+### **draw_page**
+
+```py
+ hoja = app.active.active
+ pagina_dibujo = hoja.draw_page
+```
+
+### **dp**
+
+Alias de `draw_page`
+
+```py
+ hoja = app.active.active
+ pagina_dibujo = hoja.dp
+```
+
+### **shapes**
+
+Alias de `draw_page`
+
+```py
+ hoja = app.active.active
+ pagina_dibujo = hoja.shapes
+```
+
+
+
+
+Una vez referenciada, sus métodos y propiedades son las mismas.
+
+---
+
+
+
+## Formas
+
+---
+
+### **index**
+
+Acceso por índice
+
+```py
+ forma = formas[0]
+ app.debug(forma)
+```
+
+Acceso por nombre
+
+```py
+ forma = formas['Rectangulo1']
+ app.debug(forma)
+```
+
+
+
+### **in**
+
+Verificar si una forma esta en la colección por nombre.
+
+```py
+ app.debug('Shape 1' in formas)
+```
+
+
+
+### **iter**
+
+Iterar los elementos gráficos de la página de dibujo.
+
+```py
+ for f in formas:
+ app.debug(f)
+```
+
+
+
+### **len**
+
+Contar los elementos gráficos en la página de dibujo.
+
+```py
+ app.debug(len(formas))
+```
+
+
+
+## Métodos
+
+---
+
+### **add**
+
+Agrega una nueva forma (un rectángulo) y la devuelve.
+
+```py
+ forma = formas.add('Rectangle')
+ app.debug(forma)
+```
+
+
\ No newline at end of file
diff --git a/doc/docs/dp/shapes.md b/doc/docs/dp/shapes.md
new file mode 100644
index 0000000..2e731d4
--- /dev/null
+++ b/doc/docs/dp/shapes.md
@@ -0,0 +1,7 @@
+## Propiedades
+
+---
+
+## Métodos
+
+---
\ No newline at end of file
diff --git a/doc/mkdocs.yml b/doc/mkdocs.yml
index 2ce9bd3..0e47fee 100644
--- a/doc/mkdocs.yml
+++ b/doc/mkdocs.yml
@@ -24,6 +24,14 @@ nav:
- docs/index.md
- Propiedades: docs/properties.md
- Métodos: docs/methods.md
+ - Calc:
+ - calc/index.md
+ - Hojas: calc/sheets.md
+ - Rangos: calc/ranges.md
+ - Celdas: calc/cells.md
+ - Página de dibujo:
+ - dp/drawpage.md
+ - Formas: dp/shapes.md
theme:
name: material
locale: es
diff --git a/source/easymacro/__init__.py b/source/easymacro/__init__.py
index 2798484..88ef035 100644
--- a/source/easymacro/__init__.py
+++ b/source/easymacro/__init__.py
@@ -11,6 +11,7 @@ from .easydrawpage import LOGalleries
def __getattr__(name):
classes = {
'active': LODocuments().active,
+ 'active_sheet': LODocuments().active.active,
'clipboard': ClipBoard,
'cmd': LOMain.commands,
'color': Color(),
diff --git a/source/easymacro/easycalc.py b/source/easymacro/easycalc.py
index 5b85e3c..d01452d 100644
--- a/source/easymacro/easycalc.py
+++ b/source/easymacro/easycalc.py
@@ -90,9 +90,10 @@ class LOCellStyles():
# ~ IsStartOfNewPage
class LOSheetRows():
- def __init__(self, sheet, obj):
+ def __init__(self, sheet, obj, range_address):
self._sheet = sheet
self._obj = obj
+ self._ra = range_address
def __getitem__(self, index):
if isinstance(index, int):
@@ -106,7 +107,8 @@ class LOSheetRows():
return self.obj.Count
def __str__(self):
- return self.obj.Name
+ name = f'Rows: {self._ra.StartRow} - {self._ra.EndRow}'
+ return name
@property
def obj(self):
@@ -239,6 +241,7 @@ class LOCalcRange():
def __init__(self, obj):
self._obj = obj
self._is_cell = obj.ImplementationName == self.CELL
+ self._shape = None
def __contains__(self, rango):
if isinstance(rango, LOCalcRange):
@@ -303,6 +306,7 @@ class LOCalcRange():
@property
def is_range(self):
+ """True if range is not cell"""
return not self._is_cell
@property
@@ -523,6 +527,17 @@ class LOCalcRange():
col2 = ra.EndColumn + 1
return LOCalcRange(self.sheet[row1:row2, col1:col2].obj)
+ @property
+ def shape(self):
+ return self._shape
+ @shape.setter
+ def shape(self, shape):
+ shape.anchor = self
+ shape.resize_with_cell = True
+ shape.possize = self.possize
+ return
+
+ # ~ Fix
@property
def array_formula(self):
return self.obj.ArrayFormula
@@ -535,12 +550,10 @@ class LOCalcRange():
cursor = self.obj.Spreadsheet.createCursorByRange(self.obj)
return cursor
- # ~ To doc
@property
def position(self):
return self.obj.Position
- # ~ To doc
@property
def size(self):
return self.obj.Size
@@ -561,20 +574,19 @@ class LOCalcRange():
def y(self):
return self.obj.Position.Y
- # ~ To doc
@property
def possize(self):
data = {
- 'Width': self.size.Width,
- 'Height': self.size.Height,
- 'X': self.position.X,
- 'Y': self.position.Y,
+ 'Width': self.width,
+ 'Height': self.height,
+ 'X': self.x,
+ 'Y': self.y,
}
return data
@property
def rows(self):
- return LOSheetRows(self.sheet, self.obj.Rows)
+ return LOSheetRows(self.sheet, self.obj.Rows, self.range_address)
@property
def row(self):
@@ -592,17 +604,26 @@ class LOCalcRange():
return
def offset(self, rows=0, cols=1):
+ """Get cell offset"""
ra = self.range_address
col = ra.EndColumn + cols
row = ra.EndRow + rows
return LOCalcRange(self.sheet[row, col].obj)
def to_size(self, cols: int, rows: int):
+ """Expande range to size"""
cursor = self.cursor
cursor.collapseToSize(cols, rows)
rango = self.obj.Spreadsheet[cursor.AbsoluteName]
return LOCalcRange(rango)
+ def len(self):
+ """Get size of range in rows and columns"""
+ ra = self.range_address
+ rows = ra.EndRow - ra.StartRow + 1
+ cols = ra.EndColumn - ra.StartColumn + 1
+ return rows, cols
+
class LOCalcSheet(BaseObject):
@@ -623,10 +644,12 @@ class LOCalcSheet(BaseObject):
@property
def doc(self):
+ """Get parent doc"""
return LOCalc(self.obj.DrawPage.Forms.Parent)
@property
def name(self):
+ """Get name sheet"""
return self._obj.Name
@name.setter
def name(self, value):
@@ -634,6 +657,7 @@ class LOCalcSheet(BaseObject):
@property
def code_name(self):
+ """Get code name"""
return self._obj.CodeName
@code_name.setter
def code_name(self, value):
@@ -641,6 +665,7 @@ class LOCalcSheet(BaseObject):
@property
def visible(self):
+ """Get visible"""
return self._obj.IsVisible
@visible.setter
def visible(self, value):
@@ -648,6 +673,7 @@ class LOCalcSheet(BaseObject):
@property
def color(self):
+ """Get color tab"""
return self._obj.TabColor
@color.setter
def color(self, value):
@@ -655,12 +681,14 @@ class LOCalcSheet(BaseObject):
@property
def used_area(self):
+ """Get used area"""
cursor = self.create_cursor()
cursor.gotoEndOfUsedArea(True)
return self[cursor.AbsoluteName]
@property
def is_protected(self):
+ """Get is protected"""
return self._obj.isProtected()
@property
@@ -668,10 +696,12 @@ class LOCalcSheet(BaseObject):
return ''
@password.setter
def password(self, value):
+ """Set password protect"""
self.obj.protect(value)
@property
def draw_page(self):
+ """Get draw page"""
return LODrawPage(self.obj.DrawPage)
@property
def dp(self):
@@ -686,6 +716,7 @@ class LOCalcSheet(BaseObject):
@property
def events(self):
+ """Get events"""
return LOEvents(self.obj.Events)
@property
@@ -701,6 +732,7 @@ class LOCalcSheet(BaseObject):
return
def unprotect(self, value):
+ """Unproyect sheet"""
try:
self.obj.unprotect(value)
return True
@@ -709,6 +741,7 @@ class LOCalcSheet(BaseObject):
return False
def move(self, pos: int=-1):
+ """Move sheet"""
index = pos
if pos < 0:
index = len(self.doc)
@@ -716,10 +749,12 @@ class LOCalcSheet(BaseObject):
return
def remove(self):
+ """Auto remove"""
self.doc.remove(self.name)
return
def copy(self, new_name: str='', pos: int=-1):
+ """Copy sheet"""
index = pos
if pos < 0:
index = len(self.doc)
@@ -727,8 +762,7 @@ class LOCalcSheet(BaseObject):
return new_sheet
def copy_to(self, doc: Any, target: str='', pos: int=-1):
- """Copy sheet to other document.
- """
+ """Copy sheet to other document."""
index = pos
if pos < 0:
index = len(doc)
@@ -909,7 +943,6 @@ class LOCalc(LODocument):
self._cc.select(obj)
return
- @run_in_thread
def start_range_selection(self, controllers: Any, args: dict={}):
"""Start select range selection by user
diff --git a/source/easymacro/easydrawpage.py b/source/easymacro/easydrawpage.py
index 0895238..05dca93 100644
--- a/source/easymacro/easydrawpage.py
+++ b/source/easymacro/easydrawpage.py
@@ -145,6 +145,10 @@ class LODrawPage(BaseObject):
break
return result
+ def __len__(self):
+ """Count shapes"""
+ return self.count
+
@property
def name(self):
return self.obj.Name
@@ -201,7 +205,6 @@ class LODrawPage(BaseObject):
if properties:
set_properties(shape, properties)
- # ~ return LOShape(self.obj[index], index)
return LOShape(self.obj[index])
def remove(self, shape):
diff --git a/source/easymacro/easyevents.py b/source/easymacro/easyevents.py
index 276c630..8ce65a6 100644
--- a/source/easymacro/easyevents.py
+++ b/source/easymacro/easyevents.py
@@ -80,15 +80,12 @@ class EventsRangeSelectionListener(EventsListenerBase, XRangeSelectionListener):
super().__init__(controller, '')
def done(self, event):
- range_selection = event.RangeDescriptor
- print(event)
event_name = 'range_selection_done'
if hasattr(self._controller, event_name):
- getattr(self._controller, event_name)(range_selection)
+ getattr(self._controller, event_name)(event.RangeDescriptor)
return
def aborted(self, event):
- range_selection = event.RangeDescriptor
event_name = 'range_selection_aborted'
if hasattr(self._controller, event_name):
getattr(self._controller, event_name)()
diff --git a/source/easymacro/easyshape.py b/source/easymacro/easyshape.py
index 99a7f91..f3488b4 100644
--- a/source/easymacro/easyshape.py
+++ b/source/easymacro/easyshape.py
@@ -54,6 +54,24 @@ class LOShape(BaseObject):
def obj(self):
return self._obj
+ @property
+ def anchor(self):
+ return self.obj.Anchor
+ @anchor.setter
+ def anchor(self, obj):
+ if hasattr(obj, 'obj'):
+ obj = obj.obj
+ self.obj.Anchor = obj
+ return
+
+ @property
+ def resize_with_cell(self):
+ return self.obj.ResizeWithCell
+ @resize_with_cell.setter
+ def resize_with_cell(self, value):
+ self.obj.ResizeWithCell = value
+ return
+
@property
def properties(self):
# ~ properties = self.obj.PropertySetInfo.Properties