* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
+
use std::rc::Rc;
use backend::backend::{BackendHandle, NoteId, OpenExternResult};
-use ui_rs::{action, ui_actions, UiModel};
+use ui_rs::{action, doc_cast, ui_actions, UiModel};
use ui_rs::ui::*;
use crate::AppStates;
+use crate::note::NoteViewModel;
use crate::window::NoteTypeTabView;
#[derive(UiModel)]
}
}
- pub fn into_doc(self, model: &entity::note::Model) -> UiDoc<FileNote> {
- UiDoc::new2(self, |n,d| {
+ pub fn into_doc(self, model: &entity::note::Model) -> UiDoc<dyn NoteViewModel> {
+ let doc: UiDoc<FileNote> = UiDoc::new_from_box2(Box::new(self), |n,d| {
n.doc = d.doc_ref();
n.nodename = model.nodename.clone();
n.note_type.set(NoteTypeTabView::File as i64);
n.note_nodename.set(model.nodename.as_str());
- })
+ });
+ doc_cast!(doc, dyn NoteViewModel)
}
#[action]
doc.ctx.set_state(AppStates::NoteMaximized as i32);
}
}
+
+impl NoteViewModel for FileNote {
+ fn save_note(&mut self) {
+
+ }
+}
\ No newline at end of file
use backend::backend::{BackendHandle, BroadcastMessage, NoteId, NoteTitleUpdate, SaveNoteResult};
use backend::lockmanager::NoteLock;
use entity::note::NoteType;
-use ui_rs::{action, ui_actions, UiModel};
+use ui_rs::{action, doc_cast, ui_actions, UiModel};
use ui_rs::ui::*;
use crate::AppStates;
use crate::window::NoteTypeTabView;
static TMP_ID: AtomicU64 = AtomicU64::new(1);
+pub trait NoteViewModel: UiModel + UiActions {
+ fn save_note(&mut self);
+}
+
+
#[derive(UiModel)]
pub struct Note {
pub doc: UiDocRef<Note>,
}
}
- pub fn into_doc(self, model: &entity::note::Model) -> UiDoc<Note> {
- UiDoc::new2(self, |n,d| {
+ pub fn into_doc(self, model: &entity::note::Model) -> UiDoc<dyn NoteViewModel> {
+ let doc: UiDoc<Note> = UiDoc::new_from_box2(Box::new(self), |n,d| {
n.doc = d.doc_ref();
if n.id.is_new() {
n.init_new();
}
// make sure we receive an event, when this note is attached or detached
d.ctx.on_attachment_status_change_action("attachment_status_changed");
- })
+ });
+ doc_cast!(doc, dyn NoteViewModel)
}
+
/// Handles any attachment status change, which includes if this note is
/// directly attached or detached, but also if any parent attachment status changes
#[action]
self.save_note();
}
- pub fn save_note(&mut self) {
- if !self.modified {
- return;
- }
-
- let Some(doc) = self.doc.get_doc() else {
- return;
- };
-
- let content = self.text.get();
- let title = match generate_title(content.as_str()) {
- Some(title) => title.0.to_string(),
- None => "New Note".to_string() // TODO: see NoteItem::new()
- };
-
- let (note_id, created) = match &self.id {
- NoteId::Id(id) => (Set(*id), NotSet),
- NoteId::TmpId(_) => (NotSet, Set(Utc::now().into()))
- };
-
- let note = entity::note::ActiveModel {
- note_id: note_id.clone(),
- collection_id: Set(self.collection_id),
- nodename: if let Some(nodename) = &self.nodename { Set(nodename.clone())} else { NotSet },
- kind: Set(self.kind.clone()),
- title: Set(title),
- lastmodified: Set(Utc::now().into()),
- created: created,
- version: Set(self.version)
- };
-
- let notecontent = entity::notecontent::ActiveModel {
- id: if self.content_id == 0 { NotSet } else { Set(self.content_id) },
- note_id: note_id,
- content: Set(content),
- };
-
- let proxy = doc.doc_proxy();
- self.backend.save_note(self.notebook_instance_id, self.id.clone(), note, Some(notecontent), |result|{
- proxy.call_mainthread(move |doc, note|{
- match result {
- SaveNoteResult::Ok((notemodel, content_id)) => {
- note.id = NoteId::Id(notemodel.note_id);
- note.content_id = content_id;
- note.version = notemodel.version;
- println!("Note saved: note_id: {}, content_id: {}, version: {}", notemodel.note_id, content_id, note.version);
-
- // make sure the "new note" button is always enabled within this note
- doc.ctx.unsuppress_state(AppStates::NoteEnableNew as i32);
- },
- SaveNoteResult::VersionConflict => {
- println!("Failed to save note: version conflict");
- },
- SaveNoteResult::Error(e) => {
- println!("Failed to save note: {}", e);
- },
- SaveNoteResult::ValidationError(s) => {
- println!("Failed to save note: validation error: {}", s);
- }
- SaveNoteResult::FileAlreadyExists(path) => {
- println!("Failed to save note: file already exists: {}", path.display());
- }
- SaveNoteResult::FileError(e) => {
- println!("Failed to save note: file error: {}", e);
- }
- }
- });
- });
-
- self.modified = false;
- }
-
#[action(u64)]
pub fn changed_by(&mut self, _event: &ActionEvent, _from: &u64) {
// The from id is currently unused, however it may be important when it is possible
}
}
+impl NoteViewModel for Note {
+ fn save_note(&mut self) {
+ if !self.modified {
+ return;
+ }
+
+ let Some(doc) = self.doc.get_doc() else {
+ return;
+ };
+
+ let content = self.text.get();
+ let title = match generate_title(content.as_str()) {
+ Some(title) => title.0.to_string(),
+ None => "New Note".to_string() // TODO: see NoteItem::new()
+ };
+
+ let (note_id, created) = match &self.id {
+ NoteId::Id(id) => (Set(*id), NotSet),
+ NoteId::TmpId(_) => (NotSet, Set(Utc::now().into()))
+ };
+
+ let note = entity::note::ActiveModel {
+ note_id: note_id.clone(),
+ collection_id: Set(self.collection_id),
+ nodename: if let Some(nodename) = &self.nodename { Set(nodename.clone())} else { NotSet },
+ kind: Set(self.kind.clone()),
+ title: Set(title),
+ lastmodified: Set(Utc::now().into()),
+ created: created,
+ version: Set(self.version)
+ };
+
+ let notecontent = entity::notecontent::ActiveModel {
+ id: if self.content_id == 0 { NotSet } else { Set(self.content_id) },
+ note_id: note_id,
+ content: Set(content),
+ };
+
+ let proxy = doc.doc_proxy();
+ self.backend.save_note(self.notebook_instance_id, self.id.clone(), note, Some(notecontent), |result|{
+ proxy.call_mainthread(move |doc, note|{
+ match result {
+ SaveNoteResult::Ok((notemodel, content_id)) => {
+ note.id = NoteId::Id(notemodel.note_id);
+ note.content_id = content_id;
+ note.version = notemodel.version;
+ println!("Note saved: note_id: {}, content_id: {}, version: {}", notemodel.note_id, content_id, note.version);
+
+ // make sure the "new note" button is always enabled within this note
+ doc.ctx.unsuppress_state(AppStates::NoteEnableNew as i32);
+ },
+ SaveNoteResult::VersionConflict => {
+ println!("Failed to save note: version conflict");
+ },
+ SaveNoteResult::Error(e) => {
+ println!("Failed to save note: {}", e);
+ },
+ SaveNoteResult::ValidationError(s) => {
+ println!("Failed to save note: validation error: {}", s);
+ }
+ SaveNoteResult::FileAlreadyExists(path) => {
+ println!("Failed to save note: file already exists: {}", path.display());
+ }
+ SaveNoteResult::FileError(e) => {
+ println!("Failed to save note: file error: {}", e);
+ }
+ }
+ });
+ });
+
+ self.modified = false;
+ }
+}
+
fn generate_title(s: &str) -> Option<(&str, usize)> {
for line in s.lines() {
if !line.trim().is_empty() {
use ui_rs::ui::*;
use entity::note::{Model as Note, NoteType};
-use crate::note::new_note_id;
+use crate::note::{new_note_id, NoteViewModel};
use crate::window::NavigationItem;
/// Each Notebook view model gets a unique instance id, that is mostly used to identify,
doc.ctx.on_attach_action("notebook_on_attach")
})
}
-
+
#[action]
pub fn notebook_on_attach(&mut self, event: &mut ActionEvent) {
let Some(obj) = &mut event.obj else {
// doc_ref.get_doc() should never fail here
if let Some(mut doc) = self.doc_ref.get_doc() {
// save the note
- current.doc.save_note();
+ let note_proxy = current.doc.doc_proxy();
+ note_proxy.call_mainthread(move |_doc, note|{
+ note.save_note();
+ });
// detach the note
- current.doc.detach_from(&mut doc.ctx);
+ doc.ctx.detach(¤t.doc);
self.selected_note = None;
}
}
match note.data.kind {
NoteType::File => {
let note_data = crate::file::FileNote::new(self.instance_id, note.id.clone(), self.collection_id, self.backend.clone());
- NoteDoc::File(note_data.into_doc(¬e.data))
+ note_data.into_doc(¬e.data)
},
_ => {
// default: text note (plain text or md)
let note_data = crate::note::Note::new(self.instance_id, note.id.clone(), self.collection_id, self.backend.clone());
- NoteDoc::Note(note_data.into_doc(¬e.data))
+ note_data.into_doc(¬e.data)
}
}
};
note.model = Some(doc.clone());
if let Some(mut nb) = self.doc_ref.get_doc() {
- doc.attach_to(&mut nb.ctx);
+ nb.ctx.attach(&doc);
if is_new {
nb.ctx.call_action("textarea_focus");
}
// The note is also currently attached
// Send a message to the note, that it needs an update
let arg = Box::new(update.from);
- selection.doc.ctx().call_action_with_parameter("changed_by", arg);
+ selection.doc.ctx.call_action_with_parameter("changed_by", arg);
} else {
// Note view model is not attached, we can just discard it and the next
// time the note is opened, it is recreated and the content reloaded
id: NoteId,
orig_id: NoteId,
data: entity::note::Model,
- model: Option<NoteDoc>,
+ model: Option<UiDoc<dyn NoteViewModel>>,
}
impl NoteItem {
pub struct NoteSelection {
pub id: NoteId,
- pub doc: NoteDoc,
+ pub doc: UiDoc<dyn NoteViewModel>,
pub index: usize
}
-#[derive(Clone)]
-pub enum NoteDoc {
- Note(UiDoc<crate::note::Note>),
- File(UiDoc<crate::file::FileNote>)
-}
-
-impl NoteDoc {
- pub fn ctx(&self) -> &UiContext {
- match self {
- NoteDoc::Note(doc) => { &doc.ctx }
- NoteDoc::File(doc) => { &doc.ctx }
- }
- }
-
- pub fn attach_to(&self, ctx: &mut UiContext) {
- match &self {
- NoteDoc::Note(doc) => {
- ctx.attach(doc);
- }
- NoteDoc::File(doc) => {
- ctx.attach(doc);
- }
- }
- }
-
- pub fn detach_from(&self, ctx: &mut UiContext) {
- match &self {
- NoteDoc::Note(doc) => {
- ctx.detach(doc);
- }
- NoteDoc::File(doc) => {
- ctx.detach(doc);
- }
- }
- }
-
- pub fn save_note(&self) {
- match self {
- NoteDoc::Note(doc) => {
- let note_proxy = doc.doc_proxy();
- note_proxy.call_mainthread(move |_doc, note|{
- note.save_note();
- });
- }
- NoteDoc::File(_) => {
-
- }
- }
- }
-}
\ No newline at end of file
pub target: *mut T
}
-pub struct ActionEventWrapperBoxed<T> {
- pub callback: Box<dyn FnMut(&mut T, &mut ActionEvent)>,
- pub target: *mut Box<T>
-}
-
pub extern "C" fn action_event_wrapper<T>(e: *const ffi::UiEvent, data: *mut c_void) {
unsafe {
let wrapper = &mut *(data as *mut ActionEventWrapper<T>);
}
}
-pub extern "C" fn action_event_wrapper_boxed<T>(e: *const ffi::UiEvent, data: *mut c_void) {
- unsafe {
- let wrapper = &mut *(data as *mut ActionEventWrapperBoxed<T>);
- let target = &mut *(wrapper.target);
- let target_ref = target.as_mut();
- action_callback(e, |event| {
- (wrapper.callback)(target_ref, event);
- });
- }
-}
fn action_callback<F>(e: *const ffi::UiEvent, f: F)
where F: FnOnce(&mut ActionEvent) {
use std::any::Any;
use std::ffi::{c_char, c_int, c_void, CStr, CString};
-use crate::ui::{action_event_wrapper, action_event_wrapper_boxed, event, ffi, simple_event_wrapper, ui_object_get_context, ui_object_get_windowdata, ui_reg_destructor, ui_remove_destructor, untyped_event_wrapper, ActionEventWrapper, ActionEventWrapperBoxed, Event, EventWrapper, NoAppData, SimpleEventWrapper, SubList, UntypedEventWrapper, RUST_TYPED_OBJECT_BOX_ANY};
+use crate::ui::{action_event_wrapper, event, ffi, simple_event_wrapper, ui_object_get_context, ui_object_get_windowdata, ui_reg_destructor, ui_remove_destructor, untyped_event_wrapper, ActionEventWrapper, Event, EventWrapper, NoAppData, SimpleEventWrapper, SubList, UntypedEventWrapper, RUST_TYPED_OBJECT_BOX_ANY};
use std::marker::PhantomData;
use std::mem;
// if this is a document context, use the document pointer as target
// otherwise it is a UiObject context, in that case we use the window_data as target
unsafe {
- let doc_ptr = ui_context_document(self.ptr) as *mut T;
- let target_ptr = if !doc_ptr.is_null() {
- let doc_storage: *mut *mut Box<T> = doc_ptr.cast();
- let target_ptr = *doc_storage;
- self.add_action_for_target_boxed(target_ptr, name, f);
+ let doc_ptr = ui_context_document(self.ptr);
+ if !doc_ptr.is_null() {
+ let doc_storage: *mut *mut Box<Option<T>> = doc_ptr.cast();
+ let target_box_ptr = *doc_storage;
+ let target_opt = &mut **target_box_ptr;
+ if let Some(target_ptr) = target_opt {
+ self.add_action_for_target(target_ptr, name, f);
+ }
} else {
let obj_ptr = ui_context_obj(self.ptr);
assert!(!obj_ptr.is_null());
}
}
- pub unsafe fn add_action_for_target_boxed<T, F>(&self, target_ptr: *mut Box<T>, name: &str, f: F)
- where F: FnMut(&mut T, &mut event::ActionEvent) + 'static {
- let wrapper = Box::new(ActionEventWrapperBoxed { callback: Box::new(f), target: target_ptr });
- let ptr = self.reg_box(wrapper);
- let cstr = CString::new(name).unwrap();
- unsafe {
- ui_add_action(self.ptr, cstr.as_ptr(), Some(action_event_wrapper_boxed::<T>), ptr as *mut c_void);
- }
- }
-
pub fn add_action_untyped<F>(&self, name: &str, f: F)
where F: FnMut(&mut event::ActionEvent) + 'static {
unsafe {
pub struct UiDoc<T: ?Sized + UiModel + UiActions> {
pub ctx: UiContext,
- ptr: *mut *mut Box<T>,
+ ptr: *mut *mut Option<Box<T>>,
_marker: PhantomData<T>
}
pub fn new_from_box2<F>(data: Box<T>, init: F) -> UiDoc<T>
where F: FnOnce(&mut T, &UiDoc<T>) {
unsafe {
- let doc = ui_document_new(mem::size_of::<*mut Box<T>>());
+ let doc = ui_document_new(10000 + mem::size_of::<*mut Box<T>>());
let ctx = UiContext::from_ptr(ui_document_context(doc));
ui_app_ref();
ui_reg_destructor(ctx.ptr, std::ptr::null_mut(), doc_app_unref);
- let data_ptr = ctx.reg_box(Box::new(data)); // returns *mut Box<T>
- let doc_storage: *mut *mut Box<T> = doc.cast();
+ let data_ptr = ctx.reg_box(Box::new(Some(data))); // returns *mut Box<T>
+ let doc_storage: *mut *mut Option<Box<T>> = doc.cast();
*doc_storage = data_ptr;
- let doc_ref = &mut *data_ptr;
+ let doc_ref_option = &mut *data_ptr;
+ let doc_ref = doc_ref_option.as_mut().unwrap();
doc_ref.init(&ctx);
doc_ref.init_actions(&ctx);
let doc_handle = UiDoc::<T> { ctx: ctx, ptr: doc.cast(), _marker: PhantomData };
}
}
+ /// This function should not be used directly, only via the doc_cast macro.
+ ///
+ /// It is safe as long as f returns the same Box (with the same heap address). If it returns
+ /// a completely new Box, things will break (any event wrapper, that have a pointer to the
+ /// old target pointer).
pub fn cast<U: ?Sized + UiModel + UiActions>(
self,
f: impl FnOnce(Box<T>) -> Box<U>,
) -> UiDoc<U> {
- unsafe {
- // replace the Box container for this document
- // the destructor for the previous Box must be removed
- ui_remove_destructor(self.ctx.ptr, self.ptr.cast());
- ui_document_ref(self.ptr.cast());
-
- let outer: Box<Box<T>> = Box::from_raw(*self.ptr);
-
- // Prevent dropping the outer box while extracting the inner box
- let outer = std::mem::ManuallyDrop::new(outer);
+ unsafe {
- let inner: Box<T> = std::ptr::read(&**outer);
- let new_inner: Box<U> = f(inner);
+ // because the doc is just transformed into a new type, the old doc must
+ // not be dropped
+ let doc = std::mem::ManuallyDrop::new(self);
- let new_outer: Box<Box<U>> = Box::new(new_inner);
+ // Get the top level box, that is directly stored in the document and extract the
+ // inner value, which holds the rust document data object.
+ let mut outer: Box<Option<Box<T>>> = Box::from_raw(*doc.ptr);
+ let inner_value = outer.take();
+ // Because the old outer box is replaced, it can be dropped in this function, but
+ // usually there is already a destructor registered for it. Remove the destructor,
+ // so the outer box can be droped here.
+ ui_remove_destructor(doc.ctx.ptr, *doc.ptr.cast());
- *self.ptr.cast::<*mut Box<U>>() = Box::into_raw(new_outer);
+ // Convert Box type and store it in the document
+ let new_value = inner_value.map(|v|f(v));
+ let data_ptr = doc.ctx.reg_box(Box::new(new_value));
+ let doc_storage: *mut *mut Option<Box<U>> = doc.ptr.cast();
+ *doc_storage = data_ptr;
- UiDoc {
- ctx: self.ctx.clone(),
- ptr: self.ptr.cast(),
- _marker: PhantomData,
- }
+ UiDoc {
+ ctx: doc.ctx.clone(),
+ ptr: doc_storage,
+ _marker: PhantomData,
+ }
}
}
UiDocProxy::from_ptr(self.ptr.cast())
}
- pub unsafe fn get_data_ptr(&self) -> *mut Box<T> {
+ pub unsafe fn get_data_ptr(& self) -> *mut Option<Box<T>> {
unsafe {
- let box_ptr: *mut *mut Box<T> = self.ptr.cast();
+ let box_ptr: *mut *mut Option<Box<T>> = self.ptr.cast();
*box_ptr
}
}
}
+/// Converts the document type to a dyn trait
+///
+/// Usage: doc_cast!(doc, dyn MyTrait)
#[macro_export]
macro_rules! doc_cast {
($doc:expr, dyn $trait:path) => {
- $doc.cast(|b| Box::new(*b) as Box<dyn $trait>)
+ $doc.cast(|b| {
+ b as Box<dyn $trait>
+ })
};
}
pub fn new2<F>(data: T, init: F) -> UiDoc<T>
where F: FnOnce(&mut T, &UiDoc<T>) {
- unsafe {
- let inner_box = Box::new(data);
- UiDoc::new_from_box2(inner_box, init)
- }
+ let inner_box = Box::new(data);
+ UiDoc::new_from_box2(inner_box, init)
}
/*
let doc: UiDoc<T> = UiDoc::from_ptr(self.ptr);
unsafe {
let data = doc.get_data_ptr();
- f(&doc, &mut *data);
+ let data_ref = &mut *data;
+ if let Some(dat) = data_ref {
+ f(&doc, &mut *dat);
+ }
}
drop(self);
});