summaryrefslogtreecommitdiff
path: root/src/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend')
-rw-r--r--src/frontend/qtwidgets/addEntryForm.cpp49
-rw-r--r--src/frontend/qtwidgets/addEntryForm.h24
-rw-r--r--src/frontend/qtwidgets/addGroupForm.cpp34
-rw-r--r--src/frontend/qtwidgets/addGroupForm.h21
-rw-r--r--src/frontend/qtwidgets/assignmentList.cpp169
-rw-r--r--src/frontend/qtwidgets/assignmentList.h43
-rw-r--r--src/frontend/qtwidgets/assignmentList.ui213
-rw-r--r--src/frontend/qtwidgets/editEntryForm.cpp55
-rw-r--r--src/frontend/qtwidgets/editEntryForm.h25
-rw-r--r--src/frontend/qtwidgets/editGroupForm.cpp40
-rw-r--r--src/frontend/qtwidgets/editGroupForm.h25
-rw-r--r--src/frontend/qtwidgets/entry.cpp36
-rw-r--r--src/frontend/qtwidgets/entry.h39
-rw-r--r--src/frontend/qtwidgets/entryForm.ui185
-rw-r--r--src/frontend/qtwidgets/entryLayout.cpp177
-rw-r--r--src/frontend/qtwidgets/entryLayout.h32
-rw-r--r--src/frontend/qtwidgets/group.cpp14
-rw-r--r--src/frontend/qtwidgets/group.h25
-rw-r--r--src/frontend/qtwidgets/groupForm.ui131
-rw-r--r--src/frontend/qtwidgets/groupLayout.cpp68
-rw-r--r--src/frontend/qtwidgets/groupLayout.h23
-rw-r--r--src/frontend/qtwidgets/lib.cpp22
-rw-r--r--src/frontend/qtwidgets/lib.h12
-rw-r--r--src/frontend/qtwidgets/main.cpp12
-rw-r--r--src/frontend/qtwidgets/preferencesDialog.cpp59
-rw-r--r--src/frontend/qtwidgets/preferencesDialog.h28
-rw-r--r--src/frontend/qtwidgets/preferencesDialog.ui128
-rw-r--r--src/frontend/qtwidgets/rule.cpp15
-rw-r--r--src/frontend/qtwidgets/rule.h28
-rw-r--r--src/frontend/qtwidgets/ruleLayout.cpp40
-rw-r--r--src/frontend/qtwidgets/ruleLayout.h27
-rw-r--r--src/frontend/qtwidgets/rulesDialog.cpp107
-rw-r--r--src/frontend/qtwidgets/rulesDialog.h32
-rw-r--r--src/frontend/qtwidgets/rulesDialog.ui123
34 files changed, 2061 insertions, 0 deletions
diff --git a/src/frontend/qtwidgets/addEntryForm.cpp b/src/frontend/qtwidgets/addEntryForm.cpp
new file mode 100644
index 0000000..7402167
--- /dev/null
+++ b/src/frontend/qtwidgets/addEntryForm.cpp
@@ -0,0 +1,49 @@
+#include <QDate>
+#include <QMessageBox>
+#include <QString>
+
+#include "addEntryForm.h"
+#include "../../backend/db_sqlite.h"
+
+AddEntryForm::AddEntryForm(const Group &g) :
+ parent_group(g)
+{
+ // load uic
+ ui.setupUi(this);
+
+ // set titles
+ this->setWindowTitle("Add Entry");
+ ui.title->setText("Add Entry");
+
+ // widgets setup
+ ui.entry_due->setDate(QDate::currentDate());
+}
+
+void AddEntryForm::accept() {
+ QString desc_text = ui.entry_desc->text();
+ QDateTime due_text = ui.entry_due->date().startOfDay();
+ QString due_alt_text = ui.entry_due_alt->text();
+ QString link_text = ui.entry_link->text();
+ QString color_text = ui.entry_color->text();
+ QString highlight_text = ui.entry_highlight->text();
+ QMessageBox error_message;
+ BackendDB database;
+ int new_id;
+
+ // Check that the new entry is not blank
+ if(desc_text.isEmpty()) {
+ error_message.setIcon(QMessageBox::Warning);
+ error_message.setWindowTitle("Error Message");
+ error_message.setText("Description cannot be blank");
+ error_message.exec();
+ return;
+ }
+
+ // replace due_text with null date if due_checkbox is unchecked
+ if(!ui.entry_due_checkbox->isChecked())
+ due_text = QDateTime();
+
+ new_id = database.insertEntry(Entry(0, this->parent_group.id, desc_text, due_text, due_alt_text, link_text, color_text, highlight_text));
+
+ QDialog::accept();
+}
diff --git a/src/frontend/qtwidgets/addEntryForm.h b/src/frontend/qtwidgets/addEntryForm.h
new file mode 100644
index 0000000..81535f1
--- /dev/null
+++ b/src/frontend/qtwidgets/addEntryForm.h
@@ -0,0 +1,24 @@
+#ifndef ADDENTRYFORM_H
+#define ADDENTRYFORM_H
+
+#include <QObject>
+
+#include "group.h"
+#include "ui_entryForm.h"
+
+class AddEntryForm : public QDialog {
+ Q_OBJECT
+
+ public:
+ AddEntryForm(const Group &g);
+
+ private:
+ Ui::entryDialog ui;
+
+ Group parent_group;
+
+ private slots:
+ void accept();
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/addGroupForm.cpp b/src/frontend/qtwidgets/addGroupForm.cpp
new file mode 100644
index 0000000..c7d5faa
--- /dev/null
+++ b/src/frontend/qtwidgets/addGroupForm.cpp
@@ -0,0 +1,34 @@
+#include <QMessageBox>
+
+#include "addGroupForm.h"
+#include "../../backend/db_sqlite.h"
+
+AddGroupForm::AddGroupForm() {
+ // load uic
+ ui.setupUi(this);
+
+ // set titles
+ this->setWindowTitle("Add Group");
+ ui.title->setText("Add Group");
+}
+
+void AddGroupForm::accept() {
+ QString name_text = ui.group_name->text();
+ Group::Column column_value = Group::Column(ui.group_column->currentIndex());
+ QString link_text = ui.group_link->text();
+ QMessageBox error_message;
+ BackendDB database;
+ int new_id;
+
+ if(name_text.isEmpty()) {
+ error_message.setIcon(QMessageBox::Warning);
+ error_message.setWindowTitle("Error Message");
+ error_message.setText("Name cannot be blank");
+ error_message.exec();
+ return;
+ }
+
+ new_id = database.insertGroup(Group(0, name_text, column_value, link_text));
+
+ QDialog::accept();
+}
diff --git a/src/frontend/qtwidgets/addGroupForm.h b/src/frontend/qtwidgets/addGroupForm.h
new file mode 100644
index 0000000..b181408
--- /dev/null
+++ b/src/frontend/qtwidgets/addGroupForm.h
@@ -0,0 +1,21 @@
+#ifndef ADDGROUPFORM_H
+#define ADDGROUPFORM_H
+
+#include <QObject>
+
+#include "ui_groupForm.h"
+
+class AddGroupForm : public QDialog {
+ Q_OBJECT
+
+ public:
+ AddGroupForm();
+
+ private:
+ Ui::groupDialog ui;
+
+ private slots:
+ void accept();
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/assignmentList.cpp b/src/frontend/qtwidgets/assignmentList.cpp
new file mode 100644
index 0000000..dc89544
--- /dev/null
+++ b/src/frontend/qtwidgets/assignmentList.cpp
@@ -0,0 +1,169 @@
+#include <algorithm>
+#include <QAction>
+#include <QApplication>
+#include <QCoreApplication>
+#include <QDate>
+#include <QDir>
+#include <QFile>
+#include <QMessageBox>
+#include <QObject>
+#include <QSettings>
+#include <QStandardPaths>
+#include <QUiLoader>
+
+#include <QDebug>
+#include <QErrorMessage>
+
+#include "../../../config.h"
+#include "addGroupForm.h"
+#include "assignmentList.h"
+#include "../../backend/db_sqlite.h"
+#include "entryLayout.h"
+#include "groupLayout.h"
+#include "lib.h"
+#include "preferencesDialog.h"
+
+AssignmentList::AssignmentList() {
+ // set QSettings information
+ QCoreApplication::setOrganizationName(ORGANIZATION_NAME);
+ QCoreApplication::setApplicationName(PROJECT_NAME);
+
+ // ensure QSettings location exists
+ this->initializeSettings();
+
+ // load everything from database into static global variables
+ this->initializeGlobals();
+
+ // load uic
+ ui.setupUi(this);
+ this->initializeUI();
+}
+
+void AssignmentList::initializeSettings() {
+ QSettings settings;
+ QDir local_data_dir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
+
+ settings.beginGroup("paths");
+ if(!settings.contains("db_path")) {
+ qDebug() << "Setting default db_path";
+ settings.setValue("db_path", local_data_dir.filePath("data.db"));
+ }
+ settings.endGroup();
+}
+
+// load from database into static QList variables
+void AssignmentList::initializeGlobals() {
+ BackendDB database;
+
+ Group::groups = database.loadGroups();
+ Entry::entries = database.loadEntries();
+ Rule::rules = database.loadRules();
+}
+
+void AssignmentList::initializeUI() {
+ // create menu connections
+ QObject::connect(ui.actionPreferences, &QAction::triggered, this, &AssignmentList::preferences);
+ QObject::connect(ui.actionReload, &QAction::triggered, this, &AssignmentList::reload);
+ QObject::connect(ui.actionExit, &QAction::triggered, this, QApplication::quit);
+ QObject::connect(ui.actionAdd_Group, &QAction::triggered, this, &AssignmentList::addGroup);
+ QObject::connect(ui.actionClean_Hidden, &QAction::triggered, this, &AssignmentList::cleanHidden);
+ QObject::connect(ui.actionAbout, &QAction::triggered, this, &AssignmentList::aboutDialog);
+
+ // create toolbar
+ ui.toolBar->addAction(ui.actionAdd_Group);
+
+ this->displayDate();
+ this->show();
+ this->displayWidgets();
+}
+
+void AssignmentList::displayDate() {
+ QDate today = QDate::currentDate();
+ ui.title->setText(today.toString("dddd, MMM d yyyy"));
+}
+
+void AssignmentList::displayWidgets() {
+ QVBoxLayout *column_left = new QVBoxLayout();
+ QVBoxLayout *column_right = new QVBoxLayout();
+ BackendDB database;
+ GroupLayout *new_group_layout;
+ int i;
+
+ // clear out old layouts if they exist
+ recursiveClear(ui.groups_layout);
+
+ for(i = 0; i < Group::groups.size(); ++i) {
+ if(Group::groups[i].hidden) continue;
+ new_group_layout = new GroupLayout(Group::groups[i]);
+ new_group_layout->addLayout(this->drawEntries(Group::groups[i].id)); // add entries to layout
+ if(Group::groups[i].column == Group::left) column_left->addLayout(new_group_layout);
+ else column_right->addLayout(new_group_layout);
+ }
+
+ column_left->addStretch();
+ column_right->addStretch();
+
+ ui.groups_layout->addLayout(column_left, 0, 0);
+ ui.groups_layout->addLayout(column_right, 0, 1);
+}
+
+QVBoxLayout *AssignmentList::drawEntries(int parent_id) {
+ BackendDB database;
+ QVBoxLayout *output = new QVBoxLayout;
+ int i;
+
+ // styling
+ output->setContentsMargins(5, 0, 0, 0);
+
+ // sort entries
+ std::sort(Entry::entries.begin(), Entry::entries.end(), Entry::compare);
+
+ for(i = 0; i < Entry::entries.size(); ++i) {
+ if(Entry::entries[i].parent_id != parent_id) continue;
+ // skip if this entry is set to hidden
+ if(Entry::entries[i].hidden) continue;
+ output->addLayout(new EntryLayout(Entry::entries[i]));
+ }
+
+ return output;
+}
+
+// Open the 'addGroup' form
+void AssignmentList::addGroup() {
+ AddGroupForm create_new_group_dialog;
+ if(create_new_group_dialog.exec() == QDialog::Accepted)
+ this->displayWidgets();
+}
+
+void AssignmentList::preferences() {
+ PreferencesDialog preferences_dialog;
+ if(preferences_dialog.exec() == QDialog::Accepted)
+ this->displayWidgets();
+}
+
+void AssignmentList::reload() {
+ this->initializeGlobals();
+ this->displayWidgets();
+}
+
+void AssignmentList::cleanHidden() {
+ QMessageBox::StandardButton are_you_sure;
+ BackendDB database;
+
+ // create 'are you sure?' dialog before going forward
+ are_you_sure = QMessageBox::question(this, "Are You Sure?",
+ "Are you sure? All removed groups and entries will be permanently deleted from the database.",
+ QMessageBox::Yes|QMessageBox::No);
+
+ if(are_you_sure == QMessageBox::Yes)
+ database.cleanHidden();
+}
+
+void AssignmentList::aboutDialog() {
+ QMessageBox about;
+ QString title("About " + QString(PROJECT_TITLE));
+ QString text(
+ QString(PROJECT_TITLE) + " " + QString(VERSION) + "\n" +
+ "Created by Louie S. - 2024");
+ about.about(this, title, text);
+}
diff --git a/src/frontend/qtwidgets/assignmentList.h b/src/frontend/qtwidgets/assignmentList.h
new file mode 100644
index 0000000..4b792fa
--- /dev/null
+++ b/src/frontend/qtwidgets/assignmentList.h
@@ -0,0 +1,43 @@
+#ifndef ASSIGNMENTLIST_H
+#define ASSIGNMENTLIST_H
+
+#include <QMainWindow>
+#include <QSettings>
+#include <QVBoxLayout>
+
+#include "ui_assignmentList.h"
+
+class AssignmentList : public QMainWindow {
+ Q_OBJECT
+
+ public:
+ QSettings configuration;
+
+ AssignmentList();
+ void initializeGlobals();
+ void displayWidgets();
+
+ private:
+ Ui::MainWindow ui;
+
+ void initializeSettings();
+ void initializeUI();
+ void displayDate();
+ QVBoxLayout *drawEntries(int parent_id);
+ void editEntry(int id);
+ void toggleDoneEntry(int id);
+ void removeEntry(int id);
+ void editRules(int id);
+ void entryContextMenu(int entry_id);
+ void drawGroups();
+ void drawEntries();
+
+ private slots:
+ void preferences();
+ void reload();
+ void addGroup();
+ void cleanHidden();
+ void aboutDialog();
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/assignmentList.ui b/src/frontend/qtwidgets/assignmentList.ui
new file mode 100644
index 0000000..60c66da
--- /dev/null
+++ b/src/frontend/qtwidgets/assignmentList.ui
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>640</width>
+ <height>480</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Assignment List</string>
+ </property>
+ <property name="windowIcon">
+ <iconset resource="../resources.qrc">
+ <normaloff>:/data/assignment-list.svg</normaloff>:/data/assignment-list.svg</iconset>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents_3">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>620</width>
+ <height>421</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="v_box">
+ <item>
+ <layout class="QHBoxLayout" name="title_h_box">
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="title">
+ <property name="font">
+ <font>
+ <family>Arial</family>
+ <pointsize>17</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>[DATE]</string>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="groups_layout">
+ <property name="leftMargin">
+ <number>20</number>
+ </property>
+ <property name="topMargin">
+ <number>5</number>
+ </property>
+ <property name="rightMargin">
+ <number>20</number>
+ </property>
+ <property name="bottomMargin">
+ <number>5</number>
+ </property>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>640</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menuFile">
+ <property name="title">
+ <string>File</string>
+ </property>
+ <addaction name="actionPreferences"/>
+ <addaction name="actionReload"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <widget class="QMenu" name="menuEdit">
+ <property name="title">
+ <string>Edit</string>
+ </property>
+ <addaction name="actionAdd_Group"/>
+ <addaction name="separator"/>
+ <addaction name="actionClean_Hidden"/>
+ </widget>
+ <widget class="QMenu" name="menuHelp">
+ <property name="title">
+ <string>Help</string>
+ </property>
+ <addaction name="actionAbout"/>
+ </widget>
+ <addaction name="menuFile"/>
+ <addaction name="menuEdit"/>
+ <addaction name="menuHelp"/>
+ </widget>
+ <widget class="QToolBar" name="toolBar">
+ <property name="windowTitle">
+ <string>toolBar</string>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>TopToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ <action name="actionPreferences">
+ <property name="text">
+ <string>Preferences</string>
+ </property>
+ <property name="shortcut">
+ <string>Alt+Return</string>
+ </property>
+ </action>
+ <action name="actionReload">
+ <property name="text">
+ <string>Reload</string>
+ </property>
+ <property name="shortcut">
+ <string>F5</string>
+ </property>
+ </action>
+ <action name="actionExit">
+ <property name="text">
+ <string>Exit</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ <action name="actionAdd_Group">
+ <property name="text">
+ <string>Add Group</string>
+ </property>
+ </action>
+ <action name="actionClean_Hidden">
+ <property name="text">
+ <string>Permanently Delete Removed Groups and Entries</string>
+ </property>
+ </action>
+ <action name="actionAbout">
+ <property name="text">
+ <string>About</string>
+ </property>
+ </action>
+ </widget>
+ <resources>
+ <include location="../resources.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/src/frontend/qtwidgets/editEntryForm.cpp b/src/frontend/qtwidgets/editEntryForm.cpp
new file mode 100644
index 0000000..ca788ca
--- /dev/null
+++ b/src/frontend/qtwidgets/editEntryForm.cpp
@@ -0,0 +1,55 @@
+#include <QMessageBox>
+
+#include "../../backend/db_sqlite.h"
+#include "editEntryForm.h"
+
+EditEntryForm::EditEntryForm(const Entry &e) :
+ entry(e)
+{
+ // load uic
+ ui.setupUi(this);
+
+ // set titles
+ this->setWindowTitle("Edit Entry");
+ ui.title->setText("Edit Entry");
+
+ // widgets setup
+ ui.entry_desc->setText(this->entry.desc);
+ ui.entry_due->setDate(QDate::currentDate());
+ if(this->entry.due.isValid()) {
+ ui.entry_due->setDate(entry.due.date());
+ ui.entry_due_checkbox->setChecked(true);
+ }
+ else
+ ui.entry_due_checkbox->setChecked(false);
+ ui.entry_due_alt->setText(this->entry.due_alt);
+ ui.entry_link->setText(this->entry.link.toString());
+ ui.entry_color->setText(this->entry.color);
+ ui.entry_highlight->setText(this->entry.highlight);
+}
+
+void EditEntryForm::accept() {
+ this->entry.desc = ui.entry_desc->text();
+ if(ui.entry_due_checkbox->isChecked())
+ this->entry.due = ui.entry_due->dateTime();
+ else
+ this->entry.due = QDateTime();
+ this->entry.due_alt = ui.entry_due_alt->text();
+ this->entry.link = ui.entry_link->text();
+ this->entry.color = ui.entry_color->text();
+ this->entry.highlight = ui.entry_highlight->text();
+ QMessageBox error_message;
+ BackendDB database;
+
+ if(this->entry.desc.isEmpty()) {
+ error_message.setIcon(QMessageBox::Warning);
+ error_message.setWindowTitle("Error Message");
+ error_message.setText("Name cannot be blank");
+ error_message.exec();
+ return;
+ }
+
+ database.updateEntry(this->entry);
+
+ QDialog::accept();
+}
diff --git a/src/frontend/qtwidgets/editEntryForm.h b/src/frontend/qtwidgets/editEntryForm.h
new file mode 100644
index 0000000..6231478
--- /dev/null
+++ b/src/frontend/qtwidgets/editEntryForm.h
@@ -0,0 +1,25 @@
+#ifndef EDITENTRYFORM_H
+#define EDITENTRYFORM_H
+
+#include <QDialog>
+
+#include "entry.h"
+#include "ui_entryForm.h"
+
+// form to edit/update an entry
+class EditEntryForm : public QDialog {
+ Q_OBJECT
+
+ public:
+ EditEntryForm(const Entry &e);
+
+ private:
+ Ui::entryDialog ui;
+
+ Entry entry;
+
+ private slots:
+ void accept();
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/editGroupForm.cpp b/src/frontend/qtwidgets/editGroupForm.cpp
new file mode 100644
index 0000000..fa17467
--- /dev/null
+++ b/src/frontend/qtwidgets/editGroupForm.cpp
@@ -0,0 +1,40 @@
+#include <QMessageBox>
+
+#include "../../backend/db_sqlite.h"
+#include "editGroupForm.h"
+
+EditGroupForm::EditGroupForm(const Group &g) :
+ group(g)
+{
+ // load uic
+ ui.setupUi(this);
+
+ // set titles
+ this->setWindowTitle("Edit Group");
+ ui.title->setText("Edit Group");
+
+ // widgets setup
+ ui.group_name->setText(this->group.name);
+ ui.group_column->setCurrentIndex(this->group.column);
+ ui.group_link->setText(this->group.link);
+}
+
+void EditGroupForm::accept() {
+ this->group.name = ui.group_name->text();
+ this->group.column = Group::Column(ui.group_column->currentIndex());
+ this->group.link = ui.group_link->text();
+ QMessageBox error_message;
+ BackendDB database;
+
+ if(this->group.name.isEmpty()) {
+ error_message.setIcon(QMessageBox::Warning);
+ error_message.setWindowTitle("Error Message");
+ error_message.setText("Name cannot be blank");
+ error_message.exec();
+ return;
+ }
+
+ database.updateGroup(this->group);
+
+ QDialog::accept();
+}
diff --git a/src/frontend/qtwidgets/editGroupForm.h b/src/frontend/qtwidgets/editGroupForm.h
new file mode 100644
index 0000000..e04b46e
--- /dev/null
+++ b/src/frontend/qtwidgets/editGroupForm.h
@@ -0,0 +1,25 @@
+#ifndef EDITGROUPFORM_H
+#define EDITGROUPFORM_H
+
+#include <QDialog>
+
+#include "group.h"
+#include "ui_groupForm.h"
+
+// form to edit/update a group
+class EditGroupForm : public QDialog {
+ Q_OBJECT
+
+ public:
+ EditGroupForm(const Group &g);
+
+ private:
+ Ui::groupDialog ui;
+
+ Group group;
+
+ private slots:
+ void accept();
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/entry.cpp b/src/frontend/qtwidgets/entry.cpp
new file mode 100644
index 0000000..c95bbb0
--- /dev/null
+++ b/src/frontend/qtwidgets/entry.cpp
@@ -0,0 +1,36 @@
+#include <QList>
+
+#include "entry.h"
+
+Entry::Entry(int id, int parent_id, QString desc, QDateTime due, QString due_alt, QUrl link, QString color, QString highlight, bool done, bool hidden) :
+ id(id),
+ parent_id(parent_id),
+ desc(desc),
+ due(due),
+ due_alt(due_alt),
+ link(link),
+ color(color),
+ highlight(highlight),
+ done(done),
+ hidden(hidden)
+{
+}
+
+QList<Entry> Entry::entries;
+
+bool Entry::compare(Entry a, Entry b) {
+ // 1st level: sort not done before done
+ if(a.done != b.done)
+ return a.done < b.done;
+ // next level: sort on due date
+ if(a.due != b.due)
+ return a.due < b.due;
+ // next level: sort on alt due date
+ if(a.due_alt != b.due_alt)
+ return a.due_alt < b.due_alt;
+ // next level: sort on description
+ if(a.desc != b.desc)
+ return a.desc < b.desc;
+ // otherwise, sort on id
+ return a.id < b.id;
+}
diff --git a/src/frontend/qtwidgets/entry.h b/src/frontend/qtwidgets/entry.h
new file mode 100644
index 0000000..294df7b
--- /dev/null
+++ b/src/frontend/qtwidgets/entry.h
@@ -0,0 +1,39 @@
+#ifndef ENTRY_H
+#define ENTRY_H
+
+#include <QDateTime>
+#include <QString>
+#include <QUrl>
+
+struct Entry {
+ int id;
+ int parent_id;
+ QString desc;
+ QDateTime due;
+ QString due_alt;
+ QUrl link;
+ QString color; // consider making this a QColor instead
+ QString highlight; // see color comment
+ bool done;
+ bool hidden;
+
+ Entry(
+ int id,
+ int parent_id,
+ QString desc,
+ QDateTime due = QDateTime(),
+ QString due_alt = "",
+ QUrl link = QUrl(),
+ QString color = "",
+ QString highlight = "",
+ bool done = false,
+ bool hidden = false
+ );
+
+ static QList<Entry> entries;
+
+ // function for sorting algorithm
+ static bool compare(Entry a, Entry b);
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/entryForm.ui b/src/frontend/qtwidgets/entryForm.ui
new file mode 100644
index 0000000..bce8ce3
--- /dev/null
+++ b/src/frontend/qtwidgets/entryForm.ui
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>entryDialog</class>
+ <widget class="QDialog" name="entryDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>266</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Add Entry</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="1" column="0">
+ <widget class="QLabel" name="descriptionLabel">
+ <property name="text">
+ <string>Description: </string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="entry_desc"/>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="title">
+ <property name="font">
+ <font>
+ <family>Arial</family>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>[TITLE]</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="dueDateLabel">
+ <property name="text">
+ <string>Due Date: </string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QWidget" name="due_hbox" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QDateTimeEdit" name="entry_due">
+ <property name="displayFormat">
+ <string>MM/dd/yyyy</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="entry_due_checkbox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="dueDateAltLabel">
+ <property name="text">
+ <string>Due Date (Alt):</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="entry_due_alt"/>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="linkLabel">
+ <property name="text">
+ <string>Link:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLineEdit" name="entry_link"/>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="colorLabel">
+ <property name="text">
+ <string>Color:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLineEdit" name="entry_color"/>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="highlightLabel">
+ <property name="text">
+ <string>Highlight:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QLineEdit" name="entry_highlight"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ <property name="centerButtons">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>entryDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>entryDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/frontend/qtwidgets/entryLayout.cpp b/src/frontend/qtwidgets/entryLayout.cpp
new file mode 100644
index 0000000..578ec23
--- /dev/null
+++ b/src/frontend/qtwidgets/entryLayout.cpp
@@ -0,0 +1,177 @@
+#include <QLabel>
+#include <QMenu>
+
+#include <QDebug>
+
+#include "../../backend/db_sqlite.h"
+#include "editEntryForm.h"
+#include "entryLayout.h"
+#include "lib.h"
+#include "rulesDialog.h"
+
+EntryLayout::EntryLayout(const Entry &e) :
+ entry(e)
+{
+ QLabel *bullet = new QLabel();
+ QLabel *body = new QLabel();
+ int i;
+
+ // set styling
+ this->setContentsMargins(2, 2, 2, 2);
+
+ bullet->setFont(QFont("Arial", 11));
+ bullet->setMaximumWidth(15);
+
+ body->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
+ body->setFont(QFont("Arial", 11));
+ body->setWordWrap(true);
+ body->setToolTip("Right-Click for actions");
+ body->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ // set context menu
+ body->setContextMenuPolicy(Qt::CustomContextMenu);
+ QObject::connect(body,
+ SIGNAL(customContextMenuRequested(const QPoint &)),
+ SLOT(showContextMenu()));
+
+ // Check rules
+ QList<Rule> rules = this->loadRules();
+ for(i = 0; i < rules.size(); ++i) {
+ if(
+ (rules[i].when == Rule::before &&
+ rules[i].date > QDateTime::currentDateTime()) ||
+ (rules[i].when == Rule::after &&
+ rules[i].date <= QDateTime::currentDateTime())
+ ) {
+ if(!rules[i].color.isEmpty())
+ this->entry.color = rules[i].color;
+ if(!rules[i].highlight.isEmpty())
+ this->entry.highlight = rules[i].highlight;
+ }
+ }
+
+ // set conditional styling
+ if(this->entry.done) {
+ bullet->setText("\u2713");
+ bullet->setStyleSheet(
+ "QLabel {"
+ " color: green;"
+ "}"
+ );
+ }
+ else
+ bullet->setText("- ");
+ this->addWidget(bullet);
+
+ if(!this->entry.due.isNull())
+ body->setText(this->entry.due.toString("dddd, MM/dd/yyyy: "));
+ else if(!this->entry.due_alt.isEmpty())
+ body->setText(this->entry.due_alt + ": ");
+
+ if(!this->entry.link.isEmpty()) {
+ body->setOpenExternalLinks(true);
+ body->setText(body->text() + "<a href=\"" + this->entry.link.toString() + "\" " "style=\"color: ");
+ if(this->entry.done)
+ body->setText(body->text() + "green");
+ else if(this->entry.color.isEmpty())
+ body->setText(body->text() + "default");
+ else
+ body->setText(body->text() + this->entry.color);
+ body->setText(body->text() + ";\">");
+ }
+ body->setText(body->text() + this->entry.desc);
+ if(!this->entry.link.isEmpty()) {
+ body->setText(body->text() + "</a>");
+ body->setToolTip(this->entry.link.toString());
+ }
+
+ if(this->entry.done) {
+ QFont body_font = body->font();
+ body_font.setStrikeOut(true);
+ body->setFont(body_font);
+ body->setStyleSheet(
+ "QLabel {"
+ " color: green"
+ "}"
+ );
+ }
+ else {
+ body->setStyleSheet(
+ "QLabel {"
+ " color: " + (this->entry.color.isEmpty() ? "default" : this->entry.color) + ";"
+ " background-color: " + (this->entry.highlight.isEmpty() ? "none" : this->entry.highlight) + ";"
+ " font-weight: " + (this->entry.due.isValid() && this->entry.due <= QDateTime::currentDateTime() ? "bold" : "normal") + ";"
+ "}"
+ );
+ }
+
+ this->addWidget(body);
+}
+
+QList<Rule> EntryLayout::loadRules() {
+ BackendDB database;
+
+ return database.loadRules(this->entry.id);
+}
+
+void EntryLayout::showContextMenu() {
+ QMenu menu;
+
+ QAction *edit_entry_act = new QAction("Edit Entry");
+ QObject::connect(edit_entry_act, &QAction::triggered, this, &EntryLayout::editEntry);
+ menu.addAction(edit_entry_act);
+
+ QAction *set_rules_act = new QAction("Rules");
+ QObject::connect(set_rules_act, &QAction::triggered, this, &EntryLayout::setRules);
+ menu.addAction(set_rules_act);
+
+ QAction *toggle_done_act = new QAction("Done");
+ toggle_done_act->setCheckable(true);
+ if(this->entry.done) toggle_done_act->setChecked(true);
+ QObject::connect(toggle_done_act, &QAction::triggered, this, &EntryLayout::toggleDone);
+ menu.addAction(toggle_done_act);
+
+ QAction *clone_entry_act = new QAction("Clone Entry");
+ QObject::connect(clone_entry_act, &QAction::triggered, this, &EntryLayout::cloneEntry);
+ menu.addAction(clone_entry_act);
+
+ QAction *del_entry_act = new QAction("Remove Entry");
+ QObject::connect(del_entry_act, &QAction::triggered, this, &EntryLayout::removeEntry);
+ menu.addAction(del_entry_act);
+
+ menu.exec(QCursor::pos());
+}
+
+void EntryLayout::editEntry() {
+ EditEntryForm edit_entry_dialog(this->entry);
+ if(edit_entry_dialog.exec() == QDialog::Accepted)
+ getMainWindow()->displayWidgets();
+}
+
+void EntryLayout::setRules() {
+ RulesDialog rules_dialog(this->entry);
+ if(rules_dialog.exec() == QDialog::Accepted)
+ getMainWindow()->displayWidgets();
+}
+
+void EntryLayout::toggleDone() {
+ BackendDB database;
+
+ this->entry.done = !this->entry.done;
+ database.updateEntry(this->entry);
+ getMainWindow()->displayWidgets();
+}
+
+void EntryLayout::cloneEntry() {
+ BackendDB database;
+
+ database.insertEntry(this->entry);
+ getMainWindow()->displayWidgets();
+}
+
+void EntryLayout::removeEntry() {
+ BackendDB database;
+
+ if(database.removeEntry(this->entry) > 0)
+ getMainWindow()->displayWidgets();
+}
diff --git a/src/frontend/qtwidgets/entryLayout.h b/src/frontend/qtwidgets/entryLayout.h
new file mode 100644
index 0000000..54e523f
--- /dev/null
+++ b/src/frontend/qtwidgets/entryLayout.h
@@ -0,0 +1,32 @@
+#ifndef ENTRYLAYOUT_H
+#define ENTRYLAYOUT_H
+
+#include <QDateTime>
+#include <QHBoxLayout>
+#include <QString>
+#include <QUrl>
+
+#include "entry.h"
+#include "rule.h"
+
+class EntryLayout : public QHBoxLayout {
+ Q_OBJECT
+
+ public:
+ Entry entry;
+
+ EntryLayout(const Entry &e);
+
+ private:
+ QList<Rule> loadRules();
+
+ private slots:
+ void showContextMenu();
+ void editEntry();
+ void setRules();
+ void toggleDone();
+ void cloneEntry();
+ void removeEntry();
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/group.cpp b/src/frontend/qtwidgets/group.cpp
new file mode 100644
index 0000000..ddb3b13
--- /dev/null
+++ b/src/frontend/qtwidgets/group.cpp
@@ -0,0 +1,14 @@
+#include <QList>
+
+#include "group.h"
+
+Group::Group(int id, QString name, Group::Column column, QString link, bool hidden) :
+ id(id),
+ name(name),
+ column(column),
+ link(link),
+ hidden(hidden)
+{
+}
+
+QList<Group> Group::groups;
diff --git a/src/frontend/qtwidgets/group.h b/src/frontend/qtwidgets/group.h
new file mode 100644
index 0000000..9c7bfa4
--- /dev/null
+++ b/src/frontend/qtwidgets/group.h
@@ -0,0 +1,25 @@
+#ifndef GROUP_H
+#define GROUP_H
+
+#include <QString>
+
+struct Group {
+ enum Column { left, right };
+ int id;
+ QString name;
+ Column column;
+ QString link;
+ bool hidden;
+
+ Group(
+ int id,
+ QString name,
+ Column column = left,
+ QString link = "",
+ bool hidden = false
+ );
+
+ static QList<Group> groups;
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/groupForm.ui b/src/frontend/qtwidgets/groupForm.ui
new file mode 100644
index 0000000..4f58e1d
--- /dev/null
+++ b/src/frontend/qtwidgets/groupForm.ui
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>groupDialog</class>
+ <widget class="QDialog" name="groupDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>172</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Group Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="title">
+ <property name="font">
+ <font>
+ <family>Arial</family>
+ <pointsize>18</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>[TITLE]</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="descriptionLabel">
+ <property name="text">
+ <string>Name:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="group_name"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="dueDateLabel">
+ <property name="text">
+ <string>Column:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="group_column">
+ <item>
+ <property name="text">
+ <string>Left</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Right</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="linkLabel">
+ <property name="text">
+ <string>Link:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="group_link"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ <property name="centerButtons">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>groupDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>groupDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/frontend/qtwidgets/groupLayout.cpp b/src/frontend/qtwidgets/groupLayout.cpp
new file mode 100644
index 0000000..e67d3a5
--- /dev/null
+++ b/src/frontend/qtwidgets/groupLayout.cpp
@@ -0,0 +1,68 @@
+#include <QLabel>
+#include <QMenu>
+#include <QString>
+
+#include <QDebug>
+
+#include "addEntryForm.h"
+#include "../../backend/db_sqlite.h"
+#include "editGroupForm.h"
+#include "groupLayout.h"
+#include "lib.h"
+
+GroupLayout::GroupLayout(const Group &g) :
+ group(g)
+{
+ this->setContentsMargins(0, 10, 0, 10);
+
+ QLabel *name_label = new QLabel(this->group.name);
+ name_label->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ name_label->setToolTip("Right-Click for actions");
+ name_label->setContextMenuPolicy(Qt::CustomContextMenu);
+ //name_label->customContextMenuRequested(const QPoint &pos)
+ QObject::connect(name_label,
+ SIGNAL(customContextMenuRequested(const QPoint &)),
+ SLOT(showContextMenu()));
+
+ QFont name_font = QFont("Arial", 13);
+ name_font.setUnderline(true);
+ name_label->setFont(name_font);
+
+ this->addWidget(name_label);
+}
+
+void GroupLayout::showContextMenu() {
+ QMenu menu;
+
+ QAction *add_entry_act = new QAction("Add Entry");
+ QObject::connect(add_entry_act, &QAction::triggered, this, &GroupLayout::addEntry);
+ menu.addAction(add_entry_act);
+
+ QAction *edit_group_act = new QAction("Edit Group");
+ QObject::connect(edit_group_act, &QAction::triggered, this, &GroupLayout::editGroup);
+ menu.addAction(edit_group_act);
+
+ QAction *del_group_act = new QAction("Remove Group");
+ QObject::connect(del_group_act, &QAction::triggered, this, &GroupLayout::removeGroup);
+ menu.addAction(del_group_act);
+
+ menu.exec(QCursor::pos());
+}
+
+void GroupLayout::addEntry() {
+ AddEntryForm create_new_entry_dialog(this->group);
+ if(create_new_entry_dialog.exec() == QDialog::Accepted)
+ getMainWindow()->displayWidgets();
+}
+
+void GroupLayout::editGroup() {
+ EditGroupForm edit_group_dialog(this->group);
+ if(edit_group_dialog.exec() == QDialog::Accepted)
+ getMainWindow()->displayWidgets();
+}
+
+void GroupLayout::removeGroup() {
+ BackendDB database;
+ if(database.removeGroup(this->group) > 0)
+ getMainWindow()->displayWidgets();
+}
diff --git a/src/frontend/qtwidgets/groupLayout.h b/src/frontend/qtwidgets/groupLayout.h
new file mode 100644
index 0000000..cfd33cf
--- /dev/null
+++ b/src/frontend/qtwidgets/groupLayout.h
@@ -0,0 +1,23 @@
+#ifndef GROUPLAYOUT_H
+#define GROUPLAYOUT_H
+
+#include <QVBoxLayout>
+
+#include "group.h"
+
+class GroupLayout : public QVBoxLayout {
+ Q_OBJECT
+
+ public:
+ Group group;
+
+ GroupLayout(const Group &g);
+
+ private slots:
+ void showContextMenu();
+ void addEntry();
+ void editGroup();
+ void removeGroup();
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/lib.cpp b/src/frontend/qtwidgets/lib.cpp
new file mode 100644
index 0000000..2290472
--- /dev/null
+++ b/src/frontend/qtwidgets/lib.cpp
@@ -0,0 +1,22 @@
+#include <QApplication>
+#include <QMainWindow>
+#include <QWidget>
+
+#include "lib.h"
+
+// shamelessly ripped from https://stackoverflow.com/a/46456214
+AssignmentList *getMainWindow() {
+ foreach(QWidget *w, QApplication::topLevelWidgets())
+ if(AssignmentList *mainWin = qobject_cast<AssignmentList*>(w))
+ return mainWin;
+ return nullptr;
+}
+
+void recursiveClear(QLayout *layout) {
+ QLayoutItem *child;
+ while((child = layout->takeAt(0)) != nullptr) {
+ if(child->layout()) recursiveClear(child->layout());
+ delete child->widget();
+ delete child;
+ }
+}
diff --git a/src/frontend/qtwidgets/lib.h b/src/frontend/qtwidgets/lib.h
new file mode 100644
index 0000000..14f63c1
--- /dev/null
+++ b/src/frontend/qtwidgets/lib.h
@@ -0,0 +1,12 @@
+/* Helper functions that various classes utilize */
+
+#include <QMainWindow>
+
+#include "assignmentList.h"
+
+AssignmentList *getMainWindow();
+
+QDialog *getParentDialog();
+
+// used to clear out a layout when a refresh occurs
+void recursiveClear(QLayout *layout);
diff --git a/src/frontend/qtwidgets/main.cpp b/src/frontend/qtwidgets/main.cpp
new file mode 100644
index 0000000..f7d69ee
--- /dev/null
+++ b/src/frontend/qtwidgets/main.cpp
@@ -0,0 +1,12 @@
+#include <QApplication>
+#include <QPushButton>
+
+#include "assignmentList.h"
+
+int main(int argc, char **argv) {
+ QApplication app (argc, argv);
+
+ AssignmentList window;
+
+ return app.exec();
+}
diff --git a/src/frontend/qtwidgets/preferencesDialog.cpp b/src/frontend/qtwidgets/preferencesDialog.cpp
new file mode 100644
index 0000000..d3b3dff
--- /dev/null
+++ b/src/frontend/qtwidgets/preferencesDialog.cpp
@@ -0,0 +1,59 @@
+#include <QFileDialog>
+#include <QSettings>
+
+#include "preferencesDialog.h"
+
+PreferencesDialog::PreferencesDialog() {
+ // load uic
+ ui.setupUi(this);
+
+ // display widgets
+ // TODO make this a scollable window
+ // FIXME could use some work on sizing
+ this->setupPathsTab();
+
+ // create button connections
+ QObject::connect(ui.close_button, &QPushButton::released, this, &PreferencesDialog::close);
+ QObject::connect(ui.apply_button, &QPushButton::released, this, &PreferencesDialog::apply);
+ QObject::connect(ui.reload_button, &QPushButton::released, this, &PreferencesDialog::reload);
+}
+
+void PreferencesDialog::setupPathsTab() {
+ QSettings settings;
+ settings.beginGroup("paths");
+
+ ui.db_path_edit->setText(settings.value("db_path").toString());
+ QObject::connect(ui.db_path_button, &QPushButton::released, this, &PreferencesDialog::dbPathDialog);
+}
+
+void PreferencesDialog::dbPathDialog() {
+ QFileDialog file_dialog;
+ // TODO create filter to only allow selecting .db files
+ QString new_path = file_dialog.getOpenFileName(this, "Open File");
+
+ if(!new_path.isEmpty())
+ ui.db_path_edit->setText(new_path);
+}
+
+// close the dialog, returning our ret_value
+void PreferencesDialog::close() {
+ this->done(ret_value);
+}
+
+// update the configuration in the config file
+void PreferencesDialog::apply() {
+ QSettings settings;
+
+ // save paths
+ settings.beginGroup("paths");
+ settings.setValue("db_path", ui.db_path_edit->text());
+
+ // set the return value to accepted
+ this->ret_value = QDialog::Accepted;
+}
+
+// update, reload, and close the window
+void PreferencesDialog::reload() {
+ this->apply();
+ this->accept();
+}
diff --git a/src/frontend/qtwidgets/preferencesDialog.h b/src/frontend/qtwidgets/preferencesDialog.h
new file mode 100644
index 0000000..ac9b88e
--- /dev/null
+++ b/src/frontend/qtwidgets/preferencesDialog.h
@@ -0,0 +1,28 @@
+#ifndef PREFERENCESDIALOG_H
+#define PREFERENCESDIALOG_H
+
+#include <QDialog>
+
+#include "ui_preferencesDialog.h"
+
+// set configuration options in the program
+class PreferencesDialog : public QDialog {
+ Q_OBJECT
+
+ public:
+ PreferencesDialog();
+
+ private:
+ Ui::preferencesDialog ui;
+ QDialog::DialogCode ret_value = QDialog::Rejected;
+
+ void setupPathsTab();
+
+ private slots:
+ void dbPathDialog();
+ void close();
+ void apply();
+ void reload();
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/preferencesDialog.ui b/src/frontend/qtwidgets/preferencesDialog.ui
new file mode 100644
index 0000000..343ba33
--- /dev/null
+++ b/src/frontend/qtwidgets/preferencesDialog.ui
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>preferencesDialog</class>
+ <widget class="QDialog" name="preferencesDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>320</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Preferences</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QVBoxLayout" name="main_layout">
+ <item>
+ <widget class="QTabWidget" name="tab_bar">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>Paths</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QFormLayout" name="paths_tab_layout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="databaseFileLabel">
+ <property name="text">
+ <string>Database File:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QWidget" name="db_path_hbox" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="db_path_edit"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="db_path_button">
+ <property name="maximumSize">
+ <size>
+ <width>25</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="buttons_hbox">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="close_button">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="apply_button">
+ <property name="text">
+ <string>Apply</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="reload_button">
+ <property name="text">
+ <string>Reload</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/frontend/qtwidgets/rule.cpp b/src/frontend/qtwidgets/rule.cpp
new file mode 100644
index 0000000..85da13a
--- /dev/null
+++ b/src/frontend/qtwidgets/rule.cpp
@@ -0,0 +1,15 @@
+#include <QList>
+
+#include "rule.h"
+
+Rule::Rule(int id, int entry_id, When when, QDateTime date, QString color, QString highlight) :
+ id(id),
+ entry_id(entry_id),
+ when(when),
+ date(date),
+ color(color),
+ highlight(highlight)
+{
+}
+
+QList<Rule> Rule::rules;
diff --git a/src/frontend/qtwidgets/rule.h b/src/frontend/qtwidgets/rule.h
new file mode 100644
index 0000000..cf9abfe
--- /dev/null
+++ b/src/frontend/qtwidgets/rule.h
@@ -0,0 +1,28 @@
+#ifndef RULE_H
+#define RULE_H
+
+#include <QDateTime>
+#include <QString>
+
+struct Rule {
+ enum When { before, after };
+ int id;
+ int entry_id;
+ When when;
+ QDateTime date;
+ QString color; // consider making this a QColor instead
+ QString highlight; // see color comment
+
+ Rule(
+ int id,
+ int entry_id,
+ When when,
+ QDateTime date = QDateTime::currentDateTime(),
+ QString color = "",
+ QString highlight = ""
+ );
+
+ static QList<Rule> rules;
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/ruleLayout.cpp b/src/frontend/qtwidgets/ruleLayout.cpp
new file mode 100644
index 0000000..11abf80
--- /dev/null
+++ b/src/frontend/qtwidgets/ruleLayout.cpp
@@ -0,0 +1,40 @@
+
+#include "lib.h"
+#include "ruleLayout.h"
+
+RuleLayout::RuleLayout(const Rule &r) :
+ rule(r)
+{
+ this->when_widget = new QComboBox;
+ this->date_widget = new QDateTimeEdit(QDate::currentDate());
+ this->color_widget = new QLineEdit;
+ this->highlight_widget = new QLineEdit;
+ this->del_button = new QPushButton;
+
+ QStringList when_options;
+ when_options.append("Before");
+ when_options.append("After");
+ this->when_widget->addItems(when_options);
+ this->when_widget->setCurrentIndex(this->rule.when);
+ this->addWidget(this->when_widget);
+
+ this->date_widget->setDisplayFormat("MM/dd/yyyy");
+ this->date_widget->setDateTime(this->rule.date);
+ this->addWidget(this->date_widget);
+
+ this->addStretch();
+
+ this->color_widget->setPlaceholderText("Color");
+ if(!this->rule.color.isEmpty())
+ this->color_widget->setText(this->rule.color);
+ this->addWidget(this->color_widget);
+
+ this->highlight_widget->setPlaceholderText("Highlight");
+ if(!this->rule.highlight.isEmpty())
+ this->highlight_widget->setText(this->rule.highlight);
+ this->addWidget(this->highlight_widget);
+
+ this->del_button->setText("Delete");
+ // connection needs to be made in rulesDialog.cpp
+ this->addWidget(this->del_button);
+}
diff --git a/src/frontend/qtwidgets/ruleLayout.h b/src/frontend/qtwidgets/ruleLayout.h
new file mode 100644
index 0000000..29a4deb
--- /dev/null
+++ b/src/frontend/qtwidgets/ruleLayout.h
@@ -0,0 +1,27 @@
+#ifndef RULELAYOUT_H
+#define RULELAYOUT_H
+
+#include <QComboBox>
+#include <QDate>
+#include <QDateTimeEdit>
+#include <QHBoxLayout>
+#include <QLineEdit>
+#include <QPushButton>
+
+#include "rule.h"
+
+class RuleLayout : public QHBoxLayout {
+ Q_OBJECT
+
+ public:
+ Rule rule;
+ QComboBox *when_widget;
+ QDateTimeEdit *date_widget;
+ QLineEdit *color_widget; // TODO consider making this a color selector widget
+ QLineEdit *highlight_widget; // TODO consider making this a color selector widget
+ QPushButton *del_button;
+
+ RuleLayout(const Rule &r);
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/rulesDialog.cpp b/src/frontend/qtwidgets/rulesDialog.cpp
new file mode 100644
index 0000000..cd62342
--- /dev/null
+++ b/src/frontend/qtwidgets/rulesDialog.cpp
@@ -0,0 +1,107 @@
+#include <QPushButton>
+#include <QScrollArea>
+#include <QVBoxLayout>
+
+#include <QDebug>
+
+#include "../../backend/db_sqlite.h"
+#include "lib.h"
+#include "ruleLayout.h"
+#include "rulesDialog.h"
+
+RulesDialog::RulesDialog(const Entry &entry) {
+ BackendDB database;
+
+ this->entry_id = entry.id;
+
+ // load uic
+ ui.setupUi(this);
+
+ // load rules into object member
+ this->rules = database.loadRules(entry_id);
+
+ // display widgets
+ this->drawRules();
+
+ // set connections
+ QObject::connect(ui.add_rule_button, &QPushButton::released, this, &RulesDialog::addRule);
+}
+
+// for maintaining values when adding/removing rules
+void RulesDialog::updateRulesList() {
+ int i;
+
+ for(i = 0; i < ui.rules_layout->children().size(); ++i) {
+ this->rules[i].when = Rule::When(qobject_cast<RuleLayout *>(ui.rules_layout->children()[i])->when_widget->currentIndex());
+ this->rules[i].date = qobject_cast<RuleLayout *>(ui.rules_layout->children()[i])->date_widget->dateTime();
+ this->rules[i].color = qobject_cast<RuleLayout *>(ui.rules_layout->children()[i])->color_widget->text();
+ this->rules[i].highlight = qobject_cast<RuleLayout *>(ui.rules_layout->children()[i])->highlight_widget->text();
+ }
+}
+
+void RulesDialog::drawRules() {
+ RuleLayout *new_layout;
+ int i;
+
+
+ // remove all children from layout
+ recursiveClear(ui.rules_layout);
+
+ // Draw each rule
+ for(i = 0; i < this->rules.size(); ++i) {
+ new_layout = new RuleLayout(rules[i]);
+
+ // connect delete button
+ QObject::connect(new_layout->del_button, &QPushButton::released, this, [=](){ this->deleteRule(i); });
+
+ ui.rules_layout->addLayout(new_layout);
+ }
+}
+
+void RulesDialog::addRule() {
+ Rule new_rule = Rule(0, this->entry_id, Rule::before);
+ RuleLayout *new_layout = new RuleLayout(new_rule);
+
+ // add new rule to the member variable
+ this->rules.append(new_rule);
+
+ // redraw rules
+ this->updateRulesList();
+ this->drawRules();
+}
+
+void RulesDialog::deleteRule(int i) {
+ if(i < 0) return;
+ if(i >= this->rules.size()) return;
+
+ this->updateRulesList();
+ // mark as needing to be deleted from database if id > 0
+ if(this->rules[i].id > 0)
+ this->deleted_rules.append(this->rules[i]);
+ this->rules.removeAt(i);
+ this->drawRules();
+}
+
+void RulesDialog::accept() {
+ int i;
+ BackendDB database;
+
+ this->updateRulesList();
+
+ // update rules in database
+ for(i = 0; i < this->rules.size(); ++i) {
+ // insert - this is a new rule
+ if(this->rules[i].id == 0)
+ database.insertRule(this->rules[i]);
+ // update - this is an existing rule
+ else
+ database.updateRule(this->rules[i]);
+ }
+
+ // delete deleted rules in database
+ for(i = 0; i < this->deleted_rules.size(); ++i) {
+ database.removeRule(this->deleted_rules[i]);
+ }
+
+ QDialog::accept();
+}
diff --git a/src/frontend/qtwidgets/rulesDialog.h b/src/frontend/qtwidgets/rulesDialog.h
new file mode 100644
index 0000000..7e74dc2
--- /dev/null
+++ b/src/frontend/qtwidgets/rulesDialog.h
@@ -0,0 +1,32 @@
+#ifndef RULESDIALOG_H
+#define RULESDIALOG_H
+
+#include <QDialog>
+
+#include "entry.h"
+#include "rule.h"
+#include "ui_rulesDialog.h"
+
+// show the list of rules associated with an entry
+class RulesDialog : public QDialog {
+ Q_OBJECT
+
+ public:
+ RulesDialog(const Entry &entry);
+ void drawRules();
+
+ private:
+ Ui::rulesDialog ui;
+ int entry_id;
+ QList<Rule> rules;
+ QList<Rule> deleted_rules;
+
+ void updateRulesList();
+
+ private slots:
+ void addRule();
+ void deleteRule(int i);
+ void accept();
+};
+
+#endif
diff --git a/src/frontend/qtwidgets/rulesDialog.ui b/src/frontend/qtwidgets/rulesDialog.ui
new file mode 100644
index 0000000..c362887
--- /dev/null
+++ b/src/frontend/qtwidgets/rulesDialog.ui
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>rulesDialog</class>
+ <widget class="QDialog" name="rulesDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>320</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>480</width>
+ <height>269</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QVBoxLayout" name="rules_layout"/>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="rules_button_box">
+ <item>
+ <widget class="QPushButton" name="add_rule_button">
+ <property name="text">
+ <string>Add Rule</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>rulesDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>rulesDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>