feat: implement win.save-file-as action
parent
b54780e45e
commit
390485b3b1
|
@ -82,6 +82,7 @@ dependencies = [
|
|||
"gio",
|
||||
"glib",
|
||||
"gtk4",
|
||||
"once_cell",
|
||||
"relative-path",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
@ -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"
|
|
@ -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 {}
|
||||
|
|
|
@ -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>")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: >k::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: >k::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();
|
||||
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue