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 cbf8ca9..4147e0e 100644 --- a/synfig-studio/src/gui/app.cpp +++ b/synfig-studio/src/gui/app.cpp @@ -156,6 +156,7 @@ #include #include "gui/resourcehelper.h" +#include "gui/workspacehandler.h" #endif @@ -211,6 +212,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 +240,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 +347,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 +1110,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")); @@ -1261,6 +1281,8 @@ DEFINE_ACTION("keyframe-properties", _("Properties")); " " " " " " +" " +" " " " " " " " @@ -1681,6 +1703,9 @@ 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...")); + load_custom_workspaces(); + studio_init_cb.task(_("Init auto recovery...")); auto_recover=new AutoRecover(); @@ -1894,6 +1919,9 @@ App::~App() delete dock_manager; + workspaces->save(); + delete workspaces; + instance_list.clear(); if (sound_render_done) delete sound_render_done; @@ -2227,6 +2255,78 @@ void App::set_workspace_from_template(const string& tpl) 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() +{ + std::string filename = get_config_file("workspaces"); + workspaces = new WorkspaceHandler(filename.c_str()); + if (!workspaces) + return; + signal_custom_workspaces_changed_(); +} + +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->set_workspace(name, tpl); + else + workspaces->add_workspace(name, tpl); + signal_custom_workspaces_changed_(); +} void App::restore_default_settings() diff --git a/synfig-studio/src/gui/app.h b/synfig-studio/src/gui/app.h index 606952e..5290a27 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 ----------------------------------------------- */ @@ -281,6 +285,8 @@ public: static sigc::signal &signal_recent_files_changed(); + static sigc::signal &signal_custom_workspaces_changed(); + static sigc::signal< void, etl::loose_handle @@ -339,11 +345,16 @@ public: 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 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/mainwindow.cpp b/synfig-studio/src/gui/mainwindow.cpp index bf9a4a0..6ddd50a 100644 --- a/synfig-studio/src/gui/mainwindow.cpp +++ b/synfig-studio/src/gui/mainwindow.cpp @@ -122,6 +122,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 +185,9 @@ 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", _("Save workspace...")), + sigc::ptr_fun(App::save_custom_workspace) + ); // help #define URL(action_name,title,url) \ @@ -380,6 +386,55 @@ 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 i = 0; + for (auto it = workspaces.cbegin(); it != workspaces.cend(); ++it, ++i) { + 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", i); + menu_items += ""; + + action_group->add( Gtk::Action::create(action_name, quoted), + sigc::bind(sigc::ptr_fun(&App::set_workspace_from_name), workspaces[i]) + ); + } + + 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 i = groups.begin(); i != groups.end(); ++i) + if ((*i)->get_name() == action_group->get_name()) + App::ui_manager()->remove_action_group(*i); + groups.clear(); + + App::ui_manager()->insert_action_group(action_group); + App::ui_manager()->add_ui_from_string(ui_info_popup); + App::ui_manager()->add_ui_from_string(ui_info_menubar); +} + +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..a41d761 100644 --- a/synfig-studio/src/gui/mainwindow.h +++ b/synfig-studio/src/gui/mainwindow.h @@ -59,6 +59,7 @@ 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(); diff --git a/synfig-studio/src/gui/workspacehandler.cpp b/synfig-studio/src/gui/workspacehandler.cpp new file mode 100644 index 0000000..324108c --- /dev/null +++ b/synfig-studio/src/gui/workspacehandler.cpp @@ -0,0 +1,163 @@ +/* === 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(const char *filename) + : filename(filename) +{ + if (!this->filename.empty()) + load(); +} + +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() + ); +} + +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; + return true; +} + +void +WorkspaceHandler::remove_workspace(const std::string& name) +{ + workspaces.erase(name); +} + +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) +{ + for(auto it = workspaces.cbegin(); it != workspaces.cend(); ++it) + list.push_back(it->first); +} + +void +WorkspaceHandler::save() +{ + std::ofstream ofs(filename); + if (!ofs) { + synfig::error(_("Can't save custom workspaces")); + return; + } + for (auto it = workspaces.begin(); it != workspaces.end(); ++it) + ofs << it->first << "=" << it->second << std::endl; + ofs.close(); +} + +void +WorkspaceHandler::load() +{ + workspaces.clear(); + + std::ifstream ifs(filename); + std::string line; + 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; + } +} diff --git a/synfig-studio/src/gui/workspacehandler.h b/synfig-studio/src/gui/workspacehandler.h new file mode 100644 index 0000000..ee315fe --- /dev/null +++ b/synfig-studio/src/gui/workspacehandler.h @@ -0,0 +1,65 @@ +/* === 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 + +namespace studio { + +/// Deal with custom workspaces +class WorkspaceHandler +{ +public: + WorkspaceHandler(const char *filename); + + 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); + + /// \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; + + void get_name_list(std::vector& list); + + void save(); + +private: + std::map workspaces; + + std::string filename; + void load(); +}; + +} + +#endif // SYNFIG_STUDIO_WORKSPACEHANDLER_H