From 0fdb31dc27889a3814f99af1c661fe91177b048a Mon Sep 17 00:00:00 2001 From: Konstantin Dmitriev Date: Oct 10 2019 09:24:54 +0000 Subject: Merge PR #961: Save custom workspaces. Closes #245. --- diff --git a/synfig-studio/po/POTFILES.in b/synfig-studio/po/POTFILES.in index cf538eb..a9d4400 100644 --- a/synfig-studio/po/POTFILES.in +++ b/synfig-studio/po/POTFILES.in @@ -7,6 +7,7 @@ plugins/lottie-exporter/plugin.xml.in plugins/view-unhide-all-layers/plugin.xml.in ui/canvas_options.ui ui/preview_options.ui +ui/dialog_workspaces.ui src/brushlib/brushsettings.py src/gui/actionmanagers/groupactionmanager.cpp src/gui/actionmanagers/groupactionmanager.h @@ -49,6 +50,8 @@ src/gui/dialogs/dialog_template.cpp src/gui/dialogs/dialog_template.h src/gui/dialogs/dialog_waypoint.cpp src/gui/dialogs/dialog_waypoint.h +src/gui/dialogs/dialog_workspaces.cpp +src/gui/dialogs/dialog_workspaces.h src/gui/dials/framedial.cpp src/gui/dials/framedial.h src/gui/dials/jackdial.cpp diff --git a/synfig-studio/src/gui/CMakeLists.txt b/synfig-studio/src/gui/CMakeLists.txt index 4da99df..e011e90 100644 --- a/synfig-studio/src/gui/CMakeLists.txt +++ b/synfig-studio/src/gui/CMakeLists.txt @@ -51,6 +51,7 @@ target_sources(synfigstudio "${CMAKE_CURRENT_LIST_DIR}/statemanager.cpp" "${CMAKE_CURRENT_LIST_DIR}/valuelink.cpp" "${CMAKE_CURRENT_LIST_DIR}/workarea.cpp" + "${CMAKE_CURRENT_LIST_DIR}/workspacehandler.cpp" "${CMAKE_CURRENT_LIST_DIR}/main_win32.cpp" "${CMAKE_CURRENT_LIST_DIR}/mainwindow.cpp" "${CMAKE_CURRENT_LIST_DIR}/helpers.cpp" diff --git a/synfig-studio/src/gui/Makefile.am b/synfig-studio/src/gui/Makefile.am index ae6cddf..65fb4f8 100644 --- a/synfig-studio/src/gui/Makefile.am +++ b/synfig-studio/src/gui/Makefile.am @@ -64,6 +64,7 @@ OTHER_HH = \ statemanager.h \ valuelink.h \ workarea.h \ + workspacehandler.h \ main_win32.h \ mainwindow.h \ helpers.h @@ -91,6 +92,7 @@ OTHER_CC = \ statemanager.cpp \ valuelink.cpp \ workarea.cpp \ + workspacehandler.cpp \ main_win32.cpp \ mainwindow.cpp \ helpers.cpp diff --git a/synfig-studio/src/gui/app.cpp b/synfig-studio/src/gui/app.cpp index 6de1c51..335fd85 100644 --- a/synfig-studio/src/gui/app.cpp +++ b/synfig-studio/src/gui/app.cpp @@ -95,6 +95,7 @@ #include "dialogs/dialog_gradient.h" #include "dialogs/dialog_input.h" #include "dialogs/dialog_setup.h" +#include "dialogs/dialog_workspaces.h" #include "dialogs/vectorizersettings.h" #include "onemoment.h" #include "devicetracker.h" @@ -156,6 +157,7 @@ #include #include "gui/resourcehelper.h" +#include "gui/workspacehandler.h" #endif @@ -211,6 +213,13 @@ static sigc::signal signal_recent_files_changed_; sigc::signal& App::signal_recent_files_changed() { return signal_recent_files_changed_; } +static sigc::signal signal_custom_workspaces_changed_; +sigc::signal& +App::signal_custom_workspaces_changed() +{ + return signal_custom_workspaces_changed_; +} + static sigc::signal > signal_canvas_view_focus_; sigc::signal >& App::signal_canvas_view_focus() { return signal_canvas_view_focus_; } @@ -232,6 +241,15 @@ App::signal_instance_deleted() { return signal_instance_deleted_; } static std::list recent_files; const std::list& App::get_recent_files() { return recent_files; } +const std::vector +App::get_workspaces() +{ + std::vector list; + if (workspaces) + workspaces->get_name_list(list); + return list; +} + int App::Busy::count; bool App::shutdown_in_progress; @@ -330,6 +348,8 @@ void studio::App::set_max_recent_files(int x) { max_recent_files_ = x; static synfig::String app_base_path_; +studio::WorkspaceHandler *studio::App::workspaces = nullptr; + namespace studio { bool @@ -1091,6 +1111,7 @@ DEFINE_ACTION("amount-dec", _("Decrease Layer Amount")) DEFINE_ACTION("workspace-compositing", _("Compositing")); DEFINE_ACTION("workspace-default", _("Default")); DEFINE_ACTION("workspace-animating", _("Animating")); +DEFINE_ACTION("save-workspace", _("Save workspace...")); DEFINE_ACTION("dialog-flipbook", _("Preview Dialog")); DEFINE_ACTION("panel-toolbox", _("Toolbox")); DEFINE_ACTION("panel-tool_options", _("Tool Options")); @@ -1681,6 +1702,11 @@ App::App(const synfig::String& basepath, int *argc, char ***argv): dialog_input=new studio::Dialog_Input(*App::main_window); dialog_input->signal_apply().connect( sigc::mem_fun( *device_tracker, &DeviceTracker::save_preferences) ); + studio_init_cb.task(_("Loading Custom Workspace List...")); + workspaces = new WorkspaceHandler(); + workspaces->signal_list_changed().connect( sigc::mem_fun(signal_custom_workspaces_changed_, &sigc::signal::emit) ); + load_custom_workspaces(); + studio_init_cb.task(_("Init auto recovery...")); auto_recover=new AutoRecover(); @@ -1894,6 +1920,8 @@ App::~App() delete dock_manager; + delete workspaces; + instance_list.clear(); if (sound_render_done) delete sound_render_done; @@ -2048,6 +2076,10 @@ App::save_settings() std::string filename=get_config_file("settings-1.3"); synfigapp::Main::settings().save_to_file(filename); + { + std::string filename = get_config_file("workspaces"); + workspaces->save(filename); + } } catch(...) { @@ -2145,19 +2177,6 @@ App::load_language_settings() void App::set_workspace_default() { - Glib::RefPtr display(Gdk::Display::get_default()); - Glib::RefPtr screen(display->get_default_screen()); - Gdk::Rectangle rect; - // A proper way to obtain the primary monitor is to use the - // Gdk::Screen::get_primary_monitor () const member. But as it - // was introduced in gtkmm 2.20 I assume that the monitor 0 is the - // primary one. - screen->get_monitor_geometry(0,rect); - float dx = (float)rect.get_x(); - float dy = (float)rect.get_y(); - float sx = (float)rect.get_width(); - float sy = (float)rect.get_height(); - std::string tpl = "[mainwindow|%0X|%0Y|%100x|%90y|" "[hor|%75x" @@ -2181,27 +2200,12 @@ App::set_workspace_default() "]" "]"; - std::string layout = DockManager::layout_from_template(tpl, dx, dy, sx, sy); - dock_manager->load_layout_from_string(layout); - dock_manager->show_all_dock_dialogs(); + set_workspace_from_template(tpl); } void App::set_workspace_compositing() { - Glib::RefPtr display(Gdk::Display::get_default()); - Glib::RefPtr screen(display->get_default_screen()); - Gdk::Rectangle rect; - // A proper way to obtain the primary monitor is to use the - // Gdk::Screen::get_primary_monitor () const member. But as it - // was introduced in gtkmm 2.20 I assume that the monitor 0 is the - // primary one. - screen->get_monitor_geometry(0,rect); - float dx = (float)rect.get_x(); - float dy = (float)rect.get_y(); - float sx = (float)rect.get_width(); - float sy = (float)rect.get_height(); - std::string tpl = "[mainwindow|%0X|%0Y|%100x|%90y|" "[hor|%1x" @@ -2213,27 +2217,12 @@ App::set_workspace_compositing() "]" "]"; - std::string layout = DockManager::layout_from_template(tpl, dx, dy, sx, sy); - dock_manager->load_layout_from_string(layout); - dock_manager->show_all_dock_dialogs(); + set_workspace_from_template(tpl); } void App::set_workspace_animating() { - Glib::RefPtr display(Gdk::Display::get_default()); - Glib::RefPtr screen(display->get_default_screen()); - Gdk::Rectangle rect; - // A proper way to obtain the primary monitor is to use the - // Gdk::Screen::get_primary_monitor () const member. But as it - // was introduced in gtkmm 2.20 I assume that the monitor 0 is the - // primary one. - screen->get_monitor_geometry(0,rect); - float dx = (float)rect.get_x(); - float dy = (float)rect.get_y(); - float sx = (float)rect.get_width(); - float sy = (float)rect.get_height(); - std::string tpl = "[mainwindow|%0X|%0Y|%100x|%90y|" "[hor|%70x" @@ -2247,11 +2236,113 @@ App::set_workspace_animating() "]" "]"; + set_workspace_from_template(tpl); +} + +void App::set_workspace_from_template(const string& tpl) +{ + Glib::RefPtr display(Gdk::Display::get_default()); + Glib::RefPtr screen(display->get_default_screen()); + Gdk::Rectangle rect; + // A proper way to obtain the primary monitor is to use the + // Gdk::Screen::get_primary_monitor () const member. But as it + // was introduced in gtkmm 2.20 I assume that the monitor 0 is the + // primary one. + screen->get_monitor_geometry(0,rect); + float dx = (float)rect.get_x(); + float dy = (float)rect.get_y(); + float sx = (float)rect.get_width(); + float sy = (float)rect.get_height(); + std::string layout = DockManager::layout_from_template(tpl, dx, dy, sx, sy); dock_manager->load_layout_from_string(layout); dock_manager->show_all_dock_dialogs(); } +void App::set_workspace_from_name(const string& name) +{ + std::string tpl; + bool ok = workspaces->get_workspace(name, tpl); + if (!ok) + return; + set_workspace_from_template(tpl); +} + +void App::load_custom_workspaces() +{ + workspaces->clear(); + std::string filename = get_config_file("workspaces"); + workspaces->load(filename); +} + +static void trim_string(std::string &text) +{ + text.erase(text.begin(), + std::find_if(text.begin(), text.end(), + [](int chr) { return !std::isspace(chr);}) + ); + text.erase(std::find_if(text.rbegin(), text.rend(), + [](int chr) { return !std::isspace(chr);}).base(), + text.end() + ); +} + +void App::save_custom_workspace() +{ + Gtk::MessageDialog dialog(*App::main_window, _("Type a name for this custom workspace:"), false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE); + + dialog.add_button(_("Cancel"), Gtk::RESPONSE_CANCEL); + Gtk::Button * ok_button = dialog.add_button(_("Ok"), Gtk::RESPONSE_OK); + ok_button->set_sensitive(false); + + Gtk::Entry * name_entry = Gtk::manage(new Gtk::Entry()); + name_entry->set_margin_left(16); + name_entry->set_margin_right(16); + name_entry->signal_changed().connect([&](){ + std::string name = name_entry->get_text(); + trim_string(name); + bool has_equal_sign = name.find("=") != std::string::npos; + ok_button->set_sensitive(!name.empty() && !has_equal_sign); + if (ok_button->is_sensitive()) + ok_button->grab_default(); + }); + name_entry->signal_activate().connect(sigc::mem_fun(*ok_button, &Gtk::Button::clicked)); + + dialog.get_content_area()->set_spacing(12); + dialog.get_content_area()->add(*name_entry); + + ok_button->set_can_default(true); + + dialog.show_all(); + + int response = dialog.run(); + if (response == Gtk::RESPONSE_CANCEL) + return; + + std::string name = name_entry->get_text(); + trim_string(name); + + std::string tpl = dock_manager->save_layout_to_string(); + if (!workspaces->has_workspace(name)) + workspaces->add_workspace(name, tpl); + else { + Gtk::MessageDialog confirm_dlg(dialog, _("Do you want to overwrite this workspace?"), false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL); + if (confirm_dlg.run() == Gtk::RESPONSE_CANCEL) + return; + workspaces->set_workspace(name, tpl); + } +} + +void App::edit_custom_workspace_list() +{ + Dialog_Workspaces * dlg = Dialog_Workspaces::create(*App::main_window); + if (!dlg) { + synfig::warning("Can't load Dialog_Workspaces"); + return; + } + dlg->run(); + delete dlg; +} void App::restore_default_settings() diff --git a/synfig-studio/src/gui/app.h b/synfig-studio/src/gui/app.h index 45ab2bd..bbd6afa 100644 --- a/synfig-studio/src/gui/app.h +++ b/synfig-studio/src/gui/app.h @@ -124,6 +124,8 @@ class Module; class StateManager; class IconController; +class WorkspaceHandler; + class App : public Gtk::Main, private IconController { friend class Preferences; @@ -183,6 +185,8 @@ private: // static std::list< etl::handle< Module > > module_list_; + static WorkspaceHandler *workspaces; + /* -- ** -- P U B L I C D A T A ----------------------------------------------- */ @@ -252,6 +256,8 @@ public: static Dock_Info* dock_info_; //For Render ProgressBar + static WorkspaceHandler * get_workspace_handler() {return workspaces;} + /* -- ** -- S I G N A L S ------------------------------------------------------- */ @@ -281,6 +287,8 @@ public: static sigc::signal &signal_recent_files_changed(); + static sigc::signal &signal_custom_workspaces_changed(); + static sigc::signal< void, etl::loose_handle @@ -338,11 +346,18 @@ public: static void set_workspace_default(); static void set_workspace_compositing(); static void set_workspace_animating(); + static void set_workspace_from_template(const std::string &tpl); + static void set_workspace_from_name(const std::string &name); + static void load_custom_workspaces(); + static void save_custom_workspace(); + static void edit_custom_workspace_list(); static void restore_default_settings(); static void apply_gtk_settings(); static const std::list& get_recent_files(); + static const std::vector get_workspaces(); + static const etl::handle& get_ui_interface(); diff --git a/synfig-studio/src/gui/dialogs/CMakeLists.txt b/synfig-studio/src/gui/dialogs/CMakeLists.txt index 52f99df..049bd8a 100644 --- a/synfig-studio/src/gui/dialogs/CMakeLists.txt +++ b/synfig-studio/src/gui/dialogs/CMakeLists.txt @@ -14,6 +14,7 @@ target_sources(synfigstudio "${CMAKE_CURRENT_LIST_DIR}/dialog_ffmpegparam.cpp" "${CMAKE_CURRENT_LIST_DIR}/dialog_spritesheetparam.cpp" "${CMAKE_CURRENT_LIST_DIR}/dialog_waypoint.cpp" + "${CMAKE_CURRENT_LIST_DIR}/dialog_workspaces.cpp" "${CMAKE_CURRENT_LIST_DIR}/dialog_template.cpp" "${CMAKE_CURRENT_LIST_DIR}/vectorizersettings.cpp" diff --git a/synfig-studio/src/gui/dialogs/Makefile_insert b/synfig-studio/src/gui/dialogs/Makefile_insert index 5306c31..6319717 100644 --- a/synfig-studio/src/gui/dialogs/Makefile_insert +++ b/synfig-studio/src/gui/dialogs/Makefile_insert @@ -13,6 +13,7 @@ DIALOGS_HH = \ dialogs/dialog_ffmpegparam.h \ dialogs/dialog_spritesheetparam.h \ dialogs/dialog_waypoint.h \ + dialogs/dialog_workspaces.h \ dialogs/dialog_template.h \ dialogs/vectorizersettings.h @@ -31,10 +32,10 @@ DIALOGS_CC = \ dialogs/dialog_ffmpegparam.cpp \ dialogs/dialog_spritesheetparam.cpp \ dialogs/dialog_waypoint.cpp \ + dialogs/dialog_workspaces.cpp \ dialogs/dialog_template.cpp \ dialogs/vectorizersettings.cpp - synfigstudio_src += \ $(DIALOGS_HH) \ $(DIALOGS_CC) diff --git a/synfig-studio/src/gui/dialogs/dialog_workspaces.cpp b/synfig-studio/src/gui/dialogs/dialog_workspaces.cpp new file mode 100644 index 0000000..202a59b --- /dev/null +++ b/synfig-studio/src/gui/dialogs/dialog_workspaces.cpp @@ -0,0 +1,210 @@ + +#ifdef USING_PCH +# include "pch.h" +#else +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "dialog_workspaces.h" + +#include "gui/resourcehelper.h" +#include "gui/localization.h" +#include "gui/app.h" +#include "gui/workspacehandler.h" + +#include + +#include +#include +#include +#include +#include + +#endif + +using namespace studio; + +static Glib::RefPtr load_interface() { + auto refBuilder = Gtk::Builder::create(); + try + { + refBuilder->add_from_file(ResourceHelper::get_ui_path("dialog_workspaces.ui")); + } + catch(const Glib::FileError& ex) + { + synfig::error("FileError: " + ex.what()); + return Glib::RefPtr(); + } + catch(const Glib::MarkupError& ex) + { + synfig::error("MarkupError: " + ex.what()); + return Glib::RefPtr(); + } + catch(const Gtk::BuilderError& ex) + { + synfig::error("BuilderError: " + ex.what()); + return Glib::RefPtr(); + } + return refBuilder; +} + +class WorkspaceCols: public Gtk::TreeModel::ColumnRecord { + public: + WorkspaceCols() { + this->add(this->col_name); + } + + Gtk::TreeModelColumn col_name; +}; + +Dialog_Workspaces::Dialog_Workspaces(Gtk::Dialog::BaseObjectType* cobject, const Glib::RefPtr& refGlade) : + Gtk::Dialog(cobject), + builder(refGlade), + rename_button(nullptr), + delete_button(nullptr) +{ + Gtk::Button *button = nullptr; + + refGlade->get_widget("workspaces_close_button", button); + if (button) + button->signal_clicked().connect(sigc::mem_fun(*this, &Gtk::Dialog::close)); + + refGlade->get_widget("workspaces_delete_button", delete_button); + if (delete_button) + delete_button->signal_clicked().connect(sigc::mem_fun(*this, &Dialog_Workspaces::on_delete_clicked)); + + refGlade->get_widget("workspaces_rename_button", rename_button); + if (rename_button) + rename_button->signal_clicked().connect(sigc::mem_fun(*this, &Dialog_Workspaces::on_rename_clicked)); + + Gtk::TreeView * workspace_treeview = nullptr; + refGlade->get_widget("workspaces_treeview", workspace_treeview); + if (workspace_treeview) { + current_selection = workspace_treeview->get_selection(); + current_selection->signal_changed().connect(sigc::mem_fun(*this, &Dialog_Workspaces::on_selection_changed)); + + workspace_model = Glib::RefPtr::cast_dynamic( + refGlade->get_object("workspaces_liststore") + ); +// WorkspaceCols ws_cols; +// Gtk::TreeModel::Row row = *workspace_model->append(); +// row[ws_cols.col_name] = "ui"; +// workspace_model->append()->set_value(0, Glib::ustring("ui")); + + App::signal_custom_workspaces_changed().connect(sigc::mem_fun(*this, &Dialog_Workspaces::rebuild_list)); + + rebuild_list(); + } +} + +Dialog_Workspaces* Dialog_Workspaces::create(Gtk::Window& parent) +{ + auto refBuilder = load_interface(); + if (!refBuilder) + return nullptr; + Dialog_Workspaces * dialog = nullptr; + refBuilder->get_widget_derived("dialog_workspaces", dialog); + if (dialog) { + dialog->set_transient_for(parent); + } + return dialog; +} + +Dialog_Workspaces::~Dialog_Workspaces() +{ + +} + +void Dialog_Workspaces::on_selection_changed() +{ + int count = current_selection->count_selected_rows(); + rename_button->set_sensitive(count == 1); + delete_button->set_sensitive(count > 0); +} + +void Dialog_Workspaces::on_delete_clicked() +{ + char msg[256]; + snprintf(msg, 255, _("Are you sure you want to delete %d workspaces?"), current_selection->count_selected_rows()); + Gtk::MessageDialog confirm_dlg(*this, msg, false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, true); + if (confirm_dlg.run() == Gtk::RESPONSE_NO) + return; + + // get_selected_rows() return TreePath not TreeIter + // So, deleting an item, invalidates the other TreePaths (they point to wrong items) + std::vector names; + for (auto selected_path : current_selection->get_selected_rows()) { + std::string name; + workspace_model->get_iter(selected_path)->get_value(0, name); + names.push_back(name); + } + for (const std::string & name : names) { + App::get_workspace_handler()->remove_workspace(name); + } +} + +void Dialog_Workspaces::on_rename_clicked() +{ + std::string old_name; + auto selected_path = current_selection->get_selected_rows()[0]; + workspace_model->get_iter(selected_path)->get_value(0, old_name); + + Gtk::MessageDialog dialog(*this, _("Type a name for this custom workspace:"), false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE, true); + dialog.add_button(_("Cancel"), Gtk::RESPONSE_CANCEL); + Gtk::Button * ok_button = dialog.add_button(_("Ok"), Gtk::RESPONSE_OK); + ok_button->set_sensitive(false); + + Gtk::Entry * name_entry = Gtk::manage(new Gtk::Entry()); + name_entry->set_margin_left(16); + name_entry->set_margin_right(16); + name_entry->signal_changed().connect([&](){ + std::string name = name_entry->get_text(); + WorkspaceHandler::trim_string(name); + bool has_equal_sign = name.find("=") != std::string::npos; + ok_button->set_sensitive(!name.empty() && !has_equal_sign); + if (ok_button->is_sensitive()) + ok_button->grab_default(); + }); + name_entry->signal_activate().connect(sigc::mem_fun(*ok_button, &Gtk::Button::clicked)); + name_entry->set_text(old_name); + + dialog.get_content_area()->set_spacing(12); + dialog.get_content_area()->add(*name_entry); + + ok_button->set_can_default(true); + + dialog.show_all(); + + int response = dialog.run(); + if (response == Gtk::RESPONSE_CANCEL) + return; + + std::string name = name_entry->get_text(); + WorkspaceHandler::trim_string(name); + + if (old_name == name) + return; + + if (App::get_workspace_handler()->has_workspace(name)) { + Gtk::MessageDialog error_dlg(dialog, _("There is already a workspace with this name."), false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK); + error_dlg.run(); + return; + } + + std::string tpl; + App::get_workspace_handler()->get_workspace(old_name, tpl); + App::get_workspace_handler()->remove_workspace(old_name); + App::get_workspace_handler()->add_workspace(name, tpl); +} + +void Dialog_Workspaces::rebuild_list() +{ + workspace_model->clear(); + + WorkspaceHandler *workspaces = App::get_workspace_handler(); + std::vector names; + workspaces->get_name_list(names); + for (const std::string & name : names) + workspace_model->append()->set_value(0, name); +} diff --git a/synfig-studio/src/gui/dialogs/dialog_workspaces.h b/synfig-studio/src/gui/dialogs/dialog_workspaces.h new file mode 100644 index 0000000..094daae --- /dev/null +++ b/synfig-studio/src/gui/dialogs/dialog_workspaces.h @@ -0,0 +1,41 @@ +#ifndef SYNFIG_STUDIO_DIALOG_WORKSPACES_H +#define SYNFIG_STUDIO_DIALOG_WORKSPACES_H + +#include +#include + +namespace Gtk { +class Button; +class TreeSelection; +class ListStore; +} + +namespace studio +{ + +class Dialog_Workspaces : public Gtk::Dialog +{ + const Glib::RefPtr& builder; + + Gtk::Button * rename_button; + Gtk::Button * delete_button; + + Glib::RefPtr current_selection; + Glib::RefPtr workspace_model; + +public: + Dialog_Workspaces(BaseObjectType* cobject, const Glib::RefPtr& refGlade); + static Dialog_Workspaces * create(Gtk::Window& parent); + ~Dialog_Workspaces(); + +private: + void on_selection_changed(); + void on_delete_clicked(); + void on_rename_clicked(); + + void rebuild_list(); +}; + +} + +#endif // SYNFIG_STUDIO_DIALOG_WORKSPACES_H diff --git a/synfig-studio/src/gui/mainwindow.cpp b/synfig-studio/src/gui/mainwindow.cpp index bf9a4a0..4f9fea3 100644 --- a/synfig-studio/src/gui/mainwindow.cpp +++ b/synfig-studio/src/gui/mainwindow.cpp @@ -76,7 +76,8 @@ using namespace studio; /* === M E T H O D S ======================================================= */ -MainWindow::MainWindow() +MainWindow::MainWindow() : + save_workspace_merge_id(0), custom_workspaces_merge_id(0) { register_custom_widget_types(); @@ -122,6 +123,9 @@ MainWindow::MainWindow() App::signal_recent_files_changed().connect( sigc::mem_fun(*this, &MainWindow::on_recent_files_changed) ); + App::signal_custom_workspaces_changed().connect( + sigc::mem_fun(*this, &MainWindow::on_custom_workspaces_changed) ); + signal_delete_event().connect( sigc::ptr_fun(App::shutdown_request) ); @@ -182,6 +186,13 @@ MainWindow::init_menus() action_group->add( Gtk::Action::create("workspace-default", _("Default")), sigc::ptr_fun(App::set_workspace_default) ); + action_group->add( Gtk::Action::create("save-workspace", Gtk::StockID("synfig-save_as"), _("Save workspace...")), + sigc::ptr_fun(App::save_custom_workspace) + ); + + action_group->add( Gtk::Action::create("edit-workspacelist", _("Edit workspace list...")), + sigc::ptr_fun(App::edit_custom_workspace_list) + ); // help #define URL(action_name,title,url) \ @@ -209,6 +220,8 @@ MainWindow::init_menus() //filemenu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Open Recent"),*recent_files_menu)); App::ui_manager()->insert_action_group(action_group); + + add_custom_workspace_menu_item_handlers(); } void MainWindow::register_custom_widget_types() @@ -230,6 +243,31 @@ MainWindow::toggle_show_menubar() menubar->hide(); } +void MainWindow::add_custom_workspace_menu_item_handlers() +{ + std::string ui_info_menu = + "" + " " + " " + " " + " " + " " + ""; + + std::string ui_info = + "" + " " + ui_info_menu + "" + " " + ui_info_menu + "" + ""; + + save_workspace_merge_id = App::ui_manager()->add_ui_from_string(ui_info); +} + +void MainWindow::remove_custom_workspace_menu_item_handlers() +{ + App::ui_manager()->remove_ui(save_workspace_merge_id); +} + bool MainWindow::on_key_press_event(GdkEventKey* key_event) { @@ -380,6 +418,65 @@ MainWindow::on_recent_files_changed() } void +MainWindow::on_custom_workspaces_changed() +{ + Glib::RefPtr action_group = Gtk::ActionGroup::create("mainwindow-customworkspaces"); + + vector workspaces = App::get_workspaces(); + + std::string menu_items; + unsigned int num_custom_workspaces = 0; + for (auto it = workspaces.cbegin(); it != workspaces.cend(); ++it, ++num_custom_workspaces) { + std::string raw = *it; + std::string quoted; + size_t pos = 0, last_pos = 0; + + // replace _ in names by __ or it won't show up in the menu + for (pos = last_pos = 0; (pos = raw.find('_', pos)) != string::npos; last_pos = pos) + quoted += raw.substr(last_pos, ++pos - last_pos) + '_'; + quoted += raw.substr(last_pos); + + std::string action_name = strprintf("custom-workspace-%d", num_custom_workspaces); + menu_items += ""; + + action_group->add( Gtk::Action::create(action_name, quoted), + sigc::bind(sigc::ptr_fun(&App::set_workspace_from_name), workspaces[num_custom_workspaces]) + ); + } + if (num_custom_workspaces > 0) + menu_items = "" + menu_items; + + remove_custom_workspace_menu_item_handlers(); + if (custom_workspaces_merge_id) + App::ui_manager()->remove_ui(custom_workspaces_merge_id); + + std::string ui_info = + "" + + menu_items + + ""; + std::string ui_info_popup = + "" + ui_info + ""; + std::string ui_info_menubar = + "" + ui_info + ""; + + // remove group if exists + typedef std::vector< Glib::RefPtr > ActionGroupList; + ActionGroupList groups = App::ui_manager()->get_action_groups(); + for(ActionGroupList::const_iterator it = groups.begin(); it != groups.end(); ++it) + if ((*it)->get_name() == action_group->get_name()) { + App::ui_manager()->remove_action_group(*it); + break; + } + groups.clear(); + App::ui_manager()->ensure_update(); + + App::ui_manager()->insert_action_group(action_group); + custom_workspaces_merge_id = App::ui_manager()->add_ui_from_string("" + ui_info_popup + ui_info_menubar + ""); + + add_custom_workspace_menu_item_handlers(); +} + +void MainWindow::on_dockable_registered(Dockable* dockable) { diff --git a/synfig-studio/src/gui/mainwindow.h b/synfig-studio/src/gui/mainwindow.h index 9bc1b44..d039cee 100644 --- a/synfig-studio/src/gui/mainwindow.h +++ b/synfig-studio/src/gui/mainwindow.h @@ -59,10 +59,17 @@ namespace studio { static void show_dialog_input(); void on_recent_files_changed(); + void on_custom_workspaces_changed(); void on_dockable_registered(Dockable* dockable); void on_dockable_unregistered(Dockable* dockable); void toggle_show_menubar(); + guint save_workspace_merge_id; + guint custom_workspaces_merge_id; + + void add_custom_workspace_menu_item_handlers(); + void remove_custom_workspace_menu_item_handlers(); + protected: virtual bool on_key_press_event(GdkEventKey *key_event); diff --git a/synfig-studio/src/gui/workspacehandler.cpp b/synfig-studio/src/gui/workspacehandler.cpp new file mode 100644 index 0000000..0e12442 --- /dev/null +++ b/synfig-studio/src/gui/workspacehandler.cpp @@ -0,0 +1,180 @@ +/* === S Y N F I G ========================================================= */ +/*! \file workspacehandler.cpp +** \brief Handle with custom workspaces +** +** $Id$ +** +** \legal +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** Copyright (c) 2019 Rodolfo R Gomes +** +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. +** +** This package is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** \endlegal +*/ + +/* ========================================================================= */ + +#ifdef USING_PCH +# include "pch.h" +#else +# ifdef HAVE_CONFIG_H +# include +# endif +# include "workspacehandler.h" +# include +# include +# include "synfig/general.h" +# include "gui/localization.h" +#endif + +using namespace studio; + +WorkspaceHandler::WorkspaceHandler() +{ +} + +void +WorkspaceHandler::trim_string(std::string &text) +{ + text.erase(text.begin(), + std::find_if(text.begin(), text.end(), + [](int chr) { return !std::isspace(chr);}) + ); + text.erase(std::find_if(text.rbegin(), text.rend(), + [](int chr) { return !std::isspace(chr);}).base(), + text.end() + ); +} + +bool +WorkspaceHandler::is_valid_name(const std::string& name) +{ + std::string valid_name = name; + trim_string(valid_name); + return !valid_name.empty() && valid_name.find("=") == std::string::npos; +} + +bool +WorkspaceHandler::has_workspace(const std::string& name) const +{ + return workspaces.find(name) != workspaces.end(); +} + +bool +WorkspaceHandler::add_workspace(const std::string& name, const std::string& tpl) +{ + if (!is_valid_name(name) || tpl.empty()) + return false; + std::string valid_name(name); + trim_string(valid_name); + if (has_workspace(valid_name)) + return false; + workspaces[valid_name] = tpl; + signal_list_changed_.emit(); + return true; +} + +void +WorkspaceHandler::remove_workspace(const std::string& name) +{ + size_t count = workspaces.erase(name); + if (count > 0) + signal_list_changed_.emit(); +} + +void WorkspaceHandler::clear() +{ + size_t previous_size = workspaces.size(); + workspaces.clear(); + if (previous_size > 0) + signal_list_changed_.emit(); +} + +bool +WorkspaceHandler::set_workspace(const std::string& name, const std::string& tpl) +{ + if (!is_valid_name(name) || tpl.empty()) + return false; + std::string valid_name(name); + trim_string(valid_name); + if (!has_workspace(valid_name)) + return false; + workspaces[valid_name] = tpl; + return true; +} + +bool +WorkspaceHandler::get_workspace(const std::string& name, std::string& tpl) const +{ + auto it = workspaces.find(name); + if (it == workspaces.end()) + return false; + tpl = it->second; + return true; +} + +void +WorkspaceHandler::get_name_list(std::vector& list) +{ + list.clear(); + for(auto it = workspaces.cbegin(); it != workspaces.cend(); ++it) + list.push_back(it->first); +} + +bool +WorkspaceHandler::save(const std::string& filename) +{ + std::ofstream ofs(filename); + if (!ofs) { + synfig::error(_("Can't save custom workspaces")); + return false; + } + for (auto it = workspaces.begin(); it != workspaces.end(); ++it) + ofs << it->first << "=" << it->second << std::endl; + ofs.close(); + return true; +} + +void +WorkspaceHandler::load(const std::string& filename) +{ + std::ifstream ifs(filename); + std::string line; + int count = 0; + while (ifs && !ifs.eof()) { + getline(ifs, line); + if (line.empty()) + continue; + + auto pos = line.find("="); + if (pos == std::string::npos) { + synfig::warning(_("ignoring malformed workspace line: %s"), line.c_str()); + continue; + } + + std::string name = line.substr(0, pos); + if (has_workspace(name)) { + synfig::warning(_("ignoring duplicated workspace name: %s"), name.c_str()); + continue; + } + + std::string tpl = line.substr(pos+1); + workspaces[name] = tpl; + count++; + } + if (count > 0) + signal_list_changed_.emit(); +} + +sigc::signal& WorkspaceHandler::signal_list_changed() +{ + return signal_list_changed_; +} diff --git a/synfig-studio/src/gui/workspacehandler.h b/synfig-studio/src/gui/workspacehandler.h new file mode 100644 index 0000000..325447e --- /dev/null +++ b/synfig-studio/src/gui/workspacehandler.h @@ -0,0 +1,75 @@ +/* === S Y N F I G ========================================================= */ +/*! \file workspacehandler.h +** \brief Handle with custom workspaces +** +** $Id$ +** +** \legal +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** Copyright (c) 2019 Rodolfo R Gomes +** +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. +** +** This package is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** \endlegal +*/ +/* ========================================================================= */ + +#ifndef SYNFIG_STUDIO_WORKSPACEHANDLER_H +#define SYNFIG_STUDIO_WORKSPACEHANDLER_H + +#include +#include +#include +#include +#include + +namespace studio { + +/// Deal with custom workspaces +class WorkspaceHandler +{ +public: + WorkspaceHandler(); + + static bool is_valid_name(const std::string &name); + + bool has_workspace(const std::string &name) const; + /// \param[in] tpl workspace template string + bool add_workspace(const std::string &name, const std::string &tpl); + void remove_workspace(const std::string &name); + /// remove all workspaces layout + void clear(); + + /// \param[in] tpl workspace template string + bool set_workspace(const std::string &name, const std::string &tpl); + /// \param[out] tpl workspace template string + bool get_workspace(const std::string &name, std::string &tpl) const; + + /// \param[out] list List of workspace names + void get_name_list(std::vector& list); + + /// load custom workspace layouts from a config file + void load(const std::string& filename); + /// stores custom workspace layouts in a config file + bool save(const std::string& filename); + + sigc::signal & signal_list_changed(); + + static void trim_string(std::string &text); + +private: + std::map workspaces; + + sigc::signal signal_list_changed_; +}; + +} + +#endif // SYNFIG_STUDIO_WORKSPACEHANDLER_H diff --git a/synfig-studio/ui/CMakeLists.txt b/synfig-studio/ui/CMakeLists.txt index 356bdd9..66c311b 100644 --- a/synfig-studio/ui/CMakeLists.txt +++ b/synfig-studio/ui/CMakeLists.txt @@ -1,6 +1,7 @@ set(UIFILES canvas_options.ui preview_options.ui + dialog_workspaces.ui ) foreach (UIFILE IN ITEMS ${UIFILES}) diff --git a/synfig-studio/ui/Makefile.am b/synfig-studio/ui/Makefile.am index ad19de4..5c1f6dd 100644 --- a/synfig-studio/ui/Makefile.am +++ b/synfig-studio/ui/Makefile.am @@ -4,7 +4,8 @@ MAINTAINERCLEANFILES = Makefile.in UIFILES = \ canvas_options.ui \ - preview_options.ui + preview_options.ui \ + dialog_workspaces.ui dist_ui_DATA = $(UIFILES) diff --git a/synfig-studio/ui/dialog_workspaces.ui b/synfig-studio/ui/dialog_workspaces.ui new file mode 100644 index 0000000..f625d22 --- /dev/null +++ b/synfig-studio/ui/dialog_workspaces.ui @@ -0,0 +1,152 @@ + + + + + + + + + + + + False + Custom workspace list editor + 320 + 260 + dialog + + + + + + False + vertical + + + False + end + + + gtk-close + True + True + True + True + + + True + True + 0 + + + + + False + False + 0 + + + + + True + False + 18 + 18 + 18 + 18 + 12 + + + True + True + in + + + True + True + workspaces_liststore + False + 0 + False + True + + + multiple + + + + + Name + + + + 0 + + + + + + + + + True + True + 0 + + + + + True + False + center + True + vertical + 12 + + + _Rename + True + False + True + True + True + + + False + True + 0 + + + + + gtk-delete + True + False + True + True + True + + + False + True + 1 + + + + + False + True + 1 + + + + + True + True + 0 + + + + + +