feat: implement win.save-file-as action
parent
b54780e45e
commit
390485b3b1
|
@ -82,6 +82,7 @@ dependencies = [
|
||||||
"gio",
|
"gio",
|
||||||
"glib",
|
"glib",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
|
"once_cell",
|
||||||
"relative-path",
|
"relative-path",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -15,3 +15,4 @@ relative-path = "^1.5.0"
|
||||||
gdk = { version = "^0.3.0", package = "gdk4"}
|
gdk = { version = "^0.3.0", package = "gdk4"}
|
||||||
sourceview = { package = "sourceview5", version = "^0.1.0", git = "https://github.com/EchidnaHQ/sourceview" }
|
sourceview = { package = "sourceview5", version = "^0.1.0", git = "https://github.com/EchidnaHQ/sourceview" }
|
||||||
webbrowser = { version = "^0.5.5" }
|
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
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use glib::{ParamFlags, ParamSpec, Value};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::subclass::prelude::*;
|
use gtk::subclass::prelude::*;
|
||||||
use gtk::CompositeTemplate;
|
use gtk::CompositeTemplate;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
#[derive(Default, CompositeTemplate)]
|
#[derive(Default, CompositeTemplate)]
|
||||||
#[template(file = "./editor.ui")]
|
#[template(file = "./editor.ui")]
|
||||||
|
@ -13,6 +16,7 @@ pub struct EchidnaCoreEditor {
|
||||||
pub minimap: TemplateChild<sourceview::Map>,
|
pub minimap: TemplateChild<sourceview::Map>,
|
||||||
#[template_child]
|
#[template_child]
|
||||||
pub sourceview: TemplateChild<sourceview::View>,
|
pub sourceview: TemplateChild<sourceview::View>,
|
||||||
|
pub file: RefCell<sourceview::File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[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 WidgetImpl for EchidnaCoreEditor {}
|
||||||
impl BoxImpl for EchidnaCoreEditor {}
|
impl BoxImpl for EchidnaCoreEditor {}
|
||||||
|
|
|
@ -14,7 +14,7 @@ glib::wrapper! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EchidnaCoreEditor {
|
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 =
|
let this: Self =
|
||||||
glib::Object::new(&[]).expect("Failed to create 'EchidnaCoreEditor' component.");
|
glib::Object::new(&[]).expect("Failed to create 'EchidnaCoreEditor' component.");
|
||||||
let this_imp = this.to_imp();
|
let this_imp = this.to_imp();
|
||||||
|
@ -23,10 +23,12 @@ impl EchidnaCoreEditor {
|
||||||
|
|
||||||
if file.is_some() {
|
if file.is_some() {
|
||||||
let file = file.unwrap();
|
let file = file.unwrap();
|
||||||
|
|
||||||
let file_location = file
|
let file_location = file
|
||||||
.location()
|
.location()
|
||||||
.expect("file is required to have a 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 cancellable = gio::Cancellable::new();
|
||||||
let filepath = file_location.path().expect("No filepath");
|
let filepath = file_location.path().expect("No filepath");
|
||||||
let file_info_result =
|
let file_info_result =
|
||||||
|
@ -48,7 +50,7 @@ impl EchidnaCoreEditor {
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_loader: FileLoader = FileLoader::new(&buffer, file);
|
let file_loader: FileLoader = FileLoader::new(&buffer, &file);
|
||||||
|
|
||||||
file_loader.load_async(
|
file_loader.load_async(
|
||||||
glib::Priority::default(),
|
glib::Priority::default(),
|
||||||
|
@ -75,4 +77,8 @@ impl EchidnaCoreEditor {
|
||||||
pub fn to_imp(&self) -> &imp::EchidnaCoreEditor {
|
pub fn to_imp(&self) -> &imp::EchidnaCoreEditor {
|
||||||
imp::EchidnaCoreEditor::from_instance(self)
|
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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use crate::components::editor::EchidnaCoreEditor;
|
use crate::components::editor::EchidnaCoreEditor;
|
||||||
// use gio::Cancellable;
|
use gio::Cancellable;
|
||||||
use glib::clone;
|
use glib::{clone, Priority};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
prelude::*, subclass::prelude::*, FileChooserAction, FileChooserDialog, Label, ResponseType,
|
prelude::*, subclass::prelude::*, FileChooserAction, FileChooserDialog, Label, ResponseType,
|
||||||
};
|
};
|
||||||
use sourceview::File;
|
use sourceview::{prelude::*, Buffer, File, FileSaver};
|
||||||
|
|
||||||
pub trait FileImplementedEditor {
|
pub trait FileImplementedEditor {
|
||||||
fn action_open_file(&self);
|
fn action_open_file(&self);
|
||||||
fn open_file(notebook: >k::Notebook, file: gio::File);
|
fn open_file(notebook: >k::Notebook, file: gio::File);
|
||||||
|
fn action_save_file_as(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileImplementedEditor for super::EchidnaWindow {
|
impl FileImplementedEditor for super::EchidnaWindow {
|
||||||
|
@ -48,19 +49,19 @@ impl FileImplementedEditor for super::EchidnaWindow {
|
||||||
// TODO: Somehow inserts self to this function.
|
// 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.
|
// 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, =>
|
dialog.connect_response(clone!( @weak self as window, =>
|
||||||
move |dialog, response| {
|
move |dialog, response| {
|
||||||
if response == ResponseType::Accept {
|
if response == ResponseType::Accept {
|
||||||
let file = dialog.file().expect("");
|
let file = dialog.file().expect("");
|
||||||
Self::open_file(&super::imp::EchidnaWindow::from_instance(&window).notebook, file);
|
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) {
|
fn open_file(notebook: >k::Notebook, file_location: gio::File) {
|
||||||
let file = File::builder().location(&file_location).build();
|
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(
|
notebook.prepend_page(
|
||||||
&editor_page,
|
&editor_page,
|
||||||
Some(&Label::new(Some(
|
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="label">Save</attribute>
|
||||||
<attribute name="action">win.save</attribute>
|
<attribute name="action">win.save</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label">Save As...</attribute>
|
||||||
|
<attribute name="action">win.save-file-as</attribute>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label">Save All</attribute>
|
<attribute name="label">Save All</attribute>
|
||||||
<attribute name="action">win.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.");
|
.expect("Could not get object 'menu' from builder.");
|
||||||
app.set_menubar(Some(&menubar));
|
app.set_menubar(Some(&menubar));
|
||||||
self.set_show_menubar(true);
|
self.set_show_menubar(true);
|
||||||
|
|
||||||
{
|
{
|
||||||
let act_exit: SimpleAction = SimpleAction::new("exit", None);
|
let act_exit: SimpleAction = SimpleAction::new("exit", None);
|
||||||
app.add_action(&act_exit);
|
app.add_action(&act_exit);
|
||||||
|
@ -111,5 +110,15 @@ impl MenubarImplementedEditor for EchidnaWindow {
|
||||||
window.action_open_file();
|
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