From d53a5826a891f03d6af0488b30dee2cb3c99f09a Mon Sep 17 00:00:00 2001 From: Alain Date: Tue, 22 Feb 2022 23:51:10 -0500 Subject: [PATCH] Add Shortcuts --- com.github.alainm23.planner.yml | 1 + ...com.github.alainm23.planner.appdata.xml.in | 2 +- .../com.github.alainm23.planner.gresource.xml | 4 + data/com.github.alainm23.planner.gschema.xml | 9 +- data/resources/planner-bell-dark.svg | 51 +++ data/resources/planner-bell-light.svg | 51 +++ data/resources/settings/planner-home.svg | 51 +++ .../settings/planner-notification.svg | 51 +++ data/stylesheet/sidebar.css | 16 + data/stylesheet/stylesheet.css | 13 +- meson.build | 15 +- po/com.github.alainm23.planner.pot | 258 ++++++++------ po/es.po | 260 ++++++++------ src/Application.vala | 9 +- .../ContextMenu/MenuCalendarPicker.vala | 2 +- .../DateTimePicker/DateTimePicker.vala | 24 +- src/Dialogs/DateTimePicker/TimePicker.vala | 35 +- src/Dialogs/LabelPicker/LabelPicker.vala | 71 +++- src/Dialogs/LabelPicker/LabelRow.vala | 8 +- src/Dialogs/MessageDialog.vala | 1 - src/Dialogs/ProjectPicker/ProjectPicker.vala | 54 ++- src/Dialogs/ProjectPicker/ProjectRow.vala | 4 +- src/Dialogs/QuickFind/QuickFind.vala | 37 +- src/Dialogs/QuickFind/QuickFindItem.vala | 39 +++ .../ReminderPicker/ReminderPicker.vala | 319 ++++++++++++++++++ src/Dialogs/ReminderPicker/ReminderRow.vala | 100 ++++++ src/Dialogs/Settings/Settings.vala | 232 +++++++++++++ src/Dialogs/Settings/SettingsContent.vala | 8 +- src/Dialogs/Shortcuts/ShortcutLabel.vala | 98 ++++++ src/Dialogs/Shortcuts/Shortcuts.vala | 185 ++++++++++ src/Layouts/FilterPaneRow.vala | 46 +-- src/Layouts/HeaderItem.vala | 18 + src/Layouts/ItemRow.vala | 137 ++++++-- src/Layouts/ProjectRow.vala | 2 +- src/Layouts/SectionRow.vala | 254 +++++++++++--- src/Layouts/Sidebar.vala | 2 + src/Layouts/ViewHeader.vala | 1 + src/MainWindow.vala | 56 ++- src/Objects/BaseObject.vala | 52 ++- src/Objects/Item.vala | 73 +++- src/Objects/Label.vala | 3 +- src/Objects/Pinboard.vala | 45 +++ src/Objects/Project.vala | 20 +- src/Objects/Reminder.vala | 80 +++++ src/Objects/Scheduled.vala | 46 +++ src/Objects/Section.vala | 35 +- src/Objects/Shortcut.vala | 0 src/Objects/Today.vala | 68 ++++ src/Services/ActionManager.vala | 21 +- src/Services/Badge.vala | 58 ++-- src/Services/Database.vala | 271 ++++++++++++++- src/Services/EventBus.vala | 2 +- src/Services/Notification.vala | 133 ++++++++ src/Services/Todoist.vala | 53 ++- src/Util.vala | 82 ++++- src/Views/Date.vala | 17 + src/Views/List.vala | 85 ++++- src/Views/Pinboard.vala | 6 + src/Widgets/Calendar/Calendar.vala | 14 +- src/Widgets/EditableLabel.vala | 24 +- src/Widgets/ItemSummary.vala | 2 +- src/Widgets/LoadingButton.vala | 10 +- src/Widgets/ReminderButton.vala | 41 +++ src/Widgets/SubItems.vala | 253 ++++++++++++++ src/Widgets/TopHeaderProject.vala | 4 +- 65 files changed, 3541 insertions(+), 481 deletions(-) create mode 100644 data/resources/planner-bell-dark.svg create mode 100644 data/resources/planner-bell-light.svg create mode 100644 data/resources/settings/planner-home.svg create mode 100644 data/resources/settings/planner-notification.svg create mode 100644 src/Dialogs/ReminderPicker/ReminderPicker.vala create mode 100644 src/Dialogs/ReminderPicker/ReminderRow.vala create mode 100644 src/Dialogs/Shortcuts/ShortcutLabel.vala create mode 100644 src/Dialogs/Shortcuts/Shortcuts.vala create mode 100644 src/Objects/Pinboard.vala create mode 100644 src/Objects/Reminder.vala create mode 100644 src/Objects/Scheduled.vala create mode 100644 src/Objects/Shortcut.vala create mode 100644 src/Objects/Today.vala create mode 100644 src/Services/Notification.vala create mode 100644 src/Widgets/ReminderButton.vala create mode 100644 src/Widgets/SubItems.vala diff --git a/com.github.alainm23.planner.yml b/com.github.alainm23.planner.yml index 6ec34dcce..8cc0a83f3 100644 --- a/com.github.alainm23.planner.yml +++ b/com.github.alainm23.planner.yml @@ -9,6 +9,7 @@ finish-args: - "--share=network" - "--socket=fallback-x11" - "--socket=wayland" + - "--filesystem=home" - "--metadata=X-DConf=migrate-path=/com/github/alainm23/planner/" cleanup: diff --git a/data/com.github.alainm23.planner.appdata.xml.in b/data/com.github.alainm23.planner.appdata.xml.in index 70f580038..425df3751 100644 --- a/data/com.github.alainm23.planner.appdata.xml.in +++ b/data/com.github.alainm23.planner.appdata.xml.in @@ -35,7 +35,7 @@ com.github.alainm23.planner - +

