diff --git a/Cargo.lock b/Cargo.lock index 501ac22..f199a88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,6 +72,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "bichannel" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4bdf5473d36d166934ddefbedab84ad123ba887d2dd16eb2e1a036c484c213b" + [[package]] name = "bit_field" version = "0.10.1" @@ -879,6 +885,7 @@ dependencies = [ name = "post_maker" version = "0.4.0-alpha.5" dependencies = [ + "bichannel", "clap", "dirs", "fltk", diff --git a/Cargo.toml b/Cargo.toml index 4acb9e4..29faf5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,4 @@ infer = "0.7.0" textwrap = "0.14" webbrowser = "0.5" mozjpeg = "0.9.2" +bichannel = "0.0.4" diff --git a/src/config_picker.rs b/src/config_picker.rs index 004acca..8283395 100644 --- a/src/config_picker.rs +++ b/src/config_picker.rs @@ -58,7 +58,7 @@ impl ConfigPicker { let top_padding_btn = Frame::default(); let mut panel_flex = Flex::default().row(); Frame::default(); - let apply_btn = Button::default().with_label("apply"); + let apply_btn = Button::default().with_label("Apply"); Frame::default(); panel_flex.set_size(&apply_btn, 100); panel_flex.end(); diff --git a/src/export_all_window.rs b/src/export_all_window.rs new file mode 100644 index 0000000..ee38309 --- /dev/null +++ b/src/export_all_window.rs @@ -0,0 +1,223 @@ +/* + This file is part of Post Maker. + Post Maker is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Post Maker is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Post Maker. If not, see +*/ + +//! Picker to pick config if multiple configs are present or defalut config is not present +use crate::{ + dialog, globals, + result_ext::ResultExt, + utils::{self, ImageContainer, ImageInfo, ImageProperties, ImagePropertiesFile}, +}; +use bichannel::Channel; +use fltk::{ + app::{self}, + button::Button, + enums, + frame::Frame, + group::Flex, + image::SvgImage, + misc::Progress, + prelude::*, + window::Window, +}; +use std::{ + fs::File, + sync::{Arc, RwLock}, + thread, +}; + +pub(crate) struct ExportAllWindow { + pub(crate) win: Window, + pub(crate) progress: Progress, + pub(crate) image_name: Frame, + pub(crate) close_btn: Button, + pub(crate) images_list: Arc>>, + pub(crate) channel: Arc>>>, +} + +impl ExportAllWindow { + pub(crate) fn new(images_list: Arc>>) -> Self { + let mut win = Window::new(0, 0, 500, 130, "Export All").center_screen(); + win.set_icon(Some( + SvgImage::from_data(globals::ICON.to_str().unwrap()).unwrap(), + )); + + let mut main_flex = Flex::default().size_of_parent().column(); + + //label + let mut panel_flex = Flex::default().row(); + panel_flex.set_size(&Frame::default(), 1); + Frame::default() + .with_label("Exporting all with quotes") + .with_align(enums::Align::Left | enums::Align::Inside); + panel_flex.end(); + main_flex.set_size(&panel_flex, 25); + + //image name + let mut panel_flex = Flex::default().row(); + panel_flex.set_size(&Frame::default(), 1); + let image_name = Frame::default() + .with_label("") + .with_align(enums::Align::Left | enums::Align::Inside); + panel_flex.end(); + main_flex.set_size(&panel_flex, 25); + + // progress bar + let mut panel_flex = Flex::default().row(); + Frame::default(); + let mut progress = Progress::default().with_label("Exporting..."); + progress.set_maximum(1.0); + progress.set_value(0.0); + Frame::default(); + panel_flex.set_size(&progress, 490); + panel_flex.end(); + main_flex.set_size(&panel_flex, 30); + + //close button + let mut panel_flex = Flex::default().row(); + Frame::default(); + let close_btn = Button::default().with_label("Cancel"); + panel_flex.set_size(&Frame::default(), 1); + panel_flex.set_size(&close_btn, 100); + panel_flex.end(); + main_flex.set_size(&panel_flex, 30); + + main_flex.end(); + + win.end(); + win.make_resizable(true); + + let mut config_picker = Self { + win, + progress, + image_name, + close_btn, + images_list, + channel: Arc::new(RwLock::new(None)), + }; + config_picker.event(); + + config_picker + } + + pub(crate) fn export(&mut self) { + self.image_name.set_label(""); + self.progress.set_label("Exporting..."); + self.progress.set_maximum(1.0); + self.progress.set_value(0.0); + self.win.show(); + let (left, right) = bichannel::channel(); + *rw_write!(self.channel) = Some(left); + spawn_export_thread(self, right); + while self.win.shown() { + if let Some(channel) = &*rw_read!(self.channel) { + if let Ok(msg) = channel.try_recv() { + match msg { + ThreadMessage::HideWindow => self.win.hide(), + _ => (), + } + } + } + app::wait(); + } + } + + // Set callbacks of elements + fn event(&mut self) { + let channel = Arc::clone(&self.channel); + // Close Button + self.close_btn.set_callback(move |_| { + if dialog::choice_default("Are you sure?", "Yes", "No") == 0 { + if let Some(c) = &*rw_read!(channel) { + c.send(ThreadMessage::Stop).error_log("Failed to stop task"); + } + } + }); + + let channel = Arc::clone(&self.channel); + // Window Close + self.win.set_callback(move |_| { + if dialog::choice_default("Are you sure?", "Yes", "No") == 0 { + if let Some(c) = &*rw_read!(channel) { + c.send(ThreadMessage::Stop).error_log("Failed to stop task"); + } + } + }); + } +} + +pub(crate) enum ThreadMessage { + Stop, + HideWindow, +} + +fn spawn_export_thread( + export_all: &mut ExportAllWindow, + channel: Channel, +) { + let mut win = export_all.win.clone(); + let mut progress = export_all.progress.clone(); + let mut image_name = export_all.image_name.clone(); + let images_list = Arc::clone(&export_all.images_list); + + thread::spawn(move || { + let total = rw_read!(images_list).len(); + progress.set_maximum(total as f64); + progress.set_value(0.0); + for (idx, image) in (*rw_read!(images_list)).iter().enumerate() { + image_name.set_label( + image + .path + .file_name() + .unwrap_or_default() + .to_str() + .unwrap_or_default(), + ); + let properties = Arc::new(RwLock::new(ImageProperties::default())); + let container = ImageContainer::new(image, properties); + let properties_file = utils::get_properties_path(image); + let read = match File::open(&properties_file) { + Ok(r) => r, + Err(_) => continue, + }; + let read = match serde_json::from_reader::(read) { + Ok(r) => r, + Err(_) => continue, + }; + + rw_write!(container.properties).merge(read, "", ""); + + if rw_read!(container.properties).quote.trim().len() == 0 { + continue; + } + + container.save(); + + progress.set_value(idx as f64 + 1.0); + progress.set_label(&format!("[{}/{}]", idx + 1, total)); + win.redraw(); + app::awake(); + + if let Ok(msg) = channel.try_recv() { + match msg { + ThreadMessage::Stop => break, + _ => (), + } + } + } + image_name.set_label("Done"); + channel + .send(ThreadMessage::HideWindow) + .error_log("Failed to close window"); + }); +} diff --git a/src/main.rs b/src/main.rs index 8f21f61..2389b6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ mod config_window; mod crop_window; mod dialog; mod draw_thread; +mod export_all_window; mod globals; mod main_window; mod result_ext; diff --git a/src/main_window.rs b/src/main_window.rs index 583eb6d..551d94f 100644 --- a/src/main_window.rs +++ b/src/main_window.rs @@ -19,6 +19,7 @@ use crate::{ crop_window::CropWindow, dialog, draw_thread::*, + export_all_window::ExportAllWindow, globals, result_ext::ResultExt, utils::{self, ImageInfo, ImageProperties, ImageType}, @@ -422,13 +423,6 @@ impl MainWindow { if !path.exists() { return; } - let expost_dir = path.join("export"); - if !expost_dir.exists() { - if let Err(e) = fs::create_dir(expost_dir) { - Result::<(), _>::Err(e).warn_log("Failed to create export folder!"); - return; - } - } win.set_label(&format!( "{} - Post Maker", path.file_name() @@ -462,6 +456,46 @@ impl MainWindow { }, ); + let mut export_all = ExportAllWindow::new(Arc::clone(&self.images_list)); + let mut win = self.win.clone(); + self.menubar.add( + "&Actions/Export All with Quotes...\t", + Shortcut::None, + menu::MenuFlag::Normal, + move |_| { + win.deactivate(); + + export_all.export(); + win.activate(); + win.redraw(); + fltk::app::awake(); + }, + ); + + let properties = Arc::clone(&self.properties); + let mut win = self.win.clone(); + self.menubar.add( + "&Actions/Delete Exports...\t", + Shortcut::None, + menu::MenuFlag::Normal, + move |_| { + win.deactivate(); + if dialog::choice_default("Do you want to remove exports?", "Yes", "No") == 0 { + let props = rw_read!(properties); + if let Some(prop) = &props.image_info { + let export = prop.path.parent().unwrap().join("export"); + if export.exists() { + fs::remove_dir_all(&export) + .warn_log("Failed to remove export directory"); + } + } + } + win.activate(); + win.redraw(); + fltk::app::awake(); + }, + ); + let mut config_window = ConfigWindow::new(); let sender = self.sender.clone(); let mut image = self.page.image.clone(); diff --git a/src/utils.rs b/src/utils.rs index df476da..149d66a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -756,13 +756,12 @@ pub(crate) fn get_export_image_path(image_info: &ImageInfo) -> PathBuf { export_format.as_extension() ); - let export = image_info - .path - .parent() - .unwrap() - .join("export") - .join(&image_name); + let expost_dir = image_info.path.parent().unwrap().join("export"); + if !expost_dir.exists() { + fs::create_dir(&expost_dir).expect_log("Failed to create export folder!"); + } + let export = expost_dir.join(&image_name); export }