From c8215d0df5d8100691b7f29180490d32735b3a9e Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Sat, 1 Aug 2026 13:44:19 +0200 Subject: [PATCH] implement rename for text notes --- application/backend/src/backend.rs | 9 ++-- application/backend/src/storage.rs | 10 ++-- application/note/src/main.rs | 2 + application/note/src/note.rs | 74 +++++++++++++++++++++++++++++- entity/src/collection.rs | 2 +- ui-rs/src/ui/list.rs | 12 ++--- ui-rs/src/ui/menu.rs | 4 +- ui-rs/src/ui/text.rs | 8 ++-- ui-rs/src/ui/toolbar.rs | 6 +-- ui/gtk/window.c | 4 +- 10 files changed, 103 insertions(+), 28 deletions(-) diff --git a/application/backend/src/backend.rs b/application/backend/src/backend.rs index 3e1c912..823b2f0 100644 --- a/application/backend/src/backend.rs +++ b/application/backend/src/backend.rs @@ -227,7 +227,6 @@ impl Backend { repository_id: Set(repo.repository_id), name: Set("Notebooks".to_string()), parent: Set("".to_string()), - icon: Set("".to_string()), kind: Set(CollectionType::Notebook), ..Default::default() @@ -250,7 +249,6 @@ impl Backend { repository_id: Set(repo.repository_id), name: Set("Notes".to_string()), parent: Set("/Notebooks".to_string()), - icon: Set("".to_string()), kind: Set(CollectionType::Notebook), ..Default::default() @@ -399,7 +397,6 @@ impl BackendHandle { profile_id: Set(profile_id), name: Set(name.to_string()), parent: Set(parent_path), - icon: Set("".to_string()), kind: Set(CollectionType::Notebook), ..Default::default() @@ -691,15 +688,15 @@ impl BackendHandle { return; } }; - + let mut update = Note::update_many(); if let Some(nodename ) = &nodename { - update = update.col_expr(Column::Nodename, Expr::value(nodename.clone())); + update = update.col_expr(Column::Nodename, Expr::value(nodename.clone())).col_expr(Column::FixedTitle, Expr::value(true)); } if let Some(title ) = &title { update = update.col_expr(Column::Title, Expr::value(title)); } - + let result = update .filter(Column::NoteId.eq(note_id)) .exec(&bhandle.backend.db).await; diff --git a/application/backend/src/storage.rs b/application/backend/src/storage.rs index 7ce260a..681fe85 100644 --- a/application/backend/src/storage.rs +++ b/application/backend/src/storage.rs @@ -151,9 +151,13 @@ pub async fn check_free_nodename(db: &DatabaseConnection, collection_id: i32, no let path = get_note_storage_path(db, note_id).await?; if let Some(path) = path { - let m = fs::metadata(Path::new(&path)).await; - if m.is_ok() { - return Ok(false) // file exists + let p = Path::new(&path); + if let Some(parent) = p.parent() { + let new_path = parent.join(nodename); + let m = fs::metadata(Path::new(new_path.as_path())).await; + if m.is_ok() { + return Ok(false) // file exists + } } } diff --git a/application/note/src/main.rs b/application/note/src/main.rs index dfe9151..f18e762 100644 --- a/application/note/src/main.rs +++ b/application/note/src/main.rs @@ -144,6 +144,7 @@ fn create_menubar(app: &AppContext) { menu.item("New").action("new_note").create(); menu.item("New Window").onclick(|_| new_app_window() ).create(); menu.separator(); + menu.item("Rename").action("note_rename").create(); menu.item("Delete").action("note_delete").create(); menu.item("Edit Title").action("note_edit_title").create(); }); @@ -171,6 +172,7 @@ fn create_toolbar(app: &AppContext) { app.toolbar_appmenu(|menu|{ menu.item("New Window").onclick(|_| new_app_window() ).create(); menu.separator(); + menu.item("Rename").action("note_rename").create(); menu.item("Delete").action("delete_note").create(); menu.item("Edit Title").action("note_edit_title").create(); }); diff --git a/application/note/src/note.rs b/application/note/src/note.rs index 305008b..07c3fbf 100644 --- a/application/note/src/note.rs +++ b/application/note/src/note.rs @@ -34,7 +34,7 @@ use sea_orm::sea_query::prelude::Utc; use backend::backend::{BackendHandle, BroadcastMessage, NoteContentRet, NoteId, NoteTitleUpdate, SaveNoteResult}; use backend::lockmanager::NoteLock; use entity::note::NoteType; -use ui_rs::{action, doc_cast, ui_actions, UiModel}; +use ui_rs::{action, dialog, doc_cast, ui_actions, UiModel}; use ui_rs::ui::*; use crate::AppStates; use crate::window::NoteTypeTabView; @@ -438,6 +438,78 @@ impl Note { }; doc.ctx.unset_state(AppStates::NoteShowExtModInfo as i32); } + + #[action] + pub fn note_rename(&mut self, event: &mut ActionEvent) { + let Some(doc) = self.doc.get_doc() else { + return; + }; + let Some(obj) = &event.obj else { + return; + }; + let NoteId::Id(note_id) = self.id else { + return; + }; + + let nodename = match &self.nodename { + Some(name) => name.as_str(), + None => "", + }; + + let proxy = doc.doc_proxy(); + let collection_id = self.collection_id; + let backend = self.backend.clone(); + + obj.dialog() + .title("Rename Note") + .content("File Name") + .input(true) + .input_value(nodename) + .button1_label("OK") + .closebutton_label("Cancel") + .result(move |event|{ + if let EventType::String(new_name) = &event.event_type && event.intval == 1 { + // Rename OK + // check if the new nodename is available + let new_nodename = new_name.clone(); + backend.check_nodename(collection_id, note_id, new_name.as_str(), move|result|{ + proxy.call_mainthread(move|doc, note| { + match result { + Ok(available) => { + if available { + let proxy = doc.doc_proxy(); + note.backend.rename_note(1, Some(new_nodename.clone()), None, |result|{ + proxy.call_mainthread(|_doc, note|{ + match result { + Ok(updated) => { + if updated.rows_affected != 1 { + eprintln!("rename_note: unexpected number of rows affected"); + } else { + note.nodename = Some(new_nodename); + note.fixed_title = true; + } + }, + Err(e) => { + eprintln!("note update failed: {:?}", e); + } + } + }); + }); + } else { + let msg = format!("File {} already exists", new_nodename); + dialog!(title = "Rename Note", content = msg.as_str(), button1_label = "OK"); + } + } + Err(e) => { + eprintln!("check_nodename failed: {:?}", e); + } + } + }); + }); + } + }) + .create(); + } } impl NoteViewModel for Note { diff --git a/entity/src/collection.rs b/entity/src/collection.rs index afffff6..f898178 100644 --- a/entity/src/collection.rs +++ b/entity/src/collection.rs @@ -43,7 +43,7 @@ pub struct Model { pub name: String, pub parent: String, - pub icon: String, + pub icon: Option, pub kind: CollectionType, pub storage: LocalStorageSetting } diff --git a/ui-rs/src/ui/list.rs b/ui-rs/src/ui/list.rs index fc55a26..3bb7c69 100644 --- a/ui-rs/src/ui/list.rs +++ b/ui-rs/src/ui/list.rs @@ -338,7 +338,7 @@ impl<'a, T, E> ListViewBuilder<'a, T, E> { pub fn onactivate(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ptr = self.obj.ctx.reg_box(wrapper); unsafe { ui_list_args_set_onactivate(self.args, Some(event::event_wrapper::)); @@ -357,7 +357,7 @@ impl<'a, T, E> ListViewBuilder<'a, T, E> { pub fn onselection(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ptr = self.obj.ctx.reg_box(wrapper); unsafe { ui_list_args_set_onselection(self.args, Some(event::event_wrapper::)); @@ -575,7 +575,7 @@ impl<'a, T, E> TableViewBuilder<'a, T, E> { pub fn onactivate(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ptr = self.obj.ctx.reg_box(wrapper); unsafe { ui_list_args_set_onactivate(self.args, Some(event::event_wrapper::)); @@ -595,7 +595,7 @@ impl<'a, T, E> TableViewBuilder<'a, T, E> { pub fn onselection(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ptr = self.obj.ctx.reg_box(wrapper); unsafe { ui_list_args_set_onselection(self.args, Some(event::event_wrapper::)); @@ -791,7 +791,7 @@ impl<'a, T, E> SourceListBuilder<'a, T, E> { pub fn onactivate(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ptr = self.obj.ctx.reg_box(wrapper); unsafe { ui_sourcelist_args_set_onactivate(self.args, Some(event::event_wrapper::)); @@ -802,7 +802,7 @@ impl<'a, T, E> SourceListBuilder<'a, T, E> { pub fn onbuttonclick(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ptr = self.obj.ctx.reg_box(wrapper); unsafe { ui_sourcelist_args_set_onbuttonclick(self.args, Some(event::event_wrapper::)); diff --git a/ui-rs/src/ui/menu.rs b/ui-rs/src/ui/menu.rs index bf060ec..69e9fa6 100644 --- a/ui-rs/src/ui/menu.rs +++ b/ui-rs/src/ui/menu.rs @@ -160,7 +160,7 @@ impl MenuItemBuilder { pub fn onclick(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ctx = UiContext { ptr: self.ctx }; let ptr = ctx.reg_box(wrapper); unsafe { @@ -211,7 +211,7 @@ impl MenuToggleItemBuilder { pub fn onchange(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ctx = UiContext { ptr: self.ctx }; let ptr = ctx.reg_box(wrapper); unsafe { diff --git a/ui-rs/src/ui/text.rs b/ui-rs/src/ui/text.rs index cbbe19e..5eb92ab 100644 --- a/ui-rs/src/ui/text.rs +++ b/ui-rs/src/ui/text.rs @@ -272,7 +272,7 @@ impl<'a, T> TextAreaBuilder<'a, T> { pub fn onchange(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f) ); let ptr = self.obj.ctx.reg_box(wrapper); unsafe { ui_textarea_args_set_onchange(self.args, Some(event::event_wrapper::)); @@ -291,7 +291,7 @@ impl<'a, T> TextAreaBuilder<'a, T> { pub fn ontextchanged(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ptr = self.obj.ctx.reg_box(wrapper); unsafe { ui_textarea_args_set_ontextchanged(self.args, Some(event::event_wrapper::)); @@ -584,7 +584,7 @@ impl<'a, T> TextFieldBuilder<'a, T> { pub fn onactivate(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ptr = self.obj.ctx.reg_box(wrapper); unsafe { ui_textfield_args_set_onactivate(self.args, Some(event::event_wrapper::)); @@ -603,7 +603,7 @@ impl<'a, T> TextFieldBuilder<'a, T> { pub fn onchange(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ptr = self.obj.ctx.reg_box(wrapper); unsafe { ui_textfield_args_set_onchange(self.args, Some(event::event_wrapper::)); diff --git a/ui-rs/src/ui/toolbar.rs b/ui-rs/src/ui/toolbar.rs index 7cffe1d..54678cd 100644 --- a/ui-rs/src/ui/toolbar.rs +++ b/ui-rs/src/ui/toolbar.rs @@ -190,7 +190,7 @@ impl ToolbarItemBuilder { pub fn onclick(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ctx = UiContext { ptr: self.ctx }; let ptr = ctx.reg_box(wrapper); unsafe { @@ -271,7 +271,7 @@ impl ToolbarToggleItemBuilder { pub fn onchange(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ctx = UiContext { ptr: self.ctx }; let ptr = ctx.reg_box(wrapper); unsafe { @@ -368,7 +368,7 @@ impl ToolbarContentToggleItemBuilder { pub fn onchange(&mut self, f: F) -> &mut Self where F: FnMut(&mut event::Event) + 'static { - let wrapper = Box::new(EventWrapper { callback: Box::new(f) }); + let wrapper = Box::new(EventWrapper::new(f)); let ctx = UiContext { ptr: self.ctx }; let ptr = ctx.reg_box(wrapper); unsafe { diff --git a/ui/gtk/window.c b/ui/gtk/window.c index 009d432..fc6e9e2 100644 --- a/ui/gtk/window.c +++ b/ui/gtk/window.c @@ -511,8 +511,8 @@ UIEXPORT void ui_splitview_window_set_visible(UiObject *obj, int pane, UiBool vi static void dialog_response(AdwAlertDialog *self, gchar *response, UiEventData *data) { UiEvent evt; evt.obj = data->obj; - evt.document = evt.obj->ctx->document; - evt.window = evt.obj->window; + evt.document = evt.obj ? evt.obj->ctx->document : NULL; + evt.window = evt.obj ? evt.obj->window : NULL; evt.eventdata = NULL; evt.eventdatatype = 0; evt.intval = 0; -- 2.52.0