diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:29:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:29:01 +0000 |
commit | 35a96bde514a8897f6f0fcc41c5833bf63df2e2a (patch) | |
tree | 657d15a03cc46bd099fc2c6546a7a4ad43815d9f /src/ui/widget | |
parent | Initial commit. (diff) | |
download | inkscape-upstream.tar.xz inkscape-upstream.zip |
Adding upstream version 1.0.2.upstream/1.0.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
132 files changed, 29053 insertions, 0 deletions
diff --git a/src/ui/widget/alignment-selector.cpp b/src/ui/widget/alignment-selector.cpp new file mode 100644 index 0000000..e5ac17a --- /dev/null +++ b/src/ui/widget/alignment-selector.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * anchor-selector.cpp + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/widget/alignment-selector.h" +#include "ui/icon-loader.h" +#include "ui/icon-names.h" + +#include <gtkmm/image.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +void AlignmentSelector::setupButton(const Glib::ustring& icon, Gtk::Button& button) { + Gtk::Image *buttonIcon = Gtk::manage(sp_get_icon_image(icon, Gtk::ICON_SIZE_SMALL_TOOLBAR)); + buttonIcon->show(); + + button.set_relief(Gtk::RELIEF_NONE); + button.show(); + button.add(*buttonIcon); + button.set_can_focus(false); +} + +AlignmentSelector::AlignmentSelector() + : _container() +{ + set_halign(Gtk::ALIGN_CENTER); + setupButton(INKSCAPE_ICON("boundingbox_top_left"), _buttons[0]); + setupButton(INKSCAPE_ICON("boundingbox_top"), _buttons[1]); + setupButton(INKSCAPE_ICON("boundingbox_top_right"), _buttons[2]); + setupButton(INKSCAPE_ICON("boundingbox_left"), _buttons[3]); + setupButton(INKSCAPE_ICON("boundingbox_center"), _buttons[4]); + setupButton(INKSCAPE_ICON("boundingbox_right"), _buttons[5]); + setupButton(INKSCAPE_ICON("boundingbox_bottom_left"), _buttons[6]); + setupButton(INKSCAPE_ICON("boundingbox_bottom"), _buttons[7]); + setupButton(INKSCAPE_ICON("boundingbox_bottom_right"), _buttons[8]); + + _container.set_row_homogeneous(); + _container.set_column_homogeneous(true); + + for(int i = 0; i < 9; ++i) { + _buttons[i].signal_clicked().connect( + sigc::bind(sigc::mem_fun(*this, &AlignmentSelector::btn_activated), i)); + + _container.attach(_buttons[i], i % 3, i / 3, 1, 1); + } + + this->add(_container); +} + +AlignmentSelector::~AlignmentSelector() +{ + // TODO Auto-generated destructor stub +} + +void AlignmentSelector::btn_activated(int index) +{ + _alignmentClicked.emit(index); +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/ui/widget/alignment-selector.h b/src/ui/widget/alignment-selector.h new file mode 100644 index 0000000..5bcf0fa --- /dev/null +++ b/src/ui/widget/alignment-selector.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * anchor-selector.h + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef ANCHOR_SELECTOR_H_ +#define ANCHOR_SELECTOR_H_ + +#include <gtkmm/bin.h> +#include <gtkmm/button.h> +#include <gtkmm/grid.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +class AlignmentSelector : public Gtk::Bin +{ +private: + Gtk::Button _buttons[9]; + Gtk::Grid _container; + + sigc::signal<void, int> _alignmentClicked; + + void setupButton(const Glib::ustring &icon, Gtk::Button &button); + void btn_activated(int index); + +public: + + sigc::signal<void, int> &on_alignmentClicked() { return _alignmentClicked; } + + AlignmentSelector(); + ~AlignmentSelector() override; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif /* ANCHOR_SELECTOR_H_ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/ui/widget/anchor-selector.cpp b/src/ui/widget/anchor-selector.cpp new file mode 100644 index 0000000..b151a81 --- /dev/null +++ b/src/ui/widget/anchor-selector.cpp @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * anchor-selector.cpp + * + * Created on: Mar 22, 2012 + * Author: denis + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include "ui/widget/anchor-selector.h" +#include "ui/icon-loader.h" +#include "ui/icon-names.h" + +#include <gtkmm/image.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +void AnchorSelector::setupButton(const Glib::ustring& icon, Gtk::ToggleButton& button) { + Gtk::Image *buttonIcon = Gtk::manage(sp_get_icon_image(icon, Gtk::ICON_SIZE_SMALL_TOOLBAR)); + buttonIcon->show(); + + button.set_relief(Gtk::RELIEF_NONE); + button.show(); + button.add(*buttonIcon); + button.set_can_focus(false); +} + +AnchorSelector::AnchorSelector() + : _container() +{ + set_halign(Gtk::ALIGN_CENTER); + setupButton(INKSCAPE_ICON("boundingbox_top_left"), _buttons[0]); + setupButton(INKSCAPE_ICON("boundingbox_top"), _buttons[1]); + setupButton(INKSCAPE_ICON("boundingbox_top_right"), _buttons[2]); + setupButton(INKSCAPE_ICON("boundingbox_left"), _buttons[3]); + setupButton(INKSCAPE_ICON("boundingbox_center"), _buttons[4]); + setupButton(INKSCAPE_ICON("boundingbox_right"), _buttons[5]); + setupButton(INKSCAPE_ICON("boundingbox_bottom_left"), _buttons[6]); + setupButton(INKSCAPE_ICON("boundingbox_bottom"), _buttons[7]); + setupButton(INKSCAPE_ICON("boundingbox_bottom_right"), _buttons[8]); + + _container.set_row_homogeneous(); + _container.set_column_homogeneous(true); + + for (int i = 0; i < 9; ++i) { + _buttons[i].signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &AnchorSelector::btn_activated), i)); + + _container.attach(_buttons[i], i % 3, i / 3, 1, 1); + } + _selection = 4; + _buttons[4].set_active(); + + this->add(_container); +} + +AnchorSelector::~AnchorSelector() +{ + // TODO Auto-generated destructor stub +} + +void AnchorSelector::btn_activated(int index) +{ + if (_selection == index && _buttons[index].get_active() == false) { + _buttons[index].set_active(true); + } + else if (_selection != index && _buttons[index].get_active()) { + int old_selection = _selection; + _selection = index; + _buttons[old_selection].set_active(false); + _selectionChanged.emit(); + } +} + +void AnchorSelector::setAlignment(int horizontal, int vertical) +{ + int index = 3 * vertical + horizontal; + if (index >= 0 && index < 9) { + _buttons[index].set_active(!_buttons[index].get_active()); + } +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/ui/widget/anchor-selector.h b/src/ui/widget/anchor-selector.h new file mode 100644 index 0000000..49ce0b2 --- /dev/null +++ b/src/ui/widget/anchor-selector.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * anchor-selector.h + * + * Created on: Mar 22, 2012 + * Author: denis + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef ANCHOR_SELECTOR_H_ +#define ANCHOR_SELECTOR_H_ + +#include <gtkmm/bin.h> +#include <gtkmm/togglebutton.h> +#include <gtkmm/grid.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +class AnchorSelector : public Gtk::Bin +{ +private: + Gtk::ToggleButton _buttons[9]; + int _selection; + Gtk::Grid _container; + + sigc::signal<void> _selectionChanged; + + void setupButton(const Glib::ustring &icon, Gtk::ToggleButton &button); + void btn_activated(int index); + +public: + + int getHorizontalAlignment() { return _selection % 3; } + int getVerticalAlignment() { return _selection / 3; } + + sigc::signal<void> &on_selectionChanged() { return _selectionChanged; } + + void setAlignment(int horizontal, int vertical); + + AnchorSelector(); + ~AnchorSelector() override; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif /* ANCHOR_SELECTOR_H_ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/ui/widget/attr-widget.h b/src/ui/widget/attr-widget.h new file mode 100644 index 0000000..014a540 --- /dev/null +++ b/src/ui/widget/attr-widget.h @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Nicholas Bishop <nicholasbishop@gmail.com> + * Rodrigo Kumpera <kumpera@gmail.com> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_ATTR_WIDGET_H +#define INKSCAPE_UI_WIDGET_ATTR_WIDGET_H + +#include "attributes.h" +#include "object/sp-object.h" +#include "xml/node.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +enum DefaultValueType +{ + T_NONE, + T_DOUBLE, + T_VECT_DOUBLE, + T_BOOL, + T_UINT, + T_CHARPTR +}; + +/** + * Very basic interface for classes that control attributes. + */ +class DefaultValueHolder +{ + DefaultValueType type; + union { + double d_val; + std::vector<double>* vt_val; + bool b_val; + unsigned int uint_val; + char* cptr_val; + } value; + + //FIXME remove copy ctor and assignment operator as private to avoid double free of the vector +public: + DefaultValueHolder () { + type = T_NONE; + } + + DefaultValueHolder (double d) { + type = T_DOUBLE; + value.d_val = d; + } + + DefaultValueHolder (std::vector<double>* d) { + type = T_VECT_DOUBLE; + value.vt_val = d; + } + + DefaultValueHolder (char* c) { + type = T_CHARPTR; + value.cptr_val = c; + } + + DefaultValueHolder (bool d) { + type = T_BOOL; + value.b_val = d; + } + + DefaultValueHolder (unsigned int ui) { + type = T_UINT; + value.uint_val = ui; + } + + ~DefaultValueHolder() { + if (type == T_VECT_DOUBLE) + delete value.vt_val; + } + + unsigned int as_uint() { + g_assert (type == T_UINT); + return value.uint_val; + } + + bool as_bool() { + g_assert (type == T_BOOL); + return value.b_val; + } + + double as_double() { + g_assert (type == T_DOUBLE); + return value.d_val; + } + + std::vector<double>* as_vector() { + g_assert (type == T_VECT_DOUBLE); + return value.vt_val; + } + + char* as_charptr() { + g_assert (type == T_CHARPTR); + return value.cptr_val; + } +}; + +class AttrWidget +{ +public: + AttrWidget(const SPAttributeEnum a, unsigned int value) + : _attr(a), + _default(value) + {} + + AttrWidget(const SPAttributeEnum a, double value) + : _attr(a), + _default(value) + {} + + AttrWidget(const SPAttributeEnum a, bool value) + : _attr(a), + _default(value) + {} + + AttrWidget(const SPAttributeEnum a, char* value) + : _attr(a), + _default(value) + {} + + AttrWidget(const SPAttributeEnum a) + : _attr(a), + _default() + {} + + virtual ~AttrWidget() + = default; + + virtual Glib::ustring get_as_attribute() const = 0; + virtual void set_from_attribute(SPObject*) = 0; + + SPAttributeEnum get_attribute() const + { + return _attr; + } + + sigc::signal<void>& signal_attr_changed() + { + return _signal; + } +protected: + DefaultValueHolder* get_default() { return &_default; } + const gchar* attribute_value(SPObject* o) const + { + const gchar* name = (const gchar*)sp_attribute_name(_attr); + if(name && o) { + const gchar* val = o->getRepr()->attribute(name); + return val; + } + return nullptr; + } + +private: + const SPAttributeEnum _attr; + DefaultValueHolder _default; + sigc::signal<void> _signal; +}; + +} +} +} + +#endif + +/* + 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 : diff --git a/src/ui/widget/button.cpp b/src/ui/widget/button.cpp new file mode 100644 index 0000000..f119c06 --- /dev/null +++ b/src/ui/widget/button.cpp @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Generic button widget + *//* + * Authors: + * see git history + * MenTaLguY <mental@rydia.net> + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm.h> + +#include "button.h" +#include "helper/action-context.h" +#include "helper/action.h" +#include "shortcuts.h" +#include "ui/icon-loader.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +Button::~Button() +{ + if (_action) { + _c_set_active.disconnect(); + _c_set_sensitive.disconnect(); + g_object_unref(_action); + } + + if (_doubleclick_action) { + set_doubleclick_action(nullptr); + } +} + +void +Button::get_preferred_width_vfunc(int &minimal_width, int &natural_width) const +{ + auto child = get_child(); + + if (child) { + child->get_preferred_width(minimal_width, natural_width); + } else { + minimal_width = 0; + natural_width = 0; + } + + auto context = get_style_context(); + + auto padding = context->get_padding(context->get_state()); + auto border = context->get_border(context->get_state()); + + minimal_width += MAX(2, padding.get_left() + padding.get_right() + border.get_left() + border.get_right()); + natural_width += MAX(2, padding.get_left() + padding.get_right() + border.get_left() + border.get_right()); +} + +void +Button::get_preferred_height_vfunc(int &minimal_height, int &natural_height) const +{ + auto child = get_child(); + + if (child) { + child->get_preferred_height(minimal_height, natural_height); + } else { + minimal_height = 0; + natural_height = 0; + } + + auto context = get_style_context(); + + auto padding = context->get_padding(context->get_state()); + auto border = context->get_border(context->get_state()); + + minimal_height += MAX(2, padding.get_top() + padding.get_bottom() + border.get_top() + border.get_bottom()); + natural_height += MAX(2, padding.get_top() + padding.get_bottom() + border.get_top() + border.get_bottom()); +} + +void +Button::on_clicked() +{ + if (_type == BUTTON_TYPE_TOGGLE) { + Gtk::Button::on_clicked(); + } +} + +bool +Button::process_event(GdkEvent *event) +{ + switch (event->type) { + case GDK_2BUTTON_PRESS: + if (_doubleclick_action) { + sp_action_perform(_doubleclick_action, nullptr); + } + return true; + break; + default: + break; + } + + return false; +} + +void +Button::perform_action() +{ + if (_action) { + sp_action_perform(_action, nullptr); + } +} + +Button::Button(GtkIconSize size, + ButtonType type, + SPAction *action, + SPAction *doubleclick_action) + : + _action(nullptr), + _doubleclick_action(nullptr), + _type(type), + _lsize(CLAMP(size, GTK_ICON_SIZE_MENU, GTK_ICON_SIZE_DIALOG)) +{ + set_border_width(0); + + set_can_focus(false); + set_can_default(false); + + _on_clicked = signal_clicked().connect(sigc::mem_fun(*this, &Button::perform_action)); + + signal_event().connect(sigc::mem_fun(*this, &Button::process_event)); + + set_action(action); + + if (doubleclick_action) { + set_doubleclick_action(doubleclick_action); + } + + // The Inkscape style is no-relief buttons + set_relief(Gtk::RELIEF_NONE); +} + +void +Button::toggle_set_down(bool down) +{ + _on_clicked.block(); + set_active(down); + _on_clicked.unblock(); +} + +void +Button::set_doubleclick_action(SPAction *action) +{ + if (_doubleclick_action) { + g_object_unref(_doubleclick_action); + } + _doubleclick_action = action; + if (action) { + g_object_ref(action); + } +} + +void +Button::set_action(SPAction *action) +{ + Gtk::Widget *child; + + if (_action) { + _c_set_active.disconnect(); + _c_set_sensitive.disconnect(); + child = get_child(); + if (child) { + remove(); + } + g_object_unref(_action); + } + + _action = action; + if (action) { + g_object_ref(action); + _c_set_active = action->signal_set_active.connect( + sigc::mem_fun(*this, &Button::action_set_active)); + + _c_set_sensitive = action->signal_set_sensitive.connect( + sigc::mem_fun(*this, &Gtk::Widget::set_sensitive)); + + if (action->image) { + child = Glib::wrap(sp_get_icon_image(action->image, _lsize)); + child->show(); + add(*child); + } + } + + set_composed_tooltip(action); +} + +void +Button::action_set_active(bool active) +{ + if (_type != BUTTON_TYPE_TOGGLE) { + return; + } + + /* temporarily lobotomized until SPActions are per-view */ + if (false && !active != !get_active()) { + toggle_set_down(active); + } +} + +void +Button::set_composed_tooltip(SPAction *action) +{ + if (action) { + unsigned int shortcut = sp_shortcut_get_primary(action->verb); + if (shortcut != GDK_KEY_VoidSymbol) { + // there's both action and shortcut + + gchar *key = sp_shortcut_get_label(shortcut); + + gchar *tip = g_strdup_printf("%s (%s)", action->tip, key); + set_tooltip_text(tip); + g_free(tip); + g_free(key); + } else { + // action has no shortcut + set_tooltip_text(action->tip); + } + } else { + // no action + set_tooltip_text(nullptr); + } +} + +Button::Button(GtkIconSize size, + ButtonType type, + Inkscape::UI::View::View *view, + const gchar *name, + const gchar *tip) + : + _action(nullptr), + _doubleclick_action(nullptr), + _type(type), + _lsize(CLAMP(size, GTK_ICON_SIZE_MENU, GTK_ICON_SIZE_DIALOG)) +{ + set_border_width(0); + + set_can_focus(false); + set_can_default(false); + + _on_clicked = signal_clicked().connect(sigc::mem_fun(*this, &Button::perform_action)); + signal_event().connect(sigc::mem_fun(*this, &Button::process_event)); + + auto action = sp_action_new(Inkscape::ActionContext(view), name, name, tip, name, nullptr); + set_action(action); + g_object_unref(action); +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/button.h b/src/ui/widget/button.h new file mode 100644 index 0000000..0b1bfc2 --- /dev/null +++ b/src/ui/widget/button.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Generic button widget + *//* + * Authors: + * see git history + * Lauris Kaplinski <lauris@kaplinski.com> + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_SP_BUTTON_H +#define SEEN_SP_BUTTON_H + +#include <gtkmm/togglebutton.h> +#include <sigc++/connection.h> + +struct SPAction; + +namespace Inkscape { +namespace UI { +namespace View { +class View; +} + +namespace Widget { + +enum ButtonType { + BUTTON_TYPE_NORMAL, + BUTTON_TYPE_TOGGLE +}; + +class Button : public Gtk::ToggleButton{ +private: + ButtonType _type; + GtkIconSize _lsize; + unsigned int _psize; + SPAction *_action; + SPAction *_doubleclick_action; + + sigc::connection _c_set_active; + sigc::connection _c_set_sensitive; + + void set_action(SPAction *action); + void set_doubleclick_action(SPAction *action); + void set_composed_tooltip(SPAction *action); + void action_set_active(bool active); + void perform_action(); + bool process_event(GdkEvent *event); + + sigc::connection _on_clicked; + +protected: + void get_preferred_width_vfunc(int &minimum_width, int &natural_width) const override; + void get_preferred_height_vfunc(int &minimum_height, int &natural_height) const override; + void on_clicked() override; + +public: + Button(GtkIconSize size, + ButtonType type, + SPAction *action, + SPAction *doubleclick_action); + + Button(GtkIconSize size, + ButtonType type, + Inkscape::UI::View::View *view, + const gchar *name, + const gchar *tip); + + ~Button() override; + + void toggle_set_down(bool down); +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape +#endif // !SEEN_SP_BUTTON_H + +/* + 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 : diff --git a/src/ui/widget/clipmaskicon.cpp b/src/ui/widget/clipmaskicon.cpp new file mode 100644 index 0000000..1093c1f --- /dev/null +++ b/src/ui/widget/clipmaskicon.cpp @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/widget/clipmaskicon.h" + +#include "layertypeicon.h" +#include "ui/icon-loader.h" +#include "ui/icon-names.h" +#include "widgets/toolbox.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +ClipMaskIcon::ClipMaskIcon() : + Glib::ObjectBase(typeid(ClipMaskIcon)), + Gtk::CellRendererPixbuf(), + _pixClipName(INKSCAPE_ICON("path-cut")), + _pixMaskName(INKSCAPE_ICON("path-difference")), + _pixBothName(INKSCAPE_ICON("bitmap-trace")), + _property_active(*this, "active", 0), + _property_pixbuf_clip(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr)), + _property_pixbuf_mask(*this, "pixbuf_off", Glib::RefPtr<Gdk::Pixbuf>(nullptr)), + _property_pixbuf_both(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr)) +{ + + property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE; + + _property_pixbuf_clip = sp_get_icon_pixbuf(_pixClipName, GTK_ICON_SIZE_MENU); + _property_pixbuf_mask = sp_get_icon_pixbuf(_pixMaskName, GTK_ICON_SIZE_MENU); + _property_pixbuf_both = sp_get_icon_pixbuf(_pixBothName, GTK_ICON_SIZE_MENU); + + property_pixbuf() = Glib::RefPtr<Gdk::Pixbuf>(nullptr); +} + +void ClipMaskIcon::get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const +{ + Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h); + + if (min_h) { + min_h += (min_h) >> 1; + } + + if (nat_h) { + nat_h += (nat_h) >> 1; + } +} + +void ClipMaskIcon::get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const +{ + Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w); + + if (min_w) { + min_w += (min_w) >> 1; + } + + if (nat_w) { + nat_w += (nat_w) >> 1; + } +} + +void ClipMaskIcon::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) +{ + switch (_property_active.get_value()) + { + case 1: + property_pixbuf() = _property_pixbuf_clip; + break; + case 2: + property_pixbuf() = _property_pixbuf_mask; + break; + case 3: + property_pixbuf() = _property_pixbuf_both; + break; + default: + property_pixbuf() = Glib::RefPtr<Gdk::Pixbuf>(nullptr); + break; + } + Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags ); +} + +bool ClipMaskIcon::activate_vfunc(GdkEvent* /*event*/, + Gtk::Widget& /*widget*/, + const Glib::ustring& /*path*/, + const Gdk::Rectangle& /*background_area*/, + const Gdk::Rectangle& /*cell_area*/, + Gtk::CellRendererState /*flags*/) +{ + return false; +} + + +} // namespace Widget +} // 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 : + + diff --git a/src/ui/widget/clipmaskicon.h b/src/ui/widget/clipmaskicon.h new file mode 100644 index 0000000..d8bbe52 --- /dev/null +++ b/src/ui/widget/clipmaskicon.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __UI_DIALOG_CLIPMASKICON_H__ +#define __UI_DIALOG_CLIPMASKICON_H__ +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/cellrendererpixbuf.h> +#include <gtkmm/widget.h> +#include <glibmm/property.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +class ClipMaskIcon : public Gtk::CellRendererPixbuf { +public: + ClipMaskIcon(); + ~ClipMaskIcon() override = default;; + + Glib::PropertyProxy<int> property_active() { return _property_active.get_proxy(); } + Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_on(); + Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_off(); + +protected: + + void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) override; + + void get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const override; + + void get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const override; + + bool activate_vfunc(GdkEvent *event, + Gtk::Widget &widget, + const Glib::ustring &path, + const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, + Gtk::CellRendererState flags) override; + + +private: + int phys; + + Glib::ustring _pixClipName; + Glib::ustring _pixMaskName; + Glib::ustring _pixBothName; + + Glib::Property<int> _property_active; + Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_clip; + Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_mask; + Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_both; + +}; + + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + + +#endif /* __UI_DIALOG_IMAGETOGGLER_H__ */ + +/* + 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 : diff --git a/src/ui/widget/color-entry.cpp b/src/ui/widget/color-entry.cpp new file mode 100644 index 0000000..804350c --- /dev/null +++ b/src/ui/widget/color-entry.cpp @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Entry widget for typing color value in css form + *//* + * Authors: + * Tomasz Boczkowski <penginsbacon@gmail.com> + * + * Copyright (C) 2014 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include <glibmm.h> +#include <glibmm/i18n.h> +#include <iomanip> + +#include "color-entry.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +ColorEntry::ColorEntry(SelectedColor &color) + : _color(color) + , _updating(false) + , _updatingrgba(false) + , _prevpos(0) + , _lastcolor(0) +{ + _color_changed_connection = color.signal_changed.connect(sigc::mem_fun(this, &ColorEntry::_onColorChanged)); + _color_dragged_connection = color.signal_dragged.connect(sigc::mem_fun(this, &ColorEntry::_onColorChanged)); + signal_activate().connect(sigc::mem_fun(this, &ColorEntry::_onColorChanged)); + get_buffer()->signal_inserted_text().connect(sigc::mem_fun(this, &ColorEntry::_inputCheck)); + _onColorChanged(); + + // add extra character for pasting a hash, '#11223344' + set_max_length(9); + set_width_chars(8); + set_tooltip_text(_("Hexadecimal RGBA value of the color")); +} + +ColorEntry::~ColorEntry() +{ + _color_changed_connection.disconnect(); + _color_dragged_connection.disconnect(); +} + +void ColorEntry::_inputCheck(guint pos, const gchar * /*chars*/, guint n_chars) +{ + // remember position of last character, so we can remove it. + // we only overflow by 1 character at most. + _prevpos = pos + n_chars - 1; +} + +void ColorEntry::on_changed() +{ + if (_updating) { + return; + } + if (_updatingrgba) { + return; // Typing text into entry box + } + + Glib::ustring text = get_text(); + bool changed = false; + + // Coerce the value format to hexadecimal + for (auto it = text.begin(); it != text.end(); /*++it*/) { + if (!g_ascii_isxdigit(*it)) { + text.erase(it); + changed = true; + } else { + ++it; + } + } + + if (text.size() > 8) { + text.erase(_prevpos, 1); + changed = true; + } + + // autofill rules + gchar *str = g_strdup(text.c_str()); + gchar *end = nullptr; + guint64 rgba = g_ascii_strtoull(str, &end, 16); + ptrdiff_t len = end - str; + if (len < 8) { + if (len == 0) { + rgba = _lastcolor; + } else if (len <= 2) { + if (len == 1) { + rgba *= 17; + } + rgba = (rgba << 24) + (rgba << 16) + (rgba << 8); + } else if (len <= 4) { + // display as rrggbbaa + rgba = rgba << (4 * (4 - len)); + guint64 r = rgba & 0xf000; + guint64 g = rgba & 0x0f00; + guint64 b = rgba & 0x00f0; + guint64 a = rgba & 0x000f; + rgba = 17 * ((r << 12) + (g << 8) + (b << 4) + a); + } else { + rgba = rgba << (4 * (8 - len)); + } + + if (len == 7) { + rgba = (rgba & 0xfffffff0) + (_lastcolor & 0x00f); + } else if (len == 5) { + rgba = (rgba & 0xfffff000) + (_lastcolor & 0xfff); + } else if (len != 4 && len != 8) { + rgba = (rgba & 0xffffff00) + (_lastcolor & 0x0ff); + } + } + + _updatingrgba = true; + if (changed) { + set_text(str); + } + SPColor color(rgba); + _color.setColorAlpha(color, SP_RGBA32_A_F(rgba)); + _updatingrgba = false; + + g_free(str); +} + + +void ColorEntry::_onColorChanged() +{ + if (_updatingrgba) { + return; + } + + SPColor color = _color.color(); + gdouble alpha = _color.alpha(); + + _lastcolor = color.toRGBA32(alpha); + Glib::ustring text = Glib::ustring::format(std::hex, std::setw(8), std::setfill(L'0'), _lastcolor); + + Glib::ustring old_text = get_text(); + if (old_text != text) { + _updating = true; + set_text(text); + _updating = false; + } +} +} +} +} +/* + 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 : diff --git a/src/ui/widget/color-entry.h b/src/ui/widget/color-entry.h new file mode 100644 index 0000000..4df80de --- /dev/null +++ b/src/ui/widget/color-entry.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Entry widget for typing color value in css form + *//* + * Authors: + * Tomasz Boczkowski <penginsbacon@gmail.com> + * + * Copyright (C) 2014 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLOR_ENTRY_H +#define SEEN_COLOR_ENTRY_H + +#include <gtkmm/entry.h> +#include "ui/selected-color.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +class ColorEntry : public Gtk::Entry +{ +public: + ColorEntry(SelectedColor &color); + ~ColorEntry() override; + +protected: + void on_changed() override; + +private: + void _onColorChanged(); + void _inputCheck(guint pos, const gchar * /*chars*/, guint /*n_chars*/); + + SelectedColor &_color; + sigc::connection _color_changed_connection; + sigc::connection _color_dragged_connection; + bool _updating; + bool _updatingrgba; + guint32 _lastcolor; + int _prevpos; +}; + +} +} +} + +#endif +/* + 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 : diff --git a/src/ui/widget/color-icc-selector.cpp b/src/ui/widget/color-icc-selector.cpp new file mode 100644 index 0000000..3100605 --- /dev/null +++ b/src/ui/widget/color-icc-selector.cpp @@ -0,0 +1,1074 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#include <set> +#include <utility> + +#include <gtkmm/adjustment.h> +#include <glibmm/i18n.h> + +#include "colorspace.h" +#include "document.h" +#include "inkscape.h" +#include "profile-manager.h" + +#include "svg/svg-icc-color.h" + +#include "ui/dialog-events.h" +#include "ui/util.h" +#include "ui/widget/color-icc-selector.h" +#include "ui/widget/color-scales.h" +#include "ui/widget/color-slider.h" + +#define noDEBUG_LCMS + +#if defined(HAVE_LIBLCMS2) +#include "object/color-profile.h" +#include "cms-system.h" +#include "color-profile-cms-fns.h" + +#ifdef DEBUG_LCMS +#include "preferences.h" +#endif // DEBUG_LCMS +#endif // defined(HAVE_LIBLCMS2) + +#ifdef DEBUG_LCMS +extern guint update_in_progress; +#define DEBUG_MESSAGE(key, ...) \ + { \ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); \ + bool dump = prefs->getBool("/options/scislac/" #key); \ + bool dumpD = prefs->getBool("/options/scislac/" #key "D"); \ + bool dumpD2 = prefs->getBool("/options/scislac/" #key "D2"); \ + dumpD && = ((update_in_progress == 0) || dumpD2); \ + if (dump) { \ + g_message(__VA_ARGS__); \ + } \ + if (dumpD) { \ + GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, \ + GTK_BUTTONS_OK, __VA_ARGS__); \ + g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog); \ + gtk_widget_show_all(dialog); \ + } \ + } +#endif // DEBUG_LCMS + + +#define XPAD 4 +#define YPAD 1 + +namespace { + +size_t maxColorspaceComponentCount = 0; + +#if defined(HAVE_LIBLCMS2) + +/** + * Internal variable to track all known colorspaces. + */ +std::set<cmsUInt32Number> knownColorspaces; + +#endif + +/** + * Helper function to handle GTK2/GTK3 attachment #ifdef code. + */ +void attachToGridOrTable(GtkWidget *parent, GtkWidget *child, guint left, guint top, guint width, guint height, + bool hexpand = false, bool centered = false, guint xpadding = XPAD, guint ypadding = YPAD) +{ + gtk_widget_set_margin_start(child, xpadding); + gtk_widget_set_margin_end(child, xpadding); + gtk_widget_set_margin_top(child, ypadding); + gtk_widget_set_margin_bottom(child, ypadding); + + if (hexpand) { + gtk_widget_set_hexpand(child, TRUE); + } + + if (centered) { + gtk_widget_set_halign(child, GTK_ALIGN_CENTER); + gtk_widget_set_valign(child, GTK_ALIGN_CENTER); + } + + gtk_grid_attach(GTK_GRID(parent), child, left, top, width, height); +} + +} // namespace + +/* +icSigRgbData +icSigCmykData +icSigCmyData +*/ +#define SPACE_ID_RGB 0 +#define SPACE_ID_CMY 1 +#define SPACE_ID_CMYK 2 + + +colorspace::Component::Component() + : name() + , tip() + , scale(1) +{ +} + +colorspace::Component::Component(std::string name, std::string tip, guint scale) + : name(std::move(name)) + , tip(std::move(tip)) + , scale(scale) +{ +} + +#if defined(HAVE_LIBLCMS2) +static cmsUInt16Number *getScratch() +{ + // bytes per pixel * input channels * width + static cmsUInt16Number *scritch = static_cast<cmsUInt16Number *>(g_new(cmsUInt16Number, 4 * 1024)); + + return scritch; +} + +std::vector<colorspace::Component> colorspace::getColorSpaceInfo(uint32_t space) +{ + static std::map<cmsUInt32Number, std::vector<Component> > sets; + if (sets.empty()) { + sets[cmsSigXYZData].push_back(Component("_X", "X", 2)); // TYPE_XYZ_16 + sets[cmsSigXYZData].push_back(Component("_Y", "Y", 1)); + sets[cmsSigXYZData].push_back(Component("_Z", "Z", 2)); + + sets[cmsSigLabData].push_back(Component("_L", "L", 100)); // TYPE_Lab_16 + sets[cmsSigLabData].push_back(Component("_a", "a", 256)); + sets[cmsSigLabData].push_back(Component("_b", "b", 256)); + + // cmsSigLuvData + + sets[cmsSigYCbCrData].push_back(Component("_Y", "Y", 1)); // TYPE_YCbCr_16 + sets[cmsSigYCbCrData].push_back(Component("C_b", "Cb", 1)); + sets[cmsSigYCbCrData].push_back(Component("C_r", "Cr", 1)); + + sets[cmsSigYxyData].push_back(Component("_Y", "Y", 1)); // TYPE_Yxy_16 + sets[cmsSigYxyData].push_back(Component("_x", "x", 1)); + sets[cmsSigYxyData].push_back(Component("y", "y", 1)); + + sets[cmsSigRgbData].push_back(Component(_("_R:"), _("Red"), 1)); // TYPE_RGB_16 + sets[cmsSigRgbData].push_back(Component(_("_G:"), _("Green"), 1)); + sets[cmsSigRgbData].push_back(Component(_("_B:"), _("Blue"), 1)); + + sets[cmsSigGrayData].push_back(Component(_("G:"), _("Gray"), 1)); // TYPE_GRAY_16 + + sets[cmsSigHsvData].push_back(Component(_("_H:"), _("Hue"), 360)); // TYPE_HSV_16 + sets[cmsSigHsvData].push_back(Component(_("_S:"), _("Saturation"), 1)); + sets[cmsSigHsvData].push_back(Component("_V:", "Value", 1)); + + sets[cmsSigHlsData].push_back(Component(_("_H:"), _("Hue"), 360)); // TYPE_HLS_16 + sets[cmsSigHlsData].push_back(Component(_("_L:"), _("Lightness"), 1)); + sets[cmsSigHlsData].push_back(Component(_("_S:"), _("Saturation"), 1)); + + sets[cmsSigCmykData].push_back(Component(_("_C:"), _("Cyan"), 1)); // TYPE_CMYK_16 + sets[cmsSigCmykData].push_back(Component(_("_M:"), _("Magenta"), 1)); + sets[cmsSigCmykData].push_back(Component(_("_Y:"), _("Yellow"), 1)); + sets[cmsSigCmykData].push_back(Component(_("_K:"), _("Black"), 1)); + + sets[cmsSigCmyData].push_back(Component(_("_C:"), _("Cyan"), 1)); // TYPE_CMY_16 + sets[cmsSigCmyData].push_back(Component(_("_M:"), _("Magenta"), 1)); + sets[cmsSigCmyData].push_back(Component(_("_Y:"), _("Yellow"), 1)); + + for (auto & set : sets) { + knownColorspaces.insert(set.first); + maxColorspaceComponentCount = std::max(maxColorspaceComponentCount, set.second.size()); + } + } + + std::vector<Component> target; + + if (sets.find(space) != sets.end()) { + target = sets[space]; + } + return target; +} + + +std::vector<colorspace::Component> colorspace::getColorSpaceInfo(Inkscape::ColorProfile *prof) +{ + return getColorSpaceInfo(asICColorSpaceSig(prof->getColorSpace())); +} + +#endif // defined(HAVE_LIBLCMS2) + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Class containing the parts for a single color component's UI presence. + */ +class ComponentUI { + public: + ComponentUI() + : _component() + , _adj(nullptr) + , _slider(nullptr) + , _btn(nullptr) + , _label(nullptr) + , _map(nullptr) + { + } + + ComponentUI(colorspace::Component component) + : _component(std::move(component)) + , _adj(nullptr) + , _slider(nullptr) + , _btn(nullptr) + , _label(nullptr) + , _map(nullptr) + { + } + + colorspace::Component _component; + GtkAdjustment *_adj; // Component adjustment + Inkscape::UI::Widget::ColorSlider *_slider; + GtkWidget *_btn; // spinbutton + GtkWidget *_label; // Label + guchar *_map; +}; + +/** + * Class that implements the internals of the selector. + */ +class ColorICCSelectorImpl { + public: + ColorICCSelectorImpl(ColorICCSelector *owner, SelectedColor &color); + + ~ColorICCSelectorImpl(); + + static void _adjustmentChanged(GtkAdjustment *adjustment, ColorICCSelectorImpl *cs); + + void _sliderGrabbed(); + void _sliderReleased(); + void _sliderChanged(); + + static void _profileSelected(GtkWidget *src, gpointer data); + static void _fixupHit(GtkWidget *src, gpointer data); + +#if defined(HAVE_LIBLCMS2) + void _setProfile(SVGICCColor *profile); + void _switchToProfile(gchar const *name); +#endif + void _updateSliders(gint ignore); + void _profilesChanged(std::string const &name); + + ColorICCSelector *_owner; + SelectedColor &_color; + + gboolean _updating : 1; + gboolean _dragging : 1; + + guint32 _fixupNeeded; + GtkWidget *_fixupBtn; + GtkWidget *_profileSel; + + std::vector<ComponentUI> _compUI; + + GtkAdjustment *_adj; // Channel adjustment + Inkscape::UI::Widget::ColorSlider *_slider; + GtkWidget *_sbtn; // Spinbutton + GtkWidget *_label; // Label + +#if defined(HAVE_LIBLCMS2) + std::string _profileName; + Inkscape::ColorProfile *_prof; + guint _profChannelCount; + gulong _profChangedID; +#endif // defined(HAVE_LIBLCMS2) +}; + + + +const gchar *ColorICCSelector::MODE_NAME = N_("CMS"); + +ColorICCSelector::ColorICCSelector(SelectedColor &color) + : _impl(nullptr) +{ + _impl = new ColorICCSelectorImpl(this, color); + init(); + color.signal_changed.connect(sigc::mem_fun(this, &ColorICCSelector::_colorChanged)); + // color.signal_dragged.connect(sigc::mem_fun(this, &ColorICCSelector::_colorChanged)); +} + +ColorICCSelector::~ColorICCSelector() +{ + if (_impl) { + delete _impl; + _impl = nullptr; + } +} + + + +ColorICCSelectorImpl::ColorICCSelectorImpl(ColorICCSelector *owner, SelectedColor &color) + : _owner(owner) + , _color(color) + , _updating(FALSE) + , _dragging(FALSE) + , _fixupNeeded(0) + , _fixupBtn(nullptr) + , _profileSel(nullptr) + , _compUI() + , _adj(nullptr) + , _slider(nullptr) + , _sbtn(nullptr) + , _label(nullptr) +#if defined(HAVE_LIBLCMS2) + , _profileName() + , _prof(nullptr) + , _profChannelCount(0) + , _profChangedID(0) +#endif // defined(HAVE_LIBLCMS2) +{ +} + +ColorICCSelectorImpl::~ColorICCSelectorImpl() +{ + _adj = nullptr; + _sbtn = nullptr; + _label = nullptr; +} + +void ColorICCSelector::init() +{ + gint row = 0; + + _impl->_updating = FALSE; + _impl->_dragging = FALSE; + + GtkWidget *t = GTK_WIDGET(gobj()); + + _impl->_compUI.clear(); + + // Create components + row = 0; + + + _impl->_fixupBtn = gtk_button_new_with_label(_("Fix")); + g_signal_connect(G_OBJECT(_impl->_fixupBtn), "clicked", G_CALLBACK(ColorICCSelectorImpl::_fixupHit), + (gpointer)_impl); + gtk_widget_set_sensitive(_impl->_fixupBtn, FALSE); + gtk_widget_set_tooltip_text(_impl->_fixupBtn, _("Fix RGB fallback to match icc-color() value.")); + gtk_widget_show(_impl->_fixupBtn); + + attachToGridOrTable(t, _impl->_fixupBtn, 0, row, 1, 1); + + // Combobox and store with 2 columns : label (0) and full name (1) + GtkListStore *store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); + _impl->_profileSel = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store)); + + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(_impl->_profileSel), renderer, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(_impl->_profileSel), renderer, "text", 0, NULL); + + GtkTreeIter iter; + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, _("<none>"), 1, _("<none>"), -1); + + gtk_widget_show(_impl->_profileSel); + gtk_combo_box_set_active(GTK_COMBO_BOX(_impl->_profileSel), 0); + + attachToGridOrTable(t, _impl->_profileSel, 1, row, 1, 1); + +#if defined(HAVE_LIBLCMS2) + _impl->_profChangedID = g_signal_connect(G_OBJECT(_impl->_profileSel), "changed", + G_CALLBACK(ColorICCSelectorImpl::_profileSelected), (gpointer)_impl); +#else + gtk_widget_set_sensitive(_impl->_profileSel, false); +#endif // defined(HAVE_LIBLCMS2) + + + row++; + +// populate the data for colorspaces and channels: +#if defined(HAVE_LIBLCMS2) + std::vector<colorspace::Component> things = colorspace::getColorSpaceInfo(cmsSigRgbData); +#endif // defined(HAVE_LIBLCMS2) + + for (size_t i = 0; i < maxColorspaceComponentCount; i++) { +#if defined(HAVE_LIBLCMS2) + if (i < things.size()) { + _impl->_compUI.emplace_back(things[i]); + } + else { + _impl->_compUI.emplace_back(); + } + + std::string labelStr = (i < things.size()) ? things[i].name.c_str() : ""; +#else + _impl->_compUI.push_back(ComponentUI()); + + std::string labelStr = "."; +#endif + + _impl->_compUI[i]._label = gtk_label_new_with_mnemonic(labelStr.c_str()); + + gtk_widget_set_halign(_impl->_compUI[i]._label, GTK_ALIGN_END); + gtk_widget_show(_impl->_compUI[i]._label); + gtk_widget_set_no_show_all(_impl->_compUI[i]._label, TRUE); + + attachToGridOrTable(t, _impl->_compUI[i]._label, 0, row, 1, 1); + + // Adjustment + guint scaleValue = _impl->_compUI[i]._component.scale; + gdouble step = static_cast<gdouble>(scaleValue) / 100.0; + gdouble page = static_cast<gdouble>(scaleValue) / 10.0; + gint digits = (step > 0.9) ? 0 : 2; + _impl->_compUI[i]._adj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, scaleValue, step, page, page)); + + // Slider + _impl->_compUI[i]._slider = + Gtk::manage(new Inkscape::UI::Widget::ColorSlider(Glib::wrap(_impl->_compUI[i]._adj, true))); +#if defined(HAVE_LIBLCMS2) + _impl->_compUI[i]._slider->set_tooltip_text((i < things.size()) ? things[i].tip.c_str() : ""); +#else + _impl->_compUI[i]._slider->set_tooltip_text("."); +#endif // defined(HAVE_LIBLCMS2) + _impl->_compUI[i]._slider->show(); + _impl->_compUI[i]._slider->set_no_show_all(); + + attachToGridOrTable(t, _impl->_compUI[i]._slider->gobj(), 1, row, 1, 1, true); + + _impl->_compUI[i]._btn = gtk_spin_button_new(_impl->_compUI[i]._adj, step, digits); +#if defined(HAVE_LIBLCMS2) + gtk_widget_set_tooltip_text(_impl->_compUI[i]._btn, (i < things.size()) ? things[i].tip.c_str() : ""); +#else + gtk_widget_set_tooltip_text(_impl->_compUI[i]._btn, "."); +#endif // defined(HAVE_LIBLCMS2) + sp_dialog_defocus_on_enter(_impl->_compUI[i]._btn); + gtk_label_set_mnemonic_widget(GTK_LABEL(_impl->_compUI[i]._label), _impl->_compUI[i]._btn); + gtk_widget_show(_impl->_compUI[i]._btn); + gtk_widget_set_no_show_all(_impl->_compUI[i]._btn, TRUE); + + attachToGridOrTable(t, _impl->_compUI[i]._btn, 2, row, 1, 1, false, true); + + _impl->_compUI[i]._map = g_new(guchar, 4 * 1024); + memset(_impl->_compUI[i]._map, 0x0ff, 1024 * 4); + + + // Signals + g_signal_connect(G_OBJECT(_impl->_compUI[i]._adj), "value_changed", + G_CALLBACK(ColorICCSelectorImpl::_adjustmentChanged), _impl); + + _impl->_compUI[i]._slider->signal_grabbed.connect(sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderGrabbed)); + _impl->_compUI[i]._slider->signal_released.connect( + sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderReleased)); + _impl->_compUI[i]._slider->signal_value_changed.connect( + sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderChanged)); + + row++; + } + + // Label + _impl->_label = gtk_label_new_with_mnemonic(_("_A:")); + + gtk_widget_set_halign(_impl->_label, GTK_ALIGN_END); + gtk_widget_show(_impl->_label); + + attachToGridOrTable(t, _impl->_label, 0, row, 1, 1); + + // Adjustment + _impl->_adj = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 100.0, 1.0, 10.0, 10.0)); + + // Slider + _impl->_slider = Gtk::manage(new Inkscape::UI::Widget::ColorSlider(Glib::wrap(_impl->_adj, true))); + _impl->_slider->set_tooltip_text(_("Alpha (opacity)")); + _impl->_slider->show(); + + attachToGridOrTable(t, _impl->_slider->gobj(), 1, row, 1, 1, true); + + _impl->_slider->setColors(SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 0.0), SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 0.5), + SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 1.0)); + + + // Spinbutton + _impl->_sbtn = gtk_spin_button_new(GTK_ADJUSTMENT(_impl->_adj), 1.0, 0); + gtk_widget_set_tooltip_text(_impl->_sbtn, _("Alpha (opacity)")); + sp_dialog_defocus_on_enter(_impl->_sbtn); + gtk_label_set_mnemonic_widget(GTK_LABEL(_impl->_label), _impl->_sbtn); + gtk_widget_show(_impl->_sbtn); + + attachToGridOrTable(t, _impl->_sbtn, 2, row, 1, 1, false, true); + + // Signals + g_signal_connect(G_OBJECT(_impl->_adj), "value_changed", G_CALLBACK(ColorICCSelectorImpl::_adjustmentChanged), + _impl); + + _impl->_slider->signal_grabbed.connect(sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderGrabbed)); + _impl->_slider->signal_released.connect(sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderReleased)); + _impl->_slider->signal_value_changed.connect(sigc::mem_fun(_impl, &ColorICCSelectorImpl::_sliderChanged)); + + gtk_widget_show(t); +} + +void ColorICCSelectorImpl::_fixupHit(GtkWidget * /*src*/, gpointer data) +{ + ColorICCSelectorImpl *self = reinterpret_cast<ColorICCSelectorImpl *>(data); + gtk_widget_set_sensitive(self->_fixupBtn, FALSE); + self->_adjustmentChanged(self->_compUI[0]._adj, self); +} + +#if defined(HAVE_LIBLCMS2) +void ColorICCSelectorImpl::_profileSelected(GtkWidget * /*src*/, gpointer data) +{ + ColorICCSelectorImpl *self = reinterpret_cast<ColorICCSelectorImpl *>(data); + + GtkTreeIter iter; + if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(self->_profileSel), &iter)) { + GtkTreeModel *store = gtk_combo_box_get_model(GTK_COMBO_BOX(self->_profileSel)); + gchar *name = nullptr; + + gtk_tree_model_get(store, &iter, 1, &name, -1); + self->_switchToProfile(name); + gtk_widget_set_tooltip_text(self->_profileSel, name); + + g_free(name); + } +} +#endif // defined(HAVE_LIBLCMS2) + +#if defined(HAVE_LIBLCMS2) +void ColorICCSelectorImpl::_switchToProfile(gchar const *name) +{ + bool dirty = false; + SPColor tmp(_color.color()); + + if (name) { + if (tmp.icc && tmp.icc->colorProfile == name) { +#ifdef DEBUG_LCMS + g_message("Already at name [%s]", name); +#endif // DEBUG_LCMS + } + else { +#ifdef DEBUG_LCMS + g_message("Need to switch to profile [%s]", name); +#endif // DEBUG_LCMS + if (tmp.icc) { + tmp.icc->colors.clear(); + } + else { + tmp.icc = new SVGICCColor(); + } + tmp.icc->colorProfile = name; + Inkscape::ColorProfile *newProf = SP_ACTIVE_DOCUMENT->getProfileManager()->find(name); + if (newProf) { + cmsHTRANSFORM trans = newProf->getTransfFromSRGB8(); + if (trans) { + guint32 val = _color.color().toRGBA32(0); + guchar pre[4] = { + static_cast<guchar>(SP_RGBA32_R_U(val)), + static_cast<guchar>(SP_RGBA32_G_U(val)), + static_cast<guchar>(SP_RGBA32_B_U(val)), + 255}; +#ifdef DEBUG_LCMS + g_message("Shoving in [%02x] [%02x] [%02x]", pre[0], pre[1], pre[2]); +#endif // DEBUG_LCMS + cmsUInt16Number post[4] = { 0, 0, 0, 0 }; + cmsDoTransform(trans, pre, post, 1); +#ifdef DEBUG_LCMS + g_message("got on out [%04x] [%04x] [%04x] [%04x]", post[0], post[1], post[2], post[3]); +#endif // DEBUG_LCMS +#if HAVE_LIBLCMS2 + guint count = cmsChannelsOf(asICColorSpaceSig(newProf->getColorSpace())); +#endif + + std::vector<colorspace::Component> things = + colorspace::getColorSpaceInfo(asICColorSpaceSig(newProf->getColorSpace())); + + for (guint i = 0; i < count; i++) { + gdouble val = + (((gdouble)post[i]) / 65535.0) * (gdouble)((i < things.size()) ? things[i].scale : 1); +#ifdef DEBUG_LCMS + g_message(" scaled %d by %d to be %f", i, ((i < things.size()) ? things[i].scale : 1), val); +#endif // DEBUG_LCMS + tmp.icc->colors.push_back(val); + } + cmsHTRANSFORM retrans = newProf->getTransfToSRGB8(); + if (retrans) { + cmsDoTransform(retrans, post, pre, 1); +#ifdef DEBUG_LCMS + g_message(" back out [%02x] [%02x] [%02x]", pre[0], pre[1], pre[2]); +#endif // DEBUG_LCMS + tmp.set(SP_RGBA32_U_COMPOSE(pre[0], pre[1], pre[2], 0xff)); + } + + dirty = true; + } + } + } + } + else { +#ifdef DEBUG_LCMS + g_message("NUKE THE ICC"); +#endif // DEBUG_LCMS + if (tmp.icc) { + delete tmp.icc; + tmp.icc = nullptr; + dirty = true; + _fixupHit(nullptr, this); + } + else { +#ifdef DEBUG_LCMS + g_message("No icc to nuke"); +#endif // DEBUG_LCMS + } + } + + if (dirty) { +#ifdef DEBUG_LCMS + g_message("+----------------"); + g_message("+ new color is [%s]", tmp.toString().c_str()); +#endif // DEBUG_LCMS + _setProfile(tmp.icc); + //_adjustmentChanged( _compUI[0]._adj, SP_COLOR_ICC_SELECTOR(_csel) ); + _color.setColor(tmp); +#ifdef DEBUG_LCMS + g_message("+_________________"); +#endif // DEBUG_LCMS + } +} +#endif // defined(HAVE_LIBLCMS2) + +#if defined(HAVE_LIBLCMS2) +struct _cmp { + bool operator()(const SPObject * const & a, const SPObject * const & b) + { + const Inkscape::ColorProfile &a_prof = reinterpret_cast<const Inkscape::ColorProfile &>(*a); + const Inkscape::ColorProfile &b_prof = reinterpret_cast<const Inkscape::ColorProfile &>(*b); + gchar *a_name_casefold = g_utf8_casefold(a_prof.name, -1 ); + gchar *b_name_casefold = g_utf8_casefold(b_prof.name, -1 ); + int result = g_strcmp0(a_name_casefold, b_name_casefold); + g_free(a_name_casefold); + g_free(b_name_casefold); + return result < 0; + } +}; + +template <typename From, typename To> +struct static_caster { To * operator () (From * value) const { return static_cast<To *>(value); } }; + +void ColorICCSelectorImpl::_profilesChanged(std::string const &name) +{ + GtkComboBox *combo = GTK_COMBO_BOX(_profileSel); + + g_signal_handler_block(G_OBJECT(_profileSel), _profChangedID); + + GtkListStore *store = GTK_LIST_STORE(gtk_combo_box_get_model(combo)); + gtk_list_store_clear(store); + + GtkTreeIter iter; + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, _("<none>"), 1, _("<none>"), -1); + + gtk_combo_box_set_active(combo, 0); + + int index = 1; + std::vector<SPObject *> current = SP_ACTIVE_DOCUMENT->getResourceList("iccprofile"); + + std::set<Inkscape::ColorProfile *, Inkscape::ColorProfile::pointerComparator> _current; + std::transform(current.begin(), + current.end(), + std::inserter(_current, _current.begin()), + static_caster<SPObject, Inkscape::ColorProfile>()); + + for (auto &it: _current) { + Inkscape::ColorProfile *prof = it; + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, ink_ellipsize_text(prof->name, 25).c_str(), 1, prof->name, -1); + + if (name == prof->name) { + gtk_combo_box_set_active(combo, index); + gtk_widget_set_tooltip_text(_profileSel, prof->name); + } + + index++; + } + + g_signal_handler_unblock(G_OBJECT(_profileSel), _profChangedID); +} +#else +void ColorICCSelectorImpl::_profilesChanged(std::string const & /*name*/) {} +#endif // defined(HAVE_LIBLCMS2) + +void ColorICCSelector::on_show() +{ + Gtk::Grid::on_show(); + _colorChanged(); +} + +// Helpers for setting color value + +void ColorICCSelector::_colorChanged() +{ + _impl->_updating = TRUE; +// sp_color_icc_set_color( SP_COLOR_ICC( _icc ), &color ); + +#ifdef DEBUG_LCMS + g_message("/^^^^^^^^^ %p::_colorChanged(%08x:%s)", this, _impl->_color.color().toRGBA32(_impl->_color.alpha()), + ((_impl->_color.color().icc) ? _impl->_color.color().icc->colorProfile.c_str() : "<null>")); +#endif // DEBUG_LCMS + +#ifdef DEBUG_LCMS + g_message("FLIPPIES!!!! %p '%s'", _impl->_color.color().icc, + (_impl->_color.color().icc ? _impl->_color.color().icc->colorProfile.c_str() : "<null>")); +#endif // DEBUG_LCMS + + _impl->_profilesChanged((_impl->_color.color().icc) ? _impl->_color.color().icc->colorProfile : std::string("")); + ColorScales::setScaled(_impl->_adj, _impl->_color.alpha()); + +#if defined(HAVE_LIBLCMS2) + _impl->_setProfile(_impl->_color.color().icc); + _impl->_fixupNeeded = 0; + gtk_widget_set_sensitive(_impl->_fixupBtn, FALSE); + + if (_impl->_prof) { + if (_impl->_prof->getTransfToSRGB8()) { + cmsUInt16Number tmp[4]; + for (guint i = 0; i < _impl->_profChannelCount; i++) { + gdouble val = 0.0; + if (_impl->_color.color().icc->colors.size() > i) { + if (_impl->_compUI[i]._component.scale == 256) { + val = (_impl->_color.color().icc->colors[i] + 128.0) / + static_cast<gdouble>(_impl->_compUI[i]._component.scale); + } + else { + val = _impl->_color.color().icc->colors[i] / + static_cast<gdouble>(_impl->_compUI[i]._component.scale); + } + } + tmp[i] = val * 0x0ffff; + } + guchar post[4] = { 0, 0, 0, 0 }; + cmsHTRANSFORM trans = _impl->_prof->getTransfToSRGB8(); + if (trans) { + cmsDoTransform(trans, tmp, post, 1); + guint32 other = SP_RGBA32_U_COMPOSE(post[0], post[1], post[2], 255); + if (other != _impl->_color.color().toRGBA32(255)) { + _impl->_fixupNeeded = other; + gtk_widget_set_sensitive(_impl->_fixupBtn, TRUE); +#ifdef DEBUG_LCMS + g_message("Color needs to change 0x%06x to 0x%06x", _color.toRGBA32(255) >> 8, other >> 8); +#endif // DEBUG_LCMS + } + } + } + } +#else +//(void)color; +#endif // defined(HAVE_LIBLCMS2) + _impl->_updateSliders(-1); + + + _impl->_updating = FALSE; +#ifdef DEBUG_LCMS + g_message("\\_________ %p::_colorChanged()", this); +#endif // DEBUG_LCMS +} + +#if defined(HAVE_LIBLCMS2) +void ColorICCSelectorImpl::_setProfile(SVGICCColor *profile) +{ +#ifdef DEBUG_LCMS + g_message("/^^^^^^^^^ %p::_setProfile(%s)", this, ((profile) ? profile->colorProfile.c_str() : "<null>")); +#endif // DEBUG_LCMS + bool profChanged = false; + if (_prof && (!profile || (_profileName != profile->colorProfile))) { + // Need to clear out the prior one + profChanged = true; + _profileName.clear(); + _prof = nullptr; + _profChannelCount = 0; + } + else if (profile && !_prof) { + profChanged = true; + } + + for (auto & i : _compUI) { + gtk_widget_hide(i._label); + i._slider->hide(); + gtk_widget_hide(i._btn); + } + + if (profile) { + _prof = SP_ACTIVE_DOCUMENT->getProfileManager()->find(profile->colorProfile.c_str()); + if (_prof && (asICColorProfileClassSig(_prof->getProfileClass()) != cmsSigNamedColorClass)) { +#if HAVE_LIBLCMS2 + _profChannelCount = cmsChannelsOf(asICColorSpaceSig(_prof->getColorSpace())); +#endif + + if (profChanged) { + std::vector<colorspace::Component> things = + colorspace::getColorSpaceInfo(asICColorSpaceSig(_prof->getColorSpace())); + for (size_t i = 0; (i < things.size()) && (i < _profChannelCount); ++i) { + _compUI[i]._component = things[i]; + } + + for (guint i = 0; i < _profChannelCount; i++) { + gtk_label_set_text_with_mnemonic(GTK_LABEL(_compUI[i]._label), + (i < things.size()) ? things[i].name.c_str() : ""); + + _compUI[i]._slider->set_tooltip_text((i < things.size()) ? things[i].tip.c_str() : ""); + gtk_widget_set_tooltip_text(_compUI[i]._btn, (i < things.size()) ? things[i].tip.c_str() : ""); + + _compUI[i]._slider->setColors(SPColor(0.0, 0.0, 0.0).toRGBA32(0xff), + SPColor(0.5, 0.5, 0.5).toRGBA32(0xff), + SPColor(1.0, 1.0, 1.0).toRGBA32(0xff)); + /* + _compUI[i]._adj = GTK_ADJUSTMENT( gtk_adjustment_new( val, 0.0, _fooScales[i], + step, page, page ) ); + g_signal_connect( G_OBJECT( _compUI[i]._adj ), "value_changed", G_CALLBACK( + _adjustmentChanged ), _csel ); + + sp_color_slider_set_adjustment( SP_COLOR_SLIDER(_compUI[i]._slider), + _compUI[i]._adj ); + gtk_spin_button_set_adjustment( GTK_SPIN_BUTTON(_compUI[i]._btn), + _compUI[i]._adj ); + gtk_spin_button_set_digits( GTK_SPIN_BUTTON(_compUI[i]._btn), digits ); + */ + gtk_widget_show(_compUI[i]._label); + _compUI[i]._slider->show(); + gtk_widget_show(_compUI[i]._btn); + // gtk_adjustment_set_value( _compUI[i]._adj, 0.0 ); + // gtk_adjustment_set_value( _compUI[i]._adj, val ); + } + for (size_t i = _profChannelCount; i < _compUI.size(); i++) { + gtk_widget_hide(_compUI[i]._label); + _compUI[i]._slider->hide(); + gtk_widget_hide(_compUI[i]._btn); + } + } + } + else { + // Give up for now on named colors + _prof = nullptr; + } + } + +#ifdef DEBUG_LCMS + g_message("\\_________ %p::_setProfile()", this); +#endif // DEBUG_LCMS +} +#endif // defined(HAVE_LIBLCMS2) + +void ColorICCSelectorImpl::_updateSliders(gint ignore) +{ +#if defined(HAVE_LIBLCMS2) + if (_color.color().icc) { + for (guint i = 0; i < _profChannelCount; i++) { + gdouble val = 0.0; + if (_color.color().icc->colors.size() > i) { + if (_compUI[i]._component.scale == 256) { + val = (_color.color().icc->colors[i] + 128.0) / static_cast<gdouble>(_compUI[i]._component.scale); + } + else { + val = _color.color().icc->colors[i] / static_cast<gdouble>(_compUI[i]._component.scale); + } + } + gtk_adjustment_set_value(_compUI[i]._adj, val); + } + + if (_prof) { + if (_prof->getTransfToSRGB8()) { + for (guint i = 0; i < _profChannelCount; i++) { + if (static_cast<gint>(i) != ignore) { + cmsUInt16Number *scratch = getScratch(); + cmsUInt16Number filler[4] = { 0, 0, 0, 0 }; + for (guint j = 0; j < _profChannelCount; j++) { + filler[j] = 0x0ffff * ColorScales::getScaled(_compUI[j]._adj); + } + + cmsUInt16Number *p = scratch; + for (guint x = 0; x < 1024; x++) { + for (guint j = 0; j < _profChannelCount; j++) { + if (j == i) { + *p++ = x * 0x0ffff / 1024; + } + else { + *p++ = filler[j]; + } + } + } + + cmsHTRANSFORM trans = _prof->getTransfToSRGB8(); + if (trans) { + cmsDoTransform(trans, scratch, _compUI[i]._map, 1024); + if (_compUI[i]._slider) + { + _compUI[i]._slider->setMap(_compUI[i]._map); + } + } + } + } + } + } + } +#else + (void)ignore; +#endif // defined(HAVE_LIBLCMS2) + + guint32 start = _color.color().toRGBA32(0x00); + guint32 mid = _color.color().toRGBA32(0x7f); + guint32 end = _color.color().toRGBA32(0xff); + + _slider->setColors(start, mid, end); +} + + +void ColorICCSelectorImpl::_adjustmentChanged(GtkAdjustment *adjustment, ColorICCSelectorImpl *cs) +{ +#ifdef DEBUG_LCMS + g_message("/^^^^^^^^^ %p::_adjustmentChanged()", cs); +#endif // DEBUG_LCMS + + ColorICCSelector *iccSelector = cs->_owner; + if (iccSelector->_impl->_updating) { + return; + } + + iccSelector->_impl->_updating = TRUE; + + gint match = -1; + + SPColor newColor(iccSelector->_impl->_color.color()); + gfloat scaled = ColorScales::getScaled(iccSelector->_impl->_adj); + if (iccSelector->_impl->_adj == adjustment) { +#ifdef DEBUG_LCMS + g_message("ALPHA"); +#endif // DEBUG_LCMS + } + else { +#if defined(HAVE_LIBLCMS2) + for (size_t i = 0; i < iccSelector->_impl->_compUI.size(); i++) { + if (iccSelector->_impl->_compUI[i]._adj == adjustment) { + match = i; + break; + } + } + if (match >= 0) { +#ifdef DEBUG_LCMS + g_message(" channel %d", match); +#endif // DEBUG_LCMS + } + + + cmsUInt16Number tmp[4]; + for (guint i = 0; i < 4; i++) { + tmp[i] = ColorScales::getScaled(iccSelector->_impl->_compUI[i]._adj) * 0x0ffff; + } + guchar post[4] = { 0, 0, 0, 0 }; + + cmsHTRANSFORM trans = iccSelector->_impl->_prof->getTransfToSRGB8(); + if (trans) { + cmsDoTransform(trans, tmp, post, 1); + } + + SPColor other(SP_RGBA32_U_COMPOSE(post[0], post[1], post[2], 255)); + other.icc = new SVGICCColor(); + if (iccSelector->_impl->_color.color().icc) { + other.icc->colorProfile = iccSelector->_impl->_color.color().icc->colorProfile; + } + + guint32 prior = iccSelector->_impl->_color.color().toRGBA32(255); + guint32 newer = other.toRGBA32(255); + + if (prior != newer) { +#ifdef DEBUG_LCMS + g_message("Transformed color from 0x%08x to 0x%08x", prior, newer); + g_message(" ~~~~ FLIP"); +#endif // DEBUG_LCMS + newColor = other; + newColor.icc->colors.clear(); + for (guint i = 0; i < iccSelector->_impl->_profChannelCount; i++) { + gdouble val = ColorScales::getScaled(iccSelector->_impl->_compUI[i]._adj); + val *= iccSelector->_impl->_compUI[i]._component.scale; + if (iccSelector->_impl->_compUI[i]._component.scale == 256) { + val -= 128; + } + newColor.icc->colors.push_back(val); + } + } +#endif // defined(HAVE_LIBLCMS2) + } + iccSelector->_impl->_color.setColorAlpha(newColor, scaled); + // iccSelector->_updateInternals( newColor, scaled, iccSelector->_impl->_dragging ); + iccSelector->_impl->_updateSliders(match); + + iccSelector->_impl->_updating = FALSE; +#ifdef DEBUG_LCMS + g_message("\\_________ %p::_adjustmentChanged()", cs); +#endif // DEBUG_LCMS +} + +void ColorICCSelectorImpl::_sliderGrabbed() +{ + // ColorICCSelector* iccSelector = dynamic_cast<ColorICCSelector*>(SP_COLOR_SELECTOR(cs)->base); + // if (!iccSelector->_dragging) { + // iccSelector->_dragging = TRUE; + // iccSelector->_grabbed(); + // iccSelector->_updateInternals( iccSelector->_color, ColorScales::getScaled( iccSelector->_impl->_adj ), + // iccSelector->_dragging ); + // } +} + +void ColorICCSelectorImpl::_sliderReleased() +{ + // ColorICCSelector* iccSelector = dynamic_cast<ColorICCSelector*>(SP_COLOR_SELECTOR(cs)->base); + // if (iccSelector->_dragging) { + // iccSelector->_dragging = FALSE; + // iccSelector->_released(); + // iccSelector->_updateInternals( iccSelector->_color, ColorScales::getScaled( iccSelector->_adj ), + // iccSelector->_dragging ); + // } +} + +#ifdef DEBUG_LCMS +void ColorICCSelectorImpl::_sliderChanged(SPColorSlider *slider, SPColorICCSelector *cs) +#else +void ColorICCSelectorImpl::_sliderChanged() +#endif // DEBUG_LCMS +{ +#ifdef DEBUG_LCMS + g_message("Changed %p and %p", slider, cs); +#endif // DEBUG_LCMS + // ColorICCSelector* iccSelector = dynamic_cast<ColorICCSelector*>(SP_COLOR_SELECTOR(cs)->base); + + // iccSelector->_updateInternals( iccSelector->_color, ColorScales::getScaled( iccSelector->_adj ), + // iccSelector->_dragging ); +} + +Gtk::Widget *ColorICCSelectorFactory::createWidget(Inkscape::UI::SelectedColor &color) const +{ + Gtk::Widget *w = Gtk::manage(new ColorICCSelector(color)); + return w; +} + +Glib::ustring ColorICCSelectorFactory::modeName() const { return gettext(ColorICCSelector::MODE_NAME); } +} +} +} +/* + 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 : diff --git a/src/ui/widget/color-icc-selector.h b/src/ui/widget/color-icc-selector.h new file mode 100644 index 0000000..2c5ec41 --- /dev/null +++ b/src/ui/widget/color-icc-selector.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_SP_COLOR_ICC_SELECTOR_H +#define SEEN_SP_COLOR_ICC_SELECTOR_H + +#include <gtkmm/widget.h> +#include <gtkmm/grid.h> + +#include "ui/selected-color.h" + +namespace Inkscape { + +class ColorProfile; + +namespace UI { +namespace Widget { + +class ColorICCSelectorImpl; + +class ColorICCSelector + : public Gtk::Grid + { + public: + static const gchar *MODE_NAME; + + ColorICCSelector(SelectedColor &color); + ~ColorICCSelector() override; + + virtual void init(); + + protected: + void on_show() override; + + virtual void _colorChanged(); + + void _recalcColor(gboolean changing); + + private: + friend class ColorICCSelectorImpl; + + // By default, disallow copy constructor and assignment operator + ColorICCSelector(const ColorICCSelector &obj); + ColorICCSelector &operator=(const ColorICCSelector &obj); + + ColorICCSelectorImpl *_impl; +}; + + +class ColorICCSelectorFactory : public ColorSelectorFactory { + public: + Gtk::Widget *createWidget(SelectedColor &color) const override; + Glib::ustring modeName() const override; +}; +} +} +} +#endif // SEEN_SP_COLOR_ICC_SELECTOR_H + +/* + 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 : diff --git a/src/ui/widget/color-notebook.cpp b/src/ui/widget/color-notebook.cpp new file mode 100644 index 0000000..474b4d2 --- /dev/null +++ b/src/ui/widget/color-notebook.cpp @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * A notebook with RGB, CMYK, CMS, HSL, and Wheel pages + *//* + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Tomasz Boczkowski <penginsbacon@gmail.com> (c++-sification) + * + * Copyright (C) 2001-2014 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#undef SPCS_PREVIEW +#define noDUMP_CHANGE_INFO + +#include <glibmm/i18n.h> +#include <gtkmm/label.h> +#include <gtkmm/notebook.h> +#include <gtkmm/radiobutton.h> + +#include "cms-system.h" +#include "document.h" +#include "inkscape.h" +#include "preferences.h" +#include "profile-manager.h" + +#include "object/color-profile.h" +#include "ui/icon-loader.h" + +#include "svg/svg-icc-color.h" + +#include "ui/dialog-events.h" +#include "ui/tools-switch.h" +#include "ui/tools/tool-base.h" +#include "ui/widget/color-entry.h" +#include "ui/widget/color-icc-selector.h" +#include "ui/widget/color-notebook.h" +#include "ui/widget/color-scales.h" +#include "ui/widget/color-wheel-selector.h" + +#include "widgets/spw-utilities.h" + +using Inkscape::CMSSystem; + +#define XPAD 4 +#define YPAD 1 + +namespace Inkscape { +namespace UI { +namespace Widget { + + +ColorNotebook::ColorNotebook(SelectedColor &color) + : Gtk::Grid() + , _selected_color(color) +{ + set_name("ColorNotebook"); + + Page *page; + + page = new Page(new ColorScalesFactory(SP_COLOR_SCALES_MODE_RGB), true); + _available_pages.push_back(page); + page = new Page(new ColorScalesFactory(SP_COLOR_SCALES_MODE_HSL), true); + _available_pages.push_back(page); + page = new Page(new ColorScalesFactory(SP_COLOR_SCALES_MODE_HSV), true); + _available_pages.push_back(page); + page = new Page(new ColorScalesFactory(SP_COLOR_SCALES_MODE_CMYK), true); + _available_pages.push_back(page); + page = new Page(new ColorWheelSelectorFactory, true); + _available_pages.push_back(page); +#if defined(HAVE_LIBLCMS2) + page = new Page(new ColorICCSelectorFactory, true); + _available_pages.push_back(page); +#endif + + _initUI(); + + _selected_color.signal_changed.connect(sigc::mem_fun(this, &ColorNotebook::_onSelectedColorChanged)); + _selected_color.signal_dragged.connect(sigc::mem_fun(this, &ColorNotebook::_onSelectedColorChanged)); +} + +ColorNotebook::~ColorNotebook() +{ + if (_buttons) { + delete[] _buttons; + _buttons = nullptr; + } +} + +ColorNotebook::Page::Page(Inkscape::UI::ColorSelectorFactory *selector_factory, bool enabled_full) + : selector_factory(selector_factory) + , enabled_full(enabled_full) +{ +} + + +void ColorNotebook::_initUI() +{ + guint row = 0; + + Gtk::Notebook *notebook = Gtk::manage(new Gtk::Notebook); + notebook->show(); + notebook->set_show_border(false); + notebook->set_show_tabs(false); + _book = GTK_WIDGET(notebook->gobj()); + + _buttonbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2); + gtk_box_set_homogeneous(GTK_BOX(_buttonbox), TRUE); + + gtk_widget_show(_buttonbox); + _buttons = new GtkWidget *[_available_pages.size()]; + + for (int i = 0; static_cast<size_t>(i) < _available_pages.size(); i++) { + _addPage(_available_pages[i]); + } + + gtk_widget_set_margin_start(_buttonbox, XPAD); + gtk_widget_set_margin_end(_buttonbox, XPAD); + gtk_widget_set_margin_top(_buttonbox, YPAD); + gtk_widget_set_margin_bottom(_buttonbox, YPAD); + gtk_widget_set_hexpand(_buttonbox, TRUE); + gtk_widget_set_valign(_buttonbox, GTK_ALIGN_CENTER); + attach(*Glib::wrap(_buttonbox), 0, row, 2, 1); + + row++; + + gtk_widget_set_margin_start(_book, XPAD * 2); + gtk_widget_set_margin_end(_book, XPAD * 2); + gtk_widget_set_margin_top(_book, YPAD); + gtk_widget_set_margin_bottom(_book, YPAD); + gtk_widget_set_hexpand(_book, TRUE); + gtk_widget_set_vexpand(_book, TRUE); + attach(*notebook, 0, row, 2, 1); + + // restore the last active page + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _setCurrentPage(prefs->getInt("/colorselector/page", 0)); + row++; + + GtkWidget *rgbabox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + +#if defined(HAVE_LIBLCMS2) + /* Create color management icons */ + _box_colormanaged = gtk_event_box_new(); + GtkWidget *colormanaged = sp_get_icon_image("color-management", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_container_add(GTK_CONTAINER(_box_colormanaged), colormanaged); + gtk_widget_set_tooltip_text(_box_colormanaged, _("Color Managed")); + gtk_widget_set_sensitive(_box_colormanaged, false); + gtk_box_pack_start(GTK_BOX(rgbabox), _box_colormanaged, FALSE, FALSE, 2); + + _box_outofgamut = gtk_event_box_new(); + GtkWidget *outofgamut = sp_get_icon_image("out-of-gamut-icon", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_container_add(GTK_CONTAINER(_box_outofgamut), outofgamut); + gtk_widget_set_tooltip_text(_box_outofgamut, _("Out of gamut!")); + gtk_widget_set_sensitive(_box_outofgamut, false); + gtk_box_pack_start(GTK_BOX(rgbabox), _box_outofgamut, FALSE, FALSE, 2); + + _box_toomuchink = gtk_event_box_new(); + GtkWidget *toomuchink = sp_get_icon_image("too-much-ink-icon", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_container_add(GTK_CONTAINER(_box_toomuchink), toomuchink); + gtk_widget_set_tooltip_text(_box_toomuchink, _("Too much ink!")); + gtk_widget_set_sensitive(_box_toomuchink, false); + gtk_box_pack_start(GTK_BOX(rgbabox), _box_toomuchink, FALSE, FALSE, 2); +#endif // defined(HAVE_LIBLCMS2) + + + /* Color picker */ + GtkWidget *picker = sp_get_icon_image("color-picker", GTK_ICON_SIZE_SMALL_TOOLBAR); + _btn_picker = gtk_button_new(); + gtk_button_set_relief(GTK_BUTTON(_btn_picker), GTK_RELIEF_NONE); + gtk_container_add(GTK_CONTAINER(_btn_picker), picker); + gtk_widget_set_tooltip_text(_btn_picker, _("Pick colors from image")); + gtk_box_pack_start(GTK_BOX(rgbabox), _btn_picker, FALSE, FALSE, 2); + g_signal_connect(G_OBJECT(_btn_picker), "clicked", G_CALLBACK(ColorNotebook::_onPickerClicked), this); + + /* Create RGBA entry and color preview */ + _rgbal = gtk_label_new_with_mnemonic(_("RGBA_:")); + gtk_widget_set_halign(_rgbal, GTK_ALIGN_END); + gtk_box_pack_start(GTK_BOX(rgbabox), _rgbal, TRUE, TRUE, 2); + + ColorEntry *rgba_entry = Gtk::manage(new ColorEntry(_selected_color)); + sp_dialog_defocus_on_enter(GTK_WIDGET(rgba_entry->gobj())); + gtk_box_pack_start(GTK_BOX(rgbabox), GTK_WIDGET(rgba_entry->gobj()), FALSE, FALSE, 0); + gtk_label_set_mnemonic_widget(GTK_LABEL(_rgbal), GTK_WIDGET(rgba_entry->gobj())); + + gtk_widget_show_all(rgbabox); + +#if defined(HAVE_LIBLCMS2) + // the "too much ink" icon is initially hidden + gtk_widget_hide(GTK_WIDGET(_box_toomuchink)); +#endif // defined(HAVE_LIBLCMS2) + + gtk_widget_set_margin_start(rgbabox, XPAD); + gtk_widget_set_margin_end(rgbabox, XPAD); + gtk_widget_set_margin_top(rgbabox, YPAD); + gtk_widget_set_margin_bottom(rgbabox, YPAD); + attach(*Glib::wrap(rgbabox), 0, row, 2, 1); + +#ifdef SPCS_PREVIEW + _p = sp_color_preview_new(0xffffffff); + gtk_widget_show(_p); + attach(*Glib::wrap(_p), 2, 3, row, row + 1, Gtk::FILL, Gtk::FILL, XPAD, YPAD); +#endif + + g_signal_connect(G_OBJECT(_book), "switch-page", G_CALLBACK(ColorNotebook::_onPageSwitched), this); +} + +void ColorNotebook::_onPickerClicked(GtkWidget * /*widget*/, ColorNotebook * /*colorbook*/) +{ + // Set the dropper into a "one click" mode, so it reverts to the previous tool after a click + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/tools/dropper/onetimepick", true); + Inkscape::UI::Tools::sp_toggle_dropper(SP_ACTIVE_DESKTOP); +} + +void ColorNotebook::_onButtonClicked(GtkWidget *widget, ColorNotebook *nb) +{ + if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { + return; + } + + for (gint i = 0; i < gtk_notebook_get_n_pages(GTK_NOTEBOOK(nb->_book)); i++) { + if (nb->_buttons[i] == widget) { + gtk_notebook_set_current_page(GTK_NOTEBOOK(nb->_book), i); + } + } +} + +void ColorNotebook::_onSelectedColorChanged() { _updateICCButtons(); } + +void ColorNotebook::_onPageSwitched(GtkNotebook *notebook, GtkWidget *page, guint page_num, ColorNotebook *colorbook) +{ + if (colorbook->get_visible()) { + // remember the page we switched to + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setInt("/colorselector/page", page_num); + } +} + + +// TODO pass in param so as to avoid the need for SP_ACTIVE_DOCUMENT +void ColorNotebook::_updateICCButtons() +{ + SPColor color = _selected_color.color(); + gfloat alpha = _selected_color.alpha(); + + g_return_if_fail((0.0 <= alpha) && (alpha <= 1.0)); + +#if defined(HAVE_LIBLCMS2) + /* update color management icon*/ + gtk_widget_set_sensitive(_box_colormanaged, color.icc != nullptr); + + /* update out-of-gamut icon */ + gtk_widget_set_sensitive(_box_outofgamut, false); + if (color.icc) { + Inkscape::ColorProfile *target_profile = + SP_ACTIVE_DOCUMENT->getProfileManager()->find(color.icc->colorProfile.c_str()); + if (target_profile) + gtk_widget_set_sensitive(_box_outofgamut, target_profile->GamutCheck(color)); + } + + /* update too-much-ink icon */ + gtk_widget_set_sensitive(_box_toomuchink, false); + if (color.icc) { + Inkscape::ColorProfile *prof = SP_ACTIVE_DOCUMENT->getProfileManager()->find(color.icc->colorProfile.c_str()); + if (prof && CMSSystem::isPrintColorSpace(prof)) { + gtk_widget_show(GTK_WIDGET(_box_toomuchink)); + double ink_sum = 0; + for (double i : color.icc->colors) { + ink_sum += i; + } + + /* Some literature states that when the sum of paint values exceed 320%, it is considered to be a satured + color, + which means the paper can get too wet due to an excessive amount of ink. This may lead to several + issues + such as misalignment and poor quality of printing in general.*/ + if (ink_sum > 3.2) + gtk_widget_set_sensitive(_box_toomuchink, true); + } + else { + gtk_widget_hide(GTK_WIDGET(_box_toomuchink)); + } + } +#endif // defined(HAVE_LIBLCMS2) +} + +void ColorNotebook::_setCurrentPage(int i) +{ + gtk_notebook_set_current_page(GTK_NOTEBOOK(_book), i); + + if (_buttons && (static_cast<size_t>(i) < _available_pages.size())) { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(_buttons[i]), TRUE); + } +} + +void ColorNotebook::_addPage(Page &page) +{ + Gtk::Widget *selector_widget; + + selector_widget = page.selector_factory->createWidget(_selected_color); + if (selector_widget) { + selector_widget->show(); + + Glib::ustring mode_name = page.selector_factory->modeName(); + Gtk::Widget *tab_label = Gtk::manage(new Gtk::Label(mode_name)); + tab_label->set_name("ColorModeLabel"); + gint page_num = gtk_notebook_append_page(GTK_NOTEBOOK(_book), selector_widget->gobj(), tab_label->gobj()); + + _buttons[page_num] = gtk_radio_button_new_with_label(nullptr, mode_name.c_str()); + gtk_widget_set_name(_buttons[page_num], "ColorModeButton"); + gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(_buttons[page_num]), FALSE); + if (page_num > 0) { + auto g = Glib::wrap(GTK_RADIO_BUTTON(_buttons[0]))->get_group(); + Glib::wrap(GTK_RADIO_BUTTON(_buttons[page_num]))->set_group(g); + } + gtk_widget_show(_buttons[page_num]); + gtk_box_pack_start(GTK_BOX(_buttonbox), _buttons[page_num], TRUE, TRUE, 0); + + g_signal_connect(G_OBJECT(_buttons[page_num]), "clicked", G_CALLBACK(_onButtonClicked), this); + } +} +} +} +} + +/* + 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 : diff --git a/src/ui/widget/color-notebook.h b/src/ui/widget/color-notebook.h new file mode 100644 index 0000000..c7bc7b5 --- /dev/null +++ b/src/ui/widget/color-notebook.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * A notebook with RGB, CMYK, CMS, HSL, and Wheel pages + *//* + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Tomasz Boczkowski <penginsbacon@gmail.com> (c++-sification) + * + * Copyright (C) 2001-2014 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_SP_COLOR_NOTEBOOK_H +#define SEEN_SP_COLOR_NOTEBOOK_H + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#include <boost/ptr_container/ptr_vector.hpp> +#include <gtkmm/grid.h> +#include <glib.h> + +#include "color.h" +#include "ui/selected-color.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +class ColorNotebook + : public Gtk::Grid +{ +public: + ColorNotebook(SelectedColor &color); + ~ColorNotebook() override; + +protected: + struct Page { + Page(Inkscape::UI::ColorSelectorFactory *selector_factory, bool enabled_full); + + Inkscape::UI::ColorSelectorFactory *selector_factory; + bool enabled_full; + }; + + virtual void _initUI(); + void _addPage(Page &page); + + static void _onButtonClicked(GtkWidget *widget, ColorNotebook *colorbook); + static void _onPickerClicked(GtkWidget *widget, ColorNotebook *colorbook); + static void _onPageSwitched(GtkNotebook *notebook, GtkWidget *page, guint page_num, ColorNotebook *colorbook); + virtual void _onSelectedColorChanged(); + + void _updateICCButtons(); + void _setCurrentPage(int i); + + Inkscape::UI::SelectedColor &_selected_color; + gulong _entryId; + GtkWidget *_book; + GtkWidget *_buttonbox; + GtkWidget **_buttons; + GtkWidget *_rgbal; /* RGBA entry */ +#if defined(HAVE_LIBLCMS2) + GtkWidget *_box_outofgamut, *_box_colormanaged, *_box_toomuchink; +#endif // defined(HAVE_LIBLCMS2) + GtkWidget *_btn_picker; + GtkWidget *_p; /* Color preview */ + boost::ptr_vector<Page> _available_pages; + +private: + // By default, disallow copy constructor and assignment operator + ColorNotebook(const ColorNotebook &obj) = delete; + ColorNotebook &operator=(const ColorNotebook &obj) = delete; +}; +} +} +} +#endif // SEEN_SP_COLOR_NOTEBOOK_H +/* + 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 : + diff --git a/src/ui/widget/color-picker.cpp b/src/ui/widget/color-picker.cpp new file mode 100644 index 0000000..5beb090 --- /dev/null +++ b/src/ui/widget/color-picker.cpp @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Ralf Stephan <ralf@ark.in-berlin.de> + * Abhishek Sharma + * + * Copyright (C) Authors 2000-2005 + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "color-picker.h" +#include "inkscape.h" +#include "desktop.h" +#include "document.h" +#include "document-undo.h" +#include "ui/dialog-events.h" + +#include "ui/widget/color-notebook.h" +#include "verbs.h" + + +static bool _in_use = false; + +namespace Inkscape { +namespace UI { +namespace Widget { + +ColorPicker::ColorPicker (const Glib::ustring& title, const Glib::ustring& tip, + guint32 rgba, bool undo) + : _preview(rgba), _title(title), _rgba(rgba), _undo(undo), + _colorSelectorDialog("dialogs.colorpickerwindow") +{ + setupDialog(title); + _preview.show(); + add (_preview); + set_tooltip_text (tip); + _selected_color.signal_changed.connect(sigc::mem_fun(this, &ColorPicker::_onSelectedColorChanged)); + _selected_color.signal_dragged.connect(sigc::mem_fun(this, &ColorPicker::_onSelectedColorChanged)); + _selected_color.signal_released.connect(sigc::mem_fun(this, &ColorPicker::_onSelectedColorChanged)); +} + +ColorPicker::~ColorPicker() +{ + closeWindow(); +} + +void ColorPicker::setupDialog(const Glib::ustring &title) +{ + GtkWidget *dlg = GTK_WIDGET(_colorSelectorDialog.gobj()); + sp_transientize(dlg); + + _colorSelectorDialog.hide(); + _colorSelectorDialog.set_title (title); + _colorSelectorDialog.set_border_width (4); + + _color_selector = Gtk::manage(new ColorNotebook(_selected_color)); + _colorSelectorDialog.get_content_area()->pack_start ( + *_color_selector, true, true, 0); + _color_selector->show(); +} + +void ColorPicker::setSensitive(bool sensitive) { set_sensitive(sensitive); } + +void ColorPicker::setRgba32 (guint32 rgba) +{ + if (_in_use) return; + + _preview.setRgba32 (rgba); + _rgba = rgba; + if (_color_selector) + { + _updating = true; + _selected_color.setValue(rgba); + _updating = false; + } +} + +void ColorPicker::closeWindow() +{ + _colorSelectorDialog.hide(); +} + +void ColorPicker::on_clicked() +{ + if (_color_selector) + { + _updating = true; + _selected_color.setValue(_rgba); + _updating = false; + } + _colorSelectorDialog.show(); + Glib::RefPtr<Gdk::Window> window = _colorSelectorDialog.get_parent_window(); + if (window) { + window->focus(1); + } +} + +void ColorPicker::on_changed (guint32) +{ +} + +void ColorPicker::_onSelectedColorChanged() { + if (_updating) { + return; + } + + if (_in_use) { + return; + } else { + _in_use = true; + } + + guint32 rgba = _selected_color.value(); + _preview.setRgba32(rgba); + + if (_undo && SP_ACTIVE_DESKTOP) { + DocumentUndo::done(SP_ACTIVE_DESKTOP->getDocument(), SP_VERB_NONE, + /* TODO: annotate */ "color-picker.cpp:130"); + } + + on_changed(rgba); + _in_use = false; + _changed_signal.emit(rgba); + _rgba = rgba; +} + +}//namespace Widget +}//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 : diff --git a/src/ui/widget/color-picker.h b/src/ui/widget/color-picker.h new file mode 100644 index 0000000..b98f832 --- /dev/null +++ b/src/ui/widget/color-picker.h @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Color picker button and window. + */ +/* Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Ralf Stephan <ralf@ark.in-berlin.de> + * + * Copyright (C) Authors 2000-2005 + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef __COLOR_PICKER_H__ +#define __COLOR_PICKER_H__ + +#include "labelled.h" + +#include <cstddef> + +#include "ui/selected-color.h" +#include "ui/widget/color-preview.h" +#include <gtkmm/button.h> +#include <gtkmm/dialog.h> +#include <gtkmm/window.h> +#include <sigc++/sigc++.h> + +struct SPColorSelector; + +namespace Inkscape +{ +namespace UI +{ +namespace Widget +{ + + +class ColorPicker : public Gtk::Button { +public: + + ColorPicker (const Glib::ustring& title, + const Glib::ustring& tip, + const guint32 rgba, + bool undo); + + ~ColorPicker() override; + + void setRgba32 (guint32 rgba); + void setSensitive(bool sensitive); + void closeWindow(); + sigc::connection connectChanged (const sigc::slot<void,guint>& slot) + { return _changed_signal.connect (slot); } + +protected: + + void _onSelectedColorChanged(); + void on_clicked() override; + virtual void on_changed (guint32); + + ColorPreview _preview; + + /*const*/ Glib::ustring _title; + sigc::signal<void,guint32> _changed_signal; + guint32 _rgba; + bool _undo; + bool _updating; + + //Dialog + void setupDialog(const Glib::ustring &title); + //Inkscape::UI::Dialog::Dialog _colorSelectorDialog; + Gtk::Dialog _colorSelectorDialog; + SelectedColor _selected_color; + Gtk::Widget *_color_selector; +}; + + +class LabelledColorPicker : public Labelled { +public: + + LabelledColorPicker (const Glib::ustring& label, + const Glib::ustring& title, + const Glib::ustring& tip, + const guint32 rgba, + bool undo) : Labelled(label, tip, new ColorPicker(title, tip, rgba, undo)) {} + + ~LabelledColorPicker() override + { static_cast<ColorPicker*>(_widget)->~ColorPicker(); } + + void setRgba32 (guint32 rgba) + { static_cast<ColorPicker*>(_widget)->setRgba32 (rgba); } + + void closeWindow() + { static_cast<ColorPicker*>(_widget)->closeWindow (); } + + sigc::connection connectChanged (const sigc::slot<void,guint>& slot) + { return static_cast<ColorPicker*>(_widget)->connectChanged(slot); } +}; + +}//namespace Widget +}//namespace UI +}//namespace Inkscape + +#endif /* !__COLOR_PICKER_H__ */ + +/* + 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 : diff --git a/src/ui/widget/color-preview.cpp b/src/ui/widget/color-preview.cpp new file mode 100644 index 0000000..ac8fc57 --- /dev/null +++ b/src/ui/widget/color-preview.cpp @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Lauris Kaplinski <lauris@kaplinski.com> + * Ralf Stephan <ralf@ark.in-berlin.de> + * + * Copyright (C) 2001-2005 Authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/widget/color-preview.h" +#include "display/cairo-utils.h" +#include <cairo.h> + +#define SPCP_DEFAULT_WIDTH 32 +#define SPCP_DEFAULT_HEIGHT 12 + +namespace Inkscape { + namespace UI { + namespace Widget { + +ColorPreview::ColorPreview (guint32 rgba) +{ + _rgba = rgba; + set_has_window(false); + set_name("ColorPreview"); +} + +void +ColorPreview::on_size_allocate (Gtk::Allocation &all) +{ + set_allocation (all); + if (get_is_drawable()) + queue_draw(); +} + +void +ColorPreview::get_preferred_height_vfunc(int& minimum_height, int& natural_height) const +{ + minimum_height = natural_height = SPCP_DEFAULT_HEIGHT; +} + +void +ColorPreview::get_preferred_height_for_width_vfunc(int /* width */, int& minimum_height, int& natural_height) const +{ + minimum_height = natural_height = SPCP_DEFAULT_HEIGHT; +} + +void +ColorPreview::get_preferred_width_vfunc(int& minimum_width, int& natural_width) const +{ + minimum_width = natural_width = SPCP_DEFAULT_WIDTH; +} + +void +ColorPreview::get_preferred_width_for_height_vfunc(int /* height */, int& minimum_width, int& natural_width) const +{ + minimum_width = natural_width = SPCP_DEFAULT_WIDTH; +} + +void +ColorPreview::setRgba32 (guint32 rgba) +{ + _rgba = rgba; + + if (get_is_drawable()) + queue_draw(); +} + +bool +ColorPreview::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) +{ + double x, y, width, height; + const Gtk::Allocation& allocation = get_allocation(); + x = 0; + y = 0; + width = allocation.get_width()/2.0; + height = allocation.get_height(); + + double radius = height / 7.5; + double degrees = M_PI / 180.0; + cairo_new_sub_path (cr->cobj()); + cairo_line_to(cr->cobj(), width, 0); + cairo_line_to(cr->cobj(), width, height); + cairo_arc (cr->cobj(), x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees); + cairo_arc (cr->cobj(), x + radius, y + radius, radius, 180 * degrees, 270 * degrees); + cairo_close_path (cr->cobj()); + + /* Transparent area */ + + cairo_pattern_t *checkers = ink_cairo_pattern_create_checkerboard(); + + cairo_set_source(cr->cobj(), checkers); + cr->fill_preserve(); + ink_cairo_set_source_rgba32(cr->cobj(), _rgba); + cr->fill(); + cairo_pattern_destroy(checkers); + + /* Solid area */ + + x = width; + + cairo_new_sub_path (cr->cobj()); + cairo_arc (cr->cobj(), x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees); + cairo_arc (cr->cobj(), x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees); + cairo_line_to(cr->cobj(), x, height); + cairo_line_to(cr->cobj(), x, y); + cairo_close_path (cr->cobj()); + ink_cairo_set_source_rgba32(cr->cobj(), _rgba | 0xff); + cr->fill(); + + return true; +} + +GdkPixbuf* +ColorPreview::toPixbuf (int width, int height) +{ + GdkRectangle carea; + gint w2; + w2 = width / 2; + + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + cairo_t *ct = cairo_create(s); + + /* Transparent area */ + carea.x = 0; + carea.y = 0; + carea.width = w2; + carea.height = height; + + cairo_pattern_t *checkers = ink_cairo_pattern_create_checkerboard(); + + cairo_rectangle(ct, carea.x, carea.y, carea.width, carea.height); + cairo_set_source(ct, checkers); + cairo_fill_preserve(ct); + ink_cairo_set_source_rgba32(ct, _rgba); + cairo_fill(ct); + + cairo_pattern_destroy(checkers); + + /* Solid area */ + carea.x = w2; + carea.y = 0; + carea.width = width - w2; + carea.height = height; + + cairo_rectangle(ct, carea.x, carea.y, carea.width, carea.height); + ink_cairo_set_source_rgba32(ct, _rgba | 0xff); + cairo_fill(ct); + + cairo_destroy(ct); + cairo_surface_flush(s); + + GdkPixbuf* pixbuf = ink_pixbuf_create_from_cairo_surface(s); + return pixbuf; +} + +}}} + +/* + 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 : diff --git a/src/ui/widget/color-preview.h b/src/ui/widget/color-preview.h new file mode 100644 index 0000000..b789579 --- /dev/null +++ b/src/ui/widget/color-preview.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_COLOR_PREVIEW_H +#define SEEN_COLOR_PREVIEW_H +/* + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Ralf Stephan <ralf@ark.in-berlin.de> + * + * Copyright (C) 2001-2005 Authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/widget.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A simple color preview widget, mainly used within a picker button. + */ +class ColorPreview : public Gtk::Widget { +public: + ColorPreview (guint32 rgba); + void setRgba32 (guint32 rgba); + GdkPixbuf* toPixbuf (int width, int height); + +protected: + void on_size_allocate (Gtk::Allocation &all) override; + + void get_preferred_height_vfunc(int& minimum_height, int& natural_height) const override; + void get_preferred_height_for_width_vfunc(int width, int& minimum_height, int& natural_height) const override; + void get_preferred_width_vfunc(int& minimum_width, int& natural_width) const override; + void get_preferred_width_for_height_vfunc(int height, int& minimum_width, int& natural_width) const override; + bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override; + + guint32 _rgba; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // SEEN_COLOR_PREVIEW_H + +/* + 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 : diff --git a/src/ui/widget/color-scales.cpp b/src/ui/widget/color-scales.cpp new file mode 100644 index 0000000..a93ab2a --- /dev/null +++ b/src/ui/widget/color-scales.cpp @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * bulia byak <buliabyak@users.sf.net> + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/adjustment.h> +#include <glibmm/i18n.h> + +#include "ui/dialog-events.h" +#include "ui/widget/color-scales.h" +#include "ui/widget/color-slider.h" + +#define CSC_CHANNEL_R (1 << 0) +#define CSC_CHANNEL_G (1 << 1) +#define CSC_CHANNEL_B (1 << 2) +#define CSC_CHANNEL_A (1 << 3) +#define CSC_CHANNEL_H (1 << 0) +#define CSC_CHANNEL_S (1 << 1) +#define CSC_CHANNEL_V (1 << 2) +#define CSC_CHANNEL_C (1 << 0) +#define CSC_CHANNEL_M (1 << 1) +#define CSC_CHANNEL_Y (1 << 2) +#define CSC_CHANNEL_K (1 << 3) +#define CSC_CHANNEL_CMYKA (1 << 4) + +#define CSC_CHANNELS_ALL 0 + +#define XPAD 4 +#define YPAD 1 + +#define noDUMP_CHANGE_INFO 1 + +namespace Inkscape { +namespace UI { +namespace Widget { + + +static const gchar *sp_color_scales_hue_map(); + +const gchar *ColorScales::SUBMODE_NAMES[] = { N_("None"), N_("RGB"), N_("HSL"), N_("CMYK"), N_("HSV") }; + +ColorScales::ColorScales(SelectedColor &color, SPColorScalesMode mode) + : Gtk::Grid() + , _color(color) + , _rangeLimit(255.0) + , _updating(FALSE) + , _dragging(FALSE) + , _mode(SP_COLOR_SCALES_MODE_NONE) +{ + for (gint i = 0; i < 5; i++) { + _l[i] = nullptr; + _a[i] = nullptr; + _s[i] = nullptr; + _b[i] = nullptr; + } + + _initUI(mode); + + _color.signal_changed.connect(sigc::mem_fun(this, &ColorScales::_onColorChanged)); + _color.signal_dragged.connect(sigc::mem_fun(this, &ColorScales::_onColorChanged)); +} + +ColorScales::~ColorScales() +{ + for (gint i = 0; i < 5; i++) { + _l[i] = nullptr; + _a[i] = nullptr; + _s[i] = nullptr; + _b[i] = nullptr; + } +} + +void ColorScales::_initUI(SPColorScalesMode mode) +{ + gint i; + + _updating = FALSE; + _dragging = FALSE; + + GtkWidget *t = GTK_WIDGET(gobj()); + + /* Create components */ + for (i = 0; i < static_cast<gint>(G_N_ELEMENTS(_a)); i++) { + /* Label */ + _l[i] = gtk_label_new(""); + + gtk_widget_set_halign(_l[i], GTK_ALIGN_START); + gtk_widget_show(_l[i]); + + gtk_widget_set_margin_start(_l[i], XPAD); + gtk_widget_set_margin_end(_l[i], XPAD); + gtk_widget_set_margin_top(_l[i], YPAD); + gtk_widget_set_margin_bottom(_l[i], YPAD); + gtk_grid_attach(GTK_GRID(t), _l[i], 0, i, 1, 1); + + /* Adjustment */ + _a[i] = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, _rangeLimit, 1.0, 10.0, 10.0)); + /* Slider */ + _s[i] = Gtk::manage(new Inkscape::UI::Widget::ColorSlider(Glib::wrap(_a[i], true))); + _s[i]->show(); + + _s[i]->set_margin_start(XPAD); + _s[i]->set_margin_end(XPAD); + _s[i]->set_margin_top(YPAD); + _s[i]->set_margin_bottom(YPAD); + _s[i]->set_hexpand(true); + gtk_grid_attach(GTK_GRID(t), _s[i]->gobj(), 1, i, 1, 1); + + /* Spinbutton */ + _b[i] = gtk_spin_button_new(GTK_ADJUSTMENT(_a[i]), 1.0, 0); + sp_dialog_defocus_on_enter(_b[i]); + gtk_label_set_mnemonic_widget(GTK_LABEL(_l[i]), _b[i]); + gtk_widget_show(_b[i]); + + gtk_widget_set_margin_start(_b[i], XPAD); + gtk_widget_set_margin_end(_b[i], XPAD); + gtk_widget_set_margin_top(_b[i], YPAD); + gtk_widget_set_margin_bottom(_b[i], YPAD); + gtk_widget_set_halign(_b[i], GTK_ALIGN_END); + gtk_widget_set_valign(_b[i], GTK_ALIGN_CENTER); + gtk_grid_attach(GTK_GRID(t), _b[i], 2, i, 1, 1); + + /* Attach channel value to adjustment */ + g_object_set_data(G_OBJECT(_a[i]), "channel", GINT_TO_POINTER(i)); + /* Signals */ + g_signal_connect(G_OBJECT(_a[i]), "value_changed", G_CALLBACK(_adjustmentAnyChanged), this); + _s[i]->signal_grabbed.connect(sigc::mem_fun(this, &ColorScales::_sliderAnyGrabbed)); + _s[i]->signal_released.connect(sigc::mem_fun(this, &ColorScales::_sliderAnyReleased)); + _s[i]->signal_value_changed.connect(sigc::mem_fun(this, &ColorScales::_sliderAnyChanged)); + } + + //Prevent 5th bar from being shown by PanelDialog::show_all_children + gtk_widget_set_no_show_all(_l[4], TRUE); + _s[4]->set_no_show_all(true); + gtk_widget_set_no_show_all(_b[4], TRUE); + + /* Initial mode is none, so it works */ + setMode(mode); +} + +void ColorScales::_recalcColor() +{ + SPColor color; + gfloat alpha = 1.0; + gfloat c[5]; + + switch (_mode) { + case SP_COLOR_SCALES_MODE_RGB: + case SP_COLOR_SCALES_MODE_HSL: + case SP_COLOR_SCALES_MODE_HSV: + _getRgbaFloatv(c); + color.set(c[0], c[1], c[2]); + alpha = c[3]; + break; + case SP_COLOR_SCALES_MODE_CMYK: { + _getCmykaFloatv(c); + + float rgb[3]; + SPColor::cmyk_to_rgb_floatv(rgb, c[0], c[1], c[2], c[3]); + color.set(rgb[0], rgb[1], rgb[2]); + alpha = c[4]; + break; + } + default: + g_warning("file %s: line %d: Illegal color selector mode %d", __FILE__, __LINE__, _mode); + break; + } + + _color.preserveICC(); + _color.setColorAlpha(color, alpha); +} + +void ColorScales::_updateDisplay() +{ +#ifdef DUMP_CHANGE_INFO + g_message("ColorScales::_onColorChanged( this=%p, %f, %f, %f, %f)", this, _color.color().v.c[0], + _color.color().v.c[1], _color.color().v.c[2], _color.alpha()); +#endif + gfloat tmp[3]; + gfloat c[5] = { 0.0, 0.0, 0.0, 0.0 }; + + SPColor color = _color.color(); + + switch (_mode) { + case SP_COLOR_SCALES_MODE_RGB: + color.get_rgb_floatv(c); + c[3] = _color.alpha(); + c[4] = 0.0; + break; + case SP_COLOR_SCALES_MODE_HSL: + color.get_rgb_floatv(tmp); + SPColor::rgb_to_hsl_floatv(c, tmp[0], tmp[1], tmp[2]); + c[3] = _color.alpha(); + c[4] = 0.0; + break; + case SP_COLOR_SCALES_MODE_HSV: + color.get_rgb_floatv(tmp); + SPColor::rgb_to_hsv_floatv(c, tmp[0], tmp[1], tmp[2]); + c[3] = _color.alpha(); + c[4] = 0.0; + break; + case SP_COLOR_SCALES_MODE_CMYK: + color.get_cmyk_floatv(c); + c[4] = _color.alpha(); + break; + default: + g_warning("file %s: line %d: Illegal color selector mode %d", __FILE__, __LINE__, _mode); + break; + } + + _updating = TRUE; + setScaled(_a[0], c[0]); + setScaled(_a[1], c[1]); + setScaled(_a[2], c[2]); + setScaled(_a[3], c[3]); + setScaled(_a[4], c[4]); + _updateSliders(CSC_CHANNELS_ALL); + _updating = FALSE; +} + +/* Helpers for setting color value */ +gfloat ColorScales::getScaled(const GtkAdjustment *a) +{ + gfloat val = gtk_adjustment_get_value(const_cast<GtkAdjustment *>(a)) / + gtk_adjustment_get_upper(const_cast<GtkAdjustment *>(a)); + return val; +} + +void ColorScales::setScaled(GtkAdjustment *a, gfloat v, bool constrained) +{ + gdouble upper = gtk_adjustment_get_upper(a); + gfloat val = v * upper; + if (constrained) { + // TODO: do we want preferences for these? + if (upper == 255) { + val = round(val/16) * 16; + } else { + val = round(val/10) * 10; + } + } + gtk_adjustment_set_value(a, val); +} + +void ColorScales::_setRangeLimit(gdouble upper) +{ + _rangeLimit = upper; + for (auto & i : _a) { + gtk_adjustment_set_upper(i, upper); + } +} + +void ColorScales::_onColorChanged() +{ + if (!get_visible()) { + return; + } + _updateDisplay(); +} + +void ColorScales::on_show() +{ + Gtk::Grid::on_show(); + _updateDisplay(); +} + +void ColorScales::_getRgbaFloatv(gfloat *rgba) +{ + g_return_if_fail(rgba != nullptr); + + switch (_mode) { + case SP_COLOR_SCALES_MODE_RGB: + rgba[0] = getScaled(_a[0]); + rgba[1] = getScaled(_a[1]); + rgba[2] = getScaled(_a[2]); + rgba[3] = getScaled(_a[3]); + break; + case SP_COLOR_SCALES_MODE_HSL: + SPColor::hsl_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2])); + rgba[3] = getScaled(_a[3]); + break; + case SP_COLOR_SCALES_MODE_HSV: + SPColor::hsv_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2])); + rgba[3] = getScaled(_a[3]); + break; + case SP_COLOR_SCALES_MODE_CMYK: + SPColor::cmyk_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), getScaled(_a[3])); + rgba[3] = getScaled(_a[4]); + break; + default: + g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__); + break; + } +} + +void ColorScales::_getCmykaFloatv(gfloat *cmyka) +{ + gfloat rgb[3]; + + g_return_if_fail(cmyka != nullptr); + + switch (_mode) { + case SP_COLOR_SCALES_MODE_RGB: + SPColor::rgb_to_cmyk_floatv(cmyka, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2])); + cmyka[4] = getScaled(_a[3]); + break; + case SP_COLOR_SCALES_MODE_HSL: + SPColor::hsl_to_rgb_floatv(rgb, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2])); + SPColor::rgb_to_cmyk_floatv(cmyka, rgb[0], rgb[1], rgb[2]); + cmyka[4] = getScaled(_a[3]); + break; + case SP_COLOR_SCALES_MODE_CMYK: + cmyka[0] = getScaled(_a[0]); + cmyka[1] = getScaled(_a[1]); + cmyka[2] = getScaled(_a[2]); + cmyka[3] = getScaled(_a[3]); + cmyka[4] = getScaled(_a[4]); + break; + default: + g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__); + break; + } +} + +guint32 ColorScales::_getRgba32() +{ + gfloat c[4]; + guint32 rgba; + + _getRgbaFloatv(c); + + rgba = SP_RGBA32_F_COMPOSE(c[0], c[1], c[2], c[3]); + + return rgba; +} + +void ColorScales::setMode(SPColorScalesMode mode) +{ + gfloat rgba[4]; + gfloat c[4]; + + if (_mode == mode) + return; + + if ((_mode == SP_COLOR_SCALES_MODE_RGB) || (_mode == SP_COLOR_SCALES_MODE_HSL) || + (_mode == SP_COLOR_SCALES_MODE_CMYK) || (_mode == SP_COLOR_SCALES_MODE_HSV)) { + _getRgbaFloatv(rgba); + } + else { + rgba[0] = rgba[1] = rgba[2] = rgba[3] = 1.0; + } + + _mode = mode; + + switch (mode) { + case SP_COLOR_SCALES_MODE_RGB: + _setRangeLimit(255.0); + gtk_adjustment_set_upper(_a[3], 100.0); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[0]), _("_R:")); + _s[0]->set_tooltip_text(_("Red")); + gtk_widget_set_tooltip_text(_b[0], _("Red")); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[1]), _("_G:")); + _s[1]->set_tooltip_text(_("Green")); + gtk_widget_set_tooltip_text(_b[1], _("Green")); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[2]), _("_B:")); + _s[2]->set_tooltip_text(_("Blue")); + gtk_widget_set_tooltip_text(_b[2], _("Blue")); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[3]), _("_A:")); + _s[3]->set_tooltip_text(_("Alpha (opacity)")); + gtk_widget_set_tooltip_text(_b[3], _("Alpha (opacity)")); + _s[0]->setMap(nullptr); + gtk_widget_hide(_l[4]); + _s[4]->hide(); + gtk_widget_hide(_b[4]); + _updating = TRUE; + setScaled(_a[0], rgba[0]); + setScaled(_a[1], rgba[1]); + setScaled(_a[2], rgba[2]); + setScaled(_a[3], rgba[3]); + _updateSliders(CSC_CHANNELS_ALL); + _updating = FALSE; + break; + case SP_COLOR_SCALES_MODE_HSL: + _setRangeLimit(100.0); + + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[0]), _("_H:")); + _s[0]->set_tooltip_text(_("Hue")); + gtk_widget_set_tooltip_text(_b[0], _("Hue")); + gtk_adjustment_set_upper(_a[0], 360.0); + + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[1]), _("_S:")); + _s[1]->set_tooltip_text(_("Saturation")); + gtk_widget_set_tooltip_text(_b[1], _("Saturation")); + + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[2]), _("_L:")); + _s[2]->set_tooltip_text(_("Lightness")); + gtk_widget_set_tooltip_text(_b[2], _("Lightness")); + + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[3]), _("_A:")); + _s[3]->set_tooltip_text(_("Alpha (opacity)")); + gtk_widget_set_tooltip_text(_b[3], _("Alpha (opacity)")); + _s[0]->setMap((guchar *)(sp_color_scales_hue_map())); + gtk_widget_hide(_l[4]); + _s[4]->hide(); + gtk_widget_hide(_b[4]); + _updating = TRUE; + c[0] = 0.0; + + SPColor::rgb_to_hsl_floatv(c, rgba[0], rgba[1], rgba[2]); + + setScaled(_a[0], c[0]); + setScaled(_a[1], c[1]); + setScaled(_a[2], c[2]); + setScaled(_a[3], rgba[3]); + + _updateSliders(CSC_CHANNELS_ALL); + _updating = FALSE; + break; + case SP_COLOR_SCALES_MODE_HSV: + _setRangeLimit(100.0); + + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[0]), _("_H:")); + _s[0]->set_tooltip_text(_("Hue")); + gtk_widget_set_tooltip_text(_b[0], _("Hue")); + gtk_adjustment_set_upper(_a[0], 360.0); + + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[1]), _("_S:")); + _s[1]->set_tooltip_text(_("Saturation")); + gtk_widget_set_tooltip_text(_b[1], _("Saturation")); + + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[2]), _("_V:")); + _s[2]->set_tooltip_text(_("Value")); + gtk_widget_set_tooltip_text(_b[2], _("Value")); + + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[3]), _("_A:")); + _s[3]->set_tooltip_text(_("Alpha (opacity)")); + gtk_widget_set_tooltip_text(_b[3], _("Alpha (opacity)")); + _s[0]->setMap((guchar *)(sp_color_scales_hue_map())); + gtk_widget_hide(_l[4]); + _s[4]->hide(); + gtk_widget_hide(_b[4]); + _updating = TRUE; + c[0] = 0.0; + + SPColor::rgb_to_hsv_floatv(c, rgba[0], rgba[1], rgba[2]); + + setScaled(_a[0], c[0]); + setScaled(_a[1], c[1]); + setScaled(_a[2], c[2]); + setScaled(_a[3], rgba[3]); + + _updateSliders(CSC_CHANNELS_ALL); + _updating = FALSE; + break; + case SP_COLOR_SCALES_MODE_CMYK: + _setRangeLimit(100.0); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[0]), _("_C:")); + _s[0]->set_tooltip_text(_("Cyan")); + gtk_widget_set_tooltip_text(_b[0], _("Cyan")); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[1]), _("_M:")); + _s[1]->set_tooltip_text(_("Magenta")); + gtk_widget_set_tooltip_text(_b[1], _("Magenta")); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[2]), _("_Y:")); + _s[2]->set_tooltip_text(_("Yellow")); + gtk_widget_set_tooltip_text(_b[2], _("Yellow")); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[3]), _("_K:")); + _s[3]->set_tooltip_text(_("Black")); + gtk_widget_set_tooltip_text(_b[3], _("Black")); + gtk_label_set_markup_with_mnemonic(GTK_LABEL(_l[4]), _("_A:")); + _s[4]->set_tooltip_text(_("Alpha (opacity)")); + gtk_widget_set_tooltip_text(_b[4], _("Alpha (opacity)")); + _s[0]->setMap(nullptr); + gtk_widget_show(_l[4]); + _s[4]->show(); + gtk_widget_show(_b[4]); + _updating = TRUE; + + SPColor::rgb_to_cmyk_floatv(c, rgba[0], rgba[1], rgba[2]); + setScaled(_a[0], c[0]); + setScaled(_a[1], c[1]); + setScaled(_a[2], c[2]); + setScaled(_a[3], c[3]); + + setScaled(_a[4], rgba[3]); + _updateSliders(CSC_CHANNELS_ALL); + _updating = FALSE; + break; + default: + g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__); + break; + } +} + +SPColorScalesMode ColorScales::getMode() const { return _mode; } + +void ColorScales::_adjustmentAnyChanged(GtkAdjustment *adjustment, ColorScales *cs) +{ + gint channel = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(adjustment), "channel")); + + _adjustmentChanged(cs, channel); +} + +void ColorScales::_sliderAnyGrabbed() +{ + if (_updating) { + return; + } + if (!_dragging) { + _dragging = TRUE; + _color.setHeld(true); + } +} + +void ColorScales::_sliderAnyReleased() +{ + if (_updating) { + return; + } + if (_dragging) { + _dragging = FALSE; + _color.setHeld(false); + } +} + +void ColorScales::_sliderAnyChanged() +{ + if (_updating) { + return; + } + _recalcColor(); +} + +void ColorScales::_adjustmentChanged(ColorScales *scales, guint channel) +{ + if (scales->_updating) { + return; + } + + scales->_updateSliders((1 << channel)); + scales->_recalcColor(); +} + +void ColorScales::_updateSliders(guint channels) +{ + gfloat rgb0[3], rgbm[3], rgb1[3]; +#ifdef SPCS_PREVIEW + guint32 rgba; +#endif + switch (_mode) { + case SP_COLOR_SCALES_MODE_RGB: + if ((channels != CSC_CHANNEL_R) && (channels != CSC_CHANNEL_A)) { + /* Update red */ + _s[0]->setColors(SP_RGBA32_F_COMPOSE(0.0, getScaled(_a[1]), getScaled(_a[2]), 1.0), + SP_RGBA32_F_COMPOSE(0.5, getScaled(_a[1]), getScaled(_a[2]), 1.0), + SP_RGBA32_F_COMPOSE(1.0, getScaled(_a[1]), getScaled(_a[2]), 1.0)); + } + if ((channels != CSC_CHANNEL_G) && (channels != CSC_CHANNEL_A)) { + /* Update green */ + _s[1]->setColors(SP_RGBA32_F_COMPOSE(getScaled(_a[0]), 0.0, getScaled(_a[2]), 1.0), + SP_RGBA32_F_COMPOSE(getScaled(_a[0]), 0.5, getScaled(_a[2]), 1.0), + SP_RGBA32_F_COMPOSE(getScaled(_a[0]), 1.0, getScaled(_a[2]), 1.0)); + } + if ((channels != CSC_CHANNEL_B) && (channels != CSC_CHANNEL_A)) { + /* Update blue */ + _s[2]->setColors(SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), 0.0, 1.0), + SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), 0.5, 1.0), + SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), 1.0, 1.0)); + } + if (channels != CSC_CHANNEL_A) { + /* Update alpha */ + _s[3]->setColors(SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 0.0), + SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 0.5), + SP_RGBA32_F_COMPOSE(getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 1.0)); + } + break; + case SP_COLOR_SCALES_MODE_HSL: + /* Hue is never updated */ + if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) { + /* Update saturation */ + SPColor::hsl_to_rgb_floatv(rgb0, getScaled(_a[0]), 0.0, getScaled(_a[2])); + SPColor::hsl_to_rgb_floatv(rgbm, getScaled(_a[0]), 0.5, getScaled(_a[2])); + SPColor::hsl_to_rgb_floatv(rgb1, getScaled(_a[0]), 1.0, getScaled(_a[2])); + _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), + SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), + SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); + } + if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) { + /* Update value */ + SPColor::hsl_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), 0.0); + SPColor::hsl_to_rgb_floatv(rgbm, getScaled(_a[0]), getScaled(_a[1]), 0.5); + SPColor::hsl_to_rgb_floatv(rgb1, getScaled(_a[0]), getScaled(_a[1]), 1.0); + _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), + SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), + SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); + } + if (channels != CSC_CHANNEL_A) { + /* Update alpha */ + SPColor::hsl_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2])); + _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0), + SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5), + SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0)); + } + break; + case SP_COLOR_SCALES_MODE_HSV: + /* Hue is never updated */ + if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) { + /* Update saturation */ + SPColor::hsv_to_rgb_floatv(rgb0, getScaled(_a[0]), 0.0, getScaled(_a[2])); + SPColor::hsv_to_rgb_floatv(rgbm, getScaled(_a[0]), 0.5, getScaled(_a[2])); + SPColor::hsv_to_rgb_floatv(rgb1, getScaled(_a[0]), 1.0, getScaled(_a[2])); + _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), + SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), + SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); + } + if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) { + /* Update value */ + SPColor::hsv_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), 0.0); + SPColor::hsv_to_rgb_floatv(rgbm, getScaled(_a[0]), getScaled(_a[1]), 0.5); + SPColor::hsv_to_rgb_floatv(rgb1, getScaled(_a[0]), getScaled(_a[1]), 1.0); + _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), + SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), + SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); + } + if (channels != CSC_CHANNEL_A) { + /* Update alpha */ + SPColor::hsv_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2])); + _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0), + SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5), + SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0)); + } + break; + case SP_COLOR_SCALES_MODE_CMYK: + if ((channels != CSC_CHANNEL_C) && (channels != CSC_CHANNEL_CMYKA)) { + /* Update C */ + SPColor::cmyk_to_rgb_floatv(rgb0, 0.0, getScaled(_a[1]), getScaled(_a[2]), getScaled(_a[3])); + SPColor::cmyk_to_rgb_floatv(rgbm, 0.5, getScaled(_a[1]), getScaled(_a[2]), getScaled(_a[3])); + SPColor::cmyk_to_rgb_floatv(rgb1, 1.0, getScaled(_a[1]), getScaled(_a[2]), getScaled(_a[3])); + _s[0]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), + SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), + SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); + } + if ((channels != CSC_CHANNEL_M) && (channels != CSC_CHANNEL_CMYKA)) { + /* Update M */ + SPColor::cmyk_to_rgb_floatv(rgb0, getScaled(_a[0]), 0.0, getScaled(_a[2]), getScaled(_a[3])); + SPColor::cmyk_to_rgb_floatv(rgbm, getScaled(_a[0]), 0.5, getScaled(_a[2]), getScaled(_a[3])); + SPColor::cmyk_to_rgb_floatv(rgb1, getScaled(_a[0]), 1.0, getScaled(_a[2]), getScaled(_a[3])); + _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), + SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), + SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); + } + if ((channels != CSC_CHANNEL_Y) && (channels != CSC_CHANNEL_CMYKA)) { + /* Update Y */ + SPColor::cmyk_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), 0.0, getScaled(_a[3])); + SPColor::cmyk_to_rgb_floatv(rgbm, getScaled(_a[0]), getScaled(_a[1]), 0.5, getScaled(_a[3])); + SPColor::cmyk_to_rgb_floatv(rgb1, getScaled(_a[0]), getScaled(_a[1]), 1.0, getScaled(_a[3])); + _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), + SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), + SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); + } + if ((channels != CSC_CHANNEL_K) && (channels != CSC_CHANNEL_CMYKA)) { + /* Update K */ + SPColor::cmyk_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 0.0); + SPColor::cmyk_to_rgb_floatv(rgbm, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 0.5); + SPColor::cmyk_to_rgb_floatv(rgb1, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 1.0); + _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0), + SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0), + SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0)); + } + if (channels != CSC_CHANNEL_CMYKA) { + /* Update alpha */ + SPColor::cmyk_to_rgb_floatv(rgb0, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), + getScaled(_a[3])); + _s[4]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0), + SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5), + SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0)); + } + break; + default: + g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__); + break; + } + +#ifdef SPCS_PREVIEW + rgba = sp_color_scales_get_rgba32(cs); + sp_color_preview_set_rgba32(SP_COLOR_PREVIEW(_p), rgba); +#endif +} + +static const gchar *sp_color_scales_hue_map() +{ + static gchar *map = nullptr; + + if (!map) { + gchar *p; + gint h; + map = g_new(gchar, 4 * 1024); + p = map; + for (h = 0; h < 1024; h++) { + gfloat rgb[3]; + SPColor::hsl_to_rgb_floatv(rgb, h / 1024.0, 1.0, 0.5); + *p++ = SP_COLOR_F_TO_U(rgb[0]); + *p++ = SP_COLOR_F_TO_U(rgb[1]); + *p++ = SP_COLOR_F_TO_U(rgb[2]); + *p++ = 0xFF; + } + } + + return map; +} + +ColorScalesFactory::ColorScalesFactory(SPColorScalesMode submode) + : _submode(submode) +{ +} + +ColorScalesFactory::~ColorScalesFactory() = default; + +Gtk::Widget *ColorScalesFactory::createWidget(Inkscape::UI::SelectedColor &color) const +{ + Gtk::Widget *w = Gtk::manage(new ColorScales(color, _submode)); + return w; +} + +Glib::ustring ColorScalesFactory::modeName() const { + return gettext(ColorScales::SUBMODE_NAMES[_submode]); +} + +} +} +} diff --git a/src/ui/widget/color-scales.h b/src/ui/widget/color-scales.h new file mode 100644 index 0000000..f3007fd --- /dev/null +++ b/src/ui/widget/color-scales.h @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_SP_COLOR_SCALES_H +#define SEEN_SP_COLOR_SCALES_H + +#include <gtkmm/grid.h> + +#include "ui/selected-color.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +class ColorSlider; + +enum SPColorScalesMode { + SP_COLOR_SCALES_MODE_NONE = 0, + SP_COLOR_SCALES_MODE_RGB = 1, + SP_COLOR_SCALES_MODE_HSL = 2, + SP_COLOR_SCALES_MODE_CMYK = 3, + SP_COLOR_SCALES_MODE_HSV = 4 +}; + +class ColorScales + : public Gtk::Grid +{ +public: + static const gchar *SUBMODE_NAMES[]; + + static gfloat getScaled(const GtkAdjustment *a); + static void setScaled(GtkAdjustment *a, gfloat v, bool constrained = false); + + ColorScales(SelectedColor &color, SPColorScalesMode mode); + ~ColorScales() override; + + virtual void _initUI(SPColorScalesMode mode); + + void setMode(SPColorScalesMode mode); + SPColorScalesMode getMode() const; + +protected: + void _onColorChanged(); + void on_show() override; + + static void _adjustmentAnyChanged(GtkAdjustment *adjustment, ColorScales *cs); + void _sliderAnyGrabbed(); + void _sliderAnyReleased(); + void _sliderAnyChanged(); + static void _adjustmentChanged(ColorScales *cs, guint channel); + + void _getRgbaFloatv(gfloat *rgba); + void _getCmykaFloatv(gfloat *cmyka); + guint32 _getRgba32(); + void _updateSliders(guint channels); + void _recalcColor(); + void _updateDisplay(); + + void _setRangeLimit(gdouble upper); + + SelectedColor &_color; + SPColorScalesMode _mode; + gdouble _rangeLimit; + gboolean _updating : 1; + gboolean _dragging : 1; + GtkAdjustment *_a[5]; /* Channel adjustments */ + Inkscape::UI::Widget::ColorSlider *_s[5]; /* Channel sliders */ + GtkWidget *_b[5]; /* Spinbuttons */ + GtkWidget *_l[5]; /* Labels */ + +private: + // By default, disallow copy constructor and assignment operator + ColorScales(ColorScales const &obj) = delete; + ColorScales &operator=(ColorScales const &obj) = delete; +}; + +class ColorScalesFactory : public Inkscape::UI::ColorSelectorFactory +{ +public: + ColorScalesFactory(SPColorScalesMode submode); + ~ColorScalesFactory() override; + + Gtk::Widget *createWidget(Inkscape::UI::SelectedColor &color) const override; + Glib::ustring modeName() const override; + +private: + SPColorScalesMode _submode; +}; + +} +} +} + +#endif /* !SEEN_SP_COLOR_SCALES_H */ +/* + 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 : diff --git a/src/ui/widget/color-slider.cpp b/src/ui/widget/color-slider.cpp new file mode 100644 index 0000000..2d19055 --- /dev/null +++ b/src/ui/widget/color-slider.cpp @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * A slider with colored background - implementation. + *//* + * Authors: + * see git history + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gdkmm/cursor.h> +#include <gdkmm/general.h> +#include <gtkmm/adjustment.h> +#include <gtkmm/stylecontext.h> + +#include "ui/widget/color-scales.h" +#include "ui/widget/color-slider.h" +#include "preferences.h" + +static const gint SLIDER_WIDTH = 96; +static const gint SLIDER_HEIGHT = 8; +static const gint ARROW_SIZE = 7; + +static const guchar *sp_color_slider_render_gradient(gint x0, gint y0, gint width, gint height, gint c[], gint dc[], + guint b0, guint b1, guint mask); +static const guchar *sp_color_slider_render_map(gint x0, gint y0, gint width, gint height, guchar *map, gint start, + gint step, guint b0, guint b1, guint mask); + +namespace Inkscape { +namespace UI { +namespace Widget { + +ColorSlider::ColorSlider(Glib::RefPtr<Gtk::Adjustment> adjustment) + : _dragging(false) + , _value(0.0) + , _oldvalue(0.0) + , _mapsize(0) + , _map(nullptr) +{ + _c0[0] = 0x00; + _c0[1] = 0x00; + _c0[2] = 0x00; + _c0[3] = 0xff; + + _cm[0] = 0xff; + _cm[1] = 0x00; + _cm[2] = 0x00; + _cm[3] = 0xff; + + _c0[0] = 0xff; + _c0[1] = 0xff; + _c0[2] = 0xff; + _c0[3] = 0xff; + + _b0 = 0x5f; + _b1 = 0xa0; + _bmask = 0x08; + + setAdjustment(adjustment); +} + +ColorSlider::~ColorSlider() +{ + if (_adjustment) { + _adjustment_changed_connection.disconnect(); + _adjustment_value_changed_connection.disconnect(); + _adjustment.reset(); + } +} + +void ColorSlider::on_realize() +{ + set_realized(); + + if (!_gdk_window) { + GdkWindowAttr attributes; + gint attributes_mask; + Gtk::Allocation allocation = get_allocation(); + + memset(&attributes, 0, sizeof(attributes)); + attributes.x = allocation.get_x(); + attributes.y = allocation.get_y(); + attributes.width = allocation.get_width(); + attributes.height = allocation.get_height(); + attributes.window_type = GDK_WINDOW_CHILD; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.visual = gdk_screen_get_system_visual(gdk_screen_get_default()); + attributes.event_mask = get_events(); + attributes.event_mask |= (Gdk::EXPOSURE_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | + Gdk::POINTER_MOTION_MASK | Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK); + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL; + + _gdk_window = Gdk::Window::create(get_parent_window(), &attributes, attributes_mask); + set_window(_gdk_window); + _gdk_window->set_user_data(gobj()); + } +} + +void ColorSlider::on_unrealize() +{ + _gdk_window.reset(); + + Gtk::Widget::on_unrealize(); +} + +void ColorSlider::on_size_allocate(Gtk::Allocation &allocation) +{ + set_allocation(allocation); + + if (get_realized()) { + _gdk_window->move_resize(allocation.get_x(), allocation.get_y(), allocation.get_width(), + allocation.get_height()); + } +} + +void ColorSlider::get_preferred_width_vfunc(int &minimum_width, int &natural_width) const +{ + Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context(); + Gtk::Border padding = style_context->get_padding(get_state_flags()); + int width = SLIDER_WIDTH + padding.get_left() + padding.get_right(); + minimum_width = natural_width = width; +} + +void ColorSlider::get_preferred_width_for_height_vfunc(int /*height*/, int &minimum_width, int &natural_width) const +{ + get_preferred_width(minimum_width, natural_width); +} + +void ColorSlider::get_preferred_height_vfunc(int &minimum_height, int &natural_height) const +{ + Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context(); + Gtk::Border padding = style_context->get_padding(get_state_flags()); + int height = SLIDER_HEIGHT + padding.get_top() + padding.get_bottom(); + minimum_height = natural_height = height; +} + +void ColorSlider::get_preferred_height_for_width_vfunc(int /*width*/, int &minimum_height, int &natural_height) const +{ + get_preferred_height(minimum_height, natural_height); +} + +bool ColorSlider::on_button_press_event(GdkEventButton *event) +{ + if (event->button == 1) { + Gtk::Allocation allocation = get_allocation(); + gint cx, cw; + cx = get_style_context()->get_padding(get_state_flags()).get_left(); + cw = allocation.get_width() - 2 * cx; + signal_grabbed.emit(); + _dragging = true; + _oldvalue = _value; + gfloat value = CLAMP((gfloat)(event->x - cx) / cw, 0.0, 1.0); + bool constrained = event->state & GDK_CONTROL_MASK; + ColorScales::setScaled(_adjustment->gobj(), value, constrained); + signal_dragged.emit(); + + auto window = _gdk_window->gobj(); + + auto seat = gdk_event_get_seat(reinterpret_cast<GdkEvent *>(event)); + gdk_seat_grab(seat, + window, + GDK_SEAT_CAPABILITY_ALL_POINTING, + FALSE, + nullptr, + reinterpret_cast<GdkEvent *>(event), + nullptr, + nullptr); + } + + return false; +} + +bool ColorSlider::on_button_release_event(GdkEventButton *event) +{ + if (event->button == 1) { + gdk_seat_ungrab(gdk_event_get_seat(reinterpret_cast<GdkEvent *>(event))); + _dragging = false; + signal_released.emit(); + if (_value != _oldvalue) { + signal_value_changed.emit(); + } + } + + return false; +} + +bool ColorSlider::on_motion_notify_event(GdkEventMotion *event) +{ + if (_dragging) { + gint cx, cw; + Gtk::Allocation allocation = get_allocation(); + cx = get_style_context()->get_padding(get_state_flags()).get_left(); + cw = allocation.get_width() - 2 * cx; + gfloat value = CLAMP((gfloat)(event->x - cx) / cw, 0.0, 1.0); + bool constrained = event->state & GDK_CONTROL_MASK; + ColorScales::setScaled(_adjustment->gobj(), value, constrained); + signal_dragged.emit(); + } + + return false; +} + +void ColorSlider::setAdjustment(Glib::RefPtr<Gtk::Adjustment> adjustment) +{ + if (!adjustment) { + _adjustment = Gtk::Adjustment::create(0.0, 0.0, 1.0, 0.01, 0.0, 0.0); + } + else { + adjustment->set_page_increment(0.0); + adjustment->set_page_size(0.0); + } + + if (_adjustment != adjustment) { + if (_adjustment) { + _adjustment_changed_connection.disconnect(); + _adjustment_value_changed_connection.disconnect(); + } + + _adjustment = adjustment; + _adjustment_changed_connection = + _adjustment->signal_changed().connect(sigc::mem_fun(this, &ColorSlider::_onAdjustmentChanged)); + _adjustment_value_changed_connection = + _adjustment->signal_value_changed().connect(sigc::mem_fun(this, &ColorSlider::_onAdjustmentValueChanged)); + + _value = ColorScales::getScaled(_adjustment->gobj()); + + _onAdjustmentChanged(); + } +} + +void ColorSlider::_onAdjustmentChanged() { queue_draw(); } + +void ColorSlider::_onAdjustmentValueChanged() +{ + if (_value != ColorScales::getScaled(_adjustment->gobj())) { + gint cx, cy, cw, ch; + auto style_context = get_style_context(); + auto allocation = get_allocation(); + auto padding = style_context->get_padding(get_state_flags()); + cx = padding.get_left(); + cy = padding.get_top(); + cw = allocation.get_width() - 2 * cx; + ch = allocation.get_height() - 2 * cy; + if ((gint)(ColorScales::getScaled(_adjustment->gobj()) * cw) != (gint)(_value * cw)) { + gint ax, ay; + gfloat value; + value = _value; + _value = ColorScales::getScaled(_adjustment->gobj()); + ax = (int)(cx + value * cw - ARROW_SIZE / 2 - 2); + ay = cy; + queue_draw_area(ax, ay, ARROW_SIZE + 4, ch); + ax = (int)(cx + _value * cw - ARROW_SIZE / 2 - 2); + ay = cy; + queue_draw_area(ax, ay, ARROW_SIZE + 4, ch); + } + else { + _value = ColorScales::getScaled(_adjustment->gobj()); + } + } +} + +void ColorSlider::setColors(guint32 start, guint32 mid, guint32 end) +{ + // Remove any map, if set + _map = nullptr; + + _c0[0] = start >> 24; + _c0[1] = (start >> 16) & 0xff; + _c0[2] = (start >> 8) & 0xff; + _c0[3] = start & 0xff; + + _cm[0] = mid >> 24; + _cm[1] = (mid >> 16) & 0xff; + _cm[2] = (mid >> 8) & 0xff; + _cm[3] = mid & 0xff; + + _c1[0] = end >> 24; + _c1[1] = (end >> 16) & 0xff; + _c1[2] = (end >> 8) & 0xff; + _c1[3] = end & 0xff; + + queue_draw(); +} + +void ColorSlider::setMap(const guchar *map) +{ + _map = const_cast<guchar *>(map); + + queue_draw(); +} + +void ColorSlider::setBackground(guint dark, guint light, guint size) +{ + _b0 = dark; + _b1 = light; + _bmask = size; + + queue_draw(); +} + +bool ColorSlider::on_draw(const Cairo::RefPtr<Cairo::Context> &cr) +{ + gboolean colorsOnTop = Inkscape::Preferences::get()->getBool("/options/workarounds/colorsontop", false); + + auto allocation = get_allocation(); + auto style_context = get_style_context(); + + // Draw shadow + if (colorsOnTop) { + style_context->render_frame(cr, 0, 0, allocation.get_width(), allocation.get_height()); + } + + /* Paintable part of color gradient area */ + Gdk::Rectangle carea; + Gtk::Border padding; + + padding = style_context->get_padding(get_state_flags()); + + carea.set_x(padding.get_left()); + carea.set_y(padding.get_top()); + + carea.set_width(allocation.get_width() - 2 * carea.get_x()); + carea.set_height(allocation.get_height() - 2 * carea.get_y()); + + if (_map) { + /* Render map pixelstore */ + gint d = (1024 << 16) / carea.get_width(); + gint s = 0; + + const guchar *b = + sp_color_slider_render_map(0, 0, carea.get_width(), carea.get_height(), _map, s, d, _b0, _b1, _bmask); + + if (b != nullptr && carea.get_width() > 0) { + Glib::RefPtr<Gdk::Pixbuf> pb = Gdk::Pixbuf::create_from_data( + b, Gdk::COLORSPACE_RGB, false, 8, carea.get_width(), carea.get_height(), carea.get_width() * 3); + + Gdk::Cairo::set_source_pixbuf(cr, pb, carea.get_x(), carea.get_y()); + cr->paint(); + } + } + else { + gint c[4], dc[4]; + + /* Render gradient */ + + // part 1: from c0 to cm + if (carea.get_width() > 0) { + for (gint i = 0; i < 4; i++) { + c[i] = _c0[i] << 16; + dc[i] = ((_cm[i] << 16) - c[i]) / (carea.get_width() / 2); + } + guint wi = carea.get_width() / 2; + const guchar *b = sp_color_slider_render_gradient(0, 0, wi, carea.get_height(), c, dc, _b0, _b1, _bmask); + + /* Draw pixelstore 1 */ + if (b != nullptr && wi > 0) { + Glib::RefPtr<Gdk::Pixbuf> pb = + Gdk::Pixbuf::create_from_data(b, Gdk::COLORSPACE_RGB, false, 8, wi, carea.get_height(), wi * 3); + + Gdk::Cairo::set_source_pixbuf(cr, pb, carea.get_x(), carea.get_y()); + cr->paint(); + } + } + + // part 2: from cm to c1 + if (carea.get_width() > 0) { + for (gint i = 0; i < 4; i++) { + c[i] = _cm[i] << 16; + dc[i] = ((_c1[i] << 16) - c[i]) / (carea.get_width() / 2); + } + guint wi = carea.get_width() / 2; + const guchar *b = sp_color_slider_render_gradient(carea.get_width() / 2, 0, wi, carea.get_height(), c, dc, + _b0, _b1, _bmask); + + /* Draw pixelstore 2 */ + if (b != nullptr && wi > 0) { + Glib::RefPtr<Gdk::Pixbuf> pb = + Gdk::Pixbuf::create_from_data(b, Gdk::COLORSPACE_RGB, false, 8, wi, carea.get_height(), wi * 3); + + Gdk::Cairo::set_source_pixbuf(cr, pb, carea.get_width() / 2 + carea.get_x(), carea.get_y()); + cr->paint(); + } + } + } + + /* Draw shadow */ + if (!colorsOnTop) { + style_context->render_frame(cr, 0, 0, allocation.get_width(), allocation.get_height()); + } + + /* Draw arrow */ + gint x = (int)(_value * (carea.get_width() - 1) - ARROW_SIZE / 2 + carea.get_x()); + gint y1 = carea.get_y(); + gint y2 = carea.get_y() + carea.get_height() - 1; + cr->set_line_width(1.0); + + // Define top arrow + cr->move_to(x - 0.5, y1 + 0.5); + cr->line_to(x + ARROW_SIZE - 0.5, y1 + 0.5); + cr->line_to(x + (ARROW_SIZE - 1) / 2.0, y1 + ARROW_SIZE / 2.0 + 0.5); + cr->line_to(x - 0.5, y1 + 0.5); + + // Define bottom arrow + cr->move_to(x - 0.5, y2 + 0.5); + cr->line_to(x + ARROW_SIZE - 0.5, y2 + 0.5); + cr->line_to(x + (ARROW_SIZE - 1) / 2.0, y2 - ARROW_SIZE / 2.0 + 0.5); + cr->line_to(x - 0.5, y2 + 0.5); + + // Render both arrows + cr->set_source_rgb(1.0, 1.0, 1.0); + cr->stroke_preserve(); + cr->set_source_rgb(0.0, 0.0, 0.0); + cr->fill(); + + return false; +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +/* Colors are << 16 */ + +static const guchar *sp_color_slider_render_gradient(gint x0, gint y0, gint width, gint height, gint c[], gint dc[], + guint b0, guint b1, guint mask) +{ + static guchar *buf = nullptr; + static gint bs = 0; + guchar *dp; + gint x, y; + guint r, g, b, a; + + if (buf && (bs < width * height)) { + g_free(buf); + buf = nullptr; + } + if (!buf) { + buf = g_new(guchar, width * height * 3); + bs = width * height; + } + + dp = buf; + r = c[0]; + g = c[1]; + b = c[2]; + a = c[3]; + for (x = x0; x < x0 + width; x++) { + gint cr, cg, cb, ca; + guchar *d; + cr = r >> 16; + cg = g >> 16; + cb = b >> 16; + ca = a >> 16; + d = dp; + for (y = y0; y < y0 + height; y++) { + guint bg, fc; + /* Background value */ + bg = ((x & mask) ^ (y & mask)) ? b0 : b1; + fc = (cr - bg) * ca; + d[0] = bg + ((fc + (fc >> 8) + 0x80) >> 8); + fc = (cg - bg) * ca; + d[1] = bg + ((fc + (fc >> 8) + 0x80) >> 8); + fc = (cb - bg) * ca; + d[2] = bg + ((fc + (fc >> 8) + 0x80) >> 8); + d += 3 * width; + } + r += dc[0]; + g += dc[1]; + b += dc[2]; + a += dc[3]; + dp += 3; + } + + return buf; +} + +/* Positions are << 16 */ + +static const guchar *sp_color_slider_render_map(gint x0, gint y0, gint width, gint height, guchar *map, gint start, + gint step, guint b0, guint b1, guint mask) +{ + static guchar *buf = nullptr; + static gint bs = 0; + guchar *dp; + gint x, y; + + if (buf && (bs < width * height)) { + g_free(buf); + buf = nullptr; + } + if (!buf) { + buf = g_new(guchar, width * height * 3); + bs = width * height; + } + + dp = buf; + for (x = x0; x < x0 + width; x++) { + gint cr, cg, cb, ca; + guchar *d = dp; + guchar *sp = map + 4 * (start >> 16); + cr = *sp++; + cg = *sp++; + cb = *sp++; + ca = *sp++; + for (y = y0; y < y0 + height; y++) { + guint bg, fc; + /* Background value */ + bg = ((x & mask) ^ (y & mask)) ? b0 : b1; + fc = (cr - bg) * ca; + d[0] = bg + ((fc + (fc >> 8) + 0x80) >> 8); + fc = (cg - bg) * ca; + d[1] = bg + ((fc + (fc >> 8) + 0x80) >> 8); + fc = (cb - bg) * ca; + d[2] = bg + ((fc + (fc >> 8) + 0x80) >> 8); + d += 3 * width; + } + dp += 3; + start += step; + } + + return buf; +} +/* + 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 : diff --git a/src/ui/widget/color-slider.h b/src/ui/widget/color-slider.h new file mode 100644 index 0000000..6a0834e --- /dev/null +++ b/src/ui/widget/color-slider.h @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history +* Lauris Kaplinski <lauris@kaplinski.com> + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_COLOR_SLIDER_H +#define SEEN_COLOR_SLIDER_H + +#include <gtkmm/widget.h> +#include <sigc++/signal.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +/* + * A slider with colored background + */ +class ColorSlider : public Gtk::Widget { +public: + ColorSlider(Glib::RefPtr<Gtk::Adjustment> adjustment); + ~ColorSlider() override; + + void setAdjustment(Glib::RefPtr<Gtk::Adjustment> adjustment); + + void setColors(guint32 start, guint32 mid, guint32 end); + + void setMap(const guchar *map); + + void setBackground(guint dark, guint light, guint size); + + sigc::signal<void> signal_grabbed; + sigc::signal<void> signal_dragged; + sigc::signal<void> signal_released; + sigc::signal<void> signal_value_changed; + +protected: + void on_size_allocate(Gtk::Allocation &allocation) override; + void on_realize() override; + void on_unrealize() override; + bool on_button_press_event(GdkEventButton *event) override; + bool on_button_release_event(GdkEventButton *event) override; + bool on_motion_notify_event(GdkEventMotion *event) override; + bool on_draw(const Cairo::RefPtr<Cairo::Context> &cr) override; + void get_preferred_width_vfunc(int &minimum_width, int &natural_width) const override; + void get_preferred_width_for_height_vfunc(int height, int &minimum_width, int &natural_width) const override; + void get_preferred_height_vfunc(int &minimum_height, int &natural_height) const override; + void get_preferred_height_for_width_vfunc(int width, int &minimum_height, int &natural_height) const override; + +private: + void _onAdjustmentChanged(); + void _onAdjustmentValueChanged(); + + bool _dragging; + + Glib::RefPtr<Gtk::Adjustment> _adjustment; + sigc::connection _adjustment_changed_connection; + sigc::connection _adjustment_value_changed_connection; + + gfloat _value; + gfloat _oldvalue; + guchar _c0[4], _cm[4], _c1[4]; + guchar _b0, _b1; + guchar _bmask; + + gint _mapsize; + guchar *_map; + + Glib::RefPtr<Gdk::Window> _gdk_window; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif +/* + 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 : diff --git a/src/ui/widget/color-wheel-selector.cpp b/src/ui/widget/color-wheel-selector.cpp new file mode 100644 index 0000000..9695303 --- /dev/null +++ b/src/ui/widget/color-wheel-selector.cpp @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "color-wheel-selector.h" + +#include <glibmm/i18n.h> +#include <gtkmm/adjustment.h> +#include <gtkmm/label.h> +#include <gtkmm/spinbutton.h> +#include "ui/dialog-events.h" +#include "ui/widget/color-scales.h" +#include "ui/widget/color-slider.h" +#include "ui/widget/ink-color-wheel.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + + +#define XPAD 4 +#define YPAD 1 + + +const gchar *ColorWheelSelector::MODE_NAME = N_("Wheel"); + +ColorWheelSelector::ColorWheelSelector(SelectedColor &color) + : Gtk::Grid() + , _color(color) + , _updating(false) + , _wheel(nullptr) + , _slider(nullptr) +{ + set_name("ColorWheelSelector"); + + _initUI(); + _color_changed_connection = color.signal_changed.connect(sigc::mem_fun(this, &ColorWheelSelector::_colorChanged)); + _color_dragged_connection = color.signal_dragged.connect(sigc::mem_fun(this, &ColorWheelSelector::_colorChanged)); +} + +ColorWheelSelector::~ColorWheelSelector() +{ + _color_changed_connection.disconnect(); + _color_dragged_connection.disconnect(); +} + +void ColorWheelSelector::_initUI() +{ + /* Create components */ + gint row = 0; + + _wheel = Gtk::manage(new Inkscape::UI::Widget::ColorWheel()); + _wheel->set_halign(Gtk::ALIGN_FILL); + _wheel->set_valign(Gtk::ALIGN_FILL); + _wheel->set_hexpand(true); + _wheel->set_vexpand(true); + attach(*_wheel, 0, row, 3, 1); + + row++; + + /* Label */ + Gtk::Label *label = Gtk::manage(new Gtk::Label(_("_A:"), true)); + label->set_halign(Gtk::ALIGN_END); + label->set_valign(Gtk::ALIGN_CENTER); + + label->set_margin_start(XPAD); + label->set_margin_end(XPAD); + label->set_margin_top(YPAD); + label->set_margin_bottom(YPAD); + label->set_halign(Gtk::ALIGN_FILL); + label->set_valign(Gtk::ALIGN_FILL); + attach(*label, 0, row, 1, 1); + + /* Adjustment */ + _alpha_adjustment = Gtk::Adjustment::create(0.0, 0.0, 100.0, 1.0, 10.0, 10.0); + + /* Slider */ + _slider = Gtk::manage(new Inkscape::UI::Widget::ColorSlider(_alpha_adjustment)); + _slider->set_tooltip_text(_("Alpha (opacity)")); + + _slider->set_margin_start(XPAD); + _slider->set_margin_end(XPAD); + _slider->set_margin_top(YPAD); + _slider->set_margin_bottom(YPAD); + _slider->set_hexpand(true); + _slider->set_halign(Gtk::ALIGN_FILL); + _slider->set_valign(Gtk::ALIGN_FILL); + attach(*_slider, 1, row, 1, 1); + + _slider->setColors(SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 0.0), SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 0.5), + SP_RGBA32_F_COMPOSE(1.0, 1.0, 1.0, 1.0)); + + /* Spinbutton */ + auto spin_button = Gtk::manage(new Gtk::SpinButton(_alpha_adjustment, 1.0, 0)); + spin_button->set_tooltip_text(_("Alpha (opacity)")); + sp_dialog_defocus_on_enter(GTK_WIDGET(spin_button->gobj())); + label->set_mnemonic_widget(*spin_button); + + spin_button->set_margin_start(XPAD); + spin_button->set_margin_end(XPAD); + spin_button->set_margin_top(YPAD); + spin_button->set_margin_bottom(YPAD); + spin_button->set_halign(Gtk::ALIGN_CENTER); + spin_button->set_valign(Gtk::ALIGN_CENTER); + attach(*spin_button, 2, row, 1, 1); + + /* Signals */ + _alpha_adjustment->signal_value_changed().connect(sigc::mem_fun(this, &ColorWheelSelector::_adjustmentChanged)); + _slider->signal_grabbed.connect(sigc::mem_fun(*this, &ColorWheelSelector::_sliderGrabbed)); + _slider->signal_released.connect(sigc::mem_fun(*this, &ColorWheelSelector::_sliderReleased)); + _slider->signal_value_changed.connect(sigc::mem_fun(*this, &ColorWheelSelector::_sliderChanged)); + _wheel->signal_color_changed().connect(sigc::mem_fun(*this, &ColorWheelSelector::_wheelChanged)); + + show_all(); +} + +void ColorWheelSelector::on_show() +{ + Gtk::Grid::on_show(); + _updateDisplay(); +} + +void ColorWheelSelector::_colorChanged() +{ + _updateDisplay(); +} + +void ColorWheelSelector::_adjustmentChanged() +{ + if (_updating) { + return; + } + + _color.preserveICC(); + _color.setAlpha(ColorScales::getScaled(_alpha_adjustment->gobj())); +} + +void ColorWheelSelector::_sliderGrabbed() +{ + _color.preserveICC(); + _color.setHeld(true); +} + +void ColorWheelSelector::_sliderReleased() +{ + _color.preserveICC(); + _color.setHeld(false); +} + +void ColorWheelSelector::_sliderChanged() +{ + if (_updating) { + return; + } + + _color.preserveICC(); + _color.setAlpha(ColorScales::getScaled(_alpha_adjustment->gobj())); +} + +void ColorWheelSelector::_wheelChanged() +{ + if (_updating) { + return; + } + + double rgb[3] = { 0, 0, 0 }; + _wheel->get_rgb(rgb[0], rgb[1], rgb[2]); + + SPColor color(rgb[0], rgb[1], rgb[2]); + + guint32 start = color.toRGBA32(0x00); + guint32 mid = color.toRGBA32(0x7f); + guint32 end = color.toRGBA32(0xff); + + _updating = true; + _slider->setColors(start, mid, end); + _color.preserveICC(); + + _color.setHeld(_wheel->is_adjusting()); + _color.setColor(color); + _updating = false; +} + +void ColorWheelSelector::_updateDisplay() +{ + if(_updating) { return; } + +#ifdef DUMP_CHANGE_INFO + g_message("ColorWheelSelector::_colorChanged( this=%p, %f, %f, %f, %f)", this, _color.color().v.c[0], + _color.color().v.c[1], _color.color().v.c[2], alpha); +#endif + + _updating = true; + { + float hsv[3] = { 0, 0, 0 }; + SPColor::rgb_to_hsv_floatv(hsv, _color.color().v.c[0], _color.color().v.c[1], _color.color().v.c[2]); + _wheel->set_rgb(_color.color().v.c[0], _color.color().v.c[1], _color.color().v.c[2]); + } + + guint32 start = _color.color().toRGBA32(0x00); + guint32 mid = _color.color().toRGBA32(0x7f); + guint32 end = _color.color().toRGBA32(0xff); + + _slider->setColors(start, mid, end); + + ColorScales::setScaled(_alpha_adjustment->gobj(), _color.alpha()); + + _updating = false; +} + + +Gtk::Widget *ColorWheelSelectorFactory::createWidget(Inkscape::UI::SelectedColor &color) const +{ + Gtk::Widget *w = Gtk::manage(new ColorWheelSelector(color)); + return w; +} + +Glib::ustring ColorWheelSelectorFactory::modeName() const { return gettext(ColorWheelSelector::MODE_NAME); } +} +} +} +/* + 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 : diff --git a/src/ui/widget/color-wheel-selector.h b/src/ui/widget/color-wheel-selector.h new file mode 100644 index 0000000..59cf6b6 --- /dev/null +++ b/src/ui/widget/color-wheel-selector.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Color selector widget containing GIMP color wheel and slider + */ +/* Authors: + * Tomasz Boczkowski <penginsbacon@gmail.com> (c++-sification) + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_SP_COLOR_WHEEL_SELECTOR_H +#define SEEN_SP_COLOR_WHEEL_SELECTOR_H + +#include <gtkmm/grid.h> + +#include "ui/selected-color.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +class ColorSlider; +class ColorWheel; +class ColorWheelSelector + : public Gtk::Grid +{ +public: + static const gchar *MODE_NAME; + + ColorWheelSelector(SelectedColor &color); + ~ColorWheelSelector() override; + +protected: + void _initUI(); + + void on_show() override; + + void _colorChanged(); + void _adjustmentChanged(); + void _sliderGrabbed(); + void _sliderReleased(); + void _sliderChanged(); + void _wheelChanged(); + + void _updateDisplay(); + + SelectedColor &_color; + bool _updating; + Glib::RefPtr<Gtk::Adjustment> _alpha_adjustment; + Inkscape::UI::Widget::ColorWheel *_wheel; + Inkscape::UI::Widget::ColorSlider *_slider; + +private: + // By default, disallow copy constructor and assignment operator + ColorWheelSelector(const ColorWheelSelector &obj) = delete; + ColorWheelSelector &operator=(const ColorWheelSelector &obj) = delete; + + sigc::connection _color_changed_connection; + sigc::connection _color_dragged_connection; +}; + +class ColorWheelSelectorFactory : public ColorSelectorFactory { +public: + Gtk::Widget *createWidget(SelectedColor &color) const override; + Glib::ustring modeName() const override; +}; +} +} +} + +#endif // SEEN_SP_COLOR_WHEEL_SELECTOR_H + +/* + 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 : diff --git a/src/ui/widget/combo-box-entry-tool-item.cpp b/src/ui/widget/combo-box-entry-tool-item.cpp new file mode 100644 index 0000000..9adcca1 --- /dev/null +++ b/src/ui/widget/combo-box-entry-tool-item.cpp @@ -0,0 +1,691 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A class derived from Gtk::ToolItem that wraps a GtkComboBoxEntry. + * Features: + * Setting GtkEntryBox width in characters. + * Passing a function for formatting cells. + * Displaying a warning if entry text isn't in list. + * Check comma separated values in text against list. (Useful for font-family fallbacks.) + * Setting names for GtkComboBoxEntry and GtkEntry (actionName_combobox, actionName_entry) + * to allow setting resources. + * + * Author(s): + * Tavmjong Bah + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2010 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +/* + * We must provide for both a toolbar item and a menu item. + * As we don't know which widgets are used (or even constructed), + * we must keep track of things like active entry ourselves. + */ + +#include "combo-box-entry-tool-item.h" + +#include <iostream> +#include <cstring> +#include <glibmm/ustring.h> + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <gdkmm/display.h> + +#include "ui/icon-names.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +ComboBoxEntryToolItem::ComboBoxEntryToolItem(Glib::ustring name, + Glib::ustring label, + Glib::ustring tooltip, + GtkTreeModel *model, + gint entry_width, + gint extra_width, + void *cell_data_func, + void *separator_func, + GtkWidget *focusWidget) + : _label(std::move(label)), + _tooltip(std::move(tooltip)), + _model(model), + _entry_width(entry_width), + _extra_width(extra_width), + _cell_data_func(cell_data_func), + _separator_func(separator_func), + _focusWidget(focusWidget), + _active(-1), + _text(strdup("")), + _entry_completion(nullptr), + _indicator(nullptr), + _popup(false), + _info(nullptr), + _info_cb(nullptr), + _info_cb_id(0), + _info_cb_blocked(false), + _warning(nullptr), + _warning_cb(nullptr), + _warning_cb_id(0), + _warning_cb_blocked(false), + _altx_name(nullptr) +{ + set_name(name); + + gchar *action_name = g_strdup( get_name().c_str() ); + gchar *combobox_name = g_strjoin( nullptr, action_name, "_combobox", NULL ); + gchar *entry_name = g_strjoin( nullptr, action_name, "_entry", NULL ); + g_free( action_name ); + + GtkWidget* comboBoxEntry = gtk_combo_box_new_with_model_and_entry (_model); + gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (comboBoxEntry), 0); + + // Name it so we can muck with it using an RC file + gtk_widget_set_name( comboBoxEntry, combobox_name ); + g_free( combobox_name ); + + { + gtk_widget_set_halign(comboBoxEntry, GTK_ALIGN_START); + gtk_widget_set_hexpand(comboBoxEntry, FALSE); + gtk_widget_set_vexpand(comboBoxEntry, FALSE); + add(*Glib::wrap(comboBoxEntry)); + } + + _combobox = GTK_COMBO_BOX (comboBoxEntry); + + //gtk_combo_box_set_active( GTK_COMBO_BOX( comboBoxEntry ), ink_comboboxentry_action->active ); + gtk_combo_box_set_active( GTK_COMBO_BOX( comboBoxEntry ), 0 ); + + g_signal_connect( G_OBJECT(comboBoxEntry), "changed", G_CALLBACK(combo_box_changed_cb), this ); + + // Optionally add separator function... + if( _separator_func != nullptr ) { + gtk_combo_box_set_row_separator_func( _combobox, + GtkTreeViewRowSeparatorFunc (_separator_func), + nullptr, nullptr ); + } + + // FIXME: once gtk3 migration is done this can be removed + // https://bugzilla.gnome.org/show_bug.cgi?id=734915 + gtk_widget_show_all (comboBoxEntry); + + // Optionally add formatting... + if( _cell_data_func != nullptr ) { + GtkCellRenderer *cell = gtk_cell_renderer_text_new(); + gtk_cell_layout_clear( GTK_CELL_LAYOUT( comboBoxEntry ) ); + gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( comboBoxEntry ), cell, true ); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT( comboBoxEntry ), cell, + GtkCellLayoutDataFunc (_cell_data_func), + nullptr, nullptr ); + } + + // Optionally widen the combobox width... which widens the drop-down list in list mode. + if( _extra_width > 0 ) { + GtkRequisition req; + gtk_widget_get_preferred_size(GTK_WIDGET(_combobox), &req, nullptr); + gtk_widget_set_size_request( GTK_WIDGET( _combobox ), + req.width + _extra_width, -1 ); + } + + // Get reference to GtkEntry and fiddle a bit with it. + GtkWidget *child = gtk_bin_get_child( GTK_BIN(comboBoxEntry) ); + + // Name it so we can muck with it using an RC file + gtk_widget_set_name( child, entry_name ); + g_free( entry_name ); + + if( child && GTK_IS_ENTRY( child ) ) { + + _entry = GTK_ENTRY(child); + + // Change width + if( _entry_width > 0 ) { + gtk_entry_set_width_chars (GTK_ENTRY (child), _entry_width ); + } + + // Add pop-up entry completion if required + if( _popup ) { + popup_enable(); + } + + // Add altx_name if required + if( _altx_name ) { + g_object_set_data( G_OBJECT( child ), _altx_name, _entry ); + } + + // Add signal for GtkEntry to check if finished typing. + g_signal_connect( G_OBJECT(child), "activate", G_CALLBACK(entry_activate_cb), this ); + g_signal_connect( G_OBJECT(child), "key-press-event", G_CALLBACK(keypress_cb), this ); + } + + set_tooltip(_tooltip.c_str()); + + show_all(); +} + +// Setters/Getters --------------------------------------------------- + +gchar* +ComboBoxEntryToolItem::get_active_text() +{ + gchar* text = g_strdup( _text ); + return text; +} + +/* + * For the font-family list we need to handle two cases: + * Text is in list store: + * In this case we use row number as the font-family list can have duplicate + * entries, one in the document font part and one in the system font part. In + * order that scrolling through the list works properly we must distinguish + * between the two. + * Text is not in the list store (i.e. default font-family is not on system): + * In this case we have a row number of -1, and the text must be set by hand. + */ +gboolean +ComboBoxEntryToolItem::set_active_text(const gchar* text, int row) +{ + if( strcmp( _text, text ) != 0 ) { + g_free( _text ); + _text = g_strdup( text ); + } + + // Get active row or -1 if none + if( row < 0 ) { + row = get_active_row_from_text(this, _text); + } + _active = row; + + // Set active row, check that combobox has been created. + if( _combobox ) { + gtk_combo_box_set_active( GTK_COMBO_BOX( _combobox ), _active ); + } + + // Fiddle with entry + if( _entry ) { + + // Explicitly set text in GtkEntry box (won't be set if text not in list). + gtk_entry_set_text( _entry, text ); + + // Show or hide warning -- this might be better moved to text-toolbox.cpp + if( _info_cb_id != 0 && + !_info_cb_blocked ) { + g_signal_handler_block (G_OBJECT(_entry), + _info_cb_id ); + _info_cb_blocked = true; + } + if( _warning_cb_id != 0 && + !_warning_cb_blocked ) { + g_signal_handler_block (G_OBJECT(_entry), + _warning_cb_id ); + _warning_cb_blocked = true; + } + + bool set = false; + if( _warning != nullptr ) { + Glib::ustring missing = check_comma_separated_text(); + if( !missing.empty() ) { + gtk_entry_set_icon_from_icon_name( _entry, + GTK_ENTRY_ICON_SECONDARY, + INKSCAPE_ICON("dialog-warning") ); + // Can't add tooltip until icon set + Glib::ustring warning = _warning; + warning += ": "; + warning += missing; + gtk_entry_set_icon_tooltip_text( _entry, + GTK_ENTRY_ICON_SECONDARY, + warning.c_str() ); + + if( _warning_cb ) { + + // Add callback if we haven't already + if( _warning_cb_id == 0 ) { + _warning_cb_id = + g_signal_connect( G_OBJECT(_entry), + "icon-press", + G_CALLBACK(_warning_cb), + this); + } + // Unblock signal + if( _warning_cb_blocked ) { + g_signal_handler_unblock (G_OBJECT(_entry), + _warning_cb_id ); + _warning_cb_blocked = false; + } + } + set = true; + } + } + + if( !set && _info != nullptr ) { + gtk_entry_set_icon_from_icon_name( GTK_ENTRY(_entry), + GTK_ENTRY_ICON_SECONDARY, + INKSCAPE_ICON("edit-select-all") ); + gtk_entry_set_icon_tooltip_text( _entry, + GTK_ENTRY_ICON_SECONDARY, + _info ); + + if( _info_cb ) { + // Add callback if we haven't already + if( _info_cb_id == 0 ) { + _info_cb_id = + g_signal_connect( G_OBJECT(_entry), + "icon-press", + G_CALLBACK(_info_cb), + this); + } + // Unblock signal + if( _info_cb_blocked ) { + g_signal_handler_unblock (G_OBJECT(_entry), + _info_cb_id ); + _info_cb_blocked = false; + } + } + set = true; + } + + if( !set ) { + gtk_entry_set_icon_from_icon_name( GTK_ENTRY(_entry), + GTK_ENTRY_ICON_SECONDARY, + nullptr ); + } + } + + // Return if active text in list + gboolean found = ( _active != -1 ); + return found; +} + +void +ComboBoxEntryToolItem::set_entry_width(gint entry_width) +{ + _entry_width = entry_width; + + // Clamp to limits + if(entry_width < -1) entry_width = -1; + if(entry_width > 100) entry_width = 100; + + // Widget may not have been created.... + if( _entry ) { + gtk_entry_set_width_chars( GTK_ENTRY(_entry), entry_width ); + } +} + +void +ComboBoxEntryToolItem::set_extra_width( gint extra_width ) +{ + _extra_width = extra_width; + + // Clamp to limits + if(extra_width < -1) extra_width = -1; + if(extra_width > 500) extra_width = 500; + + // Widget may not have been created.... + if( _combobox ) { + GtkRequisition req; + gtk_widget_get_preferred_size(GTK_WIDGET(_combobox), &req, nullptr); + gtk_widget_set_size_request( GTK_WIDGET( _combobox ), req.width + _extra_width, -1 ); + } +} + +void +ComboBoxEntryToolItem::focus_on_click( bool focus_on_click ) +{ + if (_combobox) { + gtk_widget_set_focus_on_click(GTK_WIDGET(_combobox), focus_on_click); + } +} + +void +ComboBoxEntryToolItem::popup_enable() +{ + _popup = true; + + // Widget may not have been created.... + if( _entry ) { + + // Check we don't already have a GtkEntryCompletion + if( _entry_completion ) return; + + _entry_completion = gtk_entry_completion_new(); + + gtk_entry_set_completion( _entry, _entry_completion ); + gtk_entry_completion_set_model( _entry_completion, _model ); + gtk_entry_completion_set_text_column( _entry_completion, 0 ); + gtk_entry_completion_set_popup_completion( _entry_completion, true ); + gtk_entry_completion_set_inline_completion( _entry_completion, false ); + gtk_entry_completion_set_inline_selection( _entry_completion, true ); + + g_signal_connect (G_OBJECT (_entry_completion), "match-selected", G_CALLBACK (match_selected_cb), this); + } +} + +void +ComboBoxEntryToolItem::popup_disable() +{ + _popup = false; + + if( _entry_completion ) { + gtk_widget_destroy(GTK_WIDGET(_entry_completion)); + _entry_completion = nullptr; + } +} + +void +ComboBoxEntryToolItem::set_tooltip(const gchar* tooltip) +{ + set_tooltip_text(tooltip); + gtk_widget_set_tooltip_text ( GTK_WIDGET(_combobox), tooltip); + + // Widget may not have been created.... + if( _entry ) { + gtk_widget_set_tooltip_text ( GTK_WIDGET(_entry), tooltip); + } +} + +void +ComboBoxEntryToolItem::set_info(const gchar* info) +{ + g_free( _info ); + _info = g_strdup( info ); + + // Widget may not have been created.... + if( _entry ) { + gtk_entry_set_icon_tooltip_text( GTK_ENTRY(_entry), + GTK_ENTRY_ICON_SECONDARY, + _info ); + } +} + +void +ComboBoxEntryToolItem::set_info_cb(gpointer info_cb) +{ + _info_cb = info_cb; +} + +void +ComboBoxEntryToolItem::set_warning(const gchar* warning) +{ + g_free( _warning ); + _warning = g_strdup( warning ); + + // Widget may not have been created.... + if( _entry ) { + gtk_entry_set_icon_tooltip_text( GTK_ENTRY(_entry), + GTK_ENTRY_ICON_SECONDARY, + _warning ); + } +} + +void +ComboBoxEntryToolItem::set_warning_cb(gpointer warning_cb) +{ + _warning_cb = warning_cb; +} + +void +ComboBoxEntryToolItem::set_altx_name(const gchar* altx_name) +{ + g_free(_altx_name); + _altx_name = g_strdup( altx_name ); + + // Widget may not have been created.... + if(_entry) { + g_object_set_data( G_OBJECT(_entry), _altx_name, _entry ); + } +} + +// Internal --------------------------------------------------- + +// Return row of active text or -1 if not found. If exclude is true, +// use 3d column if available to exclude row from checking (useful to +// skip rows added for font-families included in doc and not on +// system) +gint +ComboBoxEntryToolItem::get_active_row_from_text(ComboBoxEntryToolItem *action, + const gchar *target_text, + gboolean exclude, + gboolean ignore_case ) +{ + // Check if text in list + gint row = 0; + gboolean found = false; + GtkTreeIter iter; + gboolean valid = gtk_tree_model_get_iter_first( action->_model, &iter ); + while ( valid ) { + + // See if we should exclude a row + gboolean check = true; // If true, font-family is on system. + if( exclude && gtk_tree_model_get_n_columns( action->_model ) > 2 ) { + gtk_tree_model_get( action->_model, &iter, 2, &check, -1 ); + } + + if( check ) { + // Get text from list entry + gchar* text = nullptr; + gtk_tree_model_get( action->_model, &iter, 0, &text, -1 ); // Column 0 + + if( !ignore_case ) { + // Case sensitive compare + if( strcmp( target_text, text ) == 0 ){ + found = true; + g_free(text); + break; + } + } else { + // Case insensitive compare + gchar* target_text_casefolded = g_utf8_casefold( target_text, -1 ); + gchar* text_casefolded = g_utf8_casefold( text, -1 ); + gboolean equal = (strcmp( target_text_casefolded, text_casefolded ) == 0 ); + g_free( text_casefolded ); + g_free( target_text_casefolded ); + if( equal ) { + found = true; + g_free(text); + break; + } + } + g_free(text); + } + + ++row; + valid = gtk_tree_model_iter_next( action->_model, &iter ); + } + + if( !found ) row = -1; + + return row; +} + +// Checks if all comma separated text fragments are in the list and +// returns a ustring with a list of missing fragments. +// This is useful for checking if all fonts in a font-family fallback +// list are available on the system. +// +// This routine could also create a Pango Markup string to show which +// fragments are invalid in the entry box itself. See: +// http://developer.gnome.org/pango/stable/PangoMarkupFormat.html +// However... it appears that while one can retrieve the PangoLayout +// for a GtkEntry box, it is only a copy and changing it has no effect. +// PangoLayout * pl = gtk_entry_get_layout( entry ); +// pango_layout_set_markup( pl, "NEW STRING", -1 ); // DOESN'T WORK +Glib::ustring +ComboBoxEntryToolItem::check_comma_separated_text() +{ + Glib::ustring missing; + + // Parse fallback_list using a comma as deliminator + gchar** tokens = g_strsplit( _text, ",", 0 ); + + gint i = 0; + while( tokens[i] != nullptr ) { + + // Remove any surrounding white space. + g_strstrip( tokens[i] ); + + if( get_active_row_from_text( this, tokens[i], true, true ) == -1 ) { + missing += tokens[i]; + missing += ", "; + } + ++i; + } + g_strfreev( tokens ); + + // Remove extra comma and space from end. + if( missing.size() >= 2 ) { + missing.resize( missing.size()-2 ); + } + return missing; +} + +// Callbacks --------------------------------------------------- + +void +ComboBoxEntryToolItem::combo_box_changed_cb( GtkComboBox* widget, gpointer data ) +{ + // Two things can happen to get here: + // An item is selected in the drop-down menu. + // Text is typed. + // We only react here if an item is selected. + + // Get action + auto action = reinterpret_cast<ComboBoxEntryToolItem *>( data ); + + // Check if item selected: + gint newActive = gtk_combo_box_get_active(widget); + if( newActive >= 0 && newActive != action->_active ) { + + action->_active = newActive; + + GtkTreeIter iter; + if( gtk_combo_box_get_active_iter( GTK_COMBO_BOX( action->_combobox ), &iter ) ) { + + gchar* text = nullptr; + gtk_tree_model_get( action->_model, &iter, 0, &text, -1 ); + gtk_entry_set_text( action->_entry, text ); + + g_free( action->_text ); + action->_text = text; + } + + // Now let the world know + action->_signal_changed.emit(); + } +} + +void +ComboBoxEntryToolItem::entry_activate_cb( GtkEntry *widget, + gpointer data ) +{ + // Get text from entry box.. check if it matches a menu entry. + + // Get action + auto action = reinterpret_cast<ComboBoxEntryToolItem*>( data ); + + // Get text + g_free( action->_text ); + action->_text = g_strdup( gtk_entry_get_text( widget ) ); + + // Get row + action->_active = + get_active_row_from_text( action, action->_text ); + + // Set active row + gtk_combo_box_set_active( GTK_COMBO_BOX( action->_combobox), action->_active ); + + // Now let the world know + action->_signal_changed.emit(); +} + +gboolean +ComboBoxEntryToolItem::match_selected_cb( GtkEntryCompletion* /*widget*/, GtkTreeModel* model, GtkTreeIter* iter, gpointer data ) +{ + // Get action + auto action = reinterpret_cast<ComboBoxEntryToolItem*>(data); + GtkEntry *entry = action->_entry; + + if( entry) { + gchar *family = nullptr; + gtk_tree_model_get(model, iter, 0, &family, -1); + + // Set text in GtkEntry + gtk_entry_set_text (GTK_ENTRY (entry), family ); + + // Set text in ToolItem + g_free( action->_text ); + action->_text = family; + + // Get row + action->_active = + get_active_row_from_text( action, action->_text ); + + // Set active row + gtk_combo_box_set_active( GTK_COMBO_BOX( action->_combobox), action->_active ); + + // Now let the world know + action->_signal_changed.emit(); + + return true; + } + return false; +} + +void +ComboBoxEntryToolItem::defocus() +{ + if ( _focusWidget ) { + gtk_widget_grab_focus( _focusWidget ); + } +} + +gboolean +ComboBoxEntryToolItem::keypress_cb( GtkWidget * /*widget*/, GdkEventKey *event, gpointer data ) +{ + gboolean wasConsumed = FALSE; /* default to report event not consumed */ + guint key = 0; + auto action = reinterpret_cast<ComboBoxEntryToolItem*>(data); + gdk_keymap_translate_keyboard_state( Gdk::Display::get_default()->get_keymap(), + event->hardware_keycode, (GdkModifierType)event->state, + 0, &key, nullptr, nullptr, nullptr ); + + switch ( key ) { + + // TODO Add bindings for Tab/LeftTab + case GDK_KEY_Escape: + { + //gtk_spin_button_set_value( GTK_SPIN_BUTTON(widget), action->private_data->lastVal ); + action->defocus(); + wasConsumed = TRUE; + } + break; + + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + { + action->defocus(); + //wasConsumed = TRUE; + } + break; + + + } + + return wasConsumed; +} + +} +} +} + +/* + 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 : diff --git a/src/ui/widget/combo-box-entry-tool-item.h b/src/ui/widget/combo-box-entry-tool-item.h new file mode 100644 index 0000000..3d6440a --- /dev/null +++ b/src/ui/widget/combo-box-entry-tool-item.h @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A class derived from Gtk::ToolItem that wraps a GtkComboBoxEntry. + * Features: + * Setting GtkEntryBox width in characters. + * Passing a function for formatting cells. + * Displaying a warning if entry text isn't in list. + * Check comma separated values in text against list. (Useful for font-family fallbacks.) + * Setting names for GtkComboBoxEntry and GtkEntry (actionName_combobox, actionName_entry) + * to allow setting resources. + * + * Author(s): + * Tavmjong Bah + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2010 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INK_COMBOBOXENTRY_ACTION +#define SEEN_INK_COMBOBOXENTRY_ACTION + +#include <gtkmm/toolitem.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Creates a Gtk::ToolItem subclass that wraps a Gtk::ComboBox object. + */ +class ComboBoxEntryToolItem : public Gtk::ToolItem { +private: + Glib::ustring _tooltip; + Glib::ustring _label; + GtkTreeModel *_model; ///< Tree Model + GtkComboBox *_combobox; + GtkEntry *_entry; + gint _entry_width;// Width of GtkEntry in characters. + gint _extra_width;// Extra Width of GtkComboBox.. to widen drop-down list in list mode. + gpointer _cell_data_func; // drop-down menu format + gpointer _separator_func; + gboolean _popup; // Do we pop-up an entry-completion dialog? + GtkEntryCompletion *_entry_completion; + GtkWidget *_focusWidget; ///< The widget to return focus to + + GtkWidget *_indicator; + gint _active; // Index of active menu item (-1 if not in list). + gchar *_text; // Text of active menu item or entry box. + gchar *_info; // Text for tooltip info about entry. + gpointer _info_cb; // Callback for clicking info icon. + gint _info_cb_id; + gboolean _info_cb_blocked; + gchar *_warning; // Text for tooltip warning that entry isn't in list. + gpointer _warning_cb; // Callback for clicking warning icon. + gint _warning_cb_id; + gboolean _warning_cb_blocked; + gchar *_altx_name; // Target for Alt-X keyboard shortcut. + + // Signals + sigc::signal<void> _signal_changed; + + void (*changed) (ComboBoxEntryToolItem* action); + void (*activated) (ComboBoxEntryToolItem* action); + + static gint get_active_row_from_text(ComboBoxEntryToolItem *action, + const gchar *target_text, + gboolean exclude = false, + gboolean ignore_case = false); + void defocus(); + + static void combo_box_changed_cb( GtkComboBox* widget, gpointer data ); + static void entry_activate_cb( GtkEntry *widget, + gpointer data ); + static gboolean match_selected_cb( GtkEntryCompletion *widget, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); + static gboolean keypress_cb( GtkWidget *widget, + GdkEventKey *event, + gpointer data ); + + Glib::ustring check_comma_separated_text(); + +public: + ComboBoxEntryToolItem(const Glib::ustring name, + const Glib::ustring label, + const Glib::ustring tooltip, + GtkTreeModel *model, + gint entry_width = -1, + gint extra_width = -1, + gpointer cell_data_func = nullptr, + gpointer separator_func = nullptr, + GtkWidget* focusWidget = nullptr); + + gchar* get_active_text(); + gboolean set_active_text(const gchar* text, int row=-1); + + void set_entry_width(gint entry_width); + void set_extra_width(gint extra_width); + + void popup_enable(); + void popup_disable(); + void focus_on_click( bool focus_on_click ); + + void set_info( const gchar* info ); + void set_info_cb( gpointer info_cb ); + void set_warning( const gchar* warning_cb ); + void set_warning_cb(gpointer warning ); + void set_tooltip( const gchar* tooltip ); + + void set_altx_name( const gchar* altx_name ); + + // Accessor methods + decltype(_model) get_model() const {return _model;} + decltype(_combobox) get_combobox() const {return _combobox;} + decltype(_entry) get_entry() const {return _entry;} + decltype(_entry_width) get_entry_width() const {return _entry_width;} + decltype(_extra_width) get_extra_width() const {return _extra_width;} + decltype(_cell_data_func) get_cell_data_func() const {return _cell_data_func;} + decltype(_separator_func) get_separator_func() const {return _separator_func;} + decltype(_popup) get_popup() const {return _popup;} + decltype(_focusWidget) get_focus_widget() const {return _focusWidget;} + + decltype(_active) get_active() const {return _active;} + + decltype(_signal_changed) signal_changed() {return _signal_changed;} + + // Mutator methods + void set_model (decltype(_model) model) {_model = model;} + void set_combobox (decltype(_combobox) combobox) {_combobox = combobox;} + void set_entry (decltype(_entry) entry) {_entry = entry;} + void set_cell_data_func(decltype(_cell_data_func) cell_data_func) {_cell_data_func = cell_data_func;} + void set_separator_func(decltype(_separator_func) separator_func) {_separator_func = separator_func;} + void set_popup (decltype(_popup) popup) {_popup = popup;} + void set_focus_widget (decltype(_focusWidget) focus_widget) {_focusWidget = focus_widget;} + + // This doesn't seem right... surely we should set the active row in the Combobox too? + void set_active (decltype(_active) active) {_active = active;} +}; + +} +} +} +#endif /* SEEN_INK_COMBOBOXENTRY_ACTION */ + +/* + 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 : diff --git a/src/ui/widget/combo-enums.h b/src/ui/widget/combo-enums.h new file mode 100644 index 0000000..d574106 --- /dev/null +++ b/src/ui/widget/combo-enums.h @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Nicholas Bishop <nicholasbishop@gmail.com> + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_COMBO_ENUMS_H +#define INKSCAPE_UI_WIDGET_COMBO_ENUMS_H + +#include "ui/widget/labelled.h" +#include <gtkmm/combobox.h> +#include <gtkmm/liststore.h> +#include "attr-widget.h" +#include "util/enums.h" +#include <glibmm/i18n.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Simplified management of enumerations in the UI as combobox. + */ +template<typename E> class ComboBoxEnum : public Gtk::ComboBox, public AttrWidget +{ +private: + int on_sort_compare( const Gtk::TreeModel::iterator & a, const Gtk::TreeModel::iterator & b) + { + Glib::ustring an=(*a)[_columns.label]; + Glib::ustring bn=(*b)[_columns.label]; + return an.compare(bn); + } + + bool _sort; + +public: + ComboBoxEnum(E default_value, const Util::EnumDataConverter<E>& c, const SPAttributeEnum a = SP_ATTR_INVALID, bool sort = true) + : AttrWidget(a, (unsigned int)default_value), setProgrammatically(false), _converter(c) + { + _sort = sort; + + signal_changed().connect(signal_attr_changed().make_slot()); + gtk_widget_add_events(GTK_WIDGET(gobj()), GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK); + signal_scroll_event().connect(sigc::mem_fun(*this, &ComboBoxEnum<E>::on_scroll_event)); + _model = Gtk::ListStore::create(_columns); + set_model(_model); + + pack_start(_columns.label); + + // Initialize list + for(int i = 0; i < static_cast<int>(_converter._length); ++i) { + Gtk::TreeModel::Row row = *_model->append(); + const Util::EnumData<E>* data = &_converter.data(i); + row[_columns.data] = data; + row[_columns.label] = _( _converter.get_label(data->id).c_str() ); + } + set_active_by_id(default_value); + + // Sort the list + if (sort) { + _model->set_default_sort_func(sigc::mem_fun(*this, &ComboBoxEnum<E>::on_sort_compare)); + _model->set_sort_column(_columns.label, Gtk::SORT_ASCENDING); + } + } + + ComboBoxEnum(const Util::EnumDataConverter<E>& c, const SPAttributeEnum a = SP_ATTR_INVALID, bool sort = true) + : AttrWidget(a, (unsigned int) 0), setProgrammatically(false), _converter(c) + { + _sort = sort; + + signal_changed().connect(signal_attr_changed().make_slot()); + gtk_widget_add_events(GTK_WIDGET(gobj()), GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK); + signal_scroll_event().connect(sigc::mem_fun(*this, &ComboBoxEnum<E>::on_scroll_event)); + + _model = Gtk::ListStore::create(_columns); + set_model(_model); + + pack_start(_columns.label); + + // Initialize list + for(unsigned int i = 0; i < _converter._length; ++i) { + Gtk::TreeModel::Row row = *_model->append(); + const Util::EnumData<E>* data = &_converter.data(i); + row[_columns.data] = data; + row[_columns.label] = _( _converter.get_label(data->id).c_str() ); + } + set_active(0); + + // Sort the list + if (_sort) { + _model->set_default_sort_func(sigc::mem_fun(*this, &ComboBoxEnum<E>::on_sort_compare)); + _model->set_sort_column(_columns.label, Gtk::SORT_ASCENDING); + } + } + + Glib::ustring get_as_attribute() const override + { + return get_active_data()->key; + } + + void set_from_attribute(SPObject* o) override + { + setProgrammatically = true; + const gchar* val = attribute_value(o); + if(val) + set_active_by_id(_converter.get_id_from_key(val)); + else + set_active(get_default()->as_uint()); + } + + const Util::EnumData<E>* get_active_data() const + { + Gtk::TreeModel::iterator i = this->get_active(); + if(i) + return (*i)[_columns.data]; + return nullptr; + } + + void add_row(const Glib::ustring& s) + { + Gtk::TreeModel::Row row = *_model->append(); + row[_columns.data] = 0; + row[_columns.label] = s; + } + + void remove_row(E id) { + Gtk::TreeModel::iterator i; + + for(i = _model->children().begin(); i != _model->children().end(); ++i) { + const Util::EnumData<E>* data = (*i)[_columns.data]; + + if(data->id == id) + break; + } + + if(i != _model->children().end()) + _model->erase(i); + } + + void set_active_by_id(E id) { + setProgrammatically = true; + for(Gtk::TreeModel::iterator i = _model->children().begin(); + i != _model->children().end(); ++i) + { + const Util::EnumData<E>* data = (*i)[_columns.data]; + if(data->id == id) { + set_active(i); + break; + } + } + }; + + bool on_scroll_event(GdkEventScroll *event) override { return false; } + + void set_active_by_key(const Glib::ustring& key) { + setProgrammatically = true; + set_active_by_id( _converter.get_id_from_key(key) ); + }; + + bool setProgrammatically; + +private: + class Columns : public Gtk::TreeModel::ColumnRecord + { + public: + Columns() + { + add(data); + add(label); + } + + Gtk::TreeModelColumn<const Util::EnumData<E>*> data; + Gtk::TreeModelColumn<Glib::ustring> label; + }; + + Columns _columns; + Glib::RefPtr<Gtk::ListStore> _model; + const Util::EnumDataConverter<E>& _converter; +}; + + +/** + * Simplified management of enumerations in the UI as combobox. + */ +template<typename E> class LabelledComboBoxEnum : public Labelled +{ +public: + LabelledComboBoxEnum( Glib::ustring const &label, + Glib::ustring const &tooltip, + const Util::EnumDataConverter<E>& c, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true, + bool sorted = true) + : Labelled(label, tooltip, new ComboBoxEnum<E>(c, SP_ATTR_INVALID, sorted), suffix, icon, mnemonic) + { + } + + ComboBoxEnum<E>* getCombobox() { + return static_cast< ComboBoxEnum<E>* > (_widget); + } +}; + +} +} +} + +#endif + +/* + 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 : diff --git a/src/ui/widget/combo-tool-item.cpp b/src/ui/widget/combo-tool-item.cpp new file mode 100644 index 0000000..ffc7e75 --- /dev/null +++ b/src/ui/widget/combo-tool-item.cpp @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2017 Tavmjong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + + +/** \file + A combobox that can be displayed in a toolbar. +*/ + +#include "combo-tool-item.h" +#include "preferences.h" +#include <iostream> +#include <utility> +#include <gtkmm/toolitem.h> +#include <gtkmm/menuitem.h> +#include <gtkmm/radiomenuitem.h> +#include <gtkmm/combobox.h> +#include <gtkmm/menu.h> +#include <gtkmm/box.h> +#include <gtkmm/label.h> +#include <gtkmm/image.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +ComboToolItem* +ComboToolItem::create(const Glib::ustring &group_label, + const Glib::ustring &tooltip, + const Glib::ustring &stock_id, + Glib::RefPtr<Gtk::ListStore> store, + bool has_entry) +{ + return new ComboToolItem(group_label, tooltip, stock_id, store, has_entry); +} + +ComboToolItem::ComboToolItem(Glib::ustring group_label, + Glib::ustring tooltip, + Glib::ustring stock_id, + Glib::RefPtr<Gtk::ListStore> store, + bool has_entry) : + _active(-1), + _group_label(std::move( group_label )), + _tooltip(std::move( tooltip )), + _stock_id(std::move( stock_id )), + _store (std::move(store)), + _use_label (true), + _use_icon (false), + _use_pixbuf (true), + _icon_size ( Gtk::ICON_SIZE_LARGE_TOOLBAR ), + _combobox (nullptr), + _group_label_widget(nullptr), + _container(Gtk::manage(new Gtk::Box())), + _menuitem (nullptr) +{ + add(*_container); + _container->set_spacing(3); + + // ": " is added to the group label later + if (!_group_label.empty()) { + // we don't expect trailing spaces + // g_assert(_group_label.raw()[_group_label.raw().size() - 1] != ' '); + + // strip space (note: raw() indexing is much cheaper on Glib::ustring) + if (_group_label.raw()[_group_label.raw().size() - 1] == ' ') { + _group_label.resize(_group_label.size() - 1); + } + } + if (!_group_label.empty()) { + // we don't expect a trailing colon + // g_assert(_group_label.raw()[_group_label.raw().size() - 1] != ':'); + + // strip colon (note: raw() indexing is much cheaper on Glib::ustring) + if (_group_label.raw()[_group_label.raw().size() - 1] == ':') { + _group_label.resize(_group_label.size() - 1); + } + } + + + // Create combobox + _combobox = Gtk::manage (new Gtk::ComboBox(has_entry)); + _combobox->set_model(_store); + + populate_combobox(); + + _combobox->signal_changed().connect( + sigc::mem_fun(*this, &ComboToolItem::on_changed_combobox)); + _container->pack_start(*_combobox); + + show_all(); +} + +void +ComboToolItem::focus_on_click( bool focus_on_click ) +{ + _combobox->set_focus_on_click(focus_on_click); +} + + +void +ComboToolItem::use_label(bool use_label) +{ + _use_label = use_label; + populate_combobox(); +} + +void +ComboToolItem::use_icon(bool use_icon) +{ + _use_icon = use_icon; + populate_combobox(); +} + +void +ComboToolItem::use_pixbuf(bool use_pixbuf) +{ + _use_pixbuf = use_pixbuf; + populate_combobox(); +} + +void +ComboToolItem::use_group_label(bool use_group_label) +{ + if (use_group_label == (_group_label_widget != nullptr)) { + return; + } + if (use_group_label) { + _container->remove(*_combobox); + _group_label_widget = Gtk::manage(new Gtk::Label(_group_label + ": ")); + _container->pack_start(*_group_label_widget); + _container->pack_start(*_combobox); + } else { + _container->remove(*_group_label_widget); + delete _group_label_widget; + _group_label_widget = nullptr; + } +} + +void +ComboToolItem::populate_combobox() +{ + _combobox->clear(); + + ComboToolItemColumns columns; + if (_use_icon) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/theme/symbolicIcons", false)) { + auto children = _store->children(); + for (auto row : children) { + Glib::ustring icon = row[columns.col_icon]; + gint pos = icon.find("-symbolic"); + if (pos == std::string::npos) { + icon += "-symbolic"; + } + row[columns.col_icon] = icon; + } + } + Gtk::CellRendererPixbuf *renderer = new Gtk::CellRendererPixbuf; + renderer->set_property ("stock_size", Gtk::ICON_SIZE_LARGE_TOOLBAR); + _combobox->pack_start (*Gtk::manage(renderer), false); + _combobox->add_attribute (*renderer, "icon_name", columns.col_icon ); + } else if (_use_pixbuf) { + Gtk::CellRendererPixbuf *renderer = new Gtk::CellRendererPixbuf; + //renderer->set_property ("stock_size", Gtk::ICON_SIZE_LARGE_TOOLBAR); + _combobox->pack_start (*Gtk::manage(renderer), false); + _combobox->add_attribute (*renderer, "pixbuf", columns.col_pixbuf ); + } + + if (_use_label) { + _combobox->pack_start(columns.col_label); + } + + std::vector<Gtk::CellRenderer*> cells = _combobox->get_cells(); + for (auto & cell : cells) { + _combobox->add_attribute (*cell, "sensitive", columns.col_sensitive); + } + + set_tooltip_text(_tooltip); + _combobox->set_tooltip_text(_tooltip); + _combobox->set_active (_active); +} + +void +ComboToolItem::set_active (gint active) { + if (_active != active) { + + _active = active; + + if (_combobox) { + _combobox->set_active (active); + } + + if (active < _radiomenuitems.size()) { + _radiomenuitems[ active ]->set_active(); + } + } +} + +Glib::ustring +ComboToolItem::get_active_text () { + Gtk::TreeModel::Row row = _store->children()[_active]; + ComboToolItemColumns columns; + Glib::ustring label = row[columns.col_label]; + return label; +} + +bool +ComboToolItem::on_create_menu_proxy() +{ + if (_menuitem == nullptr) { + + _menuitem = Gtk::manage (new Gtk::MenuItem(_group_label)); + Gtk::Menu *menu = Gtk::manage (new Gtk::Menu); + + Gtk::RadioButton::Group group; + int index = 0; + auto children = _store->children(); + for (auto row : children) { + ComboToolItemColumns columns; + Glib::ustring label = row[columns.col_label ]; + Glib::ustring icon = row[columns.col_icon ]; + Glib::ustring tooltip = row[columns.col_tooltip ]; + bool sensitive = row[columns.col_sensitive ]; + + Gtk::RadioMenuItem* button = Gtk::manage(new Gtk::RadioMenuItem(group)); + button->set_label (label); + button->set_tooltip_text( tooltip ); + button->set_sensitive( sensitive ); + + button->signal_toggled().connect( sigc::bind<0>( + sigc::mem_fun(*this, &ComboToolItem::on_toggled_radiomenu), index++) + ); + + menu->add (*button); + + _radiomenuitems.push_back( button ); + } + + if ( _active < _radiomenuitems.size()) { + _radiomenuitems[ _active ]->set_active(); + } + + _menuitem->set_submenu (*menu); + _menuitem->show_all(); + } + + set_proxy_menu_item(_group_label, *_menuitem); + return true; +} + +void +ComboToolItem::on_changed_combobox() { + + int row = _combobox->get_active_row_number(); + set_active( row ); + _changed.emit (_active); + _changed_after.emit (_active); +} + +void +ComboToolItem::on_toggled_radiomenu(int n) { + + // toggled emitted twice, first for button toggled off, second for button toggled on. + // We want to react only to the button turned on. + if ( n < _radiomenuitems.size() &&_radiomenuitems[ n ]->get_active()) { + set_active ( n ); + _changed.emit (_active); + _changed_after.emit (_active); + } +} + +} +} +} +/* + 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 : diff --git a/src/ui/widget/combo-tool-item.h b/src/ui/widget/combo-tool-item.h new file mode 100644 index 0000000..1fc8b00 --- /dev/null +++ b/src/ui/widget/combo-tool-item.h @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_COMBO_TOOL_ITEM +#define SEEN_COMBO_TOOL_ITEM + +/* + * Authors: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2017 Tavmjong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +/** + A combobox that can be displayed in a toolbar +*/ + +#include <gtkmm/toolitem.h> +#include <gtkmm/liststore.h> +#include <sigc++/sigc++.h> +#include <vector> + +namespace Gtk { +class Box; +class ComboBox; +class Label; +class MenuItem; +class RadioMenuItem; +} + +namespace Inkscape { +namespace UI { +namespace Widget { +class ComboToolItemColumns : public Gtk::TreeModel::ColumnRecord { +public: + ComboToolItemColumns() { + add (col_label); + add (col_value); + add (col_icon); + add (col_pixbuf); + add (col_data); // Used to store a pointer + add (col_tooltip); + add (col_sensitive); + } + Gtk::TreeModelColumn<Glib::ustring> col_label; + Gtk::TreeModelColumn<Glib::ustring> col_value; + Gtk::TreeModelColumn<Glib::ustring> col_icon; + Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > col_pixbuf; + Gtk::TreeModelColumn<void *> col_data; + Gtk::TreeModelColumn<Glib::ustring> col_tooltip; + Gtk::TreeModelColumn<bool> col_sensitive; +}; + + +class ComboToolItem : public Gtk::ToolItem { + +public: + static ComboToolItem* create(const Glib::ustring &label, + const Glib::ustring &tooltip, + const Glib::ustring &stock_id, + Glib::RefPtr<Gtk::ListStore> store, + bool has_entry = false); + + /* Style of combobox */ + void use_label( bool use_label ); + void use_icon( bool use_icon ); + void focus_on_click( bool focus_on_click ); + void use_pixbuf( bool use_pixbuf ); + void use_group_label( bool use_group_label ); // Applies to tool item only + + gint get_active() { return _active; } + Glib::ustring get_active_text(); + void set_active( gint active ); + void set_icon_size( Gtk::BuiltinIconSize size ) { _icon_size = size; } + + Glib::RefPtr<Gtk::ListStore> get_store() { return _store; } + + sigc::signal<void, int> signal_changed() { return _changed; } + sigc::signal<void, int> signal_changed_after() { return _changed_after; } + +protected: + bool on_create_menu_proxy() override; + void populate_combobox(); + + /* Signals */ + sigc::signal<void, int> _changed; + sigc::signal<void, int> _changed_after; // Needed for unit tracker which eats _changed. + +private: + + Glib::ustring _group_label; + Glib::ustring _tooltip; + Glib::ustring _stock_id; + Glib::RefPtr<Gtk::ListStore> _store; + + gint _active; /* Active menu item/button */ + + /* Style */ + bool _use_label; + bool _use_icon; // Applies to menu item only + bool _use_pixbuf; + Gtk::BuiltinIconSize _icon_size; + + /* Combobox in tool */ + Gtk::ComboBox* _combobox; + Gtk::Label* _group_label_widget; + Gtk::Box* _container; + + Gtk::MenuItem* _menuitem; + std::vector<Gtk::RadioMenuItem*> _radiomenuitems; + + /* Internal Callbacks */ + void on_changed_combobox(); + void on_toggled_radiomenu(int n); + + ComboToolItem(Glib::ustring group_label, + Glib::ustring tooltip, + Glib::ustring stock_id, + Glib::RefPtr<Gtk::ListStore> store, + bool has_entry = false); +}; +} +} +} +#endif /* SEEN_COMBO_TOOL_ITEM */ + +/* + 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 : diff --git a/src/ui/widget/dash-selector.cpp b/src/ui/widget/dash-selector.cpp new file mode 100644 index 0000000..897b964 --- /dev/null +++ b/src/ui/widget/dash-selector.cpp @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Combobox for selecting dash patterns - implementation. + */ +/* Author: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Maximilian Albert <maximilian.albert@gmail.com> + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "dash-selector.h" + +#include <cstring> + +#include <glibmm/i18n.h> + +#include <2geom/coord.h> + +#include "preferences.h" + +#include "display/cairo-utils.h" + +#include "style.h" + +#include "ui/dialog-events.h" +#include "ui/widget/spinbutton.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +gchar const *const DashSelector::_prefs_path = "/palette/dashes"; + +static double dash_0[] = {-1.0}; +static double dash_1_1[] = {1.0, 1.0, -1.0}; +static double dash_2_1[] = {2.0, 1.0, -1.0}; +static double dash_4_1[] = {4.0, 1.0, -1.0}; +static double dash_1_2[] = {1.0, 2.0, -1.0}; +static double dash_1_4[] = {1.0, 4.0, -1.0}; + +static size_t BD_LEN = 7; // must correspond to the number of entries in the next line +static double *builtin_dashes[] = {dash_0, dash_1_1, dash_2_1, dash_4_1, dash_1_2, dash_1_4, nullptr}; + +static double **dashes = nullptr; + +DashSelector::DashSelector() + : preview_width(80), + preview_height(16), + preview_lineheight(2) +{ + set_spacing(4); + + // TODO: find something more sensible here!! + init_dashes(); + + dash_store = Gtk::ListStore::create(dash_columns); + dash_combo.set_model(dash_store); + dash_combo.pack_start(image_renderer); + dash_combo.set_cell_data_func(image_renderer, sigc::mem_fun(*this, &DashSelector::prepareImageRenderer)); + dash_combo.set_tooltip_text(_("Dash pattern")); + dash_combo.get_style_context()->add_class("combobright"); + dash_combo.show(); + dash_combo.signal_changed().connect( sigc::mem_fun(*this, &DashSelector::on_selection) ); + + this->pack_start(dash_combo, true, true, 0); + + offset = Gtk::Adjustment::create(0.0, 0.0, 10.0, 0.1, 1.0, 0.0); + offset->signal_value_changed().connect(sigc::mem_fun(*this, &DashSelector::offset_value_changed)); + auto sb = new Inkscape::UI::Widget::SpinButton(offset, 0.1, 2); + sb->set_tooltip_text(_("Pattern offset")); + sp_dialog_defocus_on_enter_cpp(sb); + sb->show(); + + this->pack_start(*sb, false, false, 0); + + int np=0; + while (dashes[np]){ np++;} + for (int i = 0; i<np-1; i++) { // all but the custom one go this way + // Add the dashes to the combobox + Gtk::TreeModel::Row row = *(dash_store->append()); + row[dash_columns.dash] = dashes[i]; + row[dash_columns.pixbuf] = Glib::wrap(sp_dash_to_pixbuf(dashes[i])); + } + // add the custom one + Gtk::TreeModel::Row row = *(dash_store->append()); + row[dash_columns.dash] = dashes[np-1]; + row[dash_columns.pixbuf] = Glib::wrap(sp_text_to_pixbuf((char *)"Custom")); + + this->set_data("pattern", dashes[0]); +} + +DashSelector::~DashSelector() { + // FIXME: for some reason this doesn't get called; does the call to manage() in + // sp_stroke_style_line_widget_new() not processed correctly? +} + +void DashSelector::prepareImageRenderer( Gtk::TreeModel::const_iterator const &row ) { + + Glib::RefPtr<Gdk::Pixbuf> pixbuf = (*row)[dash_columns.pixbuf]; + image_renderer.property_pixbuf() = pixbuf; +} + +void DashSelector::init_dashes() { + + if (!dashes) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + std::vector<Glib::ustring> dash_prefs = prefs->getAllDirs(_prefs_path); + + int pos = 0; + if (!dash_prefs.empty()) { + SPStyle style; + dashes = g_new (double *, dash_prefs.size() + 2); // +1 for custom slot, +1 for terminator slot + + for (auto & dash_pref : dash_prefs) { + style.readFromPrefs( dash_pref ); + + if (!style.stroke_dasharray.values.empty()) { + dashes[pos] = g_new (double, style.stroke_dasharray.values.size() + 1); + double *d = dashes[pos]; + unsigned i = 0; + for (; i < style.stroke_dasharray.values.size(); i++) { + d[i] = style.stroke_dasharray.values[i].value; + } + d[i] = -1; + } else { + dashes[pos] = dash_0; + } + pos += 1; + } + } else { // This code may never execute - a new preferences.xml is created for a new user. Maybe if the user deletes dashes from preferences.xml? + dashes = g_new (double *, BD_LEN + 2); // +1 for custom slot, +1 for terminator slot + unsigned i; + for(i=0;i<BD_LEN;i++) { + dashes[i] = builtin_dashes[i]; + } + pos = BD_LEN; + } + // make a place to hold the custom dashes, up to 15 positions long (+ terminator) + dashes[pos] = g_new (double, 16); + double *d = dashes[pos]; + int i=0; + for(i=0;i<15;i++){ d[i]=i; } // have to put something in there, this is a pattern hopefully nobody would choose + d[15]=-1.0; + // final terminator + dashes[++pos] = nullptr; + } +} + +void DashSelector::set_dash (int ndash, double *dash, double o) +{ + int pos = -1; // Allows custom patterns to remain unscathed by this. + int count = 0; // will hold the NULL terminator at the end of the dashes list + if (ndash > 0) { + double delta = 0.0; + for (int i = 0; i < ndash; i++) + delta += dash[i]; + delta /= 1000.0; + + for (int i = 0; dashes[i]; i++,count++) { + double *pattern = dashes[i]; + int np = 0; + while (pattern[np] >= 0.0) + np += 1; + if (np == ndash) { + int j; + for (j = 0; j < ndash; j++) { + if (!Geom::are_near(dash[j], pattern[j], delta)) { + break; + } + } + if (j == ndash) { + pos = i; + break; + } + } + } + } + else if(ndash==0) { + pos = 0; + } + if(pos>=0){ + this->set_data("pattern", dashes[pos]); + this->dash_combo.set_active(pos); + this->offset->set_value(o); + if(pos == 10) { + this->offset->set_value(10.0); + } + } + else { // Hit a custom pattern in the SVG, write it into the combobox. + count--; // the one slot for custom patterns + double *d = dashes[count]; + int i=0; + for(i=0;i< (ndash > 15 ? 15 : ndash) ;i++) { + d[i]=dash[i]; + } // store the custom pattern + d[ndash]=-1.0; //terminate it + this->set_data("pattern", dashes[count]); + this->dash_combo.set_active(count); + this->offset->set_value(o); // what does this do???? + } +} + +void DashSelector::get_dash(int *ndash, double **dash, double *off) +{ + double *pattern = (double*) this->get_data("pattern"); + + int nd = 0; + while (pattern[nd] >= 0.0) + nd += 1; + + if (nd > 0) { + if (ndash) + *ndash = nd; + if (dash) { + *dash = g_new (double, nd); + memcpy (*dash, pattern, nd * sizeof (double)); + } + if (off) + *off = offset->get_value(); + } else { + if (ndash) + *ndash = 0; + if (dash) + *dash = nullptr; + if (off) + *off = 0.0; + } +} + +/** + * Fill a pixbuf with the dash pattern using standard cairo drawing + */ +GdkPixbuf* DashSelector::sp_dash_to_pixbuf(double *pattern) +{ + int n_dashes; + for (n_dashes = 0; pattern[n_dashes] >= 0.0; n_dashes ++) ; + + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, preview_width, preview_height); + cairo_t *ct = cairo_create(s); + + cairo_set_line_width (ct, preview_lineheight); + cairo_scale (ct, preview_lineheight, 1); + //cairo_set_source_rgb (ct, 0, 0, 0); + cairo_move_to (ct, 0, preview_height/2); + cairo_line_to (ct, preview_width, preview_height/2); + cairo_set_dash(ct, pattern, n_dashes, 0); + cairo_stroke (ct); + + cairo_destroy(ct); + cairo_surface_flush(s); + + GdkPixbuf* pixbuf = ink_pixbuf_create_from_cairo_surface(s); + return pixbuf; +} + +/** + * Fill a pixbuf with a text label using standard cairo drawing + */ +GdkPixbuf* DashSelector::sp_text_to_pixbuf(char *text) +{ + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, preview_width, preview_height); + cairo_t *ct = cairo_create(s); + + cairo_select_font_face (ct, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size (ct, 12.0); + cairo_set_source_rgb (ct, 0.0, 0.0, 0.0); + cairo_move_to (ct, 16.0, 13.0); + cairo_show_text (ct, text); + + cairo_stroke (ct); + + cairo_destroy(ct); + cairo_surface_flush(s); + + GdkPixbuf* pixbuf = ink_pixbuf_create_from_cairo_surface(s); + return pixbuf; +} + +void DashSelector::on_selection () +{ + double *pattern = dash_combo.get_active()->get_value(dash_columns.dash); + this->set_data ("pattern", pattern); + + changed_signal.emit(); +} + +void DashSelector::offset_value_changed() +{ + changed_signal.emit(); +} +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/dash-selector.h b/src/ui/widget/dash-selector.h new file mode 100644 index 0000000..449392a --- /dev/null +++ b/src/ui/widget/dash-selector.h @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_SP_DASH_SELECTOR_NEW_H +#define SEEN_SP_DASH_SELECTOR_NEW_H + +/* Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Maximilian Albert <maximilian.albert> (gtkmm-ification) + * + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/box.h> +#include <gtkmm/combobox.h> +#include <gtkmm/liststore.h> + +#include <sigc++/signal.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Class that wraps a combobox and spinbutton for selecting dash patterns. + */ +class DashSelector : public Gtk::HBox { +public: + DashSelector(); + ~DashSelector() override; + + /** + * Get and set methods for dashes + */ + void set_dash(int ndash, double *dash, double offset); + void get_dash(int *ndash, double **dash, double *offset); + + sigc::signal<void> changed_signal; + +private: + + /** + * Initialize dashes list from preferences + */ + static void init_dashes(); + + /** + * Fill a pixbuf with the dash pattern using standard cairo drawing + */ + GdkPixbuf* sp_dash_to_pixbuf(double *pattern); + + /** + * Fill a pixbuf with text standard cairo drawing + */ + GdkPixbuf* sp_text_to_pixbuf(char *text); + + /** + * Callback for combobox image renderer + */ + void prepareImageRenderer( Gtk::TreeModel::const_iterator const &row ); + + /** + * Callback for offset adjustment changing + */ + void offset_value_changed(); + + /** + * Callback for combobox selection changing + */ + void on_selection(); + + /** + * Combobox columns + */ + class DashColumns : public Gtk::TreeModel::ColumnRecord { + public: + Gtk::TreeModelColumn<double *> dash; + Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > pixbuf; + + DashColumns() { + add(dash); add(pixbuf); + } + }; + DashColumns dash_columns; + Glib::RefPtr<Gtk::ListStore> dash_store; + Gtk::ComboBox dash_combo; + Gtk::CellRendererPixbuf image_renderer; + Glib::RefPtr<Gtk::Adjustment> offset; + + static gchar const *const _prefs_path; + int preview_width; + int preview_height; + int preview_lineheight; + +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // SEEN_SP_DASH_SELECTOR_NEW_H + +/* + 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 : diff --git a/src/ui/widget/dock-item.cpp b/src/ui/widget/dock-item.cpp new file mode 100644 index 0000000..01786d5 --- /dev/null +++ b/src/ui/widget/dock-item.cpp @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Gustav Broberg <broberg@kth.se> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/widget/dock.h" + +#include "desktop.h" +#include "inkscape.h" +#include "ui/icon-loader.h" +#include "ui/icon-names.h" +#include <glibmm/exceptionhandler.h> +#include <gtkmm/icontheme.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +DockItem::DockItem(Dock& dock, const Glib::ustring& name, const Glib::ustring& long_name, + const Glib::ustring& icon_name, State state, GdlDockPlacement placement) : + _dock(dock), + _prev_state(state), + _prev_position(0), + _window(nullptr), + _x(0), + _y(0), + _grab_focus_on_realize(false), + _gdl_dock_item(nullptr) +{ + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + GdlDockItemBehavior gdl_dock_behavior = + (prefs->getBool("/options/dock/cancenterdock", true) ? + GDL_DOCK_ITEM_BEH_NORMAL : + GDL_DOCK_ITEM_BEH_CANT_DOCK_CENTER); + + + if (!icon_name.empty()) { + _icon_pixbuf = sp_get_icon_pixbuf(icon_name, "/toolbox/secondary"); + } + + if ( _icon_pixbuf ) { + _gdl_dock_item = gdl_dock_item_new_with_pixbuf_icon( name.c_str(), long_name.c_str(), + _icon_pixbuf->gobj(), gdl_dock_behavior ); + } else { + _gdl_dock_item = gdl_dock_item_new(name.c_str(), long_name.c_str(), gdl_dock_behavior); + } + + _frame.set_shadow_type(Gtk::SHADOW_IN); + gtk_container_add (GTK_CONTAINER (_gdl_dock_item), GTK_WIDGET (_frame.gobj())); + _frame.add(_dock_item_box); + _dock_item_box.set_border_width(3); + + signal_drag_begin().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onDragBegin)); + signal_drag_end().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onDragEnd)); + signal_hide().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onHide), false); + signal_show().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onShow), false); + signal_state_changed().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onStateChanged)); + signal_delete_event().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onDeleteEvent)); + signal_realize().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onRealize)); + + _dock.addItem(*this, ( _prev_state == FLOATING_STATE || _prev_state == ICONIFIED_FLOATING_STATE ) ? GDL_DOCK_FLOATING : placement); + + if (_prev_state == ICONIFIED_FLOATING_STATE || _prev_state == ICONIFIED_DOCKED_STATE) { + iconify(); + } + + show_all(); + +} + +DockItem::~DockItem() +{ + g_free(_gdl_dock_item); +} + +Gtk::Widget& +DockItem::getWidget() +{ + return *Glib::wrap(GTK_WIDGET(_gdl_dock_item)); +} + +GtkWidget * +DockItem::gobj() +{ + return _gdl_dock_item; +} + +Gtk::VBox * +DockItem::get_vbox() +{ + return &_dock_item_box; +} + + +void +DockItem::get_position(int& x, int& y) +{ + if (getWindow()) { + getWindow()->get_position(x, y); + } else { + x = _x; + y = _y; + } +} + +void +DockItem::get_size(int& width, int& height) +{ + if (getWindow()) { + getWindow()->get_size(width, height); + } else { + width = get_vbox()->get_width(); + height = get_vbox()->get_height(); + } +} + + +void +DockItem::resize(int width, int height) +{ + if (_window) + _window->resize(width, height); +} + + +void +DockItem::move(int x, int y) +{ + if (_window) + _window->move(x, y); +} + +void +DockItem::set_position(Gtk::WindowPosition position) +{ + if (_window) + _window->set_position(position); +} + +void +DockItem::set_size_request(int width, int height) +{ + getWidget().set_size_request(width, height); +} + +void DockItem::size_request(Gtk::Requisition& requisition) +{ + Gtk::Requisition req_natural; + getWidget().get_preferred_size(req_natural, requisition); +} + +void +DockItem::set_title(Glib::ustring title) +{ + g_object_set (_gdl_dock_item, + "long-name", title.c_str(), + NULL); + + gdl_dock_item_set_tablabel(GDL_DOCK_ITEM(_gdl_dock_item), + gtk_label_new (title.c_str())); +} + +bool +DockItem::isAttached() const +{ + return GDL_DOCK_OBJECT_ATTACHED (_gdl_dock_item); +} + + +bool +DockItem::isFloating() const +{ + return (GTK_WIDGET(gdl_dock_object_get_toplevel(GDL_DOCK_OBJECT (_gdl_dock_item))) != + _dock.getGdlWidget()); +} + +bool +DockItem::isIconified() const +{ + return GDL_DOCK_ITEM_ICONIFIED (_gdl_dock_item); +} + +DockItem::State +DockItem::getState() const +{ + if (isIconified() && _prev_state == FLOATING_STATE) { + return ICONIFIED_FLOATING_STATE; + } else if (isIconified()) { + return ICONIFIED_DOCKED_STATE; + } else if (isFloating() && isAttached()) { + return FLOATING_STATE; + } else if (isAttached()) { + return DOCKED_STATE; + } + + return UNATTACHED; +} + +DockItem::State +DockItem::getPrevState() const +{ + return _prev_state; +} + +GdlDockPlacement +DockItem::getPlacement() const +{ + GdlDockPlacement placement = GDL_DOCK_TOP; + GdlDockObject *parent = gdl_dock_object_get_parent_object (GDL_DOCK_OBJECT(_gdl_dock_item)); + if (parent) { + gdl_dock_object_child_placement(parent, GDL_DOCK_OBJECT(_gdl_dock_item), &placement); + } + + return placement; +} + +void +DockItem::hide() +{ + gdl_dock_item_hide_item (GDL_DOCK_ITEM(_gdl_dock_item)); +} + +void +DockItem::show() +{ + gdl_dock_item_show_item (GDL_DOCK_ITEM(_gdl_dock_item)); +} + +void +DockItem::iconify() +{ + gdl_dock_item_iconify_item (GDL_DOCK_ITEM(_gdl_dock_item)); +} + +void +DockItem::show_all() +{ + gtk_widget_show_all(_gdl_dock_item); +} + +void +DockItem::present() +{ + gdl_dock_object_present(GDL_DOCK_OBJECT(_gdl_dock_item), nullptr); + + // always grab focus, even if we're already present + grab_focus(); + + if (!isFloating() && getWidget().get_realized()) + _dock.scrollToItem(*this); +} + + +void +DockItem::grab_focus() +{ + if (gtk_widget_get_realized (_gdl_dock_item)) { + + // make sure the window we're in is present + Gtk::Widget *toplevel = getWidget().get_toplevel(); + if (Gtk::Window *window = dynamic_cast<Gtk::Window *>(toplevel)) { + window->present(); + } + + gtk_widget_grab_focus (_gdl_dock_item); + + } else { + _grab_focus_on_realize = true; + } +} + + +/* Signal wrappers */ + +Glib::SignalProxy0<void> +DockItem::signal_show() +{ + return Glib::SignalProxy0<void>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)), + &_signal_show_proxy); +} + +Glib::SignalProxy0<void> +DockItem::signal_hide() +{ + return Glib::SignalProxy0<void>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)), + &_signal_hide_proxy); +} + +Glib::SignalProxy1<bool, GdkEventAny *> +DockItem::signal_delete_event() +{ + return Glib::SignalProxy1<bool, GdkEventAny *>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)), + &_signal_delete_event_proxy); +} + +Glib::SignalProxy0<void> +DockItem::signal_drag_begin() +{ + return Glib::SignalProxy0<void>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)), + &_signal_drag_begin_proxy); +} + +Glib::SignalProxy1<void, bool> +DockItem::signal_drag_end() +{ + return Glib::SignalProxy1<void, bool>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)), + &_signal_drag_end_proxy); +} + +Glib::SignalProxy0<void> +DockItem::signal_realize() +{ + return Glib::SignalProxy0<void>(Glib::wrap(GTK_WIDGET(_gdl_dock_item)), + &_signal_realize_proxy); +} + +sigc::signal<void, DockItem::State, DockItem::State> +DockItem::signal_state_changed() +{ + return _signal_state_changed; +} + +void +DockItem::_onHideWindow() +{ + if (_window) + _window->get_position(_x, _y); +} + +void +DockItem::_onHide() +{ + if (_prev_state == ICONIFIED_DOCKED_STATE) + _prev_state = DOCKED_STATE; + else if (_prev_state == ICONIFIED_FLOATING_STATE) + _prev_state = FLOATING_STATE; + + _signal_state_changed.emit(UNATTACHED, getState()); +} + +void +DockItem::_onShow() +{ + _signal_state_changed.emit(UNATTACHED, getState()); +} + +void +DockItem::_onDragBegin() +{ + _prev_state = getState(); + if (_prev_state == FLOATING_STATE) + _dock.toggleDockable(getWidget().get_width(), getWidget().get_height()); +} + +void +DockItem::_onDragEnd(bool) +{ + State state = getState(); + + if (state != _prev_state) + _signal_state_changed.emit(_prev_state, state); + + if (state == FLOATING_STATE) { + if (_prev_state == FLOATING_STATE) + _dock.toggleDockable(); + } + + _prev_state = state; +} + +void +DockItem::_onRealize() +{ + if (_grab_focus_on_realize) { + _grab_focus_on_realize = false; + grab_focus(); + } +} + +bool +DockItem::_onKeyPress(GdkEventKey *event) +{ + gboolean return_value; + g_signal_emit_by_name (_gdl_dock_item, "key_press_event", event, &return_value); + return return_value; +} + +void +DockItem::_onStateChanged(State /*prev_state*/, State new_state) +{ + _window = getWindow(); + if(_window) + _window->set_type_hint(Gdk::WINDOW_TYPE_HINT_NORMAL); + + if (new_state == FLOATING_STATE && _window) { + _window->signal_hide().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onHideWindow)); + _signal_key_press_event_connection = + _window->signal_key_press_event().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::DockItem::_onKeyPress)); + } +} + + +bool +DockItem::_onDeleteEvent(GdkEventAny */*event*/) +{ + hide(); + return false; +} + + +Gtk::Window * +DockItem::getWindow() +{ + g_return_val_if_fail(_gdl_dock_item, 0); + Gtk::Container *parent = getWidget().get_parent(); + parent = (parent ? parent->get_parent() : nullptr); + return (parent ? dynamic_cast<Gtk::Window *>(parent) : nullptr); +} + +const Glib::SignalProxyInfo +DockItem::_signal_show_proxy = +{ + "show", + (GCallback) &Glib::SignalProxyNormal::slot0_void_callback, + (GCallback) &Glib::SignalProxyNormal::slot0_void_callback +}; + +const Glib::SignalProxyInfo +DockItem::_signal_hide_proxy = +{ + "hide", + (GCallback) &Glib::SignalProxyNormal::slot0_void_callback, + (GCallback) &Glib::SignalProxyNormal::slot0_void_callback +}; + + +const Glib::SignalProxyInfo +DockItem::_signal_delete_event_proxy = +{ + "delete_event", + (GCallback) &_signal_delete_event_callback, + (GCallback) &_signal_delete_event_callback +}; + + +const Glib::SignalProxyInfo +DockItem::_signal_drag_begin_proxy = +{ + "dock-drag-begin", + (GCallback) &Glib::SignalProxyNormal::slot0_void_callback, + (GCallback) &Glib::SignalProxyNormal::slot0_void_callback +}; + + +const Glib::SignalProxyInfo +DockItem::_signal_drag_end_proxy = +{ + "dock_drag_end", + (GCallback) &_signal_drag_end_callback, + (GCallback) &_signal_drag_end_callback +}; + + +const Glib::SignalProxyInfo +DockItem::_signal_realize_proxy = +{ + "realize", + (GCallback) &Glib::SignalProxyNormal::slot0_void_callback, + (GCallback) &Glib::SignalProxyNormal::slot0_void_callback +}; + + +gboolean +DockItem::_signal_delete_event_callback(GtkWidget *self, GdkEventAny *event, void *data) +{ + using namespace Gtk; + typedef sigc::slot<bool, GdkEventAny *> SlotType; + + if (Glib::ObjectBase::_get_current_wrapper((GObject *) self)) { + try { + if(sigc::slot_base *const slot = Glib::SignalProxyNormal::data_to_slot(data)) + return static_cast<int>( (*static_cast<SlotType*>(slot))(event) ); + } catch(...) { + Glib::exception_handlers_invoke(); + } + } + + typedef gboolean RType; + return RType(); +} + +void +DockItem::_signal_drag_end_callback(GtkWidget *self, gboolean cancelled, void *data) +{ + using namespace Gtk; + typedef sigc::slot<void, bool> SlotType; + + if (Glib::ObjectBase::_get_current_wrapper((GObject *) self)) { + try { + if(sigc::slot_base *const slot = Glib::SignalProxyNormal::data_to_slot(data)) + (*static_cast<SlotType *>(slot))(cancelled); + } catch(...) { + Glib::exception_handlers_invoke(); + } + } +} + + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/dock-item.h b/src/ui/widget/dock-item.h new file mode 100644 index 0000000..be7ac77 --- /dev/null +++ b/src/ui/widget/dock-item.h @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Gustav Broberg <broberg@kth.se> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + + +#ifndef INKSCAPE_UI_WIGET_DOCK_ITEM_H +#define INKSCAPE_UI_WIGET_DOCK_ITEM_H + +#include <gtkmm/box.h> +#include <gtkmm/frame.h> +#include <gtkmm/window.h> + +#include <gdl/gdl.h> + +namespace Gtk { + class HButtonBox; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +class Dock; + +/** + * A custom wrapper around gdl-dock-item. + */ +class DockItem { + +public: + + enum State { UNATTACHED, // item not bound to the dock (a temporary state) + FLOATING_STATE, // item not in its dock (but can be docked in other, + // e.g. floating, docks) + DOCKED_STATE, // item in its assigned dock + ICONIFIED_DOCKED_STATE, // item iconified in its assigned dock from dock + ICONIFIED_FLOATING_STATE}; // item iconified in its assigned dock from float + + DockItem(Dock& dock, const Glib::ustring& name, const Glib::ustring& long_name, + const Glib::ustring& icon_name, State state, GdlDockPlacement placement); + + ~DockItem(); + + Gtk::Widget& getWidget(); + GtkWidget *gobj(); + + Gtk::VBox *get_vbox(); + + void get_position(int& x, int& y); + void get_size(int& width, int& height); + + void resize(int width, int height); + void move(int x, int y); + void set_position(Gtk::WindowPosition); + void set_size_request(int width, int height); + void size_request(Gtk::Requisition& requisition); + void set_title(Glib::ustring title); + + bool isAttached() const; + bool isFloating() const; + bool isIconified() const; + State getState() const; + State getPrevState() const; + GdlDockPlacement getPlacement() const; + + Gtk::Window *getWindow(); //< gives the parent window, if the dock item has one (i.e. it's floating) + + void hide(); + void show(); + void iconify(); + void show_all(); + + void present(); + + void grab_focus(); + + Glib::SignalProxy0<void> signal_show(); + Glib::SignalProxy0<void> signal_hide(); + Glib::SignalProxy1<bool, GdkEventAny *> signal_delete_event(); + Glib::SignalProxy0<void> signal_drag_begin(); + Glib::SignalProxy1<void, bool> signal_drag_end(); + Glib::SignalProxy0<void> signal_realize(); + + sigc::signal<void, State, State> signal_state_changed(); + +private: + Dock &_dock; //< parent dock + + State _prev_state; //< last known state + + int _prev_position; + + Gtk::Window *_window; //< reference to floating window, if any + int _x, _y; //< last known position of window, if floating + + bool _grab_focus_on_realize; //< if the dock item should grab focus on the next realize + + GtkWidget *_gdl_dock_item; + Glib::RefPtr<Gdk::Pixbuf> _icon_pixbuf; + + /** Interface widgets, will be packed like + * gdl_dock_item -> _frame -> _dock_item_box + */ + Gtk::Frame _frame; + Gtk::VBox _dock_item_box; + + /** Internal signal handlers */ + void _onHide(); + void _onHideWindow(); + void _onShow(); + void _onDragBegin(); + void _onDragEnd(bool cancelled); + void _onRealize(); + + bool _onKeyPress(GdkEventKey *event); + void _onStateChanged(State prev_state, State new_state); + bool _onDeleteEvent(GdkEventAny *event); + + sigc::connection _signal_key_press_event_connection; + + /** GdlDockItem signal proxy structures */ + static const Glib::SignalProxyInfo _signal_show_proxy; + static const Glib::SignalProxyInfo _signal_hide_proxy; + static const Glib::SignalProxyInfo _signal_delete_event_proxy; + + static const Glib::SignalProxyInfo _signal_drag_begin_proxy; + static const Glib::SignalProxyInfo _signal_drag_end_proxy; + static const Glib::SignalProxyInfo _signal_realize_proxy; + + static gboolean _signal_delete_event_callback(GtkWidget *self, GdkEventAny *event, void *data); + static void _signal_drag_end_callback(GtkWidget* self, gboolean p0, void* data); + + sigc::signal<void, State, State> _signal_state_changed; + + DockItem() = delete; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIGET_DOCK_ITEM_H + +/* + 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 : diff --git a/src/ui/widget/dock.cpp b/src/ui/widget/dock.cpp new file mode 100644 index 0000000..9720296 --- /dev/null +++ b/src/ui/widget/dock.cpp @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * A desktop dock pane to dock dialogs. + */ +/* Author: + * Gustav Broberg <broberg@kth.se> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#include "dock.h" +#include "inkscape.h" +#include "preferences.h" +#include "desktop.h" + +#include <gtkmm/adjustment.h> +#include <gtkmm/paned.h> +#include <gtkmm/scrolledwindow.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +namespace { + +void hideCallback(GObject * /*object*/, gpointer dock_ptr) +{ + g_return_if_fail( dock_ptr != nullptr ); + + Dock *dock = static_cast<Dock *>(dock_ptr); + dock->hide(); +} + +void unhideCallback(GObject * /*object*/, gpointer dock_ptr) +{ + g_return_if_fail( dock_ptr != nullptr ); + + Dock *dock = static_cast<Dock *>(dock_ptr); + dock->show(); +} + +} + +const int Dock::_default_empty_width = 0; +const int Dock::_default_dock_bar_width = 36; + + +Dock::Dock(Gtk::Orientation orientation) + : _gdl_dock(gdl_dock_new()), +#if WITH_GDL_3_6 + _gdl_dock_bar(GDL_DOCK_BAR(gdl_dock_bar_new(G_OBJECT(_gdl_dock)))), +#else + _gdl_dock_bar(GDL_DOCK_BAR(gdl_dock_bar_new(GDL_DOCK(_gdl_dock)))), +#endif + _scrolled_window (Gtk::manage(new Gtk::ScrolledWindow)) +{ + gtk_widget_set_name(_gdl_dock, "GdlDock"); + +#if WITH_GDL_3_6 + gtk_orientable_set_orientation(GTK_ORIENTABLE(_gdl_dock_bar), + static_cast<GtkOrientation>(orientation)); +#else + gdl_dock_bar_set_orientation(_gdl_dock_bar, + static_cast<GtkOrientation>(orientation)); +#endif + + _filler.set_name("DockBoxFiller"); + + _paned = Gtk::manage(new Gtk::Paned(orientation)); + _paned->set_name("DockBoxPane"); + _paned->pack1(*Glib::wrap(GTK_WIDGET(_gdl_dock)), false, false); + _paned->pack2(_filler, true, false); + // resize, shrink + + _dock_box = Gtk::manage(new Gtk::Box(orientation == Gtk::ORIENTATION_HORIZONTAL ? + Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL)); + _dock_box->set_name("DockBox"); + _dock_box->pack_start(*_paned, Gtk::PACK_EXPAND_WIDGET); + _dock_box->pack_end(*Gtk::manage(Glib::wrap(GTK_WIDGET(_gdl_dock_bar))), Gtk::PACK_SHRINK); + + _scrolled_window->set_name("DockScrolledWindow"); + _scrolled_window->add(*_dock_box); + _scrolled_window->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + _scrolled_window->set_size_request(0); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + GdlSwitcherStyle gdl_switcher_style = + static_cast<GdlSwitcherStyle>(prefs->getIntLimited("/options/dock/switcherstyle", + GDL_SWITCHER_STYLE_BOTH, 0, 4)); + + GdlDockMaster* master = nullptr; + + g_object_get(GDL_DOCK_OBJECT(_gdl_dock), + "master", &master, + NULL); + + g_object_set(master, + "switcher-style", gdl_switcher_style, + NULL); + + GdlDockBarStyle gdl_dock_bar_style = + static_cast<GdlDockBarStyle>(prefs->getIntLimited("/options/dock/dockbarstyle", + GDL_DOCK_BAR_BOTH, 0, 3)); + + gdl_dock_bar_set_style(_gdl_dock_bar, gdl_dock_bar_style); + + + INKSCAPE.signal_dialogs_hide.connect(sigc::mem_fun(*this, &Dock::hide)); + INKSCAPE.signal_dialogs_unhide.connect(sigc::mem_fun(*this, &Dock::show)); + + g_signal_connect(_paned->gobj(), "button-press-event", G_CALLBACK(_on_paned_button_event), (void *)this); + g_signal_connect(_paned->gobj(), "button-release-event", G_CALLBACK(_on_paned_button_event), (void *)this); + + signal_layout_changed().connect(sigc::mem_fun(*this, &Inkscape::UI::Widget::Dock::_onLayoutChanged)); +} + +Dock::~Dock() +{ + g_free(_gdl_dock); + g_free(_gdl_dock_bar); +} + +void Dock::addItem(DockItem& item, GdlDockPlacement placement) +{ + _dock_items.push_back(&item); + + gdl_dock_add_item(GDL_DOCK(_gdl_dock), + GDL_DOCK_ITEM(item.gobj()), + placement); +} + +Gtk::Widget &Dock::getWidget() +{ + return *_scrolled_window; +} + +Gtk::Paned *Dock::getParentPaned() +{ + g_return_val_if_fail(_dock_box, 0); + Gtk::Container *parent = getWidget().get_parent(); + return (parent != nullptr ? dynamic_cast<Gtk::Paned *>(parent) : nullptr); +} + + +Gtk::Paned *Dock::getPaned() +{ + return _paned; +} + +GtkWidget *Dock::getGdlWidget() +{ + return GTK_WIDGET(_gdl_dock); +} + +bool Dock::isEmpty() const +{ + std::list<const DockItem *>::const_iterator + i = _dock_items.begin(), + e = _dock_items.end(); + + for (; i != e; ++i) { + if ((*i)->getState() == DockItem::DOCKED_STATE) { + return false; + } + } + + return true; +} + +bool Dock::hasIconifiedItems() const +{ + std::list<const DockItem *>::const_iterator + i = _dock_items.begin(), + e = _dock_items.end(); + + for (; i != e; ++i) { + if ((*i)->isIconified()) { + return true; + } + } + + return false; +} + +void Dock::hide() +{ + getWidget().hide(); +} + +void Dock::show() +{ + getWidget().show(); +} + +void Dock::toggleDockable(int width, int height) +{ + static int prev_horizontal_position, prev_vertical_position; + + Gtk::Paned *parent_paned = getParentPaned(); + + if (width > 0 && height > 0) { + prev_horizontal_position = parent_paned->get_position(); + prev_vertical_position = _paned->get_position(); + + if (getWidget().get_width() < width) + parent_paned->set_position(parent_paned->get_width() - width); + + if (_paned->get_position() < height) + _paned->set_position(height); + + } else { + parent_paned->set_position(prev_horizontal_position); + _paned->set_position(prev_vertical_position); + } +} + +void Dock::scrollToItem(DockItem& item) +{ + int item_x, item_y; + item.getWidget().translate_coordinates(getWidget(), 0, 0, item_x, item_y); + + int dock_height = getWidget().get_height(), item_height = item.getWidget().get_height(); + double vadjustment = _scrolled_window->get_vadjustment()->get_value(); + + if (item_y < 0) + _scrolled_window->get_vadjustment()->set_value(vadjustment + item_y); + else if (item_y + item_height > dock_height) + _scrolled_window->get_vadjustment()->set_value( + vadjustment + ((item_y + item_height) - dock_height)); +} + +Glib::SignalProxy0<void> +Dock::signal_layout_changed() +{ + return Glib::SignalProxy0<void>(Glib::wrap(GTK_WIDGET(_gdl_dock)), + &_signal_layout_changed_proxy); +} + +void Dock::_onLayoutChanged() +{ + if (isEmpty()) { + if (hasIconifiedItems()) { + _paned->get_child1()->set_size_request(-1, -1); + _scrolled_window->set_size_request(_default_dock_bar_width); + } else { + _paned->get_child1()->set_size_request(-1, -1); + _scrolled_window->set_size_request(_default_empty_width); + } + getParentPaned()->set_position(10000); + + } else { + // unset any forced size requests + _paned->get_child1()->set_size_request(-1, -1); + _scrolled_window->set_size_request(-1); + } +} + +void +Dock::_onPanedButtonEvent(GdkEventButton *event) +{ + if (event->button == 1 && event->type == GDK_BUTTON_PRESS) + /* unset size request when starting a drag */ + _paned->get_child1()->set_size_request(-1, -1); +} + +gboolean +Dock::_on_paned_button_event(GtkWidget */*widget*/, GdkEventButton *event, gpointer user_data) +{ + if (Dock *dock = static_cast<Dock *>(user_data)) + dock->_onPanedButtonEvent(event); + + return FALSE; +} + +const Glib::SignalProxyInfo +Dock::_signal_layout_changed_proxy = +{ + "layout-changed", + (GCallback) &Glib::SignalProxyNormal::slot0_void_callback, + (GCallback) &Glib::SignalProxyNormal::slot0_void_callback +}; + + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/dock.h b/src/ui/widget/dock.h new file mode 100644 index 0000000..f061f59 --- /dev/null +++ b/src/ui/widget/dock.h @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief A desktop dock pane to dock dialogs, a custom wrapper around gdl-dock. + */ +/* Author: + * Gustav Broberg <broberg@kth.se> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_DOCK_H +#define INKSCAPE_UI_WIDGET_DOCK_H + +#include <gtkmm/box.h> +#include <list> +#include "ui/widget/dock-item.h" + +struct _GdlDock; +typedef _GdlDock GdlDock; +struct _GdlDockBar; +typedef _GdlDockBar GdlDockBar; + +namespace Gtk { +class Paned; +class ScrolledWindow; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +class Dock { + +public: + + Dock(Gtk::Orientation orientation=Gtk::ORIENTATION_VERTICAL); + ~Dock(); + + void addItem(DockItem& item, GdlDockPlacement placement); + + Gtk::Widget& getWidget(); //< return the top widget + Gtk::Paned *getParentPaned(); + Gtk::Paned *getPaned(); + + GtkWidget* getGdlWidget(); //< return the top gdl widget + + bool isEmpty() const; //< true iff none of the dock's items are in a docked state + bool hasIconifiedItems() const; + + Glib::SignalProxy0<void> signal_layout_changed(); + + void hide(); + void show(); + + /** Toggle size of dock between the previous dimensions and the ones sent as parameters */ + void toggleDockable(int width=0, int height=0); + + /** Scrolls the scrolled window container to make the provided dock item visible, if needed */ + void scrollToItem(DockItem& item); + +protected: + + std::list<const DockItem *> _dock_items; //< added dock items + + /** Interface widgets, will be packed like + * _scrolled_window -> (_dock_box -> (_paned -> (_dock -> _filler) | _dock_bar)) + */ + Gtk::Box *_dock_box; + Gtk::Paned *_paned; + GtkWidget *_gdl_dock; + GdlDockBar *_gdl_dock_bar; + Gtk::Box _filler; + Gtk::ScrolledWindow *_scrolled_window; + + /** Internal signal handlers */ + void _onLayoutChanged(); + void _onPanedButtonEvent(GdkEventButton *event); + + static gboolean _on_paned_button_event(GtkWidget *widget, GdkEventButton *event, + gpointer user_data); + + /** GdlDock signal proxy structures */ + static const Glib::SignalProxyInfo _signal_layout_changed_proxy; + + /** Standard widths */ + static const int _default_empty_width; + static const int _default_dock_bar_width; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif //INKSCAPE_UI_DIALOG_BEHAVIOUR_H + +/* + 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 : + diff --git a/src/ui/widget/entity-entry.cpp b/src/ui/widget/entity-entry.cpp new file mode 100644 index 0000000..6e87459 --- /dev/null +++ b/src/ui/widget/entity-entry.cpp @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * bulia byak <buliabyak@users.sf.net> + * Bryce W. Harrington <bryce@bryceharrington.org> + * Lauris Kaplinski <lauris@kaplinski.com> + * Jon Phillips <jon@rejon.org> + * Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm) + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2000 - 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "entity-entry.h" + +#include <gtkmm/scrolledwindow.h> +#include <gtkmm/entry.h> + +#include "document-undo.h" +#include "inkscape.h" +#include "preferences.h" +#include "rdf.h" +#include "verbs.h" + +#include "object/sp-root.h" + +#include "ui/widget/registry.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +//=================================================== + +//--------------------------------------------------- + +EntityEntry* +EntityEntry::create (rdf_work_entity_t* ent, Registry& wr) +{ + g_assert (ent); + EntityEntry* obj = nullptr; + switch (ent->format) + { + case RDF_FORMAT_LINE: + obj = new EntityLineEntry (ent, wr); + break; + case RDF_FORMAT_MULTILINE: + obj = new EntityMultiLineEntry (ent, wr); + break; + default: + g_warning ("An unknown RDF format was requested."); + } + + g_assert (obj); + obj->_label.show(); + return obj; +} + +EntityEntry::EntityEntry (rdf_work_entity_t* ent, Registry& wr) + : _label(Glib::ustring(_(ent->title)), Gtk::ALIGN_END), + _packable(nullptr), + _entity(ent), _wr(&wr) +{ +} + +EntityEntry::~EntityEntry() +{ + _changed_connection.disconnect(); +} + +void EntityEntry::save_to_preferences(SPDocument *doc) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + const gchar *text = rdf_get_work_entity (doc, _entity); + prefs->setString(PREFS_METADATA + Glib::ustring(_entity->name), Glib::ustring(text ? text : "")); +} + +EntityLineEntry::EntityLineEntry (rdf_work_entity_t* ent, Registry& wr) +: EntityEntry (ent, wr) +{ + Gtk::Entry *e = new Gtk::Entry; + e->set_tooltip_text (_(ent->tip)); + _packable = e; + _changed_connection = e->signal_changed().connect (sigc::mem_fun (*this, &EntityLineEntry::on_changed)); +} + +EntityLineEntry::~EntityLineEntry() +{ + delete static_cast<Gtk::Entry*>(_packable); +} + +void EntityLineEntry::update(SPDocument *doc) +{ + const char *text = rdf_get_work_entity (doc, _entity); + // If RDF title is not set, get the document's <title> and set the RDF: + if ( !text && !strcmp(_entity->name, "title") && doc->getRoot() ) { + text = doc->getRoot()->title(); + rdf_set_work_entity(doc, _entity, text); + } + static_cast<Gtk::Entry*>(_packable)->set_text (text ? text : ""); +} + + +void EntityLineEntry::load_from_preferences() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring text = prefs->getString(PREFS_METADATA + Glib::ustring(_entity->name)); + if (text.length() > 0) { + static_cast<Gtk::Entry*>(_packable)->set_text (text.c_str()); + } +} + +void +EntityLineEntry::on_changed() +{ + if (_wr->isUpdating()) return; + + _wr->setUpdating (true); + SPDocument *doc = SP_ACTIVE_DOCUMENT; + Glib::ustring text = static_cast<Gtk::Entry*>(_packable)->get_text(); + if (rdf_set_work_entity (doc, _entity, text.c_str())) { + if (doc->isSensitive()) { + DocumentUndo::done(doc, SP_VERB_NONE, "Document metadata updated"); + } + } + _wr->setUpdating (false); +} + +EntityMultiLineEntry::EntityMultiLineEntry (rdf_work_entity_t* ent, Registry& wr) +: EntityEntry (ent, wr) +{ + Gtk::ScrolledWindow *s = new Gtk::ScrolledWindow; + s->set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + s->set_shadow_type (Gtk::SHADOW_IN); + _packable = s; + _v.set_size_request (-1, 35); + _v.set_wrap_mode (Gtk::WRAP_WORD); + _v.set_accepts_tab (false); + s->add (_v); + _v.set_tooltip_text (_(ent->tip)); + _changed_connection = _v.get_buffer()->signal_changed().connect (sigc::mem_fun (*this, &EntityMultiLineEntry::on_changed)); +} + +EntityMultiLineEntry::~EntityMultiLineEntry() +{ + delete static_cast<Gtk::ScrolledWindow*>(_packable); +} + +void EntityMultiLineEntry::update(SPDocument *doc) +{ + const char *text = rdf_get_work_entity (doc, _entity); + // If RDF title is not set, get the document's <title> and set the RDF: + if ( !text && !strcmp(_entity->name, "title") && doc->getRoot() ) { + text = doc->getRoot()->title(); + rdf_set_work_entity(doc, _entity, text); + } + Gtk::ScrolledWindow *s = static_cast<Gtk::ScrolledWindow*>(_packable); + Gtk::TextView *tv = static_cast<Gtk::TextView*>(s->get_child()); + tv->get_buffer()->set_text (text ? text : ""); +} + + +void EntityMultiLineEntry::load_from_preferences() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring text = prefs->getString(PREFS_METADATA + Glib::ustring(_entity->name)); + if (text.length() > 0) { + Gtk::ScrolledWindow *s = static_cast<Gtk::ScrolledWindow*>(_packable); + Gtk::TextView *tv = static_cast<Gtk::TextView*>(s->get_child()); + tv->get_buffer()->set_text (text.c_str()); + } +} + + +void +EntityMultiLineEntry::on_changed() +{ + if (_wr->isUpdating()) return; + + _wr->setUpdating (true); + SPDocument *doc = SP_ACTIVE_DOCUMENT; + Gtk::ScrolledWindow *s = static_cast<Gtk::ScrolledWindow*>(_packable); + Gtk::TextView *tv = static_cast<Gtk::TextView*>(s->get_child()); + Glib::ustring text = tv->get_buffer()->get_text(); + if (rdf_set_work_entity (doc, _entity, text.c_str())) { + DocumentUndo::done(doc, SP_VERB_NONE, "Document metadata updated"); + } + _wr->setUpdating (false); +} + +} // 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 : diff --git a/src/ui/widget/entity-entry.h b/src/ui/widget/entity-entry.h new file mode 100644 index 0000000..3168e4c --- /dev/null +++ b/src/ui/widget/entity-entry.h @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ralf Stephan <ralf@ark.in-berlin.de> + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_ENTITY_ENTRY__H +#define INKSCAPE_UI_WIDGET_ENTITY_ENTRY__H + +#include <gtkmm/textview.h> + +struct rdf_work_entity_t; +class SPDocument; + +namespace Gtk { +class TextBuffer; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +class Registry; + +class EntityEntry { +public: + static EntityEntry* create (rdf_work_entity_t* ent, Registry& wr); + virtual ~EntityEntry() = 0; + virtual void update (SPDocument *doc) = 0; + virtual void on_changed() = 0; + virtual void load_from_preferences() = 0; + void save_to_preferences(SPDocument *doc); + Gtk::Label _label; + Gtk::Widget *_packable; + +protected: + EntityEntry (rdf_work_entity_t* ent, Registry& wr); + sigc::connection _changed_connection; + rdf_work_entity_t *_entity; + Registry *_wr; +}; + +class EntityLineEntry : public EntityEntry { +public: + EntityLineEntry (rdf_work_entity_t* ent, Registry& wr); + ~EntityLineEntry() override; + void update (SPDocument *doc) override; + void load_from_preferences() override; + +protected: + void on_changed() override; +}; + +class EntityMultiLineEntry : public EntityEntry { +public: + EntityMultiLineEntry (rdf_work_entity_t* ent, Registry& wr); + ~EntityMultiLineEntry() override; + void update (SPDocument *doc) override; + void load_from_preferences() override; + +protected: + void on_changed() override; + Gtk::TextView _v; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_ENTITY_ENTRY__H + +/* + 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 : diff --git a/src/ui/widget/entry.cpp b/src/ui/widget/entry.cpp new file mode 100644 index 0000000..e9a63c5 --- /dev/null +++ b/src/ui/widget/entry.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Johan Engelen <goejendaagh@zonnet.nl> + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "entry.h" + +#include <gtkmm/entry.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +Entry::Entry( Glib::ustring const &label, Glib::ustring const &tooltip, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Labelled(label, tooltip, new Gtk::Entry(), suffix, icon, mnemonic) +{ +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + diff --git a/src/ui/widget/entry.h b/src/ui/widget/entry.h new file mode 100644 index 0000000..3674d51 --- /dev/null +++ b/src/ui/widget/entry.h @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Johan Engelen <goejendaagh@zonnet.nl> + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_ENTRY__H +#define INKSCAPE_UI_WIDGET_ENTRY__H + +#include "labelled.h" + +namespace Gtk { +class Entry; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Helperclass for Gtk::Entry widgets. + */ +class Entry : public Labelled +{ +public: + Entry( Glib::ustring const &label, + Glib::ustring const &tooltip, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + // TO DO: add methods to access Gtk::Entry widget + + Gtk::Entry* getEntry() {return (Gtk::Entry*)(_widget);}; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_ENTRY__H diff --git a/src/ui/widget/filter-effect-chooser.cpp b/src/ui/widget/filter-effect-chooser.cpp new file mode 100644 index 0000000..d933408 --- /dev/null +++ b/src/ui/widget/filter-effect-chooser.cpp @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Filter effect selection selection widget + * + * Author: + * Nicholas Bishop <nicholasbishop@gmail.com> + * Tavmjong Bah + * + * Copyright (C) 2007, 2017 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "filter-effect-chooser.h" + +#include "document.h" + +namespace Inkscape { + +const EnumData<SPBlendMode> SPBlendModeData[SP_CSS_BLEND_ENDMODE] = { + { SP_CSS_BLEND_NORMAL, _("Normal"), "normal" }, + { SP_CSS_BLEND_MULTIPLY, _("Multiply"), "multiply" }, + { SP_CSS_BLEND_SCREEN, _("Screen"), "screen" }, + { SP_CSS_BLEND_DARKEN, _("Darken"), "darken" }, + { SP_CSS_BLEND_LIGHTEN, _("Lighten"), "lighten" }, + // New in Compositing and Blending Level 1 + { SP_CSS_BLEND_OVERLAY, _("Overlay"), "overlay" }, + { SP_CSS_BLEND_COLORDODGE, _("Color Dodge"), "color-dodge" }, + { SP_CSS_BLEND_COLORBURN, _("Color Burn"), "color-burn" }, + { SP_CSS_BLEND_HARDLIGHT, _("Hard Light"), "hard-light" }, + { SP_CSS_BLEND_SOFTLIGHT, _("Soft Light"), "soft-light" }, + { SP_CSS_BLEND_DIFFERENCE, _("Difference"), "difference" }, + { SP_CSS_BLEND_EXCLUSION, _("Exclusion"), "exclusion" }, + { SP_CSS_BLEND_HUE, _("Hue"), "hue" }, + { SP_CSS_BLEND_SATURATION, _("Saturation"), "saturation" }, + { SP_CSS_BLEND_COLOR, _("Color"), "color" }, + { SP_CSS_BLEND_LUMINOSITY, _("Luminosity"), "luminosity" } +}; +const EnumDataConverter<SPBlendMode> SPBlendModeConverter(SPBlendModeData, SP_CSS_BLEND_ENDMODE); + + +namespace UI { +namespace Widget { + +SimpleFilterModifier::SimpleFilterModifier(int flags) + : _flags(flags) + , _lb_blend(_("Blend mode:")) + , _lb_isolation("Isolate") // Translate for 1.1 + , _blend(SPBlendModeConverter, SP_ATTR_INVALID, false) + , _blur(_("Blur (%)"), 0, 0, 100, 1, 0.1, 1) + , _opacity(_("Opacity (%)"), 0, 0, 100, 1, 0.1, 1) + , _notify(true) +{ + set_name("SimpleFilterModifier"); + + _flags = flags; + + if (flags & BLEND) { + add(_hb_blend); + _lb_blend.set_use_underline(); + _hb_blend.set_halign(Gtk::ALIGN_END); + _hb_blend.set_valign(Gtk::ALIGN_CENTER); + _hb_blend.set_margin_top(3); + _hb_blend.set_margin_end(5); + _lb_blend.set_mnemonic_widget(_blend); + _hb_blend.pack_start(_lb_blend, false, false, 5); + _hb_blend.pack_start(_blend, false, false, 5); + /* + * For best fit inkscape-browsers with no GUI to isolation we need all groups, + * clones and symbols with isolation == isolate to not show to the user of + * Inkscape a "strange" behabiour from the designer point of view. + * Is strange because only happends when object not has clip, mask, + * filter, blending or opacity . + * Anyway the feature is a no-gui feature and render as spected. + */ + /* if (flags & ISOLATION) { + _isolation.property_active() = false; + _hb_blend.pack_start(_isolation, false, false, 5); + _hb_blend.pack_start(_lb_isolation, false, false, 5); + _isolation.set_tooltip_text("Don't blend childrens with objects behind"); + _lb_isolation.set_tooltip_text("Don't blend childrens with objects behind"); + } */ + Gtk::Separator *separator = Gtk::manage(new Gtk::Separator()); + separator->set_margin_top(8); + separator->set_margin_bottom(8); + add(*separator); + } + + if (flags & BLUR) { + add(_blur); + } + + if (flags & OPACITY) { + add(_opacity); + } + show_all_children(); + + _blend.signal_changed().connect(signal_blend_changed()); + _blur.signal_value_changed().connect(signal_blur_changed()); + _opacity.signal_value_changed().connect(signal_opacity_changed()); + _isolation.signal_toggled().connect(signal_isolation_changed()); +} + +sigc::signal<void> &SimpleFilterModifier::signal_isolation_changed() +{ + if (_notify) { + return _signal_isolation_changed; + } + _notify = true; + return _signal_null; +} + +sigc::signal<void>& SimpleFilterModifier::signal_blend_changed() +{ + if (_notify) { + return _signal_blend_changed; + } + _notify = true; + return _signal_null; +} + +sigc::signal<void>& SimpleFilterModifier::signal_blur_changed() +{ + // we dont use notifi to block use aberaje for multiple + return _signal_blur_changed; +} + +sigc::signal<void>& SimpleFilterModifier::signal_opacity_changed() +{ + // we dont use notifi to block use averaje for multiple + return _signal_opacity_changed; +} + +SPIsolation SimpleFilterModifier::get_isolation_mode() +{ + return _isolation.get_active() ? SP_CSS_ISOLATION_ISOLATE : SP_CSS_ISOLATION_AUTO; +} + +void SimpleFilterModifier::set_isolation_mode(const SPIsolation val, bool notify) +{ + _notify = notify; + _isolation.set_active(val == SP_CSS_ISOLATION_ISOLATE); +} + +SPBlendMode SimpleFilterModifier::get_blend_mode() +{ + const Util::EnumData<SPBlendMode> *d = _blend.get_active_data(); + if (d) { + return _blend.get_active_data()->id; + } else { + return SP_CSS_BLEND_NORMAL; + } +} + +void SimpleFilterModifier::set_blend_mode(const SPBlendMode val, bool notify) +{ + _notify = notify; + _blend.set_active(val); +} + +double SimpleFilterModifier::get_blur_value() const +{ + return _blur.get_value(); +} + +void SimpleFilterModifier::set_blur_value(const double val) +{ + _blur.set_value(val); +} + +double SimpleFilterModifier::get_opacity_value() const +{ + return _opacity.get_value(); +} + +void SimpleFilterModifier::set_opacity_value(const double val) +{ + _opacity.set_value(val); +} + +} +} +} + +/* + 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 : diff --git a/src/ui/widget/filter-effect-chooser.h b/src/ui/widget/filter-effect-chooser.h new file mode 100644 index 0000000..cbbe2b5 --- /dev/null +++ b/src/ui/widget/filter-effect-chooser.h @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __FILTER_EFFECT_CHOOSER_H__ +#define __FILTER_EFFECT_CHOOSER_H__ + +/* + * Filter effect selection selection widget + * + * Author: + * Nicholas Bishop <nicholasbishop@gmail.com> + * Tavmjong Bah + * + * Copyright (C) 2007, 2017 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/box.h> +#include <gtkmm/checkbutton.h> +#include <gtkmm/combobox.h> +#include <gtkmm/separator.h> + +#include "combo-enums.h" +#include "spin-scale.h" +#include "style-enums.h" + +using Inkscape::Util::EnumData; +using Inkscape::Util::EnumDataConverter; + +namespace Inkscape { +extern const Util::EnumDataConverter<SPBlendMode> SPBlendModeConverter; +namespace UI { +namespace Widget { + +/* Allows basic control over feBlend and feGaussianBlur effects as well as opacity. + * Common for Object, Layers, and Fill and Stroke dialogs. +*/ +class SimpleFilterModifier : public Gtk::VBox +{ +public: + enum Flags { NONE = 0, BLUR = 1, OPACITY = 2, BLEND = 4, ISOLATION = 16 }; + + SimpleFilterModifier(int flags); + + sigc::signal<void> &signal_blend_changed(); + sigc::signal<void> &signal_blur_changed(); + sigc::signal<void> &signal_opacity_changed(); + sigc::signal<void> &signal_isolation_changed(); + + SPIsolation get_isolation_mode(); + void set_isolation_mode(const SPIsolation, bool notify); + + SPBlendMode get_blend_mode(); + void set_blend_mode(const SPBlendMode, bool notify); + + double get_blur_value() const; + void set_blur_value(const double); + + double get_opacity_value() const; + void set_opacity_value(const double); + +private: + int _flags; + bool _notify; + + Gtk::HBox _hb_blend; + Gtk::Label _lb_blend; + Gtk::Label _lb_isolation; + ComboBoxEnum<SPBlendMode> _blend; + SpinScale _blur; + SpinScale _opacity; + Gtk::CheckButton _isolation; + + sigc::signal<void> _signal_null; + sigc::signal<void> _signal_blend_changed; + sigc::signal<void> _signal_blur_changed; + sigc::signal<void> _signal_opacity_changed; + sigc::signal<void> _signal_isolation_changed; +}; + +} +} +} + +#endif + +/* + 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 : diff --git a/src/ui/widget/font-button.cpp b/src/ui/widget/font-button.cpp new file mode 100644 index 0000000..e0a140a --- /dev/null +++ b/src/ui/widget/font-button.cpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "font-button.h" + +#include <glibmm/i18n.h> + +#include <gtkmm/fontbutton.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +FontButton::FontButton(Glib::ustring const &label, Glib::ustring const &tooltip, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Labelled(label, tooltip, new Gtk::FontButton("Sans 10"), suffix, icon, mnemonic) +{ +} + +Glib::ustring FontButton::getValue() const +{ + g_assert(_widget != nullptr); + return static_cast<Gtk::FontButton*>(_widget)->get_font_name(); +} + + +void FontButton::setValue (Glib::ustring fontspec) +{ + g_assert(_widget != nullptr); + static_cast<Gtk::FontButton*>(_widget)->set_font_name(fontspec); +} + +Glib::SignalProxy0<void> FontButton::signal_font_value_changed() +{ + g_assert(_widget != nullptr); + return static_cast<Gtk::FontButton*>(_widget)->signal_font_set(); +} + + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/font-button.h b/src/ui/widget/font-button.h new file mode 100644 index 0000000..a53b7d6 --- /dev/null +++ b/src/ui/widget/font-button.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Copyright (C) 2007 Author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_FONT_BUTTON_H +#define INKSCAPE_UI_WIDGET_FONT_BUTTON_H + +#include "labelled.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A labelled font button for entering font values + */ +class FontButton : public Labelled +{ +public: + /** + * Construct a FontButton Widget. + * + * @param label Label. + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to false). + */ + FontButton( Glib::ustring const &label, + Glib::ustring const &tooltip, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + Glib::ustring getValue() const; + void setValue (Glib::ustring fontspec); + /** + * Signal raised when the font button's value changes. + */ + Glib::SignalProxy0<void> signal_font_value_changed(); +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_RANDOM_H + +/* + 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 : diff --git a/src/ui/widget/font-selector-toolbar.cpp b/src/ui/widget/font-selector-toolbar.cpp new file mode 100644 index 0000000..ea53d90 --- /dev/null +++ b/src/ui/widget/font-selector-toolbar.cpp @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2018 Tavmong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm/i18n.h> +#include <glibmm/regex.h> +#include <gdkmm/display.h> + +#include "font-selector-toolbar.h" + +#include "libnrtype/font-lister.h" +#include "libnrtype/font-instance.h" + +#include "ui/icon-names.h" + +// For updating from selection +#include "inkscape.h" +#include "desktop.h" +#include "object/sp-text.h" + +// TEMP TEMP TEMP +#include "ui/toolbar/text-toolbar.h" + +/* To do: + * Fix altx. Need to store + */ + +void family_cell_data_func(const Gtk::TreeModel::const_iterator iter, Gtk::CellRendererText* cell ) { + + Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance(); + Glib::ustring markup = font_lister->get_font_family_markup(iter); + // std::cout << "Markup: " << markup << std::endl; + + cell->set_property ("markup", markup); +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +FontSelectorToolbar::FontSelectorToolbar () + : Gtk::Grid () + , family_combo (true) // true => with text entry. + , style_combo (true) + , signal_block (false) +{ + + Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance(); + + // Font family + family_combo.set_model (font_lister->get_font_list()); + family_combo.set_entry_text_column (0); + family_combo.set_name ("FontSelectorToolBar: Family"); + family_combo.set_row_separator_func (&font_lister_separator_func); + + family_combo.clear(); // Clears all CellRenderer mappings. + family_combo.set_cell_data_func (family_cell, + sigc::bind(sigc::ptr_fun(family_cell_data_func), &family_cell)); + family_combo.pack_start (family_cell); + + + Gtk::Entry* entry = family_combo.get_entry(); + entry->signal_icon_press().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_icon_pressed)); + entry->signal_key_press_event().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_key_press_event), false); // false => connect first + entry->set_data (Glib::Quark("altx-text"), entry); // Desktop will set focus to entry with Alt-x. + + + Glib::RefPtr<Gtk::EntryCompletion> completion = Gtk::EntryCompletion::create(); + completion->set_model (font_lister->get_font_list()); + completion->set_text_column (0); + completion->set_popup_completion (); + completion->set_inline_completion (false); + completion->set_inline_selection (); + // completion->signal_match_selected().connect(sigc::mem_fun(*this, &FontSelectorToolbar::on_match_selected), false); // false => connect before default handler. + entry->set_completion (completion); + + // Style + style_combo.set_model (font_lister->get_style_list()); + style_combo.set_name ("FontSelectorToolbar: Style"); + + // Grid + set_name ("FontSelectorToolbar: Grid"); + attach (family_combo, 0, 0, 1, 1); + attach (style_combo, 1, 0, 1, 1); + + // Add signals + family_combo.signal_changed().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_family_changed)); + style_combo.signal_changed().connect (sigc::mem_fun(*this, &FontSelectorToolbar::on_style_changed)); + + show_all_children(); + + // Initialize font family lists. (May already be done.) Should be done on document change. + font_lister->update_font_list(SP_ACTIVE_DESKTOP->getDocument()); + + // When FontLister is changed, update family and style shown in GUI. + font_lister->connectUpdate(sigc::mem_fun(*this, &FontSelectorToolbar::update_font)); +} + + +// Update GUI based on font-selector values. +void +FontSelectorToolbar::update_font () +{ + if (signal_block) return; + + signal_block = true; + + Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance(); + Gtk::TreeModel::Row row; + + // Set font family. + try { + row = font_lister->get_row_for_font (); + family_combo.set_active (row); + } catch (...) { + std::cerr << "FontSelectorToolbar::update_font: Couldn't find row for family: " + << font_lister->get_font_family() << std::endl; + } + + // Set style. + try { + row = font_lister->get_row_for_style (); + style_combo.set_active (row); + } catch (...) { + std::cerr << "FontSelectorToolbar::update_font: Couldn't find row for style: " + << font_lister->get_font_style() << std::endl; + } + + // Check for missing fonts. + Glib::ustring missing_fonts = get_missing_fonts(); + + // Add an icon to end of entry. + Gtk::Entry* entry = family_combo.get_entry(); + if (missing_fonts.empty()) { + // If no missing fonts, add icon for selecting all objects with this font-family. + entry->set_icon_from_icon_name (INKSCAPE_ICON("edit-select-all"), Gtk::ENTRY_ICON_SECONDARY); + entry->set_icon_tooltip_text (_("Select all text with this text family"), Gtk::ENTRY_ICON_SECONDARY); + } else { + // If missing fonts, add warning icon. + Glib::ustring warning = _("Font not found on system: ") + missing_fonts; + entry->set_icon_from_icon_name (INKSCAPE_ICON("dialog-warning"), Gtk::ENTRY_ICON_SECONDARY); + entry->set_icon_tooltip_text (warning, Gtk::ENTRY_ICON_SECONDARY); + } + + signal_block = false; +} + +// Get comma separated list of fonts in font-family that are not on system. +// To do, move to font-lister. +Glib::ustring +FontSelectorToolbar::get_missing_fonts () +{ + // Get font list in text entry which may be a font stack (with fallbacks). + Glib::ustring font_list = family_combo.get_entry_text(); + Glib::ustring missing_font_list; + Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance(); + + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s*,\\s*", font_list); + + for (auto token: tokens) { + bool found = false; + Gtk::TreeModel::Children children = font_lister->get_font_list()->children(); + for (auto iter2: children) { + Gtk::TreeModel::Row row2 = *iter2; + Glib::ustring family2 = row2[font_lister->FontList.family]; + bool onSystem2 = row2[font_lister->FontList.onSystem]; + // CSS dictates that font family names are case insensitive. + // This should really implement full Unicode case unfolding. + if (onSystem2 && token.casefold().compare(family2.casefold()) == 0) { + found = true; + break; + } + } + + if (!found) { + missing_font_list += token; + missing_font_list += ", "; + } + } + + // Remove extra comma and space from end. + if (missing_font_list.size() >= 2) { + missing_font_list.resize(missing_font_list.size() - 2); + } + + return missing_font_list; +} + + +// Callbacks + +// Need to update style list +void +FontSelectorToolbar::on_family_changed() { + + if (signal_block) return; + signal_block = true; + + Glib::ustring family = family_combo.get_entry_text(); + + Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance(); + fontlister->set_font_family (family); + + signal_block = false; + + // Let world know + changed_emit(); +} + +void +FontSelectorToolbar::on_style_changed() { + + if (signal_block) return; + signal_block = true; + + Glib::ustring style = style_combo.get_entry_text(); + + Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance(); + fontlister->set_font_style (style); + + signal_block = false; + + // Let world know + changed_emit(); +} + +void +FontSelectorToolbar::on_icon_pressed (Gtk::EntryIconPosition icon_position, const GdkEventButton* event) { + std::cout << "FontSelectorToolbar::on_entry_icon_pressed" << std::endl; + std::cout << " .... Should select all items with same font-family. FIXME" << std::endl; + // Call equivalent of sp_text_toolbox_select_cb() in text-toolbar.cpp + // Should be action! (Maybe: select_all_fontfamily( Glib::ustring font_family );). + // Check how Find dialog works. +} + +// bool +// FontSelectorToolbar::on_match_selected (const Gtk::TreeModel::iterator& iter) +// { +// std::cout << "on_match_selected" << std::endl; +// std::cout << " FIXME" << std::endl; +// Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance(); +// Glib::ustring family = (*iter)[font_lister->FontList.family]; +// std::cout << " family: " << family << std::endl; +// return false; // Leave it to default handler to set entry text. +// } + +// Return focus to canvas. +bool +FontSelectorToolbar::on_key_press_event (GdkEventKey* key_event) +{ + bool consumed = false; + + unsigned int key = 0; + gdk_keymap_translate_keyboard_state( Gdk::Display::get_default()->get_keymap(), + key_event->hardware_keycode, + (GdkModifierType)key_event->state, + 0, &key, nullptr, nullptr, nullptr ); + + switch ( key ) { + + case GDK_KEY_Escape: + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + { + // Defocus + std::cerr << "FontSelectorToolbar::on_key_press_event: Defocus: FIXME" << std::endl; + consumed = true; + } + break; + } + + return consumed; // Leave it to default handler if false. +} + +void +FontSelectorToolbar::changed_emit() { + signal_block = true; + changed_signal.emit (); + signal_block = false; +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/font-selector-toolbar.h b/src/ui/widget/font-selector-toolbar.h new file mode 100644 index 0000000..53cdcea --- /dev/null +++ b/src/ui/widget/font-selector-toolbar.h @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2018 Tavmong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * + * + * The routines here create and manage a font selector widget with two parts, + * one each for font-family and font-style. + * + * This is essentially a toolbar version of the 'FontSelector' widget. Someday + * this may be merged with it. + * + * The main functions are: + * Create the font-selector toolbar widget. + * Update the lists when a new text selection is made. + * Update the Style list when a new font-family is selected, highlighting the + * best match to the original font style (as not all fonts have the same style options). + * Update the on-screen text. + * Provide the currently selected values. + */ + +#ifndef INKSCAPE_UI_WIDGET_FONT_SELECTOR_TOOLBAR_H +#define INKSCAPE_UI_WIDGET_FONT_SELECTOR_TOOLBAR_H + +#include <gtkmm/grid.h> +#include <gtkmm/treeview.h> +#include <gtkmm/comboboxtext.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A container of widgets for selecting font faces. + * + * It is used by Text tool toolbar. The FontSelectorToolbar class utilizes the + * FontLister class to obtain a list of font-families and their associated styles for fonts either + * on the system or in the document. The FontLister class is also used by the Text toolbar. Fonts + * are kept track of by their "fontspecs" which are the same as the strings that Pango generates. + * + * The main functions are: + * Create the font-selector widget. + * Update the child widgets when a new text selection is made. + * Update the Style list when a new font-family is selected, highlighting the + * best match to the original font style (as not all fonts have the same style options). + * Emit a signal when any change is made to a child widget. + */ +class FontSelectorToolbar : public Gtk::Grid +{ + +public: + + /** + * Constructor + */ + FontSelectorToolbar (); + +protected: + + // Font family + Gtk::ComboBox family_combo; + Gtk::CellRendererText family_cell; + + // Font style + Gtk::ComboBoxText style_combo; + Gtk::CellRendererText style_cell; + +private: + + // Make a list of missing fonts for tooltip and for warning icon. + Glib::ustring get_missing_fonts (); + + // Signal handlers + void on_family_changed(); + void on_style_changed(); + void on_icon_pressed (Gtk::EntryIconPosition icon_position, const GdkEventButton* event); + // bool on_match_selected (const Gtk::TreeModel::iterator& iter); + bool on_key_press_event (GdkEventKey* key_event) override; + + // Signals + sigc::signal<void> changed_signal; + void changed_emit(); + bool signal_block; + +public: + + /** + * Update GUI based on font-selector values. + */ + void update_font (); + + /** + * Let others know that user has changed GUI settings. + */ + sigc::connection connectChanged(sigc::slot<void> slot) { + return changed_signal.connect(slot); + } +}; + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_FONT_SETTINGS_TOOLBAR_H + +/* + 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 : diff --git a/src/ui/widget/font-selector.cpp b/src/ui/widget/font-selector.cpp new file mode 100644 index 0000000..df13fa3 --- /dev/null +++ b/src/ui/widget/font-selector.cpp @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2018 Tavmong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm/i18n.h> +#include <glibmm/markup.h> + +#include "font-selector.h" + +#include "libnrtype/font-lister.h" +#include "libnrtype/font-instance.h" + +// For updating from selection +#include "inkscape.h" +#include "desktop.h" +#include "object/sp-text.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +FontSelector::FontSelector (bool with_size, bool with_variations) + : Gtk::Grid () + , family_frame (_("Font family")) + , style_frame (C_("Font selector", "Style")) + , size_label (_("Font size")) + , size_combobox (true) // With entry + , signal_block (false) + , font_size (18) +{ + + Inkscape::FontLister* font_lister = Inkscape::FontLister::get_instance(); + + // Font family + family_treecolumn.pack_start (family_cell, false); + family_treecolumn.set_fixed_width (200); + family_treecolumn.add_attribute (family_cell, "text", 0); + family_treecolumn.set_cell_data_func (family_cell, &font_lister_cell_data_func); + + family_treeview.set_row_separator_func (&font_lister_separator_func); + family_treeview.set_model (font_lister->get_font_list()); + family_treeview.set_name ("FontSelector: Family"); + family_treeview.set_headers_visible (false); + family_treeview.append_column (family_treecolumn); + + family_scroll.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + family_scroll.add (family_treeview); + + family_frame.set_hexpand (true); + family_frame.set_vexpand (true); + family_frame.add (family_scroll); + + // Style + style_treecolumn.pack_start (style_cell, false); + style_treecolumn.add_attribute (style_cell, "text", 0); + style_treecolumn.set_cell_data_func (style_cell, sigc::mem_fun(*this, &FontSelector::style_cell_data_func)); + style_treecolumn.set_title ("Face"); + style_treecolumn.set_resizable (true); + + style_treeview.set_model (font_lister->get_style_list()); + style_treeview.set_name ("FontSelectorStyle"); + style_treeview.append_column ("CSS", font_lister->FontStyleList.cssStyle); + style_treeview.append_column (style_treecolumn); + + style_treeview.get_column(0)->set_resizable (true); + + style_scroll.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + style_scroll.add (style_treeview); + + style_frame.set_hexpand (true); + style_frame.set_vexpand (true); + style_frame.add (style_scroll); + + // Size + size_combobox.set_name ("FontSelectorSize"); + set_sizes(); + size_combobox.set_active_text( "18" ); + + // Font Variations + font_variations.set_vexpand (true); + font_variations_scroll.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + font_variations_scroll.add (font_variations); + + // Grid + set_name ("FontSelectorGrid"); + set_row_spacing(4); + set_column_spacing(4); + // Add extra columns to the "family frame" to change space distribution + // by prioritizing font family over styles + const int extra = 4; + attach (family_frame, 0, 0, 1 + extra, 2); + attach (style_frame, 1 + extra, 0, 2, 1); + if (with_size) { // Glyph panel does not use size. + attach (size_label, 1 + extra, 1, 1, 1); + attach (size_combobox, 2 + extra, 1, 1, 1); + } + if (with_variations) { // Glyphs panel does not use variations. + attach (font_variations_scroll, 0, 2, 3 + extra, 1); + } + + // Add signals + family_treeview.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_family_changed)); + style_treeview.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_style_changed)); + size_combobox.signal_changed().connect(sigc::mem_fun(*this, &FontSelector::on_size_changed)); + font_variations.connectChanged(sigc::mem_fun(*this, &FontSelector::on_variations_changed)); + + show_all_children(); + + // Initialize font family lists. (May already be done.) Should be done on document change. + font_lister->update_font_list(SP_ACTIVE_DESKTOP->getDocument()); +} + +void +FontSelector::set_sizes () +{ + size_combobox.remove_all(); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT); + + int sizes[] = { + 4, 6, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 28, + 32, 36, 40, 48, 56, 64, 72, 144 + }; + + // Array must be same length as SPCSSUnit in style-internal.h + // PX PT PC MM CM IN EM EX % + double ratios[] = {1, 1, 1, 10, 4, 40, 100, 16, 8, 0.16}; + + for (int i : sizes) + { + double size = i/ratios[unit]; + size_combobox.append( Glib::ustring::format(size) ); + } +} + +void +FontSelector::set_fontsize_tooltip() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int unit = prefs->getInt("/options/font/unitType", SP_CSS_UNIT_PT); + Glib::ustring tooltip = Glib::ustring::format(_("Font size"), " (", sp_style_get_css_unit_string(unit), ")"); + size_combobox.set_tooltip_text (tooltip); +} + +// Update GUI. +// We keep a private copy of the style list as the font-family in widget is only temporary +// until the "Apply" button is set so the style list can be different from that in +// FontLister. +void +FontSelector::update_font () +{ + signal_block = true; + + Inkscape::FontLister *font_lister = Inkscape::FontLister::get_instance(); + Gtk::TreePath path; + Glib::ustring family = font_lister->get_font_family(); + Glib::ustring style = font_lister->get_font_style(); + + // Set font family + try { + path = font_lister->get_row_for_font (family); + } catch (...) { + std::cerr << "FontSelector::update_font: Couldn't find row for font-family: " + << family << std::endl; + path.clear(); + path.push_back(0); + } + + Gtk::TreePath currentPath; + Gtk::TreeViewColumn *currentColumn; + family_treeview.get_cursor(currentPath, currentColumn); + if (currentPath.empty() || !font_lister->is_path_for_font(currentPath, family)) { + family_treeview.set_cursor (path); + family_treeview.scroll_to_row (path); + } + + // Get font-lister style list for selected family + Gtk::TreeModel::Row row = *(family_treeview.get_model()->get_iter (path)); + GList *styles; + row.get_value(1, styles); + + // Copy font-lister style list to private list store, searching for match. + Gtk::TreeModel::iterator match; + FontLister::FontStyleListClass FontStyleList; + Glib::RefPtr<Gtk::ListStore> local_style_list_store = Gtk::ListStore::create(FontStyleList); + for ( ; styles; styles = styles->next ) { + Gtk::TreeModel::iterator treeModelIter = local_style_list_store->append(); + (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)styles->data)->CssName; + (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)styles->data)->DisplayName; + if (style == ((StyleNames*)styles->data)->CssName) { + match = treeModelIter; + } + } + + // Attach store to tree view and select row. + style_treeview.set_model (local_style_list_store); + if (match) { + style_treeview.get_selection()->select (match); + } + + Glib::ustring fontspec = font_lister->get_fontspec(); + update_variations(fontspec); + + signal_block = false; +} + +void +FontSelector::update_size (double size) +{ + signal_block = true; + + // Set font size + std::stringstream ss; + ss << size; + size_combobox.get_entry()->set_text( ss.str() ); + font_size = size; // Store value + set_fontsize_tooltip(); + + signal_block = false; +} + + +// If use_variations is true (default), we get variation values from variations widget otherwise we +// get values from CSS widget (we need to be able to keep the two widgets synchronized both ways). +Glib::ustring +FontSelector::get_fontspec(bool use_variations) { + + // Build new fontspec from GUI settings + Glib::ustring family = "Sans"; // Default...family list may not have been constructed. + Gtk::TreeModel::iterator iter = family_treeview.get_selection()->get_selected(); + if (iter) { + (*iter).get_value(0, family); + } + + Glib::ustring style = "Normal"; + iter = style_treeview.get_selection()->get_selected(); + if (iter) { + (*iter).get_value(0, style); + } + + if (family.empty()) { + std::cerr << "FontSelector::get_fontspec: empty family!" << std::endl; + } + + if (style.empty()) { + std::cerr << "FontSelector::get_fontspec: empty style!" << std::endl; + } + + Glib::ustring fontspec = family + ", "; + + if (use_variations) { + // Clip any font_variation data in 'style' as we'll replace it. + auto pos = style.find('@'); + if (pos != Glib::ustring::npos) { + style.erase (pos, style.length()-1); + } + + Glib::ustring variations = font_variations.get_pango_string(); + + if (variations.empty()) { + fontspec += style; + } else { + fontspec += variations; + } + } else { + fontspec += style; + } + + return fontspec; +} + +void +FontSelector::style_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter) +{ + Glib::ustring family = "Sans"; // Default...family list may not have been constructed. + Gtk::TreeModel::iterator iter_family = family_treeview.get_selection()->get_selected(); + if (iter_family) { + (*iter_family).get_value(0, family); + } + + Glib::ustring style = "Normal"; + (*iter).get_value(1, style); + + Glib::ustring style_escaped = Glib::Markup::escape_text( style ); + Glib::ustring font_desc = Glib::Markup::escape_text( family + ", " + style ); + Glib::ustring markup; + + markup = "<span font='" + font_desc + "'>" + style_escaped + "</span>"; + + // std::cout << " markup: " << markup << " (" << name << ")" << std::endl; + + renderer->set_property("markup", markup); +} + + +// Callbacks + +// Need to update style list +void +FontSelector::on_family_changed() { + + if (signal_block) return; + signal_block = true; + + Glib::RefPtr<Gtk::TreeModel> model; + Gtk::TreeModel::iterator iter = family_treeview.get_selection()->get_selected(model); + + if (!iter) { + // This can happen just after the family list is recreated. + signal_block = false; + return; + } + + Inkscape::FontLister *fontlister = Inkscape::FontLister::get_instance(); + fontlister->ensureRowStyles(model, iter); + + Gtk::TreeModel::Row row = *iter; + + // Get family name + Glib::ustring family; + row.get_value(0, family); + + // Get style list (TO DO: Get rid of GList) + GList *styles; + row.get_value(1, styles); + + // Find best style match for selected family with current style (e.g. of selected text). + Glib::ustring style = fontlister->get_font_style(); + Glib::ustring best = fontlister->get_best_style_match (family, style); + + // Create are own store of styles for selected font-family (the font-family selected + // in the dialog may not be the same as stored in the font-lister class until the + // "Apply" button is triggered). + Gtk::TreeModel::iterator it_best; + FontLister::FontStyleListClass FontStyleList; + Glib::RefPtr<Gtk::ListStore> local_style_list_store = Gtk::ListStore::create(FontStyleList); + + // Build list and find best match. + for ( ; styles; styles = styles->next ) { + Gtk::TreeModel::iterator treeModelIter = local_style_list_store->append(); + (*treeModelIter)[FontStyleList.cssStyle] = ((StyleNames *)styles->data)->CssName; + (*treeModelIter)[FontStyleList.displayStyle] = ((StyleNames *)styles->data)->DisplayName; + if (best == ((StyleNames*)styles->data)->CssName) { + it_best = treeModelIter; + } + } + + // Attach store to tree view and select row. + style_treeview.set_model (local_style_list_store); + if (it_best) { + style_treeview.get_selection()->select (it_best); + } + + signal_block = false; + + // Let world know + changed_emit(); +} + +void +FontSelector::on_style_changed() { + if (signal_block) return; + + // Update variations widget if new style selected from style widget. + signal_block = true; + Glib::ustring fontspec = get_fontspec( false ); + update_variations(fontspec); + signal_block = false; + + // Let world know + changed_emit(); +} + +void +FontSelector::on_size_changed() { + + if (signal_block) return; + + double size; + Glib::ustring input = size_combobox.get_active_text(); + try { + size = std::stod (input); + } + catch (std::invalid_argument) { + std::cerr << "FontSelector::on_size_changed: Invalid input: " << input << std::endl; + size = -1; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + // Arbitrary: Text and Font preview freezes with huge font sizes. + int max_size = prefs->getInt("/dialogs/textandfont/maxFontSize", 10000); + + if (size <= 0) { + return; + } + if (size > max_size) + size = max_size; + + if (fabs(font_size - size) > 0.001) { + font_size = size; + // Let world know + changed_emit(); + } +} + +void +FontSelector::on_variations_changed() { + + if (signal_block) return; + + // Let world know + changed_emit(); +} + +void +FontSelector::changed_emit() { + signal_block = true; + signal_changed.emit (get_fontspec()); + signal_block = false; +} + +void FontSelector::update_variations(const Glib::ustring& fontspec) { + font_variations.update(fontspec); + + // Check if there are any variations available; if not, don't expand font_variations_scroll + bool hasContent = font_variations.variations_present(); + font_variations_scroll.set_vexpand(hasContent); +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/font-selector.h b/src/ui/widget/font-selector.h new file mode 100644 index 0000000..137d411 --- /dev/null +++ b/src/ui/widget/font-selector.h @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2018 Tavmong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * + * + * The routines here create and manage a font selector widget with three parts, + * one each for font-family, font-style, and font-size. + * + * It is used by the TextEdit and Glyphs panel dialogs. The FontLister class is used + * to access the list of font-families and their associated styles for fonts either + * on the system or in the document. The FontLister class is also used by the Text + * toolbar. Fonts are kept track of by their "fontspecs" which are the same as the + * strings that Pango generates. + * + * The main functions are: + * Create the font-seletor widget. + * Update the lists when a new text selection is made. + * Update the Style list when a new font-family is selected, highlighting the + * best match to the original font style (as not all fonts have the same style options). + * Emit a signal when any change is made so that the Text Preview can be updated. + * Provide the currently selected values. + */ + +#ifndef INKSCAPE_UI_WIDGET_FONT_SELECTOR_H +#define INKSCAPE_UI_WIDGET_FONT_SELECTOR_H + +#include <gtkmm/grid.h> +#include <gtkmm/frame.h> +#include <gtkmm/scrolledwindow.h> +#include <gtkmm/treeview.h> +#include <gtkmm/label.h> +#include <gtkmm/comboboxtext.h> + +#include "ui/widget/font-variations.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A container of widgets for selecting font faces. + * + * It is used by the TextEdit and Glyphs panel dialogs. The FontSelector class utilizes the + * FontLister class to obtain a list of font-families and their associated styles for fonts either + * on the system or in the document. The FontLister class is also used by the Text toolbar. Fonts + * are kept track of by their "fontspecs" which are the same as the strings that Pango generates. + * + * The main functions are: + * Create the font-selector widget. + * Update the child widgets when a new text selection is made. + * Update the Style list when a new font-family is selected, highlighting the + * best match to the original font style (as not all fonts have the same style options). + * Emit a signal when any change is made to a child widget. + */ +class FontSelector : public Gtk::Grid +{ + +public: + + /** + * Constructor + */ + FontSelector (bool with_size = true, bool with_variations = true); + +protected: + + // Font family + Gtk::Frame family_frame; + Gtk::ScrolledWindow family_scroll; + Gtk::TreeView family_treeview; + Gtk::TreeViewColumn family_treecolumn; + Gtk::CellRendererText family_cell; + + // Font style + Gtk::Frame style_frame; + Gtk::ScrolledWindow style_scroll; + Gtk::TreeView style_treeview; + Gtk::TreeViewColumn style_treecolumn; + Gtk::CellRendererText style_cell; + + // Font size + Gtk::Label size_label; + Gtk::ComboBoxText size_combobox; + + // Font variations + Gtk::ScrolledWindow font_variations_scroll; + FontVariations font_variations; + +private: + + // Set sizes in font size combobox. + void set_sizes(); + void set_fontsize_tooltip(); + + // Use font style when listing style names. + void style_cell_data_func (Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter); + + // Signal handlers + void on_family_changed(); + void on_style_changed(); + void on_size_changed(); + void on_variations_changed(); + + // Signals + sigc::signal<void, Glib::ustring> signal_changed; + void changed_emit(); + bool signal_block; + + // Variables + double font_size; + + // control font variations update and UI element size + void update_variations(const Glib::ustring& fontspec); + +public: + + /** + * Update GUI based on fontspec + */ + void update_font (); + void update_size (double size); + + /** + * Get fontspec based on current settings. (Does not handle size, yet.) + */ + Glib::ustring get_fontspec(bool use_variations = true); + + /** + * Get font size. Could be merged with fontspec. + */ + double get_fontsize() { return font_size; }; + + /** + * Let others know that user has changed GUI settings. + * (Used to enable 'Apply' and 'Default' buttons.) + */ + sigc::connection connectChanged(sigc::slot<void, Glib::ustring> slot) { + return signal_changed.connect(slot); + } +}; + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_FONT_SETTINGS_H + +/* + 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 : diff --git a/src/ui/widget/font-variants.cpp b/src/ui/widget/font-variants.cpp new file mode 100644 index 0000000..1087431 --- /dev/null +++ b/src/ui/widget/font-variants.cpp @@ -0,0 +1,1461 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2015, 2018 Tavmong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm.h> +#include <glibmm/i18n.h> + +#include <libnrtype/font-instance.h> + +#include "font-variants.h" + +// For updating from selection +#include "desktop.h" +#include "object/sp-text.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + + // A simple class to handle UI for one feature. We could of derived this from Gtk::HBox but by + // attaching widgets directly to Gtk::Grid, we keep columns lined up (which may or may not be a + // good thing). + class Feature + { + public: + Feature( const Glib::ustring& name, OTSubstitution& glyphs, int options, Glib::ustring family, Gtk::Grid& grid, int &row, FontVariants* parent) + : _name (name) + , _options (options) + { + Gtk::Label* table_name = Gtk::manage (new Gtk::Label()); + table_name->set_markup ("\"" + name + "\" "); + + grid.attach (*table_name, 0, row, 1, 1); + + Gtk::FlowBox* flow_box = nullptr; + Gtk::ScrolledWindow* scrolled_window = nullptr; + if (options > 2) { + // If there are more than 2 option, pack them into a flowbox instead of directly putting them in the grid. + // Some fonts might have a table with many options (Bungee Hairline table 'ornm' has 113 entries). + flow_box = Gtk::manage (new Gtk::FlowBox()); + flow_box->set_selection_mode(); // Turn off selection + flow_box->set_homogeneous(); + flow_box->set_max_children_per_line (100); // Override default value + flow_box->set_min_children_per_line (10); // Override default value + + // We pack this into a scrollbar... otherwise the minimum height is set to what is required to fit all + // flow box children into the flow box when the flow box has minimum width. (Crazy if you ask me!) + scrolled_window = Gtk::manage (new Gtk::ScrolledWindow()); + scrolled_window->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + scrolled_window->add(*flow_box); + } + + Gtk::RadioButton::Group group; + for (int i = 0; i < options; ++i) { + + // Create radio button and create or add to button group. + Gtk::RadioButton* button = Gtk::manage (new Gtk::RadioButton()); + if (i == 0) { + group = button->get_group(); + } else { + button->set_group (group); + } + button->signal_clicked().connect ( sigc::mem_fun(*parent, &FontVariants::feature_callback) ); + buttons.push_back (button); + + // Create label. + Gtk::Label* label = Gtk::manage (new Gtk::Label()); + + // Restrict label width (some fonts have lots of alternatives). + label->set_line_wrap( true ); + label->set_line_wrap_mode( Pango::WRAP_WORD_CHAR ); + label->set_ellipsize( Pango::ELLIPSIZE_END ); + label->set_lines(3); + label->set_hexpand(); + + Glib::ustring markup; + markup += "<span font_family='"; + markup += family; + markup += "' font_features='"; + markup += name; + markup += " "; + markup += std::to_string (i); + markup += "'>"; + markup += Glib::Markup::escape_text (glyphs.input); + markup += "</span>"; + label->set_markup (markup); + + // Add button and label to widget + if (!flow_box) { + // Attach directly to grid (keeps things aligned row-to-row). + grid.attach (*button, 2*i+1, row, 1, 1); + grid.attach (*label, 2*i+2, row, 1, 1); + } else { + // Pack into FlowBox + + // Pack button and label into a box so they stay together. + Gtk::Box* box = Gtk::manage (new Gtk::Box()); + box->add(*button); + box->add(*label); + + flow_box->add(*box); + } + } + + if (scrolled_window) { + grid.attach (*scrolled_window, 1, row, 4, 1); + } + } + + Glib::ustring + get_css() + { + int i = 0; + for (auto b: buttons) { + if (b->get_active()) { + if (i == 0) { + // Features are always off by default (for those handled here). + return ""; + } else if (i == 1) { + // Feature without value has implied value of 1. + return ("\"" + _name + "\", "); + } else { + // Feature with value greater than 1 must be explicitly set. + return ("\"" + _name + "\" " + std::to_string (i) + ", "); + } + } + ++i; + } + return ""; + } + + void + set_active(int i) + { + if (i < buttons.size()) { + buttons[i]->set_active(); + } + } + + private: + Glib::ustring _name; + int _options; + std::vector <Gtk::RadioButton*> buttons; + }; + + FontVariants::FontVariants () : + Gtk::VBox (), + _ligatures_frame ( Glib::ustring(C_("Font feature", "Ligatures" )) ), + _ligatures_common ( Glib::ustring(C_("Font feature", "Common" )) ), + _ligatures_discretionary ( Glib::ustring(C_("Font feature", "Discretionary")) ), + _ligatures_historical ( Glib::ustring(C_("Font feature", "Historical" )) ), + _ligatures_contextual ( Glib::ustring(C_("Font feature", "Contextual" )) ), + + _position_frame ( Glib::ustring(C_("Font feature", "Position" )) ), + _position_normal ( Glib::ustring(C_("Font feature", "Normal" )) ), + _position_sub ( Glib::ustring(C_("Font feature", "Subscript" )) ), + _position_super ( Glib::ustring(C_("Font feature", "Superscript" )) ), + + _caps_frame ( Glib::ustring(C_("Font feature", "Capitals" )) ), + _caps_normal ( Glib::ustring(C_("Font feature", "Normal" )) ), + _caps_small ( Glib::ustring(C_("Font feature", "Small" )) ), + _caps_all_small ( Glib::ustring(C_("Font feature", "All small" )) ), + _caps_petite ( Glib::ustring(C_("Font feature", "Petite" )) ), + _caps_all_petite ( Glib::ustring(C_("Font feature", "All petite" )) ), + _caps_unicase ( Glib::ustring(C_("Font feature", "Unicase" )) ), + _caps_titling ( Glib::ustring(C_("Font feature", "Titling" )) ), + + _numeric_frame ( Glib::ustring(C_("Font feature", "Numeric" )) ), + _numeric_lining ( Glib::ustring(C_("Font feature", "Lining" )) ), + _numeric_old_style ( Glib::ustring(C_("Font feature", "Old Style" )) ), + _numeric_default_style ( Glib::ustring(C_("Font feature", "Default Style")) ), + _numeric_proportional ( Glib::ustring(C_("Font feature", "Proportional" )) ), + _numeric_tabular ( Glib::ustring(C_("Font feature", "Tabular" )) ), + _numeric_default_width ( Glib::ustring(C_("Font feature", "Default Width")) ), + _numeric_diagonal ( Glib::ustring(C_("Font feature", "Diagonal" )) ), + _numeric_stacked ( Glib::ustring(C_("Font feature", "Stacked" )) ), + _numeric_default_fractions( Glib::ustring(C_("Font feature", "Default Fractions")) ), + _numeric_ordinal ( Glib::ustring(C_("Font feature", "Ordinal" )) ), + _numeric_slashed_zero ( Glib::ustring(C_("Font feature", "Slashed Zero" )) ), + + _asian_frame ( Glib::ustring(C_("Font feature", "East Asian" )) ), + _asian_default_variant ( Glib::ustring(C_("Font feature", "Default" )) ), + _asian_jis78 ( Glib::ustring(C_("Font feature", "JIS78" )) ), + _asian_jis83 ( Glib::ustring(C_("Font feature", "JIS83" )) ), + _asian_jis90 ( Glib::ustring(C_("Font feature", "JIS90" )) ), + _asian_jis04 ( Glib::ustring(C_("Font feature", "JIS04" )) ), + _asian_simplified ( Glib::ustring(C_("Font feature", "Simplified" )) ), + _asian_traditional ( Glib::ustring(C_("Font feature", "Traditional" )) ), + _asian_default_width ( Glib::ustring(C_("Font feature", "Default" )) ), + _asian_full_width ( Glib::ustring(C_("Font feature", "Full Width" )) ), + _asian_proportional_width ( Glib::ustring(C_("Font feature", "Proportional" )) ), + _asian_ruby ( Glib::ustring(C_("Font feature", "Ruby" )) ), + + _feature_frame ( Glib::ustring(C_("Font feature", "Feature Settings")) ), + _feature_label ( Glib::ustring(C_("Font feature", "Selection has different Feature Settings!")) ), + + _ligatures_changed( false ), + _position_changed( false ), + _caps_changed( false ), + _numeric_changed( false ), + _asian_changed( false ) + + { + + set_name ( "FontVariants" ); + + // Ligatures -------------------------- + + // Add tooltips + _ligatures_common.set_tooltip_text( + _("Common ligatures. On by default. OpenType tables: 'liga', 'clig'")); + _ligatures_discretionary.set_tooltip_text( + _("Discretionary ligatures. Off by default. OpenType table: 'dlig'")); + _ligatures_historical.set_tooltip_text( + _("Historical ligatures. Off by default. OpenType table: 'hlig'")); + _ligatures_contextual.set_tooltip_text( + _("Contextual forms. On by default. OpenType table: 'calt'")); + + // Add signals + _ligatures_common.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) ); + _ligatures_discretionary.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) ); + _ligatures_historical.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) ); + _ligatures_contextual.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::ligatures_callback) ); + + // Restrict label widths (some fonts have lots of ligatures). Must also set ellipsize mode. + _ligatures_label_common.set_max_width_chars( 60 ); + _ligatures_label_discretionary.set_max_width_chars( 60 ); + _ligatures_label_historical.set_max_width_chars( 60 ); + _ligatures_label_contextual.set_max_width_chars( 60 ); + + _ligatures_label_common.set_ellipsize( Pango::ELLIPSIZE_END ); + _ligatures_label_discretionary.set_ellipsize( Pango::ELLIPSIZE_END ); + _ligatures_label_historical.set_ellipsize( Pango::ELLIPSIZE_END ); + _ligatures_label_contextual.set_ellipsize( Pango::ELLIPSIZE_END ); + + _ligatures_label_common.set_lines( 5 ); + _ligatures_label_discretionary.set_lines( 5 ); + _ligatures_label_historical.set_lines( 5 ); + _ligatures_label_contextual.set_lines( 5 ); + + // Allow user to select characters. Not useful as this selects the ligatures. + // _ligatures_label_common.set_selectable( true ); + // _ligatures_label_discretionary.set_selectable( true ); + // _ligatures_label_historical.set_selectable( true ); + // _ligatures_label_contextual.set_selectable( true ); + + // Add to frame + _ligatures_grid.attach( _ligatures_common, 0, 0, 1, 1); + _ligatures_grid.attach( _ligatures_discretionary, 0, 1, 1, 1); + _ligatures_grid.attach( _ligatures_historical, 0, 2, 1, 1); + _ligatures_grid.attach( _ligatures_contextual, 0, 3, 1, 1); + _ligatures_grid.attach( _ligatures_label_common, 1, 0, 1, 1); + _ligatures_grid.attach( _ligatures_label_discretionary, 1, 1, 1, 1); + _ligatures_grid.attach( _ligatures_label_historical, 1, 2, 1, 1); + _ligatures_grid.attach( _ligatures_label_contextual, 1, 3, 1, 1); + + _ligatures_grid.set_margin_start(15); + _ligatures_grid.set_margin_end(15); + + _ligatures_frame.add( _ligatures_grid ); + pack_start( _ligatures_frame, Gtk::PACK_SHRINK ); + + ligatures_init(); + + // Position ---------------------------------- + + // Add tooltips + _position_normal.set_tooltip_text( _("Normal position.")); + _position_sub.set_tooltip_text( _("Subscript. OpenType table: 'subs'") ); + _position_super.set_tooltip_text( _("Superscript. OpenType table: 'sups'") ); + + // Group buttons + Gtk::RadioButton::Group position_group = _position_normal.get_group(); + _position_sub.set_group(position_group); + _position_super.set_group(position_group); + + // Add signals + _position_normal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) ); + _position_sub.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) ); + _position_super.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::position_callback) ); + + // Add to frame + _position_grid.attach( _position_normal, 0, 0, 1, 1); + _position_grid.attach( _position_sub, 1, 0, 1, 1); + _position_grid.attach( _position_super, 2, 0, 1, 1); + + _position_grid.set_margin_start(15); + _position_grid.set_margin_end(15); + + _position_frame.add( _position_grid ); + pack_start( _position_frame, Gtk::PACK_SHRINK ); + + position_init(); + + // Caps ---------------------------------- + + // Add tooltips + _caps_normal.set_tooltip_text( _("Normal capitalization.")); + _caps_small.set_tooltip_text( _("Small-caps (lowercase). OpenType table: 'smcp'")); + _caps_all_small.set_tooltip_text( _("All small-caps (uppercase and lowercase). OpenType tables: 'c2sc' and 'smcp'")); + _caps_petite.set_tooltip_text( _("Petite-caps (lowercase). OpenType table: 'pcap'")); + _caps_all_petite.set_tooltip_text( _("All petite-caps (uppercase and lowercase). OpenType tables: 'c2sc' and 'pcap'")); + _caps_unicase.set_tooltip_text( _("Unicase (small caps for uppercase, normal for lowercase). OpenType table: 'unic'")); + _caps_titling.set_tooltip_text( _("Titling caps (lighter-weight uppercase for use in titles). OpenType table: 'titl'")); + + // Group buttons + Gtk::RadioButton::Group caps_group = _caps_normal.get_group(); + _caps_small.set_group(caps_group); + _caps_all_small.set_group(caps_group); + _caps_petite.set_group(caps_group); + _caps_all_petite.set_group(caps_group); + _caps_unicase.set_group(caps_group); + _caps_titling.set_group(caps_group); + + // Add signals + _caps_normal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) ); + _caps_small.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) ); + _caps_all_small.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) ); + _caps_petite.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) ); + _caps_all_petite.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) ); + _caps_unicase.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) ); + _caps_titling.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::caps_callback) ); + + // Add to frame + _caps_grid.attach( _caps_normal, 0, 0, 1, 1); + _caps_grid.attach( _caps_unicase, 1, 0, 1, 1); + _caps_grid.attach( _caps_titling, 2, 0, 1, 1); + _caps_grid.attach( _caps_small, 0, 1, 1, 1); + _caps_grid.attach( _caps_all_small, 1, 1, 1, 1); + _caps_grid.attach( _caps_petite, 2, 1, 1, 1); + _caps_grid.attach( _caps_all_petite, 3, 1, 1, 1); + + _caps_grid.set_margin_start(15); + _caps_grid.set_margin_end(15); + + _caps_frame.add( _caps_grid ); + pack_start( _caps_frame, Gtk::PACK_SHRINK ); + + caps_init(); + + // Numeric ------------------------------ + + // Add tooltips + _numeric_default_style.set_tooltip_text( _("Normal style.")); + _numeric_lining.set_tooltip_text( _("Lining numerals. OpenType table: 'lnum'")); + _numeric_old_style.set_tooltip_text( _("Old style numerals. OpenType table: 'onum'")); + _numeric_default_width.set_tooltip_text( _("Normal widths.")); + _numeric_proportional.set_tooltip_text( _("Proportional width numerals. OpenType table: 'pnum'")); + _numeric_tabular.set_tooltip_text( _("Same width numerals. OpenType table: 'tnum'")); + _numeric_default_fractions.set_tooltip_text( _("Normal fractions.")); + _numeric_diagonal.set_tooltip_text( _("Diagonal fractions. OpenType table: 'frac'")); + _numeric_stacked.set_tooltip_text( _("Stacked fractions. OpenType table: 'afrc'")); + _numeric_ordinal.set_tooltip_text( _("Ordinals (raised 'th', etc.). OpenType table: 'ordn'")); + _numeric_slashed_zero.set_tooltip_text( _("Slashed zeros. OpenType table: 'zero'")); + + // Group buttons + Gtk::RadioButton::Group style_group = _numeric_default_style.get_group(); + _numeric_lining.set_group(style_group); + _numeric_old_style.set_group(style_group); + + Gtk::RadioButton::Group width_group = _numeric_default_width.get_group(); + _numeric_proportional.set_group(width_group); + _numeric_tabular.set_group(width_group); + + Gtk::RadioButton::Group fraction_group = _numeric_default_fractions.get_group(); + _numeric_diagonal.set_group(fraction_group); + _numeric_stacked.set_group(fraction_group); + + // Add signals + _numeric_default_style.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) ); + _numeric_lining.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) ); + _numeric_old_style.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) ); + _numeric_default_width.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) ); + _numeric_proportional.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) ); + _numeric_tabular.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) ); + _numeric_default_fractions.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) ); + _numeric_diagonal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) ); + _numeric_stacked.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) ); + _numeric_ordinal.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) ); + _numeric_slashed_zero.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::numeric_callback) ); + + // Add to frame + _numeric_grid.attach (_numeric_default_style, 0, 0, 1, 1); + _numeric_grid.attach (_numeric_lining, 1, 0, 1, 1); + _numeric_grid.attach (_numeric_lining_label, 2, 0, 1, 1); + _numeric_grid.attach (_numeric_old_style, 3, 0, 1, 1); + _numeric_grid.attach (_numeric_old_style_label, 4, 0, 1, 1); + + _numeric_grid.attach (_numeric_default_width, 0, 1, 1, 1); + _numeric_grid.attach (_numeric_proportional, 1, 1, 1, 1); + _numeric_grid.attach (_numeric_proportional_label, 2, 1, 1, 1); + _numeric_grid.attach (_numeric_tabular, 3, 1, 1, 1); + _numeric_grid.attach (_numeric_tabular_label, 4, 1, 1, 1); + + _numeric_grid.attach (_numeric_default_fractions, 0, 2, 1, 1); + _numeric_grid.attach (_numeric_diagonal, 1, 2, 1, 1); + _numeric_grid.attach (_numeric_diagonal_label, 2, 2, 1, 1); + _numeric_grid.attach (_numeric_stacked, 3, 2, 1, 1); + _numeric_grid.attach (_numeric_stacked_label, 4, 2, 1, 1); + + _numeric_grid.attach (_numeric_ordinal, 0, 3, 1, 1); + _numeric_grid.attach (_numeric_ordinal_label, 1, 3, 4, 1); + + _numeric_grid.attach (_numeric_slashed_zero, 0, 4, 1, 1); + _numeric_grid.attach (_numeric_slashed_zero_label, 1, 4, 1, 1); + + _numeric_grid.set_margin_start(15); + _numeric_grid.set_margin_end(15); + + _numeric_frame.add( _numeric_grid ); + pack_start( _numeric_frame, Gtk::PACK_SHRINK ); + + // East Asian + + // Add tooltips + _asian_default_variant.set_tooltip_text ( _("Default variant.")); + _asian_jis78.set_tooltip_text( _("JIS78 forms. OpenType table: 'jp78'.")); + _asian_jis83.set_tooltip_text( _("JIS83 forms. OpenType table: 'jp83'.")); + _asian_jis90.set_tooltip_text( _("JIS90 forms. OpenType table: 'jp90'.")); + _asian_jis04.set_tooltip_text( _("JIS2004 forms. OpenType table: 'jp04'.")); + _asian_simplified.set_tooltip_text( _("Simplified forms. OpenType table: 'smpl'.")); + _asian_traditional.set_tooltip_text( _("Traditional forms. OpenType table: 'trad'.")); + _asian_default_width.set_tooltip_text ( _("Default width.")); + _asian_full_width.set_tooltip_text( _("Full width variants. OpenType table: 'fwid'.")); + _asian_proportional_width.set_tooltip_text(_("Proportional width variants. OpenType table: 'pwid'.")); + _asian_ruby.set_tooltip_text( _("Ruby variants. OpenType table: 'ruby'.")); + + // Add signals + _asian_default_variant.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) ); + _asian_jis78.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) ); + _asian_jis83.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) ); + _asian_jis90.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) ); + _asian_jis04.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) ); + _asian_simplified.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) ); + _asian_traditional.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) ); + _asian_default_width.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) ); + _asian_full_width.signal_clicked().connect ( sigc::mem_fun(*this, &FontVariants::asian_callback) ); + _asian_proportional_width.signal_clicked().connect (sigc::mem_fun(*this, &FontVariants::asian_callback) ); + _asian_ruby.signal_clicked().connect( sigc::mem_fun(*this, &FontVariants::asian_callback) ); + + // Add to frame + _asian_grid.attach (_asian_default_variant, 0, 0, 1, 1); + _asian_grid.attach (_asian_jis78, 1, 0, 1, 1); + _asian_grid.attach (_asian_jis83, 2, 0, 1, 1); + _asian_grid.attach (_asian_jis90, 1, 1, 1, 1); + _asian_grid.attach (_asian_jis04, 2, 1, 1, 1); + _asian_grid.attach (_asian_simplified, 1, 2, 1, 1); + _asian_grid.attach (_asian_traditional, 2, 2, 1, 1); + _asian_grid.attach (_asian_default_width, 0, 3, 1, 1); + _asian_grid.attach (_asian_full_width, 1, 3, 1, 1); + _asian_grid.attach (_asian_proportional_width, 2, 3, 1, 1); + _asian_grid.attach (_asian_ruby, 0, 4, 1, 1); + + _asian_grid.set_margin_start(15); + _asian_grid.set_margin_end(15); + + _asian_frame.add( _asian_grid ); + pack_start( _asian_frame, Gtk::PACK_SHRINK ); + + // Group Buttons + Gtk::RadioButton::Group asian_variant_group = _asian_default_variant.get_group(); + _asian_jis78.set_group(asian_variant_group); + _asian_jis83.set_group(asian_variant_group); + _asian_jis90.set_group(asian_variant_group); + _asian_jis04.set_group(asian_variant_group); + _asian_simplified.set_group(asian_variant_group); + _asian_traditional.set_group(asian_variant_group); + + Gtk::RadioButton::Group asian_width_group = _asian_default_width.get_group(); + _asian_full_width.set_group (asian_width_group); + _asian_proportional_width.set_group (asian_width_group); + + // Feature settings --------------------- + + // Add tooltips + _feature_entry.set_tooltip_text( _("Feature settings in CSS form (e.g. \"wxyz\" or \"wxyz\" 3).")); + + _feature_substitutions.set_justify( Gtk::JUSTIFY_LEFT ); + _feature_substitutions.set_line_wrap( true ); + _feature_substitutions.set_line_wrap_mode( Pango::WRAP_WORD_CHAR ); + + _feature_list.set_justify( Gtk::JUSTIFY_LEFT ); + _feature_list.set_line_wrap( true ); + + // Add to frame + _feature_vbox.pack_start( _feature_grid, Gtk::PACK_SHRINK ); + _feature_vbox.pack_start( _feature_entry, Gtk::PACK_SHRINK ); + _feature_vbox.pack_start( _feature_label, Gtk::PACK_SHRINK ); + _feature_vbox.pack_start( _feature_substitutions, Gtk::PACK_SHRINK ); + _feature_vbox.pack_start( _feature_list, Gtk::PACK_SHRINK ); + + _feature_vbox.set_margin_start(15); + _feature_vbox.set_margin_end(15); + + _feature_frame.add( _feature_vbox ); + pack_start( _feature_frame, Gtk::PACK_SHRINK ); + + // Add signals + //_feature_entry.signal_key_press_event().connect ( sigc::mem_fun(*this, &FontVariants::feature_callback) ); + _feature_entry.signal_changed().connect( sigc::mem_fun(*this, &FontVariants::feature_callback) ); + + show_all_children(); + + } + + void + FontVariants::ligatures_init() { + // std::cout << "FontVariants::ligatures_init()" << std::endl; + } + + void + FontVariants::ligatures_callback() { + // std::cout << "FontVariants::ligatures_callback()" << std::endl; + _ligatures_changed = true; + _changed_signal.emit(); + } + + void + FontVariants::position_init() { + // std::cout << "FontVariants::position_init()" << std::endl; + } + + void + FontVariants::position_callback() { + // std::cout << "FontVariants::position_callback()" << std::endl; + _position_changed = true; + _changed_signal.emit(); + } + + void + FontVariants::caps_init() { + // std::cout << "FontVariants::caps_init()" << std::endl; + } + + void + FontVariants::caps_callback() { + // std::cout << "FontVariants::caps_callback()" << std::endl; + _caps_changed = true; + _changed_signal.emit(); + } + + void + FontVariants::numeric_init() { + // std::cout << "FontVariants::numeric_init()" << std::endl; + } + + void + FontVariants::numeric_callback() { + // std::cout << "FontVariants::numeric_callback()" << std::endl; + _numeric_changed = true; + _changed_signal.emit(); + } + + void + FontVariants::asian_init() { + // std::cout << "FontVariants::asian_init()" << std::endl; + } + + void + FontVariants::asian_callback() { + // std::cout << "FontVariants::asian_callback()" << std::endl; + _asian_changed = true; + _changed_signal.emit(); + } + + void + FontVariants::feature_init() { + // std::cout << "FontVariants::feature_init()" << std::endl; + } + + void + FontVariants::feature_callback() { + // std::cout << "FontVariants::feature_callback()" << std::endl; + _feature_changed = true; + _changed_signal.emit(); + } + + // Update GUI based on query. + void + FontVariants::update( SPStyle const *query, bool different_features, Glib::ustring& font_spec ) { + + update_opentype( font_spec ); + + _ligatures_all = query->font_variant_ligatures.computed; + _ligatures_mix = query->font_variant_ligatures.value; + + _ligatures_common.set_active( _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_COMMON ); + _ligatures_discretionary.set_active(_ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY ); + _ligatures_historical.set_active( _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL ); + _ligatures_contextual.set_active( _ligatures_all & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL ); + + _ligatures_common.set_inconsistent( _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_COMMON ); + _ligatures_discretionary.set_inconsistent( _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY ); + _ligatures_historical.set_inconsistent( _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL ); + _ligatures_contextual.set_inconsistent( _ligatures_mix & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL ); + + _position_all = query->font_variant_position.computed; + _position_mix = query->font_variant_position.value; + + _position_normal.set_active( _position_all & SP_CSS_FONT_VARIANT_POSITION_NORMAL ); + _position_sub.set_active( _position_all & SP_CSS_FONT_VARIANT_POSITION_SUB ); + _position_super.set_active( _position_all & SP_CSS_FONT_VARIANT_POSITION_SUPER ); + + _position_normal.set_inconsistent( _position_mix & SP_CSS_FONT_VARIANT_POSITION_NORMAL ); + _position_sub.set_inconsistent( _position_mix & SP_CSS_FONT_VARIANT_POSITION_SUB ); + _position_super.set_inconsistent( _position_mix & SP_CSS_FONT_VARIANT_POSITION_SUPER ); + + _caps_all = query->font_variant_caps.computed; + _caps_mix = query->font_variant_caps.value; + + _caps_normal.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_NORMAL ); + _caps_small.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_SMALL ); + _caps_all_small.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL ); + _caps_petite.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_PETITE ); + _caps_all_petite.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE ); + _caps_unicase.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_UNICASE ); + _caps_titling.set_active( _caps_all & SP_CSS_FONT_VARIANT_CAPS_TITLING ); + + _caps_normal.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_NORMAL ); + _caps_small.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_SMALL ); + _caps_all_small.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL ); + _caps_petite.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_PETITE ); + _caps_all_petite.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE ); + _caps_unicase.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_UNICASE ); + _caps_titling.set_inconsistent( _caps_mix & SP_CSS_FONT_VARIANT_CAPS_TITLING ); + + _numeric_all = query->font_variant_numeric.computed; + _numeric_mix = query->font_variant_numeric.value; + + if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS) { + _numeric_lining.set_active(); + } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS) { + _numeric_old_style.set_active(); + } else { + _numeric_default_style.set_active(); + } + + if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS) { + _numeric_proportional.set_active(); + } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS) { + _numeric_tabular.set_active(); + } else { + _numeric_default_width.set_active(); + } + + if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS) { + _numeric_diagonal.set_active(); + } else if (_numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS) { + _numeric_stacked.set_active(); + } else { + _numeric_default_fractions.set_active(); + } + + _numeric_ordinal.set_active( _numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL ); + _numeric_slashed_zero.set_active( _numeric_all & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO ); + + + _numeric_lining.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS ); + _numeric_old_style.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS ); + _numeric_proportional.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS ); + _numeric_tabular.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS ); + _numeric_diagonal.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS ); + _numeric_stacked.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS ); + _numeric_ordinal.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL ); + _numeric_slashed_zero.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO ); + + _asian_all = query->font_variant_east_asian.computed; + _asian_mix = query->font_variant_east_asian.value; + + if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78) { + _asian_jis78.set_active(); + } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83) { + _asian_jis83.set_active(); + } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90) { + _asian_jis90.set_active(); + } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04) { + _asian_jis04.set_active(); + } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED) { + _asian_simplified.set_active(); + } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL) { + _asian_traditional.set_active(); + } else { + _asian_default_variant.set_active(); + } + + if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH) { + _asian_full_width.set_active(); + } else if (_asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH) { + _asian_proportional_width.set_active(); + } else { + _asian_default_width.set_active(); + } + + _asian_ruby.set_active ( _asian_all & SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY ); + + _asian_jis78.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78); + _asian_jis83.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83); + _asian_jis90.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90); + _asian_jis04.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04); + _asian_simplified.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED); + _asian_traditional.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL); + _asian_full_width.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH); + _asian_proportional_width.set_inconsistent(_asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH); + _asian_ruby.set_inconsistent( _asian_mix & SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY); + + // Fix me: Should match a space if second part matches. ---, + // : Add boundary to 'on' and 'off'. v + Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("\"(\\w{4})\"\\s*([0-9]+|on|off|)"); + Glib::MatchInfo matchInfo; + std::string setting; + + // Set feature radiobutton (if it exists) or add to _feature_entry string. + char const *val = query->font_feature_settings.value(); + if (val) { + + std::vector<Glib::ustring> tokens = + Glib::Regex::split_simple("\\s*,\\s*", val); + + for (auto token: tokens) { + regex->match(token, matchInfo); + if (matchInfo.matches()) { + Glib::ustring table = matchInfo.fetch(1); + Glib::ustring value = matchInfo.fetch(2); + + if (_features.find(table) != _features.end()) { + int v = 0; + if (value == "0" || value == "off") v = 0; + else if (value == "1" || value == "on" || value.empty() ) v = 1; + else v = std::stoi(value); + _features[table]->set_active(v); + } else { + setting += token + ", "; + } + } + } + } + + // Remove final ", " + if (setting.length() > 1) { + setting.pop_back(); + setting.pop_back(); + } + + // Tables without radiobuttons. + _feature_entry.set_text( setting ); + + if( different_features ) { + _feature_label.show(); + } else { + _feature_label.hide(); + } + } + + // Update GUI based on OpenType tables of selected font (which may be changed in font selector tab). + void + FontVariants::update_opentype (Glib::ustring& font_spec) { + + // Disable/Enable based on available OpenType tables. + font_instance* res = font_factory::Default()->FaceFromFontSpecification( font_spec.c_str() ); + if( res ) { + + std::map<Glib::ustring, OTSubstitution>::iterator it; + + if((it = res->openTypeTables.find("liga"))!= res->openTypeTables.end() || + (it = res->openTypeTables.find("clig"))!= res->openTypeTables.end()) { + _ligatures_common.set_sensitive(); + } else { + _ligatures_common.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("dlig"))!= res->openTypeTables.end()) { + _ligatures_discretionary.set_sensitive(); + } else { + _ligatures_discretionary.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("hlig"))!= res->openTypeTables.end()) { + _ligatures_historical.set_sensitive(); + } else { + _ligatures_historical.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("calt"))!= res->openTypeTables.end()) { + _ligatures_contextual.set_sensitive(); + } else { + _ligatures_contextual.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("subs"))!= res->openTypeTables.end()) { + _position_sub.set_sensitive(); + } else { + _position_sub.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("sups"))!= res->openTypeTables.end()) { + _position_super.set_sensitive(); + } else { + _position_super.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("smcp"))!= res->openTypeTables.end()) { + _caps_small.set_sensitive(); + } else { + _caps_small.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("c2sc"))!= res->openTypeTables.end() && + (it = res->openTypeTables.find("smcp"))!= res->openTypeTables.end()) { + _caps_all_small.set_sensitive(); + } else { + _caps_all_small.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("pcap"))!= res->openTypeTables.end()) { + _caps_petite.set_sensitive(); + } else { + _caps_petite.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("c2sc"))!= res->openTypeTables.end() && + (it = res->openTypeTables.find("pcap"))!= res->openTypeTables.end()) { + _caps_all_petite.set_sensitive(); + } else { + _caps_all_petite.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("unic"))!= res->openTypeTables.end()) { + _caps_unicase.set_sensitive(); + } else { + _caps_unicase.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("titl"))!= res->openTypeTables.end()) { + _caps_titling.set_sensitive(); + } else { + _caps_titling.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("lnum"))!= res->openTypeTables.end()) { + _numeric_lining.set_sensitive(); + } else { + _numeric_lining.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("onum"))!= res->openTypeTables.end()) { + _numeric_old_style.set_sensitive(); + } else { + _numeric_old_style.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("pnum"))!= res->openTypeTables.end()) { + _numeric_proportional.set_sensitive(); + } else { + _numeric_proportional.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("tnum"))!= res->openTypeTables.end()) { + _numeric_tabular.set_sensitive(); + } else { + _numeric_tabular.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("frac"))!= res->openTypeTables.end()) { + _numeric_diagonal.set_sensitive(); + } else { + _numeric_diagonal.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("afrac"))!= res->openTypeTables.end()) { + _numeric_stacked.set_sensitive(); + } else { + _numeric_stacked.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("ordn"))!= res->openTypeTables.end()) { + _numeric_ordinal.set_sensitive(); + } else { + _numeric_ordinal.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("zero"))!= res->openTypeTables.end()) { + _numeric_slashed_zero.set_sensitive(); + } else { + _numeric_slashed_zero.set_sensitive( false ); + } + + // East-Asian + if((it = res->openTypeTables.find("jp78"))!= res->openTypeTables.end()) { + _asian_jis78.set_sensitive(); + } else { + _asian_jis78.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("jp83"))!= res->openTypeTables.end()) { + _asian_jis83.set_sensitive(); + } else { + _asian_jis83.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("jp90"))!= res->openTypeTables.end()) { + _asian_jis90.set_sensitive(); + } else { + _asian_jis90.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("jp04"))!= res->openTypeTables.end()) { + _asian_jis04.set_sensitive(); + } else { + _asian_jis04.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("smpl"))!= res->openTypeTables.end()) { + _asian_simplified.set_sensitive(); + } else { + _asian_simplified.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("trad"))!= res->openTypeTables.end()) { + _asian_traditional.set_sensitive(); + } else { + _asian_traditional.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("fwid"))!= res->openTypeTables.end()) { + _asian_full_width.set_sensitive(); + } else { + _asian_full_width.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("pwid"))!= res->openTypeTables.end()) { + _asian_proportional_width.set_sensitive(); + } else { + _asian_proportional_width.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("ruby"))!= res->openTypeTables.end()) { + _asian_ruby.set_sensitive(); + } else { + _asian_ruby.set_sensitive( false ); + } + + // List available ligatures + Glib::ustring markup_liga; + Glib::ustring markup_dlig; + Glib::ustring markup_hlig; + Glib::ustring markup_calt; + + for (auto table: res->openTypeTables) { + + if (table.first == "liga" || + table.first == "clig" || + table.first == "dlig" || + table.first == "hgli" || + table.first == "calt") { + + Glib::ustring markup; + markup += "<span font_family='"; + markup += sp_font_description_get_family(res->descr); + markup += "'>"; + markup += Glib::Markup::escape_text(table.second.output); + markup += "</span>"; + + if (table.first == "liga") markup_liga += markup; + if (table.first == "clig") markup_liga += markup; + if (table.first == "dlig") markup_dlig += markup; + if (table.first == "hlig") markup_hlig += markup; + if (table.first == "calt") markup_calt += markup; + } + } + + _ligatures_label_common.set_markup ( markup_liga.c_str() ); + _ligatures_label_discretionary.set_markup ( markup_dlig.c_str() ); + _ligatures_label_historical.set_markup ( markup_hlig.c_str() ); + _ligatures_label_contextual.set_markup ( markup_calt.c_str() ); + + // List available numeric variants + Glib::ustring markup_lnum; + Glib::ustring markup_onum; + Glib::ustring markup_pnum; + Glib::ustring markup_tnum; + Glib::ustring markup_frac; + Glib::ustring markup_afrc; + Glib::ustring markup_ordn; + Glib::ustring markup_zero; + + for (auto table: res->openTypeTables) { + + Glib::ustring markup; + markup += "<span font_family='"; + markup += sp_font_description_get_family(res->descr); + markup += "' font_features='"; + markup += table.first; + markup += "'>"; + if (table.first == "lnum" || + table.first == "onum" || + table.first == "pnum" || + table.first == "tnum") markup += "0123456789"; + if (table.first == "zero") markup += "0"; + if (table.first == "ordn") markup += "[" + table.second.before + "]" + table.second.output; + if (table.first == "frac" || + table.first == "afrc" ) markup += "1/2 2/3 3/4 4/5 5/6"; // Can we do better? + markup += "</span>"; + + if (table.first == "lnum") markup_lnum += markup; + if (table.first == "onum") markup_onum += markup; + if (table.first == "pnum") markup_pnum += markup; + if (table.first == "tnum") markup_tnum += markup; + if (table.first == "frac") markup_frac += markup; + if (table.first == "afrc") markup_afrc += markup; + if (table.first == "ordn") markup_ordn += markup; + if (table.first == "zero") markup_zero += markup; + } + + _numeric_lining_label.set_markup ( markup_lnum.c_str() ); + _numeric_old_style_label.set_markup ( markup_onum.c_str() ); + _numeric_proportional_label.set_markup ( markup_pnum.c_str() ); + _numeric_tabular_label.set_markup ( markup_tnum.c_str() ); + _numeric_diagonal_label.set_markup ( markup_frac.c_str() ); + _numeric_stacked_label.set_markup ( markup_afrc.c_str() ); + _numeric_ordinal_label.set_markup ( markup_ordn.c_str() ); + _numeric_slashed_zero_label.set_markup ( markup_zero.c_str() ); + + // Make list of tables not handled above. + std::map<Glib::ustring, OTSubstitution> table_copy = res->openTypeTables; + if( (it = table_copy.find("liga")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("clig")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("dlig")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("hlig")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("calt")) != table_copy.end() ) table_copy.erase( it ); + + if( (it = table_copy.find("subs")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("sups")) != table_copy.end() ) table_copy.erase( it ); + + if( (it = table_copy.find("smcp")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("c2sc")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("pcap")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("c2pc")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("unic")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("titl")) != table_copy.end() ) table_copy.erase( it ); + + if( (it = table_copy.find("lnum")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("onum")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("pnum")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("tnum")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("frac")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("afrc")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("ordn")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("zero")) != table_copy.end() ) table_copy.erase( it ); + + if( (it = table_copy.find("jp78")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("jp83")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("jp90")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("jp04")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("smpl")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("trad")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("fwid")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("pwid")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("ruby")) != table_copy.end() ) table_copy.erase( it ); + + // An incomplete list of tables that should not be exposed to the user: + if( (it = table_copy.find("abvf")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("abvs")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("akhn")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("blwf")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("blws")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("ccmp")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("cjct")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("dnom")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("dtls")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("fina")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("half")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("haln")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("init")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("isol")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("locl")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("medi")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("nukt")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("numr")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("pref")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("pres")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("pstf")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("psts")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("rlig")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("rkrf")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("rphf")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("rtlm")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("ssty")) != table_copy.end() ) table_copy.erase( it ); + if( (it = table_copy.find("vatu")) != table_copy.end() ) table_copy.erase( it ); + + // Clear out old features + auto children = _feature_grid.get_children(); + for (auto child: children) { + _feature_grid.remove (*child); + } + _features.clear(); + + std::string markup; + int grid_row = 0; + + // GSUB lookup type 1 (1 to 1 mapping). + for (auto table: res->openTypeTables) { + if (table.first == "case" || + table.first == "hist" || + (table.first[0] == 's' && table.first[1] == 's' && !(table.first[2] == 't'))) { + + if( (it = table_copy.find(table.first)) != table_copy.end() ) table_copy.erase( it ); + + _features[table.first] = new Feature (table.first, table.second, 2, + sp_font_description_get_family(res->descr), + _feature_grid, grid_row, this); + grid_row++; + } + } + + // GSUB lookup type 3 (1 to many mapping). Optionally type 1. + for (auto table: res->openTypeTables) { + if (table.first == "salt" || + table.first == "swsh" || + table.first == "cwsh" || + table.first == "ornm" || + table.first == "nalt" || + (table.first[0] == 'c' && table.first[1] == 'v')) { + + if (table.second.input.length() == 0) { + // This can happen if a table is not in the 'DFLT' script and 'dflt' language. + // We should be using the 'lang' attribute to find the correct tables. + // std::cerr << "FontVariants::open_type_update: " + // << table.first << " has no entries!" << std::endl; + continue; + } + + if( (it = table_copy.find(table.first)) != table_copy.end() ) table_copy.erase( it ); + + // Our lame attempt at determining number of alternative glyphs for one glyph: + int number = table.second.output.length() / table.second.input.length(); + if (number < 1) { + number = 1; // Must have at least on/off, see comment above about 'lang' attribute. + // std::cout << table.first << " " + // << table.second.output.length() << "/" + // << table.second.input.length() << "=" + // << number << std::endl; + } + + _features[table.first] = new Feature (table.first, table.second, number+1, + sp_font_description_get_family(res->descr), + _feature_grid, grid_row, this); + grid_row++; + } + } + + _feature_grid.show_all(); + + _feature_substitutions.set_markup ( markup.c_str() ); + + std::string ott_list = "OpenType tables not included above: "; + for(it = table_copy.begin(); it != table_copy.end(); ++it) { + ott_list += it->first; + ott_list += ", "; + } + + if (table_copy.size() > 0) { + ott_list.pop_back(); + ott_list.pop_back(); + _feature_list.set_text( ott_list.c_str() ); + } else { + _feature_list.set_text( "" ); + } + + } else { + std::cerr << "FontVariants::update(): Couldn't find font_instance for: " + << font_spec << std::endl; + } + + _ligatures_changed = false; + _position_changed = false; + _caps_changed = false; + _numeric_changed = false; + _feature_changed = false; + } + + void + FontVariants::fill_css( SPCSSAttr *css ) { + + // Ligatures + bool common = _ligatures_common.get_active(); + bool discretionary = _ligatures_discretionary.get_active(); + bool historical = _ligatures_historical.get_active(); + bool contextual = _ligatures_contextual.get_active(); + + if( !common && !discretionary && !historical && !contextual ) { + sp_repr_css_set_property(css, "font-variant-ligatures", "none" ); + } else if ( common && !discretionary && !historical && contextual ) { + sp_repr_css_set_property(css, "font-variant-ligatures", "normal" ); + } else { + Glib::ustring css_string; + if ( !common ) + css_string += "no-common-ligatures "; + if ( discretionary ) + css_string += "discretionary-ligatures "; + if ( historical ) + css_string += "historical-ligatures "; + if ( !contextual ) + css_string += "no-contextual "; + sp_repr_css_set_property(css, "font-variant-ligatures", css_string.c_str() ); + } + + // Position + { + unsigned position_new = SP_CSS_FONT_VARIANT_POSITION_NORMAL; + Glib::ustring css_string; + if( _position_normal.get_active() ) { + css_string = "normal"; + } else if( _position_sub.get_active() ) { + css_string = "sub"; + position_new = SP_CSS_FONT_VARIANT_POSITION_SUB; + } else if( _position_super.get_active() ) { + css_string = "super"; + position_new = SP_CSS_FONT_VARIANT_POSITION_SUPER; + } + + // 'if' may not be necessary... need to test. + if( (_position_all != position_new) || ((_position_mix != 0) && _position_changed) ) { + sp_repr_css_set_property(css, "font-variant-position", css_string.c_str() ); + } + } + + // Caps + { + //unsigned caps_new; + Glib::ustring css_string; + if( _caps_normal.get_active() ) { + css_string = "normal"; + // caps_new = SP_CSS_FONT_VARIANT_CAPS_NORMAL; + } else if( _caps_small.get_active() ) { + css_string = "small-caps"; + // caps_new = SP_CSS_FONT_VARIANT_CAPS_SMALL; + } else if( _caps_all_small.get_active() ) { + css_string = "all-small-caps"; + // caps_new = SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL; + } else if( _caps_petite.get_active() ) { + css_string = "petite"; + // caps_new = SP_CSS_FONT_VARIANT_CAPS_PETITE; + } else if( _caps_all_petite.get_active() ) { + css_string = "all-petite"; + // caps_new = SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE; + } else if( _caps_unicase.get_active() ) { + css_string = "unicase"; + // caps_new = SP_CSS_FONT_VARIANT_CAPS_UNICASE; + } else if( _caps_titling.get_active() ) { + css_string = "titling"; + // caps_new = SP_CSS_FONT_VARIANT_CAPS_TITLING; + //} else { + // caps_new = SP_CSS_FONT_VARIANT_CAPS_NORMAL; + } + + // May not be necessary... need to test. + //if( (_caps_all != caps_new) || ((_caps_mix != 0) && _caps_changed) ) { + sp_repr_css_set_property(css, "font-variant-caps", css_string.c_str() ); + //} + } + + // Numeric + bool default_style = _numeric_default_style.get_active(); + bool lining = _numeric_lining.get_active(); + bool old_style = _numeric_old_style.get_active(); + + bool default_width = _numeric_default_width.get_active(); + bool proportional = _numeric_proportional.get_active(); + bool tabular = _numeric_tabular.get_active(); + + bool default_fractions = _numeric_default_fractions.get_active(); + bool diagonal = _numeric_diagonal.get_active(); + bool stacked = _numeric_stacked.get_active(); + + bool ordinal = _numeric_ordinal.get_active(); + bool slashed_zero = _numeric_slashed_zero.get_active(); + + if (default_style & default_width & default_fractions & !ordinal & !slashed_zero) { + sp_repr_css_set_property(css, "font-variant-numeric", "normal"); + } else { + Glib::ustring css_string; + if ( lining ) + css_string += "lining-nums "; + if ( old_style ) + css_string += "oldstyle-nums "; + if ( proportional ) + css_string += "proportional-nums "; + if ( tabular ) + css_string += "tabular-nums "; + if ( diagonal ) + css_string += "diagonal-fractions "; + if ( stacked ) + css_string += "stacked-fractions "; + if ( ordinal ) + css_string += "ordinal "; + if ( slashed_zero ) + css_string += "slashed-zero "; + sp_repr_css_set_property(css, "font-variant-numeric", css_string.c_str() ); + } + + // East Asian + bool default_variant = _asian_default_variant.get_active(); + bool jis78 = _asian_jis78.get_active(); + bool jis83 = _asian_jis83.get_active(); + bool jis90 = _asian_jis90.get_active(); + bool jis04 = _asian_jis04.get_active(); + bool simplified = _asian_simplified.get_active(); + bool traditional = _asian_traditional.get_active(); + bool asian_width = _asian_default_width.get_active(); + bool fwid = _asian_full_width.get_active(); + bool pwid = _asian_proportional_width.get_active(); + bool ruby = _asian_ruby.get_active(); + + if (default_style & asian_width & !ruby) { + sp_repr_css_set_property(css, "font-variant-east-asian", "normal"); + } else { + Glib::ustring css_string; + if (jis78) css_string += "jis78 "; + if (jis83) css_string += "jis83 "; + if (jis90) css_string += "jis90 "; + if (jis04) css_string += "jis04 "; + if (simplified) css_string += "simplfied "; + if (traditional) css_string += "traditional "; + + if (fwid) css_string += "fwid "; + if (pwid) css_string += "pwid "; + + if (ruby) css_string += "ruby "; + + sp_repr_css_set_property(css, "font-variant-east-asian", css_string.c_str() ); + } + + // Feature settings + Glib::ustring feature_string; + for (auto i: _features) { + feature_string += i.second->get_css(); + } + + feature_string += _feature_entry.get_text(); + // std::cout << "feature_string: " << feature_string << std::endl; + + if (!feature_string.empty()) { + sp_repr_css_set_property(css, "font-feature-settings", feature_string.c_str()); + } else { + sp_repr_css_unset_property(css, "font-feature-settings"); + } + } + + Glib::ustring + FontVariants::get_markup() { + + Glib::ustring markup; + + // Ligatures + bool common = _ligatures_common.get_active(); + bool discretionary = _ligatures_discretionary.get_active(); + bool historical = _ligatures_historical.get_active(); + bool contextual = _ligatures_contextual.get_active(); + + if (!common) markup += "liga=0,clig=0,"; // On by default. + if (discretionary) markup += "dlig=1,"; + if (historical) markup += "hlig=1,"; + if (contextual) markup += "calt=1,"; + + // Position + if ( _position_sub.get_active() ) markup += "subs=1,"; + else if ( _position_super.get_active() ) markup += "sups=1,"; + + // Caps + if ( _caps_small.get_active() ) markup += "smcp=1,"; + else if ( _caps_all_small.get_active() ) markup += "c2sc=1,smcp=1,"; + else if ( _caps_petite.get_active() ) markup += "pcap=1,"; + else if ( _caps_all_petite.get_active() ) markup += "c2pc=1,pcap=1,"; + else if ( _caps_unicase.get_active() ) markup += "unic=1,"; + else if ( _caps_titling.get_active() ) markup += "titl=1,"; + + // Numeric + bool default_style = _numeric_default_style.get_active(); + bool lining = _numeric_lining.get_active(); + bool old_style = _numeric_old_style.get_active(); + + bool default_width = _numeric_default_width.get_active(); + bool proportional = _numeric_proportional.get_active(); + bool tabular = _numeric_tabular.get_active(); + + bool default_fractions = _numeric_default_fractions.get_active(); + bool diagonal = _numeric_diagonal.get_active(); + bool stacked = _numeric_stacked.get_active(); + + bool ordinal = _numeric_ordinal.get_active(); + bool slashed_zero = _numeric_slashed_zero.get_active(); + + if (lining) markup += "lnum=1,"; + if (old_style) markup += "onum=1,"; + if (proportional) markup += "pnum=1,"; + if (tabular) markup += "tnum=1,"; + if (diagonal) markup += "frac=1,"; + if (stacked) markup += "afrc=1,"; + if (ordinal) markup += "ordn=1,"; + if (slashed_zero) markup += "zero=1,"; + + // East Asian + bool default_variant = _asian_default_variant.get_active(); + bool jis78 = _asian_jis78.get_active(); + bool jis83 = _asian_jis83.get_active(); + bool jis90 = _asian_jis90.get_active(); + bool jis04 = _asian_jis04.get_active(); + bool simplified = _asian_simplified.get_active(); + bool traditional = _asian_traditional.get_active(); + bool asian_width = _asian_default_width.get_active(); + bool fwid = _asian_full_width.get_active(); + bool pwid = _asian_proportional_width.get_active(); + bool ruby = _asian_ruby.get_active(); + + if (jis78 ) markup += "jp78=1,"; + if (jis83 ) markup += "jp83=1,"; + if (jis90 ) markup += "jp90=1,"; + if (jis04 ) markup += "jp04=1,"; + if (simplified ) markup += "smpl=1,"; + if (traditional ) markup += "trad=1,"; + + if (fwid ) markup += "fwid=1,"; + if (pwid ) markup += "pwid=1,"; + + if (ruby ) markup += "ruby=1,"; + + // Feature settings + Glib::ustring feature_string; + for (auto i: _features) { + feature_string += i.second->get_css(); + } + + feature_string += _feature_entry.get_text(); + if (!feature_string.empty()) { + markup += feature_string; + } + + // std::cout << "|" << markup << "|" << std::endl; + return markup; + } + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/font-variants.h b/src/ui/widget/font-variants.h new file mode 100644 index 0000000..83c9fa9 --- /dev/null +++ b/src/ui/widget/font-variants.h @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2015, 2018 Tavmong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_FONT_VARIANT_H +#define INKSCAPE_UI_WIDGET_FONT_VARIANT_H + +#include <gtkmm/expander.h> +#include <gtkmm/checkbutton.h> +#include <gtkmm/radiobutton.h> +#include <gtkmm/entry.h> +#include <gtkmm/grid.h> + +class SPDesktop; +class SPObject; +class SPStyle; +class SPCSSAttr; + +namespace Inkscape { +namespace UI { +namespace Widget { + +class Feature; + +/** + * A container for selecting font variants (OpenType Features). + */ +class FontVariants : public Gtk::VBox +{ + +public: + + /** + * Constructor + */ + FontVariants(); + +protected: + // Ligatures: To start, use four check buttons. + Gtk::Expander _ligatures_frame; + Gtk::Grid _ligatures_grid; + Gtk::CheckButton _ligatures_common; + Gtk::CheckButton _ligatures_discretionary; + Gtk::CheckButton _ligatures_historical; + Gtk::CheckButton _ligatures_contextual; + Gtk::Label _ligatures_label_common; + Gtk::Label _ligatures_label_discretionary; + Gtk::Label _ligatures_label_historical; + Gtk::Label _ligatures_label_contextual; + + // Position: Exclusive options + Gtk::Expander _position_frame; + Gtk::Grid _position_grid; + Gtk::RadioButton _position_normal; + Gtk::RadioButton _position_sub; + Gtk::RadioButton _position_super; + + // Caps: Exclusive options (maybe a dropdown menu to save space?) + Gtk::Expander _caps_frame; + Gtk::Grid _caps_grid; + Gtk::RadioButton _caps_normal; + Gtk::RadioButton _caps_small; + Gtk::RadioButton _caps_all_small; + Gtk::RadioButton _caps_petite; + Gtk::RadioButton _caps_all_petite; + Gtk::RadioButton _caps_unicase; + Gtk::RadioButton _caps_titling; + + // Numeric: Complicated! + Gtk::Expander _numeric_frame; + Gtk::Grid _numeric_grid; + + Gtk::RadioButton _numeric_default_style; + Gtk::RadioButton _numeric_lining; + Gtk::Label _numeric_lining_label; + Gtk::RadioButton _numeric_old_style; + Gtk::Label _numeric_old_style_label; + + Gtk::RadioButton _numeric_default_width; + Gtk::RadioButton _numeric_proportional; + Gtk::Label _numeric_proportional_label; + Gtk::RadioButton _numeric_tabular; + Gtk::Label _numeric_tabular_label; + + Gtk::RadioButton _numeric_default_fractions; + Gtk::RadioButton _numeric_diagonal; + Gtk::Label _numeric_diagonal_label; + Gtk::RadioButton _numeric_stacked; + Gtk::Label _numeric_stacked_label; + + Gtk::CheckButton _numeric_ordinal; + Gtk::Label _numeric_ordinal_label; + + Gtk::CheckButton _numeric_slashed_zero; + Gtk::Label _numeric_slashed_zero_label; + + // East Asian: Complicated! + Gtk::Expander _asian_frame; + Gtk::Grid _asian_grid; + + Gtk::RadioButton _asian_default_variant; + Gtk::RadioButton _asian_jis78; + Gtk::RadioButton _asian_jis83; + Gtk::RadioButton _asian_jis90; + Gtk::RadioButton _asian_jis04; + Gtk::RadioButton _asian_simplified; + Gtk::RadioButton _asian_traditional; + + Gtk::RadioButton _asian_default_width; + Gtk::RadioButton _asian_full_width; + Gtk::RadioButton _asian_proportional_width; + + Gtk::CheckButton _asian_ruby; + + // ----- + Gtk::Expander _feature_frame; + Gtk::Grid _feature_grid; + Gtk::VBox _feature_vbox; + Gtk::Entry _feature_entry; + Gtk::Label _feature_label; + Gtk::Label _feature_list; + Gtk::Label _feature_substitutions; + +private: + void ligatures_init(); + void ligatures_callback(); + + void position_init(); + void position_callback(); + + void caps_init(); + void caps_callback(); + + void numeric_init(); + void numeric_callback(); + + void asian_init(); + void asian_callback(); + + void feature_init(); +public: + void feature_callback(); + +private: + // To determine if we need to write out property (may not be necessary) + unsigned _ligatures_all; + unsigned _position_all; + unsigned _caps_all; + unsigned _numeric_all; + unsigned _asian_all; + + unsigned _ligatures_mix; + unsigned _position_mix; + unsigned _caps_mix; + unsigned _numeric_mix; + unsigned _asian_mix; + + bool _ligatures_changed; + bool _position_changed; + bool _caps_changed; + bool _numeric_changed; + bool _feature_changed; + bool _asian_changed; + + std::map<std::string, Feature*> _features; + + sigc::signal<void> _changed_signal; + +public: + + /** + * Update GUI based on query results. + */ + void update( SPStyle const *query, bool different_features, Glib::ustring& font_spec ); + + /** + * Update GUI based on OpenType features of selected font. + */ + void update_opentype( Glib::ustring& font_spec ); + + /** + * Fill SPCSSAttr based on settings of buttons. + */ + void fill_css( SPCSSAttr* css ); + + /** + * Get CSS string for markup. + */ + Glib::ustring get_markup(); + + /** + * Let others know that user has changed GUI settings. + * (Used to enable 'Apply' and 'Default' buttons.) + */ + sigc::connection connectChanged(sigc::slot<void> slot) { + return _changed_signal.connect(slot); + } +}; + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_FONT_VARIANT_H + +/* + 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 : diff --git a/src/ui/widget/font-variations.cpp b/src/ui/widget/font-variations.cpp new file mode 100644 index 0000000..fae5cc3 --- /dev/null +++ b/src/ui/widget/font-variations.cpp @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org> + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2018 Felipe CorrĂȘa da Silva Sanches, Tavmong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <iostream> +#include <iomanip> + +#include <gtkmm.h> +#include <glibmm/i18n.h> + +#include <libnrtype/font-instance.h> + +#include "font-variations.h" + +// For updating from selection +#include "desktop.h" +#include "object/sp-text.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +FontVariationAxis::FontVariationAxis (Glib::ustring name, OTVarAxis& axis) + : name (name) +{ + + // std::cout << "FontVariationAxis::FontVariationAxis:: " + // << " name: " << name + // << " min: " << axis.minimum + // << " max: " << axis.maximum + // << " val: " << axis.set_val << std::endl; + + label = Gtk::manage( new Gtk::Label( name ) ); + add( *label ); + + precision = 2 - int( log10(axis.maximum - axis.minimum)); + if (precision < 0) precision = 0; + + scale = Gtk::manage( new Gtk::Scale() ); + scale->set_range (axis.minimum, axis.maximum); + scale->set_value (axis.set_val); + scale->set_digits (precision); + scale->set_hexpand(true); + add( *scale ); +} + + +// ------------------------------------------------------------- // + +FontVariations::FontVariations () : + Gtk::Grid () +{ + // std::cout << "FontVariations::FontVariations" << std::endl; + set_orientation( Gtk::ORIENTATION_VERTICAL ); + set_name ("FontVariations"); + size_group = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL); + show_all_children(); +} + + +// Update GUI based on query. +void +FontVariations::update (const Glib::ustring& font_spec) { + + font_instance* res = font_factory::Default()->FaceFromFontSpecification (font_spec.c_str()); + + auto children = get_children(); + for (auto child: children) { + remove ( *child ); + } + axes.clear(); + + for (auto a: res->openTypeVarAxes) { + // std::cout << "Creating axis: " << a.first << std::endl; + FontVariationAxis* axis = Gtk::manage( new FontVariationAxis( a.first, a.second )); + axes.push_back( axis ); + add( *axis ); + size_group->add_widget( *(axis->get_label()) ); // Keep labels the same width + axis->get_scale()->signal_value_changed().connect( + sigc::mem_fun(*this, &FontVariations::on_variations_change) + ); + } + + show_all_children(); +} + +void +FontVariations::fill_css( SPCSSAttr *css ) { + + // Eventually will want to favor using 'font-weight', etc. but at the moment these + // can't handle "fractional" values. See CSS Fonts Module Level 4. + sp_repr_css_set_property(css, "font-variation-settings", get_css_string().c_str()); +} + +Glib::ustring +FontVariations::get_css_string() { + + Glib::ustring css_string; + + for (auto axis: axes) { + Glib::ustring name = axis->get_name(); + + // Translate the "named" axes. (Additional names in 'stat' table, may need to handle them.) + if (name == "Width") name = "wdth"; // 'font-stretch' + if (name == "Weight") name = "wght"; // 'font-weight' + if (name == "Optical size") name = "opsz"; // 'font-optical-sizing' Can trigger glyph substitution. + if (name == "Slant") name = "slnt"; // 'font-style' + if (name == "Italic") name = "ital"; // 'font-style' Toggles from Roman to Italic. + + std::stringstream value; + value << std::fixed << std::setprecision(axis->get_precision()) << axis->get_value(); + css_string += "'" + name + "' " + value.str() + "', "; + } + + return css_string; +} + +Glib::ustring +FontVariations::get_pango_string() { + + Glib::ustring pango_string; + + if (!axes.empty()) { + + pango_string += "@"; + + for (auto axis: axes) { + if (axis->get_value() == 0) continue; // TEMP: Should check against default value. + Glib::ustring name = axis->get_name(); + + // Translate the "named" axes. (Additional names in 'stat' table, may need to handle them.) + if (name == "Width") name = "wdth"; // 'font-stretch' + if (name == "Weight") name = "wght"; // 'font-weight' + if (name == "Optical size") name = "opsz"; // 'font-optical-sizing' Can trigger glyph substitution. + if (name == "Slant") name = "slnt"; // 'font-style' + if (name == "Italic") name = "ital"; // 'font-style' Toggles from Roman to Italic. + + std::stringstream value; + value << std::fixed << std::setprecision(axis->get_precision()) << axis->get_value(); + pango_string += name + "=" + value.str() + ","; + } + + pango_string.erase (pango_string.size() - 1); // Erase last ',' + } + + return pango_string; +} + +void +FontVariations::on_variations_change() { + // std::cout << "FontVariations::on_variations_change: " << get_css_string() << std::endl;; + signal_changed.emit (); +} + +bool FontVariations::variations_present() const { + return !axes.empty(); +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/font-variations.h b/src/ui/widget/font-variations.h new file mode 100644 index 0000000..a3d3896 --- /dev/null +++ b/src/ui/widget/font-variations.h @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org> + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2018 Felipe CorrĂȘa da Silva Sanches, Tavmong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_FONT_VARIATIONS_H +#define INKSCAPE_UI_WIDGET_FONT_VARIATIONS_H + +#include <gtkmm/grid.h> +#include <gtkmm/sizegroup.h> +#include <gtkmm/label.h> +#include <gtkmm/scale.h> + +#include "libnrtype/OpenTypeUtil.h" + +#include "style.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + + +/** + * A widget for a single axis: Label and Slider + */ +class FontVariationAxis : public Gtk::Grid +{ +public: + FontVariationAxis(Glib::ustring name, OTVarAxis& axis); + Glib::ustring get_name() { return name; } + Gtk::Label* get_label() { return label; } + double get_value() { return scale->get_value(); } + int get_precision() { return precision; } + Gtk::Scale* get_scale() { return scale; } + +private: + + // Widgets + Glib::ustring name; + Gtk::Label* label; + Gtk::Scale* scale; + + int precision; + + // Signals + sigc::signal<void> signal_changed; +}; + +/** + * A widget for selecting font variations (OpenType Variations). + */ +class FontVariations : public Gtk::Grid +{ + +public: + + /** + * Constructor + */ + FontVariations(); + +protected: + +public: + + /** + * Update GUI. + */ + void update(const Glib::ustring& font_spec); + + /** + * Fill SPCSSAttr based on settings of buttons. + */ + void fill_css( SPCSSAttr* css ); + + /** + * Get CSS String + */ + Glib::ustring get_css_string(); + + Glib::ustring get_pango_string(); + + void on_variations_change(); + + /** + * Let others know that user has changed GUI settings. + * (Used to enable 'Apply' and 'Default' buttons.) + */ + sigc::connection connectChanged(sigc::slot<void> slot) { + return signal_changed.connect(slot); + } + + // return true if there are some variations present + bool variations_present() const; + +private: + + std::vector<FontVariationAxis*> axes; + Glib::RefPtr<Gtk::SizeGroup> size_group; + + sigc::signal<void> signal_changed; +}; + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_FONT_VARIATIONS_H + +/* + 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 : diff --git a/src/ui/widget/frame.cpp b/src/ui/widget/frame.cpp new file mode 100644 index 0000000..eac4e22 --- /dev/null +++ b/src/ui/widget/frame.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Murray C + * + * Copyright (C) 2012 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "frame.h" + + +// Inkscape::UI::Widget::Frame + +namespace Inkscape { +namespace UI { +namespace Widget { + +Frame::Frame(Glib::ustring const &label_text /*= ""*/, gboolean label_bold /*= TRUE*/ ) + : _label(label_text, Gtk::ALIGN_END, Gtk::ALIGN_CENTER, true) +{ + set_shadow_type(Gtk::SHADOW_NONE); + + set_label_widget(_label); + set_label(label_text, label_bold); +} + +void +Frame::add(Widget& widget) +{ + Gtk::Frame::add(widget); + set_padding(4, 0, 8, 0); + show_all_children(); +} + +void +Frame::set_label(const Glib::ustring &label_text, gboolean label_bold /*= TRUE*/) +{ + if (label_bold) { + _label.set_markup(Glib::ustring("<b>") + label_text + "</b>"); + } else { + _label.set_text(label_text); + } +} + +void +Frame::set_padding (guint padding_top, guint padding_bottom, guint padding_left, guint padding_right) +{ + auto child = get_child(); + + if(child) + { + child->set_margin_top(padding_top); + child->set_margin_bottom(padding_bottom); + child->set_margin_start(padding_left); + child->set_margin_end(padding_right); + } +} + +Gtk::Label const * +Frame::get_label_widget() const +{ + return &_label; +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/frame.h b/src/ui/widget/frame.h new file mode 100644 index 0000000..b2934b6 --- /dev/null +++ b/src/ui/widget/frame.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Murray C + * + * Copyright (C) 2012 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_FRAME_H +#define INKSCAPE_UI_WIDGET_FRAME_H + +#include <gtkmm/frame.h> +#include <gtkmm/label.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Creates a Gnome HIG style indented frame with bold label + * See http://developer.gnome.org/hig-book/stable/controls-frames.html.en + */ +class Frame : public Gtk::Frame +{ +public: + + /** + * Construct a Frame Widget. + * + * @param label The frame text. + */ + Frame(Glib::ustring const &label = "", gboolean label_bold = TRUE); + + /** + * Return the label widget + */ + Gtk::Label const *get_label_widget() const; + + /** + * Add a widget to this frame + */ + void add(Widget& widget) override; + + /** + * Set the frame label text and if bold or not + */ + void set_label(const Glib::ustring &label, gboolean label_bold = TRUE); + + /** + * Set the frame padding + */ + void set_padding (guint padding_top, guint padding_bottom, guint padding_left, guint padding_right); + +protected: + Gtk::Label _label; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_FRAME_H + +/* + 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 : diff --git a/src/ui/widget/highlight-picker.cpp b/src/ui/widget/highlight-picker.cpp new file mode 100644 index 0000000..f35e405 --- /dev/null +++ b/src/ui/widget/highlight-picker.cpp @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm.h> +#include <gtkmm/icontheme.h> + +#include "display/cairo-utils.h" + +#include "highlight-picker.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +HighlightPicker::HighlightPicker() : + Glib::ObjectBase(typeid(HighlightPicker)), + Gtk::CellRendererPixbuf(), + _property_active(*this, "active", 0) +{ + + property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE; +} + +HighlightPicker::~HighlightPicker() += default; + +void HighlightPicker::get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const +{ + Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h); + + if (min_h) { + min_h += (min_h) >> 1; + } + + if (nat_h) { + nat_h += (nat_h) >> 1; + } +} + +void HighlightPicker::get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const +{ + Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w); + + if (min_w) { + min_w += (min_w) >> 1; + } + + if (nat_w) { + nat_w += (nat_w) >> 1; + } +} + +void HighlightPicker::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) +{ + GdkRectangle carea; + + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 20); + cairo_t *ct = cairo_create(s); + + /* Transparent area */ + carea.x = 0; + carea.y = 0; + carea.width = 10; + carea.height = 20; + + cairo_pattern_t *checkers = ink_cairo_pattern_create_checkerboard(); + + cairo_rectangle(ct, carea.x, carea.y, carea.width, carea.height / 2); + cairo_set_source(ct, checkers); + cairo_fill_preserve(ct); + ink_cairo_set_source_rgba32(ct, _property_active.get_value()); + cairo_fill(ct); + + cairo_pattern_destroy(checkers); + + cairo_rectangle(ct, carea.x, carea.y + carea.height / 2, carea.width, carea.height / 2); + ink_cairo_set_source_rgba32(ct, _property_active.get_value() | 0x000000ff); + cairo_fill(ct); + + cairo_rectangle(ct, carea.x, carea.y, carea.width, carea.height); + ink_cairo_set_source_rgba32(ct, 0x333333ff); + cairo_set_line_width(ct, 2); + cairo_stroke(ct); + + cairo_destroy(ct); + cairo_surface_flush(s); + + GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data( cairo_image_surface_get_data(s), + GDK_COLORSPACE_RGB, TRUE, 8, + 10, 20, cairo_image_surface_get_stride(s), + ink_cairo_pixbuf_cleanup, s); + convert_pixbuf_argb32_to_normal(pixbuf); + + property_pixbuf() = Glib::wrap(pixbuf); + Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags ); +} + +bool HighlightPicker::activate_vfunc(GdkEvent* /*event*/, + Gtk::Widget& /*widget*/, + const Glib::ustring& /*path*/, + const Gdk::Rectangle& /*background_area*/, + const Gdk::Rectangle& /*cell_area*/, + Gtk::CellRendererState /*flags*/) +{ + return false; +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +//should be okay to put this here +/** + * Converts GdkPixbuf's data to premultiplied ARGB. + * This function will convert a GdkPixbuf in place into Cairo's native pixel format. + * Note that this is a hack intended to save memory. When the pixbuf is in Cairo's format, + * using it with GTK will result in corrupted drawings. + */ +void +convert_pixbuf_normal_to_argb32(GdkPixbuf *pb) +{ + convert_pixels_pixbuf_to_argb32( + gdk_pixbuf_get_pixels(pb), + gdk_pixbuf_get_width(pb), + gdk_pixbuf_get_height(pb), + gdk_pixbuf_get_rowstride(pb)); +} + +/** + * Converts GdkPixbuf's data back to its native format. + * Once this is done, the pixbuf can be used with GTK again. + */ +void +convert_pixbuf_argb32_to_normal(GdkPixbuf *pb) +{ + convert_pixels_argb32_to_pixbuf( + gdk_pixbuf_get_pixels(pb), + gdk_pixbuf_get_width(pb), + gdk_pixbuf_get_height(pb), + gdk_pixbuf_get_rowstride(pb)); +} + +/* + 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 : + + diff --git a/src/ui/widget/highlight-picker.h b/src/ui/widget/highlight-picker.h new file mode 100644 index 0000000..0b77a23 --- /dev/null +++ b/src/ui/widget/highlight-picker.h @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __UI_DIALOG_HIGHLIGHT_PICKER_H__ +#define __UI_DIALOG_HIGHLIGHT_PICKER_H__ +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/cellrendererpixbuf.h> +#include <gtkmm/widget.h> +#include <glibmm/property.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +class HighlightPicker : public Gtk::CellRendererPixbuf { +public: + HighlightPicker(); + ~HighlightPicker() override; + + Glib::PropertyProxy<guint32> property_active() { return _property_active.get_proxy(); } + +protected: + void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) override; + + void get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const override; + + void get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const override; + + bool activate_vfunc(GdkEvent *event, + Gtk::Widget &widget, + const Glib::ustring &path, + const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, + Gtk::CellRendererState flags) override; + +private: + + Glib::Property<guint32> _property_active; +}; + + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + + +#endif /* __UI_DIALOG_IMAGETOGGLER_H__ */ + +/* + 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 : diff --git a/src/ui/widget/iconrenderer.cpp b/src/ui/widget/iconrenderer.cpp new file mode 100644 index 0000000..4ca250e --- /dev/null +++ b/src/ui/widget/iconrenderer.cpp @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com> + * Martin Owens 2018 <doctormo@gmail.com> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/widget/iconrenderer.h" + +#include "layertypeicon.h" +#include "ui/icon-loader.h" +#include "ui/icon-names.h" +#include "widgets/toolbox.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +IconRenderer::IconRenderer() : + Glib::ObjectBase(typeid(IconRenderer)), + Gtk::CellRendererPixbuf(), + _property_icon(*this, "icon", 0) +{ + property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE; + set_pixbuf(); +} + +/* + * Called when an icon is clicked. + */ +IconRenderer::type_signal_activated IconRenderer::signal_activated() +{ + return m_signal_activated; +} + +void IconRenderer::get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const +{ + Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h); + + if (min_h) { + min_h += (min_h) >> 1; + } + + if (nat_h) { + nat_h += (nat_h) >> 1; + } +} + +void IconRenderer::get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const +{ + Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w); + + if (min_w) { + min_w += (min_w) >> 1; + } + + if (nat_w) { + nat_w += (nat_w) >> 1; + } +} + +void IconRenderer::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) +{ + set_pixbuf(); + + Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags ); +} + +bool IconRenderer::activate_vfunc(GdkEvent* /*event*/, + Gtk::Widget& /*widget*/, + const Glib::ustring& path, + const Gdk::Rectangle& /*background_area*/, + const Gdk::Rectangle& /*cell_area*/, + Gtk::CellRendererState /*flags*/) +{ + m_signal_activated.emit(path); + return true; +} + +void IconRenderer::add_icon(Glib::ustring name) +{ + _icons.push_back(sp_get_icon_pixbuf(name.c_str(), GTK_ICON_SIZE_BUTTON)); +} + +void IconRenderer::set_pixbuf() +{ + int icon_index = property_icon().get_value(); + if(icon_index >= 0 && icon_index < _icons.size()) { + property_pixbuf() = _icons[icon_index]; + } else { + property_pixbuf() = sp_get_icon_pixbuf("image-missing", GTK_ICON_SIZE_BUTTON); + } +} + + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/iconrenderer.h b/src/ui/widget/iconrenderer.h new file mode 100644 index 0000000..662ce5b --- /dev/null +++ b/src/ui/widget/iconrenderer.h @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __UI_DIALOG_ADDTOICON_H__ +#define __UI_DIALOG_ADDTOICON_H__ +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com> + * Martin Owens 2018 <doctormo@gmail.com> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/cellrendererpixbuf.h> +#include <gtkmm/widget.h> +#include <glibmm/property.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +class IconRenderer : public Gtk::CellRendererPixbuf { +public: + IconRenderer(); + ~IconRenderer() override = default;; + + Glib::PropertyProxy<int> property_icon() { return _property_icon.get_proxy(); } + Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_on(); + Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_off(); + + void add_icon(Glib::ustring name); + + typedef sigc::signal<void, Glib::ustring> type_signal_activated; + type_signal_activated signal_activated(); +protected: + type_signal_activated m_signal_activated; + + void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) override; + + void get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const override; + + void get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const override; + + bool activate_vfunc(GdkEvent *event, + Gtk::Widget &widget, + const Glib::ustring &path, + const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, + Gtk::CellRendererState flags) override; + +private: + + Glib::Property<int> _property_icon; + std::vector<Glib::RefPtr<Gdk::Pixbuf>> _icons; + void set_pixbuf(); +}; + + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + + +#endif /* __UI_DIALOG_IMAGETOGGLER_H__ */ + +/* + 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 : diff --git a/src/ui/widget/imagetoggler.cpp b/src/ui/widget/imagetoggler.cpp new file mode 100644 index 0000000..a1d258e --- /dev/null +++ b/src/ui/widget/imagetoggler.cpp @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Jon A. Cruz + * Johan B. C. Engelen + * + * Copyright (C) 2006-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + + +#include "ui/widget/imagetoggler.h" + +#include "ui/icon-loader.h" +#include "ui/icon-names.h" +#include "widgets/toolbox.h" + +#include <iostream> + +namespace Inkscape { +namespace UI { +namespace Widget { + +ImageToggler::ImageToggler( char const* on, char const* off) : + Glib::ObjectBase(typeid(ImageToggler)), + Gtk::CellRendererPixbuf(), + _pixOnName(on), + _pixOffName(off), + _property_active(*this, "active", false), + _property_activatable(*this, "activatable", true), + _property_pixbuf_on(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr)), + _property_pixbuf_off(*this, "pixbuf_off", Glib::RefPtr<Gdk::Pixbuf>(nullptr)) +{ + property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE; + + _property_pixbuf_on = sp_get_icon_pixbuf(_pixOnName, GTK_ICON_SIZE_MENU); + _property_pixbuf_off = sp_get_icon_pixbuf(_pixOffName, GTK_ICON_SIZE_MENU); + + property_pixbuf() = _property_pixbuf_off.get_value(); +} + +void ImageToggler::get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const +{ + Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h); + + if (min_h) { + min_h += (min_h) >> 1; + } + + if (nat_h) { + nat_h += (nat_h) >> 1; + } +} + +void ImageToggler::get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const +{ + Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w); + + if (min_w) { + min_w += (min_w) >> 1; + } + + if (nat_w) { + nat_w += (nat_w) >> 1; + } +} + +void ImageToggler::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) +{ + property_pixbuf() = _property_active.get_value() ? _property_pixbuf_on : _property_pixbuf_off; + Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags ); +} + +bool +ImageToggler::activate_vfunc(GdkEvent* event, + Gtk::Widget& /*widget*/, + const Glib::ustring& path, + const Gdk::Rectangle& /*background_area*/, + const Gdk::Rectangle& /*cell_area*/, + Gtk::CellRendererState /*flags*/) +{ + _signal_pre_toggle.emit(event); + _signal_toggled.emit(path); + + return false; +} + + +} // namespace Widget +} // 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 : + + diff --git a/src/ui/widget/imagetoggler.h b/src/ui/widget/imagetoggler.h new file mode 100644 index 0000000..a03ee37 --- /dev/null +++ b/src/ui/widget/imagetoggler.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __UI_DIALOG_IMAGETOGGLER_H__ +#define __UI_DIALOG_IMAGETOGGLER_H__ +/* + * Authors: + * Jon A. Cruz + * Johan B. C. Engelen + * + * Copyright (C) 2006-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/cellrendererpixbuf.h> +#include <gtkmm/widget.h> +#include <glibmm/property.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +class ImageToggler : public Gtk::CellRendererPixbuf { +public: + ImageToggler( char const *on, char const *off); + ~ImageToggler() override = default;; + + sigc::signal<void, const Glib::ustring&> signal_toggled() { return _signal_toggled;} + sigc::signal<void, GdkEvent const *> signal_pre_toggle() { return _signal_pre_toggle; } + + Glib::PropertyProxy<bool> property_active() { return _property_active.get_proxy(); } + Glib::PropertyProxy<bool> property_activatable() { return _property_activatable.get_proxy(); } + Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_on(); + Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_off(); + +protected: + void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) override; + + void get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const override; + + void get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const override; + + bool activate_vfunc(GdkEvent *event, + Gtk::Widget &widget, + const Glib::ustring &path, + const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, + Gtk::CellRendererState flags) override; + + +private: + Glib::ustring _pixOnName; + Glib::ustring _pixOffName; + + Glib::Property<bool> _property_active; + Glib::Property<bool> _property_activatable; + Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_on; + Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_off; + + sigc::signal<void, const Glib::ustring&> _signal_toggled; + sigc::signal<void, GdkEvent const *> _signal_pre_toggle; +}; + + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + + +#endif /* __UI_DIALOG_IMAGETOGGLER_H__ */ + +/* + 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 : diff --git a/src/ui/widget/ink-color-wheel.cpp b/src/ui/widget/ink-color-wheel.cpp new file mode 100644 index 0000000..7e56ee5 --- /dev/null +++ b/src/ui/widget/ink-color-wheel.cpp @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Color wheel widget. Outer ring for Hue. Inner triangle for Saturation and Value. + * + * Copyright (C) 2018 Tavmjong Bah + * + * The contents of this file may be used under the GNU General Public License Version 2 or later. + * + */ + +#include "ink-color-wheel.h" + +// A point with a color value. +class color_point { +public: + color_point() : x(0), y(0), r(0), g(0), b(0) {}; + color_point(double x, double y, double r, double g, double b) : x(x), y(y), r(r), g(g), b(b) {}; + color_point(double x, double y, guint32 color) : x(x), y(y), + r(((color & 0xff0000) >> 16)/255.0), + g(((color & 0xff00) >> 8)/255.0), + b(((color & 0xff) )/255.0) {}; + guint32 get_color() { return (int(r*255) << 16 | int(g*255) << 8 | int(b*255)); }; + double x; + double y; + double r; + double g; + double b; +}; + +inline double lerp(const double& v0, const double& v1, const double& t0, const double&t1, const double& t) { + double s = 0; + if (t0 != t1) { + s = (t - t0)/(t1 - t0); + } + return (1.0 - s) * v0 + s * v1; +} + +inline color_point lerp(const color_point& v0, const color_point& v1, const double &t0, const double &t1, const double& t) { + double x = lerp(v0.x, v1.x, t0, t1, t); + double y = lerp(v0.y, v1.y, t0, t1, t); + double r = lerp(v0.r, v1.r, t0, t1, t); + double g = lerp(v0.g, v1.g, t0, t1, t); + double b = lerp(v0.b, v1.b, t0, t1, t); + return (color_point(x, y, r, g, b)); +} + +inline double clamp(const double& value, const double& min, const double& max) { + if (value < min) return min; + if (value > max) return max; + return value; +} + +// h, s, and v in range 0 to 1. Returns rgb value useful for use in Cairo. +guint32 hsv_to_rgb(double h, double s, double v) { + + if (h < 0.0 || h > 1.0 || + s < 0.0 || s > 1.0 || + v < 0.0 || v > 1.0) { + std::cerr << "ColorWheel: hsv_to_rgb: input out of bounds: (0-1)" + << " h: " << h << " s: " << s << " v: " << v << std::endl; + return 0x0; + } + + double r = v; + double g = v; + double b = v; + + if (s != 0.0) { + double c = s * v; + if (h == 1.0) h = 0.0; + h *= 6.0; + + double f = h - (int)h; + double p = v * (1.0 - s); + double q = v * (1.0 - s * f); + double t = v * (1.0 - s * (1.0 - f)); + + switch ((int)h) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + default: g_assert_not_reached(); + } + } + guint32 rgb = (((int)floor (r*255 + 0.5) << 16) | + ((int)floor (g*255 + 0.5) << 8) | + ((int)floor (b*255 + 0.5) )); + return rgb; +} + +double luminance(guint32 color) +{ + double r(((color & 0xff0000) >> 16)/255.0); + double g(((color & 0xff00) >> 8)/255.0); + double b(((color & 0xff) )/255.0); + return (r * 0.2125 + g * 0.7154 + b * 0.0721); +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +ColorWheel::ColorWheel() + : _hue(0.0) + , _saturation(1.0) + , _value(1.0) + , _ring_width(0.2) + , _mode(DRAG_NONE) + , _focus_on_ring(true) +{ + set_name("ColorWheel"); + add_events(Gdk::BUTTON_PRESS_MASK | + Gdk::BUTTON_RELEASE_MASK | + Gdk::BUTTON_MOTION_MASK | + Gdk::KEY_PRESS_MASK ); + set_can_focus(); +} + +void +ColorWheel::set_rgb(const double& r, const double&g, const double&b, bool override_hue) +{ + double Min = std::min({r, g, b}); + double Max = std::max({r, g, b}); + _value = Max; + if (Min == Max) { + if (override_hue) { + _hue = 0.0; + } + } else { + if (Max == r) { + _hue = ((g-b)/(Max-Min) )/6.0; + } else if (Max == g) { + _hue = ((b-r)/(Max-Min) + 2)/6.0; + } else { + _hue = ((r-g)/(Max-Min) + 4)/6.0; + } + if (_hue < 0.0) { + _hue += 1.0; + } + } + if (Max == 0) { + _saturation = 0; + } else { + _saturation = (Max - Min)/Max; + } +} + +void +ColorWheel::get_rgb(double& r, double& g, double& b) +{ + guint32 color = get_rgb(); + r = ((color & 0xff0000) >> 16)/255.0; + g = ((color & 0x00ff00) >> 8)/255.0; + b = ((color & 0x0000ff) )/255.0; +} + +guint32 +ColorWheel::get_rgb() +{ + return hsv_to_rgb(_hue, _saturation, _value); +} + +/* Pad triangle vertically if necessary */ +void +draw_vertical_padding(color_point p0, color_point p1, int padding, bool pad_upwards, + guint32 *buffer, int height, int stride); + +bool +ColorWheel::on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) { + Gtk::Allocation allocation = get_allocation(); + const int width = allocation.get_width(); + const int height = allocation.get_height(); + + const int stride = Cairo::ImageSurface::format_stride_for_width(Cairo::FORMAT_RGB24, width); + + int cx = width/2; + int cy = height/2; + + int focus_line_width; + int focus_padding; + get_style_property("focus-line-width", focus_line_width); + get_style_property("focus-padding", focus_padding); + + // Paint ring + guint32* buffer_ring = g_new (guint32, height * stride / 4); + double r_max = std::min( width, height)/2.0 - 2 * (focus_line_width + focus_padding); + double r_min = r_max * (1.0 - _ring_width); + double r2_max = (r_max+1) * (r_max+1); // Must expand a bit to avoid edge effects. + double r2_min = (r_min-1) * (r_min-1); // Must shrink a bit to avoid edge effects. + + for (int i = 0; i < height; ++i) { + guint32* p = buffer_ring + i * width; + double dy = (cy - i); + for (int j = 0; j < width; ++j) { + double dx = (j - cx); + double r2 = dx * dx + dy * dy; + if (r2 < r2_min || r2 > r2_max) { + *p++ = 0; // Save calculation time. + } else { + double angle = atan2 (dy, dx); + if (angle < 0.0) { + angle += 2.0 * M_PI; + } + double hue = angle/(2.0 * M_PI); + + *p++ = hsv_to_rgb(hue, 1.0, 1.0); + } + } + } + + Cairo::RefPtr<::Cairo::ImageSurface> source_ring = + ::Cairo::ImageSurface::create((unsigned char *)buffer_ring, + Cairo::FORMAT_RGB24, + width, height, stride); + + cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL); + + // Paint line on ring in source (so it gets clipped by stroke). + double l = 0.0; + guint32 color_on_ring = hsv_to_rgb(_hue, 1.0, 1.0); + if (luminance(color_on_ring) < 0.5) l = 1.0; + + Cairo::RefPtr<::Cairo::Context> cr_source_ring = ::Cairo::Context::create(source_ring); + cr_source_ring->set_source_rgb(l, l, l); + + cr_source_ring->move_to (cx, cy); + cr_source_ring->line_to (cx + cos(_hue * M_PI * 2.0) * r_max+1, + cy - sin(_hue * M_PI * 2.0) * r_max+1); + cr_source_ring->stroke(); + + // Paint with ring surface, clipping to ring. + cr->save(); + cr->set_source(source_ring, 0, 0); + cr->set_line_width (r_max - r_min); + cr->begin_new_path(); + cr->arc(cx, cy, (r_max + r_min)/2.0, 0, 2.0 * M_PI); + cr->stroke(); + cr->restore(); + + g_free(buffer_ring); + + // Draw focus + if (has_focus() && _focus_on_ring) { + Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context(); + style_context->render_focus(cr, 0, 0, width, height); + } + + // Paint triangle. + /* The triangle is painted by first finding color points on the + * edges of the triangle at the same y value via linearly + * interpolating between corner values, and then interpolating along + * x between the those edge points. The interpolation is in sRGB + * space which leads to a complicated mapping between x/y and + * saturation/value. This was probably done to remove the need to + * convert between HSV and RGB for each pixel. + * Black corner: v = 0, s = 1 + * White corner: v = 1, s = 0 + * Color corner; v = 1, s = 1 + */ + const int padding = 3; // Avoid edge artifacts. + double x0, y0, x1, y1, x2, y2; + triangle_corners(x0, y0, x1, y1, x2, y2); + guint32 color0 = hsv_to_rgb(_hue, 1.0, 1.0); + guint32 color1 = hsv_to_rgb(_hue, 1.0, 0.0); + guint32 color2 = hsv_to_rgb(_hue, 0.0, 1.0); + + color_point p0 (x0, y0, color0); + color_point p1 (x1, y1, color1); + color_point p2 (x2, y2, color2); + + // Reorder so we paint from top down. + if (p1.y > p2.y) { + std::swap(p1, p2); + } + + if (p0.y > p2.y) { + std::swap(p0, p2); + } + + if (p0.y > p1.y) { + std::swap(p0, p1); + } + + guint32* buffer_triangle = g_new (guint32, height * stride / 4); + + for (int y = 0; y < height; ++y) { + guint32 *p = buffer_triangle + y * (stride / 4); + + if (p0.y <= y+padding && y-padding < p2.y) { + + // Get values on side at position y. + color_point side0; + double y_inter = clamp(y, p0.y, p2.y); + if (y < p1.y) { + side0 = lerp(p0, p1, p0.y, p1.y, y_inter); + } else { + side0 = lerp(p1, p2, p1.y, p2.y, y_inter); + } + color_point side1 = lerp(p0, p2, p0.y, p2.y, y_inter); + + // side0 should be on left + if (side0.x > side1.x) { + std::swap (side0, side1); + } + + int x_start = std::max(0, int(side0.x)); + int x_end = std::min(int(side1.x), width); + + for (int x = 0; x < width; ++x) { + if (x <= x_start) { + *p++ = side0.get_color(); + } else if (x < x_end) { + *p++ = lerp(side0, side1, side0.x, side1.x, x).get_color(); + } else { + *p++ = side1.get_color(); + } + } + } + } + + // add vertical padding to each side separately + color_point temp_point = lerp(p0, p1, p0.x, p1.x, (p0.x + p1.x) / 2.0); + bool pad_upwards = is_in_triangle(temp_point.x, temp_point.y + 1); + draw_vertical_padding(p0, p1, padding, pad_upwards, buffer_triangle, height, stride / 4); + + temp_point = lerp(p0, p2, p0.x, p2.x, (p0.x + p2.x) / 2.0); + pad_upwards = is_in_triangle(temp_point.x, temp_point.y + 1); + draw_vertical_padding(p0, p2, padding, pad_upwards, buffer_triangle, height, stride / 4); + + temp_point = lerp(p1, p2, p1.x, p2.x, (p1.x + p2.x) / 2.0); + pad_upwards = is_in_triangle(temp_point.x, temp_point.y + 1); + draw_vertical_padding(p1, p2, padding, pad_upwards, buffer_triangle, height, stride / 4); + + Cairo::RefPtr<::Cairo::ImageSurface> source_triangle = + ::Cairo::ImageSurface::create((unsigned char *)buffer_triangle, + Cairo::FORMAT_RGB24, + width, height, stride); + + // Paint with triangle surface, clipping to triangle. + cr->save(); + cr->set_source(source_triangle, 0, 0); + cr->move_to(p0.x, p0.y); + cr->line_to(p1.x, p1.y); + cr->line_to(p2.x, p2.y); + cr->close_path(); + cr->fill(); + cr->restore(); + + g_free(buffer_triangle); + + // Draw marker + double mx = x1 + (x2-x1) * _value + (x0-x2) * _saturation * _value; + double my = y1 + (y2-y1) * _value + (y0-y2) * _saturation * _value; + + double a = 0.0; + guint32 color_at_marker = get_rgb(); + if (luminance(color_at_marker) < 0.5) a = 1.0; + + cr->set_source_rgb(a, a, a); + cr->begin_new_path(); + cr->arc(mx, my, 4, 0, 2 * M_PI); + cr->stroke(); + + // Draw focus + if (has_focus() && !_focus_on_ring) { + Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context(); + style_context->render_focus(cr, mx-4, my-4, 8, 8); // This doesn't seem to work. + cr->set_line_width(0.5); + cr->set_source_rgb(1-a, 1-a, 1-a); + cr->begin_new_path(); + cr->arc(mx, my, 7, 0, 2 * M_PI); + cr->stroke(); + } + + return true; +} + +void +draw_vertical_padding(color_point p0, color_point p1, int padding, bool pad_upwards, + guint32 *buffer, int height, int stride) +{ + // skip if horizontal padding is more accurate + double gradient = (p1.y - p0.y) / (p1.x - p0.x); + if (std::abs(gradient) > 1.0) { + return; + } + + double min_y = std::min(p0.y, p1.y); + double max_y = std::max(p0.y, p1.y); + + double min_x = std::min(p0.x, p1.x); + double max_x = std::max(p0.x, p1.x); + + for (int y = min_y; y <= max_y; ++y) { + double start_x = lerp(p0, p1, p0.y, p1.y, clamp(y, min_y, max_y)).x; + double end_x = lerp(p0, p1, p0.y, p1.y, clamp(y + 1, min_y, max_y)).x; + if (start_x > end_x) { + std::swap(start_x, end_x); + } + + guint32 *p = buffer + y * stride; + p += static_cast<int>(start_x); + for (int x = start_x; x <= end_x; ++x) { + color_point point = lerp(p0, p1, p0.x, p1.x, clamp(x, min_x, max_x)); + for (int offset = 0; offset <= padding; ++offset) { + if (pad_upwards && (point.y - offset) >= 0) { + *(p - (offset * stride)) = point.get_color(); + } else if (!pad_upwards && (point.y + offset) < height) { + *(p + (offset * stride)) = point.get_color(); + } + } + ++p; + } + } +} + +// Find triangle corners given hue and radius. +void +ColorWheel::triangle_corners(double &x0, double &y0, + double &x1, double &y1, + double &x2, double &y2) +{ + Gtk::Allocation allocation = get_allocation(); + const int width = allocation.get_width(); + const int height = allocation.get_height(); + + int cx = width/2; + int cy = height/2; + + int focus_line_width; + int focus_padding; + get_style_property("focus-line-width", focus_line_width); + get_style_property("focus-padding", focus_padding); + + double r_max = std::min( width, height)/2.0 - 2 * (focus_line_width + focus_padding); + double r_min = r_max * (1.0 - _ring_width); + + double angle = _hue * 2.0 * M_PI; + + x0 = cx + cos(angle) * r_min; + y0 = cy - sin(angle) * r_min; + x1 = cx + cos(angle + 2.0 * M_PI / 3.0) * r_min; + y1 = cy - sin(angle + 2.0 * M_PI / 3.0) * r_min; + x2 = cx + cos(angle + 4.0 * M_PI / 3.0) * r_min; + y2 = cy - sin(angle + 4.0 * M_PI / 3.0) * r_min; +} + +void +ColorWheel::set_from_xy(const double& x, const double& y) +{ + Gtk::Allocation allocation = get_allocation(); + const int width = allocation.get_width(); + const int height = allocation.get_height(); + double cx = width/2.0; + double cy = height/2.0; + double r = std::min(cx, cy) * (1 - _ring_width); + + // We calculate RGB value under the cursor by rotating the cursor + // and triangle by the hue value and looking at position in the + // now right pointing triangle. + double angle = _hue * 2 * M_PI; + double Sin = sin(angle); + double Cos = cos(angle); + double xp = ((x-cx) * Cos - (y-cy) * Sin) / r; + double yp = ((x-cx) * Sin + (y-cy) * Cos) / r; + + double xt = lerp(0.0, 1.0, -0.5, 1.0, xp); + xt = clamp(xt, 0, 1); + + double dy = (1-xt) * cos(M_PI/6.0); + double yt = lerp(0.0, 1.0, -dy, dy, yp); + yt = clamp(yt, 0, 1); + + color_point c0(0, 0, yt, yt, yt); // Grey point along base. + color_point c1(0, 0, hsv_to_rgb(_hue, 1, 1)); // Hue point at apex + color_point c = lerp(c0, c1, 0, 1, xt); + + set_rgb(c.r, c.g, c.b, false); // Don't override previous hue. +} + +bool +ColorWheel::is_in_ring(const double& x, const double& y) +{ + Gtk::Allocation allocation = get_allocation(); + const int width = allocation.get_width(); + const int height = allocation.get_height(); + + int cx = width/2; + int cy = height/2; + + int focus_line_width; + int focus_padding; + get_style_property("focus-line-width", focus_line_width); + get_style_property("focus-padding", focus_padding); + + double r_max = std::min( width, height)/2.0 - 2 * (focus_line_width + focus_padding); + double r_min = r_max * (1.0 - _ring_width); + double r2_max = r_max * r_max; + double r2_min = r_min * r_min; + + double dx = x - cx; + double dy = y - cy; + double r2 = dx * dx + dy * dy; + + return (r2_min < r2 && r2 < r2_max); +} + +bool +ColorWheel::is_in_triangle(const double& x, const double& y) +{ + double x0, y0, x1, y1, x2, y2; + triangle_corners(x0, y0, x1, y1, x2, y2); + + double det = (x2-x1) * (y0-y1) - (y2-y1) * (x0-x1); + double s = ((x -x1) * (y0-y1) - (y -y1) * (x0-x1)) / det; + double t = ((x2-x1) * (y -y1) - (y2-y1) * (x -x1)) / det; + + return (s >= 0.0 && t >= 0.0 && s+t <= 1.0); +} + +bool +ColorWheel::on_focus(Gtk::DirectionType direction) +{ + // In forward direction, focus passes from no focus to ring focus to triangle focus to no focus. + if (!has_focus()) { + _focus_on_ring = (direction == Gtk::DIR_TAB_FORWARD); + grab_focus(); + return true; + } + + // Already have focus + bool keep_focus = false; + + switch (direction) { + case Gtk::DIR_UP: + case Gtk::DIR_LEFT: + case Gtk::DIR_TAB_BACKWARD: + if (!_focus_on_ring) { + _focus_on_ring = true; + keep_focus = true; + } + break; + + case Gtk::DIR_DOWN: + case Gtk::DIR_RIGHT: + case Gtk::DIR_TAB_FORWARD: + if (_focus_on_ring) { + _focus_on_ring = false; + keep_focus = true; + } + break; + } + + queue_draw(); // Update focus indicators. + + return keep_focus; +} + +bool +ColorWheel::on_button_press_event(GdkEventButton* event) +{ + // Seat is automatically grabbed. + double x = event->x; + double y = event->y; + + if (is_in_ring(x, y) ) { + _mode = DRAG_H; + grab_focus(); + _focus_on_ring = true; + return true; + } + + if (is_in_triangle(x, y)) { + _mode = DRAG_SV; + grab_focus(); + _focus_on_ring = false; + return true; + } + + return false; +} + +bool +ColorWheel::on_button_release_event(GdkEventButton* event) +{ + _mode = DRAG_NONE; + return true; +} + +bool +ColorWheel::on_motion_notify_event(GdkEventMotion* event) +{ + double x = event->x; + double y = event->y; + + Gtk::Allocation allocation = get_allocation(); + const int width = allocation.get_width(); + const int height = allocation.get_height(); + double cx = width/2.0; + double cy = height/2.0; + double r = std::min(cx, cy) * (1 - _ring_width); + + if (_mode == DRAG_H) { + + double angle = -atan2(y-cy, x-cx); + if (angle < 0) angle += 2.0 * M_PI; + _hue = angle / (2.0 * M_PI); + + queue_draw(); + _signal_color_changed.emit(); + return true; + } + + if (_mode == DRAG_SV) { + + set_from_xy(x, y); + _signal_color_changed.emit(); + queue_draw(); + return true; + } + + return false; +} + +bool +ColorWheel::on_key_press_event(GdkEventKey* key_event) +{ + bool consumed = false; + + unsigned int key = 0; + gdk_keymap_translate_keyboard_state( Gdk::Display::get_default()->get_keymap(), + key_event->hardware_keycode, + (GdkModifierType)key_event->state, + 0, &key, nullptr, nullptr, nullptr ); + + double x0, y0, x1, y1, x2, y2; + triangle_corners(x0, y0, x1, y1, x2, y2); + + // Marker position + double mx = x1 + (x2-x1) * _value + (x0-x2) * _saturation * _value; + double my = y1 + (y2-y1) * _value + (y0-y2) * _saturation * _value; + + + const double delta_hue = 2.0/360.0; + + switch (key) { + + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + if (_focus_on_ring) { + _hue += delta_hue; + } else { + my -= 1.0; + set_from_xy(mx, my); + } + consumed = true; + break; + + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + if (_focus_on_ring) { + _hue -= delta_hue; + } else { + my += 1.0; + set_from_xy(mx, my); + } + consumed = true; + break; + + case GDK_KEY_Left: + case GDK_KEY_KP_Left: + if (_focus_on_ring) { + _hue += delta_hue; + } else { + mx -= 1.0; + set_from_xy(mx, my); + } + consumed = true; + break; + + case GDK_KEY_Right: + case GDK_KEY_KP_Right: + if (_focus_on_ring) { + _hue -= delta_hue; + } else { + mx += 1.0; + set_from_xy(mx, my); + } + consumed = true; + break; + + } + + if (consumed) { + if (_hue >= 1.0) _hue -= 1.0; + if (_hue < 0.0) _hue += 1.0; + _signal_color_changed.emit(); + queue_draw(); + } + + return consumed; +} + +sigc::signal<void> +ColorWheel::signal_color_changed() +{ + return _signal_color_changed; +} + +} // 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 : diff --git a/src/ui/widget/ink-color-wheel.h b/src/ui/widget/ink-color-wheel.h new file mode 100644 index 0000000..c6333b7 --- /dev/null +++ b/src/ui/widget/ink-color-wheel.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Color wheel widget. Outer ring for Hue. Inner triangle for Saturation and Value. + * + * Copyright (C) 2018 Tavmjong Bah + * + * The contents of this file may be used under the GNU General Public License Version 2 or later. + * + */ + +#ifndef INK_COLORWHEEL_H +#define INK_COLORWHEEL_H + +/* Rewrite of the C Gimp ColorWheel which came originally from GTK2. */ + +#include <gtkmm.h> +#include <iostream> + +namespace Inkscape { +namespace UI { +namespace Widget { + +class ColorWheel : public Gtk::DrawingArea +{ +public: + ColorWheel(); + void set_rgb(const double& r, const double& g, const double& b, bool override_hue = true); + void get_rgb(double& r, double& g, double& b); + guint32 get_rgb(); + bool is_adjusting() {return _mode != DRAG_NONE;} + +protected: + bool on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) override; + +private: + void triangle_corners(double& x0, double& y0, + double& x1, double& y1, + double& x2, double& y2); + void set_from_xy(const double& x, const double& y); + bool is_in_ring( const double& x, const double& y); + bool is_in_triangle(const double& x, const double& y); + + enum DragMode { + DRAG_NONE, + DRAG_H, + DRAG_SV + }; + + double _hue; // Range [0,1) + double _saturation; + double _value; + double _ring_width; + DragMode _mode; + bool _focus_on_ring; + + // Callbacks + bool on_focus(Gtk::DirectionType direction) override; + bool on_button_press_event(GdkEventButton* event) override; + bool on_button_release_event(GdkEventButton* event) override; + bool on_motion_notify_event(GdkEventMotion* event) override; + bool on_key_press_event(GdkEventKey* key_event) override; + + // Signals +public: + sigc::signal<void> signal_color_changed(); + +protected: + sigc::signal<void> _signal_color_changed; + +}; + +} // Namespace Inkscape +} +} +#endif // INK_COLOR_WHEEL_H + +/* + 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 : diff --git a/src/ui/widget/ink-flow-box.cpp b/src/ui/widget/ink-flow-box.cpp new file mode 100644 index 0000000..8485dd9 --- /dev/null +++ b/src/ui/widget/ink-flow-box.cpp @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Inkflow-box widget. + * This widget allow pack widgets in a flowbox with a controller to show-hide + * + * Author: + * Jabier Arraiza <jabier.arraiza@marker.es> + * + * Copyright (C) 2018 Jabier Arraiza + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "preferences.h" +#include "ui/icon-loader.h" +#include "ui/widget/ink-flow-box.h" +#include <gtkmm/adjustment.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +InkFlowBox::InkFlowBox(const gchar *name) +{ + set_name(name); + this->pack_start(_controller, false, false, 0); + this->pack_start(_flowbox, true, true, 0); + _flowbox.set_activate_on_single_click(true); + Gtk::ToggleButton *tbutton = new Gtk::ToggleButton("", false); + tbutton->set_always_show_image(true); + _flowbox.set_selection_mode(Gtk::SelectionMode::SELECTION_NONE); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/lock"), false); + tbutton->set_active(prefs->getBool(Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/lock"), true)); + Glib::ustring iconname = "object-unlocked"; + if (tbutton->get_active()) { + iconname = "object-locked"; + } + tbutton->set_image(*sp_get_icon_image(iconname, Gtk::ICON_SIZE_MENU)); + tbutton->signal_toggled().connect( + sigc::bind<Gtk::ToggleButton *>(sigc::mem_fun(*this, &InkFlowBox::on_global_toggle), tbutton)); + _controller.pack_start(*tbutton); + tbutton->hide(); + tbutton->set_no_show_all(true); + showing = 0; + sensitive = true; +} + +InkFlowBox::~InkFlowBox() = default; + +Glib::ustring InkFlowBox::getPrefsPath(gint pos) +{ + return Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/index_") + std::to_string(pos); +} + +bool InkFlowBox::on_filter(Gtk::FlowBoxChild *child) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool(getPrefsPath(child->get_index()), true)) { + showing++; + return true; + } + return false; +} + +void InkFlowBox::on_toggle(gint pos, Gtk::ToggleButton *tbutton) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool global = prefs->getBool(Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/lock"), true); + if (global && sensitive) { + sensitive = false; + bool active = true; + for (auto child : tbutton->get_parent()->get_children()) { + if (tbutton != child) { + dynamic_cast<Gtk::ToggleButton *>(child)->set_active(active); + active = false; + } + } + prefs->setBool(getPrefsPath(pos), true); + tbutton->set_active(true); + sensitive = true; + } else { + prefs->setBool(getPrefsPath(pos), tbutton->get_active()); + } + showing = 0; + _flowbox.set_filter_func(sigc::mem_fun(*this, &InkFlowBox::on_filter)); + _flowbox.set_max_children_per_line(showing); +} + +void InkFlowBox::on_global_toggle(Gtk::ToggleButton *tbutton) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(Glib::ustring("/dialogs/") + get_name() + Glib::ustring("/flowbox/lock"), tbutton->get_active()); + sensitive = true; + if (tbutton->get_active()) { + sensitive = false; + bool active = true; + for (auto child : tbutton->get_parent()->get_children()) { + if (tbutton != child) { + dynamic_cast<Gtk::ToggleButton *>(child)->set_active(active); + active = false; + } + } + } + Glib::ustring iconname = "object-unlocked"; + if (tbutton->get_active()) { + iconname = "object-locked"; + } + tbutton->set_image(*sp_get_icon_image(iconname, Gtk::ICON_SIZE_MENU)); + sensitive = true; +} + +void InkFlowBox::insert(Gtk::Widget *widget, Glib::ustring label, gint pos, bool active, int minwidth) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Gtk::ToggleButton *tbutton = new Gtk::ToggleButton(label, true); + tbutton->set_active(prefs->getBool(getPrefsPath(pos), active)); + tbutton->signal_toggled().connect( + sigc::bind<gint, Gtk::ToggleButton *>(sigc::mem_fun(*this, &InkFlowBox::on_toggle), pos, tbutton)); + _controller.pack_start(*tbutton); + tbutton->show(); + prefs->setBool(getPrefsPath(pos), prefs->getBool(getPrefsPath(pos), active)); + widget->set_size_request(minwidth, -1); + _flowbox.insert(*widget, pos); + showing = 0; + _flowbox.set_filter_func(sigc::mem_fun(*this, &InkFlowBox::on_filter)); + _flowbox.set_max_children_per_line(showing); +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/ink-flow-box.h b/src/ui/widget/ink-flow-box.h new file mode 100644 index 0000000..be84ee9 --- /dev/null +++ b/src/ui/widget/ink-flow-box.h @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Inkflow-box widget. + * This widget allow pack widgets in a flowbox with a controller to show-hide + * + * Author: + * Jabier Arraiza <jabier.arraiza@marker.es> + * + * Copyright (C) 2018 Jabier Arraiza + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_INK_FLOW_BOX_H +#define INKSCAPE_INK_FLOW_BOX_H + +#include <gtkmm/actionbar.h> +#include <gtkmm/box.h> +#include <gtkmm/flowbox.h> +#include <gtkmm/flowboxchild.h> +#include <gtkmm/togglebutton.h> +#include <sigc++/signal.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A flowbox widget with filter controller for dialogs. + */ + +class InkFlowBox : public Gtk::VBox { + public: + InkFlowBox(const gchar *name); + ~InkFlowBox() override; + void insert(Gtk::Widget *widget, Glib::ustring label, gint pos, bool active, int minwidth); + void on_toggle(gint pos, Gtk::ToggleButton *tbutton); + void on_global_toggle(Gtk::ToggleButton *tbutton); + void set_visible(gint pos, bool visible); + bool on_filter(Gtk::FlowBoxChild *child); + Glib::ustring getPrefsPath(gint pos); + /** + * Construct a InkFlowBox. + */ + + private: + Gtk::FlowBox _flowbox; + Gtk::ActionBar _controller; + gint showing; + bool sensitive; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_INK_FLOW_BOX_H + +/* + 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 : diff --git a/src/ui/widget/ink-ruler.cpp b/src/ui/widget/ink-ruler.cpp new file mode 100644 index 0000000..3bb117a --- /dev/null +++ b/src/ui/widget/ink-ruler.cpp @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Ruler widget. Indicates horizontal or vertical position of a cursor in a specified widget. + * + * Copyright (C) 2019 Tavmjong Bah + * + * Rewrite of the 'C' ruler code which came originally from Gimp. + * + * The contents of this file may be used under the GNU General Public License Version 2 or later. + * + */ + +#include "ink-ruler.h" + +#include <iostream> +#include <cmath> + +#include "util/units.h" + +struct SPRulerMetric +{ + gdouble ruler_scale[16]; + gint subdivide[5]; +}; + +// Ruler metric for general use. +static SPRulerMetric const ruler_metric_general = { + { 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000 }, + { 1, 5, 10, 50, 100 } +}; + +// Ruler metric for inch scales. +static SPRulerMetric const ruler_metric_inches = { + { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 }, + { 1, 2, 4, 8, 16 } +}; + +// Half width of pointer triangle. +static double half_width = 5.0; + +namespace Inkscape { +namespace UI { +namespace Widget { + +Ruler::Ruler(Gtk::Orientation orientation) + : _orientation(orientation) + , _backing_store(nullptr) + , _lower(0) + , _upper(1000) + , _max_size(1000) + , _unit(nullptr) + , _backing_store_valid(false) + , _rect() + , _position(0) +{ + set_name("InkRuler"); + + set_events(Gdk::POINTER_MOTION_MASK); + + signal_motion_notify_event().connect(sigc::mem_fun(*this, &Ruler::draw_marker_callback)); +} + +// Set display unit for ruler. +void +Ruler::set_unit(Inkscape::Util::Unit const *unit) +{ + if (_unit != unit) { + _unit = unit; + + _backing_store_valid = false; + queue_draw(); + } +} + +// Set range for ruler, update ticks. +void +Ruler::set_range(const double& lower, const double& upper) +{ + if (_lower != lower || _upper != upper) { + + _lower = lower; + _upper = upper; + _max_size = _upper - _lower; + if (_max_size == 0) { + _max_size = 1; + } + + _backing_store_valid = false; + queue_draw(); + } +} + +// Add a widget (i.e. canvas) to monitor. Note, we don't worry about removing this signal as +// our ruler is tied tightly to the canvas, if one is destroyed, so is the other. +void +Ruler::add_track_widget(Gtk::Widget& widget) +{ + widget.signal_motion_notify_event().connect(sigc::mem_fun(*this, &Ruler::draw_marker_callback), false); // false => connect first +} + + +// Draws marker in response to motion events from canvas. Position is defined in ruler pixel +// coordinates. The routine assumes that the ruler is the same width (height) as the canvas. If +// not, one could use Gtk::Widget::translate_coordinates() to convert the coordinates. +bool +Ruler::draw_marker_callback(GdkEventMotion* motion_event) +{ + double position = 0; + if (_orientation == Gtk::ORIENTATION_HORIZONTAL) { + position = motion_event->x; + } else { + position = motion_event->y; + } + + if (position != _position) { + + _position = position; + + // Find region to repaint (old and new marker positions). + Cairo::RectangleInt new_rect = marker_rect(); + Cairo::RefPtr<Cairo::Region> region = Cairo::Region::create(new_rect); + region->do_union(_rect); + + // Queue repaint + queue_draw_region(region); + + _rect = new_rect; + } + + return false; +} + + +// Find smallest dimension of ruler based on font size. +void +Ruler::size_request (Gtk::Requisition& requisition) const +{ + // Get border size + Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context(); + Gtk::Border border = style_context->get_border(get_state_flags()); + + // Get font size + Pango::FontDescription font = style_context->get_font(get_state_flags()); + int font_size = font.get_size(); + if (!font.get_size_is_absolute()) { + font_size /= Pango::SCALE; + } + + int size = 2 + font_size * 2.0; // Room for labels and ticks + + int width = border.get_left() + border.get_right(); + int height = border.get_top() + border.get_bottom(); + + if (_orientation == Gtk::ORIENTATION_HORIZONTAL) { + width += 1; + height += size; + } else { + width += size; + height += 1; + } + + // Only valid for orientation in question (smallest dimension)! + requisition.width = width; + requisition.height = height; +} + +void +Ruler::get_preferred_width_vfunc (int& minimum_width, int& natural_width) const +{ + Gtk::Requisition requisition; + size_request(requisition); + minimum_width = natural_width = requisition.width; +} + +void +Ruler::get_preferred_height_vfunc (int& minimum_height, int& natural_height) const +{ + Gtk::Requisition requisition; + size_request(requisition); + minimum_height = natural_height = requisition.height; +} + +// Update backing store when scale changes. +// Note: in principle, there should not be a border (ruler ends should match canvas ends). If there +// is a border, we calculate tick position ignoring border width at ends of ruler but move the +// ticks and position marker inside the border. +bool +Ruler::draw_scale(const::Cairo::RefPtr<::Cairo::Context>& cr_in) +{ + + // Get style information + Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context(); + Gtk::Border border = style_context->get_border(get_state_flags()); + Gdk::RGBA foreground = style_context->get_color(get_state_flags()); + + Pango::FontDescription font = style_context->get_font(get_state_flags()); + int font_size = font.get_size(); + if (!font.get_size_is_absolute()) { + font_size /= Pango::SCALE; + } + + Gtk::Allocation allocation = get_allocation(); + int awidth = allocation.get_width(); + int aheight = allocation.get_height(); + + // if (allocation.get_x() != 0 || allocation.get_y() != 0) { + // std::cerr << "Ruler::draw_scale: maybe we do have to handle allocation x and y! " + // << " x: " << allocation.get_x() << " y: " << allocation.get_y() << std::endl; + // } + + // Create backing store (need surface_in to get scale factor correct). + Cairo::RefPtr<Cairo::Surface> surface_in = cr_in->get_target(); + _backing_store = Cairo::Surface::create(surface_in, Cairo::CONTENT_COLOR_ALPHA, awidth, aheight); + + // Get context + Cairo::RefPtr<::Cairo::Context> cr = ::Cairo::Context::create(_backing_store); + style_context->render_background(cr, 0, 0, awidth, aheight); + + cr->set_line_width(1.0); + Gdk::Cairo::set_source_rgba(cr, foreground); + + // Ruler size (only smallest dimension used later). + int rwidth = awidth - (border.get_left() + border.get_right()); + int rheight = aheight - (border.get_top() + border.get_bottom()); + + // Draw bottom/right line of ruler + if (_orientation == Gtk::ORIENTATION_HORIZONTAL) { + cr->rectangle( 0, aheight - border.get_bottom() - 1, awidth, 1); + } else { + cr->rectangle( awidth - border.get_left() - 1, 0, 1, aheight); + std::swap(awidth, aheight); + std::swap(rwidth, rheight); + } + cr->fill(); + + // From here on, awidth is the longest dimension of the ruler, rheight is the shortest. + + // Figure out scale. Largest ticks must be far enough apart to fit largest text in vertical ruler. + // We actually require twice the distance. + unsigned int scale = std::ceil (_max_size); // Largest number + Glib::ustring scale_text = std::to_string(scale); + unsigned int digits = scale_text.length() + 1; // Add one for negative sign. + unsigned int minimum = digits * font_size * 2; + + double pixels_per_unit = awidth/_max_size; // pixel per distance + + SPRulerMetric ruler_metric = ruler_metric_general; + if (_unit == Inkscape::Util::unit_table.getUnit("in")) { + ruler_metric = ruler_metric_inches; + } + + unsigned scale_index; + for (scale_index = 0; scale_index < G_N_ELEMENTS (ruler_metric.ruler_scale)-1; ++scale_index) { + if (ruler_metric.ruler_scale[scale_index] * std::abs (pixels_per_unit) > minimum) break; + } + + // Now we find out what is the subdivide index for the closest ticks we can draw + unsigned divide_index; + for (divide_index = 0; divide_index < G_N_ELEMENTS (ruler_metric.subdivide)-1; ++divide_index) { + if (ruler_metric.ruler_scale[scale_index] * std::abs (pixels_per_unit) < 5 * ruler_metric.subdivide[divide_index+1]) break; + } + + // We'll loop over all ticks. + double pixels_per_tick = pixels_per_unit * + ruler_metric.ruler_scale[scale_index] / ruler_metric.subdivide[divide_index]; + + double units_per_tick = pixels_per_tick/pixels_per_unit; + double ticks_per_unit = 1.0/units_per_tick; + + // Find first and last ticks + int start = 0; + int end = 0; + if (_lower < _upper) { + start = std::floor (_lower * ticks_per_unit); + end = std::ceil (_upper * ticks_per_unit); + } else { + start = std::floor (_upper * ticks_per_unit); + end = std::ceil (_lower * ticks_per_unit); + } + + // std::cout << " start: " << start + // << " end: " << end + // << " pixels_per_unit: " << pixels_per_unit + // << " pixels_per_tick: " << pixels_per_tick + // << std::endl; + + // Loop over all ticks + for (int i = start; i < end+1; ++i) { + + // Position of tick (add 0.5 to center tick on pixel). + double position = std::floor(i*pixels_per_tick - _lower*pixels_per_unit) + 0.5; + + // Height of tick + int height = rheight; + for (int j = divide_index; j > 0; --j) { + if (i%ruler_metric.subdivide[j] == 0) break; + height = height/2 + 1; + } + + // Draw text for major ticks. + if (i%ruler_metric.subdivide[divide_index] == 0) { + + int label_value = std::round(i*units_per_tick); + Glib::ustring label = std::to_string(label_value); + + Glib::RefPtr<Pango::Layout> layout = create_pango_layout(""); + layout->set_font_description(font); + + if (_orientation == Gtk::ORIENTATION_HORIZONTAL) { + layout->set_text(label); + cr->move_to (position+2, border.get_top()); + layout->show_in_cairo_context(cr); + } else { + cr->move_to (border.get_left(), position); + int n = 0; + for (char const &c : label) { + std::string s(1, c); + layout->set_text(s); + int text_width; + int text_height; + layout->get_pixel_size(text_width, text_height); + cr->move_to(border.get_left() + (aheight-text_width)/2.0 - 1, + position + n*0.7*text_height - 1); + layout->show_in_cairo_context(cr); + ++n; + } + // Glyphs are not centered in vertical text... should specify fixed width numbers. + // Glib::RefPtr<Pango::Context> context = layout->get_context(); + // if (_orientation == Gtk::ORIENTATION_VERTICAL) { + // context->set_base_gravity(Pango::GRAVITY_EAST); + // context->set_gravity_hint(Pango::GRAVITY_HINT_STRONG); + // cr->move_to(...) + // cr->save(); + // cr->rotate(M_PI_2); + // layout->show_in_cairo_context(cr); + // cr->restore(); + // } + } + } + + // Draw ticks + if (_orientation == Gtk::ORIENTATION_HORIZONTAL) { + cr->move_to(position, rheight + border.get_top() - height); + cr->line_to(position, rheight + border.get_top()); + } else { + cr->move_to(rheight + border.get_left() - height, position); + cr->line_to(rheight + border.get_left(), position); + } + cr->stroke(); + } + + _backing_store_valid = true; + + return true; +} + +// Draw position marker, we use doubles here. +void +Ruler::draw_marker(const Cairo::RefPtr<::Cairo::Context>& cr) +{ + + // Get style information + Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context(); + Gtk::Border border = style_context->get_border(get_state_flags()); + Gdk::RGBA foreground = style_context->get_color(get_state_flags()); + + Gtk::Allocation allocation = get_allocation(); + const int awidth = allocation.get_width(); + const int aheight = allocation.get_height(); + + // Temp (to verify our redraw rectangle encloses position marker). + // Cairo::RectangleInt rect = marker_rect(); + // cr->set_source_rgb(0, 1.0, 0); + // cr->rectangle (rect.x, rect.y, rect.width, rect.height); + // cr->fill(); + + Gdk::Cairo::set_source_rgba(cr, foreground); + if (_orientation == Gtk::ORIENTATION_HORIZONTAL) { + double offset = aheight - border.get_bottom(); + cr->move_to(_position, offset); + cr->line_to(_position - half_width, offset - half_width); + cr->line_to(_position + half_width, offset - half_width); + cr->close_path(); + } else { + double offset = awidth - border.get_right(); + cr->move_to(offset, _position); + cr->line_to(offset - half_width, _position - half_width); + cr->line_to(offset - half_width, _position + half_width); + cr->close_path(); + } + cr->fill(); +} + +// This is a pixel aligned integer rectangle that encloses the position marker. Used to define the +// redraw area. +Cairo::RectangleInt +Ruler::marker_rect() +{ + // Get border size + Glib::RefPtr<Gtk::StyleContext> style_context = get_style_context(); + Gtk::Border border = style_context->get_border(get_state_flags()); + + Gtk::Allocation allocation = get_allocation(); + const int awidth = allocation.get_width(); + const int aheight = allocation.get_height(); + + int rwidth = awidth - border.get_left() - border.get_right(); + int rheight = aheight - border.get_top() - border.get_bottom(); + + Cairo::RectangleInt rect; + rect.x = 0; + rect.y = 0; + rect.width = 0; + rect.height = 0; + + // Find size of rectangle to enclose triangle. + if (_orientation == Gtk::ORIENTATION_HORIZONTAL) { + rect.x = std::floor(_position - half_width); + rect.y = std::floor(border.get_top() + rheight - half_width); + rect.width = std::ceil(half_width * 2.0 + 1); + rect.height = std::ceil(half_width); + } else { + rect.x = std::floor(border.get_left() + rwidth - half_width); + rect.y = std::floor(_position - half_width); + rect.width = std::ceil(half_width); + rect.height = std::ceil(half_width * 2.0 + 1); + } + + return rect; +} + +// Draw the ruler using the tick backing store. +bool +Ruler::on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) { + + if (!_backing_store_valid) { + draw_scale (cr); + } + + cr->set_source (_backing_store, 0, 0); + cr->paint(); + + draw_marker (cr); + + return true; +} + +// Update ruler on style change (font-size, etc.) +void +Ruler::on_style_updated() { + + Gtk::DrawingArea::on_style_updated(); + + _backing_store_valid = false; // If font-size changed we need to regenerate store. + + queue_resize(); + queue_draw(); +} + +} // 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 : diff --git a/src/ui/widget/ink-ruler.h b/src/ui/widget/ink-ruler.h new file mode 100644 index 0000000..0b9f9af --- /dev/null +++ b/src/ui/widget/ink-ruler.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Ruler widget. Indicates horizontal or vertical position of a cursor in a specified widget. + * + * Copyright (C) 2019 Tavmjong Bah + * + * The contents of this file may be used under the GNU General Public License Version 2 or later. + * + */ + +#ifndef INK_RULER_H +#define INK_RULER_H + +/* Rewrite of the C Ruler. */ + +#include <gtkmm.h> + +namespace Inkscape { +namespace Util { +class Unit; +} +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +class Ruler : public Gtk::DrawingArea +{ +public: + Ruler(Gtk::Orientation orientation); + + void set_unit(Inkscape::Util::Unit const *unit); + void set_range(const double& lower, const double& upper); + + void add_track_widget(Gtk::Widget& widget); + bool draw_marker_callback(GdkEventMotion* motion_event); + + void size_request(Gtk::Requisition& requisition) const; + void get_preferred_width_vfunc( int& minimum_width, int& natural_width ) const override; + void get_preferred_height_vfunc(int& minimum_height, int& natural_height) const override; + +protected: + bool draw_scale(const Cairo::RefPtr<::Cairo::Context>& cr); + void draw_marker(const Cairo::RefPtr<::Cairo::Context>& cr); + Cairo::RectangleInt marker_rect(); + bool on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) override; + void on_style_updated() override; + +private: + Gtk::Orientation _orientation; + + Inkscape::Util::Unit const* _unit; + double _lower; + double _upper; + double _position; + double _max_size; + double _font_scale; + + bool _backing_store_valid; + + Cairo::RefPtr<::Cairo::Surface> _backing_store; + Cairo::RectangleInt _rect; +}; + +} // Namespace Inkscape +} +} +#endif // INK_RULER_H + +/* + 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 : diff --git a/src/ui/widget/ink-spinscale.cpp b/src/ui/widget/ink-spinscale.cpp new file mode 100644 index 0000000..7c546c2 --- /dev/null +++ b/src/ui/widget/ink-spinscale.cpp @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2017 Tavmjong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +/** \file + A widget that allows entering a numerical value either by + clicking/dragging on a custom Gtk::Scale or by using a + Gtk::SpinButton. The custom Gtk::Scale differs from the stock + Gtk::Scale in that it includes a label to save space and has a + "slow dragging" mode triggered by the Alt key. +*/ + +#include "ink-spinscale.h" +#include <gdkmm/general.h> +#include <gdkmm/cursor.h> +#include <gdkmm/event.h> + +#include <gtkmm/spinbutton.h> + +#include <gdk/gdk.h> + +#include <iostream> +#include <utility> + +InkScale::InkScale(Glib::RefPtr<Gtk::Adjustment> adjustment, Gtk::SpinButton* spinbutton) + : Glib::ObjectBase("InkScale") + , Gtk::Scale(adjustment) + , _spinbutton(spinbutton) + , _dragging(false) + , _drag_start(0) + , _drag_offset(0) +{ + set_name("InkScale"); + // std::cout << "GType name: " << G_OBJECT_TYPE_NAME(gobj()) << std::endl; +} + +void +InkScale::set_label(Glib::ustring label) { + _label = label; +} + +bool +InkScale::on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) { + + Gtk::Range::on_draw(cr); + + // Get SpinButton style info... + auto style_spin = _spinbutton->get_style_context(); + auto state_spin = style_spin->get_state(); + Gdk::RGBA text_color = style_spin->get_color( state_spin ); + + // Create Pango layout. + auto layout_label = create_pango_layout(_label); + layout_label->set_ellipsize( Pango::ELLIPSIZE_END ); + layout_label->set_width(PANGO_SCALE * get_width()); + + // Get y location of SpinButton text (to match vertical position of SpinButton text). + int x, y; + _spinbutton->get_layout_offsets(x, y); + + // Fill widget proportional to value. + double fraction = get_fraction(); + + // Get trough rectangle and clipping point for text. + Gdk::Rectangle slider_area = get_range_rect(); + double clip_text_x = slider_area.get_x() + slider_area.get_width() * fraction; + + // Render text in normal text color. + cr->save(); + cr->rectangle(clip_text_x, 0, get_width(), get_height()); + cr->clip(); + Gdk::Cairo::set_source_rgba(cr, text_color); + //cr->set_source_rgba(0, 0, 0, 1); + cr->move_to(5, y ); + layout_label->show_in_cairo_context(cr); + cr->restore(); + + // Render text, clipped, in white over bar (TODO: use same color as SpinButton progress bar). + cr->save(); + cr->rectangle(0, 0, clip_text_x, get_height()); + cr->clip(); + cr->set_source_rgba(1, 1, 1, 1); + cr->move_to(5, y); + layout_label->show_in_cairo_context(cr); + cr->restore(); + + return true; +} + +bool +InkScale::on_button_press_event(GdkEventButton* button_event) { + + if (! (button_event->state & GDK_MOD1_MASK) ) { + bool constrained = button_event->state & GDK_CONTROL_MASK; + set_adjustment_value(button_event->x, constrained); + } + + // Dragging must be initialized after any adjustment due to button press. + _dragging = true; + _drag_start = button_event->x; + _drag_offset = get_width() * get_fraction(); + + return true; +} + +bool +InkScale::on_button_release_event(GdkEventButton* button_event) { + + _dragging = false; + return true; +} + +bool +InkScale::on_motion_notify_event(GdkEventMotion* motion_event) { + + double x = motion_event->x; + double y = motion_event->y; + + if (_dragging) { + + if (! (motion_event->state & GDK_MOD1_MASK) ) { + // Absolute change + bool constrained = motion_event->state & GDK_CONTROL_MASK; + set_adjustment_value(x, constrained); + } else { + // Relative change + double xx = (_drag_offset + (x - _drag_start) * 0.1); + set_adjustment_value(xx); + } + return true; + } + + if (! (motion_event->state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK))) { + + auto display = get_display(); + auto cursor = Gdk::Cursor::create(display, Gdk::SB_UP_ARROW); + // Get Gdk::window (not Gtk::window).. set cursor for entire window. + // Would need to unset with leave event. + // get_window()->set_cursor( cursor ); + + // Can't see how to do this the C++ way since GdkEventMotion + // is a structure with a C window member. There is a gdkmm + // wrapping function for Gdk::EventMotion but only in unstable. + + // If the cursor theme doesn't have the `sb_up_arrow` cursor then the pointer will be NULL + if (cursor) + gdk_window_set_cursor( motion_event->window, cursor->gobj() ); + } + + return false; +} + +double +InkScale::get_fraction() { + + Glib::RefPtr<Gtk::Adjustment> adjustment = get_adjustment(); + double upper = adjustment->get_upper(); + double lower = adjustment->get_lower(); + double value = adjustment->get_value(); + double fraction = (value - lower)/(upper - lower); + + return fraction; +} + +void +InkScale::set_adjustment_value(double x, bool constrained) { + + Glib::RefPtr<Gtk::Adjustment> adjustment = get_adjustment(); + double upper = adjustment->get_upper(); + double lower = adjustment->get_lower(); + double range = upper-lower; + + Gdk::Rectangle slider_area = get_range_rect(); + double fraction = (x - slider_area.get_x()) / (double)slider_area.get_width(); + double value = fraction * range + lower; + + if (constrained) { + // TODO: do we want preferences for (any of) these? + if (fmod(range+1,16) == 0) { + value = round(value/16) * 16; + } else if (range >= 1000 && fmod(upper,100) == 0) { + value = round(value/100) * 100; + } else if (range >= 100 && fmod(upper,10) == 0) { + value = round(value/10) * 10; + } else if (range > 20 && fmod(upper,5) == 0) { + value = round(value/5) * 5; + } else if (range > 2) { + value = round(value); + } else if (range <= 2) { + value = round(value*10) / 10; + } + } + + adjustment->set_value( value ); +} + +/*******************************************************************/ + +InkSpinScale::InkSpinScale(double value, double lower, + double upper, double step_increment, + double page_increment, double page_size) +{ + set_name("InkSpinScale"); + + g_assert (upper - lower > 0); + + _adjustment = Gtk::Adjustment::create(value, + lower, + upper, + step_increment, + page_increment, + page_size); + + _spinbutton = Gtk::manage(new Gtk::SpinButton(_adjustment)); + _spinbutton->set_numeric(); + _spinbutton->signal_key_release_event().connect(sigc::mem_fun(*this,&InkSpinScale::on_key_release_event),false); + + _scale = Gtk::manage(new InkScale(_adjustment, _spinbutton)); + _scale->set_draw_value(false); + + pack_end( *_spinbutton, Gtk::PACK_SHRINK ); + pack_end( *_scale, Gtk::PACK_EXPAND_WIDGET ); +} + +InkSpinScale::InkSpinScale(Glib::RefPtr<Gtk::Adjustment> adjustment) + : _adjustment(std::move(adjustment)) +{ + set_name("InkSpinScale"); + + g_assert (_adjustment->get_upper() - _adjustment->get_lower() > 0); + + _spinbutton = Gtk::manage(new Gtk::SpinButton(_adjustment)); + _spinbutton->set_numeric(); + + _scale = Gtk::manage(new InkScale(_adjustment, _spinbutton)); + _scale->set_draw_value(false); + + pack_end( *_spinbutton, Gtk::PACK_SHRINK ); + pack_end( *_scale, Gtk::PACK_EXPAND_WIDGET ); +} + +void +InkSpinScale::set_label(Glib::ustring label) { + _scale->set_label(label); +} + +void +InkSpinScale::set_digits(int digits) { + _spinbutton->set_digits(digits); +} + +int +InkSpinScale::get_digits() const { + return _spinbutton->get_digits(); +} + +void +InkSpinScale::set_focus_widget(GtkWidget * focus_widget) { + _focus_widget = focus_widget; +} + +// Return focus to canvas. +bool +InkSpinScale::on_key_release_event(GdkEventKey* key_event) { + + switch (key_event->keyval) { + case GDK_KEY_Escape: + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + { + if (_focus_widget) { + gtk_widget_grab_focus( _focus_widget ); + } + } + break; + } + + return false; +} diff --git a/src/ui/widget/ink-spinscale.h b/src/ui/widget/ink-spinscale.h new file mode 100644 index 0000000..ada7efd --- /dev/null +++ b/src/ui/widget/ink-spinscale.h @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INK_SPINSCALE_H +#define INK_SPINSCALE_H + +/* + * Authors: + * Tavmjong Bah <tavmjong@free.fr> + * + * Copyright (C) 2017 Tavmjong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +/** + A widget that allows entering a numerical value either by + clicking/dragging on a custom Gtk::Scale or by using a + Gtk::SpinButton. The custom Gtk::Scale differs from the stock + Gtk::Scale in that it includes a label to save space and has a + "slow-dragging" mode triggered by the Alt key. +*/ + +#include <glibmm/ustring.h> + +#include <gtkmm/box.h> +#include <gtkmm/scale.h> + +namespace Gtk { + class SpinButton; +} + +class InkScale : public Gtk::Scale +{ + public: + InkScale(Glib::RefPtr<Gtk::Adjustment>, Gtk::SpinButton* spinbutton); + ~InkScale() override = default;; + + void set_label(Glib::ustring label); + + bool on_draw(const::Cairo::RefPtr<::Cairo::Context>& cr) override; + + protected: + + bool on_button_press_event(GdkEventButton* button_event) override; + bool on_button_release_event(GdkEventButton* button_event) override; + bool on_motion_notify_event(GdkEventMotion* motion_event) override; + + private: + + double get_fraction(); + void set_adjustment_value(double x, bool constrained = false); + + Gtk::SpinButton * _spinbutton; // Needed to get placement/text color. + Glib::ustring _label; + + bool _dragging; + double _drag_start; + double _drag_offset; +}; + +class InkSpinScale : public Gtk::Box +{ + public: + + // Create an InkSpinScale with a new adjustment. + InkSpinScale(double value, + double lower, + double upper, + double step_increment = 1, + double page_increment = 10, + double page_size = 0); + + // Create an InkSpinScale with a preexisting adjustment. + InkSpinScale(Glib::RefPtr<Gtk::Adjustment>); + + ~InkSpinScale() override = default;; + + void set_label(Glib::ustring label); + void set_digits(int digits); + int get_digits() const; + void set_focus_widget(GtkWidget *focus_widget); + Glib::RefPtr<Gtk::Adjustment> get_adjustment() { return _adjustment; }; + + protected: + + InkScale* _scale; + Gtk::SpinButton* _spinbutton; + Glib::RefPtr<Gtk::Adjustment> _adjustment; + GtkWidget* _focus_widget = nullptr; + + bool on_key_release_event(GdkEventKey* key_event) override; + + private: + +}; + +#endif // INK_SPINSCALE_H diff --git a/src/ui/widget/insertordericon.cpp b/src/ui/widget/insertordericon.cpp new file mode 100644 index 0000000..0a21f3c --- /dev/null +++ b/src/ui/widget/insertordericon.cpp @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/widget/insertordericon.h" + +#include "layertypeicon.h" +#include "ui/icon-loader.h" +#include "ui/icon-names.h" +#include "widgets/toolbox.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +InsertOrderIcon::InsertOrderIcon() : + Glib::ObjectBase(typeid(InsertOrderIcon)), + Gtk::CellRendererPixbuf(), + _pixTopName(INKSCAPE_ICON("insert-top")), + _pixBottomName(INKSCAPE_ICON("insert-bottom")), + _property_active(*this, "active", 0), + _property_pixbuf_top(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr)), + _property_pixbuf_bottom(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr)) +{ + + property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE; + + _property_pixbuf_top = sp_get_icon_pixbuf(_pixTopName, GTK_ICON_SIZE_MENU); + _property_pixbuf_bottom = sp_get_icon_pixbuf(_pixBottomName, GTK_ICON_SIZE_MENU); + + property_pixbuf() = Glib::RefPtr<Gdk::Pixbuf>(nullptr); +} + + +void InsertOrderIcon::get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const +{ + Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h); + + if (min_h) { + min_h += (min_h) >> 1; + } + + if (nat_h) { + nat_h += (nat_h) >> 1; + } +} + +void InsertOrderIcon::get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const +{ + Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w); + + if (min_w) { + min_w += (min_w) >> 1; + } + + if (nat_w) { + nat_w += (nat_w) >> 1; + } +} + +void InsertOrderIcon::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) +{ + switch (_property_active.get_value()) + { + case 1: + property_pixbuf() = _property_pixbuf_top; + break; + case 2: + property_pixbuf() = _property_pixbuf_bottom; + break; + default: + property_pixbuf() = Glib::RefPtr<Gdk::Pixbuf>(nullptr); + break; + } + Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags ); +} + +bool InsertOrderIcon::activate_vfunc(GdkEvent* /*event*/, + Gtk::Widget& /*widget*/, + const Glib::ustring& /*path*/, + const Gdk::Rectangle& /*background_area*/, + const Gdk::Rectangle& /*cell_area*/, + Gtk::CellRendererState /*flags*/) +{ + return false; +} + + +} // namespace Widget +} // 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 : + + diff --git a/src/ui/widget/insertordericon.h b/src/ui/widget/insertordericon.h new file mode 100644 index 0000000..7ca1cef --- /dev/null +++ b/src/ui/widget/insertordericon.h @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __UI_DIALOG_INSERTORDERICON_H__ +#define __UI_DIALOG_INSERTORDERICON_H__ +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/cellrendererpixbuf.h> +#include <gtkmm/widget.h> + +#include <glibmm/property.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +class InsertOrderIcon : public Gtk::CellRendererPixbuf { +public: + InsertOrderIcon(); + ~InsertOrderIcon() override = default;; + + Glib::PropertyProxy<int> property_active() { return _property_active.get_proxy(); } + Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_on(); + Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_off(); + +protected: + + void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) override; + + void get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const override; + + void get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const override; + + bool activate_vfunc(GdkEvent *event, + Gtk::Widget &widget, + const Glib::ustring &path, + const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, + Gtk::CellRendererState flags) override; + + +private: + int phys; + + Glib::ustring _pixTopName; + Glib::ustring _pixBottomName; + + Glib::Property<int> _property_active; + Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_top; + Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_bottom; + +}; + + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + + +#endif /* __UI_DIALOG_IMAGETOGGLER_H__ */ + +/* + 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 : diff --git a/src/ui/widget/label-tool-item.cpp b/src/ui/widget/label-tool-item.cpp new file mode 100644 index 0000000..979cfa2 --- /dev/null +++ b/src/ui/widget/label-tool-item.cpp @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * A label that can be added to a toolbar + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "label-tool-item.h" + +#include <gtkmm/label.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * \brief Create a tool-item containing a label + * + * \param[in] label The text to display in the label + * \param[in] mnemonic True if text should use a mnemonic + */ +LabelToolItem::LabelToolItem(const Glib::ustring& label, bool mnemonic) + : _label(Gtk::manage(new Gtk::Label(label, mnemonic))) +{ + add(*_label); + show_all(); +} + +/** + * \brief Set the markup text in the label + * + * \param[in] str The markup text + */ +void +LabelToolItem::set_markup(const Glib::ustring& str) +{ + _label->set_markup(str); +} + +/** + * \brief Sets whether label uses Pango markup + * + * \param[in] setting true if the label text should be parsed for markup + */ +void +LabelToolItem::set_use_markup(bool setting) +{ + _label->set_use_markup(setting); +} + +} +} +} +/* + 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 : diff --git a/src/ui/widget/label-tool-item.h b/src/ui/widget/label-tool-item.h new file mode 100644 index 0000000..1fe6892 --- /dev/null +++ b/src/ui/widget/label-tool-item.h @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * A label that can be added to a toolbar + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_LABEL_TOOL_ITEM_H +#define SEEN_LABEL_TOOL_ITEM_H + +#include <gtkmm/toolitem.h> + +namespace Gtk { +class Label; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * \brief A label that can be added to a toolbar + */ +class LabelToolItem : public Gtk::ToolItem { +private: + Gtk::Label *_label; + +public: + LabelToolItem(const Glib::ustring& label, bool mnemonic = false); + + void set_markup(const Glib::ustring& str); + void set_use_markup(bool setting = true); +}; +} +} +} + +#endif +/* + 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 : diff --git a/src/ui/widget/labelled.cpp b/src/ui/widget/labelled.cpp new file mode 100644 index 0000000..b320b70 --- /dev/null +++ b/src/ui/widget/labelled.cpp @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Carl Hetherington <inkscape@carlh.net> + * Derek P. Moore <derekm@hackunix.org> + * + * Copyright (C) 2004 Carl Hetherington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "labelled.h" +#include "ui/icon-loader.h" +#include <gtkmm/image.h> +#include <gtkmm/label.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +Labelled::Labelled(Glib::ustring const &label, Glib::ustring const &tooltip, + Gtk::Widget *widget, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : _widget(widget), + _label(new Gtk::Label(label, Gtk::ALIGN_START, Gtk::ALIGN_CENTER, mnemonic)), + _suffix(new Gtk::Label(suffix, Gtk::ALIGN_START)) +{ + g_assert(g_utf8_validate(icon.c_str(), -1, nullptr)); + if (icon != "") { + _icon = Gtk::manage(sp_get_icon_image(icon, Gtk::ICON_SIZE_LARGE_TOOLBAR)); + pack_start(*_icon, Gtk::PACK_SHRINK); + } + + set_spacing(6); + // Setting margins separately allows for more control over them + set_margin_start(6); + set_margin_end(6); + pack_start(*Gtk::manage(_label), Gtk::PACK_SHRINK); + pack_start(*Gtk::manage(_widget), Gtk::PACK_SHRINK); + if (mnemonic) { + _label->set_mnemonic_widget(*_widget); + } + widget->set_tooltip_text(tooltip); +} + + +void Labelled::setWidgetSizeRequest(int width, int height) +{ + if (_widget) + _widget->set_size_request(width, height); + + +} + +Gtk::Widget const * +Labelled::getWidget() const +{ + return _widget; +} + +Gtk::Label const * +Labelled::getLabel() const +{ + return _label; +} + +void +Labelled::setLabelText(const Glib::ustring &str) +{ + _label->set_text(str); +} + +void +Labelled::setTooltipText(const Glib::ustring &tooltip) +{ + _label->set_tooltip_text(tooltip); + _widget->set_tooltip_text(tooltip); +} + +bool Labelled::on_mnemonic_activate ( bool group_cycling ) +{ + return _widget->mnemonic_activate ( group_cycling ); +} + +void +Labelled::set_hexpand(bool expand) +{ + // should only have 2 children, but second child may not be _widget + child_property_pack_type(*get_children().back()) = expand ? Gtk::PACK_END + : Gtk::PACK_START; + + Gtk::HBox::set_hexpand(expand); +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/labelled.h b/src/ui/widget/labelled.h new file mode 100644 index 0000000..4620b1a --- /dev/null +++ b/src/ui/widget/labelled.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Carl Hetherington <inkscape@carlh.net> + * Derek P. Moore <derekm@hackunix.org> + * + * Copyright (C) 2004 Carl Hetherington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_LABELLED_H +#define INKSCAPE_UI_WIDGET_LABELLED_H + +#include <gtkmm/box.h> + +namespace Gtk { +class Image; +class Label; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Adds a label with optional icon or suffix to another widget. + */ +class Labelled : public Gtk::HBox +{ +public: + + /** + * Construct a Labelled Widget. + * + * @param label Label. + * @param widget Widget to label; should be allocated with new, as it will + * be passed to Gtk::manage(). + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the text + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to true). + */ + Labelled(Glib::ustring const &label, Glib::ustring const &tooltip, + Gtk::Widget *widget, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Allow the setting of the width of the labelled widget + */ + void setWidgetSizeRequest(int width, int height); + Gtk::Widget const *getWidget() const; + Gtk::Label const *getLabel() const; + + void setLabelText(const Glib::ustring &str); + void setTooltipText(const Glib::ustring &tooltip); + + void set_hexpand(bool expand = true); + +private: + bool on_mnemonic_activate( bool group_cycling ) override; + +protected: + + Gtk::Widget *_widget; + Gtk::Label *_label; + Gtk::Label *_suffix; + Gtk::Image *_icon; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_LABELLED_H + +/* + 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 : diff --git a/src/ui/widget/layer-selector.cpp b/src/ui/widget/layer-selector.cpp new file mode 100644 index 0000000..779542c --- /dev/null +++ b/src/ui/widget/layer-selector.cpp @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Inkscape::Widgets::LayerSelector - layer selector widget + * + * Authors: + * MenTaLguY <mental@rydia.net> + * Abhishek Sharma + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cstring> +#include <string> + +#include "ui/dialog/layer-properties.h" +#include "ui/icon-loader.h" +#include <boost/range/adaptor/filtered.hpp> +#include <boost/range/adaptor/reversed.hpp> +#include <glibmm/i18n.h> + +#include "desktop.h" + +#include "document.h" +#include "document-undo.h" +#include "layer-manager.h" +#include "ui/icon-names.h" +#include "ui/util.h" +#include "util/reverse-list.h" +#include "verbs.h" +#include "xml/node-event-vector.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +namespace { + +class AlternateIcons : public Gtk::HBox { +public: + AlternateIcons(Gtk::BuiltinIconSize size, Glib::ustring const &a, Glib::ustring const &b) + : _a(nullptr), _b(nullptr) + { + set_name("AlternateIcons"); + if (!a.empty()) { + _a = Gtk::manage(sp_get_icon_image(a, size)); + _a->set_no_show_all(true); + add(*_a); + } + if (!b.empty()) { + _b = Gtk::manage(sp_get_icon_image(b, size)); + _b->set_no_show_all(true); + add(*_b); + } + setState(false); + } + + bool state() const { return _state; } + void setState(bool state) { + _state = state; + if (_state) { + if (_a) { + _a->hide(); + } + if (_b) { + _b->show(); + } + } else { + if (_a) { + _a->show(); + } + if (_b) { + _b->hide(); + } + } + } + +private: + Gtk::Image *_a; + Gtk::Image *_b; + bool _state; +}; + +} + +/** LayerSelector constructor. Creates lock and hide buttons, + * initializes the layer dropdown selector with a label renderer, + * and hooks up signal for setting the desktop layer when the + * selector is changed. + */ +LayerSelector::LayerSelector(SPDesktop *desktop) +: _desktop(nullptr), _layer(nullptr) +{ + set_name("LayerSelector"); + AlternateIcons *label; + + label = Gtk::manage(new AlternateIcons(Gtk::ICON_SIZE_MENU, + INKSCAPE_ICON("object-visible"), INKSCAPE_ICON("object-hidden"))); + _visibility_toggle.add(*label); + _visibility_toggle.signal_toggled().connect( + sigc::compose( + sigc::mem_fun(*label, &AlternateIcons::setState), + sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active) + ) + ); + _visibility_toggled_connection = _visibility_toggle.signal_toggled().connect( + sigc::compose( + sigc::mem_fun(*this, &LayerSelector::_hideLayer), + sigc::mem_fun(_visibility_toggle, &Gtk::ToggleButton::get_active) + ) + ); + + _visibility_toggle.set_relief(Gtk::RELIEF_NONE); + _visibility_toggle.set_tooltip_text(_("Toggle current layer visibility")); + pack_start(_visibility_toggle, Gtk::PACK_EXPAND_PADDING); + + label = Gtk::manage(new AlternateIcons(Gtk::ICON_SIZE_MENU, + INKSCAPE_ICON("object-unlocked"), INKSCAPE_ICON("object-locked"))); + _lock_toggle.add(*label); + _lock_toggle.signal_toggled().connect( + sigc::compose( + sigc::mem_fun(*label, &AlternateIcons::setState), + sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active) + ) + ); + _lock_toggled_connection = _lock_toggle.signal_toggled().connect( + sigc::compose( + sigc::mem_fun(*this, &LayerSelector::_lockLayer), + sigc::mem_fun(_lock_toggle, &Gtk::ToggleButton::get_active) + ) + ); + + _lock_toggle.set_relief(Gtk::RELIEF_NONE); + _lock_toggle.set_tooltip_text(_("Lock or unlock current layer")); + pack_start(_lock_toggle, Gtk::PACK_EXPAND_PADDING); + + _selector.set_tooltip_text(_("Current layer")); + pack_start(_selector, Gtk::PACK_EXPAND_WIDGET); + + _layer_model = Gtk::ListStore::create(_model_columns); + _selector.set_model(_layer_model); + _selector.pack_start(_label_renderer); + _selector.set_cell_data_func( + _label_renderer, + sigc::mem_fun(*this, &LayerSelector::_prepareLabelRenderer) + ); + + _selection_changed_connection = _selector.signal_changed().connect( + sigc::mem_fun(*this, &LayerSelector::_setDesktopLayer) + ); + setDesktop(desktop); +} + +/** Destructor - disconnects signal handler + */ +LayerSelector::~LayerSelector() { + setDesktop(nullptr); + _selection_changed_connection.disconnect(); +} + +/** Sets the desktop for the widget. First disconnects signals + * for the current desktop, then stores the pointer to the + * given \a desktop, and attaches its signals to this one. + * Then it selects the current layer for the desktop. + */ +void LayerSelector::setDesktop(SPDesktop *desktop) { + if ( desktop == _desktop ) { + return; + } + + if (_desktop) { +// _desktop_shutdown_connection.disconnect(); + if (_current_layer_changed_connection) + _current_layer_changed_connection.disconnect(); + if (_layers_changed_connection) + _layers_changed_connection.disconnect(); +// g_signal_handlers_disconnect_by_func(_desktop, (gpointer)&detach, this); + } + _desktop = desktop; + if (_desktop) { + // TODO we need a different signal for this, really..s +// _desktop_shutdown_connection = _desktop->connectShutdown( +// sigc::bind (sigc::ptr_fun (detach), this)); +// g_signal_connect_after(_desktop, "shutdown", GCallback(detach), this); + + LayerManager *mgr = _desktop->layer_manager; + if ( mgr ) { + _current_layer_changed_connection = mgr->connectCurrentLayerChanged( sigc::mem_fun(*this, &LayerSelector::_selectLayer) ); + //_layerUpdatedConnection = mgr->connectLayerDetailsChanged( sigc::mem_fun(*this, &LayerSelector::_updateLayer) ); + _layers_changed_connection = mgr->connectChanged( sigc::mem_fun(*this, &LayerSelector::_layersChanged) ); + } + + _selectLayer(_desktop->currentLayer()); + } +} + +namespace { + +class is_layer { +public: + is_layer(SPDesktop *desktop) : _desktop(desktop) {} + bool operator()(SPObject &object) const { + return _desktop->isLayer(&object); + } +private: + SPDesktop *_desktop; +}; + +class column_matches_object { +public: + column_matches_object(Gtk::TreeModelColumn<SPObject *> const &column, + SPObject &object) + : _column(column), _object(object) {} + bool operator()(Gtk::TreeModel::const_iterator const &iter) const { + SPObject *current=(*iter)[_column]; + return current == &_object; + } +private: + Gtk::TreeModelColumn<SPObject *> const &_column; + SPObject &_object; +}; + +} + +void LayerSelector::_layersChanged() +{ + if (_desktop) { + /* + * This code fixes #166691 but causes issues #1066543 and #1080378. + * Comment out until solution found. + */ + //_selectLayer(_desktop->currentLayer()); + } +} + +/** Selects the given layer in the dropdown selector. + */ +void LayerSelector::_selectLayer(SPObject *layer) { + using Inkscape::Util::List; + using Inkscape::Util::cons; + using Inkscape::Util::reverse_list; + + _selection_changed_connection.block(); + _visibility_toggled_connection.block(); + _lock_toggled_connection.block(); + + while (!_layer_model->children().empty()) { + Gtk::ListStore::iterator first_row(_layer_model->children().begin()); + _destroyEntry(first_row); + _layer_model->erase(first_row); + } + + SPObject *root=_desktop->currentRoot(); + + if (_layer) { + sp_object_unref(_layer, nullptr); + _layer = nullptr; + } + + if (layer) { + List<SPObject &> hierarchy=reverse_list<SPObject::ParentIterator>(layer, root); + if ( layer == root ) { + _buildEntries(0, cons(*root, hierarchy)); + } else if (hierarchy) { + _buildSiblingEntries(0, *root, hierarchy); + } + + Gtk::TreeIter row( + std::find_if( + _layer_model->children().begin(), + _layer_model->children().end(), + column_matches_object(_model_columns.object, *layer) + ) + ); + if ( row != _layer_model->children().end() ) { + _selector.set_active(row); + } + + _layer = layer; + sp_object_ref(_layer, nullptr); + } + + if ( !layer || layer == root ) { + _visibility_toggle.set_sensitive(false); + _visibility_toggle.set_active(false); + _lock_toggle.set_sensitive(false); + _lock_toggle.set_active(false); + } else { + _visibility_toggle.set_sensitive(true); + _visibility_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false )); + _lock_toggle.set_sensitive(true); + _lock_toggle.set_active(( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false )); + } + + _lock_toggled_connection.unblock(); + _visibility_toggled_connection.unblock(); + _selection_changed_connection.unblock(); +} + +/** Sets the current desktop layer to the actively selected layer. + */ +void LayerSelector::_setDesktopLayer() { + Gtk::ListStore::iterator selected(_selector.get_active()); + SPObject *layer=_selector.get_active()->get_value(_model_columns.object); + if ( _desktop && layer ) { + _current_layer_changed_connection.block(); + _layers_changed_connection.block(); + + _desktop->layer_manager->setCurrentLayer(layer); + + _current_layer_changed_connection.unblock(); + _layers_changed_connection.unblock(); + + _selectLayer(_desktop->currentLayer()); + } + if (_desktop && _desktop->canvas) { + gtk_widget_grab_focus (GTK_WIDGET(_desktop->canvas)); + } +} + +/** Creates rows in the _layer_model data structure for each item + * in \a hierarchy, to a given \a depth. + */ +void LayerSelector::_buildEntries(unsigned depth, + Inkscape::Util::List<SPObject &> hierarchy) +{ + using Inkscape::Util::List; + using Inkscape::Util::rest; + + _buildEntry(depth, *hierarchy); + + List<SPObject &> remainder=rest(hierarchy); + if (remainder) { + _buildEntries(depth+1, remainder); + } else { + _buildSiblingEntries(depth+1, *hierarchy, remainder); + } +} + +/** Creates entries in the _layer_model data structure for + * all siblings of the first child in \a parent. + */ +void LayerSelector::_buildSiblingEntries( + unsigned depth, SPObject &parent, + Inkscape::Util::List<SPObject &> hierarchy +) { + using Inkscape::Util::rest; + + auto siblings = parent.children | boost::adaptors::filtered(is_layer(_desktop)) | boost::adaptors::reversed; + + SPObject *layer( hierarchy ? &*hierarchy : nullptr ); + + for (auto& sib: siblings) { + _buildEntry(depth, sib); + if ( &sib == layer ) { + _buildSiblingEntries(depth+1, *layer, rest(hierarchy)); + } + } +} + +namespace { + +struct Callbacks { + sigc::slot<void> update_row; + sigc::slot<void> update_list; +}; + +void attribute_changed(Inkscape::XML::Node */*repr*/, gchar const *name, + gchar const */*old_value*/, gchar const */*new_value*/, + bool /*is_interactive*/, void *data) +{ + if ( !std::strcmp(name, "inkscape:groupmode") ) { + reinterpret_cast<Callbacks *>(data)->update_list(); + } else { + reinterpret_cast<Callbacks *>(data)->update_row(); + } +} + +void node_added(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, void *data) { + gchar const *mode=child->attribute("inkscape:groupmode"); + if ( mode && !std::strcmp(mode, "layer") ) { + reinterpret_cast<Callbacks *>(data)->update_list(); + } +} + +void node_removed(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, void *data) { + gchar const *mode=child->attribute("inkscape:groupmode"); + if ( mode && !std::strcmp(mode, "layer") ) { + reinterpret_cast<Callbacks *>(data)->update_list(); + } +} + +void node_reordered(Inkscape::XML::Node */*parent*/, Inkscape::XML::Node *child, + Inkscape::XML::Node */*old_ref*/, Inkscape::XML::Node */*new_ref*/, + void *data) +{ + gchar const *mode=child->attribute("inkscape:groupmode"); + if ( mode && !std::strcmp(mode, "layer") ) { + reinterpret_cast<Callbacks *>(data)->update_list(); + } +} + +void update_row_for_object(SPObject *object, + Gtk::TreeModelColumn<SPObject *> const &column, + Glib::RefPtr<Gtk::ListStore> const &model) +{ + Gtk::TreeIter row( + std::find_if( + model->children().begin(), + model->children().end(), + column_matches_object(column, *object) + ) + ); + if ( row != model->children().end() ) { + model->row_changed(model->get_path(row), row); + } +} + +void rebuild_all_rows(sigc::slot<void, SPObject *> rebuild, SPDesktop *desktop) +{ + rebuild(desktop->currentLayer()); +} + +} + +void LayerSelector::_protectUpdate(sigc::slot<void> slot) { + bool visibility_blocked=_visibility_toggled_connection.blocked(); + bool lock_blocked=_lock_toggled_connection.blocked(); + _visibility_toggled_connection.block(true); + _lock_toggled_connection.block(true); + slot(); + + SPObject *layer = _desktop ? _desktop->currentLayer() : nullptr; + if ( layer ) { + bool wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false ); + if ( _lock_toggle.get_active() != wantedValue ) { + _lock_toggle.set_active( wantedValue ); + } + wantedValue = ( SP_IS_ITEM(layer) ? SP_ITEM(layer)->isHidden() : false ); + if ( _visibility_toggle.get_active() != wantedValue ) { + _visibility_toggle.set_active( wantedValue ); + } + } + _visibility_toggled_connection.block(visibility_blocked); + _lock_toggled_connection.block(lock_blocked); +} + +/** Builds and appends a row in the layer model object. + */ +void LayerSelector::_buildEntry(unsigned depth, SPObject &object) { + Inkscape::XML::NodeEventVector *vector; + + Callbacks *callbacks=new Callbacks(); + + callbacks->update_row = sigc::bind( + sigc::mem_fun(*this, &LayerSelector::_protectUpdate), + sigc::bind( + sigc::ptr_fun(&update_row_for_object), + &object, _model_columns.object, _layer_model + ) + ); + + SPObject *layer=_desktop->currentLayer(); + if ( (&object == layer) || (&object == layer->parent) ) { + callbacks->update_list = sigc::bind( + sigc::mem_fun(*this, &LayerSelector::_protectUpdate), + sigc::bind( + sigc::ptr_fun(&rebuild_all_rows), + sigc::mem_fun(*this, &LayerSelector::_selectLayer), + _desktop + ) + ); + + Inkscape::XML::NodeEventVector events = { + &node_added, + &node_removed, + &attribute_changed, + nullptr, + &node_reordered + }; + + vector = new Inkscape::XML::NodeEventVector(events); + } else { + Inkscape::XML::NodeEventVector events = { + nullptr, + nullptr, + &attribute_changed, + nullptr, + nullptr + }; + + vector = new Inkscape::XML::NodeEventVector(events); + } + + Gtk::ListStore::iterator row(_layer_model->append()); + + row->set_value(_model_columns.depth, depth); + + sp_object_ref(&object, nullptr); + row->set_value(_model_columns.object, &object); + + Inkscape::GC::anchor(object.getRepr()); + row->set_value(_model_columns.repr, object.getRepr()); + + row->set_value(_model_columns.callbacks, reinterpret_cast<void *>(callbacks)); + + sp_repr_add_listener(object.getRepr(), vector, callbacks); +} + +/** Removes a row from the _model_columns object, disconnecting listeners + * on the slot. + */ +void LayerSelector::_destroyEntry(Gtk::ListStore::iterator const &row) { + Callbacks *callbacks=reinterpret_cast<Callbacks *>(row->get_value(_model_columns.callbacks)); + SPObject *object=row->get_value(_model_columns.object); + if (object) { + sp_object_unref(object, nullptr); + } + Inkscape::XML::Node *repr=row->get_value(_model_columns.repr); + if (repr) { + sp_repr_remove_listener_by_data(repr, callbacks); + Inkscape::GC::release(repr); + } + delete callbacks; +} + +/** Formats the label for a given layer row + */ +void LayerSelector::_prepareLabelRenderer( + Gtk::TreeModel::const_iterator const &row +) { + unsigned depth=(*row)[_model_columns.depth]; + SPObject *object=(*row)[_model_columns.object]; + bool label_defaulted(false); + + // TODO: when the currently selected row is removed, + // (or before one has been selected) something appears to + // "invent" an iterator with null data and try to render it; + // where does it come from, and how can we avoid it? + if ( object && object->getRepr() ) { + SPObject *layer=( _desktop ? _desktop->currentLayer() : nullptr ); + SPObject *root=( _desktop ? _desktop->currentRoot() : nullptr ); + + bool isancestor = !( (layer && (object->parent == layer->parent)) || ((layer == root) && (object->parent == root))); + + bool iscurrent = ( (object == layer) && (object != root) ); + + gchar *format = g_strdup_printf ( + "<span size=\"smaller\" %s><tt>%*s%s</tt>%s%s%s%%s%s%s%s</span>", + ( _desktop && _desktop->itemIsHidden (SP_ITEM(object)) ? "foreground=\"gray50\"" : "" ), + depth, "", ( iscurrent ? "•" : " " ), + ( iscurrent ? "<b>" : "" ), + ( SP_ITEM(object)->isLocked() ? "[" : "" ), + ( isancestor ? "<small>" : "" ), + ( isancestor ? "</small>" : "" ), + ( SP_ITEM(object)->isLocked() ? "]" : "" ), + ( iscurrent ? "</b>" : "" ) + ); + + gchar const *label; + if ( object != root ) { + label = object->label(); + if (!object->label()) { + label = object->defaultLabel(); + label_defaulted = true; + } + } else { + label = _("(root)"); + } + + gchar *text = g_markup_printf_escaped(format, ink_ellipsize_text (label, 50).c_str()); + _label_renderer.property_markup() = text; + g_free(text); + g_free(format); + } else { + _label_renderer.property_markup() = "<small> </small>"; + } + + _label_renderer.property_ypad() = 1; + _label_renderer.property_style() = ( label_defaulted ? + Pango::STYLE_ITALIC : + Pango::STYLE_NORMAL ); + +} + +void LayerSelector::_lockLayer(bool lock) { + if ( _layer && SP_IS_ITEM(_layer) ) { + SP_ITEM(_layer)->setLocked(lock); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_NONE, + lock? _("Lock layer") : _("Unlock layer")); + } +} + +void LayerSelector::_hideLayer(bool hide) { + if ( _layer && SP_IS_ITEM(_layer) ) { + SP_ITEM(_layer)->setHidden(hide); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_NONE, + hide? _("Hide layer") : _("Unhide layer")); + } +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/layer-selector.h b/src/ui/widget/layer-selector.h new file mode 100644 index 0000000..eadfce2 --- /dev/null +++ b/src/ui/widget/layer-selector.h @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Inkscape::UI::Widget::LayerSelector - layer selector widget + * + * Authors: + * MenTaLguY <mental@rydia.net> + * + * Copyright (C) 2004 MenTaLguY + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INKSCAPE_WIDGETS_LAYER_SELECTOR +#define SEEN_INKSCAPE_WIDGETS_LAYER_SELECTOR + +#include <gtkmm/box.h> +#include <gtkmm/combobox.h> +#include <gtkmm/togglebutton.h> +#include <gtkmm/cellrenderertext.h> +#include <gtkmm/treemodel.h> +#include <gtkmm/liststore.h> +#include <sigc++/slot.h> +#include "util/list.h" + +class SPDesktop; +class SPDocument; +class SPObject; +namespace Inkscape { +namespace XML { +class Node; +} +} + + +namespace Inkscape { +namespace UI { +namespace Widget { + +class DocumentTreeModel; + +class LayerSelector : public Gtk::HBox { +public: + LayerSelector(SPDesktop *desktop = nullptr); + ~LayerSelector() override; + + SPDesktop *desktop() { return _desktop; } + void setDesktop(SPDesktop *desktop); + +private: + class LayerModelColumns : public Gtk::TreeModel::ColumnRecord { + public: + Gtk::TreeModelColumn<unsigned> depth; + Gtk::TreeModelColumn<SPObject *> object; + Gtk::TreeModelColumn<Inkscape::XML::Node *> repr; + Gtk::TreeModelColumn<void *> callbacks; + + LayerModelColumns() { + add(depth); add(object); add(repr); add(callbacks); + } + }; + + SPDesktop *_desktop; + + Gtk::ComboBox _selector; + Gtk::ToggleButton _visibility_toggle; + Gtk::ToggleButton _lock_toggle; + + LayerModelColumns _model_columns; + Gtk::CellRendererText _label_renderer; + Glib::RefPtr<Gtk::ListStore> _layer_model; + +// sigc::connection _desktop_shutdown_connection; + sigc::connection _layers_changed_connection; + sigc::connection _current_layer_changed_connection; + sigc::connection _selection_changed_connection; + sigc::connection _visibility_toggled_connection; + sigc::connection _lock_toggled_connection; + + SPObject *_layer; + + void _selectLayer(SPObject *layer); + void _layersChanged(); + + void _setDesktopLayer(); + + void _buildEntry(unsigned depth, SPObject &object); + void _buildEntries(unsigned depth, + Inkscape::Util::List<SPObject &> hierarchy); + void _buildSiblingEntries(unsigned depth, + SPObject &parent, + Inkscape::Util::List<SPObject &> hierarchy); + void _protectUpdate(sigc::slot<void> slot); + void _destroyEntry(Gtk::ListStore::iterator const &row); + void _hideLayer(bool hide); + void _lockLayer(bool lock); + + void _prepareLabelRenderer(Gtk::TreeModel::const_iterator const &row); +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif +/* + 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 : diff --git a/src/ui/widget/layertypeicon.cpp b/src/ui/widget/layertypeicon.cpp new file mode 100644 index 0000000..d8b1378 --- /dev/null +++ b/src/ui/widget/layertypeicon.cpp @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/widget/layertypeicon.h" + +#include "ui/icon-loader.h" +#include "ui/icon-names.h" +#include "widgets/toolbox.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +LayerTypeIcon::LayerTypeIcon() : + Glib::ObjectBase(typeid(LayerTypeIcon)), + Gtk::CellRendererPixbuf(), + _pixLayerName(INKSCAPE_ICON("dialog-layers")), + _pixGroupName(INKSCAPE_ICON("layer-duplicate")), + _pixPathName(INKSCAPE_ICON("layer-rename")), + _property_active(*this, "active", false), + _property_activatable(*this, "activatable", true), + _property_pixbuf_layer(*this, "pixbuf_on", Glib::RefPtr<Gdk::Pixbuf>(nullptr)), + _property_pixbuf_group(*this, "pixbuf_off", Glib::RefPtr<Gdk::Pixbuf>(nullptr)), + _property_pixbuf_path(*this, "pixbuf_off", Glib::RefPtr<Gdk::Pixbuf>(nullptr)) +{ + + property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE; + + _property_pixbuf_layer = sp_get_icon_pixbuf(_pixLayerName, GTK_ICON_SIZE_MENU); + _property_pixbuf_group = sp_get_icon_pixbuf(_pixGroupName, GTK_ICON_SIZE_MENU); + _property_pixbuf_path = sp_get_icon_pixbuf(_pixPathName, GTK_ICON_SIZE_MENU); + + property_pixbuf() = _property_pixbuf_path.get_value(); +} + +void LayerTypeIcon::get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const +{ + Gtk::CellRendererPixbuf::get_preferred_height_vfunc(widget, min_h, nat_h); + + if (min_h) { + min_h += (min_h) >> 1; + } + + if (nat_h) { + nat_h += (nat_h) >> 1; + } +} + +void LayerTypeIcon::get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const +{ + Gtk::CellRendererPixbuf::get_preferred_width_vfunc(widget, min_w, nat_w); + + if (min_w) { + min_w += (min_w) >> 1; + } + + if (nat_w) { + nat_w += (nat_w) >> 1; + } +} + +void LayerTypeIcon::render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) +{ + property_pixbuf() = _property_active.get_value() == 1 ? _property_pixbuf_group : (_property_active.get_value() == 2 ? _property_pixbuf_layer : _property_pixbuf_path); + Gtk::CellRendererPixbuf::render_vfunc( cr, widget, background_area, cell_area, flags ); +} + +bool +LayerTypeIcon::activate_vfunc(GdkEvent* event, + Gtk::Widget& /*widget*/, + const Glib::ustring& path, + const Gdk::Rectangle& /*background_area*/, + const Gdk::Rectangle& /*cell_area*/, + Gtk::CellRendererState /*flags*/) +{ + _signal_pre_toggle.emit(event); + _signal_toggled.emit(path); + + return false; +} + + +} // namespace Widget +} // 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 : + + diff --git a/src/ui/widget/layertypeicon.h b/src/ui/widget/layertypeicon.h new file mode 100644 index 0000000..7dccf4c --- /dev/null +++ b/src/ui/widget/layertypeicon.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __UI_DIALOG_LAYERTYPEICON_H__ +#define __UI_DIALOG_LAYERTYPEICON_H__ +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/cellrendererpixbuf.h> +#include <gtkmm/widget.h> +#include <glibmm/property.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +class LayerTypeIcon : public Gtk::CellRendererPixbuf { +public: + LayerTypeIcon(); + ~LayerTypeIcon() override = default;; + + sigc::signal<void, const Glib::ustring&> signal_toggled() { return _signal_toggled;} + sigc::signal<void, GdkEvent const *> signal_pre_toggle() { return _signal_pre_toggle; } + + Glib::PropertyProxy<int> property_active() { return _property_active.get_proxy(); } + Glib::PropertyProxy<int> property_activatable() { return _property_activatable.get_proxy(); } + Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_on(); + Glib::PropertyProxy< Glib::RefPtr<Gdk::Pixbuf> > property_pixbuf_off(); + +protected: + void render_vfunc( const Cairo::RefPtr<Cairo::Context>& cr, + Gtk::Widget& widget, + const Gdk::Rectangle& background_area, + const Gdk::Rectangle& cell_area, + Gtk::CellRendererState flags ) override; + + void get_preferred_width_vfunc(Gtk::Widget& widget, + int& min_w, + int& nat_w) const override; + + void get_preferred_height_vfunc(Gtk::Widget& widget, + int& min_h, + int& nat_h) const override; + + bool activate_vfunc(GdkEvent *event, + Gtk::Widget &widget, + const Glib::ustring &path, + const Gdk::Rectangle &background_area, + const Gdk::Rectangle &cell_area, + Gtk::CellRendererState flags) override; + + +private: + Glib::ustring _pixLayerName; + Glib::ustring _pixGroupName; + Glib::ustring _pixPathName; + + Glib::Property<int> _property_active; + Glib::Property<int> _property_activatable; + Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_layer; + Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_group; + Glib::Property< Glib::RefPtr<Gdk::Pixbuf> > _property_pixbuf_path; + + sigc::signal<void, const Glib::ustring&> _signal_toggled; + sigc::signal<void, GdkEvent const *> _signal_pre_toggle; + +}; + + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + + +#endif /* __UI_DIALOG_IMAGETOGGLER_H__ */ + +/* + 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 : diff --git a/src/ui/widget/licensor.cpp b/src/ui/widget/licensor.cpp new file mode 100644 index 0000000..2ad811f --- /dev/null +++ b/src/ui/widget/licensor.cpp @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * bulia byak <buliabyak@users.sf.net> + * Bryce W. Harrington <bryce@bryceharrington.org> + * Lauris Kaplinski <lauris@kaplinski.com> + * Jon Phillips <jon@rejon.org> + * Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm) + * Abhishek Sharma + * + * Copyright (C) 2000 - 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "licensor.h" + +#include <gtkmm/entry.h> +#include <gtkmm/radiobutton.h> + +#include "ui/widget/entity-entry.h" +#include "ui/widget/registry.h" +#include "rdf.h" +#include "inkscape.h" +#include "document-undo.h" +#include "verbs.h" + + +namespace Inkscape { +namespace UI { +namespace Widget { + +//=================================================== + +const struct rdf_license_t _proprietary_license = + {_("Proprietary"), "", nullptr}; + +const struct rdf_license_t _other_license = + {Q_("MetadataLicence|Other"), "", nullptr}; + +class LicenseItem : public Gtk::RadioButton { +public: + LicenseItem (struct rdf_license_t const* license, EntityEntry* entity, Registry &wr, Gtk::RadioButtonGroup *group); +protected: + void on_toggled() override; + struct rdf_license_t const *_lic; + EntityEntry *_eep; + Registry &_wr; +}; + +LicenseItem::LicenseItem (struct rdf_license_t const* license, EntityEntry* entity, Registry &wr, Gtk::RadioButtonGroup *group) +: Gtk::RadioButton(_(license->name)), _lic(license), _eep(entity), _wr(wr) +{ + if (group) { + set_group (*group); + } +} + +/// \pre it is assumed that the license URI entry is a Gtk::Entry +void LicenseItem::on_toggled() +{ + if (_wr.isUpdating()) return; + + _wr.setUpdating (true); + SPDocument *doc = SP_ACTIVE_DOCUMENT; + rdf_set_license (doc, _lic->details ? _lic : nullptr); + if (doc->isSensitive()) { + DocumentUndo::done(doc, SP_VERB_NONE, _("Document license updated")); + } + _wr.setUpdating (false); + static_cast<Gtk::Entry*>(_eep->_packable)->set_text (_lic->uri); + _eep->on_changed(); +} + +//--------------------------------------------------- + +Licensor::Licensor() +: Gtk::VBox(false,4), + _eentry (nullptr) +{ +} + +Licensor::~Licensor() +{ + if (_eentry) delete _eentry; +} + +void Licensor::init (Registry& wr) +{ + /* add license-specific metadata entry areas */ + rdf_work_entity_t* entity = rdf_find_entity ( "license_uri" ); + _eentry = EntityEntry::create (entity, wr); + + LicenseItem *i; + wr.setUpdating (true); + i = Gtk::manage (new LicenseItem (&_proprietary_license, _eentry, wr, nullptr)); + Gtk::RadioButtonGroup group = i->get_group(); + add (*i); + LicenseItem *pd = i; + + for (struct rdf_license_t * license = rdf_licenses; + license && license->name; + license++) { + i = Gtk::manage (new LicenseItem (license, _eentry, wr, &group)); + add(*i); + } + // add Other at the end before the URI field for the confused ppl. + LicenseItem *io = Gtk::manage (new LicenseItem (&_other_license, _eentry, wr, &group)); + add (*io); + + pd->set_active(); + wr.setUpdating (false); + + Gtk::HBox *box = Gtk::manage (new Gtk::HBox); + pack_start (*box, true, true, 0); + + box->pack_start (_eentry->_label, false, false, 5); + box->pack_start (*_eentry->_packable, true, true, 0); + + show_all_children(); +} + +void Licensor::update (SPDocument *doc) +{ + /* identify the license info */ + struct rdf_license_t * license = rdf_get_license (doc); + + if (license) { + int i; + for (i=0; rdf_licenses[i].name; i++) + if (license == &rdf_licenses[i]) + break; + static_cast<LicenseItem*>(get_children()[i+1])->set_active(); + } + else { + static_cast<LicenseItem*>(get_children()[0])->set_active(); + } + + /* update the URI */ + _eentry->update (doc); +} + +} // 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 : diff --git a/src/ui/widget/licensor.h b/src/ui/widget/licensor.h new file mode 100644 index 0000000..3e1f0da --- /dev/null +++ b/src/ui/widget/licensor.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ralf Stephan <ralf@ark.in-berlin.de> + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_LICENSOR_H +#define INKSCAPE_UI_WIDGET_LICENSOR_H + +#include <gtkmm/box.h> + +class SPDocument; + +namespace Inkscape { + namespace UI { + namespace Widget { + +class EntityEntry; +class Registry; + + +/** + * Widget for specifying a document's license; part of document + * preferences dialog. + */ +class Licensor : public Gtk::VBox { +public: + Licensor(); + ~Licensor() override; + void init (Registry&); + void update (SPDocument *doc); + +protected: + EntityEntry *_eentry; +}; + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_LICENSOR_H + +/* + 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 : diff --git a/src/ui/widget/notebook-page.cpp b/src/ui/widget/notebook-page.cpp new file mode 100644 index 0000000..a189d78 --- /dev/null +++ b/src/ui/widget/notebook-page.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Notebook page widget. + * + * Author: + * Bryce Harrington <bryce@bryceharrington.org> + * + * Copyright (C) 2004 Bryce Harrington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "notebook-page.h" + +# include <gtkmm/grid.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +NotebookPage::NotebookPage(int n_rows, int n_columns, bool expand, bool fill, guint padding) + :_table(Gtk::manage(new Gtk::Grid())) +{ + set_name("NotebookPage"); + set_border_width(4); + set_spacing(4); + + _table->set_row_spacing(4); + _table->set_column_spacing(4); + + pack_start(*_table, expand, fill, padding); +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/notebook-page.h b/src/ui/widget/notebook-page.h new file mode 100644 index 0000000..cc11d30 --- /dev/null +++ b/src/ui/widget/notebook-page.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Bryce Harrington <bryce@bryceharrington.org> + * + * Copyright (C) 2004 Bryce Harrington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_NOTEBOOK_PAGE_H +#define INKSCAPE_UI_WIDGET_NOTEBOOK_PAGE_H + +#include <gtkmm/box.h> + +namespace Gtk { +class Grid; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A tabbed notebook page for dialogs. + */ +class NotebookPage : public Gtk::VBox +{ +public: + + NotebookPage(); + + /** + * Construct a NotebookPage. + */ + NotebookPage(int n_rows, int n_columns, bool expand=false, bool fill=false, guint padding=0); + + Gtk::Grid& table() { return *_table; } + +protected: + Gtk::Grid *_table; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_NOTEBOOK_PAGE_H + +/* + 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 : diff --git a/src/ui/widget/object-composite-settings.cpp b/src/ui/widget/object-composite-settings.cpp new file mode 100644 index 0000000..db2e91c --- /dev/null +++ b/src/ui/widget/object-composite-settings.cpp @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A widget for controlling object compositing (filter, opacity, etc.) + * + * Authors: + * Bryce W. Harrington <bryce@bryceharrington.org> + * Gustav Broberg <broberg@kth.se> + * Niko Kiirala <niko@kiirala.com> + * Abhishek Sharma + * + * Copyright (C) 2004--2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/widget/object-composite-settings.h" + +#include "desktop.h" + +#include "desktop-style.h" +#include "document.h" +#include "document-undo.h" +#include "filter-chemistry.h" +#include "inkscape.h" +#include "style.h" +#include "svg/css-ostringstream.h" +#include "verbs.h" +#include "display/sp-canvas.h" +#include "object/filters/blend.h" +#include "ui/widget/style-subject.h" + +constexpr double BLUR_MULTIPLIER = 4.0; + +namespace Inkscape { +namespace UI { +namespace Widget { + +ObjectCompositeSettings::ObjectCompositeSettings(unsigned int verb_code, char const *history_prefix, int flags) +: _verb_code(verb_code), + _blend_tag(Glib::ustring(history_prefix) + ":blend"), + _blur_tag(Glib::ustring(history_prefix) + ":blur"), + _opacity_tag(Glib::ustring(history_prefix) + ":opacity"), + _isolation_tag(Glib::ustring(history_prefix) + ":isolation"), + _filter_modifier(flags), + _blocked(false) +{ + set_name( "ObjectCompositeSettings"); + + // Filter Effects + pack_start(_filter_modifier, false, false, 2); + + _filter_modifier.signal_blend_changed().connect(sigc::mem_fun(*this, &ObjectCompositeSettings::_blendBlurValueChanged)); + _filter_modifier.signal_blur_changed().connect(sigc::mem_fun(*this, &ObjectCompositeSettings::_blendBlurValueChanged)); + _filter_modifier.signal_opacity_changed().connect(sigc::mem_fun(*this, &ObjectCompositeSettings::_opacityValueChanged)); + _filter_modifier.signal_isolation_changed().connect( + sigc::mem_fun(*this, &ObjectCompositeSettings::_isolationValueChanged)); + + show_all_children(); +} + +ObjectCompositeSettings::~ObjectCompositeSettings() { + setSubject(nullptr); +} + +void ObjectCompositeSettings::setSubject(StyleSubject *subject) { + _subject_changed.disconnect(); + if (subject) { + _subject = subject; + _subject_changed = _subject->connectChanged(sigc::mem_fun(*this, &ObjectCompositeSettings::_subjectChanged)); + _subject->setDesktop(SP_ACTIVE_DESKTOP); + } +} + +// We get away with sharing one callback for blend and blur as this is used by +// * the Layers dialog where only one layer can be selected at a time, +// * the Fill and Stroke dialog where only blur is used. +// If both blend and blur are used in a dialog where more than one object can +// be selected then this should be split into separate functions for blend and +// blur (like in the Objects dialog). +void +ObjectCompositeSettings::_blendBlurValueChanged() +{ + if (!_subject) { + return; + } + + SPDesktop *desktop = _subject->getDesktop(); + if (!desktop) { + return; + } + SPDocument *document = desktop->getDocument(); + + if (_blocked) + return; + _blocked = true; + + // FIXME: fix for GTK breakage, see comment in SelectedStyle::on_opacity_changed; here it results in crash 1580903 + //sp_canvas_force_full_redraw_after_interruptions(desktop->getCanvas(), 0); + + Geom::OptRect bbox = _subject->getBounds(SPItem::GEOMETRIC_BBOX); + double radius; + if (bbox) { + double perimeter = bbox->dimensions()[Geom::X] + bbox->dimensions()[Geom::Y]; // fixme: this is only half the perimeter, is that correct? + double blur_value = _filter_modifier.get_blur_value() / 100.0; + radius = blur_value * blur_value * perimeter / BLUR_MULTIPLIER; + } else { + radius = 0; + } + + //apply created filter to every selected item + std::vector<SPObject*> sel = _subject->list(); + for (auto i : sel) { + if (!SP_IS_ITEM(i)) { + continue; + } + SPItem * item = SP_ITEM(i); + SPStyle *style = item->style; + g_assert(style != nullptr); + bool change_blend = (item->style->mix_blend_mode.set ? item->style->mix_blend_mode.value : SP_CSS_BLEND_NORMAL) != _filter_modifier.get_blend_mode(); + // < 1.0 filter based blend removal + if (!item->style->mix_blend_mode.set && item->style->filter.set && item->style->getFilter()) { + remove_filter_legacy_blend(item); + } + item->style->mix_blend_mode.set = TRUE; + if (item->style->isolation.value == SP_CSS_ISOLATION_ISOLATE) { + item->style->mix_blend_mode.value = SP_CSS_BLEND_NORMAL; + } else { + item->style->mix_blend_mode.value = _filter_modifier.get_blend_mode(); + } + + if (radius == 0 && item->style->filter.set + && filter_is_single_gaussian_blur(SP_FILTER(item->style->getFilter()))) { + remove_filter(item, false); + } else if (radius != 0) { + SPFilter *filter = modify_filter_gaussian_blur_from_item(document, item, radius); + sp_style_set_property_url(item, "filter", filter, false); + } + if (change_blend) { //we do blend so we need update display style + item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT); + } else { + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + } + } + + DocumentUndo::maybeDone(document, _blur_tag.c_str(), _verb_code, + _("Change blur/blend filter")); + + // resume interruptibility + //sp_canvas_end_forced_full_redraws(desktop->getCanvas()); + + _blocked = false; +} + +void +ObjectCompositeSettings::_opacityValueChanged() +{ + if (!_subject) { + return; + } + + SPDesktop *desktop = _subject->getDesktop(); + if (!desktop) { + return; + } + + if (_blocked) + return; + _blocked = true; + + SPCSSAttr *css = sp_repr_css_attr_new (); + + Inkscape::CSSOStringStream os; + os << CLAMP (_filter_modifier.get_opacity_value() / 100, 0.0, 1.0); + sp_repr_css_set_property (css, "opacity", os.str().c_str()); + + _subject->setCSS(css); + + sp_repr_css_attr_unref (css); + + DocumentUndo::maybeDone(desktop->getDocument(), _opacity_tag.c_str(), _verb_code, + _("Change opacity")); + + // resume interruptibility + //sp_canvas_end_forced_full_redraws(desktop->getCanvas()); + + _blocked = false; +} + +void ObjectCompositeSettings::_isolationValueChanged() +{ + if (!_subject) { + return; + } + + SPDesktop *desktop = _subject->getDesktop(); + if (!desktop) { + return; + } + + if (_blocked) + return; + _blocked = true; + + for (auto item : _subject->list()) { + item->style->isolation.set = TRUE; + item->style->isolation.value = _filter_modifier.get_isolation_mode(); + if (item->style->isolation.value == SP_CSS_ISOLATION_ISOLATE) { + item->style->mix_blend_mode.set = TRUE; + item->style->mix_blend_mode.value = SP_CSS_BLEND_NORMAL; + } + item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT); + } + + DocumentUndo::maybeDone(desktop->getDocument(), _isolation_tag.c_str(), _verb_code, _("Change isolation")); + + // resume interruptibility + // sp_canvas_end_forced_full_redraws(desktop->getCanvas()); + + _blocked = false; +} + +void +ObjectCompositeSettings::_subjectChanged() { + if (!_subject) { + return; + } + + SPDesktop *desktop = _subject->getDesktop(); + if (!desktop) { + return; + } + + if (_blocked) + return; + _blocked = true; + SPStyle query(desktop->getDocument()); + int result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_MASTEROPACITY); + + switch (result) { + case QUERY_STYLE_NOTHING: + break; + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently + case QUERY_STYLE_MULTIPLE_SAME: + _filter_modifier.set_opacity_value(100 * SP_SCALE24_TO_FLOAT(query.opacity.value)); + break; + } + + //query now for current filter mode and average blurring of selection + const int isolation_result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_ISOLATION); + switch (isolation_result) { + case QUERY_STYLE_NOTHING: + _filter_modifier.set_isolation_mode(SP_CSS_ISOLATION_AUTO, false); + break; + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_SAME: + _filter_modifier.set_isolation_mode(query.isolation.value, true); // here dont work mix_blend_mode.set + break; + case QUERY_STYLE_MULTIPLE_DIFFERENT: + _filter_modifier.set_isolation_mode(SP_CSS_ISOLATION_AUTO, false); + // TODO: set text + break; + } + + // query now for current filter mode and average blurring of selection + const int blend_result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_BLEND); + switch(blend_result) { + case QUERY_STYLE_NOTHING: + _filter_modifier.set_blend_mode(SP_CSS_BLEND_NORMAL, false); + break; + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_SAME: + _filter_modifier.set_blend_mode(query.mix_blend_mode.value, true); // here dont work mix_blend_mode.set + break; + case QUERY_STYLE_MULTIPLE_DIFFERENT: + _filter_modifier.set_blend_mode(SP_CSS_BLEND_NORMAL, false); + break; + } + + int blur_result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_BLUR); + switch (blur_result) { + case QUERY_STYLE_NOTHING: // no blurring + _filter_modifier.set_blur_value(0); + break; + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: + case QUERY_STYLE_MULTIPLE_SAME: + Geom::OptRect bbox = _subject->getBounds(SPItem::GEOMETRIC_BBOX); + if (bbox) { + double perimeter = + bbox->dimensions()[Geom::X] + + bbox->dimensions()[Geom::Y]; // fixme: this is only half the perimeter, is that correct? + // update blur widget value + float radius = query.filter_gaussianBlur_deviation.value; + float percent = std::sqrt(radius * BLUR_MULTIPLIER / perimeter) * 100; + _filter_modifier.set_blur_value(percent); + } + break; + } + + // If we have nothing selected, disable dialog. + if (result == QUERY_STYLE_NOTHING && + blend_result == QUERY_STYLE_NOTHING ) { + _filter_modifier.set_sensitive( false ); + } else { + _filter_modifier.set_sensitive( true ); + } + + _blocked = false; +} + +} +} +} + +/* + 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 : diff --git a/src/ui/widget/object-composite-settings.h b/src/ui/widget/object-composite-settings.h new file mode 100644 index 0000000..9650118 --- /dev/null +++ b/src/ui/widget/object-composite-settings.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_UI_WIDGET_OBJECT_COMPOSITE_SETTINGS_H +#define SEEN_UI_WIDGET_OBJECT_COMPOSITE_SETTINGS_H + +/* + * Authors: + * Bryce W. Harrington <bryce@bryceharrington.org> + * Gustav Broberg <broberg@kth.se> + * + * Copyright (C) 2004--2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/box.h> +#include <gtkmm/adjustment.h> +#include <gtkmm/label.h> +#include <gtkmm/scale.h> +#include <glibmm/ustring.h> + +#include "ui/widget/filter-effect-chooser.h" + +class SPDesktop; +struct InkscapeApplication; + +namespace Inkscape { + +namespace UI { +namespace Widget { + +class StyleSubject; + +/* + * A widget for controlling object compositing (filter, opacity, etc.) + */ +class ObjectCompositeSettings : public Gtk::VBox { +public: + ObjectCompositeSettings(unsigned int verb_code, char const *history_prefix, int flags); + ~ObjectCompositeSettings() override; + + void setSubject(StyleSubject *subject); + +private: + unsigned int _verb_code; + Glib::ustring _blend_tag; + Glib::ustring _blur_tag; + Glib::ustring _opacity_tag; + Glib::ustring _isolation_tag; + + StyleSubject *_subject; + + SimpleFilterModifier _filter_modifier; + + bool _blocked; + gulong _desktop_activated; + sigc::connection _subject_changed; + + static void _on_desktop_activate(SPDesktop *desktop, ObjectCompositeSettings *w); + static void _on_desktop_deactivate(SPDesktop *desktop, ObjectCompositeSettings *w); + void _subjectChanged(); + void _blendBlurValueChanged(); + void _opacityValueChanged(); + void _isolationValueChanged(); +}; + +} +} +} + +#endif + +/* + 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 : diff --git a/src/ui/widget/page-sizer.cpp b/src/ui/widget/page-sizer.cpp new file mode 100644 index 0000000..d869a1c --- /dev/null +++ b/src/ui/widget/page-sizer.cpp @@ -0,0 +1,781 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * + * Paper-size widget and helper functions + */ +/* + * Authors: + * bulia byak <buliabyak@users.sf.net> + * Lauris Kaplinski <lauris@kaplinski.com> + * Jon Phillips <jon@rejon.org> + * Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm) + * Bob Jamison <ishmal@users.sf.net> + * Abhishek Sharma + * + * Copyright (C) 2000 - 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "page-sizer.h" +#include "pages-skeleton.h" +#include <glib.h> +#include <glibmm/i18n.h> +#include "verbs.h" +#include "helper/action.h" +#include "object/sp-root.h" +#include "io/resource.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + + +//######################################################################## +//# P A G E S I Z E R +//######################################################################## + +/** + * Constructor + */ +PageSizer::PageSizer(Registry & _wr) + : Gtk::VBox(false,4), + _dimensionUnits( _("U_nits:"), "units", _wr ), + _dimensionWidth( _("_Width:"), _("Width of paper"), "width", _dimensionUnits, _wr ), + _dimensionHeight( _("_Height:"), _("Height of paper"), "height", _dimensionUnits, _wr ), + _marginLock( _("Loc_k margins"), _("Lock margins"), "lock-margins", _wr, false, nullptr, nullptr), + _lock_icon(), + _marginTop( _("T_op:"), _("Top margin"), "fit-margin-top", _wr ), + _marginLeft( _("L_eft:"), _("Left margin"), "fit-margin-left", _wr), + _marginRight( _("Ri_ght:"), _("Right margin"), "fit-margin-right", _wr), + _marginBottom( _("Botto_m:"), _("Bottom margin"), "fit-margin-bottom", _wr), + _lockMarginUpdate(false), + _scaleX(_("Scale _x:"), _("Scale X"), "scale-x", _wr), + _scaleY(_("Scale _y:"), _("While SVG allows non-uniform scaling it is recommended to use only uniform scaling in Inkscape. To set a non-uniform scaling, set the 'viewBox' directly."), "scale-y", _wr), + _lockScaleUpdate(false), + _viewboxX(_("X:"), _("X"), "viewbox-x", _wr), + _viewboxY(_("Y:"), _("Y"), "viewbox-y", _wr), + _viewboxW(_("Width:"), _("Width"), "viewbox-width", _wr), + _viewboxH(_("Height:"), _("Height"), "viewbox-height", _wr), + _lockViewboxUpdate(false), + _widgetRegistry(&_wr) +{ + // set precision of scalar entry boxes + _wr.setUpdating (true); + _dimensionWidth.setDigits(5); + _dimensionHeight.setDigits(5); + _marginTop.setDigits(5); + _marginLeft.setDigits(5); + _marginRight.setDigits(5); + _marginBottom.setDigits(5); + _scaleX.setDigits(5); + _scaleY.setDigits(5); + _viewboxX.setDigits(5); + _viewboxY.setDigits(5); + _viewboxW.setDigits(5); + _viewboxH.setDigits(5); + _dimensionWidth.setRange( 0.00001, 10000000 ); + _dimensionHeight.setRange( 0.00001, 10000000 ); + _scaleX.setRange( 0.00001, 100000 ); + _scaleY.setRange( 0.00001, 100000 ); + _viewboxX.setRange( -10000000, 10000000 ); + _viewboxY.setRange( -10000000, 10000000 ); + _viewboxW.setRange( 0.00001, 10000000 ); + _viewboxH.setRange( 0.00001, 10000000 ); + + _scaleY.set_sensitive (false); // We only want to display Y scale. + + _wr.setUpdating (false); + + //# Set up the Paper Size combo box + _paperSizeListStore = Gtk::ListStore::create(_paperSizeListColumns); + _paperSizeList.set_model(_paperSizeListStore); + _paperSizeList.append_column(_("Name"), + _paperSizeListColumns.nameColumn); + _paperSizeList.append_column(_("Description"), + _paperSizeListColumns.descColumn); + _paperSizeList.set_headers_visible(false); + _paperSizeListSelection = _paperSizeList.get_selection(); + _paper_size_list_connection = + _paperSizeListSelection->signal_changed().connect ( + sigc::mem_fun (*this, &PageSizer::on_paper_size_list_changed)); + _paperSizeListScroller.add(_paperSizeList); + _paperSizeListScroller.set_shadow_type(Gtk::SHADOW_IN); + _paperSizeListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS); + _paperSizeListScroller.set_size_request(-1, 130); + + + char *path = Inkscape::IO::Resource::profile_path("pages.csv"); + if (!g_file_test(path, G_FILE_TEST_EXISTS)) { + if (!g_file_set_contents(path, pages_skeleton, -1, nullptr)) { + g_warning("%s", _("Failed to create the page file.")); + } + } + + gchar *content = nullptr; + if (g_file_get_contents(path, &content, nullptr, nullptr)) { + + gchar **lines = g_strsplit_set(content, "\n", 0); + + for (int i = 0; lines && lines[i]; ++i) { + gchar **line = g_strsplit_set(lines[i], ",", 5); + if (!line[0] || !line[1] || !line[2] || !line[3] || line[0][0]=='#') + continue; + //name, width, height, unit + double width = g_ascii_strtod(line[1], nullptr); + double height = g_ascii_strtod(line[2], nullptr); + g_strstrip(line[0]); + g_strstrip(line[3]); + Glib::ustring name = line[0]; + char formatBuf[80]; + snprintf(formatBuf, 79, "%0.1f x %0.1f", width, height); + Glib::ustring desc = formatBuf; + desc.append(" " + std::string(line[3])); + PaperSize paper(name, width, height, Inkscape::Util::unit_table.getUnit(line[3])); + _paperSizeTable[name] = paper; + Gtk::TreeModel::Row row = *(_paperSizeListStore->append()); + row[_paperSizeListColumns.nameColumn] = name; + row[_paperSizeListColumns.descColumn] = desc; + g_strfreev(line); + } + g_strfreev(lines); + g_free(content); + } + g_free(path); + + pack_start (_paperSizeListScroller, true, true, 0); + + //## Set up orientation radio buttons + pack_start (_orientationBox, false, false, 0); + _orientationLabel.set_label(_("Orientation:")); + _orientationBox.pack_start(_orientationLabel, false, false, 0); + _landscapeButton.set_use_underline(); + _landscapeButton.set_label(_("_Landscape")); + _landscapeButton.set_active(true); + Gtk::RadioButton::Group group = _landscapeButton.get_group(); + _orientationBox.pack_end (_landscapeButton, false, false, 5); + _portraitButton.set_use_underline(); + _portraitButton.set_label(_("_Portrait")); + _portraitButton.set_active(true); + _orientationBox.pack_end (_portraitButton, false, false, 5); + _portraitButton.set_group (group); + _portraitButton.set_active (true); + + // Setting default custom unit to document unit + SPDesktop *dt = SP_ACTIVE_DESKTOP; + SPNamedView *nv = dt->getNamedView(); + _wr.setUpdating (true); + if (nv->page_size_units) { + _dimensionUnits.setUnit(nv->page_size_units->abbr); + } else if (nv->display_units) { + _dimensionUnits.setUnit(nv->display_units->abbr); + } + _wr.setUpdating (false); + + + //## Set up custom size frame + _customFrame.set_label(_("Custom size")); + pack_start (_customFrame, false, false, 0); + _customFrame.add(_customDimTable); + + _customDimTable.set_border_width(4); + _customDimTable.set_row_spacing(4); + _customDimTable.set_column_spacing(4); + + _dimensionHeight.set_halign(Gtk::ALIGN_CENTER); + _dimensionUnits.set_halign(Gtk::ALIGN_END); + _customDimTable.attach(_dimensionWidth, 0, 0, 1, 1); + _customDimTable.attach(_dimensionHeight, 1, 0, 1, 1); + _customDimTable.attach(_dimensionUnits, 2, 0, 1, 1); + + _customDimTable.attach(_fitPageMarginExpander, 0, 1, 3, 1); + + //## Set up fit page expander + _fitPageMarginExpander.set_use_underline(); + _fitPageMarginExpander.set_label(_("Resi_ze page to content...")); + _fitPageMarginExpander.add(_marginTable); + + _marginTable.set_border_width(4); + _marginTable.set_row_spacing(4); + _marginTable.set_column_spacing(4); + + //### margin label and lock button + _marginLabel.set_markup(Glib::ustring("<b><i>") + _("Margins") + "</i></b>"); + _marginLabel.set_halign(Gtk::ALIGN_CENTER); + + _lock_icon.set_from_icon_name("object-unlocked", Gtk::ICON_SIZE_LARGE_TOOLBAR); + _lock_icon.show(); + _marginLock.set_active(false); + _marginLock.add(_lock_icon); + + _marginBox.set_spacing(4); + _marginBox.add(_marginLabel); + _marginBox.add(_marginLock); + _marginBox.set_halign(Gtk::ALIGN_CENTER); + _marginTable.attach(_marginBox, 1, 1, 1, 1); + + //### margins + _marginTop.set_halign(Gtk::ALIGN_CENTER); + _marginLeft.set_halign(Gtk::ALIGN_START); + _marginRight.set_halign(Gtk::ALIGN_END); + _marginBottom.set_halign(Gtk::ALIGN_CENTER); + + _marginTable.attach(_marginTop, 0, 0, 3, 1); + _marginTable.attach(_marginLeft, 0, 1, 1, 1); + _marginTable.attach(_marginRight, 2, 1, 1, 1); + _marginTable.attach(_marginBottom, 0, 2, 3, 1); + + //### fit page to drawing button + _fitPageButton.set_use_underline(); + _fitPageButton.set_label(_("_Resize page to drawing or selection (Ctrl+Shift+R)")); + _fitPageButton.set_tooltip_text(_("Resize the page to fit the current selection, or the entire drawing if there is no selection")); + + _fitPageButton.set_hexpand(); + _fitPageButton.set_halign(Gtk::ALIGN_CENTER); + _marginTable.attach(_fitPageButton, 0, 3, 3, 1); + + + //## Set up scale frame + _scaleFrame.set_label(_("Scale")); + pack_start (_scaleFrame, false, false, 0); + _scaleFrame.add(_scaleTable); + + _scaleTable.set_border_width(4); + _scaleTable.set_row_spacing(4); + _scaleTable.set_column_spacing(4); + + _scaleTable.attach(_scaleX, 0, 0, 1, 1); + _scaleTable.attach(_scaleY, 1, 0, 1, 1); + _scaleTable.attach(_scaleLabel, 2, 0, 1, 1); + + _viewboxExpander.set_hexpand(); + _scaleTable.attach(_viewboxExpander, 0, 2, 3, 1); + + _viewboxExpander.set_use_underline(); + _viewboxExpander.set_label(_("_Viewbox...")); + _viewboxExpander.add(_viewboxTable); + + _viewboxTable.set_border_width(4); + _viewboxTable.set_row_spacing(4); + _viewboxTable.set_column_spacing(4); + + _viewboxX.set_halign(Gtk::ALIGN_END); + _viewboxY.set_halign(Gtk::ALIGN_END); + _viewboxW.set_halign(Gtk::ALIGN_END); + _viewboxH.set_halign(Gtk::ALIGN_END); + _viewboxSpacer.set_hexpand(); + _viewboxTable.attach(_viewboxX, 0, 0, 1, 1); + _viewboxTable.attach(_viewboxY, 1, 0, 1, 1); + _viewboxTable.attach(_viewboxW, 0, 1, 1, 1); + _viewboxTable.attach(_viewboxH, 1, 1, 1, 1); + _viewboxTable.attach(_viewboxSpacer, 2, 0, 3, 1); + + _wr.setUpdating (true); + updateScaleUI(); + _wr.setUpdating (false); +} + + +/** + * Destructor + */ +PageSizer::~PageSizer() += default; + + + +/** + * Initialize or reset this widget + */ +void +PageSizer::init () +{ + _landscape_connection = _landscapeButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_landscape)); + _portrait_connection = _portraitButton.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_portrait)); + _changedw_connection = _dimensionWidth.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed)); + _changedh_connection = _dimensionHeight.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_value_changed)); + _changedu_connection = _dimensionUnits.getUnitMenu()->signal_changed().connect (sigc::mem_fun (*this, &PageSizer::on_units_changed)); + _fitPageButton.signal_clicked().connect(sigc::mem_fun(*this, &PageSizer::fire_fit_canvas_to_selection_or_drawing)); + _changeds_connection = _scaleX.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_scale_changed)); + _changedvx_connection = _viewboxX.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed)); + _changedvy_connection = _viewboxY.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed)); + _changedvw_connection = _viewboxW.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed)); + _changedvh_connection = _viewboxH.signal_value_changed().connect (sigc::mem_fun (*this, &PageSizer::on_viewbox_changed)); + _changedlk_connection = _marginLock.signal_toggled().connect (sigc::mem_fun (*this, &PageSizer::on_margin_lock_changed)); + _changedmt_connection = _marginTop.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginTop)); + _changedmb_connection = _marginBottom.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginBottom)); + _changedml_connection = _marginLeft.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginLeft)); + _changedmr_connection = _marginRight.signal_value_changed().connect (sigc::bind<RegisteredScalar*>(sigc::mem_fun (*this, &PageSizer::on_margin_changed), &_marginRight)); + show_all_children(); +} + + +/** + * Set document dimensions (if not called by Doc prop's update()) and + * set the PageSizer's widgets and text entries accordingly. If + * 'changeList' is true, then adjust the paperSizeList to show the closest + * standard page size. + * + * \param w, h + * \param changeList whether to modify the paper size list + */ +void +PageSizer::setDim (Inkscape::Util::Quantity w, Inkscape::Util::Quantity h, bool changeList, bool changeSize) +{ + static bool _called = false; + if (_called) { + return; + } + + _called = true; + + _paper_size_list_connection.block(); + _landscape_connection.block(); + _portrait_connection.block(); + _changedw_connection.block(); + _changedh_connection.block(); + + _unit = w.unit->abbr; + + if (SP_ACTIVE_DESKTOP && !_widgetRegistry->isUpdating()) { + SPDocument *doc = SP_ACTIVE_DESKTOP->getDocument(); + Inkscape::Util::Quantity const old_height = doc->getHeight(); + doc->setWidthAndHeight (w, h, changeSize); + // The origin for the user is in the lower left corner; this point should remain stationary when + // changing the page size. The SVG's origin however is in the upper left corner, so we must compensate for this + if (changeSize && !doc->is_yaxisdown()) { + Geom::Translate const vert_offset(Geom::Point(0, (old_height.value("px") - h.value("px")))); + doc->getRoot()->translateChildItems(vert_offset); + } + DocumentUndo::done(doc, SP_VERB_NONE, _("Set page size")); + } + + if ( w != h ) { + _landscapeButton.set_sensitive(true); + _portraitButton.set_sensitive (true); + _landscape = ( w > h ); + _landscapeButton.set_active(_landscape ? true : false); + _portraitButton.set_active (_landscape ? false : true); + } else { + _landscapeButton.set_sensitive(false); + _portraitButton.set_sensitive (false); + } + + if (changeList) + { + Gtk::TreeModel::Row row = (*find_paper_size(w, h)); + if (row) + _paperSizeListSelection->select(row); + } + + _dimensionWidth.setUnit(w.unit->abbr); + _dimensionWidth.setValue (w.quantity); + _dimensionHeight.setUnit(h.unit->abbr); + _dimensionHeight.setValue (h.quantity); + + + _paper_size_list_connection.unblock(); + _landscape_connection.unblock(); + _portrait_connection.unblock(); + _changedw_connection.unblock(); + _changedh_connection.unblock(); + + _called = false; +} + +/** + * Updates the scalar widgets for the fit margins. (Just changes the value + * of the ui widgets to match the xml). + */ +void +PageSizer::updateFitMarginsUI(Inkscape::XML::Node *nv_repr) +{ + if (!_lockMarginUpdate) { + double value = 0.0; + if (sp_repr_get_double(nv_repr, "fit-margin-top", &value)) { + _marginTop.setValue(value); + } + if (sp_repr_get_double(nv_repr, "fit-margin-left", &value)) { + _marginLeft.setValue(value); + } + if (sp_repr_get_double(nv_repr, "fit-margin-right", &value)) { + _marginRight.setValue(value); + } + if (sp_repr_get_double(nv_repr, "fit-margin-bottom", &value)) { + _marginBottom.setValue(value); + } + } +} + + +/** + * Returns an iterator pointing to a row in paperSizeListStore which + * contains a paper of the specified size, or + * paperSizeListStore->children().end() if no such paper exists. + * + * The code is not tested for the case where w and h have different units. + */ +Gtk::ListStore::iterator +PageSizer::find_paper_size (Inkscape::Util::Quantity w, Inkscape::Util::Quantity h) const +{ + // The code below assumes that w < h, so make sure that's the case: + if ( h < w ) { + std::swap(h,w); + } + + std::map<Glib::ustring, PaperSize>::const_iterator iter; + for (iter = _paperSizeTable.begin() ; + iter != _paperSizeTable.end() ; ++iter) { + PaperSize paper = iter->second; + Inkscape::Util::Quantity smallX (paper.smaller, paper.unit); + Inkscape::Util::Quantity largeX (paper.larger, paper.unit); + + // account for landscape formats (e.g. business cards) + if (largeX < smallX) { + std::swap(largeX, smallX); + } + + if ( are_near(w, smallX, 0.1) && are_near(h, largeX, 0.1) ) { + Gtk::ListStore::iterator p = _paperSizeListStore->children().begin(); + Gtk::ListStore::iterator pend = _paperSizeListStore->children().end(); + // We need to search paperSizeListStore explicitly for the + // specified paper size because it is sorted in a different + // way than paperSizeTable (which is sorted alphabetically) + for ( ; p != pend; ++p) { + if ((*p)[_paperSizeListColumns.nameColumn] == paper.name) { + return p; + } + } + } + } + return _paperSizeListStore->children().end(); +} + + + +/** + * Tell the desktop to fit the page size to the selection or drawing. + */ +void +PageSizer::fire_fit_canvas_to_selection_or_drawing() +{ + SPDesktop *dt = SP_ACTIVE_DESKTOP; + if (!dt) { + return; + } + SPDocument *doc; + SPNamedView *nv; + Inkscape::XML::Node *nv_repr; + + if ((doc = SP_ACTIVE_DESKTOP->getDocument()) + && (nv = sp_document_namedview(doc, nullptr)) + && (nv_repr = nv->getRepr())) { + _lockMarginUpdate = true; + sp_repr_set_svg_double(nv_repr, "fit-margin-top", _marginTop.getValue()); + sp_repr_set_svg_double(nv_repr, "fit-margin-left", _marginLeft.getValue()); + sp_repr_set_svg_double(nv_repr, "fit-margin-right", _marginRight.getValue()); + sp_repr_set_svg_double(nv_repr, "fit-margin-bottom", _marginBottom.getValue()); + _lockMarginUpdate = false; + } + + Verb *verb = Verb::get( SP_VERB_FIT_CANVAS_TO_SELECTION_OR_DRAWING ); + if (verb) { + SPAction *action = verb->get_action(Inkscape::ActionContext(dt)); + if (action) { + sp_action_perform(action, nullptr); + } + } +} + + + +/** + * Paper Size list callback for when a user changes the selection + */ +void +PageSizer::on_paper_size_list_changed() +{ + //Glib::ustring name = _paperSizeList.get_active_text(); + Gtk::TreeModel::iterator miter = _paperSizeListSelection->get_selected(); + if(!miter) + { + //error? + return; + } + Gtk::TreeModel::Row row = *miter; + Glib::ustring name = row[_paperSizeListColumns.nameColumn]; + std::map<Glib::ustring, PaperSize>::const_iterator piter = + _paperSizeTable.find(name); + if (piter == _paperSizeTable.end()) { + g_warning("paper size '%s' not found in table", name.c_str()); + return; + } + PaperSize paper = piter->second; + Inkscape::Util::Quantity w = Inkscape::Util::Quantity(paper.smaller, paper.unit); + Inkscape::Util::Quantity h = Inkscape::Util::Quantity(paper.larger, paper.unit); + + if ( w > h ) { + // enforce landscape mode if this is desired for the given page format + _landscape = true; + } else { + // otherwise we keep the current mode + _landscape = _landscapeButton.get_active(); + } + + if ((_landscape && (w < h)) || (!_landscape && (w > h))) + setDim (h, w, false); + else + setDim (w, h, false); + +} + + +/** + * Portrait button callback + */ +void +PageSizer::on_portrait() +{ + if (!_portraitButton.get_active()) + return; + Inkscape::Util::Quantity w = Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionWidth.getUnit()); + Inkscape::Util::Quantity h = Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionHeight.getUnit()); + if (h < w) { + setDim (h, w); + } +} + + +/** + * Landscape button callback + */ +void +PageSizer::on_landscape() +{ + if (!_landscapeButton.get_active()) + return; + Inkscape::Util::Quantity w = Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionWidth.getUnit()); + Inkscape::Util::Quantity h = Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionHeight.getUnit()); + if (w < h) { + setDim (h, w); + } +} + + +/** + * Update scale widgets + */ +void +PageSizer::updateScaleUI() +{ + + static bool _called = false; + if (_called) { + return; + } + + _called = true; + + _changeds_connection.block(); + _changedvx_connection.block(); + _changedvy_connection.block(); + _changedvw_connection.block(); + _changedvh_connection.block(); + + SPDesktop *dt = SP_ACTIVE_DESKTOP; + if (dt) { + SPDocument *doc = dt->getDocument(); + + // Update scale + Geom::Scale scale = doc->getDocumentScale(); + SPNamedView *nv = dt->getNamedView(); + + std::stringstream ss; + ss << _("User units per ") << nv->display_units->abbr << "." ; + _scaleLabel.set_text( ss.str() ); + + if( !_lockScaleUpdate ) { + + double scaleX_inv = + Inkscape::Util::Quantity::convert( scale[Geom::X], "px", nv->display_units ); + if( scaleX_inv > 0 ) { + _scaleX.setValue(1.0/scaleX_inv); + } else { + // Should never happen + std::cerr << "PageSizer::updateScaleUI(): Invalid scale value: " << scaleX_inv << std::endl; + _scaleX.setValue(1.0); + } + } + + { // Don't need to lock as scaleY widget not linked to callback. + double scaleY_inv = + Inkscape::Util::Quantity::convert( scale[Geom::Y], "px", nv->display_units ); + if( scaleY_inv > 0 ) { + _scaleY.setValue(1.0/scaleY_inv); + } else { + // Should never happen + std::cerr << "PageSizer::updateScaleUI(): Invalid scale value: " << scaleY_inv << std::endl; + _scaleY.setValue(1.0); + } + } + + if( !_lockViewboxUpdate ) { + Geom::Rect viewBox = doc->getViewBox(); + _viewboxX.setValue( viewBox.min()[Geom::X] ); + _viewboxY.setValue( viewBox.min()[Geom::Y] ); + _viewboxW.setValue( viewBox.width() ); + _viewboxH.setValue( viewBox.height() ); + } + + } else { + // Should never happen + std::cerr << "PageSizer::updateScaleUI(): No active desktop." << std::endl; + _scaleLabel.set_text( "Unknown scale" ); + } + + _changeds_connection.unblock(); + _changedvx_connection.unblock(); + _changedvy_connection.unblock(); + _changedvw_connection.unblock(); + _changedvh_connection.unblock(); + + _called = false; +} + + +/** + * Callback for the dimension widgets + */ +void +PageSizer::on_value_changed() +{ + if (_widgetRegistry->isUpdating()) return; + if (_unit != _dimensionUnits.getUnit()->abbr) return; + setDim (Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionUnits.getUnit()), + Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionUnits.getUnit())); +} + +void +PageSizer::on_units_changed() +{ + if (_widgetRegistry->isUpdating()) return; + _unit = _dimensionUnits.getUnit()->abbr; + setDim (Inkscape::Util::Quantity(_dimensionWidth.getValue(""), _dimensionUnits.getUnit()), + Inkscape::Util::Quantity(_dimensionHeight.getValue(""), _dimensionUnits.getUnit()), + true, false); +} + +/** + * Callback for scale widgets + */ +void +PageSizer::on_scale_changed() +{ + if (_widgetRegistry->isUpdating()) return; + + double value = _scaleX.getValue(); + if( value > 0 ) { + + SPDesktop *dt = SP_ACTIVE_DESKTOP; + if (dt) { + SPDocument *doc = dt->getDocument(); + SPNamedView *nv = dt->getNamedView(); + + double scaleX_inv = Inkscape::Util::Quantity(1.0/value, nv->display_units ).value("px"); + + _lockScaleUpdate = true; + doc->setDocumentScale( 1.0/scaleX_inv ); + updateScaleUI(); + _lockScaleUpdate = false; + DocumentUndo::done(doc, SP_VERB_NONE, _("Set page scale")); + } + } +} + +/** + * Callback for viewbox widgets + */ +void +PageSizer::on_viewbox_changed() +{ + if (_widgetRegistry->isUpdating()) return; + + double viewboxX = _viewboxX.getValue(); + double viewboxY = _viewboxY.getValue(); + double viewboxW = _viewboxW.getValue(); + double viewboxH = _viewboxH.getValue(); + + if( viewboxW > 0 && viewboxH > 0) { + SPDesktop *dt = SP_ACTIVE_DESKTOP; + if (dt) { + SPDocument *doc = dt->getDocument(); + _lockViewboxUpdate = true; + doc->setViewBox( Geom::Rect::from_xywh( viewboxX, viewboxY, viewboxW, viewboxH ) ); + updateScaleUI(); + _lockViewboxUpdate = false; + DocumentUndo::done(doc, SP_VERB_NONE, _("Set 'viewBox'")); + } + } else { + std::cerr + << "PageSizer::on_viewbox_changed(): width and height must both be greater than zero." + << std::endl; + } +} + +void +PageSizer::on_margin_lock_changed() +{ + if (_marginLock.get_active()) { + _lock_icon.set_from_icon_name("object-locked", Gtk::ICON_SIZE_LARGE_TOOLBAR); + double left = _marginLeft.getValue(); + double right = _marginRight.getValue(); + double top = _marginTop.getValue(); + //double bottom = _marginBottom.getValue(); + if (Geom::are_near(left,right)) { + if (Geom::are_near(left, top)) { + on_margin_changed(&_marginBottom); + } else { + on_margin_changed(&_marginTop); + } + } else { + if (Geom::are_near(left, top)) { + on_margin_changed(&_marginRight); + } else { + on_margin_changed(&_marginLeft); + } + } + } else { + _lock_icon.set_from_icon_name("object-unlocked", Gtk::ICON_SIZE_LARGE_TOOLBAR); + } +} + +void +PageSizer::on_margin_changed(RegisteredScalar* widg) +{ + double value = widg->getValue(); + if (_widgetRegistry->isUpdating()) return; + if (_marginLock.get_active() && !_lockMarginUpdate) { + _lockMarginUpdate = true; + _marginLeft.setValue(value); + _marginRight.setValue(value); + _marginTop.setValue(value); + _marginBottom.setValue(value); + _lockMarginUpdate = false; + } +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/page-sizer.h b/src/ui/widget/page-sizer.h new file mode 100644 index 0000000..b399835 --- /dev/null +++ b/src/ui/widget/page-sizer.h @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Ralf Stephan <ralf@ark.in-berlin.de> + * + * Copyright (C) 2005-2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_PAGE_SIZER_H +#define INKSCAPE_UI_WIDGET_PAGE_SIZER_H + +#include <cstddef> +#include "ui/widget/registered-widget.h" +#include <sigc++/sigc++.h> + +#include "util/units.h" + +#include <gtkmm/expander.h> +#include <gtkmm/frame.h> +#include <gtkmm/grid.h> +#include <gtkmm/liststore.h> +#include <gtkmm/scrolledwindow.h> +#include <gtkmm/radiobutton.h> + +namespace Inkscape { +namespace XML { +class Node; +} + +namespace UI { +namespace Widget { + +class Registry; + +/** + * Data class used to store common paper dimensions. Used to make + * PageSizer's _paperSizeTable. + */ +class PaperSize +{ +public: + + /** + * Default constructor + */ + PaperSize() + { init(); } + + /** + * Main constructor. Use this one. + */ + PaperSize(const Glib::ustring &nameArg, + double smallerArg, + double largerArg, + Inkscape::Util::Unit const *unitArg) + { + name = nameArg; + smaller = smallerArg; + larger = largerArg; + unit = unitArg; + } + + /** + * Copy constructor + */ + PaperSize(const PaperSize &other) + { assign(other); } + + /** + * Assignment operator + */ + PaperSize &operator=(const PaperSize &other) + { assign(other); return *this; } + + /** + * Destructor + */ + virtual ~PaperSize() + = default; + + /** + * Name of this paper specification + */ + Glib::ustring name; + + /** + * The lesser of the two dimensions + */ + double smaller; + + /** + * The greater of the two dimensions + */ + double larger; + + /** + * The units (px, pt, mm, etc) of this specification + */ + Inkscape::Util::Unit const *unit; /// pointer to object in UnitTable, do not delete + +private: + + void init() + { + name = ""; + smaller = 0.0; + larger = 0.0; + unit = unit_table.getUnit("px"); + } + + void assign(const PaperSize &other) + { + name = other.name; + smaller = other.smaller; + larger = other.larger; + unit = other.unit; + } + +}; + + + + + +/** + * A compound widget that allows the user to select the desired + * page size. This widget is used in DocumentPreferences + */ +class PageSizer : public Gtk::VBox +{ +public: + + /** + * Constructor + */ + PageSizer(Registry & _wr); + + /** + * Destructor + */ + ~PageSizer() override; + + /** + * Set up or reset this widget + */ + void init (); + + /** + * Set the page size to the given dimensions. If 'changeList' is + * true, then reset the paper size list to the closest match + */ + void setDim (Inkscape::Util::Quantity w, Inkscape::Util::Quantity h, bool changeList=true, bool changeSize=true); + + /** + * Updates the scalar widgets for the fit margins. (Just changes the value + * of the ui widgets to match the xml). + */ + void updateFitMarginsUI(Inkscape::XML::Node *nv_repr); + + /** + * Updates the margin widgets. If lock widget is active + */ + void on_margin_changed(RegisteredScalar* widg); + + void on_margin_lock_changed(); + + /** + * Updates the scale widgets. (Just changes the values of the ui widgets.) + */ + void updateScaleUI(); + +protected: + + /** + * Our handy table of all 'standard' paper sizes. + */ + std::map<Glib::ustring, PaperSize> _paperSizeTable; + + /** + * Find the closest standard paper size in the table, to the + */ + Gtk::ListStore::iterator find_paper_size (Inkscape::Util::Quantity w, Inkscape::Util::Quantity h) const; + + void fire_fit_canvas_to_selection_or_drawing(); + + //### The Paper Size selection list + Gtk::HBox _paperSizeListBox; + Gtk::Label _paperSizeListLabel; + class PaperSizeColumns : public Gtk::TreeModel::ColumnRecord + { + public: + PaperSizeColumns() + { add(nameColumn); add(descColumn); } + Gtk::TreeModelColumn<Glib::ustring> nameColumn; + Gtk::TreeModelColumn<Glib::ustring> descColumn; + }; + + PaperSizeColumns _paperSizeListColumns; + Glib::RefPtr<Gtk::ListStore> _paperSizeListStore; + Gtk::TreeView _paperSizeList; + Glib::RefPtr<Gtk::TreeSelection> _paperSizeListSelection; + Gtk::ScrolledWindow _paperSizeListScroller; + //callback + void on_paper_size_list_changed(); + sigc::connection _paper_size_list_connection; + + //### Portrait or landscape orientation + Gtk::HBox _orientationBox; + Gtk::Label _orientationLabel; + Gtk::RadioButton _portraitButton; + Gtk::RadioButton _landscapeButton; + //callbacks + void on_portrait(); + void on_landscape(); + sigc::connection _portrait_connection; + sigc::connection _landscape_connection; + + //### Custom size frame + Gtk::Frame _customFrame; + Gtk::Grid _customDimTable; + + RegisteredUnitMenu _dimensionUnits; + RegisteredScalarUnit _dimensionWidth; + RegisteredScalarUnit _dimensionHeight; + + //### Fit Page options + Gtk::Expander _fitPageMarginExpander; + + Gtk::Grid _marginTable; + Gtk::Box _marginBox; + Gtk::Label _marginLabel; + RegisteredToggleButton _marginLock; + Gtk::Image _lock_icon; + RegisteredScalar _marginTop; + RegisteredScalar _marginLeft; + RegisteredScalar _marginRight; + RegisteredScalar _marginBottom; + Gtk::Button _fitPageButton; + bool _lockMarginUpdate; + + // Document scale + Gtk::Frame _scaleFrame; + Gtk::Grid _scaleTable; + + Gtk::Label _scaleLabel; + RegisteredScalar _scaleX; + RegisteredScalar _scaleY; + bool _lockScaleUpdate; + + // Viewbox + Gtk::Expander _viewboxExpander; + Gtk::Grid _viewboxTable; + + RegisteredScalar _viewboxX; + RegisteredScalar _viewboxY; + RegisteredScalar _viewboxW; + RegisteredScalar _viewboxH; + Gtk::Box _viewboxSpacer; + bool _lockViewboxUpdate; + + //callback + void on_value_changed(); + void on_units_changed(); + void on_scale_changed(); + void on_viewbox_changed(); + sigc::connection _changedw_connection; + sigc::connection _changedh_connection; + sigc::connection _changedu_connection; + sigc::connection _changeds_connection; + sigc::connection _changedvx_connection; + sigc::connection _changedvy_connection; + sigc::connection _changedvw_connection; + sigc::connection _changedvh_connection; + sigc::connection _changedlk_connection; + sigc::connection _changedmt_connection; + sigc::connection _changedmb_connection; + sigc::connection _changedml_connection; + sigc::connection _changedmr_connection; + + Registry *_widgetRegistry; + + //### state - whether we are currently landscape or portrait + bool _landscape; + + Glib::ustring _unit; + +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + + +#endif // INKSCAPE_UI_WIDGET_PAGE_SIZER_H + +/* + 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 : diff --git a/src/ui/widget/pages-skeleton.h b/src/ui/widget/pages-skeleton.h new file mode 100644 index 0000000..c62e03e --- /dev/null +++ b/src/ui/widget/pages-skeleton.h @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * List of paper sizes + */ +/* + * Authors: + * bulia byak <buliabyak@users.sf.net> + * Lauris Kaplinski <lauris@kaplinski.com> + * Jon Phillips <jon@rejon.org> + * Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm) + * Bob Jamison <ishmal@users.sf.net> + * Abhishek Sharma + * + see git history + * + * Copyright (C) 2000 - 2018 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_PAGES_SKELETON_H +#define SEEN_PAGES_SKELETON_H + + + /** \note + * The ISO page sizes in the table below differ from ghostscript's idea of page sizes (by + * less than 1pt). Being off by <1pt should be OK for most purposes, but may cause fuzziness + * (antialiasing) problems when printing to 72dpi or 144dpi printers or bitmap files due to + * postscript's different coordinate system (y=0 meaning bottom of page in postscript and top + * of page in SVG). I haven't looked into whether this does in fact cause fuzziness, I merely + * note the possibility. Rounding done by extension/internal/ps.cpp (e.g. floor/ceil calls) + * will also affect whether fuzziness occurs. + * + * The remainder of this comment discusses the origin of the numbers used for ISO page sizes in + * this table and in ghostscript. + * + * The versions here, in mm, are the official sizes according to + * <a href="http://en.wikipedia.org/wiki/Paper_sizes">http://en.wikipedia.org/wiki/Paper_sizes</a> + * at 2005-01-25. (The ISO entries in the below table + * were produced mechanically from the table on that page.) + * + * (The rule seems to be that A0, B0, ..., D0. sizes are rounded to the nearest number of mm + * from the "theoretical size" (i.e. 1000 * sqrt(2) or pow(2.0, .25) or the like), whereas + * going from e.g. A0 to A1 always take the floor of halving -- which by chance coincides + * exactly with flooring the "theoretical size" for n != 0 instead of the rounding to nearest + * done for n==0.) + * + * Ghostscript paper sizes are given in gs_statd.ps according to gs(1). gs_statd.ps always + * uses an integer number ofpt: sometimes gs_statd.ps rounds to nearest (e.g. a1), sometimes + * floors (e.g. a10), sometimes ceils (e.g. a8). + * + * I'm not sure how ghostscript's gs_statd.ps was calculated: it isn't just rounding the + * "theoretical size" of each page topt (see a0), nor is it rounding the a0 size times an + * appropriate power of two (see a1). Possibly it was prepared manually, with a human applying + * inconsistent rounding rules when converting from mm to pt. + */ + /** \todo + * Should we include the JIS B series (used in Japan) + * (JIS B0 is sometimes called JB0, and similarly for JB1 etc)? + * Should we exclude B7--B10 and A7--10 to make the list smaller ? + * Should we include any of the ISO C, D and E series (see below) ? + */ + + + + /* See http://www.hbp.com/content/PCR_envelopes.cfm for a much larger list of US envelope + sizes. */ + /* Note that `Folio' (used in QPrinter/KPrinter) is deliberately absent from this list, as it + means different sizes to different people: different people may expect the width to be + either 8, 8.25 or 8.5 inches, and the height to be either 13 or 13.5 inches, even + restricting our interpretation to foolscap folio. If you wish to introduce a folio-like + page size to the list, then please consider using a name more specific than just `Folio' or + `Foolscap Folio'. */ + +static char const pages_skeleton[] = R"(#Inkscape page sizes +#NAME, WIDTH, HEIGHT, UNIT +A4, 210, 297, mm +US Letter, 8.5, 11, in +US Legal, 8.5, 14, in +US Executive, 7.25, 10.5, in +A0, 841, 1189, mm +A1, 594, 841, mm +A2, 420, 594, mm +A3, 297, 420, mm +A5, 148, 210, mm +A6, 105, 148, mm +A7, 74, 105, mm +A8, 52, 74, mm +A9, 37, 52, mm +A10, 26, 37, mm +B0, 1000, 1414, mm +B1, 707, 1000, mm +B2, 500, 707, mm +B3, 353, 500, mm +B4, 250, 353, mm +B5, 176, 250, mm +B6, 125, 176, mm +B7, 88, 125, mm +B8, 62, 88, mm +B9, 44, 62, mm +B10, 31, 44, mm +C0, 917, 1297, mm +C1, 648, 917, mm +C2, 458, 648, mm +C3, 324, 458, mm +C4, 229, 324, mm +C5, 162, 229, mm +C6, 114, 162, mm +C7, 81, 114, mm +C8, 57, 81, mm +C9, 40, 57, mm +C10, 28, 40, mm +D1, 545, 771, mm +D2, 385, 545, mm +D3, 272, 385, mm +D4, 192, 272, mm +D5, 136, 192, mm +D6, 96, 136, mm +D7, 68, 96, mm +E3, 400, 560, mm +E4, 280, 400, mm +E5, 200, 280, mm +E6, 140, 200, mm +CSE, 462, 649, pt +US #10 Envelope, 9.5, 4.125, in +DL Envelope, 220, 110, mm +Ledger/Tabloid, 11, 17, in +Banner 468x60, 468, 60, px +Icon 16x16, 16, 16, px +Icon 32x32, 32, 32, px +Icon 48x48, 48, 48, px +ID Card (ISO 7810), 85.60, 53.98, mm +Business Card (US), 3.5, 2, in +Business Card (Europe), 85, 55, mm +Business Card (Aus/NZ), 90, 55, mm +Arch A, 9, 12, in +Arch B, 12, 18, in +Arch C, 18, 24, in +Arch D, 24, 36, in +Arch E, 36, 48, in +Arch E1, 30, 42, in +Video SD / PAL, 768, 576, px +Video SD-Widescreen / PAL, 1024, 576, px +Video SD / NTSC, 544, 480, px +Video SD-Widescreen / NTSC, 872, 486, px +Video HD 720p, 1280, 720, px +Video HD 1080p, 1920, 1080, px +Video DCI 2k (Full Frame), 2048, 1080, px +Video UHD 4k, 3840, 2160, px +Video DCI 4k (Full Frame), 4096, 2160, px +Video UHD 8k, 7680, 4320, px +)"; + +#endif diff --git a/src/ui/widget/panel.cpp b/src/ui/widget/panel.cpp new file mode 100644 index 0000000..8ca08a0 --- /dev/null +++ b/src/ui/widget/panel.cpp @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Bryce Harrington <bryce@bryceharrington.org> + * Jon A. Cruz <jon@joncruz.org> + * Gustav Broberg <broberg@kth.se> + * + * Copyright (C) 2004 Bryce Harrington + * Copyright (C) 2005 Jon A. Cruz + * Copyright (C) 2007 Gustav Broberg + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/dialog.h> // for Gtk::RESPONSE_* + +#include <glibmm/i18n.h> + +#include "panel.h" +#include "desktop.h" + +#include "inkscape.h" +#include "preview.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +void Panel::prep() { + GtkIconSize sizes[] = { + GTK_ICON_SIZE_MENU, + GTK_ICON_SIZE_MENU, + GTK_ICON_SIZE_SMALL_TOOLBAR, + GTK_ICON_SIZE_BUTTON, + GTK_ICON_SIZE_DND, // Not used by options, but included to make the last size larger + GTK_ICON_SIZE_DIALOG + }; + Preview::set_size_mappings( G_N_ELEMENTS(sizes), sizes ); +} + +Panel::Panel(gchar const *prefs_path, int verb_num) : + _prefs_path(prefs_path), + _desktop(SP_ACTIVE_DESKTOP), + _verb_num(verb_num), + _action_area(nullptr) +{ + set_name("InkscapePanel"); + set_orientation(Gtk::ORIENTATION_VERTICAL); + + signalResponse().connect(sigc::mem_fun(*this, &Panel::_handleResponse)); + signalActivateDesktop().connect(sigc::mem_fun(*this, &Panel::setDesktop)); + + pack_start(_contents, true, true); + + show_all_children(); +} + +Panel::~Panel() += default; + +void Panel::present() +{ + _signal_present.emit(); +} + +sigc::signal<void, int> &Panel::signalResponse() +{ + return _signal_response; +} + +sigc::signal<void> &Panel::signalPresent() +{ + return _signal_present; +} + +gchar const *Panel::getPrefsPath() const +{ + return _prefs_path.data(); +} + +int const &Panel::getVerb() const +{ + return _verb_num; +} + +void Panel::setDesktop(SPDesktop *desktop) +{ + _desktop = desktop; +} + +void Panel::_apply() +{ + g_warning("Apply button clicked for panel [Panel::_apply()]"); +} + +Gtk::Button *Panel::addResponseButton(const Glib::ustring &button_text, int response_id, bool pack_start) +{ + // Create a button box for the response buttons if it's the first button to be added + if (!_action_area) { + _action_area = new Gtk::ButtonBox(); + _action_area->set_layout(Gtk::BUTTONBOX_END); + _action_area->set_spacing(6); + _action_area->set_border_width(4); + pack_end(*_action_area, Gtk::PACK_SHRINK, 0); + } + + Gtk::Button *button = new Gtk::Button(button_text, true); + + _action_area->pack_end(*button); + + if (pack_start) { + _action_area->set_child_secondary(*button , true); + } + + if (response_id != 0) { + // Re-emit clicked signals as response signals + button->signal_clicked().connect(sigc::bind(_signal_response.make_slot(), response_id)); + _response_map[response_id] = button; + } + + return button; +} + +void Panel::setResponseSensitive(int response_id, bool setting) +{ + if (_response_map[response_id]) + _response_map[response_id]->set_sensitive(setting); +} + +sigc::signal<void, SPDesktop *, SPDocument *> & +Panel::signalDocumentReplaced() +{ + return _signal_document_replaced; +} + +sigc::signal<void, SPDesktop *> & +Panel::signalActivateDesktop() +{ + return _signal_activate_desktop; +} + +sigc::signal<void, SPDesktop *> & +Panel::signalDeactiveDesktop() +{ + return _signal_deactive_desktop; +} + +void Panel::_handleResponse(int response_id) +{ + switch (response_id) { + case Gtk::RESPONSE_APPLY: { + _apply(); + break; + } + } +} + +Inkscape::Selection *Panel::_getSelection() +{ + return _desktop->getSelection(); +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/panel.h b/src/ui/widget/panel.h new file mode 100644 index 0000000..3f08218 --- /dev/null +++ b/src/ui/widget/panel.h @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Bryce Harrington <bryce@bryceharrington.org> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2004 Bryce Harrington + * Copyright (C) 2005 Jon A. Cruz + * Copyright (C) 2012 Kris De Gussem + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INKSCAPE_UI_WIDGET_PANEL_H +#define SEEN_INKSCAPE_UI_WIDGET_PANEL_H + +#include <gtkmm/box.h> +#include <map> + +class SPDesktop; +class SPDocument; + +namespace Gtk { + class Button; + class ButtonBox; +} + +struct InkscapeApplication; + +namespace Inkscape { + +class Selection; + +namespace UI { + +namespace Widget { + +/** + * A generic dockable container. + * + * Inkscape::UI::Widget::Panel is a base class from which dockable dialogs + * are created. A new dockable dialog is created by deriving a class from panel. + * Child widgets are private data members of Panel (no need to use pointers and + * new). + * + * @see UI::Dialog::DesktopTracker to handle desktop change, selection change and selected object modifications. + * @see UI::Dialog::DialogManager manages the dialogs within inkscape. + */ +class Panel : public Gtk::Box { +public: + static void prep(); + + /** + * Construct a Panel. + * + * @param prefs_path characteristic path to load/save dialog position. + * @param verb_num the dialog verb. + */ + Panel(gchar const *prefs_path = nullptr, int verb_num = 0); + ~Panel() override; + + gchar const *getPrefsPath() const; + + int const &getVerb() const; + + virtual void present(); //< request to be present + + void restorePanelPrefs(); + + virtual void setDesktop(SPDesktop *desktop); + SPDesktop *getDesktop() { return _desktop; } + + /* Signal accessors */ + virtual sigc::signal<void, int> &signalResponse(); + virtual sigc::signal<void> &signalPresent(); + + /* Methods providing a Gtk::Dialog like interface for adding buttons that emit Gtk::RESPONSE + * signals on click. */ + Gtk::Button* addResponseButton (const Glib::ustring &button_text, int response_id, bool pack_start=false); + void setResponseSensitive(int response_id, bool setting); + + /* Return signals. Signals emitted by PanelDialog. */ + virtual sigc::signal<void, SPDesktop *, SPDocument *> &signalDocumentReplaced(); + virtual sigc::signal<void, SPDesktop *> &signalActivateDesktop(); + virtual sigc::signal<void, SPDesktop *> &signalDeactiveDesktop(); + +protected: + /** + * Returns a pointer to a Gtk::Box containing the child widgets. + */ + Gtk::Box *_getContents() { return &_contents; } + virtual void _apply(); + + virtual void _handleResponse(int response_id); + + /* Helper methods */ + Inkscape::Selection *_getSelection(); + + /** + * Stores characteristic path for loading/saving the dialog position. + */ + Glib::ustring const _prefs_path; + + /* Signals */ + sigc::signal<void, int> _signal_response; + sigc::signal<void> _signal_present; + sigc::signal<void, SPDesktop *, SPDocument *> _signal_document_replaced; + sigc::signal<void, SPDesktop *> _signal_activate_desktop; + sigc::signal<void, SPDesktop *> _signal_deactive_desktop; + +private: + SPDesktop *_desktop; + + int _verb_num; + + Gtk::VBox _contents; + Gtk::ButtonBox *_action_area; //< stores response buttons + + /* A map to store which widget that emits a certain response signal */ + typedef std::map<int, Gtk::Widget *> ResponseMap; + ResponseMap _response_map; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // SEEN_INKSCAPE_UI_WIDGET_PANEL_H + +/* + 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 : diff --git a/src/ui/widget/point.cpp b/src/ui/widget/point.cpp new file mode 100644 index 0000000..e0d6eed --- /dev/null +++ b/src/ui/widget/point.cpp @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Johan Engelen <j.b.c.engelen@utwente.nl> + * Carl Hetherington <inkscape@carlh.net> + * Derek P. Moore <derekm@hackunix.org> + * Bryce Harrington <bryce@bryceharrington.org> + * + * Copyright (C) 2007 Authors + * Copyright (C) 2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/widget/point.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +Point::Point(Glib::ustring const &label, Glib::ustring const &tooltip, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Labelled(label, tooltip, new Gtk::VBox(), suffix, icon, mnemonic), + xwidget("X:",""), + ywidget("Y:","") +{ + static_cast<Gtk::VBox*>(_widget)->pack_start(xwidget, true, true); + static_cast<Gtk::VBox*>(_widget)->pack_start(ywidget, true, true); + static_cast<Gtk::VBox*>(_widget)->show_all_children(); +} + +Point::Point(Glib::ustring const &label, Glib::ustring const &tooltip, + unsigned digits, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Labelled(label, tooltip, new Gtk::VBox(), suffix, icon, mnemonic), + xwidget("X:","", digits), + ywidget("Y:","", digits) +{ + static_cast<Gtk::VBox*>(_widget)->pack_start(xwidget, true, true); + static_cast<Gtk::VBox*>(_widget)->pack_start(ywidget, true, true); + static_cast<Gtk::VBox*>(_widget)->show_all_children(); +} + +Point::Point(Glib::ustring const &label, Glib::ustring const &tooltip, + Glib::RefPtr<Gtk::Adjustment> &adjust, + unsigned digits, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Labelled(label, tooltip, new Gtk::VBox(), suffix, icon, mnemonic), + xwidget("X:","", adjust, digits), + ywidget("Y:","", adjust, digits) +{ + static_cast<Gtk::VBox*>(_widget)->pack_start(xwidget, true, true); + static_cast<Gtk::VBox*>(_widget)->pack_start(ywidget, true, true); + static_cast<Gtk::VBox*>(_widget)->show_all_children(); +} + +unsigned Point::getDigits() const +{ + return xwidget.getDigits(); +} + +double Point::getStep() const +{ + return xwidget.getStep(); +} + +double Point::getPage() const +{ + return xwidget.getPage(); +} + +double Point::getRangeMin() const +{ + return xwidget.getRangeMin(); +} + +double Point::getRangeMax() const +{ + return xwidget.getRangeMax(); +} + +double Point::getXValue() const +{ + return xwidget.getValue(); +} + +double Point::getYValue() const +{ + return ywidget.getValue(); +} + +Geom::Point Point::getValue() const +{ + return Geom::Point( getXValue() , getYValue() ); +} + +int Point::getXValueAsInt() const +{ + return xwidget.getValueAsInt(); +} + +int Point::getYValueAsInt() const +{ + return ywidget.getValueAsInt(); +} + + +void Point::setDigits(unsigned digits) +{ + xwidget.setDigits(digits); + ywidget.setDigits(digits); +} + +void Point::setIncrements(double step, double page) +{ + xwidget.setIncrements(step, page); + ywidget.setIncrements(step, page); +} + +void Point::setRange(double min, double max) +{ + xwidget.setRange(min, max); + ywidget.setRange(min, max); +} + +void Point::setValue(Geom::Point const & p) +{ + xwidget.setValue(p[0]); + ywidget.setValue(p[1]); +} + +void Point::update() +{ + xwidget.update(); + ywidget.update(); +} + +bool Point::setProgrammatically() +{ + return (xwidget.setProgrammatically || ywidget.setProgrammatically); +} + +void Point::clearProgrammatically() +{ + xwidget.setProgrammatically = false; + ywidget.setProgrammatically = false; +} + + +Glib::SignalProxy0<void> Point::signal_x_value_changed() +{ + return xwidget.signal_value_changed(); +} + +Glib::SignalProxy0<void> Point::signal_y_value_changed() +{ + return ywidget.signal_value_changed(); +} + + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/point.h b/src/ui/widget/point.h new file mode 100644 index 0000000..018be5b --- /dev/null +++ b/src/ui/widget/point.h @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Johan Engelen <j.b.c.engelen@utwente.nl> + * Carl Hetherington <inkscape@carlh.net> + * Derek P. Moore <derekm@hackunix.org> + * Bryce Harrington <bryce@bryceharrington.org> + * + * Copyright (C) 2007 Authors + * Copyright (C) 2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef INKSCAPE_UI_WIDGET_POINT_H +#define INKSCAPE_UI_WIDGET_POINT_H + +#include "ui/widget/labelled.h" +#include <2geom/point.h> +#include "ui/widget/scalar.h" + +namespace Gtk { +class Adjustment; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A labelled text box, with spin buttons and optional icon or suffix, for + * entering arbitrary coordinate values. + */ +class Point : public Labelled +{ +public: + + + /** + * Construct a Point Widget. + * + * @param label Label. + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to false). + */ + Point( Glib::ustring const &label, + Glib::ustring const &tooltip, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Construct a Point Widget. + * + * @param label Label. + * @param digits Number of decimal digits to display. + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to false). + */ + Point( Glib::ustring const &label, + Glib::ustring const &tooltip, + unsigned digits, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Construct a Point Widget. + * + * @param label Label. + * @param adjust Adjustment to use for the SpinButton. + * @param digits Number of decimal digits to display (defaults to 0). + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to true). + */ + Point( Glib::ustring const &label, + Glib::ustring const &tooltip, + Glib::RefPtr<Gtk::Adjustment> &adjust, + unsigned digits = 0, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Fetches the precision of the spin button. + */ + unsigned getDigits() const; + + /** + * Gets the current step increment used by the spin button. + */ + double getStep() const; + + /** + * Gets the current page increment used by the spin button. + */ + double getPage() const; + + /** + * Gets the minimum range value allowed for the spin button. + */ + double getRangeMin() const; + + /** + * Gets the maximum range value allowed for the spin button. + */ + double getRangeMax() const; + + bool getSnapToTicks() const; + + /** + * Get the value in the spin_button. + */ + double getXValue() const; + + double getYValue() const; + + Geom::Point getValue() const; + + /** + * Get the value spin_button represented as an integer. + */ + int getXValueAsInt() const; + + int getYValueAsInt() const; + + /** + * Sets the precision to be displayed by the spin button. + */ + void setDigits(unsigned digits); + + /** + * Sets the step and page increments for the spin button. + */ + void setIncrements(double step, double page); + + /** + * Sets the minimum and maximum range allowed for the spin button. + */ + void setRange(double min, double max); + + /** + * Sets the value of the spin button. + */ + void setValue(Geom::Point const & p); + + /** + * Manually forces an update of the spin button. + */ + void update(); + + /** + * Signal raised when the spin button's value changes. + */ + Glib::SignalProxy0<void> signal_x_value_changed(); + + Glib::SignalProxy0<void> signal_y_value_changed(); + + /** + * Check 'setProgrammatically' of both scalar widgets. False if value is changed by user by clicking the widget. + * true if the value was set by setValue, not changed by the user; + * if a callback checks it, it must reset it back to false. + */ + bool setProgrammatically(); + + void clearProgrammatically(); + +protected: + Scalar xwidget; + Scalar ywidget; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_POINT_H + +/* + 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 : diff --git a/src/ui/widget/preferences-widget.cpp b/src/ui/widget/preferences-widget.cpp new file mode 100644 index 0000000..92c101b --- /dev/null +++ b/src/ui/widget/preferences-widget.cpp @@ -0,0 +1,1023 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Inkscape Preferences dialog. + * + * Authors: + * Marco Scholten + * Bruno Dilly <bruno.dilly@gmail.com> + * + * Copyright (C) 2004, 2006, 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm/i18n.h> +#include <glibmm/convert.h> +#include <glibmm/regex.h> + +#include <gtkmm/box.h> +#include <gtkmm/frame.h> +#include <gtkmm/scale.h> +#include <gtkmm/table.h> + + +#include "desktop.h" +#include "inkscape.h" +#include "message-stack.h" +#include "preferences.h" +#include "selcue.h" +#include "selection-chemistry.h" +#include "verbs.h" + +#include "include/gtkmm_version.h" + +#include "io/sys.h" + +#include "ui/dialog/filedialog.h" +#include "ui/icon-loader.h" +#include "ui/widget/preferences-widget.h" + + +#ifdef _WIN32 +#include <windows.h> +#endif + +using namespace Inkscape::UI::Widget; + +namespace Inkscape { +namespace UI { +namespace Widget { + +DialogPage::DialogPage() +{ + set_border_width(12); + + set_orientation(Gtk::ORIENTATION_VERTICAL); + set_column_spacing(12); + set_row_spacing(6); +} + +/** + * Add a widget to the bottom row of the dialog page + * + * \param[in] indent Whether the widget should be indented by one column + * \param[in] label The label text for the widget + * \param[in] widget The widget to add to the page + * \param[in] suffix Text for an optional label at the right of the widget + * \param[in] tip Tooltip text for the widget + * \param[in] expand_widget Whether to expand the widget horizontally + * \param[in] other_widget An optional additional widget to display at the right of the first one + */ +void DialogPage::add_line(bool indent, + Glib::ustring const &label, + Gtk::Widget &widget, + Glib::ustring const &suffix, + const Glib::ustring &tip, + bool expand_widget, + Gtk::Widget *other_widget) +{ + if (tip != "") + widget.set_tooltip_text (tip); + + auto hb = Gtk::manage(new Gtk::Box()); + hb->set_spacing(12); + hb->set_hexpand(true); + hb->pack_start(widget, expand_widget, expand_widget); + + // Pack an additional widget into a box with the widget if desired + if (other_widget) + hb->pack_start(*other_widget, expand_widget, expand_widget); + + hb->set_valign(Gtk::ALIGN_CENTER); + + // Add a label in the first column if provided + if (label != "") + { + Gtk::Label* label_widget = Gtk::manage(new Gtk::Label(label, Gtk::ALIGN_START, + Gtk::ALIGN_CENTER, true)); + label_widget->set_mnemonic_widget(widget); + label_widget->set_markup(label_widget->get_text()); + + if (indent) { + label_widget->set_margin_start(12); + } + + label_widget->set_valign(Gtk::ALIGN_CENTER); + add(*label_widget); + attach_next_to(*hb, *label_widget, Gtk::POS_RIGHT, 1, 1); + } + + // Now add the widget to the bottom of the dialog + if (label == "") + { + if (indent) { + hb->set_margin_start(12); + } + + add(*hb); + + GValue width = G_VALUE_INIT; + g_value_init(&width, G_TYPE_INT); + g_value_set_int(&width, 2); + gtk_container_child_set_property(GTK_CONTAINER(gobj()), GTK_WIDGET(hb->gobj()), "width", &width); + } + + // Add a label on the right of the widget if desired + if (suffix != "") + { + Gtk::Label* suffix_widget = Gtk::manage(new Gtk::Label(suffix , Gtk::ALIGN_START , Gtk::ALIGN_CENTER, true)); + suffix_widget->set_markup(suffix_widget->get_text()); + hb->pack_start(*suffix_widget,false,false); + } + +} + +void DialogPage::add_group_header(Glib::ustring name) +{ + if (name != "") + { + Gtk::Label* label_widget = Gtk::manage(new Gtk::Label(Glib::ustring(/*"<span size='large'>*/"<b>") + name + + Glib::ustring("</b>"/*</span>"*/) , Gtk::ALIGN_START , Gtk::ALIGN_CENTER, true)); + + label_widget->set_use_markup(true); + label_widget->set_valign(Gtk::ALIGN_CENTER); + add(*label_widget); + } +} + +void DialogPage::set_tip(Gtk::Widget& widget, Glib::ustring const &tip) +{ + widget.set_tooltip_text (tip); +} + +void PrefCheckButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path, + bool default_value) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + this->set_label(label); + this->set_active( prefs->getBool(_prefs_path, default_value) ); +} + +void PrefCheckButton::on_toggled() +{ + this->changed_signal.emit(this->get_active()); + if (this->get_visible()) //only take action if the user toggled it + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(_prefs_path, this->get_active()); + } +} + +void PrefRadioButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path, + Glib::ustring const &string_value, bool default_value, PrefRadioButton* group_member) +{ + _prefs_path = prefs_path; + _value_type = VAL_STRING; + _string_value = string_value; + (void)default_value; + this->set_label(label); + if (group_member) + { + Gtk::RadioButtonGroup rbg = group_member->get_group(); + this->set_group(rbg); + } + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring val = prefs->getString(_prefs_path); + if ( !val.empty() ) + this->set_active(val == _string_value); + else + this->set_active( false ); +} + +void PrefRadioButton::init(Glib::ustring const &label, Glib::ustring const &prefs_path, + int int_value, bool default_value, PrefRadioButton* group_member) +{ + _prefs_path = prefs_path; + _value_type = VAL_INT; + _int_value = int_value; + this->set_label(label); + if (group_member) + { + Gtk::RadioButtonGroup rbg = group_member->get_group(); + this->set_group(rbg); + } + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (default_value) + this->set_active( prefs->getInt(_prefs_path, int_value) == _int_value ); + else + this->set_active( prefs->getInt(_prefs_path, int_value + 1) == _int_value ); +} + +void PrefRadioButton::on_toggled() +{ + this->changed_signal.emit(this->get_active()); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (this->get_visible() && this->get_active() ) //only take action if toggled by user (to active) + { + if ( _value_type == VAL_STRING ) + prefs->setString(_prefs_path, _string_value); + else if ( _value_type == VAL_INT ) + prefs->setInt(_prefs_path, _int_value); + } +} + +void PrefSpinButton::init(Glib::ustring const &prefs_path, + double lower, double upper, double step_increment, double /*page_increment*/, + double default_value, bool is_int, bool is_percent) +{ + _prefs_path = prefs_path; + _is_int = is_int; + _is_percent = is_percent; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double value; + if (is_int) { + if (is_percent) { + value = 100 * prefs->getDoubleLimited(prefs_path, default_value, lower/100.0, upper/100.0); + } else { + value = (double) prefs->getIntLimited(prefs_path, (int) default_value, (int) lower, (int) upper); + } + } else { + value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper); + } + + this->set_range (lower, upper); + this->set_increments (step_increment, 0); + this->set_value (value); + this->set_width_chars(6); + if (is_int) + this->set_digits(0); + else if (step_increment < 0.1) + this->set_digits(4); + else + this->set_digits(2); + +} + +void PrefSpinButton::on_value_changed() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (this->get_visible()) //only take action if user changed value + { + if (_is_int) { + if (_is_percent) { + prefs->setDouble(_prefs_path, this->get_value()/100.0); + } else { + prefs->setInt(_prefs_path, (int) this->get_value()); + } + } else { + prefs->setDouble(_prefs_path, this->get_value()); + } + } +} + +void PrefSpinUnit::init(Glib::ustring const &prefs_path, + double lower, double upper, double step_increment, + double default_value, UnitType unit_type, Glib::ustring const &default_unit) +{ + _prefs_path = prefs_path; + _is_percent = (unit_type == UNIT_TYPE_DIMENSIONLESS); + + resetUnitType(unit_type); + setUnit(default_unit); + setRange (lower, upper); /// @fixme this disregards changes of units + setIncrements (step_increment, 0); + if (step_increment < 0.1) { + setDigits(4); + } else { + setDigits(2); + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper); + Glib::ustring unitstr = prefs->getUnit(prefs_path); + if (unitstr.length() == 0) { + unitstr = default_unit; + // write the assumed unit to preferences: + prefs->setDoubleUnit(_prefs_path, value, unitstr); + } + setValue(value, unitstr); + + signal_value_changed().connect_notify(sigc::mem_fun(*this, &PrefSpinUnit::on_my_value_changed)); +} + +void PrefSpinUnit::on_my_value_changed() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (getWidget()->get_visible()) //only take action if user changed value + { + prefs->setDoubleUnit(_prefs_path, getValue(getUnit()->abbr), getUnit()->abbr); + } +} + +const double ZoomCorrRuler::textsize = 7; +const double ZoomCorrRuler::textpadding = 5; + +ZoomCorrRuler::ZoomCorrRuler(int width, int height) : + _unitconv(1.0), + _border(5) +{ + set_size(width, height); +} + +void ZoomCorrRuler::set_size(int x, int y) +{ + _min_width = x; + _height = y; + set_size_request(x + _border*2, y + _border*2); +} + +// The following two functions are borrowed from 2geom's toy-framework-2; if they are useful in +// other locations, we should perhaps make them (or adapted versions of them) publicly available +static void +draw_text(cairo_t *cr, Geom::Point loc, const char* txt, bool bottom = false, + double fontsize = ZoomCorrRuler::textsize, std::string fontdesc = "Sans") { + PangoLayout* layout = pango_cairo_create_layout (cr); + pango_layout_set_text(layout, txt, -1); + + // set font and size + std::ostringstream sizestr; + sizestr << fontsize; + fontdesc = fontdesc + " " + sizestr.str(); + PangoFontDescription *font_desc = pango_font_description_from_string(fontdesc.c_str()); + pango_layout_set_font_description(layout, font_desc); + pango_font_description_free (font_desc); + + PangoRectangle logical_extent; + pango_layout_get_pixel_extents(layout, nullptr, &logical_extent); + cairo_move_to(cr, loc[Geom::X], loc[Geom::Y] - (bottom ? logical_extent.height : 0)); + pango_cairo_show_layout(cr, layout); +} + +static void +draw_number(cairo_t *cr, Geom::Point pos, double num) { + std::ostringstream number; + number << num; + draw_text(cr, pos, number.str().c_str(), true); +} + +/* + * \arg dist The distance between consecutive minor marks + * \arg major_interval Number of marks after which to draw a major mark + */ +void +ZoomCorrRuler::draw_marks(Cairo::RefPtr<Cairo::Context> cr, double dist, int major_interval) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + const double zoomcorr = prefs->getDouble("/options/zoomcorrection/value", 1.0); + double mark = 0; + int i = 0; + while (mark <= _drawing_width) { + cr->move_to(mark, _height); + if ((i % major_interval) == 0) { + // major mark + cr->line_to(mark, 0); + Geom::Point textpos(mark + 3, ZoomCorrRuler::textsize + ZoomCorrRuler::textpadding); + draw_number(cr->cobj(), textpos, dist * i); + } else { + // minor mark + cr->line_to(mark, ZoomCorrRuler::textsize + 2 * ZoomCorrRuler::textpadding); + } + mark += dist * zoomcorr / _unitconv; + ++i; + } +} + +bool +ZoomCorrRuler::on_draw(const Cairo::RefPtr<Cairo::Context>& cr) { + Glib::RefPtr<Gdk::Window> window = get_window(); + + int w = window->get_width(); + _drawing_width = w - _border * 2; + + cr->set_source_rgb(1.0, 1.0, 1.0); + cr->set_fill_rule(Cairo::FILL_RULE_WINDING); + cr->rectangle(0, 0, w, _height + _border*2); + cr->fill(); + + cr->set_source_rgb(0.0, 0.0, 0.0); + cr->set_line_width(0.5); + + cr->translate(_border, _border); // so that we have a small white border around the ruler + cr->move_to (0, _height); + cr->line_to (_drawing_width, _height); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring abbr = prefs->getString("/options/zoomcorrection/unit"); + if (abbr == "cm") { + draw_marks(cr, 0.1, 10); + } else if (abbr == "in") { + draw_marks(cr, 0.25, 4); + } else if (abbr == "mm") { + draw_marks(cr, 10, 10); + } else if (abbr == "pc") { + draw_marks(cr, 1, 10); + } else if (abbr == "pt") { + draw_marks(cr, 10, 10); + } else if (abbr == "px") { + draw_marks(cr, 10, 10); + } else { + draw_marks(cr, 1, 1); + } + cr->stroke(); + + return true; +} + + +void +ZoomCorrRulerSlider::on_slider_value_changed() +{ + if (this->get_visible() || freeze) //only take action if user changed value + { + freeze = true; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble("/options/zoomcorrection/value", _slider->get_value() / 100.0); + _sb.set_value(_slider->get_value()); + _ruler.queue_draw(); + freeze = false; + } +} + +void +ZoomCorrRulerSlider::on_spinbutton_value_changed() +{ + if (this->get_visible() || freeze) //only take action if user changed value + { + freeze = true; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble("/options/zoomcorrection/value", _sb.get_value() / 100.0); + _slider->set_value(_sb.get_value()); + _ruler.queue_draw(); + freeze = false; + } +} + +void +ZoomCorrRulerSlider::on_unit_changed() { + if (GPOINTER_TO_INT(_unit.get_data("sensitive")) == 0) { + // when the unit menu is initialized, the unit is set to the default but + // it needs to be reset later so we don't perform the change in this case + return; + } + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString("/options/zoomcorrection/unit", _unit.getUnitAbbr()); + double conv = _unit.getConversion(_unit.getUnitAbbr(), "px"); + _ruler.set_unit_conversion(conv); + if (_ruler.get_visible()) { + _ruler.queue_draw(); + } +} + +bool ZoomCorrRulerSlider::on_mnemonic_activate ( bool group_cycling ) +{ + return _sb.mnemonic_activate ( group_cycling ); +} + + +void +ZoomCorrRulerSlider::init(int ruler_width, int ruler_height, double lower, double upper, + double step_increment, double page_increment, double default_value) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double value = prefs->getDoubleLimited("/options/zoomcorrection/value", default_value, lower, upper) * 100.0; + + freeze = false; + + _ruler.set_size(ruler_width, ruler_height); + + _slider = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_HORIZONTAL)); + + _slider->set_size_request(_ruler.width(), -1); + _slider->set_range (lower, upper); + _slider->set_increments (step_increment, page_increment); + _slider->set_value (value); + _slider->set_digits(2); + + _slider->signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_slider_value_changed)); + _sb.signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_spinbutton_value_changed)); + _unit.signal_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_unit_changed)); + + _sb.set_range (lower, upper); + _sb.set_increments (step_increment, 0); + _sb.set_value (value); + _sb.set_digits(2); + _sb.set_halign(Gtk::ALIGN_CENTER); + _sb.set_valign(Gtk::ALIGN_END); + + _unit.set_data("sensitive", GINT_TO_POINTER(0)); + _unit.setUnitType(UNIT_TYPE_LINEAR); + _unit.set_data("sensitive", GINT_TO_POINTER(1)); + _unit.setUnit(prefs->getString("/options/zoomcorrection/unit")); + _unit.set_halign(Gtk::ALIGN_CENTER); + _unit.set_valign(Gtk::ALIGN_END); + + auto table = Gtk::manage(new Gtk::Grid()); + table->attach(*_slider, 0, 0, 1, 1); + table->attach(_sb, 1, 0, 1, 1); + table->attach(_ruler, 0, 1, 1, 1); + table->attach(_unit, 1, 1, 1, 1); + + pack_start(*table, Gtk::PACK_SHRINK); +} + +void +PrefSlider::on_slider_value_changed() +{ + if (this->get_visible() || freeze) //only take action if user changed value + { + freeze = true; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble(_prefs_path, _slider->get_value()); + _sb.set_value(_slider->get_value()); + freeze = false; + } +} + +void +PrefSlider::on_spinbutton_value_changed() +{ + if (this->get_visible() || freeze) //only take action if user changed value + { + freeze = true; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble(_prefs_path, _sb.get_value()); + _slider->set_value(_sb.get_value()); + freeze = false; + } +} + +bool PrefSlider::on_mnemonic_activate ( bool group_cycling ) +{ + return _sb.mnemonic_activate ( group_cycling ); +} + +void +PrefSlider::init(Glib::ustring const &prefs_path, + double lower, double upper, double step_increment, double page_increment, double default_value, int digits) +{ + _prefs_path = prefs_path; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double value = prefs->getDoubleLimited(prefs_path, default_value, lower, upper); + + freeze = false; + + _slider = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_HORIZONTAL)); + + _slider->set_range (lower, upper); + _slider->set_increments (step_increment, page_increment); + _slider->set_value (value); + _slider->set_digits(digits); + _slider->signal_value_changed().connect(sigc::mem_fun(*this, &PrefSlider::on_slider_value_changed)); + + _sb.signal_value_changed().connect(sigc::mem_fun(*this, &PrefSlider::on_spinbutton_value_changed)); + _sb.set_range (lower, upper); + _sb.set_increments (step_increment, 0); + _sb.set_value (value); + _sb.set_digits(digits); + _sb.set_halign(Gtk::ALIGN_CENTER); + _sb.set_valign(Gtk::ALIGN_END); + + auto table = Gtk::manage(new Gtk::Grid()); + _slider->set_hexpand(); + table->attach(*_slider, 0, 0, 1, 1); + table->attach(_sb, 1, 0, 1, 1); + + this->pack_start(*table, Gtk::PACK_EXPAND_WIDGET); +} + +void PrefCombo::init(Glib::ustring const &prefs_path, + Glib::ustring labels[], int values[], int num_items, int default_value) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int row = 0; + int value = prefs->getInt(_prefs_path, default_value); + + for (int i = 0 ; i < num_items; ++i) + { + this->append(labels[i]); + _values.push_back(values[i]); + if (value == values[i]) + row = i; + } + this->set_active(row); +} + +void PrefCombo::init(Glib::ustring const &prefs_path, + Glib::ustring labels[], Glib::ustring values[], int num_items, Glib::ustring default_value) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int row = 0; + Glib::ustring value = prefs->getString(_prefs_path); + if(value.empty()) + { + value = default_value; + } + + for (int i = 0 ; i < num_items; ++i) + { + this->append(labels[i]); + _ustr_values.push_back(values[i]); + if (value == values[i]) + row = i; + } + this->set_active(row); +} + +void PrefCombo::init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels, std::vector<int> values, + int default_value) +{ + size_t labels_size = labels.size(); + size_t values_size = values.size(); + if (values_size != labels_size) { + std::cout << "PrefCombo::" + << "Different number of values/labels in " << prefs_path << std::endl; + return; + } + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int row = 0; + int value = prefs->getInt(_prefs_path, default_value); + + for (int i = 0; i < labels_size; ++i) { + this->append(labels[i]); + _values.push_back(values[i]); + if (value == values[i]) + row = i; + } + this->set_active(row); +} + +void PrefCombo::init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels, + std::vector<Glib::ustring> values, Glib::ustring default_value) +{ + size_t labels_size = labels.size(); + size_t values_size = values.size(); + if (values_size != labels_size) { + std::cout << "PrefCombo::" + << "Different number of values/labels in " << prefs_path << std::endl; + return; + } + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int row = 0; + Glib::ustring value = prefs->getString(_prefs_path); + if (value.empty()) { + value = default_value; + } + + for (int i = 0; i < labels_size; ++i) { + this->append(labels[i]); + _ustr_values.push_back(values[i]); + if (value == values[i]) + row = i; + } + this->set_active(row); +} + +void PrefCombo::on_changed() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if(!_values.empty()) + { + prefs->setInt(_prefs_path, _values[this->get_active_row_number()]); + } + else + { + prefs->setString(_prefs_path, _ustr_values[this->get_active_row_number()]); + } + } +} + +void PrefEntryButtonHBox::init(Glib::ustring const &prefs_path, + bool visibility, Glib::ustring const &default_string) +{ + _prefs_path = prefs_path; + _default_string = default_string; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + relatedEntry = new Gtk::Entry(); + relatedButton = new Gtk::Button(_("Reset")); + relatedEntry->set_invisible_char('*'); + relatedEntry->set_visibility(visibility); + relatedEntry->set_text(prefs->getString(_prefs_path)); + this->pack_start(*relatedEntry); + this->pack_start(*relatedButton); + relatedButton->signal_clicked().connect( + sigc::mem_fun(*this, &PrefEntryButtonHBox::onRelatedButtonClickedCallback)); + relatedEntry->signal_changed().connect( + sigc::mem_fun(*this, &PrefEntryButtonHBox::onRelatedEntryChangedCallback)); +} + +void PrefEntryButtonHBox::onRelatedEntryChangedCallback() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, relatedEntry->get_text()); + } +} + +void PrefEntryButtonHBox::onRelatedButtonClickedCallback() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, _default_string); + relatedEntry->set_text(_default_string); + } +} + +bool PrefEntryButtonHBox::on_mnemonic_activate ( bool group_cycling ) +{ + return relatedEntry->mnemonic_activate ( group_cycling ); +} + +void PrefEntryFileButtonHBox::init(Glib::ustring const &prefs_path, + bool visibility) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + relatedEntry = new Gtk::Entry(); + relatedEntry->set_invisible_char('*'); + relatedEntry->set_visibility(visibility); + relatedEntry->set_text(prefs->getString(_prefs_path)); + + relatedButton = new Gtk::Button(); + Gtk::HBox* pixlabel = new Gtk::HBox(false, 3); + Gtk::Image *im = sp_get_icon_image("applications-graphics", Gtk::ICON_SIZE_BUTTON); + pixlabel->pack_start(*im); + Gtk::Label *l = new Gtk::Label(); + l->set_markup_with_mnemonic(_("_Browse...")); + pixlabel->pack_start(*l); + relatedButton->add(*pixlabel); + + this->pack_end(*relatedButton, false, false, 4); + this->pack_start(*relatedEntry, true, true, 0); + + relatedButton->signal_clicked().connect( + sigc::mem_fun(*this, &PrefEntryFileButtonHBox::onRelatedButtonClickedCallback)); + relatedEntry->signal_changed().connect( + sigc::mem_fun(*this, &PrefEntryFileButtonHBox::onRelatedEntryChangedCallback)); +} + +void PrefEntryFileButtonHBox::onRelatedEntryChangedCallback() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, relatedEntry->get_text()); + } +} + +static Inkscape::UI::Dialog::FileOpenDialog * selectPrefsFileInstance = nullptr; + +void PrefEntryFileButtonHBox::onRelatedButtonClickedCallback() +{ + if (this->get_visible()) //only take action if user changed value + { + //# Get the current directory for finding files + static Glib::ustring open_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + + Glib::ustring attr = prefs->getString(_prefs_path); + if (!attr.empty()) open_path = attr; + + //# Test if the open_path directory exists + if (!Inkscape::IO::file_test(open_path.c_str(), + (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) + open_path = ""; + +#ifdef _WIN32 + //# If no open path, default to our win32 documents folder + if (open_path.empty()) + { + // The path to the My Documents folder is read from the + // value "HKEY_CURRENT_USER\Software\Windows\CurrentVersion\Explorer\Shell Folders\Personal" + HKEY key = NULL; + if(RegOpenKeyExA(HKEY_CURRENT_USER, + "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", + 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) + { + WCHAR utf16path[_MAX_PATH]; + DWORD value_type; + DWORD data_size = sizeof(utf16path); + if(RegQueryValueExW(key, L"Personal", NULL, &value_type, + (BYTE*)utf16path, &data_size) == ERROR_SUCCESS) + { + g_assert(value_type == REG_SZ); + gchar *utf8path = g_utf16_to_utf8( + (const gunichar2*)utf16path, -1, NULL, NULL, NULL); + if(utf8path) + { + open_path = Glib::ustring(utf8path); + g_free(utf8path); + } + } + } + } +#endif + + //# If no open path, default to our home directory + if (open_path.empty()) + { + open_path = g_get_home_dir(); + open_path.append(G_DIR_SEPARATOR_S); + } + + //# Create a dialog + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (!selectPrefsFileInstance) { + selectPrefsFileInstance = + Inkscape::UI::Dialog::FileOpenDialog::create( + *desktop->getToplevel(), + open_path, + Inkscape::UI::Dialog::EXE_TYPES, + _("Select a bitmap editor")); + } + + //# Show the dialog + bool const success = selectPrefsFileInstance->show(); + + if (!success) { + return; + } + + //# User selected something. Get name and type + Glib::ustring fileName = selectPrefsFileInstance->getFilename(); + + if (!fileName.empty()) + { + Glib::ustring newFileName = Glib::filename_to_utf8(fileName); + + if ( newFileName.size() > 0) + open_path = newFileName; + else + g_warning( "ERROR CONVERTING OPEN FILENAME TO UTF-8" ); + + prefs->setString(_prefs_path, open_path); + } + + relatedEntry->set_text(fileName); + } +} + +bool PrefEntryFileButtonHBox::on_mnemonic_activate ( bool group_cycling ) +{ + return relatedEntry->mnemonic_activate ( group_cycling ); +} + +void PrefOpenFolder::init(Glib::ustring const &entry_string, Glib::ustring const &tooltip) +{ + relatedEntry = new Gtk::Entry(); + relatedButton = new Gtk::Button(); + Gtk::HBox *pixlabel = new Gtk::HBox(false, 3); + Gtk::Image *im = sp_get_icon_image("document-open", Gtk::ICON_SIZE_BUTTON); + pixlabel->pack_start(*im); + Gtk::Label *l = new Gtk::Label(); + l->set_markup_with_mnemonic(_("Open")); + pixlabel->pack_start(*l); + relatedButton->add(*pixlabel); + relatedButton->set_tooltip_text(tooltip); + relatedEntry->set_text(entry_string); + relatedEntry->set_sensitive(false); + this->pack_end(*relatedButton, false, false, 4); + this->pack_start(*relatedEntry, true, true, 0); + relatedButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefOpenFolder::onRelatedButtonClickedCallback)); +} + +void PrefOpenFolder::onRelatedButtonClickedCallback() +{ + g_mkdir_with_parents(relatedEntry->get_text().c_str(), 0700); + // https://stackoverflow.com/questions/42442189/how-to-open-spawn-a-file-with-glib-gtkmm-in-windows +#ifdef _WIN32 + ShellExecute(NULL, "open", relatedEntry->get_text().c_str(), NULL, NULL, SW_SHOWDEFAULT); +#elif defined(__APPLE__) + std::vector<std::string> argv = { "open", relatedEntry->get_text().raw() }; + Glib::spawn_async("", argv, Glib::SpawnFlags::SPAWN_SEARCH_PATH); +#else + gchar *path = g_filename_to_uri(relatedEntry->get_text().c_str(), NULL, NULL); + Glib::ustring xgd = "xdg-open "; + xgd += path; + system((xgd).c_str()); + g_free(path); +#endif +} + +void PrefFileButton::init(Glib::ustring const &prefs_path) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + select_filename(Glib::filename_from_utf8(prefs->getString(_prefs_path))); + + signal_selection_changed().connect(sigc::mem_fun(*this, &PrefFileButton::onFileChanged)); +} + +void PrefFileButton::onFileChanged() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, Glib::filename_to_utf8(get_filename())); +} + +void PrefEntry::init(Glib::ustring const &prefs_path, bool visibility) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + this->set_invisible_char('*'); + this->set_visibility(visibility); + this->set_text(prefs->getString(_prefs_path)); +} + +void PrefEntry::on_changed() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, this->get_text()); + } +} + +void PrefMultiEntry::init(Glib::ustring const &prefs_path, int height) +{ + // TODO: Figure out if there's a way to specify height in lines instead of px + // and how to obtain a reasonable default width if 'expand_widget' is not used + set_size_request(100, height); + set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + set_shadow_type(Gtk::SHADOW_IN); + + add(_text); + + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring value = prefs->getString(_prefs_path); + value = Glib::Regex::create("\\|")->replace_literal(value, 0, "\n", (Glib::RegexMatchFlags)0); + _text.get_buffer()->set_text(value); + _text.get_buffer()->signal_changed().connect(sigc::mem_fun(*this, &PrefMultiEntry::on_changed)); +} + +void PrefMultiEntry::on_changed() +{ + if (get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring value = _text.get_buffer()->get_text(); + value = Glib::Regex::create("\\n")->replace_literal(value, 0, "|", (Glib::RegexMatchFlags)0); + prefs->setString(_prefs_path, value); + } +} + +void PrefColorPicker::init(Glib::ustring const &label, Glib::ustring const &prefs_path, + guint32 default_rgba) +{ + _prefs_path = prefs_path; + _title = label; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + this->setRgba32( prefs->getInt(_prefs_path, (int)default_rgba) ); +} + +void PrefColorPicker::on_changed (guint32 rgba) +{ + if (this->get_visible()) //only take action if the user toggled it + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setInt(_prefs_path, (int) rgba); + } +} + +void PrefUnit::init(Glib::ustring const &prefs_path) +{ + _prefs_path = prefs_path; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + setUnitType(UNIT_TYPE_LINEAR); + setUnit(prefs->getString(_prefs_path)); +} + +void PrefUnit::on_changed() +{ + if (this->get_visible()) //only take action if user changed value + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(_prefs_path, getUnitAbbr()); + } +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/preferences-widget.h b/src/ui/widget/preferences-widget.h new file mode 100644 index 0000000..3e132c0 --- /dev/null +++ b/src/ui/widget/preferences-widget.h @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Widgets for Inkscape Preferences dialog. + */ +/* + * Authors: + * Marco Scholten + * Bruno Dilly <bruno.dilly@gmail.com> + * + * Copyright (C) 2004, 2006, 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_INKSCAPE_PREFERENCES_H +#define INKSCAPE_UI_WIDGET_INKSCAPE_PREFERENCES_H + +#include <iostream> +#include <vector> + +#include <gtkmm/filechooserbutton.h> +#include "ui/widget/spinbutton.h" +#include <cstddef> +#include <sigc++/sigc++.h> +#include <gtkmm/checkbutton.h> +#include <gtkmm/radiobutton.h> +#include <gtkmm/scrolledwindow.h> +#include <gtkmm/textview.h> +#include <gtkmm/comboboxtext.h> +#include <gtkmm/drawingarea.h> +#include <gtkmm/grid.h> + +#include "ui/widget/color-picker.h" +#include "ui/widget/unit-menu.h" +#include "ui/widget/spinbutton.h" +#include "ui/widget/scalar-unit.h" + +namespace Gtk { +class Scale; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +class PrefCheckButton : public Gtk::CheckButton +{ +public: + void init(Glib::ustring const &label, Glib::ustring const &prefs_path, + bool default_value); + sigc::signal<void, bool> changed_signal; +protected: + Glib::ustring _prefs_path; + void on_toggled() override; +}; + +class PrefRadioButton : public Gtk::RadioButton +{ +public: + void init(Glib::ustring const &label, Glib::ustring const &prefs_path, + int int_value, bool default_value, PrefRadioButton* group_member); + void init(Glib::ustring const &label, Glib::ustring const &prefs_path, + Glib::ustring const &string_value, bool default_value, PrefRadioButton* group_member); + sigc::signal<void, bool> changed_signal; +protected: + Glib::ustring _prefs_path; + Glib::ustring _string_value; + int _value_type; + enum + { + VAL_INT, + VAL_STRING + }; + int _int_value; + void on_toggled() override; +}; + +class PrefSpinButton : public SpinButton +{ +public: + void init(Glib::ustring const &prefs_path, + double lower, double upper, double step_increment, double page_increment, + double default_value, bool is_int, bool is_percent); +protected: + Glib::ustring _prefs_path; + bool _is_int; + bool _is_percent; + void on_value_changed() override; +}; + +class PrefSpinUnit : public ScalarUnit +{ +public: + PrefSpinUnit() : ScalarUnit("", "") {}; + + void init(Glib::ustring const &prefs_path, + double lower, double upper, double step_increment, + double default_value, + UnitType unit_type, Glib::ustring const &default_unit); +protected: + Glib::ustring _prefs_path; + bool _is_percent; + void on_my_value_changed(); +}; + +class ZoomCorrRuler : public Gtk::DrawingArea { +public: + ZoomCorrRuler(int width = 100, int height = 20); + void set_size(int x, int y); + void set_unit_conversion(double conv) { _unitconv = conv; } + + int width() { return _min_width + _border*2; } + + static const double textsize; + static const double textpadding; + +private: + bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override; + + void draw_marks(Cairo::RefPtr<Cairo::Context> cr, double dist, int major_interval); + + double _unitconv; + int _min_width; + int _height; + int _border; + int _drawing_width; +}; + +class ZoomCorrRulerSlider : public Gtk::VBox +{ +public: + void init(int ruler_width, int ruler_height, double lower, double upper, + double step_increment, double page_increment, double default_value); + +private: + void on_slider_value_changed(); + void on_spinbutton_value_changed(); + void on_unit_changed(); + bool on_mnemonic_activate( bool group_cycling ) override; + + Inkscape::UI::Widget::SpinButton _sb; + UnitMenu _unit; + Gtk::Scale* _slider; + ZoomCorrRuler _ruler; + bool freeze; // used to block recursive updates of slider and spinbutton +}; + +class PrefSlider : public Gtk::HBox +{ +public: + void init(Glib::ustring const &prefs_path, + double lower, double upper, double step_increment, double page_increment, double default_value, int digits); + +private: + void on_slider_value_changed(); + void on_spinbutton_value_changed(); + bool on_mnemonic_activate( bool group_cycling ) override; + + Glib::ustring _prefs_path; + Inkscape::UI::Widget::SpinButton _sb; + + Gtk::Scale* _slider; + + bool freeze; // used to block recursive updates of slider and spinbutton +}; + + +class PrefCombo : public Gtk::ComboBoxText +{ +public: + void init(Glib::ustring const &prefs_path, + Glib::ustring labels[], int values[], int num_items, int default_value); + + /** + * Initialize a combo box. + * second form uses strings as key values. + */ + void init(Glib::ustring const &prefs_path, + Glib::ustring labels[], Glib::ustring values[], int num_items, Glib::ustring default_value); + /** + * Initialize a combo box. + * with vectors. + */ + void init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels, std::vector<int> values, + int default_value); + + void init(Glib::ustring const &prefs_path, std::vector<Glib::ustring> labels, std::vector<Glib::ustring> values, + Glib::ustring default_value); + + protected: + Glib::ustring _prefs_path; + std::vector<int> _values; + std::vector<Glib::ustring> _ustr_values; ///< string key values used optionally instead of numeric _values + void on_changed() override; +}; + +class PrefEntry : public Gtk::Entry +{ +public: + void init(Glib::ustring const &prefs_path, bool mask); +protected: + Glib::ustring _prefs_path; + void on_changed() override; +}; + +class PrefMultiEntry : public Gtk::ScrolledWindow +{ +public: + void init(Glib::ustring const &prefs_path, int height); +protected: + Glib::ustring _prefs_path; + Gtk::TextView _text; + void on_changed(); +}; + +class PrefEntryButtonHBox : public Gtk::HBox +{ +public: + void init(Glib::ustring const &prefs_path, + bool mask, Glib::ustring const &default_string); + +protected: + Glib::ustring _prefs_path; + Glib::ustring _default_string; + Gtk::Button *relatedButton; + Gtk::Entry *relatedEntry; + void onRelatedEntryChangedCallback(); + void onRelatedButtonClickedCallback(); + bool on_mnemonic_activate( bool group_cycling ) override; +}; + +class PrefEntryFileButtonHBox : public Gtk::HBox +{ +public: + void init(Glib::ustring const &prefs_path, + bool mask); +protected: + Glib::ustring _prefs_path; + Gtk::Button *relatedButton; + Gtk::Entry *relatedEntry; + void onRelatedEntryChangedCallback(); + void onRelatedButtonClickedCallback(); + bool on_mnemonic_activate( bool group_cycling ) override; +}; + +class PrefOpenFolder : public Gtk::HBox { + public: + void init(Glib::ustring const &entry_string, Glib::ustring const &tooltip); + + protected: + Gtk::Button *relatedButton; + Gtk::Entry *relatedEntry; + void onRelatedButtonClickedCallback(); +}; + +class PrefFileButton : public Gtk::FileChooserButton +{ +public: + void init(Glib::ustring const &prefs_path); + +protected: + Glib::ustring _prefs_path; + void onFileChanged(); +}; + +class PrefColorPicker : public ColorPicker +{ +public: + PrefColorPicker() : ColorPicker("", "", 0, false) {}; + ~PrefColorPicker() override = default;; + + void init(Glib::ustring const &abel, Glib::ustring const &prefs_path, + guint32 default_rgba); + +protected: + Glib::ustring _prefs_path; + void on_changed (guint32 rgba) override; +}; + +class PrefUnit : public UnitMenu +{ +public: + void init(Glib::ustring const &prefs_path); +protected: + Glib::ustring _prefs_path; + void on_changed() override; +}; + +class DialogPage : public Gtk::Grid +{ +public: + DialogPage(); + void add_line(bool indent, Glib::ustring const &label, Gtk::Widget& widget, Glib::ustring const &suffix, Glib::ustring const &tip, bool expand = true, Gtk::Widget *other_widget = nullptr); + void add_group_header(Glib::ustring name); + void set_tip(Gtk::Widget &widget, Glib::ustring const &tip); +}; + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif //INKSCAPE_UI_WIDGET_INKSCAPE_PREFERENCES_H + +/* + 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 : diff --git a/src/ui/widget/preview.cpp b/src/ui/widget/preview.cpp new file mode 100644 index 0000000..a56639c --- /dev/null +++ b/src/ui/widget/preview.cpp @@ -0,0 +1,502 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MPL-1.1 OR LGPL-2.1-or-later +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Eek Preview Stuffs. + * + * The Initial Developer of the Original Code is + * Jon A. Cruz. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include <algorithm> +#include <gdkmm/general.h> +#include "preview.h" +#include "preferences.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +#define PRIME_BUTTON_MAGIC_NUMBER 1 + +/* Keep in sync with last value in eek-preview.h */ +#define PREVIEW_SIZE_LAST PREVIEW_SIZE_HUGE +#define PREVIEW_SIZE_NEXTFREE (PREVIEW_SIZE_HUGE + 1) + +#define PREVIEW_MAX_RATIO 500 + +void +Preview::set_color(int r, int g, int b ) +{ + _r = r; + _g = g; + _b = b; + + queue_draw(); +} + + +void +Preview::set_pixbuf(const Glib::RefPtr<Gdk::Pixbuf> &pixbuf) +{ + _previewPixbuf = pixbuf; + + queue_draw(); + + if (_scaled) + { + _scaled.reset(); + } + + _scaledW = _previewPixbuf->get_width(); + _scaledH = _previewPixbuf->get_height(); +} + +static gboolean setupDone = FALSE; +static GtkRequisition sizeThings[PREVIEW_SIZE_NEXTFREE]; + +void +Preview::set_size_mappings( guint count, GtkIconSize const* sizes ) +{ + gint width = 0; + gint height = 0; + gint smallest = 512; + gint largest = 0; + guint i = 0; + guint delta = 0; + + for ( i = 0; i < count; ++i ) { + gboolean worked = gtk_icon_size_lookup( sizes[i], &width, &height ); + if ( worked ) { + if ( width < smallest ) { + smallest = width; + } + if ( width > largest ) { + largest = width; + } + } + } + + smallest = (smallest * 3) / 4; + + delta = largest - smallest; + + for ( i = 0; i < G_N_ELEMENTS(sizeThings); ++i ) { + guint val = smallest + ( (i * delta) / (G_N_ELEMENTS(sizeThings) - 1) ); + sizeThings[i].width = val; + sizeThings[i].height = val; + } + + setupDone = TRUE; +} + +void +Preview::size_request(GtkRequisition* req) const +{ + int width = 0; + int height = 0; + + if ( !setupDone ) { + GtkIconSize sizes[] = { + GTK_ICON_SIZE_MENU, + GTK_ICON_SIZE_SMALL_TOOLBAR, + GTK_ICON_SIZE_LARGE_TOOLBAR, + GTK_ICON_SIZE_BUTTON, + GTK_ICON_SIZE_DIALOG + }; + set_size_mappings( G_N_ELEMENTS(sizes), sizes ); + } + + width = sizeThings[_size].width; + height = sizeThings[_size].height; + + if ( _view == VIEW_TYPE_LIST ) { + width *= 3; + } + + if ( _ratio != 100 ) { + width = (width * _ratio) / 100; + if ( width < 0 ) { + width = 1; + } + } + + req->width = width; + req->height = height; +} + +void +Preview::get_preferred_width_vfunc(int &minimal_width, int &natural_width) const +{ + GtkRequisition requisition; + size_request(&requisition); + minimal_width = natural_width = requisition.width; +} + +void +Preview::get_preferred_height_vfunc(int &minimal_height, int &natural_height) const +{ + GtkRequisition requisition; + size_request(&requisition); + minimal_height = natural_height = requisition.height; +} + +bool +Preview::on_draw(const Cairo::RefPtr<Cairo::Context> &cr) +{ + auto allocation = get_allocation(); + + gint insetTop = 0, insetBottom = 0; + gint insetLeft = 0, insetRight = 0; + + if (_border == BORDER_SOLID) { + insetTop = 1; + insetLeft = 1; + } + if (_border == BORDER_SOLID_LAST_ROW) { + insetTop = insetBottom = 1; + insetLeft = 1; + } + if (_border == BORDER_WIDE) { + insetTop = insetBottom = 1; + insetLeft = insetRight = 1; + } + + auto context = get_style_context(); + + context->render_frame(cr, + 0, 0, + allocation.get_width(), allocation.get_height()); + + context->render_background(cr, + 0, 0, + allocation.get_width(), allocation.get_height()); + + // Border + if (_border != BORDER_NONE) { + cr->set_source_rgb(0.0, 0.0, 0.0); + cr->rectangle(0, 0, allocation.get_width(), allocation.get_height()); + cr->fill(); + } + + cr->set_source_rgb(_r/65535.0, _g/65535.0, _b/65535.0 ); + cr->rectangle(insetLeft, insetTop, allocation.get_width() - (insetLeft + insetRight), allocation.get_height() - (insetTop + insetBottom)); + cr->fill(); + + if (_previewPixbuf ) + { + if ((allocation.get_width() != _scaledW) || (allocation.get_height() != _scaledH)) { + if (_scaled) + { + _scaled.reset(); + } + + _scaledW = allocation.get_width() - (insetLeft + insetRight); + _scaledH = allocation.get_height() - (insetTop + insetBottom); + + _scaled = _previewPixbuf->scale_simple(_scaledW, + _scaledH, + Gdk::INTERP_BILINEAR); + } + + Glib::RefPtr<Gdk::Pixbuf> pix = (_scaled) ? _scaled : _previewPixbuf; + + // Border + if (_border != BORDER_NONE) { + cr->set_source_rgb(0.0, 0.0, 0.0); + cr->rectangle(0, 0, allocation.get_width(), allocation.get_height()); + cr->fill(); + } + + Gdk::Cairo::set_source_pixbuf(cr, pix, insetLeft, insetTop); + cr->paint(); + } + + if (_linked) + { + /* Draw arrow */ + GdkRectangle possible = {insetLeft, + insetTop, + (allocation.get_width() - (insetLeft + insetRight)), + (allocation.get_height() - (insetTop + insetBottom)) + }; + + GdkRectangle area = {possible.x, + possible.y, + possible.width / 2, + possible.height / 2 }; + + /* Make it square */ + if ( area.width > area.height ) + area.width = area.height; + if ( area.height > area.width ) + area.height = area.width; + + /* Center it horizontally */ + if ( area.width < possible.width ) { + int diff = (possible.width - area.width) / 2; + area.x += diff; + } + + if (_linked & PREVIEW_LINK_IN) + { + context->render_arrow(cr, + G_PI, // Down-pointing arrow + area.x, area.y, + std::min(area.width, area.height) + ); + } + + if (_linked & PREVIEW_LINK_OUT) + { + GdkRectangle otherArea = {area.x, area.y, area.width, area.height}; + if ( otherArea.height < possible.height ) { + otherArea.y = possible.y + (possible.height - otherArea.height); + } + + context->render_arrow(cr, + G_PI, // Down-pointing arrow + otherArea.x, otherArea.y, + std::min(otherArea.width, otherArea.height) + ); + } + + if (_linked & PREVIEW_LINK_OTHER) + { + GdkRectangle otherArea = {insetLeft, area.y, area.width, area.height}; + if ( otherArea.height < possible.height ) { + otherArea.y = possible.y + (possible.height - otherArea.height) / 2; + } + + context->render_arrow(cr, + 1.5*G_PI, // Left-pointing arrow + otherArea.x, otherArea.y, + std::min(otherArea.width, otherArea.height) + ); + } + + + if (_linked & PREVIEW_FILL) + { + GdkRectangle otherArea = {possible.x + ((possible.width / 4) - (area.width / 2)), + area.y, + area.width, area.height}; + if ( otherArea.height < possible.height ) { + otherArea.y = possible.y + (possible.height - otherArea.height) / 2; + } + context->render_check(cr, + otherArea.x, otherArea.y, + otherArea.width, otherArea.height ); + } + + if (_linked & PREVIEW_STROKE) + { + GdkRectangle otherArea = {possible.x + (((possible.width * 3) / 4) - (area.width / 2)), + area.y, + area.width, area.height}; + if ( otherArea.height < possible.height ) { + otherArea.y = possible.y + (possible.height - otherArea.height) / 2; + } + // This should be a diamond too? + context->render_check(cr, + otherArea.x, otherArea.y, + otherArea.width, otherArea.height ); + } + } + + + if ( has_focus() ) { + allocation = get_allocation(); + + context->render_focus(cr, + 0 + 1, 0 + 1, + allocation.get_width() - 2, allocation.get_height() - 2 ); + } + + return false; +} + + +bool +Preview::on_enter_notify_event(GdkEventCrossing* event ) +{ + _within = true; + set_state_flags(_hot ? Gtk::STATE_FLAG_ACTIVE : Gtk::STATE_FLAG_PRELIGHT, false); + + return false; +} + +bool +Preview::on_leave_notify_event(GdkEventCrossing* event) +{ + _within = false; + set_state_flags(Gtk::STATE_FLAG_NORMAL, false); + + return false; +} + +bool +Preview::on_button_press_event(GdkEventButton *event) +{ + if (_takesFocus && !has_focus() ) + { + grab_focus(); + } + + if ( event->button == PRIME_BUTTON_MAGIC_NUMBER || + event->button == 2 ) + { + _hot = true; + + if ( _within ) + { + set_state_flags(Gtk::STATE_FLAG_ACTIVE, false); + } + } + + return false; +} + +bool +Preview::on_button_release_event(GdkEventButton* event) +{ + _hot = false; + set_state_flags(Gtk::STATE_FLAG_NORMAL, false); + + if (_within && + (event->button == PRIME_BUTTON_MAGIC_NUMBER || + event->button == 2)) + { + gboolean isAlt = ( ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) || + (event->button == 2)); + + if ( isAlt ) + { + _signal_alt_clicked(2); + } + else + { + _signal_clicked.emit(); + } + } + + return false; +} + +void +Preview::set_linked(LinkType link) +{ + link = (LinkType)(link & PREVIEW_LINK_ALL); + + if (link != _linked) + { + _linked = link; + + queue_draw(); + } +} + +LinkType +Preview::get_linked() const +{ + return (LinkType)_linked; +} + +void +Preview::set_details(ViewType view, + PreviewSize size, + guint ratio, + guint border) +{ + _view = view; + + if ( size > PREVIEW_SIZE_LAST ) + { + size = PREVIEW_SIZE_LAST; + } + + _size = size; + + if ( ratio > PREVIEW_MAX_RATIO ) + { + ratio = PREVIEW_MAX_RATIO; + } + + _ratio = ratio; + _border = border; + + queue_draw(); +} + +Preview::Preview() + : _r(0x80), + _g(0x80), + _b(0xcc), + _scaledW(0), + _scaledH(0), + _hot(false), + _within(false), + _takesFocus(false), + _view(VIEW_TYPE_LIST), + _size(PREVIEW_SIZE_SMALL), + _ratio(100), + _border(BORDER_NONE), + _previewPixbuf(nullptr), + _scaled(nullptr), + _linked(PREVIEW_LINK_NONE) +{ + set_can_focus(true); + set_receives_default(true); + + set_sensitive(true); + + add_events(Gdk::BUTTON_PRESS_MASK + |Gdk::BUTTON_RELEASE_MASK + |Gdk::KEY_PRESS_MASK + |Gdk::KEY_RELEASE_MASK + |Gdk::FOCUS_CHANGE_MASK + |Gdk::ENTER_NOTIFY_MASK + |Gdk::LEAVE_NOTIFY_MASK ); +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/preview.h b/src/ui/widget/preview.h new file mode 100644 index 0000000..b455367 --- /dev/null +++ b/src/ui/widget/preview.h @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MPL-1.1 OR LGPL-2.1-or-later +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Eek Preview Stuffs. + * + * The Initial Developer of the Original Code is + * Jon A. Cruz. + * Portions created by the Initial Developer are Copyright (C) 2005-2008 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef SEEN_EEK_PREVIEW_H +#define SEEN_EEK_PREVIEW_H + +#include <gtkmm/drawingarea.h> + +/** + * @file + * Generic implementation of an object that can be shown by a preview. + */ + +namespace Inkscape { +namespace UI { +namespace Widget { + +enum PreviewStyle { + PREVIEW_STYLE_ICON = 0, + PREVIEW_STYLE_PREVIEW, + PREVIEW_STYLE_NAME, + PREVIEW_STYLE_BLURB, + PREVIEW_STYLE_ICON_NAME, + PREVIEW_STYLE_ICON_BLURB, + PREVIEW_STYLE_PREVIEW_NAME, + PREVIEW_STYLE_PREVIEW_BLURB +}; + +enum ViewType { + VIEW_TYPE_LIST = 0, + VIEW_TYPE_GRID +}; + +enum PreviewSize { + PREVIEW_SIZE_TINY = 0, + PREVIEW_SIZE_SMALL, + PREVIEW_SIZE_MEDIUM, + PREVIEW_SIZE_BIG, + PREVIEW_SIZE_BIGGER, + PREVIEW_SIZE_HUGE +}; + +enum LinkType { + PREVIEW_LINK_NONE = 0, + PREVIEW_LINK_IN = 1, + PREVIEW_LINK_OUT = 2, + PREVIEW_LINK_OTHER = 4, + PREVIEW_FILL = 8, + PREVIEW_STROKE = 16, + PREVIEW_LINK_ALL = 31 +}; + +enum BorderStyle { + BORDER_NONE = 0, + BORDER_SOLID, + BORDER_WIDE, + BORDER_SOLID_LAST_ROW, +}; + +class Preview : public Gtk::DrawingArea { +private: + int _scaledW; + int _scaledH; + + int _r; + int _g; + int _b; + + bool _hot; + bool _within; + bool _takesFocus; ///< flag to grab focus when clicked + ViewType _view; + PreviewSize _size; + unsigned int _ratio; + LinkType _linked; + unsigned int _border; + + Glib::RefPtr<Gdk::Pixbuf> _previewPixbuf; + Glib::RefPtr<Gdk::Pixbuf> _scaled; + + // signals + sigc::signal<void> _signal_clicked; + sigc::signal<void, int> _signal_alt_clicked; + + void size_request(GtkRequisition *req) const; + +protected: + void get_preferred_width_vfunc(int &minimal_width, int &natural_width) const override; + void get_preferred_height_vfunc(int &minimal_height, int &natural_height) const override; + bool on_draw(const Cairo::RefPtr<Cairo::Context> &cr) override; + bool on_button_press_event(GdkEventButton *button_event) override; + bool on_button_release_event(GdkEventButton *button_event) override; + bool on_enter_notify_event(GdkEventCrossing* event ) override; + bool on_leave_notify_event(GdkEventCrossing* event ) override; + +public: + Preview(); + bool get_focus_on_click() const {return _takesFocus;} + void set_focus_on_click(bool focus_on_click) {_takesFocus = focus_on_click;} + LinkType get_linked() const; + void set_linked(LinkType link); + void set_details(ViewType view, + PreviewSize size, + guint ratio, + guint border); + void set_color(int r, int g, int b); + void set_pixbuf(const Glib::RefPtr<Gdk::Pixbuf> &pixbuf); + static void set_size_mappings(guint count, GtkIconSize const* sizes); + + decltype(_signal_clicked) signal_clicked() {return _signal_clicked;} + decltype(_signal_alt_clicked) signal_alt_clicked() {return _signal_alt_clicked;} +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif /* SEEN_EEK_PREVIEW_H */ + +/* + 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 : diff --git a/src/ui/widget/random.cpp b/src/ui/widget/random.cpp new file mode 100644 index 0000000..495a778 --- /dev/null +++ b/src/ui/widget/random.cpp @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Carl Hetherington <inkscape@carlh.net> + * Derek P. Moore <derekm@hackunix.org> + * Bryce Harrington <bryce@bryceharrington.org> + * + * Copyright (C) 2004 Carl Hetherington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "random.h" +#include "ui/icon-loader.h" +#include <glibmm/i18n.h> + +#include <gtkmm/button.h> +#include <gtkmm/image.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +Random::Random(Glib::ustring const &label, Glib::ustring const &tooltip, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Scalar(label, tooltip, suffix, icon, mnemonic) +{ + startseed = 0; + addReseedButton(); +} + +Random::Random(Glib::ustring const &label, Glib::ustring const &tooltip, + unsigned digits, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Scalar(label, tooltip, digits, suffix, icon, mnemonic) +{ + startseed = 0; + addReseedButton(); +} + +Random::Random(Glib::ustring const &label, Glib::ustring const &tooltip, + Glib::RefPtr<Gtk::Adjustment> &adjust, + unsigned digits, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Scalar(label, tooltip, adjust, digits, suffix, icon, mnemonic) +{ + startseed = 0; + addReseedButton(); +} + +long Random::getStartSeed() const +{ + return startseed; +} + +void Random::setStartSeed(long newseed) +{ + startseed = newseed; +} + +void Random::addReseedButton() +{ + Gtk::Image *pIcon = Gtk::manage(sp_get_icon_image("randomize", Gtk::ICON_SIZE_BUTTON)); + Gtk::Button * pButton = Gtk::manage(new Gtk::Button()); + pButton->set_relief(Gtk::RELIEF_NONE); + pIcon->show(); + pButton->add(*pIcon); + pButton->show(); + pButton->signal_clicked().connect(sigc::mem_fun(*this, &Random::onReseedButtonClick)); + pButton->set_tooltip_text(_("Reseed the random number generator; this creates a different sequence of random numbers.")); + + pack_start(*pButton, Gtk::PACK_SHRINK, 0); +} + +void +Random::onReseedButtonClick() +{ + startseed = g_random_int(); + signal_reseeded.emit(); +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/random.h b/src/ui/widget/random.h new file mode 100644 index 0000000..2648cb2 --- /dev/null +++ b/src/ui/widget/random.h @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * + * Copyright (C) 2007 Author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_RANDOM_H +#define INKSCAPE_UI_WIDGET_RANDOM_H + +#include "scalar.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A labelled text box, with spin buttons and optional + * icon or suffix, for entering arbitrary number values. It adds an extra + * number called "startseed", that is not UI edittable, but should be put in SVG. + * This does NOT generate a random number, but provides merely the saving of + * the startseed value. + */ +class Random : public Scalar +{ +public: + + /** + * Construct a Random scalar Widget. + * + * @param label Label. + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to false). + */ + Random(Glib::ustring const &label, + Glib::ustring const &tooltip, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Construct a Random Scalar Widget. + * + * @param label Label. + * @param digits Number of decimal digits to display. + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to false). + */ + Random(Glib::ustring const &label, + Glib::ustring const &tooltip, + unsigned digits, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Construct a Random Scalar Widget. + * + * @param label Label. + * @param adjust Adjustment to use for the SpinButton. + * @param digits Number of decimal digits to display (defaults to 0). + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to true). + */ + Random(Glib::ustring const &label, + Glib::ustring const &tooltip, + Glib::RefPtr<Gtk::Adjustment> &adjust, + unsigned digits = 0, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Gets the startseed. + */ + long getStartSeed() const; + + /** + * Sets the startseed number. + */ + void setStartSeed(long newseed); + + sigc::signal <void> signal_reseeded; + +protected: + long startseed; + +private: + + /** + * Add reseed button to the widget. + */ + void addReseedButton(); + + void onReseedButtonClick(); +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_RANDOM_H + +/* + 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 : diff --git a/src/ui/widget/registered-enums.h b/src/ui/widget/registered-enums.h new file mode 100644 index 0000000..b0cc199 --- /dev/null +++ b/src/ui/widget/registered-enums.h @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_REGISTERED_ENUMS_H +#define INKSCAPE_UI_WIDGET_REGISTERED_ENUMS_H + +#include "ui/widget/combo-enums.h" +#include "ui/widget/registered-widget.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Simplified management of enumerations in the UI as combobox. + */ +template<typename E> class RegisteredEnum : public RegisteredWidget< LabelledComboBoxEnum<E> > +{ +public: + ~RegisteredEnum() override { + _changed_connection.disconnect(); + } + + RegisteredEnum ( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + const Util::EnumDataConverter<E>& c, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr, + bool sorted = true ) + : RegisteredWidget< LabelledComboBoxEnum<E> >(label, tip, c, (const Glib::ustring &)"", (const Glib::ustring &)"", true, sorted) + { + RegisteredWidget< LabelledComboBoxEnum<E> >::init_parent(key, wr, repr_in, doc_in); + _changed_connection = combobox()->signal_changed().connect (sigc::mem_fun (*this, &RegisteredEnum::on_changed)); + } + + void set_active_by_id (E id) { + combobox()->set_active_by_id(id); + }; + + void set_active_by_key (const Glib::ustring& key) { + combobox()->set_active_by_key(key); + } + + inline const Util::EnumData<E>* get_active_data() { + combobox()->get_active_data(); + } + + ComboBoxEnum<E> * combobox() { + return LabelledComboBoxEnum<E>::getCombobox(); + } + + sigc::connection _changed_connection; + +protected: + void on_changed() { + if (combobox()->setProgrammatically) { + combobox()->setProgrammatically = false; + return; + } + + if (RegisteredWidget< LabelledComboBoxEnum<E> >::_wr->isUpdating()) + return; + + RegisteredWidget< LabelledComboBoxEnum<E> >::_wr->setUpdating (true); + + const Util::EnumData<E>* data = combobox()->get_active_data(); + if (data) { + RegisteredWidget< LabelledComboBoxEnum<E> >::write_to_xml(data->key.c_str()); + } + + RegisteredWidget< LabelledComboBoxEnum<E> >::_wr->setUpdating (false); + } +}; + +} +} +} + +#endif + +/* + 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 : diff --git a/src/ui/widget/registered-widget.cpp b/src/ui/widget/registered-widget.cpp new file mode 100644 index 0000000..bd62b73 --- /dev/null +++ b/src/ui/widget/registered-widget.cpp @@ -0,0 +1,845 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Johan Engelen <j.b.c.engelen@utwente.nl> + * bulia byak <buliabyak@users.sf.net> + * Bryce W. Harrington <bryce@bryceharrington.org> + * Lauris Kaplinski <lauris@kaplinski.com> + * Jon Phillips <jon@rejon.org> + * Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm) + * Abhishek Sharma + * + * Copyright (C) 2000 - 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "registered-widget.h" + +#include <gtkmm/radiobutton.h> + +#include "verbs.h" + +#include "object/sp-root.h" + +#include "svg/svg-color.h" +#include "svg/stringstream.h" + +#include "widgets/spinbutton-events.h" + + +namespace Inkscape { +namespace UI { +namespace Widget { + +/*######################################### + * Registered CHECKBUTTON + */ + +RegisteredCheckButton::~RegisteredCheckButton() +{ + _toggled_connection.disconnect(); +} + +RegisteredCheckButton::RegisteredCheckButton (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, Registry& wr, bool right, Inkscape::XML::Node* repr_in, SPDocument *doc_in, char const *active_str, char const *inactive_str) + : RegisteredWidget<Gtk::CheckButton>() + , _active_str(active_str) + , _inactive_str(inactive_str) +{ + init_parent(key, wr, repr_in, doc_in); + + setProgrammatically = false; + + set_tooltip_text (tip); + Gtk::Label *l = new Gtk::Label(); + l->set_markup(label); + l->set_use_underline (true); + add (*manage (l)); + + if(right) set_halign(Gtk::ALIGN_END); + else set_halign(Gtk::ALIGN_START); + + set_valign(Gtk::ALIGN_CENTER); + _toggled_connection = signal_toggled().connect (sigc::mem_fun (*this, &RegisteredCheckButton::on_toggled)); +} + +void +RegisteredCheckButton::setActive (bool b) +{ + setProgrammatically = true; + set_active (b); + //The slave button is greyed out if the master button is unchecked + for (std::list<Gtk::Widget*>::const_iterator i = _slavewidgets.begin(); i != _slavewidgets.end(); ++i) { + (*i)->set_sensitive(b); + } + setProgrammatically = false; +} + +void +RegisteredCheckButton::on_toggled() +{ + if (setProgrammatically) { + setProgrammatically = false; + return; + } + + if (_wr->isUpdating()) + return; + _wr->setUpdating (true); + + write_to_xml(get_active() ? _active_str : _inactive_str); + //The slave button is greyed out if the master button is unchecked + for (std::list<Gtk::Widget*>::const_iterator i = _slavewidgets.begin(); i != _slavewidgets.end(); ++i) { + (*i)->set_sensitive(get_active()); + } + + _wr->setUpdating (false); +} + +/*######################################### + * Registered TOGGLEBUTTON + */ + +RegisteredToggleButton::~RegisteredToggleButton() +{ + _toggled_connection.disconnect(); +} + +RegisteredToggleButton::RegisteredToggleButton (const Glib::ustring& /*label*/, const Glib::ustring& tip, const Glib::ustring& key, Registry& wr, bool right, Inkscape::XML::Node* repr_in, SPDocument *doc_in, char const *icon_active, char const *icon_inactive) + : RegisteredWidget<Gtk::ToggleButton>() +{ + init_parent(key, wr, repr_in, doc_in); + setProgrammatically = false; + set_tooltip_text (tip); + + if(right) set_halign(Gtk::ALIGN_END); + else set_halign(Gtk::ALIGN_START); + + set_valign(Gtk::ALIGN_CENTER); + _toggled_connection = signal_toggled().connect (sigc::mem_fun (*this, &RegisteredToggleButton::on_toggled)); +} + +void +RegisteredToggleButton::setActive (bool b) +{ + setProgrammatically = true; + set_active (b); + //The slave button is greyed out if the master button is untoggled + for (std::list<Gtk::Widget*>::const_iterator i = _slavewidgets.begin(); i != _slavewidgets.end(); ++i) { + (*i)->set_sensitive(b); + } + setProgrammatically = false; +} + +void +RegisteredToggleButton::on_toggled() +{ + if (setProgrammatically) { + setProgrammatically = false; + return; + } + + if (_wr->isUpdating()) + return; + _wr->setUpdating (true); + + write_to_xml(get_active() ? "true" : "false"); + //The slave button is greyed out if the master button is untoggled + for (std::list<Gtk::Widget*>::const_iterator i = _slavewidgets.begin(); i != _slavewidgets.end(); ++i) { + (*i)->set_sensitive(get_active()); + } + + _wr->setUpdating (false); +} + +/*######################################### + * Registered UNITMENU + */ + +RegisteredUnitMenu::~RegisteredUnitMenu() +{ + _changed_connection.disconnect(); +} + +RegisteredUnitMenu::RegisteredUnitMenu (const Glib::ustring& label, const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in) + : RegisteredWidget<Labelled> (label, "" /*tooltip*/, new UnitMenu()) +{ + init_parent(key, wr, repr_in, doc_in); + + getUnitMenu()->setUnitType (UNIT_TYPE_LINEAR); + _changed_connection = getUnitMenu()->signal_changed().connect (sigc::mem_fun (*this, &RegisteredUnitMenu::on_changed)); +} + +void +RegisteredUnitMenu::setUnit (Glib::ustring unit) +{ + getUnitMenu()->setUnit(unit); +} + +void +RegisteredUnitMenu::on_changed() +{ + if (_wr->isUpdating()) + return; + + Inkscape::SVGOStringStream os; + os << getUnitMenu()->getUnitAbbr(); + + _wr->setUpdating (true); + + write_to_xml(os.str().c_str()); + + _wr->setUpdating (false); +} + + +/*######################################### + * Registered SCALARUNIT + */ + +RegisteredScalarUnit::~RegisteredScalarUnit() +{ + _value_changed_connection.disconnect(); +} + +RegisteredScalarUnit::RegisteredScalarUnit (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, const RegisteredUnitMenu &rum, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in, RSU_UserUnits user_units) + : RegisteredWidget<ScalarUnit>(label, tip, UNIT_TYPE_LINEAR, "", "", rum.getUnitMenu()), + _um(nullptr) +{ + init_parent(key, wr, repr_in, doc_in); + + setProgrammatically = false; + + initScalar (-1e6, 1e6); + setUnit (rum.getUnitMenu()->getUnitAbbr()); + setDigits (2); + _um = rum.getUnitMenu(); + _user_units = user_units; + _value_changed_connection = signal_value_changed().connect (sigc::mem_fun (*this, &RegisteredScalarUnit::on_value_changed)); +} + + +void +RegisteredScalarUnit::on_value_changed() +{ + if (setProgrammatically) { + setProgrammatically = false; + return; + } + + if (_wr->isUpdating()) + return; + + _wr->setUpdating (true); + + Inkscape::SVGOStringStream os; + if (_user_units != RSU_none) { + // Output length in 'user units', taking into account scale in 'x' or 'y'. + double scale = 1.0; + if (doc) { + SPRoot *root = doc->getRoot(); + if (root->viewBox_set) { + // check to see if scaling is uniform + if(Geom::are_near((root->viewBox.width() * root->height.computed) / (root->width.computed * root->viewBox.height()), 1.0, Geom::EPSILON)) { + scale = (root->viewBox.width() / root->width.computed + root->viewBox.height() / root->height.computed)/2.0; + } else if (_user_units == RSU_x) { + scale = root->viewBox.width() / root->width.computed; + } else { + scale = root->viewBox.height() / root->height.computed; + } + } + } + os << getValue("px") * scale; + } else { + // Output using unit identifiers. + os << getValue(""); + if (_um) + os << _um->getUnitAbbr(); + } + + write_to_xml(os.str().c_str()); + _wr->setUpdating (false); +} + + +/*######################################### + * Registered SCALAR + */ + +RegisteredScalar::~RegisteredScalar() +{ + _value_changed_connection.disconnect(); +} + +RegisteredScalar::RegisteredScalar ( const Glib::ustring& label, const Glib::ustring& tip, + const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, + SPDocument * doc_in ) + : RegisteredWidget<Scalar>(label, tip) +{ + init_parent(key, wr, repr_in, doc_in); + + setProgrammatically = false; + setRange (-1e6, 1e6); + setDigits (2); + setIncrements(0.1, 1.0); + _value_changed_connection = signal_value_changed().connect (sigc::mem_fun (*this, &RegisteredScalar::on_value_changed)); +} + +void +RegisteredScalar::on_value_changed() +{ + if (setProgrammatically) { + setProgrammatically = false; + return; + } + if (_wr->isUpdating()) { + return; + } + _wr->setUpdating (true); + + Inkscape::SVGOStringStream os; + //Force exact 0 if decimals over to 6 + double val = getValue() < 1e-6 && getValue() > -1e-6?0.0:getValue(); + os << val; + //TODO: Test is ok remove this sensitives + //also removed in registered text and in registered random + //set_sensitive(false); + write_to_xml(os.str().c_str()); + //set_sensitive(true); + _wr->setUpdating (false); +} + + +/*######################################### + * Registered TEXT + */ + +RegisteredText::~RegisteredText() +{ + _activate_connection.disconnect(); +} + +RegisteredText::RegisteredText ( const Glib::ustring& label, const Glib::ustring& tip, + const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, + SPDocument * doc_in ) + : RegisteredWidget<Text>(label, tip) +{ + init_parent(key, wr, repr_in, doc_in); + + setProgrammatically = false; + _activate_connection = signal_activate().connect (sigc::mem_fun (*this, &RegisteredText::on_activate)); +} + +void +RegisteredText::on_activate() +{ + if (setProgrammatically) { + setProgrammatically = false; + return; + } + + if (_wr->isUpdating()) { + return; + } + _wr->setUpdating (true); + Glib::ustring str(getText()); + Inkscape::SVGOStringStream os; + os << str; + write_to_xml(os.str().c_str()); + _wr->setUpdating (false); +} + + +/*######################################### + * Registered COLORPICKER + */ + +RegisteredColorPicker::RegisteredColorPicker(const Glib::ustring& label, + const Glib::ustring& title, + const Glib::ustring& tip, + const Glib::ustring& ckey, + const Glib::ustring& akey, + Registry& wr, + Inkscape::XML::Node* repr_in, + SPDocument *doc_in) + : RegisteredWidget<LabelledColorPicker> (label, title, tip, 0, true) +{ + init_parent("", wr, repr_in, doc_in); + + _ckey = ckey; + _akey = akey; + _changed_connection = connectChanged (sigc::mem_fun (*this, &RegisteredColorPicker::on_changed)); +} + +RegisteredColorPicker::~RegisteredColorPicker() +{ + _changed_connection.disconnect(); +} + +void +RegisteredColorPicker::setRgba32 (guint32 rgba) +{ + LabelledColorPicker::setRgba32 (rgba); +} + +void +RegisteredColorPicker::closeWindow() +{ + LabelledColorPicker::closeWindow(); +} + +void +RegisteredColorPicker::on_changed (guint32 rgba) +{ + if (_wr->isUpdating()) + return; + + _wr->setUpdating (true); + + // Use local repr here. When repr is specified, use that one, but + // if repr==NULL, get the repr of namedview of active desktop. + Inkscape::XML::Node *local_repr = repr; + SPDocument *local_doc = doc; + if (!local_repr) { + // no repr specified, use active desktop's namedview's repr + SPDesktop *dt = SP_ACTIVE_DESKTOP; + if (!dt) + return; + local_repr = dt->getNamedView()->getRepr(); + local_doc = dt->getDocument(); + } + gchar c[32]; + if (_akey == _ckey + "_opacity_LPE") { //For LPE parameter we want stored with alpha + sprintf(c, "#%08x", rgba); + } else { + sp_svg_write_color(c, sizeof(c), rgba); + } + bool saved = DocumentUndo::getUndoSensitive(local_doc); + DocumentUndo::setUndoSensitive(local_doc, false); + local_repr->setAttribute(_ckey, c); + sp_repr_set_css_double(local_repr, _akey.c_str(), (rgba & 0xff) / 255.0); + DocumentUndo::setUndoSensitive(local_doc, saved); + + local_doc->setModifiedSinceSave(); + DocumentUndo::done(local_doc, SP_VERB_NONE, + /* TODO: annotate */ "registered-widget.cpp: RegisteredColorPicker::on_changed"); + + _wr->setUpdating (false); +} + + +/*######################################### + * Registered SUFFIXEDINTEGER + */ + +RegisteredSuffixedInteger::~RegisteredSuffixedInteger() +{ + _changed_connection.disconnect(); +} + +RegisteredSuffixedInteger::RegisteredSuffixedInteger (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& suffix, const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in) + : RegisteredWidget<Scalar>(label, tip, 0, suffix), + setProgrammatically(false) +{ + init_parent(key, wr, repr_in, doc_in); + + setRange (0, 1e6); + setDigits (0); + setIncrements(1, 10); + + _changed_connection = signal_value_changed().connect (sigc::mem_fun(*this, &RegisteredSuffixedInteger::on_value_changed)); +} + +void +RegisteredSuffixedInteger::on_value_changed() +{ + if (setProgrammatically) { + setProgrammatically = false; + return; + } + + if (_wr->isUpdating()) + return; + + _wr->setUpdating (true); + + Inkscape::SVGOStringStream os; + os << getValue(); + + write_to_xml(os.str().c_str()); + + _wr->setUpdating (false); +} + + +/*######################################### + * Registered RADIOBUTTONPAIR + */ + +RegisteredRadioButtonPair::~RegisteredRadioButtonPair() +{ + _changed_connection.disconnect(); +} + +RegisteredRadioButtonPair::RegisteredRadioButtonPair (const Glib::ustring& label, + const Glib::ustring& label1, const Glib::ustring& label2, + const Glib::ustring& tip1, const Glib::ustring& tip2, + const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in) + : RegisteredWidget<Gtk::HBox>(), + _rb1(nullptr), + _rb2(nullptr) +{ + init_parent(key, wr, repr_in, doc_in); + + setProgrammatically = false; + + add(*Gtk::manage(new Gtk::Label(label))); + _rb1 = Gtk::manage(new Gtk::RadioButton(label1, true)); + add (*_rb1); + Gtk::RadioButtonGroup group = _rb1->get_group(); + _rb2 = Gtk::manage(new Gtk::RadioButton(group, label2, true)); + add (*_rb2); + _rb2->set_active(); + _rb1->set_tooltip_text(tip1); + _rb2->set_tooltip_text(tip2); + _changed_connection = _rb1->signal_toggled().connect (sigc::mem_fun (*this, &RegisteredRadioButtonPair::on_value_changed)); +} + +void +RegisteredRadioButtonPair::setValue (bool second) +{ + if (!_rb1 || !_rb2) + return; + + setProgrammatically = true; + if (second) { + _rb2->set_active(); + } else { + _rb1->set_active(); + } +} + +void +RegisteredRadioButtonPair::on_value_changed() +{ + if (setProgrammatically) { + setProgrammatically = false; + return; + } + + if (_wr->isUpdating()) + return; + + _wr->setUpdating (true); + + bool second = _rb2->get_active(); + write_to_xml(second ? "true" : "false"); + + _wr->setUpdating (false); +} + + +/*######################################### + * Registered POINT + */ + +RegisteredPoint::~RegisteredPoint() +{ + _value_x_changed_connection.disconnect(); + _value_y_changed_connection.disconnect(); +} + +RegisteredPoint::RegisteredPoint ( const Glib::ustring& label, const Glib::ustring& tip, + const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, + SPDocument* doc_in ) + : RegisteredWidget<Point> (label, tip) +{ + init_parent(key, wr, repr_in, doc_in); + + setRange (-1e6, 1e6); + setDigits (2); + setIncrements(0.1, 1.0); + _value_x_changed_connection = signal_x_value_changed().connect (sigc::mem_fun (*this, &RegisteredPoint::on_value_changed)); + _value_y_changed_connection = signal_y_value_changed().connect (sigc::mem_fun (*this, &RegisteredPoint::on_value_changed)); +} + +void +RegisteredPoint::on_value_changed() +{ + if (setProgrammatically()) { + clearProgrammatically(); + return; + } + + if (_wr->isUpdating()) + return; + + _wr->setUpdating (true); + + Inkscape::SVGOStringStream os; + os << getXValue() << "," << getYValue(); + + write_to_xml(os.str().c_str()); + + _wr->setUpdating (false); +} + +/*######################################### + * Registered TRANSFORMEDPOINT + */ + +RegisteredTransformedPoint::~RegisteredTransformedPoint() +{ + _value_x_changed_connection.disconnect(); + _value_y_changed_connection.disconnect(); +} + +RegisteredTransformedPoint::RegisteredTransformedPoint ( const Glib::ustring& label, const Glib::ustring& tip, + const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, + SPDocument* doc_in ) + : RegisteredWidget<Point> (label, tip), + to_svg(Geom::identity()) +{ + init_parent(key, wr, repr_in, doc_in); + + setRange (-1e6, 1e6); + setDigits (2); + setIncrements(0.1, 1.0); + _value_x_changed_connection = signal_x_value_changed().connect (sigc::mem_fun (*this, &RegisteredTransformedPoint::on_value_changed)); + _value_y_changed_connection = signal_y_value_changed().connect (sigc::mem_fun (*this, &RegisteredTransformedPoint::on_value_changed)); +} + +void +RegisteredTransformedPoint::setValue(Geom::Point const & p) +{ + Geom::Point new_p = p * to_svg.inverse(); + Point::setValue(new_p); // the Point widget should display things in canvas coordinates +} + +void +RegisteredTransformedPoint::setTransform(Geom::Affine const & canvas_to_svg) +{ + // check if matrix is singular / has inverse + if ( ! canvas_to_svg.isSingular() ) { + to_svg = canvas_to_svg; + } else { + // set back to default + to_svg = Geom::identity(); + } +} + +void +RegisteredTransformedPoint::on_value_changed() +{ + if (setProgrammatically()) { + clearProgrammatically(); + return; + } + + if (_wr->isUpdating()) + return; + + _wr->setUpdating (true); + + Geom::Point pos = getValue() * to_svg; + + Inkscape::SVGOStringStream os; + os << pos; + + write_to_xml(os.str().c_str()); + + _wr->setUpdating (false); +} + +/*######################################### + * Registered TRANSFORMEDPOINT + */ + +RegisteredVector::~RegisteredVector() +{ + _value_x_changed_connection.disconnect(); + _value_y_changed_connection.disconnect(); +} + +RegisteredVector::RegisteredVector ( const Glib::ustring& label, const Glib::ustring& tip, + const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, + SPDocument* doc_in ) + : RegisteredWidget<Point> (label, tip), + _polar_coords(false) +{ + init_parent(key, wr, repr_in, doc_in); + + setRange (-1e6, 1e6); + setDigits (2); + setIncrements(0.1, 1.0); + _value_x_changed_connection = signal_x_value_changed().connect (sigc::mem_fun (*this, &RegisteredVector::on_value_changed)); + _value_y_changed_connection = signal_y_value_changed().connect (sigc::mem_fun (*this, &RegisteredVector::on_value_changed)); +} + +void +RegisteredVector::setValue(Geom::Point const & p) +{ + if (!_polar_coords) { + Point::setValue(p); + } else { + Geom::Point polar; + polar[Geom::X] = atan2(p) *180/M_PI; + polar[Geom::Y] = p.length(); + Point::setValue(polar); + } +} + +void +RegisteredVector::setValue(Geom::Point const & p, Geom::Point const & origin) +{ + RegisteredVector::setValue(p); + _origin = origin; +} + +void RegisteredVector::setPolarCoords(bool polar_coords) +{ + _polar_coords = polar_coords; + if (polar_coords) { + xwidget.setLabelText("Angle:"); + ywidget.setLabelText("Distance:"); + } else { + xwidget.setLabelText("X:"); + ywidget.setLabelText("Y:"); + } +} + +void +RegisteredVector::on_value_changed() +{ + if (setProgrammatically()) { + clearProgrammatically(); + return; + } + + if (_wr->isUpdating()) + return; + + _wr->setUpdating (true); + + Geom::Point origin = _origin; + Geom::Point vector = getValue(); + if (_polar_coords) { + vector = Geom::Point::polar(vector[Geom::X]*M_PI/180, vector[Geom::Y]); + } + + Inkscape::SVGOStringStream os; + os << origin << " , " << vector; + + write_to_xml(os.str().c_str()); + + _wr->setUpdating (false); +} + +/*######################################### + * Registered RANDOM + */ + +RegisteredRandom::~RegisteredRandom() +{ + _value_changed_connection.disconnect(); + _reseeded_connection.disconnect(); +} + +RegisteredRandom::RegisteredRandom ( const Glib::ustring& label, const Glib::ustring& tip, + const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, + SPDocument * doc_in ) + : RegisteredWidget<Random> (label, tip) +{ + init_parent(key, wr, repr_in, doc_in); + + setProgrammatically = false; + setRange (-1e6, 1e6); + setDigits (2); + setIncrements(0.1, 1.0); + _value_changed_connection = signal_value_changed().connect (sigc::mem_fun (*this, &RegisteredRandom::on_value_changed)); + _reseeded_connection = signal_reseeded.connect(sigc::mem_fun(*this, &RegisteredRandom::on_value_changed)); +} + +void +RegisteredRandom::setValue (double val, long startseed) +{ + Scalar::setValue (val); + setStartSeed(startseed); +} + +void +RegisteredRandom::on_value_changed() +{ + if (setProgrammatically) { + setProgrammatically = false; + return; + } + + if (_wr->isUpdating()) { + return; + } + _wr->setUpdating (true); + + Inkscape::SVGOStringStream os; + //Force exact 0 if decimals over to 6 + double val = getValue() < 1e-6 && getValue() > -1e-6?0.0:getValue(); + os << val << ';' << getStartSeed(); + write_to_xml(os.str().c_str()); + _wr->setUpdating (false); +} + +/*######################################### + * Registered FONT-BUTTON + */ + +RegisteredFontButton::~RegisteredFontButton() +{ + _signal_font_set.disconnect(); +} + +RegisteredFontButton::RegisteredFontButton ( const Glib::ustring& label, const Glib::ustring& tip, + const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, + SPDocument* doc_in ) + : RegisteredWidget<FontButton>(label, tip) +{ + init_parent(key, wr, repr_in, doc_in); + _signal_font_set = signal_font_value_changed().connect (sigc::mem_fun (*this, &RegisteredFontButton::on_value_changed)); +} + +void +RegisteredFontButton::setValue (Glib::ustring fontspec) +{ + FontButton::setValue(fontspec); +} + +void +RegisteredFontButton::on_value_changed() +{ + + if (_wr->isUpdating()) + return; + + _wr->setUpdating (true); + + Inkscape::SVGOStringStream os; + os << getValue(); + + write_to_xml(os.str().c_str()); + + _wr->setUpdating (false); +} + +} // 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 : diff --git a/src/ui/widget/registered-widget.h b/src/ui/widget/registered-widget.h new file mode 100644 index 0000000..d0b728a --- /dev/null +++ b/src/ui/widget/registered-widget.h @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ralf Stephan <ralf@ark.in-berlin.de> + * Johan Engelen <j.b.c.engelen@utwente.nl> + * Abhishek Sharma + * + * Copyright (C) 2005-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_REGISTERED_WIDGET__H_ +#define INKSCAPE_UI_WIDGET_REGISTERED_WIDGET__H_ + +#include <2geom/affine.h> +#include "xml/node.h" +#include "registry.h" + +#include "ui/widget/scalar.h" +#include "ui/widget/scalar-unit.h" +#include "ui/widget/point.h" +#include "ui/widget/text.h" +#include "ui/widget/random.h" +#include "ui/widget/unit-menu.h" +#include "ui/widget/font-button.h" +#include "ui/widget/color-picker.h" +#include "inkscape.h" + +#include "document.h" +#include "document-undo.h" +#include "desktop.h" +#include "object/sp-namedview.h" + +#include <gtkmm/checkbutton.h> + +class SPDocument; + +namespace Gtk { + class HScale; + class RadioButton; + class SpinButton; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +class Registry; + +template <class W> +class RegisteredWidget : public W { +public: + void set_undo_parameters(const unsigned int _event_type, Glib::ustring _event_description) + { + event_type = _event_type; + event_description = _event_description; + write_undo = true; + } + void set_xml_target(Inkscape::XML::Node *xml_node, SPDocument *document) + { + repr = xml_node; + doc = document; + } + + bool is_updating() {if (_wr) return _wr->isUpdating(); else return false;} + +protected: + RegisteredWidget() : W() { construct(); } + template< typename A > + explicit RegisteredWidget( A& a ): W( a ) { construct(); } + template< typename A, typename B > + RegisteredWidget( A& a, B& b ): W( a, b ) { construct(); } + template< typename A, typename B, typename C > + RegisteredWidget( A& a, B& b, C* c ): W( a, b, c ) { construct(); } + template< typename A, typename B, typename C > + RegisteredWidget( A& a, B& b, C& c ): W( a, b, c ) { construct(); } + template< typename A, typename B, typename C, typename D > + RegisteredWidget( A& a, B& b, C c, D d ): W( a, b, c, d ) { construct(); } + template< typename A, typename B, typename C, typename D, typename E > + RegisteredWidget( A& a, B& b, C& c, D d, E e ): W( a, b, c, d, e ) { construct(); } + template< typename A, typename B, typename C, typename D, typename E , typename F> + RegisteredWidget( A& a, B& b, C c, D& d, E& e, F* f): W( a, b, c, d, e, f) { construct(); } + template< typename A, typename B, typename C, typename D, typename E , typename F, typename G> + RegisteredWidget( A& a, B& b, C& c, D& d, E& e, F f, G& g): W( a, b, c, d, e, f, g) { construct(); } + + ~RegisteredWidget() override = default;; + + void init_parent(const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in) + { + _wr = ≀ + _key = key; + repr = repr_in; + doc = doc_in; + if (repr && !doc) // doc cannot be NULL when repr is not NULL + g_warning("Initialization of registered widget using defined repr but with doc==NULL"); + } + + void write_to_xml(const char * svgstr) + { + // Use local repr here. When repr is specified, use that one, but + // if repr==NULL, get the repr of namedview of active desktop. + Inkscape::XML::Node *local_repr = repr; + SPDocument *local_doc = doc; + if (!local_repr) { + // no repr specified, use active desktop's namedview's repr + SPDesktop* dt = SP_ACTIVE_DESKTOP; + local_repr = reinterpret_cast<SPObject *>(dt->getNamedView())->getRepr(); + local_doc = dt->getDocument(); + } + + bool saved = DocumentUndo::getUndoSensitive(local_doc); + DocumentUndo::setUndoSensitive(local_doc, false); + const char * svgstr_old = local_repr->attribute(_key.c_str()); + if (!write_undo) { + local_repr->setAttribute(_key, svgstr); + } + DocumentUndo::setUndoSensitive(local_doc, saved); + if (svgstr_old && svgstr && strcmp(svgstr_old,svgstr)) { + local_doc->setModifiedSinceSave(); + } + + if (write_undo) { + local_repr->setAttribute(_key, svgstr); + DocumentUndo::done(local_doc, event_type, event_description); + } + } + + Registry * _wr; + Glib::ustring _key; + Inkscape::XML::Node * repr; + SPDocument * doc; + unsigned int event_type; + Glib::ustring event_description; + bool write_undo; + +private: + void construct() { + _wr = nullptr; + repr = nullptr; + doc = nullptr; + write_undo = false; + event_type = 0; //SP_VERB_INVALID + } +}; + +//####################################################### + +class RegisteredCheckButton : public RegisteredWidget<Gtk::CheckButton> { +public: + ~RegisteredCheckButton() override; + RegisteredCheckButton (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, Registry& wr, bool right=false, Inkscape::XML::Node* repr_in=nullptr, SPDocument *doc_in=nullptr, char const *active_str = "true", char const *inactive_str = "false"); + + void setActive (bool); + + std::list<Gtk::Widget*> _slavewidgets; + + // a slave button is only sensitive when the master button is active + // i.e. a slave button is greyed-out when the master button is not checked + + void setSlaveWidgets(std::list<Gtk::Widget*> btns) { + _slavewidgets = btns; + } + + bool setProgrammatically; // true if the value was set by setActive, not changed by the user; + // if a callback checks it, it must reset it back to false + +protected: + char const *_active_str, *_inactive_str; + sigc::connection _toggled_connection; + void on_toggled() override; +}; + +class RegisteredToggleButton : public RegisteredWidget<Gtk::ToggleButton> { +public: + ~RegisteredToggleButton() override; + RegisteredToggleButton (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, Registry& wr, bool right=true, Inkscape::XML::Node* repr_in=nullptr, SPDocument *doc_in=nullptr, char const *icon_active = "true", char const *icon_inactive = "false"); + + void setActive (bool); + + std::list<Gtk::Widget*> _slavewidgets; + + // a slave button is only sensitive when the master button is active + // i.e. a slave button is greyed-out when the master button is not checked + + void setSlaveWidgets(std::list<Gtk::Widget*> btns) { + _slavewidgets = btns; + } + + bool setProgrammatically; // true if the value was set by setActive, not changed by the user; + // if a callback checks it, it must reset it back to false + +protected: + sigc::connection _toggled_connection; + void on_toggled() override; +}; + +class RegisteredUnitMenu : public RegisteredWidget<Labelled> { +public: + ~RegisteredUnitMenu() override; + RegisteredUnitMenu ( const Glib::ustring& label, + const Glib::ustring& key, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr ); + + void setUnit (const Glib::ustring); + Unit const * getUnit() const { return static_cast<UnitMenu*>(_widget)->getUnit(); }; + UnitMenu* getUnitMenu() const { return static_cast<UnitMenu*>(_widget); }; + sigc::connection _changed_connection; + +protected: + void on_changed(); +}; + +// Allow RegisteredScalarUnit to output lengths in 'user units' (which may have direction dependent +// scale factors). +enum RSU_UserUnits { + RSU_none, + RSU_x, + RSU_y +}; + +class RegisteredScalarUnit : public RegisteredWidget<ScalarUnit> { +public: + ~RegisteredScalarUnit() override; + RegisteredScalarUnit ( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + const RegisteredUnitMenu &rum, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr, + RSU_UserUnits _user_units = RSU_none ); + +protected: + sigc::connection _value_changed_connection; + UnitMenu *_um; + void on_value_changed(); + RSU_UserUnits _user_units; +}; + +class RegisteredScalar : public RegisteredWidget<Scalar> { +public: + ~RegisteredScalar() override; + RegisteredScalar (const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr ); +protected: + sigc::connection _value_changed_connection; + void on_value_changed(); +}; + +class RegisteredText : public RegisteredWidget<Text> { +public: + ~RegisteredText() override; + RegisteredText (const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr ); + +protected: + sigc::connection _activate_connection; + void on_activate(); +}; + +class RegisteredColorPicker : public RegisteredWidget<LabelledColorPicker> { +public: + ~RegisteredColorPicker() override; + + RegisteredColorPicker (const Glib::ustring& label, + const Glib::ustring& title, + const Glib::ustring& tip, + const Glib::ustring& ckey, + const Glib::ustring& akey, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr); + + void setRgba32 (guint32); + void closeWindow(); + +protected: + Glib::ustring _ckey, _akey; + void on_changed (guint32); + sigc::connection _changed_connection; +}; + +class RegisteredSuffixedInteger : public RegisteredWidget<Scalar> { +public: + ~RegisteredSuffixedInteger() override; + RegisteredSuffixedInteger ( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& suffix, + const Glib::ustring& key, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr ); + + bool setProgrammatically; // true if the value was set by setValue, not changed by the user; + // if a callback checks it, it must reset it back to false + +protected: + sigc::connection _changed_connection; + void on_value_changed(); +}; + +class RegisteredRadioButtonPair : public RegisteredWidget<Gtk::HBox> { +public: + ~RegisteredRadioButtonPair() override; + RegisteredRadioButtonPair ( const Glib::ustring& label, + const Glib::ustring& label1, + const Glib::ustring& label2, + const Glib::ustring& tip1, + const Glib::ustring& tip2, + const Glib::ustring& key, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr ); + + void setValue (bool second); + + bool setProgrammatically; // true if the value was set by setValue, not changed by the user; + // if a callback checks it, it must reset it back to false +protected: + Gtk::RadioButton *_rb1, *_rb2; + sigc::connection _changed_connection; + void on_value_changed(); +}; + +class RegisteredPoint : public RegisteredWidget<Point> { +public: + ~RegisteredPoint() override; + RegisteredPoint ( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr ); + +protected: + sigc::connection _value_x_changed_connection; + sigc::connection _value_y_changed_connection; + void on_value_changed(); +}; + + +class RegisteredTransformedPoint : public RegisteredWidget<Point> { +public: + ~RegisteredTransformedPoint() override; + RegisteredTransformedPoint ( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr ); + + // redefine setValue, because transform must be applied + void setValue(Geom::Point const & p); + + void setTransform(Geom::Affine const & canvas_to_svg); + +protected: + sigc::connection _value_x_changed_connection; + sigc::connection _value_y_changed_connection; + void on_value_changed(); + + Geom::Affine to_svg; +}; + + +class RegisteredVector : public RegisteredWidget<Point> { +public: + ~RegisteredVector() override; + RegisteredVector (const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr ); + + // redefine setValue, because transform must be applied + void setValue(Geom::Point const & p); + void setValue(Geom::Point const & p, Geom::Point const & origin); + + /** + * Changes the widgets text to polar coordinates. The SVG output will still be a normal carthesian vector. + * Careful: when calling getValue(), the return value's X-coord will be the angle, Y-value will be the distance/length. + * After changing the coords type (polar/non-polar), the value has to be reset (setValue). + */ + void setPolarCoords(bool polar_coords = true); + +protected: + sigc::connection _value_x_changed_connection; + sigc::connection _value_y_changed_connection; + void on_value_changed(); + + Geom::Point _origin; + bool _polar_coords; +}; + + +class RegisteredRandom : public RegisteredWidget<Random> { +public: + ~RegisteredRandom() override; + RegisteredRandom ( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr); + + void setValue (double val, long startseed); + +protected: + sigc::connection _value_changed_connection; + sigc::connection _reseeded_connection; + void on_value_changed(); +}; + +class RegisteredFontButton : public RegisteredWidget<FontButton> { +public: + ~RegisteredFontButton() override; + RegisteredFontButton ( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Registry& wr, + Inkscape::XML::Node* repr_in = nullptr, + SPDocument *doc_in = nullptr); + + void setValue (Glib::ustring fontspec); + +protected: + sigc::connection _signal_font_set; + void on_value_changed(); +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_REGISTERED_WIDGET__H_ + +/* + 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 : diff --git a/src/ui/widget/registry.cpp b/src/ui/widget/registry.cpp new file mode 100644 index 0000000..2834007 --- /dev/null +++ b/src/ui/widget/registry.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ralf Stephan <ralf@ark.in-berlin.de> + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "registry.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +//=================================================== + +//--------------------------------------------------- + +Registry::Registry() : _updating(false) {} + +Registry::~Registry() = default; + +bool +Registry::isUpdating() +{ + return _updating; +} + +void +Registry::setUpdating (bool upd) +{ + _updating = upd; +} + +//==================================================== + + +} // 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 : diff --git a/src/ui/widget/registry.h b/src/ui/widget/registry.h new file mode 100644 index 0000000..190aaac --- /dev/null +++ b/src/ui/widget/registry.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ralf Stephan <ralf@ark.in-berlin.de> + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef INKSCAPE_UI_WIDGET_REGISTRY__H +#define INKSCAPE_UI_WIDGET_REGISTRY__H + +namespace Gtk { + class Object; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +class Registry { +public: + Registry(); + ~Registry(); + + bool isUpdating(); + void setUpdating (bool); + +protected: + bool _updating; +}; + +} // namespace Dialog +} // namespace UI +} // namespace Widget + +#endif // INKSCAPE_UI_WIDGET_REGISTRY__H + +/* + 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 : diff --git a/src/ui/widget/rendering-options.cpp b/src/ui/widget/rendering-options.cpp new file mode 100644 index 0000000..549f494 --- /dev/null +++ b/src/ui/widget/rendering-options.cpp @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Kees Cook <kees@outflux.net> + * + * Copyright (C) 2007 Kees Cook + * Copyright (C) 2004 Bryce Harrington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm.h> + +#include "preferences.h" +#include "rendering-options.h" +#include "util/units.h" +#include <glibmm/i18n.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +void RenderingOptions::_toggled() +{ + _frame_bitmap.set_sensitive(as_bitmap()); +} + +RenderingOptions::RenderingOptions () : + Gtk::VBox (), + _frame_backends ( Glib::ustring(_("Backend")) ), + _radio_vector ( Glib::ustring(_("Vector")) ), + _radio_bitmap ( Glib::ustring(_("Bitmap")) ), + _frame_bitmap ( Glib::ustring(_("Bitmap options")) ), + _dpi( _("DPI"), + Glib::ustring(_("Preferred resolution of rendering, " + "in dots per inch.")), + 1, + Glib::ustring(""), Glib::ustring(""), + false) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + // set up tooltips + _radio_vector.set_tooltip_text( + _("Render using Cairo vector operations. " + "The resulting image is usually smaller in file " + "size and can be arbitrarily scaled, but some " + "filter effects will not be correctly rendered.")); + _radio_bitmap.set_tooltip_text( + _("Render everything as bitmap. The resulting image " + "is usually larger in file size and cannot be " + "arbitrarily scaled without quality loss, but all " + "objects will be rendered exactly as displayed.")); + + set_border_width(2); + + Gtk::RadioButtonGroup group = _radio_vector.get_group (); + _radio_bitmap.set_group (group); + _radio_bitmap.signal_toggled().connect(sigc::mem_fun(*this, &RenderingOptions::_toggled)); + + // default to vector operations + if (prefs->getBool("/dialogs/printing/asbitmap", false)) { + _radio_bitmap.set_active(); + } else { + _radio_vector.set_active(); + } + + // configure default DPI + _dpi.setRange(Inkscape::Util::Quantity::convert(1, "in", "pt"),2400.0); + _dpi.setValue(prefs->getDouble("/dialogs/printing/dpi", + Inkscape::Util::Quantity::convert(1, "in", "pt"))); + _dpi.setIncrements(1.0,10.0); + _dpi.setDigits(0); + _dpi.update(); + + // fill frames + Gtk::VBox *box_vector = Gtk::manage( new Gtk::VBox () ); + box_vector->set_border_width (2); + box_vector->add (_radio_vector); + box_vector->add (_radio_bitmap); + _frame_backends.add (*box_vector); + + Gtk::HBox *box_bitmap = Gtk::manage( new Gtk::HBox () ); + box_bitmap->set_border_width (2); + box_bitmap->add (_dpi); + _frame_bitmap.add (*box_bitmap); + + // fill up container + add (_frame_backends); + add (_frame_bitmap); + + // initialize states + _toggled(); + + show_all_children (); +} + +bool +RenderingOptions::as_bitmap () +{ + return _radio_bitmap.get_active(); +} + +double +RenderingOptions::bitmap_dpi () +{ + return _dpi.getValue(); +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/widget/rendering-options.h b/src/ui/widget/rendering-options.h new file mode 100644 index 0000000..2e10ff3 --- /dev/null +++ b/src/ui/widget/rendering-options.h @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Kees Cook <kees@outflux.net> + * + * Copyright (C) 2007 Kees Cook + * Copyright (C) 2004 Bryce Harrington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_RENDERING_OPTIONS_H +#define INKSCAPE_UI_WIDGET_RENDERING_OPTIONS_H + +#include "scalar.h" + +#include <gtkmm/frame.h> +#include <gtkmm/radiobutton.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A container for selecting rendering options. + */ +class RenderingOptions : public Gtk::VBox +{ +public: + + /** + * Construct a Rendering Options widget. + */ + RenderingOptions(); + + bool as_bitmap(); // should we render as a bitmap? + double bitmap_dpi(); // at what DPI should we render the bitmap? + +protected: + // Radio buttons to select desired rendering + Gtk::Frame _frame_backends; + Gtk::RadioButton _radio_vector; + Gtk::RadioButton _radio_bitmap; + + // Bitmap options + Gtk::Frame _frame_bitmap; + Scalar _dpi; // DPI of bitmap to render + + // callback for bitmap button + void _toggled(); +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_RENDERING_OPTIONS_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/widget/rotateable.cpp b/src/ui/widget/rotateable.cpp new file mode 100644 index 0000000..639f8d1 --- /dev/null +++ b/src/ui/widget/rotateable.cpp @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * buliabyak@gmail.com + * + * Copyright (C) 2007 authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/box.h> +#include <gtkmm/eventbox.h> +#include <2geom/point.h> +#include "ui/tools/tool-base.h" +#include "rotateable.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +Rotateable::Rotateable(): + axis(-M_PI/4), + maxdecl(M_PI/4) +{ + dragging = false; + working = false; + scrolling = false; + modifier = 0; + current_axis = axis; + + signal_button_press_event().connect(sigc::mem_fun(*this, &Rotateable::on_click)); + signal_motion_notify_event().connect(sigc::mem_fun(*this, &Rotateable::on_motion)); + signal_button_release_event().connect(sigc::mem_fun(*this, &Rotateable::on_release)); + gtk_widget_add_events(GTK_WIDGET(gobj()), GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK); + signal_scroll_event().connect(sigc::mem_fun(*this, &Rotateable::on_scroll)); + +} + +bool Rotateable::on_click(GdkEventButton *event) { + if (event->button == 1) { + drag_started_x = event->x; + drag_started_y = event->y; + modifier = get_single_modifier(modifier, event->state); + dragging = true; + working = false; + current_axis = axis; + return true; + } + return false; +} + +guint Rotateable::get_single_modifier(guint old, guint state) { + + if (old == 0 || old == 3) { + if (state & GDK_CONTROL_MASK) + return 1; // ctrl + if (state & GDK_SHIFT_MASK) + return 2; // shift + if (state & GDK_MOD1_MASK) + return 3; // alt + return 0; + } else { + if (!(state & GDK_CONTROL_MASK) && !(state & GDK_SHIFT_MASK)) { + if (state & GDK_MOD1_MASK) + return 3; // alt + else + return 0; // none + } + if (old == 1) { + if (state & GDK_SHIFT_MASK && !(state & GDK_CONTROL_MASK)) + return 2; // shift + if (state & GDK_MOD1_MASK && !(state & GDK_CONTROL_MASK)) + return 3; // alt + return 1; + } + if (old == 2) { + if (state & GDK_CONTROL_MASK && !(state & GDK_SHIFT_MASK)) + return 1; // ctrl + if (state & GDK_MOD1_MASK && !(state & GDK_SHIFT_MASK)) + return 3; // alt + return 2; + } + return old; + } +} + + +bool Rotateable::on_motion(GdkEventMotion *event) { + if (dragging) { + double dist = Geom::L2(Geom::Point(event->x, event->y) - Geom::Point(drag_started_x, drag_started_y)); + double angle = atan2(event->y - drag_started_y, event->x - drag_started_x); + if (dist > 20) { + working = true; + double force = CLAMP (-(angle - current_axis)/maxdecl, -1, 1); + if (fabs(force) < 0.002) + force = 0; // snap to zero + if (modifier != get_single_modifier(modifier, event->state)) { + // user has switched modifiers in mid drag, close past drag and start a new + // one, redefining axis temporarily + do_release(force, modifier); + current_axis = angle; + modifier = get_single_modifier(modifier, event->state); + } else { + do_motion(force, modifier); + } + } + Inkscape::UI::Tools::gobble_motion_events(GDK_BUTTON1_MASK); + return true; + } + return false; +} + + +bool Rotateable::on_release(GdkEventButton *event) { + if (dragging && working) { + double angle = atan2(event->y - drag_started_y, event->x - drag_started_x); + double force = CLAMP(-(angle - current_axis) / maxdecl, -1, 1); + if (fabs(force) < 0.002) + force = 0; // snap to zero + do_release(force, modifier); + current_axis = axis; + dragging = false; + working = false; + return true; + } + dragging = false; + working = false; + return false; +} + +bool Rotateable::on_scroll(GdkEventScroll* event) +{ + double change = 0.0; + + if (event->direction == GDK_SCROLL_UP) { + change = 1.0; + } else if (event->direction == GDK_SCROLL_DOWN) { + change = -1.0; + } else if (event->direction == GDK_SCROLL_SMOOTH) { + double delta_y_clamped = CLAMP(event->delta_y, -1.0, 1.0); // values > 1 result in excessive changes + change = 1.0 * -delta_y_clamped; + } else { + return FALSE; + } + + drag_started_x = event->x; + drag_started_y = event->y; + modifier = get_single_modifier(modifier, event->state); + dragging = false; + working = false; + scrolling = true; + current_axis = axis; + + do_scroll(change, modifier); + + dragging = false; + working = false; + scrolling = false; + + return TRUE; +} + +Rotateable::~Rotateable() = default; + + + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/rotateable.h b/src/ui/widget/rotateable.h new file mode 100644 index 0000000..c174a09 --- /dev/null +++ b/src/ui/widget/rotateable.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * buliabyak@gmail.com + * + * Copyright (C) 2007 authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_ROTATEABLE_H +#define INKSCAPE_UI_ROTATEABLE_H + +#include <gtkmm/box.h> +#include <gtkmm/eventbox.h> +#include <glibmm/i18n.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Widget adjustable by dragging it to rotate away from a zero-change axis. + */ +class Rotateable: public Gtk::EventBox +{ +public: + Rotateable(); + + ~Rotateable() override; + + bool on_click(GdkEventButton *event); + bool on_motion(GdkEventMotion *event); + bool on_release(GdkEventButton *event); + bool on_scroll(GdkEventScroll* event); + + double axis; + double current_axis; + double maxdecl; + bool scrolling; + +private: + double drag_started_x; + double drag_started_y; + guint modifier; + bool dragging; + bool working; + + guint get_single_modifier(guint old, guint state); + + virtual void do_motion (double /*by*/, guint /*state*/) {} + virtual void do_release (double /*by*/, guint /*state*/) {} + virtual void do_scroll (double /*by*/, guint /*state*/) {} +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_ROTATEABLE_H + +/* + 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 : diff --git a/src/ui/widget/scalar-unit.cpp b/src/ui/widget/scalar-unit.cpp new file mode 100644 index 0000000..2b6d001 --- /dev/null +++ b/src/ui/widget/scalar-unit.cpp @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Bryce Harrington <bryce@bryceharrington.org> + * Derek P. Moore <derekm@hackunix.org> + * buliabyak@gmail.com + * + * Copyright (C) 2004-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "scalar-unit.h" +#include "spinbutton.h" + +using Inkscape::Util::unit_table; + +namespace Inkscape { +namespace UI { +namespace Widget { + +ScalarUnit::ScalarUnit(Glib::ustring const &label, Glib::ustring const &tooltip, + UnitType unit_type, + Glib::ustring const &suffix, + Glib::ustring const &icon, + UnitMenu *unit_menu, + bool mnemonic) + : Scalar(label, tooltip, suffix, icon, mnemonic), + _unit_menu(unit_menu), + _hundred_percent(0), + _absolute_is_increment(false), + _percentage_is_increment(false) +{ + if (_unit_menu == nullptr) { + _unit_menu = new UnitMenu(); + g_assert(_unit_menu); + _unit_menu->setUnitType(unit_type); + + remove(*_widget); + Gtk::Box *widget_holder = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 6); + widget_holder->pack_start(*_widget, Gtk::PACK_SHRINK); + widget_holder->pack_start(*Gtk::manage(_unit_menu), Gtk::PACK_SHRINK); + pack_start(*Gtk::manage(widget_holder), Gtk::PACK_SHRINK); + } + _unit_menu->signal_changed() + .connect_notify(sigc::mem_fun(*this, &ScalarUnit::on_unit_changed)); + + static_cast<SpinButton*>(_widget)->setUnitMenu(_unit_menu); + + lastUnits = _unit_menu->getUnitAbbr(); +} + +ScalarUnit::ScalarUnit(Glib::ustring const &label, Glib::ustring const &tooltip, + ScalarUnit &take_unitmenu, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Scalar(label, tooltip, suffix, icon, mnemonic), + _unit_menu(take_unitmenu._unit_menu), + _hundred_percent(0), + _absolute_is_increment(false), + _percentage_is_increment(false) +{ + _unit_menu->signal_changed() + .connect_notify(sigc::mem_fun(*this, &ScalarUnit::on_unit_changed)); + + static_cast<SpinButton*>(_widget)->setUnitMenu(_unit_menu); + + lastUnits = _unit_menu->getUnitAbbr(); +} + + +void ScalarUnit::initScalar(double min_value, double max_value) +{ + g_assert(_unit_menu != nullptr); + Scalar::setDigits(_unit_menu->getDefaultDigits()); + Scalar::setIncrements(_unit_menu->getDefaultStep(), + _unit_menu->getDefaultPage()); + Scalar::setRange(min_value, max_value); +} + +bool ScalarUnit::setUnit(Glib::ustring const &unit) +{ + g_assert(_unit_menu != nullptr); + // First set the unit + if (!_unit_menu->setUnit(unit)) { + return false; + } + lastUnits = unit; + return true; +} + +void ScalarUnit::setUnitType(UnitType unit_type) +{ + g_assert(_unit_menu != nullptr); + _unit_menu->setUnitType(unit_type); + lastUnits = _unit_menu->getUnitAbbr(); +} + +void ScalarUnit::resetUnitType(UnitType unit_type) +{ + g_assert(_unit_menu != nullptr); + _unit_menu->resetUnitType(unit_type); + lastUnits = _unit_menu->getUnitAbbr(); +} + +Unit const * ScalarUnit::getUnit() const +{ + g_assert(_unit_menu != nullptr); + return _unit_menu->getUnit(); +} + +UnitType ScalarUnit::getUnitType() const +{ + g_assert(_unit_menu); + return _unit_menu->getUnitType(); +} + +void ScalarUnit::setValue(double number, Glib::ustring const &units) +{ + g_assert(_unit_menu != nullptr); + _unit_menu->setUnit(units); + Scalar::setValue(number); +} + +void ScalarUnit::setValueKeepUnit(double number, Glib::ustring const &units) +{ + g_assert(_unit_menu != nullptr); + if (units == "") { + // set the value in the default units + Scalar::setValue(number); + } else { + double conversion = _unit_menu->getConversion(units); + Scalar::setValue(number / conversion); + } +} + +void ScalarUnit::setValue(double number) +{ + Scalar::setValue(number); +} + +double ScalarUnit::getValue(Glib::ustring const &unit_name) const +{ + g_assert(_unit_menu != nullptr); + if (unit_name == "") { + // Return the value in the default units + return Scalar::getValue(); + } else { + double conversion = _unit_menu->getConversion(unit_name); + return conversion * Scalar::getValue(); + } +} + +void ScalarUnit::grabFocusAndSelectEntry() +{ + _widget->grab_focus(); + static_cast<SpinButton*>(_widget)->select_region(0, 20); +} + + +void ScalarUnit::setHundredPercent(double number) +{ + _hundred_percent = number; +} + +void ScalarUnit::setAbsoluteIsIncrement(bool value) +{ + _absolute_is_increment = value; +} + +void ScalarUnit::setPercentageIsIncrement(bool value) +{ + _percentage_is_increment = value; +} + +double ScalarUnit::PercentageToAbsolute(double value) +{ + // convert from percent to absolute + double convertedVal = 0; + double hundred_converted = _hundred_percent / _unit_menu->getConversion("px"); // _hundred_percent is in px + if (_percentage_is_increment) + value += 100; + convertedVal = 0.01 * hundred_converted * value; + if (_absolute_is_increment) + convertedVal -= hundred_converted; + + return convertedVal; +} + +double ScalarUnit::AbsoluteToPercentage(double value) +{ + double convertedVal = 0; + // convert from absolute to percent + if (_hundred_percent == 0) { + if (_percentage_is_increment) + convertedVal = 0; + else + convertedVal = 100; + } else { + double hundred_converted = _hundred_percent / _unit_menu->getConversion("px", lastUnits); // _hundred_percent is in px + if (_absolute_is_increment) + value += hundred_converted; + convertedVal = 100 * value / hundred_converted; + if (_percentage_is_increment) + convertedVal -= 100; + } + + return convertedVal; +} + +double ScalarUnit::getAsPercentage() +{ + double convertedVal = AbsoluteToPercentage(Scalar::getValue()); + return convertedVal; +} + + +void ScalarUnit::setFromPercentage(double value) +{ + double absolute = PercentageToAbsolute(value); + Scalar::setValue(absolute); +} + + +void ScalarUnit::on_unit_changed() +{ + g_assert(_unit_menu != nullptr); + + Glib::ustring abbr = _unit_menu->getUnitAbbr(); + _suffix->set_label(abbr); + + Inkscape::Util::Unit const *new_unit = unit_table.getUnit(abbr); + Inkscape::Util::Unit const *old_unit = unit_table.getUnit(lastUnits); + + double convertedVal = 0; + if (old_unit->type == UNIT_TYPE_DIMENSIONLESS && new_unit->type == UNIT_TYPE_LINEAR) { + convertedVal = PercentageToAbsolute(Scalar::getValue()); + } else if (old_unit->type == UNIT_TYPE_LINEAR && new_unit->type == UNIT_TYPE_DIMENSIONLESS) { + convertedVal = AbsoluteToPercentage(Scalar::getValue()); + } else { + double conversion = _unit_menu->getConversion(lastUnits); + convertedVal = Scalar::getValue() / conversion; + } + Scalar::setValue(convertedVal); + + lastUnits = abbr; +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/scalar-unit.h b/src/ui/widget/scalar-unit.h new file mode 100644 index 0000000..e82c41d --- /dev/null +++ b/src/ui/widget/scalar-unit.h @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Bryce Harrington <bryce@bryceharrington.org> + * Derek P. Moore <derekm@hackunix.org> + * buliabyak@gmail.com + * + * Copyright (C) 2004-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_SCALAR_UNIT_H +#define INKSCAPE_UI_WIDGET_SCALAR_UNIT_H + +#include "scalar.h" +#include "unit-menu.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A labelled text box, with spin buttons and optional icon or suffix, for + * entering the values of various unit types. + * + * A ScalarUnit is a control for entering, viewing, or manipulating + * numbers with units. This differs from ordinary numbers like 2 or + * 3.14 because the number portion of a scalar *only* has meaning + * when considered with its unit type. For instance, 12 m and 12 in + * have very different actual values, but 1 m and 100 cm have the same + * value. The ScalarUnit allows us to abstract the presentation of + * the scalar to the user from the internal representations used by + * the program. + */ +class ScalarUnit : public Scalar +{ +public: + /** + * Construct a ScalarUnit. + * + * @param label Label. + * @param unit_type Unit type (defaults to UNIT_TYPE_LINEAR). + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param unit_menu UnitMenu drop down; if not specified, one will be created + * and displayed after the widget (defaults to NULL). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to true). + */ + ScalarUnit(Glib::ustring const &label, Glib::ustring const &tooltip, + UnitType unit_type = UNIT_TYPE_LINEAR, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + UnitMenu *unit_menu = nullptr, + bool mnemonic = true); + + /** + * Construct a ScalarUnit. + * + * @param label Label. + * @param tooltip Tooltip text. + * @param take_unitmenu Use the unitmenu from this parameter. + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to true). + */ + ScalarUnit(Glib::ustring const &label, Glib::ustring const &tooltip, + ScalarUnit &take_unitmenu, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Initializes the scalar based on the settings in _unit_menu. + * Requires that _unit_menu has already been initialized. + */ + void initScalar(double min_value, double max_value); + + /** + * Gets the object for the currently selected unit. + */ + Unit const * getUnit() const; + + /** + * Gets the UnitType ID for the unit. + */ + UnitType getUnitType() const; + + /** + * Returns the value in the given unit system. + */ + double getValue(Glib::ustring const &units) const; + + /** + * Sets the unit for the ScalarUnit widget. + */ + bool setUnit(Glib::ustring const &units); + + /** + * Adds the unit type to the ScalarUnit widget. + */ + void setUnitType(UnitType unit_type); + + /** + * Resets the unit type for the ScalarUnit widget. + */ + void resetUnitType(UnitType unit_type); + + /** + * Sets the number and unit system. + */ + void setValue(double number, Glib::ustring const &units); + + /** + * Convert and sets the number only and keeps the current unit. + */ + void setValueKeepUnit(double number, Glib::ustring const &units); + + /** + * Sets the number only. + */ + void setValue(double number); + + /** + * Grab focus, and select the text that is in the entry field. + */ + void grabFocusAndSelectEntry(); + + void setHundredPercent(double number); + + void setAbsoluteIsIncrement(bool value); + + void setPercentageIsIncrement(bool value); + + /** + * Convert value from % to absolute, using _hundred_percent and *_is_increment flags. + */ + double PercentageToAbsolute(double value); + + /** + * Convert value from absolute to %, using _hundred_percent and *_is_increment flags. + */ + double AbsoluteToPercentage(double value); + + /** + * Assuming the current unit is absolute, get the corresponding % value. + */ + double getAsPercentage(); + + /** + * Assuming the current unit is absolute, set the value corresponding to a given %. + */ + void setFromPercentage(double value); + + /** + * Signal handler for updating the value and suffix label when unit is changed. + */ + void on_unit_changed(); + +protected: + UnitMenu *_unit_menu; + + double _hundred_percent; // the length that corresponds to 100%, in px, for %-to/from-absolute conversions + + bool _absolute_is_increment; // if true, 120% with _hundred_percent=100px gets converted to/from 20px; otherwise, to/from 120px + bool _percentage_is_increment; // if true, 120px with _hundred_percent=100px gets converted to/from 20%; otherwise, to/from 120% + // if both are true, 20px is converted to/from 20% if _hundred_percent=100px + + Glib::ustring lastUnits; // previously selected unit, for conversions +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_SCALAR_UNIT_H + +/* + 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 : diff --git a/src/ui/widget/scalar.cpp b/src/ui/widget/scalar.cpp new file mode 100644 index 0000000..471de49 --- /dev/null +++ b/src/ui/widget/scalar.cpp @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Carl Hetherington <inkscape@carlh.net> + * Derek P. Moore <derekm@hackunix.org> + * Bryce Harrington <bryce@bryceharrington.org> + * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl> + * + * Copyright (C) 2004-2011 authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "scalar.h" +#include "spinbutton.h" +#include <gtkmm/scale.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +Scalar::Scalar(Glib::ustring const &label, Glib::ustring const &tooltip, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Labelled(label, tooltip, new SpinButton(), suffix, icon, mnemonic), + setProgrammatically(false) +{ +} + +Scalar::Scalar(Glib::ustring const &label, Glib::ustring const &tooltip, + unsigned digits, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Labelled(label, tooltip, new SpinButton(0.0, digits), suffix, icon, mnemonic), + setProgrammatically(false) +{ +} + +Scalar::Scalar(Glib::ustring const &label, Glib::ustring const &tooltip, + Glib::RefPtr<Gtk::Adjustment> &adjust, + unsigned digits, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Labelled(label, tooltip, new SpinButton(adjust, 0.0, digits), suffix, icon, mnemonic), + setProgrammatically(false) +{ +} + +unsigned Scalar::getDigits() const +{ + g_assert(_widget != nullptr); + return static_cast<SpinButton*>(_widget)->get_digits(); +} + +double Scalar::getStep() const +{ + g_assert(_widget != nullptr); + double step, page; + static_cast<SpinButton*>(_widget)->get_increments(step, page); + return step; +} + +double Scalar::getPage() const +{ + g_assert(_widget != nullptr); + double step, page; + static_cast<SpinButton*>(_widget)->get_increments(step, page); + return page; +} + +double Scalar::getRangeMin() const +{ + g_assert(_widget != nullptr); + double min, max; + static_cast<SpinButton*>(_widget)->get_range(min, max); + return min; +} + +double Scalar::getRangeMax() const +{ + g_assert(_widget != nullptr); + double min, max; + static_cast<SpinButton*>(_widget)->get_range(min, max); + return max; +} + +double Scalar::getValue() const +{ + g_assert(_widget != nullptr); + return static_cast<SpinButton*>(_widget)->get_value(); +} + +int Scalar::getValueAsInt() const +{ + g_assert(_widget != nullptr); + return static_cast<SpinButton*>(_widget)->get_value_as_int(); +} + + +void Scalar::setDigits(unsigned digits) +{ + g_assert(_widget != nullptr); + static_cast<SpinButton*>(_widget)->set_digits(digits); +} + +void Scalar::setIncrements(double step, double /*page*/) +{ + g_assert(_widget != nullptr); + static_cast<SpinButton*>(_widget)->set_increments(step, 0); +} + +void Scalar::setRange(double min, double max) +{ + g_assert(_widget != nullptr); + static_cast<SpinButton*>(_widget)->set_range(min, max); +} + +void Scalar::setValue(double value, bool setProg) +{ + g_assert(_widget != nullptr); + if (setProg) { + setProgrammatically = true; // callback is supposed to reset back, if it cares + } + static_cast<SpinButton*>(_widget)->set_value(value); +} + +void Scalar::setWidthChars(unsigned chars) +{ + g_assert(_widget != NULL); + static_cast<SpinButton*>(_widget)->set_width_chars(chars); +} + +void Scalar::update() +{ + g_assert(_widget != nullptr); + static_cast<SpinButton*>(_widget)->update(); +} + +void Scalar::addSlider() +{ + auto scale = new Gtk::Scale(static_cast<SpinButton*>(_widget)->get_adjustment()); + scale->set_draw_value(false); + add (*manage (scale)); +} + +Glib::SignalProxy0<void> Scalar::signal_value_changed() +{ + return static_cast<SpinButton*>(_widget)->signal_value_changed(); +} + +Glib::SignalProxy1<bool, GdkEventButton*> Scalar::signal_button_release_event() +{ + return static_cast<SpinButton*>(_widget)->signal_button_release_event(); +} + + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/scalar.h b/src/ui/widget/scalar.h new file mode 100644 index 0000000..29a14d1 --- /dev/null +++ b/src/ui/widget/scalar.h @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Carl Hetherington <inkscape@carlh.net> + * Derek P. Moore <derekm@hackunix.org> + * Bryce Harrington <bryce@bryceharrington.org> + * + * Copyright (C) 2004 Carl Hetherington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_SCALAR_H +#define INKSCAPE_UI_WIDGET_SCALAR_H + +#include "labelled.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A labelled text box, with spin buttons and optional + * icon or suffix, for entering arbitrary number values. + */ +class Scalar : public Labelled +{ +public: + /** + * Construct a Scalar Widget. + * + * @param label Label. + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to false). + */ + Scalar(Glib::ustring const &label, + Glib::ustring const &tooltip, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Construct a Scalar Widget. + * + * @param label Label. + * @param digits Number of decimal digits to display. + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to false). + */ + Scalar(Glib::ustring const &label, + Glib::ustring const &tooltip, + unsigned digits, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Construct a Scalar Widget. + * + * @param label Label. + * @param adjust Adjustment to use for the SpinButton. + * @param digits Number of decimal digits to display (defaults to 0). + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to true). + */ + Scalar(Glib::ustring const &label, + Glib::ustring const &tooltip, + Glib::RefPtr<Gtk::Adjustment> &adjust, + unsigned digits = 0, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Fetches the precision of the spin button. + */ + unsigned getDigits() const; + + /** + * Gets the current step increment used by the spin button. + */ + double getStep() const; + + /** + * Gets the current page increment used by the spin button. + */ + double getPage() const; + + /** + * Gets the minimum range value allowed for the spin button. + */ + double getRangeMin() const; + + /** + * Gets the maximum range value allowed for the spin button. + */ + double getRangeMax() const; + + bool getSnapToTicks() const; + + /** + * Get the value in the spin_button. + */ + double getValue() const; + + /** + * Get the value spin_button represented as an integer. + */ + int getValueAsInt() const; + + /** + * Sets the precision to be displayed by the spin button. + */ + void setDigits(unsigned digits); + + /** + * Sets the step and page increments for the spin button. + * @todo Remove the second parameter - deprecated + */ + void setIncrements(double step, double page); + + /** + * Sets the minimum and maximum range allowed for the spin button. + */ + void setRange(double min, double max); + + /** + * Sets the value of the spin button. + */ + void setValue(double value, bool setProg = true); + + /** + * Sets the width of the spin button by number of characters. + */ + void setWidthChars(unsigned chars); + + /** + * Manually forces an update of the spin button. + */ + void update(); + + /** + * Adds a slider (HScale) to the left of the spinbox. + */ + void addSlider(); + + /** + * Signal raised when the spin button's value changes. + */ + Glib::SignalProxy0<void> signal_value_changed(); + + /** + * Signal raised when the spin button's pressed. + */ + Glib::SignalProxy1<bool, GdkEventButton*> signal_button_release_event(); + + /** + * true if the value was set by setValue, not changed by the user; + * if a callback checks it, it must reset it back to false. + */ + bool setProgrammatically; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_SCALAR_H + +/* + 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 : diff --git a/src/ui/widget/selected-style.cpp b/src/ui/widget/selected-style.cpp new file mode 100644 index 0000000..9417a9f --- /dev/null +++ b/src/ui/widget/selected-style.cpp @@ -0,0 +1,1488 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * buliabyak@gmail.com + * Abhishek Sharma + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2005 author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "selected-style.h" + +#include <gtkmm/separatormenuitem.h> + + +#include "desktop-style.h" +#include "document-undo.h" +#include "gradient-chemistry.h" +#include "message-context.h" +#include "selection.h" +#include "sp-cursor.h" + +#include "display/sp-canvas.h" + +#include "include/gtkmm_version.h" + +#include "object/sp-hatch.h" +#include "object/sp-linear-gradient.h" +#include "object/sp-mesh-gradient.h" +#include "object/sp-namedview.h" +#include "object/sp-pattern.h" +#include "object/sp-radial-gradient.h" +#include "style.h" + +#include "ui/pixmaps/cursor-adj-a.xpm" +#include "ui/pixmaps/cursor-adj-h.xpm" +#include "ui/pixmaps/cursor-adj-l.xpm" +#include "ui/pixmaps/cursor-adj-s.xpm" + +#include "svg/css-ostringstream.h" +#include "svg/svg-color.h" + +#include "ui/dialog/dialog-manager.h" +#include "ui/dialog/fill-and-stroke.h" +#include "ui/dialog/panel-dialog.h" +#include "ui/tools/tool-base.h" +#include "ui/widget/color-preview.h" + +#include "widgets/ege-paint-def.h" +#include "widgets/gradient-image.h" +#include "widgets/spinbutton-events.h" +#include "widgets/spw-utilities.h" +#include "widgets/widget-sizes.h" + +using Inkscape::Util::unit_table; + +static gdouble const _sw_presets[] = { 32 , 16 , 10 , 8 , 6 , 4 , 3 , 2 , 1.5 , 1 , 0.75 , 0.5 , 0.25 , 0.1 }; +static gchar const *const _sw_presets_str[] = {"32", "16", "10", "8", "6", "4", "3", "2", "1.5", "1", "0.75", "0.5", "0.25", "0.1"}; + +static void +ss_selection_changed (Inkscape::Selection *, gpointer data) +{ + Inkscape::UI::Widget::SelectedStyle *ss = (Inkscape::UI::Widget::SelectedStyle *) data; + ss->update(); +} + +static void +ss_selection_modified( Inkscape::Selection *selection, guint flags, gpointer data ) +{ + // Don't update the style when dragging or doing non-style related changes + if (flags & (SP_OBJECT_STYLE_MODIFIED_FLAG)) { + ss_selection_changed (selection, data); + } +} + +static void +ss_subselection_changed( gpointer /*dragger*/, gpointer data ) +{ + ss_selection_changed (nullptr, data); +} + +namespace { + +void clearTooltip( Gtk::Widget &widget ) +{ + widget.set_tooltip_text(""); + widget.set_has_tooltip(false); +} + +} // namespace + +namespace Inkscape { +namespace UI { +namespace Widget { + + +struct DropTracker { + SelectedStyle* parent; + int item; +}; + +/* Drag and Drop */ +enum ui_drop_target_info { + APP_OSWB_COLOR +}; + +//TODO: warning: deprecated conversion from string constant to âgchar*â +// +//Turn out to be warnings that we should probably leave in place. The +// pointers/types used need to be read-only. So until we correct the using +// code, those warnings are actually desired. They say "Hey! Fix this". We +// definitely don't want to hide/ignore them. --JonCruz +static const GtkTargetEntry ui_drop_target_entries [] = { + {g_strdup("application/x-oswb-color"), 0, APP_OSWB_COLOR} +}; + +static guint nui_drop_target_entries = G_N_ELEMENTS(ui_drop_target_entries); + +/* convenience function */ +static Dialog::FillAndStroke *get_fill_and_stroke_panel(SPDesktop *desktop); + +SelectedStyle::SelectedStyle(bool /*layout*/) + : current_stroke_width(0) + , _sw_unit(nullptr) + , _desktop(nullptr) + , _table() + , _fill_label(_("Fill:")) + , _stroke_label(_("Stroke:")) + , _opacity_label(_("O:")) + , _fill_place(this, SS_FILL) + , _stroke_place(this, SS_STROKE) + , _fill_flag_place() + , _stroke_flag_place() + , _opacity_place() + , _opacity_adjustment(Gtk::Adjustment::create(100, 0.0, 100, 1.0, 10.0)) + , _opacity_sb(0.02, 0) + , _fill() + , _stroke() + , _stroke_width_place(this) + , _stroke_width("") + , _fill_empty_space("") + , _opacity_blocked(false) +{ + set_name("SelectedStyle"); + _drop[0] = _drop[1] = nullptr; + _dropEnabled[0] = _dropEnabled[1] = false; + + _fill_label.set_halign(Gtk::ALIGN_START); + _fill_label.set_valign(Gtk::ALIGN_CENTER); + _fill_label.set_margin_top(0); + _fill_label.set_margin_bottom(0); + _stroke_label.set_halign(Gtk::ALIGN_START); + _stroke_label.set_valign(Gtk::ALIGN_CENTER); + _stroke_label.set_margin_top(0); + _stroke_label.set_margin_bottom(0); + _opacity_label.set_halign(Gtk::ALIGN_START); + _opacity_label.set_valign(Gtk::ALIGN_CENTER); + _opacity_label.set_margin_top(0); + _opacity_label.set_margin_bottom(0); + _stroke_width.set_name("monoStrokeWidth"); + _fill_empty_space.set_name("fillEmptySpace"); + + _fill_label.set_margin_start(0); + _fill_label.set_margin_end(0); + _stroke_label.set_margin_start(0); + _stroke_label.set_margin_end(0); + _opacity_label.set_margin_start(0); + _opacity_label.set_margin_end(0); + + _table.set_column_spacing(2); + _table.set_row_spacing(0); + + for (int i = SS_FILL; i <= SS_STROKE; i++) { + + _na[i].set_markup (_("N/A")); + _na[i].show_all(); + __na[i] = (_("Nothing selected")); + + if (i == SS_FILL) { + _none[i].set_markup (C_("Fill", "<i>None</i>")); + } else { + _none[i].set_markup (C_("Stroke", "<i>None</i>")); + } + _none[i].show_all(); + __none[i] = (i == SS_FILL)? (C_("Fill and stroke", "No fill, middle-click for black fill")) : (C_("Fill and stroke", "No stroke, middle-click for black stroke")); + + _pattern[i].set_markup (_("Pattern")); + _pattern[i].show_all(); + __pattern[i] = (i == SS_FILL)? (_("Pattern fill")) : (_("Pattern stroke")); + + _hatch[i].set_markup(_("Hatch")); + _hatch[i].show_all(); + __hatch[i] = (i == SS_FILL) ? (_("Hatch fill")) : (_("Hatch stroke")); + + _lgradient[i].set_markup (_("<b>L</b>")); + _lgradient[i].show_all(); + __lgradient[i] = (i == SS_FILL)? (_("Linear gradient fill")) : (_("Linear gradient stroke")); + + _gradient_preview_l[i] = GTK_WIDGET(sp_gradient_image_new (nullptr)); + _gradient_box_l[i].pack_start(_lgradient[i]); + _gradient_box_l[i].pack_start(*(Glib::wrap(_gradient_preview_l[i]))); + _gradient_box_l[i].show_all(); + + _rgradient[i].set_markup (_("<b>R</b>")); + _rgradient[i].show_all(); + __rgradient[i] = (i == SS_FILL)? (_("Radial gradient fill")) : (_("Radial gradient stroke")); + + _gradient_preview_r[i] = GTK_WIDGET(sp_gradient_image_new (nullptr)); + _gradient_box_r[i].pack_start(_rgradient[i]); + _gradient_box_r[i].pack_start(*(Glib::wrap(_gradient_preview_r[i]))); + _gradient_box_r[i].show_all(); + +#ifdef WITH_MESH + _mgradient[i].set_markup (_("<b>M</b>")); + _mgradient[i].show_all(); + __mgradient[i] = (i == SS_FILL)? (_("Mesh gradient fill")) : (_("Mesh gradient stroke")); + + _gradient_preview_m[i] = GTK_WIDGET(sp_gradient_image_new (nullptr)); + _gradient_box_m[i].pack_start(_mgradient[i]); + _gradient_box_m[i].pack_start(*(Glib::wrap(_gradient_preview_m[i]))); + _gradient_box_m[i].show_all(); +#endif + + _many[i].set_markup (_("Different")); + _many[i].show_all(); + __many[i] = (i == SS_FILL)? (_("Different fills")) : (_("Different strokes")); + + _unset[i].set_markup (_("<b>Unset</b>")); + _unset[i].show_all(); + __unset[i] = (i == SS_FILL)? (_("Unset fill")) : (_("Unset stroke")); + + _color_preview[i] = new Inkscape::UI::Widget::ColorPreview (0); + __color[i] = (i == SS_FILL)? (_("Flat color fill")) : (_("Flat color stroke")); + + // TRANSLATORS: A means "Averaged" + _averaged[i].set_markup (_("<b>a</b>")); + _averaged[i].show_all(); + __averaged[i] = (i == SS_FILL)? (_("Fill is averaged over selected objects")) : (_("Stroke is averaged over selected objects")); + + // TRANSLATORS: M means "Multiple" + _multiple[i].set_markup (_("<b>m</b>")); + _multiple[i].show_all(); + __multiple[i] = (i == SS_FILL)? (_("Multiple selected objects have the same fill")) : (_("Multiple selected objects have the same stroke")); + + _popup_edit[i].add(*(new Gtk::Label((i == SS_FILL)? _("Edit fill...") : _("Edit stroke..."), Gtk::ALIGN_START))); + _popup_edit[i].signal_activate().connect(sigc::mem_fun(*this, + (i == SS_FILL)? &SelectedStyle::on_fill_edit : &SelectedStyle::on_stroke_edit )); + + _popup_lastused[i].add(*(new Gtk::Label(_("Last set color"), Gtk::ALIGN_START))); + _popup_lastused[i].signal_activate().connect(sigc::mem_fun(*this, + (i == SS_FILL)? &SelectedStyle::on_fill_lastused : &SelectedStyle::on_stroke_lastused )); + + _popup_lastselected[i].add(*(new Gtk::Label(_("Last selected color"), Gtk::ALIGN_START))); + _popup_lastselected[i].signal_activate().connect(sigc::mem_fun(*this, + (i == SS_FILL)? &SelectedStyle::on_fill_lastselected : &SelectedStyle::on_stroke_lastselected )); + + _popup_invert[i].add(*(new Gtk::Label(_("Invert"), Gtk::ALIGN_START))); + _popup_invert[i].signal_activate().connect(sigc::mem_fun(*this, + (i == SS_FILL)? &SelectedStyle::on_fill_invert : &SelectedStyle::on_stroke_invert )); + + _popup_white[i].add(*(new Gtk::Label(_("White"), Gtk::ALIGN_START))); + _popup_white[i].signal_activate().connect(sigc::mem_fun(*this, + (i == SS_FILL)? &SelectedStyle::on_fill_white : &SelectedStyle::on_stroke_white )); + + _popup_black[i].add(*(new Gtk::Label(_("Black"), Gtk::ALIGN_START))); + _popup_black[i].signal_activate().connect(sigc::mem_fun(*this, + (i == SS_FILL)? &SelectedStyle::on_fill_black : &SelectedStyle::on_stroke_black )); + + _popup_copy[i].add(*(new Gtk::Label(_("Copy color"), Gtk::ALIGN_START))); + _popup_copy[i].signal_activate().connect(sigc::mem_fun(*this, + (i == SS_FILL)? &SelectedStyle::on_fill_copy : &SelectedStyle::on_stroke_copy )); + + _popup_paste[i].add(*(new Gtk::Label(_("Paste color"), Gtk::ALIGN_START))); + _popup_paste[i].signal_activate().connect(sigc::mem_fun(*this, + (i == SS_FILL)? &SelectedStyle::on_fill_paste : &SelectedStyle::on_stroke_paste )); + + _popup_swap[i].add(*(new Gtk::Label(_("Swap fill and stroke"), Gtk::ALIGN_START))); + _popup_swap[i].signal_activate().connect(sigc::mem_fun(*this, + &SelectedStyle::on_fillstroke_swap)); + + _popup_opaque[i].add(*(new Gtk::Label((i == SS_FILL)? _("Make fill opaque") : _("Make stroke opaque"), Gtk::ALIGN_START))); + _popup_opaque[i].signal_activate().connect(sigc::mem_fun(*this, + (i == SS_FILL)? &SelectedStyle::on_fill_opaque : &SelectedStyle::on_stroke_opaque )); + + //TRANSLATORS COMMENT: unset is a verb here + _popup_unset[i].add(*(new Gtk::Label((i == SS_FILL)? _("Unset fill") : _("Unset stroke"), Gtk::ALIGN_START))); + _popup_unset[i].signal_activate().connect(sigc::mem_fun(*this, + (i == SS_FILL)? &SelectedStyle::on_fill_unset : &SelectedStyle::on_stroke_unset )); + + _popup_remove[i].add(*(new Gtk::Label((i == SS_FILL)? _("Remove fill") : _("Remove stroke"), Gtk::ALIGN_START))); + _popup_remove[i].signal_activate().connect(sigc::mem_fun(*this, + (i == SS_FILL)? &SelectedStyle::on_fill_remove : &SelectedStyle::on_stroke_remove )); + + _popup[i].attach(_popup_edit[i], 0,1, 0,1); + _popup[i].attach(*(new Gtk::SeparatorMenuItem()), 0,1, 1,2); + _popup[i].attach(_popup_lastused[i], 0,1, 2,3); + _popup[i].attach(_popup_lastselected[i], 0,1, 3,4); + _popup[i].attach(*(new Gtk::SeparatorMenuItem()), 0,1, 4,5); + _popup[i].attach(_popup_invert[i], 0,1, 5,6); + _popup[i].attach(*(new Gtk::SeparatorMenuItem()), 0,1, 6,7); + _popup[i].attach(_popup_white[i], 0,1, 7,8); + _popup[i].attach(_popup_black[i], 0,1, 8,9); + _popup[i].attach(*(new Gtk::SeparatorMenuItem()), 0,1, 9,10); + _popup[i].attach(_popup_copy[i], 0,1, 10,11); + _popup_copy[i].set_sensitive(false); + _popup[i].attach(_popup_paste[i], 0,1, 11,12); + _popup[i].attach(_popup_swap[i], 0,1, 12,13); + _popup[i].attach(*(new Gtk::SeparatorMenuItem()), 0,1, 13,14); + _popup[i].attach(_popup_opaque[i], 0,1, 14,15); + _popup[i].attach(_popup_unset[i], 0,1, 15,16); + _popup[i].attach(_popup_remove[i], 0,1, 16,17); + _popup[i].show_all(); + + _mode[i] = SS_NA; + } + + { + int row = 0; + + Inkscape::Util::UnitTable::UnitMap m = unit_table.units(Inkscape::Util::UNIT_TYPE_LINEAR); + Inkscape::Util::UnitTable::UnitMap::iterator iter = m.begin(); + while(iter != m.end()) { + Gtk::RadioMenuItem *mi = Gtk::manage(new Gtk::RadioMenuItem(_sw_group)); + mi->add(*(new Gtk::Label(iter->first, Gtk::ALIGN_START))); + _unit_mis.push_back(mi); + Inkscape::Util::Unit const *u = unit_table.getUnit(iter->first); + mi->signal_activate().connect(sigc::bind<Inkscape::Util::Unit const *>(sigc::mem_fun(*this, &SelectedStyle::on_popup_units), u)); + _popup_sw.attach(*mi, 0,1, row, row+1); + row++; + ++iter; + } + + _popup_sw.attach(*(new Gtk::SeparatorMenuItem()), 0,1, row, row+1); + row++; + + for (guint i = 0; i < G_N_ELEMENTS(_sw_presets_str); ++i) { + Gtk::MenuItem *mi = Gtk::manage(new Gtk::MenuItem()); + mi->add(*(new Gtk::Label(_sw_presets_str[i], Gtk::ALIGN_START))); + mi->signal_activate().connect(sigc::bind<int>(sigc::mem_fun(*this, &SelectedStyle::on_popup_preset), i)); + _popup_sw.attach(*mi, 0,1, row, row+1); + row++; + } + + _popup_sw.attach(*(new Gtk::SeparatorMenuItem()), 0,1, row, row+1); + row++; + + _popup_sw_remove.add(*(new Gtk::Label(_("Remove"), Gtk::ALIGN_START))); + _popup_sw_remove.signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::on_stroke_remove)); + _popup_sw.attach(_popup_sw_remove, 0,1, row, row+1); + row++; + + _popup_sw.show_all(); + } + + _fill_place.add(_na[SS_FILL]); + _fill_place.set_tooltip_text(__na[SS_FILL]); + _fill.pack_start(_fill_place, Gtk::PACK_SHRINK); + _fill.pack_start(_fill_empty_space, Gtk::PACK_SHRINK); + + _stroke_place.add(_na[SS_STROKE]); + _stroke_place.set_tooltip_text(__na[SS_STROKE]); + + _stroke.pack_start(_stroke_place); + _stroke_width_place.add(_stroke_width); + _stroke.pack_start(_stroke_width_place, Gtk::PACK_SHRINK); + + _opacity_sb.set_adjustment(_opacity_adjustment); + _opacity_sb.set_size_request (SELECTED_STYLE_SB_WIDTH, -1); + _opacity_sb.set_sensitive (false); + + _table.attach(_fill_label, 0, 0, 1, 1); + _table.attach(_stroke_label, 0, 1, 1, 1); + + _table.attach(_fill_flag_place, 1, 0, 1, 1); + _table.attach(_stroke_flag_place, 1, 1, 1, 1); + + _table.attach(_fill, 2, 0, 1, 1); + _table.attach(_stroke, 2, 1, 1, 1); + + _opacity_place.add(_opacity_label); + + _table.attach(_opacity_place, 4, 0, 1, 2); + _table.attach(_opacity_sb, 5, 0, 1, 2); + + pack_start(_table, true, true, 2); + + set_size_request (SELECTED_STYLE_WIDTH, -1); + + _drop[SS_FILL] = new DropTracker(); + ((DropTracker*)_drop[SS_FILL])->parent = this; + ((DropTracker*)_drop[SS_FILL])->item = SS_FILL; + + _drop[SS_STROKE] = new DropTracker(); + ((DropTracker*)_drop[SS_STROKE])->parent = this; + ((DropTracker*)_drop[SS_STROKE])->item = SS_STROKE; + + g_signal_connect(_stroke_place.gobj(), + "drag_data_received", + G_CALLBACK(dragDataReceived), + _drop[SS_STROKE]); + + g_signal_connect(_fill_place.gobj(), + "drag_data_received", + G_CALLBACK(dragDataReceived), + _drop[SS_FILL]); + + _fill_place.signal_button_release_event().connect(sigc::mem_fun(*this, &SelectedStyle::on_fill_click)); + _stroke_place.signal_button_release_event().connect(sigc::mem_fun(*this, &SelectedStyle::on_stroke_click)); + _opacity_place.signal_button_press_event().connect(sigc::mem_fun(*this, &SelectedStyle::on_opacity_click)); + _stroke_width_place.signal_button_press_event().connect(sigc::mem_fun(*this, &SelectedStyle::on_sw_click)); + _stroke_width_place.signal_button_release_event().connect(sigc::mem_fun(*this, &SelectedStyle::on_sw_click)); + _opacity_sb.signal_populate_popup().connect(sigc::mem_fun(*this, &SelectedStyle::on_opacity_menu)); + _opacity_sb.signal_value_changed().connect(sigc::mem_fun(*this, &SelectedStyle::on_opacity_changed)); + // Connect to key-press to ensure focus is consistent with other spin buttons when using the keys vs mouse-click + g_signal_connect (G_OBJECT (_opacity_sb.gobj()), "key-press-event", G_CALLBACK (spinbutton_keypress), _opacity_sb.gobj()); + g_signal_connect (G_OBJECT (_opacity_sb.gobj()), "focus-in-event", G_CALLBACK (spinbutton_focus_in), _opacity_sb.gobj()); +} + +SelectedStyle::~SelectedStyle() +{ + selection_changed_connection->disconnect(); + delete selection_changed_connection; + selection_modified_connection->disconnect(); + delete selection_modified_connection; + subselection_changed_connection->disconnect(); + delete subselection_changed_connection; + _unit_mis.clear(); + + for (int i = SS_FILL; i <= SS_STROKE; i++) { + delete _color_preview[i]; + // FIXME: do we need this? the destroy methods are not exported + //sp_gradient_image_destroy(GTK_OBJECT(_gradient_preview_l[i])); + //sp_gradient_image_destroy(GTK_OBJECT(_gradient_preview_r[i])); + } + + delete (DropTracker*)_drop[SS_FILL]; + delete (DropTracker*)_drop[SS_STROKE]; +} + +void +SelectedStyle::setDesktop(SPDesktop *desktop) +{ + _desktop = desktop; + g_object_set_data (G_OBJECT(_opacity_sb.gobj()), "dtw", _desktop->canvas); + + Inkscape::Selection *selection = desktop->getSelection(); + + selection_changed_connection = new sigc::connection (selection->connectChanged( + sigc::bind ( + sigc::ptr_fun(&ss_selection_changed), + this ) + )); + selection_modified_connection = new sigc::connection (selection->connectModified( + sigc::bind ( + sigc::ptr_fun(&ss_selection_modified), + this ) + )); + subselection_changed_connection = new sigc::connection (desktop->connectToolSubselectionChanged( + sigc::bind ( + sigc::ptr_fun(&ss_subselection_changed), + this ) + )); + + _sw_unit = desktop->getNamedView()->display_units; + + // Set the doc default unit active in the units list + for ( auto mi:_unit_mis ) { + if (mi && mi->get_label() == _sw_unit->abbr) { + mi->set_active(); + break; + } + } +} + +void SelectedStyle::dragDataReceived( GtkWidget */*widget*/, + GdkDragContext */*drag_context*/, + gint /*x*/, gint /*y*/, + GtkSelectionData *data, + guint /*info*/, + guint /*event_time*/, + gpointer user_data ) +{ + DropTracker* tracker = (DropTracker*)user_data; + + // copied from drag-and-drop.cpp, case APP_OSWB_COLOR + bool worked = false; + Glib::ustring colorspec; + if (gtk_selection_data_get_format(data) == 8) { + ege::PaintDef color; + worked = color.fromMIMEData("application/x-oswb-color", + reinterpret_cast<char const *>(gtk_selection_data_get_data(data)), + gtk_selection_data_get_length(data), + gtk_selection_data_get_format(data)); + if (worked) { + if (color.getType() == ege::PaintDef::CLEAR) { + colorspec = ""; // TODO check if this is sufficient + } else if (color.getType() == ege::PaintDef::NONE) { + colorspec = "none"; + } else { + unsigned int r = color.getR(); + unsigned int g = color.getG(); + unsigned int b = color.getB(); + + gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b); + colorspec = tmp; + g_free(tmp); + } + } + } + if (worked) { + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, (tracker->item == SS_FILL) ? "fill":"stroke", colorspec.c_str()); + + sp_desktop_set_style(tracker->parent->_desktop, css); + sp_repr_css_attr_unref(css); + DocumentUndo::done(tracker->parent->_desktop->getDocument(), SP_VERB_NONE, _("Drop color")); + } +} + +void SelectedStyle::on_fill_remove() { + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "fill", "none"); + sp_desktop_set_style (_desktop, css, true, true); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Remove fill")); +} + +void SelectedStyle::on_stroke_remove() { + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "stroke", "none"); + sp_desktop_set_style (_desktop, css, true, true); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Remove stroke")); +} + +void SelectedStyle::on_fill_unset() { + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_unset_property (css, "fill"); + sp_desktop_set_style (_desktop, css, true, true); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Unset fill")); +} + +void SelectedStyle::on_stroke_unset() { + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_unset_property (css, "stroke"); + sp_repr_css_unset_property (css, "stroke-opacity"); + sp_repr_css_unset_property (css, "stroke-width"); + sp_repr_css_unset_property (css, "stroke-miterlimit"); + sp_repr_css_unset_property (css, "stroke-linejoin"); + sp_repr_css_unset_property (css, "stroke-linecap"); + sp_repr_css_unset_property (css, "stroke-dashoffset"); + sp_repr_css_unset_property (css, "stroke-dasharray"); + sp_desktop_set_style (_desktop, css, true, true); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Unset stroke")); +} + +void SelectedStyle::on_fill_opaque() { + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "fill-opacity", "1"); + sp_desktop_set_style (_desktop, css, true); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Make fill opaque")); +} + +void SelectedStyle::on_stroke_opaque() { + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "stroke-opacity", "1"); + sp_desktop_set_style (_desktop, css, true); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Make fill opaque")); +} + +void SelectedStyle::on_fill_lastused() { + SPCSSAttr *css = sp_repr_css_attr_new (); + guint32 color = sp_desktop_get_color(_desktop, true); + gchar c[64]; + sp_svg_write_color (c, sizeof(c), color); + sp_repr_css_set_property (css, "fill", c); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Apply last set color to fill")); +} + +void SelectedStyle::on_stroke_lastused() { + SPCSSAttr *css = sp_repr_css_attr_new (); + guint32 color = sp_desktop_get_color(_desktop, false); + gchar c[64]; + sp_svg_write_color (c, sizeof(c), color); + sp_repr_css_set_property (css, "stroke", c); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Apply last set color to stroke")); +} + +void SelectedStyle::on_fill_lastselected() { + SPCSSAttr *css = sp_repr_css_attr_new (); + gchar c[64]; + sp_svg_write_color (c, sizeof(c), _lastselected[SS_FILL]); + sp_repr_css_set_property (css, "fill", c); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Apply last selected color to fill")); +} + +void SelectedStyle::on_stroke_lastselected() { + SPCSSAttr *css = sp_repr_css_attr_new (); + gchar c[64]; + sp_svg_write_color (c, sizeof(c), _lastselected[SS_STROKE]); + sp_repr_css_set_property (css, "stroke", c); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Apply last selected color to stroke")); +} + +void SelectedStyle::on_fill_invert() { + SPCSSAttr *css = sp_repr_css_attr_new (); + guint32 color = _thisselected[SS_FILL]; + gchar c[64]; + if (_mode[SS_FILL] == SS_LGRADIENT || _mode[SS_FILL] == SS_RGRADIENT) { + sp_gradient_invert_selected_gradients(_desktop, Inkscape::FOR_FILL); + return; + + } + + if (_mode[SS_FILL] != SS_COLOR) return; + sp_svg_write_color (c, sizeof(c), + SP_RGBA32_U_COMPOSE( + (255 - SP_RGBA32_R_U(color)), + (255 - SP_RGBA32_G_U(color)), + (255 - SP_RGBA32_B_U(color)), + SP_RGBA32_A_U(color) + ) + ); + sp_repr_css_set_property (css, "fill", c); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Invert fill")); +} + +void SelectedStyle::on_stroke_invert() { + SPCSSAttr *css = sp_repr_css_attr_new (); + guint32 color = _thisselected[SS_STROKE]; + gchar c[64]; + if (_mode[SS_STROKE] == SS_LGRADIENT || _mode[SS_STROKE] == SS_RGRADIENT) { + sp_gradient_invert_selected_gradients(_desktop, Inkscape::FOR_STROKE); + return; + } + if (_mode[SS_STROKE] != SS_COLOR) return; + sp_svg_write_color (c, sizeof(c), + SP_RGBA32_U_COMPOSE( + (255 - SP_RGBA32_R_U(color)), + (255 - SP_RGBA32_G_U(color)), + (255 - SP_RGBA32_B_U(color)), + SP_RGBA32_A_U(color) + ) + ); + sp_repr_css_set_property (css, "stroke", c); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Invert stroke")); +} + +void SelectedStyle::on_fill_white() { + SPCSSAttr *css = sp_repr_css_attr_new (); + gchar c[64]; + sp_svg_write_color (c, sizeof(c), 0xffffffff); + sp_repr_css_set_property (css, "fill", c); + sp_repr_css_set_property (css, "fill-opacity", "1"); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("White fill")); +} + +void SelectedStyle::on_stroke_white() { + SPCSSAttr *css = sp_repr_css_attr_new (); + gchar c[64]; + sp_svg_write_color (c, sizeof(c), 0xffffffff); + sp_repr_css_set_property (css, "stroke", c); + sp_repr_css_set_property (css, "stroke-opacity", "1"); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("White stroke")); +} + +void SelectedStyle::on_fill_black() { + SPCSSAttr *css = sp_repr_css_attr_new (); + gchar c[64]; + sp_svg_write_color (c, sizeof(c), 0x000000ff); + sp_repr_css_set_property (css, "fill", c); + sp_repr_css_set_property (css, "fill-opacity", "1.0"); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Black fill")); +} + +void SelectedStyle::on_stroke_black() { + SPCSSAttr *css = sp_repr_css_attr_new (); + gchar c[64]; + sp_svg_write_color (c, sizeof(c), 0x000000ff); + sp_repr_css_set_property (css, "stroke", c); + sp_repr_css_set_property (css, "stroke-opacity", "1.0"); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Black stroke")); +} + +void SelectedStyle::on_fill_copy() { + if (_mode[SS_FILL] == SS_COLOR) { + gchar c[64]; + sp_svg_write_color (c, sizeof(c), _thisselected[SS_FILL]); + Glib::ustring text; + text += c; + if (!text.empty()) { + Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get(); + refClipboard->set_text(text); + } + } +} + +void SelectedStyle::on_stroke_copy() { + if (_mode[SS_STROKE] == SS_COLOR) { + gchar c[64]; + sp_svg_write_color (c, sizeof(c), _thisselected[SS_STROKE]); + Glib::ustring text; + text += c; + if (!text.empty()) { + Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get(); + refClipboard->set_text(text); + } + } +} + +void SelectedStyle::on_fill_paste() { + Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get(); + Glib::ustring const text = refClipboard->wait_for_text(); + + if (!text.empty()) { + guint32 color = sp_svg_read_color(text.c_str(), 0x000000ff); // impossible value, as SVG color cannot have opacity + if (color == 0x000000ff) // failed to parse color string + return; + + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "fill", text.c_str()); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Paste fill")); + } +} + +void SelectedStyle::on_stroke_paste() { + Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get(); + Glib::ustring const text = refClipboard->wait_for_text(); + + if (!text.empty()) { + guint32 color = sp_svg_read_color(text.c_str(), 0x000000ff); // impossible value, as SVG color cannot have opacity + if (color == 0x000000ff) // failed to parse color string + return; + + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "stroke", text.c_str()); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Paste stroke")); + } +} + +void SelectedStyle::on_fillstroke_swap() { + _desktop->getSelection()->swapFillStroke(); +} + +void SelectedStyle::on_fill_edit() { + if (Dialog::FillAndStroke *fs = get_fill_and_stroke_panel(_desktop)) + fs->showPageFill(); +} + +void SelectedStyle::on_stroke_edit() { + if (Dialog::FillAndStroke *fs = get_fill_and_stroke_panel(_desktop)) + fs->showPageStrokePaint(); +} + +bool +SelectedStyle::on_fill_click(GdkEventButton *event) +{ + if (event->button == 1) { // click, open fill&stroke + + if (Dialog::FillAndStroke *fs = get_fill_and_stroke_panel(_desktop)) + fs->showPageFill(); + + } else if (event->button == 3) { // right-click, popup menu + _popup[SS_FILL].popup_at_pointer(reinterpret_cast<GdkEvent *>(event)); + } else if (event->button == 2) { // middle click, toggle none/lastcolor + if (_mode[SS_FILL] == SS_NONE) { + on_fill_lastused(); + } else { + on_fill_remove(); + } + } + return true; +} + +bool +SelectedStyle::on_stroke_click(GdkEventButton *event) +{ + if (event->button == 1) { // click, open fill&stroke + if (Dialog::FillAndStroke *fs = get_fill_and_stroke_panel(_desktop)) + fs->showPageStrokePaint(); + } else if (event->button == 3) { // right-click, popup menu + _popup[SS_STROKE].popup_at_pointer(reinterpret_cast<GdkEvent *>(event)); + } else if (event->button == 2) { // middle click, toggle none/lastcolor + if (_mode[SS_STROKE] == SS_NONE) { + on_stroke_lastused(); + } else { + on_stroke_remove(); + } + } + return true; +} + +bool +SelectedStyle::on_sw_click(GdkEventButton *event) +{ + if (event->button == 1) { // click, open fill&stroke + if (Dialog::FillAndStroke *fs = get_fill_and_stroke_panel(_desktop)) + fs->showPageStrokeStyle(); + } else if (event->button == 3) { // right-click, popup menu + _popup_sw.popup_at_pointer(reinterpret_cast<GdkEvent *>(event)); + } else if (event->button == 2) { // middle click, toggle none/lastwidth? + // + } + return true; +} + +bool +SelectedStyle::on_opacity_click(GdkEventButton *event) +{ + if (event->button == 2) { // middle click + const char* opacity = _opacity_sb.get_value() < 50? "0.5" : (_opacity_sb.get_value() == 100? "0" : "1"); + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "opacity", opacity); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE, + _("Change opacity")); + return true; + } + + return false; +} + +void SelectedStyle::on_popup_units(Inkscape::Util::Unit const *unit) { + _sw_unit = unit; + update(); +} + +void SelectedStyle::on_popup_preset(int i) { + SPCSSAttr *css = sp_repr_css_attr_new (); + gdouble w; + if (_sw_unit) { + w = Inkscape::Util::Quantity::convert(_sw_presets[i], _sw_unit, "px"); + } else { + w = _sw_presets[i]; + } + Inkscape::CSSOStringStream os; + os << w; + sp_repr_css_set_property (css, "stroke-width", os.str().c_str()); + // FIXME: update dash patterns! + sp_desktop_set_style (_desktop, css, true); + sp_repr_css_attr_unref (css); + DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_SWATCHES, + _("Change stroke width")); +} + +void +SelectedStyle::update() +{ + if (_desktop == nullptr) + return; + + // create temporary style + SPStyle query(_desktop->getDocument()); + + for (int i = SS_FILL; i <= SS_STROKE; i++) { + Gtk::EventBox *place = (i == SS_FILL)? &_fill_place : &_stroke_place; + Gtk::EventBox *flag_place = (i == SS_FILL)? &_fill_flag_place : &_stroke_flag_place; + + place->remove(); + flag_place->remove(); + + clearTooltip(*place); + clearTooltip(*flag_place); + + _mode[i] = SS_NA; + _paintserver_id[i].clear(); + + _popup_copy[i].set_sensitive(false); + + // query style from desktop. This returns a result flag and fills query with the style of subselection, if any, or selection + int result = sp_desktop_query_style (_desktop, &query, + (i == SS_FILL)? QUERY_STYLE_PROPERTY_FILL : QUERY_STYLE_PROPERTY_STROKE); + switch (result) { + case QUERY_STYLE_NOTHING: + place->add(_na[i]); + place->set_tooltip_text(__na[i]); + _mode[i] = SS_NA; + if ( _dropEnabled[i] ) { + gtk_drag_dest_unset( GTK_WIDGET((i==SS_FILL) ? _fill_place.gobj():_stroke_place.gobj()) ); + _dropEnabled[i] = false; + } + break; + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: + case QUERY_STYLE_MULTIPLE_SAME: + if ( !_dropEnabled[i] ) { + gtk_drag_dest_set( GTK_WIDGET( (i==SS_FILL) ? _fill_place.gobj():_stroke_place.gobj()), + GTK_DEST_DEFAULT_ALL, + ui_drop_target_entries, + nui_drop_target_entries, + GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE) ); + _dropEnabled[i] = true; + } + SPIPaint *paint; + if (i == SS_FILL) { + paint = &(query.fill); + } else { + paint = &(query.stroke); + } + if (paint->set && paint->isPaintserver()) { + SPPaintServer *server = (i == SS_FILL)? SP_STYLE_FILL_SERVER (&query) : SP_STYLE_STROKE_SERVER (&query); + if ( server ) { + Inkscape::XML::Node *srepr = server->getRepr(); + _paintserver_id[i] += "url(#"; + _paintserver_id[i] += srepr->attribute("id"); + _paintserver_id[i] += ")"; + + if (SP_IS_LINEARGRADIENT(server)) { + SPGradient *vector = SP_GRADIENT(server)->getVector(); + sp_gradient_image_set_gradient(SP_GRADIENT_IMAGE(_gradient_preview_l[i]), vector); + place->add(_gradient_box_l[i]); + place->set_tooltip_text(__lgradient[i]); + _mode[i] = SS_LGRADIENT; + } else if (SP_IS_RADIALGRADIENT(server)) { + SPGradient *vector = SP_GRADIENT(server)->getVector(); + sp_gradient_image_set_gradient(SP_GRADIENT_IMAGE(_gradient_preview_r[i]), vector); + place->add(_gradient_box_r[i]); + place->set_tooltip_text(__rgradient[i]); + _mode[i] = SS_RGRADIENT; +#ifdef WITH_MESH + } else if (SP_IS_MESHGRADIENT(server)) { + SPGradient *array = SP_GRADIENT(server)->getArray(); + sp_gradient_image_set_gradient(SP_GRADIENT_IMAGE(_gradient_preview_m[i]), array); + place->add(_gradient_box_m[i]); + place->set_tooltip_text(__mgradient[i]); + _mode[i] = SS_MGRADIENT; +#endif + } else if (SP_IS_PATTERN(server)) { + place->add(_pattern[i]); + place->set_tooltip_text(__pattern[i]); + _mode[i] = SS_PATTERN; + } else if (SP_IS_HATCH(server)) { + place->add(_hatch[i]); + place->set_tooltip_text(__hatch[i]); + _mode[i] = SS_HATCH; + } + } else { + g_warning ("file %s: line %d: Unknown paint server", __FILE__, __LINE__); + } + } else if (paint->set && paint->isColor()) { + guint32 color = paint->value.color.toRGBA32( + SP_SCALE24_TO_FLOAT ((i == SS_FILL)? query.fill_opacity.value : query.stroke_opacity.value)); + _lastselected[i] = _thisselected[i]; + _thisselected[i] = color; // include opacity + ((Inkscape::UI::Widget::ColorPreview*)_color_preview[i])->setRgba32 (color); + _color_preview[i]->show_all(); + place->add(*_color_preview[i]); + gchar c_string[64]; + g_snprintf (c_string, 64, "%06x/%.3g", color >> 8, SP_RGBA32_A_F(color)); + place->set_tooltip_text(__color[i] + ": " + c_string + _(", drag to adjust, middle-click to remove")); + _mode[i] = SS_COLOR; + _popup_copy[i].set_sensitive(true); + + } else if (paint->set && paint->isNone()) { + place->add(_none[i]); + place->set_tooltip_text(__none[i]); + _mode[i] = SS_NONE; + } else if (!paint->set) { + place->add(_unset[i]); + place->set_tooltip_text(__unset[i]); + _mode[i] = SS_UNSET; + } + if (result == QUERY_STYLE_MULTIPLE_AVERAGED) { + flag_place->add(_averaged[i]); + flag_place->set_tooltip_text(__averaged[i]); + } else if (result == QUERY_STYLE_MULTIPLE_SAME) { + flag_place->add(_multiple[i]); + flag_place->set_tooltip_text(__multiple[i]); + } + break; + case QUERY_STYLE_MULTIPLE_DIFFERENT: + place->add(_many[i]); + place->set_tooltip_text(__many[i]); + _mode[i] = SS_MANY; + break; + default: + break; + } + } + +// Now query opacity + clearTooltip(_opacity_place); + clearTooltip(_opacity_sb); + + int result = sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_MASTEROPACITY); + + switch (result) { + case QUERY_STYLE_NOTHING: + _opacity_place.set_tooltip_text(_("Nothing selected")); + _opacity_sb.set_tooltip_text(_("Nothing selected")); + _opacity_sb.set_sensitive(false); + break; + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: + case QUERY_STYLE_MULTIPLE_SAME: + _opacity_place.set_tooltip_text(_("Opacity (%)")); + _opacity_sb.set_tooltip_text(_("Opacity (%)")); + if (_opacity_blocked) break; + _opacity_blocked = true; + _opacity_sb.set_sensitive(true); + _opacity_adjustment->set_value(SP_SCALE24_TO_FLOAT(query.opacity.value) * 100); + _opacity_blocked = false; + break; + } + +// Now query stroke_width + int result_sw = sp_desktop_query_style (_desktop, &query, QUERY_STYLE_PROPERTY_STROKEWIDTH); + switch (result_sw) { + case QUERY_STYLE_NOTHING: + _stroke_width.set_markup(""); + current_stroke_width = 0; + break; + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: + case QUERY_STYLE_MULTIPLE_SAME: + { + double w; + if (_sw_unit) { + w = Inkscape::Util::Quantity::convert(query.stroke_width.computed, "px", _sw_unit); + } else { + w = query.stroke_width.computed; + } + current_stroke_width = w; + + { + gchar *str = g_strdup_printf(" %#.3g", w); + if (str[strlen(str) - 1] == ',' || str[strlen(str) - 1] == '.') { + str[strlen(str)-1] = '\0'; + } + _stroke_width.set_markup(str); + g_free (str); + } + { + gchar *str = g_strdup_printf(_("Stroke width: %.5g%s%s"), + w, + _sw_unit? _sw_unit->abbr.c_str() : "px", + (result_sw == QUERY_STYLE_MULTIPLE_AVERAGED)? + _(" (averaged)") : ""); + _stroke_width_place.set_tooltip_text(str); + g_free (str); + } + break; + } + default: + break; + } +} + +void SelectedStyle::opacity_0() {_opacity_sb.set_value(0);} +void SelectedStyle::opacity_025() {_opacity_sb.set_value(25);} +void SelectedStyle::opacity_05() {_opacity_sb.set_value(50);} +void SelectedStyle::opacity_075() {_opacity_sb.set_value(75);} +void SelectedStyle::opacity_1() {_opacity_sb.set_value(100);} + +void SelectedStyle::on_opacity_menu (Gtk::Menu *menu) { + + Glib::ListHandle<Gtk::Widget *> children = menu->get_children(); + for (auto iter : children) { + menu->remove(*iter); + } + + { + Gtk::MenuItem *item = new Gtk::MenuItem; + item->add(*(new Gtk::Label(_("0 (transparent)"), Gtk::ALIGN_START, Gtk::ALIGN_START))); + item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_0 )); + menu->add(*item); + } + { + Gtk::MenuItem *item = new Gtk::MenuItem; + item->add(*(new Gtk::Label("25%", Gtk::ALIGN_START, Gtk::ALIGN_START))); + item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_025 )); + menu->add(*item); + } + { + Gtk::MenuItem *item = new Gtk::MenuItem; + item->add(*(new Gtk::Label("50%", Gtk::ALIGN_START, Gtk::ALIGN_START))); + item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_05 )); + menu->add(*item); + } + { + Gtk::MenuItem *item = new Gtk::MenuItem; + item->add(*(new Gtk::Label("75%", Gtk::ALIGN_START, Gtk::ALIGN_START))); + item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_075 )); + menu->add(*item); + } + { + Gtk::MenuItem *item = new Gtk::MenuItem; + item->add(*(new Gtk::Label(_("100% (opaque)"), Gtk::ALIGN_START, Gtk::ALIGN_START))); + item->signal_activate().connect(sigc::mem_fun(*this, &SelectedStyle::opacity_1 )); + menu->add(*item); + } + + menu->show_all(); +} + +void SelectedStyle::on_opacity_changed () +{ + g_return_if_fail(_desktop); // TODO this shouldn't happen! + if (_opacity_blocked) + return; + _opacity_blocked = true; + SPCSSAttr *css = sp_repr_css_attr_new (); + Inkscape::CSSOStringStream os; + os << CLAMP ((_opacity_adjustment->get_value() / 100), 0.0, 1.0); + sp_repr_css_set_property (css, "opacity", os.str().c_str()); + // FIXME: workaround for GTK breakage: display interruptibility sometimes results in GTK + // sending multiple value-changed events. As if when Inkscape interrupts redraw for main loop + // iterations, GTK discovers that this callback hasn't finished yet, and for some weird reason + // decides to add yet another value-changed event to the queue. Totally braindead if you ask + // me. As a result, scrolling the spinbutton once results in runaway change until it hits 1.0 + // or 0.0. (And no, this is not a race with ::update, I checked that.) + // Sigh. So we disable interruptibility while we're setting the new value. + _desktop->getCanvas()->forceFullRedrawAfterInterruptions(0); + sp_desktop_set_style (_desktop, css); + sp_repr_css_attr_unref (css); + DocumentUndo::maybeDone(_desktop->getDocument(), "fillstroke:opacity", SP_VERB_DIALOG_FILL_STROKE, + _("Change opacity")); + // resume interruptibility + _desktop->getCanvas()->endForcedFullRedraws(); + // spinbutton_defocus(GTK_WIDGET(_opacity_sb.gobj())); + _opacity_blocked = false; +} + +/* ============================================= RotateableSwatch */ + +RotateableSwatch::RotateableSwatch(SelectedStyle *parent, guint mode) : + fillstroke(mode), + parent(parent), + startcolor(0), + startcolor_set(false), + undokey("ssrot1"), + cr(nullptr), + cr_set(false) + +{ +} + +RotateableSwatch::~RotateableSwatch() = default; + +double +RotateableSwatch::color_adjust(float *hsla, double by, guint32 cc, guint modifier) +{ + SPColor::rgb_to_hsl_floatv (hsla, SP_RGBA32_R_F(cc), SP_RGBA32_G_F(cc), SP_RGBA32_B_F(cc)); + hsla[3] = SP_RGBA32_A_F(cc); + double diff = 0; + if (modifier == 2) { // saturation + double old = hsla[1]; + if (by > 0) { + hsla[1] += by * (1 - hsla[1]); + } else { + hsla[1] += by * (hsla[1]); + } + diff = hsla[1] - old; + } else if (modifier == 1) { // lightness + double old = hsla[2]; + if (by > 0) { + hsla[2] += by * (1 - hsla[2]); + } else { + hsla[2] += by * (hsla[2]); + } + diff = hsla[2] - old; + } else if (modifier == 3) { // alpha + double old = hsla[3]; + hsla[3] += by/2; + if (hsla[3] < 0) { + hsla[3] = 0; + } else if (hsla[3] > 1) { + hsla[3] = 1; + } + diff = hsla[3] - old; + } else { // hue + double old = hsla[0]; + hsla[0] += by/2; + while (hsla[0] < 0) + hsla[0] += 1; + while (hsla[0] > 1) + hsla[0] -= 1; + diff = hsla[0] - old; + } + + float rgb[3]; + SPColor::hsl_to_rgb_floatv (rgb, hsla[0], hsla[1], hsla[2]); + + gchar c[64]; + sp_svg_write_color (c, sizeof(c), + SP_RGBA32_U_COMPOSE( + (SP_COLOR_F_TO_U(rgb[0])), + (SP_COLOR_F_TO_U(rgb[1])), + (SP_COLOR_F_TO_U(rgb[2])), + 0xff + ) + ); + + SPCSSAttr *css = sp_repr_css_attr_new (); + + if (modifier == 3) { // alpha + Inkscape::CSSOStringStream osalpha; + osalpha << hsla[3]; + sp_repr_css_set_property(css, (fillstroke == SS_FILL) ? "fill-opacity" : "stroke-opacity", osalpha.str().c_str()); + } else { + sp_repr_css_set_property (css, (fillstroke == SS_FILL) ? "fill" : "stroke", c); + } + sp_desktop_set_style (parent->getDesktop(), css); + sp_repr_css_attr_unref (css); + return diff; +} + +void +RotateableSwatch::do_motion(double by, guint modifier) { + if (parent->_mode[fillstroke] != SS_COLOR) + return; + + if (!scrolling && !cr_set) { + GtkWidget *w = GTK_WIDGET(gobj()); + GdkPixbuf *pixbuf = nullptr; + + if (modifier == 2) { // saturation + pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **)cursor_adj_s_xpm); + } else if (modifier == 1) { // lightness + pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **)cursor_adj_l_xpm); + } else if (modifier == 3) { // alpha + pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **)cursor_adj_a_xpm); + } else { // hue + pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **)cursor_adj_h_xpm); + } + + if (pixbuf != nullptr) { + cr = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), pixbuf, 16, 16); + g_object_unref(pixbuf); + gdk_window_set_cursor(gtk_widget_get_window(w), cr); + g_object_unref(cr); + cr = nullptr; + cr_set = true; + } + } + + guint32 cc; + if (!startcolor_set) { + cc = startcolor = parent->_thisselected[fillstroke]; + startcolor_set = true; + } else { + cc = startcolor; + } + + float hsla[4]; + double diff = 0; + + diff = color_adjust(hsla, by, cc, modifier); + + if (modifier == 3) { // alpha + DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, + SP_VERB_DIALOG_FILL_STROKE, (_("Adjust alpha"))); + double ch = hsla[3]; + parent->getDesktop()->event_context->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Adjusting <b>alpha</b>: was %.3g, now <b>%.3g</b> (diff %.3g); with <b>Ctrl</b> to adjust lightness, with <b>Shift</b> to adjust saturation, without modifiers to adjust hue"), ch - diff, ch, diff); + + } else if (modifier == 2) { // saturation + DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, + SP_VERB_DIALOG_FILL_STROKE, (_("Adjust saturation"))); + double ch = hsla[1]; + parent->getDesktop()->event_context->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Adjusting <b>saturation</b>: was %.3g, now <b>%.3g</b> (diff %.3g); with <b>Ctrl</b> to adjust lightness, with <b>Alt</b> to adjust alpha, without modifiers to adjust hue"), ch - diff, ch, diff); + + } else if (modifier == 1) { // lightness + DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, + SP_VERB_DIALOG_FILL_STROKE, (_("Adjust lightness"))); + double ch = hsla[2]; + parent->getDesktop()->event_context->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Adjusting <b>lightness</b>: was %.3g, now <b>%.3g</b> (diff %.3g); with <b>Shift</b> to adjust saturation, with <b>Alt</b> to adjust alpha, without modifiers to adjust hue"), ch - diff, ch, diff); + + } else { // hue + DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, + SP_VERB_DIALOG_FILL_STROKE, (_("Adjust hue"))); + double ch = hsla[0]; + parent->getDesktop()->event_context->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Adjusting <b>hue</b>: was %.3g, now <b>%.3g</b> (diff %.3g); with <b>Shift</b> to adjust saturation, with <b>Alt</b> to adjust alpha, with <b>Ctrl</b> to adjust lightness"), ch - diff, ch, diff); + } +} + + +void +RotateableSwatch::do_scroll(double by, guint modifier) { + do_motion(by/30.0, modifier); + do_release(by/30.0, modifier); +} + +void +RotateableSwatch::do_release(double by, guint modifier) { + if (parent->_mode[fillstroke] != SS_COLOR) + return; + + float hsla[4]; + color_adjust(hsla, by, startcolor, modifier); + + if (cr_set) { + GtkWidget *w = GTK_WIDGET(gobj()); + gdk_window_set_cursor(gtk_widget_get_window(w), nullptr); + if (cr) { + g_object_unref(cr); + cr = nullptr; + } + cr_set = false; + } + + if (modifier == 3) { // alpha + DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, + SP_VERB_DIALOG_FILL_STROKE, ("Adjust alpha")); + } else if (modifier == 2) { // saturation + DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, + SP_VERB_DIALOG_FILL_STROKE, ("Adjust saturation")); + + } else if (modifier == 1) { // lightness + DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, + SP_VERB_DIALOG_FILL_STROKE, ("Adjust lightness")); + + } else { // hue + DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, + SP_VERB_DIALOG_FILL_STROKE, ("Adjust hue")); + } + + if (!strcmp(undokey, "ssrot1")) { + undokey = "ssrot2"; + } else { + undokey = "ssrot1"; + } + + parent->getDesktop()->event_context->message_context->clear(); + startcolor_set = false; +} + +/* ============================================= RotateableStrokeWidth */ + +RotateableStrokeWidth::RotateableStrokeWidth(SelectedStyle *parent) : + parent(parent), + startvalue(0), + startvalue_set(false), + undokey("swrot1") +{ +} + +RotateableStrokeWidth::~RotateableStrokeWidth() = default; + +double +RotateableStrokeWidth::value_adjust(double current, double by, guint /*modifier*/, bool final) +{ + double newval; + // by is -1..1 + double max_f = 50; // maximum width is (current * max_f), minimum - zero + newval = current * (std::exp(std::log(max_f-1) * (by+1)) - 1) / (max_f-2); + + SPCSSAttr *css = sp_repr_css_attr_new (); + if (final && newval < 1e-6) { + // if dragged into zero and this is the final adjust on mouse release, delete stroke; + // if it's not final, leave it a chance to increase again (which is not possible with "none") + sp_repr_css_set_property (css, "stroke", "none"); + } else { + newval = Inkscape::Util::Quantity::convert(newval, parent->_sw_unit, "px"); + Inkscape::CSSOStringStream os; + os << newval; + sp_repr_css_set_property (css, "stroke-width", os.str().c_str()); + } + + sp_desktop_set_style (parent->getDesktop(), css); + sp_repr_css_attr_unref (css); + return newval - current; +} + +void +RotateableStrokeWidth::do_motion(double by, guint modifier) { + + // if this is the first motion after a mouse grab, remember the current width + if (!startvalue_set) { + startvalue = parent->current_stroke_width; + // if it's 0, adjusting (which uses multiplication) will not be able to change it, so we + // cheat and provide a non-zero value + if (startvalue == 0) + startvalue = 1; + startvalue_set = true; + } + + if (modifier == 3) { // Alt, do nothing + } else { + double diff = value_adjust(startvalue, by, modifier, false); + DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, + SP_VERB_DIALOG_FILL_STROKE, (_("Adjust stroke width"))); + parent->getDesktop()->event_context->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Adjusting <b>stroke width</b>: was %.3g, now <b>%.3g</b> (diff %.3g)"), startvalue, startvalue + diff, diff); + } +} + +void +RotateableStrokeWidth::do_release(double by, guint modifier) { + + if (modifier == 3) { // do nothing + + } else { + value_adjust(startvalue, by, modifier, true); + startvalue_set = false; + DocumentUndo::maybeDone(parent->getDesktop()->getDocument(), undokey, + SP_VERB_DIALOG_FILL_STROKE, (_("Adjust stroke width"))); + } + + if (!strcmp(undokey, "swrot1")) { + undokey = "swrot2"; + } else { + undokey = "swrot1"; + } + parent->getDesktop()->event_context->message_context->clear(); +} + +void +RotateableStrokeWidth::do_scroll(double by, guint modifier) { + do_motion(by/10.0, modifier); + startvalue_set = false; +} + +Dialog::FillAndStroke *get_fill_and_stroke_panel(SPDesktop *desktop) +{ + if (Dialog::PanelDialogBase *panel_dialog = + dynamic_cast<Dialog::PanelDialogBase *>(desktop->_dlg_mgr->getDialog("FillAndStroke"))) { + try { + Dialog::FillAndStroke &fill_and_stroke = + dynamic_cast<Dialog::FillAndStroke &>(panel_dialog->getPanel()); + return &fill_and_stroke; + } catch (std::exception &e) { } + } + + return nullptr; +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/selected-style.h b/src/ui/widget/selected-style.h new file mode 100644 index 0000000..388f802 --- /dev/null +++ b/src/ui/widget/selected-style.h @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * buliabyak@gmail.com + * scislac@users.sf.net + * + * Copyright (C) 2005 authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_CURRENT_STYLE_H +#define INKSCAPE_UI_CURRENT_STYLE_H + +#include <gtkmm/box.h> +#include <gtkmm/grid.h> + +#include <gtkmm/label.h> +#include <gtkmm/eventbox.h> +#include <gtkmm/enums.h> +#include <gtkmm/menu.h> +#include <gtkmm/menuitem.h> +#include <gtkmm/adjustment.h> +#include <gtkmm/radiobuttongroup.h> +#include <gtkmm/radiomenuitem.h> +#include "ui/widget/spinbutton.h" + +#include <cstddef> +#include <sigc++/sigc++.h> + +#include "rotateable.h" + +class SPDesktop; + +namespace Inkscape { + +namespace Util { + class Unit; +} + +namespace UI { +namespace Widget { + +enum { + SS_NA, + SS_NONE, + SS_UNSET, + SS_PATTERN, + SS_LGRADIENT, + SS_RGRADIENT, +#ifdef WITH_MESH + SS_MGRADIENT, +#endif + SS_MANY, + SS_COLOR, + SS_HATCH +}; + +enum { + SS_FILL, + SS_STROKE +}; + +class SelectedStyle; + +class RotateableSwatch : public Rotateable { + public: + RotateableSwatch(SelectedStyle *parent, guint mode); + ~RotateableSwatch() override; + + double color_adjust (float *hsl, double by, guint32 cc, guint state); + + void do_motion (double by, guint state) override; + void do_release (double by, guint state) override; + void do_scroll (double by, guint state) override; + +private: + guint fillstroke; + + SelectedStyle *parent; + + guint32 startcolor; + bool startcolor_set; + + gchar const *undokey; + + GdkCursor *cr; + bool cr_set; +}; + +class RotateableStrokeWidth : public Rotateable { + public: + RotateableStrokeWidth(SelectedStyle *parent); + ~RotateableStrokeWidth() override; + + double value_adjust(double current, double by, guint modifier, bool final); + void do_motion (double by, guint state) override; + void do_release (double by, guint state) override; + void do_scroll (double by, guint state) override; + +private: + SelectedStyle *parent; + + double startvalue; + bool startvalue_set; + + gchar const *undokey; +}; + +/** + * Selected style indicator (fill, stroke, opacity). + */ +class SelectedStyle : public Gtk::HBox +{ +public: + SelectedStyle(bool layout = true); + + ~SelectedStyle() override; + + void setDesktop(SPDesktop *desktop); + SPDesktop *getDesktop() {return _desktop;} + void update(); + + guint32 _lastselected[2]; + guint32 _thisselected[2]; + + guint _mode[2]; + + double current_stroke_width; + Inkscape::Util::Unit const *_sw_unit; // points to object in UnitTable, do not delete + +protected: + SPDesktop *_desktop; + + Gtk::Grid _table; + + Gtk::Label _fill_label; + Gtk::Label _stroke_label; + Gtk::Label _opacity_label; + + RotateableSwatch _fill_place; + RotateableSwatch _stroke_place; + + Gtk::EventBox _fill_flag_place; + Gtk::EventBox _stroke_flag_place; + + Gtk::EventBox _opacity_place; + Glib::RefPtr<Gtk::Adjustment> _opacity_adjustment; + Inkscape::UI::Widget::SpinButton _opacity_sb; + + Gtk::Label _na[2]; + Glib::ustring __na[2]; + + Gtk::Label _none[2]; + Glib::ustring __none[2]; + + Gtk::Label _pattern[2]; + Glib::ustring __pattern[2]; + + Gtk::Label _hatch[2]; + Glib::ustring __hatch[2]; + + Gtk::Label _lgradient[2]; + Glib::ustring __lgradient[2]; + + GtkWidget *_gradient_preview_l[2]; + Gtk::HBox _gradient_box_l[2]; + + Gtk::Label _rgradient[2]; + Glib::ustring __rgradient[2]; + + GtkWidget *_gradient_preview_r[2]; + Gtk::HBox _gradient_box_r[2]; + +#ifdef WITH_MESH + Gtk::Label _mgradient[2]; + Glib::ustring __mgradient[2]; + + GtkWidget *_gradient_preview_m[2]; + Gtk::HBox _gradient_box_m[2]; +#endif + + Gtk::Label _many[2]; + Glib::ustring __many[2]; + + Gtk::Label _unset[2]; + Glib::ustring __unset[2]; + + Gtk::Widget *_color_preview[2]; + Glib::ustring __color[2]; + + Gtk::Label _averaged[2]; + Glib::ustring __averaged[2]; + Gtk::Label _multiple[2]; + Glib::ustring __multiple[2]; + + Gtk::HBox _fill; + Gtk::HBox _stroke; + RotateableStrokeWidth _stroke_width_place; + Gtk::Label _stroke_width; + Gtk::Label _fill_empty_space; + + Glib::ustring _paintserver_id[2]; + + sigc::connection *selection_changed_connection; + sigc::connection *selection_modified_connection; + sigc::connection *subselection_changed_connection; + + static void dragDataReceived( GtkWidget *widget, + GdkDragContext *drag_context, + gint x, gint y, + GtkSelectionData *data, + guint info, + guint event_time, + gpointer user_data ); + + bool on_fill_click(GdkEventButton *event); + bool on_stroke_click(GdkEventButton *event); + bool on_opacity_click(GdkEventButton *event); + bool on_sw_click(GdkEventButton *event); + + bool _opacity_blocked; + void on_opacity_changed(); + void on_opacity_menu(Gtk::Menu *menu); + void opacity_0(); + void opacity_025(); + void opacity_05(); + void opacity_075(); + void opacity_1(); + + void on_fill_remove(); + void on_stroke_remove(); + void on_fill_lastused(); + void on_stroke_lastused(); + void on_fill_lastselected(); + void on_stroke_lastselected(); + void on_fill_unset(); + void on_stroke_unset(); + void on_fill_edit(); + void on_stroke_edit(); + void on_fillstroke_swap(); + void on_fill_invert(); + void on_stroke_invert(); + void on_fill_white(); + void on_stroke_white(); + void on_fill_black(); + void on_stroke_black(); + void on_fill_copy(); + void on_stroke_copy(); + void on_fill_paste(); + void on_stroke_paste(); + void on_fill_opaque(); + void on_stroke_opaque(); + + Gtk::Menu _popup[2]; + Gtk::MenuItem _popup_edit[2]; + Gtk::MenuItem _popup_lastused[2]; + Gtk::MenuItem _popup_lastselected[2]; + Gtk::MenuItem _popup_invert[2]; + Gtk::MenuItem _popup_white[2]; + Gtk::MenuItem _popup_black[2]; + Gtk::MenuItem _popup_copy[2]; + Gtk::MenuItem _popup_paste[2]; + Gtk::MenuItem _popup_swap[2]; + Gtk::MenuItem _popup_opaque[2]; + Gtk::MenuItem _popup_unset[2]; + Gtk::MenuItem _popup_remove[2]; + + Gtk::Menu _popup_sw; + Gtk::RadioButtonGroup _sw_group; + std::vector<Gtk::RadioMenuItem*> _unit_mis; + void on_popup_units(Inkscape::Util::Unit const *u); + void on_popup_preset(int i); + Gtk::MenuItem _popup_sw_remove; + + void *_drop[2]; + bool _dropEnabled[2]; +}; + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_BUTTON_H + +/* + 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 : diff --git a/src/ui/widget/spin-button-tool-item.cpp b/src/ui/widget/spin-button-tool-item.cpp new file mode 100644 index 0000000..b283939 --- /dev/null +++ b/src/ui/widget/spin-button-tool-item.cpp @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "spin-button-tool-item.h" + +#include <gtkmm/box.h> +#include <gtkmm/image.h> +#include <gtkmm/radiomenuitem.h> +#include <gtkmm/toolbar.h> + +#include <utility> + +#include "spinbutton.h" +#include "ui/icon-loader.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * \brief Handler for the button's "focus-in-event" signal + * + * \param focus_event The event that triggered the signal + * + * \detail This just logs the current value of the spin-button + * and sets the _transfer_focus flag + */ +bool +SpinButtonToolItem::on_btn_focus_in_event(GdkEventFocus * /* focus_event */) +{ + _last_val = _btn->get_value(); + _transfer_focus = true; + + return false; // Event not consumed +} + +/** + * \brief Handler for the button's "focus-out-event" signal + * + * \param focus_event The event that triggered the signal + * + * \detail This just unsets the _transfer_focus flag + */ +bool +SpinButtonToolItem::on_btn_focus_out_event(GdkEventFocus * /* focus_event */) +{ + _transfer_focus = false; + + return false; // Event not consumed +} + +/** + * \brief Handler for the button's "key-press-event" signal + * + * \param key_event The event that triggered the signal + * + * \detail If the ESC key was pressed, restore the last value and defocus. + * If the Enter key was pressed, just defocus. + */ +bool +SpinButtonToolItem::on_btn_key_press_event(GdkEventKey *key_event) +{ + bool was_consumed = false; // Whether event has been consumed or not + auto display = Gdk::Display::get_default(); + auto keymap = display->get_keymap(); + guint key = 0; + gdk_keymap_translate_keyboard_state(keymap, key_event->hardware_keycode, + static_cast<GdkModifierType>(key_event->state), + 0, &key, 0, 0, 0); + + auto val = _btn->get_value(); + + switch(key) { + case GDK_KEY_Escape: + { + _transfer_focus = true; + _btn->set_value(_last_val); + defocus(); + was_consumed = true; + } + break; + + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + { + _transfer_focus = true; + defocus(); + was_consumed = true; + } + break; + + case GDK_KEY_Tab: + { + _transfer_focus = false; + was_consumed = process_tab(1); + } + break; + + case GDK_KEY_ISO_Left_Tab: + { + _transfer_focus = false; + was_consumed = process_tab(-1); + } + break; + + // TODO: Enable variable step-size if this is ever used + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + { + _transfer_focus = false; + _btn->set_value(val+1); + was_consumed=true; + } + break; + + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + { + _transfer_focus = false; + _btn->set_value(val-1); + was_consumed=true; + } + break; + + case GDK_KEY_Page_Up: + case GDK_KEY_KP_Page_Up: + { + _transfer_focus = false; + _btn->set_value(val+10); + was_consumed=true; + } + break; + + case GDK_KEY_Page_Down: + case GDK_KEY_KP_Page_Down: + { + _transfer_focus = false; + _btn->set_value(val-10); + was_consumed=true; + } + break; + + case GDK_KEY_z: + case GDK_KEY_Z: + { + _transfer_focus = false; + _btn->set_value(_last_val); + was_consumed = true; + } + break; + } + + return was_consumed; +} + +/** + * \brief Shift focus to a different widget + * + * \details This only has an effect if the _transfer_focus flag and the _focus_widget are set + */ +void +SpinButtonToolItem::defocus() +{ + if(_transfer_focus && _focus_widget) { + _focus_widget->grab_focus(); + } +} + +/** + * \brief Move focus to another spinbutton in the toolbar + * + * \param increment[in] The number of places to shift within the toolbar + */ +bool +SpinButtonToolItem::process_tab(int increment) +{ + // If the increment is zero, do nothing + if(increment == 0) return true; + + // Here, we're working through the widget hierarchy: + // Toolbar + // |- ToolItem (*this) + // |-> Box + // |-> SpinButton (*_btn) + // + // Our aim is to find the next/previous spin-button within a toolitem in our toolbar + + bool handled = false; + + // We only bother doing this if the current item is actually in a toolbar! + auto toolbar = dynamic_cast<Gtk::Toolbar *>(get_parent()); + + if (toolbar) { + // Get the index of the current item within the toolbar and the total number of items + auto my_index = toolbar->get_item_index(*this); + auto n_items = toolbar->get_n_items(); + + auto test_index = my_index + increment; // The index of the item we want to check + + // Loop through tool items as long as we're within the bounds of the toolbar and + // we haven't yet found our new item to focus on + while(test_index > 0 && test_index <= n_items && !handled) { + + auto tool_item = toolbar->get_nth_item(test_index); + + if(tool_item) { + // There are now two options that we support: + if(dynamic_cast<SpinButtonToolItem *>(tool_item)) { + // (1) The tool item is a SpinButtonToolItem, in which case, we just pass + // focus to its spin-button + dynamic_cast<SpinButtonToolItem *>(tool_item)->grab_button_focus(); + handled = true; + } + else if(dynamic_cast<Gtk::SpinButton *>(tool_item->get_child())) { + // (2) The tool item contains a plain Gtk::SpinButton, in which case we + // pass focus directly to it + tool_item->get_child()->grab_focus(); + } + } + + test_index += increment; + } + } + + return handled; +} + +/** + * \brief Handler for toggle events on numeric menu items + * + * \details Sets the adjustment to the desired value + */ +void +SpinButtonToolItem::on_numeric_menu_item_toggled(double value) +{ + auto adj = _btn->get_adjustment(); + adj->set_value(value); +} + +Gtk::RadioMenuItem * +SpinButtonToolItem::create_numeric_menu_item(Gtk::RadioButtonGroup *group, + double value, + const Glib::ustring& label) +{ + // Represent the value as a string + std::ostringstream ss; + ss << value; + + // Append the label if specified + if (!label.empty()) { + ss << ": " << label; + } + + auto numeric_option = Gtk::manage(new Gtk::RadioMenuItem(*group, ss.str())); + + // Set the adjustment value in response to changes in the selected item + auto toggled_handler = sigc::bind(sigc::mem_fun(*this, &SpinButtonToolItem::on_numeric_menu_item_toggled), value); + numeric_option->signal_toggled().connect(toggled_handler); + + return numeric_option; +} + +/** + * \brief Create a menu containing fixed numeric options for the adjustment + * + * \details Each of these values represents a snap-point for the adjustment's value + */ +Gtk::Menu * +SpinButtonToolItem::create_numeric_menu() +{ + auto numeric_menu = Gtk::manage(new Gtk::Menu()); + + Gtk::RadioMenuItem::Group group; + + // Get values for the adjustment + auto adj = _btn->get_adjustment(); + auto adj_value = adj->get_value(); + auto lower = adj->get_lower(); + auto upper = adj->get_upper(); + auto step = adj->get_step_increment(); + auto page = adj->get_page_increment(); + + auto digits = _btn->get_digits(); + + // A number a little smaller than the smallest increment that can be + // displayed in the spinbutton entry. + // + // For example, if digits = 0, we are displaying integers only and + // epsilon = 0.9 * 10^-0 = 0.9 + // + // For digits = 1, we get epsilon = 0.9 * 10^-1 = 0.09 + // For digits = 2, we get epsilon = 0.9 * 10^-2 = 0.009 etc... + auto epsilon = 0.9 * pow(10.0, -float(digits)); + + // Start by setting some fixed values based on the adjustment's + // parameters. + NumericMenuData values; + values.push_back(std::make_pair(upper, "")); + values.push_back(std::make_pair(adj_value + page, "")); + values.push_back(std::make_pair(adj_value + step, "")); + values.push_back(std::make_pair(adj_value, "")); + values.push_back(std::make_pair(adj_value - step, "")); + values.push_back(std::make_pair(adj_value - page, "")); + values.push_back(std::make_pair(lower, "")); + + // Now add any custom items + for (auto custom_data : _custom_menu_data) { + values.push_back(custom_data); + } + + // Sort the numeric menu items into reverse numerical order (largest at top of menu) + std::sort (begin(values), end(values)); + std::reverse(begin(values), end(values)); + + for (auto value : values) + { + auto numeric_menu_item = create_numeric_menu_item(&group, value.first, value.second); + numeric_menu->append(*numeric_menu_item); + + if (fabs(adj_value - value.first) < epsilon) { + // If the adjustment value is very close to the value of this menu item, + // make this menu item active + numeric_menu_item->set_active(); + } + } + + return numeric_menu; +} + +/** + * \brief Create a menu-item in response to the "create-menu-proxy" signal + * + * \detail This is an override for the default Gtk::ToolItem handler so + * we don't need to explicitly connect this to the signal. It + * runs if the toolitem is unable to fit on the toolbar, and + * must be represented by a menu item instead. + */ +bool +SpinButtonToolItem::on_create_menu_proxy() +{ + // The main menu-item. It just contains the label that normally appears + // next to the spin-button, and an indicator for a sub-menu. + auto menu_item = Gtk::manage(new Gtk::MenuItem(_label_text)); + auto numeric_menu = create_numeric_menu(); + menu_item->set_submenu(*numeric_menu); + + set_proxy_menu_item(_name, *menu_item); + + return true; // Finished handling the event +} + +/** + * \brief Create a new SpinButtonToolItem + * + * \param[in] name A unique ID for this tool-item (not translatable) + * \param[in] label_text The text to display in the toolbar + * \param[in] adjustment The Gtk::Adjustment to attach to the spinbutton + * \param[in] climb_rate The climb rate for the spin button (default = 0) + * \param[in] digits Number of decimal places to display + */ +SpinButtonToolItem::SpinButtonToolItem(const Glib::ustring name, + const Glib::ustring& label_text, + Glib::RefPtr<Gtk::Adjustment>& adjustment, + double climb_rate, + int digits) + : _btn(Gtk::manage(new SpinButton(adjustment, climb_rate, digits))), + _name(std::move(name)), + _label_text(label_text), + _last_val(0.0), + _transfer_focus(false), + _focus_widget(nullptr) +{ + set_margin_start(3); + set_margin_end(3); + set_name(_name); + + // Handle popup menu + _btn->signal_popup_menu().connect(sigc::mem_fun(*this, &SpinButtonToolItem::on_popup_menu), false); + + // Handle button events + auto btn_focus_in_event_cb = sigc::mem_fun(*this, &SpinButtonToolItem::on_btn_focus_in_event); + _btn->signal_focus_in_event().connect(btn_focus_in_event_cb, false); + + auto btn_focus_out_event_cb = sigc::mem_fun(*this, &SpinButtonToolItem::on_btn_focus_out_event); + _btn->signal_focus_out_event().connect(btn_focus_out_event_cb, false); + + auto btn_key_press_event_cb = sigc::mem_fun(*this, &SpinButtonToolItem::on_btn_key_press_event); + _btn->signal_key_press_event().connect(btn_key_press_event_cb, false); + + auto btn_button_press_event_cb = sigc::mem_fun(*this, &SpinButtonToolItem::on_btn_button_press_event); + _btn->signal_button_press_event().connect(btn_button_press_event_cb, false); + + _btn->add_events(Gdk::KEY_PRESS_MASK); + + // Create a label + _label = Gtk::manage(new Gtk::Label(label_text)); + + // Arrange the widgets in a horizontal box + _hbox = Gtk::manage(new Gtk::Box()); + _hbox->set_spacing(3); + _hbox->pack_start(*_label); + _hbox->pack_start(*_btn); + add(*_hbox); + show_all(); +} + +void +SpinButtonToolItem::set_icon(const Glib::ustring& icon_name) +{ + _hbox->remove(*_label); + _icon = Gtk::manage(sp_get_icon_image(icon_name, Gtk::ICON_SIZE_SMALL_TOOLBAR)); + + if(_icon) { + _hbox->pack_start(*_icon); + _hbox->reorder_child(*_icon, 0); + } + + show_all(); +} + +bool +SpinButtonToolItem::on_btn_button_press_event(const GdkEventButton *button_event) +{ + if (gdk_event_triggers_context_menu(reinterpret_cast<const GdkEvent *>(button_event)) && + button_event->type == GDK_BUTTON_PRESS) { + do_popup_menu(button_event); + return true; + } + + return false; +} + +void +SpinButtonToolItem::do_popup_menu(const GdkEventButton *button_event) +{ + auto menu = create_numeric_menu(); + menu->attach_to_widget(*_btn); + menu->show_all(); + menu->popup_at_pointer(reinterpret_cast<const GdkEvent *>(button_event)); +} + +/** + * \brief Create a popup menu + */ +bool +SpinButtonToolItem::on_popup_menu() +{ + do_popup_menu(nullptr); + return true; +} + +/** + * \brief Transfers focus to the child spinbutton by default + */ +void +SpinButtonToolItem::on_grab_focus() +{ + grab_button_focus(); +} + +/** + * \brief Set the tooltip to display on this (and all child widgets) + * + * \param[in] text The tooltip to display + */ +void +SpinButtonToolItem::set_all_tooltip_text(const Glib::ustring& text) +{ + set_tooltip_text(text); + _btn->set_tooltip_text(text); +} + +/** + * \brief Set the widget that focus moves to when this one loses focus + * + * \param widget The widget that will gain focus + */ +void +SpinButtonToolItem::set_focus_widget(Gtk::Widget *widget) +{ + _focus_widget = widget; +} + +/** + * \brief Grab focus on the spin-button widget + */ +void +SpinButtonToolItem::grab_button_focus() +{ + _btn->grab_focus(); +} + +void +SpinButtonToolItem::set_custom_numeric_menu_data(std::vector<double>& values, + const std::vector<Glib::ustring>& labels) +{ + if(values.size() != labels.size() && !labels.empty()) { + g_warning("Cannot add custom menu items. Value and label arrays are different sizes"); + return; + } + + _custom_menu_data.clear(); + + int i = 0; + + for (auto value : values) { + if(labels.empty()) { + _custom_menu_data.push_back(std::make_pair(value, "")); + } + else { + _custom_menu_data.push_back(std::make_pair(value, labels[i++])); + } + } +} + +Glib::RefPtr<Gtk::Adjustment> +SpinButtonToolItem::get_adjustment() +{ + return _btn->get_adjustment(); +} +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/spin-button-tool-item.h b/src/ui/widget/spin-button-tool-item.h new file mode 100644 index 0000000..c073f56 --- /dev/null +++ b/src/ui/widget/spin-button-tool-item.h @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_SPIN_BUTTON_TOOL_ITEM_H +#define SEEN_SPIN_BUTTON_TOOL_ITEM_H + +#include <gtkmm/toolitem.h> + +namespace Gtk { +class Box; +class RadioButtonGroup; +class RadioMenuItem; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +class SpinButton; + +/** + * \brief A spin-button with a label that can be added to a toolbar + */ +class SpinButtonToolItem : public Gtk::ToolItem +{ +private: + typedef std::vector< std::pair<double, Glib::ustring> > NumericMenuData; + + Glib::ustring _name; ///< A unique ID for the widget (NOT translatable) + SpinButton *_btn; ///< The spin-button within the widget + Glib::ustring _label_text; ///< A string to use in labels for the widget (translatable) + double _last_val; ///< The last value of the adjustment + bool _transfer_focus; ///< Whether or not to transfer focus + + Gtk::Box *_hbox; ///< Horizontal box, to store widgets + Gtk::Widget *_label; ///< A text label to describe the setting + Gtk::Widget *_icon; ///< An icon to describe the setting + + /** A widget that grabs focus when this one loses it */ + Gtk::Widget * _focus_widget; + + // Custom values and labels to add to the numeric popup-menu + NumericMenuData _custom_menu_data; + + // Event handlers + bool on_btn_focus_in_event(GdkEventFocus *focus_event); + bool on_btn_focus_out_event(GdkEventFocus *focus_event); + bool on_btn_key_press_event(GdkEventKey *key_event); + bool on_btn_button_press_event(const GdkEventButton *button_event); + bool on_popup_menu(); + void do_popup_menu(const GdkEventButton *button_event); + + void defocus(); + bool process_tab(int direction); + + void on_numeric_menu_item_toggled(double value); + + Gtk::Menu * create_numeric_menu(); + + Gtk::RadioMenuItem * create_numeric_menu_item(Gtk::RadioButtonGroup *group, + double value, + const Glib::ustring& label = ""); + +protected: + bool on_create_menu_proxy() override; + void on_grab_focus() override; + +public: + SpinButtonToolItem(const Glib::ustring name, + const Glib::ustring& label_text, + Glib::RefPtr<Gtk::Adjustment>& adjustment, + double climb_rate = 0.1, + int digits = 3); + + void set_all_tooltip_text(const Glib::ustring& text); + void set_focus_widget(Gtk::Widget *widget); + void grab_button_focus(); + + void set_custom_numeric_menu_data(std::vector<double>& values, + const std::vector<Glib::ustring>& labels = std::vector<Glib::ustring>()); + Glib::RefPtr<Gtk::Adjustment> get_adjustment(); + void set_icon(const Glib::ustring& icon_name); +}; +} // namespace Widget +} // namespace UI +} // namespace Inkscape +#endif // SEEN_SPIN_BUTTON_TOOL_ITEM_H +/* + 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 : diff --git a/src/ui/widget/spin-scale.cpp b/src/ui/widget/spin-scale.cpp new file mode 100644 index 0000000..5e3a2a2 --- /dev/null +++ b/src/ui/widget/spin-scale.cpp @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * + * Copyright (C) 2012 Author + * 2017 Tavmjong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "spin-scale.h" + +#include <glibmm/i18n.h> +#include <glibmm/stringutils.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +SpinScale::SpinScale(const Glib::ustring label, double value, + double lower, double upper, + double step_increment, double page_increment, int digits, + const SPAttributeEnum a, const Glib::ustring tip_text) + : AttrWidget(a, value) + , _inkspinscale(value, lower, upper, step_increment, page_increment, 0) +{ + set_name("SpinScale"); + + _inkspinscale.set_label (label); + _inkspinscale.set_digits (digits); + _inkspinscale.set_tooltip_text (tip_text); + + _adjustment = _inkspinscale.get_adjustment(); + + signal_value_changed().connect(signal_attr_changed().make_slot()); + + pack_start(_inkspinscale); + + show_all_children(); +} + +SpinScale::SpinScale(const Glib::ustring label, + Glib::RefPtr<Gtk::Adjustment> adjustment, int digits, + const SPAttributeEnum a, const Glib::ustring tip_text) + : AttrWidget(a, 0.0) + , _inkspinscale(adjustment) +{ + set_name("SpinScale"); + + _inkspinscale.set_label (label); + _inkspinscale.set_digits (digits); + _inkspinscale.set_tooltip_text (tip_text); + + _adjustment = _inkspinscale.get_adjustment(); + + signal_value_changed().connect(signal_attr_changed().make_slot()); + + pack_start(_inkspinscale); + + show_all_children(); +} + +Glib::ustring SpinScale::get_as_attribute() const +{ + const double val = _adjustment->get_value(); + + if( _inkspinscale.get_digits() == 0) + return Glib::Ascii::dtostr((int)val); + else + return Glib::Ascii::dtostr(val); +} + +void SpinScale::set_from_attribute(SPObject* o) +{ + const gchar* val = attribute_value(o); + if (val) + _adjustment->set_value(Glib::Ascii::strtod(val)); + else + _adjustment->set_value(get_default()->as_double()); +} + +Glib::SignalProxy0<void> SpinScale::signal_value_changed() +{ + return _adjustment->signal_value_changed(); +} + +double SpinScale::get_value() const +{ + return _adjustment->get_value(); +} + +void SpinScale::set_value(const double val) +{ + _adjustment->set_value(val); +} + +void SpinScale::set_focuswidget(GtkWidget *widget) +{ + _inkspinscale.set_focus_widget(widget); +} + +const decltype(SpinScale::_adjustment) SpinScale::get_adjustment() const +{ + return _adjustment; +} + +decltype(SpinScale::_adjustment) SpinScale::get_adjustment() +{ + return _adjustment; +} + + +DualSpinScale::DualSpinScale(const Glib::ustring label1, const Glib::ustring label2, + double value, double lower, double upper, + double step_increment, double page_increment, int digits, + const SPAttributeEnum a, + const Glib::ustring tip_text1, const Glib::ustring tip_text2) + : AttrWidget(a), + _s1(label1, value, lower, upper, step_increment, page_increment, digits, SP_ATTR_INVALID, tip_text1), + _s2(label2, value, lower, upper, step_increment, page_increment, digits, SP_ATTR_INVALID, tip_text2), + //TRANSLATORS: "Link" means to _link_ two sliders together + _link(C_("Sliders", "Link")) +{ + set_name("DualSpinScale"); + signal_value_changed().connect(signal_attr_changed().make_slot()); + + _s1.get_adjustment()->signal_value_changed().connect(_signal_value_changed.make_slot()); + _s2.get_adjustment()->signal_value_changed().connect(_signal_value_changed.make_slot()); + _s1.get_adjustment()->signal_value_changed().connect(sigc::mem_fun(*this, &DualSpinScale::update_linked)); + + _link.signal_toggled().connect(sigc::mem_fun(*this, &DualSpinScale::link_toggled)); + + Gtk::Box* vb = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + vb->add(_s1); + vb->add(_s2); + pack_start(*vb); + pack_start(_link, false, false); + _link.set_active(true); + + show_all(); +} + +Glib::ustring DualSpinScale::get_as_attribute() const +{ + if(_link.get_active()) + return _s1.get_as_attribute(); + else + return _s1.get_as_attribute() + " " + _s2.get_as_attribute(); +} + +void DualSpinScale::set_from_attribute(SPObject* o) +{ + const gchar* val = attribute_value(o); + if(val) { + // Split val into parts + gchar** toks = g_strsplit(val, " ", 2); + + if(toks) { + double v1 = 0.0, v2 = 0.0; + if(toks[0]) + v1 = v2 = Glib::Ascii::strtod(toks[0]); + if(toks[1]) + v2 = Glib::Ascii::strtod(toks[1]); + + _link.set_active(toks[1] == nullptr); + + _s1.get_adjustment()->set_value(v1); + _s2.get_adjustment()->set_value(v2); + + g_strfreev(toks); + } + } +} + +sigc::signal<void>& DualSpinScale::signal_value_changed() +{ + return _signal_value_changed; +} + +const SpinScale& DualSpinScale::get_SpinScale1() const +{ + return _s1; +} + +SpinScale& DualSpinScale::get_SpinScale1() +{ + return _s1; +} + +const SpinScale& DualSpinScale::get_SpinScale2() const +{ + return _s2; +} + +SpinScale& DualSpinScale::get_SpinScale2() +{ + return _s2; +} + +void DualSpinScale::link_toggled() +{ + _s2.set_sensitive(!_link.get_active()); + update_linked(); +} + +void DualSpinScale::update_linked() +{ + if(_link.get_active()) + _s2.set_value(_s1.get_value()); +} + + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/spin-scale.h b/src/ui/widget/spin-scale.h new file mode 100644 index 0000000..b154cb3 --- /dev/null +++ b/src/ui/widget/spin-scale.h @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * + * Copyright (C) 2012 Author + * 2017 Tavmjong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_SPIN_SCALE_H +#define INKSCAPE_UI_WIDGET_SPIN_SCALE_H + +#include <gtkmm/adjustment.h> +#include <gtkmm/box.h> +#include <gtkmm/togglebutton.h> +#include "attr-widget.h" +#include "ink-spinscale.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Wrap the InkSpinScale class and attach an attribute. + * A combo widget with label, scale slider, spinbutton, and adjustment; + */ +class SpinScale : public Gtk::Box, public AttrWidget +{ + +public: + SpinScale(const Glib::ustring label, double value, + double lower, double upper, + double step_increment, double page_increment, int digits, + const SPAttributeEnum a = SP_ATTR_INVALID, const Glib::ustring tip_text = ""); + + // Used by extensions + SpinScale(const Glib::ustring label, + Glib::RefPtr<Gtk::Adjustment> adjustment, int digits, + const SPAttributeEnum a = SP_ATTR_INVALID, const Glib::ustring tip_text = ""); + + Glib::ustring get_as_attribute() const override; + void set_from_attribute(SPObject*) override; + + // Shortcuts to _adjustment + Glib::SignalProxy0<void> signal_value_changed(); + double get_value() const; + void set_value(const double); + void set_focuswidget(GtkWidget *widget); + +private: + Glib::RefPtr<Gtk::Adjustment> _adjustment; + InkSpinScale _inkspinscale; + +public: + const decltype(_adjustment) get_adjustment() const; + decltype(_adjustment) get_adjustment(); +}; + + +/** + * Contains two SpinScales for controlling number-opt-number attributes. + * + * @see SpinScale + */ +class DualSpinScale : public Gtk::Box, public AttrWidget +{ +public: + DualSpinScale(const Glib::ustring label1, const Glib::ustring label2, + double value, double lower, double upper, + double step_increment, double page_increment, int digits, + const SPAttributeEnum a, + const Glib::ustring tip_text1, const Glib::ustring tip_text2); + + Glib::ustring get_as_attribute() const override; + void set_from_attribute(SPObject*) override; + + sigc::signal<void>& signal_value_changed(); + + const SpinScale& get_SpinScale1() const; + SpinScale& get_SpinScale1(); + + const SpinScale& get_SpinScale2() const; + SpinScale& get_SpinScale2(); + + //void remove_scale(); +private: + void link_toggled(); + void update_linked(); + sigc::signal<void> _signal_value_changed; + SpinScale _s1, _s2; + Gtk::ToggleButton _link; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_SPIN_SCALE_H + +/* + 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 : diff --git a/src/ui/widget/spin-slider.cpp b/src/ui/widget/spin-slider.cpp new file mode 100644 index 0000000..e4cd0c6 --- /dev/null +++ b/src/ui/widget/spin-slider.cpp @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Nicholas Bishop <nicholasbishop@gmail.com> + * Felipe C. da S. Sanches <juca@members.fsf.org> + * + * Copyright (C) 2007 Author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "spin-slider.h" + +#include <glibmm/i18n.h> +#include <glibmm/stringutils.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +SpinSlider::SpinSlider(double value, double lower, double upper, double step_inc, + double climb_rate, int digits, const SPAttributeEnum a, const char* tip_text) + : AttrWidget(a, value), + _adjustment(Gtk::Adjustment::create(value, lower, upper, step_inc)), + _scale(_adjustment), _spin(_adjustment, climb_rate, digits) +{ + set_name("SpinSlider"); + signal_value_changed().connect(signal_attr_changed().make_slot()); + + pack_start(_scale); + pack_start(_spin, false, false); + if (tip_text){ + _scale.set_tooltip_text(tip_text); + _spin.set_tooltip_text(tip_text); + } + + _scale.set_draw_value(false); + + show_all_children(); +} + +Glib::ustring SpinSlider::get_as_attribute() const +{ + const auto val = _adjustment->get_value(); + + if(_spin.get_digits() == 0) + return Glib::Ascii::dtostr((int)val); + else + return Glib::Ascii::dtostr(val); +} + +void SpinSlider::set_from_attribute(SPObject* o) +{ + const gchar* val = attribute_value(o); + if(val) + _adjustment->set_value(Glib::Ascii::strtod(val)); + else + _adjustment->set_value(get_default()->as_double()); +} + +Glib::SignalProxy0<void> SpinSlider::signal_value_changed() +{ + return _adjustment->signal_value_changed(); +} + +double SpinSlider::get_value() const +{ + return _adjustment->get_value(); +} + +void SpinSlider::set_value(const double val) +{ + _adjustment->set_value(val); +} + +const decltype(SpinSlider::_adjustment) SpinSlider::get_adjustment() const +{ + return _adjustment; +} + +decltype(SpinSlider::_adjustment) SpinSlider::get_adjustment() +{ + return _adjustment; +} + +const Gtk::Scale& SpinSlider::get_scale() const +{ + return _scale; +} + +Gtk::Scale& SpinSlider::get_scale() +{ + return _scale; +} + +const Inkscape::UI::Widget::SpinButton& SpinSlider::get_spin_button() const +{ + return _spin; +} +Inkscape::UI::Widget::SpinButton& SpinSlider::get_spin_button() +{ + return _spin; +} + +void SpinSlider::remove_scale() +{ + remove(_scale); +} + +DualSpinSlider::DualSpinSlider(double value, double lower, double upper, double step_inc, + double climb_rate, int digits, const SPAttributeEnum a, char* tip_text1, char* tip_text2) + : AttrWidget(a), + _s1(value, lower, upper, step_inc, climb_rate, digits, SP_ATTR_INVALID, tip_text1), + _s2(value, lower, upper, step_inc, climb_rate, digits, SP_ATTR_INVALID, tip_text2), + //TRANSLATORS: "Link" means to _link_ two sliders together + _link(C_("Sliders", "Link")) +{ + signal_value_changed().connect(signal_attr_changed().make_slot()); + + _s1.get_adjustment()->signal_value_changed().connect(_signal_value_changed.make_slot()); + _s2.get_adjustment()->signal_value_changed().connect(_signal_value_changed.make_slot()); + _s1.get_adjustment()->signal_value_changed().connect(sigc::mem_fun(*this, &DualSpinSlider::update_linked)); + _link.signal_toggled().connect(sigc::mem_fun(*this, &DualSpinSlider::link_toggled)); + + Gtk::VBox* vb = Gtk::manage(new Gtk::VBox); + vb->add(_s1); + vb->add(_s2); + pack_start(*vb); + pack_start(_link, false, false); + _link.set_active(true); + + show_all(); +} + +Glib::ustring DualSpinSlider::get_as_attribute() const +{ + if(_link.get_active()) + return _s1.get_as_attribute(); + else + return _s1.get_as_attribute() + " " + _s2.get_as_attribute(); +} + +void DualSpinSlider::set_from_attribute(SPObject* o) +{ + const gchar* val = attribute_value(o); + if(val) { + // Split val into parts + gchar** toks = g_strsplit(val, " ", 2); + + if(toks) { + double v1 = 0.0, v2 = 0.0; + if(toks[0]) + v1 = v2 = Glib::Ascii::strtod(toks[0]); + if(toks[1]) + v2 = Glib::Ascii::strtod(toks[1]); + + _link.set_active(toks[1] == nullptr); + + _s1.get_adjustment()->set_value(v1); + _s2.get_adjustment()->set_value(v2); + + g_strfreev(toks); + } + } +} + +sigc::signal<void>& DualSpinSlider::signal_value_changed() +{ + return _signal_value_changed; +} + +const SpinSlider& DualSpinSlider::get_spinslider1() const +{ + return _s1; +} + +SpinSlider& DualSpinSlider::get_spinslider1() +{ + return _s1; +} + +const SpinSlider& DualSpinSlider::get_spinslider2() const +{ + return _s2; +} + +SpinSlider& DualSpinSlider::get_spinslider2() +{ + return _s2; +} + +void DualSpinSlider::remove_scale() +{ + _s1.remove_scale(); + _s2.remove_scale(); +} + +void DualSpinSlider::link_toggled() +{ + _s2.set_sensitive(!_link.get_active()); + update_linked(); +} + +void DualSpinSlider::update_linked() +{ + if(_link.get_active()) + _s2.set_value(_s1.get_value()); +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/spin-slider.h b/src/ui/widget/spin-slider.h new file mode 100644 index 0000000..24a18a0 --- /dev/null +++ b/src/ui/widget/spin-slider.h @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Nicholas Bishop <nicholasbishop@gmail.com> + * + * Copyright (C) 2007 Author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_SPIN_SLIDER_H +#define INKSCAPE_UI_WIDGET_SPIN_SLIDER_H + +#include <gtkmm/adjustment.h> +#include <gtkmm/box.h> +#include <gtkmm/scale.h> +#include <gtkmm/togglebutton.h> +#include "spinbutton.h" +#include "attr-widget.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Groups an HScale and a SpinButton together using the same Adjustment. + */ +class SpinSlider : public Gtk::HBox, public AttrWidget +{ +public: + SpinSlider(double value, double lower, double upper, double step_inc, + double climb_rate, int digits, const SPAttributeEnum a = SP_ATTR_INVALID, const char* tip_text = nullptr); + + Glib::ustring get_as_attribute() const override; + void set_from_attribute(SPObject*) override; + + // Shortcuts to _adjustment + Glib::SignalProxy0<void> signal_value_changed(); + double get_value() const; + void set_value(const double); + + const Gtk::Scale& get_scale() const; + Gtk::Scale& get_scale(); + + const Inkscape::UI::Widget::SpinButton& get_spin_button() const; + Inkscape::UI::Widget::SpinButton& get_spin_button(); + + // Change the SpinSlider into a SpinButton with AttrWidget support) + void remove_scale(); +private: + Glib::RefPtr<Gtk::Adjustment> _adjustment; + Gtk::Scale _scale; + Inkscape::UI::Widget::SpinButton _spin; + +public: + const decltype(_adjustment) get_adjustment() const; + decltype(_adjustment) get_adjustment(); +}; + +/** + * Contains two SpinSliders for controlling number-opt-number attributes. + * + * @see SpinSlider + */ +class DualSpinSlider : public Gtk::HBox, public AttrWidget +{ +public: + DualSpinSlider(double value, double lower, double upper, double step_inc, + double climb_rate, int digits, const SPAttributeEnum, char* tip_text1, char* tip_text2); + + Glib::ustring get_as_attribute() const override; + void set_from_attribute(SPObject*) override; + + sigc::signal<void>& signal_value_changed(); + + const SpinSlider& get_spinslider1() const; + SpinSlider& get_spinslider1(); + + const SpinSlider& get_spinslider2() const; + SpinSlider& get_spinslider2(); + + void remove_scale(); +private: + void link_toggled(); + void update_linked(); + sigc::signal<void> _signal_value_changed; + SpinSlider _s1, _s2; + Gtk::ToggleButton _link; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_SPIN_SLIDER_H + +/* + 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 : diff --git a/src/ui/widget/spinbutton.cpp b/src/ui/widget/spinbutton.cpp new file mode 100644 index 0000000..c633035 --- /dev/null +++ b/src/ui/widget/spinbutton.cpp @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Johan B. C. Engelen + * + * Copyright (C) 2011 Author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "spinbutton.h" +#include "unit-menu.h" +#include "unit-tracker.h" +#include "util/expression-evaluator.h" +#include "ui/tools/tool-base.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + + +void +SpinButton::connect_signals() { + signal_input().connect(sigc::mem_fun(*this, &SpinButton::on_input)); + signal_focus_in_event().connect(sigc::mem_fun(*this, &SpinButton::on_my_focus_in_event)); + signal_key_press_event().connect(sigc::mem_fun(*this, &SpinButton::on_my_key_press_event)); + gtk_widget_add_events(GTK_WIDGET(gobj()), GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK); + signal_scroll_event().connect(sigc::mem_fun(*this, &SpinButton::on_scroll_event)); + set_focus_on_click(true); +}; + +int SpinButton::on_input(double* newvalue) +{ + try { + Inkscape::Util::EvaluatorQuantity result; + if (_unit_menu || _unit_tracker) { + Unit const *unit = nullptr; + if (_unit_menu) { + unit = _unit_menu->getUnit(); + } else { + unit = _unit_tracker->getActiveUnit(); + } + Inkscape::Util::ExpressionEvaluator eval = Inkscape::Util::ExpressionEvaluator(get_text().c_str(), unit); + result = eval.evaluate(); + // check if output dimension corresponds to input unit + if (result.dimension != (unit->isAbsolute() ? 1 : 0) ) { + throw Inkscape::Util::EvaluatorException("Input dimensions do not match with parameter dimensions.",""); + } + } else { + Inkscape::Util::ExpressionEvaluator eval = Inkscape::Util::ExpressionEvaluator(get_text().c_str(), nullptr); + result = eval.evaluate(); + } + *newvalue = result.value; + } + catch(Inkscape::Util::EvaluatorException &e) { + g_message ("%s", e.what()); + + return false; + } + + return true; +} + +bool SpinButton::on_my_focus_in_event(GdkEventFocus* /*event*/) +{ + _on_focus_in_value = get_value(); + return false; // do not consume the event +} + + + +bool SpinButton::on_scroll_event(GdkEventScroll *event) +{ + if (!is_focus()) { + return false; + } + double step, page; + get_increments(step, page); + if (event->state & GDK_CONTROL_MASK) { + step = page; + } + double change = 0.0; + if (event->direction == GDK_SCROLL_UP) { + change = step; + } else if (event->direction == GDK_SCROLL_DOWN) { + change = -step; + } else if (event->direction == GDK_SCROLL_SMOOTH) { + double delta_y_clamped = CLAMP(event->delta_y, -1, 1); // values > 1 result in excessive changes + change = step * -delta_y_clamped; + } else { + return false; + } + set_value(get_value() + change); + return true; +} + +bool SpinButton::on_my_key_press_event(GdkEventKey* event) +{ + switch (Inkscape::UI::Tools::get_latin_keyval (event)) { + case GDK_KEY_Escape: + undo(); + return true; // I consumed the event + break; + case GDK_KEY_z: + case GDK_KEY_Z: + if (event->state & GDK_CONTROL_MASK) { + undo(); + return true; // I consumed the event + } + break; + default: + break; + } + + return false; // do not consume the event +} + +void SpinButton::undo() +{ + set_value(_on_focus_in_value); +} + + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/spinbutton.h b/src/ui/widget/spinbutton.h new file mode 100644 index 0000000..710b511 --- /dev/null +++ b/src/ui/widget/spinbutton.h @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Johan B. C. Engelen + * + * Copyright (C) 2011 Author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_SPINBUTTON_H +#define INKSCAPE_UI_WIDGET_SPINBUTTON_H + +#include <gtkmm/spinbutton.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +class UnitMenu; +class UnitTracker; + +/** + * SpinButton widget, that allows entry of simple math expressions (also units, when linked with UnitMenu), + * and allows entry of both '.' and ',' for the decimal, even when in numeric mode. + * + * Calling "set_numeric()" effectively disables the expression parsing. If no unit menu is linked, all unitlike characters are ignored. + */ +class SpinButton : public Gtk::SpinButton +{ +public: + SpinButton(double climb_rate = 0.0, guint digits = 0) + : Gtk::SpinButton(climb_rate, digits), + _unit_menu(nullptr), + _unit_tracker(nullptr), + _on_focus_in_value(0.) + { + connect_signals(); + }; + explicit SpinButton(Glib::RefPtr<Gtk::Adjustment>& adjustment, double climb_rate = 0.0, guint digits = 0) + : Gtk::SpinButton(adjustment, climb_rate, digits), + _unit_menu(nullptr), + _unit_tracker(nullptr), + _on_focus_in_value(0.) + { + connect_signals(); + }; + + ~SpinButton() override = default; + + // noncopyable + SpinButton(const SpinButton&) = delete; + SpinButton& operator=(const SpinButton&) = delete; + + void setUnitMenu(UnitMenu* unit_menu) { _unit_menu = unit_menu; }; + + void addUnitTracker(UnitTracker* ut) { _unit_tracker = ut; }; + +protected: + UnitMenu *_unit_menu; /// Linked unit menu for unit conversion in entered expressions. + UnitTracker *_unit_tracker; // Linked unit tracker for unit conversion in entered expressions. + double _on_focus_in_value; + + void connect_signals(); + + /** + * This callback function should try to convert the entered text to a number and write it to newvalue. + * It calls a method to evaluate the (potential) mathematical expression. + * + * @retval false No conversion done, continue with default handler. + * @retval true Conversion successful, don't call default handler. + */ + int on_input(double* newvalue) override; + + /** + * When focus is obtained, save the value to enable undo later. + * @retval false continue with default handler. + * @retval true don't call default handler. + */ + bool on_my_focus_in_event(GdkEventFocus* event); + + /** + * When scroll is done. + * @retval false continue with default handler. + * @retval true don't call default handler. + */ + bool on_scroll_event(GdkEventScroll *event) override; + /** + * Handle specific keypress events, like Ctrl+Z. + * + * @retval false continue with default handler. + * @retval true don't call default handler. + */ + bool on_my_key_press_event(GdkEventKey* event); + + /** + * Undo the editing, by resetting the value upon when the spinbutton got focus. + */ + void undo(); +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_SPINBUTTON_H + +/* + 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 : diff --git a/src/ui/widget/style-subject.cpp b/src/ui/widget/style-subject.cpp new file mode 100644 index 0000000..9c30a42 --- /dev/null +++ b/src/ui/widget/style-subject.cpp @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 MenTaLguY <mental@rydia.net> + * Abhishek Sharma + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "style-subject.h" + +#include "desktop.h" +#include "desktop-style.h" +#include "selection.h" + +#include "xml/sp-css-attr.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +StyleSubject::StyleSubject() : _desktop(nullptr) { +} + +StyleSubject::~StyleSubject() { + setDesktop(nullptr); +} + +void StyleSubject::setDesktop(SPDesktop *desktop) { + if (desktop != _desktop) { + if (desktop) { + GC::anchor(desktop); + } + if (_desktop) { + GC::release(_desktop); + } + _desktop = desktop; + _afterDesktopSwitch(desktop); + _emitChanged(); + } +} + +StyleSubject::Selection::Selection() = default; + +StyleSubject::Selection::~Selection() = default; + +Inkscape::Selection *StyleSubject::Selection::_getSelection() const { + SPDesktop *desktop = getDesktop(); + if (desktop) { + return desktop->getSelection(); + } else { + return nullptr; + } +} + +std::vector<SPObject*> StyleSubject::Selection::list() { + Inkscape::Selection *selection = _getSelection(); + if(selection) { + return std::vector<SPObject *>(selection->objects().begin(), selection->objects().end()); + } + + return std::vector<SPObject*>(); +} + +Geom::OptRect StyleSubject::Selection::getBounds(SPItem::BBoxType type) { + Inkscape::Selection *selection = _getSelection(); + if (selection) { + return selection->bounds(type); + } else { + return Geom::OptRect(); + } +} + +int StyleSubject::Selection::queryStyle(SPStyle *query, int property) { + SPDesktop *desktop = getDesktop(); + if (desktop) { + return sp_desktop_query_style(desktop, query, property); + } else { + return QUERY_STYLE_NOTHING; + } +} + +void StyleSubject::Selection::_afterDesktopSwitch(SPDesktop *desktop) { + _sel_changed.disconnect(); + _subsel_changed.disconnect(); + _sel_modified.disconnect(); + if (desktop) { + _subsel_changed = desktop->connectToolSubselectionChanged(sigc::hide(sigc::mem_fun(*this, &Selection::_emitChanged))); + Inkscape::Selection *selection = desktop->getSelection(); + if (selection) { + _sel_changed = selection->connectChanged(sigc::hide(sigc::mem_fun(*this, &Selection::_emitChanged))); + _sel_modified = selection->connectModified(sigc::mem_fun(*this, &Selection::_emitModified)); + } + } +} + +void StyleSubject::Selection::setCSS(SPCSSAttr *css) { + SPDesktop *desktop = getDesktop(); + if (desktop) { + sp_desktop_set_style(desktop, css); + } +} + +StyleSubject::CurrentLayer::CurrentLayer() { + _element = nullptr; +} + +StyleSubject::CurrentLayer::~CurrentLayer() = default; + +void StyleSubject::CurrentLayer::_setLayer(SPObject *layer) { + _layer_release.disconnect(); + _layer_modified.disconnect(); + if (_element) { + sp_object_unref(_element, nullptr); + } + _element = layer; + if (layer) { + sp_object_ref(layer, nullptr); + _layer_release = layer->connectRelease(sigc::hide(sigc::bind(sigc::mem_fun(*this, &CurrentLayer::_setLayer), (SPObject *)nullptr))); + _layer_modified = layer->connectModified(sigc::hide(sigc::hide(sigc::mem_fun(*this, &CurrentLayer::_emitChanged)))); + } + _emitChanged(); +} + +SPObject *StyleSubject::CurrentLayer::_getLayer() const { + return _element; +} + +SPObject *StyleSubject::CurrentLayer::_getLayerSList() const { + return _element; + +} + +std::vector<SPObject*> StyleSubject::CurrentLayer::list(){ + std::vector<SPObject*> list; + list.push_back(_element); + return list; +} + +Geom::OptRect StyleSubject::CurrentLayer::getBounds(SPItem::BBoxType type) { + SPObject *layer = _getLayer(); + if (layer && SP_IS_ITEM(layer)) { + return SP_ITEM(layer)->desktopBounds(type); + } else { + return Geom::OptRect(); + } +} + +int StyleSubject::CurrentLayer::queryStyle(SPStyle *query, int property) { + std::vector<SPItem*> list; + SPObject* i=_getLayerSList(); + if (i) { + list.push_back((SPItem*)i); + return sp_desktop_query_style_from_list(list, query, property); + } else { + return QUERY_STYLE_NOTHING; + } +} + +void StyleSubject::CurrentLayer::setCSS(SPCSSAttr *css) { + SPObject *layer = _getLayer(); + if (layer) { + sp_desktop_apply_css_recursive(layer, css, true); + } +} + +void StyleSubject::CurrentLayer::_afterDesktopSwitch(SPDesktop *desktop) { + _layer_switched.disconnect(); + if (desktop) { + _layer_switched = desktop->connectCurrentLayerChanged(sigc::mem_fun(*this, &CurrentLayer::_setLayer)); + _setLayer(desktop->currentLayer()); + } else { + _setLayer(nullptr); + } +} + +} +} +} + +/* + 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 : diff --git a/src/ui/widget/style-subject.h b/src/ui/widget/style-subject.h new file mode 100644 index 0000000..c2f2b3f --- /dev/null +++ b/src/ui/widget/style-subject.h @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Abstraction for different style widget operands. + */ +/* + * Copyright (C) 2007 MenTaLguY <mental@rydia.net> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_INKSCAPE_UI_WIDGET_STYLE_SUBJECT_H +#define SEEN_INKSCAPE_UI_WIDGET_STYLE_SUBJECT_H + +#include <boost/optional.hpp> +#include <2geom/rect.h> +#include <cstddef> +#include <sigc++/sigc++.h> + +#include "object/sp-item.h" +#include "object/sp-tag.h" +#include "object/sp-tag-use.h" +#include "object/sp-tag-use-reference.h" + +class SPDesktop; +class SPObject; +class SPCSSAttr; +class SPStyle; + +namespace Inkscape { +class Selection; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +class StyleSubject { +public: + class Selection; + class CurrentLayer; + + + StyleSubject(); + virtual ~StyleSubject(); + + void setDesktop(SPDesktop *desktop); + SPDesktop *getDesktop() const { return _desktop; } + + virtual Geom::OptRect getBounds(SPItem::BBoxType type) = 0; + virtual int queryStyle(SPStyle *query, int property) = 0; + virtual void setCSS(SPCSSAttr *css) = 0; + virtual std::vector<SPObject*> list(){return std::vector<SPObject*>();}; + + sigc::connection connectChanged(sigc::signal<void>::slot_type slot) { + return _changed_signal.connect(slot); + } + +protected: + virtual void _afterDesktopSwitch(SPDesktop */*desktop*/) {} + void _emitChanged() { _changed_signal.emit(); } + void _emitModified(Inkscape::Selection* selection, guint flags) { + // Do not say this object has styles unless it's style has been modified + if (flags & (SP_OBJECT_STYLE_MODIFIED_FLAG)) { + _emitChanged(); + } + } + +private: + sigc::signal<void> _changed_signal; + SPDesktop *_desktop; +}; + +class StyleSubject::Selection : public StyleSubject { +public: + Selection(); + ~Selection() override; + + Geom::OptRect getBounds(SPItem::BBoxType type) override; + int queryStyle(SPStyle *query, int property) override; + void setCSS(SPCSSAttr *css) override; + std::vector<SPObject*> list() override; + +protected: + void _afterDesktopSwitch(SPDesktop *desktop) override; + +private: + Inkscape::Selection *_getSelection() const; + + sigc::connection _sel_changed; + sigc::connection _subsel_changed; + sigc::connection _sel_modified; +}; + +class StyleSubject::CurrentLayer : public StyleSubject { +public: + CurrentLayer(); + ~CurrentLayer() override; + + Geom::OptRect getBounds(SPItem::BBoxType type) override; + int queryStyle(SPStyle *query, int property) override; + void setCSS(SPCSSAttr *css) override; + std::vector<SPObject*> list() override; + +protected: + void _afterDesktopSwitch(SPDesktop *desktop) override; + +private: + SPObject *_getLayer() const; + void _setLayer(SPObject *layer); + SPObject *_getLayerSList() const; + + sigc::connection _layer_switched; + sigc::connection _layer_release; + sigc::connection _layer_modified; + mutable SPObject* _element; +}; + +} +} +} + +#endif // SEEN_INKSCAPE_UI_WIDGET_STYLE_SUBJECT_H + +/* + 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 : diff --git a/src/ui/widget/style-swatch.cpp b/src/ui/widget/style-swatch.cpp new file mode 100644 index 0000000..734f092 --- /dev/null +++ b/src/ui/widget/style-swatch.cpp @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Static style swatch (fill, stroke, opacity). + */ +/* Authors: + * buliabyak@gmail.com + * Krzysztof KosiĆski <tweenk.pl@gmail.com> + * + * Copyright (C) 2005-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "style-swatch.h" + +#include <glibmm/i18n.h> +#include <gtkmm/grid.h> + +#include "inkscape.h" +#include "verbs.h" + +#include "object/sp-linear-gradient.h" +#include "object/sp-pattern.h" +#include "object/sp-radial-gradient.h" +#include "style.h" + +#include "helper/action.h" + +#include "ui/widget/color-preview.h" +#include "util/units.h" + +#include "widgets/spw-utilities.h" +#include "widgets/widget-sizes.h" + +#include "xml/sp-css-attr.h" + +enum { + SS_FILL, + SS_STROKE +}; + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * Watches whether the tool uses the current style. + */ +class StyleSwatch::ToolObserver : public Inkscape::Preferences::Observer { +public: + ToolObserver(Glib::ustring const &path, StyleSwatch &ss) : + Observer(path), + _style_swatch(ss) + {} + void notify(Inkscape::Preferences::Entry const &val) override; +private: + StyleSwatch &_style_swatch; +}; + +/** + * Watches for changes in the observed style pref. + */ +class StyleSwatch::StyleObserver : public Inkscape::Preferences::Observer { +public: + StyleObserver(Glib::ustring const &path, StyleSwatch &ss) : + Observer(path), + _style_swatch(ss) + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + this->notify(prefs->getEntry(path)); + } + void notify(Inkscape::Preferences::Entry const &val) override { + SPCSSAttr *css = val.getInheritedStyle(); + _style_swatch.setStyle(css); + sp_repr_css_attr_unref(css); + } +private: + StyleSwatch &_style_swatch; +}; + +void StyleSwatch::ToolObserver::notify(Inkscape::Preferences::Entry const &val) +{ + bool usecurrent = val.getBool(); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (_style_swatch._style_obs) delete _style_swatch._style_obs; + + if (usecurrent) { + _style_swatch._style_obs = new StyleObserver("/desktop/style", _style_swatch); + + // If desktop's last-set style is empty, a tool uses its own fixed style even if set to use + // last-set (so long as it's empty). To correctly show this, we get the tool's style + // if the desktop's style is empty. + SPCSSAttr *css = prefs->getStyle("/desktop/style"); + if (!css->attributeList()) { + SPCSSAttr *css2 = prefs->getInheritedStyle(_style_swatch._tool_path + "/style"); + _style_swatch.setStyle(css2); + sp_repr_css_attr_unref(css2); + } + sp_repr_css_attr_unref(css); + } else { + _style_swatch._style_obs = new StyleObserver(_style_swatch._tool_path + "/style", _style_swatch); + } + prefs->addObserver(*_style_swatch._style_obs); +} + +StyleSwatch::StyleSwatch(SPCSSAttr *css, gchar const *main_tip) + : + _desktop(nullptr), + _verb_t(0), + _css(nullptr), + _tool_obs(nullptr), + _style_obs(nullptr), + _table(Gtk::manage(new Gtk::Grid())), + _sw_unit(nullptr) +{ + set_name("StyleSwatch"); + + _label[SS_FILL].set_markup(_("Fill:")); + _label[SS_STROKE].set_markup(_("Stroke:")); + + for (int i = SS_FILL; i <= SS_STROKE; i++) { + _label[i].set_halign(Gtk::ALIGN_START); + _label[i].set_valign(Gtk::ALIGN_CENTER); + _label[i].set_margin_top(0); + _label[i].set_margin_bottom(0); + _label[i].set_margin_start(0); + _label[i].set_margin_end(0); + + _color_preview[i] = new Inkscape::UI::Widget::ColorPreview (0); + } + + _opacity_value.set_halign(Gtk::ALIGN_START); + _opacity_value.set_valign(Gtk::ALIGN_CENTER); + _opacity_value.set_margin_top(0); + _opacity_value.set_margin_bottom(0); + _opacity_value.set_margin_start(0); + _opacity_value.set_margin_end(0); + + _table->set_column_spacing(2); + _table->set_row_spacing(0); + + _stroke.pack_start(_place[SS_STROKE]); + _stroke_width_place.add(_stroke_width); + _stroke.pack_start(_stroke_width_place, Gtk::PACK_SHRINK); + + _opacity_place.add(_opacity_value); + + _table->attach(_label[SS_FILL], 0, 0, 1, 1); + _table->attach(_label[SS_STROKE], 0, 1, 1, 1); + _table->attach(_place[SS_FILL], 1, 0, 1, 1); + _table->attach(_stroke, 1, 1, 1, 1); + _table->attach(_opacity_place, 2, 0, 1, 2); + + _swatch.add(*_table); + pack_start(_swatch, true, true, 0); + + set_size_request (STYLE_SWATCH_WIDTH, -1); + + setStyle (css); + + _swatch.signal_button_press_event().connect(sigc::mem_fun(*this, &StyleSwatch::on_click)); + + if (main_tip) + { + _swatch.set_tooltip_text(main_tip); + } +} + +void StyleSwatch::setClickVerb(sp_verb_t verb_t) { + _verb_t = verb_t; +} + +void StyleSwatch::setDesktop(SPDesktop *desktop) { + _desktop = desktop; +} + +bool +StyleSwatch::on_click(GdkEventButton */*event*/) +{ + if (this->_desktop && this->_verb_t != SP_VERB_NONE) { + Inkscape::Verb *verb = Inkscape::Verb::get(this->_verb_t); + SPAction *action = verb->get_action(Inkscape::ActionContext((Inkscape::UI::View::View *) this->_desktop)); + sp_action_perform (action, nullptr); + return true; + } + return false; +} + +StyleSwatch::~StyleSwatch() +{ + if (_css) + sp_repr_css_attr_unref (_css); + + for (int i = SS_FILL; i <= SS_STROKE; i++) { + delete _color_preview[i]; + } + + if (_style_obs) delete _style_obs; + if (_tool_obs) delete _tool_obs; +} + +void +StyleSwatch::setWatchedTool(const char *path, bool synthesize) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (_tool_obs) { + delete _tool_obs; + _tool_obs = nullptr; + } + + if (path) { + _tool_path = path; + _tool_obs = new ToolObserver(_tool_path + "/usecurrent", *this); + prefs->addObserver(*_tool_obs); + } else { + _tool_path = ""; + } + + // hack until there is a real synthesize events function for prefs, + // which shouldn't be hard to write once there is sufficient need for it + if (synthesize && _tool_obs) { + _tool_obs->notify(prefs->getEntry(_tool_path + "/usecurrent")); + } +} + + +void StyleSwatch::setStyle(SPCSSAttr *css) +{ + if (_css) + sp_repr_css_attr_unref (_css); + + if (!css) + return; + + _css = sp_repr_css_attr_new(); + sp_repr_css_merge(_css, css); + + Glib::ustring css_string; + sp_repr_css_write_string (_css, css_string); + + SPStyle style(SP_ACTIVE_DOCUMENT); + if (!css_string.empty()) { + style.mergeString(css_string.c_str()); + } + setStyle (&style); +} + +void StyleSwatch::setStyle(SPStyle *query) +{ + _place[SS_FILL].remove(); + _place[SS_STROKE].remove(); + + bool has_stroke = true; + + for (int i = SS_FILL; i <= SS_STROKE; i++) { + Gtk::EventBox *place = &(_place[i]); + + SPIPaint *paint; + if (i == SS_FILL) { + paint = &(query->fill); + } else { + paint = &(query->stroke); + } + + if (paint->set && paint->isPaintserver()) { + SPPaintServer *server = (i == SS_FILL)? SP_STYLE_FILL_SERVER (query) : SP_STYLE_STROKE_SERVER (query); + + if (SP_IS_LINEARGRADIENT (server)) { + _value[i].set_markup(_("L Gradient")); + place->add(_value[i]); + place->set_tooltip_text((i == SS_FILL)? (_("Linear gradient fill")) : (_("Linear gradient stroke"))); + } else if (SP_IS_RADIALGRADIENT (server)) { + _value[i].set_markup(_("R Gradient")); + place->add(_value[i]); + place->set_tooltip_text((i == SS_FILL)? (_("Radial gradient fill")) : (_("Radial gradient stroke"))); + } else if (SP_IS_PATTERN (server)) { + _value[i].set_markup(_("Pattern")); + place->add(_value[i]); + place->set_tooltip_text((i == SS_FILL)? (_("Pattern fill")) : (_("Pattern stroke"))); + } + + } else if (paint->set && paint->isColor()) { + guint32 color = paint->value.color.toRGBA32( SP_SCALE24_TO_FLOAT ((i == SS_FILL)? query->fill_opacity.value : query->stroke_opacity.value) ); + ((Inkscape::UI::Widget::ColorPreview*)_color_preview[i])->setRgba32 (color); + _color_preview[i]->show_all(); + place->add(*_color_preview[i]); + gchar *tip; + if (i == SS_FILL) { + tip = g_strdup_printf (_("Fill: %06x/%.3g"), color >> 8, SP_RGBA32_A_F(color)); + } else { + tip = g_strdup_printf (_("Stroke: %06x/%.3g"), color >> 8, SP_RGBA32_A_F(color)); + } + place->set_tooltip_text(tip); + g_free (tip); + } else if (paint->set && paint->isNone()) { + _value[i].set_markup(C_("Fill and stroke", "<i>None</i>")); + place->add(_value[i]); + place->set_tooltip_text((i == SS_FILL)? (C_("Fill and stroke", "No fill")) : (C_("Fill and stroke", "No stroke"))); + if (i == SS_STROKE) has_stroke = false; + } else if (!paint->set) { + _value[i].set_markup(_("<b>Unset</b>")); + place->add(_value[i]); + place->set_tooltip_text((i == SS_FILL)? (_("Unset fill")) : (_("Unset stroke"))); + if (i == SS_STROKE) has_stroke = false; + } + } + +// Now query stroke_width + if (has_stroke) { + double w; + if (_sw_unit) { + w = Inkscape::Util::Quantity::convert(query->stroke_width.computed, "px", _sw_unit); + } else { + w = query->stroke_width.computed; + } + + { + gchar *str = g_strdup_printf(" %.3g", w); + _stroke_width.set_markup(str); + g_free (str); + } + { + gchar *str = g_strdup_printf(_("Stroke width: %.5g%s"), + w, + _sw_unit? _sw_unit->abbr.c_str() : "px"); + _stroke_width_place.set_tooltip_text(str); + g_free (str); + } + } else { + _stroke_width_place.set_tooltip_text(""); + _stroke_width.set_markup(""); + _stroke_width.set_has_tooltip(false); + } + + gdouble op = SP_SCALE24_TO_FLOAT(query->opacity.value); + if (op != 1) { + { + gchar *str; + str = g_strdup_printf(_("O: %2.0f"), (op*100.0)); + _opacity_value.set_markup (str); + g_free (str); + } + { + gchar *str = g_strdup_printf(_("Opacity: %2.1f %%"), (op*100.0)); + _opacity_place.set_tooltip_text(str); + g_free (str); + } + } else { + _opacity_place.set_tooltip_text(""); + _opacity_value.set_markup(""); + _opacity_value.set_has_tooltip(false); + } + + show_all(); +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/style-swatch.h b/src/ui/widget/style-swatch.h new file mode 100644 index 0000000..4c7dc51 --- /dev/null +++ b/src/ui/widget/style-swatch.h @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Static style swatch (fill, stroke, opacity) + */ +/* Authors: + * buliabyak@gmail.com + * Krzysztof KosiĆski <tweenk.pl@gmail.com> + * + * Copyright (C) 2005-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_CURRENT_STYLE_H +#define INKSCAPE_UI_CURRENT_STYLE_H + +#include <gtkmm/box.h> +#include <gtkmm/label.h> +#include <gtkmm/eventbox.h> +#include <gtkmm/enums.h> + +#include "desktop.h" +#include "preferences.h" + +class SPStyle; +class SPCSSAttr; + +namespace Gtk { +class Grid; +} + +namespace Inkscape { + +namespace Util { + class Unit; +} + +namespace UI { +namespace Widget { + +class StyleSwatch : public Gtk::HBox +{ +public: + StyleSwatch (SPCSSAttr *attr, gchar const *main_tip); + + ~StyleSwatch() override; + + void setStyle(SPStyle *style); + void setStyle(SPCSSAttr *attr); + SPCSSAttr *getStyle(); + + void setWatchedTool (const char *path, bool synthesize); + + void setClickVerb(sp_verb_t verb_t); + void setDesktop(SPDesktop *desktop); + bool on_click(GdkEventButton *event); + +private: + class ToolObserver; + class StyleObserver; + + SPDesktop *_desktop; + sp_verb_t _verb_t; + SPCSSAttr *_css; + ToolObserver *_tool_obs; + StyleObserver *_style_obs; + Glib::ustring _tool_path; + + Gtk::EventBox _swatch; + + Gtk::Grid *_table; + + Gtk::Label _label[2]; + Gtk::EventBox _place[2]; + Gtk::EventBox _opacity_place; + Gtk::Label _value[2]; + Gtk::Label _opacity_value; + Gtk::Widget *_color_preview[2]; + Glib::ustring __color[2]; + Gtk::HBox _stroke; + Gtk::EventBox _stroke_width_place; + Gtk::Label _stroke_width; + + Inkscape::Util::Unit *_sw_unit; + +friend class ToolObserver; +}; + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_BUTTON_H + +/* + 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 : diff --git a/src/ui/widget/text.cpp b/src/ui/widget/text.cpp new file mode 100644 index 0000000..656ec45 --- /dev/null +++ b/src/ui/widget/text.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Carl Hetherington <inkscape@carlh.net> + * Maximilian Albert <maximilian.albert@gmail.com> + * + * Copyright (C) 2004 Carl Hetherington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "text.h" +#include <gtkmm/entry.h> + +namespace Inkscape { +namespace UI { +namespace Widget { + +Text::Text(Glib::ustring const &label, Glib::ustring const &tooltip, + Glib::ustring const &suffix, + Glib::ustring const &icon, + bool mnemonic) + : Labelled(label, tooltip, new Gtk::Entry(), suffix, icon, mnemonic), + setProgrammatically(false) +{ +} + +Glib::ustring const Text::getText() const +{ + g_assert(_widget != nullptr); + return static_cast<Gtk::Entry*>(_widget)->get_text(); +} + +void Text::setText(Glib::ustring const text) +{ + g_assert(_widget != nullptr); + setProgrammatically = true; // callback is supposed to reset back, if it cares + static_cast<Gtk::Entry*>(_widget)->set_text(text); // FIXME: set correctly +} + +Glib::SignalProxy0<void> Text::signal_activate() +{ + return static_cast<Gtk::Entry*>(_widget)->signal_activate(); +} + + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/text.h b/src/ui/widget/text.h new file mode 100644 index 0000000..87c9357 --- /dev/null +++ b/src/ui/widget/text.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Carl Hetherington <inkscape@carlh.net> + * Maximilian Albert <maximilian.albert@gmail.com> + * + * Copyright (C) 2004 Carl Hetherington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_TEXT_H +#define INKSCAPE_UI_WIDGET_TEXT_H + +#include "labelled.h" + + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A labelled text box, with optional icon or suffix, for entering arbitrary number values. + */ +class Text : public Labelled +{ +public: + + /** + * Construct a Text Widget. + * + * @param label Label. + * @param suffix Suffix, placed after the widget (defaults to ""). + * @param icon Icon filename, placed before the label (defaults to ""). + * @param mnemonic Mnemonic toggle; if true, an underscore (_) in the label + * indicates the next character should be used for the + * mnemonic accelerator key (defaults to false). + */ + Text(Glib::ustring const &label, + Glib::ustring const &tooltip, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true); + + /** + * Get the text in the entry. + */ + Glib::ustring const getText() const; + + /** + * Sets the text of the text entry. + */ + void setText(Glib::ustring const text); + + void update(); + + /** + * Signal raised when the spin button's value changes. + */ + Glib::SignalProxy0<void> signal_activate(); + + bool setProgrammatically; // true if the value was set by setValue, not changed by the user; + // if a callback checks it, it must reset it back to false +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_TEXT_H + +/* + 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 : diff --git a/src/ui/widget/tolerance-slider.cpp b/src/ui/widget/tolerance-slider.cpp new file mode 100644 index 0000000..b1b28a7 --- /dev/null +++ b/src/ui/widget/tolerance-slider.cpp @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ralf Stephan <ralf@ark.in-berlin.de> + * Abhishek Sharma + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "tolerance-slider.h" + +#include "registry.h" + +#include <gtkmm/adjustment.h> +#include <gtkmm/box.h> +#include <gtkmm/label.h> +#include <gtkmm/radiobutton.h> +#include <gtkmm/scale.h> + +#include "inkscape.h" +#include "document.h" +#include "document-undo.h" +#include "desktop.h" + +#include "object/sp-namedview.h" + +#include "svg/stringstream.h" + +#include "xml/repr.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +//=================================================== + +//--------------------------------------------------- + + + +//==================================================== + +ToleranceSlider::ToleranceSlider(const Glib::ustring& label1, const Glib::ustring& label2, const Glib::ustring& label3, const Glib::ustring& tip1, const Glib::ustring& tip2, const Glib::ustring& tip3, const Glib::ustring& key, Registry& wr) +: _vbox(nullptr) +{ + init(label1, label2, label3, tip1, tip2, tip3, key, wr); +} + +ToleranceSlider::~ToleranceSlider() +{ + if (_vbox) delete _vbox; + _scale_changed_connection.disconnect(); +} + +void ToleranceSlider::init (const Glib::ustring& label1, const Glib::ustring& label2, const Glib::ustring& label3, const Glib::ustring& tip1, const Glib::ustring& tip2, const Glib::ustring& tip3, const Glib::ustring& key, Registry& wr) +{ + // hbox = label + slider + // + // e.g. + // + // snap distance |-------X---| 37 + + // vbox = checkbutton + // + + // hbox + + _vbox = new Gtk::VBox; + _hbox = Gtk::manage(new Gtk::HBox); + + Gtk::Label *theLabel1 = Gtk::manage(new Gtk::Label(label1)); + theLabel1->set_use_underline(); + theLabel1->set_halign(Gtk::ALIGN_START); + theLabel1->set_valign(Gtk::ALIGN_CENTER); + // align the label with the checkbox text above by indenting 22 px. + _hbox->pack_start(*theLabel1, Gtk::PACK_EXPAND_WIDGET, 22); + + _hscale = Gtk::manage(new Gtk::Scale(Gtk::ORIENTATION_HORIZONTAL)); + _hscale->set_range(1.0, 51.0); + + theLabel1->set_mnemonic_widget (*_hscale); + _hscale->set_draw_value (true); + _hscale->set_value_pos (Gtk::POS_RIGHT); + _hscale->set_size_request (100, -1); + _old_val = 10; + _hscale->set_value (_old_val); + _hscale->set_tooltip_text (tip1); + _hbox->add (*_hscale); + + + Gtk::Label *theLabel2 = Gtk::manage(new Gtk::Label(label2)); + theLabel2->set_use_underline(); + Gtk::Label *theLabel3 = Gtk::manage(new Gtk::Label(label3)); + theLabel3->set_use_underline(); + _button1 = Gtk::manage(new Gtk::RadioButton); + _radio_button_group = _button1->get_group(); + _button2 = Gtk::manage(new Gtk::RadioButton); + _button2->set_group(_radio_button_group); + _button1->set_tooltip_text (tip2); + _button2->set_tooltip_text (tip3); + _button1->add (*theLabel3); + _button1->set_halign(Gtk::ALIGN_START); + _button1->set_valign(Gtk::ALIGN_CENTER); + _button2->add (*theLabel2); + _button2->set_halign(Gtk::ALIGN_START); + _button2->set_valign(Gtk::ALIGN_CENTER); + + _vbox->add (*_button1); + _vbox->add (*_button2); + // Here we need some extra pixels to get the vertical spacing right. Why? + _vbox->pack_end(*_hbox, true, true, 3); // add 3 px. + _key = key; + _scale_changed_connection = _hscale->signal_value_changed().connect (sigc::mem_fun (*this, &ToleranceSlider::on_scale_changed)); + _btn_toggled_connection = _button2->signal_toggled().connect (sigc::mem_fun (*this, &ToleranceSlider::on_toggled)); + _wr = ≀ + _vbox->show_all_children(); +} + +void ToleranceSlider::setValue (double val) +{ + auto adj = _hscale->get_adjustment(); + + adj->set_lower (1.0); + adj->set_upper (51.0); + adj->set_step_increment (1.0); + + if (val > 9999.9) // magic value 10000.0 + { + _button1->set_active (true); + _button2->set_active (false); + _hbox->set_sensitive (false); + val = 50.0; + } + else + { + _button1->set_active (false); + _button2->set_active (true); + _hbox->set_sensitive (true); + } + _hscale->set_value (val); + _hbox->show_all(); +} + +void ToleranceSlider::setLimits (double theMin, double theMax) +{ + _hscale->set_range (theMin, theMax); + _hscale->get_adjustment()->set_step_increment (1); +} + +void ToleranceSlider::on_scale_changed() +{ + update (_hscale->get_value()); +} + +void ToleranceSlider::on_toggled() +{ + if (!_button2->get_active()) + { + _old_val = _hscale->get_value(); + _hbox->set_sensitive (false); + _hbox->show_all(); + setValue (10000.0); + update (10000.0); + } + else + { + _hbox->set_sensitive (true); + _hbox->show_all(); + setValue (_old_val); + update (_old_val); + } +} + +void ToleranceSlider::update (double val) +{ + if (_wr->isUpdating()) + return; + + SPDesktop *dt = SP_ACTIVE_DESKTOP; + if (!dt) + return; + + Inkscape::SVGOStringStream os; + os << val; + + _wr->setUpdating (true); + + SPDocument *doc = dt->getDocument(); + bool saved = DocumentUndo::getUndoSensitive(doc); + DocumentUndo::setUndoSensitive(doc, false); + Inkscape::XML::Node *repr = dt->getNamedView()->getRepr(); + repr->setAttribute(_key, os.str()); + DocumentUndo::setUndoSensitive(doc, saved); + + doc->setModifiedSinceSave(); + + _wr->setUpdating (false); +} + + +} // 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 : diff --git a/src/ui/widget/tolerance-slider.h b/src/ui/widget/tolerance-slider.h new file mode 100644 index 0000000..1c4af1d --- /dev/null +++ b/src/ui/widget/tolerance-slider.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ralf Stephan <ralf@ark.in-berlin.de> + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_TOLERANCE_SLIDER__H_ +#define INKSCAPE_UI_WIDGET_TOLERANCE_SLIDER__H_ + +#include <gtkmm/radiobuttongroup.h> + +namespace Gtk { +class RadioButton; +class Scale; +class VBox; +class HBox; +} + +namespace Inkscape { +namespace UI { +namespace Widget { + +class Registry; + +/** + * Implementation of tolerance slider widget. + * This widget is part of the Document properties dialog. + */ +class ToleranceSlider { +public: + ToleranceSlider(const Glib::ustring& label1, + const Glib::ustring& label2, + const Glib::ustring& label3, + const Glib::ustring& tip1, + const Glib::ustring& tip2, + const Glib::ustring& tip3, + const Glib::ustring& key, + Registry& wr); + ~ToleranceSlider(); + void setValue (double); + void setLimits (double, double); + Gtk::VBox* _vbox; +private: + void init (const Glib::ustring& label1, + const Glib::ustring& label2, + const Glib::ustring& label3, + const Glib::ustring& tip1, + const Glib::ustring& tip2, + const Glib::ustring& tip3, + const Glib::ustring& key, + Registry& wr); + +protected: + void on_scale_changed(); + void on_toggled(); + void update (double val); + Gtk::HBox *_hbox; + Gtk::Scale *_hscale; + Gtk::RadioButtonGroup _radio_button_group; + Gtk::RadioButton *_button1; + Gtk::RadioButton *_button2; + Registry *_wr; + Glib::ustring _key; + sigc::connection _scale_changed_connection; + sigc::connection _btn_toggled_connection; + double _old_val; +}; + + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_TOLERANCE_SLIDER__H_ + +/* + 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 : diff --git a/src/ui/widget/unit-menu.cpp b/src/ui/widget/unit-menu.cpp new file mode 100644 index 0000000..aaf565f --- /dev/null +++ b/src/ui/widget/unit-menu.cpp @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Bryce Harrington <bryce@bryceharrington.org> + * + * Copyright (C) 2004 Bryce Harrington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cmath> + +#include "unit-menu.h" + +using Inkscape::Util::unit_table; + +namespace Inkscape { +namespace UI { +namespace Widget { + +UnitMenu::UnitMenu() : _type(UNIT_TYPE_NONE) +{ + set_active(0); + gtk_widget_add_events(GTK_WIDGET(gobj()), GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK); + signal_scroll_event().connect(sigc::mem_fun(*this, &UnitMenu::on_scroll_event)); +} + +UnitMenu::~UnitMenu() = default; + +bool UnitMenu::setUnitType(UnitType unit_type) +{ + // Expand the unit widget with unit entries from the unit table + UnitTable::UnitMap m = unit_table.units(unit_type); + + for (auto & i : m) { + append(i.first); + } + _type = unit_type; + set_active_text(unit_table.primary(unit_type)); + + return true; +} + +bool UnitMenu::resetUnitType(UnitType unit_type) +{ + remove_all(); + + return setUnitType(unit_type); +} + +void UnitMenu::addUnit(Unit const& u) +{ + unit_table.addUnit(u, false); + append(u.abbr); +} + +Unit const * UnitMenu::getUnit() const +{ + if (get_active_text() == "") { + g_assert(_type != UNIT_TYPE_NONE); + return unit_table.getUnit(unit_table.primary(_type)); + } + return unit_table.getUnit(get_active_text()); +} + +bool UnitMenu::setUnit(Glib::ustring const & unit) +{ + // TODO: Determine if 'unit' is available in the dropdown. + // If not, return false + + set_active_text(unit); + return true; +} + +Glib::ustring UnitMenu::getUnitAbbr() const +{ + if (get_active_text() == "") { + return ""; + } + return getUnit()->abbr; +} + +UnitType UnitMenu::getUnitType() const +{ + return getUnit()->type; +} + +double UnitMenu::getUnitFactor() const +{ + return getUnit()->factor; +} + +int UnitMenu::getDefaultDigits() const +{ + return getUnit()->defaultDigits(); +} + +double UnitMenu::getDefaultStep() const +{ + int factor_digits = -1*int(log10(getUnit()->factor)); + return pow(10.0, factor_digits); +} + +double UnitMenu::getDefaultPage() const +{ + return 10 * getDefaultStep(); +} + +double UnitMenu::getConversion(Glib::ustring const &new_unit_abbr, Glib::ustring const &old_unit_abbr) const +{ + double old_factor = getUnit()->factor; + if (old_unit_abbr != "no_unit") { + old_factor = unit_table.getUnit(old_unit_abbr)->factor; + } + Unit const * new_unit = unit_table.getUnit(new_unit_abbr); + + // Catch the case of zero or negative unit factors (error!) + if (old_factor < 0.0000001 || + new_unit->factor < 0.0000001) { + // TODO: Should we assert here? + return 0.00; + } + + return old_factor / new_unit->factor; +} + +bool UnitMenu::isAbsolute() const +{ + return getUnitType() != UNIT_TYPE_DIMENSIONLESS; +} + +bool UnitMenu::isRadial() const +{ + return getUnitType() == UNIT_TYPE_RADIAL; +} + +bool UnitMenu::on_scroll_event(GdkEventScroll *event) { return false; } + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/unit-menu.h b/src/ui/widget/unit-menu.h new file mode 100644 index 0000000..b8e3ab7 --- /dev/null +++ b/src/ui/widget/unit-menu.h @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Bryce Harrington <bryce@bryceharrington.org> + * + * Copyright (C) 2004 Bryce Harrington + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_UNIT_H +#define INKSCAPE_UI_WIDGET_UNIT_H + +#include <gtkmm/comboboxtext.h> +#include "util/units.h" + +using namespace Inkscape::Util; + +namespace Inkscape { +namespace UI { +namespace Widget { + +/** + * A drop down menu for choosing unit types. + */ +class UnitMenu : public Gtk::ComboBoxText +{ +public: + + /** + * Construct a UnitMenu + */ + UnitMenu(); + + ~UnitMenu() override; + + /** + * Adds the unit type to the widget. This extracts the corresponding + * units from the unit map matching the given type, and appends them + * to the dropdown widget. It causes the primary unit for the given + * unit_type to be selected. + */ + bool setUnitType(UnitType unit_type); + + /** + * Removes all unit entries, then adds the unit type to the widget. + * This extracts the corresponding + * units from the unit map matching the given type, and appends them + * to the dropdown widget. It causes the primary unit for the given + * unit_type to be selected. + */ + bool resetUnitType(UnitType unit_type); + + /** + * Adds a unit, possibly user-defined, to the menu. + */ + void addUnit(Unit const& u); + + /** + * Sets the dropdown widget to the given unit abbreviation. + * Returns true if the unit was selectable, false if not + * (i.e., if the unit was not present in the widget). + */ + bool setUnit(Glib::ustring const &unit); + + /** + * Returns the Unit object corresponding to the current selection + * in the dropdown widget. + */ + Unit const * getUnit() const; + + /** + * Returns the abbreviated unit name of the selected unit. + */ + Glib::ustring getUnitAbbr() const; + + /** + * Returns the UnitType of the selected unit. + */ + UnitType getUnitType() const; + + /** + * Returns the unit factor for the selected unit. + */ + double getUnitFactor() const; + + /** + * Returns the recommended number of digits for displaying + * numbers of this unit type. + */ + int getDefaultDigits() const; + + /** + * Returns the recommended step size in spin buttons + * displaying units of this type. + */ + double getDefaultStep() const; + + /** + * Returns the recommended page size (when hitting pgup/pgdn) + * in spin buttons displaying units of this type. + */ + double getDefaultPage() const; + + /** + * Returns the conversion factor required to convert values + * of the currently selected unit into units of type + * new_unit_abbr. + */ + double getConversion(Glib::ustring const &new_unit_abbr, Glib::ustring const &old_unit_abbr = "no_unit") const; + + /** + * Returns true if the selected unit is not dimensionless + * (false for %, true for px, pt, cm, etc). + */ + bool isAbsolute() const; + + /** + * Returns true if the selected unit is radial (deg or rad). + */ + bool isRadial() const; + +protected: + UnitType _type; + /** + * block scroll from widget if is inside a scrolled window. + */ + bool on_scroll_event(GdkEventScroll *event) override; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_UNIT_H + +/* + 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 : diff --git a/src/ui/widget/unit-tracker.cpp b/src/ui/widget/unit-tracker.cpp new file mode 100644 index 0000000..40d5ccf --- /dev/null +++ b/src/ui/widget/unit-tracker.cpp @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Inkscape::UI::Widget::UnitTracker + * Simple mediator to synchronize changes to unit menus + * + * Authors: + * Jon A. Cruz <jon@joncruz.org> + * Matthew Petroff <matthew@mpetroff.net> + * + * Copyright (C) 2007 Jon A. Cruz + * Copyright (C) 2013 Matthew Petroff + * Copyright (C) 2018 Tavmjong Bah + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <algorithm> +#include <iostream> + +#include "unit-tracker.h" + +#include "combo-tool-item.h" + +#define COLUMN_STRING 0 + +using Inkscape::Util::UnitTable; +using Inkscape::Util::unit_table; + +namespace Inkscape { +namespace UI { +namespace Widget { + +UnitTracker::UnitTracker(UnitType unit_type) : + _active(0), + _isUpdating(false), + _activeUnit(nullptr), + _activeUnitInitialized(false), + _store(nullptr), + _priorValues() +{ + UnitTable::UnitMap m = unit_table.units(unit_type); + + ComboToolItemColumns columns; + _store = Gtk::ListStore::create(columns); + Gtk::TreeModel::Row row; + + for (auto & m_iter : m) { + + Glib::ustring unit = m_iter.first; + + row = *(_store->append()); + row[columns.col_label ] = unit; + row[columns.col_value ] = unit; + row[columns.col_tooltip ] = (""); + row[columns.col_icon ] = "NotUsed"; + row[columns.col_sensitive] = true; + } + + // Why? + gint count = _store->children().size(); + if ((count > 0) && (_active > count)) { + _setActive(--count); + } else { + _setActive(_active); + } +} + +UnitTracker::~UnitTracker() +{ + _combo_list.clear(); + + // Unhook weak references to GtkAdjustments + for (auto i : _adjList) { + g_object_weak_unref(G_OBJECT(i), _adjustmentFinalizedCB, this); + } + _adjList.clear(); +} + +bool UnitTracker::isUpdating() const +{ + return _isUpdating; +} + +Inkscape::Util::Unit const * UnitTracker::getActiveUnit() const +{ + return _activeUnit; +} + +void UnitTracker::changeLabel(Glib::ustring new_label, gint pos, bool onlylabel) +{ + ComboToolItemColumns columns; + _store->children()[pos][columns.col_label] = new_label; + if (!onlylabel) { + _store->children()[pos][columns.col_value] = new_label; + } +} + +void UnitTracker::setActiveUnit(Inkscape::Util::Unit const *unit) +{ + if (unit) { + + ComboToolItemColumns columns; + int index = 0; + for (auto& row: _store->children() ) { + Glib::ustring storedUnit = row[columns.col_value]; + if (!unit->abbr.compare (storedUnit)) { + _setActive (index); + break; + } + index++; + } + } +} + +void UnitTracker::setActiveUnitByAbbr(gchar const *abbr) +{ + Inkscape::Util::Unit const *u = unit_table.getUnit(abbr); + setActiveUnit(u); +} + +void UnitTracker::addAdjustment(GtkAdjustment *adj) +{ + if (std::find(_adjList.begin(),_adjList.end(),adj) == _adjList.end()) { + g_object_weak_ref(G_OBJECT(adj), _adjustmentFinalizedCB, this); + _adjList.push_back(adj); + } else { + std::cerr << "UnitTracker::addAjustment: Adjustment already added!" << std::endl; + } +} + +void UnitTracker::addUnit(Inkscape::Util::Unit const *u) +{ + ComboToolItemColumns columns; + + Gtk::TreeModel::Row row; + row = *(_store->append()); + row[columns.col_label ] = u ? u->abbr.c_str() : ""; + row[columns.col_value ] = u ? u->abbr.c_str() : ""; + row[columns.col_tooltip ] = (""); + row[columns.col_icon ] = "NotUsed"; + row[columns.col_sensitive] = true; +} + +void UnitTracker::prependUnit(Inkscape::Util::Unit const *u) +{ + ComboToolItemColumns columns; + + Gtk::TreeModel::Row row; + row = *(_store->prepend()); + row[columns.col_label ] = u ? u->abbr.c_str() : ""; + row[columns.col_value ] = u ? u->abbr.c_str() : ""; + row[columns.col_tooltip ] = (""); + row[columns.col_icon ] = "NotUsed"; + row[columns.col_sensitive] = true; + + /* Re-shuffle our default selection here (_active gets out of sync) */ + setActiveUnit(_activeUnit); + +} + +void UnitTracker::setFullVal(GtkAdjustment *adj, gdouble val) +{ + _priorValues[adj] = val; +} + +ComboToolItem * +UnitTracker::create_tool_item(Glib::ustring const &label, + Glib::ustring const &tooltip) +{ + auto combo = ComboToolItem::create(label, tooltip, "NotUsed", _store); + combo->set_active(_active); + combo->signal_changed().connect(sigc::mem_fun(*this, &UnitTracker::_unitChangedCB)); + combo->set_data("unit-tracker", this); + _combo_list.push_back(combo); + return combo; +} + +void UnitTracker::_unitChangedCB(int active) +{ + _setActive(active); +} + +void UnitTracker::_adjustmentFinalizedCB(gpointer data, GObject *where_the_object_was) +{ + if (data && where_the_object_was) { + UnitTracker *self = reinterpret_cast<UnitTracker *>(data); + self->_adjustmentFinalized(where_the_object_was); + } +} + +void UnitTracker::_adjustmentFinalized(GObject *where_the_object_was) +{ + GtkAdjustment* adj = (GtkAdjustment*)(where_the_object_was); + auto it = std::find(_adjList.begin(),_adjList.end(), adj); + if (it != _adjList.end()) { + _adjList.erase(it); + } else { + g_warning("Received a finalization callback for unknown object %p", where_the_object_was); + } +} + +void UnitTracker::_setActive(gint active) +{ + if ( active != _active || !_activeUnitInitialized ) { + gint oldActive = _active; + + if (_store) { + + // Find old and new units + ComboToolItemColumns columns; + int index = 0; + Glib::ustring oldAbbr( "NotFound" ); + Glib::ustring newAbbr( "NotFound" ); + for (auto& row: _store->children() ) { + if (index == _active) { + oldAbbr = row[columns.col_value]; + } + if (index == active) { + newAbbr = row[columns.col_value]; + } + if (newAbbr != "NotFound" && oldAbbr != "NotFound") break; + ++index; + } + + if (oldAbbr != "NotFound") { + + if (newAbbr != "NotFound") { + Inkscape::Util::Unit const *oldUnit = unit_table.getUnit(oldAbbr); + Inkscape::Util::Unit const *newUnit = unit_table.getUnit(newAbbr); + _activeUnit = newUnit; + + if (!_adjList.empty()) { + _fixupAdjustments(oldUnit, newUnit); + } + } else { + std::cerr << "UnitTracker::_setActive: Did not find new unit: " << active << std::endl; + } + + } else { + std::cerr << "UnitTracker::_setActive: Did not find old unit: " << oldActive + << " new: " << active << std::endl; + } + } + _active = active; + + for (auto combo : _combo_list) { + if(combo) combo->set_active(active); + } + + _activeUnitInitialized = true; + } +} + +void UnitTracker::_fixupAdjustments(Inkscape::Util::Unit const *oldUnit, Inkscape::Util::Unit const *newUnit) +{ + _isUpdating = true; + for ( auto adj : _adjList ) { + gdouble oldVal = gtk_adjustment_get_value(adj); + gdouble val = oldVal; + + if ( (oldUnit->type != Inkscape::Util::UNIT_TYPE_DIMENSIONLESS) + && (newUnit->type == Inkscape::Util::UNIT_TYPE_DIMENSIONLESS) ) + { + val = newUnit->factor * 100; + _priorValues[adj] = Inkscape::Util::Quantity::convert(oldVal, oldUnit, "px"); + } else if ( (oldUnit->type == Inkscape::Util::UNIT_TYPE_DIMENSIONLESS) + && (newUnit->type != Inkscape::Util::UNIT_TYPE_DIMENSIONLESS) ) + { + if (_priorValues.find(adj) != _priorValues.end()) { + val = Inkscape::Util::Quantity::convert(_priorValues[adj], "px", newUnit); + } + } else { + val = Inkscape::Util::Quantity::convert(oldVal, oldUnit, newUnit); + } + + gtk_adjustment_set_value(adj, val); + } + _isUpdating = false; +} + +} // namespace Widget +} // 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 : diff --git a/src/ui/widget/unit-tracker.h b/src/ui/widget/unit-tracker.h new file mode 100644 index 0000000..b85da06 --- /dev/null +++ b/src/ui/widget/unit-tracker.h @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Inkscape::UI::Widget::UnitTracker + * Simple mediator to synchronize changes to unit menus + * + * Authors: + * Jon A. Cruz <jon@joncruz.org> + * Matthew Petroff <matthew@mpetroff.net> + * + * Copyright (C) 2007 Jon A. Cruz + * Copyright (C) 2013 Matthew Petroff + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_UI_WIDGET_UNIT_TRACKER_H +#define INKSCAPE_UI_WIDGET_UNIT_TRACKER_H + +#include <map> +#include <vector> + +#include <gtkmm/liststore.h> + +#include "util/units.h" + +using Inkscape::Util::Unit; +using Inkscape::Util::UnitType; + +typedef struct _GObject GObject; +typedef struct _GtkAdjustment GtkAdjustment; +typedef struct _GtkListStore GtkListStore; + +namespace Inkscape { +namespace UI { +namespace Widget { +class ComboToolItem; + +class UnitTracker { +public: + UnitTracker(UnitType unit_type); + virtual ~UnitTracker(); + + bool isUpdating() const; + + void setActiveUnit(Inkscape::Util::Unit const *unit); + void setActiveUnitByAbbr(gchar const *abbr); + Inkscape::Util::Unit const * getActiveUnit() const; + + void addUnit(Inkscape::Util::Unit const *u); + void addAdjustment(GtkAdjustment *adj); + void prependUnit(Inkscape::Util::Unit const *u); + void setFullVal(GtkAdjustment *adj, gdouble val); + void changeLabel(Glib::ustring new_label, gint pos, bool onlylabel = false); + + ComboToolItem *create_tool_item(Glib::ustring const &label, + Glib::ustring const &tooltip); + +protected: + UnitType _type; + +private: + // Callbacks + void _unitChangedCB(int active); + static void _adjustmentFinalizedCB(gpointer data, GObject *where_the_object_was); + + void _setActive(gint index); + void _fixupAdjustments(Inkscape::Util::Unit const *oldUnit, Inkscape::Util::Unit const *newUnit); + + // Cleanup + void _adjustmentFinalized(GObject *where_the_object_was); + + gint _active; + bool _isUpdating; + Inkscape::Util::Unit const *_activeUnit; + bool _activeUnitInitialized; + + Glib::RefPtr<Gtk::ListStore> _store; + std::vector<ComboToolItem *> _combo_list; + std::vector<GtkAdjustment*> _adjList; + std::map <GtkAdjustment *, gdouble> _priorValues; +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // INKSCAPE_UI_WIDGET_UNIT_TRACKER_H |