#[cxx_qt::bridge] pub mod presentation_model { unsafe extern "C++" { include!(< QAbstractListModel >); include!("cxx-qt-lib/qhash.h"); type QHash_i32_QByteArray = cxx_qt_lib::QHash; include!("cxx-qt-lib/qmap.h"); type QMap_QString_QVariant = cxx_qt_lib::QMap; include!("cxx-qt-lib/qvariant.h"); type QVariant = cxx_qt_lib::QVariant; include!("cxx-qt-lib/qstring.h"); type QString = cxx_qt_lib::QString; include!("cxx-qt-lib/qurl.h"); type QUrl = cxx_qt_lib::QUrl; include!("cxx-qt-lib/qmodelindex.h"); type QModelIndex = cxx_qt_lib::QModelIndex; include!("cxx-qt-lib/qvector.h"); type QVector_i32 = cxx_qt_lib::QVector; // include!("cxx-qt-lib/qstringlist.h"); // type QStringList = cxx_qt_lib::QStringList; // include!("cxx-qt-lib/qlist.h"); // type QList_QString = cxx_qt_lib::QList; } #[qenum(PresentationModel)] enum PresRoles { Id, Title, Path, Html, PageCount, } unsafe extern "RustQt" { #[qobject] #[base = "QAbstractListModel"] #[qml_element] #[qproperty(i32, count)] type PresentationModel = super::PresentationModelRust; #[inherit] #[qsignal] fn data_changed( self: Pin<&mut PresentationModel>, top_left: &QModelIndex, bottom_right: &QModelIndex, roles: &QVector_i32, ); #[qinvokable] fn clear(self: Pin<&mut PresentationModel>); #[qinvokable] fn setup(self: Pin<&mut PresentationModel>); #[qinvokable] fn remove_item( self: Pin<&mut PresentationModel>, index: i32, ) -> bool; #[qinvokable] fn new_item( self: Pin<&mut PresentationModel>, url: QUrl, new_page_count: i32, ); // #[qinvokable] // fn update_path( // self: Pin<&mut PresentationModel>, // index: i32, // updated_path: QString, // ) -> bool; // #[qinvokable] // fn duplicate_item(self: Pin<&mut Self>, index: i32) -> bool; #[qinvokable] fn get_item( self: Pin<&mut PresentationModel>, index: i32, ) -> QMap_QString_QVariant; #[qinvokable] fn update_title( self: Pin<&mut PresentationModel>, index: i32, updated_title: QString, ) -> bool; #[qinvokable] fn update_page_count( self: Pin<&mut PresentationModel>, index: i32, updated_page_count: i32, ) -> bool; #[qinvokable] fn search( self: Pin<&mut PresentationModel>, search_term: QString, ); } impl cxx_qt::Threading for PresentationModel {} unsafe extern "RustQt" { #[inherit] unsafe fn begin_insert_rows( self: Pin<&mut PresentationModel>, parent: &QModelIndex, first: i32, last: i32, ); #[inherit] unsafe fn end_insert_rows(self: Pin<&mut PresentationModel>); #[inherit] unsafe fn begin_remove_rows( self: Pin<&mut PresentationModel>, parent: &QModelIndex, first: i32, last: i32, ); #[inherit] unsafe fn begin_move_rows( self: Pin<&mut PresentationModel>, source_parent: &QModelIndex, source_first: i32, source_last: i32, destination_parent: &QModelIndex, destination_child: i32, ) -> bool; #[inherit] unsafe fn end_move_rows(self: Pin<&mut PresentationModel>); #[inherit] unsafe fn end_remove_rows(self: Pin<&mut PresentationModel>); #[inherit] unsafe fn begin_reset_model( self: Pin<&mut PresentationModel>, ); #[inherit] unsafe fn end_reset_model(self: Pin<&mut PresentationModel>); #[inherit] fn can_fetch_more( self: &PresentationModel, parent: &QModelIndex, ) -> bool; #[inherit] fn index( self: &PresentationModel, row: i32, column: i32, parent: &QModelIndex, ) -> QModelIndex; #[qinvokable] #[cxx_override] fn data( self: &PresentationModel, index: &QModelIndex, role: i32, ) -> QVariant; #[qinvokable] #[cxx_override] fn role_names( self: &PresentationModel, ) -> QHash_i32_QByteArray; #[qinvokable] #[cxx_override] fn row_count( self: &PresentationModel, _parent: &QModelIndex, ) -> i32; } } use crate::presentation_model::presentation_model::QMap_QString_QVariant; use crate::reveal_js; use cxx_qt::CxxQtType; use cxx_qt_lib::{QModelIndex, QString, QUrl, QVariant}; use sqlx::{query, query_as, Connection, SqliteConnection}; use std::path::PathBuf; use std::pin::Pin; use tracing::{debug, error}; use self::presentation_model::{ PresRoles, QHash_i32_QByteArray, QVector_i32, }; #[derive(Default, Clone, Debug)] pub struct Presentation { id: i32, title: String, pub html: bool, pub path: String, pub page_count: i32, } #[derive(Debug)] pub struct PresentationModelRust { count: i32, highest_id: i32, presentations: Vec, inner_presentations: Vec, db: SqliteConnection, } impl Default for PresentationModelRust { fn default() -> Self { Self { count: 0, highest_id: 0, presentations: vec![], inner_presentations: vec![], db: { let rt = tokio::runtime::Runtime::new().unwrap(); let mut data = dirs::data_local_dir().unwrap(); data.push("lumina"); data.push("library-db.sqlite3"); let mut db_url = String::from("sqlite://"); db_url.push_str(data.to_str().unwrap()); rt.block_on(async { SqliteConnection::connect(&db_url) .await .expect("problems") }) }, } } } pub fn get_presentation( index: i32, ) -> color_eyre::Result { let rt = tokio::runtime::Runtime::new().unwrap(); let mut db = { let mut data = dirs::data_local_dir().unwrap(); data.push("lumina"); data.push("library-db.sqlite3"); let mut db_url = String::from("sqlite://"); db_url.push_str(data.to_str().unwrap()); rt.block_on(async { SqliteConnection::connect(&db_url) .await .expect("problems") }) }; rt.block_on(async { let result = query_as!(Presentation, r#"SELECT id as "id: i32", title as "title!", file_path as "path!", html as "html!", pageCount as "page_count!: i32" from presentations where id = ?"#, index).fetch_one(&mut db).await?; Ok(result) }) } impl presentation_model::PresentationModel { pub fn clear(mut self: Pin<&mut Self>) { unsafe { self.as_mut().begin_reset_model(); self.as_mut().rust_mut().presentations.clear(); self.as_mut().end_reset_model(); } } pub fn setup(mut self: Pin<&mut Self>) { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { let result = query_as!(Presentation, r#"SELECT id as "id: i32", title as "title!", file_path as "path!", html as "html!", pageCount as "page_count!: i32" from presentations"#).fetch_all(&mut self.as_mut().rust_mut().db).await; match result { Ok(p) => p.into_iter().for_each(|p| self.as_mut().add_presentation(p)), Err(e) => error!("There was an error in converting songs: {e}"), } }); } pub fn remove_item(mut self: Pin<&mut Self>, index: i32) -> bool { if index < 0 || (index as usize) >= self.presentations.len() { return false; } let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { let result = query!( "delete from presentations where id = ?", index ) .execute(&mut self.as_mut().rust_mut().db) .await; match result { Ok(_i) => { unsafe { self.as_mut().begin_remove_rows( &QModelIndex::default(), index, index, ); self.as_mut() .rust_mut() .presentations .remove(index as usize); self.as_mut() .rust_mut() .inner_presentations .remove(index as usize); self.as_mut().end_remove_rows(); } debug!("removed-item-at-index: {:?}", index); true } Err(e) => { error!("Cannot connect to database: {e}"); false } } }); true } pub fn new_item( mut self: Pin<&mut Self>, url: QUrl, new_page_count: i32, ) { println!("LETS INSERT THIS SUCKER!"); let file_path = PathBuf::from(url.path().to_string()); let name = file_path.file_stem().unwrap().to_str().unwrap(); let presentation_id = self.highest_id + 1; let presentation_title = QString::from(name); let presentation_path = url; let presentation_html = file_path.extension().unwrap() == "html"; debug!(html = presentation_html, ?file_path, extension = ?file_path.extension()); if self.as_mut().add_item( presentation_id, presentation_title, presentation_path, presentation_html, new_page_count, ) { debug!(filename = ?name, "Item inserted"); self.as_mut().rust_mut().highest_id = presentation_id; let cnt = self.as_mut().row_count(&QModelIndex::default()); self.as_mut().set_count(cnt); } else { debug!("Error in inserting item"); } } pub fn add_item( mut self: Pin<&mut Self>, presentation_id: i32, presentation_title: QString, presentation_path: QUrl, presentation_html: bool, new_page_count: i32, ) -> bool { let mut actual_page_count = new_page_count; if presentation_html { let actual_path = PathBuf::from(presentation_path.path().to_string()); actual_page_count = reveal_js::count_slides_and_fragments(&actual_path); } debug!( page_count = actual_page_count, html = presentation_html ); let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { let presentation_title = presentation_title.to_string(); let presentation_path = presentation_path.to_string(); let result = query!(r#"INSERT into presentations (id, title, file_path, html, pageCount) VALUES (?, ?, ?, ?, ?)"#, presentation_id, presentation_title, presentation_path, presentation_html, actual_page_count) .execute(&mut self.as_mut().rust_mut().db).await; match result { Ok(_i) => { let presentation = Presentation { id: presentation_id, title: presentation_title.to_string(), path: presentation_path.to_string(), html: presentation_html, page_count: actual_page_count, }; self.as_mut().add_presentation(presentation); debug!("{:?}", self.as_mut().presentations); true } Err(e) => { error!( "Cannot connect to database: {e}" ); false } } }); true } fn add_presentation( mut self: Pin<&mut Self>, presentation: self::Presentation, ) { let index = self.as_ref().presentations.len() as i32; println!("{:?}", presentation); unsafe { self.as_mut().begin_insert_rows( &QModelIndex::default(), index, index, ); self.as_mut() .rust_mut() .presentations .push(presentation.clone()); self.as_mut() .rust_mut() .inner_presentations .push(presentation); self.as_mut().end_insert_rows(); } } // fn insert_presentation( // mut self: Pin<&mut Self>, // presentation: Presentation, // index: i32, // ) { // unsafe { // self.as_mut().begin_insert_rows( // &QModelIndex::default(), // index, // index, // ); // self.as_mut() // .rust_mut() // .presentations // .insert(index as usize, presentation); // self.as_mut().end_insert_rows(); // } // let iter = self // .as_mut() // .presentations // .iter() // .enumerate() // .filter(|p| p.id > index); // for p in iter { // p.id = p.id + 1; // } // } pub fn get_item( self: Pin<&mut Self>, index: i32, ) -> QMap_QString_QVariant { println!("{index}"); let mut qvariantmap = QMap_QString_QVariant::default(); let idx = self.index(index, 0, &QModelIndex::default()); if !idx.is_valid() { return qvariantmap; } let role_names = self.as_ref().role_names(); let role_names_iter = role_names.iter(); if let Some(presentation) = self.presentations.get(index as usize) { for i in role_names_iter { qvariantmap.insert( QString::from(&i.1.to_string()), self.as_ref().data(&idx, *i.0), ); } debug!("{:?}", presentation); }; qvariantmap } pub fn duplicate_item( mut self: Pin<&mut Self>, index: i32, ) -> bool { let binding = self.as_mut(); let pres = binding.presentations.get(index as usize); if let Some(item) = pres { let item = item.clone(); binding.add_presentation(item); true } else { false } } pub fn update_title( mut self: Pin<&mut Self>, index: i32, updated_title: QString, ) -> bool { let mut vector_roles = QVector_i32::default(); vector_roles.append(self.as_ref().get_role(PresRoles::Title)); let model_index = &self.as_ref().index(index, 0, &QModelIndex::default()); let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { let title = updated_title.to_string(); let result = query!( "UPDATE presentations SET title = ? where id = ?", title, index ) .execute(&mut self.as_mut().rust_mut().db) .await; match result { Ok(_i) => { for presentation in self .as_mut() .rust_mut() .presentations .iter_mut() .filter(|x| x.id == index) { presentation.title = title.clone(); debug!( title = presentation.title, title = title, "updated presentation title" ); } self.as_mut().data_changed( model_index, model_index, &vector_roles, ); true } Err(e) => { error!("Error connecting to db: {e}"); false } } }); true } pub fn update_page_count( mut self: Pin<&mut Self>, index: i32, updated_page_count: i32, ) -> bool { let mut vector_roles = QVector_i32::default(); vector_roles .append(self.as_ref().get_role(PresRoles::PageCount)); let model_index = &self.as_ref().index(index, 0, &QModelIndex::default()); let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async { let result = query!( "UPDATE presentations SET pageCount = ? where id = ?", updated_page_count, index ) .execute(&mut self.as_mut().rust_mut().db) .await; match result { Ok(_i) => { for presentation in self .as_mut() .rust_mut() .presentations .iter_mut() .filter(|x| x.id == index) { presentation.page_count = updated_page_count.clone(); debug!( title = presentation.title, page_count = updated_page_count, "updated presentation page_count" ); } self.as_mut().data_changed( model_index, model_index, &vector_roles, ); true } Err(e) => { error!("Error connecting to db: {e}"); false } } }); true } fn search(mut self: Pin<&mut Self>, search_term: QString) { let search_term = search_term.to_string().to_lowercase(); debug!(search_term); let searched_presentations: Vec = self .inner_presentations .iter() .filter(|presentation| { presentation .title .to_lowercase() .contains(&search_term) }) .cloned() .collect(); debug!(search = ?&searched_presentations); unsafe { self.as_mut().begin_reset_model(); self.as_mut().rust_mut().presentations = searched_presentations; self.as_mut().end_reset_model(); } } fn get_role(&self, role: PresRoles) -> i32 { match role { PresRoles::Id => 0, PresRoles::Title => 1, PresRoles::Path => 2, PresRoles::Html => 3, PresRoles::PageCount => 4, _ => 0, } } } // QAbstractListModel implementation impl presentation_model::PresentationModel { fn data(&self, index: &QModelIndex, role: i32) -> QVariant { let role = PresRoles { repr: role }; if let Some(presentation) = self.presentations.get(index.row() as usize) { return match role { PresRoles::Id => QVariant::from(&presentation.id), PresRoles::Title => QVariant::from(&QString::from( &presentation.title, )), PresRoles::Path => { QVariant::from(&QString::from(&presentation.path)) } PresRoles::Html => QVariant::from(&presentation.html), PresRoles::PageCount => { QVariant::from(&presentation.page_count) } _ => QVariant::default(), }; } QVariant::default() } // Example of overriding a C++ virtual method and calling the base class implementation. // pub fn can_fetch_more(&self, parent: &QModelIndex) -> bool { // self.base_can_fetch_more(parent) // } pub fn role_names(&self) -> QHash_i32_QByteArray { let mut roles = QHash_i32_QByteArray::default(); roles.insert( PresRoles::Id.repr, cxx_qt_lib::QByteArray::from("id"), ); roles.insert( PresRoles::Title.repr, cxx_qt_lib::QByteArray::from("title"), ); roles.insert( PresRoles::Path.repr, cxx_qt_lib::QByteArray::from("filePath"), ); roles.insert( PresRoles::Html.repr, cxx_qt_lib::QByteArray::from("html"), ); roles.insert( PresRoles::PageCount.repr, cxx_qt_lib::QByteArray::from("pageCount"), ); roles } pub fn row_count(&self, _parent: &QModelIndex) -> i32 { // println!("row count is {cnt}"); self.presentations.len() as i32 } }