Merge branch 'main' into feat/new-file

feat/new-file
Nefo Fortressia 2021-12-28 08:55:01 +07:00
commit 319bc3914b
Signed by: fortressia
GPG Key ID: 6D7972CC76174995
16 changed files with 368 additions and 337 deletions

118
Cargo.lock generated
View File

@ -20,12 +20,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
[[package]] [[package]]
name = "cairo-rs" name = "cairo-rs"
version = "0.14.7" version = "0.14.7"
@ -68,12 +62,6 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "echidna" name = "echidna"
version = "0.1.0" version = "0.1.0"
@ -87,7 +75,6 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sourceview5", "sourceview5",
"webbrowser",
] ]
[[package]] [[package]]
@ -438,36 +425,12 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "js-sys"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.102" version = "0.2.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "memoffset" name = "memoffset"
version = "0.6.4" version = "0.6.4"
@ -843,87 +806,6 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "wasm-bindgen"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
[[package]]
name = "web-sys"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webbrowser"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecad156490d6b620308ed411cfee90d280b3cbd13e189ea0d3fada8acc89158a"
dependencies = [
"web-sys",
"widestring",
"winapi",
]
[[package]]
name = "widestring"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "echidna" name = "echidna"
version = "0.1.0" version = "0.1.0"
edition = "2018" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -14,5 +14,4 @@ serde = { version = "^1.0.130", features = ["derive"] }
relative-path = "^1.5.0" 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" }
once_cell = "1" once_cell = "1"

View File

@ -1 +0,0 @@
"clippy::style" = "deny"

View File

@ -15,10 +15,11 @@
<object class="GtkSourceView" id="sourceview"> <object class="GtkSourceView" id="sourceview">
<property name="vexpand">1</property> <property name="vexpand">1</property>
<property name="hexpand">1</property> <property name="hexpand">1</property>
<property name="show-line-numbers">1</property>
<property name="show-line-marks">1</property>
<property name="buffer"> <property name="buffer">
<object class="GtkSourceBuffer"></object> <object class="GtkSourceBuffer"></object>
</property> </property>
</object> </object>
</child> </child>
</object> </object>

View File

