diff options
| author | Jabiertxof <jtx@jtx> | 2017-03-16 19:08:44 +0000 |
|---|---|---|
| committer | Jabiertxof <jtx@jtx> | 2017-03-16 19:08:44 +0000 |
| commit | 8330d0ef2b97c73121ead78ea9fbcec6ee01f879 (patch) | |
| tree | 1b1717d1706ee6ebfecc800f2cc80430eb0450e0 /src/ui | |
| parent | update to trunk (diff) | |
| parent | Fix rendering when canvas rotated. General code clean-up and documentation. (diff) | |
| download | inkscape-8330d0ef2b97c73121ead78ea9fbcec6ee01f879.tar.gz inkscape-8330d0ef2b97c73121ead78ea9fbcec6ee01f879.zip | |
Update to trunk
(bzr r13645.1.170)
Diffstat (limited to 'src/ui')
29 files changed, 2362 insertions, 236 deletions
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index f2a256698..259a06013 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -57,6 +57,7 @@ set(ui_SRC dialog/calligraphic-profile-rename.cpp dialog/clonetiler.cpp dialog/color-item.cpp + dialog/cssdialog.cpp dialog/debug.cpp dialog/desktop-tracker.cpp dialog/dialog-manager.cpp @@ -98,7 +99,9 @@ set(ui_SRC dialog/polar-arrange-tab.cpp dialog/print-colors-preview-dialog.cpp dialog/print.cpp + dialog/prototype.cpp dialog/spellcheck.cpp + dialog/styledialog.cpp dialog/svg-fonts-dialog.cpp dialog/swatches.cpp dialog/symbols.cpp @@ -195,6 +198,7 @@ set(ui_SRC dialog/calligraphic-profile-rename.h dialog/clonetiler.h dialog/color-item.h + dialog/cssdialog.h dialog/debug.h dialog/desktop-tracker.h dialog/dialog-manager.h @@ -238,7 +242,9 @@ set(ui_SRC dialog/polar-arrange-tab.h dialog/print-colors-preview-dialog.h dialog/print.h + dialog/prototype.h dialog/spellcheck.h + dialog/styledialog.h dialog/svg-fonts-dialog.h dialog/swatches.h dialog/symbols.h diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp index c1e824c1e..734640584 100644 --- a/src/ui/clipboard.cpp +++ b/src/ui/clipboard.cpp @@ -76,6 +76,7 @@ #include "sp-namedview.h" #include "persp3d.h" #include "object-set.h" +#include "extension/find_extension_by_mime.h" /// Made up mimetype to represent Gdk::Pixbuf clipboard contents. #define CLIPBOARD_GDK_PIXBUF_TARGET "image/x-gdk-pixbuf" @@ -143,6 +144,11 @@ private: Inkscape::XML::Node *_clipnode; ///< The node that holds extra information Inkscape::XML::Document *_doc; ///< Reference to the clipboard's Inkscape::XML::Document std::set<SPItem*> cloned_elements; + std::vector<SPCSSAttr*> te_selected_style; + std::vector<unsigned> te_selected_style_positions; + int nr_blocks = 0; + unsigned copied_style_length = 0; + // we need a way to copy plain text AND remember its style; // the standard _clipnode is only available in an SVG tree, hence this special storage @@ -241,6 +247,9 @@ void ClipboardManagerImpl::copy(ObjectSet *set) sp_repr_css_attr_unref(_text_style); _text_style = NULL; } + te_selected_style.clear(); + te_selected_style_positions.clear(); + te_selected_style = Inkscape::UI::Tools::sp_text_get_selected_style(desktop->event_context, &copied_style_length, &nr_blocks, &te_selected_style_positions); _text_style = Inkscape::UI::Tools::sp_text_get_style_at_cursor(desktop->event_context); return; } @@ -945,15 +954,7 @@ bool ClipboardManagerImpl::_pasteImage(SPDocument *doc) return false; } - // TODO unify with interface.cpp's sp_ui_drag_data_received() - // AARGH stupid - 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(), "image/png" ) != 0) { - ++i; - } - Inkscape::Extension::Extension *png = *i; + Inkscape::Extension::Extension *png = Inkscape::Extension::find_by_mime("image/png"); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring attr_saved = prefs->getString("/dialogs/import/link"); bool ask_saved = prefs->getBool("/dialogs/import/ask"); @@ -983,7 +984,42 @@ bool ClipboardManagerImpl::_pasteText(SPDesktop *desktop) // if the text editing tool is active, paste the text into the active text object if (tools_isactive(desktop, TOOLS_TEXT)) { - return Inkscape::UI::Tools::sp_text_paste_inline(desktop->event_context); + if(Inkscape::UI::Tools::sp_text_paste_inline(desktop->event_context) == false) + return false; + //apply the saved style to pasted text + Glib::RefPtr<Gtk::Clipboard> refClipboard = Gtk::Clipboard::get(); + Glib::ustring const clip_text = refClipboard->wait_for_text(); + Glib::ustring text(clip_text); + if(text.length() == copied_style_length) + { + Inkscape::UI::Tools::TextTool *tc = SP_TEXT_CONTEXT(desktop->event_context); + Inkscape::Text::Layout const *layout = te_get_layout(tc->text); + Inkscape::Text::Layout::iterator it_next; + Inkscape::Text::Layout::iterator it = tc->text_sel_end; + + SPCSSAttr *css = take_style_from_item(tc->text); + for (int i = 0; i < nr_blocks; ++i) + { + gchar const *w = sp_repr_css_property(css, "font-size", "40px"); + if (w) + sp_repr_css_set_property(te_selected_style[i], "font-size", w); + } + + for (int i = 0; i < text.length(); ++i) + it.prevCharacter(); + it_next = layout->charIndexToIterator(layout->iteratorToCharIndex(it)); + + for (int i = 0; i < nr_blocks; ++i) + { + for (int j = te_selected_style_positions[i]; j < te_selected_style_positions[i+1]; ++j) + it_next.nextCharacter(); + sp_te_apply_style(tc->text, it, it_next, te_selected_style[i]); + te_update_layout_now_recursive(tc->text); + for (int j = te_selected_style_positions[i]; j < te_selected_style_positions[i+1]; ++j) + it.nextCharacter(); + } + } + return true; } // try to parse the text as a color and, if successful, apply it as the current style diff --git a/src/ui/dialog/aboutbox.cpp b/src/ui/dialog/aboutbox.cpp index 8f0545e96..805bfb562 100644 --- a/src/ui/dialog/aboutbox.cpp +++ b/src/ui/dialog/aboutbox.cpp @@ -115,7 +115,7 @@ AboutBox::AboutBox() : Gtk::Dialog(_("About Inkscape")) { Gtk::Label *link = new Gtk::Label(); const gchar *website_link = - "<a href=\"https://www.inkscape.org\"> https://www.inkscape.org</a>"; + "<a href=\"https://www.inkscape.org\">https://www.inkscape.org</a>"; link->set_markup(website_link); link->set_alignment(Gtk::ALIGN_END); @@ -152,16 +152,16 @@ Gtk::Widget *build_splash_widget() { the `screens' directory. Thus the translation of "about.svg" should be the filename of its translated version, e.g. about.zh.svg for Chinese. - N.B. about.svg changes once per release. (We should probably rename - the original to about-0.40.svg etc. as soon as we have a translation. - If we do so, then add an item to release-checklist saying that the - string here should be changed.) */ + Please don't translate the filename unless the translated picture exists. */ // FIXME? INKSCAPE_SCREENSDIR and "about.svg" are in UTF-8, not the // native filename encoding... and the filename passed to sp_document_new // should be in UTF-*8.. char *about=g_build_filename(INKSCAPE_SCREENSDIR, _("about.svg"), NULL); + if (!g_file_test (about, G_FILE_TEST_EXISTS)) { + about=g_build_filename(INKSCAPE_SCREENSDIR, "about.svg", NULL); + } SPDocument *doc=SPDocument::createNewDoc (about, TRUE); g_free(about); g_return_val_if_fail(doc != NULL, NULL); diff --git a/src/ui/dialog/cssdialog.cpp b/src/ui/dialog/cssdialog.cpp new file mode 100644 index 000000000..85c804b75 --- /dev/null +++ b/src/ui/dialog/cssdialog.cpp @@ -0,0 +1,146 @@ +/** @file + * @brief A dialog for CSS selectors + */ +/* Authors: + * Kamalpreet Kaur Grewal + * Tavmjong Bah + * + * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com> + * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "cssdialog.h" +#include "ui/widget/addtoicon.h" +#include "widgets/icon.h" +#include "verbs.h" +#include "sp-object.h" +#include "selection.h" +#include "xml/attribute-record.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + +/** + * Constructor + * A treeview whose each row corresponds to a CSS property of selector selected. + * New CSS property can be added by clicking '+' at bottom of the CSS pane. '-' + * in front of the CSS property row can be clicked to delete the CSS property. + * Besides clicking on an already selected property row makes the property editable + * and clicking 'Enter' updates the property with changes reflected in the + * drawing. + */ +CssDialog::CssDialog(): + UI::Widget::Panel("", "/dialogs/css", SP_VERB_DIALOG_CSS), + _desktop(0) +{ + set_size_request(20, 15); + _mainBox.pack_start(_scrolledWindow, Gtk::PACK_EXPAND_WIDGET); + _treeView.set_headers_visible(true); + _scrolledWindow.add(_treeView); + _scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + + _store = Gtk::ListStore::create(_cssColumns); + _treeView.set_model(_store); + + Inkscape::UI::Widget::AddToIcon * addRenderer = manage(new Inkscape::UI::Widget::AddToIcon()); + addRenderer->property_active() = false; + + int addCol = _treeView.append_column("", *addRenderer) - 1; + Gtk::TreeViewColumn *col = _treeView.get_column(addCol); + if (col) { + col->add_attribute(addRenderer->property_active(), _cssColumns._colUnsetProp); + } + + _propRenderer = Gtk::manage(new Gtk::CellRendererText()); + _propRenderer->property_editable() = true; + int nameColNum = _treeView.append_column("CSS Property", *_propRenderer) - 1; + _propCol = _treeView.get_column(nameColNum); + if (_propCol) { + _propCol->add_attribute(_propRenderer->property_text(), _cssColumns._propertyLabel); + } + + _sheetRenderer = Gtk::manage(new Gtk::CellRendererText()); + _sheetRenderer->property_editable() = true; + int sheetColNum = _treeView.append_column("Style Sheet", *_sheetRenderer) - 1; + _sheetCol = _treeView.get_column(sheetColNum); + if (_sheetCol) { + _sheetCol->add_attribute(_sheetRenderer->property_text(), _cssColumns._styleSheetVal); + } + + _attrRenderer = Gtk::manage(new Gtk::CellRendererText()); + _attrRenderer->property_editable() = false; + int attrColNum = _treeView.append_column("Style Attribute", *_attrRenderer) - 1; + _attrCol = _treeView.get_column(attrColNum); + if (_attrCol) { + _attrCol->add_attribute(_attrRenderer->property_text(), _cssColumns._styleAttrVal); + } + + _styleButton(_buttonAddProperty, "list-add", "Add a new property"); + + _mainBox.pack_end(_buttonBox, Gtk::PACK_SHRINK); + _buttonBox.pack_start(_buttonAddProperty, Gtk::PACK_SHRINK); + + _getContents()->pack_start(_mainBox, Gtk::PACK_EXPAND_WIDGET); + + setDesktop(getDesktop()); + + _buttonAddProperty.signal_clicked().connect(sigc::mem_fun(*this, &CssDialog::_addProperty)); +} + + +/** + * @brief CssDialog::~CssDialog + * Class destructor + */ +CssDialog::~CssDialog() +{ + setDesktop(NULL); +} + + +/** + * @brief CssDialog::setDesktop + * @param desktop + * This function sets the 'desktop' for the CSS pane. + */ +void CssDialog::setDesktop(SPDesktop* desktop) +{ + _desktop = desktop; +} + + +/** + * @brief CssDialog::_styleButton + * @param btn + * @param iconName + * @param tooltip + * This function sets the style of '+'button at the bottom of dialog. + */ +void CssDialog::_styleButton(Gtk::Button& btn, char const* iconName, + char const* tooltip) +{ + GtkWidget *child = sp_icon_new(Inkscape::ICON_SIZE_SMALL_TOOLBAR, iconName); + gtk_widget_show(child); + btn.add(*manage(Glib::wrap(child))); + btn.set_relief(Gtk::RELIEF_NONE); + btn.set_tooltip_text(tooltip); +} + + +/** + * @brief CssDialog::_addProperty + * This function is a slot to signal_clicked for '+' button at the bottom of CSS + * panel. A new row is added, double clicking which text for new property can be + * added. + */ +void CssDialog::_addProperty() +{ + _propRow = *(_store->append()); +} + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape diff --git a/src/ui/dialog/cssdialog.h b/src/ui/dialog/cssdialog.h new file mode 100644 index 000000000..31eb67e3f --- /dev/null +++ b/src/ui/dialog/cssdialog.h @@ -0,0 +1,93 @@ +/** @file + * @brief A dialog for CSS selectors + */ +/* Authors: + * Kamalpreet Kaur Grewal + * Tavmjong Bah + * + * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com> + * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef CSSDIALOG_H +#define CSSDIALOG_H + +#include <gtkmm/treeview.h> +#include <gtkmm/liststore.h> +#include <gtkmm/scrolledwindow.h> +#include <gtkmm/dialog.h> +#include <ui/widget/panel.h> + +#include "desktop.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + +/** + * @brief The CssDialog class + * This dialog allows to add, delete and modify CSS properties for selectors + * created in Style Dialog. Double clicking any selector in Style dialog, a list + * of CSS properties will show up in this dialog (if any exist), else new properties + * can be added and each new property forms a new row in this pane. + */ +class CssDialog : public UI::Widget::Panel +{ +public: + CssDialog(); + ~CssDialog(); + + static CssDialog &getInstance() { return *new CssDialog(); } + + // Data structure + class CssColumns : public Gtk::TreeModel::ColumnRecord { + public: + CssColumns() { + add(_colUnsetProp); + add(_propertyLabel); + add(_styleSheetVal); + add(_styleAttrVal); + } + Gtk::TreeModelColumn<bool> _colUnsetProp; + Gtk::TreeModelColumn<Glib::ustring> _propertyLabel; + Gtk::TreeModelColumn<Glib::ustring> _styleSheetVal; + Gtk::TreeModelColumn<Glib::ustring> _styleAttrVal; + }; + CssColumns _cssColumns; + + // TreeView + Gtk::TreeView _treeView; + Glib::RefPtr<Gtk::ListStore> _store; + Gtk::TreeModel::Row _propRow; + Gtk::CellRendererText *_propRenderer; + Gtk::CellRendererText *_sheetRenderer; + Gtk::CellRendererText *_attrRenderer; + Gtk::TreeViewColumn *_propCol; + Gtk::TreeViewColumn *_sheetCol; + Gtk::TreeViewColumn *_attrCol; + + // Widgets + Gtk::VBox _mainBox; + Gtk::ScrolledWindow _scrolledWindow; + Gtk::HBox _buttonBox; + Gtk::Button _buttonAddProperty; + + // Variables - Inkscape + SPDesktop* _desktop; + + // Helper functions + void setDesktop(SPDesktop* desktop); + void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip); + + // Signal handlers + void _addProperty(); +}; + + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + +#endif // CSSDIALOG_H diff --git a/src/ui/dialog/dialog-manager.cpp b/src/ui/dialog/dialog-manager.cpp index c53112656..01cd9dd0f 100644 --- a/src/ui/dialog/dialog-manager.cpp +++ b/src/ui/dialog/dialog-manager.cpp @@ -19,6 +19,7 @@ #include "ui/dialog/dialog-manager.h" +#include "ui/dialog/prototype.h" #include "ui/dialog/align-and-distribute.h" #include "ui/dialog/document-metadata.h" #include "ui/dialog/document-properties.h" @@ -57,6 +58,8 @@ #include "ui/dialog/svg-fonts-dialog.h" #include "ui/dialog/objects.h" #include "ui/dialog/tags.h" +#include "ui/dialog/styledialog.h" +#include "ui/dialog/cssdialog.h" namespace Inkscape { namespace UI { @@ -103,6 +106,7 @@ DialogManager::DialogManager() { registerFactory("InkscapePreferences", &create<InkscapePreferences, FloatingBehavior>); if (dialogs_type == FLOATING) { + registerFactory("Prototype", &create<Prototype, FloatingBehavior>); registerFactory("AlignAndDistribute", &create<AlignAndDistribute, FloatingBehavior>); registerFactory("DocumentMetadata", &create<DocumentMetadata, FloatingBehavior>); registerFactory("DocumentProperties", &create<DocumentProperties, FloatingBehavior>); @@ -125,6 +129,8 @@ DialogManager::DialogManager() { registerFactory("Swatches", &create<SwatchesPanel, FloatingBehavior>); registerFactory("TileDialog", &create<ArrangeDialog, FloatingBehavior>); registerFactory("Symbols", &create<SymbolsDialog, FloatingBehavior>); + registerFactory("StyleDialog", &create<StyleDialog, FloatingBehavior>); + registerFactory("CssDialog", &create<CssDialog, FloatingBehavior>); #if HAVE_POTRACE registerFactory("Trace", &create<TraceDialog, FloatingBehavior>); @@ -142,6 +148,7 @@ DialogManager::DialogManager() { } else { + registerFactory("Prototype", &create<Prototype, DockBehavior>); registerFactory("AlignAndDistribute", &create<AlignAndDistribute, DockBehavior>); registerFactory("DocumentMetadata", &create<DocumentMetadata, DockBehavior>); registerFactory("DocumentProperties", &create<DocumentProperties, DockBehavior>); @@ -164,6 +171,8 @@ DialogManager::DialogManager() { registerFactory("Swatches", &create<SwatchesPanel, DockBehavior>); registerFactory("TileDialog", &create<ArrangeDialog, DockBehavior>); registerFactory("Symbols", &create<SymbolsDialog, DockBehavior>); + registerFactory("StyleDialog", &create<StyleDialog, DockBehavior>); + registerFactory("CssDialog", &create<CssDialog, DockBehavior>); #if HAVE_POTRACE registerFactory("Trace", &create<TraceDialog, DockBehavior>); diff --git a/src/ui/dialog/dialog-manager.h b/src/ui/dialog/dialog-manager.h index 15573f760..36e9a12be 100644 --- a/src/ui/dialog/dialog-manager.h +++ b/src/ui/dialog/dialog-manager.h @@ -30,11 +30,11 @@ public: static DialogManager &getInstance(); - sigc::signal<void> show_dialogs; - sigc::signal<void> show_f12; - sigc::signal<void> hide_dialogs; - sigc::signal<void> hide_f12; - sigc::signal<void> transientize; + // sigc::signal<void> show_dialogs; + // sigc::signal<void> show_f12; + // sigc::signal<void> hide_dialogs; + // sigc::signal<void> hide_f12; + // sigc::signal<void> transientize; /* generic dialog management start */ typedef std::map<GQuark, DialogFactory> FactoryMap; diff --git a/src/ui/dialog/filedialogimpl-win32.cpp b/src/ui/dialog/filedialogimpl-win32.cpp index 02d77cba1..1efec7d52 100644 --- a/src/ui/dialog/filedialogimpl-win32.cpp +++ b/src/ui/dialog/filedialogimpl-win32.cpp @@ -18,38 +18,23 @@ #endif #include "filedialogimpl-win32.h" //General includes -#include <list> -#include <unistd.h> -#include <sys/stat.h> -#include <errno.h> -#include <set> +#include <cairomm/win32_surface.h> #include <gdk/gdkwin32.h> -#include <glib/gstdio.h> -#include <glibmm/i18n.h> +#include <gdkmm/general.h> #include <glibmm/fileutils.h> -#include <gtkmm/window.h> +#include <glibmm/i18n.h> //Inkscape includes -#include "inkscape.h" -#include "ui/dialog-events.h" +#include "display/cairo-utils.h" +#include "document.h" +#include "extension/db.h" #include "extension/input.h" #include "extension/output.h" -#include "extension/db.h" - -//#include "display/drawing-item.h" -//#include "display/drawing.h" -#include "sp-item.h" -#include "display/canvas-arena.h" - #include "filedialog.h" - -#include "sp-root.h" +#include "helper/pixbuf-ops.h" #include "preferences.h" +#include "util/units.h" -#include <zlib.h> -#include <cairomm/win32_surface.h> -#include <cairomm/context.h> -#include <gdkmm/general.h> using namespace std; using namespace Glib; @@ -807,7 +792,7 @@ LRESULT CALLBACK FileOpenDialogImplWin32::preview_wnd_proc(HWND hwnd, UINT uMsg, _wsplitpath(pImpl->_path_string, NULL, NULL, szFileName, NULL); const int iLength = snwprintf(szCaption, - sizeof(szCaption), L"%s\n%d kB", + sizeof(szCaption), L"%ls\n%d kB", szFileName, pImpl->_preview_file_size); DrawTextW(dc, szCaption, iLength, &rcCaptionRect, @@ -1020,15 +1005,11 @@ void FileOpenDialogImplWin32::free_preview() bool FileOpenDialogImplWin32::set_svg_preview() { - return false; - // NOTE: it's not worth the effort to fix this to use Cairo. - // Native file dialogs are unmaintainable and should be removed anyway. - #if 0 const int PreviewSize = 512; gchar *utf8string = g_utf16_to_utf8((const gunichar2*)_path_string, _MAX_PATH, NULL, NULL, NULL); - SPDocument *svgDoc = SPDocument::createNewDoc (utf8string, true); + SPDocument *svgDoc = SPDocument::createNewDoc (utf8string, 0); g_free(utf8string); // Check the document loaded properly @@ -1042,87 +1023,39 @@ bool FileOpenDialogImplWin32::set_svg_preview() } // Get the size of the document - const double svgWidth = svgDoc->getWidth(); - const double svgHeight = svgDoc->getHeight(); + Inkscape::Util::Quantity svgWidth = svgDoc->getWidth(); + Inkscape::Util::Quantity svgHeight = svgDoc->getHeight(); + const double svgWidth_px = svgWidth.value("px"); + const double svgHeight_px = svgHeight.value("px"); // Find the minimum scale to fit the image inside the preview area - const double scaleFactorX = PreviewSize / svgWidth; - const double scaleFactorY = PreviewSize / svgHeight; + const double scaleFactorX = PreviewSize / svgWidth_px; + const double scaleFactorY = PreviewSize / svgHeight_px; const double scaleFactor = (scaleFactorX > scaleFactorY) ? scaleFactorY : scaleFactorX; // Now get the resized values - const double scaledSvgWidth = scaleFactor * svgWidth; - const double scaledSvgHeight = scaleFactor * svgHeight; - - Geom::Rect area(Geom::Point(0, 0), Geom::Point(scaledSvgWidth, scaledSvgHeight)); - NRRectL areaL = {0, 0, scaledSvgWidth, scaledSvgHeight}; - NRRectL bbox = {0, 0, scaledSvgWidth, scaledSvgHeight}; - - // write object bbox to area - svgDoc->ensureUpToDate(); - Geom::OptRect maybeArea = area | svgDoc->getRoot()->desktopVisualBounds(); - - NRArena *const arena = NRArena::create(); - - unsigned const key = SPItem::display_key_new(1); - - NRArenaItem *root = svgDoc->getRoot()->invoke_show( - arena, key, SP_ITEM_SHOW_DISPLAY); - - NRGC gc(NULL); - gc.transform = Geom::Affine(Geom::Scale(scaleFactor, scaleFactor)); + const int scaledSvgWidth = round(scaleFactor * svgWidth_px); + const int scaledSvgHeight = round(scaleFactor * svgHeight_px); - nr_arena_item_invoke_update (root, NULL, &gc, - NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE); - - // Prepare a GDI compatible NRPixBlock - NRPixBlock pixBlock; - pixBlock.size = NR_PIXBLOCK_SIZE_BIG; - pixBlock.mode = NR_PIXBLOCK_MODE_R8G8B8; - pixBlock.empty = 1; - pixBlock.visible_area.x0 = pixBlock.area.x0 = 0; - pixBlock.visible_area.y0 = pixBlock.area.y0 = 0; - pixBlock.visible_area.x1 = pixBlock.area.x1 = scaledSvgWidth; - pixBlock.visible_area.y1 = pixBlock.area.y1 = scaledSvgHeight; - pixBlock.rs = 4 * ((3 * (int)scaledSvgWidth + 3) / 4); - pixBlock.data.px = g_try_new (unsigned char, pixBlock.rs * scaledSvgHeight); - - // Fail if the pixblock failed to allocate - if(pixBlock.data.px == NULL) - { - svgDoc->doUnref(); - return false; - } - - memset(pixBlock.data.px, 0xFF, pixBlock.rs * scaledSvgHeight); - - memcpy(&root->bbox, &areaL, sizeof(areaL)); - - // Render the image - nr_arena_item_invoke_render(NULL, root, &bbox, &pixBlock, /*0*/NR_ARENA_ITEM_RENDER_NO_CACHE); + const double dpi = 96*scaleFactor; + Inkscape::Pixbuf * pixbuf = sp_generate_internal_bitmap(svgDoc, NULL, 0, 0, svgWidth_px, svgHeight_px, scaledSvgWidth, scaledSvgHeight, dpi, dpi, (guint32) 0xffffff00, NULL); // Tidy up svgDoc->doUnref(); - svgDoc->getRoot()->invoke_hide(key); - nr_object_unref((NRObject *) arena); + if (pixbuf == NULL) { + return false; + } // Create the GDK pixbuf _mutex->lock(); - - _preview_bitmap_image = Gdk::Pixbuf::create_from_data( - pixBlock.data.px, Gdk::COLORSPACE_RGB, false, 8, - (int)scaledSvgWidth, (int)scaledSvgHeight, pixBlock.rs, - sigc::ptr_fun(destroy_svg_rendering)); - - _preview_document_width = scaledSvgWidth; - _preview_document_height = scaledSvgHeight; - _preview_image_width = svgWidth; - _preview_image_height = svgHeight; - + _preview_bitmap_image = Glib::wrap(pixbuf->getPixbufRaw()); + _preview_document_width = svgWidth_px; + _preview_document_height = svgHeight_px; + _preview_image_width = scaledSvgWidth; + _preview_image_height = scaledSvgHeight; _mutex->unlock(); return true; - #endif } void FileOpenDialogImplWin32::destroy_svg_rendering(const guint8 *buffer) @@ -1301,17 +1234,8 @@ bool FileOpenDialogImplWin32::set_emf_preview() const double emfWidth = w; const double emfHeight = h; - // Find the minimum scale to fit the image inside the preview area - const double scaleFactorX = PreviewSize / emfWidth; - const double scaleFactorY = PreviewSize / emfHeight; - const double scaleFactor = (scaleFactorX > scaleFactorY) ? scaleFactorY : scaleFactorX; - - // Now get the resized values - const double scaledEmfWidth = scaleFactor * emfWidth; - const double scaledEmfHeight = scaleFactor * emfHeight; - - _preview_document_width = scaledEmfWidth; - _preview_document_height = scaledEmfHeight; + _preview_document_width = emfWidth / 2540 * 96; // width is in units of 0.01 mm + _preview_document_height = emfHeight / 2540 * 96; // height is in units of 0.01 mm _preview_image_width = emfWidth; _preview_image_height = emfHeight; @@ -1370,17 +1294,13 @@ void FileOpenDialogImplWin32::render_preview() } // Find the minimum scale to fit the image inside the preview area - const double scaleFactorX = - ((double)_preview_width - pagePadding * 2 - blurRadius) / _preview_document_width; - const double scaleFactorY = - ((double)_preview_height - pagePadding * 2 - - shaddowOffsetY - halfBlurRadius - captionHeight) / _preview_document_height; - double scaleFactor = (scaleFactorX > scaleFactorY) ? scaleFactorY : scaleFactorX; - scaleFactor = (scaleFactor > 1.0) ? 1.0 : scaleFactor; + const double scaleFactorX = ((double)_preview_width - pagePadding * 2 - blurRadius) / _preview_image_width; + const double scaleFactorY = ((double)_preview_height - pagePadding * 2 - shaddowOffsetY - halfBlurRadius - captionHeight) / _preview_image_height; + const double scaleFactor = (scaleFactorX > scaleFactorY) ? scaleFactorY : scaleFactorX; // Now get the resized values - const double scaledSvgWidth = scaleFactor * _preview_document_width; - const double scaledSvgHeight = scaleFactor * _preview_document_height; + const double scaledSvgWidth = scaleFactor * _preview_image_width; + const double scaledSvgHeight = scaleFactor * _preview_image_height; const int svgX = pagePadding + halfBlurRadius; const int svgY = pagePadding; @@ -1565,7 +1485,7 @@ int FileOpenDialogImplWin32::format_caption(wchar_t *caption, int caption_size) _wsplitpath(_path_string, NULL, NULL, szFileName, NULL); return snwprintf(caption, caption_size, - L"%s\n%d kB\n%d \xD7 %d", szFileName, _preview_file_size, + L"%ls\n%d\u2009kB\n%d\u2009px \xD7 %d\u2009px", szFileName, _preview_file_size, (int)_preview_document_width, (int)_preview_document_height); } @@ -1971,18 +1891,18 @@ UINT_PTR CALLBACK FileSaveDialogImplWin32::GetSaveFileName_hookproc( if(dlgFont) SendMessage(pImpl->_title_edit, WM_SETFONT, (WPARAM)dlgFont, MAKELPARAM(FALSE, 0)); SetWindowPos(pImpl->_title_edit, NULL, rCB1.left-rROOT.left, rCB1.top+ydelta-rROOT.top, rCB1.right-rCB1.left, rCB1.bottom-rCB1.top, SWP_SHOWWINDOW|SWP_NOZORDER); - // TODO: make sure this works for Unicode - SetWindowText(pImpl->_title_edit, pImpl->myDocTitle.c_str()); + SetWindowTextW(pImpl->_title_edit, + (const wchar_t*)g_utf8_to_utf16(pImpl->myDocTitle.c_str(), -1, NULL, NULL, NULL)); } } break; case WM_DESTROY: { if(pImpl->_title_edit) { - int length = GetWindowTextLength(pImpl->_title_edit)+1; - char* temp_title = new char[length]; - GetWindowText(pImpl->_title_edit, temp_title, length); - pImpl->myDocTitle = temp_title; + int length = GetWindowTextLengthW(pImpl->_title_edit)+1; + wchar_t* temp_title = new wchar_t[length]; + GetWindowTextW(pImpl->_title_edit, temp_title, length); + pImpl->myDocTitle = g_utf16_to_utf8((gunichar2*)temp_title, -1, NULL, NULL, NULL); delete[] temp_title; DestroyWindow(pImpl->_title_label); pImpl->_title_label = NULL; diff --git a/src/ui/dialog/lpe-fillet-chamfer-properties.cpp b/src/ui/dialog/lpe-fillet-chamfer-properties.cpp index c349ffd78..82c6035d4 100644 --- a/src/ui/dialog/lpe-fillet-chamfer-properties.cpp +++ b/src/ui/dialog/lpe-fillet-chamfer-properties.cpp @@ -34,32 +34,30 @@ FilletChamferPropertiesDialog::FilletChamferPropertiesDialog() { Gtk::Box *mainVBox = get_vbox(); mainVBox->set_homogeneous(false); - _layout_table.set_spacings(4); - _layout_table.resize(3, 3); + _layout_table.set_row_spacing(4); + _layout_table.set_column_spacing(4); // Layer name widgets _fillet_chamfer_position_numeric.set_digits(4); _fillet_chamfer_position_numeric.set_increments(1,1); //todo: get tha max aloable infinity freeze the widget _fillet_chamfer_position_numeric.set_range(0., SCALARPARAM_G_MAXDOUBLE); + _fillet_chamfer_position_numeric.set_hexpand(); _fillet_chamfer_position_label.set_label(_("Radius (pixels):")); _fillet_chamfer_position_label.set_alignment(1.0, 0.5); - _layout_table.attach(_fillet_chamfer_position_label, 0, 1, 0, 1, Gtk::FILL, - Gtk::FILL); - _layout_table.attach(_fillet_chamfer_position_numeric, 1, 2, 0, 1, - Gtk::FILL | Gtk::EXPAND, Gtk::FILL); + _layout_table.attach(_fillet_chamfer_position_label, 0, 0, 1, 1); + _layout_table.attach(_fillet_chamfer_position_numeric, 1, 0, 1, 1); _fillet_chamfer_chamfer_subdivisions.set_digits(0); _fillet_chamfer_chamfer_subdivisions.set_increments(1,1); //todo: get tha max aloable infinity freeze the widget _fillet_chamfer_chamfer_subdivisions.set_range(0, SCALARPARAM_G_MAXDOUBLE); + _fillet_chamfer_chamfer_subdivisions.set_hexpand(); _fillet_chamfer_chamfer_subdivisions_label.set_label(_("Chamfer subdivisions:")); _fillet_chamfer_chamfer_subdivisions_label.set_alignment(1.0, 0.5); - _layout_table.attach(_fillet_chamfer_chamfer_subdivisions_label, 0, 1, 1, 2, Gtk::FILL, - Gtk::FILL); - _layout_table.attach(_fillet_chamfer_chamfer_subdivisions, 1, 2, 1, 2, - Gtk::FILL | Gtk::EXPAND, Gtk::FILL); + _layout_table.attach(_fillet_chamfer_chamfer_subdivisions_label, 0, 1, 1, 1); + _layout_table.attach(_fillet_chamfer_chamfer_subdivisions, 1, 1, 1, 1); _fillet_chamfer_type_fillet.set_label(_("Fillet")); _fillet_chamfer_type_fillet.set_group(_fillet_chamfer_type_group); _fillet_chamfer_type_inverse_fillet.set_label(_("Inverse fillet")); diff --git a/src/ui/dialog/lpe-fillet-chamfer-properties.h b/src/ui/dialog/lpe-fillet-chamfer-properties.h index 105cafe68..4021d6152 100644 --- a/src/ui/dialog/lpe-fillet-chamfer-properties.h +++ b/src/ui/dialog/lpe-fillet-chamfer-properties.h @@ -51,7 +51,7 @@ protected: Gtk::Label _fillet_chamfer_chamfer_subdivisions_label; Gtk::SpinButton _fillet_chamfer_chamfer_subdivisions; - Gtk::Table _layout_table; + Gtk::Grid _layout_table; bool _position_visible; Gtk::Button _close_button; diff --git a/src/ui/dialog/lpe-powerstroke-properties.cpp b/src/ui/dialog/lpe-powerstroke-properties.cpp index ca10721db..0757aa1c3 100644 --- a/src/ui/dialog/lpe-powerstroke-properties.cpp +++ b/src/ui/dialog/lpe-powerstroke-properties.cpp @@ -40,14 +40,15 @@ PowerstrokePropertiesDialog::PowerstrokePropertiesDialog() { Gtk::Box *mainVBox = get_vbox(); - _layout_table.set_spacings(4); - _layout_table.resize (2, 2); + _layout_table.set_row_spacing(4); + _layout_table.set_column_spacing(4); // Layer name widgets _powerstroke_position_entry.set_activates_default(true); _powerstroke_position_entry.set_digits(4); _powerstroke_position_entry.set_increments(1,1); _powerstroke_position_entry.set_range(-SCALARPARAM_G_MAXDOUBLE, SCALARPARAM_G_MAXDOUBLE); + _powerstroke_position_entry.set_hexpand(); _powerstroke_position_label.set_label(_("Position:")); _powerstroke_position_label.set_alignment(1.0, 0.5); @@ -55,16 +56,14 @@ PowerstrokePropertiesDialog::PowerstrokePropertiesDialog() _powerstroke_width_entry.set_digits(4); _powerstroke_width_entry.set_increments(1,1); _powerstroke_width_entry.set_range(-SCALARPARAM_G_MAXDOUBLE, SCALARPARAM_G_MAXDOUBLE); + _powerstroke_width_entry.set_hexpand(); _powerstroke_width_label.set_label(_("Width:")); _powerstroke_width_label.set_alignment(1.0, 0.5); - _layout_table.attach(_powerstroke_position_label, - 0, 1, 0, 1, Gtk::FILL, Gtk::FILL); - _layout_table.attach(_powerstroke_position_entry, - 1, 2, 0, 1, Gtk::FILL | Gtk::EXPAND, Gtk::FILL); - - _layout_table.attach(_powerstroke_width_label, 0, 1, 1, 2, Gtk::FILL, Gtk::FILL); - _layout_table.attach(_powerstroke_width_entry, 1, 2, 1, 2, Gtk::FILL | Gtk::EXPAND, Gtk::FILL); + _layout_table.attach(_powerstroke_position_label,0,0,1,1); + _layout_table.attach(_powerstroke_position_entry,1,0,1,1); + _layout_table.attach(_powerstroke_width_label, 0,1,1,1); + _layout_table.attach(_powerstroke_width_entry, 1,1,1,1); mainVBox->pack_start(_layout_table, true, true, 4); diff --git a/src/ui/dialog/lpe-powerstroke-properties.h b/src/ui/dialog/lpe-powerstroke-properties.h index 1e4c1df5b..56b6e499d 100644 --- a/src/ui/dialog/lpe-powerstroke-properties.h +++ b/src/ui/dialog/lpe-powerstroke-properties.h @@ -40,7 +40,7 @@ protected: Gtk::SpinButton _powerstroke_position_entry; Gtk::Label _powerstroke_width_label; Gtk::SpinButton _powerstroke_width_entry; - Gtk::Table _layout_table; + Gtk::Grid _layout_table; bool _position_visible; Gtk::Button _close_button; diff --git a/src/ui/dialog/prototype.cpp b/src/ui/dialog/prototype.cpp new file mode 100644 index 000000000..b3bf60aab --- /dev/null +++ b/src/ui/dialog/prototype.cpp @@ -0,0 +1,171 @@ +/* + * A bare minimum example of deriving from Inkscape::UI:Widget::Panel. + * + * Author: + * Tavmjong Bah + * + * Copyright (C) Tavmjong Bah <tavmjong@free.fr> + * + * Released under the GNU GPL, read the file 'COPYING' for more information. + */ + +#include "ui/dialog/prototype.h" +#include "verbs.h" +#include "desktop.h" +#include "document.h" +#include "selection.h" + +// Only for use in demonstration widget. +#include "sp-root.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + +Prototype::Prototype() : + // UI::Widget::Panel("Prototype Label", "/dialogs/prototype", SP_VERB_DIALOG_PROTOTYPE, + // "Prototype Apply Label", true), + UI::Widget::Panel("Prototype Label", "/dialogs/prototype", SP_VERB_DIALOG_PROTOTYPE), + + desktopTracker() //, + // desktopChangedConnection() +{ + std::cout << "Prototype::Prototype()" << std::endl; + + // A widget for demonstration that displays the current SVG's id. + _getContents()->pack_start(label); // Panel::_getContents() + + // desktop is set by Panel constructor so this should never be NULL. + // Note, we need to use getDesktop() since _desktop is private in Panel.h. + // It should probably be protected instead... but need to verify in doesn't break anything. + if (getDesktop() == NULL) { + std::cerr << "Prototype::Prototype: desktop is NULL!" << std::endl; + } + + connectionDesktopChanged = desktopTracker.connectDesktopChanged( + sigc::mem_fun(*this, &Prototype::handleDesktopChanged) ); + desktopTracker.connect(GTK_WIDGET(gobj())); + + // This results in calling handleDocumentReplaced twice. Fix me! + connectionDocumentReplaced = getDesktop()->connectDocumentReplaced( + sigc::mem_fun(this, &Prototype::handleDocumentReplaced)); + + // Alternative mechanism but results in calling handleDocumentReplaced four times. + // signalDocumentReplaced().connect( + // sigc::mem_fun(this, &Prototype::handleDocumentReplaced)); + + connectionSelectionChanged = getDesktop()->getSelection()->connectChanged( + sigc::hide(sigc::mem_fun(this, &Prototype::handleSelectionChanged))); + + updateLabel(); +} + +Prototype::~Prototype() +{ + // Never actually called. + std::cout << "Prototype::~Prototype()" << std::endl; + connectionDesktopChanged.disconnect(); + connectionDocumentReplaced.disconnect(); + connectionSelectionChanged.disconnect(); +} + +/* + * Called when a dialog is displayed, including when a dialog is reopened. + * (When a dialog is closed, it is not destroyed so the contructor is not called. + * This function can handle any reinitialization needed.) + */ +void +Prototype::present() +{ + std::cout << "Prototype::present()" << std::endl; + UI::Widget::Panel::present(); +} + +/* + * When Inkscape is first opened, a default document is shown. If another document is immediately + * opened, it will replace the default document in the same desktop. This function handles the + * change. Bug: This is called twice for some reason. + */ +void +Prototype::handleDocumentReplaced(SPDesktop *desktop, SPDocument * /* document */) +{ + std::cout << "Prototype::handleDocumentReplaced()" << std::endl; + if (getDesktop() != desktop) { + std::cerr << "Prototype::handleDocumentReplaced(): Error: panel desktop not equal to existing desktop!" << std::endl; + } + + connectionSelectionChanged.disconnect(); + + connectionSelectionChanged = desktop->getSelection()->connectChanged( + sigc::hide(sigc::mem_fun(this, &Prototype::handleSelectionChanged))); + + // Update demonstration widget. + updateLabel(); +} + +/* + * When a dialog is floating, it is connected to the active desktop. + */ +void +Prototype::handleDesktopChanged(SPDesktop* desktop) { + std::cout << "Prototype::handleDesktopChanged(): " << desktop << std::endl; + + if (getDesktop() == desktop) { + // This will happen after construction of Prototype. We've already + // set up signals so just return. + std::cout << " getDesktop() == desktop" << std::endl; + return; + } + + // Connections are disconnect safe. + connectionSelectionChanged.disconnect(); + connectionDocumentReplaced.disconnect(); + + setDesktop( desktop ); + + connectionSelectionChanged = desktop->getSelection()->connectChanged( + sigc::hide(sigc::mem_fun(this, &Prototype::handleSelectionChanged))); + connectionDocumentReplaced = desktop->connectDocumentReplaced( + sigc::mem_fun(this, &Prototype::handleDocumentReplaced)); + + // Update demonstration widget. + updateLabel(); +} + +/* + * Handle a change in which objects are selected in a document. + */ +void +Prototype::handleSelectionChanged() { + std::cout << "Prototype::handleSelectionChanged()" << std::endl; + + // Update demonstration widget. + label.set_label("Selection Changed!"); +} + +/* + * Update label... just a utility function for this example. + */ +void +Prototype::updateLabel() { + + const gchar* root_id = getDesktop()->getDocument()->getRoot()->getId(); + Glib::ustring label_string("Document's SVG id: "); + label_string += (root_id?root_id:"null"); + label.set_label(label_string); +} + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/dialog/prototype.h b/src/ui/dialog/prototype.h new file mode 100644 index 000000000..95c3856f8 --- /dev/null +++ b/src/ui/dialog/prototype.h @@ -0,0 +1,80 @@ +/* + * A bare minimum example of deriving from Inkscape::UI:Widget::Panel. + * + * Author: + * Tavmjong Bah + * + * Copyright (C) Tavmjong Bah <tavmjong@free.fr> + * + * Released under the GNU GPL, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_PROTOTYPE_PANEL_H +#define SEEN_PROTOTYPE_PANEL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <iostream> +#include "ui/widget/panel.h" +#include "ui/dialog/desktop-tracker.h" + +// Only to display status. +#include <gtkmm/label.h> + +namespace Inkscape { +namespace UI { +namespace Dialog { + +/** + * A panel that does almost nothing! + */ +class Prototype : public UI::Widget::Panel +{ +public: + virtual ~Prototype(); + + static Prototype& getInstance() { return *new Prototype(); }; + + virtual void present(); + +private: + + // No default constructor, noncopyable, nonassignable + Prototype(); + Prototype(Prototype const &d); + Prototype operator=(Prototype const &d); + + // Signals and handlers + sigc::connection connectionDocumentReplaced; + sigc::connection connectionDesktopChanged; + sigc::connection connectionSelectionChanged; + + void handleDocumentReplaced(SPDesktop* desktop, SPDocument *document); + void handleDesktopChanged(SPDesktop* desktop); + void handleSelectionChanged(); + + DesktopTracker desktopTracker; + + // Just for example + Gtk::Label label; + void updateLabel(); +}; + +} //namespace Dialogs +} //namespace UI +} //namespace Inkscape + +#endif // SEEN_PROTOTYPE_PANEL_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp new file mode 100644 index 000000000..4576671e7 --- /dev/null +++ b/src/ui/dialog/styledialog.cpp @@ -0,0 +1,1395 @@ +/** @file + * @brief A dialog for CSS selectors + */ +/* Authors: + * Kamalpreet Kaur Grewal + * Tavmjong Bah + * + * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com> + * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "styledialog.h" +#include "ui/widget/addtoicon.h" +#include "widgets/icon.h" +#include "verbs.h" +#include "sp-object.h" +#include "selection.h" +#include "xml/attribute-record.h" +#include "xml/node-observer.h" +#include "attribute-rel-svg.h" +#include "inkscape.h" +#include "document-undo.h" + +#include <glibmm/i18n.h> +#include <glibmm/regex.h> + +#include <map> + +//#define DEBUG_STYLEDIALOG + +using Inkscape::DocumentUndo; +using Inkscape::Util::List; +using Inkscape::XML::AttributeRecord; + +/** + * This macro is used to remove spaces around selectors or any strings when + * parsing is done to update XML style element or row labels in this dialog. + */ +#define REMOVE_SPACES(x) x.erase(0, x.find_first_not_of(' ')); \ + x.erase(x.find_last_not_of(' ') + 1); + +namespace Inkscape { +namespace UI { +namespace Dialog { + +class StyleDialog::NodeObserver : public Inkscape::XML::NodeObserver { +public: + NodeObserver(StyleDialog* styleDialog) : + _styleDialog(styleDialog) + { +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::NodeObserver: Constructor" << std::endl; +#endif + }; + + virtual void notifyContentChanged(Inkscape::XML::Node &node, + Inkscape::Util::ptr_shared<char> old_content, + Inkscape::Util::ptr_shared<char> new_content); + + StyleDialog * _styleDialog; +}; + + +void +StyleDialog::NodeObserver::notifyContentChanged( + Inkscape::XML::Node &/*node*/, + Inkscape::Util::ptr_shared<char> /*old_content*/, + Inkscape::Util::ptr_shared<char> /*new_content*/ ) { + +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::NodeObserver::notifyContentChanged" << std::endl; +#endif + + _styleDialog->_readStyleElement(); + _styleDialog->_selectRow(); +} + + +StyleDialog::TreeStore::TreeStore() +{ +} + + +/** + * Allow dragging only selectors. + */ +bool +StyleDialog::TreeStore::row_draggable_vfunc(const Gtk::TreeModel::Path& path) const +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::TreeStore::row_draggable_vfunc" << std::endl; +#endif + auto unconstThis = const_cast<StyleDialog::TreeStore*>(this); + const_iterator iter = unconstThis->get_iter(path); + if (iter) { + Gtk::TreeModel::Row row = *iter; + bool is_draggable = row[_styledialog->_mColumns._colIsSelector]; + return is_draggable; + } + return Gtk::TreeStore::row_draggable_vfunc(path); +} + + +/** + * Allow dropping only inbetween other selectors. + */ +bool +StyleDialog::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest, + const Gtk::SelectionData& selection_data) const +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::TreeStore::row_drop_possible_vfunc" << std::endl; +#endif + + Gtk::TreeModel::Path dest_parent = dest; + dest_parent.up(); + return dest_parent.empty(); +} + + +// This is only here to handle updating style element after a drag and drop. +void +StyleDialog::TreeStore::on_row_deleted(const TreeModel::Path& path) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "on_row_deleted" << std::endl; +#endif + + if (_styledialog->_updating) return; // Don't write if we deleted row (other than from DND) + + _styledialog->_writeStyleElement(); +} + + +Glib::RefPtr<StyleDialog::TreeStore> StyleDialog::TreeStore::create(StyleDialog *styledialog) +{ + StyleDialog::TreeStore * store = new StyleDialog::TreeStore(); + store->_styledialog = styledialog; + store->set_column_types( store->_styledialog->_mColumns ); + return Glib::RefPtr<StyleDialog::TreeStore>( store ); +} + +/** + * Constructor + * A treeview and a set of two buttons are added to the dialog. _addSelector + * adds selectors to treeview. _delSelector deletes the selector from the dialog. + * Any addition/deletion of the selectors updates XML style element accordingly. + */ +StyleDialog::StyleDialog() : + UI::Widget::Panel("", "/dialogs/style", SP_VERB_DIALOG_STYLE), + _updating(false), + _textNode(NULL), + _desktopTracker() +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::StyleDialog" << std::endl; +#endif + + // Tree + Inkscape::UI::Widget::AddToIcon * addRenderer = manage( + new Inkscape::UI::Widget::AddToIcon() ); + + _store = TreeStore::create(this); + _treeView.set_model(_store); + _treeView.set_headers_visible(true); + _treeView.enable_model_drag_source(); + _treeView.enable_model_drag_dest( Gdk::ACTION_MOVE ); + + int addCol = _treeView.append_column("", *addRenderer) - 1; + Gtk::TreeViewColumn *col = _treeView.get_column(addCol); + if ( col ) { + col->add_attribute( addRenderer->property_active(), _mColumns._colIsSelector ); + } + _treeView.append_column("CSS Selector", _mColumns._colSelector); + _treeView.set_expander_column(*(_treeView.get_column(1))); + + // Pack widgets + _paned.pack1(_mainBox, Gtk::SHRINK); + _mainBox.pack_start(_scrolledWindow, Gtk::PACK_EXPAND_WIDGET); + _scrolledWindow.add(_treeView); + _scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + + create = manage( new Gtk::Button() ); + _styleButton(*create, "list-add", "Add a new CSS Selector"); + create->signal_clicked().connect(sigc::mem_fun(*this, &StyleDialog::_addSelector)); + + del = manage( new Gtk::Button() ); + _styleButton(*del, "list-remove", "Remove a CSS Selector"); + del->signal_clicked().connect(sigc::mem_fun(*this, &StyleDialog::_delSelector)); + del->set_sensitive(false); + + _mainBox.pack_end(_buttonBox, Gtk::PACK_SHRINK); + + _buttonBox.pack_start(*create, Gtk::PACK_SHRINK); + _buttonBox.pack_start(*del, Gtk::PACK_SHRINK); + + _getContents()->pack_start(_paned, Gtk::PACK_EXPAND_WIDGET); + + // Dialog size request + Gtk::Requisition sreq1, sreq2; + get_preferred_size(sreq1, sreq2); + int minWidth = 300; + int minHeight = 300; + minWidth = (sreq2.width > minWidth ? sreq2.width : minWidth ); + minHeight = (sreq2.height > minHeight ? sreq2.height : minHeight); + set_size_request(minWidth, minHeight); + + // Signal handlers + _treeView.signal_button_release_event().connect( // Needs to be release, not press. + sigc::mem_fun(*this, &StyleDialog::_handleButtonEvent), + false); + + _treeView.signal_button_release_event().connect_notify( + sigc::mem_fun(*this, &StyleDialog::_buttonEventsSelectObjs), + false); + + //_treeView.get_selection()->signal_changed().connect( + // sigc::mem_fun(*this, &StyleDialog::_selChanged)); + + _objObserver.signal_changed().connect(sigc::mem_fun(*this, &StyleDialog::_objChanged)); + + + // Add CSS dialog + _cssPane = new CssDialog; + _paned.pack2(*_cssPane, Gtk::SHRINK); + _cssPane->show_all(); + + _cssPane->_propRenderer->signal_edited().connect( + sigc::mem_fun(*this, &StyleDialog::_handleProp)); + _cssPane->_sheetRenderer->signal_edited().connect( + sigc::mem_fun(*this, &StyleDialog::_handleSheet)); + _cssPane->_attrRenderer->signal_edited().connect( + sigc::mem_fun(*this, &StyleDialog::_handleAttr)); + _cssPane->_treeView.signal_button_release_event().connect( + sigc::mem_fun(*this, &StyleDialog::_delProperty), + false); + + // Document & Desktop + _desktop_changed_connection = _desktopTracker.connectDesktopChanged( + sigc::mem_fun(*this, &StyleDialog::_handleDesktopChanged) ); + _desktopTracker.connect(GTK_WIDGET(gobj())); + + _document_replaced_connection = getDesktop()->connectDocumentReplaced( + sigc::mem_fun(this, &StyleDialog::_handleDocumentReplaced)); + + _selection_changed_connection = getDesktop()->getSelection()->connectChanged( + sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged))); + + // Load tree + _readStyleElement(); + _selectRow(); + + if (!_store->children().empty()) { + del->set_sensitive(true); + } + +} + + +/** + * @brief StyleDialog::~StyleDialog + * Class destructor + */ +StyleDialog::~StyleDialog() +{ +#ifdef DEBUG_STYLEDIALOOG + std::cout << "StyleDialog::~StyleDialog" << std::endl; +#endif + _desktop_changed_connection.disconnect(); + _document_replaced_connection.disconnect(); + _selection_changed_connection.disconnect(); +} + + +/** + * @brief StyleDialog::_styleTextNode + * @return Inkscape::XML::Node* pointing to a style element's text node. + * Returns the style element's text node. If there is no style element, one is created. + * Ditto for text node. + */ +Inkscape::XML::Node* StyleDialog::_getStyleTextNode() +{ + + Inkscape::XML::Node *styleNode = NULL; + Inkscape::XML::Node *textNode = NULL; + + Inkscape::XML::Node *root = SP_ACTIVE_DOCUMENT->getReprRoot(); + for (unsigned i = 0; i < root->childCount(); ++i) { + if (Glib::ustring(root->nthChild(i)->name()) == "svg:style") { + + styleNode = root->nthChild(i); + + for (unsigned j = 0; j < styleNode->childCount(); ++j) { + if (styleNode->nthChild(j)->type() == Inkscape::XML::TEXT_NODE) { + textNode = styleNode->nthChild(j); + } + } + + if (textNode == NULL) { + // Style element found but does not contain text node! + std::cerr << "StyleDialog::_getStyleTextNode(): No text node!" << std::endl; + textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode(""); + styleNode->appendChild(textNode); + Inkscape::GC::release(textNode); + } + } + } + + if (styleNode == NULL) { + // Style element not found, create one + styleNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createElement("svg:style"); + textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode(""); + + styleNode->appendChild(textNode); + Inkscape::GC::release(textNode); + + root->addChild(styleNode, NULL); + Inkscape::GC::release(styleNode); + } + + if (_textNode != textNode) { + _textNode = textNode; + NodeObserver *no = new NodeObserver(this); + textNode->addObserver(*no); + } + + return textNode; +} + + +/** + * @brief StyleDialog::_readStyleElement + * Fill the Gtk::TreeStore from the svg:style element. + */ +void StyleDialog::_readStyleElement() +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_readStyleElement: updating " << (_updating?"true":"false")<< std::endl; +#endif + + if (_updating) return; // Don't read if we wrote style element. + _updating = true; + _store->clear(); + + Inkscape::XML::Node * textNode = _getStyleTextNode(); + if (textNode == NULL) { + std::cerr << "StyleDialog::_readStyleElement: No text node!" << std::endl; + } + + // Get content from style text node. + std::string content = (textNode->content() ? textNode->content() : ""); + + // Remove end-of-lines (check it works on Windoze). + content.erase(std::remove(content.begin(), content.end(), '\n'), content.end()); + + // First split into selector/value chunks. + // An attempt to use Glib::Regex failed. A C++11 version worked but + // reportedly has problems on Windows. Using split_simple() is simpler + // and probably faster. + // + // Glib::RefPtr<Glib::Regex> regex1 = + // Glib::Regex::create("([^\\{]+)\\{([^\\{]+)\\}"); + // + // Glib::MatchInfo minfo; + // regex1->match(content, minfo); + + // Split on curly brackets. Even tokens are selectors, odd are values. + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[}{]", content); + + // If text node is empty, return (avoids problem with negative below). + if (tokens.size() == 0) { + _updating = false; + return; + } + + for (unsigned i = 0; i < tokens.size()-1; i += 2) { + + Glib::ustring selector = tokens[i]; + REMOVE_SPACES(selector); // Remove leading/trailing spaces + + // Get list of objects selector matches + std::vector<SPObject *> objVec = _getObjVec( selector ); + + Glib::ustring properties; + // Check to make sure we do have a value to match selector. + if ((i+1) < tokens.size()) { + properties = tokens[i+1]; + } else { + std::cerr << "StyleDialog::_readStyleElement: Missing values " + "for last selector!" << std::endl; + } + REMOVE_SPACES(properties); + + Gtk::TreeModel::Row row = *(_store->append()); + row[_mColumns._colSelector] = selector; + row[_mColumns._colIsSelector] = true; + row[_mColumns._colObj] = objVec; + row[_mColumns._colProperties] = properties; + + // Add as children, objects that match selector. + for (auto& obj: objVec) { + Gtk::TreeModel::Row childrow = *(_store->append(row->children())); + childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId()); + childrow[_mColumns._colIsSelector] = false; + childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj); + childrow[_mColumns._colProperties] = ""; // Unused + } + } + _updating = false; +} + + +/** + * @brief StyleDialog::_writeStyleElement + * Update the content of the style element as selectors (or objects) are added/removed. + */ +void StyleDialog::_writeStyleElement() +{ + _updating = true; + + Glib::ustring styleContent; + for (auto& row: _store->children()) { + styleContent = styleContent + row[_mColumns._colSelector] + + " { " + row[_mColumns._colProperties] + " }\n"; + } + // We could test if styleContent is empty and then delete the style node here but there is no + // harm in keeping it around ... + + Inkscape::XML::Node *textNode = _getStyleTextNode(); + textNode->setContent(styleContent.c_str()); + + DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_STYLE, _("Edited style element.")); + + _updating = false; +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_writeStyleElement(): |" << styleContent << "|" << std::endl; +#endif +} + + +/** + * @brief StyleDialog::_addToSelector + * @param row + * Add selected objects on the desktop to the selector corresponding to 'row'. + */ +void StyleDialog::_addToSelector(Gtk::TreeModel::Row row) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_addToSelector: Entrance" << std::endl; +#endif + if (*row) { + + Glib::ustring selector = row[_mColumns._colSelector]; + + if (selector[0] == '#') { + // 'id' selector... add selected object's id's to list. + Inkscape::Selection* selection = getDesktop()->getSelection(); + for (auto& obj: selection->objects()) { + + Glib::ustring id = (obj->getId()?obj->getId():""); + + std::vector<SPObject *> objVec = row[_mColumns._colObj]; + bool found = false; + for (auto& obj: objVec) { + if (id == obj->getId()) { + found = true; + break; + } + } + + if (!found) { + // Update row + objVec.push_back(obj); // Adding to copy so need to update tree + row[_mColumns._colObj] = objVec; + row[_mColumns._colSelector] = _getIdList( objVec ); + + // Add child row + Gtk::TreeModel::Row childrow = *(_store->append(row->children())); + childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId()); + childrow[_mColumns._colIsSelector] = false; + childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj); + childrow[_mColumns._colProperties] = ""; // Unused + } + } + } + + else if (selector[0] == '.') { + // 'class' selector... add value to class attribute of selected objects. + + // Get first class (split on white space or comma) + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selector); + Glib::ustring className = tokens[0]; + className.erase(0,1); + + // Get list of objects to modify + Inkscape::Selection* selection = getDesktop()->getSelection(); + std::vector<SPObject *> objVec( selection->objects().begin(), + selection->objects().end() ); + + _insertClass( objVec, className ); + + row[_mColumns._colObj] = _getObjVec( selector ); + + for (auto& obj: objVec) { + // Add child row + Gtk::TreeModel::Row childrow = *(_store->append(row->children())); + childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId()); + childrow[_mColumns._colIsSelector] = false; + childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj); + childrow[_mColumns._colProperties] = ""; // Unused + } + } + + else { + // Do nothing for element selectors. + // std::cout << " Element selector... doing nothing!" << std::endl; + } + } + + _writeStyleElement(); +} + + +/** + * @brief StyleDialog::_removeFromSelector + * @param row + * Remove the object corresponding to 'row' from the parent selector. + */ +void StyleDialog::_removeFromSelector(Gtk::TreeModel::Row row) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_removeFromSelector: Entrance" << std::endl; +#endif + if (*row) { + + Glib::ustring objectLabel = row[_mColumns._colSelector]; + Gtk::TreeModel::iterator iter = row->parent(); + if (iter) { + Gtk::TreeModel::Row parent = *iter; + Glib::ustring selector = parent[_mColumns._colSelector]; + + if (selector[0] == '#') { + // 'id' selector... remove selected object's id's to list. + + // Erase from selector label. + auto i = selector.find(objectLabel); + if (i != Glib::ustring::npos) { + selector.erase(i, objectLabel.length()); + } + // Erase any comma/space + if (i != Glib::ustring::npos && selector[i] == ',') { + selector.erase(i, 1); + } + if (i != Glib::ustring::npos && selector[i] == ' ') { + selector.erase(i, 1); + } + + // Update store + if (selector.empty()) { + _store->erase(parent); + } else { + // Save new selector and update object vector. + parent[_mColumns._colSelector] = selector; + parent[_mColumns._colObj] = _getObjVec( selector ); + _store->erase(row); + } + } + + else if (selector[0] == '.') { + // 'class' selector... remove value to class attribute of selected objects. + + std::vector<SPObject *> objVec = row[_mColumns._colObj]; // Just one + + // Get first class (split on white space or comma) + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selector); + Glib::ustring className = tokens[0]; + className.erase(0,1); // Erase '.' + + // Erase class name from 'class' attribute. + Glib::ustring classAttr = objVec[0]->getRepr()->attribute("class"); + auto i = classAttr.find( className ); + if (i != Glib::ustring::npos) { + classAttr.erase(i, className.length()); + } + if (i != Glib::ustring::npos && classAttr[i] == ' ') { + classAttr.erase(i, 1); + } + objVec[0]->getRepr()->setAttribute("class", classAttr); + + parent[_mColumns._colObj] = _getObjVec( selector ); + _store->erase(row); + } + + else { + // Do nothing for element selectors. + // std::cout << " Element selector... doing nothing!" << std::endl; + } + } + } + + _writeStyleElement(); + +} + + +/** + * @brief StyleDialog::_getIdList + * @param sel + * @return This function returns a comma seperated list of ids for objects in input vector. + * It is used in creating an 'id' selector. It relies on objects having 'id's. + */ +Glib::ustring StyleDialog::_getIdList(std::vector<SPObject*> sel) +{ + Glib::ustring str; + for (auto& obj: sel) { + str += "#" + Glib::ustring(obj->getId()) + ", "; + } + if (!str.empty()) { + str.erase(str.size()-1); // Remove space at end. c++11 has pop_back() but not ustring. + str.erase(str.size()-1); // Remove comma at end. + } + return str; +} + +/** + * @brief StyleDialog::_getObjVec + * @param selector: a valid CSS selector string. + * @return objVec: a vector of pointers to SPObject's the selector matches. + * Return a vector of all objects that selector matches. + */ +std::vector<SPObject *> StyleDialog::_getObjVec(Glib::ustring selector) { + + std::vector<SPObject *> objVec = SP_ACTIVE_DOCUMENT->getObjectsBySelector( selector ); + +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_getObjVec: |" << selector << "|" << std::endl; + for (auto& obj: objVec) { + std::cout << " " << (obj->getId()?obj->getId():"null") << std::endl; + } +#endif + + return objVec; +} + + +/** + * @brief StyleDialog::_insertClass + * @param objs: list of objects to insert class + * @param class: class to insert + * Insert a class name into objects' 'class' attribute. + */ +void StyleDialog::_insertClass(const std::vector<SPObject *>& objVec, const Glib::ustring& className) { + + for (auto& obj: objVec) { + + if (!obj->getRepr()->attribute("class")) { + // 'class' attribute does not exist, create it. + obj->getRepr()->setAttribute("class", className); + } else { + // 'class' attribute exists, append. + Glib::ustring classAttr = obj->getRepr()->attribute("class"); + + // Split on white space. + std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s+", classAttr); + bool add = true; + for (auto& token: tokens) { + if (token == className) { + add = false; // Might be useful to still add... + break; + } + } + if (add) { + obj->getRepr()->setAttribute("class", classAttr + " " + className ); + } + } + } + } + + +/** + * @brief StyleDialog::_selectObjects + * @param eventX + * @param eventY + * This function selects objects in the drawing corresponding to the selector + * selected in the treeview. + */ +void StyleDialog::_selectObjects(int eventX, int eventY) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_selectObjects: " << eventX << ", " << eventY << std::endl; +#endif + + getDesktop()->selection->clear(); + Gtk::TreeViewColumn *col = _treeView.get_column(1); + Gtk::TreeModel::Path path; + int x2 = 0; + int y2 = 0; + // To do: We should be able to do this via passing in row. + if (_treeView.get_path_at_pos(eventX, eventY, path, col, x2, y2)) { + if (col == _treeView.get_column(1)) { + Gtk::TreeModel::iterator iter = _store->get_iter(path); + if (iter) { + Gtk::TreeModel::Row row = *iter; + Gtk::TreeModel::Children children = row.children(); + std::vector<SPObject *> objVec = row[_mColumns._colObj]; + for (unsigned i = 0; i < objVec.size(); ++i) { + SPObject *obj = objVec[i]; + getDesktop()->selection->add(obj); + } + } + } + } +} + + +/** + * @brief StyleDialog::_addSelector + * + * This function opens a dialog to add a selector. The dialog is prefilled + * with an 'id' selector containing a list of the id's of selected objects + * or with a 'class' selector if no objects are selected. + */ +void StyleDialog::_addSelector() +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_addSelector: Entrance" << std::endl; +#endif + + // Store list of selected elements on desktop (not to be confused with selector). + Inkscape::Selection* selection = getDesktop()->getSelection(); + std::vector<SPObject *> objVec( selection->objects().begin(), + selection->objects().end() ); + + // ==== Create popup dialog ==== + Gtk::Dialog *textDialogPtr = new Gtk::Dialog(); + textDialogPtr->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL); + textDialogPtr->add_button(_("Add"), Gtk::RESPONSE_OK); + + Gtk::Entry *textEditPtr = manage ( new Gtk::Entry() ); + textDialogPtr->get_vbox()->pack_start(*textEditPtr, Gtk::PACK_SHRINK); + + Gtk::Label *textLabelPtr = manage ( new Gtk::Label( + _("Invalid entry: Not an id (#), class (.), or element CSS selector.") + ) ); + textDialogPtr->get_vbox()->pack_start(*textLabelPtr, Gtk::PACK_SHRINK); + + /** + * By default, the entrybox contains 'Class1' as text. However, if object(s) + * is(are) selected and user clicks '+' at the bottom of dialog, the + * entrybox will have the id(s) of the selected objects as text. + */ + if (getDesktop()->getSelection()->isEmpty()) { + textEditPtr->set_text(".Class1"); + } else { + textEditPtr->set_text(_getIdList(objVec)); + } + + Gtk::Requisition sreq1, sreq2; + textDialogPtr->get_preferred_size(sreq1, sreq2); + int minWidth = 200; + int minHeight = 100; + minWidth = (sreq2.width > minWidth ? sreq2.width : minWidth ); + minHeight = (sreq2.height > minHeight ? sreq2.height : minHeight); + textDialogPtr->set_size_request(minWidth, minHeight); + textEditPtr->show(); + textLabelPtr->hide(); + textDialogPtr->show(); + + + // ==== Get response ==== + int result = -1; + bool invalid = true; + Glib::ustring selectorValue; + + while (invalid) { + result = textDialogPtr->run(); + if (result != Gtk::RESPONSE_OK) { // Cancel, close dialog, etc. + textDialogPtr->hide(); + return; + } + /** + * @brief selectorName + * This string stores selector name. The text from entrybox is saved as name + * for selector. If the entrybox is empty, the text (thus selectorName) is + * set to ".Class1" + */ + selectorValue = textEditPtr->get_text(); + Glib::ustring firstWord = selectorValue.substr(0, selectorValue.find(" ")); + + del->set_sensitive(true); + + if (selectorValue[0] == '.' || + selectorValue[0] == '#' || + selectorValue[0] == '*' || + SPAttributeRelSVG::isSVGElement( firstWord ) ) { + invalid = false; + } else { + textLabelPtr->show(); + } + } + delete textDialogPtr; + + // ==== Handle response ==== + + // If class selector, add selector name to class attribute for each object + if (selectorValue[0] == '.') { + + Glib::ustring className = selectorValue; + className.erase(0,1); + _insertClass(objVec, className); + } + + // Generate a new object vector (we could have an element selector, + // the user could have edited the id selector list, etc.). + objVec = _getObjVec( selectorValue ); + + // Add entry to GUI tree + Gtk::TreeModel::Row row = *(_store->append()); + row[_mColumns._colSelector] = selectorValue; + row[_mColumns._colIsSelector] = true; + row[_mColumns._colObj] = objVec; + + // Add as children objects that match selector. + for (auto& obj: objVec) { + Gtk::TreeModel::Row childrow = *(_store->append(row->children())); + childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId()); + childrow[_mColumns._colIsSelector] = false; + childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj); + } + + // Add entry to style element + _writeStyleElement(); +} + +/** + * @brief StyleDialog::_delSelector + * This function deletes selector when '-' at the bottom is clicked. + * Note: If deleting a class selector, class attributes are NOT changed. + */ +void StyleDialog::_delSelector() +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_delSelector" << std::endl; +#endif + Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection(); + Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + _updating = true; + _store->erase(iter); + _updating = false; + _writeStyleElement(); + } +} + +/** + * @brief StyleDialog::_handleButtonEvent + * @param event + * @return + * Handles the event when '+' button in front of a selector name is clicked or when a '-' button in + * front of a child object is clicked. In the first case, the selected objects on the desktop (if + * any) are added as children of the selector in the treeview. In the latter case, the object + * corresponding to the row is removed from the selector. + */ +bool StyleDialog::_handleButtonEvent(GdkEventButton *event) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_handleButtonEvent: Entrance" << std::endl; +#endif + if (event->type == GDK_BUTTON_RELEASE && event->button == 1) { + Gtk::TreeViewColumn *col = 0; + Gtk::TreeModel::Path path; + int x = static_cast<int>(event->x); + int y = static_cast<int>(event->y); + int x2 = 0; + int y2 = 0; + if (_treeView.get_path_at_pos(x, y, path, col, x2, y2)) { + if (col == _treeView.get_column(0)) { + Gtk::TreeModel::iterator iter = _store->get_iter(path); + Gtk::TreeModel::Row row = *iter; + + // Add or remove objects from a + if (!row.parent()) { + // Add selected objects to selector. + _addToSelector(row); + } else { + // Remove object from selector + _removeFromSelector(row); + } + } + } + } + return false; +} + +// ------------------------------------------------------------------- + +class PropertyData +{ +public: + PropertyData() {}; + PropertyData(Glib::ustring name) : _name(name) {}; + + void _setSheetValue(Glib::ustring value) { _sheetValue = value; }; + void _setAttrValue(Glib::ustring value) { _attrValue = value; }; + Glib::ustring _getName() { return _name; }; + Glib::ustring _getSheetValue() { return _sheetValue; }; + Glib::ustring _getAttrValue() { return _attrValue; }; + +private: + Glib::ustring _name; + Glib::ustring _sheetValue; + Glib::ustring _attrValue; +}; + +// ------------------------------------------------------------------- + + +/** + * @brief StyleDialog::_updateCSSPanel + * Updates CSS panel according to row in Style panel. + */ +void StyleDialog::_updateCSSPanel() +{ + // This should probably be in a member function of CSSDialog. +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_updateCSSPanel" << std::endl; +#endif + _updating = true; + + _cssPane->_store->clear(); + + Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection(); + Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + Glib::ustring properties; + Glib::ustring sheet; + Glib::ustring attr; + if (row[_mColumns._colIsSelector]) { + _cssPane->_propRenderer->property_editable() = true; + _cssPane->_sheetRenderer->property_editable() = true; + _cssPane->_sheetRenderer->property_foreground_rgba() = Gdk::RGBA("black"); + _cssPane->_attrRenderer->property_editable() = false; + _cssPane->_buttonAddProperty.set_sensitive(true); + + properties = row[_mColumns._colProperties]; + sheet = row[_mColumns._colProperties]; + _objObserver.set( NULL ); + } else { + _cssPane->_propRenderer->property_editable() = false; + _cssPane->_sheetRenderer->property_editable() = false; + _cssPane->_sheetRenderer->property_foreground_rgba() = Gdk::RGBA("gray"); + _cssPane->_attrRenderer->property_editable() = false; // false for now... + _cssPane->_buttonAddProperty.set_sensitive(false); + + std::vector<SPObject *> objects = row[_mColumns._colObj]; + Gtk::TreeModel::iterator piter = row.parent(); + if (piter) { + Gtk::TreeModel::Row prow = *piter; + sheet = prow[_mColumns._colProperties]; + } + _objObserver.set( objects[0] ); + if (objects[0] && objects[0]->getAttribute("style") != NULL) { + properties = objects[0]->getAttribute("style"); + attr = objects[0]->getAttribute("style"); + } + } + REMOVE_SPACES(properties); // Remove leading/trailing spaces + + std::map<Glib::ustring, PropertyData> propMap; + + std::vector<Glib::ustring> sheetList = Glib::Regex::split_simple("\\s*;\\s*", sheet); + for (auto& token: sheetList) { + + if (token.empty()) break; + + std::vector<Glib::ustring> pair = + Glib::Regex::split_simple("\\s*:\\s*", token); + if( pair.size() > 1) { + PropertyData temp( pair[0] ); + temp._setSheetValue( pair[1] ); + propMap[pair[0]] = temp; + } + } + + std::vector<Glib::ustring> attrList = Glib::Regex::split_simple("\\s*;\\s*", attr); + for (auto& token: attrList) { + + if (token.empty()) break; + + std::vector<Glib::ustring> pair = + Glib::Regex::split_simple("\\s*:\\s*", token); + + if( pair.size() > 1) { + auto it = propMap.find(pair[0]); + if (it != propMap.end()) { + (*it).second._setAttrValue( pair[1] ); + } else { + PropertyData temp(pair[0]); + temp._setAttrValue( pair[1] ); + propMap[pair[0]] = temp; + } + } + } + + for (auto it : propMap) { + // std::cout << " " << it.first + // << " " << it.second._getName() + // << " " << it.second._getSheetValue() + // << " " << it.second._getAttrValue() + // << std::endl; + _cssPane->_propRow = *(_cssPane->_store->append()); + _cssPane->_propRow[_cssPane->_cssColumns._colUnsetProp] = false; + _cssPane->_propRow[_cssPane->_cssColumns._propertyLabel] = it.second._getName(); + _cssPane->_propRow[_cssPane->_cssColumns._styleSheetVal] = it.second._getSheetValue(); + _cssPane->_propRow[_cssPane->_cssColumns._styleAttrVal ] = it.second._getAttrValue(); + } + } + + _updating = false; +} + + +/** + * Handle document replaced. (Happens when a default document is immediately replaced by another + * document in a new window.) + */ +void +StyleDialog::_handleDocumentReplaced(SPDesktop *desktop, SPDocument * /* document */) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::handleDocumentReplaced()" << std::endl; +#endif + + _selection_changed_connection.disconnect(); + + _selection_changed_connection = desktop->getSelection()->connectChanged( + sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged))); + + _readStyleElement(); + _selectRow(); +} + + +/* + * When a dialog is floating, it is connected to the active desktop. + */ +void +StyleDialog::_handleDesktopChanged(SPDesktop* desktop) { +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::handleDesktopReplaced()" << std::endl; +#endif + + if (getDesktop() == desktop) { + // This will happen after construction of dialog. We've already + // set up signals so just return. + return; + } + + _selection_changed_connection.disconnect(); + _document_replaced_connection.disconnect(); + + setDesktop( desktop ); + + _selection_changed_connection = desktop->getSelection()->connectChanged( + sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged))); + _document_replaced_connection = desktop->connectDocumentReplaced( + sigc::mem_fun(this, &StyleDialog::_handleDocumentReplaced)); + + _readStyleElement(); + _selectRow(); +} + + +/* + * Handle a change in which objects are selected in a document. + */ +void +StyleDialog::_handleSelectionChanged() { +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_handleSelectionChanged()" << std::endl; +#endif + + _selectRow(); +} + + +/** + * @brief StyleDialog::_buttonEventsSelectObjs + * @param event + * This function detects single or double click on a selector in any row. Clicking + * on a selector selects the matching objects on the desktop. A double click will + * in addition open the CSS dialog. + */ +void StyleDialog::_buttonEventsSelectObjs(GdkEventButton* event ) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_buttonEventsSelectObjs" << std::endl; +#endif + + _updating = true; + + if (event->type == GDK_BUTTON_RELEASE && event->button == 1) { + int x = static_cast<int>(event->x); + int y = static_cast<int>(event->y); + _selectObjects(x, y); + //} + //else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { + //int x = static_cast<int>(event->x); + //int y = static_cast<int>(event->y); + //_selectObjects(x, y); + + _updateCSSPanel(); + } + _updating = false; +} + + +/** + * @brief StyleDialog::_selectRow + * This function selects the row in treeview corresponding to an object selected + * in the drawing. If more than one row matches, the first is chosen. + */ +void StyleDialog::_selectRow() +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_selectRow: updating: " << (_updating?"true":"false") << std::endl; +#endif + if (_updating || !getDesktop()) return; // Avoid updating if we have set row via dialog. + + if (SP_ACTIVE_DESKTOP != getDesktop()) { + std::cerr << "StyleDialog::_selectRow: SP_ACTIVE_DESKTOP != getDesktop()" << std::endl; + return; + } + + Inkscape::Selection* selection = getDesktop()->getSelection(); + if (!selection->isEmpty()) { + SPObject *obj = selection->objects().back(); + + Gtk::TreeModel::Children children = _store->children(); + for(Gtk::TreeModel::Children::iterator iter = children.begin(); + iter != children.end(); ++iter) { + + Gtk::TreeModel::Row row = *iter; + std::vector<SPObject *> objVec = row[_mColumns._colObj]; + for (unsigned i = 0; i < objVec.size(); ++i) { + if (obj->getId() == objVec[i]->getId()) { + _treeView.get_selection()->select(row); + _updateCSSPanel(); + return; + } + } + } + } + + // Selection empty or no row matches. + _treeView.get_selection()->unselect_all(); + _updateCSSPanel(); +} + + +void StyleDialog::_objChanged() { +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_objChanged" << std::endl; +#endif + if (_updating) return; + _updateCSSPanel(); +} + + +/** + * @brief StyleDialog::_handleProp + * @param path + * @param new_text + * Called when new text is entered into a "prop" cell.. + */ +void StyleDialog::_handleProp(const Glib::ustring& path, const Glib::ustring& new_text) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_handleProp: path: " << path + << " new_text: " << new_text << std::endl; +#endif + + Gtk::TreeModel::iterator iterCss = _cssPane->_treeView.get_model()->get_iter(path); + if (iterCss) { + Gtk::TreeModel::Row row = *iterCss; + row[_cssPane->_cssColumns._propertyLabel] = new_text; + } + + // To do: validate. +} + +/** + * @brief StyleDialog::_handleSheet + * @param path + * @param new_text + * Called when new text is entered into a "sheet" cell.. + */ +void StyleDialog::_handleSheet(const Glib::ustring& path, const Glib::ustring& new_text) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_handleSheet: path: " << path + << " new_text: " << new_text << std::endl; +#endif + + Gtk::TreeModel::iterator iterCss = _cssPane->_treeView.get_model()->get_iter(path); + if (iterCss) { + Gtk::TreeModel::Row row = *iterCss; + row[_cssPane->_cssColumns._styleSheetVal] = new_text; + } + + // To do: validate (run through style.read()/style.write()?). + + Glib::ustring properties; + for (auto& crow: _cssPane->_store->children()) { + properties = properties + + crow[_cssPane->_cssColumns._propertyLabel] + ": " + + crow[_cssPane->_cssColumns._styleSheetVal] + "; "; + } + + // Update selector data. + Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection(); + Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + row[_mColumns._colProperties] = properties; + _writeStyleElement(); + } +} + +/** + * @brief StyleDialog::_handleAttr + * @param path + * @param new_text + * Called when new text is entered into an "attr" cell.. + */ +void StyleDialog::_handleAttr(const Glib::ustring& path, const Glib::ustring& new_text) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_handleAttr: path: " << path + << " new_text: " << new_text << std::endl; +#endif + + Gtk::TreeModel::iterator iterCss = _cssPane->_treeView.get_model()->get_iter(path); + if (iterCss) { + Gtk::TreeModel::Row row = *iterCss; + row[_cssPane->_cssColumns._styleAttrVal] = new_text; + } + + // To do: validate (run through style.read()/style.write()?). + + Glib::ustring properties; + for (auto& crow: _cssPane->_store->children()) { + properties = properties + + crow[_cssPane->_cssColumns._propertyLabel] + ": "; + crow[_cssPane->_cssColumns._styleAttrVal] + "; "; + } + + std::cout << "StyleDialog::_handlerAttr(): Unimplemented write." << std::endl; +} + +/** + * @brief StyleDialog::_delProperty + * @param event + * @return + * Delete a property from the CSS dialog and then update trees. + */ +bool StyleDialog::_delProperty(GdkEventButton *event) +{ +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_delProperty" << std::endl; +#endif + + if (event->type == GDK_BUTTON_RELEASE && event->button == 1) { + Gtk::TreeViewColumn *col = 0; + Gtk::TreeModel::Path path; + int x = static_cast<int>(event->x); + int y = static_cast<int>(event->y); + int x2 = 0; + int y2 = 0; + Gtk::TreeModel::Row cssRow; + Glib::ustring toDelProperty; + if (_cssPane->_treeView.get_path_at_pos(x, y, path, col, x2, y2)) { + if (col == _cssPane->_treeView.get_column(0)) { + Gtk::TreeModel::iterator cssIter = + _cssPane->_treeView.get_selection()->get_selected(); + if (cssIter) { + + Gtk::TreeModel::Row cssRow = *cssIter; + + // Update selector data. + Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection(); + Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); + if (iter) { + + Gtk::TreeModel::Row row = *iter; + + if ( row[_mColumns._colIsSelector]) { + + // We only care about style sheet for selectors so erase row in CSS pane. + _cssPane->_store->erase(cssIter); + + // Update style sheet + Glib::ustring properties; + for (auto& crow: _cssPane->_store->children()) { + Glib::ustring sheetVal = crow[_cssPane->_cssColumns._styleSheetVal]; + if (!sheetVal.empty()) { + properties = properties + + crow[_cssPane->_cssColumns._propertyLabel] + ": " + + crow[_cssPane->_cssColumns._styleSheetVal] + "; "; + } + } + + row[_mColumns._colProperties] = properties; + _writeStyleElement(); + + } else { + + // We only erase row if style sheet does not contain deleted property. + // Otherwise we set style attr value to empty string. + Gtk::TreeModel::Row cssRow = *cssIter; + Glib::ustring val = cssRow[_cssPane->_cssColumns._styleSheetVal]; + if (val.empty()) { + _cssPane->_store->erase(cssIter); + } else { + cssRow[_cssPane->_cssColumns._styleAttrVal] = Glib::ustring(); + } + + // Update style attribute + std::vector<SPObject *> objects = row[_mColumns._colObj]; + Glib::ustring properties; + for (auto& crow: _cssPane->_store->children()) { + Glib::ustring attrVal = crow[_cssPane->_cssColumns._styleAttrVal]; + if (!attrVal.empty()) { + properties = properties + + crow[_cssPane->_cssColumns._propertyLabel] + ": " + + crow[_cssPane->_cssColumns._styleAttrVal] + "; "; + } + } + + if (objects[0]) { + if (properties.empty()) { + objects[0]->setAttribute("style", NULL); + } else { + objects[0]->setAttribute("style", properties); + } + DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_STYLE, + _("Deleted property from style attribute.")); + + } + } + } + } + } + } + } + return false; +} + + +/** + * @brief StyleDialog::_styleButton + * @param btn + * @param iconName + * @param tooltip + * Set the style of '+' and '-' buttons at the bottom of dialog. + */ +void StyleDialog::_styleButton(Gtk::Button& btn, char const* iconName, + char const* tooltip) +{ + GtkWidget *child = sp_icon_new(Inkscape::ICON_SIZE_SMALL_TOOLBAR, iconName); + gtk_widget_show(child); + btn.add(*manage(Glib::wrap(child))); + btn.set_relief(Gtk::RELIEF_NONE); + btn.set_tooltip_text (tooltip); +} + + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/dialog/styledialog.h b/src/ui/dialog/styledialog.h new file mode 100644 index 000000000..e84489e66 --- /dev/null +++ b/src/ui/dialog/styledialog.h @@ -0,0 +1,175 @@ +/** @file + * @brief A dialog for CSS selectors + */ +/* Authors: + * Kamalpreet Kaur Grewal + * Tavmjong Bah + * + * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com> + * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef STYLEDIALOG_H +#define STYLEDIALOG_H + +#include <ui/widget/panel.h> +#include <gtkmm/treeview.h> +#include <gtkmm/treestore.h> +#include <gtkmm/scrolledwindow.h> +#include <gtkmm/dialog.h> +#include <gtkmm/treeselection.h> +#include <gtkmm/paned.h> + +#include "ui/dialog/desktop-tracker.h" +#include "ui/dialog/cssdialog.h" + +#include "xml/helper-observer.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + +/** + * @brief The StyleDialog class + * A list of CSS selectors will show up in this dialog. This dialog allows one to + * add and delete selectors. Elements can be added to and removed from the selectors + * in the dialog. Selection of any selector row selects the matching objects in + * the drawing and vice-versa. (Only simple selectors supported for now.) + * + * This class must keep two things in sync: + * 1. The text node of the style element. + * 2. The Gtk::TreeModel. + */ +class StyleDialog : public Widget::Panel { + +public: + ~StyleDialog(); + + static StyleDialog &getInstance() { return *new StyleDialog(); } + +private: + // No default constructor, noncopyable, nonassignable + StyleDialog(); + StyleDialog(StyleDialog const &d); + StyleDialog operator=(StyleDialog const &d); + + // Monitor <style> element for changes. + class NodeObserver; + + // Data structure + class ModelColumns : public Gtk::TreeModel::ColumnRecord { + public: + ModelColumns() { + add(_colSelector); + add(_colIsSelector); + add(_colObj); + add(_colProperties); + } + Gtk::TreeModelColumn<Glib::ustring> _colSelector; // Selector or matching object id. + Gtk::TreeModelColumn<bool> _colIsSelector; // Selector row or child object row. + Gtk::TreeModelColumn<std::vector<SPObject *> > _colObj; // List of matching objects. + Gtk::TreeModelColumn<Glib::ustring> _colProperties; // List of properties. + }; + ModelColumns _mColumns; + + // Override Gtk::TreeStore to control drag-n-drop (only allow dragging and dropping of selectors). + // See: https://developer.gnome.org/gtkmm-tutorial/stable/sec-treeview-examples.html.en + // + // TreeStore implements simple drag and drop (DND) but there appears no way to know when a DND + // has been completed (other than doing the whole DND ourselves). As a hack, we use + // on_row_deleted to trigger write of style element. + class TreeStore : public Gtk::TreeStore { + protected: + TreeStore(); + bool row_draggable_vfunc(const Gtk::TreeModel::Path& path) const override; + bool row_drop_possible_vfunc(const Gtk::TreeModel::Path& path, + const Gtk::SelectionData& selection_data) const override; + void on_row_deleted(const TreeModel::Path& path) override; + + public: + static Glib::RefPtr<StyleDialog::TreeStore> create(StyleDialog *styledialog); + + private: + StyleDialog *_styledialog; + }; + + // TreeView + Gtk::TreeView _treeView; + Glib::RefPtr<TreeStore> _store; + + // Widgets + Gtk::VPaned _paned; + Gtk::VBox _mainBox; + Gtk::HBox _buttonBox; + Gtk::ScrolledWindow _scrolledWindow; + Gtk::Button* del; + Gtk::Button* create; + CssDialog *_cssPane; + + // Reading and writing the style element. + Inkscape::XML::Node *_getStyleTextNode(); + void _readStyleElement(); + void _writeStyleElement(); + + // Manipulate Tree + void _addToSelector(Gtk::TreeModel::Row row); + void _removeFromSelector(Gtk::TreeModel::Row row); + Glib::ustring _getIdList(std::vector<SPObject *>); + std::vector<SPObject *> _getObjVec(Glib::ustring selector); + void _insertClass(const std::vector<SPObject *>& objVec, const Glib::ustring& className); + void _selectObjects(int, int); + void _updateCSSPanel(); + + // Variables + bool _updating; // Prevent cyclic actions: read <-> write, select via dialog <-> via desktop + Inkscape::XML::Node *_textNode; // Track so we know when to add a NodeObserver. + + // Signals and handlers - External + sigc::connection _document_replaced_connection; + sigc::connection _desktop_changed_connection; + sigc::connection _selection_changed_connection; + + void _handleDocumentReplaced(SPDesktop* desktop, SPDocument *document); + void _handleDesktopChanged(SPDesktop* desktop); + void _handleSelectionChanged(); + + DesktopTracker _desktopTracker; + + Inkscape::XML::SignalObserver _objObserver; // Track object in selected row (for style change). + + // Signal and handlers - Internal + void _addSelector(); + void _delSelector(); + bool _handleButtonEvent(GdkEventButton *event); + void _buttonEventsSelectObjs(GdkEventButton *event); + void _selectRow(); // Select row in tree when selection changed. + void _objChanged(); + + // Signal handlers for CssDialog + void _handleProp( const Glib::ustring& path, const Glib::ustring& new_text); + void _handleSheet(const Glib::ustring& path, const Glib::ustring& new_text); + void _handleAttr( const Glib::ustring& path, const Glib::ustring& new_text); + bool _delProperty(GdkEventButton *event); + + // GUI + void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip); +}; + +} // namespace Dialogc +} // namespace UI +} // namespace Inkscape + +#endif // STYLEDIALOG_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/dialog/symbols.cpp b/src/ui/dialog/symbols.cpp index 4840b897b..740ff4f39 100644 --- a/src/ui/dialog/symbols.cpp +++ b/src/ui/dialog/symbols.cpp @@ -25,6 +25,7 @@ #include <gtkmm/comboboxtext.h> #include <gtkmm/iconview.h> #include <gtkmm/liststore.h> +#include <glibmm/regex.h> #include <glibmm/stringutils.h> #include <glibmm/markup.h> #include <glibmm/i18n.h> @@ -127,7 +128,7 @@ SymbolsDialog::SymbolsDialog( gchar const* prefsPath ) : sigc::connection connSet = symbolSet->signal_changed().connect( sigc::mem_fun(*this, &SymbolsDialog::rebuild)); instanceConns.push_back(connSet); - + ++row; /********************* Icon View **************************/ @@ -342,7 +343,7 @@ void SymbolsDialog::rebuild() { addSymbol->set_sensitive( true ); removeSymbol->set_sensitive( true ); } else { - addSymbol->set_sensitive( false ); + addSymbol->set_sensitive( false ); removeSymbol->set_sensitive( false ); } add_symbols( symbolDocument ); @@ -451,10 +452,21 @@ void SymbolsDialog::iconChanged() { #ifdef WITH_LIBVISIO // Read Visio stencil files -SPDocument* read_vss( gchar* fullname, gchar* filename ) { +SPDocument* read_vss( gchar* fullname, Glib::ustring name ) { + + #ifdef WIN32 + // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows + // therefore attempt to convert uri to the system codepage + // even if this is not possible the alternate short (8.3) file name will be used if available + fullname = g_win32_locale_filename_from_utf8(fullname); + #endif RVNGFileStream input(fullname); + #ifdef WIN32 + g_free(fullname); + #endif + if (!libvisio::VisioDocument::isSupported(&input)) { return NULL; } @@ -474,6 +486,12 @@ SPDocument* read_vss( gchar* fullname, gchar* filename ) { return NULL; } + // prepare a valid title for the symbol file + Glib::ustring title = Glib::Markup::escape_text(name); + // prepare a valid id prefix for the symbols (unfortunately libvisio doesn't give us a name) + Glib::RefPtr<Glib::Regex> regex1 = Glib::Regex::create("[^a-zA-Z0-9_-]"); + Glib::ustring id = regex1->replace(name, 0, "_", Glib::REGEX_MATCH_PARTIAL); + Glib::ustring tmpSVGOutput; tmpSVGOutput += "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"; tmpSVGOutput += "<svg\n"; @@ -483,17 +501,10 @@ SPDocument* read_vss( gchar* fullname, gchar* filename ) { tmpSVGOutput += " version=\"1.1\"\n"; tmpSVGOutput += " style=\"fill:none;stroke:#000000;stroke-width:2\">\n"; tmpSVGOutput += " <title>"; - tmpSVGOutput += filename; + tmpSVGOutput += title; tmpSVGOutput += "</title>\n"; tmpSVGOutput += " <defs>\n"; - // Create a string we can use for the symbol id (libvisio doesn't give us a name) - std::string sanitized( filename ); - sanitized.erase( sanitized.find_last_of(".vss")-3 ); - sanitized.erase( std::remove_if( sanitized.begin(), sanitized.end(), ispunct ), sanitized.end() ); - std::replace( sanitized.begin(), sanitized.end(), ' ', '_' ); - // std::cout << filename << " |" << sanitized << "|" << std::endl; - // Each "symbol" is in it's own SVG file, we wrap with <symbol> and merge into one file. for (unsigned i=0; i<output.size(); ++i) { @@ -501,7 +512,7 @@ SPDocument* read_vss( gchar* fullname, gchar* filename ) { ss << i; tmpSVGOutput += " <symbol id=\""; - tmpSVGOutput += sanitized; + tmpSVGOutput += id; tmpSVGOutput += "_"; tmpSVGOutput += ss.str(); tmpSVGOutput += "\">\n"; @@ -521,12 +532,12 @@ SPDocument* read_vss( gchar* fullname, gchar* filename ) { tmpSVGOutput += " </defs>\n"; tmpSVGOutput += "</svg>\n"; - + return SPDocument::createNewDocFromMem( tmpSVGOutput.c_str(), strlen( tmpSVGOutput.c_str()), 0 ); } #endif - + /* Hunts preference directories for symbol files */ void SymbolsDialog::get_symbols() { @@ -565,11 +576,14 @@ void SymbolsDialog::get_symbols() { #ifdef WITH_LIBVISIO if( tag.compare( "vss" ) == 0 ) { + // strip extension from filename and use it as name for the symbol set + Glib::ustring name = Glib::ustring(filename); + name = name.erase(name.rfind('.')); - symbol_doc = read_vss( fullname, filename ); + symbol_doc = read_vss( fullname, name ); if( symbol_doc ) { - symbolSets[Glib::ustring(filename)]= symbol_doc; - symbolSet->append(filename); + symbolSets[name]= symbol_doc; + symbolSet->append(name); } } #endif @@ -630,7 +644,7 @@ GSList* SymbolsDialog::symbols_in_doc( SPDocument* symbolDocument ) { } GSList* SymbolsDialog::use_in_doc_recursive (SPObject *r, GSList *l) -{ +{ if ( dynamic_cast<SPUse *>(r) ) { l = g_slist_prepend (l, r); @@ -816,7 +830,7 @@ SPDocument* SymbolsDialog::symbols_preview_doc() " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"" " xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"" " xmlns:xlink=\"http://www.w3.org/1999/xlink\">" -" <defs id=\"defs\">" +" <defs id=\"defs\">" " <symbol id=\"the_symbol\"/>" " </defs>" " <use id=\"the_use\" xlink:href=\"#the_symbol\"/>" diff --git a/src/ui/interface.cpp b/src/ui/interface.cpp index d50c56b76..5e85065d1 100644 --- a/src/ui/interface.cpp +++ b/src/ui/interface.cpp @@ -72,6 +72,7 @@ #include "sp-mask.h" #include "message-stack.h" #include "ui/dialog/layer-properties.h" +#include "extension/find_extension_by_mime.h" using Inkscape::DocumentUndo; @@ -1234,15 +1235,7 @@ sp_ui_drag_data_received(GtkWidget *widget, 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; + Inkscape::Extension::Extension *ext = Inkscape::Extension::find_by_mime((info == JPEG_DATA ? "image/jpeg" : "image/png")); bool save = (strcmp(ext->get_param_optiongroup("link"), "embed") == 0); ext->set_param_optiongroup("link", "embed"); ext->set_gui(false); diff --git a/src/ui/object-edit.cpp b/src/ui/object-edit.cpp index cf2c03396..b11a61710 100644 --- a/src/ui/object-edit.cpp +++ b/src/ui/object-edit.cpp @@ -812,7 +812,9 @@ ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*or g_assert(arc != NULL); gint side = sp_genericellipse_side(arc, p); - if(side != 0) { arc->setClosed(side == -1); } + if(side != 0) { arc->setArcType( (side == -1) ? + SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE : + SP_GENERIC_ELLIPSE_ARC_TYPE_ARC); } Geom::Point delta = p - Geom::Point(arc->cx.computed, arc->cy.computed); Geom::Scale sc(arc->rx.computed, arc->ry.computed); @@ -861,7 +863,9 @@ ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*orig g_assert(arc != NULL); gint side = sp_genericellipse_side(arc, p); - if(side != 0) { arc->setClosed(side == -1); } + if(side != 0) { arc->setArcType( (side == -1) ? + SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE : + SP_GENERIC_ELLIPSE_ARC_TYPE_ARC); } Geom::Point delta = p - Geom::Point(arc->cx.computed, arc->cy.computed); Geom::Scale sc(arc->rx.computed, arc->ry.computed); diff --git a/src/ui/tool/multi-path-manipulator.h b/src/ui/tool/multi-path-manipulator.h index c908cede2..4f152e0a2 100644 --- a/src/ui/tool/multi-path-manipulator.h +++ b/src/ui/tool/multi-path-manipulator.h @@ -82,8 +82,19 @@ private: template <typename R> void invokeForAll(R (PathManipulator::*method)()) { - for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) { - ((i->second.get())->*method)(); + for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ) { + // Sometimes the PathManipulator got freed at loop end, thus + // invalidating the iterator so make sure that next_i will + // be a valid iterator and then assign i to it. + MapType::iterator next_i = i; + ++next_i; + // i->second is a boost::shared_ptr so try to hold on to it so + // it won't get freed prematurely by the WriteXML() method or + // whatever. See https://bugs.launchpad.net/inkscape/+bug/1617615 + // Applicable to empty paths. + boost::shared_ptr<PathManipulator> hold(i->second); + ((hold.get())->*method)(); + i = next_i; } } template <typename R, typename A> diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h index 025c460e2..a05f0e3b9 100644 --- a/src/ui/tool/node.h +++ b/src/ui/tool/node.h @@ -20,12 +20,7 @@ #include <iosfwd> #include <stdexcept> #include <cstddef> - -#if __cplusplus >= 201103L #include <functional> -#else -#include <tr1/functional> -#endif #include <boost/enable_shared_from_this.hpp> #include <boost/shared_ptr.hpp> diff --git a/src/ui/tools/flood-tool.cpp b/src/ui/tools/flood-tool.cpp index 1801a0ea0..6e1d085aa 100644 --- a/src/ui/tools/flood-tool.cpp +++ b/src/ui/tools/flood-tool.cpp @@ -21,6 +21,7 @@ #include <config.h> #endif +#include <cmath> #include "trace/potrace/inkscape-potrace.h" #include <2geom/pathvector.h> #include <gdk/gdkkeysyms.h> @@ -186,6 +187,21 @@ inline unsigned char * get_trace_pixel(guchar *trace_px, int x, int y, int width } /** + * \brief Check whether two unsigned integers are close to each other + * + * \param[in] a The 1st unsigned int + * \param[in] b The 2nd unsigned int + * \param[in] d The threshold for comparison + * + * \return true if |a-b| <= d; false otherwise + */ +static bool compare_guint32(guint32 const a, guint32 const b, guint32 const d) +{ + const int difference = std::abs(static_cast<int>(a) - static_cast<int>(b)); + return difference <= d; +} + +/** * Compare a pixel in a pixel buffer with another pixel to determine if a point should be included in the fill operation. * @param check The pixel in the pixel buffer to check. * @param orig The original selected pixel to use as the fill target color. @@ -196,7 +212,6 @@ inline unsigned char * get_trace_pixel(guchar *trace_px, int x, int y, int width */ static bool compare_pixels(guint32 check, guint32 orig, guint32 merged_orig_pixel, guint32 dtc, int threshold, PaintBucketChannels method) { - int diff = 0; float hsl_check[3] = {0,0,0}, hsl_orig[3] = {0,0,0}; guint32 ac = 0, rc = 0, gc = 0, bc = 0; @@ -222,27 +237,35 @@ static bool compare_pixels(guint32 check, guint32 orig, guint32 merged_orig_pixe switch (method) { case FLOOD_CHANNELS_ALPHA: - return abs(static_cast<int>(ac) - ao) <= threshold; + return compare_guint32(ac, ao, threshold); case FLOOD_CHANNELS_R: - return abs(static_cast<int>(ac ? unpremul_alpha(rc, ac) : 0) - (ao ? unpremul_alpha(ro, ao) : 0)) <= threshold; + return compare_guint32(ac ? unpremul_alpha(rc, ac) : 0, + ao ? unpremul_alpha(ro, ao) : 0, + threshold); case FLOOD_CHANNELS_G: - return abs(static_cast<int>(ac ? unpremul_alpha(gc, ac) : 0) - (ao ? unpremul_alpha(go, ao) : 0)) <= threshold; + return compare_guint32(ac ? unpremul_alpha(gc, ac) : 0, + ao ? unpremul_alpha(go, ao) : 0, + threshold); case FLOOD_CHANNELS_B: - return abs(static_cast<int>(ac ? unpremul_alpha(bc, ac) : 0) - (ao ? unpremul_alpha(bo, ao) : 0)) <= threshold; + return compare_guint32(ac ? unpremul_alpha(bc, ac) : 0, + ao ? unpremul_alpha(bo, ao) : 0, + threshold); case FLOOD_CHANNELS_RGB: - guint32 amc, rmc, bmc, gmc; - //amc = 255*255 - (255-ac)*(255-ad); amc = (amc + 127) / 255; - //amc = (255-ac)*ad + 255*ac; amc = (amc + 127) / 255; - amc = 255; // Why are we looking at desktop? Cairo version ignores destop alpha - rmc = (255-ac)*rd + 255*rc; rmc = (rmc + 127) / 255; - gmc = (255-ac)*gd + 255*gc; gmc = (gmc + 127) / 255; - bmc = (255-ac)*bd + 255*bc; bmc = (bmc + 127) / 255; - - diff += abs(static_cast<int>(amc ? unpremul_alpha(rmc, amc) : 0) - (amop ? unpremul_alpha(rmop, amop) : 0)); - diff += abs(static_cast<int>(amc ? unpremul_alpha(gmc, amc) : 0) - (amop ? unpremul_alpha(gmop, amop) : 0)); - diff += abs(static_cast<int>(amc ? unpremul_alpha(bmc, amc) : 0) - (amop ? unpremul_alpha(bmop, amop) : 0)); - return ((diff / 3) <= ((threshold * 3) / 4)); - + { + guint32 amc, rmc, bmc, gmc; + //amc = 255*255 - (255-ac)*(255-ad); amc = (amc + 127) / 255; + //amc = (255-ac)*ad + 255*ac; amc = (amc + 127) / 255; + amc = 255; // Why are we looking at desktop? Cairo version ignores destop alpha + rmc = (255-ac)*rd + 255*rc; rmc = (rmc + 127) / 255; + gmc = (255-ac)*gd + 255*gc; gmc = (gmc + 127) / 255; + bmc = (255-ac)*bd + 255*bc; bmc = (bmc + 127) / 255; + + int diff = 0; // The total difference between each of the 3 color components + diff += std::abs(static_cast<int>(amc ? unpremul_alpha(rmc, amc) : 0) - static_cast<int>(amop ? unpremul_alpha(rmop, amop) : 0)); + diff += std::abs(static_cast<int>(amc ? unpremul_alpha(gmc, amc) : 0) - static_cast<int>(amop ? unpremul_alpha(gmop, amop) : 0)); + diff += std::abs(static_cast<int>(amc ? unpremul_alpha(bmc, amc) : 0) - static_cast<int>(amop ? unpremul_alpha(bmop, amop) : 0)); + return ((diff / 3) <= ((threshold * 3) / 4)); + } case FLOOD_CHANNELS_H: return ((int)(fabs(hsl_check[0] - hsl_orig[0]) * 100.0) <= threshold); case FLOOD_CHANNELS_S: diff --git a/src/ui/tools/freehand-base.h b/src/ui/tools/freehand-base.h index a3e7b42f9..3ee4cd7d0 100644 --- a/src/ui/tools/freehand-base.h +++ b/src/ui/tools/freehand-base.h @@ -76,8 +76,10 @@ public: GSList *white_curves; GSList *white_anchors; - //ALternative curve to use on continuing exisiting curve in case of bspline or spirolive - //because usigh anchor curves give memory and random bugs, - and obscure code- in some plataform reported by su_v in mac + // Alternative curve to use on continuing the exisiting curve in case of + // bspline or spirolive, because using anchor curves gives random memory + // bugs as reported by su_v when running this code on macOS (as well as + // making the code hard to understand). SPCurve *overwrite_curve; // Start anchor diff --git a/src/ui/tools/mesh-tool.cpp b/src/ui/tools/mesh-tool.cpp index e628094d9..ac43b6c9d 100644 --- a/src/ui/tools/mesh-tool.cpp +++ b/src/ui/tools/mesh-tool.cpp @@ -1137,7 +1137,7 @@ static void sp_mesh_new_default(MeshTool &rc) { Inkscape::FOR_FILL : Inkscape::FOR_STROKE; // Ensure mesh is immediately editable. - // Editting both fill and stroke at same time doesn't work well so avoid. + // Editing both fill and stroke at same time doesn't work well so avoid. if (fill_or_stroke == Inkscape::FOR_FILL) { prefs->setBool("/tools/mesh/edit_fill", true ); prefs->setBool("/tools/mesh/edit_stroke", false); diff --git a/src/ui/tools/select-tool.cpp b/src/ui/tools/select-tool.cpp index 6c450b3dc..bae1793ed 100644 --- a/src/ui/tools/select-tool.cpp +++ b/src/ui/tools/select-tool.cpp @@ -142,7 +142,7 @@ void SelectTool::setup() { this->_describer = new Inkscape::SelectionDescriber( desktop->selection, desktop->messageStack(), - _("Click selection to toggle scale/rotation handles"), + _("Click selection to toggle scale/rotation handles (or Shift+s)"), _("No objects selected. Click, Shift+click, Alt+scroll mouse on top of objects, or drag around objects to select.") ); diff --git a/src/ui/tools/text-tool.cpp b/src/ui/tools/text-tool.cpp index 559187764..649bbb045 100644 --- a/src/ui/tools/text-tool.cpp +++ b/src/ui/tools/text-tool.cpp @@ -48,6 +48,8 @@ #include "ui/control-manager.h" #include "verbs.h" #include "xml/node-event-vector.h" +#include "xml/attribute-record.h" +#include "xml/sp-css-attr.h" using Inkscape::ControlManager; using Inkscape::DocumentUndo; @@ -1357,6 +1359,59 @@ SPCSSAttr *sp_text_get_style_at_cursor(ToolBase const *ec) return NULL; } +static bool css_attrs_are_equal(SPCSSAttr const *first, SPCSSAttr const *second) +{ + Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrs = first->attributeList(); + for ( ; attrs ; attrs++) { + gchar const *other_attr = second->attribute(g_quark_to_string(attrs->key)); + if (other_attr == NULL || strcmp(attrs->value, other_attr)) + return false; + } + attrs = second->attributeList(); + for ( ; attrs ; attrs++) { + gchar const *other_attr = first->attribute(g_quark_to_string(attrs->key)); + if (other_attr == NULL || strcmp(attrs->value, other_attr)) + return false; + } + return true; +} + +std::vector<SPCSSAttr*> sp_text_get_selected_style(ToolBase const *ec, unsigned *k, int *b, std::vector<unsigned> *positions) +{ + std::vector<SPCSSAttr*> vec; + SPCSSAttr *css, *css_new; + TextTool *tc = SP_TEXT_CONTEXT(ec); + Inkscape::Text::Layout::iterator i = std::min(tc->text_sel_start, tc->text_sel_end); + SPObject const *obj = sp_te_object_at_position(tc->text, i); + if (obj) { + css = take_style_from_item(const_cast<SPObject*>(obj)); + } + vec.push_back(css); + positions->push_back(0); + i.nextCharacter(); + *k = 1; + *b = 1; + while (i != std::max(tc->text_sel_start, tc->text_sel_end)) + { + obj = sp_te_object_at_position(tc->text, i); + if (obj) { + css_new = take_style_from_item(const_cast<SPObject*>(obj)); + } + if(!css_attrs_are_equal(css, css_new)) + { + vec.push_back(css_new); + css = sp_repr_css_attr_new(); + sp_repr_css_merge(css, css_new); + positions->push_back(*k); + (*b)++; + } + i.nextCharacter(); + (*k)++; + } + positions->push_back(*k); + return vec; +} + /** Deletes the currently selected characters. Returns false if there is no text selection currently. @@ -1442,7 +1497,6 @@ bool TextTool::_styleSet(SPCSSAttr const *css) _("Set text style")); sp_text_context_update_cursor(this); sp_text_context_update_text_selection(this); - return true; } diff --git a/src/ui/tools/text-tool.h b/src/ui/tools/text-tool.h index 289ee180d..f2e6fea1a 100644 --- a/src/ui/tools/text-tool.h +++ b/src/ui/tools/text-tool.h @@ -94,6 +94,7 @@ private: bool sp_text_paste_inline(ToolBase *ec); Glib::ustring sp_text_get_selected_text(ToolBase const *ec); SPCSSAttr *sp_text_get_style_at_cursor(ToolBase const *ec); +std::vector<SPCSSAttr*> sp_text_get_selected_style(ToolBase const *ec, unsigned *k, int *b, std::vector<unsigned> *positions); bool sp_text_delete_selection(ToolBase *ec); void sp_text_context_place_cursor (TextTool *tc, SPObject *text, Inkscape::Text::Layout::iterator where); void sp_text_context_place_cursor_at (TextTool *tc, SPObject *text, Geom::Point const p); diff --git a/src/ui/tools/tool-base.cpp b/src/ui/tools/tool-base.cpp index aab256712..8c7f54d14 100644 --- a/src/ui/tools/tool-base.cpp +++ b/src/ui/tools/tool-base.cpp @@ -497,7 +497,7 @@ bool ToolBase::root_handler(GdkEvent* event) { Geom::Point const motion_w(event->motion.x, event->motion.y); Geom::Point const moved_w(motion_w - button_w); - this->desktop->scroll_world(moved_w, true); // we're still scrolling, do not redraw + this->desktop->scroll_relative(moved_w, true); // we're still scrolling, do not redraw ret = TRUE; } } else if (zoom_rb) { @@ -575,7 +575,7 @@ bool ToolBase::root_handler(GdkEvent* event) { Geom::Point const motion_w(event->button.x, event->button.y); Geom::Point const moved_w(motion_w - button_w); - this->desktop->scroll_world(moved_w); + this->desktop->scroll_relative(moved_w); desktop->updateNow(); ret = TRUE; } else if (zoom_rb == event->button.button) { @@ -675,7 +675,7 @@ bool ToolBase::root_handler(GdkEvent* event) { acceleration, desktop->getCanvas())); gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); - this->desktop->scroll_world(i, 0); + this->desktop->scroll_relative(Geom::Point(i, 0)); ret = TRUE; } break; @@ -688,7 +688,7 @@ bool ToolBase::root_handler(GdkEvent* event) { acceleration, desktop->getCanvas())); gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); - this->desktop->scroll_world(0, i); + this->desktop->scroll_relative(Geom::Point(0, i)); ret = TRUE; } break; @@ -701,7 +701,7 @@ bool ToolBase::root_handler(GdkEvent* event) { acceleration, desktop->getCanvas())); gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); - this->desktop->scroll_world(-i, 0); + this->desktop->scroll_relative(Geom::Point(-i, 0)); ret = TRUE; } break; @@ -714,7 +714,7 @@ bool ToolBase::root_handler(GdkEvent* event) { acceleration, desktop->getCanvas())); gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); - this->desktop->scroll_world(0, -i); + this->desktop->scroll_relative(Geom::Point(0, -i)); ret = TRUE; } break; @@ -856,11 +856,11 @@ bool ToolBase::root_handler(GdkEvent* event) { if (event->scroll.state & GDK_SHIFT_MASK) { switch (event->scroll.direction) { case GDK_SCROLL_UP: - desktop->scroll_world(wheel_scroll, 0); + desktop->scroll_relative(Geom::Point(wheel_scroll, 0)); break; case GDK_SCROLL_DOWN: - desktop->scroll_world(-wheel_scroll, 0); + desktop->scroll_relative(Geom::Point(-wheel_scroll, 0)); break; default: @@ -896,24 +896,24 @@ bool ToolBase::root_handler(GdkEvent* event) { } else { switch (event->scroll.direction) { case GDK_SCROLL_UP: - desktop->scroll_world(0, wheel_scroll); + desktop->scroll_relative(Geom::Point(0, wheel_scroll)); break; case GDK_SCROLL_DOWN: - desktop->scroll_world(0, -wheel_scroll); + desktop->scroll_relative(Geom::Point(0, -wheel_scroll)); break; case GDK_SCROLL_LEFT: - desktop->scroll_world(wheel_scroll, 0); + desktop->scroll_relative(Geom::Point(wheel_scroll, 0)); break; case GDK_SCROLL_RIGHT: - desktop->scroll_world(-wheel_scroll, 0); + desktop->scroll_relative(Geom::Point(-wheel_scroll, 0)); break; case GDK_SCROLL_SMOOTH: gdk_event_get_scroll_deltas(event, &delta_x, &delta_y); - desktop->scroll_world(delta_x, delta_y); + desktop->scroll_relative(Geom::Point(delta_x, delta_y)); break; } } diff --git a/src/ui/widget/panel.h b/src/ui/widget/panel.h index 9cbf39de9..b5498498d 100644 --- a/src/ui/widget/panel.h +++ b/src/ui/widget/panel.h @@ -104,6 +104,7 @@ public: void setDefaultResponse(int response_id); void setResponseSensitive(int response_id, bool setting); + /* Return signals. Signals emited by PanelDialog. */ virtual sigc::signal<void, SPDesktop *, SPDocument *> &signalDocumentReplaced(); virtual sigc::signal<void, SPDesktop *> &signalActivateDesktop(); virtual sigc::signal<void, SPDesktop *> &signalDeactiveDesktop(); |
