feat: implement win.save-file-as action

merge-requests/8/head
Nefo Fortressia 2021-10-31 16:58:20 +07:00
parent b54780e45e
commit 390485b3b1
7 changed files with 134 additions and 16 deletions

1
Cargo.lock generated
View File

@ -82,6 +82,7 @@ dependencies = [
"gio",
"glib",
"gtk4",
"once_cell",
"relative-path",
"serde",
"serde_json",

View File

@ -15,3 +15,4 @@ relative-path = "^1.5.0"
gdk = { version = "^0.3.0", package = "gdk4"}
sourceview = { package = "sourceview5", version = "^0.1.0", git = "https://github.com/EchidnaHQ/sourceview" }
webbrowser = { version = "^0.5.5" }
once_cell = "1"

View File

@ -2,9 +2,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use glib::{ParamFlags, ParamSpec, Value};
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::CompositeTemplate;
use once_cell::sync::Lazy;
use std::cell::RefCell;
#[derive(Default, CompositeTemplate)]
#[template(file = "./editor.ui")]
@ -13,6 +16,7 @@ pub struct EchidnaCoreEditor {
pub minimap: TemplateChild<sourceview::Map>,
#[template_child]
pub sourceview: TemplateChild<sourceview::View>,
pub file: RefCell<sourceview::File>,
}
#[glib::object_subclass]
@ -30,6 +34,39 @@ impl ObjectSubclass for EchidnaCoreEditor {
}
}
impl ObjectImpl for EchidnaCoreEditor {}
impl ObjectImpl for EchidnaCoreEditor {
fn properties() -> &'static [ParamSpec] {
static PROPERTIES: Lazy<Vec<ParamSpec>> = Lazy::new(|| {
vec![ParamSpec::new_object(
"file",
"file",
"the file of the editor",
sourceview::File::static_type(),
ParamFlags::READWRITE,
)]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _obj: &Self::Type, _id: usize, value: &Value, spec: &ParamSpec) {
match spec.name() {
"file" => {
let file: sourceview::File = value
.get()
.expect("The file needs to be a sourceview::File");
self.file.replace(file);
}
_ => unimplemented!(),
}
}
fn property(&self, _obj: &Self::Type, _id: usize, spec: &ParamSpec) -> Value {
match spec.name() {
"file" => self.file.borrow().to_value(),
_ => unimplemented!(),
}
}
}
impl WidgetImpl for EchidnaCoreEditor {}
impl BoxImpl for EchidnaCoreEditor {}

View File

@ -14,7 +14,7 @@ glib::wrapper! {
}
impl EchidnaCoreEditor {
pub fn new<P: glib::IsA<sourceview::File>>(file: Option<&P>) -> Self {
pub fn new(file: Option<sourceview::File>) -> Self {
let this: Self =
glib::Object::new(&[]).expect("Failed to create 'EchidnaCoreEditor' component.");
let this_imp = this.to_imp();
@ -23,10 +23,12 @@ impl EchidnaCoreEditor {
if file.is_some() {
let file = file.unwrap();
let file_location = file
.location()
.expect("file is required to have a location");
this.set_property("file", &file).expect("Could not set the 'file' property of EchidnaCoreEditor");
let cancellable = gio::Cancellable::new();
let filepath = file_location.path().expect("No filepath");
let file_info_result =
@ -48,7 +50,7 @@ impl EchidnaCoreEditor {
None => {}
}
let file_loader: FileLoader = FileLoader::new(&buffer, file);
let file_loader: FileLoader = FileLoader::new(&buffer, &file);
file_loader.load_async(
glib::Priority::default(),
@ -75,4 +77,8 @@ impl EchidnaCoreEditor {
pub fn to_imp(&self) -> &imp::EchidnaCoreEditor {
imp::EchidnaCoreEditor::from_instance(self)
}
pub fn file(&self) -> sourceview::File {
self.property("file").expect("Could not get property 'file' of EchidnaCoreEditor").get::<sourceview::File>().expect("Could not get property 'file' of EchidnaCoreEditor because its type is not IsA<sourceview::File>")
}
}

View File

@ -3,16 +3,17 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::components::editor::EchidnaCoreEditor;
// use gio::Cancellable;
use glib::clone;
use gio::Cancellable;
use glib::{clone, Priority};
use gtk::{
prelude::*, subclass::prelude::*, FileChooserAction, FileChooserDialog, Label, ResponseType,
};
use sourceview::File;
use sourceview::{prelude::*, Buffer, File, FileSaver};
pub trait FileImplementedEditor {
fn action_open_file(&self);
fn open_file(notebook: &gtk::Notebook, file: gio::File);
fn action_save_file_as(&self);
}
impl FileImplementedEditor for super::EchidnaWindow {
@ -48,19 +49,19 @@ impl FileImplementedEditor for super::EchidnaWindow {
// TODO: Somehow inserts self to this function.
// This function sets the callback function as 'static, which for some reasons ban cloning self into it. Idk why.
dialog.connect_response(clone!( @weak self as window, =>
move |dialog, response| {
if response == ResponseType::Accept {
let file = dialog.file().expect("");
Self::open_file(&super::imp::EchidnaWindow::from_instance(&window).notebook, file);
move |dialog, response| {
if response == ResponseType::Accept {
let file = dialog.file().expect("");
Self::open_file(&super::imp::EchidnaWindow::from_instance(&window).notebook, file);
} else if response == ResponseType::Cancel {
dialog.destroy();
} }));
}
dialog.destroy();
}));
}
fn open_file(notebook: &gtk::Notebook, file_location: gio::File) {
let file = File::builder().location(&file_location).build();
let editor_page = EchidnaCoreEditor::new(Some(&file));
let editor_page = EchidnaCoreEditor::new(Some(file));
notebook.prepend_page(
&editor_page,
Some(&Label::new(Some(
@ -74,4 +75,63 @@ impl FileImplementedEditor for super::EchidnaWindow {
))),
);
}
fn action_save_file_as(&self) {
let dialog = FileChooserDialog::new(
Some("Save File As"),
Some(self),
FileChooserAction::Save,
&[
("Cancel", ResponseType::Cancel),
("Save", ResponseType::Accept),
],
);
dialog.set_current_name("untitled");
dialog.show();
dialog.connect_response(clone!( @weak self as window, =>
move |dialog, response| {
if response == ResponseType::Accept {
let file = dialog.file().expect("");
let window_imp = window.to_imp();
let page: EchidnaCoreEditor;
match window_imp.notebook
.nth_page(
Some(window_imp.notebook
.current_page()
.expect(
"No tabs is the current tab, probably all tabs closed. No files to save"
)
)
).expect(
"Couldn't get the page of the current index. Try again."
).downcast::<EchidnaCoreEditor>() {
Ok(res) => page = res,
Err(e) => panic!(format!("We got an error when trying to downcast the current tab page into EchidnaCoreEditor:\n{}", e))
}
let buffer: Buffer = page.to_imp().sourceview.buffer().downcast().expect("Could not downcast the editor's buffer to GtkSourceBuffer.");
let cancellable = Cancellable::new();
let file_saver = FileSaver::with_target(
&buffer,
&page.file(), &file);
file_saver.save_async(
Priority::default(),
Some(&cancellable),
|_, _| {},
|result| {
if result.is_err() {
panic!(format!("Found an error while saving the file:\n{}", result.err().expect("No error")))
}
});
}
dialog.destroy();
}));
}
}

View File

@ -51,6 +51,10 @@
<attribute name="label">Save</attribute>
<attribute name="action">win.save</attribute>
</item>
<item>
<attribute name="label">Save As...</attribute>
<attribute name="action">win.save-file-as</attribute>
</item>
<item>
<attribute name="label">Save All</attribute>
<attribute name="action">win.save-all</attribute>

View File

@ -24,7 +24,6 @@ impl MenubarImplementedEditor for EchidnaWindow {
.expect("Could not get object 'menu' from builder.");
app.set_menubar(Some(&menubar));
self.set_show_menubar(true);
{
let act_exit: SimpleAction = SimpleAction::new("exit", None);
app.add_action(&act_exit);
@ -111,5 +110,15 @@ impl MenubarImplementedEditor for EchidnaWindow {
window.action_open_file();
}));
}
{
let action_save_file_as = SimpleAction::new("save-file-as", None);
self.add_action(&action_save_file_as);
action_save_file_as.connect_activate(clone!(@weak self as window =>
move |_action, _variant| {
window.action_save_file_as();
}));
}
}
}