#!/usr/bin/python3 import sys import time from PyQt5.QtWidgets import QAction, QApplication, QHBoxLayout, QLabel, QMainWindow, QMessageBox, QPushButton, QScrollArea, QToolBar, QVBoxLayout, QWidget from PyQt5.QtGui import QFont from PyQt5.QtCore import QDate, QDateTime, Qt from add_group_form import addGroupForm from edit_group_form import editGroupForm from add_entry_form import addEntryForm from edit_entry_form import editEntryForm Globals = __import__("globals") DB = __import__("db_sqlite") class AssignmentList(QMainWindow): def __init__(self): super().__init__() # Class globals self.renderGroupButtons = True self.renderEntryButtons = True self.initializeUI() def initializeUI(self): self.resize(640, 480) self.setWindowTitle("Assignment List") self.createMenu() self.createToolbar() self.setupDB() self.displayWidgets() self.show() def createMenu(self): menu_bar = self.menuBar() file_menu = menu_bar.addMenu("File") edit_menu = menu_bar.addMenu("Edit") view_menu = menu_bar.addMenu("View") help_menu = menu_bar.addMenu("Help") exit_act = QAction("Exit", self) exit_act.setShortcut("Ctrl+Q") exit_act.triggered.connect(self.close) file_menu.addAction(exit_act) self.add_group_act = QAction("Add Group", self) self.add_group_act.triggered.connect(self.addGroup) edit_menu.addAction(self.add_group_act) edit_menu.addSeparator() self.clean_hidden_act = QAction("Permanently Delete Removed Groups and Entries", self) self.clean_hidden_act.triggered.connect(self.cleanHidden) edit_menu.addAction(self.clean_hidden_act) self.hide_entry_buttons = QAction("Hide Entry Buttons", self, checkable=True) self.hide_entry_buttons.setShortcut("Ctrl+H") self.hide_entry_buttons.triggered.connect(self.hideEntryButtons) view_menu.addAction(self.hide_entry_buttons) self.hide_group_buttons = QAction("Hide Group Buttons", self, checkable=True) self.hide_group_buttons.setShortcut("Ctrl+Shift+H") self.hide_group_buttons.triggered.connect(self.hideGroupButtons) view_menu.addAction(self.hide_group_buttons) about_act = QAction("About", self) about_act.triggered.connect(self.aboutDialog) help_menu.addAction(about_act) def createToolbar(self): tool_bar = QToolBar("Toolbar") self.addToolBar(tool_bar) tool_bar.addAction(self.add_group_act) def setupDB(self): DB.initDB() def displayWidgets(self): main_widget_scroll_area = QScrollArea(self) main_widget_scroll_area.setWidgetResizable(True) main_widget = QWidget() self.setCentralWidget(main_widget_scroll_area) title = QLabel(time.strftime("%A, %b %d %Y")) title.setFont(QFont("Arial", 17)) title.setTextInteractionFlags(Qt.TextSelectableByMouse) title_h_box = QHBoxLayout() title_h_box.addStretch() title_h_box.addWidget(title) title_h_box.addStretch() self.groups_hbox = QHBoxLayout() self.groups_hbox.setContentsMargins(20, 5, 20, 5) self.drawGroups() v_box = QVBoxLayout() v_box.addLayout(title_h_box) v_box.addLayout(self.groups_hbox) v_box.addStretch() main_widget.setLayout(v_box) main_widget_scroll_area.setWidget(main_widget) def addGroup(self): """ Open the 'addGroup' form """ old_count = len(Globals.groups) self.create_new_group_dialog = addGroupForm() if old_count != len(Globals.groups): self.drawGroups() def editGroup(self, id): """ Open the 'editGroup' form """ self.create_edit_group_dialog = editGroupForm(id) self.drawGroups() def removeGroup(self, id): """ Delete a group with a given id """ # TODO might want to add a warning # TODO might want to make part of the a destructor in the Group class removed = DB.removeGroup(id) if removed > 0: Globals.entries = list(filter(lambda e: e.parent_id != id, Globals.entries)) Globals.groups = list(filter(lambda g: g.id != id, Globals.groups)) self.drawGroups() def hideGroupButtons(self): """ Set a flag to avoid rendering buttons under groups """ self.renderGroupButtons = not self.renderGroupButtons self.drawGroups() def addEntry(self, parent): """ Open the 'addEntry' form """ old_count = len(Globals.entries) self.create_new_entry_dialog = addEntryForm(parent) if old_count != len(Globals.entries): self.drawGroups() # TODO see if we can do this with only redrawing a single group def editEntry(self, id): """ Open the 'editEntry' form """ self.create_edit_entry_dialog = editEntryForm(id) self.drawGroups() def toggleDoneEntry(self, id): """ Toggle the 'done' flag on the entry with the given id """ entry = list(filter(lambda e: e.id == id, Globals.entries))[0] if entry.done: entry.done = False else: entry.done = True DB.updateEntry(entry) Globals.entries = list(filter(lambda e: e.id != id, Globals.entries)) Globals.entries.append(entry) self.drawGroups() def removeEntry(self, id): """ Delete an entry with a given id """ # TODO might want to add a warning # TODO might want to make part of the a destructor in the Entry class removed = DB.removeEntry(id) if removed > 0: Globals.entries = list(filter(lambda e: e.id != id, Globals.entries)) self.drawGroups() def hideEntryButtons(self): """ Set a flag to avoid rendering buttons under entries """ self.renderEntryButtons = not self.renderEntryButtons self.drawGroups() def cleanHidden(self): """ Permanently delete removed groups and entries from db """ # TODO consider creating a warning dialogue for this DB.cleanHidden() def drawGroups(self): """ Redraw the groups_hbox """ # Remove all children from layout def recursiveClear(layout): while layout.count(): child = layout.takeAt(0) if child.widget(): child.widget().deleteLater() elif child.layout(): recursiveClear(child) recursiveClear(self.groups_hbox) # Sort the groups Globals.groups = sorted(Globals.groups, key=lambda g: g.id) # Sort the entries (by due_date for now) Globals.entries = sorted(Globals.entries, key=lambda e: (e.parent_id, (e.due if e.due else QDate.currentDate()), e.done, e.id)) # Create columns as vertical boxes column_left = QVBoxLayout() column_right = QVBoxLayout() for g in Globals.groups: # skip if this group is set to hidden if g.hidden: continue g_layout = g.buildLayout() # Draw entries belonging to this group g_layout.addLayout(self.drawEntries(g.id)) # Include buttons at the bottom to edit the group if self.renderGroupButtons: buttons_hbox = QHBoxLayout() add_entry_button = QPushButton() add_entry_button.setText("Add Entry") add_entry_button.clicked.connect((lambda id: lambda: self.addEntry(id))(g.id)) buttons_hbox.addWidget(add_entry_button) edit_group_button = QPushButton() edit_group_button.setText("Edit Group") edit_group_button.clicked.connect((lambda id: lambda: self.editGroup(id))(g.id)) buttons_hbox.addWidget(edit_group_button) del_group_button = QPushButton() del_group_button.setText("Remove Group") del_group_button.clicked.connect((lambda id: lambda: self.removeGroup(id))(g.id)) buttons_hbox.addWidget(del_group_button) buttons_hbox.addStretch() g_layout.addLayout(buttons_hbox) if g.column.lower() == "left": column_left.addLayout(g_layout) else: column_right.addLayout(g_layout) column_left.addStretch() column_right.addStretch() self.groups_hbox.addLayout(column_left) self.groups_hbox.addStretch() self.groups_hbox.addLayout(column_right) self.groups_hbox.addStretch() def drawEntries(self, group_id): """ Redraw the entries of a specific group """ # TODO consider having code to remove existing widgets to make this function more modular entries = list(filter(lambda e: e.parent_id == group_id, Globals.entries)) entries_vbox = QVBoxLayout() entries_vbox.setContentsMargins(5, 0, 0, 0) for e in entries: # skip if this entry is set to hidden if e.hidden: continue entries_vbox.addLayout(e.buildLayout()) # entry modifier buttons if self.renderEntryButtons: buttons_hbox = QHBoxLayout() edit_entry_button = QPushButton() edit_entry_button.setText("Edit Entry") edit_entry_button.clicked.connect((lambda id: lambda: self.editEntry(id))(e.id)) buttons_hbox.addWidget(edit_entry_button) mark_done_button = QPushButton() if e.done: mark_done_button.setText("Not Done") else: mark_done_button.setText("Done") mark_done_button.clicked.connect((lambda id: lambda: self.toggleDoneEntry(id))(e.id)) buttons_hbox.addWidget(mark_done_button) del_entry_button = QPushButton() del_entry_button.setText("Remove Entry") del_entry_button.clicked.connect((lambda id: lambda: self.removeEntry(id))(e.id)) buttons_hbox.addWidget(del_entry_button) buttons_hbox.addStretch() entries_vbox.addLayout(buttons_hbox) return entries_vbox def aboutDialog(self): QMessageBox.about(self, "About Assignment List", "Created by Louie S. - 2023") if __name__ == "__main__": app = QApplication(sys.argv) window = AssignmentList() sys.exit(app.exec_())