Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified core/__pycache__/file_manager.cpython-312.pyc
Binary file not shown.
32 changes: 32 additions & 0 deletions core/undo_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,38 @@ def undo_last_action(self) -> bool:

return False

def undo_action(self, index: int) -> bool:
"""
Anulează o acțiune specifică din istoric (după index).
Necesar pentru butoanele de Undo pe fiecare rând din Activity History (US 10).

Returns:
True dacă s-a făcut undo cu succes, False altfel.
"""
if index < 0 or index >= len(self.history):
return False

action = self.history[index]
old_path = action["old_path"]
new_path = action["new_path"]

if new_path.exists():
old_path.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(new_path), str(old_path))
del self.history[index]
return True

return False

def get_history(self) -> list[dict]:
"""
Returnează istoricul acțiunilor ca o listă de dicționare (pentru UI).
"""
return [
{"old_path": str(a["old_path"]), "new_path": str(a["new_path"])}
for a in self.history
]


# O instanță globală pe care o vom folosi în restul aplicației (pentru integrare ușoară)
undo_manager = UndoManager()
Binary file modified tests/__pycache__/test_core.cpython-312-pytest-9.0.3.pyc
Binary file not shown.
18 changes: 15 additions & 3 deletions ui/app_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
from ui.tabs.scan_tab import ScanTab
from ui.tabs.rules_tab import RulesTab
from ui.tabs.quarantine_tab import QuarantineTab
from ui.tabs.history_tab import HistoryTab


class AppWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("ClutterKill - AI File Organizer")
self.resize(800, 600)
self.resize(900, 650)

# Set up the central widget and layout
central_widget = QWidget()
Expand All @@ -20,7 +21,7 @@ def __init__(self):
self.tabs = QTabWidget()
layout.addWidget(self.tabs)

# Add the ScanTab we just created
# Add the ScanTab
self.scan_tab = ScanTab()
self.tabs.addTab(self.scan_tab, "Scan")

Expand All @@ -32,4 +33,15 @@ def __init__(self):
self.quarantine_tab = QuarantineTab()
self.tabs.addTab(self.quarantine_tab, "Quarantine")

# (Future tabs like History will go here later)
# Add the HistoryTab (US 10)
self.history_tab = HistoryTab()
self.tabs.addTab(self.history_tab, "History")

# Refresh tab-urile cu date din DB/undo_manager la schimbare
self.tabs.currentChanged.connect(self._on_tab_changed)

def _on_tab_changed(self, index):
"""Refresh datele când utilizatorul schimbă tab-ul."""
current_widget = self.tabs.widget(index)
if hasattr(current_widget, "refresh"):
current_widget.refresh()
147 changes: 147 additions & 0 deletions ui/tabs/history_tab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
from pathlib import Path

from PyQt6.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
QLabel,
QPushButton,
QTableWidget,
QTableWidgetItem,
QHeaderView,
QAbstractItemView,
)
from PyQt6.QtCore import Qt, QTimer

from core.undo_manager import undo_manager


class HistoryTab(QWidget):
"""
Tab-ul Activity History (US 10):
Afișează ultimele 50 de fișiere mutate și un buton „Undo" pe fiecare rând,
astfel încât utilizatorul să readucă instantaneu un fișier la locația originală.
"""

def __init__(self):
super().__init__()
self.init_ui()
self.refresh()

def init_ui(self):
layout = QVBoxLayout(self)

# Header
header_layout = QHBoxLayout()
header_label = QLabel("Activity History")
header_label.setStyleSheet(
"font-size: 18px; font-weight: bold; color: #0078d4;"
)
header_layout.addWidget(header_label)
header_layout.addStretch()

# Buton refresh
self.btn_refresh = QPushButton("Refresh")
self.btn_refresh.clicked.connect(self.refresh)
header_layout.addWidget(self.btn_refresh)

layout.addLayout(header_layout)

# Tabel cu istoricul acțiunilor
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(
["#", "Fisier Original", "Mutat La", "Actiune"]
)
self.table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.table.verticalHeader().setVisible(False)

# Configuram coloanele
header = self.table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch)
header.setSectionResizeMode(3, QHeaderView.ResizeMode.Fixed)
self.table.setColumnWidth(0, 40)
self.table.setColumnWidth(3, 100)

layout.addWidget(self.table)

# Status label
self.status_label = QLabel("")
self.status_label.hide()
layout.addWidget(self.status_label)

# Mesaj cand nu sunt actiuni
self.empty_label = QLabel(
"Nicio actiune inregistrata.\n"
"Muta fisiere din Quarantine pentru a le vedea aici."
)
self.empty_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.empty_label.setStyleSheet("color: #7d7d7d; font-size: 14px;")
self.empty_label.hide()
layout.addWidget(self.empty_label)

def refresh(self):
"""Reincarca tabelul cu datele din undo_manager."""
history = undo_manager.get_history()
self.table.setRowCount(0)

if not history:
self.table.hide()
self.empty_label.show()
return

self.table.show()
self.empty_label.hide()
self.table.setRowCount(len(history))

for i, action in enumerate(history):
# Coloana #
idx_item = QTableWidgetItem(str(i + 1))
idx_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.table.setItem(i, 0, idx_item)

# Coloana Fisier Original (doar numele)
old_name = Path(action["old_path"]).name
old_item = QTableWidgetItem(old_name)
old_item.setToolTip(action["old_path"]) # Path complet in tooltip
self.table.setItem(i, 1, old_item)

# Coloana Mutat La (doar numele)
new_name = Path(action["new_path"]).name
new_item = QTableWidgetItem(new_name)
new_item.setToolTip(action["new_path"])
self.table.setItem(i, 2, new_item)

# Coloana Undo (buton)
btn_undo = QPushButton("Undo")
btn_undo.setObjectName("rejectButton")
btn_undo.clicked.connect(self._make_undo_handler(i))
self.table.setCellWidget(i, 3, btn_undo)

def _make_undo_handler(self, index):
"""Creeaza un handler pentru butonul Undo (captura index-ul prin closure)."""

def handler():
self._undo_action(index)

return handler

def _undo_action(self, index):
"""Anuleaza actiunea de la index-ul specificat."""
success = undo_manager.undo_action(index)

if success:
self.show_status("Undo reusit! Fisierul a fost readus.", "#4caf50")
else:
self.show_status("Nu s-a putut face undo (fisierul nu mai exista?).", "#ff5c5c")

self.refresh()

def show_status(self, text, color):
self.status_label.setText(text)
self.status_label.setStyleSheet(f"color: {color}; font-weight: bold;")
self.status_label.show()
QTimer.singleShot(3000, self.status_label.hide)
Loading
Loading