Refactor GUI to use Multi Document Interface

This commit is contained in:
Thomas Peetz
2025-01-21 20:12:50 +01:00
parent 4e884fdbe5
commit e8660faa06
5 changed files with 122 additions and 68 deletions
+20 -13
View File
@@ -1,42 +1,49 @@
from PySide6.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QTabWidget, QMenu, QTableView from PySide6.QtCore import Signal
from PySide6.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QTabWidget, QMenu, QTableView, QMdiSubWindow
from gui.model_config import KontorModelConfig from gui.model_config import KontorModelConfig
from gui.table_model import KontorTableModel from gui.table_model import KontorTableModel
class ComicWindow(QWidget): class ComicWindow(QMdiSubWindow):
closed = Signal()
def __init__(self, main_window): def __init__(self, main_window):
super().__init__() super().__init__()
self.statusBar = main_window.statusBar
self._main_window = main_window
self.data_views = list() self.data_views = list()
# self._create_menubar() self._main_window = main_window
self.log = main_window.log
self._init_gui() self._init_gui()
self.tick = main_window.tick
self.cross = main_window.cross
def _init_gui(self): def _init_gui(self):
self.central_widget = QWidget() self.setWindowTitle("Comics")
self.setWidget(QWidget())
layout = QVBoxLayout() layout = QVBoxLayout()
#self.central_widget.setLayout(parent_layout)
self.tabs = QTabWidget() self.tabs = QTabWidget()
self.tabs.addTab(self.generate_data_tab("comic"), "Comics") self.tabs.addTab(self.generate_data_tab("comic"), "Comics")
self.tabs.addTab(self.generate_data_tab("publisher"), "Publisher") self.tabs.addTab(self.generate_data_tab("publisher"), "Publisher")
self.tabs.currentChanged.connect(self._tab_changed) self.tabs.currentChanged.connect(self._tab_changed)
# label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self.tabs) layout.addWidget(self.tabs)
self.setLayout(layout) self.setLayout(layout)
# self.setCentralWidget(self.central_widget) self.setWidget(self.tabs)
# def _create_menubar(self): def closeEvent(self, event):
# menu_bar = self.menuBar() self.closed.emit()
# comic_menu = QMenu("Comic") super().closeEvent(event)
# menu_bar.addMenu(comic_menu) self._main_window.remove_sub_window('comic')
def refresh(self): def refresh(self):
# self.log.info("refresh")
self.data_views[self.tabs.currentIndex()].refresh() self.data_views[self.tabs.currentIndex()].refresh()
def _tab_changed(self, tab_index): def _tab_changed(self, tab_index):
self.data_views[tab_index].refresh() self.data_views[tab_index].refresh()
def update_status(self, message):
self._main_window.update_status(message)
def generate_data_tab(self, table_name): def generate_data_tab(self, table_name):
data_tab = QWidget() data_tab = QWidget()
+57 -26
View File
@@ -1,5 +1,5 @@
from PySide6.QtGui import QAction, QIcon, QGuiApplication from PySide6.QtGui import QAction, QIcon, QGuiApplication
from PySide6.QtWidgets import QWidget, QVBoxLayout, QMenu, QMessageBox, QTabWidget, QTableView, QProgressBar from PySide6.QtWidgets import QWidget, QVBoxLayout, QMenu, QMessageBox, QTabWidget, QTableView, QProgressBar, QMdiArea
from PySide6.QtWidgets import QLabel, QMainWindow from PySide6.QtWidgets import QLabel, QMainWindow
from sqlalchemy import Engine from sqlalchemy import Engine
from kontor_schema import KontorDB from kontor_schema import KontorDB
@@ -22,32 +22,39 @@ class MainWindow(QMainWindow):
self.export_icon = QIcon("res/application-export.png") self.export_icon = QIcon("res/application-export.png")
self.circle_icon = QIcon("res/arrow-circle-double.png") self.circle_icon = QIcon("res/arrow-circle-double.png")
self.data = []
self.filter = {}
self.kontor_db = KontorDB(engine, log)
self.log = log
self._subwindows = {}
self._setup_ui()
#self.tabs = QTabWidget()
#self.tabs.addTab(self.generate_data_tab("comic"), "Comics")
#self.tabs.addTab(self.generate_data_tab("media_file"), "MediaFile")
#self.tabs.currentChanged.connect(self._tab_changed)
#label.setAlignment(Qt.AlignmentFlag.AlignCenter)
#parent_layout.addWidget(self.tabs)
self.setCentralWidget(self.central_widget)
def _setup_ui(self):
self.setWindowTitle("Kontor") self.setWindowTitle("Kontor")
self.setMinimumSize(800, 500) self.setMinimumSize(1200, 800)
self._create_actions() self._create_actions()
self.central_widget = QWidget()
# parent_layout = QVBoxLayout()
# self.central_widget.setLayout(parent_layout)
self.mdi_area = QMdiArea(self.central_widget)
self.mdi_area.setObjectName('mdi_area')
self.setCentralWidget(self.central_widget)
self._create_menubar() self._create_menubar()
self._create_toolbars() self._create_toolbars()
self.status_progress = QProgressBar() self.status_progress = QProgressBar()
self.progress_update = ProgressUpdate(self.status_progress) self.progress_update = ProgressUpdate(self.status_progress)
self._create_statusbar() self._create_statusbar()
self.data = []
self.filter = {}
self.kontor_db = KontorDB(engine, log)
self.log = log
self.comic_window = ComicWindow(self)
self.central_widget = QWidget()
parent_layout = QVBoxLayout()
self.central_widget.setLayout(parent_layout)
self.tabs = QTabWidget()
self.tabs.addTab(self.generate_data_tab("comic"), "Comics")
self.tabs.addTab(self.generate_data_tab("media_file"), "MediaFile")
self.tabs.currentChanged.connect(self._tab_changed)
#label.setAlignment(Qt.AlignmentFlag.AlignCenter)
parent_layout.addWidget(self.tabs)
self.setCentralWidget(self.central_widget)
centerPoint = QGuiApplication.screens()[0].geometry().center() centerPoint = QGuiApplication.screens()[0].geometry().center()
self.move(centerPoint - self.frameGeometry().center()) self.move(centerPoint - self.frameGeometry().center())
@@ -117,10 +124,26 @@ class MainWindow(QMainWindow):
QMessageBox.about(self.central_widget, "Über Kontor", f"Python: 3.11\nKontor: 0.1.0") QMessageBox.about(self.central_widget, "Über Kontor", f"Python: 3.11\nKontor: 0.1.0")
def show_comic_window(self): def show_comic_window(self):
if self.comic_window.isHidden(): if 'comic' not in self._subwindows:
self.comic_window.show() comic = ComicWindow(self)
comic.closed.connect(self.sub_window_closed)
self._subwindows['comic'] = comic
self.mdi_area.addSubWindow(comic)
comic.show()
else: else:
self.comic_window.hide() comic = self._subwindows.pop('comic')
comic.close()
self.mdi_area.removeSubWindow(comic)
def remove_sub_window(self, name: str):
# self.log.info("remove subwindow %s", name)
if name in self._subwindows:
window = self._subwindows.pop(name)
window.close()
self.mdi_area.removeSubWindow(window)
def sub_window_closed(self):
self.log.info("close subwindow")
def import_from_file(self): def import_from_file(self):
import_dlg = ImportKontorDialog(self) import_dlg = ImportKontorDialog(self)
@@ -162,10 +185,17 @@ class MainWindow(QMainWindow):
self.kontor_db.check_files() self.kontor_db.check_files()
def refresh(self): def refresh(self):
self.data[self.tabs.currentIndex()].refresh() self.log.info("refresh")
for (_, window) in self._subwindows.items():
window.refresh()
# def refresh(self):
# self.data[self.tabs.currentIndex()].refresh()
def _tab_changed(self, tab_index): # def _tab_changed(self, tab_index):
self.data[tab_index].refresh() # self.data[tab_index].refresh()
def update_status(self, message, timeout=3000):
self.statusBar.showMessage(message, timeout=timeout)
def generate_data_tab(self, table_name): def generate_data_tab(self, table_name):
data_tab = QWidget() data_tab = QWidget()
@@ -181,3 +211,4 @@ class MainWindow(QMainWindow):
layout.addWidget(table_view) layout.addWidget(table_view)
model.refresh() model.refresh()
return data_tab return data_tab
+14 -6
View File
@@ -1,4 +1,4 @@
from PySide6.QtWidgets import QHBoxLayout, QCheckBox from PySide6.QtWidgets import QHBoxLayout, QCheckBox, QMdiSubWindow
from kontor_schema import KontorDB from kontor_schema import KontorDB
@@ -8,15 +8,23 @@ class KontorModelConfig:
self.header = {} self.header = {}
self.filter = {} self.filter = {}
self.main_window = main_window self.main_window = main_window
self.log = main_window.log
self._table_name = table_name self._table_name = table_name
self.kontor_db = kontor_db self.kontor_db = kontor_db
self.get_table_config() self.get_table_config()
def __str__(self):
return f"KontorModelConfig({self._table_name})"
def get_table_config(self): def get_table_config(self):
# self.log.info("get_table_config %s", self)
self.header = self.kontor_db.get_column_meta_data(self._table_name) self.header = self.kontor_db.get_column_meta_data(self._table_name)
self.filter = self.kontor_db.get_filters(self._table_name) self.filter = self.kontor_db.get_filters(self._table_name)
# self.log.info("headers: %s", self.header)
# self.log.info("%s filters: %s", self, self.filter)
def filters(self) -> dict: def filters(self) -> dict:
# self.log.info("%s filters: %s", self, self.filter)
_filters = {} _filters = {}
# print(self.filter["download"].isChecked()) # print(self.filter["download"].isChecked())
for column, filter_info in self.filter.items(): for column, filter_info in self.filter.items():
@@ -24,18 +32,17 @@ class KontorModelConfig:
if filter_info['widget'].isChecked(): if filter_info['widget'].isChecked():
_filters[column] = True _filters[column] = True
# print(f"{filter_rule=}") # print(f"{filter_rule=}")
# self.log.info("filters -> %s", _filters)
return _filters return _filters
def get_data(self) -> list: def get_data(self) -> list:
# data = self.kontor_db.get_data(self._table_name, self.header, self.get_filter()) # self.log.info("get_data")
# data.clear()
data = self.kontor_db.data(self._table_name, self.header, self.filters()) data = self.kontor_db.data(self._table_name, self.header, self.filters())
# print(f"KontorModelConfig.get_data: {len(data)}") # self.log.info("get_data: %d %s", len(data), data)
# comics = self.kontor_db.session.query(Comic).all()
# print(f'{len(comics)} Comics loaded')
return data return data
def get_filter_layout(self) -> QHBoxLayout: def get_filter_layout(self) -> QHBoxLayout:
# self.log.info("get_filter_layout: %s", self.filter)
filter_layout = QHBoxLayout() filter_layout = QHBoxLayout()
for column, filter_info in self.filter.items(): for column, filter_info in self.filter.items():
filter_checkbox = QCheckBox() filter_checkbox = QCheckBox()
@@ -44,4 +51,5 @@ class KontorModelConfig:
self.filter[column]['widget'] = filter_checkbox self.filter[column]['widget'] = filter_checkbox
filter_layout.addWidget(filter_checkbox) filter_layout.addWidget(filter_checkbox)
filter_layout.addStretch() filter_layout.addStretch()
# self.log.info("get_filter_layout: %s", self.filter)
return filter_layout return filter_layout
+23 -19
View File
@@ -13,8 +13,13 @@ class KontorTableModel(QAbstractTableModel):
self._main_window = model_config.main_window self._main_window = model_config.main_window
self._config = model_config self._config = model_config
self._data = [] self._data = []
self.log = model_config.log
def __str__(self):
return f"KontorTableModel({self._config})"
def refresh(self): def refresh(self):
# self.log.info("refresh")
data = self._config.get_data() data = self._config.get_data()
count = 0 count = 0
# print(data) # print(data)
@@ -27,9 +32,10 @@ class KontorTableModel(QAbstractTableModel):
# print(data) # print(data)
# print(self._data) # print(self._data)
self.layoutChanged.emit() self.layoutChanged.emit()
self._main_window.statusBar.showMessage(f"{count} Einträge geladen", 3000) self._main_window.update_status(f"{count} Einträge geladen")
def rowCount(self, parent=QModelIndex()): def rowCount(self, parent=QModelIndex()):
# self.log.info("rowCount %s: %d", self, len(self._data))
# The length of the outer list. # The length of the outer list.
if self._data is None: if self._data is None:
return 0 return 0
@@ -53,53 +59,51 @@ class KontorTableModel(QAbstractTableModel):
return value return value
if isinstance(value, bytes): if isinstance(value, bytes):
if value == b'\x01': if value == b'\x01':
return self._main_window.tick return self._config.main_window.tick
else: else:
return self._main_window.cross return self._config.main_window.cross
if isinstance(value, int): if isinstance(value, int):
# print('{}:: {}: {}'.format(index, value, type(value))) # print('{}:: {}: {}'.format(index, value, type(value)))
if value == 1: if value == 1:
return self._main_window.tick return self._config.main_window.tick
else: else:
return self._main_window.cross return self._config.main_window.cross
if isinstance(value, bool): if isinstance(value, bool):
if value: if value:
return self._main_window.tick return self._config.main_window.tick
else: else:
return self._main_window.cross return self._config.main_window.cross
return str(value) return str(value)
if role == Qt.ItemDataRole.DecorationRole: if role == Qt.ItemDataRole.DecorationRole:
if isinstance(value, bytes): if isinstance(value, bytes):
if value == b'\x01': if value == b'\x01':
return self._main_window.tick return self._config.main_window.tick
else: else:
return self._main_window.cross return self._config.main_window.cross
if isinstance(value, int): if isinstance(value, int):
if value == 1: if value == 1:
return self._main_window.tick return self._config.main_window.tick
else: else:
return self._main_window.cross return self._config.main_window.cross
if isinstance(value, bool): if isinstance(value, bool):
if value: if value:
return self._main_window.tick return self._config.main_window.tick
else: else:
return self._main_window.cross return self._config.main_window.cross
def columnCount(self, index=QModelIndex()): def columnCount(self, index=QModelIndex()):
# The following takes the first sub-list, and returns # self.log.info("rowCount %s: %d", self, len(self._config.header))
# the length (only works if all rows are an equal length)
# print(f"Header count: {len(self._config.get_header())}")
return len(self._config.header) return len(self._config.header)
def setData(self, index, value, role: int) -> bool: def setData(self, index, value, role: int) -> bool:
print(index, role) # print(index, role)
if role == Qt.ItemDataRole.EditRole: if role == Qt.ItemDataRole.EditRole:
self._data[index.row()][index.column()] = value self._data[index.row()][index.column()] = value
print(self._data[index.row()][index.column()]) # print(self._data[index.row()][index.column()])
self.dataChanged.emit(index, index) self.dataChanged.emit(index, index)
return True return True
if role == Qt.ItemDataRole.CheckStateRole: if role == Qt.ItemDataRole.CheckStateRole:
print("role == Qt.ItemDataRole.CheckStateRole") # print("role == Qt.ItemDataRole.CheckStateRole")
checked = value == Qt.CheckState.Checked checked = value == Qt.CheckState.Checked
self._data[index.row()][index.column()] = checked self._data[index.row()][index.column()] = checked
return False return False
@@ -83,6 +83,7 @@ class KontorDB:
filter(MetaDataTable.id == MetaDataColumn.table_id). filter(MetaDataTable.id == MetaDataColumn.table_id).
filter(MetaDataTable.table_name == table_name). filter(MetaDataTable.table_name == table_name).
filter(MetaDataColumn.is_shown == 1).all()): filter(MetaDataColumn.is_shown == 1).all()):
# self.log.info("get_column_meta_data: %s %s %d", column.column_name, column.column_label, column.column_order)
meta_data[order] = {'column': column.column_name, 'label': column.column_label, meta_data[order] = {'column': column.column_name, 'label': column.column_label,
'order': column.column_order, 'ref_column': column.ref_column} 'order': column.column_order, 'ref_column': column.ref_column}
order += 1 order += 1
@@ -96,6 +97,7 @@ class KontorDB:
'ref_column': column.ref_column 'ref_column': column.ref_column
} }
order += 1 order += 1
# self.log.info("get_column_meta_data: %s", meta_data)
return meta_data return meta_data
def get_columns(self, table_name: str) -> dict: def get_columns(self, table_name: str) -> dict:
@@ -131,6 +133,7 @@ class KontorDB:
else: else:
entries = session.scalars(select(table).filter_by(**filters)).all() entries = session.scalars(select(table).filter_by(**filters)).all()
for entry in entries: for entry in entries:
# self.log.info("data: %s", entry)
row = [] row = []
for order in columns.keys(): for order in columns.keys():
column_name = columns[order]['column'] column_name = columns[order]['column']
@@ -142,6 +145,7 @@ class KontorDB:
else: else:
row.append(getattr(entry, column_name)) row.append(getattr(entry, column_name))
data.append(row) data.append(row)
# self.log.info("data: %s", data)
return data return data
def export_db(self, export_type: str, export_file_name: str) -> dict: def export_db(self, export_type: str, export_file_name: str) -> dict:
@@ -259,7 +263,7 @@ class KontorDB:
return existing_ids return existing_ids
def add_entry(self, table_name: str, update_item: dict): def add_entry(self, table_name: str, update_item: dict):
self.log.info("add entry to table %s with %s", table_name, update_item) # self.log.info("add entry to table %s with %s", table_name, update_item)
__session__ = sessionmaker(self.engine) __session__ = sessionmaker(self.engine)
with __session__() as session: with __session__() as session:
add_item = self.registry[table_name]() add_item = self.registry[table_name]()
@@ -270,7 +274,7 @@ class KontorDB:
session.commit() session.commit()
def update_entry(self, table_name, current_id, update_item: dict) -> bool: def update_entry(self, table_name, current_id, update_item: dict) -> bool:
self.log.info("update entry to table %s", table_name) # self.log.info("update entry to table %s", table_name)
__session__ = sessionmaker(self.engine) __session__ = sessionmaker(self.engine)
with __session__() as session: with __session__() as session:
existing_item = session.query(self.registry[table_name]).get(current_id) existing_item = session.query(self.registry[table_name]).get(current_id)
@@ -335,7 +339,7 @@ class KontorDB:
def delete_entries(self): def delete_entries(self):
for (table_name, table) in self.registry.items(): for (table_name, table) in self.registry.items():
self.log.info("delete entries from table %s", table_name) # self.log.info("delete entries from table %s", table_name)
__session__ = sessionmaker(self.engine) __session__ = sessionmaker(self.engine)
with __session__() as session: with __session__() as session:
items = session.query(table).all() items = session.query(table).all()