import os import json from PySide6.QtWidgets import ( QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel, QToolBar, QFileDialog, QMessageBox ) from PySide6.QtGui import (QKeySequence, QAction, QWheelEvent) from PySide6.QtCore import Qt from PySide6.QtGui import QUndoStack # Importy lokalne from widgets.drag_tree_widget import DragTreeWidget from widgets.drop_tree_widget import DropTreeWidget from widgets.common_tree_delegate import CommonTreeDelegate from utils.colors import color_item_by_title from utils.indentation import apply_indentation_to_all_columns from utils.helpers import display_number_or_dash, display_obligatory class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Drag & Drop - Dół -> Góra, Pastelowe kolory węzłów i dzieci") self.undo_stack = QUndoStack(self) # Górne drzewo: DROP (puste na starcie) self.drop_tree = DropTreeWidget(self.undo_stack) self.drop_tree.setHeaderLabels(["Punkty", "Obowiązkowe", "Nr", "Opis"]) self.configure_header(self.drop_tree) # Dolne drzewo: DRAG (załadowane z pp.json) self.drag_tree = DragTreeWidget() self.drag_tree.setHeaderLabels(["Punkty", "Obowiązkowe", "Nr", "Opis"]) self.configure_header(self.drag_tree) self.init_ui() self.resize(1100, 700) # Ładujemy ./data/pp.json -> DOLNE drzewo self.init_load_data() # <-- kluczowy fragment # Stosujemy wcięcia dla obu drzew apply_indentation_to_all_columns(self.drag_tree) apply_indentation_to_all_columns(self.drop_tree) def configure_header(self, tw): """Ustawienie parametrów nagłówka w TreeWidget.""" hd = tw.header() # Możesz tu dostosować szerokości kolumn itd. hd.setStretchLastSection(True) def init_ui(self): """Tworzenie toolbaru i layoutu głównego.""" tb = QToolBar("Toolbar", self) self.addToolBar(tb) # Undo / Redo undo_act = self.undo_stack.createUndoAction(self, "Undo") undo_act.setShortcut(QKeySequence.Undo) tb.addAction(undo_act) redo_act = self.undo_stack.createRedoAction(self, "Redo") redo_act.setShortcut(QKeySequence("Ctrl+Y")) tb.addAction(redo_act) tb.addSeparator() # Przyciski import/export dla testów (opcjonalnie) imp_d = QAction("Import Dolne", self) imp_d.triggered.connect(lambda: self.import_tree(self.drag_tree, True)) exp_d = QAction("Export Dolne", self) exp_d.triggered.connect(lambda: self.export_tree(self.drag_tree)) imp_g = QAction("Import Górne", self) imp_g.triggered.connect(lambda: self.import_tree(self.drop_tree, False)) exp_g = QAction("Export Górne", self) exp_g.triggered.connect(lambda: self.export_tree(self.drop_tree)) tb.addAction(imp_d) tb.addAction(exp_d) tb.addAction(imp_g) tb.addAction(exp_g) # Dodaj akcję Ctrl + T do rozwijania/zwijania toggle_expand_action = QAction("Toggle Expand/Collapse (Ctrl+T)", self) toggle_expand_action.setShortcut(QKeySequence("Ctrl+T")) toggle_expand_action.triggered.connect(self.drop_tree.toggle_expand_selected_items) tb.addAction(toggle_expand_action) # Dodaj akcję usuwania delete_action = QAction("Usuń zaznaczone (Backspace)", self) delete_action.setShortcut(QKeySequence(Qt.Key_Backspace)) delete_action.triggered.connect(self.drop_tree.delete_selected_items) tb.addAction(delete_action) # Ustaw kontekst skrótu tylko dla drzewa docelowego delete_action.setShortcutContext(Qt.WidgetWithChildrenShortcut) self.drop_tree.addAction(delete_action) # Layout główny layout = QVBoxLayout() layout.addWidget(QLabel("System Oceniania")) layout.addWidget(self.drop_tree) layout.addWidget(QLabel("Podstawa Programowa")) layout.addWidget(self.drag_tree) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) # ---------------------------------------------------------------- # Wczytywanie pliku pp.json do DRAG TREE (dolnego) # ---------------------------------------------------------------- # main.py (fragment) def init_load_data(self): path = os.path.join("data", "pp.json") if not os.path.isfile(path): print("Brak pliku data/pp.json. Pomijam.") return try: with open(path, "r", encoding="utf-8") as f: data = json.load(f) self.drag_tree.clear() # 1) Wymagania ogólne it_ogolne = self.create_top_item([ "-", # punkty display_obligatory(-1), # '-' "-", # nr "Wymagania ogólne" ]) self.drag_tree.addTopLevelItem(it_ogolne) if "wymagania_ogolne" in data: for req_data in data["wymagania_ogolne"]: self.add_tree_item(it_ogolne, req_data, keep_removable=True, subitems_key="wymagania") # 2) Wymagania szczegółowe it_szcz = self.create_top_item([ "-", # punkty display_obligatory(-1), # '-' "-", # nr "Wymagania szczegółowe" ]) self.drag_tree.addTopLevelItem(it_szcz) if "wymagania_szczegolowe" in data: for block in data["wymagania_szczegolowe"]: # block.get("nr", -1) => może zwrócić -1, wtedy na ekranie będzie '-' block_atryb = block.get("atrybuty", {}) punkty_val = block_atryb.get("punkty", -1) obow_val = block_atryb.get("obowiązkowe", -1) nr_val = block.get("nr", -1) opis_val = block.get("opis", "") block_item = self.create_top_item([ display_number_or_dash(punkty_val), display_obligatory(obow_val), display_number_or_dash(nr_val), opis_val ]) it_szcz.addChild(block_item) # Zakresy for zakres_type in ["zakres_podstawowy", "zakres_rozszerzony"]: zakres_data = block.get(zakres_type, {}) atrybuty = zakres_data.get("atrybuty", {}) punkty_val = atrybuty.get("punkty", -1) obow_val = atrybuty.get("obowiązkowe", -1) nr_val = zakres_data.get("nr", -1) opis_val = zakres_data.get("opis", "") zakres_item = self.create_top_item([ display_number_or_dash(punkty_val), display_obligatory(obow_val), display_number_or_dash(nr_val), opis_val ]) block_item.addChild(zakres_item) # Rekurencja dla "uczen" for uczen_data in zakres_data.get("uczen", []): self.add_tree_item(zakres_item, uczen_data, keep_removable=True) apply_indentation_to_all_columns(self.drag_tree) except Exception as e: QMessageBox.warning(self, "Błąd", f"Błąd ładowania danych: {str(e)}") def create_top_item(self, texts): """Tworzy węzeł top-level z automatycznym kolorowaniem.""" from PySide6.QtWidgets import QTreeWidgetItem it = QTreeWidgetItem(texts) # Ustaw userRole (opcjonalnie - np. flaga 'removable') it.setData(0, Qt.UserRole, True) color_item_by_title(it) return it def add_tree_item(self, parent_item, item_data, keep_removable=False, subitems_key="podpunkty"): from PySide6.QtWidgets import QTreeWidgetItem from utils.colors import color_item_by_title from utils.helpers import display_number_or_dash, display_obligatory opis = item_data.get("opis", "") atrybuty = item_data.get("atrybuty", {}) nr_val = item_data.get("nr", -1) punkty_val = atrybuty.get("punkty", -1) obow_val = atrybuty.get("obowiązkowe", 0) nr_str = display_number_or_dash(nr_val) punkty_str = display_number_or_dash(punkty_val) obow_str = display_obligatory(obow_val) it = QTreeWidgetItem([punkty_str, obow_str, nr_str, opis]) if keep_removable: it.setData(0, Qt.UserRole, True) color_item_by_title(it) parent_item.addChild(it) # Obsługa podpunktów for podpunkt in item_data.get(subitems_key, []): self.add_tree_item(it, podpunkt, keep_removable, subitems_key) # ---------------------------------------------------------------- # Funkcje import/export (dla testów) # ---------------------------------------------------------------- def import_tree(self, tree, keep_drag_flag=False): fn, _ = QFileDialog.getOpenFileName(self, "Import JSON", "./data", "JSON Files (*.json)") if not fn: return try: with open(fn, "r", encoding="utf-8") as f: data = json.load(f) self.json_to_tree(data, tree, keep_drag_flag) QMessageBox.information(self, "Import", f"Wczytano: {fn}") except Exception as e: QMessageBox.warning(self, "Błąd importu", str(e)) def export_tree(self, tree): fn, _ = QFileDialog.getSaveFileName(self, "Export JSON", "./data", "JSON Files (*.json)") if not fn: return data = self.tree_to_json(tree) try: with open(fn, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) QMessageBox.information(self, "Export", f"Zapisano: {fn}") except Exception as e: QMessageBox.warning(self, "Błąd zapisu", str(e)) def tree_to_json(self, tree): def item_to_dict(it): node = { "punkty": it.text(0).strip(), "obowiązkowe": it.text(1).strip(), "nr": it.text(2).strip(), "opis": it.text(3).strip(), "children": [] } for i in range(it.childCount()): node["children"].append(item_to_dict(it.child(i))) return node data = {"items": []} for i in range(tree.topLevelItemCount()): top_item = tree.topLevelItem(i) data["items"].append(item_to_dict(top_item)) return data def json_to_tree(self, data: dict, tree, keep_drag_flag=False): tree.clear() def dict_to_item(nd): from PySide6.QtWidgets import QTreeWidgetItem p_s = nd.get("punkty", "0").strip() o_s = nd.get("obowiązkowe", "nie").strip() nr_s = nd.get("nr", "").strip() op_s = nd.get("opis", "").strip() it = QTreeWidgetItem([p_s, o_s, nr_s, op_s]) if keep_drag_flag: it.setData(0, Qt.UserRole, True) color_item_by_title(it) for ch_d in nd.get("children", []): ch_it = dict_to_item(ch_d) it.addChild(ch_it) return it for nd in data.get("items", []): t_it = dict_to_item(nd) tree.addTopLevelItem(t_it) apply_indentation_to_all_columns(tree) # ---------------------------------------------------------------- # Zoom (Ctrl + scroll) # ---------------------------------------------------------------- def wheelEvent(self, event: QWheelEvent): if event.modifiers() & Qt.ControlModifier: delta = event.angleDelta().y() step = 1 if delta > 0 else -1 self.change_font_size(step) event.accept() else: super().wheelEvent(event) def change_font_size(self, delta): font = self.drop_tree.font() sz = font.pointSize() + delta sz = max(6, sz) font.setPointSize(sz) self.drop_tree.setFont(font) self.drag_tree.setFont(font) if __name__ == "__main__": app = QApplication([]) w = MainWindow() w.show() app.exec()