Planner 3.0 Beta1 is here...

    diff --git a/data/com.github.alainm23.planner.gresource.xml b/data/com.github.alainm23.planner.gresource.xml index 012c1dce7..e2ae0636f 100644 --- a/data/com.github.alainm23.planner.gresource.xml +++ b/data/com.github.alainm23.planner.gresource.xml @@ -31,6 +31,8 @@ resources/settings/planner-heart.svg resources/settings/planner-mail.svg resources/settings/planner-appearance.svg + resources/settings/planner-notification.svg + resources/settings/planner-home.svg resources/planner-calendar-light.svg resources/planner-calendar-dark.svg @@ -69,5 +71,7 @@ resources/planner-pinned-activated.svg resources/planner-settings-dark.svg resources/planner-settings-light.svg + resources/planner-bell-light.svg + resources/planner-bell-dark.svg diff --git a/data/com.github.alainm23.planner.gschema.xml b/data/com.github.alainm23.planner.gschema.xml index d52a9143d..436bf9689 100644 --- a/data/com.github.alainm23.planner.gschema.xml +++ b/data/com.github.alainm23.planner.gschema.xml @@ -10,9 +10,10 @@ - - - + + + + @@ -290,7 +291,7 @@ - '0.0.0' + '3.0' The currently installed version. The currently installed version of Planner, updated only after the user launched the app. This is used to know when to trigger the Release Dialog. diff --git a/data/resources/planner-bell-dark.svg b/data/resources/planner-bell-dark.svg new file mode 100644 index 000000000..1a9b5936a --- /dev/null +++ b/data/resources/planner-bell-dark.svg @@ -0,0 +1,51 @@ + + + + + + + diff --git a/data/resources/planner-bell-light.svg b/data/resources/planner-bell-light.svg new file mode 100644 index 000000000..ec697ce33 --- /dev/null +++ b/data/resources/planner-bell-light.svg @@ -0,0 +1,51 @@ + + + + + + + diff --git a/data/resources/settings/planner-home.svg b/data/resources/settings/planner-home.svg new file mode 100644 index 000000000..06bc5da95 --- /dev/null +++ b/data/resources/settings/planner-home.svg @@ -0,0 +1,51 @@ + + + + + + + diff --git a/data/resources/settings/planner-notification.svg b/data/resources/settings/planner-notification.svg new file mode 100644 index 000000000..24e21785f --- /dev/null +++ b/data/resources/settings/planner-notification.svg @@ -0,0 +1,51 @@ + + + + + + + diff --git a/data/stylesheet/sidebar.css b/data/stylesheet/sidebar.css index 949d767d2..9d8be30f2 100644 --- a/data/stylesheet/sidebar.css +++ b/data/stylesheet/sidebar.css @@ -50,6 +50,10 @@ color: @color_primary; } +.filter-pane-row-inbox:selected image { + animation: fancy-turn 0.7s ease-in-out; +} + .filter-pane-row-inbox-selected { box-shadow: 0px 0px 3px 1px @color_primary; } @@ -62,6 +66,10 @@ color: #fad000; } +.filter-pane-row-today:selected image { + animation: fancy-turn 0.7s ease-in-out; +} + .filter-pane-row-today-selected { box-shadow: 0px 0px 3px 1px #fad000; } @@ -78,6 +86,10 @@ box-shadow: 0px 0px 3px 1px #fa1955; } +.filter-pane-row-scheduled:selected image { + animation: fancy-turn 0.7s ease-in-out; +} + .padding-3 { padding: 3px; } @@ -92,4 +104,8 @@ .filter-pane-row-pinboard-selected { box-shadow: 0px 0px 3px 1px #7e8087; +} + +.filter-pane-row-pinboard:selected image { + animation: fancy-turn 0.7s ease-in-out; } \ No newline at end of file diff --git a/data/stylesheet/stylesheet.css b/data/stylesheet/stylesheet.css index 716d133df..1da247e5f 100644 --- a/data/stylesheet/stylesheet.css +++ b/data/stylesheet/stylesheet.css @@ -191,8 +191,19 @@ button:disabled { font-weight: 500; } -.card { +.mt-12 { margin-top: 24px; +} + +.mt-24 { + margin-top: 24px; +} + +.mb-12 { + margin-bottom: 12px; +} + +.card { border-radius: 6px; padding: 9px 9px 6px 9px; background-color: @item_bg_color; diff --git a/meson.build b/meson.build index f41b580cd..03f77ef97 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project ( 'com.github.alainm23.planner', 'vala', 'c', - version: '3.0.4' + version: '3.0.6' ) gnome = import ('gnome') @@ -89,6 +89,8 @@ executable ( 'src/Widgets/ItemLabels.vala', 'src/Widgets/ItemLabelChild.vala', 'src/Widgets/Placeholder.vala', + 'src/Widgets/SubItems.vala', + 'src/Widgets/ReminderButton.vala', 'src/Widgets/Calendar/Calendar.vala', 'src/Widgets/Calendar/CalendarDay.vala', @@ -115,6 +117,9 @@ executable ( 'src/Dialogs/QuickFind/QuickFind.vala', 'src/Dialogs/QuickFind/QuickFindItem.vala', 'src/Dialogs/QuickFind/Synapse.vala', + + 'src/Dialogs/ReminderPicker/ReminderPicker.vala', + 'src/Dialogs/ReminderPicker/ReminderRow.vala', 'src/Dialogs/Settings/Settings.vala', 'src/Dialogs/Settings/SettingsHeader.vala', @@ -138,11 +143,15 @@ executable ( 'src/Dialogs/ContextMenu/MenuCalendarPicker.vala', 'src/Dialogs/ContextMenu/MenuSwitch.vala', + 'src/Dialogs/Shortcuts/Shortcuts.vala', + 'src/Dialogs/Shortcuts/ShortcutLabel.vala', + 'src/Services/EventBus.vala', 'src/Services/Database.vala', 'src/Services/Todoist.vala', 'src/Services/ActionManager.vala', 'src/Services/Badge.vala', + 'src/Services/Notification.vala', 'src/Objects/v2.vala', 'src/Objects/BaseObject.vala', @@ -153,6 +162,10 @@ executable ( 'src/Objects/Color.vala', 'src/Objects/DueDate.vala', 'src/Objects/ItemLabel.vala', + 'src/Objects/Today.vala', + 'src/Objects/Pinboard.vala', + 'src/Objects/Scheduled.vala', + 'src/Objects/Reminder.vala', gresource, config_file, dependencies: planner_deps, diff --git a/po/com.github.alainm23.planner.pot b/po/com.github.alainm23.planner.pot index a3224f508..578e3f6ab 100644 --- a/po/com.github.alainm23.planner.pot +++ b/po/com.github.alainm23.planner.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: com.github.alainm23.planner\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-10 04:56-0500\n" +"POT-Creation-Date: 2022-02-17 06:59-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,175 +17,224 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/Util.vala:97 src/Layouts/HeaderItem.vala:186 -#: src/Dialogs/ProjectPicker/ProjectPicker.vala:88 +#: src/Util.vala:65 src/Util.vala:389 src/Util.vala:551 +#: src/Layouts/FilterPaneRow.vala:83 src/Views/Today.vala:11 +#: src/Views/Date.vala:68 src/Views/Date.vala:101 +#: src/Dialogs/DateTimePicker/DateTimePicker.vala:105 +msgid "Today" +msgstr "" + +#: src/Util.vala:68 src/Util.vala:386 src/MainWindow.vala:327 +#: src/Layouts/FilterPaneRow.vala:86 src/Widgets/TopHeaderProject.vala:55 +#: src/Dialogs/ProjectPicker/ProjectRow.vala:105 +msgid "Inbox" +msgstr "" + +#: src/Util.vala:71 src/Layouts/FilterPaneRow.vala:89 src/Views/Date.vala:101 +#: src/Views/Scheduled/Scheduled.vala:19 +msgid "Scheduled" +msgstr "" + +#: src/Util.vala:74 src/Layouts/FilterPaneRow.vala:92 +#: src/Views/Pinboard.vala:20 src/Views/Pinboard.vala:77 +msgid "Pinboard" +msgstr "" + +#: src/Util.vala:117 src/Layouts/HeaderItem.vala:186 +#: src/Dialogs/ProjectPicker/ProjectPicker.vala:92 msgid "Projects" msgstr "" -#: src/Util.vala:100 +#: src/Util.vala:120 msgid "Setions" msgstr "" -#: src/Util.vala:103 +#: src/Util.vala:123 msgid "Items" msgstr "" -#: src/Util.vala:106 src/Layouts/HeaderItem.vala:190 -#: src/Dialogs/LabelPicker/LabelPicker.vala:75 +#: src/Util.vala:126 src/Layouts/HeaderItem.vala:190 +#: src/Dialogs/LabelPicker/LabelPicker.vala:77 msgid "Labels" msgstr "" -#: src/Util.vala:140 +#: src/Util.vala:129 +msgid "Filters" +msgstr "" + +#: src/Util.vala:168 msgid "Berry Red" msgstr "" -#: src/Util.vala:141 +#: src/Util.vala:169 msgid "Red" msgstr "" -#: src/Util.vala:142 +#: src/Util.vala:170 msgid "Orange" msgstr "" -#: src/Util.vala:143 +#: src/Util.vala:171 msgid "Olive Green" msgstr "" -#: src/Util.vala:144 +#: src/Util.vala:172 msgid "Yellow" msgstr "" -#: src/Util.vala:145 +#: src/Util.vala:173 msgid "Lime Green" msgstr "" -#: src/Util.vala:146 +#: src/Util.vala:174 msgid "Green" msgstr "" -#: src/Util.vala:147 +#: src/Util.vala:175 msgid "Mint Green" msgstr "" -#: src/Util.vala:148 +#: src/Util.vala:176 msgid "Teal" msgstr "" -#: src/Util.vala:149 +#: src/Util.vala:177 msgid "Sky Blue" msgstr "" -#: src/Util.vala:150 +#: src/Util.vala:178 msgid "Light Blue" msgstr "" -#: src/Util.vala:151 +#: src/Util.vala:179 msgid "Blue" msgstr "" -#: src/Util.vala:152 +#: src/Util.vala:180 msgid "Grape" msgstr "" -#: src/Util.vala:153 +#: src/Util.vala:181 msgid "Violet" msgstr "" -#: src/Util.vala:154 +#: src/Util.vala:182 msgid "Lavander" msgstr "" -#: src/Util.vala:155 +#: src/Util.vala:183 msgid "Magenta" msgstr "" -#: src/Util.vala:156 +#: src/Util.vala:184 msgid "Salmon" msgstr "" -#: src/Util.vala:157 +#: src/Util.vala:185 msgid "Charcoal" msgstr "" -#: src/Util.vala:158 +#: src/Util.vala:186 msgid "Grey" msgstr "" -#: src/Util.vala:159 +#: src/Util.vala:187 msgid "Taupe" msgstr "" -#: src/Util.vala:336 +#: src/Util.vala:364 msgid "Light" msgstr "" -#: src/Util.vala:339 +#: src/Util.vala:367 msgid "Dark" msgstr "" -#: src/Util.vala:342 +#: src/Util.vala:370 msgid "Dark Blue" msgstr "" -#: src/Util.vala:501 src/Layouts/FilterPaneRow.vala:83 src/Views/Today.vala:11 -#: src/Views/Date.vala:68 src/Views/Date.vala:101 -#: src/Dialogs/DateTimePicker/DateTimePicker.vala:105 -msgid "Today" +#: src/Util.vala:383 +msgid "None" msgstr "" -#: src/Util.vala:503 src/Dialogs/DateTimePicker/DateTimePicker.vala:108 +#: src/Util.vala:392 +msgid "Today + Inbox" +msgstr "" + +#: src/Util.vala:553 src/Dialogs/DateTimePicker/DateTimePicker.vala:108 msgid "Tomorrow" msgstr "" -#: src/Util.vala:505 +#: src/Util.vala:555 msgid "Yesterday" msgstr "" -#: src/Application.vala:134 -msgid "Are you sure you want to reset all?" +#: src/Util.vala:788 src/Util.vala:1212 src/Layouts/ItemRow.vala:282 +#: src/Dialogs/Project.vala:214 src/Dialogs/Label.vala:152 +#: src/Dialogs/ProjectPicker/ProjectPicker.vala:85 +#: src/Dialogs/DateTimePicker/DateTimePicker.vala:83 +#: src/Objects/Project.vala:416 src/Objects/Label.vala:195 +#: src/Objects/Section.vala:213 +msgid "Cancel" +msgstr "" + +#: src/Util.vala:789 +msgid "Reset all" +msgstr "" + +#: src/Util.vala:828 +msgid "Welcome to Planner 3" msgstr "" -#: src/Application.vala:135 +#: src/Util.vala:829 msgid "" -"It process removes all stored information without the possibility of undoing " -"it." +"We have detected that you have a Planner 2 configuration started, currently " +"the v3 database is not compatible with v2, if you wish you can download a " +"backup in JSON format and migrate your data manually, or you can start with " +"v3 with a new configuration." msgstr "" -#: src/Application.vala:141 src/Layouts/ItemRow.vala:282 -#: src/Dialogs/Project.vala:214 src/Dialogs/Label.vala:152 -#: src/Dialogs/ProjectPicker/ProjectPicker.vala:81 -#: src/Dialogs/DateTimePicker/DateTimePicker.vala:83 -#: src/Objects/Project.vala:410 src/Objects/Label.vala:196 -#: src/Objects/Section.vala:214 -msgid "Cancel" +#: src/Util.vala:835 +msgid "Create backup" msgstr "" -#: src/Application.vala:142 -msgid "Reset all" +#: src/Util.vala:836 +msgid "Starting over" msgstr "" -#: src/MainWindow.vala:36 src/Views/Welcome.vala:27 -msgid "Planner" +#: src/Util.vala:1209 +msgid "Save backup file" msgstr "" -#: src/MainWindow.vala:270 src/Layouts/FilterPaneRow.vala:86 -#: src/Widgets/TopHeaderProject.vala:55 -#: src/Dialogs/ProjectPicker/ProjectRow.vala:103 -msgid "Inbox" +#: src/Util.vala:1211 +msgid "Save" msgstr "" -#: src/Layouts/Sidebar.vala:59 -msgid "Settings" +#: src/Util.vala:1235 +msgid "JSON files" msgstr "" -#: src/Layouts/FilterPaneRow.vala:89 src/Views/Date.vala:101 -#: src/Views/Scheduled/Scheduled.vala:19 -msgid "Scheduled" +#: src/Util.vala:1240 +msgid "All files" msgstr "" -#: src/Layouts/FilterPaneRow.vala:92 src/Views/Pinboard.vala:20 -#: src/Views/Pinboard.vala:77 -msgid "Pinboard" +#: src/Application.vala:142 +msgid "Are you sure you want to reset all?" +msgstr "" + +#: src/Application.vala:143 +msgid "" +"It process removes all stored information without the possibility of undoing " +"it." +msgstr "" + +#: src/MainWindow.vala:36 src/Views/Welcome.vala:27 +msgid "Planner" +msgstr "" + +#: src/Layouts/Sidebar.vala:59 +msgid "Settings" msgstr "" #: src/Layouts/HeaderItem.vala:187 src/Dialogs/Project.vala:208 @@ -210,7 +259,7 @@ msgstr "" msgid "Favorites" msgstr "" -#: src/Layouts/LabelRow.vala:155 src/Objects/Label.vala:192 +#: src/Layouts/LabelRow.vala:155 src/Objects/Label.vala:191 msgid "Delete label" msgstr "" @@ -222,25 +271,25 @@ msgstr "" msgid "Add Task" msgstr "" -#: src/Layouts/ItemRow.vala:408 +#: src/Layouts/ItemRow.vala:407 msgid "Task name" msgstr "" -#: src/Layouts/ItemRow.vala:962 src/Widgets/ScheduleButton.vala:27 +#: src/Layouts/ItemRow.vala:971 src/Widgets/ScheduleButton.vala:27 #: src/Widgets/ScheduleButton.vala:62 #: src/Dialogs/DateTimePicker/DateTimePicker.vala:91 msgid "Schedule" msgstr "" -#: src/Layouts/ItemRow.vala:963 +#: src/Layouts/ItemRow.vala:972 msgid "Complete" msgstr "" -#: src/Layouts/ItemRow.vala:964 +#: src/Layouts/ItemRow.vala:973 msgid "Edit" msgstr "" -#: src/Layouts/ItemRow.vala:966 +#: src/Layouts/ItemRow.vala:975 msgid "Delete task" msgstr "" @@ -249,8 +298,8 @@ msgstr "" msgid "Share" msgstr "" -#: src/Layouts/ProjectRow.vala:465 src/Widgets/TopHeaderProject.vala:155 -#: src/Objects/Project.vala:406 +#: src/Layouts/ProjectRow.vala:465 src/Objects/Project.vala:412 +#: src/Objects/Project.vala:519 msgid "Delete project" msgstr "" @@ -262,37 +311,34 @@ msgstr "" msgid "Email" msgstr "" -#: src/Layouts/SectionRow.vala:71 +#: src/Layouts/SectionRow.vala:75 msgid "(No Section)" msgstr "" -#: src/Layouts/SectionRow.vala:85 src/Widgets/TopHeaderProject.vala:188 +#: src/Layouts/SectionRow.vala:89 src/Objects/Project.vala:571 msgid "New section" msgstr "" -#: src/Layouts/SectionRow.vala:482 -msgid "Edit section" -msgstr "" - -#. var move_item = new Dialogs.ContextMenu.MenuItemSelector (_("Move section"), true); -#: src/Layouts/SectionRow.vala:484 src/Objects/Section.vala:210 -msgid "Delete section" +#: src/Layouts/SectionRow.vala:531 +msgid "Add task" msgstr "" -#: src/Layouts/SectionRow.vala:524 -msgid "No tasks available. Create one by clicking on the '+' button" +#: src/Layouts/SectionRow.vala:532 +msgid "Edit section" msgstr "" -#: src/Widgets/TopHeaderProject.vala:149 -msgid "Edit project" +#: src/Layouts/SectionRow.vala:533 +msgid "Move section" msgstr "" -#: src/Widgets/TopHeaderProject.vala:150 -msgid "Add section" +#: src/Layouts/SectionRow.vala:534 src/Objects/Section.vala:209 +msgid "Delete section" msgstr "" -#: src/Widgets/TopHeaderProject.vala:153 -msgid "Show completed" +#: src/Layouts/SectionRow.vala:595 +msgid "" +"No tasks available. Create one by dragging the '+' button here or clicking " +"on this space." msgstr "" #: src/Widgets/PriorityButton.vala:35 @@ -369,7 +415,7 @@ msgstr "" msgid "Synchronize with your Todoist Account." msgstr "" -#: src/Views/List.vala:39 +#: src/Views/List.vala:46 msgid "What will you accomplish?" msgstr "" @@ -442,18 +488,18 @@ msgstr "" msgid "Update label" msgstr "" -#: src/Dialogs/ProjectPicker/ProjectPicker.vala:73 +#: src/Dialogs/ProjectPicker/ProjectPicker.vala:77 #: src/Dialogs/DateTimePicker/DateTimePicker.vala:74 -#: src/Dialogs/LabelPicker/LabelPicker.vala:58 +#: src/Dialogs/LabelPicker/LabelPicker.vala:60 msgid "Done" msgstr "" -#: src/Dialogs/ProjectPicker/ProjectPicker.vala:99 +#: src/Dialogs/ProjectPicker/ProjectPicker.vala:103 msgid "Type a project" msgstr "" #: src/Dialogs/DateTimePicker/DateTimePicker.vala:46 -#: src/Dialogs/LabelPicker/LabelPicker.vala:67 +#: src/Dialogs/LabelPicker/LabelPicker.vala:69 msgid "Clear" msgstr "" @@ -485,26 +531,38 @@ msgstr "" msgid ":" msgstr "" -#: src/Dialogs/LabelPicker/LabelPicker.vala:90 +#: src/Dialogs/LabelPicker/LabelPicker.vala:92 msgid "Search or Create" msgstr "" -#: src/Objects/Project.vala:407 src/Objects/Label.vala:193 +#: src/Objects/Project.vala:413 src/Objects/Label.vala:192 #, c-format msgid "Are you sure you want to delete %s?" msgstr "" -#: src/Objects/Project.vala:414 src/Objects/Label.vala:200 -#: src/Objects/Section.vala:218 +#: src/Objects/Project.vala:420 src/Objects/Label.vala:199 +#: src/Objects/Section.vala:217 msgid "Delete" msgstr "" -#: src/Objects/Section.vala:204 +#: src/Objects/Project.vala:513 +msgid "Edit project" +msgstr "" + +#: src/Objects/Project.vala:514 +msgid "Add section" +msgstr "" + +#: src/Objects/Project.vala:517 +msgid "Show completed" +msgstr "" + +#: src/Objects/Section.vala:203 #, c-format msgid "Are you sure you want to delete %s?" msgstr "" -#: src/Objects/Section.vala:206 +#: src/Objects/Section.vala:205 #, c-format msgid "Delete %s with its %d tasks?" msgstr "" diff --git a/po/es.po b/po/es.po index a0658421f..c815cac3f 100644 --- a/po/es.po +++ b/po/es.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: com.github.alainm23.planner\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-10 04:56-0500\n" +"POT-Creation-Date: 2022-02-17 06:59-0500\n" "PO-Revision-Date: 2022-02-05 05:17-0500\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,175 +17,224 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: src/Util.vala:97 src/Layouts/HeaderItem.vala:186 -#: src/Dialogs/ProjectPicker/ProjectPicker.vala:88 +#: src/Util.vala:65 src/Util.vala:389 src/Util.vala:551 +#: src/Layouts/FilterPaneRow.vala:83 src/Views/Today.vala:11 +#: src/Views/Date.vala:68 src/Views/Date.vala:101 +#: src/Dialogs/DateTimePicker/DateTimePicker.vala:105 +msgid "Today" +msgstr "Hoy" + +#: src/Util.vala:68 src/Util.vala:386 src/MainWindow.vala:327 +#: src/Layouts/FilterPaneRow.vala:86 src/Widgets/TopHeaderProject.vala:55 +#: src/Dialogs/ProjectPicker/ProjectRow.vala:105 +msgid "Inbox" +msgstr "" + +#: src/Util.vala:71 src/Layouts/FilterPaneRow.vala:89 src/Views/Date.vala:101 +#: src/Views/Scheduled/Scheduled.vala:19 +msgid "Scheduled" +msgstr "" + +#: src/Util.vala:74 src/Layouts/FilterPaneRow.vala:92 +#: src/Views/Pinboard.vala:20 src/Views/Pinboard.vala:77 +msgid "Pinboard" +msgstr "" + +#: src/Util.vala:117 src/Layouts/HeaderItem.vala:186 +#: src/Dialogs/ProjectPicker/ProjectPicker.vala:92 msgid "Projects" msgstr "" -#: src/Util.vala:100 +#: src/Util.vala:120 msgid "Setions" msgstr "" -#: src/Util.vala:103 +#: src/Util.vala:123 msgid "Items" msgstr "" -#: src/Util.vala:106 src/Layouts/HeaderItem.vala:190 -#: src/Dialogs/LabelPicker/LabelPicker.vala:75 +#: src/Util.vala:126 src/Layouts/HeaderItem.vala:190 +#: src/Dialogs/LabelPicker/LabelPicker.vala:77 msgid "Labels" msgstr "" -#: src/Util.vala:140 +#: src/Util.vala:129 +msgid "Filters" +msgstr "" + +#: src/Util.vala:168 msgid "Berry Red" msgstr "" -#: src/Util.vala:141 +#: src/Util.vala:169 msgid "Red" msgstr "" -#: src/Util.vala:142 +#: src/Util.vala:170 msgid "Orange" msgstr "" -#: src/Util.vala:143 +#: src/Util.vala:171 msgid "Olive Green" msgstr "" -#: src/Util.vala:144 +#: src/Util.vala:172 msgid "Yellow" msgstr "" -#: src/Util.vala:145 +#: src/Util.vala:173 msgid "Lime Green" msgstr "" -#: src/Util.vala:146 +#: src/Util.vala:174 msgid "Green" msgstr "" -#: src/Util.vala:147 +#: src/Util.vala:175 msgid "Mint Green" msgstr "" -#: src/Util.vala:148 +#: src/Util.vala:176 msgid "Teal" msgstr "" -#: src/Util.vala:149 +#: src/Util.vala:177 msgid "Sky Blue" msgstr "" -#: src/Util.vala:150 +#: src/Util.vala:178 msgid "Light Blue" msgstr "" -#: src/Util.vala:151 +#: src/Util.vala:179 msgid "Blue" msgstr "" -#: src/Util.vala:152 +#: src/Util.vala:180 msgid "Grape" msgstr "" -#: src/Util.vala:153 +#: src/Util.vala:181 msgid "Violet" msgstr "" -#: src/Util.vala:154 +#: src/Util.vala:182 msgid "Lavander" msgstr "" -#: src/Util.vala:155 +#: src/Util.vala:183 msgid "Magenta" msgstr "" -#: src/Util.vala:156 +#: src/Util.vala:184 msgid "Salmon" msgstr "" -#: src/Util.vala:157 +#: src/Util.vala:185 msgid "Charcoal" msgstr "" -#: src/Util.vala:158 +#: src/Util.vala:186 msgid "Grey" msgstr "" -#: src/Util.vala:159 +#: src/Util.vala:187 msgid "Taupe" msgstr "" -#: src/Util.vala:336 +#: src/Util.vala:364 msgid "Light" msgstr "" -#: src/Util.vala:339 +#: src/Util.vala:367 msgid "Dark" msgstr "" -#: src/Util.vala:342 +#: src/Util.vala:370 msgid "Dark Blue" msgstr "" -#: src/Util.vala:501 src/Layouts/FilterPaneRow.vala:83 src/Views/Today.vala:11 -#: src/Views/Date.vala:68 src/Views/Date.vala:101 -#: src/Dialogs/DateTimePicker/DateTimePicker.vala:105 -msgid "Today" -msgstr "Hoy" - -#: src/Util.vala:503 src/Dialogs/DateTimePicker/DateTimePicker.vala:108 -msgid "Tomorrow" +#: src/Util.vala:383 +msgid "None" msgstr "" -#: src/Util.vala:505 -msgid "Yesterday" +#: src/Util.vala:392 +msgid "Today + Inbox" msgstr "" -#: src/Application.vala:134 -msgid "Are you sure you want to reset all?" +#: src/Util.vala:553 src/Dialogs/DateTimePicker/DateTimePicker.vala:108 +msgid "Tomorrow" msgstr "" -#: src/Application.vala:135 -msgid "" -"It process removes all stored information without the possibility of undoing " -"it." +#: src/Util.vala:555 +msgid "Yesterday" msgstr "" -#: src/Application.vala:141 src/Layouts/ItemRow.vala:282 +#: src/Util.vala:788 src/Util.vala:1212 src/Layouts/ItemRow.vala:282 #: src/Dialogs/Project.vala:214 src/Dialogs/Label.vala:152 -#: src/Dialogs/ProjectPicker/ProjectPicker.vala:81 +#: src/Dialogs/ProjectPicker/ProjectPicker.vala:85 #: src/Dialogs/DateTimePicker/DateTimePicker.vala:83 -#: src/Objects/Project.vala:410 src/Objects/Label.vala:196 -#: src/Objects/Section.vala:214 +#: src/Objects/Project.vala:416 src/Objects/Label.vala:195 +#: src/Objects/Section.vala:213 msgid "Cancel" msgstr "" -#: src/Application.vala:142 +#: src/Util.vala:789 msgid "Reset all" msgstr "" -#: src/MainWindow.vala:36 src/Views/Welcome.vala:27 -msgid "Planner" +#: src/Util.vala:828 +msgid "Welcome to Planner 3" msgstr "" -#: src/MainWindow.vala:270 src/Layouts/FilterPaneRow.vala:86 -#: src/Widgets/TopHeaderProject.vala:55 -#: src/Dialogs/ProjectPicker/ProjectRow.vala:103 -msgid "Inbox" +#: src/Util.vala:829 +msgid "" +"We have detected that you have a Planner 2 configuration started, currently " +"the v3 database is not compatible with v2, if you wish you can download a " +"backup in JSON format and migrate your data manually, or you can start with " +"v3 with a new configuration." msgstr "" -#: src/Layouts/Sidebar.vala:59 -msgid "Settings" +#: src/Util.vala:835 +msgid "Create backup" msgstr "" -#: src/Layouts/FilterPaneRow.vala:89 src/Views/Date.vala:101 -#: src/Views/Scheduled/Scheduled.vala:19 -msgid "Scheduled" +#: src/Util.vala:836 +msgid "Starting over" msgstr "" -#: src/Layouts/FilterPaneRow.vala:92 src/Views/Pinboard.vala:20 -#: src/Views/Pinboard.vala:77 -msgid "Pinboard" +#: src/Util.vala:1209 +msgid "Save backup file" +msgstr "" + +#: src/Util.vala:1211 +msgid "Save" +msgstr "" + +#: src/Util.vala:1235 +msgid "JSON files" +msgstr "" + +#: src/Util.vala:1240 +msgid "All files" +msgstr "" + +#: src/Application.vala:142 +msgid "Are you sure you want to reset all?" +msgstr "" + +#: src/Application.vala:143 +msgid "" +"It process removes all stored information without the possibility of undoing " +"it." +msgstr "" + +#: src/MainWindow.vala:36 src/Views/Welcome.vala:27 +msgid "Planner" +msgstr "" + +#: src/Layouts/Sidebar.vala:59 +msgid "Settings" msgstr "" #: src/Layouts/HeaderItem.vala:187 src/Dialogs/Project.vala:208 @@ -210,7 +259,7 @@ msgstr "" msgid "Favorites" msgstr "" -#: src/Layouts/LabelRow.vala:155 src/Objects/Label.vala:192 +#: src/Layouts/LabelRow.vala:155 src/Objects/Label.vala:191 msgid "Delete label" msgstr "" @@ -222,25 +271,25 @@ msgstr "" msgid "Add Task" msgstr "" -#: src/Layouts/ItemRow.vala:408 +#: src/Layouts/ItemRow.vala:407 msgid "Task name" msgstr "" -#: src/Layouts/ItemRow.vala:962 src/Widgets/ScheduleButton.vala:27 +#: src/Layouts/ItemRow.vala:971 src/Widgets/ScheduleButton.vala:27 #: src/Widgets/ScheduleButton.vala:62 #: src/Dialogs/DateTimePicker/DateTimePicker.vala:91 msgid "Schedule" msgstr "" -#: src/Layouts/ItemRow.vala:963 +#: src/Layouts/ItemRow.vala:972 msgid "Complete" msgstr "" -#: src/Layouts/ItemRow.vala:964 +#: src/Layouts/ItemRow.vala:973 msgid "Edit" msgstr "" -#: src/Layouts/ItemRow.vala:966 +#: src/Layouts/ItemRow.vala:975 msgid "Delete task" msgstr "" @@ -249,8 +298,8 @@ msgstr "" msgid "Share" msgstr "" -#: src/Layouts/ProjectRow.vala:465 src/Widgets/TopHeaderProject.vala:155 -#: src/Objects/Project.vala:406 +#: src/Layouts/ProjectRow.vala:465 src/Objects/Project.vala:412 +#: src/Objects/Project.vala:519 msgid "Delete project" msgstr "" @@ -262,37 +311,34 @@ msgstr "" msgid "Email" msgstr "" -#: src/Layouts/SectionRow.vala:71 +#: src/Layouts/SectionRow.vala:75 msgid "(No Section)" msgstr "" -#: src/Layouts/SectionRow.vala:85 src/Widgets/TopHeaderProject.vala:188 +#: src/Layouts/SectionRow.vala:89 src/Objects/Project.vala:571 msgid "New section" msgstr "" -#: src/Layouts/SectionRow.vala:482 -msgid "Edit section" -msgstr "" - -#. var move_item = new Dialogs.ContextMenu.MenuItemSelector (_("Move section"), true); -#: src/Layouts/SectionRow.vala:484 src/Objects/Section.vala:210 -msgid "Delete section" +#: src/Layouts/SectionRow.vala:531 +msgid "Add task" msgstr "" -#: src/Layouts/SectionRow.vala:524 -msgid "No tasks available. Create one by clicking on the '+' button" +#: src/Layouts/SectionRow.vala:532 +msgid "Edit section" msgstr "" -#: src/Widgets/TopHeaderProject.vala:149 -msgid "Edit project" +#: src/Layouts/SectionRow.vala:533 +msgid "Move section" msgstr "" -#: src/Widgets/TopHeaderProject.vala:150 -msgid "Add section" +#: src/Layouts/SectionRow.vala:534 src/Objects/Section.vala:209 +msgid "Delete section" msgstr "" -#: src/Widgets/TopHeaderProject.vala:153 -msgid "Show completed" +#: src/Layouts/SectionRow.vala:595 +msgid "" +"No tasks available. Create one by dragging the '+' button here or clicking " +"on this space." msgstr "" #: src/Widgets/PriorityButton.vala:35 @@ -369,7 +415,7 @@ msgstr "" msgid "Synchronize with your Todoist Account." msgstr "" -#: src/Views/List.vala:39 +#: src/Views/List.vala:46 msgid "What will you accomplish?" msgstr "" @@ -442,18 +488,18 @@ msgstr "" msgid "Update label" msgstr "" -#: src/Dialogs/ProjectPicker/ProjectPicker.vala:73 +#: src/Dialogs/ProjectPicker/ProjectPicker.vala:77 #: src/Dialogs/DateTimePicker/DateTimePicker.vala:74 -#: src/Dialogs/LabelPicker/LabelPicker.vala:58 +#: src/Dialogs/LabelPicker/LabelPicker.vala:60 msgid "Done" msgstr "" -#: src/Dialogs/ProjectPicker/ProjectPicker.vala:99 +#: src/Dialogs/ProjectPicker/ProjectPicker.vala:103 msgid "Type a project" msgstr "" #: src/Dialogs/DateTimePicker/DateTimePicker.vala:46 -#: src/Dialogs/LabelPicker/LabelPicker.vala:67 +#: src/Dialogs/LabelPicker/LabelPicker.vala:69 msgid "Clear" msgstr "" @@ -485,26 +531,38 @@ msgstr "" msgid ":" msgstr "" -#: src/Dialogs/LabelPicker/LabelPicker.vala:90 +#: src/Dialogs/LabelPicker/LabelPicker.vala:92 msgid "Search or Create" msgstr "" -#: src/Objects/Project.vala:407 src/Objects/Label.vala:193 +#: src/Objects/Project.vala:413 src/Objects/Label.vala:192 #, c-format msgid "Are you sure you want to delete %s?" msgstr "" -#: src/Objects/Project.vala:414 src/Objects/Label.vala:200 -#: src/Objects/Section.vala:218 +#: src/Objects/Project.vala:420 src/Objects/Label.vala:199 +#: src/Objects/Section.vala:217 msgid "Delete" msgstr "" -#: src/Objects/Section.vala:204 +#: src/Objects/Project.vala:513 +msgid "Edit project" +msgstr "" + +#: src/Objects/Project.vala:514 +msgid "Add section" +msgstr "" + +#: src/Objects/Project.vala:517 +msgid "Show completed" +msgstr "" + +#: src/Objects/Section.vala:203 #, c-format msgid "Are you sure you want to delete %s?" msgstr "" -#: src/Objects/Section.vala:206 +#: src/Objects/Section.vala:205 #, c-format msgid "Delete %s with its %d tasks?" msgstr "" diff --git a/src/Application.vala b/src/Application.vala index 152448d0d..8c1c9ddb5 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -54,6 +54,10 @@ public class Planner : Gtk.Application { { null } }; + static construct { + settings = new Settings ("com.github.alainm23.planner"); + } + construct { application_id = "com.github.alainm23.planner"; flags |= ApplicationFlags.HANDLES_OPEN; @@ -64,12 +68,11 @@ public class Planner : Gtk.Application { Intl.bind_textdomain_codeset (Constants.GETTEXT_PACKAGE, "UTF-8"); Intl.textdomain (Constants.GETTEXT_PACKAGE); + add_main_option_entries (PLANNER_OPTIONS); + create_dir_with_parents ("/com.github.alainm23.planner"); - settings = new Settings ("com.github.alainm23.planner"); event_bus = new Services.EventBus (); - - add_main_option_entries (PLANNER_OPTIONS); } protected override void activate () { diff --git a/src/Dialogs/ContextMenu/MenuCalendarPicker.vala b/src/Dialogs/ContextMenu/MenuCalendarPicker.vala index cdc023888..2a59b045f 100644 --- a/src/Dialogs/ContextMenu/MenuCalendarPicker.vala +++ b/src/Dialogs/ContextMenu/MenuCalendarPicker.vala @@ -57,7 +57,7 @@ public class Dialogs.ContextMenu.MenuCalendarPicker : Gtk.EventBox { menu_button_context.add_class (Gtk.STYLE_CLASS_FLAT); menu_button_context.add_class ("transition"); - var calendar = new Widgets.Calendar.Calendar (); + var calendar = new Widgets.Calendar.Calendar (true); var content_revealer = new Gtk.Revealer () { transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN diff --git a/src/Dialogs/DateTimePicker/DateTimePicker.vala b/src/Dialogs/DateTimePicker/DateTimePicker.vala index a4d688197..037b2edde 100644 --- a/src/Dialogs/DateTimePicker/DateTimePicker.vala +++ b/src/Dialogs/DateTimePicker/DateTimePicker.vala @@ -133,15 +133,37 @@ public class Dialogs.DateTimePicker.DateTimePicker : Hdy.Window { date_grid.add (next_week_item); date_grid.add (calendar_item); + var time_icon = new Widgets.DynamicIcon () { + margin_start = 3 + }; + time_icon.size = 19; + time_icon.update_icon_name ("planner-clock"); + + var time_label = new Gtk.Label (_("Time")) { + margin_start = 6 + }; + time_label.get_style_context ().add_class ("font-weight-500"); + time_picker = new Dialogs.DateTimePicker.TimePicker (); + var time_picker_grid = new Gtk.Grid () { + margin = 9, + margin_top = 0 + }; + time_picker_grid.add (time_icon); + time_picker_grid.add (time_label); + time_picker_grid.add (time_picker); + + unowned Gtk.StyleContext time_picker_grid_context = time_picker_grid.get_style_context (); + time_picker_grid_context.add_class ("picker-content"); + var main_grid = new Gtk.Grid () { orientation = Gtk.Orientation.VERTICAL, width_request = 225 }; main_grid.add (headerbar); main_grid.add (date_grid); - main_grid.add (time_picker); + main_grid.add (time_picker_grid); unowned Gtk.StyleContext main_grid_context = main_grid.get_style_context (); main_grid_context.add_class ("picker"); diff --git a/src/Dialogs/DateTimePicker/TimePicker.vala b/src/Dialogs/DateTimePicker/TimePicker.vala index 568032d1f..81c151fbc 100644 --- a/src/Dialogs/DateTimePicker/TimePicker.vala +++ b/src/Dialogs/DateTimePicker/TimePicker.vala @@ -25,6 +25,7 @@ public class Dialogs.DateTimePicker.TimePicker : Gtk.EventBox { private Gtk.SpinButton hours_spinbutton; private Gtk.SpinButton minutes_spinbutton; private Gtk.Stack time_stack; + private Gtk.Revealer no_time_revealer; public string format_12 { get; construct; } public string format_24 { get; construct; } @@ -64,19 +65,18 @@ public class Dialogs.DateTimePicker.TimePicker : Gtk.EventBox { } } + public bool no_time_visible { + set { + no_time_revealer.reveal_child = value; + } + } + private string old_string = ""; private bool changing_time = false; public signal void time_changed (); - construct { - var time_icon = new Widgets.DynamicIcon (); - time_icon.size = 19; - time_icon.update_icon_name ("planner-clock"); - - var title_label = new Gtk.Label (_("Time")); - title_label.get_style_context ().add_class ("font-weight-500"); - + construct { time_button = new Gtk.Button.with_label ("") { valign = Gtk.Align.CENTER, can_focus = false @@ -97,9 +97,15 @@ public class Dialogs.DateTimePicker.TimePicker : Gtk.EventBox { no_time_button.add (close_circle_icon); no_time_button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + no_time_revealer = new Gtk.Revealer () { + transition_type = Gtk.RevealerTransitionType.SLIDE_RIGHT, + reveal_child = true + }; + no_time_revealer.add (no_time_button); + var time_grid = new Gtk.Grid (); time_grid.add (time_button); - time_grid.add (no_time_button); + time_grid.add (no_time_revealer); var add_circle_icon = new Widgets.DynamicIcon (); add_circle_icon.size = 19; @@ -124,8 +130,7 @@ public class Dialogs.DateTimePicker.TimePicker : Gtk.EventBox { margin_start = 3, hexpand = true }; - timepicker_box.pack_start (time_icon, false, false, 0); - timepicker_box.pack_start (title_label, false, true, 6); + timepicker_box.pack_end (time_stack, false, false, 0); /* @@ -201,17 +206,11 @@ public class Dialogs.DateTimePicker.TimePicker : Gtk.EventBox { popover.add (pop_grid); var main_grid = new Gtk.Grid () { - hexpand = true, - margin = 9, - margin_top = 0, - margin_bottom = 12 + hexpand = true }; main_grid.add (timepicker_box); - unowned Gtk.StyleContext main_grid_context = main_grid.get_style_context (); - main_grid_context.add_class ("picker-content"); - add (main_grid); time_button.clicked.connect (() => { diff --git a/src/Dialogs/LabelPicker/LabelPicker.vala b/src/Dialogs/LabelPicker/LabelPicker.vala index 97e06441d..af1700a0a 100644 --- a/src/Dialogs/LabelPicker/LabelPicker.vala +++ b/src/Dialogs/LabelPicker/LabelPicker.vala @@ -27,6 +27,7 @@ public class Dialogs.LabelPicker.LabelPicker : Hdy.Window { private Gtk.Button cancel_clear_button; public Gee.HashMap labels_map; + public Gee.HashMap labels_widgets_map; public signal void labels_changed (Gee.HashMap labels_map); @@ -42,6 +43,7 @@ public class Dialogs.LabelPicker.LabelPicker : Hdy.Window { construct { labels_map = new Gee.HashMap (); + labels_widgets_map = new Gee.HashMap (); foreach (var entry in item.labels.entries) { labels_map [entry.key] = entry.value.label; @@ -90,13 +92,14 @@ public class Dialogs.LabelPicker.LabelPicker : Hdy.Window { placeholder_text = _("Search or Create"), valign = Gtk.Align.CENTER, hexpand = true, - margin_start = 12, - margin_end = 12, + margin_start = 9, + margin_end = 9, margin_top = 3, margin_bottom = 12 }; search_entry.get_style_context ().add_class ("border-radius-6"); + search_entry.get_style_context ().add_class ("picker-background"); listbox = new Gtk.ListBox () { hexpand = true @@ -143,7 +146,27 @@ public class Dialogs.LabelPicker.LabelPicker : Hdy.Window { add (main_grid); add_all_labels (); + key_press_event.connect ((event) => { + var key = Gdk.keyval_name (event.keyval).replace ("KP_", ""); + + if (key == "Up" || key == "Down") { + return false; + } else if (key == "Enter" || key == "Return" || key == "KP_Enter") { + return false; + } else { + if (!search_entry.has_focus) { + search_entry.grab_focus (); + search_entry.move_cursor (Gtk.MovementStep.BUFFER_ENDS, 0, false); + } + + return false; + } + + return true; + }); + focus_out_event.connect (() => { + labels_changed (labels_map); hide_destroy (); return false; }); @@ -170,16 +193,56 @@ public class Dialogs.LabelPicker.LabelPicker : Hdy.Window { search_entry.search_changed.connect (() => { listbox.invalidate_filter (); }); + + search_entry.activate.connect (() => { + if (Util.get_default ().is_input_valid (search_entry)) { + Objects.Label label = Planner.database.get_label_by_name (search_entry.text, true); + if (label != null) { + if (labels_widgets_map.has_key (label.id_string)) { + labels_widgets_map [label.id_string].update_checked_toggled (); + } + } else { + add_assign_label (); + } + } + }); + } + + private void add_assign_label () { + BackendType backend_type = (BackendType) Planner.settings.get_enum ("backend-type"); + + var label = new Objects.Label (); + label.color = Util.get_default ().get_random_color (); + label.name = search_entry.text; + + if (backend_type == BackendType.TODOIST) { + label.todoist = true; + Planner.todoist.add.begin (label, (obj, res) => { + label.id = Planner.todoist.add.end (res); + Planner.database.insert_label (label); + checked_toggled (label, true); + labels_changed (labels_map); + hide_destroy (); + }); + } else if (backend_type == BackendType.LOCAL) { + label.id = Util.get_default ().generate_id (); + Planner.database.insert_label (label); + checked_toggled (label, true); + labels_changed (labels_map); + hide_destroy (); + } } private void add_all_labels () { foreach (Objects.Label label in Planner.database.labels) { - var row = new Dialogs.LabelPicker.LabelRow (label, item.labels.has_key (label.id_string)); + Dialogs.LabelPicker.LabelRow row = new Dialogs.LabelPicker.LabelRow (label, item.labels.has_key (label.id_string)); row.checked_toggled.connect (checked_toggled); + labels_widgets_map [label.id_string] = row; listbox.add (row); - listbox.show_all (); } + + listbox.show_all (); } private void hide_destroy () { diff --git a/src/Dialogs/LabelPicker/LabelRow.vala b/src/Dialogs/LabelPicker/LabelRow.vala index f8437d72a..10c4b4916 100644 --- a/src/Dialogs/LabelPicker/LabelRow.vala +++ b/src/Dialogs/LabelPicker/LabelRow.vala @@ -57,9 +57,13 @@ public class Dialogs.LabelPicker.LabelRow : Gtk.ListBoxRow { add (main_grid); checked_button.button_release_event.connect (() => { - checked_button.active = !checked_button.active; - checked_toggled (label, checked_button.active); + update_checked_toggled (); return Gdk.EVENT_STOP; }); } + + public void update_checked_toggled () { + checked_button.active = !checked_button.active; + checked_toggled (label, checked_button.active); + } } diff --git a/src/Dialogs/MessageDialog.vala b/src/Dialogs/MessageDialog.vala index 0ad01b0e0..2c0876fe4 100644 --- a/src/Dialogs/MessageDialog.vala +++ b/src/Dialogs/MessageDialog.vala @@ -173,7 +173,6 @@ public class Dialogs.MessageDialog : Hdy.Window { Gtk.ResponseType response = Gtk.ResponseType.CANCEL) { button.clicked.connect (() => { default_action (response); - hide_destroy (); }); action_grid.add (button); action_grid.show_all (); diff --git a/src/Dialogs/ProjectPicker/ProjectPicker.vala b/src/Dialogs/ProjectPicker/ProjectPicker.vala index 193464a60..40fce16bd 100644 --- a/src/Dialogs/ProjectPicker/ProjectPicker.vala +++ b/src/Dialogs/ProjectPicker/ProjectPicker.vala @@ -20,6 +20,9 @@ */ public class Dialogs.ProjectPicker.ProjectPicker : Hdy.Window { + public bool show_sections { get; construct; } + + private Gtk.SearchEntry search_entry; private Gtk.ListBox listbox; public Gee.HashMap projects_hashmap; @@ -50,8 +53,9 @@ public class Dialogs.ProjectPicker.ProjectPicker : Hdy.Window { public signal void changed (int64 project_id, int64 section_id); - public ProjectPicker () { + public ProjectPicker (bool show_sections = true) { Object ( + show_sections: show_sections, transient_for: (Gtk.Window) Planner.instance.main_window.get_toplevel (), destroy_with_parent: true, window_position: Gtk.WindowPosition.MOUSE, @@ -95,23 +99,25 @@ public class Dialogs.ProjectPicker.ProjectPicker : Hdy.Window { header_box.set_center_widget (title_label); header_box.pack_end (done_button, false, false, 0); - var search_entry = new Gtk.SearchEntry () { + search_entry = new Gtk.SearchEntry () { placeholder_text = _("Type a project"), hexpand = true, - margin_start = 12, - margin_end = 12, + margin_start = 9, + margin_end = 9, margin_top = 3, margin_bottom = 6 }; unowned Gtk.StyleContext search_entry_context = search_entry.get_style_context (); search_entry_context.add_class ("border-radius-6"); + search_entry_context.add_class ("picker-background"); headerbar.set_custom_title (header_box); listbox = new Gtk.ListBox () { hexpand = true }; + listbox.set_filter_func (filter_func); unowned Gtk.StyleContext listbox_context = listbox.get_style_context (); listbox_context.add_class ("listbox-separator-3"); @@ -145,12 +151,31 @@ public class Dialogs.ProjectPicker.ProjectPicker : Hdy.Window { add (main_grid); add_projects (); + key_press_event.connect ((event) => { + var key = Gdk.keyval_name (event.keyval).replace ("KP_", ""); + + if (key == "Up" || key == "Down") { + return false; + } else if (key == "Enter" || key == "Return" || key == "KP_Enter") { + return false; + } else { + if (!search_entry.has_focus) { + search_entry.grab_focus (); + search_entry.move_cursor (Gtk.MovementStep.BUFFER_ENDS, 0, false); + } + + return false; + } + + return true; + }); + focus_out_event.connect (() => { hide_destroy (); return false; }); - key_release_event.connect ((key) => { + key_release_event.connect ((key) => { if (key.keyval == 65307) { hide_destroy (); } @@ -158,6 +183,10 @@ public class Dialogs.ProjectPicker.ProjectPicker : Hdy.Window { return false; }); + search_entry.search_changed.connect (() => { + listbox.invalidate_filter (); + }); + cancel_button.clicked.connect (() => { hide_destroy (); }); @@ -187,10 +216,12 @@ public class Dialogs.ProjectPicker.ProjectPicker : Hdy.Window { projects_hashmap [project.id_string] = new Dialogs.ProjectPicker.ProjectRow (project); listbox.add (projects_hashmap [project.id_string]); } - - foreach (Objects.Section section in Planner.database.sections) { - if (projects_hashmap.has_key (section.project_id.to_string ())) { - projects_hashmap [section.project_id.to_string ()].add_section (section); + + if (show_sections) { + foreach (Objects.Section section in Planner.database.sections) { + if (projects_hashmap.has_key (section.project_id.to_string ())) { + projects_hashmap [section.project_id.to_string ()].add_section (section); + } } } @@ -200,4 +231,9 @@ public class Dialogs.ProjectPicker.ProjectPicker : Hdy.Window { public void popup () { show_all (); } + + private bool filter_func (Gtk.ListBoxRow row) { + var project = ((Dialogs.ProjectPicker.ProjectRow) row).project; + return search_entry.text.down () in project.name.down (); + } } diff --git a/src/Dialogs/ProjectPicker/ProjectRow.vala b/src/Dialogs/ProjectPicker/ProjectRow.vala index a0f8cfad5..3bcbbae85 100644 --- a/src/Dialogs/ProjectPicker/ProjectRow.vala +++ b/src/Dialogs/ProjectPicker/ProjectRow.vala @@ -41,7 +41,9 @@ public class Dialogs.ProjectPicker.ProjectRow : Gtk.ListBoxRow { var projectrow_grid = new Gtk.Grid () { column_spacing = 6, - margin = 6 + margin = 6, + margin_top = 3, + margin_bottom = 3 }; projectrow_grid.add (icon_project); diff --git a/src/Dialogs/QuickFind/QuickFind.vala b/src/Dialogs/QuickFind/QuickFind.vala index bddec3eab..01b724249 100644 --- a/src/Dialogs/QuickFind/QuickFind.vala +++ b/src/Dialogs/QuickFind/QuickFind.vala @@ -61,7 +61,7 @@ public class Dialogs.QuickFind.QuickFind : Hdy.Window { listbox.set_header_func (header_function); unowned Gtk.StyleContext listbox_context = listbox.get_style_context (); - listbox_context.add_class ("picker-background"); + listbox_context.add_class ("listbox-background"); listbox_context.add_class ("listbox-separator-3"); var listbox_grid = new Gtk.Grid () { @@ -71,10 +71,6 @@ public class Dialogs.QuickFind.QuickFind : Hdy.Window { }; listbox_grid.add (listbox); - unowned Gtk.StyleContext placeholder_grid_context = listbox_grid.get_style_context (); - placeholder_grid_context.add_class ("transition"); - placeholder_grid_context.add_class ("picker-content"); - var listbox_scrolled = new Gtk.ScrolledWindow (null, null) { expand = true, hscrollbar_policy = Gtk.PolicyType.NEVER @@ -95,6 +91,18 @@ public class Dialogs.QuickFind.QuickFind : Hdy.Window { if (search_entry.text.strip () != "") { clean_results (); + Objects.BaseObject[] filters = { + Objects.Today.get_default (), + Objects.Scheduled.get_default (), + Objects.Pinboard.get_default () + }; + foreach (Objects.BaseObject object in filters) { + if (search_entry.text.down () in object.name.down ()) { + listbox.add (new Dialogs.QuickFind.QuickFindItem (object, search_entry.text)); + listbox.show_all (); + } + } + foreach (Objects.Project project in Planner.database.get_all_projects_by_search (search_entry.text)) { listbox.add (new Dialogs.QuickFind.QuickFindItem (project, search_entry.text)); listbox.show_all (); @@ -104,13 +112,18 @@ public class Dialogs.QuickFind.QuickFind : Hdy.Window { listbox.add (new Dialogs.QuickFind.QuickFindItem (item, search_entry.text)); listbox.show_all (); } + + foreach (Objects.Label label in Planner.database.get_all_labels_by_search (search_entry.text)) { + listbox.add (new Dialogs.QuickFind.QuickFindItem (label, search_entry.text)); + listbox.show_all (); + } } else { clean_results (); } }); focus_out_event.connect (() => { - // hide_destroy (); + hide_destroy (); return false; }); @@ -180,6 +193,18 @@ public class Dialogs.QuickFind.QuickFind : Hdy.Window { Planner.event_bus.pane_selected (PaneType.PROJECT, ((Objects.Item) base_object).project_id.to_string () ); + } else if (base_object.object_type == ObjectType.LABEL) { + Planner.event_bus.pane_selected (PaneType.LABEL, + ((Objects.Label) base_object).id_string + ); + } else if (base_object.object_type == ObjectType.FILTER) { + if (base_object is Objects.Today) { + Planner.event_bus.pane_selected (PaneType.FILTER, FilterType.TODAY.to_string ()); + } else if (base_object is Objects.Scheduled) { + Planner.event_bus.pane_selected (PaneType.FILTER, FilterType.SCHEDULED.to_string ()); + } else if (base_object is Objects.Pinboard) { + Planner.event_bus.pane_selected (PaneType.FILTER, FilterType.PINBOARD.to_string ()); + } } hide_destroy (); diff --git a/src/Dialogs/QuickFind/QuickFindItem.vala b/src/Dialogs/QuickFind/QuickFindItem.vala index ee45bb95d..3454a1325 100644 --- a/src/Dialogs/QuickFind/QuickFindItem.vala +++ b/src/Dialogs/QuickFind/QuickFindItem.vala @@ -81,6 +81,45 @@ public class Dialogs.QuickFind.QuickFindItem : Gtk.ListBoxRow { main_grid.attach (checked_button, 0, 0, 1, 2); main_grid.attach (content_label, 1, 0, 1, 1); main_grid.attach (project_label, 1, 1, 1, 1); + } else if (base_object is Objects.Label) { + Objects.Label label = ((Objects.Label) base_object); + + var widget_color = new Gtk.Grid () { + valign = Gtk.Align.CENTER, + height_request = 12, + width_request = 12 + }; + + unowned Gtk.StyleContext widget_color_context = widget_color.get_style_context (); + widget_color_context.add_class ("label-color"); + Util.get_default ().set_widget_color (Util.get_default ().get_color (label.color), widget_color); + + var name_label = new Gtk.Label (markup_string_with_search (label.name, pattern)) { + ellipsize = Pango.EllipsizeMode.END, + xalign = 0, + use_markup = true + }; + + main_grid.add (widget_color); + main_grid.add (name_label); + } else if (base_object is Objects.Today || base_object is Objects.Scheduled || + base_object is Objects.Pinboard) { + + var filter_icon = new Gtk.Image () { + gicon = new ThemedIcon (base_object.icon_name), + pixel_size = 16, + valign = Gtk.Align.CENTER, + halign = Gtk.Align.CENTER + }; + + var name_label = new Gtk.Label (markup_string_with_search (base_object.name, pattern)) { + ellipsize = Pango.EllipsizeMode.END, + xalign = 0, + use_markup = true + }; + + main_grid.add (filter_icon); + main_grid.add (name_label); } add (main_grid); diff --git a/src/Dialogs/ReminderPicker/ReminderPicker.vala b/src/Dialogs/ReminderPicker/ReminderPicker.vala new file mode 100644 index 000000000..25fb699bf --- /dev/null +++ b/src/Dialogs/ReminderPicker/ReminderPicker.vala @@ -0,0 +1,319 @@ +/* +* Copyright © 2019 Alain M. (https://github.com/alainm23/planner) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public +* License as published by the Free Software Foundation; either +* version 3 of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301 USA +* +* Authored by: Alain M. +*/ + +public class Dialogs.ReminderPicker.ReminderPicker : Hdy.Window { + public Objects.Item item { get; construct; } + + private Widgets.LoadingButton done_button; + private Gtk.ListBox listbox; + private Gtk.Button cancel_button; + private Widgets.Calendar.Calendar calendar; + private Dialogs.DateTimePicker.TimePicker time_picker; + private Gtk.Revealer cancel_revealer; + private Gtk.Stack main_stack; + + private Gee.HashMap reminders_map; + + public ReminderPicker (Objects.Item item) { + Object ( + item: item, + transient_for: (Gtk.Window) Planner.instance.main_window.get_toplevel (), + destroy_with_parent: true, + window_position: Gtk.WindowPosition.MOUSE, + resizable: false + ); + } + + construct { + reminders_map = new Gee.HashMap (); + + var headerbar = new Hdy.HeaderBar () { + has_subtitle = false, + show_close_button = false, + hexpand = true + }; + headerbar.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + headerbar.get_style_context ().add_class ("default-decoration"); + + done_button = new Widgets.LoadingButton (LoadingButtonType.LABEL, _("Done")) { + halign = Gtk.Align.CENTER, + valign = Gtk.Align.CENTER, + can_focus = false + }; + done_button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + done_button.get_style_context ().add_class ("primary-color"); + done_button.get_style_context ().add_class (Granite.STYLE_CLASS_SMALL_LABEL); + + cancel_button = new Gtk.Button.with_label (_("Cancel")) { + halign = Gtk.Align.CENTER, + valign = Gtk.Align.CENTER, + can_focus = false + }; + cancel_button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + cancel_button.get_style_context ().add_class (Granite.STYLE_CLASS_SMALL_LABEL); + + cancel_revealer = new Gtk.Revealer () { + transition_type = Gtk.RevealerTransitionType.CROSSFADE, + reveal_child = false + }; + cancel_revealer.add (cancel_button); + + var title_label = new Gtk.Label (_("Reminders")); + title_label.get_style_context ().add_class ("h4"); + + var header_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { + hexpand = true, + margin_start = 3, + margin_end = 3 + }; + header_box.pack_start (cancel_revealer, false, false, 0); + header_box.set_center_widget (title_label); + header_box.pack_end (done_button, false, false, 0); + + headerbar.set_custom_title (header_box); + + var reminder_new = new Dialogs.ReminderPicker.ReminderRow.new (); + + listbox = new Gtk.ListBox () { + hexpand = true + }; + + listbox.add (reminder_new); + + var listbox_scrolled = new Gtk.ScrolledWindow (null, null) { + hscrollbar_policy = Gtk.PolicyType.NEVER, + vscrollbar_policy = Gtk.PolicyType.NEVER, + expand = true + }; + listbox_scrolled.add (listbox); + + unowned Gtk.StyleContext listbox_context = listbox.get_style_context (); + listbox_context.add_class ("picker-background"); + listbox_context.add_class ("listbox-separator-3"); + + var listbox_grid = new Gtk.Grid (); + listbox_grid.add (listbox_scrolled); + + unowned Gtk.StyleContext listbox_grid_context = listbox_grid.get_style_context (); + listbox_grid_context.add_class ("picker-content"); + + main_stack = new Gtk.Stack (); + main_stack.expand = true; + main_stack.homogeneous = false; + main_stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT; + + main_stack.add_named (listbox_grid, "listbox"); + main_stack.add_named (get_picker (), "picker"); + + var content_grid = new Gtk.Grid () { + orientation = Gtk.Orientation.VERTICAL, + hexpand = true, + margin = 9, + margin_top = 0, + margin_bottom = 12 + }; + + content_grid.add (main_stack); + + var main_grid = new Gtk.Grid () { + orientation = Gtk.Orientation.VERTICAL, + width_request = 225 + }; + main_grid.add (headerbar); + main_grid.add (content_grid); + + unowned Gtk.StyleContext main_grid_context = main_grid.get_style_context (); + main_grid_context.add_class ("picker"); + + add (main_grid); + add_reminders (); + + focus_out_event.connect (() => { + hide_destroy (); + return false; + }); + + key_release_event.connect ((key) => { + if (key.keyval == 65307) { + hide_destroy (); + } + + return false; + }); + + cancel_button.clicked.connect (() => { + if (main_stack.visible_child_name == "picker") { + main_stack.visible_child_name = "listbox"; + cancel_revealer.reveal_child = false; + } else { + hide_destroy (); + } + }); + + reminder_new.activated.connect (() => { + cancel_revealer.reveal_child = true; + + time_picker.has_time = true; + time_picker.no_time_visible = false; + time_picker.time = new GLib.DateTime.now_local ().add_hours (1); + main_stack.visible_child_name = "picker"; + }); + + done_button.clicked.connect (() => { + if (main_stack.visible_child_name == "picker") { + insert_reminder (); + } else { + hide_destroy (); + } + }); + + item.reminder_added.connect (add_reminder); + + Planner.database.reminder_deleted.connect ((id) => { + if (reminders_map.has_key (id.to_string ())) { + reminders_map[id.to_string ()].hide_destroy (); + reminders_map.unset (id.to_string ()); + } + }); + } + + private void add_reminders () { + foreach (Objects.Reminder reminder in item.reminders) { + add_reminder (reminder); + } + } + + private void add_reminder (Objects.Reminder reminder) { + if (!reminders_map.has_key (reminder.id_string)) { + reminders_map [reminder.id_string] = new Dialogs.ReminderPicker.ReminderRow (reminder); + listbox.insert (reminders_map[reminder.id_string], 0); + listbox.show_all (); + } + } + + private void insert_reminder () { + var reminder = new Objects.Reminder (); + reminder.due.date = Util.get_default ().get_todoist_datetime_format (get_datetime_picker ()); + reminder.item_id = item.id; + + if (item.project.todoist) { + done_button.is_loading = true; + Planner.todoist.add.begin (reminder, (obj, res) => { + int64? id = Planner.todoist.add.end (res); + if (id != null) { + reminder.id = id; + Planner.database.insert_reminder (reminder); + } else { + reminder.id = Util.get_default ().generate_id (); + Planner.database.insert_reminder (reminder); + } + + main_stack.visible_child_name = "listbox"; + cancel_revealer.reveal_child = false; + + done_button.is_loading = false; + }); + } else { + reminder.id = Util.get_default ().generate_id (); + Planner.database.insert_reminder (reminder); + + main_stack.visible_child_name = "listbox"; + cancel_revealer.reveal_child = false; + } + } + + private GLib.DateTime get_datetime_picker () { + return new DateTime.local ( + calendar.date.get_year (), + calendar.date.get_month (), + calendar.date.get_day_of_month (), + time_picker.time.get_hour (), + time_picker.time.get_minute (), + 0 + ); + } + + private Gtk.Widget get_picker () { + calendar = new Widgets.Calendar.Calendar (true) { + expand = true + }; + + var calendar_grid = new Gtk.Grid () { + orientation = Gtk.Orientation.VERTICAL + }; + + calendar_grid.add (calendar); + + unowned Gtk.StyleContext calendar_grid_context = calendar_grid.get_style_context (); + calendar_grid_context.add_class ("picker-content"); + + var time_icon = new Widgets.DynamicIcon () { + margin_start = 3 + }; + time_icon.size = 19; + time_icon.update_icon_name ("planner-clock"); + + var time_label = new Gtk.Label (_("Time")) { + margin_start = 6 + }; + time_label.get_style_context ().add_class ("font-weight-500"); + + time_picker = new Dialogs.DateTimePicker.TimePicker (); + + var time_picker_grid = new Gtk.Grid (); + time_picker_grid.add (time_label); + time_picker_grid.add (time_picker); + + unowned Gtk.StyleContext time_picker_grid_context = time_picker_grid.get_style_context (); + time_picker_grid_context.add_class ("picker-content"); + + var main_grid = new Gtk.Grid () { + orientation = Gtk.Orientation.VERTICAL, + row_spacing = 9 + }; + + main_grid.add (calendar_grid); + main_grid.add (time_picker_grid); + + return main_grid; + } + + private void hide_destroy () { + hide (); + + Timeout.add (500, () => { + destroy (); + return GLib.Source.REMOVE; + }); + } + + public void popup () { + show_all (); + + // Gdk.Rectangle rect; + // get_allocation (out rect); + + // int root_x, root_y; + // get_position (out root_x, out root_y); + + // move (root_x + (rect.width / 3), root_y + (rect.height / 3) + 24); + } +} \ No newline at end of file diff --git a/src/Dialogs/ReminderPicker/ReminderRow.vala b/src/Dialogs/ReminderPicker/ReminderRow.vala new file mode 100644 index 000000000..1076d35f4 --- /dev/null +++ b/src/Dialogs/ReminderPicker/ReminderRow.vala @@ -0,0 +1,100 @@ +/* +* Copyright © 2019 Alain M. (https://github.com/alainm23/planner) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public +* License as published by the Free Software Foundation; either +* version 3 of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301 USA +* +* Authored by: Alain M. +*/ + +public class Dialogs.ReminderPicker.ReminderRow : Gtk.ListBoxRow { + public Objects.Reminder reminder { get; construct; } + + private Gtk.Revealer main_revealer; + + public bool is_creating { + get { + return reminder.id == Constants.INACTIVE; + } + } + + public signal void activated (); + + public ReminderRow (Objects.Reminder reminder) { + Object ( + reminder: reminder + ); + } + + public ReminderRow.new () { + var reminder = new Objects.Reminder (); + + Object ( + reminder: reminder + ); + } + + construct { + get_style_context ().add_class ("row"); + + var reminder_image = new Widgets.DynamicIcon (); + reminder_image.size = 19; + reminder_image.update_icon_name (is_creating ? "planner-plus-circle" : "planner-bell"); + + var reminder_label = new Gtk.Label (is_creating ? _("Add reminder") : Util.get_default ().get_relative_date_from_date (reminder.due.datetime)); + + var main_grid = new Gtk.Grid () { + column_spacing = 6, + margin = 3 + }; + + main_grid.add (reminder_image); + main_grid.add (reminder_label); + + var reminder_eventbox = new Gtk.EventBox (); + reminder_eventbox.get_style_context ().add_class ("transition"); + reminder_eventbox.add (main_grid); + + main_revealer = new Gtk.Revealer () { + transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN + }; + main_revealer.add (reminder_eventbox); + + add (main_revealer); + + Timeout.add (main_revealer.transition_duration, () => { + main_revealer.reveal_child = true; + return GLib.Source.REMOVE; + }); + + if (is_creating) { + reminder_eventbox.button_press_event.connect ((sender, evt) => { + if (evt.type == Gdk.EventType.BUTTON_PRESS && evt.button == 1) { + activated (); + } + + return Gdk.EVENT_PROPAGATE; + }); + } + } + + public void hide_destroy () { + main_revealer.reveal_child = false; + Timeout.add (main_revealer.transition_duration, () => { + destroy (); + return GLib.Source.REMOVE; + }); + } +} diff --git a/src/Dialogs/Settings/Settings.vala b/src/Dialogs/Settings/Settings.vala index 77b4ad602..314c13368 100644 --- a/src/Dialogs/Settings/Settings.vala +++ b/src/Dialogs/Settings/Settings.vala @@ -82,13 +82,27 @@ public class Dialogs.Settings.Settings : Hdy.Window { var general_content = new Dialogs.Settings.SettingsContent (_("General")); + var homepage_item = new Dialogs.Settings.SettingsItem ( + "planner-home", + _("Home Page"), + Util.get_default ().get_filter ().get_name () + ); + var appearance_item = new Dialogs.Settings.SettingsItem ( "planner-appearance", _("Appearance"), Util.get_default ().get_theme_name () ); + var badge_count_item = new Dialogs.Settings.SettingsItem ( + "planner-notification", + _("Notification settings"), + _("Manage your notification settings") + ); + + general_content.add_child (homepage_item); general_content.add_child (appearance_item); + general_content.add_child (badge_count_item); var contact_content = new Dialogs.Settings.SettingsContent (_("Contact us")); @@ -132,6 +146,14 @@ public class Dialogs.Settings.Settings : Hdy.Window { go_setting_view ("appearance"); }); + badge_count_item.activated.connect (() => { + go_setting_view ("notification"); + }); + + homepage_item.activated.connect (() => { + go_setting_view ("home-page"); + }); + mail_item.activated.connect (() => { try { AppInfo.launch_default_for_uri ("https://github.com/alainm23/planner/issues", null); @@ -159,6 +181,8 @@ public class Dialogs.Settings.Settings : Hdy.Window { Planner.settings.changed.connect ((key) => { if (key == "appearance") { appearance_item.description = Util.get_default ().get_theme_name (); + } else if (key == "homepage-item") { + homepage_item.description = Util.get_default ().get_filter ().get_name (); } }); @@ -234,6 +258,208 @@ public class Dialogs.Settings.Settings : Hdy.Window { return main_grid; } + private Gtk.Widget get_badge_view () { + var settings_header = new Dialogs.Settings.SettingsHeader (_("Notification settings")); + + var content = new Dialogs.Settings.SettingsContent (_("Badge Count")); + + var none_item = new Gtk.RadioButton.with_label (null, _("None")) { + hexpand = true, + margin_top = 3, + margin_start = 6 + }; + var inbox_item = new Gtk.RadioButton.with_label_from_widget (none_item, _("Inbox")) { + hexpand = true, + margin_top = 3, + margin_left = 6 + }; + + var today_item = new Gtk.RadioButton.with_label_from_widget (none_item, _("Today")) { + hexpand = true, + margin_top = 3, + margin_start = 6 + }; + + var today_inbox_item = new Gtk.RadioButton.with_label_from_widget (none_item, _("Today + Inbox")) { + hexpand = true, + margin_top = 3, + margin_start = 6, + margin_bottom = 6 + }; + + content.add_child (none_item); + content.add_child (inbox_item); + content.add_child (today_item); + content.add_child (today_inbox_item); + + var main_grid = new Gtk.Grid () { + expand = true, + orientation = Gtk.Orientation.VERTICAL, + valign = Gtk.Align.START + }; + + main_grid.add (settings_header); + main_grid.add (content); + + int badge = Planner.settings.get_enum ("badge-count"); + if (badge == 0) { + none_item.active = true; + } else if (badge == 1) { + inbox_item.active = true; + } else if (badge == 2) { + today_item.active = true; + } else if (badge == 3) { + today_inbox_item.active = true; + } + + settings_header.done_activated.connect (() => { + hide_destroy (); + }); + + settings_header.back_activated.connect (() => { + go_setting_view ("settings"); + }); + + none_item.toggled.connect (() => { + Planner.settings.set_enum ("badge-count", 0); + }); + + inbox_item.toggled.connect (() => { + Planner.settings.set_enum ("badge-count", 1); + }); + + today_item.toggled.connect (() => { + Planner.settings.set_enum ("badge-count", 2); + }); + + today_inbox_item.toggled.connect (() => { + Planner.settings.set_enum ("badge-count", 3); + }); + + main_grid.show_all (); + return main_grid; + } + + private Gtk.Widget get_home_page_view () { + var settings_header = new Dialogs.Settings.SettingsHeader (_("Notification settings")); + + var filters_content = new Dialogs.Settings.SettingsContent (_("Filters")); + var projects_content = new Dialogs.Settings.SettingsContent (_("Projects")); + + var inbox_item = new Gtk.RadioButton.with_label (null, _("Inbox")) { + hexpand = true, + margin_top = 3, + margin_start = 6 + }; + var today_item = new Gtk.RadioButton.with_label_from_widget (inbox_item, _("Today")) { + hexpand = true, + margin_top = 3, + margin_left = 6 + }; + + var scheduled_item = new Gtk.RadioButton.with_label_from_widget (inbox_item, _("Scheduled")) { + hexpand = true, + margin_top = 3, + margin_start = 6 + }; + + var pinboard_item = new Gtk.RadioButton.with_label_from_widget (inbox_item, _("Pinboard")) { + hexpand = true, + margin_top = 3, + margin_start = 6, + margin_bottom = 6 + }; + + if (!Planner.settings.get_boolean ("homepage-project")) { + int type = Planner.settings.get_enum ("homepage-item"); + if (type == 0) { + inbox_item.active = true; + } else if (type == 1) { + today_item.active = true; + } else if (type == 2) { + scheduled_item.active = true; + } else { + pinboard_item.active = true; + } + } + + filters_content.add_child (inbox_item); + filters_content.add_child (today_item); + filters_content.add_child (scheduled_item); + filters_content.add_child (pinboard_item); + + foreach (Objects.Project project in Planner.database.projects) { + if (!project.inbox_project) { + var project_item = new Gtk.RadioButton.with_label_from_widget (inbox_item, project.name) { + hexpand = true, + margin_top = 3, + margin_start = 6, + margin_bottom = 6 + }; + + projects_content.add_child (project_item); + + project_item.toggled.connect (() => { + Planner.settings.set_boolean ("homepage-project", true); + Planner.settings.set_int64 ("homepage-project-id", project.id); + }); + + if (Planner.settings.get_boolean ("homepage-project")) { + if (Planner.settings.get_int64 ("homepage-project-id") == project.id) { + project_item.active = true; + } + } + } + } + + var main_grid = new Gtk.Grid () { + expand = true, + orientation = Gtk.Orientation.VERTICAL, + valign = Gtk.Align.START + }; + + main_grid.add (settings_header); + main_grid.add (filters_content); + main_grid.add (projects_content); + + var scrolled = new Gtk.ScrolledWindow (null, null) { + hscrollbar_policy = Gtk.PolicyType.NEVER, + expand = true + }; + scrolled.add (main_grid); + + settings_header.done_activated.connect (() => { + hide_destroy (); + }); + + settings_header.back_activated.connect (() => { + go_setting_view ("settings"); + }); + + inbox_item.toggled.connect (() => { + Planner.settings.set_boolean ("homepage-project", false); + Planner.settings.set_enum ("homepage-item", 0); + }); + + today_item.toggled.connect (() => { + Planner.settings.set_boolean ("homepage-project", false); + Planner.settings.set_enum ("homepage-item", 1); + }); + + scheduled_item.toggled.connect (() => { + Planner.settings.set_boolean ("homepage-project", false); + Planner.settings.set_enum ("homepage-item", 2); + }); + + pinboard_item.toggled.connect (() => { + Planner.settings.set_boolean ("homepage-project", false); + Planner.settings.set_enum ("homepage-item", 3); + }); + + scrolled.show_all (); + return scrolled; + } + private void go_setting_view (string view) { if (!views.has_key (view)) { views[view] = get_setting_view (view); @@ -250,9 +476,15 @@ public class Dialogs.Settings.Settings : Hdy.Window { case "settings": returned = get_settings_view (); break; + case "home-page": + returned = get_home_page_view (); + break; case "appearance": returned = get_appearance_view (); break; + case "notification": + returned = get_badge_view (); + break; } return returned; diff --git a/src/Dialogs/Settings/SettingsContent.vala b/src/Dialogs/Settings/SettingsContent.vala index ed758c8fd..b772db5c5 100644 --- a/src/Dialogs/Settings/SettingsContent.vala +++ b/src/Dialogs/Settings/SettingsContent.vala @@ -13,7 +13,8 @@ public class Dialogs.Settings.SettingsContent : Gtk.EventBox { construct { var title_label = new Gtk.Label (title) { - halign = Gtk.Align.START + halign = Gtk.Align.START, + margin_start = 3 }; title_label.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL); title_label.get_style_context ().add_class (Granite.STYLE_CLASS_SMALL_LABEL); @@ -45,4 +46,9 @@ public class Dialogs.Settings.SettingsContent : Gtk.EventBox { content_grid.add (child); content_grid.show_all (); } + + public void attach_child (Gtk.Widget child, int left, int top) { + content_grid.attach (child, left, top); + content_grid.show_all (); + } } diff --git a/src/Dialogs/Shortcuts/ShortcutLabel.vala b/src/Dialogs/Shortcuts/ShortcutLabel.vala new file mode 100644 index 000000000..5f9efa1ca --- /dev/null +++ b/src/Dialogs/Shortcuts/ShortcutLabel.vala @@ -0,0 +1,98 @@ +/* +* Copyright © 2019 Alain M. (https://github.com/alainm23/planner) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public +* License as published by the Free Software Foundation; either +* version 3 of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301 USA +* +* Authored by: Alain M. +*/ + +public class Dialogs.Shortcuts.ShortcutLabel : Gtk.Grid { + public string title { get; construct; } + public string[] accels { get; construct; } + + public ShortcutLabel (string title, string[] accels) { + Object ( + accels: accels, + title: title + ); + } + + construct { + valign = Gtk.Align.CENTER; + update_accels (accels); + } + + public void update_accels (string[] accels) { + var accels_grid = new Gtk.Grid () { + column_spacing = 3 + }; + + int index = 0; + foreach (var child in accels_grid.get_children ()) { + child.destroy (); + } + + if (accels[0] != "") { + foreach (unowned string accel in accels) { + index += 1; + if (accel == "") { + continue; + } + var label = new Gtk.Label (accel.replace ("Super", "⌘")); + label.get_style_context ().add_class ("keycap"); + accels_grid.add (label); + + if (index < accels.length) { + label = new Gtk.Label ("+"); + label.get_style_context ().add_class ("font-bold"); + accels_grid.add (label); + } + } + } else { + var label = new Gtk.Label (_("Disabled")); + label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL); + accels_grid.add (label); + } + + var title_label = new NameLabel (title); + + var main_grid = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12) { + hexpand = true, + margin_start = 6, + margin_end = 3 + }; + main_grid.pack_start (title_label, false, true, 0); + main_grid.pack_end (accels_grid, false, false, 0); + + add (main_grid); + + show_all (); + } +} + +private class NameLabel : Gtk.Label { + public NameLabel (string label) { + Object ( + label: label + ); + } + + construct { + halign = Gtk.Align.START; + xalign = 0; + wrap = true; + } +} \ No newline at end of file diff --git a/src/Dialogs/Shortcuts/Shortcuts.vala b/src/Dialogs/Shortcuts/Shortcuts.vala new file mode 100644 index 000000000..2e89929d3 --- /dev/null +++ b/src/Dialogs/Shortcuts/Shortcuts.vala @@ -0,0 +1,185 @@ +/* +* Copyright © 2019 Alain M. (https://github.com/alainm23/planner) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public +* License as published by the Free Software Foundation; either +* version 3 of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301 USA +* +* Authored by: Alain M. +*/ + +public class Dialogs.Shortcuts.Shortcuts : Hdy.Window { + public Shortcuts () { + Object ( + transient_for: (Gtk.Window) Planner.instance.main_window.get_toplevel (), + destroy_with_parent: true, + window_position: Gtk.WindowPosition.CENTER_ON_PARENT, + resizable: false, + height_request: 475 + ); + } + + construct { + var headerbar = new Hdy.HeaderBar () { + has_subtitle = false, + show_close_button = false, + hexpand = true + }; + headerbar.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + headerbar.get_style_context ().add_class ("default-decoration"); + + var done_button = new Gtk.Button.with_label (_("Done")) { + halign = Gtk.Align.CENTER, + valign = Gtk.Align.CENTER, + can_focus = false + }; + done_button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + done_button.get_style_context ().add_class ("primary-color"); + + var title_label = new Gtk.Label (_("Shortcuts")); + title_label.get_style_context ().add_class ("h4"); + + var header_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { + hexpand = true, + margin_start = 3, + margin_end = 3 + }; + header_box.set_center_widget (title_label); + // header_box.pack_end (done_button, false, false, 0); + + headerbar.set_custom_title (header_box); + + var anywhere_content = new Dialogs.Settings.SettingsContent (_("Used anywhere")); + + anywhere_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Create a new task"), + {"a"}) + ); + anywhere_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Paste plain text to create new task"), + {"Ctrl", "v"}) + ); + anywhere_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Create a new project"), + {"p"}) + ); + anywhere_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Create a new section in a project"), + {"s"}) + ); + + var search_content = new Dialogs.Settings.SettingsContent (_("Search")); + + search_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Open Quick Find"), + {"Ctrl", "f"}) + ); + + search_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Start typing to begin a search"), + {"any key"}) + ); + + var windows_content = new Dialogs.Settings.SettingsContent (_("Control windows")); + + windows_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Quit"), + {"Ctrl", "q"}) + ); + + windows_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Open preferences"), + {"Ctrl", ","}) + ); + + windows_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Open Keyboard Shortcuts"), + {"f1"}) + ); + + var navigate_content = new Dialogs.Settings.SettingsContent (_("Navigate")); + + navigate_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Open Inbox"), + {"Ctrl", "1"}) + ); + + navigate_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Open Today"), + {"Ctrl", "2"}) + ); + + navigate_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Open Scheduled"), + {"Ctrl", "3"}) + ); + + navigate_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Open Pinboard"), + {"Ctrl", "4"}) + ); + + navigate_content.add_child ( + new Dialogs.Shortcuts.ShortcutLabel (_("Go to start page"), + {"h"}) + ); + + var main_grid = new Gtk.Grid () { + orientation = Gtk.Orientation.VERTICAL, + width_request = 300 + }; + main_grid.add (headerbar); + main_grid.add (anywhere_content); + main_grid.add (search_content); + main_grid.add (windows_content); + main_grid.add (navigate_content); + + unowned Gtk.StyleContext main_grid_context = main_grid.get_style_context (); + main_grid_context.add_class ("picker"); + + var stack_scrolled = new Gtk.ScrolledWindow (null, null) { + hscrollbar_policy = Gtk.PolicyType.NEVER, + expand = true + }; + stack_scrolled.add (main_grid); + + add (stack_scrolled); + + focus_out_event.connect (() => { + hide_destroy (); + return false; + }); + + key_release_event.connect ((key) => { + if (key.keyval == 65307) { + hide_destroy (); + } + + return false; + }); + + done_button.clicked.connect (() => { + hide_destroy (); + }); + } + + private void hide_destroy () { + hide (); + + Timeout.add (500, () => { + destroy (); + return GLib.Source.REMOVE; + }); + } +} \ No newline at end of file diff --git a/src/Layouts/FilterPaneRow.vala b/src/Layouts/FilterPaneRow.vala index c6f28944b..018450e3a 100644 --- a/src/Layouts/FilterPaneRow.vala +++ b/src/Layouts/FilterPaneRow.vala @@ -100,19 +100,9 @@ public class Layouts.FilterPaneRow : Gtk.FlowBoxChild { public void init () { if (filter_type == FilterType.TODAY) { - GLib.DateTime date = new GLib.DateTime.now_local (); - update_count_label (Planner.database.get_items_by_date (date, false).size); - - Planner.database.item_added.connect (() => { - update_count_label (Planner.database.get_items_by_date (date, false).size); - }); - - Planner.database.item_deleted.connect (() => { - update_count_label (Planner.database.get_items_by_date (date, false).size); - }); - - Planner.database.item_updated.connect (() => { - update_count_label (Planner.database.get_items_by_date (date, false).size); + update_count_label (Objects.Today.get_default ().today_count); + Objects.Today.get_default ().today_count_updated.connect (() => { + update_count_label (Objects.Today.get_default ().today_count); }); } else if (filter_type == FilterType.INBOX) { Objects.Project inbox_project = Planner.database.get_project (Planner.settings.get_int64 ("inbox-project-id")); @@ -122,32 +112,14 @@ public class Layouts.FilterPaneRow : Gtk.FlowBoxChild { update_count_label (inbox_project.project_count); }); } else if (filter_type == FilterType.SCHEDULED) { - update_count_label (Planner.database.get_items_by_scheduled (false).size); - - Planner.database.item_added.connect (() => { - update_count_label (Planner.database.get_items_by_scheduled (false).size); - }); - - Planner.database.item_deleted.connect (() => { - update_count_label (Planner.database.get_items_by_scheduled (false).size); - }); - - Planner.database.item_updated.connect (() => { - update_count_label (Planner.database.get_items_by_scheduled (false).size); + update_count_label (Objects.Scheduled.get_default ().scheduled_count); + Objects.Scheduled.get_default ().scheduled_count_updated.connect (() => { + update_count_label (Objects.Scheduled.get_default ().scheduled_count); }); } else if (filter_type == FilterType.PINBOARD) { - update_count_label (Planner.database.get_items_pinned (false).size); - - Planner.database.item_added.connect (() => { - update_count_label (Planner.database.get_items_pinned (false).size); - }); - - Planner.database.item_deleted.connect (() => { - update_count_label (Planner.database.get_items_pinned (false).size); - }); - - Planner.database.item_updated.connect (() => { - update_count_label (Planner.database.get_items_pinned (false).size); + update_count_label (Objects.Pinboard.get_default ().pinboard_count); + Objects.Pinboard.get_default ().pinboard_count_updated.connect (() => { + update_count_label (Objects.Pinboard.get_default ().pinboard_count); }); } } diff --git a/src/Layouts/HeaderItem.vala b/src/Layouts/HeaderItem.vala index 559181c34..3593fa420 100644 --- a/src/Layouts/HeaderItem.vala +++ b/src/Layouts/HeaderItem.vala @@ -223,4 +223,22 @@ public class Layouts.HeaderItem : Gtk.EventBox { flowbox.show_all (); } } + + public void init_update_position_project () { + listbox.add.connect (update_projects_position); + listbox.remove.connect (update_projects_position); + } + + private void update_projects_position () { + Timeout.add (main_revealer.transition_duration, () => { + GLib.List projects = listbox.get_children (); + for (int index = 0; index < projects.length (); index++) { + Objects.Project project = ((Layouts.ProjectRow) projects.nth_data (index)).project; + project.child_order = index + 1; + Planner.database.update_child_order (project); + } + + return GLib.Source.REMOVE; + }); + } } diff --git a/src/Layouts/ItemRow.vala b/src/Layouts/ItemRow.vala index c10a81b0c..beb53227e 100644 --- a/src/Layouts/ItemRow.vala +++ b/src/Layouts/ItemRow.vala @@ -3,6 +3,7 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { public int64 project_id { get; set; default = Constants.INACTIVE; } public int64 section_id { get; set; default = Constants.INACTIVE; } + public int64 parent_id { get; set; default = Constants.INACTIVE; } private Gtk.CheckButton checked_button; private Widgets.Entry content_entry; @@ -32,9 +33,12 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { private Widgets.PriorityButton priority_button; private Widgets.LabelButton label_button; private Widgets.PinButton pin_button; + private Widgets.ReminderButton reminder_button; + private Gtk.Button add_button; private Gtk.Revealer submit_cancel_revealer; private Gtk.Button delete_button; private Gtk.Button menu_button; + private Widgets.SubItems subitems; private Gtk.Grid main_grid; bool _edit = false; @@ -44,6 +48,8 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { if (value) { handle_grid.get_style_context ().add_class ("card"); + handle_grid.get_style_context ().add_class (is_creating ? "mt-12" : "mt-24"); + get_style_context ().add_class ("mb-12"); content_entry.get_style_context ().add_class ("font-weight-500"); detail_revealer.reveal_child = true; @@ -64,6 +70,9 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { } } else { handle_grid.get_style_context ().remove_class ("card"); + handle_grid.get_style_context ().remove_class ("mt-12"); + handle_grid.get_style_context ().remove_class ("mt-24"); + get_style_context ().remove_class ("mb-12"); content_entry.get_style_context ().remove_class ("font-weight-500"); detail_revealer.reveal_child = false; @@ -139,6 +148,18 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { ); } + public ItemRow.for_parent (Objects.Item _item) { + var item = new Objects.Item (); + item.project_id = _item.project_id; + item.section_id = _item.section_id; + item.parent_id = _item.id; + + Object ( + item: item, + can_focus: false + ); + } + public ItemRow.for_section (Objects.Section section) { var item = new Objects.Item (); item.section_id = section.id; @@ -155,6 +176,7 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { project_id = item.project_id; section_id = item.section_id; + parent_id = item.parent_id; build_content (); @@ -201,6 +223,27 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { pin_button = new Widgets.PinButton (item); pin_button.get_style_context ().add_class ("no-padding"); + + reminder_button = new Widgets.ReminderButton (item); + reminder_button.get_style_context ().add_class ("no-padding"); + + var add_image = new Widgets.DynamicIcon (); + add_image.size = 19; + add_image.update_icon_name ("planner-plus-circle"); + + add_button = new Gtk.Button () { + valign = Gtk.Align.CENTER, + can_focus = false, + tooltip_text = _("Addd subtask"), + margin_top = 1, + no_show_all = is_creating + }; + + add_button.add (add_image); + + unowned Gtk.StyleContext add_button_context = add_button.get_style_context (); + add_button_context.add_class (Gtk.STYLE_CLASS_FLAT); + add_button_context.add_class ("no-padding"); var action_grid = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12) { margin_start = 20, @@ -212,8 +255,10 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { action_grid.pack_start (schedule_button, false, false, 0); action_grid.pack_end (pin_button, false, false, 0); + action_grid.pack_end (reminder_button, false, false, 0); action_grid.pack_end (priority_button, false, false, 0); action_grid.pack_end (label_button, false, false, 0); + action_grid.pack_end (add_button, false, false, 0); var details_grid = new Gtk.Grid () { orientation = Gtk.Orientation.VERTICAL @@ -261,7 +306,9 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { var bottom_motion_grid = new Gtk.Grid () { margin_start = 6, - margin_end = 6 + margin_end = 6, + margin_top = 6, + margin_bottom = 6 }; bottom_motion_grid.get_style_context ().add_class ("grid-motion"); bottom_motion_grid.height_request = 16; @@ -328,7 +375,6 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { margin = 3, margin_start = 6, margin_end = 6, - margin_bottom = 12 }; actionbar_box.pack_start (submit_cancel_revealer, false, false, 0); @@ -342,6 +388,8 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { actionbar_revealer.add (actionbar_box); + subitems = new Widgets.SubItems (item); + main_grid = new Gtk.Grid () { orientation = Gtk.Orientation.VERTICAL }; @@ -349,6 +397,7 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { main_grid.add (top_motion_revealer); main_grid.add (itemrow_eventbox); main_grid.add (actionbar_revealer); + main_grid.add (subitems); main_grid.add (bottom_motion_revealer); main_revealer = new Gtk.Revealer () { @@ -360,20 +409,22 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { update_request (); - if (!item.checked) { - Gtk.drag_source_set (this, Gdk.ModifierType.BUTTON1_MASK, Util.get_default ().ITEMROW_TARGET_ENTRIES, Gdk.DragAction.MOVE); - drag_begin.connect (on_drag_begin); - drag_data_get.connect (on_drag_data_get); - drag_end.connect (clear_indicator); - - build_drag_and_drop (false); - } - Timeout.add (main_revealer.transition_duration, () => { main_revealer.reveal_child = true; + if (is_creating) { edit = true; } + + if (!item.checked) { + Gtk.drag_source_set (this, Gdk.ModifierType.BUTTON1_MASK, Util.get_default ().ITEMROW_TARGET_ENTRIES, Gdk.DragAction.MOVE); + drag_begin.connect (on_drag_begin); + drag_data_get.connect (on_drag_data_get); + drag_end.connect (clear_indicator); + + build_drag_and_drop (false); + } + return GLib.Source.REMOVE; }); @@ -391,8 +442,7 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { hexpand = true, xalign = 0, wrap = false, - ellipsize = Pango.EllipsizeMode.END, - use_markup = true + ellipsize = Pango.EllipsizeMode.END }; content_label_revealer = new Gtk.Revealer () { @@ -538,7 +588,11 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { content_entry.key_release_event.connect ((key) => { if (key.keyval == 65307) { - Planner.event_bus.item_selected (null); + if (is_creating) { + hide_destroy (); + } else { + Planner.event_bus.item_selected (null); + } } else { if (!is_creating) { update (); @@ -552,7 +606,11 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { description_textview.key_release_event.connect ((key) => { if (key.keyval == 65307) { - Planner.event_bus.item_selected (null); + if (is_creating) { + hide_destroy (); + } else { + Planner.event_bus.item_selected (null); + } } else { if (!is_creating) { update (); @@ -663,6 +721,10 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { menu_button.clicked.connect (() => { }); + + add_button.clicked.connect (() => { + subitems.prepare_new_item (); + }); } private void labels_changed (Gee.HashMap labels) { @@ -683,6 +745,7 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { Planner.database.update_item (item); Planner.event_bus.item_moved (item, old_project_id, old_section_id); + project_button.update_request (); } private void update () { @@ -724,6 +787,9 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { submit_cancel_revealer.reveal_child = false; submit_button.is_loading = false; + add_button.no_show_all = false; + add_button.show_all (); + delete_button.no_show_all = false; delete_button.show_all (); @@ -737,6 +803,7 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { if (complete_timeout <= 0) { Util.get_default ().set_widget_priority (item.priority, checked_button); checked_button.active = item.completed; + if (item.completed && Planner.settings.get_boolean ("underline-completed-tasks")) { content_label.get_style_context ().add_class ("line-through"); } @@ -750,6 +817,7 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { item_summary.update_request (); schedule_button.update_request (); priority_button.update_request (); + project_button.update_request (); pin_button.update_request (); if (!edit) { @@ -855,29 +923,38 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { if (source_row == target_row || target_row == null) { return; - } - - int64 old_project_id = source_row.item.project_id; - int64 old_section_id = source_row.item.section_id; + } - if (source_row.item.project_id != target_row.item.project_id) { - source_row.item.project_id = target_row.item.project_id; - } + if (source_row.item.project_id != target_row.item.project_id || + source_row.item.section_id != target_row.item.section_id || + source_row.item.parent_id != target_row.item.parent_id) { - if (source_row.item.section_id != target_row.item.section_id) { - source_row.item.section_id = target_row.item.section_id; - } + if (source_row.item.project_id != target_row.item.project_id) { + source_row.item.project_id = target_row.item.project_id; + } - if (old_project_id != source_row.item.project_id || old_section_id != source_row.item.section_id) { - if (source_row.item.project.todoist) { + if (source_row.item.section_id != target_row.item.section_id) { + source_row.item.section_id = target_row.item.section_id; + } + if (source_row.item.parent_id != target_row.item.parent_id) { + source_row.item.parent_id = target_row.item.parent_id; + } + + if (source_row.item.project.todoist) { int64 move_id = source_row.item.project_id; string move_type = "project_id"; + if (source_row.item.section_id != Constants.INACTIVE) { - move_type = "section_id"; move_id = source_row.item.section_id; + move_type = "section_id"; } - + + if (source_row.item.parent_id != Constants.INACTIVE) { + move_id = source_row.item.parent_id; + move_type = "parent_id"; + } + Planner.todoist.move_item.begin (source_row.item, move_type, move_id, (obj, res) => { if (Planner.todoist.move_item.end (res)) { Planner.database.update_item (source_row.item); @@ -1024,7 +1101,7 @@ public class Layouts.ItemRow : Gtk.ListBoxRow { new GLib.DateTime.now_local ()).to_string (); Planner.database.checked_toggled (item, old_checked); - + return GLib.Source.REMOVE; }); } else { diff --git a/src/Layouts/ProjectRow.vala b/src/Layouts/ProjectRow.vala index 2f4e98783..7daad3b62 100644 --- a/src/Layouts/ProjectRow.vala +++ b/src/Layouts/ProjectRow.vala @@ -491,7 +491,7 @@ public class Layouts.ProjectRow : Gtk.ListBoxRow { menu.add_item (edit_item); menu.add_item (new Dialogs.ContextMenu.MenuSeparator ()); // menu.add_item (move_item); - menu.add_item (share_item); + // menu.add_item (share_item); menu.add_item (new Dialogs.ContextMenu.MenuSeparator ()); menu.add_item (delete_item); diff --git a/src/Layouts/SectionRow.vala b/src/Layouts/SectionRow.vala index 119ec3a18..ef815b675 100644 --- a/src/Layouts/SectionRow.vala +++ b/src/Layouts/SectionRow.vala @@ -24,6 +24,7 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { public Objects.Section section { get; construct; } private Widgets.EditableLabel name_editable; + private Widgets.LoadingButton menu_loading_button; private Gtk.ListBox listbox; private Gtk.ListBox checked_listbox; private Gtk.Revealer checked_revealer; @@ -33,6 +34,9 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { private Gtk.Grid placeholder_grid; private Gtk.EventBox placeholder_eventbox; private Gtk.Revealer placeholder_revealer; + private Gtk.Grid content_grid; + private Gtk.Revealer top_motion_revealer; + private Gtk.Revealer bottom_motion_revealer; public bool is_inbox_section { get { @@ -82,12 +86,22 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { items = new Gee.HashMap (); items_checked = new Gee.HashMap (); - name_editable = new Widgets.EditableLabel ("font-bold", _("New section")) { + name_editable = new Widgets.EditableLabel ("font-bold", _("New section"), false) { valign = Gtk.Align.CENTER, hexpand = true }; name_editable.text = section.name; + menu_loading_button = new Widgets.LoadingButton (LoadingButtonType.ICON, "content-loading-symbolic") { + valign = Gtk.Align.CENTER, + halign = Gtk.Align.END, + can_focus = false, + hexpand = false + }; + menu_loading_button.get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + menu_loading_button.get_style_context ().add_class ("no-padding"); + menu_loading_button.get_style_context ().add_class (Granite.STYLE_CLASS_SMALL_LABEL); + var menu_image = new Gtk.Image () { gicon = new ThemedIcon ("content-loading-symbolic"), pixel_size = 16 @@ -106,10 +120,12 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { var sectionrow_grid = new Gtk.Grid () { column_spacing = 6, margin_start = 6, - margin_end = 6 + margin_end = 6, + margin_top = 3, + margin_bottom = 3 }; sectionrow_grid.add (name_editable); - sectionrow_grid.add (menu_button); + sectionrow_grid.add (menu_loading_button); var sectionrow_revealer = new Gtk.Revealer () { transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN, @@ -167,22 +183,56 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { checked_revealer.add (checked_listbox_grid); - var main_grid = new Gtk.Grid () { + var top_motion_grid = new Gtk.Grid () { + margin_top = 6, + margin_start = 6, + margin_end = 6, + margin_bottom = 12, + height_request = 16 + }; + top_motion_grid.get_style_context ().add_class ("grid-motion"); + + top_motion_revealer = new Gtk.Revealer () { + transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN + }; + top_motion_revealer.add (top_motion_grid); + + var bottom_motion_grid = new Gtk.Grid () { + margin_start = 6, + margin_end = 6, + margin_top = 12 + }; + bottom_motion_grid.get_style_context ().add_class ("grid-motion"); + bottom_motion_grid.height_request = 16; + + bottom_motion_revealer = new Gtk.Revealer () { + transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN + }; + bottom_motion_revealer.add (bottom_motion_grid); + + content_grid = new Gtk.Grid () { orientation = Gtk.Orientation.VERTICAL, hexpand = true }; - main_grid.add (sectionrow_eventbox); - main_grid.add (listbox_grid); - main_grid.add (checked_revealer); + content_grid.add (top_motion_revealer); + content_grid.add (sectionrow_eventbox); + content_grid.add (listbox_grid); + content_grid.add (checked_revealer); + content_grid.add (bottom_motion_revealer); + content_grid.get_style_context ().add_class ("red-background"); main_revealer = new Gtk.Revealer () { transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN }; - main_revealer.add (main_grid); + main_revealer.add (content_grid); add (main_revealer); + if (!is_inbox_section) { + build_drag_and_drop (); + } + add_items (); Timeout.add (main_revealer.transition_duration, () => { @@ -203,10 +253,6 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { name_editable.text = section.name; }); - section.deleted.connect (() => { - hide_destroy (); - }); - if (is_inbox_section) { section.project.item_added.connect ((item) => { add_item (item); @@ -234,17 +280,25 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { }); sectionrow_eventbox.button_press_event.connect ((sender, evt) => { - if (evt.type == Gdk.EventType.BUTTON_PRESS && evt.button == 3) { + if (evt.type == Gdk.EventType.BUTTON_PRESS && evt.button == 1) { + Timeout.add (Constants.DRAG_TIMEOUT, () => { + if (main_revealer.reveal_child) { + name_editable.editing (true); + } + return GLib.Source.REMOVE; + }); + } else if (evt.type == Gdk.EventType.BUTTON_PRESS && evt.button == 3) { build_content_menu (); } return Gdk.EVENT_PROPAGATE; }); - menu_button.clicked.connect (build_content_menu); + menu_loading_button.clicked.connect (build_content_menu); Planner.event_bus.checked_toggled.connect ((item, old_checked) => { - if (item.project_id == section.project_id && item.section_id == section.id) { + if (item.project_id == section.project_id && item.section_id == section.id && + item.parent_id == Constants.INACTIVE) { if (!old_checked) { if (items.has_key (item.id_string)) { items [item.id_string].hide_destroy (); @@ -295,7 +349,7 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { } }); - Planner.event_bus.item_moved.connect ((item, old_project_id, old_section_id, insert) => { + Planner.event_bus.item_moved.connect ((item, old_project_id, old_section_id, old_parent_id, insert) => { if (old_project_id == section.project_id && old_section_id == section.id) { if (items.has_key (item.id_string)) { items [item.id_string].hide_destroy (); @@ -308,7 +362,8 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { } } - if (item.project_id == section.project_id && item.section_id == section.id) { + if (item.project_id == section.project_id && item.section_id == section.id && + item.parent_id == Constants.INACTIVE) { add_item (item); } }); @@ -364,7 +419,7 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { for (int index = 0; index < items.length (); index++) { Objects.Item item = ((Layouts.ItemRow) items.nth_data (index)).item; item.child_order = index + 1; - Planner.database.update_item_position (item); + Planner.database.update_child_order (item); } return GLib.Source.REMOVE; }); @@ -481,23 +536,28 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { private void build_content_menu () { var menu = new Dialogs.ContextMenu.Menu (); + var add_item = new Dialogs.ContextMenu.MenuItem (_("Add task"), "planner-plus-circle"); var edit_item = new Dialogs.ContextMenu.MenuItem (_("Edit section"), "planner-edit"); - // var move_item = new Dialogs.ContextMenu.MenuItemSelector (_("Move section"), true); + var move_item = new Dialogs.ContextMenu.MenuItem (_("Move section"), "chevron-right"); var delete_item = new Dialogs.ContextMenu.MenuItem (_("Delete section"), "planner-trash"); var delete_item_context = delete_item.get_style_context (); delete_item_context.add_class ("menu-item-danger"); - // foreach (Objects.Project project in Planner.database.projects) { - // move_item.add_item (new Dialogs.ProjectPicker.ProjectRow (project)); - // } - + menu.add_item (add_item); + menu.add_item (new Dialogs.ContextMenu.MenuSeparator ()); menu.add_item (edit_item); - // menu.add_item (move_item); + menu.add_item (move_item); + menu.add_item (new Dialogs.ContextMenu.MenuSeparator ()); menu.add_item (delete_item); menu.popup (); + add_item.activate_item.connect (() => { + menu.hide_destroy (); + prepare_new_item (); + }); + edit_item.activate_item.connect (() => { menu.hide_destroy (); name_editable.editing (true); @@ -508,22 +568,39 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { section.delete (); }); - // move_item.activate_item.connect ((row) => { - // menu.hide_destroy (); - - // // int64 old_parent_id = project.parent_id; + move_item.activate_item.connect ((row) => { + var picker = new Dialogs.ProjectPicker.ProjectPicker (false); + picker.popup (); - // // project.parent_id = ((Dialogs.ProjectSelector.ProjectRow) row).project.id; - // // Planner.database.update_project (project); + picker.project = section.project; - // // if (project.parent_id != old_parent_id) { - // // Planner.event_bus.project_parent_changed (project, old_parent_id); - // // } - // }); + picker.changed.connect ((project_id, section_id) => { + move_section (project_id); + }); + }); + } + + private void move_section (int64 project_id) { + int64 old_section_id = int64.parse (section.project_id.to_string ()); + section.project_id = project_id; + + if (section.project.todoist) { + menu_loading_button.is_loading = true; + Planner.todoist.move_section.begin (section, project_id, (obj, res) => { + if (Planner.todoist.move_section.end (res)) { + Planner.database.move_section (section, old_section_id); + menu_loading_button.is_loading = false; + } else { + menu_loading_button.is_loading = false; + } + }); + } else { + Planner.database.move_section (section, project_id); + } } private Gtk.Widget get_placeholder () { - var message_label = new Gtk.Label (_("No tasks available. Create one by clicking on the '+' button")) { + var message_label = new Gtk.Label (_("No tasks available. Create one by dragging the '+' button here or clicking on this space.")) { wrap = true, justify = Gtk.Justification.CENTER, hexpand = true, @@ -553,6 +630,15 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { reveal_child = true }; placeholder_revealer.add (placeholder_eventbox); + + placeholder_eventbox.button_press_event.connect ((sender, evt) => { + if (evt.type == Gdk.EventType.BUTTON_PRESS && evt.button == 1) { + prepare_new_item (); + } + + return Gdk.EVENT_PROPAGATE; + }); + placeholder_revealer.show_all (); build_placeholder_drag_and_drop (false); @@ -597,28 +683,29 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { Gtk.SelectionData selection_data, uint target_type, uint time) { var data = ((Gtk.Widget[]) selection_data.get_data ()) [0]; var source_row = (Layouts.ItemRow) data; - - int64 old_project_id = source_row.item.project_id; - int64 old_section_id = source_row.item.section_id; - if (source_row.item.project_id != section.project_id) { - source_row.item.project_id = section.project_id; - } + if (source_row.item.project_id != section.project_id || + source_row.item.section_id != section.id) { - if (source_row.item.section_id != section.id) { - source_row.item.section_id = section.id; - } + if (source_row.item.project_id != section.project_id) { + source_row.item.project_id = section.project_id; + } - if (old_project_id != source_row.item.project_id || old_section_id != source_row.item.section_id) { - if (source_row.item.project.todoist) { + if (source_row.item.section_id != section.id) { + source_row.item.section_id = section.id; + } + + source_row.item.parent_id = Constants.INACTIVE; + if (source_row.item.project.todoist) { int64 move_id = source_row.item.project_id; string move_type = "project_id"; + if (source_row.item.section_id != Constants.INACTIVE) { - move_type = "section_id"; move_id = source_row.item.section_id; + move_type = "section_id"; } - + Planner.todoist.move_item.begin (source_row.item, move_type, move_id, (obj, res) => { if (Planner.todoist.move_item.end (res)) { Planner.database.update_item (source_row.item); @@ -638,4 +725,75 @@ public class Layouts.SectionRow : Gtk.ListBoxRow { Planner.event_bus.update_items_position (section.project_id, section.id); } + + private void build_drag_and_drop () { + Gtk.drag_source_set (this, Gdk.ModifierType.BUTTON1_MASK, Util.get_default ().SECTIONROW_TARGET_ENTRIES, Gdk.DragAction.MOVE); + drag_begin.connect (on_drag_begin); + drag_data_get.connect (on_drag_data_get); + drag_end.connect (clear_indicator); + + Gtk.drag_dest_set (this, Gtk.DestDefaults.MOTION, Util.get_default ().SECTIONROW_TARGET_ENTRIES, Gdk.DragAction.MOVE); + drag_motion.connect (on_drag_motion); + drag_leave.connect (on_drag_leave); + } + + public bool on_drag_motion (Gdk.DragContext context, int x, int y, uint time) { + Gtk.Allocation alloc; + get_allocation (out alloc); + + if (get_index () == 1) { + if (y > (alloc.height / 2)) { + bottom_motion_revealer.reveal_child = true; + top_motion_revealer.reveal_child = false; + } else { + bottom_motion_revealer.reveal_child = false; + top_motion_revealer.reveal_child = true; + } + } else { + bottom_motion_revealer.reveal_child = true; + } + + return true; + } + + public void on_drag_leave (Gdk.DragContext context, uint time) { + bottom_motion_revealer.reveal_child = false; + top_motion_revealer.reveal_child = false; + } + + private void on_drag_begin (Gtk.Widget widget, Gdk.DragContext context) { + var row = ((Layouts.SectionRow) widget).handle_grid; + + Gtk.Allocation row_alloc; + row.get_allocation (out row_alloc); + + var surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, row_alloc.width, row_alloc.height); + var cairo_context = new Cairo.Context (surface); + + var style_context = row.get_style_context (); + style_context.add_class ("drag-begin"); + row.draw_to_cairo_context (cairo_context); + style_context.remove_class ("drag-begin"); + + int drag_icon_x, drag_icon_y; + widget.translate_coordinates (row, 0, 0, out drag_icon_x, out drag_icon_y); + surface.set_device_offset (-drag_icon_x, -drag_icon_y); + + Gtk.drag_set_icon_surface (context, surface); + main_revealer.reveal_child = false; + } + + private void on_drag_data_get (Gtk.Widget widget, Gdk.DragContext context, + Gtk.SelectionData selection_data, uint target_type, uint time) { + uchar[] data = new uchar[(sizeof (Layouts.SectionRow))]; + ((Gtk.Widget[])data)[0] = widget; + + selection_data.set ( + Gdk.Atom.intern_static_string ("SECTIONROW"), 32, data + ); + } + + public void clear_indicator (Gdk.DragContext context) { + main_revealer.reveal_child = true; + } } diff --git a/src/Layouts/Sidebar.vala b/src/Layouts/Sidebar.vala index ccc654b9a..73f26bbb4 100644 --- a/src/Layouts/Sidebar.vala +++ b/src/Layouts/Sidebar.vala @@ -184,6 +184,8 @@ public class Layouts.Sidebar : Gtk.EventBox { foreach (Objects.Project project in Planner.database.projects) { add_row_project (project); } + + projects_header.init_update_position_project (); } private void add_all_favorites () { diff --git a/src/Layouts/ViewHeader.vala b/src/Layouts/ViewHeader.vala index 3489ace45..2b4c49728 100644 --- a/src/Layouts/ViewHeader.vala +++ b/src/Layouts/ViewHeader.vala @@ -137,6 +137,7 @@ public class Layouts.ViewHeader : Hdy.HeaderBar { private void project_update_request () { project_revealer.reveal_child = false; + end_revealer.reveal_child = false; project_progress.progress_fill_color = Util.get_default ().get_color (project.color); project_progress.percentage = project.percentage; diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 8641a5a4d..e4c749805 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -117,8 +117,9 @@ public class MainWindow : Hdy.Window { Planner.settings.bind ("slim-mode", flap_view, "reveal_flap", GLib.SettingsBindFlags.DEFAULT); welcome_view.activated.connect ((index) => { - Planner.settings.set_enum ("backend-type", index + 1); - init_backend (); + if (Planner.settings.set_enum ("backend-type", index + 1)) { + init_backend (); + } }); Timeout.add (main_stack.transition_duration, () => { @@ -147,6 +148,11 @@ public class MainWindow : Hdy.Window { Planner.settings.changed.connect ((key) => { if (key == "appearance") { Util.get_default ().update_theme (); + } else if (key == "badge-count") { + Timeout.add (main_stack.transition_duration, () => { + Services.Badge.get_default ().update_badge (); + return GLib.Source.REMOVE; + }); } }); } @@ -241,12 +247,20 @@ public class MainWindow : Hdy.Window { return project_view; } + public void valid_view_removed (Objects.Project project) { + Views.Project? project_view; + project_view = (Views.Project) views_stack.get_child_by_name (project.view_id); + if (project_view != null) { + project_view.destroy (); + go_homepage (); + } + } + private void init_backend () { BackendType backend_type = (BackendType) Planner.settings.get_enum ("backend-type"); if (backend_type == BackendType.LOCAL) { Planner.database = Services.Database.get_default (); Planner.database.init_database (); - // Services.Badge.get_default ().init (); main_stack.visible_child_name = "main-view"; @@ -254,8 +268,7 @@ public class MainWindow : Hdy.Window { create_inbox_project (); } - sidebar.init (backend_type); - Planner.event_bus.pane_selected (PaneType.FILTER, FilterType.INBOX.to_string ()); + init_local_todoist_backend (backend_type); } else if (backend_type == BackendType.TODOIST) { Planner.database = Services.Database.get_default (); Planner.database.init_database (); @@ -272,17 +285,13 @@ public class MainWindow : Hdy.Window { }); Planner.todoist.first_sync_finished.connect (() => { - sidebar.init (backend_type); - // Services.Badge.get_default ().init (); - Planner.event_bus.pane_selected (PaneType.FILTER, FilterType.INBOX.to_string ()); + init_local_todoist_backend (backend_type); }); main_stack.visible_child_name = "main-view"; if (!Planner.todoist.invalid_token () && !Planner.database.is_database_empty ()) { - sidebar.init (backend_type); - // Services.Badge.get_default ().init (); - Planner.event_bus.pane_selected (PaneType.FILTER, FilterType.INBOX.to_string ()); + init_local_todoist_backend (backend_type); Timeout.add (Constants.TODOIST_SYNC_TIMEOUT, () => { Services.Todoist.get_default ().sync_async (); @@ -296,6 +305,31 @@ public class MainWindow : Hdy.Window { } } + public void init_local_todoist_backend (BackendType backend_type) { + sidebar.init (backend_type); + Services.Badge.get_default (); + Services.Notification.get_default (); + go_homepage (); + + Planner.database.project_deleted.connect (valid_view_removed); + } + + public void go_homepage () { + if (Planner.settings.get_boolean ("homepage-project")) { + int64 project_id = Planner.settings.get_int64 ("homepage-project-id"); + if (Planner.database.get_project (project_id) != null) { + Planner.event_bus.pane_selected (PaneType.PROJECT, project_id.to_string ()); + } else { + Planner.event_bus.pane_selected (PaneType.FILTER, FilterType.INBOX.to_string ()); + } + } else { + Planner.event_bus.pane_selected ( + PaneType.FILTER, + Util.get_default ().get_filter ().to_string () + ); + } + } + private void create_inbox_project () { Objects.Project inbox_project = new Objects.Project (); inbox_project.id = Util.get_default ().generate_id (); diff --git a/src/Objects/BaseObject.vala b/src/Objects/BaseObject.vala index ebb934df2..72145620c 100644 --- a/src/Objects/BaseObject.vala +++ b/src/Objects/BaseObject.vala @@ -21,7 +21,7 @@ public class Objects.BaseObject : GLib.Object { public int64 id { get; set; default = Constants.INACTIVE; } - + public string name { get; set; default = ""; } public signal void deleted (); public signal void updated (); @@ -59,8 +59,56 @@ public class Objects.BaseObject : GLib.Object { return ObjectType.SECTION; } else if (this is Objects.Item) { return ObjectType.ITEM; - } else { + } else if (this is Objects.Label) { return ObjectType.LABEL; + } else { + return ObjectType.FILTER; + } + } + } + + public string icon_name { + get { + if (this is Objects.Today) { + return "planner-today"; + } else if (this is Objects.Scheduled) { + return "planner-scheduled"; + } else if (this is Objects.Pinboard) { + return "planner-pin-tack"; + } else { + return ""; + } + } + } + + public string table_name { + get { + if (this is Objects.Item) { + return "Items"; + } else if (this is Objects.Section) { + return "Sections"; + } else if (this is Objects.Project) { + return "Projects"; + } else if (this is Objects.Label) { + return "Labels"; + } else { + return ""; + } + } + } + + public string column_order_name { + get { + if (this is Objects.Item) { + return "child_order"; + } else if (this is Objects.Section) { + return "section_order"; + } else if (this is Objects.Project) { + return "child_order"; + } else if (this is Objects.Label) { + return "item_order"; + } else { + return ""; } } } diff --git a/src/Objects/Item.vala b/src/Objects/Item.vala index e665fcfb8..b4ce7103d 100644 --- a/src/Objects/Item.vala +++ b/src/Objects/Item.vala @@ -51,6 +51,7 @@ public class Objects.Item : Objects.BaseObject { public bool checked { get; set; default = false; } public bool is_deleted { get; set; default = false; } public bool collapsed { get; set; default = false; } + public bool pinned { get; set; default = false; } public string pinned_icon { get { @@ -87,6 +88,14 @@ public class Objects.Item : Objects.BaseObject { } } + Objects.Item? _parent; + public Objects.Item parent { + get { + _parent = Planner.database.get_item (parent_id); + return _parent; + } + } + Objects.Project? _project; public Objects.Project project { get { @@ -116,7 +125,25 @@ public class Objects.Item : Objects.BaseObject { } } + Gee.ArrayList _items; + public Gee.ArrayList items { + get { + _items = Planner.database.get_subitems (this); + return _items; + } + } + + Gee.ArrayList _reminders; + public Gee.ArrayList reminders { + get { + _reminders = Planner.database.get_reminders_by_item (this); + return _reminders; + } + } + public signal void item_label_added (Objects.ItemLabel item_label); + public signal void item_added (Objects.Item item); + public signal void reminder_added (Objects.Reminder reminder); construct { deleted.connect (() => { @@ -178,11 +205,15 @@ public class Objects.Item : Objects.BaseObject { } public void set_section (Objects.Section section) { - this._section = section; + _section = section; + } + + public void set_parent (Objects.Item item) { + _parent = item; } public void set_project (Objects.Project project) { - this._project = project; + _project = project; } public override string get_add_json (string temp_id, string uuid) { @@ -434,7 +465,12 @@ public class Objects.Item : Objects.BaseObject { if (temp_id != null) { builder.set_member_name ("project_id"); builder.add_int_value (project_id); - + + if (parent_id != Constants.INACTIVE) { + builder.set_member_name ("parent_id"); + builder.add_int_value (parent_id); + } + if (section_id != Constants.INACTIVE) { builder.set_member_name ("section_id"); builder.add_int_value (section_id); @@ -516,4 +552,35 @@ public class Objects.Item : Objects.BaseObject { Planner.database.delete_item (this); } } + + public Objects.Item add_item_if_not_exists (Objects.Item new_item, bool insert=true) { + Objects.Item? return_value = null; + lock (_items) { + return_value = get_item (new_item.id); + if (return_value == null) { + new_item.set_parent (this); + add_item (new_item); + Planner.database.insert_item (new_item, insert); + return_value = new_item; + } + return return_value; + } + } + + public Objects.Item? get_item (int64 id) { + Objects.Item? return_value = null; + lock (_items) { + foreach (var item in items) { + if (item.id == id) { + return_value = item; + break; + } + } + } + return return_value; + } + + public void add_item (Objects.Item item) { + _items.add (item); + } } diff --git a/src/Objects/Label.vala b/src/Objects/Label.vala index f807e4fa3..1657a613c 100644 --- a/src/Objects/Label.vala +++ b/src/Objects/Label.vala @@ -20,9 +20,8 @@ */ public class Objects.Label : Objects.BaseObject { - public string name { get; set; default = ""; } public string color { get; set; default = ""; } - public int item_order { get; set; default = 0; } + public int item_order { get; set; default = Constants.INACTIVE; } public bool is_deleted { get; set; default = false; } public bool is_favorite { get; set; default = false; } public bool todoist { get; set; default = false; } diff --git a/src/Objects/Pinboard.vala b/src/Objects/Pinboard.vala new file mode 100644 index 000000000..e0d1e3826 --- /dev/null +++ b/src/Objects/Pinboard.vala @@ -0,0 +1,45 @@ +public class Objects.Pinboard : Objects.BaseObject { + private static Pinboard? _instance; + public static Pinboard get_default () { + if (_instance == null) { + _instance = new Pinboard (); + } + + return _instance; + } + + int? _pinboard_count = null; + public int pinboard_count { + get { + if (_pinboard_count == null) { + _pinboard_count = Planner.database.get_items_pinned (false).size; + } + + return _pinboard_count; + } + + set { + _pinboard_count = value; + } + } + + public signal void pinboard_count_updated (); + + construct { + name = ("Pinboard"); + Planner.database.item_added.connect (() => { + _pinboard_count = Planner.database.get_items_pinned (false).size; + pinboard_count_updated (); + }); + + Planner.database.item_deleted.connect (() => { + _pinboard_count = Planner.database.get_items_pinned (false).size; + pinboard_count_updated (); + }); + + Planner.database.item_updated.connect (() => { + _pinboard_count = Planner.database.get_items_pinned (false).size; + pinboard_count_updated (); + }); + } +} diff --git a/src/Objects/Project.vala b/src/Objects/Project.vala index 63f909059..468297d7b 100644 --- a/src/Objects/Project.vala +++ b/src/Objects/Project.vala @@ -1,7 +1,5 @@ public class Objects.Project : Objects.BaseObject { public int64 parent_id { get; set; default = 0; } - - public string name { get; set; default = ""; } public string due_date { get; set; default = ""; } public string color { get; set; default = ""; } public string emoji { get; set; default = ""; } @@ -156,6 +154,14 @@ public class Objects.Project : Objects.BaseObject { project_count_updated (); } }); + + Planner.database.section_moved.connect ((section, old_project_id) => { + if (section.project_id == id || old_project_id == id) { + _project_count = update_project_count (); + _percentage = update_percentage (); + project_count_updated (); + } + }); } public Project.from_json (Json.Node node) { @@ -513,10 +519,14 @@ public class Objects.Project : Objects.BaseObject { var delete_item = new Dialogs.ContextMenu.MenuItem (_("Delete project"), "planner-trash"); delete_item.get_style_context ().add_class ("menu-item-danger"); - menu.add_item (edit_item); - menu.add_item (show_completed_item); - menu.add_item (new Dialogs.ContextMenu.MenuSeparator ()); + if (!inbox_project) { + menu.add_item (edit_item); + } + menu.add_item (add_section_item); + menu.add_item (new Dialogs.ContextMenu.MenuSeparator ()); + menu.add_item (show_completed_item); + if (!inbox_project) { menu.add_item (new Dialogs.ContextMenu.MenuSeparator ()); diff --git a/src/Objects/Reminder.vala b/src/Objects/Reminder.vala new file mode 100644 index 000000000..df1cc2c4f --- /dev/null +++ b/src/Objects/Reminder.vala @@ -0,0 +1,80 @@ +public class Objects.Reminder : Objects.BaseObject { + public int64 notify_uid { get; set; default = Constants.INACTIVE; } + public int64 item_id { get; set; default = Constants.INACTIVE; } + public string service { get; set; default = ""; } + public Objects.DueDate due { get; set; default = new Objects.DueDate (); } + public int mm_offset { get; set; default = Constants.INACTIVE; } + public int is_deleted { get; set; default = Constants.INACTIVE; } + + Objects.Item? _item; + public Objects.Item item { + get { + _item = Planner.database.get_item (item_id); + return _item; + } + + set { + _item = value; + } + } + + public override string get_add_json (string temp_id, string uuid) { + return get_update_json (uuid, temp_id); + } + + public override string get_update_json (string uuid, string? temp_id = null) { + var builder = new Json.Builder (); + builder.begin_array (); + builder.begin_object (); + + // Set type + builder.set_member_name ("type"); + builder.add_string_value (temp_id == null ? "reminder_update" : "reminder_add"); + + builder.set_member_name ("uuid"); + builder.add_string_value (uuid); + + if (temp_id != null) { + builder.set_member_name ("temp_id"); + builder.add_string_value (temp_id); + } + + builder.set_member_name ("args"); + builder.begin_object (); + + if (temp_id == null) { + builder.set_member_name ("id"); + builder.add_int_value (id); + } + + builder.set_member_name ("item_id"); + builder.add_int_value (item_id); + + builder.set_member_name ("due"); + builder.begin_object (); + + builder.set_member_name ("date"); + builder.add_string_value (due.date); + + builder.set_member_name ("is_recurring"); + builder.add_boolean_value (due.is_recurring); + + builder.set_member_name ("string"); + builder.add_string_value (due.text); + + builder.set_member_name ("lang"); + builder.add_string_value (due.lang); + + builder.end_object (); + + builder.end_object (); + builder.end_object (); + builder.end_array (); + + Json.Generator generator = new Json.Generator (); + Json.Node root = builder.get_root (); + generator.set_root (root); + + return generator.to_data (null); + } +} diff --git a/src/Objects/Scheduled.vala b/src/Objects/Scheduled.vala new file mode 100644 index 000000000..fef16a583 --- /dev/null +++ b/src/Objects/Scheduled.vala @@ -0,0 +1,46 @@ +public class Objects.Scheduled : Objects.BaseObject { + private static Scheduled? _instance; + public static Scheduled get_default () { + if (_instance == null) { + _instance = new Scheduled (); + } + + return _instance; + } + + int? _scheduled_count = null; + public int scheduled_count { + get { + if (_scheduled_count == null) { + _scheduled_count = Planner.database.get_items_by_scheduled (false).size; + } + + return _scheduled_count; + } + + set { + _scheduled_count = value; + } + } + + public signal void scheduled_count_updated (); + + construct { + name = _("Scheduled"); + + Planner.database.item_added.connect (() => { + _scheduled_count = Planner.database.get_items_by_scheduled (false).size; + scheduled_count_updated (); + }); + + Planner.database.item_deleted.connect (() => { + _scheduled_count = Planner.database.get_items_by_scheduled (false).size; + scheduled_count_updated (); + }); + + Planner.database.item_updated.connect (() => { + _scheduled_count = Planner.database.get_items_by_scheduled (false).size; + scheduled_count_updated (); + }); + } +} diff --git a/src/Objects/Section.vala b/src/Objects/Section.vala index bda89721d..e706a6656 100644 --- a/src/Objects/Section.vala +++ b/src/Objects/Section.vala @@ -21,10 +21,9 @@ public class Objects.Section : Objects.BaseObject { public int64 project_id { get; set; default = 0; } - public string name { get; set; default = ""; } public string archived_at { get; set; default = ""; } public string added_at { get; set; default = ""; } - public int section_order { get; set; default = -1; } + public int section_order { get; set; default = 0; } public bool collapsed { get; set; default = true; } public bool is_deleted { get; set; default = true; } public bool is_archived { get; set; default = true; } @@ -241,4 +240,36 @@ public class Objects.Section : Objects.BaseObject { } }); } + + public string get_move_section (string uuid, int64 new_project_id) { + var builder = new Json.Builder (); + builder.begin_array (); + builder.begin_object (); + + // Set type + builder.set_member_name ("type"); + builder.add_string_value ("section_move"); + + builder.set_member_name ("uuid"); + builder.add_string_value (uuid); + + builder.set_member_name ("args"); + builder.begin_object (); + + builder.set_member_name ("id"); + builder.add_int_value (id); + + builder.set_member_name ("project_id"); + builder.add_int_value (new_project_id); + + builder.end_object (); + builder.end_object (); + builder.end_array (); + + Json.Generator generator = new Json.Generator (); + Json.Node root = builder.get_root (); + generator.set_root (root); + + return generator.to_data (null); + } } diff --git a/src/Objects/Shortcut.vala b/src/Objects/Shortcut.vala new file mode 100644 index 000000000..e69de29bb diff --git a/src/Objects/Today.vala b/src/Objects/Today.vala new file mode 100644 index 000000000..4ca68ccb6 --- /dev/null +++ b/src/Objects/Today.vala @@ -0,0 +1,68 @@ +public class Objects.Today : Objects.BaseObject { + private static Today? _instance; + public static Today get_default () { + if (_instance == null) { + _instance = new Today (); + } + + return _instance; + } + + int? _today_count = null; + public int today_count { + get { + if (_today_count == null) { + _today_count = Planner.database.get_items_by_date ( + new GLib.DateTime.now_local (), false).size; + } + + return _today_count; + } + + set { + _today_count = value; + } + } + + int? _overdeue_count = null; + public int overdeue_count { + get { + if (_overdeue_count == null) { + _overdeue_count = Planner.database.get_items_by_overdeue_view (false).size; + } + + return _overdeue_count; + } + + set { + _overdeue_count = value; + } + } + + public signal void today_count_updated (); + + construct { + name = _("Today"); + + Planner.database.item_added.connect (() => { + _today_count = Planner.database.get_items_by_date ( + new GLib.DateTime.now_local (), false).size; + _overdeue_count = Planner.database.get_items_by_overdeue_view (false).size; + today_count_updated (); + }); + + Planner.database.item_deleted.connect (() => { + _today_count = Planner.database.get_items_by_date ( + new GLib.DateTime.now_local (), false).size; + _overdeue_count = Planner.database.get_items_by_overdeue_view (false).size; + today_count_updated (); + }); + + Planner.database.item_updated.connect (() => { + _today_count = Planner.database.get_items_by_date ( + new GLib.DateTime.now_local (), false).size; + _overdeue_count = Planner.database.get_items_by_overdeue_view (false).size; + today_count_updated (); + }); + } +} \ No newline at end of file diff --git a/src/Services/ActionManager.vala b/src/Services/ActionManager.vala index fd6fd17da..2feadacaf 100644 --- a/src/Services/ActionManager.vala +++ b/src/Services/ActionManager.vala @@ -28,7 +28,7 @@ public class Services.ActionManager : Object { public const string ACTION_PREFIX = "win."; public const string ACTION_QUIT = "action_quit"; public const string ACTION_PREFERENCES = "action_preferences"; - // public const string ACTION_SHORTCUTS = "action_shortcuts"; + public const string ACTION_SHORTCUTS = "action_shortcuts"; public const string ACTION_ADD_TASK = "action_add_task"; public const string ACTION_ADD_TASK_PASTE = "action_add_task_paste"; public const string ACTION_OPEN_SEARCH = "action_open_search"; @@ -39,7 +39,7 @@ public class Services.ActionManager : Object { public const string ACTION_VIEW_TODAY = "action_view_today"; public const string ACTION_VIEW_SCHEDULED = "action_view_scheduled"; public const string ACTION_VIEW_PINBOARD = "action_view_pinboard"; - // public const string ACTION_VIEW_HOME = "action_view_home"; + public const string ACTION_VIEW_HOME = "action_view_home"; public const string ACTION_ESC = "action_esc"; // public const string ACTION_SORT_DATE = "action_sort_date"; // public const string ACTION_SORT_PRIORITY = "action_sort_priority"; @@ -53,7 +53,7 @@ public class Services.ActionManager : Object { private const ActionEntry[] ACTION_ENTRIES = { { ACTION_QUIT, action_quit }, { ACTION_PREFERENCES, action_preferences }, - // { ACTION_SHORTCUTS, action_shortcuts }, + { ACTION_SHORTCUTS, action_shortcuts }, { ACTION_ADD_TASK, action_add_task }, { ACTION_ADD_TASK_PASTE, action_add_task_paste }, { ACTION_OPEN_SEARCH, action_open_search }, @@ -64,7 +64,7 @@ public class Services.ActionManager : Object { { ACTION_VIEW_TODAY, action_view_today }, { ACTION_VIEW_SCHEDULED, action_view_scheduled }, { ACTION_VIEW_PINBOARD, action_view_pinboard }, - // { ACTION_VIEW_HOME, action_view_home }, + { ACTION_VIEW_HOME, action_view_home }, { ACTION_ESC, action_esc }, // { ACTION_SORT_DATE, action_sort_date }, // { ACTION_SORT_PRIORITY, action_sort_priority }, @@ -82,7 +82,7 @@ public class Services.ActionManager : Object { static construct { action_accelerators.set (ACTION_QUIT, "q"); action_accelerators.set (ACTION_PREFERENCES, "comma"); - // action_accelerators.set (ACTION_SHORTCUTS, "F1"); + action_accelerators.set (ACTION_SHORTCUTS, "F1"); action_accelerators.set (ACTION_OPEN_SEARCH, "f"); action_accelerators.set (ACTION_SYNC_MANUALLY, "s"); action_accelerators.set (ACTION_VIEW_INBOX, "1"); @@ -100,7 +100,7 @@ public class Services.ActionManager : Object { // typing_accelerators.set (ACTION_SORT_DATE, "d"); // typing_accelerators.set (ACTION_SORT_PRIORITY, "r"); // typing_accelerators.set (ACTION_SORT_NAME, "n"); - // typing_accelerators.set (ACTION_VIEW_HOME, "h"); + typing_accelerators.set (ACTION_VIEW_HOME, "h"); } construct { @@ -191,4 +191,13 @@ public class Services.ActionManager : Object { window.add_task_action (content); } + + private void action_shortcuts () { + var dialog = new Dialogs.Shortcuts.Shortcuts (); + dialog.show_all (); + } + + private void action_view_home () { + window.go_homepage (); + } } \ No newline at end of file diff --git a/src/Services/Badge.vala b/src/Services/Badge.vala index 8bb6974a2..986850d2e 100644 --- a/src/Services/Badge.vala +++ b/src/Services/Badge.vala @@ -29,32 +29,38 @@ public class Services.Badge : GLib.Object { return _instance; } - int? _badge_count = null; public int badge_count { get { - if (_badge_count == null) { - _badge_count = get_badge_count_size (); - } - - return _badge_count; + return get_badge_count_size (); } + } - set { - _badge_count = value; + public bool badge_visible { + get { + if (Planner.settings.get_enum ("badge-count") == 0) { + return false; + } + + return badge_count > 0; } } - public void init () { + construct { update_badge (); + + Planner.database.item_added.connect (update_badge); + Planner.database.item_deleted.connect (update_badge); + Planner.database.item_updated.connect (update_badge); } public void update_badge () { - if (badge_visible ()) { + update_badge_visible (badge_visible); + if (badge_visible) { update_badge_count (); } } - private bool badge_visible (bool visible = Planner.settings.get_enum ("badge-count") != 0) { + private void update_badge_visible (bool visible) { Granite.Services.Application.set_badge_visible.begin (visible, (obj, res) => { try { Granite.Services.Application.set_badge_visible.end (res); @@ -62,16 +68,12 @@ public class Services.Badge : GLib.Object { critical (e.message); } }); - - return visible; } private void update_badge_count () { Granite.Services.Application.set_badge.begin (badge_count, (obj, res) => { try { - if (Granite.Services.Application.set_badge.end (res)) { - badge_visible (badge_count > 0); - } + Granite.Services.Application.set_badge.end (res); } catch (GLib.Error e) { critical (e.message); } @@ -79,17 +81,23 @@ public class Services.Badge : GLib.Object { } private int get_badge_count_size () { - int count = 0; + int returned = 0; int badge_count = Planner.settings.get_enum ("badge-count"); - if (badge_count == 1) { - count = Planner.database.get_project (Planner.settings.get_int64 ("inbox-project-id")) - .project_count; - } else if (badge_count == 2) { - count = Planner.database.get_items_by_date (new GLib.DateTime.now_local (), false).size; - } else if (badge_count == 3) { - + if (badge_count == 0) { + return returned; + } + + if (badge_count == 1) { // Inbox + returned = Planner.database.get_project ( + Planner.settings.get_int64 ("inbox-project-id")).project_count; + } if (badge_count == 2) { // Today + returned = Objects.Today.get_default ().today_count; + } else if (badge_count == 3) { // Inbox + Today + returned = Planner.database.get_project ( + Planner.settings.get_int64 ("inbox-project-id")).project_count + + Objects.Today.get_default ().today_count; } - return count; + return returned; } } \ No newline at end of file diff --git a/src/Services/Database.vala b/src/Services/Database.vala index 0f2ef3c04..06bbf24da 100644 --- a/src/Services/Database.vala +++ b/src/Services/Database.vala @@ -14,6 +14,7 @@ public class Services.Database : GLib.Object { public signal void label_deleted (Objects.Label label); public signal void section_deleted (Objects.Section section); + public signal void section_moved (Objects.Section section, int64 old_project_id); public signal void item_deleted (Objects.Item item); public signal void item_added (Objects.Item item, bool insert = true); @@ -22,6 +23,9 @@ public class Services.Database : GLib.Object { public signal void item_label_added (Objects.ItemLabel item_label); public signal void item_label_deleted (Objects.ItemLabel item_label); + public signal void reminder_added (Objects.Reminder reminder); + public signal void reminder_deleted (int64 id); + private static Database? _instance; public static Database get_default () { if (_instance == null) { @@ -71,6 +75,17 @@ public class Services.Database : GLib.Object { } } + Gee.ArrayList _reminders = null; + public Gee.ArrayList reminders { + get { + if (_reminders == null) { + _reminders = get_reminders_collection (); + } + + return _reminders; + } + } + construct { label_deleted.connect ((label) => { if (_labels.remove (label)) { @@ -95,6 +110,16 @@ public class Services.Database : GLib.Object { debug ("item Removed: %s", item.content); } }); + + reminder_deleted.connect ((id) => { + foreach (Objects.Reminder reminder in reminders) { + if (reminder.id == id) { + if (_reminders.remove (reminder)) { + debug ("Reminder Removed: %s", id.to_string ()); + } + } + } + }); } public void init_database () { @@ -216,6 +241,24 @@ public class Services.Database : GLib.Object { ); """; + if (db.exec (sql, null, out errormsg) != Sqlite.OK) { + warning (errormsg); + } + + sql = """ + CREATE TABLE IF NOT EXISTS Reminders ( + id INTEGER PRIMARY KEY, + notify_uid INTEGER, + item_id INTEGER, + service TEXT, + type TEXT, + due TEXT, + mm_offset INTEGER, + is_deleted INTEGER, + FOREIGN KEY (item_id) REFERENCES Items (id) ON DELETE CASCADE + ); + """; + if (db.exec (sql, null, out errormsg) != Sqlite.OK) { warning (errormsg); } @@ -266,6 +309,19 @@ public class Services.Database : GLib.Object { } } + public Gee.ArrayList get_subitems (Objects.Item i) { + Gee.ArrayList return_value = new Gee.ArrayList (); + lock (_items) { + foreach (var item in items) { + if (item.parent_id == i.id) { + return_value.add (item); + } + } + + return return_value; + } + } + public Objects.Project _fill_project (Sqlite.Statement stmt) { Objects.Project return_value = new Objects.Project (); return_value.id = stmt.column_int64 (0); @@ -458,6 +514,19 @@ public class Services.Database : GLib.Object { return return_value; } + public Gee.ArrayList get_all_labels_by_search (string search_text) { + Gee.ArrayList return_value = new Gee.ArrayList (); + lock (_labels) { + foreach (var label in labels) { + if (search_text.down () in label.name.down ()) { + return_value.add (label); + } + } + + return return_value; + } + } + public Objects.Label _fill_label (Sqlite.Statement stmt) { Objects.Label return_value = new Objects.Label (); return_value.id = stmt.column_int64 (0); @@ -526,13 +595,20 @@ public class Services.Database : GLib.Object { } } - public Objects.Label get_label_by_name (string name) { + public Objects.Label get_label_by_name (string name, bool lowercase = false) { Objects.Label? return_value = null; lock (_labels) { foreach (var label in labels) { - if (label.name == name) { - return_value = label; - break; + if (lowercase) { + if (label.name.down () == name.down ()) { + return_value = label; + break; + } + } else { + if (label.name == name) { + return_value = label; + break; + } } } @@ -555,6 +631,7 @@ public class Services.Database : GLib.Object { } else { warning ("Error: %d: %s", db.errcode (), db.errmsg ()); } + stmt.reset (); } @@ -757,6 +834,7 @@ public class Services.Database : GLib.Object { foreach (Objects.Item item in section.items) { delete_item (item); } + section.deleted (); } else { warning ("Error: %d: %s", db.errcode (), db.errmsg ()); @@ -790,9 +868,46 @@ public class Services.Database : GLib.Object { } else { warning ("Error: %d: %s", db.errcode (), db.errmsg ()); } + stmt.reset (); } + public void move_section (Objects.Section section, int64 old_project_id) { + Sqlite.Statement stmt; + + sql = """ + UPDATE Sections SET project_id=$project_id WHERE id=$id; + """; + + db.prepare_v2 (sql, sql.length, out stmt); + set_parameter_int64 (stmt, "$project_id", section.project_id); + set_parameter_int64 (stmt, "$id", section.id); + + if (stmt.step () == Sqlite.DONE) { + stmt.reset (); + + sql = """ + UPDATE Items SET project_id=$project_id WHERE section_id=$section_id; + """; + + db.prepare_v2 (sql, sql.length, out stmt); + set_parameter_int64 (stmt, "$project_id", section.project_id); + set_parameter_int64 (stmt, "$section_id", section.id); + + if (stmt.step () == Sqlite.DONE) { + foreach (Objects.Item item in section.items) { + item.project_id = section.project_id; + } + + section_moved (section, old_project_id); + } + + stmt.reset (); + } else { + warning ("Error: %d: %s", db.errcode (), db.errmsg ()); + } + } + /* Items */ @@ -833,10 +948,14 @@ public class Services.Database : GLib.Object { item_added (item, insert); if (insert) { - if (item.section_id == Constants.INACTIVE) { - item.project.item_added (item); + if (item.parent_id != Constants.INACTIVE) { + item.parent.item_added (item); } else { - item.section.item_added (item); + if (item.section_id == Constants.INACTIVE) { + item.project.item_added (item); + } else { + item.section.item_added (item); + } } } @@ -918,7 +1037,7 @@ public class Services.Database : GLib.Object { return_value.id = stmt.column_int64 (0); return_value.content = stmt.column_text (1); return_value.description = stmt.column_text (2); - get_due_parameter (return_value, stmt, 3); + return_value.due.update_from_json (get_due_parameter (stmt, 3)); return_value.added_at = stmt.column_text (4); return_value.completed_at = stmt.column_text (5); return_value.updated_at = stmt.column_text (6); @@ -1032,6 +1151,10 @@ public class Services.Database : GLib.Object { set_parameter_int64 (stmt, "$id", item.id); if (stmt.step () == Sqlite.DONE) { + foreach (Objects.Item subitem in item.items) { + delete_item (subitem); + } + item.deleted (); } else { warning ("Error: %d: %s", db.errcode (), db.errmsg ()); @@ -1085,20 +1208,27 @@ public class Services.Database : GLib.Object { sql = """ UPDATE Items SET checked=$checked, completed_at=$completed_at - WHERE id=$id OR parent_id=$parent_id; + WHERE id=$id OR parent_id=$id; """; db.prepare_v2 (sql, sql.length, out stmt); set_parameter_bool (stmt, "$checked", item.checked); set_parameter_str (stmt, "$completed_at", item.completed_at); set_parameter_int64 (stmt, "$id", item.id); - set_parameter_int64 (stmt, "$parent_id", item.id); if (stmt.step () != Sqlite.DONE) { warning ("Error: %d: %s", db.errcode (), db.errmsg ()); } else { + foreach (Objects.Item subitem in item.items) { + subitem.checked = item.checked; + subitem.completed_at = item.completed_at; + + checked_toggled (subitem, old_checked); + } + item.updated (); item_updated (item, Constants.INACTIVE); + Planner.event_bus.checked_toggled (item, old_checked); if (item.project.todoist) { Planner.todoist.complete_item.begin (item, (obj, res) => { @@ -1110,16 +1240,26 @@ public class Services.Database : GLib.Object { stmt.reset (); } - public void update_item_position (Objects.Item item) { + public void update_child_order (Objects.BaseObject base_object) { Sqlite.Statement stmt; sql = """ - UPDATE Items SET child_order=$child_order WHERE id=$id; - """; + UPDATE %s SET %s=$order WHERE id=$id; + """.printf (base_object.table_name, base_object.column_order_name); db.prepare_v2 (sql, sql.length, out stmt); - set_parameter_int (stmt, "$child_order", item.child_order); - set_parameter_int64 (stmt, "$id", item.id); + + if (base_object is Objects.Item) { + set_parameter_int (stmt, "$order", ((Objects.Item) base_object).child_order); + } else if (base_object is Objects.Section) { + set_parameter_int (stmt, "$order", ((Objects.Section) base_object).section_order); + } else if (base_object is Objects.Project) { + set_parameter_int (stmt, "$order", ((Objects.Project) base_object).child_order); + } else if (base_object is Objects.Label) { + set_parameter_int (stmt, "$order", ((Objects.Label) base_object).item_order); + } + + set_parameter_int64 (stmt, "$id", base_object.id); if (stmt.step () != Sqlite.DONE) { warning ("Error: %d: %s", db.errcode (), db.errmsg ()); @@ -1159,6 +1299,103 @@ public class Services.Database : GLib.Object { } } + // Reminders + public void insert_reminder (Objects.Reminder reminder) { + Sqlite.Statement stmt; + string sql; + + sql = """ + INSERT OR IGNORE INTO Reminders (id, item_id, due) + VALUES ($id, $item_id, $due); + """; + + db.prepare_v2 (sql, sql.length, out stmt); + set_parameter_int64 (stmt, "$id", reminder.id); + set_parameter_int64 (stmt, "$item_id", reminder.item_id); + set_parameter_str (stmt, "$due", reminder.due.to_string ()); + + if (stmt.step () != Sqlite.DONE) { + warning ("Error: %d: %s", db.errcode (), db.errmsg ()); + } else { + reminder.item.reminder_added (reminder); + reminder_added (reminder); + reminders.add (reminder); + } + + stmt.reset (); + } + + public Gee.ArrayList get_reminders_collection () { + Gee.ArrayList return_value = new Gee.ArrayList (); + Sqlite.Statement stmt; + + sql = """ + SELECT id, item_id, due FROM Reminders; + """; + + db.prepare_v2 (sql, sql.length, out stmt); + + while (stmt.step () == Sqlite.ROW) { + return_value.add (_fill_reminder (stmt)); + } + stmt.reset (); + return return_value; + } + + public Objects.Reminder _fill_reminder (Sqlite.Statement stmt) { + Objects.Reminder return_value = new Objects.Reminder (); + return_value.id = stmt.column_int64 (0); + return_value.item_id = stmt.column_int64 (1); + return_value.due.update_from_json (get_due_parameter (stmt, 2)); + return return_value; + } + + public Gee.ArrayList get_reminders_by_item (Objects.Item item) { + Gee.ArrayList return_value = new Gee.ArrayList (); + lock (_reminders) { + foreach (var reminder in reminders) { + if (reminder.item_id == item.id) { + return_value.add (reminder); + } + } + + return return_value; + } + } + + public Objects.Reminder get_reminder (int64 id) { + Objects.Reminder? return_value = null; + lock (_reminders) { + foreach (var reminder in reminders) { + if (reminder.id == id) { + return_value = reminder; + break; + } + } + + return return_value; + } + } + + public void delete_reminder (int64 id) { + Sqlite.Statement stmt; + + sql = """ + DELETE FROM Reminders WHERE id=$id; + """; + + db.prepare_v2 (sql, sql.length, out stmt); + set_parameter_int64 (stmt, "$id", id); + + if (stmt.step () == Sqlite.DONE) { + reminder_deleted (id); + } else { + warning ("Error: %d: %s", db.errcode (), db.errmsg ()); + } + + stmt.reset (); + } + // PARAMENTER REGION private void set_parameter_int (Sqlite.Statement? stmt, string par, int val) { int par_position = stmt.bind_parameter_index (par); @@ -1184,7 +1421,7 @@ public class Services.Database : GLib.Object { } Json.Parser parser; - private void get_due_parameter (Objects.Item item, Sqlite.Statement stmt, int col) { + private Json.Object? get_due_parameter (Sqlite.Statement stmt, int col) { if (parser == null) { parser = new Json.Parser (); } @@ -1195,7 +1432,7 @@ public class Services.Database : GLib.Object { debug (e.message); } - item.due.update_from_json (parser.get_root ().get_object ()); + return parser.get_root ().get_object (); } public bool column_exists (string table, string column) { diff --git a/src/Services/EventBus.vala b/src/Services/EventBus.vala index b106df1ee..712b8b63c 100644 --- a/src/Services/EventBus.vala +++ b/src/Services/EventBus.vala @@ -15,7 +15,7 @@ public class Services.EventBus : Object { public signal void project_parent_changed (Objects.Project project, int64 old_parent_id); public signal void checked_toggled (Objects.Item item, bool old_checked); public signal void favorite_toggled (Objects.Project project); - public signal void item_moved (Objects.Item item, int64 old_project_id, int64 old_section_id, bool insert = true); + public signal void item_moved (Objects.Item item, int64 old_project_id, int64 old_section_id, int64 old_parent_id = Constants.INACTIVE, bool insert = true); public signal void update_items_position (int64 project_id, int64 section_id); public signal void update_inserted_item_map (Layouts.ItemRow row); } diff --git a/src/Services/Notification.vala b/src/Services/Notification.vala new file mode 100644 index 000000000..1df42942e --- /dev/null +++ b/src/Services/Notification.vala @@ -0,0 +1,133 @@ +/* +* Copyright © 2019 Alain M. (https://github.com/alainm23/planner) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public +* License as published by the Free Software Foundation; either +* version 3 of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301 USA +* +* Authored by: Alain M. +*/ + +public class Services.Notification : GLib.Object { + private static Notification? _instance; + public static Notification get_default () { + if (_instance == null) { + _instance = new Notification (); + } + + return _instance; + } + + private Gee.HashMap reminders; + private GLib.TimeSpan time; + + construct { + time = time_until_tomorrow (); + regresh (); + + Timeout.add_seconds (((uint) time), () => { + time = time_until_tomorrow (); + regresh (); + + return true; + }); + } + + private void regresh () { + if (reminders == null) { + reminders = new Gee.HashMap (); + } else { + reminders.clear (); + } + + foreach (var reminder in Planner.database.reminders) { + reminder_added (reminder); + } + + Planner.database.reminder_added.connect ((reminder) => { + reminder_added (reminder); + }); + + Planner.database.reminder_deleted.connect ((id) => { + if (reminders.has_key (id.to_string ())) { + reminders.unset (id.to_string ()); + } + }); + } + + private void reminder_added (Objects.Reminder reminder) { + if (reminder.due.datetime.compare (new GLib.DateTime.now_local ()) <= 0) { + var notification = new GLib.Notification (reminder.item.project.short_name); + notification.set_body (reminder.item.content); + notification.set_icon (new ThemedIcon ("com.github.alainm23.planner")); + notification.set_priority (GLib.NotificationPriority.URGENT); + + notification.set_default_action_and_target_value ( + "app.show-item", + new Variant.int64 (reminder.item_id) + ); + + Planner.instance.send_notification (reminder.id.to_string (), notification); + Planner.database.delete_reminder (reminder.id); + } else if (Granite.DateTime.is_same_day (reminder.due.datetime, new GLib.DateTime.now_local ())) { + var interval = (uint) time_until_now (reminder.due.datetime); + var uid = "%u-%u".printf (interval, GLib.Random.next_int ()); + reminders.set (reminder.id_string, uid); + + Timeout.add_seconds (interval, () => { + queue_reminder_notification (reminder.id, uid); + return GLib.Source.REMOVE; + }); + } + } + + private TimeSpan time_until_now (GLib.DateTime dt) { + var now = new DateTime.now_local (); + return dt.difference (now) / TimeSpan.SECOND; + } + + private TimeSpan time_until_tomorrow () { + var now = new DateTime.now_local (); + var tomorrow = new DateTime.local ( + now.add_days (1).get_year (), + now.add_days (1).get_month (), + now.add_days (1).get_day_of_month (), + 0, + 0, + 0 + ); + + return tomorrow.difference (now) / TimeSpan.SECOND; + } + + public void queue_reminder_notification (int64 reminder_id, string uid) { + if (reminders.values.contains (uid) == false) { + return; + } + + var reminder = Planner.database.get_reminder (reminder_id); + var notification = new GLib.Notification (reminder.item.project.short_name); + notification.set_body (reminder.item.content); + notification.set_icon (new ThemedIcon ("com.github.alainm23.planner")); + notification.set_priority (GLib.NotificationPriority.URGENT); + + notification.set_default_action_and_target_value ( + "app.show-item", + new Variant.int64 (reminder.item_id) + ); + + Planner.instance.send_notification (uid, notification); + Planner.database.delete_reminder (reminder.id); + } +} \ No newline at end of file diff --git a/src/Services/Todoist.vala b/src/Services/Todoist.vala index 2a2406fa0..9b53a85ca 100644 --- a/src/Services/Todoist.vala +++ b/src/Services/Todoist.vala @@ -302,14 +302,16 @@ public class Services.Todoist : GLib.Object { } else { int64 old_project_id = item.project_id; int64 old_section_id = item.section_id; + int64 old_parent_id = item.parent_id; bool old_checked = item.checked; item.update_from_json (_node); item.update_labels_from_json (_node); Planner.database.update_item (item); - if (old_project_id != item.project_id || old_section_id != item.section_id) { - Planner.event_bus.item_moved (item, old_project_id, old_section_id); + if (old_project_id != item.project_id || old_section_id != item.section_id || + old_parent_id != item.parent_id) { + Planner.event_bus.item_moved (item, old_project_id, old_section_id, old_parent_id); } if (old_checked != item.checked) { @@ -328,7 +330,11 @@ public class Services.Todoist : GLib.Object { private void add_item_if_not_exists (Json.Node node) { if (!node.get_object ().get_null_member ("parent_id")) { - // Add to parent + Objects.Item? item = Planner.database.get_item (node.get_object ().get_int_member ("parent_id")); + if (item != null) { + item.add_item_if_not_exists (new Objects.Item.from_json (node)); + } + return; } @@ -591,4 +597,45 @@ public class Services.Todoist : GLib.Object { return success; } + + public async bool move_section (Objects.Section section, int64 new_project_id) { + string uuid = Util.get_default ().generate_string (); + bool success = false; + + string url = "%s?token=%s&commands=%s".printf ( + TODOIST_SYNC_URL, + Planner.settings.get_string ("todoist-access-token"), + section.get_move_section (uuid, new_project_id) + ); + + var message = new Soup.Message ("POST", url); + + try { + var stream = yield session.send_async (message, null); + yield parser.load_from_stream_async (stream); + + print_root (parser.get_root ()); + + var sync_status = parser.get_root ().get_object ().get_object_member ("sync_status"); + var uuid_member = sync_status.get_member (uuid); + + if (uuid_member.get_node_type () == Json.NodeType.VALUE) { + Planner.settings.set_string ( + "todoist-sync-token", + parser.get_root ().get_object ().get_string_member ("sync_token") + ); + success = true; + } else { + // project_updated_error ( + // project.id, + // (int32) sync_status.get_object_member (uuid).get_int_member ("http_code"), + // sync_status.get_object_member (uuid).get_string_member ("error") + // ); + } + } catch (Error e) { + debug (e.message); + } + + return success; + } } diff --git a/src/Util.vala b/src/Util.vala index 9bf46ef76..456c7699a 100644 --- a/src/Util.vala +++ b/src/Util.vala @@ -35,10 +35,10 @@ public enum ProjectIconStyle { } public enum FilterType { - TODAY, - INBOX, - SCHEDULED, - PINBOARD; + TODAY = 0, + INBOX = 1, + SCHEDULED = 2, + PINBOARD = 3; public string to_string () { switch (this) { @@ -58,6 +58,25 @@ public enum FilterType { assert_not_reached(); } } + + public string get_name () { + switch (this) { + case TODAY: + return _("Today"); + + case INBOX: + return _("Inbox"); + + case SCHEDULED: + return _("Scheduled"); + + case PINBOARD: + return _("Pinboard"); + + default: + assert_not_reached(); + } + } } public enum BackendType { @@ -89,7 +108,8 @@ public enum ObjectType { PROJECT, SECTION, ITEM, - LABEL; + LABEL, + FILTER; public string get_header () { switch (this) { @@ -105,6 +125,9 @@ public enum ObjectType { case LABEL: return _("Labels"); + case FILTER: + return _("Filters"); + default: assert_not_reached(); } @@ -120,6 +143,10 @@ public class Util : GLib.Object { {"ITEMROW", Gtk.TargetFlags.SAME_APP, 0} }; + public Gtk.TargetEntry[] SECTIONROW_TARGET_ENTRIES = { + {"SECTIONROW", Gtk.TargetFlags.SAME_APP, 0} + }; + private static Util? _instance; public static Util get_default () { if (_instance == null) { @@ -128,6 +155,7 @@ public class Util : GLib.Object { return _instance; } + /* * Colors Utils */ @@ -346,6 +374,28 @@ public class Util : GLib.Object { return returned; } + public string get_badge_name () { + string returned = ""; + int badge_count = Planner.settings.get_enum ("badge-count"); + + switch (badge_count) { + case 0: + returned = _("None"); + break; + case 1: + returned = _("Inbox"); + break; + case 2: + returned = _("Today"); + break; + case 3: + returned = _("Today + Inbox"); + break; + } + + return returned; + } + public void update_theme () { string _css = """ @define-color base_color %s; @@ -377,7 +427,7 @@ public class Util : GLib.Object { Gtk.Settings.get_default ().gtk_application_prefer_dark_theme = false; } else if (appearance_mode == 1) { base_color = "#151515"; - bg_color = "#222222"; + bg_color = "shade (#151515, 1.4)"; item_bg_color = "@bg_color"; item_border_color = "#333333"; picker_bg = "@base_color"; @@ -565,7 +615,6 @@ public class Util : GLib.Object { if (!insert) { Planner.event_bus.update_inserted_item_map (row); - /// items [row.item.id_string] = row; row.update_inserted_item (); } else { row.hide_destroy (); @@ -685,6 +734,7 @@ public class Util : GLib.Object { _dynamic_icons.set ("planner-tag", true); _dynamic_icons.set ("planner-pinned", true); _dynamic_icons.set ("planner-settings", true); + _dynamic_icons.set ("planner-bell", true); } return _dynamic_icons; @@ -791,6 +841,7 @@ public class Util : GLib.Object { if (response == Gtk.ResponseType.CANCEL) { clear_database_query (); Planner.settings.set_string ("version", Constants.VERSION); + message_dialog.destroy (); } else { export_v2_database (); } @@ -946,7 +997,7 @@ public class Util : GLib.Object { return all; } - public bool export_to_json (string path, Sqlite.Database db) { + public bool export_to_json (string path, Sqlite.Database db) { Json.Builder builder = new Json.Builder (); bool returned = false; @@ -1190,4 +1241,19 @@ public class Util : GLib.Object { filter.set_filter_name (_("All files")); chooser.add_filter (filter); } + + public FilterType get_filter () { + switch (Planner.settings.get_enum ("homepage-item")) { + case 0: + return FilterType.INBOX; + case 1: + return FilterType.TODAY; + case 2: + return FilterType.SCHEDULED; + case 3: + return FilterType.PINBOARD; + default: + assert_not_reached(); + } + } } diff --git a/src/Views/Date.vala b/src/Views/Date.vala index a3c0270d9..ada6f7563 100644 --- a/src/Views/Date.vala +++ b/src/Views/Date.vala @@ -154,6 +154,15 @@ public class Views.Date : Gtk.EventBox { Planner.database.item_added.connect (valid_add_item); Planner.database.item_deleted.connect (valid_delete_item); Planner.database.item_updated.connect (valid_update_item); + Planner.event_bus.item_moved.connect ((item) => { + if (items.has_key (item.id_string)) { + items[item.id_string].update_request (); + } + + if (overdue_items.has_key (item.id_string)) { + items[item.id_string].update_request (); + } + }); listbox.add.connect (validate_placeholder); listbox.remove.connect (validate_placeholder); @@ -194,6 +203,14 @@ public class Views.Date : Gtk.EventBox { } private void valid_update_item (Objects.Item item) { + if (items.has_key (item.id_string)) { + items[item.id_string].update_request (); + } + + if (overdue_items.has_key (item.id_string)) { + overdue_items[item.id_string].update_request (); + } + if (items.has_key (item.id_string) && !item.has_due) { items[item.id_string].hide_destroy (); items.unset (item.id_string); diff --git a/src/Views/List.vala b/src/Views/List.vala index c272fb347..1e723c471 100644 --- a/src/Views/List.vala +++ b/src/Views/List.vala @@ -11,6 +11,8 @@ public class Views.List : Gtk.EventBox { } } + public Gee.HashMap sections_map; + public List (Objects.Project project) { Object ( project: project @@ -18,6 +20,8 @@ public class Views.List : Gtk.EventBox { } construct { + sections_map = new Gee.HashMap (); + var top_project = new Widgets.TopHeaderProject (project); listbox = new Gtk.ListBox () { @@ -27,6 +31,9 @@ public class Views.List : Gtk.EventBox { expand = true }; + Gtk.drag_dest_set (listbox, Gtk.DestDefaults.ALL, Util.get_default ().SECTIONROW_TARGET_ENTRIES, Gdk.DragAction.MOVE); + listbox.drag_data_received.connect (on_drag_section_received); + unowned Gtk.StyleContext listbox_context = listbox.get_style_context (); listbox_context.add_class ("listbox-background"); listbox_context.add_class ("listbox-separator-12"); @@ -72,6 +79,7 @@ public class Views.List : Gtk.EventBox { add_sections (); Timeout.add (listbox_placeholder_stack.transition_duration, () => { + set_sort_func (); children_size_changed (); return GLib.Source.REMOVE; }); @@ -89,11 +97,13 @@ public class Views.List : Gtk.EventBox { }); listbox.add.connect (() => { - children_size_changed (); + children_size_changed (); + update_projects_position (); }); listbox.remove.connect (() => { - children_size_changed (); + children_size_changed (); + update_projects_position (); }); scrolled_window.vadjustment.value_changed.connect (() => { @@ -103,6 +113,49 @@ public class Views.List : Gtk.EventBox { Planner.event_bus.view_header (false); } }); + + Planner.database.section_moved.connect ((section, old_project_id) => { + if (project.id == old_project_id && sections_map.has_key (section.id_string)) { + sections_map [section.id_string].hide_destroy (); + sections_map.unset (section.id_string); + } + + if (project.id == section.project_id && + !sections_map.has_key (section.id_string)) { + add_section (section); + } + }); + + Planner.database.section_deleted.connect ((section) => { + if (sections_map.has_key (section.id_string)) { + sections_map [section.id_string].hide_destroy (); + sections_map.unset (section.id_string); + } + }); + } + + private void set_sort_func () { + listbox.set_sort_func ((row1, row2) => { + Objects.Section item1 = ((Layouts.SectionRow) row1).section; + Objects.Section item2 = ((Layouts.SectionRow) row2).section; + + return item1.section_order - item2.section_order; + }); + + listbox.set_sort_func (null); + } + + private void update_projects_position () { + Timeout.add (listbox_placeholder_stack.transition_duration, () => { + GLib.List sections = listbox.get_children (); + for (int index = 1; index < sections.length (); index++) { + Objects.Section section = ((Layouts.SectionRow) sections.nth_data (index)).section; + section.section_order = index; + Planner.database.update_child_order (section); + } + + return GLib.Source.REMOVE; + }); } public void add_sections () { @@ -126,10 +179,13 @@ public class Views.List : Gtk.EventBox { private void add_section (Objects.Section section) { var row = new Layouts.SectionRow (section); + row.children_size_changed.connect (() => { children_size_changed (); }); + listbox.add (row); + sections_map [section.id_string] = row; listbox.show_all (); } @@ -150,4 +206,29 @@ public class Views.List : Gtk.EventBox { private void children_size_changed () { listbox_placeholder_stack.visible_child_name = validate_children () ? "listbox" : "placeholder"; } + + private void on_drag_section_received (Gdk.DragContext context, int x, int y, + Gtk.SelectionData selection_data, uint target_type, uint time) { + Layouts.SectionRow target; + Layouts.SectionRow source; + Gtk.Allocation alloc; + + target = (Layouts.SectionRow) listbox.get_row_at_y (y); + target.get_allocation (out alloc); + + var row = ((Gtk.Widget[]) selection_data.get_data ()) [0]; + source = (Layouts.SectionRow) row; + + if (target != null) { + source.get_parent ().remove (source); + + if (target.get_index () == 1 && y < (alloc.height / 2)) { + listbox.insert (source, target.get_index ()); + } else { + listbox.insert (source, target.get_index () + 1); + } + + listbox.show_all (); + } + } } diff --git a/src/Views/Pinboard.vala b/src/Views/Pinboard.vala index 4eb034328..2a6762f7c 100644 --- a/src/Views/Pinboard.vala +++ b/src/Views/Pinboard.vala @@ -129,6 +129,12 @@ public class Views.Pinboard : Gtk.EventBox { Planner.database.item_deleted.connect (valid_delete_item); Planner.database.item_updated.connect (valid_update_item); + Planner.event_bus.item_moved.connect ((item) => { + if (items.has_key (item.id_string)) { + items[item.id_string].update_request (); + } + }); + listbox.add.connect (() => { validate_placeholder (); }); diff --git a/src/Widgets/Calendar/Calendar.vala b/src/Widgets/Calendar/Calendar.vala index d8444a0a2..74d0806e7 100644 --- a/src/Widgets/Calendar/Calendar.vala +++ b/src/Widgets/Calendar/Calendar.vala @@ -45,16 +45,22 @@ public class Widgets.Calendar.Calendar : Gtk.Box { public Calendar (bool block_past_days = false) { Object ( block_past_days: block_past_days, - orientation: Gtk.Orientation.VERTICAL, - height_request: 200 + orientation: Gtk.Orientation.VERTICAL ); } construct { current_date = new GLib.DateTime.now_local (); - calendar_header = new Widgets.Calendar.CalendarHeader (); - calendar_week = new Widgets.Calendar.CalendarWeek (); + calendar_header = new Widgets.Calendar.CalendarHeader () { + margin_top = 6 + }; + + calendar_week = new Widgets.Calendar.CalendarWeek () { + margin_top = 12, + margin_bottom = 6 + }; + calendar_view = new Widgets.Calendar.CalendarView (); pack_start (calendar_header); diff --git a/src/Widgets/EditableLabel.vala b/src/Widgets/EditableLabel.vala index 1843bcafb..1d6874435 100644 --- a/src/Widgets/EditableLabel.vala +++ b/src/Widgets/EditableLabel.vala @@ -1,6 +1,7 @@ public class Widgets.EditableLabel : Gtk.EventBox { public string title_style { get; construct; } public string placeholder_text { get; construct; } + public bool auto_focus { get; construct; } public signal void focus_changed (bool active); public signal void changed (); @@ -50,10 +51,11 @@ public class Widgets.EditableLabel : Gtk.EventBox { } } - public EditableLabel (string title_style, string placeholder_text = "") { + public EditableLabel (string title_style, string placeholder_text = "", bool auto_focus = true) { Object ( title_style: title_style, - placeholder_text: placeholder_text + placeholder_text: placeholder_text, + auto_focus: auto_focus ); } @@ -101,10 +103,16 @@ public class Widgets.EditableLabel : Gtk.EventBox { bind_property ("text", title, "label"); - button_press_event.connect ((event) => { - editing (true); - return Gdk.EVENT_PROPAGATE; - }); + if (auto_focus) { + button_press_event.connect ((event) => { + editing (true); + return Gdk.EVENT_PROPAGATE; + }); + + grab_focus.connect (() => { + editing (true); + }); + } entry.activate.connect (() => { if (stack.visible_child == entry) { @@ -112,10 +120,6 @@ public class Widgets.EditableLabel : Gtk.EventBox { } }); - grab_focus.connect (() => { - editing (true); - }); - entry.focus_out_event.connect ((event) => { if (stack.visible_child == entry && !entry_menu_opened) { editing (false); diff --git a/src/Widgets/ItemSummary.vala b/src/Widgets/ItemSummary.vala index d2e4219e6..9c549211b 100644 --- a/src/Widgets/ItemSummary.vala +++ b/src/Widgets/ItemSummary.vala @@ -147,7 +147,7 @@ public class Widgets.ItemSummary : Gtk.Revealer { public void check_revealer () { summary_revealer.reveal_child = calendar_revealer.reveal_child || flowbox_revealer.reveal_child; - reveal_child = summary_revealer.reveal_child && !itemrow.edit; + reveal_child = summary_revealer.reveal_child && !itemrow.edit && !item.checked; } private void update_labels () { diff --git a/src/Widgets/LoadingButton.vala b/src/Widgets/LoadingButton.vala index c0594c482..54dab4953 100644 --- a/src/Widgets/LoadingButton.vala +++ b/src/Widgets/LoadingButton.vala @@ -1,5 +1,5 @@ public class Widgets.LoadingButton : Gtk.Button { - public string text { get; construct; } + public string text_icon { get; construct; } public LoadingButtonType loading_type { get; construct; } bool _is_loading; @@ -22,10 +22,10 @@ public class Widgets.LoadingButton : Gtk.Button { } } - public LoadingButton (LoadingButtonType loading_type, string text) { + public LoadingButton (LoadingButtonType loading_type, string text_icon) { Object ( loading_type: loading_type, - text: text + text_icon: text_icon ); } @@ -42,11 +42,11 @@ public class Widgets.LoadingButton : Gtk.Button { }; if (loading_type == LoadingButtonType.LABEL) { - submit_stack.add_named (new Gtk.Label (text), "button"); + submit_stack.add_named (new Gtk.Label (text_icon), "button"); } else { var icon = new Widgets.DynamicIcon (); icon.size = 19; - icon.update_icon_name (text); + icon.update_icon_name (text_icon); submit_stack.add_named (icon, "button"); } diff --git a/src/Widgets/ReminderButton.vala b/src/Widgets/ReminderButton.vala new file mode 100644 index 000000000..9fa094487 --- /dev/null +++ b/src/Widgets/ReminderButton.vala @@ -0,0 +1,41 @@ +public class Widgets.ReminderButton : Gtk.Button { + public Objects.Item item { get; construct; } + + public signal void dialog_open (bool value); + + public ReminderButton (Objects.Item item) { + Object ( + item: item, + can_focus: false, + valign: Gtk.Align.CENTER, + halign: Gtk.Align.CENTER + ); + } + + construct { + get_style_context ().add_class (Gtk.STYLE_CLASS_FLAT); + + var button_image = new Widgets.DynamicIcon (); + button_image.size = 19; + button_image.update_icon_name ("planner-bell"); + + var button_grid = new Gtk.Grid () { + column_spacing = 6, + valign = Gtk.Align.CENTER + }; + button_grid.add (button_image); + + add (button_grid); + + clicked.connect (() => { + var dialog = new Dialogs.ReminderPicker.ReminderPicker (item); + + dialog_open (true); + dialog.popup (); + + dialog.destroy.connect (() => { + dialog_open (false); + }); + }); + } +} diff --git a/src/Widgets/SubItems.vala b/src/Widgets/SubItems.vala new file mode 100644 index 000000000..01ccbc521 --- /dev/null +++ b/src/Widgets/SubItems.vala @@ -0,0 +1,253 @@ +public class Widgets.SubItems : Gtk.EventBox { + public Objects.Item item_parent { get; construct; } + + private Gtk.ListBox listbox; + private Gtk.ListBox checked_listbox; + private Gtk.Revealer checked_revealer; + private Gtk.Revealer main_revealer; + + public Gee.HashMap items; + public Gee.HashMap items_checked; + + public bool has_children { + get { + return listbox.get_children ().length () > 0 || (checked_listbox.get_children ().length () > 0 && item_parent.project.show_completed); + } + } + + public bool is_creating { + get { + return item_parent.id == Constants.INACTIVE; + } + } + + public SubItems (Objects.Item item_parent) { + Object ( + item_parent: item_parent + ); + } + + construct { + items = new Gee.HashMap (); + items_checked = new Gee.HashMap (); + + listbox = new Gtk.ListBox () { + valign = Gtk.Align.START, + activate_on_single_click = true, + selection_mode = Gtk.SelectionMode.SINGLE, + hexpand = true + }; + + unowned Gtk.StyleContext listbox_context = listbox.get_style_context (); + listbox_context.add_class ("listbox-background"); + + var listbox_grid = new Gtk.Grid () { + margin_left = 21, + }; + listbox_grid.add (listbox); + + checked_listbox = new Gtk.ListBox () { + valign = Gtk.Align.START, + activate_on_single_click = true, + selection_mode = Gtk.SelectionMode.SINGLE, + expand = true + }; + + unowned Gtk.StyleContext checked_listbox_context = checked_listbox.get_style_context (); + checked_listbox_context.add_class ("listbox-background"); + + var checked_listbox_grid = new Gtk.Grid () { + margin_left = 21, + }; + checked_listbox_grid.add (checked_listbox); + + checked_revealer = new Gtk.Revealer () { + transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN, + reveal_child = item_parent.project.show_completed + }; + + checked_revealer.add (checked_listbox_grid); + + var main_grid = new Gtk.Grid () { + orientation = Gtk.Orientation.VERTICAL, + hexpand = true + }; + + main_grid.add (listbox_grid); + main_grid.add (checked_revealer); + + main_revealer = new Gtk.Revealer () { + transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN + }; + main_revealer.add (main_grid); + + add (main_revealer); + + if (!is_creating) { + add_items (); + } + + Timeout.add (main_revealer.transition_duration, () => { + main_revealer.reveal_child = true; + return GLib.Source.REMOVE; + }); + + item_parent.item_added.connect (add_item); + + listbox.add.connect (() => { + main_revealer.reveal_child = has_children; + }); + + listbox.remove.connect (() => { + main_revealer.reveal_child = has_children; + }); + + Planner.database.item_updated.connect ((item, update_id) => { + if (items.has_key (item.id_string)) { + if (items [item.id_string].update_id != update_id) { + items [item.id_string].update_request (); + } + } + + if (items_checked.has_key (item.id_string)) { + items_checked [item.id_string].update_request (); + } + }); + + Planner.database.item_deleted.connect ((item) => { + if (items.has_key (item.id_string)) { + items [item.id_string].hide_destroy (); + items.unset (item.id_string); + } + + if (items_checked.has_key (item.id_string)) { + items_checked [item.id_string].hide_destroy (); + items_checked.unset (item.id_string); + } + }); + + Planner.event_bus.item_moved.connect ((item, old_project_id, old_section_id, old_parent_id, insert) => { + if (old_parent_id == item_parent.id) { + if (items.has_key (item.id_string)) { + items [item.id_string].hide_destroy (); + items.unset (item.id_string); + } + + if (items_checked.has_key (item.id_string)) { + items_checked [item.id_string].hide_destroy (); + items_checked.unset (item.id_string); + } + } + + if (item.parent_id == item_parent.id) { + add_item (item); + } + }); + + Planner.event_bus.checked_toggled.connect ((item, old_checked) => { + if (item.parent.id == item_parent.id) { + if (!old_checked) { + if (items.has_key (item.id_string)) { + items [item.id_string].hide_destroy (); + items.unset (item.id_string); + } + + if (!items_checked.has_key (item.id_string)) { + items_checked [item.id_string] = new Layouts.ItemRow (item); + checked_listbox.insert (items_checked [item.id_string], 0); + checked_listbox.show_all (); + } + } else { + if (items_checked.has_key (item.id_string)) { + items_checked [item.id_string].hide_destroy (); + items_checked.unset (item.id_string); + } + + if (!items.has_key (item.id_string)) { + items [item.id_string] = new Layouts.ItemRow (item); + listbox.add (items [item.id_string]); + listbox.show_all (); + } + } + } + }); + + item_parent.project.show_completed_changed.connect (() => { + if (item_parent.project.show_completed) { + add_completed_items (); + checked_revealer.reveal_child = item_parent.project.show_completed; + } else { + items_checked.clear (); + foreach (unowned Gtk.Widget child in checked_listbox.get_children ()) { + child.destroy (); + } + + checked_revealer.reveal_child = item_parent.project.show_completed; + } + }); + } + + private void add_items () { + items.clear (); + + foreach (unowned Gtk.Widget child in listbox.get_children ()) { + child.destroy (); + } + + foreach (Objects.Item item in item_parent.items) { + add_item (item); + } + + if (item_parent.project.show_completed) { + add_completed_items (); + } + + main_revealer.reveal_child = has_children; + } + + public void add_completed_items () { + items_checked.clear (); + + foreach (unowned Gtk.Widget child in checked_listbox.get_children ()) { + child.destroy (); + } + + foreach (Objects.Item item in item_parent.items) { + add_complete_item (item); + } + } + + public void add_complete_item (Objects.Item item) { + if (item_parent.project.show_completed && item.checked) { + if (!items_checked.has_key (item.id_string)) { + items_checked [item.id_string] = new Layouts.ItemRow (item); + checked_listbox.add (items_checked [item.id_string]); + checked_listbox.show_all (); + } + } + } + + public void add_item (Objects.Item item) { + if (!item.checked && !items.has_key (item.id_string)) { + items [item.id_string] = new Layouts.ItemRow (item); + listbox.add (items [item.id_string]); + listbox.show_all (); + } + } + + public void prepare_new_item (string content = "") { + Planner.event_bus.item_selected (null); + + Layouts.ItemRow row = new Layouts.ItemRow.for_parent (item_parent); + row.update_content (content); + + row.item_added.connect (() => { + items [row.item.id_string] = row; + row.update_inserted_item (); + item_parent.add_item_if_not_exists (row.item); + }); + + listbox.add (row); + listbox.show_all (); + } +} \ No newline at end of file diff --git a/src/Widgets/TopHeaderProject.vala b/src/Widgets/TopHeaderProject.vala index a5edc5f20..92ae87e67 100644 --- a/src/Widgets/TopHeaderProject.vala +++ b/src/Widgets/TopHeaderProject.vala @@ -81,9 +81,7 @@ public class Widgets.TopHeaderProject : Gtk.EventBox { var projectrow_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { valign = Gtk.Align.START, - hexpand = true, - margin_start = 2, - margin_end = 6 + hexpand = true }; projectrow_box.pack_start (icon_progress_stack, false, false, 0);