diff --git a/document_compare.py b/document_compare.py new file mode 100644 index 0000000..924aba9 --- /dev/null +++ b/document_compare.py @@ -0,0 +1,286 @@ +from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QLabel, QComboBox, QScrollArea, QSplitter, + QMessageBox) +from PySide6.QtCore import Qt +from PySide6.QtGui import QImage, QPixmap +import fitz +from database import get_session +from models import Document, DocumentVersion +import difflib +import os +from PIL import Image +import numpy as np +import cv2 + +class DocumentCompare(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.doc1 = None + self.doc2 = None + self.current_page = 0 + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Document selection + select_layout = QHBoxLayout() + + # First document + doc1_layout = QVBoxLayout() + doc1_layout.addWidget(QLabel("Documento 1:")) + self.doc1_combo = QComboBox() + self.doc1_combo.currentIndexChanged.connect(self.load_comparison) + doc1_layout.addWidget(self.doc1_combo) + select_layout.addLayout(doc1_layout) + + # Second document + doc2_layout = QVBoxLayout() + doc2_layout.addWidget(QLabel("Documento 2:")) + self.doc2_combo = QComboBox() + self.doc2_combo.currentIndexChanged.connect(self.load_comparison) + doc2_layout.addWidget(self.doc2_combo) + select_layout.addLayout(doc2_layout) + + layout.addLayout(select_layout) + + # Navigation + nav_layout = QHBoxLayout() + + self.prev_btn = QPushButton("Anterior") + self.prev_btn.clicked.connect(self.prev_page) + nav_layout.addWidget(self.prev_btn) + + self.page_label = QLabel("Página 0 de 0") + nav_layout.addWidget(self.page_label) + + self.next_btn = QPushButton("Próxima") + self.next_btn.clicked.connect(self.next_page) + nav_layout.addWidget(self.next_btn) + + nav_layout.addStretch() + + self.diff_mode_combo = QComboBox() + self.diff_mode_combo.addItems(["Visual", "Texto", "Híbrido"]) + self.diff_mode_combo.currentTextChanged.connect(self.load_comparison) + nav_layout.addWidget(self.diff_mode_combo) + + layout.addLayout(nav_layout) + + # Comparison view + self.splitter = QSplitter(Qt.Horizontal) + + # Left document + self.left_scroll = QScrollArea() + self.left_scroll.setWidgetResizable(True) + self.left_label = QLabel() + self.left_label.setAlignment(Qt.AlignCenter) + self.left_scroll.setWidget(self.left_label) + self.splitter.addWidget(self.left_scroll) + + # Right document + self.right_scroll = QScrollArea() + self.right_scroll.setWidgetResizable(True) + self.right_label = QLabel() + self.right_label.setAlignment(Qt.AlignCenter) + self.right_scroll.setWidget(self.right_label) + self.splitter.addWidget(self.right_scroll) + + # Difference view + self.diff_scroll = QScrollArea() + self.diff_scroll.setWidgetResizable(True) + self.diff_label = QLabel() + self.diff_label.setAlignment(Qt.AlignCenter) + self.diff_scroll.setWidget(self.diff_label) + self.splitter.addWidget(self.diff_scroll) + + layout.addWidget(self.splitter) + + # Difference summary + self.diff_summary = QLabel() + layout.addWidget(self.diff_summary) + + self.refresh_documents() + + def refresh_documents(self): + try: + session = get_session() + documents = session.query(Document).all() + + # Update combo boxes + self.doc1_combo.clear() + self.doc2_combo.clear() + + for doc in documents: + item_text = f"{doc.file_name} ({doc.marca} {doc.modelo})" + self.doc1_combo.addItem(item_text, doc.id) + self.doc2_combo.addItem(item_text, doc.id) + + session.close() + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao carregar documentos: {str(e)}") + + def load_comparison(self): + if (self.doc1_combo.currentData() is None or + self.doc2_combo.currentData() is None): + return + + try: + session = get_session() + + # Load documents + doc1 = session.query(Document).get(self.doc1_combo.currentData()) + doc2 = session.query(Document).get(self.doc2_combo.currentData()) + + if doc1 and doc2: + self.doc1 = fitz.open(doc1.file_path) + self.doc2 = fitz.open(doc2.file_path) + self.current_page = 0 + self.update_comparison() + + session.close() + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao carregar documentos: {str(e)}") + + def update_comparison(self): + if not self.doc1 or not self.doc2: + return + + try: + # Update page navigation + max_pages = min(len(self.doc1), len(self.doc2)) + self.page_label.setText(f"Página {self.current_page + 1} de {max_pages}") + + # Get pages + page1 = self.doc1[self.current_page] + page2 = self.doc2[self.current_page] + + # Process based on comparison mode + mode = self.diff_mode_combo.currentText() + + if mode == "Visual": + self.compare_visual(page1, page2) + elif mode == "Texto": + self.compare_text(page1, page2) + else: # Hybrid + self.compare_hybrid(page1, page2) + + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao comparar páginas: {str(e)}") + + def compare_visual(self, page1, page2): + # Convert pages to images + pix1 = page1.get_pixmap() + pix2 = page2.get_pixmap() + + # Convert to numpy arrays + img1 = np.frombuffer(pix1.samples, dtype=np.uint8).reshape( + pix1.height, pix1.width, 3) + img2 = np.frombuffer(pix2.samples, dtype=np.uint8).reshape( + pix2.height, pix2.width, 3) + + # Resize images to same size + height = max(img1.shape[0], img2.shape[0]) + width = max(img1.shape[1], img2.shape[1]) + + img1 = cv2.resize(img1, (width, height)) + img2 = cv2.resize(img2, (width, height)) + + # Calculate difference + diff = cv2.absdiff(img1, img2) + diff_highlighted = img2.copy() + diff_highlighted[diff.mean(axis=2) > 30] = [0, 0, 255] # Red for differences + + # Convert to QPixmap and display + self.display_image(img1, self.left_label) + self.display_image(img2, self.right_label) + self.display_image(diff_highlighted, self.diff_label) + + # Update summary + diff_percentage = (diff.mean(axis=2) > 30).mean() * 100 + self.diff_summary.setText( + f"Diferença visual: {diff_percentage:.2f}% da página") + + def compare_text(self, page1, page2): + # Extract text + text1 = page1.get_text() + text2 = page2.get_text() + + # Compare text + diff = difflib.unified_diff( + text1.splitlines(), + text2.splitlines(), + lineterm='' + ) + + # Format differences + diff_html = [] + for line in diff: + if line.startswith('+'): + diff_html.append(f'{line}') + elif line.startswith('-'): + diff_html.append(f'{line}') + else: + diff_html.append(line) + + # Display original texts and differences + self.left_label.setText(f"
{text1}")
+ self.right_label.setText(f"{text2}")
+ self.diff_label.setText(f"{'
'.join(diff_html)}")
+
+ # Update summary
+ changes = len([l for l in diff_html if l.startswith(' 0:
+ self.current_page -= 1
+ self.update_comparison()
+
+ def next_page(self):
+ if (self.doc1 and self.doc2 and
+ self.current_page < min(len(self.doc1), len(self.doc2)) - 1):
+ self.current_page += 1
+ self.update_comparison()
+
+ def closeEvent(self, event):
+ if self.doc1:
+ self.doc1.close()
+ if self.doc2:
+ self.doc2.close()
+ super().closeEvent(event)
diff --git a/document_form.py b/document_form.py
new file mode 100644
index 0000000..ba2c32d
--- /dev/null
+++ b/document_form.py
@@ -0,0 +1,228 @@
+from PySide6.QtWidgets import (QWidget, QFormLayout, QLineEdit, QComboBox,
+ QTextEdit, QPushButton, QFileDialog, QSpinBox,
+ QVBoxLayout, QLabel, QMessageBox)
+from database import get_session
+from models import Document, Folder
+import os
+import shutil
+from datetime import datetime
+from preferences import Preferences
+
+class DocumentForm(QWidget):
+ def __init__(self, folder_id=None, parent=None):
+ super().__init__(parent)
+ self.folder_id = folder_id
+ self.selected_file = None
+ self.preferences = Preferences()
+ self.setup_ui()
+
+ # Create documents directory if it doesn't exist
+ self.docs_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "documentos")
+ if not os.path.exists(self.docs_dir):
+ os.makedirs(self.docs_dir)
+
+ def setup_ui(self):
+ layout = QVBoxLayout(self)
+ form_layout = QFormLayout()
+
+ # File selection
+ self.file_button = QPushButton("Selecionar Arquivo")
+ self.file_button.clicked.connect(self.select_file)
+ self.file_label = QLabel("Nenhum arquivo selecionado")
+ form_layout.addRow("Arquivo:", self.file_button)
+ form_layout.addRow("", self.file_label)
+
+ # Required fields
+ self.marca_edit = QLineEdit()
+ self.modelo_edit = QLineEdit()
+ self.ano_spin = QSpinBox()
+ self.ano_spin.setRange(1900, 2100)
+ self.ano_spin.setValue(2024)
+
+ form_layout.addRow("Marca*:", self.marca_edit)
+ form_layout.addRow("Modelo*:", self.modelo_edit)
+ form_layout.addRow("Ano*:", self.ano_spin)
+
+ # Optional fields
+ self.cilindrada_edit = QLineEdit()
+ self.codigo_motor_edit = QLineEdit()
+ form_layout.addRow("Cilindrada:", self.cilindrada_edit)
+ form_layout.addRow("Código Motor:", self.codigo_motor_edit)
+
+ # Document type
+ self.tipo_combo = QComboBox()
+ self.tipo_combo.addItems(["Elétrico", "Mecânico"])
+ form_layout.addRow("Tipo de Documento*:", self.tipo_combo)
+
+ # Variant
+ self.variante_combo = QComboBox()
+ self.variante_combo.addItems([
+ "Esquema", "Esquema OEM", "TSB",
+ "Esquemas Varios", "Documentação de Instruções"
+ ])
+ form_layout.addRow("Variante*:", self.variante_combo)
+
+ # Observations
+ self.observacoes_edit = QTextEdit()
+ form_layout.addRow("Observações:", self.observacoes_edit)
+
+ layout.addLayout(form_layout)
+
+ # Save button
+ self.save_button = QPushButton("Salvar")
+ self.save_button.clicked.connect(self.save_document)
+ layout.addWidget(self.save_button)
+
+ def get_save_location(self, filename):
+ """Prompt user for save location"""
+ base_path = self.preferences.get_save_path()
+ print(f"Base save path from preferences: {base_path}") # Debug print
+
+ if not base_path:
+ QMessageBox.warning(self, "Erro", "Por favor, configure o diretório de salvamento nas preferências primeiro.")
+ return None
+
+ # Get current folder path if folder_id is set
+ if self.folder_id:
+ print(f"Getting path for folder ID: {self.folder_id}") # Debug print
+ session = get_session()
+ folder = session.query(Folder).get(self.folder_id)
+ if folder:
+ print(f"Found folder: {folder.name}") # Debug print
+ current_folder = folder
+ path_parts = []
+ while current_folder:
+ path_parts.insert(0, current_folder.name)
+ current_folder = current_folder.parent
+ suggested_path = os.path.join(base_path, *path_parts, filename)
+ print(f"Suggested save path: {suggested_path}") # Debug print
+ else:
+ print(f"No folder found for ID: {self.folder_id}") # Debug print
+ suggested_path = os.path.join(base_path, filename)
+ session.close()
+ else:
+ print("No folder ID set") # Debug print
+ suggested_path = os.path.join(base_path, filename)
+
+ save_path, _ = QFileDialog.getSaveFileName(
+ self,
+ "Salvar Documento",
+ suggested_path,
+ "Todos os arquivos (*.*)"
+ )
+ print(f"User selected save path: {save_path}") # Debug print
+ return save_path
+
+ def select_file(self):
+ file_path, _ = QFileDialog.getOpenFileName(
+ self,
+ "Selecionar Documento",
+ "",
+ "Todos os arquivos (*.*);;Documentos PDF (*.pdf);;Imagens (*.png *.jpg *.jpeg)"
+ )
+
+ if file_path:
+ self.selected_file = file_path
+ self.file_label.setText(os.path.basename(file_path))
+
+ def save_document(self):
+ if not self.selected_file:
+ QMessageBox.warning(self, "Erro", "Por favor, selecione um arquivo primeiro.")
+ return
+
+ if not self.validate_form():
+ return
+
+ print(f"Starting document save process...") # Debug print
+ print(f"Current folder ID: {self.folder_id}") # Debug print
+ print(f"Selected file: {self.selected_file}") # Debug print
+
+ # Get save location from user
+ filename = os.path.basename(self.selected_file)
+ save_path = self.get_save_location(filename)
+
+ if not save_path:
+ print("Save cancelled - no path selected") # Debug print
+ return
+
+ try:
+ # Create directory if it doesn't exist
+ save_dir = os.path.dirname(save_path)
+ print(f"Creating directory: {save_dir}") # Debug print
+ os.makedirs(save_dir, exist_ok=True)
+
+ # Copy file to new location
+ print(f"Copying file from {self.selected_file} to {save_path}") # Debug print
+ shutil.copy2(self.selected_file, save_path)
+
+ # Create document record
+ session = get_session()
+
+ # Double check folder exists
+ if self.folder_id:
+ folder = session.query(Folder).get(self.folder_id)
+ print(f"Verified folder: {folder.name if folder else 'Not Found'}") # Debug print
+
+ document = Document(
+ file_path=save_path,
+ file_name=os.path.basename(save_path),
+ marca=self.marca_edit.text(),
+ modelo=self.modelo_edit.text(),
+ ano=self.ano_spin.value(),
+ cilindrada=self.cilindrada_edit.text(),
+ codigo_motor=self.codigo_motor_edit.text(),
+ tipo_documento=self.tipo_combo.currentText(),
+ variante=self.variante_combo.currentText(),
+ observacoes=self.observacoes_edit.toPlainText(),
+ folder_id=self.folder_id
+ )
+
+ print(f"Created document object with attributes:") # Debug print
+ print(f" file_path: {document.file_path}")
+ print(f" file_name: {document.file_name}")
+ print(f" folder_id: {document.folder_id}")
+
+ session.add(document)
+ session.commit()
+ print(f"Document saved to database with ID: {document.id}") # Debug print
+ session.close()
+
+ QMessageBox.information(self, "Sucesso", "Documento salvo com sucesso!")
+ self.clear_form()
+
+ except Exception as e:
+ print(f"Error saving document: {str(e)}") # Debug print
+ print(f"Error type: {type(e)}") # Debug print
+ import traceback
+ print(f"Stack trace: {traceback.format_exc()}") # Debug print
+ QMessageBox.critical(self, "Erro", f"Erro ao salvar documento: {str(e)}")
+
+ def validate_form(self):
+ if not self.selected_file:
+ QMessageBox.warning(self, "Validação",
+ "Por favor, selecione um arquivo.")
+ return False
+
+ if not self.marca_edit.text():
+ QMessageBox.warning(self, "Validação",
+ "Por favor, preencha a marca.")
+ return False
+
+ if not self.modelo_edit.text():
+ QMessageBox.warning(self, "Validação",
+ "Por favor, preencha o modelo.")
+ return False
+
+ return True
+
+ def clear_form(self):
+ self.selected_file = None
+ self.file_label.setText("Nenhum arquivo selecionado")
+ self.marca_edit.clear()
+ self.modelo_edit.clear()
+ self.ano_spin.setValue(2024)
+ self.cilindrada_edit.clear()
+ self.codigo_motor_edit.clear()
+ self.tipo_combo.setCurrentIndex(0)
+ self.variante_combo.setCurrentIndex(0)
+ self.observacoes_edit.clear()
diff --git a/document_list.py b/document_list.py
new file mode 100644
index 0000000..f4e515a
--- /dev/null
+++ b/document_list.py
@@ -0,0 +1,248 @@
+from PySide6.QtWidgets import (QWidget, QVBoxLayout, QTableWidget,
+ QTableWidgetItem, QHeaderView, QMenu,
+ QMessageBox, QPushButton, QHBoxLayout)
+from PySide6.QtCore import Qt, Signal
+from database import get_session
+from models import Document, Folder
+import os
+from preferences import Preferences
+
+class DocumentListView(QWidget):
+ document_selected = Signal(int) # Signal emitted when document is selected
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.current_folder_id = None
+ self.setup_ui()
+
+ def setup_ui(self):
+ layout = QVBoxLayout(self)
+
+ # Toolbar
+ toolbar = QHBoxLayout()
+ self.refresh_btn = QPushButton("Atualizar")
+ self.refresh_btn.clicked.connect(self.refresh_documents)
+ toolbar.addWidget(self.refresh_btn)
+
+ self.delete_btn = QPushButton("Excluir Selecionados")
+ self.delete_btn.clicked.connect(self.delete_selected)
+ toolbar.addWidget(self.delete_btn)
+
+ layout.addLayout(toolbar)
+
+ # Documents table
+ self.docs_table = QTableWidget()
+ self.docs_table.setColumnCount(7)
+ self.docs_table.setHorizontalHeaderLabels([
+ "Marca", "Modelo", "Ano", "Tipo", "Variante",
+ "Nome do Arquivo", "Observações"
+ ])
+ header = self.docs_table.horizontalHeader()
+ header.setSectionResizeMode(QHeaderView.ResizeToContents)
+ header.setSectionResizeMode(6, QHeaderView.Stretch) # Observações column stretches
+
+ self.docs_table.setSortingEnabled(True)
+ self.docs_table.setSelectionBehavior(QTableWidget.SelectRows)
+ self.docs_table.setSelectionMode(QTableWidget.SingleSelection)
+
+ self.docs_table.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.docs_table.customContextMenuRequested.connect(self.show_context_menu)
+ self.docs_table.doubleClicked.connect(self.open_document)
+
+ layout.addWidget(self.docs_table)
+
+ def set_folder(self, folder_id):
+ self.current_folder_id = folder_id
+ self.refresh_documents()
+
+ def refresh_documents(self):
+ session = get_session()
+
+ try:
+ # First, let's check all documents and fix their folder associations
+ all_documents = session.query(Document).all()
+ for doc in all_documents:
+ # Check if the document exists
+ if os.path.exists(doc.file_path):
+ # Always try to find the most specific folder
+ folders = session.query(Folder).all()
+ best_folder = self.find_best_matching_folder(doc.file_path, folders)
+
+ if best_folder:
+ if doc.folder_id != best_folder.id:
+ doc.folder_id = best_folder.id
+ session.add(doc)
+ else:
+ session.delete(doc)
+
+ # Commit any changes made
+ session.commit()
+
+ # Now get documents for the current folder
+ if self.current_folder_id is not None:
+ documents = session.query(Document).filter(
+ Document.folder_id == self.current_folder_id
+ ).all()
+ else:
+ documents = session.query(Document).all()
+
+ # Update the table
+ self.docs_table.setRowCount(len(documents))
+ for row, doc in enumerate(documents):
+ self.docs_table.setItem(row, 0, QTableWidgetItem(doc.marca))
+ self.docs_table.setItem(row, 1, QTableWidgetItem(doc.modelo))
+ self.docs_table.setItem(row, 2, QTableWidgetItem(str(doc.ano)))
+ self.docs_table.setItem(row, 3, QTableWidgetItem(doc.tipo_documento))
+ self.docs_table.setItem(row, 4, QTableWidgetItem(doc.variante))
+ self.docs_table.setItem(row, 5, QTableWidgetItem(doc.file_name))
+ self.docs_table.setItem(row, 6, QTableWidgetItem(doc.observacoes))
+
+ # Store document ID in the first column
+ self.docs_table.item(row, 0).setData(Qt.UserRole, doc.id)
+
+ except Exception as e:
+ QMessageBox.critical(self, "Erro", f"Erro ao atualizar documentos: {str(e)}")
+ finally:
+ session.close()
+
+ def get_folder_path(self, folder):
+ """Get the full path for a folder"""
+ try:
+ path_parts = []
+ current = folder
+ while current:
+ path_parts.insert(0, current.name)
+ current = current.parent
+
+ # Get base path from preferences
+ preferences = Preferences()
+ base_path = preferences.get_save_path()
+ if base_path:
+ # Make sure we're using forward slashes and the correct base path
+ # Only add 'Root' if it's not already in the path
+ if path_parts and path_parts[0] != "Root":
+ path_parts.insert(0, "Root")
+ full_path = os.path.join(base_path, *path_parts)
+ # Convert to forward slashes for consistency
+ return full_path.replace("\\", "/")
+ except Exception as e:
+ print(f"Error getting folder path: {str(e)}")
+ return None
+
+ def find_best_matching_folder(self, doc_path, folders):
+ """Find the most specific matching folder for a document path"""
+ doc_path = doc_path.replace("\\", "/")
+ best_match = None
+ longest_match = 0
+
+ for folder in folders:
+ folder_path = self.get_folder_path(folder)
+ if folder_path:
+ folder_path = folder_path.replace("\\", "/")
+
+ # Make sure paths end with slash for exact directory matching
+ folder_path = folder_path.rstrip("/") + "/"
+ doc_dir = os.path.dirname(doc_path).rstrip("/") + "/"
+
+ if doc_dir.startswith(folder_path):
+ match_length = len(folder_path)
+ if match_length > longest_match:
+ longest_match = match_length
+ best_match = folder
+
+ return best_match
+
+ def show_context_menu(self, position):
+ menu = QMenu()
+ open_action = menu.addAction("Abrir Documento")
+ open_folder_action = menu.addAction("Abrir Pasta")
+ menu.addSeparator()
+ delete_action = menu.addAction("Excluir")
+
+ action = menu.exec_(self.docs_table.mapToGlobal(position))
+
+ current_row = self.docs_table.currentRow()
+ if current_row >= 0:
+ doc_id = self.docs_table.item(current_row, 0).data(Qt.UserRole)
+
+ if action == open_action:
+ self.open_document()
+ elif action == open_folder_action:
+ self.open_document_folder(doc_id)
+ elif action == delete_action:
+ self.delete_document(doc_id)
+
+ def open_document(self, index=None):
+ """Open the selected document"""
+ if index:
+ row = index.row()
+ else:
+ row = self.docs_table.currentRow()
+
+ if row >= 0:
+ doc_id = self.docs_table.item(row, 0).data(Qt.UserRole)
+ session = get_session()
+ try:
+ doc = session.query(Document).get(doc_id)
+ if doc and os.path.exists(doc.file_path):
+ # Use the default system application to open the file
+ os.startfile(doc.file_path)
+ else:
+ QMessageBox.warning(self, "Erro", "Arquivo não encontrado.")
+ except Exception as e:
+ QMessageBox.critical(self, "Erro", f"Erro ao abrir documento: {str(e)}")
+ finally:
+ session.close()
+
+ def open_document_folder(self, doc_id):
+ session = get_session()
+ document = session.query(Document).get(doc_id)
+
+ if document and os.path.exists(document.file_path):
+ folder_path = os.path.dirname(document.file_path)
+ os.startfile(folder_path)
+ else:
+ QMessageBox.warning(self, "Erro",
+ "Não foi possível abrir a pasta do documento.")
+
+ session.close()
+
+ def delete_document(self, doc_id):
+ reply = QMessageBox.question(
+ self,
+ "Confirmar Exclusão",
+ "Tem certeza que deseja excluir este documento?",
+ QMessageBox.Yes | QMessageBox.No
+ )
+
+ if reply == QMessageBox.Yes:
+ session = get_session()
+ document = session.query(Document).get(doc_id)
+ if document:
+ session.delete(document)
+ session.commit()
+ session.close()
+ self.refresh_documents()
+
+ def delete_selected(self):
+ selected_rows = set(item.row() for item in self.docs_table.selectedItems())
+ if not selected_rows:
+ return
+
+ reply = QMessageBox.question(
+ self,
+ "Confirmar Exclusão",
+ f"Tem certeza que deseja excluir {len(selected_rows)} documento(s)?",
+ QMessageBox.Yes | QMessageBox.No
+ )
+
+ if reply == QMessageBox.Yes:
+ session = get_session()
+ for row in selected_rows:
+ doc_id = self.docs_table.item(row, 0).data(Qt.UserRole)
+ document = session.query(Document).get(doc_id)
+ if document:
+ session.delete(document)
+ session.commit()
+ session.close()
+ self.refresh_documents()
diff --git a/document_viewer.py b/document_viewer.py
new file mode 100644
index 0000000..f1d12f1
--- /dev/null
+++ b/document_viewer.py
@@ -0,0 +1,164 @@
+from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
+ QLabel, QScrollArea, QSplitter, QTabWidget)
+from PySide6.QtCore import Qt, Signal
+from PySide6.QtGui import QImage, QPixmap
+import fitz
+from database import get_session
+from models import Document
+from comments import CommentsList
+from preferences import Preferences
+
+class DocumentViewer(QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.current_document = None
+ self.current_page = 0
+ self.preferences = Preferences()
+ self.setup_ui()
+
+ def setup_ui(self):
+ layout = QVBoxLayout(self)
+
+ # Toolbar
+ toolbar = QHBoxLayout()
+
+ self.prev_btn = QPushButton("Anterior")
+ self.prev_btn.clicked.connect(self.prev_page)
+ toolbar.addWidget(self.prev_btn)
+
+ self.page_label = QLabel("Página 0 de 0")
+ toolbar.addWidget(self.page_label)
+
+ self.next_btn = QPushButton("Próxima")
+ self.next_btn.clicked.connect(self.next_page)
+ toolbar.addWidget(self.next_btn)
+
+ toolbar.addStretch()
+
+ self.zoom_in_btn = QPushButton("Zoom +")
+ self.zoom_in_btn.clicked.connect(self.zoom_in)
+ toolbar.addWidget(self.zoom_in_btn)
+
+ self.zoom_out_btn = QPushButton("Zoom -")
+ self.zoom_out_btn.clicked.connect(self.zoom_out)
+ toolbar.addWidget(self.zoom_out_btn)
+
+ layout.addLayout(toolbar)
+
+ # Main content area
+ splitter = QSplitter(Qt.Horizontal)
+
+ # Document view
+ self.scroll_area = QScrollArea()
+ self.scroll_area.setWidgetResizable(True)
+ self.document_label = QLabel()
+ self.document_label.setAlignment(Qt.AlignCenter)
+ self.scroll_area.setWidget(self.document_label)
+ splitter.addWidget(self.scroll_area)
+
+ # Side panel with tabs
+ side_panel = QTabWidget()
+
+ # Comments tab
+ self.comments_list = CommentsList()
+ side_panel.addTab(self.comments_list, "Comentários")
+
+ splitter.addWidget(side_panel)
+
+ # Set initial splitter sizes
+ splitter.setSizes([int(self.width() * 0.7), int(self.width() * 0.3)])
+
+ layout.addWidget(splitter)
+
+ self.zoom_level = 1.0
+ self.doc = None
+ self.update_navigation()
+
+ def load_document(self, doc_id):
+ try:
+ session = get_session()
+ document = session.query(Document).get(doc_id)
+
+ if document and document.file_path:
+ self.current_document = doc_id
+ self.doc = fitz.open(document.file_path)
+ self.current_page = 0
+ self.zoom_level = 1.0
+ self.update_page()
+ self.update_navigation()
+
+ # Load comments
+ self.comments_list.load_document(doc_id)
+
+ # Add to recent documents if auto_preview is enabled
+ if self.preferences.get("auto_preview"):
+ from preferences import RecentDocuments
+ recent = RecentDocuments(self.preferences)
+ recent.add_document(doc_id, document.file_name)
+
+ session.close()
+ except Exception as e:
+ print(f"Error loading document: {str(e)}")
+
+ def update_page(self):
+ if not self.doc:
+ return
+
+ try:
+ page = self.doc[self.current_page]
+ zoom = self.zoom_level
+
+ # Adjust zoom based on preview size preference
+ preview_size = self.preferences.get("preview_size")
+ if preview_size == "small":
+ zoom *= 0.8
+ elif preview_size == "large":
+ zoom *= 1.2
+
+ mat = fitz.Matrix(zoom, zoom)
+ pix = page.get_pixmap(matrix=mat)
+
+ img = QImage(pix.samples, pix.width, pix.height,
+ pix.stride, QImage.Format_RGB888)
+
+ pixmap = QPixmap.fromImage(img)
+ self.document_label.setPixmap(pixmap)
+
+ self.page_label.setText(f"Página {self.current_page + 1} de {len(self.doc)}")
+ except Exception as e:
+ print(f"Error updating page: {str(e)}")
+
+ def update_navigation(self):
+ has_doc = self.doc is not None
+ has_prev = has_doc and self.current_page > 0
+ has_next = has_doc and self.current_page < len(self.doc) - 1
+
+ self.prev_btn.setEnabled(has_prev)
+ self.next_btn.setEnabled(has_next)
+ self.zoom_in_btn.setEnabled(has_doc)
+ self.zoom_out_btn.setEnabled(has_doc)
+
+ def prev_page(self):
+ if self.doc and self.current_page > 0:
+ self.current_page -= 1
+ self.update_page()
+ self.update_navigation()
+
+ def next_page(self):
+ if self.doc and self.current_page < len(self.doc) - 1:
+ self.current_page += 1
+ self.update_page()
+ self.update_navigation()
+
+ def zoom_in(self):
+ self.zoom_level *= 1.2
+ self.update_page()
+
+ def zoom_out(self):
+ self.zoom_level /= 1.2
+ self.update_page()
+
+ def closeEvent(self, event):
+ if self.doc:
+ self.doc.close()
+ super().closeEvent(event)
diff --git a/folder_view.py b/folder_view.py
new file mode 100644
index 0000000..905c105
--- /dev/null
+++ b/folder_view.py
@@ -0,0 +1,176 @@
+from PySide6.QtWidgets import (QTreeView, QFileDialog, QMenu, QInputDialog,
+ QMessageBox)
+from PySide6.QtCore import Qt, QModelIndex, Signal
+from PySide6.QtGui import QStandardItemModel, QStandardItem
+from database import get_session
+from models import Folder
+import os
+from preferences import Preferences
+
+class FolderTreeView(QTreeView):
+ folder_selected = Signal(int) # Signal for folder selection
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.model = QStandardItemModel()
+ self.model.setHorizontalHeaderLabels(['Pastas'])
+ self.setModel(self.model)
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.customContextMenuRequested.connect(self.show_context_menu)
+ self.preferences = Preferences()
+ self.load_folders()
+
+ # Connect selection changed signal
+ self.selectionModel().selectionChanged.connect(self.on_folder_selected)
+
+ def load_folders(self):
+ self.model.clear()
+ self.model.setHorizontalHeaderLabels(['Pastas'])
+ session = get_session()
+
+ root_folders = session.query(Folder).filter(Folder.parent_id == None).all()
+
+ for folder in root_folders:
+ item = QStandardItem(folder.name)
+ item.setData(folder.id)
+ self.model.appendRow(item)
+ self.load_subfolders(folder, item)
+
+ session.close()
+
+ def load_subfolders(self, folder, parent_item):
+ session = get_session()
+ subfolders = session.query(Folder).filter(Folder.parent_id == folder.id).all()
+
+ for subfolder in subfolders:
+ item = QStandardItem(subfolder.name)
+ item.setData(subfolder.id)
+ parent_item.appendRow(item)
+ self.load_subfolders(subfolder, item)
+
+ session.close()
+
+ def get_physical_path(self, folder):
+ """Get the physical path for a folder"""
+ base_path = self.preferences.get_save_path()
+ if not base_path:
+ QMessageBox.warning(self, "Erro", "Por favor, configure o diretório de salvamento nas preferências primeiro.")
+ return None
+
+ path_parts = []
+ current = folder
+ while current:
+ path_parts.insert(0, current.name)
+ current = current.parent
+
+ # Make sure we're using forward slashes and the correct base path
+ # Only add 'Root' if it's not already in the path
+ if path_parts and path_parts[0] != "Root":
+ path_parts.insert(0, "Root")
+ full_path = os.path.join(base_path, *path_parts)
+ # Convert to forward slashes for consistency
+ return full_path.replace("\\", "/")
+
+ def create_physical_folder(self, folder):
+ """Create the physical folder on disk"""
+ physical_path = self.get_physical_path(folder)
+ if physical_path and not os.path.exists(physical_path):
+ try:
+ os.makedirs(physical_path)
+ return True
+ except Exception as e:
+ QMessageBox.warning(self, "Erro", f"Erro ao criar pasta física: {str(e)}")
+ return False
+ return True
+
+ def show_context_menu(self, position):
+ menu = QMenu()
+ add_action = menu.addAction("Nova Pasta")
+ delete_action = menu.addAction("Excluir Pasta")
+ rename_action = menu.addAction("Renomear")
+
+ action = menu.exec_(self.mapToGlobal(position))
+
+ if action == add_action:
+ self.add_folder()
+ elif action == delete_action:
+ self.delete_folder()
+ elif action == rename_action:
+ self.rename_folder()
+
+ def on_folder_selected(self, selected, deselected):
+ indexes = selected.indexes()
+ if indexes:
+ current_item = self.model.itemFromIndex(indexes[0])
+ folder_id = current_item.data()
+ self.folder_selected.emit(folder_id)
+
+ def add_folder(self):
+ name, ok = QInputDialog.getText(self, "Nova Pasta", "Nome da pasta:")
+ if ok and name:
+ session = get_session()
+ current_index = self.currentIndex()
+
+ new_folder = Folder(name=name)
+ if current_index.isValid():
+ parent_id = self.model.itemFromIndex(current_index).data()
+ parent_folder = session.query(Folder).get(parent_id)
+ new_folder.parent_id = parent_id
+ new_folder.parent = parent_folder
+
+ session.add(new_folder)
+ session.commit()
+
+ # Create physical folder
+ if not self.create_physical_folder(new_folder):
+ session.delete(new_folder)
+ session.commit()
+
+ session.close()
+ self.load_folders()
+
+ def delete_folder(self):
+ current_index = self.currentIndex()
+ if not current_index.isValid():
+ return
+
+ reply = QMessageBox.question(self, "Confirmar Exclusão",
+ "Tem certeza que deseja excluir esta pasta?\nIsso também excluirá a pasta física do disco.",
+ QMessageBox.Yes | QMessageBox.No)
+
+ if reply == QMessageBox.Yes:
+ session = get_session()
+ folder_id = self.model.itemFromIndex(current_index).data()
+ folder = session.query(Folder).get(folder_id)
+ if folder:
+ # Delete physical folder
+ physical_path = self.get_physical_path(folder)
+ if physical_path and os.path.exists(physical_path):
+ try:
+ os.rmdir(physical_path) # Only removes if empty
+ except Exception as e:
+ QMessageBox.warning(self, "Aviso", f"Não foi possível excluir a pasta física: {str(e)}")
+
+ session.delete(folder)
+ session.commit()
+ session.close()
+ self.load_folders()
+
+ def rename_folder(self):
+ current_index = self.currentIndex()
+ if not current_index.isValid():
+ return
+
+ current_name = self.model.itemFromIndex(current_index).text()
+ name, ok = QInputDialog.getText(self, "Renomear Pasta",
+ "Novo nome:", text=current_name)
+
+ if ok and name:
+ session = get_session()
+ folder_id = self.model.itemFromIndex(current_index).data()
+ folder = session.query(Folder).get(folder_id)
+ if folder:
+ folder.name = name
+ session.commit()
+ session.close()
+ self.load_folders()