Carregar ficheiros para "/"
This commit is contained in:
286
document_compare.py
Normal file
286
document_compare.py
Normal file
@ -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'<span style="background-color: #aaffaa">{line}</span>')
|
||||
elif line.startswith('-'):
|
||||
diff_html.append(f'<span style="background-color: #ffaaaa">{line}</span>')
|
||||
else:
|
||||
diff_html.append(line)
|
||||
|
||||
# Display original texts and differences
|
||||
self.left_label.setText(f"<pre>{text1}</pre>")
|
||||
self.right_label.setText(f"<pre>{text2}</pre>")
|
||||
self.diff_label.setText(f"<pre>{'<br>'.join(diff_html)}</pre>")
|
||||
|
||||
# Update summary
|
||||
changes = len([l for l in diff_html if l.startswith('<span')])
|
||||
self.diff_summary.setText(
|
||||
f"Diferenças encontradas: {changes} linhas modificadas")
|
||||
|
||||
def compare_hybrid(self, page1, page2):
|
||||
# Combine visual and text comparison
|
||||
self.compare_visual(page1, page2)
|
||||
|
||||
# Add text comparison summary
|
||||
text1 = page1.get_text()
|
||||
text2 = page2.get_text()
|
||||
|
||||
# Calculate text similarity
|
||||
similarity = difflib.SequenceMatcher(None, text1, text2).ratio()
|
||||
|
||||
# Update summary
|
||||
current_summary = self.diff_summary.text()
|
||||
self.diff_summary.setText(
|
||||
f"{current_summary}\nSimilaridade do texto: {similarity:.2f}%")
|
||||
|
||||
def display_image(self, img, label):
|
||||
height, width = img.shape[:2]
|
||||
bytes_per_line = 3 * width
|
||||
|
||||
q_img = QImage(img.data, width, height, bytes_per_line, QImage.Format_RGB888)
|
||||
pixmap = QPixmap.fromImage(q_img)
|
||||
|
||||
# Scale to fit while maintaining aspect ratio
|
||||
scaled_pixmap = pixmap.scaled(
|
||||
label.size(),
|
||||
Qt.KeepAspectRatio,
|
||||
Qt.SmoothTransformation
|
||||
)
|
||||
label.setPixmap(scaled_pixmap)
|
||||
|
||||
def prev_page(self):
|
||||
if self.current_page > 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)
|
||||
228
document_form.py
Normal file
228
document_form.py
Normal file
@ -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()
|
||||
248
document_list.py
Normal file
248
document_list.py
Normal file
@ -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()
|
||||
164
document_viewer.py
Normal file
164
document_viewer.py
Normal file
@ -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)
|
||||
176
folder_view.py
Normal file
176
folder_view.py
Normal file
@ -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()
|
||||
Reference in New Issue
Block a user