@ -3,9 +3,12 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
pub mod imp; pub mod imp;
use gio::Cancellable;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
use sourceview::{prelude::*, Buffer, FileExt as SourceFileExt, FileLoader, LanguageManager}; use sourceview::{
prelude::*, Buffer, FileExt as SourceFileExt, FileLoader, FileSaver, LanguageManager,
};
glib::wrapper! { glib::wrapper! {
pub struct EchidnaCoreEditor(ObjectSubclass<imp::EchidnaCoreEditor>) pub struct EchidnaCoreEditor(ObjectSubclass<imp::EchidnaCoreEditor>)
@ -21,57 +24,59 @@ impl EchidnaCoreEditor {
// Without cloning it, for some reasons the Rust compiler complains about &this.to_imp().sourceview not being IsA<sourceview::View> // Without cloning it, for some reasons the Rust compiler complains about &this.to_imp().sourceview not being IsA<sourceview::View>
this_imp.minimap.set_view(&this_imp.sourceview.clone()); this_imp.minimap.set_view(&this_imp.sourceview.clone());
if file.is_some() { match file {
let file = file.unwrap(); Some(file) => {
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) this.set_property("file", &file)
.expect("Could not set the 'file' property of EchidnaCoreEditor"); .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 info = file_location let info = file_location
.query_info("*", gio::FileQueryInfoFlags::NONE, Some(&cancellable)) .query_info("*", gio::FileQueryInfoFlags::NONE, Some(&cancellable))
.expect("Could not query the info for file"); .expect("Could not query the info for file");
let content_type = info let content_type = info
.content_type() .content_type()
.expect(format!("It does not seem like {:?} has a type", filepath).as_str()); .expect(format!("It does not seem like {:?} has a type", filepath).as_str());
{ {
println!( println!(
"Opened {} and found its content type is {}.", "Opened {} and found its content type is {}.",
"file", "file",
content_type.to_string() content_type.to_string()
); );
let buffer = this_imp.sourceview.buffer().downcast::<Buffer>().expect("Cannot downcast the sourceview's buffer. Maybe the sourceview's buffer is not IsA<sourceview::Buffer>."); let buffer = this_imp.sourceview.buffer().downcast::<Buffer>().expect("Cannot downcast the sourceview's buffer. Maybe the sourceview's buffer is not IsA<sourceview::Buffer>.");
let language_manager = LanguageManager::new(); let language_manager = LanguageManager::new();
let language = language_manager.guess_language( let language = language_manager.guess_language(
Some(&info.name().to_str().expect( Some(&info.name().to_str().expect(
"Could not open the file because its name is not supported by Unicode.", "Could not open the file because its name is not supported by Unicode.",
)), )),
None, None,
); );
match language { match language {
Some(lang) => buffer.set_language(Some(&lang)), Some(lang) => buffer.set_language(Some(&lang)),
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(),
Some(&cancellable), Some(&cancellable),
|_, _| {}, |_, _| {},
|result| { |result| {
if result.is_err() { if result.is_err() {
panic!(result.err()); panic!("Found an error when loading the file into the text editor's buffer. {:#?}", result.err());
} }
}, },
); );
}
} }
None => {}
} }
this this
} }
@ -83,4 +88,50 @@ impl EchidnaCoreEditor {
pub fn file(&self) -> sourceview::File { 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>") 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>")
} }
pub fn save_file(&self, save_as: Option<&gio::File>) -> Result<(), &str> {
let window_imp = self.to_imp();
let buffer = self.to_imp().sourceview.buffer().downcast::<Buffer>();
match buffer {
Ok(buffer) => {
let cancellable = Cancellable::new();
let mut file_saver: Option<FileSaver> = None;
let result: Result<(), &str> = match save_as {
Some(file) => {
file_saver = Some(FileSaver::with_target(&buffer, &self.file(), file));
Ok(())
}
None => match self.file().location() {
Some(_) => {
file_saver = Some(FileSaver::new(&buffer, &self.file()));
Ok(())
}
None => Err("The file location must exist. Please do \"Save As\""),
},
};
match result {
Err(result) => Err(result),
Ok(_) => {
file_saver.unwrap().save_async(
glib::Priority::default(),
Some(&cancellable),
|_, _| {},
|result| {
if result.is_err() {
panic!(
"Found an error while saving the file:\n{}",
result.err().expect("No error")
)
}
},
);
Ok(())
}
}
}
Err(_) => Err("Can't downcast the buffer to GtkSourceBuffer."),
}
}
} }

View File

@ -5,11 +5,13 @@
pub mod app; pub mod app;
pub mod editor; pub mod editor;
pub mod sidebar; pub mod sidebar;
pub mod tab_label;
pub mod window; pub mod window;
pub use app::EchidnaEditor; pub use app::EchidnaEditor;
pub use editor::EchidnaCoreEditor; pub use editor::EchidnaCoreEditor;
pub use sidebar::EchidnaSidebar; pub use sidebar::EchidnaSidebar;
pub use tab_label::TabLabel;
pub use window::EchidnaWindow; pub use window::EchidnaWindow;
pub mod prelude { pub mod prelude {

View File

@ -15,3 +15,9 @@ impl EchidnaSidebar {
glib::Object::new(&[]).expect("Failed to create 'EchidnaSidebar' component.") glib::Object::new(&[]).expect("Failed to create 'EchidnaSidebar' component.")
} }
} }
impl Default for EchidnaSidebar {
fn default() -> Self {
Self::new()
}
}

View File

