refactor: move all traits to EchidnaWindow struct

Before, all the traits are implemented for EchidnaApplication.

This is added to better support using the app with many windows open.

EchidnaApplication may be obsolete now, but I'm not removing it for now.
merge-requests/8/head
Nefo Fortressia 2021-10-24 14:20:53 +07:00
parent b68e93c3d4
commit 385f2dda32
12 changed files with 359 additions and 234 deletions

View File

@ -1,198 +0,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/. */
use std::str::from_utf8;
use sourceview::{View, Buffer, LanguageManager, };
use sourceview::prelude::*;
use gtk::subclass::prelude::*;
use gtk::prelude::*;
use super::menubar::MenubarImplementedEditor;
use gtk::{
ApplicationWindow,
Application,
FileChooserDialog,
FileChooserAction,
ResponseType,
Label
};
use gio::{
MenuModel,
SimpleAction,
Cancellable
};
//use super::workspace;
use glib::clone;
#[derive(Debug, Default)]
pub struct EchidnaEditor {
pub name: &'static str,
pub app_id: &'static str
}
pub trait EchidnaEditorExt {
fn action_open_file(window: ApplicationWindow, app: super::EchidnaEditor, action: &SimpleAction, variant: Option<&glib::Variant>, notebook: gtk::Notebook);
fn open_file(notebook: &gtk::Notebook, file: gio::File);
}
#[glib::object_subclass]
impl ObjectSubclass for EchidnaEditor {
const NAME: &'static str = "EchidnaEditorApplication";
type Type = super::EchidnaEditor;
type ParentType = Application;
fn new() -> Self {
Self {
name: "Echidna Code Editor",
app_id: "land.echidna.editor"
}
}
}
impl EchidnaEditorExt for EchidnaEditor {
/*
Open a file and put it in an editor and the opened files bar.
- Open a file chooser dialog.
- Connect a signal to it and get the file choosen.
- Read th file's information and
- TODO: if it's a plain text,
- TODO: Load the file's content
- TODO: Create an editor
- TODO: Set the editor's content to the file's content.
- TODO: Somehow keep track of what file belongs to what editor/opened file bar widget.
- TODO: If the user enables Autosave, listen to the editor's changes and automatically save the editor's content.
- TODO: Close the editor and the tab widget when the file is closed (triggered by the X button on them).
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(window: ApplicationWindow, app: super::EchidnaEditor, _action: &SimpleAction, variant: Option<&glib::Variant>, notebook: gtk::Notebook){
let dialog: FileChooserDialog = FileChooserDialog::new(Some("Open a file"),
Some(&window),
FileChooserAction::Open,
&[("Cancel", ResponseType::Cancel),
("Open", ResponseType::Accept) ]);
dialog.set_visible(true);
// 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 app, @weak window, =>
move |dialog, response| {
if response == ResponseType::Accept {
let file_option = dialog.file();
match file_option {
Some(file) => {
dialog.destroy();
Self::open_file(&notebook, file);
},
None => {
},
}
} else if response == ResponseType::Cancel {
dialog.destroy();
} }));
}
fn open_file(notebook: &gtk::Notebook, file: gio::File){
let cancellable = Cancellable::new();
let filepath = file.path().expect("No filepath");
let file_info_result = file.query_info(
"*",
gio::FileQueryInfoFlags::NONE,
Some(&cancellable));
match file_info_result {
Ok(info) => {
match info.content_type() {
Some(content_type) => {
println!("Opened {} and found its content type is {}.", "file", content_type.to_string());
let content_cancellable = Cancellable::new();
let file_content = file.load_contents(Some(&content_cancellable));
match file_content {
Ok(content) => {
let (int_vec, _text_option) = content;
let language_manager = LanguageManager::new();
let language = language_manager.guess_language(Some(&info.name().to_str().expect("Could not open the file because its name is not supported by Unicode.")), None);
let buffer = Buffer::new(None);
buffer.set_text(from_utf8(&int_vec).expect(format!("Could not parse the contents of {:?} as it's unsupported by UTF-8", filepath).as_str()));
match language {
Some(lang) => buffer.set_language(Some(&lang)),
None => {}
}
let sourceview = View::with_buffer(&buffer);
notebook.prepend_page(&sourceview,
Some(&Label::new(Some(&info.name().to_str()
.expect("Could not parse file's name")))));
}
Err(e) => println!("Could not open {:?} because:\n{:#?}", filepath, e),
}
},
None => println!("It does not seem like {:?} has a type", filepath),
}
},
Err(e) => println!("Could not retrieve file information for {:?} because:\n{}", filepath, e),
}
}
}
impl ObjectImpl for EchidnaEditor {
}
impl ApplicationImpl for EchidnaEditor {
fn activate(&self, app: &Self::Type){
let builder = gtk::Builder::from_string(include_str!("../../ui/window.ui"));
let window: ApplicationWindow = builder
.object("window")
.expect("Could not get object 'window' from builder.");
let menubuilder = gtk::Builder::from_string(include_str!("../../ui/menu.ui"));
let menubar: MenuModel = menubuilder
.object("menu")
.expect("Could not get object 'menu' from builder.");
self.setup_menubar(app, &window, &builder.clone());
window.set_application(Some(app));
window.present();
}
}
impl GtkApplicationImpl for EchidnaEditor {}

60
src/components/app/imp.rs Normal file
View File

@ -0,0 +1,60 @@
/* 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 super::super::window::EchidnaWindow;
use super::super::window::menubar::MenubarImplementedEditor;
use gtk::subclass::prelude::*;
use gtk::prelude::*;
use gtk::{
Application,
};
#[derive(Debug, Default)]
pub struct EchidnaEditor {
pub name: &'static str,
pub app_id: &'static str
}
#[glib::object_subclass]
impl ObjectSubclass for EchidnaEditor {
const NAME: &'static str = "EchidnaEditorApplication";
type Type = super::EchidnaEditor;
type ParentType = Application;
fn new() -> Self {
Self {
name: "Echidna Code Editor",
app_id: "land.echidna.editor"
}
}
}
impl ObjectImpl for EchidnaEditor {
}
impl ApplicationImpl for EchidnaEditor {
fn activate(&self, app: &Self::Type){
let window = EchidnaWindow::new(app);
window.setup_menubar();
window.set_application(Some(app));
window.present();
}
}
impl GtkApplicationImpl for EchidnaEditor {}

View File

@ -3,12 +3,12 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
pub mod imp;
pub mod menubar;
pub mod workspace;
use glib::wrapper;
wrapper! {
pub struct EchidnaEditor(ObjectSubclass<imp::EchidnaEditor>) @extends gio::Application, gtk::Application, @implements gio::ActionGroup, gio::ActionMap;
}
impl Default for EchidnaEditor {

View File

@ -3,3 +3,4 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
pub mod app;
pub mod window;

View File

@ -0,0 +1,134 @@
use glib::{
clone
};
use sourceview::{
View,
Buffer,
LanguageManager,
prelude::*
};
use gio::{
Cancellable
};
use gtk::{
FileChooserDialog,
FileChooserAction,
ResponseType,
Label,
prelude::*,
subclass::prelude::*
};
pub trait FileImplementedEditor {
fn action_open_file/*<P: IsA<gtk::Application>>*/(window: Self);
fn open_file(notebook: &gtk::Notebook, file: gio::File);
}
impl FileImplementedEditor for super::EchidnaWindow {
/*
Open a file and put it in an editor and the opened files bar.
- Open a file chooser dialog.
- Connect a signal to it and get the file choosen.
- Read th file's information and
- TODO: if it's a plain text,
- TODO: Load the file's content
- TODO: Create an editor
- TODO: Set the editor's content to the file's content.
- TODO: Somehow keep track of what file belongs to what editor/opened file bar widget.
- TODO: If the user enables Autosave, listen to the editor's changes and automatically save the editor's content.
- TODO: Close the editor and the tab widget when the file is closed (triggered by the X button on them).
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/*<P: IsA<gtk::Application>>*/(window: Self){
let dialog: FileChooserDialog = FileChooserDialog::new(Some("Open a file"),
Some(&window),
FileChooserAction::Open,
&[("Cancel", ResponseType::Cancel),
("Open", ResponseType::Accept) ]);
dialog.set_visible(true);
// 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 window, =>
move |dialog, response| {
if response == ResponseType::Accept {
let file_option = dialog.file();
match file_option {
Some(file) => {
dialog.destroy();
Self::open_file(&super::imp::EchidnaWindow::from_instance(&window).notebook, file);
},
None => {
},
}
} else if response == ResponseType::Cancel {
dialog.destroy();
} }));
}
fn open_file(notebook: &gtk::Notebook, file: gio::File){
let cancellable = Cancellable::new();
let filepath = file.path().expect("No filepath");
let file_info_result = file.query_info(
"*",
gio::FileQueryInfoFlags::NONE,
Some(&cancellable));
match file_info_result {
Ok(info) => {
match info.content_type() {
Some(content_type) => {
println!("Opened {} and found its content type is {}.", "file", content_type.to_string());
let content_cancellable = Cancellable::new();
let file_content = file.load_contents(Some(&content_cancellable));
match file_content {
Ok(content) => {
let (int_vec, _text_option) = content;
let language_manager = LanguageManager::new();
let language = language_manager.guess_language(Some(&info.name().to_str().expect("Could not open the file because its name is not supported by Unicode.")), None);
let buffer = Buffer::new(None);
buffer.set_text(std::str::from_utf8(&int_vec).expect(format!("Could not parse the contents of {:?} as it's unsupported by UTF-8", filepath).as_str()));
match language {
Some(lang) => buffer.set_language(Some(&lang)),
None => {}
}
let sourceview = View::with_buffer(&buffer);
notebook.prepend_page(&sourceview,
Some(&Label::new(Some(&info.name().to_str()
.expect("Could not parse file's name")))));
}
Err(e) => println!("Could not open {:?} because:\n{:#?}", filepath, e),
}
},
None => println!("It does not seem like {:?} has a type", filepath),
}
},
Err(e) => println!("Could not retrieve file information for {:?} because:\n{}", filepath, e),
}
}
}

