// SPDX-License-Identifier: GPL-2.0-or-later /** * @file Object properties dialog. */ /* * Inkscape, an Open Source vector graphics editor * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Copyright (C) 2012 Kris De Gussem * c++ version based on former C-version (GPL v2+) with authors: * Lauris Kaplinski * bulia byak * Johan Engelen * Abhishek Sharma */ #include "object-properties.h" #include #include #include "document-undo.h" #include "document.h" #include "inkscape.h" #include "style.h" #include "style-enums.h" #include "object/sp-image.h" #include "ui/icon-names.h" #include "widgets/sp-attribute-widget.h" namespace Inkscape { namespace UI { namespace Dialog { ObjectProperties::ObjectProperties() : DialogBase("/dialogs/object/", "ObjectProperties") , _blocked(false) , _current_item(nullptr) , _label_id(_("_ID:"), true) , _label_label(_("_Label:"), true) , _label_title(_("_Title:"), true) , _label_dpi(_("_DPI SVG:"), true) , _label_image_rendering(_("_Image Rendering:"), true) , _label_color(_("Highlight Color:"), true) , _highlight_color(_("Highlight Color"), "", 0xff0000ff, true) , _cb_hide(_("_Hide"), true) , _cb_lock(_("L_ock"), true) , _cb_aspect_ratio(_("Preserve Ratio"), true) , _exp_interactivity(_("_Interactivity"), true) , _attr_table(Gtk::manage(new SPAttributeTable())) { //initialize labels for the table at the bottom of the dialog _int_attrs.emplace_back("onclick"); _int_attrs.emplace_back("onmouseover"); _int_attrs.emplace_back("onmouseout"); _int_attrs.emplace_back("onmousedown"); _int_attrs.emplace_back("onmouseup"); _int_attrs.emplace_back("onmousemove"); _int_attrs.emplace_back("onfocusin"); _int_attrs.emplace_back("onfocusout"); _int_attrs.emplace_back("onload"); _int_labels.emplace_back("onclick:"); _int_labels.emplace_back("onmouseover:"); _int_labels.emplace_back("onmouseout:"); _int_labels.emplace_back("onmousedown:"); _int_labels.emplace_back("onmouseup:"); _int_labels.emplace_back("onmousemove:"); _int_labels.emplace_back("onfocusin:"); _int_labels.emplace_back("onfocusout:"); _int_labels.emplace_back("onload:"); _init(); } void ObjectProperties::_init() { set_spacing(0); auto grid_top = Gtk::manage(new Gtk::Grid()); grid_top->set_row_spacing(4); grid_top->set_column_spacing(0); grid_top->set_border_width(4); pack_start(*grid_top, false, false, 0); /* Create the label for the object id */ _label_id.set_label(_label_id.get_label() + " "); _label_id.set_halign(Gtk::ALIGN_START); _label_id.set_valign(Gtk::ALIGN_CENTER); /* Create the entry box for the object id */ _entry_id.set_tooltip_text(_("The id= attribute (only letters, digits, and the characters .-_: allowed)")); _entry_id.set_max_length(64); _entry_id.set_hexpand(); _entry_id.set_valign(Gtk::ALIGN_CENTER); _label_id.set_mnemonic_widget(_entry_id); // pressing enter in the id field is the same as clicking Set: _entry_id.signal_activate().connect(sigc::mem_fun(this, &ObjectProperties::_labelChanged)); // focus is in the id field initially: _entry_id.grab_focus(); /* Create the label for the object label */ _label_label.set_label(_label_label.get_label() + " "); _label_label.set_halign(Gtk::ALIGN_START); _label_label.set_valign(Gtk::ALIGN_CENTER); /* Create the entry box for the object label */ _entry_label.set_tooltip_text(_("A freeform label for the object")); _entry_label.set_max_length(256); _entry_label.set_hexpand(); _entry_label.set_valign(Gtk::ALIGN_CENTER); _label_label.set_mnemonic_widget(_entry_label); // pressing enter in the label field is the same as clicking Set: _entry_label.signal_activate().connect(sigc::mem_fun(this, &ObjectProperties::_labelChanged)); /* Create the label for the object title */ _label_title.set_label(_label_title.get_label() + " "); _label_title.set_halign(Gtk::ALIGN_START); _label_title.set_valign(Gtk::ALIGN_CENTER); /* Create the entry box for the object title */ _entry_title.set_sensitive (FALSE); _entry_title.set_max_length (256); _entry_title.set_hexpand(); _entry_title.set_valign(Gtk::ALIGN_CENTER); _label_title.set_mnemonic_widget(_entry_title); // pressing enter in the label field is the same as clicking Set: _entry_title.signal_activate().connect(sigc::mem_fun(this, &ObjectProperties::_labelChanged)); _label_color.set_mnemonic_widget(_highlight_color); _label_color.set_halign(Gtk::ALIGN_START); _highlight_color.connectChanged(sigc::mem_fun(*this, &ObjectProperties::_highlightChanged)); /* Create the frame for the object description */ Gtk::Label *label_desc = Gtk::manage(new Gtk::Label(_("_Description:"), true)); UI::Widget::Frame *frame_desc = Gtk::manage(new UI::Widget::Frame("", FALSE)); frame_desc->set_label_widget(*label_desc); frame_desc->set_padding (0,0,0,0); pack_start(*frame_desc, true, true, 0); /* Create the text view box for the object description */ _ft_description.set_border_width(4); _ft_description.set_sensitive(FALSE); frame_desc->add(_ft_description); _ft_description.set_shadow_type(Gtk::SHADOW_IN); _tv_description.set_wrap_mode(Gtk::WRAP_WORD); _tv_description.get_buffer()->set_text(""); _ft_description.add(_tv_description); _tv_description.add_mnemonic_label(*label_desc); /* Create the label for the object title */ _label_dpi.set_label(_label_dpi.get_label() + " "); _label_dpi.set_halign(Gtk::ALIGN_START); _label_dpi.set_valign(Gtk::ALIGN_CENTER); /* Create the entry box for the SVG DPI */ _spin_dpi.set_digits(2); _spin_dpi.set_range(1, 1200); _label_dpi.set_mnemonic_widget(_spin_dpi); // pressing enter in the label field is the same as clicking Set: _spin_dpi.signal_activate().connect(sigc::mem_fun(this, &ObjectProperties::_labelChanged)); /* Image rendering */ /* Create the label for the object ImageRendering */ _label_image_rendering.set_label(_label_image_rendering.get_label() + " "); _label_image_rendering.set_halign(Gtk::ALIGN_START); _label_image_rendering.set_valign(Gtk::ALIGN_CENTER); /* Create the combo box text for the 'image-rendering' property */ for (unsigned i = 0; enum_image_rendering[i].key; ++i) { _combo_image_rendering.append(enum_image_rendering[i].key); } _combo_image_rendering.set_tooltip_text(_("The 'image-rendering' property can influence how a bitmap is re-scaled:\n" "\t• 'auto': no preference (scaled image is usually smooth but blurred)\n" "\t• 'optimizeQuality': prefer rendering quality (usually smooth but blurred)\n" "\t• 'optimizeSpeed': prefer rendering speed (usually blocky)\n" "\t• 'crisp-edges': rescale without blurring edges (often blocky)\n" "\t• 'pixelated': render blocky\n" "Note that the specification of this property is not finalized. " "Support and interpretation of these values varies between renderers.")); _combo_image_rendering.set_valign(Gtk::ALIGN_CENTER); _label_image_rendering.set_mnemonic_widget(_combo_image_rendering); _combo_image_rendering.signal_changed().connect( sigc::mem_fun(this, &ObjectProperties::_imageRenderingChanged) ); grid_top->attach(_label_id, 0, 0, 1, 1); grid_top->attach(_entry_id, 1, 0, 1, 1); grid_top->attach(_label_label, 0, 1, 1, 1); grid_top->attach(_entry_label, 1, 1, 1, 1); grid_top->attach(_label_title, 0, 2, 1, 1); grid_top->attach(_entry_title, 1, 2, 1, 1); grid_top->attach(_label_color, 0, 3, 1, 1); grid_top->attach(_highlight_color, 1, 3, 1, 1); grid_top->attach(_label_dpi, 0, 4, 1, 1); grid_top->attach(_spin_dpi, 1, 4, 1, 1); grid_top->attach(_label_image_rendering, 0, 5, 1, 1); grid_top->attach(_combo_image_rendering, 1, 5, 1, 1); /* Check boxes */ Gtk::Box *hb_checkboxes = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); pack_start(*hb_checkboxes, Gtk::PACK_SHRINK, 0); auto grid_cb = Gtk::manage(new Gtk::Grid()); grid_cb->set_row_homogeneous(); grid_cb->set_column_homogeneous(true); grid_cb->set_border_width(4); hb_checkboxes->pack_start(*grid_cb, true, true, 0); /* Hide */ _cb_hide.set_tooltip_text (_("Check to make the object invisible")); _cb_hide.set_hexpand(); _cb_hide.set_valign(Gtk::ALIGN_CENTER); grid_cb->attach(_cb_hide, 0, 0, 1, 1); _cb_hide.signal_toggled().connect(sigc::mem_fun(this, &ObjectProperties::_hiddenToggled)); /* Lock */ // TRANSLATORS: "Lock" is a verb here _cb_lock.set_tooltip_text(_("Check to make the object insensitive (not selectable by mouse)")); _cb_lock.set_hexpand(); _cb_lock.set_valign(Gtk::ALIGN_CENTER); grid_cb->attach(_cb_lock, 1, 0, 1, 1); _cb_lock.signal_toggled().connect(sigc::mem_fun(this, &ObjectProperties::_sensitivityToggled)); /* Preserve aspect ratio */ _cb_aspect_ratio.set_tooltip_text(_("Check to preserve aspect ratio on images")); _cb_aspect_ratio.set_hexpand(); _cb_aspect_ratio.set_valign(Gtk::ALIGN_CENTER); grid_cb->attach(_cb_aspect_ratio, 0, 1, 1, 1); _cb_aspect_ratio.signal_toggled().connect(sigc::mem_fun(this, &ObjectProperties::_aspectRatioToggled)); /* Button for setting the object's id, label, title and description. */ Gtk::Button *btn_set = Gtk::manage(new Gtk::Button(_("_Set"), true)); btn_set->set_hexpand(); btn_set->set_valign(Gtk::ALIGN_CENTER); grid_cb->attach(*btn_set, 1, 1, 1, 1); btn_set->signal_clicked().connect(sigc::mem_fun(this, &ObjectProperties::_labelChanged)); /* Interactivity options */ _exp_interactivity.set_vexpand(false); pack_start(_exp_interactivity, Gtk::PACK_SHRINK); show_all(); } void ObjectProperties::update_entries() { if (_blocked || !getDesktop()) { return; } auto selection = getSelection(); if (!selection) return; if (!selection->singleItem()) { set_sensitive (false); _current_item = nullptr; //no selection anymore or multiple objects selected, means that we need //to close the connections to the previously selected object _attr_table->clear(); _highlight_color.setRgba32(0x0); return; } else { set_sensitive (true); } SPItem *item = selection->singleItem(); if (_current_item == item) { //otherwise we would end up wasting resources through the modify selection //callback when moving an object (endlessly setting the labels and recreating _attr_table) return; } _blocked = true; _cb_aspect_ratio.set_active(g_strcmp0(item->getAttribute("preserveAspectRatio"), "none") != 0); _cb_lock.set_active(item->isLocked()); /* Sensitive */ _cb_hide.set_active(item->isExplicitlyHidden()); /* Hidden */ _highlight_color.setRgba32(item->highlight_color()); _highlight_color.closeWindow(); if (item->cloned) { /* ID */ _entry_id.set_text(""); _entry_id.set_sensitive(FALSE); _label_id.set_text(_("Ref")); /* Label */ _entry_label.set_text(""); _entry_label.set_sensitive(FALSE); _label_label.set_text(_("Ref")); } else { SPObject *obj = static_cast(item); /* ID */ _entry_id.set_text(obj->getId() ? obj->getId() : ""); _entry_id.set_sensitive(TRUE); _label_id.set_markup_with_mnemonic(_("_ID:") + Glib::ustring(" ")); /* Label */ char const *currentlabel = obj->label(); char const *placeholder = ""; if (!currentlabel) { currentlabel = ""; placeholder = obj->defaultLabel(); } _entry_label.set_text(currentlabel); _entry_label.set_placeholder_text(placeholder); _entry_label.set_sensitive(TRUE); /* Title */ gchar *title = obj->title(); if (title) { _entry_title.set_text(title); g_free(title); } else { _entry_title.set_text(""); } _entry_title.set_sensitive(TRUE); /* Image Rendering */ if (SP_IS_IMAGE(item)) { _combo_image_rendering.show(); _label_image_rendering.show(); _combo_image_rendering.set_active(obj->style->image_rendering.value); if (obj->getAttribute("inkscape:svg-dpi")) { _spin_dpi.set_value(std::stod(obj->getAttribute("inkscape:svg-dpi"))); _spin_dpi.show(); _label_dpi.show(); } else { _spin_dpi.hide(); _label_dpi.hide(); } } else { _combo_image_rendering.hide(); _combo_image_rendering.unset_active(); _label_image_rendering.hide(); _spin_dpi.hide(); _label_dpi.hide(); } /* Description */ gchar *desc = obj->desc(); if (desc) { _tv_description.get_buffer()->set_text(desc); g_free(desc); } else { _tv_description.get_buffer()->set_text(""); } _ft_description.set_sensitive(TRUE); if (_current_item == nullptr) { _attr_table->set_object(obj, _int_labels, _int_attrs, (GtkWidget*) _exp_interactivity.gobj()); } else { _attr_table->change_object(obj); } _attr_table->show_all(); } _current_item = item; _blocked = false; } void ObjectProperties::_labelChanged() { if (_blocked) { return; } SPItem *item = getSelection()->singleItem(); g_return_if_fail (item != nullptr); _blocked = true; /* Retrieve the label widget for the object's id */ gchar *id = g_strdup(_entry_id.get_text().c_str()); g_strcanon(id, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.:", '_'); if (g_strcmp0(id, item->getId()) == 0) { _label_id.set_markup_with_mnemonic(_("_ID:") + Glib::ustring(" ")); } else if (!*id || !isalnum (*id)) { _label_id.set_text(_("Id invalid! ")); } else if (getDocument()->getObjectById(id) != nullptr) { _label_id.set_text(_("Id exists! ")); } else { _label_id.set_markup_with_mnemonic(_("_ID:") + Glib::ustring(" ")); item->setAttribute("id", id); DocumentUndo::done(getDocument(), _("Set object ID"), INKSCAPE_ICON("dialog-object-properties")); } g_free(id); /* Retrieve the label widget for the object's label */ Glib::ustring label = _entry_label.get_text(); /* Give feedback on success of setting the drawing object's label * using the widget's label text */ SPObject *obj = static_cast(item); char const *currentlabel = obj->label(); if (label.compare(currentlabel ? currentlabel : "")) { obj->setLabel(label.c_str()); DocumentUndo::done(getDocument(), _("Set object label"), INKSCAPE_ICON("dialog-object-properties")); } /* Retrieve the title */ if (obj->setTitle(_entry_title.get_text().c_str())) { DocumentUndo::done(getDocument(), _("Set object title"), INKSCAPE_ICON("dialog-object-properties")); } /* Retrieve the DPI */ if (SP_IS_IMAGE(obj)) { Glib::ustring dpi_value = Glib::ustring::format(_spin_dpi.get_value()); obj->setAttribute("inkscape:svg-dpi", dpi_value); DocumentUndo::done(getDocument(), _("Set image DPI"), INKSCAPE_ICON("dialog-object-properties")); } /* Retrieve the description */ Gtk::TextBuffer::iterator start, end; _tv_description.get_buffer()->get_bounds(start, end); Glib::ustring desc = _tv_description.get_buffer()->get_text(start, end, TRUE); if (obj->setDesc(desc.c_str())) { DocumentUndo::done(getDocument(), _("Set object description"), INKSCAPE_ICON("dialog-object-properties")); } _blocked = false; } void ObjectProperties::_highlightChanged(guint rgba) { if (_blocked) return; if (auto item = getSelection()->singleItem()) { item->setHighlight(rgba); DocumentUndo::done(getDocument(), _("Set item highlight color"), INKSCAPE_ICON("dialog-object-properties")); } } void ObjectProperties::_imageRenderingChanged() { if (_blocked) { return; } SPItem *item = getSelection()->singleItem(); g_return_if_fail (item != nullptr); _blocked = true; Glib::ustring scale = _combo_image_rendering.get_active_text(); // We should unset if the parent computed value is auto and the desired value is auto. SPCSSAttr *css = sp_repr_css_attr_new(); sp_repr_css_set_property(css, "image-rendering", scale.c_str()); Inkscape::XML::Node *image_node = item->getRepr(); if (image_node) { sp_repr_css_change(image_node, css, "style"); DocumentUndo::done(getDocument(), _("Set image rendering option"), INKSCAPE_ICON("dialog-object-properties")); } sp_repr_css_attr_unref(css); _blocked = false; } void ObjectProperties::_sensitivityToggled() { if (_blocked) { return; } SPItem *item = getSelection()->singleItem(); g_return_if_fail(item != nullptr); _blocked = true; item->setLocked(_cb_lock.get_active()); DocumentUndo::done(getDocument(), _cb_lock.get_active() ? _("Lock object") : _("Unlock object"), INKSCAPE_ICON("dialog-object-properties")); _blocked = false; } void ObjectProperties::_aspectRatioToggled() { if (_blocked) { return; } SPItem *item = getSelection()->singleItem(); g_return_if_fail(item != nullptr); _blocked = true; const char *active; if (_cb_aspect_ratio.get_active()) { active = "xMidYMid"; } else { active = "none"; } /* Retrieve the DPI */ if (SP_IS_IMAGE(item)) { Glib::ustring dpi_value = Glib::ustring::format(_spin_dpi.get_value()); item->setAttribute("preserveAspectRatio", active); DocumentUndo::done(getDocument(), _("Set preserve ratio"), INKSCAPE_ICON("dialog-object-properties")); } _blocked = false; } void ObjectProperties::_hiddenToggled() { if (_blocked) { return; } SPItem *item = getSelection()->singleItem(); g_return_if_fail(item != nullptr); _blocked = true; item->setExplicitlyHidden(_cb_hide.get_active()); DocumentUndo::done(getDocument(), _cb_hide.get_active() ? _("Hide object") : _("Unhide object"), INKSCAPE_ICON("dialog-object-properties")); _blocked = false; } void ObjectProperties::selectionChanged(Selection *selection) { update_entries(); } void ObjectProperties::desktopReplaced() { update_entries(); } } } } /* 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 :