From 39dbb95672c0997c9dd5f6081e908229fec8df42 Mon Sep 17 00:00:00 2001 From: godax84 Date: Thu, 19 Dec 2024 09:03:36 -0800 Subject: [PATCH] Carregar ficheiros para "/" --- document_compare.py | 286 ++++++++++++++++++++++++++++++++++++++++++++ document_form.py | 228 +++++++++++++++++++++++++++++++++++ document_list.py | 248 ++++++++++++++++++++++++++++++++++++++ document_viewer.py | 164 +++++++++++++++++++++++++ folder_view.py | 176 +++++++++++++++++++++++++++ 5 files changed, 1102 insertions(+) create mode 100644 document_compare.py create mode 100644 document_form.py create mode 100644 document_list.py create mode 100644 document_viewer.py create mode 100644 folder_view.py 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()