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) from PyQt5.QtCore import Qt, QDate from PyQt5.QtGui import QFont from datetime import datetime, timedelta from calendar_manager import CalendarManager from date_calculator import DateCalculator from prediction_controller import PredictionController class AddEventDialog(QDialog): def __init__(self, keyword_list, parent=None): super().__init__(parent) self.setWindowTitle("Add New Event") self.keyword_list = keyword_list self.init_ui() def init_ui(self): layout = QFormLayout() # Start date selector self.start_date = QDateEdit() self.start_date.setCalendarPopup(True) self.start_date.setDate(QDate.currentDate()) layout.addRow("Start Date:", self.start_date) # End date selector self.end_date = QDateEdit() self.end_date.setCalendarPopup(True) self.end_date.setDate(QDate.currentDate().addDays(1)) layout.addRow("End Date:", self.end_date) # Keyword selector self.keyword = QComboBox() self.keyword.addItems(self.keyword_list) layout.addRow("Keyword:", self.keyword) # Buttons button_layout = QHBoxLayout() self.save_button = QPushButton("Save") self.save_button.clicked.connect(self.accept) self.cancel_button = QPushButton("Cancel") self.cancel_button.clicked.connect(self.reject) button_layout.addWidget(self.save_button) button_layout.addWidget(self.cancel_button) layout.addRow("", button_layout) self.setLayout(layout) def get_data(self): start_date = self.start_date.date().toString("yyyy-MM-dd") end_date = self.end_date.date().toString("yyyy-MM-dd") keyword = self.keyword.currentText() return start_date, end_date, keyword class ModifyEventDialog(QDialog): def __init__(self, entry, keyword_list, parent=None): super().__init__(parent) self.setWindowTitle("Modify Event") self.entry = entry self.keyword_list = keyword_list self.init_ui() def init_ui(self): layout = QFormLayout() # Start date selector self.start_date = QDateEdit() self.start_date.setCalendarPopup(True) entry_start = QDate(self.entry.start_date.year, self.entry.start_date.month, self.entry.start_date.day) self.start_date.setDate(entry_start) layout.addRow("Start Date:", self.start_date) # End date selector self.end_date = QDateEdit() self.end_date.setCalendarPopup(True) entry_end = QDate(self.entry.end_date.year, self.entry.end_date.month, self.entry.end_date.day) self.end_date.setDate(entry_end) layout.addRow("End Date:", self.end_date) # Keyword selector self.keyword = QComboBox() self.keyword.addItems(self.keyword_list) current_index = self.keyword_list.index(self.entry.keyword) if self.entry.keyword in self.keyword_list else 0 self.keyword.setCurrentIndex(current_index) layout.addRow("Keyword:", self.keyword) # Buttons button_layout = QHBoxLayout() self.save_button = QPushButton("Save") self.save_button.clicked.connect(self.accept) self.cancel_button = QPushButton("Cancel") self.cancel_button.clicked.connect(self.reject) button_layout.addWidget(self.save_button) button_layout.addWidget(self.cancel_button) layout.addRow("", button_layout) self.setLayout(layout) def get_data(self): start_date = self.start_date.date().toString("yyyy-MM-dd") end_date = self.end_date.date().toString("yyyy-MM-dd") keyword = self.keyword.currentText() return start_date, end_date, keyword class CalendarManagerGUI(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Calendar Manager") self.setMinimumSize(800, 600) # Initialize backend components self.keyword_list = ["full_project", "half_project", "event"] self.calendar_manager = CalendarManager() self.date_calculator = DateCalculator() self.prediction_controller = PredictionController( self.calendar_manager, self.date_calculator, self.keyword_list ) self.init_ui() self.create_menus() def create_menus(self): # Create menu bar menubar = self.menuBar() # File menu file_menu = menubar.addMenu('File') # Save action save_action = QAction('Save', self) save_action.setShortcut('Ctrl+S') save_action.triggered.connect(self.save_file) file_menu.addAction(save_action) # Load action load_action = QAction('Load', self) load_action.setShortcut('Ctrl+O') load_action.triggered.connect(self.load_file) file_menu.addAction(load_action) # Clear action clear_action = QAction('Clear', self) clear_action.setShortcut('Ctrl+N') clear_action.triggered.connect(self.clear_entries) file_menu.addAction(clear_action) # Exit action exit_action = QAction('Exit', self) exit_action.setShortcut('Ctrl+Q') exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) def init_ui(self): self.setWindowTitle('Calendar Manager GUI') self.setMinimumSize(800, 600) central_widget = QWidget() main_layout = QVBoxLayout() top_layout = QHBoxLayout() # Launch date input launch_date_layout = QVBoxLayout() launch_date_label = QLabel("Launch Date:") self.launch_date_edit = QDateEdit() self.launch_date_edit.setCalendarPopup(True) # Enable calendar popup self.launch_date_edit.setDate(QDate.currentDate()) self.launch_date_edit.dateChanged.connect(self.update_prediction) # Auto-update on change launch_date_layout.addWidget(launch_date_label) launch_date_layout.addWidget(self.launch_date_edit) top_layout.addLayout(launch_date_layout) # Duration input duration_layout = QVBoxLayout() duration_label = QLabel("Duration (years):") self.duration_spin = QSpinBox() self.duration_spin.setRange(1, 99) self.duration_spin.setValue(1) self.duration_spin.valueChanged.connect(self.update_prediction) # Auto-update on change duration_layout.addWidget(duration_label) duration_layout.addWidget(self.duration_spin) top_layout.addLayout(duration_layout) # Prediction result prediction_result_layout = QVBoxLayout() prediction_result_label = QLabel("Predicted Completion Date:") self.prediction_result = QDateEdit() self.prediction_result.setReadOnly(True) self.prediction_result.setButtonSymbols(QDateEdit.ButtonSymbols.NoButtons) prediction_result_layout.addWidget(prediction_result_label) prediction_result_layout.addWidget(self.prediction_result) top_layout.addLayout(prediction_result_layout) main_layout.addLayout(top_layout) # Events section events_layout = QVBoxLayout() events_title = QLabel("

