summaryrefslogtreecommitdiff
path: root/calendar_gui.py
diff options
context:
space:
mode:
authormatin <matin.kaufmann@gmail.com>2025-09-30 15:38:15 +0200
committermatin <matin.kaufmann@gmail.com>2025-09-30 15:38:15 +0200
commitb3892bb47dd30d7ab0bbf5bc12de2539ee2ffa03 (patch)
tree904e8cc3404e46bfcff414371ace0b5650586a48 /calendar_gui.py
parent3054eb8c0de880655a3a01bf4c2da154e6c3ab42 (diff)
- Tabelle ist editierbar
- Kommentarfeld macht Zeilenumbrüche - im Editier-Dialog wird das Startdatum nun auch automatisch verstellt, wenn das enddatum derart geändert wird, dass ein ungültiger Zeitraum entstehen würde
Diffstat (limited to 'calendar_gui.py')
-rw-r--r--calendar_gui.py202
1 files changed, 159 insertions, 43 deletions
diff --git a/calendar_gui.py b/calendar_gui.py
index 8863b8e..5fadd73 100644
--- a/calendar_gui.py
+++ b/calendar_gui.py
@@ -2,7 +2,8 @@ import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QDateEdit, QTableWidget,
QTableWidgetItem, QHeaderView, QDialog, QFormLayout, QComboBox,
- QMessageBox, QSpinBox, QAction, QFileDialog, QMenuBar, QTextEdit)
+ QMessageBox, QSpinBox, QAction, QFileDialog, QMenuBar, QTextEdit,
+ QAbstractItemView)
from PyQt5.QtCore import Qt, QDate, QLocale
from PyQt5.QtGui import QFont, QFontDatabase
from datetime import datetime, timedelta
@@ -112,6 +113,9 @@ class EventDialog(QDialog):
self.setLayout(layout)
+ # Connect end date change to validate start date (keep range consistent both ways)
+ self.end_date.dateChanged.connect(self.validate_start_date)
+
# Handle initial keyword selection
self.on_keyword_changed(self.keyword.currentText())
@@ -139,6 +143,11 @@ class EventDialog(QDialog):
"""Ensure end date is not before start date"""
if self.end_date.date() < self.start_date.date():
self.end_date.setDate(self.start_date.date())
+
+ def validate_start_date(self):
+ """Ensure start date is not after end date when end changes"""
+ if self.start_date.date() > self.end_date.date():
+ self.start_date.setDate(self.end_date.date())
def get_data(self):
start_date = DateService.format_date_for_iso(self.start_date.date())
keyword = self.keyword.currentText()
@@ -282,6 +291,15 @@ class CalendarManagerGUI(QMainWindow):
self.events_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.events_table.horizontalHeader().setFont(self.app_font)
self.events_table.setColumnHidden(0, True) # Hide ID column
+ # Wrap long text and auto-resize rows so comments are readable
+ self.events_table.setWordWrap(True)
+ self.events_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
+ # Enable inline editing on double click / Enter; keep computed column read-only in population step
+ self._suppress_item_changed = False
+ self.events_table.setEditTriggers(
+ QAbstractItemView.DoubleClicked | QAbstractItemView.SelectedClicked | QAbstractItemView.EditKeyPressed
+ )
+ self.events_table.itemChanged.connect(self.on_events_item_changed)
events_layout.addWidget(self.events_table)
main_layout.addLayout(events_layout)
@@ -363,50 +381,148 @@ class CalendarManagerGUI(QMainWindow):
def update_events_table(self):
# Clear table
- self.events_table.setRowCount(0)
+ self._suppress_item_changed = True
+ try:
+ self.events_table.setRowCount(0)
- # Add entries to table
- entries = self.calendar_manager.list_entries()
- for i, entry in enumerate(entries):
- self.events_table.insertRow(i)
-
- # Set item data
- self.events_table.setItem(i, 0, QTableWidgetItem(entry.id))
-
- # Format dates using unified display format
- start_date_text = DateService.format_date_for_display(entry.start_date, self.dateformat)
- end_date_text = DateService.format_date_for_display(entry.end_date, self.dateformat)
- self.events_table.setItem(i, 1, QTableWidgetItem(start_date_text))
- self.events_table.setItem(i, 2, QTableWidgetItem(end_date_text))
- self.events_table.setItem(i, 3, QTableWidgetItem(entry.keyword))
-
- # Relevant accounted time from stored time_period
- relevant_text = getattr(entry, 'time_period', "") or ""
- self.events_table.setItem(i, 4, QTableWidgetItem(relevant_text))
+ # Add entries to table
+ entries = self.calendar_manager.list_entries()
+ for i, entry in enumerate(entries):
+ self.events_table.insertRow(i)
+
+ # Set item data
+ self.events_table.setItem(i, 0, QTableWidgetItem(entry.id))
+
+ # Format dates using unified display format
+ start_date_text = DateService.format_date_for_display(entry.start_date, self.dateformat)
+ end_date_text = DateService.format_date_for_display(entry.end_date, self.dateformat)
+ self.events_table.setItem(i, 1, QTableWidgetItem(start_date_text))
+ self.events_table.setItem(i, 2, QTableWidgetItem(end_date_text))
+ # Keyword as dropdown (combobox) for safer selection
+ keyword_combo = QComboBox()
+ keyword_combo.setFont(self.app_font)
+ keyword_combo.addItems(EventConfig.KEYWORDS)
+ if entry.keyword in EventConfig.KEYWORDS:
+ keyword_combo.setCurrentIndex(EventConfig.KEYWORDS.index(entry.keyword))
+ keyword_combo.currentTextChanged.connect(lambda new_kw, eid=entry.id: self.on_keyword_changed_in_table(eid, new_kw))
+ self.events_table.setCellWidget(i, 3, keyword_combo)
+
+ # Relevant accounted time from stored time_period
+ relevant_text = getattr(entry, 'time_period', "") or ""
+ relevant_item = QTableWidgetItem(relevant_text)
+ # Make computed column read-only
+ relevant_item.setFlags(relevant_item.flags() & ~Qt.ItemIsEditable)
+ self.events_table.setItem(i, 4, relevant_item)
- # Commentary
- commentary_text = getattr(entry, 'commentary', "") or ""
- self.events_table.setItem(i, 5, QTableWidgetItem(commentary_text))
-
- # Action buttons
- actions_widget = QWidget()
- actions_layout = QHBoxLayout()
- actions_layout.setContentsMargins(0, 0, 0, 0)
-
- modify_button = QPushButton("Editieren")
- modify_button.setFont(self.app_font)
- delete_button = QPushButton("Löschen")
- delete_button.setFont(self.app_font)
-
- # Use lambda with default argument to capture the correct event_id
- modify_button.clicked.connect(lambda checked, eid=entry.id: self.modify_event(eid))
- delete_button.clicked.connect(lambda checked, eid=entry.id: self.delete_event(eid))
-
- actions_layout.addWidget(modify_button)
- actions_layout.addWidget(delete_button)
- actions_widget.setLayout(actions_layout)
-
- self.events_table.setCellWidget(i, 6, actions_widget)
+ # Commentary
+ commentary_text = getattr(entry, 'commentary', "") or ""
+ commentary_item = QTableWidgetItem(commentary_text)
+ self.events_table.setItem(i, 5, commentary_item)
+
+ # Action buttons
+ actions_widget = QWidget()
+ actions_layout = QHBoxLayout()
+ actions_layout.setContentsMargins(0, 0, 0, 0)
+
+ modify_button = QPushButton("Editieren")
+ modify_button.setFont(self.app_font)
+ delete_button = QPushButton("Löschen")
+ delete_button.setFont(self.app_font)
+
+ # Use lambda with default argument to capture the correct event_id
+ modify_button.clicked.connect(lambda checked, eid=entry.id: self.modify_event(eid))
+ delete_button.clicked.connect(lambda checked, eid=entry.id: self.delete_event(eid))
+
+ actions_layout.addWidget(modify_button)
+ actions_layout.addWidget(delete_button)
+ actions_widget.setLayout(actions_layout)
+
+ self.events_table.setCellWidget(i, 6, actions_widget)
+ # Adjust row heights after population to show wrapped text
+ self.events_table.resizeRowsToContents()
+ finally:
+ self._suppress_item_changed = False
+
+ def on_events_item_changed(self, item):
+ # Avoid reacting to programmatic changes
+ if self._suppress_item_changed:
+ return
+ row = item.row()
+ col = item.column()
+ # Ignore non-editable/computed/action columns
+ if col in (0, 4, 6):
+ return
+
+ # Retrieve entry id
+ id_item = self.events_table.item(row, 0)
+ if id_item is None:
+ return
+ event_id = id_item.text()
+
+ # Collect current row values
+ start_text = self.events_table.item(row, 1).text() if self.events_table.item(row, 1) else ""
+ end_text = self.events_table.item(row, 2).text() if self.events_table.item(row, 2) else ""
+ # Keyword comes from the combobox cell widget
+ keyword_widget = self.events_table.cellWidget(row, 3)
+ keyword_text = keyword_widget.currentText() if isinstance(keyword_widget, QComboBox) else (self.events_table.item(row, 3).text() if self.events_table.item(row, 3) else "")
+ commentary_text = self.events_table.item(row, 5).text() if self.events_table.item(row, 5) else ""
+
+ # Normalize to ISO strings for backend
+ try:
+ start_iso = DateService.format_date_for_iso(DateService.parse_date_from_string(start_text)) if start_text else None
+ end_iso = DateService.format_date_for_iso(DateService.parse_date_from_string(end_text)) if end_text else None
+ except Exception as e:
+ QMessageBox.warning(self, "Ungültiges Datum", f"Bitte ein gültiges Datum eingeben.\nFehler: {str(e)}")
+ # Revert table to backend values
+ self.update_events_table()
+ return
+
+ # Validate before applying
+ errors = []
+ if start_iso and end_iso:
+ errors = self.calendar_manager.validate_entry_data(start_iso, end_iso, keyword_text)
+ if errors:
+ QMessageBox.warning(self, "Validierungsfehler", "\n".join(errors))
+ self.update_events_table()
+ return
+
+ # Apply modification
+ modified = self.calendar_manager.modify_entry(
+ event_id,
+ start_date=start_iso if start_iso else None,
+ end_date=end_iso if end_iso else None,
+ keyword=keyword_text if keyword_text else None,
+ commentary=commentary_text if commentary_text is not None else None,
+ )
+ if modified is None:
+ QMessageBox.critical(self, "Fehler", "Eintrag konnte nicht aktualisiert werden.")
+ self.update_events_table()
+ return
+
+ # Refresh to reflect normalized display and recomputed values
+ self.update_events_table()
+ self.update_prediction()
+
+ def on_keyword_changed_in_table(self, event_id, new_keyword):
+ # Validate using current start/end from the entry
+ entry = self.calendar_manager.get_entry_by_id(event_id)
+ if not entry:
+ return
+ start_iso = DateService.format_date_for_iso(entry.start_date)
+ end_iso = DateService.format_date_for_iso(entry.end_date)
+ errors = self.calendar_manager.validate_entry_data(start_iso, end_iso, new_keyword)
+ if errors:
+ QMessageBox.warning(self, "Validierungsfehler", "\n".join(errors))
+ # Re-render to revert combo to valid value
+ self.update_events_table()
+ return
+ modified = self.calendar_manager.modify_entry(event_id, keyword=new_keyword)
+ if modified is None:
+ QMessageBox.critical(self, "Fehler", "Eintrag konnte nicht aktualisiert werden.")
+ self.update_events_table()
+ return
+ self.update_events_table()
+ self.update_prediction()
def save_file(self):
"""Save the complete prediction state to a .prediction.json file"""