// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * 3d box aux toolbar */ /* Authors: * MenTaLguY * Lauris Kaplinski * bulia byak * Frank Felfe * John Cliff * David Turner * Josh Andler * Jon A. Cruz * Maximilian Albert * Tavmjong Bah * Abhishek Sharma * Kris De Gussem * * Copyright (C) 2004 David Turner * Copyright (C) 2003 MenTaLguY * Copyright (C) 1999-2011 authors * Copyright (C) 2001-2002 Ximian, Inc. * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "box3d-toolbar.h" #include #include #include "desktop.h" #include "document-undo.h" #include "document.h" #include "selection.h" #include "object/box3d.h" #include "object/persp3d.h" #include "ui/icon-names.h" #include "ui/tools/box3d-tool.h" #include "ui/widget/canvas.h" #include "ui/widget/spin-button-tool-item.h" using Inkscape::DocumentUndo; namespace Inkscape { namespace UI { namespace Toolbar { Box3DToolbar::Box3DToolbar(SPDesktop *desktop) : Toolbar(desktop) { auto prefs = Inkscape::Preferences::get(); auto document = desktop->getDocument(); auto persp_impl = document->getCurrentPersp3DImpl(); /* Angle X */ { std::vector values = {-90, -60, -30, 0, 30, 60, 90}; auto angle_x_val = prefs->getDouble("/tools/shapes/3dbox/box3d_angle_x", 30); _angle_x_adj = Gtk::Adjustment::create(angle_x_val, -360.0, 360.0, 1.0, 10.0); _angle_x_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("box3d-angle-x", _("Angle X:"), _angle_x_adj)); // TRANSLATORS: PL is short for 'perspective line' _angle_x_item->set_tooltip_text(_("Angle of PLs in X direction")); _angle_x_item->set_custom_numeric_menu_data(values); _angle_x_item->set_focus_widget(desktop->canvas); _angle_x_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::angle_value_changed), _angle_x_adj, Proj::X)); add(*_angle_x_item); } if (!persp_impl || !Persp3D::VP_is_finite(persp_impl, Proj::X)) { _angle_x_item->set_sensitive(true); } else { _angle_x_item->set_sensitive(false); } /* VP X state */ { // TRANSLATORS: VP is short for 'vanishing point' _vp_x_state_item = add_toggle_button(_("State of VP in X direction"), _("Toggle VP in X direction between 'finite' and 'infinite' (=parallel)")); _vp_x_state_item->set_icon_name(INKSCAPE_ICON("perspective-parallel")); _vp_x_state_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::vp_state_changed), Proj::X)); _angle_x_item->set_sensitive( !prefs->getBool("/tools/shapes/3dbox/vp_x_state", true) ); _vp_x_state_item->set_active( prefs->getBool("/tools/shapes/3dbox/vp_x_state", true) ); } /* Angle Y */ { auto angle_y_val = prefs->getDouble("/tools/shapes/3dbox/box3d_angle_y", 30); _angle_y_adj = Gtk::Adjustment::create(angle_y_val, -360.0, 360.0, 1.0, 10.0); _angle_y_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("box3d-angle-y", _("Angle Y:"), _angle_y_adj)); // TRANSLATORS: PL is short for 'perspective line' _angle_y_item->set_tooltip_text(_("Angle of PLs in Y direction")); std::vector values = {-90, -60, -30, 0, 30, 60, 90}; _angle_y_item->set_custom_numeric_menu_data(values); _angle_y_item->set_focus_widget(desktop->canvas); _angle_y_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::angle_value_changed), _angle_y_adj, Proj::Y)); add(*_angle_y_item); } if (!persp_impl || !Persp3D::VP_is_finite(persp_impl, Proj::Y)) { _angle_y_item->set_sensitive(true); } else { _angle_y_item->set_sensitive(false); } /* VP Y state */ { // TRANSLATORS: VP is short for 'vanishing point' _vp_y_state_item = add_toggle_button(_("State of VP in Y direction"), _("Toggle VP in Y direction between 'finite' and 'infinite' (=parallel)")); _vp_y_state_item->set_icon_name(INKSCAPE_ICON("perspective-parallel")); _vp_y_state_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::vp_state_changed), Proj::Y)); _angle_y_item->set_sensitive( !prefs->getBool("/tools/shapes/3dbox/vp_y_state", true) ); _vp_y_state_item->set_active( prefs->getBool("/tools/shapes/3dbox/vp_y_state", true) ); } /* Angle Z */ { auto angle_z_val = prefs->getDouble("/tools/shapes/3dbox/box3d_angle_z", 30); _angle_z_adj = Gtk::Adjustment::create(angle_z_val, -360.0, 360.0, 1.0, 10.0); _angle_z_item = Gtk::manage(new UI::Widget::SpinButtonToolItem("box3d-angle-z", _("Angle Z:"), _angle_z_adj)); // TRANSLATORS: PL is short for 'perspective line' _angle_z_item->set_tooltip_text(_("Angle of PLs in Z direction")); std::vector values = {-90, -60, -30, 0, 30, 60, 90}; _angle_z_item->set_custom_numeric_menu_data(values); _angle_z_item->set_focus_widget(desktop->canvas); _angle_z_adj->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::angle_value_changed), _angle_z_adj, Proj::Z)); add(*_angle_z_item); } if (!persp_impl || !Persp3D::VP_is_finite(persp_impl, Proj::Z)) { _angle_z_item->set_sensitive(true); } else { _angle_z_item->set_sensitive(false); } /* VP Z state */ { // TRANSLATORS: VP is short for 'vanishing point' _vp_z_state_item = add_toggle_button(_("State of VP in Z direction"), _("Toggle VP in Z direction between 'finite' and 'infinite' (=parallel)")); _vp_z_state_item->set_icon_name(INKSCAPE_ICON("perspective-parallel")); _vp_z_state_item->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &Box3DToolbar::vp_state_changed), Proj::Z)); _angle_z_item->set_sensitive(!prefs->getBool("/tools/shapes/3dbox/vp_z_state", true)); _vp_z_state_item->set_active( prefs->getBool("/tools/shapes/3dbox/vp_z_state", true) ); } desktop->connectEventContextChanged(sigc::mem_fun(*this, &Box3DToolbar::check_ec)); show_all(); } GtkWidget * Box3DToolbar::create(SPDesktop *desktop) { auto toolbar = new Box3DToolbar(desktop); return GTK_WIDGET(toolbar->gobj()); } void Box3DToolbar::angle_value_changed(Glib::RefPtr &adj, Proj::Axis axis) { SPDocument *document = _desktop->getDocument(); // quit if run by the attr_changed or selection changed listener if (_freeze) { return; } // in turn, prevent listener from responding _freeze = true; std::list sel_persps = _desktop->getSelection()->perspList(); if (sel_persps.empty()) { // this can happen when the document is created; we silently ignore it return; } Persp3D *persp = sel_persps.front(); persp->perspective_impl->tmat.set_infinite_direction (axis, adj->get_value()); persp->updateRepr(); // TODO: use the correct axis here, too DocumentUndo::maybeDone(document, "perspangle", _("3D Box: Change perspective (angle of infinite axis)"), INKSCAPE_ICON("draw-cuboid")); _freeze = false; } void Box3DToolbar::vp_state_changed(Proj::Axis axis) { // TODO: Take all selected perspectives into account auto sel_persps = _desktop->getSelection()->perspList(); if (sel_persps.empty()) { // this can happen when the document is created; we silently ignore it return; } Persp3D *persp = sel_persps.front(); Gtk::ToggleToolButton *btn = nullptr; switch(axis) { case Proj::X: btn = _vp_x_state_item; break; case Proj::Y: btn = _vp_y_state_item; break; case Proj::Z: btn = _vp_z_state_item; break; default: return; } bool set_infinite = btn->get_active(); persp->set_VP_state (axis, set_infinite ? Proj::VP_INFINITE : Proj::VP_FINITE); } void Box3DToolbar::check_ec(SPDesktop* desktop, Inkscape::UI::Tools::ToolBase* ec) { if (dynamic_cast(ec)) { _changed = desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &Box3DToolbar::selection_changed)); selection_changed(desktop->getSelection()); } else { if (_changed) _changed.disconnect(); if (_repr) { // remove old listener _repr->removeObserver(*this); Inkscape::GC::release(_repr); _repr = nullptr; } } } Box3DToolbar::~Box3DToolbar() { if (_repr) { // remove old listener _repr->removeObserver(*this); Inkscape::GC::release(_repr); _repr = nullptr; } } /** * \param selection Should not be NULL. */ // FIXME: This should rather be put into persp3d-reference.cpp or something similar so that it reacts upon each // Change of the perspective, and not of the current selection (but how to refer to the toolbar then?) void Box3DToolbar::selection_changed(Inkscape::Selection *selection) { // Here the following should be done: If all selected boxes have finite VPs in a certain direction, // disable the angle entry fields for this direction (otherwise entering a value in them should only // update the perspectives with infinite VPs and leave the other ones untouched). Inkscape::XML::Node *persp_repr = nullptr; if (_repr) { // remove old listener _repr->removeObserver(*this); Inkscape::GC::release(_repr); _repr = nullptr; } SPItem *item = selection->singleItem(); auto box = cast(item); if (box) { // FIXME: Also deal with multiple selected boxes Persp3D *persp = box->get_perspective(); if (!persp) { g_warning("Box has no perspective set!"); return; } persp_repr = persp->getRepr(); if (persp_repr) { _repr = persp_repr; Inkscape::GC::anchor(_repr); _repr->addObserver(*this); _repr->synthesizeEvents(*this); selection->document()->setCurrentPersp3D(Persp3D::get_from_repr(_repr)); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); prefs->setString("/tools/shapes/3dbox/persp", _repr->attribute("id")); _freeze = true; resync_toolbar(_repr); _freeze = false; } } } void Box3DToolbar::resync_toolbar(Inkscape::XML::Node *persp_repr) { if (!persp_repr) { g_warning ("No perspective given to box3d_resync_toolbar()."); return; } Persp3D *persp = Persp3D::get_from_repr(persp_repr); if (!persp) { // Hmm, is it an error if this happens? return; } set_button_and_adjustment(persp, Proj::X, _angle_x_adj, _angle_x_item, _vp_x_state_item); set_button_and_adjustment(persp, Proj::Y, _angle_y_adj, _angle_y_item, _vp_y_state_item); set_button_and_adjustment(persp, Proj::Z, _angle_z_adj, _angle_z_item, _vp_z_state_item); } void Box3DToolbar::set_button_and_adjustment(Persp3D *persp, Proj::Axis axis, Glib::RefPtr& adj, UI::Widget::SpinButtonToolItem *spin_btn, Gtk::ToggleToolButton *toggle_btn) { // TODO: Take all selected perspectives into account but don't touch the state button if not all of them // have the same state (otherwise a call to box3d_vp_z_state_changed() is triggered and the states // are reset). bool is_infinite = !Persp3D::VP_is_finite(persp->perspective_impl.get(), axis); if (is_infinite) { toggle_btn->set_active(true); spin_btn->set_sensitive(true); double angle = persp->get_infinite_angle(axis); if (angle != Geom::infinity()) { // FIXME: We should catch this error earlier (don't show the spinbutton at all) adj->set_value(normalize_angle(angle)); } } else { toggle_btn->set_active(false); spin_btn->set_sensitive(false); } } void Box3DToolbar::notifyAttributeChanged(Inkscape::XML::Node &repr, GQuark, Inkscape::Util::ptr_shared, Inkscape::Util::ptr_shared) { // quit if run by the attr_changed or selection changed listener if (_freeze) { return; } // set freeze so that it can be caught in box3d_angle_z_value_changed() (to avoid calling // SPDocumentUndo::maybeDone() when the document is undo insensitive) _freeze = true; // TODO: Only update the appropriate part of the toolbar // if (!strcmp(name, "inkscape:vp_z")) { resync_toolbar(&repr); // } Persp3D *persp = Persp3D::get_from_repr(&repr); if (persp) { persp->update_box_reprs(); } _freeze = false; } /** * \brief normalize angle so that it lies in the interval [0,360] * * TODO: Isn't there something in 2Geom or cmath that does this? */ double Box3DToolbar::normalize_angle(double a) { double angle = a + ((int) (a/360.0))*360; if (angle < 0) { angle += 360.0; } return angle; } } } } /* 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 :