diff options
| author | Liam P. White <inkscapebrony@gmail.com> | 2014-10-08 02:22:03 +0000 |
|---|---|---|
| committer | Liam P. White <inkscapebrony@gmail.com> | 2014-10-08 02:22:03 +0000 |
| commit | f3840fe9d0d423f6ddf5a68d776a903d57ffb7b5 (patch) | |
| tree | c8e28b27f5885928ccf866a69eb6cbc99fde599c /src/ui | |
| parent | Update to experimental r13565 (diff) | |
| parent | Some template cleanup... (diff) | |
| download | inkscape-f3840fe9d0d423f6ddf5a68d776a903d57ffb7b5.tar.gz inkscape-f3840fe9d0d423f6ddf5a68d776a903d57ffb7b5.zip | |
Update to experimental r13598
(bzr r13341.5.17)
Diffstat (limited to 'src/ui')
71 files changed, 4872 insertions, 98 deletions
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 56908f58e..98a5a224c 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -3,7 +3,12 @@ set(ui_SRC clipboard.cpp control-manager.cpp dialog-events.cpp + draw-anchor.cpp + interface.cpp + object-edit.cpp previewholder.cpp + shape-editor.cpp + tools-switch.cpp uxmanager.cpp cache/svg_preview_cache.cpp @@ -91,7 +96,6 @@ set(ui_SRC dialog/print.cpp dialog/symbols.cpp dialog/xml-tree.cpp - # dialog/session-player.cpp dialog/spellcheck.cpp dialog/svg-fonts-dialog.cpp dialog/swatches.cpp @@ -103,9 +107,6 @@ set(ui_SRC dialog/pixelartdialog.cpp dialog/transformation.cpp dialog/undo-history.cpp - # dialog/whiteboard-connect.cpp - # dialog/whiteboard-sharewithchat.cpp - # dialog/whiteboard-sharewithuser.cpp widget/anchor-selector.cpp widget/button.cpp @@ -158,10 +159,16 @@ set(ui_SRC control-manager.h control-types.h dialog-events.h + draw-anchor.h icon-names.h + interface.h + object-edit.h previewable.h previewfillable.h previewholder.h + shape-editor.h + tool-factory.h + tools-switch.h uxmanager.h cache/svg_preview_cache.h diff --git a/src/ui/Makefile_insert b/src/ui/Makefile_insert index 94064d0cf..7aeb4a83d 100644 --- a/src/ui/Makefile_insert +++ b/src/ui/Makefile_insert @@ -8,10 +8,21 @@ ink_common_sources += \ ui/control-types.h \ ui/dialog-events.cpp \ ui/dialog-events.h \ + ui/draw-anchor.cpp \ + ui/draw-anchor.h \ ui/icon-names.h \ + ui/interface.cpp \ + ui/interface.h \ + ui/object-edit.cpp \ + ui/object-edit.h \ ui/previewable.h \ ui/previewfillable.h \ ui/previewholder.cpp \ ui/previewholder.h \ + ui/shape-editor.cpp \ + ui/shape-editor.h \ + ui/tool-factory.h \ + ui/tools-switch.cpp \ + ui/tools-switch.h \ ui/uxmanager.cpp \ ui/uxmanager.h diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp index 1209b19cd..40500cf15 100644 --- a/src/ui/clipboard.cpp +++ b/src/ui/clipboard.cpp @@ -76,7 +76,7 @@ #include "svg/css-ostringstream.h" // used in copy #include "ui/tools/text-tool.h" #include "text-editing.h" -#include "tools-switch.h" +#include "ui/tools-switch.h" #include "path-chemistry.h" #include "util/units.h" #include "helper/png-write.h" diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index dbb7c1244..c538968d6 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -38,7 +38,7 @@ #include "sp-item-transform.h" #include "sp-text.h" #include "text-editing.h" -#include "tools-switch.h" +#include "ui/tools-switch.h" #include "ui/icon-names.h" #include "ui/tools/node-tool.h" #include "ui/tool/multi-path-manipulator.h" diff --git a/src/ui/dialog/clonetiler.cpp b/src/ui/dialog/clonetiler.cpp index dd6be2604..8d3d2b57c 100644 --- a/src/ui/dialog/clonetiler.cpp +++ b/src/ui/dialog/clonetiler.cpp @@ -38,7 +38,7 @@ #include "util/units.h" #include "helper/window.h" #include "inkscape.h" -#include "interface.h" +#include "ui/interface.h" #include "macros.h" #include "message-stack.h" #include "preferences.h" diff --git a/src/ui/dialog/desktop-tracker.h b/src/ui/dialog/desktop-tracker.h index 28f9243c8..8cb205a35 100644 --- a/src/ui/dialog/desktop-tracker.h +++ b/src/ui/dialog/desktop-tracker.h @@ -12,6 +12,7 @@ typedef struct _GtkWidget GtkWidget; class SPDesktop; +struct InkscapeApplication; namespace Inkscape { diff --git a/src/ui/dialog/dialog-manager.cpp b/src/ui/dialog/dialog-manager.cpp index 5c0437133..7b1b36908 100644 --- a/src/ui/dialog/dialog-manager.cpp +++ b/src/ui/dialog/dialog-manager.cpp @@ -72,13 +72,13 @@ inline Dialog *create() { return PanelDialog<B>::template create<T>(); } /** * This class is provided as a container for Inkscape's various - * dialogs. This allows Inkscape::Application to treat the various + * dialogs. This allows InkscapeApplication to treat the various * dialogs it invokes, as abstractions. * * DialogManager is essentially a cache of dialogs. It lets us * initialize dialogs lazily - instead of constructing them during * application startup, they're constructed the first time they're - * actually invoked by Inkscape::Application. The constructed + * actually invoked by InkscapeApplication. The constructed * dialog is held here after that, so future invokations of the * dialog don't need to get re-constructed each time. The memory for * the dialogs are then reclaimed when the DialogManager is destroyed. diff --git a/src/ui/dialog/dialog.cpp b/src/ui/dialog/dialog.cpp index 13e828337..6dabcfb6b 100644 --- a/src/ui/dialog/dialog.cpp +++ b/src/ui/dialog/dialog.cpp @@ -28,7 +28,7 @@ #include "desktop-handles.h" #include "shortcuts.h" #include "preferences.h" -#include "interface.h" +#include "ui/interface.h" #include "verbs.h" #include "ui/tool/event-utils.h" diff --git a/src/ui/dialog/dialog.h b/src/ui/dialog/dialog.h index 90501550b..9c7efd47b 100644 --- a/src/ui/dialog/dialog.h +++ b/src/ui/dialog/dialog.h @@ -18,6 +18,7 @@ #include "floating-behavior.h" class SPDesktop; +struct InkscapeApplication; namespace Inkscape { class Selection; diff --git a/src/ui/dialog/dock-behavior.cpp b/src/ui/dialog/dock-behavior.cpp index f65b298c9..50a6db208 100644 --- a/src/ui/dialog/dock-behavior.cpp +++ b/src/ui/dialog/dock-behavior.cpp @@ -18,7 +18,7 @@ #include "dock-behavior.h" #include "inkscape.h" #include "desktop.h" -#include "interface.h" +#include "ui/interface.h" #include "widgets/icon.h" #include "ui/widget/dock.h" #include "verbs.h" diff --git a/src/ui/dialog/document-properties.cpp b/src/ui/dialog/document-properties.cpp index c7fcbbfc4..93e5eb51b 100644 --- a/src/ui/dialog/document-properties.cpp +++ b/src/ui/dialog/document-properties.cpp @@ -31,13 +31,13 @@ #include "inkscape.h" #include "io/sys.h" #include "preferences.h" -#include "shape-editor.h" +#include "ui/shape-editor.h" #include "sp-namedview.h" #include "sp-root.h" #include "sp-script.h" #include "style.h" #include "svg/stringstream.h" -#include "tools-switch.h" +#include "ui/tools-switch.h" #include "ui/widget/color-picker.h" #include "ui/widget/scalar-unit.h" #include "ui/dialog/filedialog.h" @@ -1747,7 +1747,8 @@ void DocumentProperties::onDocUnitChange() doc->getRoot()->scaleChildItemsRec(Geom::Scale(scale), Geom::Point(-viewscale*doc->getRoot()->viewBox.min()[Geom::X] + (doc->getWidth().value("px") - viewscale*doc->getRoot()->viewBox.width())/2, viewscale*doc->getRoot()->viewBox.min()[Geom::Y] + - (doc->getHeight().value("px") + viewscale*doc->getRoot()->viewBox.height())/2)); + (doc->getHeight().value("px") + viewscale*doc->getRoot()->viewBox.height())/2), + false); ShapeEditor::blockSetItem(false); } prefs->setBool("/options/transform/stroke", transform_stroke); diff --git a/src/ui/dialog/export.cpp b/src/ui/dialog/export.cpp index 188135e2b..6d5d894ef 100644 --- a/src/ui/dialog/export.cpp +++ b/src/ui/dialog/export.cpp @@ -65,7 +65,7 @@ #include "ui/dialog-events.h" #include "preferences.h" #include "verbs.h" -#include "interface.h" +#include "ui/interface.h" #include "sp-root.h" #include "extension/output.h" diff --git a/src/ui/dialog/extension-editor.cpp b/src/ui/dialog/extension-editor.cpp index cd5491c24..9bdddc0e0 100644 --- a/src/ui/dialog/extension-editor.cpp +++ b/src/ui/dialog/extension-editor.cpp @@ -25,7 +25,7 @@ #include "verbs.h" #include "preferences.h" -#include "interface.h" +#include "ui/interface.h" #include "extension/extension.h" #include "extension/db.h" diff --git a/src/ui/dialog/filedialogimpl-gtkmm.cpp b/src/ui/dialog/filedialogimpl-gtkmm.cpp index 310630f3e..17cf835cd 100644 --- a/src/ui/dialog/filedialogimpl-gtkmm.cpp +++ b/src/ui/dialog/filedialogimpl-gtkmm.cpp @@ -21,9 +21,11 @@ #include <config.h> #endif +#include <iostream> + #include "filedialogimpl-gtkmm.h" #include "ui/dialog-events.h" -#include "interface.h" +#include "ui/interface.h" #include "io/sys.h" #include "path-prefix.h" #include "preferences.h" @@ -40,6 +42,8 @@ #include <glibmm/i18n.h> #include <glibmm/miscutils.h> +#include <glibmm/regex.h> + #include "document.h" #include "extension/input.h" #include "extension/output.h" @@ -191,6 +195,40 @@ void SVGPreview::showImage(Glib::ustring &theFileName) { Glib::ustring fileName = theFileName; + // Let's get real width and height from SVG file. These are template + // files so we assume they are well formed. + + // std::cout << "SVGPreview::showImage: " << theFileName << std::endl; + std::ifstream input(theFileName.c_str()); + + std::string width; + std::string height; + + if( !input ) { + std::cerr << "SVGPreview::showImage: Failed to open file: " << theFileName << std::endl; + } else { + + std::string token; + + Glib::MatchInfo match_info; + Glib::RefPtr<Glib::Regex> regex1 = Glib::Regex::create("width=\"(.*)\""); + Glib::RefPtr<Glib::Regex> regex2 = Glib::Regex::create("height=\"(.*)\""); + + while( !input.eof() && (height.empty() || width.empty()) ) { + + input >> token; + // std::cout << "|" << token << "|" << std::endl; + + if (regex1->match(token, match_info)) { + width = match_info.fetch(1).raw(); + } + + if (regex2->match(token, match_info)) { + height = match_info.fetch(1).raw(); + } + + } + } /*##################################### # LET'S HAVE SOME FUN WITH SVG! @@ -270,7 +308,7 @@ void SVGPreview::showImage(Glib::ustring &theFileName) " style=\"font-size:24.000000;font-style:normal;font-weight:normal;" " fill:#000000;fill-opacity:1.0000000;stroke:none;" " font-family:Sans\"\n" - " x=\"10\" y=\"26\">%d x %d</text>\n" //# VALUES HERE + " x=\"10\" y=\"26\">%s x %s</text>\n" //# VALUES HERE "</svg>\n\n"; // if (!Glib::get_charset()) //If we are not utf8 @@ -280,7 +318,7 @@ void SVGPreview::showImage(Glib::ustring &theFileName) /* FIXME: Do proper XML quoting for fileName. */ gchar *xmlBuffer = g_strdup_printf(xformat, previewWidth, previewHeight, imgX, imgY, scaledImgWidth, scaledImgHeight, - fileName.c_str(), rectX, rectY, rectWidth, rectHeight, imgWidth, imgHeight); + fileName.c_str(), rectX, rectY, rectWidth, rectHeight, width.c_str(), height.c_str() ); // g_message("%s\n", xmlBuffer); diff --git a/src/ui/dialog/filter-effects-dialog.cpp b/src/ui/dialog/filter-effects-dialog.cpp index c2367c2a2..bd44846a3 100644 --- a/src/ui/dialog/filter-effects-dialog.cpp +++ b/src/ui/dialog/filter-effects-dialog.cpp @@ -546,7 +546,7 @@ public: _matrix(SP_ATTR_VALUES, _("This matrix determines a linear transform on color space. Each line affects one of the color components. Each column determines how much of each color component from the input is passed to the output. The last column does not depend on input colors, so can be used to adjust a constant component value.")), _saturation("", 0, 0, 1, 0.1, 0.01, 2, SP_ATTR_VALUES), _angle("", 0, 0, 360, 0.1, 0.01, 1, SP_ATTR_VALUES), - _label(_("None"), Gtk::ALIGN_START), + _label(C_("Label", "None"), Gtk::ALIGN_START), _use_stored(false), _saturation_store(0), _angle_store(0) diff --git a/src/ui/dialog/find.cpp b/src/ui/dialog/find.cpp index 9b814bb9f..1a4823e4a 100644 --- a/src/ui/dialog/find.cpp +++ b/src/ui/dialog/find.cpp @@ -33,7 +33,7 @@ #include "ui/dialog-events.h" #include "verbs.h" -#include "interface.h" +#include "ui/interface.h" #include "preferences.h" #include "sp-text.h" #include "sp-flowtext.h" diff --git a/src/ui/dialog/floating-behavior.cpp b/src/ui/dialog/floating-behavior.cpp index f286588b2..11db14801 100644 --- a/src/ui/dialog/floating-behavior.cpp +++ b/src/ui/dialog/floating-behavior.cpp @@ -29,7 +29,7 @@ #include "inkscape.h" #include "desktop.h" #include "ui/dialog-events.h" -#include "interface.h" +#include "ui/interface.h" #include "preferences.h" #include "verbs.h" diff --git a/src/ui/dialog/guides.cpp b/src/ui/dialog/guides.cpp index 76c26a3cb..4519a905f 100644 --- a/src/ui/dialog/guides.cpp +++ b/src/ui/dialog/guides.cpp @@ -100,7 +100,7 @@ void GuidelinePropertiesDialog::_onOK() double rad_angle = Geom::deg_to_rad( deg_angle ); normal = Geom::rot90(Geom::Point::polar(rad_angle, 1.0)); } - sp_guide_set_normal(*_guide, normal, true); + _guide->set_normal(normal, true); double const points_x = _spin_button_x.getValue("px"); double const points_y = _spin_button_y.getValue("px"); @@ -108,11 +108,11 @@ void GuidelinePropertiesDialog::_onOK() if (!_mode) newpos += _oldpos; - sp_guide_moveto(*_guide, newpos, true); + _guide->moveto(newpos, true); const gchar* name = g_strdup( _label_entry.getEntry()->get_text().c_str() ); - sp_guide_set_label(*_guide, name, true); + _guide->set_label(name, true); g_free((gpointer) name); #if WITH_GTKMM_3_0 @@ -124,7 +124,7 @@ void GuidelinePropertiesDialog::_onOK() #endif //TODO: why 257? verify this! - sp_guide_set_color(*_guide, r, g, b, true); + _guide->set_color(r, g, b, true); DocumentUndo::done(_guide->document, SP_VERB_NONE, _("Set guide properties")); @@ -295,16 +295,17 @@ void GuidelinePropertiesDialog::_setup() { signal_response().connect(sigc::mem_fun(*this, &GuidelinePropertiesDialog::_response)); // initialize dialog - _oldpos = _guide->point_on_line; + _oldpos = _guide->getPoint(); if (_guide->isVertical()) { _oldangle = 90; } else if (_guide->isHorizontal()) { _oldangle = 0; } else { - _oldangle = Geom::rad_to_deg( std::atan2( - _guide->normal_to_line[Geom::X], _guide->normal_to_line[Geom::Y] ) ); + _oldangle = Geom::rad_to_deg( std::atan2( - _guide->getNormal()[Geom::X], _guide->getNormal()[Geom::Y] ) ); } { + // FIXME holy crap!!! Inkscape::XML::Node *repr = _guide->getRepr(); const gchar *guide_id = repr->attribute("id"); gchar *label = g_strdup_printf(_("Guideline ID: %s"), guide_id); @@ -312,7 +313,7 @@ void GuidelinePropertiesDialog::_setup() { g_free(label); } { - gchar *guide_description = sp_guide_description(_guide, false); + gchar *guide_description = _guide->description(false); gchar *label = g_strdup_printf(_("Current: %s"), guide_description); g_free(guide_description); _label_descr.set_markup(label); @@ -320,15 +321,15 @@ void GuidelinePropertiesDialog::_setup() { } // init name entry - _label_entry.getEntry()->set_text(_guide->label ? _guide->label : ""); + _label_entry.getEntry()->set_text(_guide->getLabel() ? _guide->getLabel() : ""); #if WITH_GTKMM_3_0 Gdk::RGBA c; - c.set_rgba(((_guide->color>>24)&0xff) / 255.0, ((_guide->color>>16)&0xff) / 255.0, ((_guide->color>>8)&0xff) / 255.0); + c.set_rgba(((_guide->getColor()>>24)&0xff) / 255.0, ((_guide->getColor()>>16)&0xff) / 255.0, ((_guide->getColor()>>8)&0xff) / 255.0); _color.set_rgba(c); #else Gdk::Color c; - c.set_rgb_p(((_guide->color>>24)&0xff) / 255.0, ((_guide->color>>16)&0xff) / 255.0, ((_guide->color>>8)&0xff) / 255.0); + c.set_rgb_p(((_guide->getColor()>>24)&0xff) / 255.0, ((_guide->getColor()>>16)&0xff) / 255.0, ((_guide->getColor()>>8)&0xff) / 255.0); _color.set_color(c); #endif diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index 2b1198ba0..e113b325f 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -19,6 +19,7 @@ #include "inkscape-preferences.h" #include <glibmm/i18n.h> +#include <glibmm/markup.h> #include <glibmm/miscutils.h> #include <gtkmm/main.h> #include <gtkmm/frame.h> @@ -330,7 +331,7 @@ void InkscapePreferences::initPageTools() _page_selector.add_line( true, "", _t_sel_trans_outl, "", _("Show only a box outline of the objects when moving or transforming")); _page_selector.add_group_header( _("Per-object selection cue")); - _t_sel_cue_none.init ( _("None"), "/options/selcue/value", Inkscape::SelCue::NONE, false, 0); + _t_sel_cue_none.init ( C_("Selection cue", "None"), "/options/selcue/value", Inkscape::SelCue::NONE, false, 0); _page_selector.add_line( true, "", _t_sel_cue_none, "", _("No per-object selection indication")); _t_sel_cue_mark.init ( _("Mark"), "/options/selcue/value", Inkscape::SelCue::MARK, true, &_t_sel_cue_none); @@ -525,11 +526,11 @@ void InkscapePreferences::initPageUI() _("Mongolian (mn)"), _("Nepali (ne)"), _("Norwegian Bokmål (nb)"), _("Norwegian Nynorsk (nn)"), _("Panjabi (pa)"), _("Polish (pl)"), _("Portuguese (pt)"), _("Portuguese/Brazil (pt_BR)"), _("Romanian (ro)"), _("Russian (ru)"), _("Serbian (sr)"), _("Serbian in Latin script (sr@latin)"), _("Slovak (sk)"), _("Slovenian (sl)"), _("Spanish (es)"), _("Spanish/Mexico (es_MX)"), - _("Swedish (sv)"),_("Telugu (te_IN)"), _("Thai (th)"), _("Turkish (tr)"), _("Ukrainian (uk)"), _("Vietnamese (vi)")}; + _("Swedish (sv)"),_("Telugu (te)"), _("Thai (th)"), _("Turkish (tr)"), _("Ukrainian (uk)"), _("Vietnamese (vi)")}; Glib::ustring langValues[] = {"", "sq", "am", "ar", "hy", "az", "eu", "be", "bg", "bn", "bn_BD", "br", "ca", "ca@valencia", "zh_CN", "zh_TW", "hr", "cs", "da", "nl", "dz", "de", "el", "en", "en_AU", "en_CA", "en_GB", "en_US@piglatin", "eo", "et", "fa", "fi", "fr", "ga", "gl", "he", "hu", "id", "it", "ja", "km", "rw", "ko", "lt", "lv", "mk", "mn", "ne", "nb", "nn", "pa", - "pl", "pt", "pt_BR", "ro", "ru", "sr", "sr@latin", "sk", "sl", "es", "es_MX", "sv", "te_IN", "th", "tr", "uk", "vi" }; + "pl", "pt", "pt_BR", "ro", "ru", "sr", "sr@latin", "sk", "sl", "es", "es_MX", "sv", "te", "th", "tr", "uk", "vi" }; { // sorting languages according to translated name @@ -641,7 +642,7 @@ void InkscapePreferences::initPageUI() _win_save_viewport.init ( _("Save and restore documents viewport"), "/options/savedocviewport/value", true); _win_zoom_resize.init ( _("Zoom when window is resized"), "/options/stickyzoom/value", false); _win_show_close.init ( _("Show close button on dialogs"), "/dialogs/showclose", false); - _win_ontop_none.init ( _("None"), "/options/transientpolicy/value", 0, false, 0); + _win_ontop_none.init ( C_("Dialog on top", "None"), "/options/transientpolicy/value", 0, false, 0); _win_ontop_normal.init ( _("Normal"), "/options/transientpolicy/value", 1, true, &_win_ontop_none); _win_ontop_agressive.init ( _("Aggressive"), "/options/transientpolicy/value", 2, false, &_win_ontop_none); @@ -1259,7 +1260,7 @@ void InkscapePreferences::initPageBehavior() _page_steps.add_line( false, "", _steps_compass, "", _("When on, angles are displayed with 0 at north, 0 to 360 range, positive clockwise; otherwise with 0 at east, -180 to 180 range, positive counterclockwise")); int const num_items = 17; - Glib::ustring labels[num_items] = {"90", "60", "45", "36", "30", "22.5", "18", "15", "12", "10", "7.5", "6", "3", "2", "1", "0.5", _("None")}; + Glib::ustring labels[num_items] = {"90", "60", "45", "36", "30", "22.5", "18", "15", "12", "10", "7.5", "6", "3", "2", "1", "0.5", C_("Rotation angle", "None")}; int values[num_items] = {2, 3, 4, 5, 6, 8, 10, 12, 15, 18, 24, 30, 60, 90, 180, 360, 0}; _steps_rot_snap.set_size_request(_sb_width); _steps_rot_snap.init("/options/rotationsnapsperpi/value", labels, values, num_items, 12); @@ -1835,7 +1836,7 @@ void InkscapePreferences::initPageSpellcheck() AspellDictInfoEnumeration *dels = aspell_dict_info_list_elements(dlist); - languages.push_back(Glib::ustring(_("None"))); + languages.push_back(Glib::ustring(C_("Spellchecker language", "None"))); langValues.push_back(Glib::ustring("")); const AspellDictInfo *entry; @@ -2054,7 +2055,8 @@ void InkscapePreferences::on_pagelist_selection_changed() if (!_init) { prefs->setInt("/dialogs/preferences/page", row[_page_list_columns._col_id]); } - _page_title.set_markup("<span size='large'><b>" + row[_page_list_columns._col_name] + "</b></span>"); + Glib::ustring col_name_escaped = Glib::Markup::escape_text( row[_page_list_columns._col_name] ); + _page_title.set_markup("<span size='large'><b>" + col_name_escaped + "</b></span>"); _page_frame.add(*_current_page); _current_page->show(); while (Gtk::Main::events_pending()) diff --git a/src/ui/dialog/input.cpp b/src/ui/dialog/input.cpp index 4be6716a5..8343cd6fe 100644 --- a/src/ui/dialog/input.cpp +++ b/src/ui/dialog/input.cpp @@ -1622,7 +1622,7 @@ void InputDialogImpl::ConfPanel::setAxis(gint count) if (barNum < count) { row[axisColumns.value] = Glib::ustring::format(barNum+1); } else { - row[axisColumns.value] = _("None"); + row[axisColumns.value] = C_("Input device axe", "None"); } } diff --git a/src/ui/dialog/objects.cpp b/src/ui/dialog/objects.cpp index 07a27c96e..93d5dfbd5 100644 --- a/src/ui/dialog/objects.cpp +++ b/src/ui/dialog/objects.cpp @@ -43,7 +43,7 @@ #include "sp-root.h" #include "sp-shape.h" #include "style.h" -#include "tools-switch.h" +#include "ui/tools-switch.h" #include "ui/icon-names.h" #include "ui/widget/imagetoggler.h" #include "ui/widget/layertypeicon.h" diff --git a/src/ui/dialog/ocaldialogs.cpp b/src/ui/dialog/ocaldialogs.cpp index 607087f6d..c4dd9df98 100644 --- a/src/ui/dialog/ocaldialogs.cpp +++ b/src/ui/dialog/ocaldialogs.cpp @@ -25,7 +25,7 @@ #include "path-prefix.h" #include "filedialogimpl-gtkmm.h" -#include "interface.h" +#include "ui/interface.h" #include "gc-core.h" #include "ui/dialog-events.h" #include "io/sys.h" diff --git a/src/ui/dialog/pixelartdialog.cpp b/src/ui/dialog/pixelartdialog.cpp index 2d25f54d7..5113f172a 100644 --- a/src/ui/dialog/pixelartdialog.cpp +++ b/src/ui/dialog/pixelartdialog.cpp @@ -385,8 +385,8 @@ void PixelArtDialogImpl::vectorize() if ( input.pixbuf->get_width() > 256 || input.pixbuf->get_height() > 256 ) { - char *msg = _("Image looks too big. Process may take a while and is" - " wise to save your document before continue." + char *msg = _("Image looks too big. Process may take a while and it is" + " wise to save your document before continuing." "\n\nContinue the procedure (without saving)?"); Gtk::MessageDialog dialog(msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK_CANCEL, true); diff --git a/src/ui/dialog/spellcheck.cpp b/src/ui/dialog/spellcheck.cpp index a887a7355..9faa8a2cb 100644 --- a/src/ui/dialog/spellcheck.cpp +++ b/src/ui/dialog/spellcheck.cpp @@ -23,9 +23,9 @@ #include "selection.h" #include "desktop.h" #include "desktop-handles.h" -#include "tools-switch.h" +#include "ui/tools-switch.h" #include "ui/tools/text-tool.h" -#include "interface.h" +#include "ui/interface.h" #include "preferences.h" #include "sp-text.h" #include "sp-flowtext.h" diff --git a/src/ui/dialog/template-load-tab.cpp b/src/ui/dialog/template-load-tab.cpp index ff94aad5d..fca1f7b30 100644 --- a/src/ui/dialog/template-load-tab.cpp +++ b/src/ui/dialog/template-load-tab.cpp @@ -24,7 +24,7 @@ #include "extension/db.h" #include "extension/effect.h" #include "inkscape.h" -#include "interface.h" +#include "ui/interface.h" #include "file.h" #include "path-prefix.h" #include "preferences.h" diff --git a/src/ui/dialog/text-edit.cpp b/src/ui/dialog/text-edit.cpp index e44809c65..0f4a6f7f1 100644 --- a/src/ui/dialog/text-edit.cpp +++ b/src/ui/dialog/text-edit.cpp @@ -32,7 +32,6 @@ extern "C" { #include <gtkmm/stock.h> #include <libnrtype/font-instance.h> -#include <libnrtype/font-style-to-pos.h> #include <libnrtype/font-lister.h> #include <xml/repr.h> @@ -52,7 +51,7 @@ extern "C" { #include "ui/icon-names.h" #include "preferences.h" #include "verbs.h" -#include "interface.h" +#include "ui/interface.h" #include "svg/css-ostringstream.h" #include "widgets/icon.h" #include "widgets/font-selector.h" diff --git a/src/ui/dialog/xml-tree.cpp b/src/ui/dialog/xml-tree.cpp index 9a8c188b2..7ab6c78ba 100644 --- a/src/ui/dialog/xml-tree.cpp +++ b/src/ui/dialog/xml-tree.cpp @@ -30,7 +30,7 @@ #include "ui/tools/tool-base.h" #include "helper/window.h" #include "inkscape.h" -#include "interface.h" +#include "ui/interface.h" #include "macros.h" #include "message-context.h" #include "message-stack.h" diff --git a/src/ui/draw-anchor.cpp b/src/ui/draw-anchor.cpp new file mode 100644 index 000000000..84c919018 --- /dev/null +++ b/src/ui/draw-anchor.cpp @@ -0,0 +1,105 @@ +/** \file + * Anchors implementation. + */ + +/* + * Authors: + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * Copyright (C) 2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "ui/draw-anchor.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "ui/tools/tool-base.h" +#include "ui/tools/lpe-tool.h" +#include "display/sodipodi-ctrl.h" +#include "display/curve.h" +#include "ui/control-manager.h" + +using Inkscape::ControlManager; + +#define FILL_COLOR_NORMAL 0xffffff7f +#define FILL_COLOR_MOUSEOVER 0xff0000ff + +/** + * Creates an anchor object and initializes it. + */ +SPDrawAnchor *sp_draw_anchor_new(Inkscape::UI::Tools::FreehandBase *dc, SPCurve *curve, bool start, Geom::Point delta) +{ + if (SP_IS_LPETOOL_CONTEXT(dc)) { + // suppress all kinds of anchors in LPEToolContext + return NULL; + } + + SPDrawAnchor *a = g_new(SPDrawAnchor, 1); + + a->dc = dc; + a->curve = curve; + curve->ref(); + a->start = start; + a->active = FALSE; + a->dp = delta; + a->ctrl = ControlManager::getManager().createControl(sp_desktop_controls(&dc->getDesktop()), Inkscape::CTRL_TYPE_ANCHOR); + + SP_CTRL(a->ctrl)->moveto(delta); + + ControlManager::getManager().track(a->ctrl); + + return a; +} + +/** + * Destroys the anchor's canvas item and frees the anchor object. + */ +SPDrawAnchor *sp_draw_anchor_destroy(SPDrawAnchor *anchor) +{ + if (anchor->curve) { + anchor->curve->unref(); + } + if (anchor->ctrl) { + sp_canvas_item_destroy(anchor->ctrl); + } + g_free(anchor); + return NULL; +} + +/** + * Test if point is near anchor, if so fill anchor on canvas and return + * pointer to it or NULL. + */ +SPDrawAnchor *sp_draw_anchor_test(SPDrawAnchor *anchor, Geom::Point w, bool activate) +{ + SPCtrl *ctrl = SP_CTRL(anchor->ctrl); + + if ( activate && ( Geom::LInfty( w - anchor->dc->getDesktop().d2w(anchor->dp) ) <= (ctrl->box.width() / 2.0) ) ) { + if (!anchor->active) { + g_object_set(anchor->ctrl, "fill_color", FILL_COLOR_MOUSEOVER, NULL); + anchor->active = TRUE; + } + return anchor; + } + + if (anchor->active) { + g_object_set(anchor->ctrl, "fill_color", FILL_COLOR_NORMAL, NULL); + anchor->active = FALSE; + } + return NULL; +} + + +/* + 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/draw-anchor.h b/src/ui/draw-anchor.h new file mode 100644 index 000000000..1f7b55920 --- /dev/null +++ b/src/ui/draw-anchor.h @@ -0,0 +1,52 @@ +#ifndef SEEN_DRAW_ANCHOR_H +#define SEEN_DRAW_ANCHOR_H + +/** \file + * Drawing anchors. + */ + +#include <2geom/point.h> + +namespace Inkscape { +namespace UI { +namespace Tools { + +class FreehandBase; + +} +} +} + +class SPCurve; +struct SPCanvasItem; + +/// The drawing anchor. +/// \todo Make this a regular knot, this will allow to set statusbar tips. +struct SPDrawAnchor { + Inkscape::UI::Tools::FreehandBase *dc; + SPCurve *curve; + unsigned int start : 1; + unsigned int active : 1; + Geom::Point dp; + SPCanvasItem *ctrl; +}; + + +SPDrawAnchor *sp_draw_anchor_new(Inkscape::UI::Tools::FreehandBase *dc, SPCurve *curve, bool start, + Geom::Point delta); +SPDrawAnchor *sp_draw_anchor_destroy(SPDrawAnchor *anchor); +SPDrawAnchor *sp_draw_anchor_test(SPDrawAnchor *anchor, Geom::Point w, bool activate); + + +#endif /* !SEEN_DRAW_ANCHOR_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/interface.cpp b/src/ui/interface.cpp new file mode 100644 index 000000000..7abbb5934 --- /dev/null +++ b/src/ui/interface.cpp @@ -0,0 +1,2230 @@ +/** + * @file + * Main UI stuff. + */ +/* Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Frank Felfe <innerspace@iname.com> + * bulia byak <buliabyak@users.sf.net> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * Kris De Gussem <Kris.DeGussem@gmail.com> + * + * Copyright (C) 2012 Kris De Gussem + * Copyright (C) 2010 authors + * Copyright (C) 1999-2005 authors + * Copyright (C) 2004 David Turner + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "ui/dialog/dialog-manager.h" +#include <gtkmm/icontheme.h> +#include "file.h" +#include <gtkmm/imagemenuitem.h> +#include <gtkmm/separatormenuitem.h> + +#include "inkscape.h" +#include "extension/db.h" +#include "extension/effect.h" +#include "extension/input.h" +#include "widgets/icon.h" +#include "preferences.h" +#include "path-prefix.h" +#include "shortcuts.h" +#include "document.h" +#include "desktop-handles.h" +#include "ui/interface.h" +#include "desktop.h" +#include "selection.h" +#include "selection-chemistry.h" +#include "svg-view-widget.h" +#include "widgets/desktop-widget.h" +#include "sp-item-group.h" +#include "sp-text.h" +#include "sp-gradient.h" +#include "sp-flowtext.h" +#include "sp-namedview.h" +#include "sp-root.h" +#include "ui/view/view.h" +#include "helper/action.h" +#include "helper/action-context.h" +#include "helper/gnome-utils.h" +#include "helper/window.h" +#include "io/sys.h" +#include "ui/dialog-events.h" +#include "message-context.h" +#include "ui/uxmanager.h" +#include "ui/clipboard.h" + +#include "display/sp-canvas.h" +#include "color.h" +#include "svg/svg-color.h" +#include "desktop-style.h" +#include "style.h" +#include "ui/tools/tool-base.h" +#include "gradient-drag.h" +#include "widgets/ege-paint-def.h" +#include "document-undo.h" +#include "sp-anchor.h" +#include "sp-clippath.h" +#include "sp-image.h" +#include "sp-item.h" +#include "sp-mask.h" +#include "message-stack.h" +#include "ui/dialog/layer-properties.h" + +#include <gdk/gdkkeysyms.h> + +#include <glibmm/miscutils.h> + +using Inkscape::DocumentUndo; + +/* Drag and Drop */ +typedef enum { + URI_LIST, + SVG_XML_DATA, + SVG_DATA, + PNG_DATA, + JPEG_DATA, + IMAGE_DATA, + APP_X_INKY_COLOR, + APP_X_COLOR, + APP_OSWB_COLOR, + APP_X_INK_PASTE +} ui_drop_target_info; + +static GtkTargetEntry ui_drop_target_entries [] = { + {(gchar *)"text/uri-list", 0, URI_LIST }, + {(gchar *)"image/svg+xml", 0, SVG_XML_DATA }, + {(gchar *)"image/svg", 0, SVG_DATA }, + {(gchar *)"image/png", 0, PNG_DATA }, + {(gchar *)"image/jpeg", 0, JPEG_DATA }, +#if ENABLE_MAGIC_COLORS + {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR}, +#endif // ENABLE_MAGIC_COLORS + {(gchar *)"application/x-oswb-color", 0, APP_OSWB_COLOR }, + {(gchar *)"application/x-color", 0, APP_X_COLOR }, + {(gchar *)"application/x-inkscape-paste", 0, APP_X_INK_PASTE } +}; + +static GtkTargetEntry *completeDropTargets = 0; +static int completeDropTargetsCount = 0; +static bool temporarily_block_actions = false; + +#define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0]) +static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries); +static void sp_ui_import_files(gchar *buffer); +static void sp_ui_import_one_file(char const *filename); +static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused); +static void sp_ui_drag_data_received(GtkWidget *widget, + GdkDragContext *drag_context, + gint x, gint y, + GtkSelectionData *data, + guint info, + guint event_time, + gpointer user_data); +static void sp_ui_drag_motion( GtkWidget *widget, + GdkDragContext *drag_context, + gint x, gint y, + GtkSelectionData *data, + guint info, + guint event_time, + gpointer user_data ); +static void sp_ui_drag_leave( GtkWidget *widget, + GdkDragContext *drag_context, + guint event_time, + gpointer user_data ); +static void sp_ui_menu_item_set_name(GtkWidget *data, + Glib::ustring const &name); +static void sp_recent_open(GtkRecentChooser *, gpointer); + +static void injectRenamedIcons(); + +static const int MIN_ONSCREEN_DISTANCE = 50; + +void +sp_create_window(SPViewWidget *vw, bool editable) +{ + g_return_if_fail(vw != NULL); + g_return_if_fail(SP_IS_VIEW_WIDGET(vw)); + + Gtk::Window *win = Inkscape::UI::window_new("", TRUE); + + gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw)); + gtk_widget_show(GTK_WIDGET(vw)); + + if (editable) { + g_object_set_data(G_OBJECT(vw), "window", win); + + SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw); + SPDesktop* desktop = desktop_widget->desktop; + + desktop_widget->window = win; + + win->set_data("desktop", desktop); + win->set_data("desktopwidget", desktop_widget); + + win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI)); + win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent)); + win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent)); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint prefs_geometry = + (2==prefs->getInt("/options/savewindowgeometry/value", 0)); + if (prefs_geometry) { + gint pw = prefs->getInt("/desktop/geometry/width", -1); + gint ph = prefs->getInt("/desktop/geometry/height", -1); + gint px = prefs->getInt("/desktop/geometry/x", -1); + gint py = prefs->getInt("/desktop/geometry/y", -1); + gint full = prefs->getBool("/desktop/geometry/fullscreen"); + gint maxed = prefs->getBool("/desktop/geometry/maximized"); + if (pw>0 && ph>0) { + gint w = MIN(gdk_screen_width(), pw); + gint h = MIN(gdk_screen_height(), ph); + gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px); + gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py); + if (w>0 && h>0) { + x = MIN(gdk_screen_width() - w, x); + y = MIN(gdk_screen_height() - h, y); + desktop->setWindowSize(w, h); + } + + // Only restore xy for the first window so subsequent windows don't overlap exactly + // with first. (Maybe rule should be only restore xy if it's different from xy of + // other desktops?) + + // Empirically it seems that active_desktop==this desktop only the first time a + // desktop is created. + SPDesktop *active_desktop = SP_ACTIVE_DESKTOP; + if (active_desktop == desktop || active_desktop==NULL) { + desktop->setWindowPosition(Geom::Point(x, y)); + } + } + if (maxed) { + win->maximize(); + } + if (full) { + win->fullscreen(); + } + } + + } + + if ( completeDropTargets == 0 || completeDropTargetsCount == 0 ) + { + std::vector<gchar*> types; + + GSList *list = gdk_pixbuf_get_formats(); + while ( list ) { + int i = 0; + GdkPixbufFormat *one = (GdkPixbufFormat*)list->data; + gchar** typesXX = gdk_pixbuf_format_get_mime_types(one); + for ( i = 0; typesXX[i]; i++ ) { + types.push_back(g_strdup(typesXX[i])); + } + g_strfreev(typesXX); + + list = g_slist_next(list); + } + completeDropTargetsCount = nui_drop_target_entries + types.size(); + completeDropTargets = new GtkTargetEntry[completeDropTargetsCount]; + for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) { + completeDropTargets[i] = ui_drop_target_entries[i]; + } + int pos = nui_drop_target_entries; + + for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) { + completeDropTargets[pos].target = *it; + completeDropTargets[pos].flags = 0; + completeDropTargets[pos].info = IMAGE_DATA; + pos++; + } + } + + gtk_drag_dest_set((GtkWidget*)win->gobj(), + GTK_DEST_DEFAULT_ALL, + completeDropTargets, + completeDropTargetsCount, + GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE)); + + + g_signal_connect(G_OBJECT(win->gobj()), + "drag_data_received", + G_CALLBACK(sp_ui_drag_data_received), + NULL); + g_signal_connect(G_OBJECT(win->gobj()), + "drag_motion", + G_CALLBACK(sp_ui_drag_motion), + NULL); + g_signal_connect(G_OBJECT(win->gobj()), + "drag_leave", + G_CALLBACK(sp_ui_drag_leave), + NULL); + win->show(); + + // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet + if ( SP_IS_DESKTOP_WIDGET(vw) ) { + INKSCAPE.reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop); + } +} + +void +sp_ui_new_view() +{ + SPDocument *document; + SPViewWidget *dtw; + + document = SP_ACTIVE_DOCUMENT; + if (!document) return; + + dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL)); + g_return_if_fail(dtw != NULL); + + sp_create_window(dtw, TRUE); + sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view)); + sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view)); +} + +void sp_ui_new_view_preview() +{ + SPDocument *document = SP_ACTIVE_DOCUMENT; + if ( document ) { + SPViewWidget *dtw = reinterpret_cast<SPViewWidget *>(sp_svg_view_widget_new(document)); + g_return_if_fail(dtw != NULL); + SP_SVG_VIEW_WIDGET(dtw)->setResize(true, 400.0, 400.0); + + sp_create_window(dtw, FALSE); + } +} + +void +sp_ui_close_view(GtkWidget */*widget*/) +{ + SPDesktop *dt = SP_ACTIVE_DESKTOP; + + if (dt == NULL) { + return; + } + + if (dt->shutdown()) { + return; // Shutdown operation has been canceled, so do nothing + } + + // If closing the last document, open a new document so Inkscape doesn't quit. + std::list<SPDesktop *> desktops; + INKSCAPE.get_all_desktops(desktops); + if (desktops.size() == 1) { + Glib::ustring templateUri = sp_file_default_template_uri(); + SPDocument *doc = SPDocument::createNewDoc( templateUri.c_str() , TRUE, true ); + // Set viewBox if it doesn't exist + if (!doc->getRoot()->viewBox_set) { + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDefaultUnit()), doc->getHeight().value(doc->getDefaultUnit()))); + } + dt->change_document(doc); + sp_namedview_window_from_document(dt); + sp_namedview_update_layers_from_document(dt); + return; + } + + // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP, + // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad) + dt->destroyWidget(); +} + + +unsigned int +sp_ui_close_all(void) +{ + /* Iterate through all the windows, destroying each in the order they + become active */ + while (SP_ACTIVE_DESKTOP) { + SPDesktop *dt = SP_ACTIVE_DESKTOP; + if (dt->shutdown()) { + /* The user canceled the operation, so end doing the close */ + return FALSE; + } + // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP, + // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad) + dt->destroyWidget(); + } + + return TRUE; +} + +/* + * Some day when the right-click menus are ready to start working + * smarter with the verbs, we'll need to change this NULL being + * sent to sp_action_perform to something useful, or set some kind + * of global "right-clicked position" variable for actions to + * investigate when they're called. + */ +static void +sp_ui_menu_activate(void */*object*/, SPAction *action) +{ + if (!temporarily_block_actions) { + sp_action_perform(action, NULL); + } +} + +static void +sp_ui_menu_select_action(void */*object*/, SPAction *action) +{ + sp_action_get_view(action)->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip); +} + +static void +sp_ui_menu_deselect_action(void */*object*/, SPAction *action) +{ + sp_action_get_view(action)->tipsMessageContext()->clear(); +} + +static void +sp_ui_menu_select(gpointer object, gpointer tip) +{ + Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view")); + view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip); +} + +static void +sp_ui_menu_deselect(gpointer object) +{ + Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view")); + view->tipsMessageContext()->clear(); +} + +/** + * Creates and attaches a scaled icon to the given menu item. + */ +static void +sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name ) +{ + static bool iconsInjected = false; + if ( !iconsInjected ) { + iconsInjected = true; + injectRenamedIcons(); + } + GtkWidget *icon; + + icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name ); + gtk_widget_show(icon); + gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon); +} // end of sp_ui_menu_add_icon + +void +sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c) +{ + SPAction *action; + unsigned int shortcut; + gchar *s; + gchar *atitle; + + action = verb->get_action(Inkscape::ActionContext()); + if (!action) + return; + + atitle = sp_action_get_title(action); + + s = g_stpcpy(c, atitle); + + g_free(atitle); + + shortcut = sp_shortcut_get_primary(verb); + if (shortcut!=GDK_KEY_VoidSymbol) { + gchar* key = sp_shortcut_get_label(shortcut); + s = g_stpcpy(s, " ("); + s = g_stpcpy(s, key); + s = g_stpcpy(s, ")"); + g_free(key); + } +} + + +/** + * Appends a custom menu UI from a verb. + * + * @see ContextMenu::AppendItemFromVerb for a c++ified alternative. Consider dropping sp_ui_menu_append_item_from_verb when c++ifying interface.cpp. + */ +static GtkWidget *sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL) +{ + SPAction *action; + GtkWidget *item; + + if (verb->get_code() == SP_VERB_NONE) { + + item = gtk_separator_menu_item_new(); + + } else { + + action = verb->get_action(Inkscape::ActionContext(view)); + if (!action) return NULL; + + if (radio) { + item = gtk_radio_menu_item_new_with_mnemonic(group, action->name); + } else { + item = gtk_image_menu_item_new_with_mnemonic(action->name); + } + + gtk_label_set_markup_with_mnemonic( GTK_LABEL(gtk_bin_get_child(GTK_BIN (item))), action->name); + + GtkAccelGroup *accel_group = sp_shortcut_get_accel_group(); + gtk_menu_set_accel_group(menu, accel_group); + + sp_shortcut_add_accelerator(item, sp_shortcut_get_primary(verb)); + + action->signal_set_sensitive.connect( + sigc::bind<0>( + sigc::ptr_fun(>k_widget_set_sensitive), + item)); + action->signal_set_name.connect( + sigc::bind<0>( + sigc::ptr_fun(&sp_ui_menu_item_set_name), + item)); + + if (!action->sensitive) { + gtk_widget_set_sensitive(item, FALSE); + } + + if (action->image) { + sp_ui_menuitem_add_icon(item, action->image); + } + gtk_widget_set_events(item, GDK_KEY_PRESS_MASK); + g_object_set_data(G_OBJECT(item), "view", (gpointer) view); + g_signal_connect( G_OBJECT(item), "activate", G_CALLBACK(sp_ui_menu_activate), action ); + g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action ); + g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action ); + } + + gtk_widget_show(item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + + return item; + +} // end of sp_ui_menu_append_item_from_verb + + +Glib::ustring getLayoutPrefPath( Inkscape::UI::View::View *view ) +{ + Glib::ustring prefPath; + + if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) { + prefPath = "/focus/"; + } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) { + prefPath = "/fullscreen/"; + } else { + prefPath = "/window/"; + } + + return prefPath; +} + +static void +checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data) +{ + gchar const *pref = (gchar const *) user_data; + Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view"); + SPAction *action = (SPAction *) g_object_get_data(G_OBJECT(menuitem), "action"); + + if (action) { + + sp_ui_menu_activate(menuitem, action); + + } else if (pref) { + // All check menu items should have actions now, but just in case + Glib::ustring pref_path = getLayoutPrefPath( view ); + pref_path += pref; + pref_path += "/state"; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gboolean checked = gtk_check_menu_item_get_active(menuitem); + prefs->setBool(pref_path, checked); + + reinterpret_cast<SPDesktop*>(view)->layoutWidget(); + } +} + +static bool getViewStateFromPref(Inkscape::UI::View::View *view, gchar const *pref) +{ + Glib::ustring pref_path = getLayoutPrefPath( view ); + pref_path += pref; + pref_path += "/state"; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + return prefs->getBool(pref_path, true); +} + +#if GTK_CHECK_VERSION(3,0,0) +static gboolean checkitem_update(GtkWidget *widget, cairo_t * /*cr*/, gpointer user_data) +#else +static gboolean checkitem_update(GtkWidget *widget, GdkEventExpose * /*event*/, gpointer user_data) +#endif +{ + GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget); + + gchar const *pref = (gchar const *) user_data; + Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view"); + SPAction *action = (SPAction *) g_object_get_data(G_OBJECT(menuitem), "action"); + SPDesktop *dt = static_cast<SPDesktop*>(view); + + bool ison = false; + if (action) { + + if (!strcmp(action->id, "ToggleGrid")) { + ison = dt->gridsEnabled(); + } + else if (!strcmp(action->id, "ToggleGuides")) { + ison = dt->namedview->getGuides(); + } + else if (!strcmp(action->id, "ViewCmsToggle")) { + ison = dt->colorProfAdjustEnabled(); + } + else { + ison = getViewStateFromPref(view, pref); + } + } else if (pref) { + // The Show/Hide menu items without actions + ison = getViewStateFromPref(view, pref); + } + + g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data); + gtk_check_menu_item_set_active(menuitem, ison); + g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data); + + return FALSE; +} + + +static void taskToggled(GtkCheckMenuItem *menuitem, gpointer userData) +{ + if ( gtk_check_menu_item_get_active(menuitem) ) { + gint taskNum = GPOINTER_TO_INT(userData); + taskNum = (taskNum < 0) ? 0 : (taskNum > 2) ? 2 : taskNum; + + Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view"); + + // note: this will change once more options are in the task set support: + Inkscape::UI::UXManager::getInstance()->setTask( dynamic_cast<SPDesktop*>(view), taskNum ); + } +} + + +/** + * Callback function to update the status of the radio buttons in the View -> Display mode menu (Normal, No Filters, Outline) and Color display mode. + */ +#if GTK_CHECK_VERSION(3,0,0) +static gboolean update_view_menu(GtkWidget *widget, cairo_t * /*cr*/, gpointer user_data) +#else +static gboolean update_view_menu(GtkWidget *widget, GdkEventExpose * /*event*/, gpointer user_data) +#endif +{ + SPAction *action = (SPAction *) user_data; + g_assert(action->id != NULL); + + Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(widget), "view"); + SPDesktop *dt = static_cast<SPDesktop*>(view); + Inkscape::RenderMode mode = dt->getMode(); + Inkscape::ColorMode colormode = dt->getColorMode(); + + bool new_state = false; + if (!strcmp(action->id, "ViewModeNormal")) { + new_state = mode == Inkscape::RENDERMODE_NORMAL; + } else if (!strcmp(action->id, "ViewModeNoFilters")) { + new_state = mode == Inkscape::RENDERMODE_NO_FILTERS; + } else if (!strcmp(action->id, "ViewModeOutline")) { + new_state = mode == Inkscape::RENDERMODE_OUTLINE; + } else if (!strcmp(action->id, "ViewColorModeNormal")) { + new_state = colormode == Inkscape::COLORMODE_NORMAL; + } else if (!strcmp(action->id, "ViewColorModeGrayscale")) { + new_state = colormode == Inkscape::COLORMODE_GRAYSCALE; + } else if (!strcmp(action->id, "ViewColorModePrintColorsPreview")) { + new_state = colormode == Inkscape::COLORMODE_PRINT_COLORS_PREVIEW; + } else { + g_warning("update_view_menu does not handle this verb"); + } + + if (new_state) { //only one of the radio buttons has to be activated; the others will automatically be deactivated + if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) { + // When the GtkMenuItem version of the "activate" signal has been emitted by a GtkRadioMenuItem, there is a second + // emission as the most recently active item is toggled to inactive. This is dealt with before the original signal is handled. + // This emission however should not invoke any actions, hence we block it here: + temporarily_block_actions = true; + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (widget), TRUE); + temporarily_block_actions = false; + } + } + + return FALSE; +} + +static void +sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref, + void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data), +#if GTK_CHECK_VERSION(3,0,0) + gboolean (*callback_update)(GtkWidget *widget, cairo_t *cr, gpointer user_data), +#else + gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data), +#endif + Inkscape::Verb *verb) +{ + unsigned int shortcut = (verb) ? sp_shortcut_get_primary(verb) : 0; + SPAction *action = (verb) ? verb->get_action(Inkscape::ActionContext(view)) : 0; + GtkWidget *item = gtk_check_menu_item_new_with_mnemonic(action ? action->name : label); + +#if 0 + if (!action->sensitive) { + gtk_widget_set_sensitive(item, FALSE); + } +#endif + + sp_shortcut_add_accelerator(item, shortcut); + + gtk_widget_show(item); + + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + + g_object_set_data(G_OBJECT(item), "view", (gpointer) view); + g_object_set_data(G_OBJECT(item), "action", (gpointer) action); + + g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref); + +#if GTK_CHECK_VERSION(3,0,0) + g_signal_connect( G_OBJECT(item), "draw", (GCallback) callback_update, (void *) pref); +#else + g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref); +#endif + + (*callback_update)(item, NULL, (void *)pref); + + g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip)); + g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL); + +} + +static void +sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/) +{ + // dealing with the bizarre filename convention in Inkscape for now + gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu)); + gchar *local_fn = g_filename_from_uri(uri, NULL, NULL); + gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL); + sp_file_open(utf8_fn, NULL); + g_free(utf8_fn); + g_free(local_fn); + g_free(uri); +} + +static void +sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view) +{ + //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu", + // checkitem_toggled, checkitem_update, 0); + sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "commands", + checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_COMMANDS_TOOLBAR)); + sp_ui_menu_append_check_item_from_verb(m, view,NULL, NULL, "snaptoolbox", + checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SNAP_TOOLBAR)); + sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "toppanel", + checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_TOOL_TOOLBAR)); + sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "toolbox", + checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_TOOLBOX)); + sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers", + checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS)); + sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars", + checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS)); + sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "panels", + checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_PALETTE)); + sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "statusbar", + checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_STATUSBAR)); +} + + +static void addTaskMenuItems(GtkMenu *menu, Inkscape::UI::View::View *view) +{ + gchar const* data[] = { + C_("Interface setup", "Default"), _("Default interface setup"), + C_("Interface setup", "Custom"), _("Setup for custom task"), + C_("Interface setup", "Wide"), _("Setup for widescreen work"), + 0, 0 + }; + + GSList *group = 0; + int count = 0; + gint active = Inkscape::UI::UXManager::getInstance()->getDefaultTask( dynamic_cast<SPDesktop*>(view) ); + for (gchar const **strs = data; strs[0]; strs += 2, count++) + { + GtkWidget *item = gtk_radio_menu_item_new_with_label( group, strs[0] ); + group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item) ); + if ( count == active ) + { + gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(item), TRUE ); + } + + g_object_set_data( G_OBJECT(item), "view", view ); + g_signal_connect( G_OBJECT(item), "toggled", reinterpret_cast<GCallback>(taskToggled), GINT_TO_POINTER(count) ); + g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), const_cast<gchar*>(strs[1]) ); + g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), 0 ); + + gtk_widget_show( item ); + gtk_menu_shell_append( GTK_MENU_SHELL(menu), item ); + } +} + + +/** + * Observer that updates the recent list's max document count. + */ +class MaxRecentObserver : public Inkscape::Preferences::Observer { +public: + MaxRecentObserver(GtkWidget *recent_menu) : + Observer("/options/maxrecentdocuments/value"), + _rm(recent_menu) + {} + virtual void notify(Inkscape::Preferences::Entry const &e) { + gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt()); + // hack: the recent menu doesn't repopulate after changing the limit, so we force it + g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed"); + } +private: + GtkWidget *_rm; +}; + +/** + * This function turns XML into a menu. + * + * This function is realitively simple as it just goes through the XML + * and parses the individual elements. In the case of a submenu, it + * just calls itself recursively. Because it is only reasonable to have + * a couple of submenus, it is unlikely this will go more than two or + * three times. + * + * In the case of an unrecognized verb, a menu item is made to identify + * the verb that is missing, and display that. The menu item is also made + * insensitive. + * + * @param menus This is the XML that defines the menu + * @param menu Menu to be added to + * @param view The View that this menu is being built for + */ +static void sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view) +{ + if (menus == NULL) return; + if (menu == NULL) return; + GSList *group = NULL; + + for (Inkscape::XML::Node *menu_pntr = menus; + menu_pntr != NULL; + menu_pntr = menu_pntr->next()) { + if (!strcmp(menu_pntr->name(), "submenu")) { + GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name"))); + GtkWidget *submenu = gtk_menu_new(); + sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu)); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem); + continue; + } + if (!strcmp(menu_pntr->name(), "verb")) { + gchar const *verb_name = menu_pntr->attribute("verb-id"); + Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name); + + if (verb != NULL) { + if (menu_pntr->attribute("radio") != NULL) { + GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group); + group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item)); + if (menu_pntr->attribute("default") != NULL) { + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); + } + if (verb->get_code() != SP_VERB_NONE) { + SPAction *action = verb->get_action(Inkscape::ActionContext(view)); +#if GTK_CHECK_VERSION(3,0,0) + g_signal_connect( G_OBJECT(item), "draw", (GCallback) update_view_menu, (void *) action); +#else + g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) update_view_menu, (void *) action); +#endif + } + } else if (menu_pntr->attribute("check") != NULL) { + SPAction *action = NULL; + if (verb->get_code() != SP_VERB_NONE) { + action = verb->get_action(Inkscape::ActionContext(view)); + } + sp_ui_menu_append_check_item_from_verb(GTK_MENU(menu), view, action->name, action->tip, NULL, + checkitem_toggled, checkitem_update, verb); + + } else { + sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view); + group = NULL; + } + } else { + gchar string[120]; + g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name); + string[119] = '\0'; /* may not be terminated */ + GtkWidget *item = gtk_menu_item_new_with_label(string); + gtk_widget_set_sensitive(item, false); + gtk_widget_show(item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + } + continue; + } + if (!strcmp(menu_pntr->name(), "separator") + // This was spelt wrong in the original version + // and so this is for backward compatibility. It can + // probably be dropped after the 0.44 release. + || !strcmp(menu_pntr->name(), "seperator")) { + GtkWidget *item = gtk_separator_menu_item_new(); + gtk_widget_show(item); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + continue; + } + + if (!strcmp(menu_pntr->name(), "recent-file-list")) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // create recent files menu + int max_recent = prefs->getInt("/options/maxrecentdocuments/value"); + GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default()); + gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent); + // sort most recently used documents first to preserve previous behavior + gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU); + g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL); + + // add filter to only open files added by Inkscape + GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new(); + gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname()); + gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter); + + gtk_recent_chooser_set_show_tips (GTK_RECENT_CHOOSER(recent_menu), TRUE); + gtk_recent_chooser_set_show_not_found (GTK_RECENT_CHOOSER(recent_menu), FALSE); + + GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent")); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu); + + gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(recent_item)); + // this will just sit and update the list's item count + static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu); + prefs->addObserver(*mro); + continue; + } + if (!strcmp(menu_pntr->name(), "objects-checkboxes")) { + sp_ui_checkboxes_menus(GTK_MENU(menu), view); + continue; + } + if (!strcmp(menu_pntr->name(), "task-checkboxes")) { + addTaskMenuItems(GTK_MENU(menu), view); + continue; + } + } +} + +GtkWidget *sp_ui_main_menubar(Inkscape::UI::View::View *view) +{ + GtkWidget *mbar = gtk_menu_bar_new(); + sp_ui_build_dyn_menus(INKSCAPE.get_menus(), mbar, view); + return mbar; +} + + +/* Drag and Drop */ +void +sp_ui_drag_data_received(GtkWidget *widget, + GdkDragContext *drag_context, + gint x, gint y, + GtkSelectionData *data, + guint info, + guint /*event_time*/, + gpointer /*user_data*/) +{ + SPDocument *doc = SP_ACTIVE_DOCUMENT; + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + switch (info) { +#if ENABLE_MAGIC_COLORS + case APP_X_INKY_COLOR: + { + int destX = 0; + int destY = 0; + gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY ); + Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) ); + + SPItem *item = desktop->getItemAtPoint( where, true ); + if ( item ) + { + bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE); + + if ( data->length >= 8 ) { + cmsHPROFILE srgbProf = cmsCreate_sRGBProfile(); + + gchar c[64] = {0}; + // Careful about endian issues. + guint16* dataVals = (guint16*)data->data; + sp_svg_write_color( c, sizeof(c), + SP_RGBA32_U_COMPOSE( + 0x0ff & (dataVals[0] >> 8), + 0x0ff & (dataVals[1] >> 8), + 0x0ff & (dataVals[2] >> 8), + 0xff // can't have transparency in the color itself + //0x0ff & (data->data[3] >> 8), + )); + SPCSSAttr *css = sp_repr_css_attr_new(); + bool updatePerformed = false; + + if ( data->length > 14 ) { + int flags = dataVals[4]; + + // piggie-backed palette entry info + int index = dataVals[5]; + Glib::ustring palName; + for ( int i = 0; i < dataVals[6]; i++ ) { + palName += (gunichar)dataVals[7+i]; + } + + // Now hook in a magic tag of some sort. + if ( !palName.empty() && (flags & 1) ) { + gchar* str = g_strdup_printf("%d|", index); + palName.insert( 0, str ); + g_free(str); + str = 0; + + item->setAttribute( + fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag", + palName.c_str(), + false ); + item->updateRepr(); + + sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c ); + updatePerformed = true; + } + } + + if ( !updatePerformed ) { + sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c ); + } + + sp_desktop_apply_css_recursive( item, css, true ); + item->updateRepr(); + + SPDocumentUndo::done( doc , SP_VERB_NONE, + _("Drop color")); + + if ( srgbProf ) { + cmsCloseProfile( srgbProf ); + } + } + } + } + break; +#endif // ENABLE_MAGIC_COLORS + + case APP_X_COLOR: + { + int destX = 0; + int destY = 0; + gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY ); + Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) ); + Geom::Point const button_dt(desktop->w2d(where)); + Geom::Point const button_doc(desktop->dt2doc(button_dt)); + + if ( gtk_selection_data_get_length (data) == 8 ) { + gchar colorspec[64] = {0}; + // Careful about endian issues. + guint16* dataVals = (guint16*)gtk_selection_data_get_data (data); + sp_svg_write_color( colorspec, sizeof(colorspec), + SP_RGBA32_U_COMPOSE( + 0x0ff & (dataVals[0] >> 8), + 0x0ff & (dataVals[1] >> 8), + 0x0ff & (dataVals[2] >> 8), + 0xff // can't have transparency in the color itself + //0x0ff & (data->data[3] >> 8), + )); + + SPItem *item = desktop->getItemAtPoint( where, true ); + + bool consumed = false; + if (desktop->event_context && desktop->event_context->get_drag()) { + consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt); + if (consumed) { + DocumentUndo::done( doc , SP_VERB_NONE, _("Drop color on gradient") ); + desktop->event_context->get_drag()->updateDraggers(); + } + } + + //if (!consumed && tools_active(desktop, TOOLS_TEXT)) { + // consumed = sp_text_context_drop_color(c, button_doc); + // if (consumed) { + // SPDocumentUndo::done( doc , SP_VERB_NONE, _("Drop color on gradient stop")); + // } + //} + + if (!consumed && item) { + bool fillnotstroke = (gdk_drag_context_get_actions (drag_context) != GDK_ACTION_MOVE); + if (fillnotstroke && + (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) { + Path *livarot_path = Path_for_item(item, true, true); + livarot_path->ConvertWithBackData(0.04); + + boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc); + if (position) { + Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t); + Geom::Point delta = nearest - button_doc; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + delta = desktop->d2w(delta); + double stroke_tolerance = + ( !item->style->stroke.isNone() ? + desktop->current_zoom() * + item->style->stroke_width.computed * + item->i2dt_affine().descrim() * 0.5 + : 0.0) + + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + if (Geom::L2 (delta) < stroke_tolerance) { + fillnotstroke = false; + } + } + delete livarot_path; + } + + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec ); + + sp_desktop_apply_css_recursive( item, css, true ); + item->updateRepr(); + + DocumentUndo::done( doc , SP_VERB_NONE, + _("Drop color") ); + } + } + } + break; + + 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(); + + SPGradient* matches = 0; + const GSList *gradients = doc->getResourceList("gradient"); + for (const GSList *item = gradients; item; item = item->next) { + SPGradient* grad = SP_GRADIENT(item->data); + if ( color.descr == grad->getId() ) { + if ( grad->hasStops() ) { + matches = grad; + break; + } + } + } + if (matches) { + colorspec = "url(#"; + colorspec += matches->getId(); + colorspec += ")"; + } else { + gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b); + colorspec = tmp; + g_free(tmp); + } + } + } + } + if ( worked ) { + int destX = 0; + int destY = 0; + gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY ); + Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) ); + Geom::Point const button_dt(desktop->w2d(where)); + Geom::Point const button_doc(desktop->dt2doc(button_dt)); + + SPItem *item = desktop->getItemAtPoint( where, true ); + + bool consumed = false; + if (desktop->event_context && desktop->event_context->get_drag()) { + consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt); + if (consumed) { + DocumentUndo::done( doc , SP_VERB_NONE, _("Drop color on gradient") ); + desktop->event_context->get_drag()->updateDraggers(); + } + } + + if (!consumed && item) { + bool fillnotstroke = (gdk_drag_context_get_actions (drag_context) != GDK_ACTION_MOVE); + if (fillnotstroke && + (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) { + Path *livarot_path = Path_for_item(item, true, true); + livarot_path->ConvertWithBackData(0.04); + + boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc); + if (position) { + Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t); + Geom::Point delta = nearest - button_doc; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + delta = desktop->d2w(delta); + double stroke_tolerance = + ( !item->style->stroke.isNone() ? + desktop->current_zoom() * + item->style->stroke_width.computed * + item->i2dt_affine().descrim() * 0.5 + : 0.0) + + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + if (Geom::L2 (delta) < stroke_tolerance) { + fillnotstroke = false; + } + } + delete livarot_path; + } + + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() ); + + sp_desktop_apply_css_recursive( item, css, true ); + item->updateRepr(); + + DocumentUndo::done( doc , SP_VERB_NONE, + _("Drop color") ); + } + } + } + break; + + case SVG_DATA: + case SVG_XML_DATA: { + gchar *svgdata = (gchar *)gtk_selection_data_get_data (data); + + Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, gtk_selection_data_get_length (data), SP_SVG_NS_URI); + + if (rnewdoc == NULL) { + sp_ui_error_dialog(_("Could not parse SVG data")); + return; + } + + Inkscape::XML::Node *repr = rnewdoc->root(); + gchar const *style = repr->attribute("style"); + + Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g"); + newgroup->setAttribute("style", style); + + Inkscape::XML::Document * xml_doc = doc->getReprDoc(); + for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) { + Inkscape::XML::Node *newchild = child->duplicate(xml_doc); + newgroup->appendChild(newchild); + } + + Inkscape::GC::release(rnewdoc); + + // Add it to the current layer + + // Greg's edits to add intelligent positioning of svg drops + SPObject *new_obj = NULL; + new_obj = desktop->currentLayer()->appendChildRepr(newgroup); + + Inkscape::Selection *selection = sp_desktop_selection(desktop); + selection->set(SP_ITEM(new_obj)); + + // move to mouse pointer + { + sp_desktop_document(desktop)->ensureUpToDate(); + Geom::OptRect sel_bbox = selection->visualBounds(); + if (sel_bbox) { + Geom::Point m( desktop->point() - sel_bbox->midpoint() ); + sp_selection_move_relative(selection, m, false); + } + } + + Inkscape::GC::release(newgroup); + DocumentUndo::done( doc, SP_VERB_NONE, + _("Drop SVG") ); + break; + } + + case URI_LIST: { + gchar *uri = (gchar *)gtk_selection_data_get_data (data); + sp_ui_import_files(uri); + break; + } + + case APP_X_INK_PASTE: { + Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get(); + cm->paste(desktop); + DocumentUndo::done( doc, SP_VERB_NONE, _("Drop Symbol") ); + break; + } + + case PNG_DATA: + case JPEG_DATA: + case IMAGE_DATA: { + const char *mime = (info == JPEG_DATA ? "image/jpeg" : "image/png"); + + Inkscape::Extension::DB::InputList o; + Inkscape::Extension::db.get_input_list(o); + Inkscape::Extension::DB::InputList::const_iterator i = o.begin(); + while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) { + ++i; + } + Inkscape::Extension::Extension *ext = *i; + bool save = (strcmp(ext->get_param_optiongroup("link"), "embed") == 0); + ext->set_param_optiongroup("link", "embed"); + ext->set_gui(false); + + gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-dnd-import", NULL ); + g_file_set_contents(filename, + reinterpret_cast<gchar const *>(gtk_selection_data_get_data (data)), + gtk_selection_data_get_length (data), + NULL); + file_import(doc, filename, ext); + g_free(filename); + + ext->set_param_optiongroup("link", save ? "embed" : "link"); + ext->set_gui(true); + DocumentUndo::done( doc , SP_VERB_NONE, + _("Drop bitmap image") ); + break; + } + } +} + +#include "ui/tools/gradient-tool.h" + +void sp_ui_drag_motion( GtkWidget */*widget*/, + GdkDragContext */*drag_context*/, + gint /*x*/, gint /*y*/, + GtkSelectionData */*data*/, + guint /*info*/, + guint /*event_time*/, + gpointer /*user_data*/) +{ +// SPDocument *doc = SP_ACTIVE_DOCUMENT; +// SPDesktop *desktop = SP_ACTIVE_DESKTOP; + + +// g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time); +} + +static void sp_ui_drag_leave( GtkWidget */*widget*/, + GdkDragContext */*drag_context*/, + guint /*event_time*/, + gpointer /*user_data*/ ) +{ +// g_message("drag-n-drop leave at %d", event_time); +} + +static void +sp_ui_import_files(gchar *buffer) +{ + GList *list = gnome_uri_list_extract_filenames(buffer); + if (!list) + return; + g_list_foreach(list, sp_ui_import_one_file_with_check, NULL); + g_list_foreach(list, (GFunc) g_free, NULL); + g_list_free(list); +} + +static void +sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/) +{ + if (filename) { + if (strlen((char const *)filename) > 2) + sp_ui_import_one_file((char const *)filename); + } +} + +static void +sp_ui_import_one_file(char const *filename) +{ + SPDocument *doc = SP_ACTIVE_DOCUMENT; + if (!doc) return; + + if (filename == NULL) return; + + // Pass off to common implementation + // TODO might need to get the proper type of Inkscape::Extension::Extension + file_import( doc, filename, NULL ); +} + +void +sp_ui_error_dialog(gchar const *message) +{ + GtkWidget *dlg; + gchar *safeMsg = Inkscape::IO::sanitizeString(message); + + dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, "%s", safeMsg); + sp_transientize(dlg); + gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE); + gtk_dialog_run(GTK_DIALOG(dlg)); + gtk_widget_destroy(dlg); + g_free(safeMsg); +} + +bool +sp_ui_overwrite_file(gchar const *filename) +{ + bool return_value = FALSE; + + if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) { + Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel(); + gchar* baseName = g_path_get_basename( filename ); + gchar* dirName = g_path_get_dirname( filename ); + GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(), + (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n" + "The file already exists in \"%s\". Replacing it will overwrite its contents." ), + baseName, + dirName + ); + gtk_dialog_add_buttons( GTK_DIALOG(dialog), + _("_Cancel"), GTK_RESPONSE_NO, + _("Replace"), GTK_RESPONSE_YES, + NULL ); + gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES ); + + if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) { + return_value = TRUE; + } else { + return_value = FALSE; + } + gtk_widget_destroy(dialog); + g_free( baseName ); + g_free( dirName ); + } else { + return_value = TRUE; + } + + return return_value; +} + +static void +sp_ui_menu_item_set_name(GtkWidget *data, Glib::ustring const &name) +{ + if (data || GTK_IS_BIN (data)) { + void *child = gtk_bin_get_child (GTK_BIN (data)); + //child is either + //- a GtkBox, whose first child is a label displaying name if the menu + //item has an accel key + //- a GtkLabel if the menu has no accel key + if (child != NULL){ + if (GTK_IS_LABEL(child)) { + gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str()); + } else if (GTK_IS_BOX(child)) { + gtk_label_set_markup_with_mnemonic( + GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data), + name.c_str()); + }//else sp_ui_menu_append_item_from_verb has been modified and can set + //a menu item in yet another way... + } + } +} + +void injectRenamedIcons() +{ + Glib::RefPtr<Gtk::IconTheme> iconTheme = Gtk::IconTheme::get_default(); + + std::vector< std::pair<Glib::ustring, Glib::ustring> > renamed; + renamed.push_back(std::make_pair("gtk-file", "document-x-generic")); + renamed.push_back(std::make_pair("gtk-directory", "folder")); + + for ( std::vector< std::pair<Glib::ustring, Glib::ustring> >::iterator it = renamed.begin(); it < renamed.end(); ++it ) { + bool hasIcon = iconTheme->has_icon(it->first); + bool hasSecondIcon = iconTheme->has_icon(it->second); + + if ( !hasIcon && hasSecondIcon ) { + Glib::ArrayHandle<int> sizes = iconTheme->get_icon_sizes(it->second); + for ( Glib::ArrayHandle<int>::iterator it2 = sizes.begin(); it2 < sizes.end(); ++it2 ) { + Glib::RefPtr<Gdk::Pixbuf> pb = iconTheme->load_icon( it->second, *it2 ); + if ( pb ) { + // install a private copy of the pixbuf to avoid pinning a theme + Glib::RefPtr<Gdk::Pixbuf> pbCopy = pb->copy(); + Gtk::IconTheme::add_builtin_icon( it->first, *it2, pbCopy ); + } + } + } + } +} + + +ContextMenu::ContextMenu(SPDesktop *desktop, SPItem *item) : + _item(item), + MIGroup(), + MIParent(_("Go to parent")) +{ +// g_message("ContextMenu"); + _object = static_cast<SPObject *>(item); + _desktop = desktop; + + AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_UNDO)); + AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_REDO)); + AddSeparator(); + AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_CUT)); + AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_COPY)); + AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_PASTE)); + AddSeparator(); + AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE)); + AppendItemFromVerb(Inkscape::Verb::get(SP_VERB_EDIT_DELETE)); + + positionOfLastDialog = 10; // 9 in front + 1 for the separator in the next if; used to position the dialog menu entries below each other + + /* Item menu */ + if (item!=NULL) { + AddSeparator(); + MakeObjectMenu(); + } + + /* layer menu */ + SPGroup *group=NULL; + if (item) { + if (SP_IS_GROUP(item)) { + group = SP_GROUP(item); + } else if ( item != _desktop->currentRoot() && SP_IS_GROUP(item->parent) ) { + group = SP_GROUP(item->parent); + } + } + + if (( group && group != _desktop->currentLayer() ) || + ( _desktop->currentLayer() != _desktop->currentRoot() && _desktop->currentLayer()->parent != _desktop->currentRoot() ) ) { + AddSeparator(); + } + + if ( group && group != _desktop->currentLayer() ) { + /* TRANSLATORS: #%1 is the id of the group e.g. <g id="#g7">, not a number. */ + MIGroup.set_label (Glib::ustring::compose(_("Enter group #%1"), group->getId())); + MIGroup.set_data("group", group); + MIGroup.signal_activate().connect(sigc::bind(sigc::mem_fun(*this, &ContextMenu::EnterGroup),&MIGroup)); + MIGroup.show(); + append(MIGroup); + } + + if ( _desktop->currentLayer() != _desktop->currentRoot() ) { + if ( _desktop->currentLayer()->parent != _desktop->currentRoot() ) { + MIParent.signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::LeaveGroup)); + MIParent.show(); + append(MIParent); + } + } +} + +ContextMenu::~ContextMenu(void) +{ +} + +Gtk::SeparatorMenuItem* ContextMenu::AddSeparator(void) +{ + Gtk::SeparatorMenuItem* sep = Gtk::manage(new Gtk::SeparatorMenuItem()); + sep->show(); + append(*sep); + return sep; +} + +void ContextMenu::EnterGroup(Gtk::MenuItem* mi) +{ + _desktop->setCurrentLayer(reinterpret_cast<SPObject *>(mi->get_data("group"))); + _desktop->selection->clear(); +} + +void ContextMenu::LeaveGroup(void) +{ + _desktop->setCurrentLayer(_desktop->currentLayer()->parent); +} + +void ContextMenu::AppendItemFromVerb(Inkscape::Verb *verb)//, SPDesktop *view)//, bool radio, GSList *group) +{ + SPAction *action; + SPDesktop *view = _desktop; + + if (verb->get_code() == SP_VERB_NONE) { + Gtk::MenuItem *item = AddSeparator(); + item->show(); + append(*item); + } else { + action = verb->get_action(Inkscape::ActionContext(view)); + if (!action) { + return; + } + + Gtk::ImageMenuItem *item = Gtk::manage(new Gtk::ImageMenuItem(action->name, true)); + + sp_shortcut_add_accelerator(GTK_WIDGET(item->gobj()), sp_shortcut_get_primary(verb)); + + action->signal_set_sensitive.connect(sigc::mem_fun(*this, &ContextMenu::set_sensitive)); + action->signal_set_name.connect(sigc::mem_fun(*item, &ContextMenu::set_name)); + + if (!action->sensitive) { + item->set_sensitive(FALSE); + } + + if (action->image) { + sp_ui_menuitem_add_icon((GtkWidget*)item->gobj(), action->image); + } + item->set_events(Gdk::KEY_PRESS_MASK); + item->signal_activate().connect(sigc::bind(sigc::ptr_fun(sp_ui_menu_activate),item,action)); + item->signal_select().connect(sigc::bind(sigc::ptr_fun(sp_ui_menu_select_action),item,action)); + item->signal_deselect().connect(sigc::bind(sigc::ptr_fun(sp_ui_menu_deselect_action),item,action)); + item->show(); + append(*item); + } +} + +void ContextMenu::MakeObjectMenu(void) +{ +// GObjectClass *klass = G_OBJECT_GET_CLASS(_object); //to deduce the object's type from its class +// +// if (G_TYPE_CHECK_CLASS_TYPE(klass, SP_TYPE_ITEM)) +// { +// MakeItemMenu (); +// } +// if (G_TYPE_CHECK_CLASS_TYPE(klass, SP_TYPE_GROUP)) +// { +// MakeGroupMenu(); +// } +// if (G_TYPE_CHECK_CLASS_TYPE(klass, SP_TYPE_ANCHOR)) +// { +// MakeAnchorMenu(); +// } +// if (G_TYPE_CHECK_CLASS_TYPE(klass, SP_TYPE_IMAGE)) +// { +// MakeImageMenu(); +// } +// if (G_TYPE_CHECK_CLASS_TYPE(klass, SP_TYPE_SHAPE)) +// { +// MakeShapeMenu(); +// } +// if (G_TYPE_CHECK_CLASS_TYPE(klass, SP_TYPE_TEXT)) +// { +// MakeTextMenu(); +// } + + if (SP_IS_ITEM(_object)) { + MakeItemMenu(); + } + + if (SP_IS_GROUP(_object)) { + MakeGroupMenu(); + } + + if (SP_IS_ANCHOR(_object)) { + MakeAnchorMenu(); + } + + if (SP_IS_IMAGE(_object)) { + MakeImageMenu(); + } + + if (SP_IS_SHAPE(_object)) { + MakeShapeMenu(); + } + + if (SP_IS_TEXT(_object)) { + MakeTextMenu(); + } +} + +void ContextMenu::MakeItemMenu (void) +{ + Gtk::MenuItem* mi; + + /* Item dialog */ + mi = Gtk::manage(new Gtk::MenuItem(_("_Object Properties..."),1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ItemProperties)); + mi->show(); + append(*mi);//insert(*mi,positionOfLastDialog++); + + AddSeparator(); + + /* Select item */ + if (Inkscape::Verb::getbyid( "org.inkscape.followlink" )) { + mi = Gtk::manage(new Gtk::MenuItem(_("_Select This"), 1)); + if (_desktop->selection->includes(_item)) { + mi->set_sensitive(FALSE); + } else { + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ItemSelectThis)); + } + mi->show(); + append(*mi); + } + + + mi = Gtk::manage(new Gtk::MenuItem(_("Select Same"))); + mi->show(); + Gtk::Menu *select_same_submenu = Gtk::manage(new Gtk::Menu()); + if (_desktop->selection->isEmpty()) { + mi->set_sensitive(FALSE); + } + mi->set_submenu(*select_same_submenu); + append(*mi); + + /* Select same fill and stroke */ + mi = Gtk::manage(new Gtk::MenuItem(_("Fill and Stroke"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SelectSameFillStroke)); + mi->set_sensitive(!SP_IS_ANCHOR(_item)); + mi->show(); + select_same_submenu->append(*mi); + + /* Select same fill color */ + mi = Gtk::manage(new Gtk::MenuItem(_("Fill Color"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SelectSameFillColor)); + mi->set_sensitive(!SP_IS_ANCHOR(_item)); + mi->show(); + select_same_submenu->append(*mi); + + /* Select same stroke color */ + mi = Gtk::manage(new Gtk::MenuItem(_("Stroke Color"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SelectSameStrokeColor)); + mi->set_sensitive(!SP_IS_ANCHOR(_item)); + mi->show(); + select_same_submenu->append(*mi); + + /* Select same stroke style */ + mi = Gtk::manage(new Gtk::MenuItem(_("Stroke Style"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SelectSameStrokeStyle)); + mi->set_sensitive(!SP_IS_ANCHOR(_item)); + mi->show(); + select_same_submenu->append(*mi); + + /* Select same stroke style */ + mi = Gtk::manage(new Gtk::MenuItem(_("Object type"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SelectSameObjectType)); + mi->set_sensitive(!SP_IS_ANCHOR(_item)); + mi->show(); + select_same_submenu->append(*mi); + + /* Move to layer */ + mi = Gtk::manage(new Gtk::MenuItem(_("_Move to layer ..."), 1)); + if (_desktop->selection->isEmpty()) { + mi->set_sensitive(FALSE); + } else { + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ItemMoveTo)); + } + mi->show(); + append(*mi); + + /* Create link */ + mi = Gtk::manage(new Gtk::MenuItem(_("Create _Link"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ItemCreateLink)); + mi->set_sensitive(!SP_IS_ANCHOR(_item)); + mi->show(); + append(*mi); + + bool ClipRefOK=false; + bool MaskRefOK=false; + if (_item){ + if (_item->clip_ref){ + if (_item->clip_ref->getObject()){ + ClipRefOK=true; + } + } + } + if (_item){ + if (_item->mask_ref){ + if (_item->mask_ref->getObject()){ + MaskRefOK=true; + } + } + } + /* Set mask */ + mi = Gtk::manage(new Gtk::MenuItem(_("Set Mask"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SetMask)); + if (ClipRefOK || MaskRefOK) { + mi->set_sensitive(FALSE); + } else { + mi->set_sensitive(TRUE); + } + mi->show(); + append(*mi); + + /* Release mask */ + mi = Gtk::manage(new Gtk::MenuItem(_("Release Mask"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ReleaseMask)); + if (MaskRefOK) { + mi->set_sensitive(TRUE); + } else { + mi->set_sensitive(FALSE); + } + mi->show(); + append(*mi); + + /*SSet Clip Group */ + mi = Gtk::manage(new Gtk::MenuItem(_("Create Clip G_roup"),1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::CreateGroupClip)); + mi->set_sensitive(TRUE); + mi->show(); + append(*mi); + + /* Set Clip */ + mi = Gtk::manage(new Gtk::MenuItem(_("Set Cl_ip"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SetClip)); + if (ClipRefOK || MaskRefOK) { + mi->set_sensitive(FALSE); + } else { + mi->set_sensitive(TRUE); + } + mi->show(); + append(*mi); + + /* Release Clip */ + mi = Gtk::manage(new Gtk::MenuItem(_("Release C_lip"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ReleaseClip)); + if (ClipRefOK) { + mi->set_sensitive(TRUE); + } else { + mi->set_sensitive(FALSE); + } + mi->show(); + append(*mi); + + /* Group */ + mi = Gtk::manage(new Gtk::MenuItem(_("_Group"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ActivateGroup)); + if (_desktop->selection->isEmpty() || _desktop->selection->single()) { + mi->set_sensitive(FALSE); + } else { + mi->set_sensitive(TRUE); + } + mi->show(); + append(*mi); +} + +void ContextMenu::SelectSameFillStroke(void) +{ + sp_select_same_fill_stroke_style(_desktop, true, true, true); +} + +void ContextMenu::SelectSameFillColor(void) +{ + sp_select_same_fill_stroke_style(_desktop, true, false, false); +} + +void ContextMenu::SelectSameStrokeColor(void) +{ + sp_select_same_fill_stroke_style(_desktop, false, true, false); +} + +void ContextMenu::SelectSameStrokeStyle(void) +{ + sp_select_same_stroke_style(_desktop); +} + +void ContextMenu::SelectSameObjectType(void) +{ + sp_select_same_object_type(_desktop); +} + +void ContextMenu::ItemProperties(void) +{ + _desktop->selection->set(_item); + _desktop->_dlg_mgr->showDialog("ObjectProperties"); +} + +void ContextMenu::ItemSelectThis(void) +{ + _desktop->selection->set(_item); +} + +void ContextMenu::ItemMoveTo(void) +{ + Inkscape::UI::Dialogs::LayerPropertiesDialog::showMove(_desktop, _desktop->currentLayer()); +} + + + +void ContextMenu::ItemCreateLink(void) +{ + Inkscape::XML::Document *xml_doc = _desktop->doc()->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:a"); + _item->parent->getRepr()->addChild(repr, _item->getRepr()); + SPObject *object = _item->document->getObjectByRepr(repr); + g_return_if_fail(SP_IS_ANCHOR(object)); + + const char *id = _item->getRepr()->attribute("id"); + Inkscape::XML::Node *child = _item->getRepr()->duplicate(xml_doc); + _item->deleteObject(false); + repr->addChild(child, NULL); + child->setAttribute("id", id); + + Inkscape::GC::release(repr); + Inkscape::GC::release(child); + + DocumentUndo::done(object->document, SP_VERB_NONE, _("Create link")); + + _desktop->selection->set(SP_ITEM(object)); + _desktop->_dlg_mgr->showDialog("ObjectAttributes"); +} + +void ContextMenu::SetMask(void) +{ + sp_selection_set_mask(_desktop, false, false); +} + +void ContextMenu::ReleaseMask(void) +{ + sp_selection_unset_mask(_desktop, false); +} + +void ContextMenu::CreateGroupClip(void) +{ + sp_selection_set_clipgroup(_desktop); +} + +void ContextMenu::SetClip(void) +{ + sp_selection_set_mask(_desktop, true, false); +} + + +void ContextMenu::ReleaseClip(void) +{ + sp_selection_unset_mask(_desktop, true); +} + +void ContextMenu::MakeGroupMenu(void) +{ + /* Ungroup */ + Gtk::MenuItem* mi = Gtk::manage(new Gtk::MenuItem(_("_Ungroup"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ActivateUngroup)); + mi->show(); + append(*mi); +} + +void ContextMenu::ActivateGroup(void) +{ + sp_selection_group(_desktop->selection, _desktop); +} + +void ContextMenu::ActivateUngroup(void) +{ + GSList *children = NULL; + + sp_item_group_ungroup(static_cast<SPGroup*>(_item), &children); + _desktop->selection->setList(children); + g_slist_free(children); +} + +void ContextMenu::MakeAnchorMenu(void) +{ + Gtk::MenuItem* mi; + + /* Link dialog */ + mi = Gtk::manage(new Gtk::MenuItem(_("Link _Properties..."), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::AnchorLinkProperties)); + mi->show(); + insert(*mi,positionOfLastDialog++); + + /* Select item */ + mi = Gtk::manage(new Gtk::MenuItem(_("_Follow Link"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::AnchorLinkFollow)); + mi->show(); + append(*mi); + + /* Reset transformations */ + mi = Gtk::manage(new Gtk::MenuItem(_("_Remove Link"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::AnchorLinkRemove)); + mi->show(); + append(*mi); +} + +void ContextMenu::AnchorLinkProperties(void) +{ + _desktop->_dlg_mgr->showDialog("ObjectAttributes"); +} + +void ContextMenu::AnchorLinkFollow(void) +{ + + if (_desktop->selection->isEmpty()) { + _desktop->selection->set(_item); + } + // Opening the selected links with a python extension + Inkscape::Verb *verb = Inkscape::Verb::getbyid( "org.inkscape.followlink" ); + if (verb) { + SPAction *action = verb->get_action(Inkscape::ActionContext(_desktop)); + if (action) { + sp_action_perform(action, NULL); + } + } +} + +void ContextMenu::AnchorLinkRemove(void) +{ + GSList *children = NULL; + sp_item_group_ungroup(static_cast<SPAnchor*>(_item), &children, false); + DocumentUndo::done(_desktop->doc(), SP_VERB_NONE, _("Remove link")); + g_slist_free(children); +} + +void ContextMenu::MakeImageMenu (void) +{ + Gtk::MenuItem* mi; + Inkscape::XML::Node *ir = _object->getRepr(); + const gchar *href = ir->attribute("xlink:href"); + + /* Image properties */ + mi = Gtk::manage(new Gtk::MenuItem(_("Image _Properties..."), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageProperties)); + mi->show(); + insert(*mi,positionOfLastDialog++); + + /* Edit externally */ + mi = Gtk::manage(new Gtk::MenuItem(_("Edit Externally..."), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageEdit)); + mi->show(); + insert(*mi,positionOfLastDialog++); + if ( (!href) || ((strncmp(href, "data:", 5) == 0)) ) { + mi->set_sensitive( FALSE ); + } + + /* Trace Bitmap */ + mi = Gtk::manage(new Gtk::MenuItem(_("_Trace Bitmap..."), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageTraceBitmap)); + mi->show(); + insert(*mi,positionOfLastDialog++); + if (_desktop->selection->isEmpty()) { + mi->set_sensitive(FALSE); + } + + /* Trace Pixel Art */ + mi = Gtk::manage(new Gtk::MenuItem(_("Trace Pixel Art"), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageTracePixelArt)); + mi->show(); + insert(*mi,positionOfLastDialog++); + if (_desktop->selection->isEmpty()) { + mi->set_sensitive(FALSE); + } + + /* Embed image */ + if (Inkscape::Verb::getbyid( "org.ekips.filter.embedselectedimages" )) { + mi = Gtk::manage(new Gtk::MenuItem(C_("Context menu", "Embed Image"))); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageEmbed)); + mi->show(); + insert(*mi,positionOfLastDialog++); + if ( (!href) || ((strncmp(href, "data:", 5) == 0)) ) { + mi->set_sensitive( FALSE ); + } + } + + /* Extract image */ + if (Inkscape::Verb::getbyid( "org.ekips.filter.extractimage" )) { + mi = Gtk::manage(new Gtk::MenuItem(C_("Context menu", "Extract Image..."))); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageExtract)); + mi->show(); + insert(*mi,positionOfLastDialog++); + if ( (!href) || ((strncmp(href, "data:", 5) != 0)) ) { + mi->set_sensitive( FALSE ); + } + } +} + +void ContextMenu::ImageProperties(void) +{ + _desktop->_dlg_mgr->showDialog("ObjectAttributes"); +} + +Glib::ustring ContextMenu::getImageEditorName() { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring value; + Glib::ustring choices = prefs->getString("/options/bitmapeditor/value"); + if (!choices.empty()) { + value = choices; + } + else { + value = "gimp"; + } + return value; +} + +void ContextMenu::ImageEdit(void) +{ + if (_desktop->selection->isEmpty()) { + _desktop->selection->set(_item); + } + + GSList const *selected = _desktop->selection->itemList(); + + GError* errThing = 0; + Glib::ustring cmdline = getImageEditorName(); + Glib::ustring name; + Glib::ustring fullname; + +#ifdef WIN32 + // g_spawn_command_line_sync parsing is done according to Unix shell rules, + // not Windows command interpreter rules. Thus we need to enclose the + // executable path with single quotes. + int index = cmdline.find(".exe"); + if ( index < 0 ) index = cmdline.find(".bat"); + if ( index < 0 ) index = cmdline.find(".com"); + if ( index >= 0 ) { + Glib::ustring editorBin = cmdline.substr(0, index + 4).c_str(); + Glib::ustring args = cmdline.substr(index + 4, cmdline.length()).c_str(); + editorBin.insert(0, "'"); + editorBin.append("'"); + cmdline = editorBin; + cmdline.append(args); + } else { + // Enclose the whole command line if no executable path can be extracted. + cmdline.insert(0, "'"); + cmdline.append("'"); + } +#endif + + for (GSList const *iter = selected; iter != NULL; iter = iter->next) { + Inkscape::XML::Node *ir = SP_ITEM(iter->data)->getRepr(); + const gchar *href = ir->attribute("xlink:href"); + + if (strncmp (href,"file:",5) == 0) { + // URI to filename conversion + name = g_filename_from_uri(href, NULL, NULL); + } else { + name.append(href); + } + + if (Glib::path_is_absolute(name)) { + fullname = name; + } else if (SP_ACTIVE_DOCUMENT->getBase()) { + fullname = Glib::build_filename(SP_ACTIVE_DOCUMENT->getBase(), name); + } else { + fullname = Glib::build_filename(Glib::get_current_dir(), name); + } + + cmdline.append(" '"); + cmdline.append(fullname.c_str()); + cmdline.append("'"); + } + + //g_warning("##Command line: %s\n", cmdline.c_str()); + + g_spawn_command_line_async(cmdline.c_str(), &errThing); + + if ( errThing ) { + g_warning("Problem launching editor (%d). %s", errThing->code, errThing->message); + (_desktop->messageStack())->flash(Inkscape::ERROR_MESSAGE, errThing->message); + g_error_free(errThing); + errThing = 0; + } +} + +void ContextMenu::ImageTraceBitmap(void) +{ + INKSCAPE.dialogs_unhide(); + _desktop->_dlg_mgr->showDialog("Trace"); +} + +void ContextMenu::ImageTracePixelArt(void) +{ + INKSCAPE.dialogs_unhide(); + _desktop->_dlg_mgr->showDialog("PixelArt"); +} + +void ContextMenu::ImageEmbed(void) +{ + if (_desktop->selection->isEmpty()) { + _desktop->selection->set(_item); + } + + Inkscape::Verb *verb = Inkscape::Verb::getbyid( "org.ekips.filter.embedselectedimages" ); + if (verb) { + SPAction *action = verb->get_action(Inkscape::ActionContext(_desktop)); + if (action) { + sp_action_perform(action, NULL); + } + } +} + +void ContextMenu::ImageExtract(void) +{ + if (_desktop->selection->isEmpty()) { + _desktop->selection->set(_item); + } + + Inkscape::Verb *verb = Inkscape::Verb::getbyid( "org.ekips.filter.extractimage" ); + if (verb) { + SPAction *action = verb->get_action(Inkscape::ActionContext(_desktop)); + if (action) { + sp_action_perform(action, NULL); + } + } +} + +void ContextMenu::MakeShapeMenu (void) +{ + Gtk::MenuItem* mi; + + /* Item dialog */ + mi = Gtk::manage(new Gtk::MenuItem(_("_Fill and Stroke..."), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::FillSettings)); + mi->show(); + insert(*mi,positionOfLastDialog++); +} + +void ContextMenu::FillSettings(void) +{ + if (_desktop->selection->isEmpty()) { + _desktop->selection->set(_item); + } + + _desktop->_dlg_mgr->showDialog("FillAndStroke"); +} + +void ContextMenu::MakeTextMenu (void) +{ + Gtk::MenuItem* mi; + + /* Fill and Stroke dialog */ + mi = Gtk::manage(new Gtk::MenuItem(_("_Fill and Stroke..."), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::FillSettings)); + mi->show(); + insert(*mi,positionOfLastDialog++); + + /* Edit Text dialog */ + mi = Gtk::manage(new Gtk::MenuItem(_("_Text and Font..."), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::TextSettings)); + mi->show(); + insert(*mi,positionOfLastDialog++); + + /* Spellcheck dialog */ + mi = Gtk::manage(new Gtk::MenuItem(_("Check Spellin_g..."), 1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::SpellcheckSettings)); + mi->show(); + insert(*mi,positionOfLastDialog++); +} + +void ContextMenu::TextSettings (void) +{ + if (_desktop->selection->isEmpty()) { + _desktop->selection->set(_item); + } + + _desktop->_dlg_mgr->showDialog("TextFont"); +} + +void ContextMenu::SpellcheckSettings (void) +{ + if (_desktop->selection->isEmpty()) { + _desktop->selection->set(_item); + } + + _desktop->_dlg_mgr->showDialog("SpellCheck"); +} + +/* + 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/interface.h b/src/ui/interface.h new file mode 100644 index 000000000..6fb74046f --- /dev/null +++ b/src/ui/interface.h @@ -0,0 +1,277 @@ +#ifndef SEEN_SP_INTERFACE_H +#define SEEN_SP_INTERFACE_H + +/* + * Main UI stuff + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Frank Felfe <innerspace@iname.com> + * Abhishek Sharma + * Kris De Gussem <Kris.DeGussem@gmail.com> + * + * Copyright (C) 2012 Kris De Gussem + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +//#ifdef HAVE_CONFIG_H +//# include <config.h> +//#endif + +#include <gtkmm/menu.h> + +class SPItem; +class SPObject; +class SPDesktop; +class SPViewWidget; + +namespace Gtk { +class SeparatorMenuItem; +} + +namespace Inkscape { + +class Verb; + +namespace UI { +namespace View { +class View; +} // namespace View +} // namespace UI +} // namespace Inkscape + +/** + * Create a new document window. + */ +void sp_create_window (SPViewWidget *vw, bool editable); + +/** + * \param widget unused + */ +void sp_ui_close_view (GtkWidget *widget); + +void sp_ui_new_view (void); + +/** + * @todo TODO: not yet working. To be re-enabled (by adding to menu) once it works. + */ +void sp_ui_new_view_preview (void); + +/** + * This function is called to exit the program, and iterates through all + * open document view windows, attempting to close each in turn. If the + * view has unsaved information, the user will be prompted to save, + * discard, or cancel. + * + * Returns FALSE if the user cancels the close_all operation, TRUE + * otherwise. + */ +unsigned int sp_ui_close_all (void); + +/** + * Build the main tool bar. + * + * Currently the main tool bar is built as a dynamic XML menu using + * \c sp_ui_build_dyn_menus. This function builds the bar, and then + * pass it to get items attached to it. + * + * @param view View to build the bar for + */ +GtkWidget *sp_ui_main_menubar (Inkscape::UI::View::View *view); + +void sp_menu_append_recent_documents (GtkWidget *menu); +void sp_ui_dialog_title_string (Inkscape::Verb * verb, char* c); + +Glib::ustring getLayoutPrefPath( Inkscape::UI::View::View *view ); + +/** + * + */ +void sp_ui_error_dialog (char const* message); +bool sp_ui_overwrite_file (char const* filename); + + +/** + * Implements the Inkscape context menu. + * + * For the context menu implementation, the ContextMenu class stores the object + * that was selected in a private data member. This should be farely safe to do + * and a pointer to the SPItem as well as SPObject class are kept. + * All callbacks of the context menu entries are implemented as private + * functions. + * + * @todo add callbacks to destroy the context menu when it is closed (=key or mouse button pressed out of the scope of the context menu) + */ +class ContextMenu : public Gtk::Menu +{ + public: + /** + * The ContextMenu constructor contains all code to create and show the + * menu entries (aka child widgets). + * + * @param desktop pointer to the desktop the user is currently working on. + * @param item SPItem pointer to the object selected at the time the ContextMenu is created. + */ + ContextMenu(SPDesktop *desktop, SPItem *item); + ~ContextMenu(void); + + private: + SPItem *_item; // pointer to the object selected at the time the ContextMenu is created + SPObject *_object; // pointer to the object selected at the time the ContextMenu is created + SPDesktop *_desktop; //pointer to the desktop the user was currently working on at the time the ContextMenu is created + + int positionOfLastDialog; + + Gtk::MenuItem MIGroup; //menu entry to enter a group + Gtk::MenuItem MIParent; //menu entry to leave a group + + /** + * auxiliary function that adds a separator line in the context menu + */ + Gtk::SeparatorMenuItem* AddSeparator(void); + + /** + * c++ified version of sp_ui_menu_append_item. + * + * @see sp_ui_menu_append_item_from_verb and synchronize/drop that function when c++ifying other code in interface.cpp + */ + void AppendItemFromVerb(Inkscape::Verb *verb); + + /** + * main function which is responsible for creating the context sensitive menu items, + * calls subfunctions below to create the menu entry widgets. + */ + void MakeObjectMenu (void); + /** + * creates menu entries for an SP_TYPE_ITEM object + */ + void MakeItemMenu (void); + /** + * creates menu entries for a grouped object + */ + void MakeGroupMenu (void); + /** + * creates menu entries for an anchor object + */ + void MakeAnchorMenu (void); + /** + * creates menu entries for a bitmap image object + */ + void MakeImageMenu (void); + /** + * creates menu entries for a shape object + */ + void MakeShapeMenu (void); + /** + * creates menu entries for a text object + */ + void MakeTextMenu (void); + + void EnterGroup(Gtk::MenuItem* mi); + void LeaveGroup(void); + ////////////////////////////////////////// + //callbacks for the context menu entries of an SP_TYPE_ITEM object + void ItemProperties(void); + void ItemSelectThis(void); + void ItemMoveTo(void); + void SelectSameFillStroke(void); + void SelectSameFillColor(void); + void SelectSameStrokeColor(void); + void SelectSameStrokeStyle(void); + void SelectSameObjectType(void); + void ItemCreateLink(void); + void CreateGroupClip(void); + void SetMask(void); + void ReleaseMask(void); + void SetClip(void); + void ReleaseClip(void); + ////////////////////////////////////////// + + + /** + * callback, is executed on clicking the anchor "Group" and "Ungroup" menu entry + */ + void ActivateUngroup(void); + void ActivateGroup(void); + + void AnchorLinkProperties(void); + /** + * placeholder for callback to be executed on clicking the anchor "Follow link" context menu entry + * @todo add code to follow link externally + */ + void AnchorLinkFollow(void); + + /** + * callback, is executed on clicking the anchor "Link remove" menu entry + */ + void AnchorLinkRemove(void); + + + /** + * callback, opens the image properties dialog and is executed on clicking the context menu entry with similar name + */ + void ImageProperties(void); + + /** + * callback, is executed on clicking the image "Edit Externally" menu entry + */ + void ImageEdit(void); + + /** + * auxiliary function that loads the external image editor name from the settings. + */ + Glib::ustring getImageEditorName(); + + /** + * callback, is executed on clicking the "Embed Image" menu entry + */ + void ImageEmbed(void); + + /** + * callback, is executed on clicking the "Trace Bitmap" menu entry + */ + void ImageTraceBitmap(void); + + /** + * callback, is executed on clicking the "Trace Pixel Art" menu entry + */ + void ImageTracePixelArt(void); + + /** + * callback, is executed on clicking the "Extract Image" menu entry + */ + void ImageExtract(void); + + + /** + * callback, is executed on clicking the "Fill and Stroke" menu entry + */ + void FillSettings(void); + + + /** + * callback, is executed on clicking the "Text and Font" menu entry + */ + void TextSettings(void); + + /** + * callback, is executed on clicking the "Check spelling" menu entry + */ + void SpellcheckSettings(void); +}; + +#endif // SEEN_SP_INTERFACE_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/object-edit.cpp b/src/ui/object-edit.cpp new file mode 100644 index 000000000..cc96727f8 --- /dev/null +++ b/src/ui/object-edit.cpp @@ -0,0 +1,1399 @@ +/* + * Node editing extension to objects + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Mitsuru Oka + * Maximilian Albert <maximilian.albert@gmail.com> + * Abhishek Sharma + * + * Licensed under GNU GPL + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + + +#include "sp-item.h" +#include "sp-rect.h" +#include "box3d.h" +#include "sp-ellipse.h" +#include "sp-star.h" +#include "sp-spiral.h" +#include "sp-offset.h" +#include "sp-flowtext.h" +#include "preferences.h" +#include "style.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "sp-namedview.h" +#include "live_effects/effect.h" +#include "sp-pattern.h" +#include "sp-path.h" +#include <glibmm/i18n.h> +#include "ui/object-edit.h" +#include "xml/repr.h" +#include <2geom/math-utils.h> +#include "knot-holder-entity.h" + +#define sp_round(v,m) (((v) < 0.0) ? ((ceil((v) / (m) - 0.5)) * (m)) : ((floor((v) / (m) + 0.5)) * (m))) + +namespace { + +static KnotHolder *sp_lpe_knot_holder(SPItem *item, SPDesktop *desktop) +{ + KnotHolder *knot_holder = new KnotHolder(desktop, item, NULL); + + Inkscape::LivePathEffect::Effect *effect = SP_LPE_ITEM(item)->getCurrentLPE(); + effect->addHandles(knot_holder, desktop, item); + + return knot_holder; +} + +} // namespace + +namespace Inkscape { +namespace UI { + +KnotHolder *createKnotHolder(SPItem *item, SPDesktop *desktop) +{ + KnotHolder *knotholder = NULL; + + if (SP_IS_LPE_ITEM(item) && + SP_LPE_ITEM(item)->getCurrentLPE() && + SP_LPE_ITEM(item)->getCurrentLPE()->isVisible() && + SP_LPE_ITEM(item)->getCurrentLPE()->providesKnotholder()) { + knotholder = sp_lpe_knot_holder(item, desktop); + } else if (SP_IS_RECT(item)) { + knotholder = new RectKnotHolder(desktop, item, NULL); + } else if (SP_IS_BOX3D(item)) { + knotholder = new Box3DKnotHolder(desktop, item, NULL); + } else if (SP_IS_GENERICELLIPSE(item)) { + knotholder = new ArcKnotHolder(desktop, item, NULL); + } else if (SP_IS_STAR(item)) { + knotholder = new StarKnotHolder(desktop, item, NULL); + } else if (SP_IS_SPIRAL(item)) { + knotholder = new SpiralKnotHolder(desktop, item, NULL); + } else if (SP_IS_OFFSET(item)) { + knotholder = new OffsetKnotHolder(desktop, item, NULL); + } else if (SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) { + knotholder = new FlowtextKnotHolder(desktop, SP_FLOWTEXT(item)->get_frame(NULL), NULL); + } else if ((item->style->fill.isPaintserver() && SP_IS_PATTERN(item->style->getFillPaintServer())) || + (item->style->stroke.isPaintserver() && SP_IS_PATTERN(item->style->getStrokePaintServer()))) { + knotholder = new KnotHolder(desktop, item, NULL); + knotholder->add_pattern_knotholder(); + } + + return knotholder; +} + +} +} // namespace Inkscape + +/* SPRect */ + +/* handle for horizontal rounding radius */ +class RectKnotHolderEntityRX : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); + virtual void knot_click(unsigned int state); +}; + +/* handle for vertical rounding radius */ +class RectKnotHolderEntityRY : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); + virtual void knot_click(unsigned int state); +}; + +/* handle for width/height adjustment */ +class RectKnotHolderEntityWH : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); + +protected: + void set_internal(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +/* handle for x/y adjustment */ +class RectKnotHolderEntityXY : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +Geom::Point +RectKnotHolderEntityRX::knot_get() const +{ + SPRect *rect = SP_RECT(item); + + return Geom::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed); +} + +void +RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state) +{ + SPRect *rect = SP_RECT(item); + + //In general we cannot just snap this radius to an arbitrary point, as we have only a single + //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap + //the radius then we should have a constrained snap. snap_knot_position() is unconstrained + Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0)), state); + + if (state & GDK_CONTROL_MASK) { + gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0; + rect->rx.computed = rect->ry.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, temp); + rect->rx._set = rect->ry._set = true; + + } else { + rect->rx.computed = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, rect->width.computed / 2.0); + rect->rx._set = true; + } + + update_knot(); + + (static_cast<SPObject*>(rect))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void +RectKnotHolderEntityRX::knot_click(unsigned int state) +{ + SPRect *rect = SP_RECT(item); + + if (state & GDK_SHIFT_MASK) { + /* remove rounding from rectangle */ + rect->getRepr()->setAttribute("rx", NULL); + rect->getRepr()->setAttribute("ry", NULL); + } else if (state & GDK_CONTROL_MASK) { + /* Ctrl-click sets the vertical rounding to be the same as the horizontal */ + rect->getRepr()->setAttribute("ry", rect->getRepr()->attribute("rx")); + } + +} + +Geom::Point +RectKnotHolderEntityRY::knot_get() const +{ + SPRect *rect = SP_RECT(item); + + return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed); +} + +void +RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state) +{ + SPRect *rect = SP_RECT(item); + + //In general we cannot just snap this radius to an arbitrary point, as we have only a single + //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap + //the radius then we should have a constrained snap. snap_knot_position() is unconstrained + Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1)), state); + + if (state & GDK_CONTROL_MASK) { // When holding control then rx will be kept equal to ry, + // resulting in a perfect circle (and not an ellipse) + gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0; + rect->rx.computed = rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed, 0.0, temp); + rect->ry._set = rect->rx._set = true; + } else { + if (!rect->rx._set || rect->rx.computed == 0) { + rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed, + 0.0, + MIN(rect->height.computed / 2.0, rect->width.computed / 2.0)); + } else { + rect->ry.computed = CLAMP(s[Geom::Y] - rect->y.computed, + 0.0, + rect->height.computed / 2.0); + } + + rect->ry._set = true; + } + + update_knot(); + + (static_cast<SPObject *>(rect))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void +RectKnotHolderEntityRY::knot_click(unsigned int state) +{ + SPRect *rect = SP_RECT(item); + + if (state & GDK_SHIFT_MASK) { + /* remove rounding */ + rect->getRepr()->setAttribute("rx", NULL); + rect->getRepr()->setAttribute("ry", NULL); + } else if (state & GDK_CONTROL_MASK) { + /* Ctrl-click sets the vertical rounding to be the same as the horizontal */ + rect->getRepr()->setAttribute("rx", rect->getRepr()->attribute("ry")); + } +} + +#define SGN(x) ((x)>0?1:((x)<0?-1:0)) + +static void sp_rect_clamp_radii(SPRect *rect) +{ + // clamp rounding radii so that they do not exceed width/height + if (2 * rect->rx.computed > rect->width.computed) { + rect->rx.computed = 0.5 * rect->width.computed; + rect->rx._set = true; + } + if (2 * rect->ry.computed > rect->height.computed) { + rect->ry.computed = 0.5 * rect->height.computed; + rect->ry._set = true; + } +} + +Geom::Point +RectKnotHolderEntityWH::knot_get() const +{ + SPRect *rect = SP_RECT(item); + + return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed); +} + +void +RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, unsigned int state) +{ + SPRect *rect = SP_RECT(item); + + Geom::Point s = p; + + if (state & GDK_CONTROL_MASK) { + // original width/height when drag started + gdouble const w_orig = (origin[Geom::X] - rect->x.computed); + gdouble const h_orig = (origin[Geom::Y] - rect->y.computed); + + //original ratio + gdouble ratio = (w_orig / h_orig); + + // mouse displacement since drag started + gdouble minx = p[Geom::X] - origin[Geom::X]; + gdouble miny = p[Geom::Y] - origin[Geom::Y]; + + Geom::Point p_handle(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed); + + if (fabs(minx) > fabs(miny)) { + // snap to horizontal or diagonal + if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) { + // closer to the diagonal and in same-sign quarters, change both using ratio + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)), state); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->height.computed = MAX(h_orig + minx / ratio, 0); + } else { + // closer to the horizontal, change only width, height is h_orig + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0)), state); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->height.computed = MAX(h_orig, 0); + } + rect->width.computed = MAX(w_orig + minx, 0); + + } else { + // snap to vertical or diagonal + if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) { + // closer to the diagonal and in same-sign quarters, change both using ratio + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)), state); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->width.computed = MAX(w_orig + miny * ratio, 0); + } else { + // closer to the vertical, change only height, width is w_orig + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1)), state); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->width.computed = MAX(w_orig, 0); + } + rect->height.computed = MAX(h_orig + miny, 0); + + } + + rect->width._set = rect->height._set = true; + + } else { + // move freely + s = snap_knot_position(p, state); + rect->width.computed = MAX(s[Geom::X] - rect->x.computed, 0); + rect->height.computed = MAX(s[Geom::Y] - rect->y.computed, 0); + rect->width._set = rect->height._set = true; + } + + sp_rect_clamp_radii(rect); + + (static_cast<SPObject *>(rect))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void +RectKnotHolderEntityWH::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) +{ + set_internal(p, origin, state); + update_knot(); +} + +Geom::Point +RectKnotHolderEntityXY::knot_get() const +{ + SPRect *rect = SP_RECT(item); + + return Geom::Point(rect->x.computed, rect->y.computed); +} + +void +RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) +{ + SPRect *rect = SP_RECT(item); + + // opposite corner (unmoved) + gdouble opposite_x = (rect->x.computed + rect->width.computed); + gdouble opposite_y = (rect->y.computed + rect->height.computed); + + // original width/height when drag started + gdouble w_orig = opposite_x - origin[Geom::X]; + gdouble h_orig = opposite_y - origin[Geom::Y]; + + Geom::Point s = p; + Geom::Point p_handle(rect->x.computed, rect->y.computed); + + // mouse displacement since drag started + gdouble minx = p[Geom::X] - origin[Geom::X]; + gdouble miny = p[Geom::Y] - origin[Geom::Y]; + + if (state & GDK_CONTROL_MASK) { + //original ratio + gdouble ratio = (w_orig / h_orig); + + if (fabs(minx) > fabs(miny)) { + // snap to horizontal or diagonal + if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) { + // closer to the diagonal and in same-sign quarters, change both using ratio + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)), state); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->y.computed = MIN(origin[Geom::Y] + minx / ratio, opposite_y); + rect->height.computed = MAX(h_orig - minx / ratio, 0); + } else { + // closer to the horizontal, change only width, height is h_orig + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0)), state); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->y.computed = MIN(origin[Geom::Y], opposite_y); + rect->height.computed = MAX(h_orig, 0); + } + rect->x.computed = MIN(s[Geom::X], opposite_x); + rect->width.computed = MAX(w_orig - minx, 0); + } else { + // snap to vertical or diagonal + if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) { + // closer to the diagonal and in same-sign quarters, change both using ratio + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)), state); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->x.computed = MIN(origin[Geom::X] + miny * ratio, opposite_x); + rect->width.computed = MAX(w_orig - miny * ratio, 0); + } else { + // closer to the vertical, change only height, width is w_orig + s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1)), state); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + rect->x.computed = MIN(origin[Geom::X], opposite_x); + rect->width.computed = MAX(w_orig, 0); + } + rect->y.computed = MIN(s[Geom::Y], opposite_y); + rect->height.computed = MAX(h_orig - miny, 0); + } + + rect->width._set = rect->height._set = rect->x._set = rect->y._set = true; + + } else { + // move freely + s = snap_knot_position(p, state); + minx = s[Geom::X] - origin[Geom::X]; + miny = s[Geom::Y] - origin[Geom::Y]; + + rect->x.computed = MIN(s[Geom::X], opposite_x); + rect->width.computed = MAX(w_orig - minx, 0); + rect->y.computed = MIN(s[Geom::Y], opposite_y); + rect->height.computed = MAX(h_orig - miny, 0); + rect->width._set = rect->height._set = rect->x._set = rect->y._set = true; + } + + sp_rect_clamp_radii(rect); + + update_knot(); + + (static_cast<SPObject *>(rect))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) : + KnotHolder(desktop, item, relhandler) +{ + RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX(); + RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY(); + RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH(); + RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY(); + + entity_rx->create(desktop, item, this, Inkscape::CTRL_TYPE_ROTATE, + _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> " + "to make the vertical radius the same"), + SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR); + + entity_ry->create(desktop, item, this, Inkscape::CTRL_TYPE_ROTATE, + _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> " + "to make the horizontal radius the same"), + SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR); + + entity_wh->create(desktop, item, this, Inkscape::CTRL_TYPE_SIZER, + _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> " + "to lock ratio or stretch in one dimension only"), + SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR); + + entity_xy->create(desktop, item, this, Inkscape::CTRL_TYPE_SIZER, + _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> " + "to lock ratio or stretch in one dimension only"), + SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR); + + entity.push_back(entity_rx); + entity.push_back(entity_ry); + entity.push_back(entity_wh); + entity.push_back(entity_xy); + + add_pattern_knotholder(); +} + +/* Box3D (= the new 3D box structure) */ + +class Box3DKnotHolderEntity : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const = 0; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) = 0; + + Geom::Point knot_get_generic(SPItem *item, unsigned int knot_id) const; + void knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &p, unsigned int state); +}; + +Geom::Point +Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id) const +{ + return box3d_get_corner_screen(SP_BOX3D(item), knot_id); +} + +void +Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &new_pos, unsigned int state) +{ + Geom::Point const s = snap_knot_position(new_pos, state); + + g_assert(item != NULL); + SPBox3D *box = SP_BOX3D(item); + Geom::Affine const i2dt (item->i2dt_affine ()); + + Box3D::Axis movement; + if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) { + movement = Box3D::XY; + } else { + movement = Box3D::Z; + } + + box3d_set_corner (box, knot_id, s * i2dt, movement, (state & GDK_CONTROL_MASK)); + box3d_set_z_orders(box); + box3d_position_set(box); +} + +class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +class Box3DKnotHolderEntityCenter : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +Geom::Point +Box3DKnotHolderEntity0::knot_get() const +{ + return knot_get_generic(item, 0); +} + +Geom::Point +Box3DKnotHolderEntity1::knot_get() const +{ + return knot_get_generic(item, 1); +} + +Geom::Point +Box3DKnotHolderEntity2::knot_get() const +{ + return knot_get_generic(item, 2); +} + +Geom::Point +Box3DKnotHolderEntity3::knot_get() const +{ + return knot_get_generic(item, 3); +} + +Geom::Point +Box3DKnotHolderEntity4::knot_get() const +{ + return knot_get_generic(item, 4); +} + +Geom::Point +Box3DKnotHolderEntity5::knot_get() const +{ + return knot_get_generic(item, 5); +} + +Geom::Point +Box3DKnotHolderEntity6::knot_get() const +{ + return knot_get_generic(item, 6); +} + +Geom::Point +Box3DKnotHolderEntity7::knot_get() const +{ + return knot_get_generic(item, 7); +} + +Geom::Point +Box3DKnotHolderEntityCenter::knot_get() const +{ + return box3d_get_center_screen(SP_BOX3D(item)); +} + +void +Box3DKnotHolderEntity0::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state) +{ + knot_set_generic(item, 0, new_pos, state); +} + +void +Box3DKnotHolderEntity1::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state) +{ + knot_set_generic(item, 1, new_pos, state); +} + +void +Box3DKnotHolderEntity2::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state) +{ + knot_set_generic(item, 2, new_pos, state); +} + +void +Box3DKnotHolderEntity3::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state) +{ + knot_set_generic(item, 3, new_pos, state); +} + +void +Box3DKnotHolderEntity4::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state) +{ + knot_set_generic(item, 4, new_pos, state); +} + +void +Box3DKnotHolderEntity5::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state) +{ + knot_set_generic(item, 5, new_pos, state); +} + +void +Box3DKnotHolderEntity6::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state) +{ + knot_set_generic(item, 6, new_pos, state); +} + +void +Box3DKnotHolderEntity7::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state) +{ + knot_set_generic(item, 7, new_pos, state); +} + +void +Box3DKnotHolderEntityCenter::knot_set(Geom::Point const &new_pos, Geom::Point const &origin, unsigned int state) +{ + Geom::Point const s = snap_knot_position(new_pos, state); + + SPBox3D *box = SP_BOX3D(item); + Geom::Affine const i2dt (item->i2dt_affine ()); + + box3d_set_center (SP_BOX3D(item), s * i2dt, origin * i2dt, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z, + state & GDK_CONTROL_MASK); + + box3d_set_z_orders(box); + box3d_position_set(box); +} + +Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) : + KnotHolder(desktop, item, relhandler) +{ + Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0(); + Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1(); + Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2(); + Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3(); + Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4(); + Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5(); + Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6(); + Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7(); + Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter(); + + entity_corner0->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; " + "with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); + + entity_corner1->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; " + "with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); + + entity_corner2->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; " + "with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); + + entity_corner3->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; " + "with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); + + entity_corner4->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; " + "with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); + + entity_corner5->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; " + "with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); + + entity_corner6->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; " + "with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); + + entity_corner7->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; " + "with <b>Ctrl</b> to constrain to the directions of edges or diagonals")); + + entity_center->create(desktop, item, this, Inkscape::CTRL_TYPE_POINT, + _("Move the box in perspective"), + SP_KNOT_SHAPE_CROSS); + + entity.push_back(entity_corner0); + entity.push_back(entity_corner1); + entity.push_back(entity_corner2); + entity.push_back(entity_corner3); + entity.push_back(entity_corner4); + entity.push_back(entity_corner5); + entity.push_back(entity_corner6); + entity.push_back(entity_corner7); + entity.push_back(entity_center); + + add_pattern_knotholder(); +} + +/* SPArc */ + +class ArcKnotHolderEntityStart : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); + virtual void knot_click(unsigned int state); +}; + +class ArcKnotHolderEntityEnd : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); + virtual void knot_click(unsigned int state); +}; + +class ArcKnotHolderEntityRX : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); + virtual void knot_click(unsigned int state); +}; + +class ArcKnotHolderEntityRY : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); + virtual void knot_click(unsigned int state); +}; + +/* + * return values: + * 1 : inside + * 0 : on the curves + * -1 : outside + */ +static gint +sp_genericellipse_side(SPGenericEllipse *ellipse, Geom::Point const &p) +{ + gdouble dx = (p[Geom::X] - ellipse->cx.computed) / ellipse->rx.computed; + gdouble dy = (p[Geom::Y] - ellipse->cy.computed) / ellipse->ry.computed; + + gdouble s = dx * dx + dy * dy; + if (s < 1.0) return 1; + if (s > 1.0) return -1; + return 0; +} + +void +ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state) +{ + int snaps = Inkscape::Preferences::get()->getInt("/options/rotationsnapsperpi/value", 12); + + SPGenericEllipse *arc = SP_GENERICELLIPSE(item); + + arc->setClosed(sp_genericellipse_side(arc, p) == -1); + + Geom::Point delta = p - Geom::Point(arc->cx.computed, arc->cy.computed); + Geom::Scale sc(arc->rx.computed, arc->ry.computed); + + arc->start = atan2(delta * sc.inverse()); + + if ((state & GDK_CONTROL_MASK) && snaps) { + arc->start = sp_round(arc->start, M_PI / snaps); + } + + arc->normalize(); + arc->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +Geom::Point +ArcKnotHolderEntityStart::knot_get() const +{ + SPGenericEllipse const *ge = SP_GENERICELLIPSE(item); + + return ge->getPointAtAngle(ge->start); +} + +void +ArcKnotHolderEntityStart::knot_click(unsigned int state) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + if (state & GDK_SHIFT_MASK) { + ge->end = ge->start = 0; + (static_cast<SPObject *>(ge))->updateRepr(); + } +} + +void +ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state) +{ + int snaps = Inkscape::Preferences::get()->getInt("/options/rotationsnapsperpi/value", 12); + + SPGenericEllipse *arc = SP_GENERICELLIPSE(item); + + arc->setClosed(sp_genericellipse_side(arc, p) == -1); + + Geom::Point delta = p - Geom::Point(arc->cx.computed, arc->cy.computed); + Geom::Scale sc(arc->rx.computed, arc->ry.computed); + + arc->end = atan2(delta * sc.inverse()); + + if ((state & GDK_CONTROL_MASK) && snaps) { + arc->end = sp_round(arc->end, M_PI/snaps); + } + + arc->normalize(); + arc->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +Geom::Point +ArcKnotHolderEntityEnd::knot_get() const +{ + SPGenericEllipse const *ge = SP_GENERICELLIPSE(item); + + return ge->getPointAtAngle(ge->end); +} + + +void +ArcKnotHolderEntityEnd::knot_click(unsigned int state) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + if (state & GDK_SHIFT_MASK) { + ge->end = ge->start = 0; + (static_cast<SPObject *>(ge))->updateRepr(); + } +} + + +void +ArcKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + Geom::Point const s = snap_knot_position(p, state); + + ge->rx.computed = fabs( ge->cx.computed - s[Geom::X] ); + + if ( state & GDK_CONTROL_MASK ) { + ge->ry.computed = ge->rx.computed; + } + + (static_cast<SPObject *>(item))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +Geom::Point +ArcKnotHolderEntityRX::knot_get() const +{ + SPGenericEllipse const *ge = SP_GENERICELLIPSE(item); + + return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(ge->rx.computed, 0)); +} + +void +ArcKnotHolderEntityRX::knot_click(unsigned int state) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + if (state & GDK_CONTROL_MASK) { + ge->ry.computed = ge->rx.computed; + (static_cast<SPObject *>(ge))->updateRepr(); + } +} + +void +ArcKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + Geom::Point const s = snap_knot_position(p, state); + + ge->ry.computed = fabs( ge->cy.computed - s[Geom::Y] ); + + if ( state & GDK_CONTROL_MASK ) { + ge->rx.computed = ge->ry.computed; + } + + (static_cast<SPObject *>(item))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +Geom::Point +ArcKnotHolderEntityRY::knot_get() const +{ + SPGenericEllipse const *ge = SP_GENERICELLIPSE(item); + + return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(0, ge->ry.computed)); +} + +void +ArcKnotHolderEntityRY::knot_click(unsigned int state) +{ + SPGenericEllipse *ge = SP_GENERICELLIPSE(item); + + if (state & GDK_CONTROL_MASK) { + ge->rx.computed = ge->ry.computed; + (static_cast<SPObject *>(ge))->updateRepr(); + } +} + +ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) : + KnotHolder(desktop, item, relhandler) +{ + ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX(); + ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY(); + ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart(); + ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd(); + + entity_rx->create(desktop, item, this, Inkscape::CTRL_TYPE_SIZER, + _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"), + SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR); + + entity_ry->create(desktop, item, this, Inkscape::CTRL_TYPE_SIZER, + _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"), + SP_KNOT_SHAPE_SQUARE, SP_KNOT_MODE_XOR); + + entity_start->create(desktop, item, this, Inkscape::CTRL_TYPE_ROTATE, + _("Position the <b>start point</b> of the arc or segment; with <b>Ctrl</b> " + "to snap angle; drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"), + SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR); + + entity_end->create(desktop, item, this, Inkscape::CTRL_TYPE_ROTATE, + _("Position the <b>end point</b> of the arc or segment; with <b>Ctrl</b> to snap angle; " + "drag <b>inside</b> the ellipse for arc, <b>outside</b> for segment"), + SP_KNOT_SHAPE_CIRCLE, SP_KNOT_MODE_XOR); + + entity.push_back(entity_rx); + entity.push_back(entity_ry); + entity.push_back(entity_start); + entity.push_back(entity_end); + + add_pattern_knotholder(); +} + +/* SPStar */ + +class StarKnotHolderEntity1 : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); + virtual void knot_click(unsigned int state); +}; + +class StarKnotHolderEntity2 : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); + virtual void knot_click(unsigned int state); +}; + +void +StarKnotHolderEntity1::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state) +{ + SPStar *star = SP_STAR(item); + + Geom::Point const s = snap_knot_position(p, state); + + Geom::Point d = s - star->center; + + double arg1 = atan2(d); + double darg1 = arg1 - star->arg[0]; + + if (state & GDK_MOD1_MASK) { + star->randomized = darg1/(star->arg[0] - star->arg[1]); + } else if (state & GDK_SHIFT_MASK) { + star->rounded = darg1/(star->arg[0] - star->arg[1]); + } else if (state & GDK_CONTROL_MASK) { + star->r[0] = L2(d); + } else { + star->r[0] = L2(d); + star->arg[0] = arg1; + star->arg[1] += darg1; + } + (static_cast<SPObject *>(star))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void +StarKnotHolderEntity2::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state) +{ + SPStar *star = SP_STAR(item); + + Geom::Point const s = snap_knot_position(p, state); + + if (star->flatsided == false) { + Geom::Point d = s - star->center; + + double arg1 = atan2(d); + double darg1 = arg1 - star->arg[1]; + + if (state & GDK_MOD1_MASK) { + star->randomized = darg1/(star->arg[0] - star->arg[1]); + } else if (state & GDK_SHIFT_MASK) { + star->rounded = fabs(darg1/(star->arg[0] - star->arg[1])); + } else if (state & GDK_CONTROL_MASK) { + star->r[1] = L2(d); + star->arg[1] = star->arg[0] + M_PI / star->sides; + } + else { + star->r[1] = L2(d); + star->arg[1] = atan2(d); + } + (static_cast<SPObject *>(star))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } +} + +Geom::Point +StarKnotHolderEntity1::knot_get() const +{ + g_assert(item != NULL); + + SPStar const *star = SP_STAR(item); + + return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0); + +} + +Geom::Point +StarKnotHolderEntity2::knot_get() const +{ + g_assert(item != NULL); + + SPStar const *star = SP_STAR(item); + + return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0); +} + +static void +sp_star_knot_click(SPItem *item, unsigned int state) +{ + SPStar *star = SP_STAR(item); + + if (state & GDK_MOD1_MASK) { + star->randomized = 0; + (static_cast<SPObject *>(star))->updateRepr(); + } else if (state & GDK_SHIFT_MASK) { + star->rounded = 0; + (static_cast<SPObject *>(star))->updateRepr(); + } else if (state & GDK_CONTROL_MASK) { + star->arg[1] = star->arg[0] + M_PI / star->sides; + (static_cast<SPObject *>(star))->updateRepr(); + } +} + +void +StarKnotHolderEntity1::knot_click(unsigned int state) +{ + sp_star_knot_click(item, state); +} + +void +StarKnotHolderEntity2::knot_click(unsigned int state) +{ + sp_star_knot_click(item, state); +} + +StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) : + KnotHolder(desktop, item, relhandler) +{ + SPStar *star = SP_STAR(item); + + StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1(); + entity1->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Adjust the <b>tip radius</b> of the star or polygon; " + "with <b>Shift</b> to round; with <b>Alt</b> to randomize")); + + entity.push_back(entity1); + + if (star->flatsided == false) { + StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2(); + entity2->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays " + "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize")); + entity.push_back(entity2); + } + + add_pattern_knotholder(); +} + +/* SPSpiral */ + +class SpiralKnotHolderEntityInner : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); + virtual void knot_click(unsigned int state); +}; + +class SpiralKnotHolderEntityOuter : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + + +/* + * set attributes via inner (t=t0) knot point: + * [default] increase/decrease inner point + * [shift] increase/decrease inner and outer arg synchronizely + * [control] constrain inner arg to round per PI/4 + */ +void +SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); + + SPSpiral *spiral = SP_SPIRAL(item); + + gdouble dx = p[Geom::X] - spiral->cx; + gdouble dy = p[Geom::Y] - spiral->cy; + + gdouble moved_y = p[Geom::Y] - origin[Geom::Y]; + + if (state & GDK_MOD1_MASK) { + // adjust divergence by vertical drag, relative to rad + if (spiral->rad > 0) { + double exp_delta = 0.1*moved_y/(spiral->rad); // arbitrary multiplier to slow it down + spiral->exp += exp_delta; + if (spiral->exp < 1e-3) + spiral->exp = 1e-3; + } + } else { + // roll/unroll from inside + gdouble arg_t0; + spiral->getPolar(spiral->t0, NULL, &arg_t0); + + gdouble arg_tmp = atan2(dy, dx) - arg_t0; + gdouble arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0; + spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo); + + /* round inner arg per PI/snaps, if CTRL is pressed */ + if ( ( state & GDK_CONTROL_MASK ) + && ( fabs(spiral->revo) > SP_EPSILON_2 ) + && ( snaps != 0 ) ) { + gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg; + spiral->t0 = (sp_round(arg, M_PI/snaps) - spiral->arg)/(2.0*M_PI*spiral->revo); + } + + spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999); + } + + (static_cast<SPObject *>(spiral))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/* + * set attributes via outer (t=1) knot point: + * [default] increase/decrease revolution factor + * [control] constrain inner arg to round per PI/4 + */ +void +SpiralKnotHolderEntityOuter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); + + SPSpiral *spiral = SP_SPIRAL(item); + + gdouble dx = p[Geom::X] - spiral->cx; + gdouble dy = p[Geom::Y] - spiral->cy; + + if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll + spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo; + if (!(state & GDK_MOD1_MASK)) { + // if alt not pressed, change also rad; otherwise it is locked + spiral->rad = MAX(hypot(dx, dy), 0.001); + } + if ( ( state & GDK_CONTROL_MASK ) + && snaps ) { + spiral->arg = sp_round(spiral->arg, M_PI/snaps); + } + } else { // roll/unroll + // arg of the spiral outer end + double arg_1; + spiral->getPolar(1, NULL, &arg_1); + + // its fractional part after the whole turns are subtracted + double arg_r = arg_1 - sp_round(arg_1, 2.0*M_PI); + + // arg of the mouse point relative to spiral center + double mouse_angle = atan2(dy, dx); + if (mouse_angle < 0) + mouse_angle += 2*M_PI; + + // snap if ctrl + if ( ( state & GDK_CONTROL_MASK ) && snaps ) { + mouse_angle = sp_round(mouse_angle, M_PI/snaps); + } + + // by how much we want to rotate the outer point + double diff = mouse_angle - arg_r; + if (diff > M_PI) + diff -= 2*M_PI; + else if (diff < -M_PI) + diff += 2*M_PI; + + // calculate the new rad; + // the value of t corresponding to the angle arg_1 + diff: + double t_temp = ((arg_1 + diff) - spiral->arg)/(2*M_PI*spiral->revo); + // the rad at that t: + double rad_new = 0; + if (t_temp > spiral->t0) + spiral->getPolar(t_temp, &rad_new, NULL); + + // change the revo (converting diff from radians to the number of turns) + spiral->revo += diff/(2*M_PI); + if (spiral->revo < 1e-3) + spiral->revo = 1e-3; + + // if alt not pressed and the values are sane, change the rad + if (!(state & GDK_MOD1_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) { + // adjust t0 too so that the inner point stays unmoved + double r0; + spiral->getPolar(spiral->t0, &r0, NULL); + spiral->rad = rad_new; + spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp); + } + if (!IS_FINITE(spiral->t0)) spiral->t0 = 0.0; + spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999); + } + + (static_cast<SPObject *>(spiral))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +Geom::Point +SpiralKnotHolderEntityInner::knot_get() const +{ + SPSpiral const *spiral = SP_SPIRAL(item); + + return spiral->getXY(spiral->t0); +} + +Geom::Point +SpiralKnotHolderEntityOuter::knot_get() const +{ + SPSpiral const *spiral = SP_SPIRAL(item); + + return spiral->getXY(1.0); +} + +void +SpiralKnotHolderEntityInner::knot_click(unsigned int state) +{ + SPSpiral *spiral = SP_SPIRAL(item); + + if (state & GDK_MOD1_MASK) { + spiral->exp = 1; + (static_cast<SPObject *>(spiral))->updateRepr(); + } else if (state & GDK_SHIFT_MASK) { + spiral->t0 = 0; + (static_cast<SPObject *>(spiral))->updateRepr(); + } +} + +SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) : + KnotHolder(desktop, item, relhandler) +{ + SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner(); + SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter(); + + entity_inner->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; " + "with <b>Alt</b> to converge/diverge")); + + entity_outer->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; " + "with <b>Shift</b> to scale/rotate; with <b>Alt</b> to lock radius")); + + entity.push_back(entity_inner); + entity.push_back(entity_outer); + + add_pattern_knotholder(); +} + +/* SPOffset */ + +class OffsetKnotHolderEntity : public KnotHolderEntity { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +void +OffsetKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int /*state*/) +{ + SPOffset *offset = SP_OFFSET(item); + + offset->rad = sp_offset_distance_to_original(offset, p); + offset->knot = p; + offset->knotSet = true; + + (static_cast<SPObject *>(offset))->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + + +Geom::Point +OffsetKnotHolderEntity::knot_get() const +{ + SPOffset const *offset = SP_OFFSET(item); + + Geom::Point np; + sp_offset_top_point(offset,&np); + return np; +} + +OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) : + KnotHolder(desktop, item, relhandler) +{ + OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity(); + entity_offset->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Adjust the <b>offset distance</b>")); + entity.push_back(entity_offset); + + add_pattern_knotholder(); +} + +// TODO: this is derived from RectKnotHolderEntityWH because it used the same static function +// set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes +// sense logically. +class FlowtextKnotHolderEntity : public RectKnotHolderEntityWH { +public: + virtual Geom::Point knot_get() const; + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state); +}; + +Geom::Point +FlowtextKnotHolderEntity::knot_get() const +{ + SPRect const *rect = SP_RECT(item); + + return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed); +} + +void +FlowtextKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) +{ + set_internal(p, origin, state); +} + +FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) : + KnotHolder(desktop, item, relhandler) +{ + g_assert(item != NULL); + + FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity(); + entity_flowtext->create(desktop, item, this, Inkscape::CTRL_TYPE_SHAPER, + _("Drag to resize the <b>flowed text frame</b>")); + entity.push_back(entity_flowtext); +} + +/* + 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/object-edit.h b/src/ui/object-edit.h new file mode 100644 index 000000000..75f3ce12b --- /dev/null +++ b/src/ui/object-edit.h @@ -0,0 +1,84 @@ +#ifndef OBJECT_EDIT_H_SEEN +#define OBJECT_EDIT_H_SEEN + +/* + * Node editing extension to objects + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Mitsuru Oka + * Jon A. Cruz <jon@joncruz.org> + * + * Licensed under GNU GPL + */ + +#include "knotholder.h" + +namespace Inkscape { +namespace UI { + +KnotHolder *createKnotHolder(SPItem *item, SPDesktop *desktop); + +} +} + +class RectKnotHolder : public KnotHolder { +public: + RectKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); + virtual ~RectKnotHolder() {}; +}; + +class Box3DKnotHolder : public KnotHolder { +public: + Box3DKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); + virtual ~Box3DKnotHolder() {}; +}; + +class ArcKnotHolder : public KnotHolder { +public: + ArcKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); + virtual ~ArcKnotHolder() {}; +}; + +class StarKnotHolder : public KnotHolder { +public: + StarKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); + virtual ~StarKnotHolder() {}; +}; + +class SpiralKnotHolder : public KnotHolder { +public: + SpiralKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); + virtual ~SpiralKnotHolder() {}; +}; + +class OffsetKnotHolder : public KnotHolder { +public: + OffsetKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); + virtual ~OffsetKnotHolder() {}; +}; + +class FlowtextKnotHolder : public KnotHolder { +public: + FlowtextKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); + virtual ~FlowtextKnotHolder() {}; +}; + +class MiscKnotHolder : public KnotHolder { +public: + MiscKnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); + virtual ~MiscKnotHolder() {}; +}; + +#endif // OBJECT_EDIT_H_SEEN + +/* + 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/shape-editor.cpp b/src/ui/shape-editor.cpp new file mode 100644 index 000000000..0b9fc24c5 --- /dev/null +++ b/src/ui/shape-editor.cpp @@ -0,0 +1,177 @@ +/* + * Inkscape::ShapeEditor + * + * Authors: + * bulia byak <buliabyak@users.sf.net> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include <glibmm/i18n.h> + +#include "desktop-handles.h" +#include "document.h" +#include "gc-anchored.h" +#include "knotholder.h" +#include "ui/object-edit.h" +#include "sp-item.h" +#include "sp-object.h" +#include "ui/shape-editor.h" +#include "xml/node-event-vector.h" + +//using Inkscape::createKnotHolder; + +namespace Inkscape { +namespace UI { + +bool ShapeEditor::_blockSetItem = false; + +ShapeEditor::ShapeEditor(SPDesktop *dt) { + this->desktop = dt; + this->knotholder = NULL; + this->knotholder_listener_attached_for = NULL; +} + +ShapeEditor::~ShapeEditor() { + unset_item(); +} + +void ShapeEditor::unset_item(bool keep_knotholder) { + if (this->knotholder) { + Inkscape::XML::Node *old_repr = this->knotholder->repr; + if (old_repr && old_repr == knotholder_listener_attached_for) { + sp_repr_remove_listener_by_data(old_repr, this); + Inkscape::GC::release(old_repr); + knotholder_listener_attached_for = NULL; + } + + if (!keep_knotholder) { + delete this->knotholder; + this->knotholder = NULL; + } + } +} + +bool ShapeEditor::has_knotholder() { + return this->knotholder != NULL; +} + +void ShapeEditor::update_knotholder() { + if (this->knotholder) + this->knotholder->update_knots(); +} + +bool ShapeEditor::has_local_change() { + return (this->knotholder && this->knotholder->local_change != 0); +} + +void ShapeEditor::decrement_local_change() { + if (this->knotholder) { + this->knotholder->local_change = FALSE; + } +} + +const SPItem *ShapeEditor::get_item() { + const SPItem *item = NULL; + if (this->has_knotholder()) { + item = this->knotholder->getItem(); + } + return item; +} + +void ShapeEditor::event_attr_changed(Inkscape::XML::Node *, gchar const *name, gchar const *, gchar const *, bool, void *data) +{ + g_assert(data); + ShapeEditor *sh = static_cast<ShapeEditor *>(data); + bool changed_kh = false; + + if (sh->has_knotholder()) + { + changed_kh = !sh->has_local_change(); + sh->decrement_local_change(); + if (changed_kh) { + // this can happen if an LPEItem's knotholder handle was dragged, in which case we want + // to keep the knotholder; in all other cases (e.g., if the LPE itself changes) we delete it + sh->reset_item(!strcmp(name, "d")); + } + } +} + +static Inkscape::XML::NodeEventVector shapeeditor_repr_events = { + NULL, /* child_added */ + NULL, /* child_removed */ + ShapeEditor::event_attr_changed, + NULL, /* content_changed */ + NULL /* order_changed */ +}; + + +void ShapeEditor::set_item(SPItem *item, bool keep_knotholder) { + if (_blockSetItem) { + return; + } + + // this happens (and should only happen) when for an LPEItem having both knotholder and + // nodepath the knotholder is adapted; in this case we don't want to delete the knotholder + // since this freezes the handles + unset_item(keep_knotholder); + + if (item) { + Inkscape::XML::Node *repr; + if (!this->knotholder) { + // only recreate knotholder if none is present + this->knotholder = createKnotHolder(item, desktop); + } + if (this->knotholder) { + this->knotholder->update_knots(); + // setting new listener + repr = this->knotholder->repr; + if (repr != knotholder_listener_attached_for) { + Inkscape::GC::anchor(repr); + sp_repr_add_listener(repr, &shapeeditor_repr_events, this); + knotholder_listener_attached_for = repr; + } + } + } +} + + +/** FIXME: This thing is only called when the item needs to be updated in response to repr change. + Why not make a reload function in KnotHolder? */ +void ShapeEditor::reset_item(bool keep_knotholder) +{ + if (knotholder) { + SPObject *obj = sp_desktop_document(desktop)->getObjectByRepr(knotholder_listener_attached_for); /// note that it is not certain that this is an SPItem; it could be a LivePathEffectObject. + set_item(SP_ITEM(obj), keep_knotholder); + } +} + +/** + * Returns true if this ShapeEditor has a knot above which the mouse currently hovers. + */ +bool ShapeEditor::knot_mouseover() const { + if (this->knotholder) { + return knotholder->knot_mouseover(); + } + + return false; +} + +} // 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 : diff --git a/src/ui/shape-editor.h b/src/ui/shape-editor.h new file mode 100644 index 000000000..142a2493b --- /dev/null +++ b/src/ui/shape-editor.h @@ -0,0 +1,71 @@ +#ifndef SEEN_SHAPE_EDITOR_H +#define SEEN_SHAPE_EDITOR_H + +/* + * Inkscape::ShapeEditor + * + * This is a container class which contains a knotholder for shapes. + * It is attached to a single item. + * + * Authors: + * bulia byak <buliabyak@users.sf.net> + * + */ + +class KnotHolder; +class LivePathEffectObject; +class SPDesktop; +class SPItem; + +namespace Inkscape { namespace XML { class Node; } +namespace UI { + +class ShapeEditor { +public: + + ShapeEditor(SPDesktop *desktop); + ~ShapeEditor(); + + void set_item(SPItem *item, bool keep_knotholder = false); + void unset_item(bool keep_knotholder = false); + + void update_knotholder(); //((deprecated)) + + bool has_local_change(); + void decrement_local_change(); + + bool knot_mouseover() const; + + static void blockSetItem(bool b) { _blockSetItem = b; } // kludge + + static void event_attr_changed(Inkscape::XML::Node * /*repr*/, char const *name, char const * /*old_value*/, + char const * /*new_value*/, bool /*is_interactive*/, void *data); +private: + bool has_knotholder(); + void reset_item (bool keep_knotholder = true); + const SPItem *get_item(); + + static bool _blockSetItem; + + SPDesktop *desktop; + KnotHolder *knotholder; + Inkscape::XML::Node *knotholder_listener_attached_for; +}; + +} // namespace UI +} // namespace Inkscape + +#endif // SEEN_SHAPE_EDITOR_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/tool-factory.h b/src/ui/tool-factory.h new file mode 100644 index 000000000..726706732 --- /dev/null +++ b/src/ui/tool-factory.h @@ -0,0 +1,40 @@ +/** @file + * Factory for ToolBase tree + *//* + * Authors: + * Markus Engel + * + * Copyright (C) 2013 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef TOOL_FACTORY_SEEN +#define TOOL_FACTORY_SEEN + +#include "factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +class ToolBase; + +} +} +} + +typedef Singleton< Factory<Inkscape::UI::Tools::ToolBase> > ToolFactory; + + +#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/tool/node.cpp b/src/ui/tool/node.cpp index c52bd4c07..4abc901d2 100644 --- a/src/ui/tool/node.cpp +++ b/src/ui/tool/node.cpp @@ -27,10 +27,11 @@ #include "ui/tool/event-utils.h" #include "ui/tool/node.h" #include "ui/tool/path-manipulator.h" +#include "ui/tools/node-tool.h" +#include "ui/tools-switch.h" #include <gdk/gdkkeysyms.h> #include <cmath> - namespace { Inkscape::ControlType nodeTypeToCtrlType(Inkscape::UI::NodeType type) @@ -329,6 +330,10 @@ bool Handle::grabbed(GdkEventMotion *) void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event) { + if (tools_isactive(_desktop, TOOLS_NODES)) { + Inkscape::UI::Tools::NodeTool *nt = static_cast<Inkscape::UI::Tools::NodeTool*>(_desktop->event_context); + nt->update_helperpath(); + } Geom::Point parent_pos = _parent->position(); Geom::Point origin = _last_drag_origin(); SnapManager &sm = _desktop->namedview->snap_manager; @@ -1222,6 +1227,10 @@ bool Node::grabbed(GdkEventMotion *event) void Node::dragged(Geom::Point &new_pos, GdkEventMotion *event) { + if (tools_isactive(_desktop, TOOLS_NODES)) { + Inkscape::UI::Tools::NodeTool *nt = static_cast<Inkscape::UI::Tools::NodeTool*>(_desktop->event_context); + nt->update_helperpath(); + } // For a note on how snapping is implemented in Inkscape, see snap.h. SnapManager &sm = _desktop->namedview->snap_manager; // even if we won't really snap, we might still call the one of the diff --git a/src/ui/tools-switch.cpp b/src/ui/tools-switch.cpp new file mode 100644 index 000000000..79d6b8930 --- /dev/null +++ b/src/ui/tools-switch.cpp @@ -0,0 +1,196 @@ +/* + * Utility functions for switching tools (= contexts) + * + * Authors: + * bulia byak <buliabyak@users.sf.net> + * Josh Andler <scislac@users.sf.net> + * + * Copyright (C) 2003-2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <gtkmm.h> // prevents deprecation warnings + +#include <cstring> +#include <string> + +#include "inkscape.h" +#include "desktop.h" +#include "desktop-handles.h" +#include <glibmm/i18n.h> + +#include <xml/repr.h> + +#include "ui/tools-switch.h" + +#include "box3d.h" +#include "sp-ellipse.h" +#include "sp-flowtext.h" +#include "sp-offset.h" +#include "sp-path.h" +#include "sp-rect.h" +#include "sp-star.h" +#include "sp-spiral.h" +#include "sp-text.h" + +// TODO: How many of these are actually needed? +#include "ui/tools/arc-tool.h" +#include "ui/tools/box3d-tool.h" +#include "ui/tools/calligraphic-tool.h" +#include "ui/tools/connector-tool.h" +#include "ui/tools/dropper-tool.h" +#include "ui/tools/eraser-tool.h" +#include "ui/tools/flood-tool.h" +#include "ui/tools/gradient-tool.h" +#include "ui/tools/lpe-tool.h" +#include "ui/tools/measure-tool.h" +#include "ui/tools/mesh-tool.h" +#include "ui/tools/node-tool.h" +#include "ui/tools/pen-tool.h" +#include "ui/tools/pencil-tool.h" +#include "ui/tools/rect-tool.h" +#include "ui/tools/select-tool.h" +#include "ui/tools/spiral-tool.h" +#include "ui/tools/spray-tool.h" +#include "ui/tools/text-tool.h" +#include "ui/tools/tweak-tool.h" +#include "ui/tools/zoom-tool.h" + +#include "message-context.h" + +static char const *const tool_names[] = { + NULL, + "/tools/select", + "/tools/nodes", + "/tools/tweak", + "/tools/spray", + "/tools/shapes/rect", + "/tools/shapes/3dbox", + "/tools/shapes/arc", + "/tools/shapes/star", + "/tools/shapes/spiral", + "/tools/freehand/pencil", + "/tools/freehand/pen", + "/tools/calligraphic", + "/tools/text", + "/tools/gradient", + "/tools/mesh", + "/tools/zoom", + "/tools/measure", + "/tools/dropper", + "/tools/connector", + "/tools/paintbucket", + "/tools/eraser", + "/tools/lpetool", + NULL +}; + +// TODO: HEY! these belong to the tools themselves! +static char const *const tool_msg[] = { + NULL, + N_("<b>Click</b> to Select and Transform objects, <b>Drag</b> to select many objects."), + N_("Modify selected path points (nodes) directly."), + N_("To tweak a path by pushing, select it and drag over it."), + N_("<b>Drag</b>, <b>click</b> or <b>click and scroll</b> to spray the selected objects."), + N_("<b>Drag</b> to create a rectangle. <b>Drag controls</b> to round corners and resize. <b>Click</b> to select."), + N_("<b>Drag</b> to create a 3D box. <b>Drag controls</b> to resize in perspective. <b>Click</b> to select (with <b>Ctrl+Alt</b> for single faces)."), + N_("<b>Drag</b> to create an ellipse. <b>Drag controls</b> to make an arc or segment. <b>Click</b> to select."), + N_("<b>Drag</b> to create a star. <b>Drag controls</b> to edit the star shape. <b>Click</b> to select."), + N_("<b>Drag</b> to create a spiral. <b>Drag controls</b> to edit the spiral shape. <b>Click</b> to select."), + N_("<b>Drag</b> to create a freehand line. <b>Shift</b> appends to selected path, <b>Alt</b> activates sketch mode."), + N_("<b>Click</b> or <b>click and drag</b> to start a path; with <b>Shift</b> to append to selected path. <b>Ctrl+click</b> to create single dots (straight line modes only)."), + N_("<b>Drag</b> to draw a calligraphic stroke; with <b>Ctrl</b> to track a guide path. <b>Arrow keys</b> adjust width (left/right) and angle (up/down)."), + N_("<b>Click</b> to select or create text, <b>drag</b> to create flowed text; then type."), + N_("<b>Drag</b> or <b>double click</b> to create a gradient on selected objects, <b>drag handles</b> to adjust gradients."), + N_("<b>Drag</b> or <b>double click</b> to create a mesh on selected objects, <b>drag handles</b> to adjust meshes."), + N_("<b>Click</b> or <b>drag around an area</b> to zoom in, <b>Shift+click</b> to zoom out."), + N_("<b>Drag</b> to measure the dimensions of objects."), + N_("<b>Click</b> to set fill, <b>Shift+click</b> to set stroke; <b>drag</b> to average color in area; with <b>Alt</b> to pick inverse color; <b>Ctrl+C</b> to copy the color under mouse to clipboard"), + N_("<b>Click and drag</b> between shapes to create a connector."), + N_("<b>Click</b> to paint a bounded area, <b>Shift+click</b> to union the new fill with the current selection, <b>Ctrl+click</b> to change the clicked object's fill and stroke to the current setting."), + N_("<b>Drag</b> to erase."), + N_("Choose a subtool from the toolbar"), +}; + +static int +tools_prefpath2num(char const *id) +{ + int i = 1; + while (tool_names[i]) { + if (strcmp(tool_names[i], id) == 0) + return i; + else i++; + } + g_assert( 0 == TOOLS_INVALID ); + return 0; //nothing found +} + +int +tools_isactive(SPDesktop *dt, unsigned num) +{ + g_assert( num < G_N_ELEMENTS(tool_names) ); + if (SP_IS_EVENT_CONTEXT(dt->event_context)) + return dt->event_context->pref_observer->observed_path == tool_names[num]; + else return FALSE; +} + +int +tools_active(SPDesktop *dt) +{ + return tools_prefpath2num(dt->event_context->pref_observer->observed_path.data()); +} + +void +tools_switch(SPDesktop *dt, int num) +{ + dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, gettext( tool_msg[num] ) ); + if (dt) { + // This event may change the above message + dt->_tool_changed.emit(num); + } + + dt->set_event_context2(tool_names[num]); + /* fixme: This is really ugly hack. We should bind and unbind class methods */ + /* First 4 tools use guides, first is undefined but we don't care */ + dt->activate_guides(num < 5); + INKSCAPE.eventcontext_set(dt->getEventContext()); +} + +void tools_switch_by_item(SPDesktop *dt, SPItem *item, Geom::Point const p) +{ + if (SP_IS_RECT(item)) { + tools_switch(dt, TOOLS_SHAPES_RECT); + } else if (SP_IS_BOX3D(item)) { + tools_switch(dt, TOOLS_SHAPES_3DBOX); + } else if (SP_IS_GENERICELLIPSE(item)) { + tools_switch(dt, TOOLS_SHAPES_ARC); + } else if (SP_IS_STAR(item)) { + tools_switch(dt, TOOLS_SHAPES_STAR); + } else if (SP_IS_SPIRAL(item)) { + tools_switch(dt, TOOLS_SHAPES_SPIRAL); + } else if (SP_IS_PATH(item)) { + if (Inkscape::UI::Tools::cc_item_is_connector(item)) { + tools_switch(dt, TOOLS_CONNECTOR); + } + else { + tools_switch(dt, TOOLS_NODES); + } + } else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { + tools_switch(dt, TOOLS_TEXT); + sp_text_context_place_cursor_at (SP_TEXT_CONTEXT(dt->event_context), SP_OBJECT(item), p); + } else if (SP_IS_OFFSET(item)) { + tools_switch(dt, TOOLS_NODES); + } +} + +/* + 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/tools-switch.h b/src/ui/tools-switch.h new file mode 100644 index 000000000..280837e87 --- /dev/null +++ b/src/ui/tools-switch.h @@ -0,0 +1,64 @@ +/* + * Utility functions for switching tools (= contexts) + * + * Authors: + * bulia byak <bulia@dr.com> + * + * Copyright (C) 2003 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_TOOLS_SWITCH_H +#define SEEN_TOOLS_SWITCH_H + +class SPDesktop; +class SPItem; +namespace Geom { +class Point; +} + + +enum { + TOOLS_INVALID, + TOOLS_SELECT, + TOOLS_NODES, + TOOLS_TWEAK, + TOOLS_SPRAY, + TOOLS_SHAPES_RECT, + TOOLS_SHAPES_3DBOX, + TOOLS_SHAPES_ARC, + TOOLS_SHAPES_STAR, + TOOLS_SHAPES_SPIRAL, + TOOLS_FREEHAND_PENCIL, + TOOLS_FREEHAND_PEN, + TOOLS_CALLIGRAPHIC, + TOOLS_TEXT, + TOOLS_GRADIENT, + TOOLS_MESH, + TOOLS_ZOOM, + TOOLS_MEASURE, + TOOLS_DROPPER, + TOOLS_CONNECTOR, + TOOLS_PAINTBUCKET, + TOOLS_ERASER, + TOOLS_LPETOOL +}; + +int tools_isactive(SPDesktop *dt, unsigned num); +int tools_active(SPDesktop *dt); +void tools_switch(SPDesktop *dt, int num); +void tools_switch_by_item (SPDesktop *dt, SPItem *item, Geom::Point const p); + +#endif // !SEEN_TOOLS_SWITCH_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/tools/arc-tool.cpp b/src/ui/tools/arc-tool.cpp index 9fd68f1b9..4f64ade25 100644 --- a/src/ui/tools/arc-tool.cpp +++ b/src/ui/tools/arc-tool.cpp @@ -40,7 +40,7 @@ #include "desktop-style.h" #include "context-fns.h" #include "verbs.h" -#include "shape-editor.h" +#include "ui/shape-editor.h" #include "ui/tools/tool-base.h" #include "ui/tools/arc-tool.h" @@ -48,7 +48,7 @@ using Inkscape::DocumentUndo; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/box3d-tool.cpp b/src/ui/tools/box3d-tool.cpp index b998267c2..0a20a0842 100644 --- a/src/ui/tools/box3d-tool.cpp +++ b/src/ui/tools/box3d-tool.cpp @@ -47,12 +47,12 @@ #include "box3d-side.h" #include "document-private.h" #include "line-geometry.h" -#include "shape-editor.h" +#include "ui/shape-editor.h" #include "verbs.h" using Inkscape::DocumentUndo; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/calligraphic-tool.cpp b/src/ui/tools/calligraphic-tool.cpp index 64097e834..d297fe5e1 100644 --- a/src/ui/tools/calligraphic-tool.cpp +++ b/src/ui/tools/calligraphic-tool.cpp @@ -82,7 +82,7 @@ using Inkscape::DocumentUndo; #define DYNA_MIN_WIDTH 1.0e-6 -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/connector-tool.cpp b/src/ui/tools/connector-tool.cpp index 19759b733..776bc9c89 100644 --- a/src/ui/tools/connector-tool.cpp +++ b/src/ui/tools/connector-tool.cpp @@ -109,7 +109,7 @@ using Inkscape::DocumentUndo; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/dropper-tool.cpp b/src/ui/tools/dropper-tool.cpp index 99d42a211..6c55f7484 100644 --- a/src/ui/tools/dropper-tool.cpp +++ b/src/ui/tools/dropper-tool.cpp @@ -52,7 +52,7 @@ using Inkscape::DocumentUndo; static GdkCursor *cursor_dropper_fill = NULL; static GdkCursor *cursor_dropper_stroke = NULL; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/eraser-tool.cpp b/src/ui/tools/eraser-tool.cpp index 4106785e7..bf4015b4c 100644 --- a/src/ui/tools/eraser-tool.cpp +++ b/src/ui/tools/eraser-tool.cpp @@ -84,7 +84,7 @@ using Inkscape::DocumentUndo; #define DRAG_DEFAULT 1.0 #define DRAG_MAX 1.0 -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/flood-tool.cpp b/src/ui/tools/flood-tool.cpp index 3fb56b2ad..5745fc9cc 100644 --- a/src/ui/tools/flood-tool.cpp +++ b/src/ui/tools/flood-tool.cpp @@ -50,7 +50,7 @@ #include "preferences.h" #include "rubberband.h" #include "selection.h" -#include "shape-editor.h" +#include "ui/shape-editor.h" #include "sp-defs.h" #include "sp-item.h" #include "splivarot.h" @@ -74,7 +74,7 @@ using Inkscape::Display::ExtractARGB32; using Inkscape::Display::ExtractRGB32; using Inkscape::Display::AssembleARGB32; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/freehand-base.cpp b/src/ui/tools/freehand-base.cpp index 46ab53eef..6434c30d2 100644 --- a/src/ui/tools/freehand-base.cpp +++ b/src/ui/tools/freehand-base.cpp @@ -30,7 +30,7 @@ #include "desktop-handles.h" #include "desktop-style.h" #include "document.h" -#include "draw-anchor.h" +#include "ui/draw-anchor.h" #include "macros.h" #include "message-stack.h" #include "ui/tools/pen-tool.h" diff --git a/src/ui/tools/gradient-tool.cpp b/src/ui/tools/gradient-tool.cpp index 4f9a7b59b..9c853917e 100644 --- a/src/ui/tools/gradient-tool.cpp +++ b/src/ui/tools/gradient-tool.cpp @@ -51,7 +51,7 @@ using Inkscape::DocumentUndo; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/lpe-tool.cpp b/src/ui/tools/lpe-tool.cpp index e9b9421f1..1fd1ebf8c 100644 --- a/src/ui/tools/lpe-tool.cpp +++ b/src/ui/tools/lpe-tool.cpp @@ -28,7 +28,7 @@ #include "desktop.h" #include "message-context.h" #include "preferences.h" -#include "shape-editor.h" +#include "ui/shape-editor.h" #include "selection.h" #include "desktop-handles.h" #include "document.h" @@ -59,7 +59,7 @@ SubtoolEntry lpesubtools[] = { }; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/measure-tool.cpp b/src/ui/tools/measure-tool.cpp index feeb68288..6b5cbeccd 100644 --- a/src/ui/tools/measure-tool.cpp +++ b/src/ui/tools/measure-tool.cpp @@ -49,7 +49,7 @@ using Inkscape::ControlManager; using Inkscape::CTLINE_SECONDARY; using Inkscape::Util::unit_table; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/mesh-tool.cpp b/src/ui/tools/mesh-tool.cpp index 7d6d3bc23..8a1fb7c72 100644 --- a/src/ui/tools/mesh-tool.cpp +++ b/src/ui/tools/mesh-tool.cpp @@ -53,7 +53,7 @@ using Inkscape::DocumentUndo; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/node-tool.cpp b/src/ui/tools/node-tool.cpp index 0b98bacc1..e2bb85d11 100644 --- a/src/ui/tools/node-tool.cpp +++ b/src/ui/tools/node-tool.cpp @@ -23,7 +23,7 @@ #include "live_effects/lpeobject.h" #include "message-context.h" #include "selection.h" -#include "shape-editor.h" // temporary! +#include "ui/shape-editor.h" // temporary! #include "live_effects/effect.h" #include "display/curve.h" #include "sp-clippath.h" @@ -107,7 +107,7 @@ using Inkscape::ControlManager; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/pen-tool.cpp b/src/ui/tools/pen-tool.cpp index 2c5ffc182..92937a135 100644 --- a/src/ui/tools/pen-tool.cpp +++ b/src/ui/tools/pen-tool.cpp @@ -26,7 +26,7 @@ #include "desktop-handles.h" #include "selection.h" #include "selection-chemistry.h" -#include "draw-anchor.h" +#include "ui/draw-anchor.h" #include "message-stack.h" #include "message-context.h" #include "preferences.h" @@ -40,7 +40,7 @@ #include <glibmm/i18n.h> #include "macros.h" #include "context-fns.h" -#include "tools-switch.h" +#include "ui/tools-switch.h" #include "ui/control-manager.h" // we include the necessary files for BSpline & Spiro #include "live_effects/effect.h" @@ -70,7 +70,7 @@ #include "live_effects/lpe-bspline.h" #include <2geom/nearest-point.h> -#include "tool-factory.h" +#include "ui/tool-factory.h" #include "live_effects/effect.h" diff --git a/src/ui/tools/pencil-tool.cpp b/src/ui/tools/pencil-tool.cpp index ca6dffc12..3ea2ae843 100644 --- a/src/ui/tools/pencil-tool.cpp +++ b/src/ui/tools/pencil-tool.cpp @@ -23,7 +23,7 @@ #include "desktop-handles.h" #include "selection.h" #include "selection-chemistry.h" -#include "draw-anchor.h" +#include "ui/draw-anchor.h" #include "message-stack.h" #include "message-context.h" #include "sp-path.h" @@ -43,7 +43,7 @@ #include "display/sp-canvas.h" #include "display/curve.h" #include "livarot/Path.h" -#include "tool-factory.h" +#include "ui/tool-factory.h" #include "ui/tool/event-utils.h" namespace Inkscape { diff --git a/src/ui/tools/rect-tool.cpp b/src/ui/tools/rect-tool.cpp index 819671dd6..de91dcff4 100644 --- a/src/ui/tools/rect-tool.cpp +++ b/src/ui/tools/rect-tool.cpp @@ -40,13 +40,13 @@ #include "xml/node-event-vector.h" #include "preferences.h" #include "context-fns.h" -#include "shape-editor.h" +#include "ui/shape-editor.h" #include "verbs.h" #include "display/sp-canvas-item.h" using Inkscape::DocumentUndo; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/select-tool.cpp b/src/ui/tools/select-tool.cpp index 83bef17c9..394b0b369 100644 --- a/src/ui/tools/select-tool.cpp +++ b/src/ui/tools/select-tool.cpp @@ -41,7 +41,7 @@ #include "desktop-handles.h" #include "sp-root.h" #include "preferences.h" -#include "tools-switch.h" +#include "ui/tools-switch.h" #include "message-stack.h" #include "selection-describer.h" #include "seltrans.h" @@ -49,7 +49,7 @@ #include "display/sp-canvas.h" #include "display/sp-canvas-item.h" #include "display/drawing-item.h" -#include "tool-factory.h" +#include "ui/tool-factory.h" using Inkscape::DocumentUndo; diff --git a/src/ui/tools/spiral-tool.cpp b/src/ui/tools/spiral-tool.cpp index 83712457a..18c3e4e2d 100644 --- a/src/ui/tools/spiral-tool.cpp +++ b/src/ui/tools/spiral-tool.cpp @@ -39,13 +39,13 @@ #include "xml/node-event-vector.h" #include "preferences.h" #include "context-fns.h" -#include "shape-editor.h" +#include "ui/shape-editor.h" #include "verbs.h" #include "display/sp-canvas-item.h" using Inkscape::DocumentUndo; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/spray-tool.cpp b/src/ui/tools/spray-tool.cpp index 29f1b9a73..933da6fb1 100644 --- a/src/ui/tools/spray-tool.cpp +++ b/src/ui/tools/spray-tool.cpp @@ -84,7 +84,7 @@ using namespace std; // Please enable again when working on 1.0 #define ENABLE_SPRAY_MODE_SINGLE_PATH -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/star-tool.cpp b/src/ui/tools/star-tool.cpp index ed28c0a8d..7604ba04e 100644 --- a/src/ui/tools/star-tool.cpp +++ b/src/ui/tools/star-tool.cpp @@ -41,7 +41,7 @@ #include "xml/repr.h" #include "xml/node-event-vector.h" #include "context-fns.h" -#include "shape-editor.h" +#include "ui/shape-editor.h" #include "verbs.h" #include "display/sp-canvas-item.h" @@ -49,7 +49,7 @@ using Inkscape::DocumentUndo; -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/text-tool.cpp b/src/ui/tools/text-tool.cpp index b60a39e5d..a72748733 100644 --- a/src/ui/tools/text-tool.cpp +++ b/src/ui/tools/text-tool.cpp @@ -40,7 +40,7 @@ #include "rubberband.h" #include "selection-chemistry.h" #include "selection.h" -#include "shape-editor.h" +#include "ui/shape-editor.h" #include "sp-flowtext.h" #include "sp-namedview.h" #include "sp-text.h" @@ -52,7 +52,7 @@ #include "xml/node-event-vector.h" #include "xml/repr.h" #include <gtk/gtk.h> -#include "tool-factory.h" +#include "ui/tool-factory.h" using Inkscape::ControlManager; using Inkscape::DocumentUndo; diff --git a/src/ui/tools/tool-base.cpp b/src/ui/tools/tool-base.cpp index c28b86416..37ca5eeea 100644 --- a/src/ui/tools/tool-base.cpp +++ b/src/ui/tools/tool-base.cpp @@ -44,9 +44,9 @@ #include "desktop-style.h" #include "sp-namedview.h" #include "selection.h" -#include "interface.h" +#include "ui/interface.h" #include "macros.h" -#include "tools-switch.h" +#include "ui/tools-switch.h" #include "preferences.h" #include "message-context.h" #include "gradient-drag.h" @@ -55,7 +55,7 @@ #include "selcue.h" #include "ui/tools/lpe-tool.h" #include "ui/tool/control-point.h" -#include "shape-editor.h" +#include "ui/shape-editor.h" #include "sp-guide.h" #include "color.h" #include "knot.h" diff --git a/src/ui/tools/tool-base.h b/src/ui/tools/tool-base.h index b27de9030..7a6ab83e7 100644 --- a/src/ui/tools/tool-base.h +++ b/src/ui/tools/tool-base.h @@ -30,7 +30,6 @@ namespace Glib { class GrDrag; class SPDesktop; class SPItem; -class ShapeEditor; namespace Inkscape { class MessageContext; @@ -42,6 +41,9 @@ namespace Inkscape { namespace Inkscape { namespace UI { + +class ShapeEditor; + namespace Tools { class ToolBase; diff --git a/src/ui/tools/tweak-tool.cpp b/src/ui/tools/tweak-tool.cpp index 75650d3af..340f64a0b 100644 --- a/src/ui/tools/tweak-tool.cpp +++ b/src/ui/tools/tweak-tool.cpp @@ -91,7 +91,7 @@ using Inkscape::DocumentUndo; #define DYNA_MIN_WIDTH 1.0e-6 -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/tools/zoom-tool.cpp b/src/ui/tools/zoom-tool.cpp index 9f99cfe2e..b3fb78c8f 100644 --- a/src/ui/tools/zoom-tool.cpp +++ b/src/ui/tools/zoom-tool.cpp @@ -25,7 +25,7 @@ #include "selection-chemistry.h" #include "ui/tools/zoom-tool.h" -#include "tool-factory.h" +#include "ui/tool-factory.h" namespace Inkscape { namespace UI { diff --git a/src/ui/widget/highlight-picker.cpp b/src/ui/widget/highlight-picker.cpp index 2afdc02a6..bc6bdcf22 100644 --- a/src/ui/widget/highlight-picker.cpp +++ b/src/ui/widget/highlight-picker.cpp @@ -11,14 +11,14 @@ # include "config.h" #endif +#include <gtkmm/icontheme.h> + #if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H #include <glibmm/threads.h> #endif #include "display/cairo-utils.h" -#include <gtkmm/icontheme.h> - #include "highlight-picker.h" #include "widgets/icon.h" #include "widgets/toolbox.h" diff --git a/src/ui/widget/insertordericon.cpp b/src/ui/widget/insertordericon.cpp index 2f06225bc..97aa30aa6 100644 --- a/src/ui/widget/insertordericon.cpp +++ b/src/ui/widget/insertordericon.cpp @@ -7,6 +7,8 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ +#include "ui/widget/insertordericon.h" + #ifdef HAVE_CONFIG_H # include "config.h" #endif @@ -15,8 +17,6 @@ #include <glibmm/threads.h> #endif -#include "ui/widget/insertordericon.h" - #include <gtkmm/icontheme.h> #include "widgets/icon.h" diff --git a/src/ui/widget/insertordericon.h b/src/ui/widget/insertordericon.h index 4b4b51de2..e6c2e1c5b 100644 --- a/src/ui/widget/insertordericon.h +++ b/src/ui/widget/insertordericon.h @@ -13,9 +13,9 @@ #include "config.h" #endif -#include <glibmm/property.h> #include <gtkmm/cellrendererpixbuf.h> #include <gtkmm/widget.h> +#include <glibmm/property.h> namespace Inkscape { namespace UI { diff --git a/src/ui/widget/object-composite-settings.h b/src/ui/widget/object-composite-settings.h index 26d83fdc3..5a723a2fd 100644 --- a/src/ui/widget/object-composite-settings.h +++ b/src/ui/widget/object-composite-settings.h @@ -30,6 +30,7 @@ #include "ui/widget/spinbutton.h" class SPDesktop; +struct InkscapeApplication; namespace Inkscape { diff --git a/src/ui/widget/panel.h b/src/ui/widget/panel.h index b9466e07a..5680cac30 100644 --- a/src/ui/widget/panel.h +++ b/src/ui/widget/panel.h @@ -45,6 +45,8 @@ namespace Gtk { class MenuItem; } +struct InkscapeApplication; + namespace Inkscape { class Selection; diff --git a/src/ui/widget/selected-style.cpp b/src/ui/widget/selected-style.cpp index a0a163286..d3adb6307 100644 --- a/src/ui/widget/selected-style.cpp +++ b/src/ui/widget/selected-style.cpp @@ -180,7 +180,11 @@ SelectedStyle::SelectedStyle(bool /*layout*/) _na[i].show_all(); __na[i] = (_("Nothing selected")); - _none[i].set_markup (C_("Fill and stroke", "<i>None</i>")); + if (i == SS_FILL) { + _none[i].set_markup (C_("Fill", "<i>None</i>")); + } else { + _none[i].set_markup (C_("Stroke", "<i>None</i>")); + } sp_set_font_size_smaller (GTK_WIDGET(_none[i].gobj())); _none[i].show_all(); __none[i] = (i == SS_FILL)? (C_("Fill and stroke", "No fill")) : (C_("Fill and stroke", "No stroke")); diff --git a/src/ui/widget/unit-tracker.cpp b/src/ui/widget/unit-tracker.cpp index 67eb1f48d..c6318db25 100644 --- a/src/ui/widget/unit-tracker.cpp +++ b/src/ui/widget/unit-tracker.cpp @@ -13,7 +13,7 @@ */ #include "unit-tracker.h" -#include "ege-select-one-action.h" +#include "widgets/ege-select-one-action.h" #define COLUMN_STRING 0 |
