diff options
Diffstat (limited to 'src/ui/dialog/swatches.cpp')
-rw-r--r-- | src/ui/dialog/swatches.cpp | 1418 |
1 files changed, 1418 insertions, 0 deletions
diff --git a/src/ui/dialog/swatches.cpp b/src/ui/dialog/swatches.cpp new file mode 100644 index 0000000..c79b660 --- /dev/null +++ b/src/ui/dialog/swatches.cpp @@ -0,0 +1,1418 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Authors: + * Jon A. Cruz + * John Bintz + * Abhishek Sharma + * + * Copyright (C) 2005 Jon A. Cruz + * Copyright (C) 2008 John Bintz + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <map> +#include <algorithm> +#include <iomanip> +#include <set> + +#include "swatches.h" +#include <gtkmm/radiomenuitem.h> + +#include <gtkmm/menu.h> +#include <gtkmm/checkmenuitem.h> +#include <gtkmm/radiomenuitem.h> +#include <gtkmm/separatormenuitem.h> +#include <gtkmm/menubutton.h> + +#include <glibmm/i18n.h> +#include <glibmm/main.h> +#include <glibmm/timer.h> +#include <glibmm/fileutils.h> +#include <glibmm/miscutils.h> + +#include "color-item.h" +#include "desktop.h" + +#include "desktop-style.h" +#include "document.h" +#include "document-undo.h" +#include "extension/db.h" +#include "inkscape.h" +#include "io/sys.h" +#include "io/resource.h" +#include "message-context.h" +#include "path-prefix.h" + +#include "ui/previewholder.h" +#include "widgets/desktop-widget.h" +#include "widgets/gradient-vector.h" +#include "display/cairo-utils.h" + +#include "object/sp-defs.h" +#include "object/sp-gradient-reference.h" + +#include "dialog-manager.h" +#include "verbs.h" +#include "gradient-chemistry.h" +#include "helper/action.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + + +enum { + SWATCHES_SETTINGS_SIZE = 0, + SWATCHES_SETTINGS_MODE = 1, + SWATCHES_SETTINGS_SHAPE = 2, + SWATCHES_SETTINGS_WRAP = 3, + SWATCHES_SETTINGS_BORDER = 4, + SWATCHES_SETTINGS_PALETTE = 5 +}; + +#define VBLOCK 16 +#define PREVIEW_PIXBUF_WIDTH 128 + +void _loadPaletteFile( gchar const *filename, gboolean user=FALSE ); + +std::list<SwatchPage*> userSwatchPages; +std::list<SwatchPage*> systemSwatchPages; +static std::map<SPDocument*, SwatchPage*> docPalettes; +static std::vector<DocTrack*> docTrackings; +static std::map<SwatchesPanel*, SPDocument*> docPerPanel; + + +class SwatchesPanelHook : public SwatchesPanel +{ +public: + static void convertGradient( GtkMenuItem *menuitem, gpointer userData ); + static void deleteGradient( GtkMenuItem *menuitem, gpointer userData ); +}; + +static void handleClick( GtkWidget* /*widget*/, gpointer callback_data ) { + ColorItem* item = reinterpret_cast<ColorItem*>(callback_data); + if ( item ) { + item->buttonClicked(false); + } +} + +static void handleSecondaryClick( GtkWidget* /*widget*/, gint /*arg1*/, gpointer callback_data ) { + ColorItem* item = reinterpret_cast<ColorItem*>(callback_data); + if ( item ) { + item->buttonClicked(true); + } +} + +static GtkWidget* popupMenu = nullptr; +static GtkWidget *popupSubHolder = nullptr; +static GtkWidget *popupSub = nullptr; +static std::vector<Glib::ustring> popupItems; +static std::vector<GtkWidget*> popupExtras; +static ColorItem* bounceTarget = nullptr; +static SwatchesPanel* bouncePanel = nullptr; + +static void redirClick( GtkMenuItem *menuitem, gpointer /*user_data*/ ) +{ + if ( bounceTarget ) { + handleClick( GTK_WIDGET(menuitem), bounceTarget ); + } +} + +static void redirSecondaryClick( GtkMenuItem *menuitem, gpointer /*user_data*/ ) +{ + if ( bounceTarget ) { + handleSecondaryClick( GTK_WIDGET(menuitem), 0, bounceTarget ); + } +} + +static void editGradientImpl( SPDesktop* desktop, SPGradient* gr ) +{ + if ( gr ) { + bool shown = false; + if ( desktop && desktop->doc() ) { + Inkscape::Selection *selection = desktop->getSelection(); + std::vector<SPItem*> const items(selection->items().begin(), selection->items().end()); + if (!items.empty()) { + SPStyle query( desktop->doc() ); + int result = objects_query_fillstroke((items), &query, true); + if ( (result == QUERY_STYLE_MULTIPLE_SAME) || (result == QUERY_STYLE_SINGLE) ) { + // could be pertinent + if (query.fill.isPaintserver()) { + SPPaintServer* server = query.getFillPaintServer(); + if ( SP_IS_GRADIENT(server) ) { + SPGradient* grad = SP_GRADIENT(server); + if ( grad->isSwatch() && grad->getId() == gr->getId()) { + desktop->_dlg_mgr->showDialog("FillAndStroke"); + shown = true; + } + } + } + } + } + } + + if (!shown) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/dialogs/gradienteditor/showlegacy", false)) { + // Legacy gradient dialog + GtkWidget *dialog = sp_gradient_vector_editor_new( gr ); + gtk_widget_show( dialog ); + } else { + // Invoke the gradient tool + Inkscape::Verb *verb = Inkscape::Verb::get( SP_VERB_CONTEXT_GRADIENT ); + if ( verb ) { + SPAction *action = verb->get_action( Inkscape::ActionContext( ( Inkscape::UI::View::View * ) SP_ACTIVE_DESKTOP ) ); + if ( action ) { + sp_action_perform( action, nullptr ); + } + } + } + } + } +} + +static void editGradient( GtkMenuItem */*menuitem*/, gpointer /*user_data*/ ) +{ + if ( bounceTarget ) { + SwatchesPanel* swp = bouncePanel; + SPDesktop* desktop = swp ? swp->getDesktop() : nullptr; + SPDocument *doc = desktop ? desktop->doc() : nullptr; + if (doc) { + std::string targetName(bounceTarget->def.descr); + std::vector<SPObject *> gradients = doc->getResourceList("gradient"); + for (auto gradient : gradients) { + SPGradient* grad = SP_GRADIENT(gradient); + if ( targetName == grad->getId() ) { + editGradientImpl( desktop, grad ); + break; + } + } + } + } +} + +void SwatchesPanelHook::convertGradient( GtkMenuItem * /*menuitem*/, gpointer userData ) +{ + if ( bounceTarget ) { + SwatchesPanel* swp = bouncePanel; + SPDesktop* desktop = swp ? swp->getDesktop() : nullptr; + SPDocument *doc = desktop ? desktop->doc() : nullptr; + gint index = GPOINTER_TO_INT(userData); + if ( doc && (index >= 0) && (static_cast<guint>(index) < popupItems.size()) ) { + Glib::ustring targetName = popupItems[index]; + std::vector<SPObject *> gradients = doc->getResourceList("gradient"); + for (auto gradient : gradients) { + SPGradient* grad = SP_GRADIENT(gradient); + + if ( targetName == grad->getId() ) { + grad->setSwatch(); + DocumentUndo::done(doc, SP_VERB_CONTEXT_GRADIENT, + _("Add gradient stop")); + break; + } + } + } + } +} + +void SwatchesPanelHook::deleteGradient( GtkMenuItem */*menuitem*/, gpointer /*userData*/ ) +{ + if ( bounceTarget ) { + SwatchesPanel* swp = bouncePanel; + SPDesktop* desktop = swp ? swp->getDesktop() : nullptr; + sp_gradient_unset_swatch(desktop, bounceTarget->def.descr); + } +} + +static SwatchesPanel* findContainingPanel( GtkWidget *widget ) +{ + SwatchesPanel *swp = nullptr; + + std::map<GtkWidget*, SwatchesPanel*> rawObjects; + for (std::map<SwatchesPanel*, SPDocument*>::iterator it = docPerPanel.begin(); it != docPerPanel.end(); ++it) { + rawObjects[GTK_WIDGET(it->first->gobj())] = it->first; + } + + for (GtkWidget* curr = widget; curr && !swp; curr = gtk_widget_get_parent(curr)) { + if (rawObjects.find(curr) != rawObjects.end()) { + swp = rawObjects[curr]; + } + } + + return swp; +} + +static void removeit( GtkWidget *widget, gpointer data ) +{ + gtk_container_remove( GTK_CONTAINER(data), widget ); +} + +/* extern'ed from color-item.cpp */ +bool colorItemHandleButtonPress(GdkEventButton* event, UI::Widget::Preview *preview, gpointer user_data) +{ + gboolean handled = FALSE; + + if ( event && (event->button == 3) && (event->type == GDK_BUTTON_PRESS) ) { + SwatchesPanel* swp = findContainingPanel( GTK_WIDGET(preview->gobj()) ); + + if ( !popupMenu ) { + popupMenu = gtk_menu_new(); + GtkWidget* child = nullptr; + + //TRANSLATORS: An item in context menu on a colour in the swatches + child = gtk_menu_item_new_with_label(_("Set fill")); + g_signal_connect( G_OBJECT(child), + "activate", + G_CALLBACK(redirClick), + user_data); + gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child); + + //TRANSLATORS: An item in context menu on a colour in the swatches + child = gtk_menu_item_new_with_label(_("Set stroke")); + + g_signal_connect( G_OBJECT(child), + "activate", + G_CALLBACK(redirSecondaryClick), + user_data); + gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child); + + child = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child); + popupExtras.push_back(child); + + child = gtk_menu_item_new_with_label(_("Delete")); + g_signal_connect( G_OBJECT(child), + "activate", + G_CALLBACK(SwatchesPanelHook::deleteGradient), + user_data ); + gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child); + popupExtras.push_back(child); + gtk_widget_set_sensitive( child, FALSE ); + + child = gtk_menu_item_new_with_label(_("Edit...")); + g_signal_connect( G_OBJECT(child), + "activate", + G_CALLBACK(editGradient), + user_data ); + gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child); + popupExtras.push_back(child); + + child = gtk_separator_menu_item_new(); + gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child); + popupExtras.push_back(child); + + child = gtk_menu_item_new_with_label(_("Convert")); + gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child); + //popupExtras.push_back(child); + //gtk_widget_set_sensitive( child, FALSE ); + { + popupSubHolder = child; + popupSub = gtk_menu_new(); + gtk_menu_item_set_submenu( GTK_MENU_ITEM(child), popupSub ); + } + + gtk_widget_show_all(popupMenu); + } + + if ( user_data ) { + ColorItem* item = reinterpret_cast<ColorItem*>(user_data); + bool show = swp && (swp->getSelectedIndex() == 0); + for (auto & popupExtra : popupExtras) { + gtk_widget_set_sensitive(popupExtra, show); + } + + bounceTarget = item; + bouncePanel = swp; + popupItems.clear(); + if ( popupMenu ) { + gtk_container_foreach(GTK_CONTAINER(popupSub), removeit, popupSub); + bool processed = false; + GtkWidget *wdgt = gtk_widget_get_ancestor(GTK_WIDGET(preview->gobj()), SP_TYPE_DESKTOP_WIDGET); + if ( wdgt ) { + SPDesktopWidget *dtw = SP_DESKTOP_WIDGET(wdgt); + if ( dtw && dtw->desktop ) { + // Pick up all gradients with vectors + std::vector<SPObject *> gradients = (dtw->desktop->doc())->getResourceList("gradient"); + gint index = 0; + for (auto gradient : gradients) { + SPGradient* grad = SP_GRADIENT(gradient); + if ( grad->hasStops() && !grad->isSwatch() ) { + //gl = g_slist_prepend(gl, curr->data); + processed = true; + GtkWidget *child = gtk_menu_item_new_with_label(grad->getId()); + gtk_menu_shell_append(GTK_MENU_SHELL(popupSub), child); + + popupItems.emplace_back(grad->getId()); + g_signal_connect( G_OBJECT(child), + "activate", + G_CALLBACK(SwatchesPanelHook::convertGradient), + GINT_TO_POINTER(index) ); + index++; + } + } + + gtk_widget_show_all(popupSub); + } + } + gtk_widget_set_sensitive( popupSubHolder, processed ); + gtk_menu_popup_at_pointer(GTK_MENU(popupMenu), reinterpret_cast<GdkEvent *>(event)); + handled = TRUE; + } + } + } + + return handled; +} + + +static char* trim( char* str ) { + char* ret = str; + while ( *str && (*str == ' ' || *str == '\t') ) { + str++; + } + ret = str; + while ( *str ) { + str++; + } + str--; + while ( str >= ret && (( *str == ' ' || *str == '\t' ) || *str == '\r' || *str == '\n') ) { + *str-- = 0; + } + return ret; +} + +static void skipWhitespace( char*& str ) { + while ( *str == ' ' || *str == '\t' ) { + str++; + } +} + +static bool parseNum( char*& str, int& val ) { + val = 0; + while ( '0' <= *str && *str <= '9' ) { + val = val * 10 + (*str - '0'); + str++; + } + bool retval = !(*str == 0 || *str == ' ' || *str == '\t' || *str == '\r' || *str == '\n'); + return retval; +} + + +void _loadPaletteFile(Glib::ustring path, gboolean user/*=FALSE*/) +{ + Glib::ustring filename = Glib::path_get_basename(path); + char block[1024]; + FILE *f = Inkscape::IO::fopen_utf8name(path.c_str(), "r"); + if ( f ) { + char* result = fgets( block, sizeof(block), f ); + if ( result ) { + if ( strncmp( "GIMP Palette", block, 12 ) == 0 ) { + bool inHeader = true; + bool hasErr = false; + + SwatchPage *onceMore = new SwatchPage(); + onceMore->_name = filename.c_str(); + + do { + result = fgets( block, sizeof(block), f ); + block[sizeof(block) - 1] = 0; + if ( result ) { + if ( block[0] == '#' ) { + // ignore comment + } else { + char *ptr = block; + // very simple check for header versus entry + while ( *ptr == ' ' || *ptr == '\t' ) { + ptr++; + } + if ( (*ptr == 0) || (*ptr == '\r') || (*ptr == '\n') ) { + // blank line. skip it. + } else if ( '0' <= *ptr && *ptr <= '9' ) { + // should be an entry link + inHeader = false; + ptr = block; + Glib::ustring name(""); + skipWhitespace(ptr); + if ( *ptr ) { + int r = 0; + int g = 0; + int b = 0; + hasErr = parseNum(ptr, r); + if ( !hasErr ) { + skipWhitespace(ptr); + hasErr = parseNum(ptr, g); + } + if ( !hasErr ) { + skipWhitespace(ptr); + hasErr = parseNum(ptr, b); + } + if ( !hasErr && *ptr ) { + char* n = trim(ptr); + if (n != nullptr && *n) { + name = g_dpgettext2(nullptr, "Palette", n); + } + if (name == "") { + name = Glib::ustring::compose("#%1%2%3", + Glib::ustring::format(std::hex, std::setw(2), std::setfill(L'0'), r), + Glib::ustring::format(std::hex, std::setw(2), std::setfill(L'0'), g), + Glib::ustring::format(std::hex, std::setw(2), std::setfill(L'0'), b) + ).uppercase(); + } + } + if ( !hasErr ) { + // Add the entry now + Glib::ustring nameStr(name); + ColorItem* item = new ColorItem( r, g, b, nameStr ); + onceMore->_colors.push_back(item); + } + } else { + hasErr = true; + } + } else { + if ( !inHeader ) { + // Hmmm... probably bad. Not quite the format we want? + hasErr = true; + } else { + char* sep = strchr(result, ':'); + if ( sep ) { + *sep = 0; + char* val = trim(sep + 1); + char* name = trim(result); + if ( *name ) { + if ( strcmp( "Name", name ) == 0 ) + { + onceMore->_name = val; + } + else if ( strcmp( "Columns", name ) == 0 ) + { + gchar* endPtr = nullptr; + guint64 numVal = g_ascii_strtoull( val, &endPtr, 10 ); + if ( (numVal == G_MAXUINT64) && (ERANGE == errno) ) { + // overflow + } else if ( (numVal == 0) && (endPtr == val) ) { + // failed conversion + } else { + onceMore->_prefWidth = numVal; + } + } + } else { + // error + hasErr = true; + } + } else { + // error + hasErr = true; + } + } + } + } + } + } while ( result && !hasErr ); + if ( !hasErr ) { + if (user) + userSwatchPages.push_back(onceMore); + else + systemSwatchPages.push_back(onceMore); +#if ENABLE_MAGIC_COLORS + ColorItem::_wireMagicColors( onceMore ); +#endif // ENABLE_MAGIC_COLORS + } else { + delete onceMore; + } + } + } + + fclose(f); + } +} + +static bool +compare_swatch_names(SwatchPage const *a, SwatchPage const *b) { + + return g_utf8_collate(a->_name.c_str(), b->_name.c_str()) < 0; +} + +static void load_palettes() +{ + static bool init_done = false; + + if (init_done) { + return; + } + init_done = true; + + for (auto &filename: Inkscape::IO::Resource::get_filenames(Inkscape::IO::Resource::PALETTES, {".gpl"})) { + bool userPalette = Inkscape::IO::file_is_writable(filename.c_str()); + _loadPaletteFile(filename, userPalette); + } + + // Sort the list of swatches by name, grouped by user/system + userSwatchPages.sort(compare_swatch_names); + systemSwatchPages.sort(compare_swatch_names); +} + +SwatchesPanel& SwatchesPanel::getInstance() +{ + return *new SwatchesPanel(); +} + + +/** + * Constructor + */ +SwatchesPanel::SwatchesPanel(gchar const* prefsPath) : + Inkscape::UI::Widget::Panel(prefsPath, SP_VERB_DIALOG_SWATCHES), + _menu(nullptr), + _holder(nullptr), + _clear(nullptr), + _remove(nullptr), + _currentIndex(0), + _currentDesktop(nullptr), + _currentDocument(nullptr) +{ + _holder = new PreviewHolder(); + + _build_menu(); + + auto menu_button = Gtk::manage(new Gtk::MenuButton()); + menu_button->set_halign(Gtk::ALIGN_END); + menu_button->set_relief(Gtk::RELIEF_NONE); + menu_button->set_image_from_icon_name("pan-start-symbolic", Gtk::ICON_SIZE_SMALL_TOOLBAR); + menu_button->set_popup(*_menu); + + auto box = Gtk::manage(new Gtk::Box()); + + if (_prefs_path == "/dialogs/swatches") { + box->set_orientation(Gtk::ORIENTATION_VERTICAL); + box->pack_start(*menu_button, Gtk::PACK_SHRINK); + } else { + box->set_orientation(Gtk::ORIENTATION_HORIZONTAL); + box->pack_end(*menu_button, Gtk::PACK_SHRINK); + _updateSettings(SWATCHES_SETTINGS_MODE, 1); + _holder->setOrientation(SP_ANCHOR_SOUTH); + } + + box->pack_start(*_holder, Gtk::PACK_EXPAND_WIDGET); + _getContents()->pack_start(*box); + + load_palettes(); + + Gtk::RadioMenuItem* hotItem = nullptr; + _clear = new ColorItem( ege::PaintDef::CLEAR ); + _remove = new ColorItem( ege::PaintDef::NONE ); + + if (docPalettes.empty()) { + SwatchPage *docPalette = new SwatchPage(); + + docPalette->_name = "Auto"; + docPalettes[nullptr] = docPalette; + } + + if ( !systemSwatchPages.empty() || !userSwatchPages.empty()) { + SwatchPage* first = nullptr; + int index = 0; + Glib::ustring targetName; + if ( !_prefs_path.empty() ) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + targetName = prefs->getString(_prefs_path + "/palette"); + if (!targetName.empty()) { + if (targetName == "Auto") { + first = docPalettes[nullptr]; + } else { + std::vector<SwatchPage*> pages = _getSwatchSets(); + for (auto & page : pages) { + if ( page->_name == targetName ) { + first = page; + break; + } + index++; + } + } + } + } + + if ( !first ) { + first = docPalettes[nullptr]; + _currentIndex = 0; + } else { + _currentIndex = index; + } + + _rebuild(); + + Gtk::RadioMenuItem::Group groupOne; + + int i = 0; + std::vector<SwatchPage*> swatchSets = _getSwatchSets(); + for (auto curr : swatchSets) { + Gtk::RadioMenuItem* single = Gtk::manage(new Gtk::RadioMenuItem(groupOne, curr->_name)); + if ( curr == first ) { + hotItem = single; + } + _regItem(single, i); + + i++; + } + } + + if ( hotItem ) { + hotItem->set_active(); + } + + show_all_children(); +} + +SwatchesPanel::~SwatchesPanel() +{ + _trackDocument( this, nullptr ); + + _documentConnection.disconnect(); + _selChanged.disconnect(); + + if ( _clear ) { + delete _clear; + } + if ( _remove ) { + delete _remove; + } + if ( _holder ) { + delete _holder; + } + + delete _menu; +} + +void SwatchesPanel::_build_menu() +{ + guint panel_size = 0, panel_mode = 0, panel_ratio = 100, panel_border = 0; + bool panel_wrap = false; + if (!_prefs_path.empty()) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + panel_wrap = prefs->getBool(_prefs_path + "/panel_wrap"); + panel_size = prefs->getIntLimited(_prefs_path + "/panel_size", 1, 0, UI::Widget::PREVIEW_SIZE_HUGE); + panel_mode = prefs->getIntLimited(_prefs_path + "/panel_mode", 1, 0, 10); + panel_ratio = prefs->getIntLimited(_prefs_path + "/panel_ratio", 100, 0, 500 ); + panel_border = prefs->getIntLimited(_prefs_path + "/panel_border", UI::Widget::BORDER_NONE, 0, 2 ); + } + + _menu = new Gtk::Menu(); + + if (_prefs_path == "/dialogs/swatches") { + Gtk::RadioMenuItem::Group group; + Glib::ustring list_label(_("List")); + Glib::ustring grid_label(_("Grid")); + Gtk::RadioMenuItem *list_item = Gtk::manage(new Gtk::RadioMenuItem(group, list_label)); + Gtk::RadioMenuItem *grid_item = Gtk::manage(new Gtk::RadioMenuItem(group, grid_label)); + + if (panel_mode == 0) { + list_item->set_active(true); + } else if (panel_mode == 1) { + grid_item->set_active(true); + } + + _menu->append(*list_item); + _menu->append(*grid_item); + _menu->append(*Gtk::manage(new Gtk::SeparatorMenuItem())); + + list_item->signal_activate().connect(sigc::bind<int, int>(sigc::mem_fun(*this, &SwatchesPanel::_updateSettings), SWATCHES_SETTINGS_MODE, 0)); + grid_item->signal_activate().connect(sigc::bind<int, int>(sigc::mem_fun(*this, &SwatchesPanel::_updateSettings), SWATCHES_SETTINGS_MODE, 1)); + } + + { + Glib::ustring heightItemLabel(C_("Swatches", "Size")); + + //TRANSLATORS: Indicates size of colour swatches + const gchar *heightLabels[] = { + NC_("Swatches height", "Tiny"), + NC_("Swatches height", "Small"), + NC_("Swatches height", "Medium"), + NC_("Swatches height", "Large"), + NC_("Swatches height", "Huge") + }; + + Gtk::MenuItem *sizeItem = Gtk::manage(new Gtk::MenuItem(heightItemLabel)); + Gtk::Menu *sizeMenu = Gtk::manage(new Gtk::Menu()); + sizeItem->set_submenu(*sizeMenu); + + Gtk::RadioMenuItem::Group heightGroup; + for (unsigned int i = 0; i < G_N_ELEMENTS(heightLabels); i++) { + Glib::ustring _label(g_dpgettext2(nullptr, "Swatches height", heightLabels[i])); + Gtk::RadioMenuItem* _item = Gtk::manage(new Gtk::RadioMenuItem(heightGroup, _label)); + sizeMenu->append(*_item); + if (i == panel_size) { + _item->set_active(true); + } + _item->signal_activate().connect(sigc::bind<int, int>(sigc::mem_fun(*this, &SwatchesPanel::_updateSettings), SWATCHES_SETTINGS_SIZE, i)); + } + + _menu->append(*sizeItem); + } + + { + Glib::ustring widthItemLabel(C_("Swatches", "Width")); + + //TRANSLATORS: Indicates width of colour swatches + const gchar *widthLabels[] = { + NC_("Swatches width", "Narrower"), + NC_("Swatches width", "Narrow"), + NC_("Swatches width", "Medium"), + NC_("Swatches width", "Wide"), + NC_("Swatches width", "Wider") + }; + + Gtk::MenuItem *item = Gtk::manage( new Gtk::MenuItem(widthItemLabel)); + Gtk::Menu *type_menu = Gtk::manage(new Gtk::Menu()); + item->set_submenu(*type_menu); + _menu->append(*item); + + Gtk::RadioMenuItem::Group widthGroup; + + guint values[] = {0, 25, 50, 100, 200, 400}; + guint hot_index = 3; + for ( guint i = 0; i < G_N_ELEMENTS(widthLabels); ++i ) { + // Assume all values are in increasing order + if ( values[i] <= panel_ratio ) { + hot_index = i; + } + } + for ( guint i = 0; i < G_N_ELEMENTS(widthLabels); ++i ) { + Glib::ustring _label(g_dpgettext2(nullptr, "Swatches width", widthLabels[i])); + Gtk::RadioMenuItem *_item = Gtk::manage(new Gtk::RadioMenuItem(widthGroup, _label)); + type_menu->append(*_item); + if ( i <= hot_index ) { + _item->set_active(true); + } + _item->signal_activate().connect(sigc::bind<int, int>(sigc::mem_fun(*this, &SwatchesPanel::_updateSettings), SWATCHES_SETTINGS_SHAPE, values[i])); + } + } + + { + Glib::ustring widthItemLabel(C_("Swatches", "Border")); + + //TRANSLATORS: Indicates border of colour swatches + const gchar *widthLabels[] = { + NC_("Swatches border", "None"), + NC_("Swatches border", "Solid"), + NC_("Swatches border", "Wide"), + }; + + Gtk::MenuItem *item = Gtk::manage( new Gtk::MenuItem(widthItemLabel)); + Gtk::Menu *type_menu = Gtk::manage(new Gtk::Menu()); + item->set_submenu(*type_menu); + _menu->append(*item); + + Gtk::RadioMenuItem::Group widthGroup; + + guint values[] = {0, 1, 2}; + guint hot_index = 0; + for ( guint i = 0; i < G_N_ELEMENTS(widthLabels); ++i ) { + // Assume all values are in increasing order + if ( values[i] <= panel_border ) { + hot_index = i; + } + } + for ( guint i = 0; i < G_N_ELEMENTS(widthLabels); ++i ) { + Glib::ustring _label(g_dpgettext2(nullptr, "Swatches border", widthLabels[i])); + Gtk::RadioMenuItem *_item = Gtk::manage(new Gtk::RadioMenuItem(widthGroup, _label)); + type_menu->append(*_item); + if ( i <= hot_index ) { + _item->set_active(true); + } + _item->signal_activate().connect(sigc::bind<int, int>(sigc::mem_fun(*this, &SwatchesPanel::_updateSettings), SWATCHES_SETTINGS_BORDER, values[i])); + } + } + + if (_prefs_path == "/embedded/swatches") { + //TRANSLATORS: "Wrap" indicates how colour swatches are displayed + Glib::ustring wrap_label(C_("Swatches","Wrap")); + Gtk::CheckMenuItem *check = Gtk::manage(new Gtk::CheckMenuItem(wrap_label)); + check->set_active(panel_wrap); + _menu->append(*check); + + check->signal_toggled().connect(sigc::bind<Gtk::CheckMenuItem*>(sigc::mem_fun(*this, &SwatchesPanel::_wrapToggled), check)); + } + + _menu->append(*Gtk::manage(new Gtk::SeparatorMenuItem())); + + _menu->show_all(); + + _updateSettings(SWATCHES_SETTINGS_SIZE, panel_size); + _updateSettings(SWATCHES_SETTINGS_MODE, panel_mode); + _updateSettings(SWATCHES_SETTINGS_SHAPE, panel_ratio); + _updateSettings(SWATCHES_SETTINGS_WRAP, panel_wrap); + _updateSettings(SWATCHES_SETTINGS_BORDER, panel_border); +} + +void SwatchesPanel::setDesktop( SPDesktop* desktop ) +{ + if ( desktop != _currentDesktop ) { + if ( _currentDesktop ) { + _documentConnection.disconnect(); + _selChanged.disconnect(); + } + + _currentDesktop = desktop; + + if ( desktop ) { + _currentDesktop->selection->connectChanged( + sigc::hide(sigc::mem_fun(*this, &SwatchesPanel::_updateFromSelection))); + + _currentDesktop->selection->connectModified( + sigc::hide(sigc::hide(sigc::mem_fun(*this, &SwatchesPanel::_updateFromSelection)))); + + _currentDesktop->connectToolSubselectionChanged( + sigc::hide(sigc::mem_fun(*this, &SwatchesPanel::_updateFromSelection))); + + sigc::bound_mem_functor1<void, SwatchesPanel, SPDocument*> first = sigc::mem_fun(*this, &SwatchesPanel::_setDocument); + sigc::slot<void, SPDocument*> base2 = first; + sigc::slot<void,SPDesktop*, SPDocument*> slot2 = sigc::hide<0>( base2 ); + _documentConnection = desktop->connectDocumentReplaced( slot2 ); + + _setDocument( desktop->doc() ); + } else { + _setDocument(nullptr); + } + } +} + + +void SwatchesPanel::_updateSettings(int settings, int value) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + switch (settings) { + case SWATCHES_SETTINGS_SIZE: { + prefs->setInt(_prefs_path + "/panel_size", value); + + auto curr_type = _holder->getPreviewType(); + guint curr_ratio = _holder->getPreviewRatio(); + auto curr_border = _holder->getPreviewBorder(); + + switch (value) { + case 0: + _holder->setStyle(UI::Widget::PREVIEW_SIZE_TINY, curr_type, curr_ratio, curr_border); + break; + case 1: + _holder->setStyle(UI::Widget::PREVIEW_SIZE_SMALL, curr_type, curr_ratio, curr_border); + break; + case 2: + _holder->setStyle(UI::Widget::PREVIEW_SIZE_MEDIUM, curr_type, curr_ratio, curr_border); + break; + case 3: + _holder->setStyle(UI::Widget::PREVIEW_SIZE_BIG, curr_type, curr_ratio, curr_border); + break; + case 4: + _holder->setStyle(UI::Widget::PREVIEW_SIZE_HUGE, curr_type, curr_ratio, curr_border); + break; + default: + break; + } + + break; + } + case SWATCHES_SETTINGS_MODE: { + prefs->setInt(_prefs_path + "/panel_mode", value); + + auto curr_size = _holder->getPreviewSize(); + guint curr_ratio = _holder->getPreviewRatio(); + auto curr_border = _holder->getPreviewBorder(); + switch (value) { + case 0: + _holder->setStyle(curr_size, UI::Widget::VIEW_TYPE_LIST, curr_ratio, curr_border); + break; + case 1: + _holder->setStyle(curr_size, UI::Widget::VIEW_TYPE_GRID, curr_ratio, curr_border); + break; + default: + break; + } + break; + } + case SWATCHES_SETTINGS_SHAPE: { + prefs->setInt(_prefs_path + "/panel_ratio", value); + + auto curr_type = _holder->getPreviewType(); + auto curr_size = _holder->getPreviewSize(); + auto curr_border = _holder->getPreviewBorder(); + + _holder->setStyle(curr_size, curr_type, value, curr_border); + break; + } + case SWATCHES_SETTINGS_BORDER: { + prefs->setInt(_prefs_path + "/panel_border", value); + + auto curr_size = _holder->getPreviewSize(); + auto curr_type = _holder->getPreviewType(); + guint curr_ratio = _holder->getPreviewRatio(); + + switch (value) { + case 0: + _holder->setStyle(curr_size, curr_type, curr_ratio, UI::Widget::BORDER_NONE); + break; + case 1: + _holder->setStyle(curr_size, curr_type, curr_ratio, UI::Widget::BORDER_SOLID); + break; + case 2: + _holder->setStyle(curr_size, curr_type, curr_ratio, UI::Widget::BORDER_WIDE); + break; + default: + break; + } + break; + } + case SWATCHES_SETTINGS_WRAP: { + prefs->setBool(_prefs_path + "/panel_wrap", value); + _holder->setWrap(value); + break; + } + case SWATCHES_SETTINGS_PALETTE: { + std::vector<SwatchPage*> pages = _getSwatchSets(); + if (value >= 0 && value < static_cast<int>(pages.size()) ) { + _currentIndex = value; + + prefs->setString(_prefs_path + "/palette", pages[_currentIndex]->_name); + + _rebuild(); + } + } + default: + break; + } +} + +void SwatchesPanel::_wrapToggled(Gtk::CheckMenuItem* toggler) +{ + if (toggler) { + _updateSettings(SWATCHES_SETTINGS_WRAP, toggler->get_active() ? 1 : 0); + } +} + +void SwatchesPanel::_regItem(Gtk::MenuItem* item, int id) +{ + _menu->append(*item); + item->signal_activate().connect(sigc::bind<int, int>(sigc::mem_fun(*this, &SwatchesPanel::_updateSettings), SWATCHES_SETTINGS_PALETTE, id)); + item->show(); +} + + +class DocTrack +{ +public: + DocTrack(SPDocument *doc, sigc::connection &gradientRsrcChanged, sigc::connection &defsChanged, sigc::connection &defsModified) : + doc(doc->doRef()), + updatePending(false), + lastGradientUpdate(0.0), + gradientRsrcChanged(gradientRsrcChanged), + defsChanged(defsChanged), + defsModified(defsModified) + { + if ( !timer ) { + timer = new Glib::Timer(); + refreshTimer = Glib::signal_timeout().connect( sigc::ptr_fun(handleTimerCB), 33 ); + } + timerRefCount++; + } + + ~DocTrack() + { + timerRefCount--; + if ( timerRefCount <= 0 ) { + refreshTimer.disconnect(); + timerRefCount = 0; + if ( timer ) { + timer->stop(); + delete timer; + timer = nullptr; + } + } + if (doc) { + gradientRsrcChanged.disconnect(); + defsChanged.disconnect(); + defsModified.disconnect(); + doc->doUnref(); + doc = nullptr; + } + } + + static bool handleTimerCB(); + + /** + * Checks if update should be queued or executed immediately. + * + * @return true if the update was queued and should not be immediately executed. + */ + static bool queueUpdateIfNeeded(SPDocument *doc); + + static Glib::Timer *timer; + static int timerRefCount; + static sigc::connection refreshTimer; + + SPDocument *doc; + bool updatePending; + double lastGradientUpdate; + sigc::connection gradientRsrcChanged; + sigc::connection defsChanged; + sigc::connection defsModified; + +private: + DocTrack(DocTrack const &) = delete; // no copy + DocTrack &operator=(DocTrack const &) = delete; // no assign +}; + +Glib::Timer *DocTrack::timer = nullptr; +int DocTrack::timerRefCount = 0; +sigc::connection DocTrack::refreshTimer; + +static const double DOC_UPDATE_THREASHOLD = 0.090; + +bool DocTrack::handleTimerCB() +{ + double now = timer->elapsed(); + + std::vector<DocTrack *> needCallback; + for (auto track : docTrackings) { + if ( track->updatePending && ( (now - track->lastGradientUpdate) >= DOC_UPDATE_THREASHOLD) ) { + needCallback.push_back(track); + } + } + + for (auto track : needCallback) { + if ( std::find(docTrackings.begin(), docTrackings.end(), track) != docTrackings.end() ) { // Just in case one gets deleted while we are looping + // Note: calling handleDefsModified will call queueUpdateIfNeeded and thus update the time and flag. + SwatchesPanel::handleDefsModified(track->doc); + } + } + + return true; +} + +bool DocTrack::queueUpdateIfNeeded( SPDocument *doc ) +{ + bool deferProcessing = false; + for (auto track : docTrackings) { + if ( track->doc == doc ) { + double now = timer->elapsed(); + double elapsed = now - track->lastGradientUpdate; + + if ( elapsed < DOC_UPDATE_THREASHOLD ) { + deferProcessing = true; + track->updatePending = true; + } else { + track->lastGradientUpdate = now; + track->updatePending = false; + } + + break; + } + } + return deferProcessing; +} + +void SwatchesPanel::_trackDocument( SwatchesPanel *panel, SPDocument *document ) +{ + SPDocument *oldDoc = nullptr; + if (docPerPanel.find(panel) != docPerPanel.end()) { + oldDoc = docPerPanel[panel]; + if (!oldDoc) { + docPerPanel.erase(panel); // Should not be needed, but clean up just in case. + } + } + if (oldDoc != document) { + if (oldDoc) { + docPerPanel[panel] = nullptr; + bool found = false; + for (std::map<SwatchesPanel*, SPDocument*>::iterator it = docPerPanel.begin(); (it != docPerPanel.end()) && !found; ++it) { + found = (it->second == document); + } + if (!found) { + for (std::vector<DocTrack*>::iterator it = docTrackings.begin(); it != docTrackings.end(); ++it){ + if ((*it)->doc == oldDoc) { + delete *it; + docTrackings.erase(it); + break; + } + } + } + } + + if (document) { + bool found = false; + for (std::map<SwatchesPanel*, SPDocument*>::iterator it = docPerPanel.begin(); (it != docPerPanel.end()) && !found; ++it) { + found = (it->second == document); + } + docPerPanel[panel] = document; + if (!found) { + sigc::connection conn1 = document->connectResourcesChanged( "gradient", sigc::bind(sigc::ptr_fun(&SwatchesPanel::handleGradientsChange), document) ); + sigc::connection conn2 = document->getDefs()->connectRelease( sigc::hide(sigc::bind(sigc::ptr_fun(&SwatchesPanel::handleDefsModified), document)) ); + sigc::connection conn3 = document->getDefs()->connectModified( sigc::hide(sigc::hide(sigc::bind(sigc::ptr_fun(&SwatchesPanel::handleDefsModified), document))) ); + + DocTrack *dt = new DocTrack(document, conn1, conn2, conn3); + docTrackings.push_back(dt); + + if (docPalettes.find(document) == docPalettes.end()) { + SwatchPage *docPalette = new SwatchPage(); + docPalette->_name = "Auto"; + docPalettes[document] = docPalette; + } + } + } + } +} + +void SwatchesPanel::_setDocument( SPDocument *document ) +{ + if ( document != _currentDocument ) { + _trackDocument(this, document); + _currentDocument = document; + handleGradientsChange( document ); + } +} + +static void recalcSwatchContents(SPDocument* doc, + boost::ptr_vector<ColorItem> &tmpColors, + std::map<ColorItem*, cairo_pattern_t*> &previewMappings, + std::map<ColorItem*, SPGradient*> &gradMappings) +{ + std::vector<SPGradient*> newList; + std::vector<SPObject *> gradients = doc->getResourceList("gradient"); + for (auto gradient : gradients) { + SPGradient* grad = SP_GRADIENT(gradient); + if ( grad->isSwatch() ) { + newList.push_back(SP_GRADIENT(gradient)); + } + } + + if ( !newList.empty() ) { + std::reverse(newList.begin(), newList.end()); + for (auto grad : newList) + { + cairo_surface_t *preview = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + PREVIEW_PIXBUF_WIDTH, VBLOCK); + cairo_t *ct = cairo_create(preview); + + Glib::ustring name( grad->getId() ); + ColorItem* item = new ColorItem( 0, 0, 0, name ); + + cairo_pattern_t *check = ink_cairo_pattern_create_checkerboard(); + cairo_pattern_t *gradient = grad->create_preview_pattern(PREVIEW_PIXBUF_WIDTH); + cairo_set_source(ct, check); + cairo_paint(ct); + cairo_set_source(ct, gradient); + cairo_paint(ct); + + cairo_destroy(ct); + cairo_pattern_destroy(gradient); + cairo_pattern_destroy(check); + + cairo_pattern_t *prevpat = cairo_pattern_create_for_surface(preview); + cairo_surface_destroy(preview); + + previewMappings[item] = prevpat; + + tmpColors.push_back(item); + gradMappings[item] = grad; + } + } +} + +void SwatchesPanel::handleGradientsChange(SPDocument *document) +{ + SwatchPage *docPalette = (docPalettes.find(document) != docPalettes.end()) ? docPalettes[document] : nullptr; + if (docPalette) { + boost::ptr_vector<ColorItem> tmpColors; + std::map<ColorItem*, cairo_pattern_t*> tmpPrevs; + std::map<ColorItem*, SPGradient*> tmpGrads; + recalcSwatchContents(document, tmpColors, tmpPrevs, tmpGrads); + + for (auto & tmpPrev : tmpPrevs) { + tmpPrev.first->setPattern(tmpPrev.second); + cairo_pattern_destroy(tmpPrev.second); + } + + for (auto & tmpGrad : tmpGrads) { + tmpGrad.first->setGradient(tmpGrad.second); + } + + docPalette->_colors.swap(tmpColors); + + // Figure out which SwatchesPanel instances are affected and update them. + + for (auto & it : docPerPanel) { + if (it.second == document) { + SwatchesPanel* swp = it.first; + std::vector<SwatchPage*> pages = swp->_getSwatchSets(); + SwatchPage* curr = pages[swp->_currentIndex]; + if (curr == docPalette) { + swp->_rebuild(); + } + } + } + } +} + +void SwatchesPanel::handleDefsModified(SPDocument *document) +{ + SwatchPage *docPalette = (docPalettes.find(document) != docPalettes.end()) ? docPalettes[document] : nullptr; + if (docPalette && !DocTrack::queueUpdateIfNeeded(document) ) { + boost::ptr_vector<ColorItem> tmpColors; + std::map<ColorItem*, cairo_pattern_t*> tmpPrevs; + std::map<ColorItem*, SPGradient*> tmpGrads; + recalcSwatchContents(document, tmpColors, tmpPrevs, tmpGrads); + + if ( tmpColors.size() != docPalette->_colors.size() ) { + handleGradientsChange(document); + } else { + int cap = std::min(docPalette->_colors.size(), tmpColors.size()); + for (int i = 0; i < cap; i++) { + ColorItem *newColor = &tmpColors[i]; + ColorItem *oldColor = &docPalette->_colors[i]; + if ( (newColor->def.getType() != oldColor->def.getType()) || + (newColor->def.getR() != oldColor->def.getR()) || + (newColor->def.getG() != oldColor->def.getG()) || + (newColor->def.getB() != oldColor->def.getB()) ) { + oldColor->def.setRGB(newColor->def.getR(), newColor->def.getG(), newColor->def.getB()); + } + if (tmpGrads.find(newColor) != tmpGrads.end()) { + oldColor->setGradient(tmpGrads[newColor]); + } + if ( tmpPrevs.find(newColor) != tmpPrevs.end() ) { + oldColor->setPattern(tmpPrevs[newColor]); + } + } + } + + for (auto & tmpPrev : tmpPrevs) { + cairo_pattern_destroy(tmpPrev.second); + } + } +} + + +std::vector<SwatchPage*> SwatchesPanel::_getSwatchSets() const +{ + std::vector<SwatchPage*> tmp; + if (docPalettes.find(_currentDocument) != docPalettes.end()) { + tmp.push_back(docPalettes[_currentDocument]); + } + + tmp.insert(tmp.end(), userSwatchPages.begin(), userSwatchPages.end()); + tmp.insert(tmp.end(), systemSwatchPages.begin(), systemSwatchPages.end()); + + return tmp; +} + +void SwatchesPanel::_updateFromSelection() +{ + SwatchPage *docPalette = (docPalettes.find(_currentDocument) != docPalettes.end()) ? docPalettes[_currentDocument] : nullptr; + if ( docPalette ) { + Glib::ustring fillId; + Glib::ustring strokeId; + + SPStyle tmpStyle(_currentDesktop->getDocument()); + int result = sp_desktop_query_style( _currentDesktop, &tmpStyle, QUERY_STYLE_PROPERTY_FILL ); + switch (result) { + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: + case QUERY_STYLE_MULTIPLE_SAME: + { + if (tmpStyle.fill.set && tmpStyle.fill.isPaintserver()) { + SPPaintServer* server = tmpStyle.getFillPaintServer(); + if ( SP_IS_GRADIENT(server) ) { + SPGradient* target = nullptr; + SPGradient* grad = SP_GRADIENT(server); + + if ( grad->isSwatch() ) { + target = grad; + } else if ( grad->ref ) { + SPGradient *tmp = grad->ref->getObject(); + if ( tmp && tmp->isSwatch() ) { + target = tmp; + } + } + if ( target ) { + //XML Tree being used directly here while it shouldn't be + gchar const* id = target->getRepr()->attribute("id"); + if ( id ) { + fillId = id; + } + } + } + } + break; + } + } + + result = sp_desktop_query_style( _currentDesktop, &tmpStyle, QUERY_STYLE_PROPERTY_STROKE ); + switch (result) { + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: + case QUERY_STYLE_MULTIPLE_SAME: + { + if (tmpStyle.stroke.set && tmpStyle.stroke.isPaintserver()) { + SPPaintServer* server = tmpStyle.getStrokePaintServer(); + if ( SP_IS_GRADIENT(server) ) { + SPGradient* target = nullptr; + SPGradient* grad = SP_GRADIENT(server); + if ( grad->isSwatch() ) { + target = grad; + } else if ( grad->ref ) { + SPGradient *tmp = grad->ref->getObject(); + if ( tmp && tmp->isSwatch() ) { + target = tmp; + } + } + if ( target ) { + //XML Tree being used directly here while it shouldn't be + gchar const* id = target->getRepr()->attribute("id"); + if ( id ) { + strokeId = id; + } + } + } + } + break; + } + } + + for (auto & _color : docPalette->_colors) { + ColorItem* item = &_color; + bool isFill = (fillId == item->def.descr); + bool isStroke = (strokeId == item->def.descr); + item->setState( isFill, isStroke ); + } + } +} + +void SwatchesPanel::_rebuild() +{ + std::vector<SwatchPage*> pages = _getSwatchSets(); + SwatchPage* curr = pages[_currentIndex]; + _holder->clear(); + + if ( curr->_prefWidth > 0 ) { + _holder->setColumnPref( curr->_prefWidth ); + } + _holder->freezeUpdates(); + // TODO restore once 'clear' works _holder->addPreview(_clear); + _holder->addPreview(_remove); + for (auto & _color : curr->_colors) { + _holder->addPreview(&_color); + } + _holder->thawUpdates(); +} + +} //namespace Dialog +} //namespace UI +} //namespace Inkscape + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |