diff options
-rw-r--r--[-rwxr-xr-x] | po/Furtherance.pot | 69 | ||||
-rwxr-xr-x | src/gtk/task_details.ui | 27 | ||||
-rwxr-xr-x | src/ui/task_details.rs | 204 |
3 files changed, 261 insertions, 39 deletions
diff --git a/po/Furtherance.pot b/po/Furtherance.pot index f7d233f..1e280dc 100755..100644 --- a/po/Furtherance.pot +++ b/po/Furtherance.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: furtherance\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-02 14:33+0200\n" +"POT-Creation-Date: 2022-10-18 11:58+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -139,7 +139,8 @@ msgstr "" #: src/gtk/dialogs.ui:10 src/application.rs:211 src/application.rs:347 #: src/application.rs:385 src/ui/preferences_window.rs:233 -#: src/ui/task_details.rs:579 src/ui/window.rs:324 src/ui/window.rs:858 +#: src/ui/task_details.rs:586 src/ui/task_details.rs:626 src/ui/window.rs:324 +#: src/ui/window.rs:858 msgid "Cancel" msgstr "" @@ -331,8 +332,8 @@ msgstr "" msgid "Date range" msgstr "" -#: src/gtk/report.ui:52 src/gtk/task_details.ui:88 src/ui/task_details.rs:193 -#: src/ui/window.rs:338 +#: src/gtk/report.ui:52 src/gtk/task_details.ui:88 src/ui/task_details.rs:200 +#: src/ui/task_details.rs:642 src/ui/window.rs:338 msgid "Start" msgstr "" @@ -372,7 +373,7 @@ msgstr "" msgid "Tasks" msgstr "" -#: src/gtk/report.ui:137 src/ui/task_details.rs:184 +#: src/gtk/report.ui:137 src/ui/task_details.rs:191 msgid "Tags" msgstr "" @@ -388,8 +389,8 @@ msgstr "" msgid "Task Details" msgstr "" -#: src/gtk/task_details.ui:96 src/application.rs:332 src/ui/task_details.rs:195 -#: src/ui/window.rs:340 src/ui/window.rs:626 +#: src/gtk/task_details.ui:96 src/application.rs:332 src/ui/task_details.rs:202 +#: src/ui/task_details.rs:644 src/ui/window.rs:340 src/ui/window.rs:626 msgid "Stop" msgstr "" @@ -397,7 +398,11 @@ msgstr "" msgid "Total" msgstr "" -#: src/gtk/task_details.ui:131 +#: src/gtk/task_details.ui:135 +msgid "Add Similar Task" +msgstr "" + +#: src/gtk/task_details.ui:144 msgid "Delete all" msgstr "" @@ -453,7 +458,7 @@ msgstr "" msgid "Delete history?" msgstr "" -#: src/application.rs:212 src/ui/task_details.rs:580 +#: src/application.rs:212 src/ui/task_details.rs:587 msgid "Delete" msgstr "" @@ -523,64 +528,68 @@ msgstr "" msgid "no tags" msgstr "" -#: src/ui/task_details.rs:175 src/ui/task_details.rs:506 +#: src/ui/task_details.rs:182 src/ui/task_details.rs:513 msgid "Edit Task" msgstr "" -#: src/ui/task_details.rs:214 src/ui/window.rs:365 +#: src/ui/task_details.rs:221 src/ui/task_details.rs:669 src/ui/window.rs:365 msgid "*Use the format YYYY-MM-DD HH:MM:SS" msgstr "" -#: src/ui/task_details.rs:216 src/ui/window.rs:367 +#: src/ui/task_details.rs:223 src/ui/task_details.rs:671 src/ui/window.rs:367 msgid "*Use the format YYYY-MM-DD HH:MM" msgstr "" -#: src/ui/task_details.rs:222 src/ui/window.rs:373 +#: src/ui/task_details.rs:229 src/ui/task_details.rs:677 src/ui/window.rs:373 msgid "*Start time cannot be later than stop time." msgstr "" -#: src/ui/task_details.rs:227 src/ui/window.rs:378 +#: src/ui/task_details.rs:234 src/ui/task_details.rs:682 src/ui/window.rs:378 msgid "*Time cannot be in the future." msgstr "" -#: src/ui/task_details.rs:233 +#: src/ui/task_details.rs:240 msgid "Delete task" msgstr "" -#: src/ui/task_details.rs:260 +#: src/ui/task_details.rs:267 msgid "Delete task?" msgstr "" -#: src/ui/task_details.rs:512 +#: src/ui/task_details.rs:519 msgid "New Name #tags" msgstr "" -#: src/ui/task_details.rs:515 +#: src/ui/task_details.rs:522 msgid "Task name cannot be empty." msgstr "" -#: src/ui/task_details.rs:575 +#: src/ui/task_details.rs:582 msgid "Delete All?" msgstr "" -#: src/ui/task_details.rs:577 +#: src/ui/task_details.rs:584 msgid "This will delete all occurrences of this task on this day." msgstr "" -#: src/ui/tasks_page.rs:156 -msgid "Today" +#: src/ui/task_details.rs:622 src/ui/window.rs:320 +msgid "New Task" msgstr "" -#: src/ui/tasks_page.rs:158 -msgid "Yesterday" +#: src/ui/task_details.rs:627 src/ui/window.rs:325 +msgid "Add" msgstr "" -#: src/ui/window.rs:320 -msgid "New Task" +#: src/ui/task_details.rs:687 src/ui/window.rs:383 +msgid "*Task name cannot be blank." msgstr "" -#: src/ui/window.rs:325 -msgid "Add" +#: src/ui/tasks_page.rs:156 +msgid "Today" +msgstr "" + +#: src/ui/tasks_page.rs:158 +msgid "Yesterday" msgstr "" #: src/ui/window.rs:331 @@ -591,10 +600,6 @@ msgstr "" msgid "tags" msgstr "" -#: src/ui/window.rs:383 -msgid "*Task name cannot be blank." -msgstr "" - #: src/ui/window.rs:568 msgid "You have been idle for " msgstr "" diff --git a/src/gtk/task_details.ui b/src/gtk/task_details.ui index 6febe75..c164e20 100755 --- a/src/gtk/task_details.ui +++ b/src/gtk/task_details.ui @@ -126,12 +126,27 @@ <property name="halign">end</property> <property name="valign">end</property> <child> - <object class="GtkButton" id="delete_all_btn"> - <property name="icon-name">user-trash-symbolic</property> - <property name="tooltip-text" translatable="yes">Delete all</property> - <style> - <class name="delete-all-button"/> - </style> + <object class="GtkBox"> + <property name="halign">end</property> + <property name="spacing">12</property> + <child> + <object class="GtkButton" id="add_similar_btn"> + <property name="icon-name">list-add-symbolic</property> + <property name="tooltip-text" translatable="yes">Add Similar Task</property> + <style> + <class name="add-similar-button"/> + </style> + </object> + </child> + <child> + <object class="GtkButton" id="delete_all_btn"> + <property name="icon-name">user-trash-symbolic</property> + <property name="tooltip-text" translatable="yes">Delete all</property> + <style> + <class name="delete-all-button"/> + </style> + </object> + </child> </object> </child> </object> diff --git a/src/ui/task_details.rs b/src/ui/task_details.rs index 2c05619..f24a1bc 100755 --- a/src/ui/task_details.rs +++ b/src/ui/task_details.rs @@ -15,7 +15,7 @@ // along with this program. If not, see <https://www.gnu.org/licenses/>. use adw::subclass::prelude::*; -use chrono::{offset::TimeZone, DateTime, Local, NaiveDateTime, ParseError}; +use chrono::{offset::TimeZone, DateTime, Local, NaiveDateTime, ParseError, Duration}; use gettextrs::*; use glib::clone; use gtk::subclass::prelude::*; @@ -51,10 +51,14 @@ mod imp { pub main_box: TemplateChild<gtk::Box>, #[template_child] + pub add_similar_btn: TemplateChild<gtk::Button>, + #[template_child] pub delete_all_btn: TemplateChild<gtk::Button>, pub all_boxes: RefCell<Vec<gtk::Box>>, pub all_task_ids: RefCell<Vec<i32>>, + pub this_task_name: RefCell<String>, + pub this_task_tags: RefCell<String>, pub this_day: RefCell<String>, pub orig_tags: RefCell<String>, pub orig_name_with_tags: RefCell<String>, @@ -79,6 +83,7 @@ mod imp { fn constructed(&self, obj: &Self::Type) { obj.setup_signals(); obj.setup_delete_all(); + obj.setup_add_similar(); self.parent_constructed(obj); } } @@ -113,6 +118,8 @@ impl FurTaskDetails { imp.task_name_label.set_text(&task_group[0].task_name); let this_day_str = DateTime::parse_from_rfc3339(&task_group[0].start_time).unwrap(); + *imp.this_task_name.borrow_mut() = task_group[0].task_name.clone(); + *imp.this_task_tags.borrow_mut() = task_group[0].tags.clone(); *imp.this_day.borrow_mut() = this_day_str.format("%F").to_string(); *imp.orig_tags.borrow_mut() = task_group[0].tags.clone(); *imp.orig_name_with_tags.borrow_mut() = task_group[0].task_name.clone() + " #" + &task_group[0].tags.clone(); @@ -603,6 +610,201 @@ impl FurTaskDetails { })); } + fn setup_add_similar(&self) { + let imp = imp::FurTaskDetails::from_instance(self); + imp.add_similar_btn.connect_clicked(clone!(@weak self as this => move |_|{ + let imp2 = imp::FurTaskDetails::from_instance(&this); + let dialog = gtk::MessageDialog::new( + Some(&this), + gtk::DialogFlags::MODAL, + gtk::MessageType::Question, + gtk::ButtonsType::None, + &format!("<span size='x-large' weight='bold'>{}</span>", &gettext("New Task")), + ); + dialog.set_use_markup(true); + dialog.add_buttons(&[ + (&gettext("Cancel"), gtk::ResponseType::Cancel), + (&gettext("Add"), gtk::ResponseType::Ok) + ]); + + let message_area = dialog.message_area().downcast::<gtk::Box>().unwrap(); + let vert_box = gtk::Box::new(gtk::Orientation::Vertical, 5); + let task_name_edit = gtk::Entry::new(); + task_name_edit.set_placeholder_text(Some(&imp2.this_task_name.borrow())); + task_name_edit.set_text(&imp2.this_task_name.borrow()); + let task_tags_edit = gtk::Entry::new(); + let tags_placeholder = format!("#{}", imp2.this_task_tags.borrow()); + task_tags_edit.set_placeholder_text(Some(&tags_placeholder)); + task_tags_edit.set_text(&tags_placeholder); + + let labels_box = gtk::Box::new(gtk::Orientation::Horizontal, 5); + labels_box.set_homogeneous(true); + let start_label = gtk::Label::new(Some(&gettext("Start"))); + start_label.add_css_class("title-4"); + let stop_label = gtk::Label::new(Some(&gettext("Stop"))); + stop_label.add_css_class("title-4"); + let times_box = gtk::Box::new(gtk::Orientation::Horizontal, 5); + times_box.set_homogeneous(true); + + let stop_time = Local::now(); + let start_time = stop_time - Duration::minutes(1); + + let time_formatter = "%F %H:%M:%S"; + let time_formatter_no_secs = "%F %H:%M"; + + let mut start_time_w_year = start_time.format(time_formatter).to_string(); + if !settings_manager::get_bool("show-seconds") { + start_time_w_year = start_time.format(time_formatter_no_secs).to_string(); + } + let mut stop_time_w_year = stop_time.format(time_formatter).to_string(); + if !settings_manager::get_bool("show-seconds") { + stop_time_w_year = stop_time.format(time_formatter_no_secs).to_string(); + } + let start_time_edit = gtk::Entry::new(); + start_time_edit.set_text(&start_time_w_year); + let stop_time_edit = gtk::Entry::new(); + stop_time_edit.set_text(&stop_time_w_year); + + let instructions = gtk::Label::new(Some( + &gettext("*Use the format YYYY-MM-DD HH:MM:SS"))); + if !settings_manager::get_bool("show-seconds") { + instructions.set_text(&gettext("*Use the format YYYY-MM-DD HH:MM")); + } + instructions.set_visible(false); + instructions.add_css_class("error_message"); + + let time_error = gtk::Label::new(Some( + &gettext("*Start time cannot be later than stop time."))); + time_error.set_visible(false); + time_error.add_css_class("error_message"); + + let future_error = gtk::Label::new(Some( + &gettext("*Time cannot be in the future."))); + future_error.set_visible(false); + future_error.add_css_class("error_message"); + + let name_error = gtk::Label::new(Some( + &gettext("*Task name cannot be blank."))); + name_error.set_visible(false); + name_error.add_css_class("error_message"); + + vert_box.append(&task_name_edit); + vert_box.append(&task_tags_edit); + labels_box.append(&start_label); + labels_box.append(&stop_label); + times_box.append(&start_time_edit); + times_box.append(&stop_time_edit); + vert_box.append(&labels_box); + vert_box.append(×_box); + vert_box.append(&instructions); + vert_box.append(&time_error); + vert_box.append(&future_error); + vert_box.append(&name_error); + message_area.append(&vert_box); + + dialog.connect_response(clone!(@strong dialog => move |_ , resp| { + if resp == gtk::ResponseType::Ok { + instructions.set_visible(false); + time_error.set_visible(false); + future_error.set_visible(false); + name_error.set_visible(false); + let mut do_not_close = false; + let mut new_start_time_local = Local::now(); + let mut new_stop_time_local = Local::now(); + + // Task Name + if task_name_edit.text().trim().is_empty() { + name_error.set_visible(true); + do_not_close = true; + } + + // Start Time + let new_start_time_str = start_time_edit.text(); + let new_start_time: Result<NaiveDateTime, ParseError>; + if settings_manager::get_bool("show-seconds") { + new_start_time = NaiveDateTime::parse_from_str( + &new_start_time_str, + time_formatter); + } else { + new_start_time = NaiveDateTime::parse_from_str( + &new_start_time_str, + time_formatter_no_secs); + } + if let Err(_) = new_start_time { + instructions.set_visible(true); + do_not_close = true; + } else { + new_start_time_local = Local.from_local_datetime(&new_start_time.unwrap()).unwrap(); + if (Local::now() - new_start_time_local).num_seconds() < 0 { + future_error.set_visible(true); + do_not_close = true; + } + } + + // Stop Time + let new_stop_time_str = stop_time_edit.text(); + let new_stop_time: Result<NaiveDateTime, ParseError>; + if settings_manager::get_bool("show-seconds") { + new_stop_time = NaiveDateTime::parse_from_str( + &new_stop_time_str, + time_formatter); + } else { + new_stop_time = NaiveDateTime::parse_from_str( + &new_stop_time_str, + time_formatter_no_secs); + } + if let Err(_) = new_stop_time { + instructions.set_visible(true); + do_not_close = true; + } else { + new_stop_time_local = Local.from_local_datetime(&new_stop_time.unwrap()).unwrap(); + if (Local::now() - new_stop_time_local).num_seconds() < 0 { + future_error.set_visible(true); + do_not_close = true; + } + } + + // Start time can't be later than stop time + if !do_not_close && (new_stop_time_local - new_start_time_local).num_seconds() < 0 { + time_error.set_visible(true); + do_not_close = true; + } + + // Tags + let mut new_tag_list = "".to_string(); + if !task_tags_edit.text().trim().is_empty() { + let new_tags = task_tags_edit.text(); + let mut split_tags: Vec<&str> = new_tags.trim().split("#").collect(); + split_tags = split_tags.iter().map(|x| x.trim()).collect(); + // Don't allow empty tags + split_tags.retain(|&x| !x.trim().is_empty()); + // Handle duplicate tags before they are saved + split_tags = split_tags.into_iter().unique().collect(); + // Lowercase tags + let lower_tags: Vec<String> = split_tags.iter().map(|x| x.to_lowercase()).collect(); + new_tag_list = lower_tags.join(" #"); + } + + if !do_not_close { + let _ = database::db_write(task_name_edit.text().trim(), + new_start_time_local, + new_stop_time_local, + new_tag_list); + let window = FurtheranceWindow::default(); + window.reset_history_box(); + dialog.close(); + this.close(); + } + + } else if resp == gtk::ResponseType::Cancel { + dialog.close(); + } + })); + + dialog.show(); + })); + } + fn delete_all(&self) { let imp = imp::FurTaskDetails::from_instance(self); let _ = database::delete_by_ids(imp.all_task_ids.borrow().to_vec()); |