View File

@ -0,0 +1,37 @@
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::CompositeTemplate;
#[derive(Debug, Default, CompositeTemplate)]
#[template(file = "./window.ui")]
pub struct EchidnaWindow {
#[template_child]
pub notebook: TemplateChild<gtk::Notebook>,
}
#[glib::object_subclass]
impl ObjectSubclass for EchidnaWindow {
const NAME: &'static str = "EchidnaWindow";
type Type = super::EchidnaWindow;
type ParentType = gtk::ApplicationWindow;
fn class_init(class: &mut Self::Class) {
Self::bind_template(class);
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for EchidnaWindow {}
impl WidgetImpl for EchidnaWindow {}
impl WindowImpl for EchidnaWindow {}
impl ApplicationWindowImpl for EchidnaWindow {}
impl BuildableImpl for EchidnaWindow {}

View File

@ -1,32 +1,25 @@
use super::imp::EchidnaEditor;
use super::imp::EchidnaEditorExt;
use super::file::FileImplementedEditor;
use super::EchidnaWindow;
use gio::{MenuModel, SimpleAction};
use glib::clone;
use gtk::prelude::*;
use gtk::AboutDialog;
pub trait MenubarImplementedEditor {
fn setup_menubar(
&self,
app: &super::EchidnaEditor,
window: &gtk::ApplicationWindow,
builder: &gtk::Builder,
);
fn setup_menubar(&self);
}
impl MenubarImplementedEditor for EchidnaEditor {
fn setup_menubar(
&self,
app: &super::EchidnaEditor,
window: &gtk::ApplicationWindow,
builder: &gtk::Builder,
) {
let menubuilder = gtk::Builder::from_string(include_str!("../../ui/menu.ui"));
impl MenubarImplementedEditor for EchidnaWindow {
fn setup_menubar(&self) {
let app = &self
.application()
.expect("&self does not have an application set.");
let menubuilder = gtk::Builder::from_string(include_str!("../../../ui/menu.ui"));
let menubar: MenuModel = menubuilder
.object("menu")
.expect("Could not get object 'menu' from builder.");
app.set_menubar(Some(&menubar));
window.set_show_menubar(true);
&self.set_show_menubar(true);
let act_exit: SimpleAction = SimpleAction::new("exit", None);
app.add_action(&act_exit);
@ -49,9 +42,6 @@ impl MenubarImplementedEditor for EchidnaEditor {
about_dialog.set_visible(true);
});
let notebook: gtk::Notebook = builder
.object("echidna-notebook")
.expect("Could not get 'echidna-notebook' from builder.");
//app.notebook = Some(Rc::new(RefCell::new(notebook)));
let act_exit: SimpleAction = SimpleAction::new("exit", None);
app.add_action(&act_exit);
@ -92,20 +82,22 @@ impl MenubarImplementedEditor for EchidnaEditor {
let act_window_close = SimpleAction::new("close", None);
window.add_action(&act_window_close);
&self.add_action(&act_window_close);
{
let window = self.clone();
act_window_close.connect_activate(clone!(@weak window =>
move | _action, _variant | {
act_window_close.connect_activate(move |_action, _variant| {
window.close();
}
));
});
}
let action_open_file: SimpleAction = SimpleAction::new("open-file", None);
window.add_action(&action_open_file);
action_open_file.connect_activate(clone!(@weak window, @weak app, @weak notebook =>
&self.add_action(&action_open_file);
action_open_file.connect_activate(clone!(@weak self as window =>
move |action, variant| {
Self::action_open_file(window, app, action, variant, notebook);
Self::action_open_file(window);
}));
}
}

View File

@ -0,0 +1,36 @@
mod file;
mod imp;
pub mod menubar;
use glib::{
object::IsA,
};
glib::wrapper! {
pub struct EchidnaWindow(ObjectSubclass<imp::EchidnaWindow>)
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow,
@implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager;
}
impl EchidnaWindow {
pub fn new<P: IsA<gtk::Application>>(application: &P) -> Self {
let object = glib::Object::new(&[
("application", &application)
]);
match object {
Ok(o) => o,
Err(e) => panic!("Error in making EchidnaApplication {}", e),
}
}
}

View File

@ -0,0 +1,9 @@
trait SidebarImplementedEditor {
fn setup_sidebar();
}
impl SidebarImplementedEditor for super::imp::EchidnaEditor {
fn setup_sidebar(){
}
}

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0" />
<template class="EchidnaWindow">
<property name="title">Echidna Code Editor</property>
<property name="default-width">800</property>
<property name="default-height">600</property>
<child>
<object class="GtkBox" id="echidna-root">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="bars-box">
<property name="vexpand">1</property>
<child>
<object class="GtkStackSidebar" id="sidebar"></object>
</child>
<child>
<placeholder />
</child>
<child>
<!-- <object class="GtkGrid" id="editor-tabs">
<property name="hexpand">1</property>
<child>
-->
<object class="GtkNotebook" id="notebook">
<child>
<object id="getting-started" class="GtkLabel">
<property name="label">Hello from Echidna!</property>
</object>
</child>
<child type="tab">
<object class="GtkLabel" id="notebook-welcome">
<property name="label">Welcome</property>
</object>
</child>
</object>
<!-- </child>
</object> -->
</child>
</object>
</child>
<child>
<object class="GtkStatusbar" id="status-bar">
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="margin-top">6</property>
<property name="margin-bottom">6</property>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -2,15 +2,14 @@
* 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/. */
mod app;
mod components;
use gtk::prelude::ApplicationExtManual;
use app::EchidnaEditor;
use components::app;
use gtk::prelude::ApplicationExtManual;
fn main() {
let app = EchidnaEditor::new(
"land.echidna.editor",
);
let app = EchidnaEditor::new("land.echidna.editor");
std::process::exit(app.run());
}
}