From 549803558d22b36ac6ac178722f2a8cc0548531e Mon Sep 17 00:00:00 2001 From: Joan Lledó Date: Sat, 16 Sep 2023 11:07:56 +0200 Subject: Query DB by task name --- src/database.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'src') diff --git a/src/database.rs b/src/database.rs index 7050cbb..6c558fe 100644 --- a/src/database.rs +++ b/src/database.rs @@ -343,6 +343,29 @@ pub fn get_list_by_id(id_list: Vec) -> Result, rusqlite::Error> { Ok(tasks_vec) } +pub fn get_list_by_name_or_tag(task_name: String) -> Result, rusqlite::Error> { + let conn = Connection::open(get_directory())?; + let mut tasks_vec: Vec = Vec::new(); + let name_param = format!("%{}%", task_name); + + let mut query = conn.prepare("SELECT * FROM tasks WHERE lower(task_name) LIKE lower(:task_name)")?; + let task_iter = query.query_map(&[(":task_name", &name_param)], |row| { + Ok(Task { + id: row.get(0)?, + task_name: row.get(1)?, + start_time: row.get(2)?, + stop_time: row.get(3)?, + tags: row.get(4)?, + }) + })?; + + for task_item in task_iter { + tasks_vec.push(task_item.unwrap()); + } + + Ok(tasks_vec) +} + pub fn check_for_tasks() -> Result { let conn = Connection::open(get_directory())?; -- cgit 1.4.1 From 5ff42c0a2d7944fd4bbfb424dd0fa05ced266469 Mon Sep 17 00:00:00 2001 From: Joan Lledó Date: Sat, 16 Sep 2023 11:34:25 +0200 Subject: Implement ToString trait for Task --- src/database.rs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/database.rs b/src/database.rs index 6c558fe..f422d23 100644 --- a/src/database.rs +++ b/src/database.rs @@ -38,6 +38,12 @@ pub struct Task { pub tags: String, } +impl ToString for Task { + fn to_string(&self) -> String { + format!("{} #{}", self.task_name, self.tags) + } +} + #[derive( Debug, Clone, -- cgit 1.4.1 From a03368c2c0295c4a4fd64afc1ea684a9bfce80e4 Mon Sep 17 00:00:00 2001 From: Joan Lledó Date: Sat, 16 Sep 2023 11:40:29 +0200 Subject: Rename get_list_by_name --- src/database.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/database.rs b/src/database.rs index f422d23..4553460 100644 --- a/src/database.rs +++ b/src/database.rs @@ -349,7 +349,7 @@ pub fn get_list_by_id(id_list: Vec) -> Result, rusqlite::Error> { Ok(tasks_vec) } -pub fn get_list_by_name_or_tag(task_name: String) -> Result, rusqlite::Error> { +pub fn get_list_by_name(task_name: String) -> Result, rusqlite::Error> { let conn = Connection::open(get_directory())?; let mut tasks_vec: Vec = Vec::new(); let name_param = format!("%{}%", task_name); -- cgit 1.4.1 From 5adf507e2a0d17f88ab6bc4541f6f2cb12bde193 Mon Sep 17 00:00:00 2001 From: Joan Lledó Date: Sat, 16 Sep 2023 12:55:40 +0200 Subject: DB Query: order by name So the dedup method works properly --- src/database.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/database.rs b/src/database.rs index 4553460..8a7c6c9 100644 --- a/src/database.rs +++ b/src/database.rs @@ -354,7 +354,7 @@ pub fn get_list_by_name(task_name: String) -> Result, rusqlite::Error> let mut tasks_vec: Vec = Vec::new(); let name_param = format!("%{}%", task_name); - let mut query = conn.prepare("SELECT * FROM tasks WHERE lower(task_name) LIKE lower(:task_name)")?; + let mut query = conn.prepare("SELECT * FROM tasks WHERE lower(task_name) LIKE lower(:task_name) ORDER BY task_name")?; let task_iter = query.query_map(&[(":task_name", &name_param)], |row| { Ok(Task { id: row.get(0)?, -- cgit 1.4.1 From 39761d3597c4ce6db1709501e3601b018aaeb233 Mon Sep 17 00:00:00 2001 From: Joan Lledó Date: Sat, 23 Sep 2023 13:41:53 +0200 Subject: Add autocomplete popup --- src/ui/window.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/ui/window.rs b/src/ui/window.rs index 2270711..0f54828 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -115,6 +115,8 @@ glib::wrapper! { } impl FurtheranceWindow { + const MIN_PREFIX_LENGTH: i32 = 3; + pub fn new(app: &Application) -> Self { glib::Object::builder() .property("application", Some(app)) @@ -182,6 +184,13 @@ impl FurtheranceWindow { imp.start_button.set_sensitive(false); imp.start_button.add_css_class("suggested-action"); self.refresh_timer(); + + let task_autocomplete = gtk::EntryCompletion::new(); + task_autocomplete.set_text_column(0); + task_autocomplete.set_minimum_key_length(FurtheranceWindow::MIN_PREFIX_LENGTH); + task_autocomplete.set_match_func(|_ac, _s, _it| { true }); + imp.task_input.set_completion(Some(&task_autocomplete)); + imp.task_input.grab_focus(); if settings_manager::get_bool("autosave") { @@ -200,11 +209,18 @@ impl FurtheranceWindow { let imp2 = imp::FurtheranceWindow::from_obj(&this); let task_input_text = task_input.text(); let split_tags: Vec<&str> = task_input_text.trim().split('#').collect(); - if split_tags[0].trim().is_empty() { + let task_name = split_tags[0]; + if task_name.trim().is_empty() { imp2.start_button.set_sensitive(false); } else { imp2.start_button.set_sensitive(true); } + + if task_input.text().len() >= FurtheranceWindow::MIN_PREFIX_LENGTH.try_into().unwrap() { + let task_autocomplete = task_input.completion().unwrap(); + let model = Self::update_list_model(task_name.to_string()); + task_autocomplete.set_model(Some(&model)); + } })); imp.start_button.connect_clicked(clone!(@weak self as this => move |button| { @@ -528,6 +544,18 @@ impl FurtheranceWindow { imp.task_input.set_activates_default(true); } + fn update_list_model(task_name: String) -> gtk::ListStore { + let col_types: [glib::Type; 1] = [glib::Type::STRING]; + let mut task_list = database::get_list_by_name(task_name).unwrap(); + task_list.dedup_by(|a, b| a.task_name == b.task_name && a.tags == b.tags); + let store = gtk::ListStore::new(&col_types); + + for task in task_list { + store.set(&store.append(), &[(0, &task.to_string())]); + } + store + } + fn get_idle_time(&self) -> Result> { let c = Connection::new_session()?; -- cgit 1.4.1 From c6d5440c44ac7b94a9d7db38c36e638097872852 Mon Sep 17 00:00:00 2001 From: Joan Lledó Date: Sat, 23 Sep 2023 14:04:18 +0200 Subject: Error management for `update_list_model()` --- src/ui/window.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/ui/window.rs b/src/ui/window.rs index 0f54828..ccf2364 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -218,7 +218,7 @@ impl FurtheranceWindow { if task_input.text().len() >= FurtheranceWindow::MIN_PREFIX_LENGTH.try_into().unwrap() { let task_autocomplete = task_input.completion().unwrap(); - let model = Self::update_list_model(task_name.to_string()); + let model = Self::update_list_model(task_name.to_string()).unwrap(); task_autocomplete.set_model(Some(&model)); } })); @@ -544,16 +544,16 @@ impl FurtheranceWindow { imp.task_input.set_activates_default(true); } - fn update_list_model(task_name: String) -> gtk::ListStore { + fn update_list_model(task_name: String) -> Result { let col_types: [glib::Type; 1] = [glib::Type::STRING]; - let mut task_list = database::get_list_by_name(task_name).unwrap(); + let mut task_list = database::get_list_by_name(task_name)?; task_list.dedup_by(|a, b| a.task_name == b.task_name && a.tags == b.tags); let store = gtk::ListStore::new(&col_types); for task in task_list { store.set(&store.append(), &[(0, &task.to_string())]); } - store + Ok(store) } fn get_idle_time(&self) -> Result> { -- cgit 1.4.1 From 14ae87d469555af8301356f99a11c2254edee1a4 Mon Sep 17 00:00:00 2001 From: Joan Lledó Date: Sat, 30 Sep 2023 13:06:01 +0200 Subject: Implement search by tag list --- src/database.rs | 24 +++++++++++++++--------- src/ui/window.rs | 12 ++++++------ 2 files changed, 21 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/database.rs b/src/database.rs index 8a7c6c9..2f97d2d 100644 --- a/src/database.rs +++ b/src/database.rs @@ -349,13 +349,23 @@ pub fn get_list_by_id(id_list: Vec) -> Result, rusqlite::Error> { Ok(tasks_vec) } -pub fn get_list_by_name(task_name: String) -> Result, rusqlite::Error> { +pub fn get_list_by_name_and_tags(task_name: String, tag_list: Vec) -> Result, rusqlite::Error> { let conn = Connection::open(get_directory())?; - let mut tasks_vec: Vec = Vec::new(); + let name_param = format!("%{}%", task_name); + let tag_list_params: Vec = tag_list.iter().map(|tag| format!("%{}%", tag)).collect(); + + let mut sql_query = String::from("SELECT * FROM tasks WHERE lower(task_name) LIKE lower(?)"); + tag_list_params.iter().for_each(|_| sql_query.push_str(" AND lower(tags) LIKE lower(?)")); + sql_query.push_str(" ORDER BY task_name"); + + let mut query = conn.prepare(sql_query.as_str())?; + query.raw_bind_parameter(1, name_param)?; + for (i, tag) in tag_list_params.iter().enumerate() { + query.raw_bind_parameter(i + 2, tag)?; + } - let mut query = conn.prepare("SELECT * FROM tasks WHERE lower(task_name) LIKE lower(:task_name) ORDER BY task_name")?; - let task_iter = query.query_map(&[(":task_name", &name_param)], |row| { + let tasks_vec = query.raw_query().mapped(|row| { Ok(Task { id: row.get(0)?, task_name: row.get(1)?, @@ -363,11 +373,7 @@ pub fn get_list_by_name(task_name: String) -> Result, rusqlite::Error> stop_time: row.get(3)?, tags: row.get(4)?, }) - })?; - - for task_item in task_iter { - tasks_vec.push(task_item.unwrap()); - } + }).map(|task_item| task_item.unwrap()).collect(); Ok(tasks_vec) } diff --git a/src/ui/window.rs b/src/ui/window.rs index ccf2364..8972ed9 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -208,9 +208,9 @@ impl FurtheranceWindow { .connect_changed(clone!(@weak self as this => move |task_input| { let imp2 = imp::FurtheranceWindow::from_obj(&this); let task_input_text = task_input.text(); - let split_tags: Vec<&str> = task_input_text.trim().split('#').collect(); - let task_name = split_tags[0]; - if task_name.trim().is_empty() { + let mut split_tags: Vec = task_input_text.split('#').map(|tag| String::from(tag.trim())).collect(); + let task_name = split_tags.remove(0); + if task_name.is_empty() { imp2.start_button.set_sensitive(false); } else { imp2.start_button.set_sensitive(true); @@ -218,7 +218,7 @@ impl FurtheranceWindow { if task_input.text().len() >= FurtheranceWindow::MIN_PREFIX_LENGTH.try_into().unwrap() { let task_autocomplete = task_input.completion().unwrap(); - let model = Self::update_list_model(task_name.to_string()).unwrap(); + let model = Self::update_list_model(task_name.to_string(), split_tags).unwrap(); task_autocomplete.set_model(Some(&model)); } })); @@ -544,9 +544,9 @@ impl FurtheranceWindow { imp.task_input.set_activates_default(true); } - fn update_list_model(task_name: String) -> Result { + fn update_list_model(task_name: String, tag_list: Vec) -> Result { let col_types: [glib::Type; 1] = [glib::Type::STRING]; - let mut task_list = database::get_list_by_name(task_name)?; + let mut task_list = database::get_list_by_name_and_tags(task_name, tag_list)?; task_list.dedup_by(|a, b| a.task_name == b.task_name && a.tags == b.tags); let store = gtk::ListStore::new(&col_types); -- cgit 1.4.1