Calendar Events

") events_layout.addWidget(events_title) # Add event button add_event_button = QPushButton("Add Event") add_event_button.clicked.connect(self.add_event) events_layout.addWidget(add_event_button) # Events table self.events_table = QTableWidget() self.events_table.setColumnCount(7) # ID (hidden), Start, End, Keyword, CorrectedStart, CorrectedEnd, Actions self.events_table.setHorizontalHeaderLabels(["ID", "Start Date", "End Date", "Keyword", "Corrected Start", "Corrected End", "Actions"]) self.events_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.events_table.setColumnHidden(0, True) # Hide ID column events_layout.addWidget(self.events_table) main_layout.addLayout(events_layout) # Set main layout central_widget.setLayout(main_layout) self.setCentralWidget(central_widget) # Load initial data self.update_events_table() self.update_prediction() # Calculate initial prediction def update_prediction(self): """Update prediction whenever needed""" try: launch_date = self.launch_date_edit.date().toString("yyyy-MM-dd") duration_years = self.duration_spin.value() self.prediction_controller.make_prediction(launch_date, duration_years) prediction_date = self.prediction_controller.get_prediction() if prediction_date: self.prediction_result.setDate(prediction_date) # Update table to show corrected dates self.update_events_table() except Exception as e: self.prediction_result.setText("Error in calculation") print(f"Error calculating prediction: {str(e)}") def add_event(self): dialog = AddEventDialog(self.keyword_list, self) if dialog.exec_(): start_date, end_date, keyword = dialog.get_data() try: self.calendar_manager.add_entry(start_date, end_date, keyword) self.update_events_table() self.update_prediction() # Auto-update prediction except Exception as e: QMessageBox.critical(self, "Error", f"Error adding event: {str(e)}") def modify_event(self, event_id): entry = self.calendar_manager.get_entry_by_id(event_id) if entry: dialog = ModifyEventDialog(entry, self.keyword_list, self) if dialog.exec_(): start_date, end_date, keyword = dialog.get_data() try: self.calendar_manager.modify_entry(event_id, datetime.fromisoformat(start_date), datetime.fromisoformat(end_date), keyword) self.update_events_table() self.update_prediction() # Auto-update prediction except Exception as e: QMessageBox.critical(self, "Error", f"Error modifying event: {str(e)}") def delete_event(self, event_id): reply = QMessageBox.question(self, "Delete Event", "Are you sure you want to delete this event?", QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: try: self.calendar_manager.delete_entry(event_id) self.update_events_table() self.update_prediction() # Auto-update prediction except Exception as e: QMessageBox.critical(self, "Error", f"Error deleting event: {str(e)}") def update_events_table(self): # Clear table 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)) self.events_table.setItem(i, 1, QTableWidgetItem(entry.start_date.strftime("%Y-%m-%d"))) self.events_table.setItem(i, 2, QTableWidgetItem(entry.end_date.strftime("%Y-%m-%d"))) self.events_table.setItem(i, 3, QTableWidgetItem(entry.keyword)) # Corrected dates corrected_start = entry.corrected_start_date.strftime("%Y-%m-%d") if entry.corrected_start_date else "" corrected_end = entry.corrected_end_date.strftime("%Y-%m-%d") if entry.corrected_end_date else "" self.events_table.setItem(i, 4, QTableWidgetItem(corrected_start)) self.events_table.setItem(i, 5, QTableWidgetItem(corrected_end)) # Action buttons actions_widget = QWidget() actions_layout = QHBoxLayout() actions_layout.setContentsMargins(0, 0, 0, 0) modify_button = QPushButton("Modify") delete_button = QPushButton("Delete") # 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) def save_file(self): """Save calendar entries to a JSON file""" if not self.calendar_manager.filename: file_path, _ = QFileDialog.getSaveFileName(self, "Save Calendar", "", "JSON Files (*.json)") if not file_path: return self.calendar_manager.switch_file(file_path) try: self.calendar_manager.save_entries() QMessageBox.information(self, "Save Successful", f"Calendar saved to {self.calendar_manager.filename}") except Exception as e: QMessageBox.critical(self, "Error", f"Failed to save calendar: {str(e)}") def load_file(self): """Load calendar entries from a JSON file""" file_path, _ = QFileDialog.getOpenFileName(self, "Load Calendar", "", "JSON Files (*.json)") if not file_path: return try: self.calendar_manager.load_file(file_path) self.update_events_table() self.update_prediction() # Auto-update prediction QMessageBox.information(self, "Load Successful", f"Calendar loaded from {file_path}") except Exception as e: QMessageBox.critical(self, "Error", f"Failed to load calendar: {str(e)}") def clear_entries(self): """Clear all calendar entries""" reply = QMessageBox.question(self, "Clear Calendar", "Are you sure you want to clear all events?", QMessageBox.Yes | QMessageBox.No) if reply == QMessageBox.Yes: try: self.calendar_manager.clear_entries() self.update_events_table() self.update_prediction() # Auto-update prediction QMessageBox.information(self, "Success", "Calendar cleared successfully") except Exception as e: QMessageBox.critical(self, "Error", f"Failed to clear calendar: {str(e)}") def main(): app = QApplication(sys.argv) app.setStyle('Fusion') window = CalendarManagerGUI() window.show() sys.exit(app.exec_()) if __name__ == "__main__": main()