@ -6,67 +6,71 @@
- file, You can obtain one at https://mozilla.org/MPL/2.0/. - file, You can obtain one at https://mozilla.org/MPL/2.0/.
--> -->
<template class="EchidnaSidebar"> <template class="EchidnaSidebar">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child> <child>
<object class="GtkStackSidebar"> <object class="GtkStackSidebar">
<property name="stack"> <property name="stack">sidebar_stack</property>
<object class="GtkStack">
<child>
<object class="GtkStackPage">
<!-- Explorer Tab -->
<property name="name">explorer</property>
<property name="title">Explorer</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkTreeView" id="treeview">
<property name="model">liststore1</property>
<child></child>
<child internal-child="selection">
<object class="GtkTreeSelection" id="selection">
<signal name="changed" handler="on_treeview_selection_changed" />
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<!-- Search Tab -->
<property name="name">search</property>
<property name="title">Search</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkLabel">
<property name="label">Search</property>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<!-- Extensions Tab -->
<property name="name">extensions</property>
<property name="title">Extensions</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
</object>
</property>
</object>
</child>
</object>
</property>
</object> </object>
</child> </child>
<child>
<object class="GtkStack" id="sidebar_stack">
<property name="hhomogeneous">1</property>
<property name="width-request">170</property>
<child>
<object class="GtkStackPage">
<!-- Explorer Tab -->
<property name="name">explorer</property>
<property name="title">Explorer</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkLabel">
<property name="label">Explorer</property>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<!-- Search Tab -->
<property name="name">search</property>
<property name="title">Search</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkLabel">
<property name="label">Search</property>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkStackPage">
<!-- Extensions Tab -->
<property name="name">extensions</property>
<property name="title">Extensions</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkLabel">
<property name="label">Extensions</property>
</object>
</child>
</object>
</property>
</object>
</child>
</object>
</child>
</template> </template>
</interface> </interface>

View File

