diff --git a/build.rs b/build.rs index f04d8b3..4d2ad88 100644 --- a/build.rs +++ b/build.rs @@ -9,5 +9,6 @@ fn main() { .file("src/rust/slide_model.rs") .file("src/rust/image_model.rs") .file("src/rust/video_model.rs") + .file("src/rust/presentation_model.rs") .build(); } diff --git a/src/rust/lib.rs b/src/rust/lib.rs index f094a18..69b30e0 100644 --- a/src/rust/lib.rs +++ b/src/rust/lib.rs @@ -17,6 +17,7 @@ mod file_helper; pub mod image_model; pub mod models; +pub mod presentation_model; pub mod schema; mod service_thing; mod settings; diff --git a/src/rust/models.rs b/src/rust/models.rs index fdb7d00..20adc2f 100644 --- a/src/rust/models.rs +++ b/src/rust/models.rs @@ -16,3 +16,11 @@ pub struct Video { pub end_time: Option, pub looping: bool, } + +#[derive(Queryable)] +pub struct Presentation { + pub id: i32, + pub title: String, + pub path: String, + pub page_count: Option, +} diff --git a/src/rust/presentation_model.rs b/src/rust/presentation_model.rs new file mode 100644 index 0000000..c5fbd09 --- /dev/null +++ b/src/rust/presentation_model.rs @@ -0,0 +1,323 @@ +#[cxx_qt::bridge] +mod presentation_model { + use crate::models::*; + use crate::presentation_model::presentation_model::Presentation; + use crate::schema::presentations::dsl::*; + use diesel::sqlite::SqliteConnection; + use diesel::{delete, insert_into, prelude::*}; + use std::path::{Path, PathBuf}; + + 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; + } + + #[derive(Default, Clone, Debug)] + pub struct Presentation { + id: i32, + title: QString, + path: QString, + } + + #[cxx_qt::qobject(base = "QAbstractListModel")] + #[derive(Default, Debug)] + pub struct PresentationModel { + highest_id: i32, + presentations: Vec, + } + + #[cxx_qt::qsignals(PresentationModel)] + pub enum Signals<'a> { + #[inherit] + DataChanged { + top_left: &'a QModelIndex, + bottom_right: &'a QModelIndex, + roles: &'a QVector_i32, + }, + } + + enum Role { + IdRole, + PathRole, + TitleRole, + } + + // use crate::entities::{presentations, prelude::Presentations}; + // use sea_orm::{ConnectionTrait, Database, DbBackend, DbErr, Statement, ActiveValue}; + impl qobject::PresentationModel { + #[qinvokable] + pub fn clear(mut self: Pin<&mut Self>) { + unsafe { + self.as_mut().begin_reset_model(); + self.as_mut().presentations_mut().clear(); + self.as_mut().end_reset_model(); + } + } + + #[qinvokable] + pub fn test_database(mut self: Pin<&mut Self>) { + let db = &mut self.as_mut().get_db(); + let results = presentations + .load::(db) + .expect("Error loading presentations"); + self.as_mut().set_highest_id(0); + + println!("SHOWING PRESENTATIONS"); + println!("--------------"); + for presentation in results { + println!("{}", presentation.title); + println!("{}", presentation.id); + println!("{}", presentation.path); + println!("--------------"); + if self.as_mut().highest_id() < &presentation.id { + self.as_mut().set_highest_id(presentation.id); + } + + let img = self::Presentation { + id: presentation.id, + title: QString::from(&presentation.title), + path: QString::from(&presentation.path), + }; + + self.as_mut().add_presentation(img); + } + println!("--------------------------------------"); + println!("{:?}", self.as_mut().presentations()); + println!("--------------------------------------"); + } + + #[qinvokable] + pub fn remove_item(mut self: Pin<&mut Self>, index: i32) -> bool { + if index < 0 || (index as usize) >= self.presentations().len() { + return false; + } + let db = &mut self.as_mut().get_db(); + + let presentation_id = self.presentations().get(index as usize).unwrap().id; + + let result = delete(presentations.filter(id.eq(presentation_id))).execute(db); + + match result { + Ok(_i) => { + unsafe { + self.as_mut() + .begin_remove_rows(&QModelIndex::default(), index, index); + self.as_mut().presentations_mut().remove(index as usize); + self.as_mut().end_remove_rows(); + } + println!("removed-item-at-index: {:?}", presentation_id); + println!("new-Vec: {:?}", self.as_mut().presentations()); + true + } + Err(_e) => { + println!("Cannot connect to database"); + false + } + } + } + + fn get_db(self: Pin<&mut Self>) -> SqliteConnection { + const DATABASE_URL: &str = "sqlite:///home/chris/.local/share/librepresenter/Libre Presenter/library-db.sqlite3"; + + SqliteConnection::establish(DATABASE_URL) + .unwrap_or_else(|_| panic!("error connecting to {}", DATABASE_URL)) + // self.rust().db = db; + } + + #[qinvokable] + pub fn new_item(mut self: Pin<&mut Self>, url: QUrl) { + 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.rust().highest_id + 1; + let presentation_title = QString::from(name); + let presentation_path = url.to_qstring(); + + if self + .as_mut() + .add_item(presentation_id, presentation_title, presentation_path) + { + println!("filename: {:?}", name); + self.as_mut().set_highest_id(presentation_id); + } else { + println!("Error in inserting item"); + } + } + + #[qinvokable] + pub fn add_item( + mut self: Pin<&mut Self>, + presentation_id: i32, + presentation_title: QString, + presentation_path: QString, + ) -> bool { + let db = &mut self.as_mut().get_db(); + // println!("{:?}", db); + let presentation = self::Presentation { + id: presentation_id, + title: presentation_title.clone(), + path: presentation_path.clone(), + }; + println!("{:?}", presentation); + + let result = insert_into(presentations) + .values(( + id.eq(&presentation_id), + title.eq(&presentation_title.to_string()), + path.eq(&presentation_path.to_string()), + )) + .execute(db); + println!("{:?}", result); + + match result { + Ok(_i) => { + self.as_mut().add_presentation(presentation); + println!("{:?}", self.as_mut().presentations()); + true + } + Err(_e) => { + println!("Cannot connect to database"); + false + } + } + } + + 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().presentations_mut().push(presentation); + self.as_mut().end_insert_rows(); + } + } + + #[qinvokable] + 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.rust().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), + ); + } + }; + qvariantmap + } + + fn get_role(&self, role: Role) -> i32 { + match role { + Role::IdRole => 0, + Role::TitleRole => 1, + Role::PathRole => 2, + _ => 0, + } + } + } + + // Create Rust bindings for C++ functions of the base class (QAbstractItemModel) + #[cxx_qt::inherit] + extern "C++" { + unsafe fn begin_insert_rows( + self: Pin<&mut qobject::PresentationModel>, + parent: &QModelIndex, + first: i32, + last: i32, + ); + unsafe fn end_insert_rows(self: Pin<&mut qobject::PresentationModel>); + + unsafe fn begin_remove_rows( + self: Pin<&mut qobject::PresentationModel>, + parent: &QModelIndex, + first: i32, + last: i32, + ); + unsafe fn end_remove_rows(self: Pin<&mut qobject::PresentationModel>); + + unsafe fn begin_reset_model(self: Pin<&mut qobject::PresentationModel>); + unsafe fn end_reset_model(self: Pin<&mut qobject::PresentationModel>); + } + + #[cxx_qt::inherit] + unsafe extern "C++" { + #[cxx_name = "canFetchMore"] + fn base_can_fetch_more(self: &qobject::PresentationModel, parent: &QModelIndex) -> bool; + + fn index( + self: &qobject::PresentationModel, + row: i32, + column: i32, + parent: &QModelIndex, + ) -> QModelIndex; + } + + // QAbstractListModel implementation + impl qobject::PresentationModel { + #[qinvokable(cxx_override)] + fn data(&self, index: &QModelIndex, role: i32) -> QVariant { + if let Some(presentation) = self.presentations().get(index.row() as usize) { + return match role { + 0 => QVariant::from(&presentation.id), + 1 => QVariant::from(&presentation.title), + 2 => QVariant::from(&presentation.path), + _ => QVariant::default(), + }; + } + + QVariant::default() + } + + // Example of overriding a C++ virtual method and calling the base class implementation. + #[qinvokable(cxx_override)] + pub fn can_fetch_more(&self, parent: &QModelIndex) -> bool { + self.base_can_fetch_more(parent) + } + + #[qinvokable(cxx_override)] + pub fn role_names(&self) -> QHash_i32_QByteArray { + let mut roles = QHash_i32_QByteArray::default(); + roles.insert(0, cxx_qt_lib::QByteArray::from("id")); + roles.insert(1, cxx_qt_lib::QByteArray::from("title")); + roles.insert(2, cxx_qt_lib::QByteArray::from("filePath")); + roles + } + + #[qinvokable(cxx_override)] + pub fn row_count(&self, _parent: &QModelIndex) -> i32 { + let cnt = self.rust().presentations.len() as i32; + // println!("row count is {cnt}"); + cnt + } + + #[qinvokable] + pub fn count(&self) -> i32 { + self.rust().presentations.len() as i32 + } + } +} diff --git a/src/rust/schema.rs b/src/rust/schema.rs index 4aaa819..20f8b25 100644 --- a/src/rust/schema.rs +++ b/src/rust/schema.rs @@ -13,8 +13,10 @@ diesel::table! { presentations (id) { id -> Integer, title -> Text, - filePath -> Text, - pageCount -> Nullable, + #[sql_name = "filePath"] + path -> Text, + #[sql_name = "pageCount"] + page_count -> Nullable, } }