diff --git a/Gtk4_Reset/res/text_menu.xml b/Gtk4_Reset/res/text_menu.xml index ae02db2..491f57e 100644 --- a/Gtk4_Reset/res/text_menu.xml +++ b/Gtk4_Reset/res/text_menu.xml @@ -22,6 +22,11 @@ win.text_copy <Control>c + + Cut + win.text_cut + <Control>c + Paste win.text_paste diff --git a/Gtk4_Reset/src/apps/TextEditor.h b/Gtk4_Reset/src/apps/TextEditor.h index 69fc9b0..7630202 100644 --- a/Gtk4_Reset/src/apps/TextEditor.h +++ b/Gtk4_Reset/src/apps/TextEditor.h @@ -5,3 +5,5 @@ G_DECLARE_FINAL_TYPE(TextEditor, text_editor, TEXT, EDITOR, GtkApplicationWindow) TextEditor *text_editor_new(); + +void text_editor_save_config(TextEditor *self); diff --git a/Gtk4_Reset/src/apps/TextEditor.hh b/Gtk4_Reset/src/apps/TextEditor.hh deleted file mode 100644 index 2238cf0..0000000 --- a/Gtk4_Reset/src/apps/TextEditor.hh +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include - -class TextEditor : public Gtk::ApplicationWindow -{ -public: - TextEditor(); - -private: - // Header widgets - Gtk::HeaderBar header; - Gtk::MenuButton menubtn; - Gtk::Popover popover; - Gtk::ToggleButton search_button; - Glib::RefPtr menu_builder, expend_builder; - - // SearchBar - Gtk::SearchBar searchbar; - Gtk::SearchEntry search_entry; - Gtk::Box searchbox; - Gtk::Button search_up, search_down; - Glib::RefPtr search_binding; - Gtk::TextIter curr_iter_up, curr_iter_down; - - // Window widgets - Gtk::Box vbox, hbox, *infobox; - Gtk::ScrolledWindow sw1; - Glib::RefPtr buffer1; - Gtk::TextView textview1; - Gtk::InfoBar infobar; - Gtk::Label label1; - Gtk::Expander *expender; - Gtk::Button *btns[26], *btntab, *btnenter; - Gtk::ToggleButton *btnshift, *btncaps; - - // File Dialog - Glib::RefPtr dialog; - Glib::ustring curr_filename; - bool file_opened; - - // Signal Handlers - bool window_delete_event(GdkEventAny *event); - - // File Operation functions - void btnopen_clicked(); - void opendialog_response(int response); - void btnsave_clicked(); - void btnsaveas_clicked(); - void savedialog_response(int response); - - // Copy, Paste and text operations - void btncopy_clicked(); - void btnpaste_clicked(); - void btnclose_clicked(); - void buffer1_changed(); - void clipboard_receive(const Glib::ustring &text); - void infobar_response(int response); - - // Search funtion - void search_entry_changed(); - void search_forward(); - void search_backward(); - - // Keyboard press - void key_pressed(Gtk::Button *button); - void btntab_clicked(); - void btnenter_clicked(); - - // Other Signal Handlers - void about_activated(); -}; diff --git a/Gtk4_Reset/src/text_app/MyInfoBar.cpp b/Gtk4_Reset/src/text_app/MyInfoBar.cpp index 6731edd..1705fd7 100644 --- a/Gtk4_Reset/src/text_app/MyInfoBar.cpp +++ b/Gtk4_Reset/src/text_app/MyInfoBar.cpp @@ -5,7 +5,8 @@ struct _MyInfoBar GtkBox parent_instance; GtkWidget *btn_box; GtkWidget *space_label; - GtkWidget *revealer, *msg_label, *button; + // GtkWidget *revealer; + GtkWidget *msg_label, *button; }; G_DEFINE_TYPE(MyInfoBar, my_infobar, GTK_TYPE_BOX) @@ -19,7 +20,7 @@ static void my_infobar_btn_clicked(GtkButton *btn, MyInfoBar *self) static void my_infobar_init(MyInfoBar *self) { // Create widgets - self->revealer = gtk_revealer_new(); + // self->revealer = gtk_revealer_new(); self->btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); self->msg_label = gtk_label_new(""); self->space_label = gtk_label_new(" "); @@ -35,11 +36,13 @@ static void my_infobar_init(MyInfoBar *self) gtk_widget_set_halign(self->msg_label, GTK_ALIGN_START); gtk_widget_set_valign(self->button, GTK_ALIGN_END); gtk_widget_set_hexpand(self->space_label, TRUE); - gtk_widget_set_margin_start(self->btn_box, 10); - gtk_widget_set_margin_end(self->btn_box, 10); + gtk_widget_set_margin_start(self->btn_box, 20); + gtk_widget_set_margin_end(self->btn_box, 30); + gtk_box_prepend(GTK_BOX(self), self->btn_box); - gtk_revealer_set_child(GTK_REVEALER(self->revealer), self->btn_box); - gtk_box_append(GTK_BOX(self), self->revealer); + // gtk_revealer_set_child(GTK_REVEALER(self->revealer), self->btn_box); + // gtk_box_append(GTK_BOX(self), self->revealer); + gtk_widget_set_visible(self->btn_box, FALSE); } static void my_infobar_class_init(MyInfoBarClass *self) @@ -51,8 +54,9 @@ MyInfoBar *my_infobar_new() return MY_INFOBAR(g_object_new(my_infobar_get_type(), NULL)); } -void my_info_bar_set_message(MyInfoBar *info_bar, const char *message) +void my_infobar_show_message(MyInfoBar *info_bar, const char *message) { // Set label for info bar gtk_label_set_label(GTK_LABEL(info_bar->msg_label), message); + gtk_widget_set_visible(info_bar->btn_box, TRUE); } diff --git a/Gtk4_Reset/src/text_app/MyInfoBar.h b/Gtk4_Reset/src/text_app/MyInfoBar.h index 9afd26b..abc96d0 100644 --- a/Gtk4_Reset/src/text_app/MyInfoBar.h +++ b/Gtk4_Reset/src/text_app/MyInfoBar.h @@ -7,4 +7,4 @@ G_DECLARE_FINAL_TYPE(MyInfoBar, my_infobar, MY, INFOBAR, GtkBox) MyInfoBar *my_infobar_new(); // Copy message, the limit is 256 characters -void my_info_bar_set_message(MyInfoBar *info_bar, const char *message); +void my_infobar_show_message(MyInfoBar *info_bar, const char *message); diff --git a/Gtk4_Reset/src/text_app/TextEditor.cc b/Gtk4_Reset/src/text_app/TextEditor.cc deleted file mode 100644 index c0a85f0..0000000 --- a/Gtk4_Reset/src/text_app/TextEditor.cc +++ /dev/null @@ -1,433 +0,0 @@ -#include "TextEditor.hh" -#include "text_types.hh" -#include "../json_nlohmann/json.hpp" -#include -#include -#include - -using json = nlohmann::json; - -TextEditor::TextEditor() - : vbox(Gtk::Orientation::VERTICAL, 5), - hbox(Gtk::Orientation::HORIZONTAL, 5), - searchbox(Gtk::Orientation::HORIZONTAL, 5), - file_opened(false) -{ - // Load window config from json file - int width = 800, height = 450; - std::ifstream json_file("text_config.json"); - if (json_file.is_open()) - { - json data = json::parse(json_file); - width = data["width"]; - height = data["height"]; - } - json_file.close(); - - // Initalize Window - set_default_size(width, height); - set_icon_name("my_textedit"); - - // Initalize HeaderBar - header.set_decoration_layout("close,minimize,maximize:menu"); - header.set_show_close_button(); - menubtn.set_image_from_icon_name("open-menu"); - search_button.set_image_from_icon_name("find"); - header.prepend(menubtn); - header.prepend(search_button); - header.set_title("Simple Text Editor"); - set_titlebar(header); - - // Add a menu - menu_builder = Gtk::Builder::create_from_resource("/org/gtk/daleclack/text_menu.xml"); - auto object = menu_builder->get_object("text_menu"); - auto gmenu = Glib::RefPtr::cast_dynamic(object); - popover.bind_model(gmenu); - menubtn.set_popover(popover); - - // Initalize Text Buffers - buffer1 = textview1.get_buffer(); - buffer1->signal_changed().connect(sigc::mem_fun(*this, &TextEditor::buffer1_changed)); - - // Pack Widgets - sw1.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC); - sw1.add(textview1); - hbox.append(sw1); - - // Add actions and signal handlers - add_action("text_open", sigc::mem_fun(*this, &TextEditor::btnopen_clicked)); - add_action("text_save", sigc::mem_fun(*this, &TextEditor::btnsave_clicked)); - add_action("text_saveas", sigc::mem_fun(*this, &TextEditor::btnsaveas_clicked)); - add_action("text_copy", sigc::mem_fun(*this, &TextEditor::btncopy_clicked)); - add_action("text_paste", sigc::mem_fun(*this, &TextEditor::btnpaste_clicked)); - add_action("text_close", sigc::mem_fun(*this, &TextEditor::btnclose_clicked)); - add_action("text_about", sigc::mem_fun(*this, &TextEditor::about_activated)); - - // Add searchbar and search up and down buttons - search_up.set_image_from_icon_name("up"); - search_down.set_image_from_icon_name("down"); - - // Bind property and signals - search_binding = Glib::Binding::bind_property(search_button.property_active(), - searchbar.property_search_mode_enabled(), - Glib::BINDING_BIDIRECTIONAL); - search_entry.signal_changed().connect(sigc::mem_fun(*this, &TextEditor::search_entry_changed)); - search_up.signal_clicked().connect(sigc::mem_fun(*this, &TextEditor::search_backward)); - search_down.signal_clicked().connect(sigc::mem_fun(*this, &TextEditor::search_forward)); - - // Pack widgets - searchbox.append(search_entry); - searchbox.append(search_up); - searchbox.append(search_down); - searchbar.add(searchbox); - vbox.append(searchbar); - - // A InfoBar - infobar.add_button("OK", Gtk::RESPONSE_OK); - infobar.signal_response().connect(sigc::mem_fun(*this, &TextEditor::infobar_response)); - infobox = dynamic_cast(infobar.get_content_area()); - infobox->append(label1); - vbox.append(infobar); - vbox.append(hbox); - - // Save config when the window is closed - signal_delete_event().connect(sigc::mem_fun(*this, &TextEditor::window_delete_event)); - - // Add Intergated keyboard - expend_builder = Gtk::Builder::create_from_resource("/org/gtk/daleclack/expender.ui"); - expend_builder->get_widget("key_expend", expender); - expend_builder->get_widget("btnshift", btnshift); - expend_builder->get_widget("btn_caps", btncaps); - expend_builder->get_widget("btntab", btntab); - expend_builder->get_widget("btnenter", btnenter); - vbox.append(*expender); - - // Get alphabet buttons - for(int i = 0; i < 26; i++){ - char name[10]; - sprintf(name, "btn%d", i); - expend_builder->get_widget(name, btns[i]); - btns[i]->signal_clicked().connect(sigc::bind( - sigc::mem_fun(*this, &TextEditor::key_pressed), - btns[i] - )); - } - btntab->signal_clicked().connect(sigc::mem_fun(*this, &TextEditor::btntab_clicked)); - btnenter->signal_clicked().connect(sigc::mem_fun(*this, &TextEditor::btnenter_clicked)); - - // Show everything - add(vbox); - show_all_children(); - infobar.hide(); -} - -void TextEditor::key_pressed(Gtk::Button *button){ - auto label = button->get_label(); - Glib::ustring::size_type pos = 0,len = 1; - char buf[2]; - if(btncaps->get_active() || btnshift->get_active()){ - btnshift->set_active(false); - }else{ - sprintf(buf, "%c", label[0] + 32); - label.replace(pos, len, buf); - } - //std::cout << label << std::endl; - buffer1->insert_at_cursor(label); -} - -void TextEditor::btntab_clicked(){ - buffer1->insert_at_cursor("\t"); -} - -void TextEditor::btnenter_clicked(){ - buffer1->insert_at_cursor("\n"); -} - -bool TextEditor::window_delete_event(GdkEventAny *event) -{ - // Create json raw data - json data = json::parse(R"({ - "width":800, - "height":450 - })"); - - // Override config in json file - data["width"] = sw1.get_width(); - data["height"] = sw1.get_height(); - - // Output json data to file - std::fstream outfile; - outfile.open("config.json", std::ios_base::out); - if (outfile.is_open()) - { - outfile << data; - } - outfile.close(); - return false; -} - -void TextEditor::btnopen_clicked() -{ - // Create a dialog - dialog = Gtk::FileChooserNative::create("Open a text file", *this, - Gtk::FILE_CHOOSER_ACTION_OPEN, "OK", "Cancel"); - - dialog->signal_response().connect(sigc::mem_fun(*this, &TextEditor::opendialog_response)); - - // Add Filters - auto filter = Gtk::FileFilter::create(); - filter->set_name("Text Files"); - if (mimetype_supported()) - { - filter->add_mime_type("text/*"); - } - else - { - for (int i = 0; text_globs[i] != NULL; i++) - { - const char *glob = text_globs[i]; - filter->add_pattern(glob); - } - } - dialog->add_filter(filter); - - auto filter_any = Gtk::FileFilter::create(); - filter_any->set_name("Any Files"); - filter_any->add_pattern("*"); - dialog->add_filter(filter_any); - - dialog->show(); -} - -void TextEditor::opendialog_response(int response) -{ - if (response == Gtk::RESPONSE_ACCEPT) - { - // Load Contents of a file - auto file = dialog->get_file(); - curr_filename = file->get_path(); - file_opened = true; - char *contents; - gsize length; - if (file->load_contents(contents, length)) - { - buffer1->set_text(contents); - } - } - dialog.reset(); -} - -void TextEditor::btnsave_clicked() -{ - if (file_opened) - { - // Get Text - Glib::ustring text; - text = buffer1->get_text(); - // Set file opened to true to use save mode - file_opened = true; - // Save to a file - std::ofstream outfile; - outfile.open(curr_filename, std::ios_base::out); - outfile << text; - outfile.close(); - } -} - -void TextEditor::btnsaveas_clicked() -{ - // Create a dialog - dialog = Gtk::FileChooserNative::create("Save file", *this, - Gtk::FILE_CHOOSER_ACTION_SAVE, "OK", "Cancel"); - - dialog->signal_response().connect(sigc::mem_fun(*this, &TextEditor::savedialog_response)); - - // Add Filters - auto filter = Gtk::FileFilter::create(); - filter->set_name("Text Files"); - if (mimetype_supported()) - { - filter->add_mime_type("text/*"); - } - else - { - for (int i = 0; text_globs[i] != NULL; i++) - { - const char *glob = text_globs[i]; - filter->add_pattern(glob); - } - } - dialog->add_filter(filter); - - auto filter_any = Gtk::FileFilter::create(); - filter_any->set_name("Any Files"); - filter_any->add_pattern("*"); - dialog->add_filter(filter_any); - - dialog->show(); -} - -void TextEditor::savedialog_response(int response) -{ - if (response == Gtk::RESPONSE_ACCEPT) - { - // Get Filename - auto file = dialog->get_file(); - std::string filename = file->get_path(); - // Get Text - Glib::ustring text; - text = buffer1->get_text(); - // Save to a file - std::ofstream outfile; - outfile.open(filename, std::ios_base::out); - outfile << text; - outfile.close(); - } - dialog.reset(); -} - -void TextEditor::buffer1_changed() -{ - // When the text changed,enable the copy button -} - -void TextEditor::search_entry_changed() -{ - // Get Text to search - const Glib::ustring text = search_entry.get_text(); - - Gtk::TextIter start, end; - // If get text to search, select the text and storage the position - if (text.length() != 0) - { - if (buffer1->begin().forward_search(text, Gtk::TEXT_SEARCH_CASE_INSENSITIVE, start, end)) - { - curr_iter_up = start; - curr_iter_down = end; - buffer1->select_range(start, end); - textview1.scroll_to(start); - } - } -} - -void TextEditor::search_forward() -{ - // Get Text to search - const Glib::ustring search_text = search_entry.get_text(); - - Gtk::TextIter start, end; - // Get Text to search, down to the end of text - if (search_text.length() != 0) - { - if (curr_iter_down.forward_search(search_text, Gtk::TEXT_SEARCH_CASE_INSENSITIVE, start, end)) - { - curr_iter_up = start; - curr_iter_down = end; - buffer1->select_range(start, end); - textview1.scroll_to(start); - } - } -} - -void TextEditor::search_backward() -{ - // Get Text to search, up to the start of text - const Glib::ustring search_text = search_entry.get_text(); - - Gtk::TextIter start, end; - // Get Text to search - if (search_text.length() != 0) - { - if (curr_iter_up.backward_search(search_text, Gtk::TEXT_SEARCH_CASE_INSENSITIVE, start, end)) - { - curr_iter_up = start; - curr_iter_down = end; - buffer1->select_range(start, end); - textview1.scroll_to(start); - } - } -} - -void TextEditor::btncopy_clicked() -{ - // Get Text - Glib::ustring text; - Gtk::TextBuffer::iterator start, end; - if (buffer1->get_selection_bounds(start, end)) - { - text = buffer1->get_text(start, end); - } - else - { - text = buffer1->get_text(); - } - - // Get Clipboard and set text - auto refClipboard = Gtk::Clipboard::get(); - refClipboard->set_text(text); - - // Show InfoBar - label1.set_label("The Text is copyed"); - infobar.show(); -} - -void TextEditor::btnpaste_clicked() -{ - // Get ClipBoard - auto refClipboard = Gtk::Clipboard::get(); - refClipboard->request_text(sigc::mem_fun(*this, &TextEditor::clipboard_receive)); -} - -void TextEditor::clipboard_receive(const Glib::ustring &text) -{ - if (buffer1->insert_interactive_at_cursor(text)) - { - // Show InfoBar - label1.set_label("The Text is Pasted at cursor position"); - infobar.show(); - } - else - { - // Show InfoBar - label1.set_label("Text Paste Error!"); - infobar.show(); - } -} - -void TextEditor::btnclose_clicked() -{ - buffer1->set_text(""); - file_opened = false; -} - -void TextEditor::infobar_response(int response) -{ - infobar.hide(); -} - -void TextEditor::about_activated() -{ - char *version, *copyright; - // The Gtkmm Version - version = g_strdup_printf("1.0\nRunning Against Gtkmm %d.%d.%d", - GTKMM_MAJOR_VERSION, - GTKMM_MINOR_VERSION, - GTKMM_MICRO_VERSION); - const char *authors[] = {"Dale Clack", NULL}; - // Copyright Informaion - copyright = g_strdup_printf("© 2019—2022 The Xe Project"); - // Show the about dialog - gtk_show_about_dialog(GTK_WINDOW(this->gobj()), - "program-name", "Text Editot", - "version", version, - "copyright", copyright, - "comments", "A simple text editor", - "authors", authors, - "license-type", GTK_LICENSE_GPL_3_0, - "logo-icon-name", "org.gtk.daleclack", - "title", "About Simple text editor", - (char *)NULL); - // Free memory - g_free(version); - g_free(copyright); -} diff --git a/Gtk4_Reset/src/text_app/TextEditor.cpp b/Gtk4_Reset/src/text_app/TextEditor.cpp index c7d9c2b..58a9f2e 100644 --- a/Gtk4_Reset/src/text_app/TextEditor.cpp +++ b/Gtk4_Reset/src/text_app/TextEditor.cpp @@ -1,4 +1,10 @@ #include "TextEditor.h" +#include "MyInfoBar.h" +#include "../json_nlohmann/json.hpp" +#include +#include + +using json = nlohmann::json; struct _TextEditor { @@ -9,14 +15,188 @@ struct _TextEditor GtkTextBuffer *text_buffer; GtkWidget *menu_btn, *popover; GtkBuilder *menu_builder, *expander_builder; + // Input keyboard GtkWidget *expander; GtkWidget *btns[26]; GtkWidget *btnshift, *btntab, *btn_caps, *btnenter; + + // Info + MyInfoBar *info_bar; + + // Search Area + GtkWidget *btnsearch; + GtkWidget *search_box; + GtkWidget *search_bar, *search_entry; + GtkWidget *btn_up, *btn_down; + GtkTextIter curr_start, curr_end; + + // Others int width, height; + char file_path[PATH_MAX] = {0}; }; G_DEFINE_TYPE(TextEditor, text_editor, GTK_TYPE_APPLICATION_WINDOW) +static void text_dialog_open_file(GObject *dialog, GAsyncResult *result, gpointer data) +{ + GFile *file; + TextEditor *editor = TEXT_EDITOR(data); + char *contents; + gsize length; + + // Get file to open + file = gtk_file_dialog_open_finish(GTK_FILE_DIALOG(dialog), result, NULL); + if (file != NULL) + { + // If file opened, try to load contents + if (g_file_load_contents(file, NULL, &contents, &length, NULL, NULL)) + { + gtk_text_buffer_set_text(editor->text_buffer, contents, length); + + // Load path for file opened + auto path = g_file_get_path(file); + strncpy(editor->file_path, path, PATH_MAX); + g_free(path); + } + g_object_unref(file); + g_free(contents); + } +} + +static void text_editor_open_activated(GSimpleAction *action, + GVariant *parmeter, + gpointer data) +{ + TextEditor *editor = TEXT_EDITOR(data); + // Create GtkFileDialog + GtkFileDialog *dialog = gtk_file_dialog_new(); + gtk_file_dialog_open(dialog, GTK_WINDOW(editor), NULL, text_dialog_open_file, editor); +} + +static char *text_editor_get_text(TextEditor *editor) +{ + // Get Content of text buffer + GtkTextIter start_iter, end_iter; + gtk_text_buffer_get_start_iter(editor->text_buffer, &start_iter); + gtk_text_buffer_get_end_iter(editor->text_buffer, &end_iter); + char *content = gtk_text_buffer_get_text(editor->text_buffer, &start_iter, &end_iter, TRUE); + return content; +} + +static void text_dialog_save_file(GObject *dialog, GAsyncResult *result, gpointer data) +{ + GFile *file; + TextEditor *editor = TEXT_EDITOR(data); + + // Get Text + char *content = text_editor_get_text(editor); + + // Open the file to save text + file = gtk_file_dialog_save_finish(GTK_FILE_DIALOG(dialog), result, NULL); + if (file != NULL) + { + char *path = g_file_get_path(file); + if (g_file_set_contents(path, content, strlen(content), NULL)) + { + // Update path and show infomation + strncpy(editor->file_path, path, PATH_MAX); + my_infobar_show_message(editor->info_bar, "Content Saved!"); + } + } +} + +static void text_editor_save_activated(GSimpleAction *action, + GVariant *parmeter, + gpointer data) +{ + TextEditor *editor = TEXT_EDITOR(data); + + // Check the file for content + if (strlen(editor->file_path) != 0) + { + char *content = text_editor_get_text(editor); + // Save content to opened file + if (g_file_set_contents(editor->file_path, content, strlen(content), NULL)) + { + my_infobar_show_message(editor->info_bar, "Content saved!"); + } + g_free(content); + } + else + { + // Open a file dialog to save file + GtkFileDialog *dialog = gtk_file_dialog_new(); + gtk_file_dialog_save(dialog, GTK_WINDOW(editor), NULL, text_dialog_save_file, editor); + } +} + +static void text_editor_saveas_activated(GSimpleAction *action, + GVariant *parmeter, + gpointer data) +{ + // Open a file dialog to save file + GtkFileDialog *dialog = gtk_file_dialog_new(); + gtk_file_dialog_save(dialog, GTK_WINDOW(data), NULL, text_dialog_save_file, data); +} + +static void text_editor_copy_activated(GSimpleAction *action, + GVariant *parmeter, + gpointer data) +{ + TextEditor *editor = TEXT_EDITOR(data); + + // Get ClipBoard + GdkClipboard *clipboard = gtk_widget_get_clipboard(editor->text_view); + + // Get Selected text + gtk_text_buffer_copy_clipboard(editor->text_buffer, clipboard); + my_infobar_show_message(editor->info_bar, "Text Copied!"); +} + +static void text_editor_cut_activated(GSimpleAction *action, + GVariant *parmeter, + gpointer data) +{ + TextEditor *editor = TEXT_EDITOR(data); + + // Get ClipBoard + GdkClipboard *clipboard = gtk_widget_get_clipboard(editor->text_view); + + // Get Selected text + gtk_text_buffer_cut_clipboard(editor->text_buffer, clipboard, TRUE); + my_infobar_show_message(editor->info_bar, "Text Cutted!"); +} + +static void text_editor_paste_activated(GSimpleAction *action, + GVariant *parmeter, + gpointer data) +{ + TextEditor *editor = TEXT_EDITOR(data); + + // Get ClipBoard + GdkClipboard *clipboard = gtk_widget_get_clipboard(editor->text_view); + + // Get Selected text + GtkTextIter insert_iter; + gtk_text_buffer_paste_clipboard(editor->text_buffer, clipboard, NULL, TRUE); + my_infobar_show_message(editor->info_bar, "Text Pasted!"); +} + +static void text_editor_close_activated(GSimpleAction *action, + GVariant *parmeter, + gpointer data) +{ + TextEditor *editor = TEXT_EDITOR(data); + gtk_text_buffer_set_text(editor->text_buffer, "", 0); + gtk_window_close(GTK_WINDOW(data)); +} + +static void text_editor_about_activated(GSimpleAction *action, + GVariant *parmeter, + gpointer data) +{ +} + static void text_editor_btn_clicked(GtkButton *btn, TextEditor *self) { char tmp[2] = {0}; @@ -28,7 +208,9 @@ static void text_editor_btn_clicked(GtkButton *btn, TextEditor *self) { tmp[0] = label[0]; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->btnshift), FALSE); - }else{ + } + else + { tmp[0] = label[0] + 32; } @@ -48,13 +230,95 @@ static void text_editor_btntab_clicked(GtkButton *btn, TextEditor *self) gtk_text_buffer_insert_at_cursor(self->text_buffer, "\t", 1); } +static void text_editor_search_backward(GtkWidget *widget, TextEditor *self) +{ + GtkTextIter start_iter, match_start, match_end; + + // Get text to search + const char *search_text = gtk_editable_get_text(GTK_EDITABLE(self->search_entry)); + start_iter = self->curr_start; + if (gtk_text_iter_backward_search(&start_iter, search_text, GTK_TEXT_SEARCH_CASE_INSENSITIVE, + &match_start, &match_end, NULL)) + { + // Select text when search success + self->curr_start = match_start; + self->curr_end = match_end; + gtk_text_buffer_select_range(self->text_buffer, &match_start, &match_end); + gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(self->text_view), &match_start, + 0.0, FALSE, 0.0, 0.0); + } +} + +static void text_editor_search_forward(GtkWidget *widget, TextEditor *self) +{ + GtkTextIter start_iter, match_start, match_end; + + // Get text to search + const char *search_text = gtk_editable_get_text(GTK_EDITABLE(self->search_entry)); + start_iter = self->curr_end; + if (gtk_text_iter_forward_search(&start_iter, search_text, GTK_TEXT_SEARCH_CASE_INSENSITIVE, + &match_start, &match_end, NULL)) + { + // Select text when search success + self->curr_start = match_start; + self->curr_end = match_end; + gtk_text_buffer_select_range(self->text_buffer, &match_start, &match_end); + gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(self->text_view), &match_start, + 0.0, FALSE, 0.0, 0.0); + } +} + +static void text_editor_search_changed(GtkSearchEntry *entry, TextEditor *self) +{ + GtkTextIter start_iter, match_start, match_end; + + // Get text to search + const char *search_text = gtk_editable_get_text(GTK_EDITABLE(entry)); + gtk_text_buffer_get_start_iter(self->text_buffer, &start_iter); + if (gtk_text_iter_forward_search(&start_iter, search_text, GTK_TEXT_SEARCH_CASE_INSENSITIVE, + &match_start, &match_end, NULL)) + { + // Select text when search success + self->curr_start = match_start; + self->curr_end = match_end; + gtk_text_buffer_select_range(self->text_buffer, &match_start, &match_end); + gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(self->text_view), &match_start, + 0.0, FALSE, 0.0, 0.0); + } +} + static void text_editor_init(TextEditor *self) { + // Load window config from json file + int width = 800, height = 450; + std::ifstream json_file("text_config.json"); + if (json_file.is_open()) + { + json data = json::parse(json_file); + width = data["width"]; + height = data["height"]; + } + json_file.close(); + // Use headerbar for title and more info self->header = gtk_header_bar_new(); gtk_window_set_title(GTK_WINDOW(self), "Text editor"); gtk_window_set_titlebar(GTK_WINDOW(self), self->header); - gtk_window_set_default_size(GTK_WINDOW(self), 800, 450); + gtk_window_set_default_size(GTK_WINDOW(self), width, height); + + // Add Actions for menu + GActionEntry entries[] = + { + {"text_open", text_editor_open_activated, NULL, NULL, NULL}, + {"text_save", text_editor_save_activated, NULL, NULL, NULL}, + {"text_saveas", text_editor_saveas_activated, NULL, NULL, NULL}, + {"text_copy", text_editor_copy_activated, NULL, NULL, NULL}, + {"text_cut", text_editor_cut_activated, NULL, NULL, NULL}, + {"text_paste", text_editor_paste_activated, NULL, NULL, NULL}, + {"text_close", text_editor_close_activated, NULL, NULL, NULL}, + {"text_about", text_editor_about_activated, NULL, NULL, NULL}}; + g_action_map_add_action_entries(G_ACTION_MAP(self), + entries, G_N_ELEMENTS(entries), self); // Add menu button self->menu_btn = gtk_menu_button_new(); @@ -74,8 +338,32 @@ static void text_editor_init(TextEditor *self) self->main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); self->scrolled_win = gtk_scrolled_window_new(); + // Add Search Button + self->btnsearch = gtk_toggle_button_new(); + gtk_button_set_icon_name(GTK_BUTTON(self->btnsearch), "find"); + gtk_header_bar_pack_end(GTK_HEADER_BAR(self->header), self->btnsearch); + + // Add a search bar + self->search_bar = gtk_search_bar_new(); + self->search_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + self->btn_up = gtk_button_new_from_icon_name("up"); + self->btn_down = gtk_button_new_from_icon_name("down"); + self->search_entry = gtk_search_entry_new(); + gtk_box_append(GTK_BOX(self->search_box), self->search_entry); + gtk_box_append(GTK_BOX(self->search_box), self->btn_up); + gtk_box_append(GTK_BOX(self->search_box), self->btn_down); + gtk_search_bar_set_child(GTK_SEARCH_BAR(self->search_bar), self->search_box); + gtk_box_append(GTK_BOX(self->main_box), self->search_bar); + g_object_bind_property(self->btnsearch, "active", + self->search_bar, "search-mode-enabled", G_BINDING_DEFAULT); + g_signal_connect(self->search_entry, "search-changed", + G_CALLBACK(text_editor_search_changed), self); + g_signal_connect(self->btn_up, "clicked", G_CALLBACK(text_editor_search_backward), self); + g_signal_connect(self->btn_down, "clicked", G_CALLBACK(text_editor_search_forward), self); + // Add info area - + self->info_bar = my_infobar_new(); + gtk_box_append(GTK_BOX(self->main_box), GTK_WIDGET(self->info_bar)); // Create text view self->text_view = gtk_text_view_new(); @@ -95,7 +383,7 @@ static void text_editor_init(TextEditor *self) self->btnenter = GTK_WIDGET(gtk_builder_get_object(self->expander_builder, "btnenter")); self->btnshift = GTK_WIDGET(gtk_builder_get_object(self->expander_builder, "btnshift")); self->btntab = GTK_WIDGET(gtk_builder_get_object(self->expander_builder, "btntab")); - for(int i = 0; i < 26; i++) + for (int i = 0; i < 26; i++) { char name[10]; sprintf(name, "btn%d", i); @@ -115,4 +403,26 @@ static void text_editor_class_init(TextEditorClass *klass) TextEditor *text_editor_new() { return TEXT_EDITOR(g_object_new(text_editor_get_type(), NULL)); -} \ No newline at end of file +} + +void text_editor_save_config(TextEditor *self) +{ + // Create json raw data + json data = json::parse(R"({ + "width":800, + "height":450 + })"); + + // Override config in json file + data["width"] = gtk_widget_get_width(self->main_box); + data["height"] = gtk_widget_get_height(self->main_box); + + // Output json data to file + std::fstream outfile; + outfile.open("text_config.json", std::ios_base::out); + if (outfile.is_open()) + { + outfile << data; + } + outfile.close(); +} diff --git a/Gtk4_Reset/src/ui/MyDock.cpp b/Gtk4_Reset/src/ui/MyDock.cpp index cbdd0cf..dea9660 100644 --- a/Gtk4_Reset/src/ui/MyDock.cpp +++ b/Gtk4_Reset/src/ui/MyDock.cpp @@ -421,6 +421,7 @@ static void btnedit_clicked(GtkWidget *widget, MyDock *dock) static gboolean edit_win_closed(GtkWidget *win, MyDock *dock) { // Hide the window + text_editor_save_config(TEXT_EDITOR(win)); gtk_widget_set_visible(win, FALSE); gtk_image_set_from_icon_name(GTK_IMAGE(dock->image_edit), "my_textedit"); return TRUE;