@ -0,0 +1,45 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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 gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::CompositeTemplate;
#[derive(Default, CompositeTemplate)]
#[template(file = "./tab-label.ui")]
pub struct TabLabel {
#[template_child]
pub button: TemplateChild<gtk::Button>,
}
#[glib::object_subclass]
impl ObjectSubclass for TabLabel {
const NAME: &'static str = "TabLabel";
type Type = super::TabLabel;
type ParentType = gtk::Box;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for TabLabel {}
impl WidgetImpl for TabLabel {}
impl BoxImpl for TabLabel {}
impl BuildableImpl for TabLabel {
fn add_child(
&self,
buildable: &Self::Type,
_builder: &gtk::Builder,
child: &glib::Object,
_type_: Option<&str>,
) {
buildable.prepend(child.downcast_ref::<gtk::Widget>().unwrap());
}
}

View File

@ -0,0 +1,30 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
pub mod imp;
use glib::IsA;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
glib::wrapper! {
pub struct TabLabel(ObjectSubclass<imp::TabLabel>)
@extends gtk::Box, gtk::Widget,
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
impl TabLabel {
pub fn new<U: IsA<gtk::Widget>>(tab_label: Option<&U>) -> Self {
let this: Self = glib::Object::new(&[]).expect("Failed to create 'TabLabel' component.");
match tab_label {
Some(tab_label) => this.prepend(tab_label),
None => {}
}
this
}
pub fn to_imp(&self) -> &imp::TabLabel {
imp::TabLabel::from_instance(self)
}
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="4.5.0" />
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- 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/.
-->
<template class="TabLabel">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child>
<object class="GtkButton" id="button">
<property name="has-frame">0</property>
<property name="icon-name">window-close-symbolic</property>
</object>
</child>
</template>
</interface>

View File

@ -4,18 +4,18 @@
use crate::components::editor::EchidnaCoreEditor; use crate::components::editor::EchidnaCoreEditor;
use crate::lib::prelude::*; use crate::lib::prelude::*;
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, FileChooserNative, Label, ResponseType,
}; };
use sourceview::{prelude::*, Buffer, File, FileSaver}; use sourceview::{File, FileExt as SourceFileExt};
pub trait FileImplementedEditor { pub trait FileImplementedEditor {
fn action_open_file(&self); fn action_open_file(&self);
fn open_file(notebook: &gtk::Notebook, file: gio::File); fn open_file(notebook: &gtk::Notebook, file: gio::File);
fn action_save_file_as(&self); fn action_save_file_as(&self);
fn action_new_file(&self); fn action_new_file(&self);
fn action_save_file(&self);
} }
impl FileImplementedEditor for super::EchidnaWindow { impl FileImplementedEditor for super::EchidnaWindow {
@ -36,14 +36,12 @@ impl FileImplementedEditor for super::EchidnaWindow {
Perhaps some of the last points should not be implemented in this function but rather in another function that keeps track of every files. Perhaps some of the last points should not be implemented in this function but rather in another function that keeps track of every files.
*/ */
fn action_open_file(&self) { fn action_open_file(&self) {
let dialog: FileChooserDialog = FileChooserDialog::new( let dialog = FileChooserNative::new(
Some("Open a file"), Some("Open a file"),
Some(self), Some(self),
FileChooserAction::Open, FileChooserAction::Open,
&[ Some("Open"),
("Cancel", ResponseType::Cancel), Some("Cancel"),
("Open", ResponseType::Accept),
],
); );
dialog.set_visible(true); dialog.set_visible(true);
@ -69,7 +67,7 @@ impl FileImplementedEditor for super::EchidnaWindow {
notebook.prepend_closable_page( notebook.prepend_closable_page(
&editor_page, &editor_page,
Some(&Label::new(Some( Some(&Label::new(Some(
&file_location file_location
.path() .path()
.expect("The file's path is missing") .expect("The file's path is missing")
.file_name() .file_name()
@ -79,16 +77,13 @@ impl FileImplementedEditor for super::EchidnaWindow {
))), ))),
); );
} }
fn action_save_file_as(&self) { fn action_save_file_as(&self) {
let dialog = FileChooserDialog::new( let dialog = FileChooserNative::new(
Some("Save File As"), Some("Save File As"),
Some(self), Some(self),
FileChooserAction::Save, FileChooserAction::Save,
&[ Some("Open"),
("Cancel", ResponseType::Cancel), Some("Cancel"),
("Save", ResponseType::Accept),
],
); );
dialog.set_current_name("untitled"); dialog.set_current_name("untitled");
@ -99,41 +94,9 @@ impl FileImplementedEditor for super::EchidnaWindow {
move |dialog, response| { move |dialog, response| {
if response == ResponseType::Accept { if response == ResponseType::Accept {
let file = dialog.file().expect(""); let file = dialog.file().expect("");
let window_imp = window.to_imp(); let tab: EchidnaCoreEditor = window.get_current_tab().expect("error");
let page: EchidnaCoreEditor; tab.save_file(Some(&file));
}
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(); dialog.destroy();
@ -147,4 +110,16 @@ impl FileImplementedEditor for super::EchidnaWindow {
.notebook .notebook
.prepend_closable_page(&editor_page, Some(&gtk::Label::new(Some(&"Untitled")))); .prepend_closable_page(&editor_page, Some(&gtk::Label::new(Some(&"Untitled"))));
} }
fn action_save_file(&self) {
let page: EchidnaCoreEditor = self
.get_current_tab()
.expect("Can't find the current tab because there are no tabs.");
match page.file().location() {
Some(_) => {
page.save_file(None);
}
None => self.action_save_file_as(),
}
}
} }

View File

@ -67,7 +67,7 @@ impl MenubarImplementedEditor for EchidnaWindow {
about_dialog.set_license_type(gtk::License::Mpl20); about_dialog.set_license_type(gtk::License::Mpl20);
about_dialog.set_program_name(Some("Echidna Code Editor")); about_dialog.set_program_name(Some("Echidna Code Editor"));
about_dialog.set_website(Some("https://github.com/EchidnaHQ/Echidna")); about_dialog.set_website(Some("https://gitlab.com/EchidnaHQ/Echidna"));
about_dialog.set_authors(&["FortressValkriye"]); about_dialog.set_authors(&["FortressValkriye"]);
about_dialog.set_copyright(Some("Made with by ❤️ Echidna contributors")); about_dialog.set_copyright(Some("Made with by ❤️ Echidna contributors"));
about_dialog.set_visible(true); about_dialog.set_visible(true);
@ -78,18 +78,20 @@ impl MenubarImplementedEditor for EchidnaWindow {
app.add_action(&act_report_issue); app.add_action(&act_report_issue);
act_report_issue.connect_activate(|_action, _variant| { act_report_issue.connect_activate(clone!(@weak self as win =>
webbrowser::open("https://github.com/EchidnaHQ/Echidna/issues/new"); move |_action, _variant| {
}); gtk::show_uri(Some(&win), "https://github.com/EchidnaHQ/Echidna/issues/new", gdk::CURRENT_TIME);
}));
} }
{ {
let act_search_feature_requests = SimpleAction::new("search-feature-requests", None); let act_search_feature_requests = SimpleAction::new("search-feature-requests", None);
app.add_action(&act_search_feature_requests); app.add_action(&act_search_feature_requests);
act_search_feature_requests.connect_activate(|_action, _variant| { act_search_feature_requests.connect_activate(clone!(@weak self as win =>
webbrowser::open("https://github.com/EchidnaHQ/Echidna/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement"); move |_action, _variant| {
}); gtk::show_uri(Some(&win), "https://github.com/EchidnaHQ/Echidna/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement", gdk::CURRENT_TIME);
}));
} }
{ {
let act_window_close = SimpleAction::new("close", None); let act_window_close = SimpleAction::new("close", None);
@ -130,5 +132,16 @@ impl MenubarImplementedEditor for EchidnaWindow {
window.action_new_file(); window.action_new_file();
})); }));
} }
{
let action_save = SimpleAction::new("save", None);
self.add_action(&action_save);
action_save.connect_activate(clone!(@weak self as window =>
move |_, _| {
window.action_save_file();
}
));
}
} }
} }

