so-gui/main.py
baiobelfer 2017e7a4c5 init
2025-02-01 18:16:23 +01:00

333 lines
12 KiB
Python

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()