diff options
Diffstat (limited to 'src/ui/dialog/swatches.cpp')
-rw-r--r-- | src/ui/dialog/swatches.cpp | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/src/ui/dialog/swatches.cpp b/src/ui/dialog/swatches.cpp new file mode 100644 index 0000000..a059796 --- /dev/null +++ b/src/ui/dialog/swatches.cpp @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Authors: + * Jon A. Cruz + * John Bintz + * Abhishek Sharma + * PBS <pbs3141@gmail.com> + * + * Copyright (C) 2005 Jon A. Cruz + * Copyright (C) 2008 John Bintz + * Copyright (C) 2022 PBS + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "swatches.h" + +#include <algorithm> +#include <glibmm/i18n.h> + +#include "document.h" +#include "object/sp-defs.h" +#include "style.h" +#include "desktop-style.h" +#include "object/sp-gradient-reference.h" + +#include "inkscape-preferences.h" +#include "widgets/paintdef.h" +#include "ui/widget/color-palette.h" +#include "ui/dialog/global-palettes.h" +#include "ui/dialog/color-item.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + +/* + * Lifecycle + */ + +SwatchesPanel::SwatchesPanel(char const *prefsPath) + : DialogBase(prefsPath, "Swatches") +{ + _palette = Gtk::make_managed<Inkscape::UI::Widget::ColorPalette>(); + pack_start(*_palette); + update_palettes(); + + bool embedded = _prefs_path != "/dialogs/swatches"; + _palette->set_compact(embedded); + + Inkscape::Preferences* prefs = Inkscape::Preferences::get(); + + index = name_to_index(prefs->getString(_prefs_path + "/palette")); + + // restore palette settings + _palette->set_tile_size(prefs->getInt(_prefs_path + "/tile_size", 16)); + _palette->set_aspect(prefs->getDoubleLimited(_prefs_path + "/tile_aspect", 0.0, -2, 2)); + _palette->set_tile_border(prefs->getInt(_prefs_path + "/tile_border", 1)); + _palette->set_rows(prefs->getInt(_prefs_path + "/rows", 1)); + _palette->enable_stretch(prefs->getBool(_prefs_path + "/tile_stretch", false)); + _palette->set_large_pinned_panel(embedded && prefs->getBool(_prefs_path + "/enlarge_pinned", true)); + _palette->enable_labels(!embedded && prefs->getBool(_prefs_path + "/show_labels", true)); + + // save settings when they change + _palette->get_settings_changed_signal().connect([=] { + prefs->setInt(_prefs_path + "/tile_size", _palette->get_tile_size()); + prefs->setDouble(_prefs_path + "/tile_aspect", _palette->get_aspect()); + prefs->setInt(_prefs_path + "/tile_border", _palette->get_tile_border()); + prefs->setInt(_prefs_path + "/rows", _palette->get_rows()); + prefs->setBool(_prefs_path + "/tile_stretch", _palette->is_stretch_enabled()); + prefs->setBool(_prefs_path + "/enlarge_pinned", _palette->is_pinned_panel_large()); + prefs->setBool(_prefs_path + "/show_labels", !embedded && _palette->are_labels_enabled()); + }); + + // Respond to requests from the palette widget to change palettes. + _palette->get_palette_selected_signal().connect([this] (Glib::ustring name) { + Preferences::get()->setString(_prefs_path + "/palette", name); + set_index(name_to_index(name)); + }); + + // Watch for pinned palette options. + _pinned_observer = prefs->createObserver(_prefs_path + "/pinned/", [this]() { + rebuild(); + }); + + rebuild(); +} + +SwatchesPanel::~SwatchesPanel() +{ + untrack_gradients(); +} + +/* + * Activation + */ + +// Note: The "Auto" palette shows the list of gradients that are swatches. When this palette is +// shown (and we have a document), we therefore need to track both addition/removal of gradients +// and changes to the isSwatch() status to keep the palette up-to-date. + +void SwatchesPanel::documentReplaced() +{ + if (getDocument()) { + if (index == PALETTE_AUTO) { + track_gradients(); + } + } else { + untrack_gradients(); + } + + if (index == PALETTE_AUTO) { + rebuild(); + } +} + +void SwatchesPanel::desktopReplaced() +{ + documentReplaced(); +} + +void SwatchesPanel::set_index(PaletteIndex new_index) +{ + if (index == new_index) return; + index = new_index; + + if (index == PALETTE_AUTO) { + if (getDocument()) { + track_gradients(); + } + } else { + untrack_gradients(); + } + + rebuild(); +} + +void SwatchesPanel::track_gradients() +{ + auto doc = getDocument(); + + // Subscribe to the addition and removal of gradients. + conn_gradients.disconnect(); + conn_gradients = doc->connectResourcesChanged("gradient", [this] { + gradients_changed = true; + queue_resize(); + }); + + // Subscribe to child modifications of the defs section. We will use this to monitor + // each gradient for whether its isSwatch() status changes. + conn_defs.disconnect(); + conn_defs = doc->getDefs()->connectModified([this] (SPObject*, unsigned flags) { + if (flags & SP_OBJECT_CHILD_MODIFIED_FLAG) { + defs_changed = true; + queue_resize(); + } + }); + + gradients_changed = false; + defs_changed = false; + rebuild_isswatch(); +} + +void SwatchesPanel::untrack_gradients() +{ + conn_gradients.disconnect(); + conn_defs.disconnect(); + gradients_changed = false; + defs_changed = false; +} + +/* + * Updating + */ + +void SwatchesPanel::selectionChanged(Selection*) +{ + selection_changed = true; + queue_resize(); +} + +void SwatchesPanel::selectionModified(Selection*, guint flags) +{ + if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { + selection_changed = true; + queue_resize(); + } +} + +// Document updates are handled asynchronously by setting a flag and queuing a resize. This results in +// the following function being run at the last possible moment before the widget will be repainted. +// This ensures that multiple document updates only result in a single UI update. +void SwatchesPanel::on_size_allocate(Gtk::Allocation &alloc) +{ + if (gradients_changed) { + assert(index = PALETTE_AUTO); + // We are in the "Auto" palette, and a gradient was added or removed. + // The list of widgets has therefore changed, and must be completely rebuilt. + // We must also rebuild the tracking information for each gradient's isSwatch() status. + rebuild_isswatch(); + rebuild(); + } else if (defs_changed) { + assert(index = PALETTE_AUTO); + // We are in the "Auto" palette, and a gradient's isSwatch() status was possibly modified. + // Check if it has; if so, then the list of widgets has changed, and must be rebuilt. + if (update_isswatch()) { + rebuild(); + } + } + + if (selection_changed) { + update_fillstroke_indicators(); + } + + selection_changed = false; + gradients_changed = false; + defs_changed = false; + + // Necessary to perform *after* the above widget modifications, so GTK can process the new layout. + DialogBase::on_size_allocate(alloc); +} + +// TODO: The following two functions can made much nicer using C++20 ranges. + +void SwatchesPanel::rebuild_isswatch() +{ + auto grads = getDocument()->getResourceList("gradient"); + + isswatch.resize(grads.size()); + + for (int i = 0; i < grads.size(); i++) { + isswatch[i] = static_cast<SPGradient*>(grads[i])->isSwatch(); + } +} + +bool SwatchesPanel::update_isswatch() +{ + auto grads = getDocument()->getResourceList("gradient"); + + // Should be guaranteed because we catch all size changes and call rebuild_isswatch() instead. + assert(isswatch.size() == grads.size()); + + bool modified = false; + + for (int i = 0; i < grads.size(); i++) { + if (isswatch[i] != static_cast<SPGradient*>(grads[i])->isSwatch()) { + isswatch[i].flip(); + modified = true; + } + } + + return modified; +} + +static auto spcolor_to_rgb(SPColor const &color) +{ + float rgbf[3]; + color.get_rgb_floatv(rgbf); + + std::array<unsigned, 3> rgb; + for (int i = 0; i < 3; i++) { + rgb[i] = SP_COLOR_F_TO_U(rgbf[i]); + }; + + return rgb; +} + +void SwatchesPanel::update_fillstroke_indicators() +{ + auto doc = getDocument(); + auto style = SPStyle(doc); + + // Get the current fill or stroke as a ColorKey. + auto current_color = [&, this] (bool fill) -> std::optional<ColorKey> { + switch (sp_desktop_query_style(getDesktop(), &style, fill ? QUERY_STYLE_PROPERTY_FILL : QUERY_STYLE_PROPERTY_STROKE)) + { + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: + case QUERY_STYLE_MULTIPLE_SAME: + break; + default: + return {}; + } + + auto attr = style.getFillOrStroke(fill); + if (!attr->set) { + return {}; + } + + if (attr->isNone()) { + return std::monostate{}; + } else if (attr->isColor()) { + return spcolor_to_rgb(attr->value.color); + } else if (attr->isPaintserver()) { + if (auto grad = cast<SPGradient>(fill ? style.getFillPaintServer() : style.getStrokePaintServer())) { + if (grad->isSwatch()) { + return grad; + } else if (grad->ref) { + if (auto ref = grad->ref->getObject(); ref && ref->isSwatch()) { + return ref; + } + } + } + } + + return {}; + }; + + for (auto w : current_fill) w->set_fill(false); + for (auto w : current_stroke) w->set_stroke(false); + + current_fill.clear(); + current_stroke.clear(); + + if (auto fill = current_color(true)) { + auto range = widgetmap.equal_range(*fill); + for (auto it = range.first; it != range.second; ++it) { + current_fill.emplace_back(it->second); + } + } + + if (auto stroke = current_color(false)) { + auto range = widgetmap.equal_range(*stroke); + for (auto it = range.first; it != range.second; ++it) { + current_stroke.emplace_back(it->second); + } + } + + for (auto w : current_fill) w->set_fill(true); + for (auto w : current_stroke) w->set_stroke(true); +} + +SwatchesPanel::PaletteIndex SwatchesPanel::name_to_index(Glib::ustring const &name) +{ + auto &palettes = GlobalPalettes::get().palettes; + if (name == "Auto") { + return PALETTE_AUTO; + } else if (auto it = std::find_if(palettes.begin(), palettes.end(), [&] (PaletteFileData const &p) {return p.name == name;}); it != palettes.end()) { + return (PaletteIndex)(PALETTE_GLOBAL + std::distance(palettes.begin(), it)); + } else { + return PALETTE_NONE; + } +} + +Glib::ustring SwatchesPanel::index_to_name(PaletteIndex index) +{ + auto &palettes = GlobalPalettes::get().palettes; + if (index == PALETTE_AUTO) { + return "Auto"; + } else if (auto n = index - PALETTE_GLOBAL; n >= 0 && n < palettes.size()) { + return palettes[n].name; + } else { + return ""; + } +} + +/** + * Process the list of available palettes and update the list in the _palette widget. + */ +void SwatchesPanel::update_palettes() +{ + std::vector<Inkscape::UI::Widget::ColorPalette::palette_t> palettes; + palettes.reserve(1 + GlobalPalettes::get().palettes.size()); + + // The first palette in the list is always the "Auto" palette. Although this + // will contain colors when selected, the preview we show for it is empty. + palettes.push_back({"Auto", {}}); + + // The remaining palettes in the list are the global palettes. + for (auto &p : GlobalPalettes::get().palettes) { + Inkscape::UI::Widget::ColorPalette::palette_t palette; + palette.name = p.name; + for (auto const &c : p.colors) { + auto [r, g, b] = c.rgb; + palette.colors.push_back({r / 255.0, g / 255.0, b / 255.0}); + } + palettes.emplace_back(std::move(palette)); + } + + _palette->set_palettes(palettes); +} + +/** + * Rebuild the list of color items shown by the palette. + */ +void SwatchesPanel::rebuild() +{ + std::vector<ColorItem*> palette; + + // The pointers in widgetmap are to widgets owned by the ColorPalette. It is assumed it will not + // delete them unless we ask, via the call to set_colors() later in this function. + widgetmap.clear(); + current_fill.clear(); + current_stroke.clear(); + + // Add the "remove-color" color. + auto w = Gtk::make_managed<ColorItem>(PaintDef(), this); + w->set_pinned_pref(_prefs_path); + palette.emplace_back(w); + widgetmap.emplace(std::monostate{}, w); + + if (index >= PALETTE_GLOBAL) { + auto &pal = GlobalPalettes::get().palettes[index - PALETTE_GLOBAL]; + palette.reserve(palette.size() + pal.colors.size()); + for (auto &c : pal.colors) { + auto w = Gtk::make_managed<ColorItem>(PaintDef(c.rgb, c.name), this); + w->set_pinned_pref(_prefs_path); + palette.emplace_back(w); + widgetmap.emplace(c.rgb, w); + } + } else if (index == PALETTE_AUTO && getDocument()) { + auto grads = getDocument()->getResourceList("gradient"); + for (auto obj : grads) { + auto grad = static_cast<SPGradient*>(obj); + if (grad->isSwatch()) { + auto w = Gtk::make_managed<ColorItem>(grad, this); + palette.emplace_back(w); + widgetmap.emplace(grad, w); + // Rebuild if the gradient gets pinned or unpinned + w->signal_pinned().connect([=]() { + rebuild(); + }); + } + } + } + + if (getDocument()) { + update_fillstroke_indicators(); + } + + _palette->set_colors(palette); + _palette->set_selected(index_to_name(index)); +} + +} //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 : |