View File

@ -6,7 +6,7 @@ pub mod file;
pub mod imp; pub mod imp;
pub mod menubar; pub mod menubar;
use glib::object::IsA; use glib::object::{Cast, IsA};
use gtk::subclass::prelude::*; use gtk::subclass::prelude::*;
glib::wrapper! { glib::wrapper! {
@ -28,4 +28,25 @@ impl EchidnaWindow {
pub fn to_imp(&self) -> &imp::EchidnaWindow { pub fn to_imp(&self) -> &imp::EchidnaWindow {
imp::EchidnaWindow::from_instance(self) imp::EchidnaWindow::from_instance(self)
} }
pub fn get_current_tab<A: IsA<gtk::Widget>>(&self) -> Result<A, &str> {
let window_imp = self.to_imp();
let nth = window_imp.notebook.current_page();
match nth {
None => Err("No tabs are currently opened, maybe there are no tabs."),
Some(nth) => {
let page = window_imp
.notebook
.nth_page(Some(nth))
.expect("Couldn't get the page of the current index.");
match page.downcast::<A>()
{
Ok(page) => Ok(page),
Err(e) => Err("Cannot downcast to type parameter A. Maybe it's not in the type you are looking for."),
}
}
}
}
} }

View File

@ -9,7 +9,7 @@ use glib::clone;
use glib::subclass::types::ObjectSubclassExt; use glib::subclass::types::ObjectSubclassExt;
use glib::types::Type; use glib::types::Type;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{ApplicationWindow, FileChooserAction, FileChooserDialog, ResponseType, TreeStore}; use gtk::{ApplicationWindow, FileChooserAction, FileChooserNative, ResponseType, TreeStore};
use relative_path::RelativePath; use relative_path::RelativePath;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::Path; use std::path::Path;
@ -46,14 +46,12 @@ impl WorkspaceImplementedEditor for EchidnaEditor {
_action: &SimpleAction, _action: &SimpleAction,
_variant: Option<&glib::Variant>, _variant: Option<&glib::Variant>,
) { ) {
let dialog: FileChooserDialog = FileChooserDialog::new( let dialog = FileChooserNative::new(
Some("Open a file"), Some("Open a file"),
Some(&window), Some(&window),
FileChooserAction::Open, FileChooserAction::Open,
&[ Some("Open"),
("Cancel", ResponseType::Cancel), Some("Cancel"),
("Open", ResponseType::Accept),
],
); );
dialog.set_visible(true); dialog.set_visible(true);
dialog.connect_response(clone!(@weak window, @weak app => dialog.connect_response(clone!(@weak window, @weak app =>
@ -78,7 +76,7 @@ impl WorkspaceImplementedEditor for EchidnaEditor {
* *
* Basically, this is just the same as Open Folder, but it's many folders. * Basically, this is just the same as Open Folder, but it's many folders.
* *
* - Open a FileChooserDialog, set to only view .code-workspace files. * - Open a FileChooserNative, set to only view .code-workspace files.
* - If the user pressed cancel, destroy the dialog. If the user opened a .code-workspace file: * - If the user pressed cancel, destroy the dialog. If the user opened a .code-workspace file:
* - Get the workspace file, load and parse its content, .code-workspace files are in JSON with comments. But JSON only should be fine, for now. * - Get the workspace file, load and parse its content, .code-workspace files are in JSON with comments. But JSON only should be fine, for now.
* - Iterate over folders listed in the workspace file. * - Iterate over folders listed in the workspace file.
@ -125,31 +123,30 @@ impl WorkspaceImplementedEditor for EchidnaEditor {
self.open_folder(folder); self.open_folder(folder);
} }
} }
}
/** /**
* *
* *
*/ */
fn recursive_add_files_into_tree_store(&self, parent_file: File, tree: &TreeStore) { fn recursive_add_files_into_tree_store(&self, parent_file: File, tree: &TreeStore) {
let child_enumerate_cancellable = Cancellable::new(); let child_enumerate_cancellable = Cancellable::new();
let child_files = parent_file let child_files = parent_file
.enumerate_children( .enumerate_children(
"*", "*",
FileQueryInfoFlags::NONE, FileQueryInfoFlags::NONE,
Some(&child_enumerate_cancellable), Some(&child_enumerate_cancellable),
)
.expect(
format!(
"Could not look up the children files of {:?} because:\n{:#?}",
filepath
) )
.as_str(), .expect(
); format!(
let filepath = &parent_file "Could not look up the children files of {:?} because:\n{:#?}",
.path() filepath
.expect("Could not get the file path of the file."); )
.as_str(),
);
let filepath = &parent_file
.path()
.expect("Could not get the file path of the file.");
for file_iter in files { for file_iter in files {
let file_info = file_iter.expect(); let file_info = file_iter.expect();
let file = parent_file.child(file_info.name()); let file = parent_file.child(file_info.name());

View File

@ -2,8 +2,9 @@
* 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 crate::components::tab_label::TabLabel;
use glib::IsA; use glib::IsA;
use gtk::{prelude::*, Box, Button, Widget}; use gtk::{prelude::*, Widget};
pub trait ClosableTabImplementedNotebook { pub trait ClosableTabImplementedNotebook {
fn prepend_closable_page<T: IsA<Widget>, U: IsA<Widget>>( fn prepend_closable_page<T: IsA<Widget>, U: IsA<Widget>>(
@ -16,39 +17,24 @@ pub trait ClosableTabImplementedNotebook {
child: &T, child: &T,
tab_label: Option<&U>, tab_label: Option<&U>,
) -> u32; ) -> u32;
fn create_closable_tab<U: IsA<Widget>>(tab_label: Option<&U>) -> (Box, Button);
} }
impl ClosableTabImplementedNotebook for gtk::Notebook { impl ClosableTabImplementedNotebook for gtk::Notebook {
fn create_closable_tab<U: IsA<Widget>>(tab_label: Option<&U>) -> (Box, Button) {
let tab = Box::new(gtk::Orientation::Horizontal, 5);
if tab_label.is_some() {
tab.append(tab_label.unwrap());
}
let button = gtk::Button::new();
button.set_icon_name("window-close-symbolic");
button.set_has_frame(false);
tab.append(&button);
(tab, button)
}
fn prepend_closable_page<T: IsA<Widget>, U: IsA<Widget>>( fn prepend_closable_page<T: IsA<Widget>, U: IsA<Widget>>(
&self, &self,
child: &T, child: &T,
tab_label: Option<&U>, tab_label: Option<&U>,
) -> u32 { ) -> u32 {
let (tab, button) = &Self::create_closable_tab(tab_label); let tab_label_widget = TabLabel::new(tab_label);
let page = self.prepend_page(child, Some(tab)); let page = self.prepend_page(child, Some(&tab_label_widget));
button.connect_clicked(glib::clone!(@weak self as notebook => tab_label_widget
move |_| { .to_imp()
notebook.remove_page(Some(page)); .button
})); .connect_clicked(glib::clone!(@weak self as notebook =>
move |_| {
notebook.remove_page(Some(page));
}));
page page
} }
@ -58,13 +44,16 @@ impl ClosableTabImplementedNotebook for gtk::Notebook {
child: &T, child: &T,
tab_label: Option<&U>, tab_label: Option<&U>,
) -> u32 { ) -> u32 {
let (tab, button) = &Self::create_closable_tab(tab_label); let tab_label_widget = TabLabel::new(tab_label);
let page = self.append_page(child, Some(tab)); let page = self.append_page(child, Some(&tab_label_widget));
button.connect_clicked(glib::clone!(@weak self as notebook => tab_label_widget
move |_| { .to_imp()
notebook.remove_page(Some(page)); .button
})); .connect_clicked(glib::clone!(@weak self as notebook =>
move |_| {
notebook.remove_page(Some(page));
}));
page page
} }