diff options
| author | Denis Declara <declara91@gmail.com> | 2012-03-26 15:59:00 +0000 |
|---|---|---|
| committer | Denis Declara <declara91@gmail.com> | 2012-03-26 15:59:00 +0000 |
| commit | 8874f80dd60361ed721554497862ce3a6fb5ea8e (patch) | |
| tree | fe114b3559c430d1a90f073a3458f4bd1c6d2285 /src/ui | |
| parent | Added anchor-selection widget, it doesn't do much at the moment, next step wi... (diff) | |
| parent | powerstroke: spiro join: more robust detection of tangent direction. fixes in... (diff) | |
| download | inkscape-8874f80dd60361ed721554497862ce3a6fb5ea8e.tar.gz inkscape-8874f80dd60361ed721554497862ce3a6fb5ea8e.zip | |
Trunk merge
(bzr r11073.1.4)
Diffstat (limited to 'src/ui')
| -rw-r--r-- | src/ui/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/ui/context-menu.cpp | 1372 | ||||
| -rw-r--r-- | src/ui/dialog/Makefile_insert | 2 | ||||
| -rw-r--r-- | src/ui/dialog/clonetiler.cpp | 2833 | ||||
| -rw-r--r-- | src/ui/dialog/clonetiler.h | 183 | ||||
| -rw-r--r-- | src/ui/dialog/dialog-manager.cpp | 3 | ||||
| -rw-r--r-- | src/ui/dialog/find.cpp | 887 | ||||
| -rw-r--r-- | src/ui/dialog/find.h | 282 | ||||
| -rw-r--r-- | src/ui/dialog/guides.cpp | 6 | ||||
| -rw-r--r-- | src/ui/dialog/transformation.cpp | 2 | ||||
| -rw-r--r-- | src/ui/dialog/xml-tree.cpp | 23 | ||||
| -rw-r--r-- | src/ui/dialog/xml-tree.h | 7 | ||||
| -rw-r--r-- | src/ui/widget/button.cpp | 16 | ||||
| -rw-r--r-- | src/ui/widget/button.h | 14 | ||||
| -rw-r--r-- | src/ui/widget/entry.cpp | 1 | ||||
| -rw-r--r-- | src/ui/widget/entry.h | 2 |
16 files changed, 4672 insertions, 963 deletions
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index e21c3795f..bfd8cca95 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -25,6 +25,7 @@ set(ui_SRC dialog/align-and-distribute.cpp dialog/calligraphic-profile-rename.cpp dialog/color-item.cpp + dialog/clonetiler.cpp dialog/debug.cpp dialog/desktop-tracker.cpp dialog/dialog-manager.cpp @@ -135,6 +136,7 @@ set(ui_SRC dialog/behavior.h dialog/calligraphic-profile-rename.h dialog/color-item.h + dialog/clonetiler.h dialog/debug.h dialog/desktop-tracker.h dialog/dialog-manager.h diff --git a/src/ui/context-menu.cpp b/src/ui/context-menu.cpp index bf116f5fb..9e5c65af9 100644 --- a/src/ui/context-menu.cpp +++ b/src/ui/context-menu.cpp @@ -1,679 +1,693 @@ -/*
- * Unser-interface related object extension
- *
- * Authors:
- * Lauris Kaplinski <lauris@kaplinski.com>
- * Jon A. Cruz <jon@joncruz.org>
- * Abhishek Sharma
- *
- * This code is in public domain
- */
-
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif
-
-#include "ui/dialog/dialog-manager.h"
-#include "context-menu.h"
-#include "../xml/repr.h"
-#include "desktop.h"
-#include "document.h"
-#include "document-undo.h"
-#include "helper/action.h" //sp_action_perform
-#include "inkscape.h"
-#include "message-stack.h"
-#include "preferences.h"
-#include "verbs.h"
-
-using Inkscape::DocumentUndo;
-
-static void sp_object_type_menu(GType type, SPObject *object, SPDesktop *desktop, GtkMenu *menu);
-
-/* Append object-specific part to context menu */
-
-void sp_object_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu)
-{
- GObjectClass *klass;
- klass = G_OBJECT_GET_CLASS(object);
- while (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_OBJECT)) {
- GType type;
- type = G_TYPE_FROM_CLASS(klass);
- sp_object_type_menu(type, object, desktop, menu);
- klass = (GObjectClass*)g_type_class_peek_parent(klass);
- }
-}
-
-/* Implementation */
-
-#include <gtk/gtk.h>
-#include <glibmm/i18n.h>
-
-#include "selection.h"
-#include "selection-chemistry.h"
-#include "sp-anchor.h"
-#include "sp-clippath.h"
-#include "sp-image.h"
-#include "sp-mask.h"
-#include "sp-path.h"
-#include "sp-text.h"
-#include "desktop-handles.h"
-#include "ui/dialog/object-attributes.h"
-#include "ui/dialog/object-properties.h"
-#include "ui/dialog/spellcheck.h"
-
-
-static void sp_item_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
-static void sp_group_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
-static void sp_anchor_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
-static void sp_image_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
-static void sp_shape_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
-static void sp_text_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
-
-static void sp_object_type_menu(GType type, SPObject *object, SPDesktop *desktop, GtkMenu *menu)
-{
- static GHashTable *t2m = NULL;
- void (* handler)(SPObject *object, SPDesktop *desktop, GtkMenu *menu);
- if (!t2m) {
- t2m = g_hash_table_new(NULL, NULL);
- g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_ITEM), (void*)sp_item_menu);
- g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_GROUP), (void*)sp_group_menu);
- g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_ANCHOR), (void*)sp_anchor_menu);
- g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_IMAGE), (void*)sp_image_menu);
- g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_SHAPE), (void*)sp_shape_menu);
- g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_TEXT), (void*)sp_text_menu);
- }
- handler = (void (*)(SPObject*, SPDesktop*, GtkMenu*))g_hash_table_lookup(t2m, GUINT_TO_POINTER(type));
- if (handler) handler(object, desktop, menu);
-}
-
-/* SPItem */
-
-static void sp_item_properties(GtkMenuItem *menuitem, SPItem *item);
-static void sp_item_select_this(GtkMenuItem *menuitem, SPItem *item);
-static void sp_item_create_link(GtkMenuItem *menuitem, SPItem *item);
-static void sp_set_mask(GtkMenuItem *menuitem, SPItem *item);
-static void sp_release_mask(GtkMenuItem *menuitem, SPItem *item);
-static void sp_set_clip(GtkMenuItem *menuitem, SPItem *item);
-static void sp_release_clip(GtkMenuItem *menuitem, SPItem *item);
-
-/* Generate context menu item section */
-static void sp_item_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m)
-{
- SPItem *item;
- GtkWidget *w;
-
- item = (SPItem *) object;
-
- /* Item dialog */
- w = gtk_menu_item_new_with_mnemonic(_("_Object Properties..."));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_item_properties), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- /* Separator */
- w = gtk_menu_item_new();
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- /* Select item */
- w = gtk_menu_item_new_with_mnemonic(_("_Select This"));
- if (sp_desktop_selection(desktop)->includes(item)) {
- gtk_widget_set_sensitive(w, FALSE);
- } else {
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_item_select_this), item);
- }
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- /* Create link */
- w = gtk_menu_item_new_with_mnemonic(_("_Create Link"));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_item_create_link), item);
- gtk_widget_set_sensitive(w, !SP_IS_ANCHOR(item));
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- /* Set mask */
- w = gtk_menu_item_new_with_mnemonic(_("Set Mask"));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_set_mask), item);
- if ((item && item->mask_ref && item->mask_ref->getObject()) || (item->clip_ref && item->clip_ref->getObject())) {
- gtk_widget_set_sensitive(w, FALSE);
- } else {
- gtk_widget_set_sensitive(w, TRUE);
- }
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- /* Release mask */
- w = gtk_menu_item_new_with_mnemonic(_("Release Mask"));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_release_mask), item);
- if (item && item->mask_ref && item->mask_ref->getObject()) {
- gtk_widget_set_sensitive(w, TRUE);
- } else {
- gtk_widget_set_sensitive(w, FALSE);
- }
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- /* Set Clip */
- w = gtk_menu_item_new_with_mnemonic(_("Set _Clip"));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_set_clip), item);
- if ((item && item->mask_ref && item->mask_ref->getObject()) || (item->clip_ref && item->clip_ref->getObject())) {
- gtk_widget_set_sensitive(w, FALSE);
- } else {
- gtk_widget_set_sensitive(w, TRUE);
- }
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- /* Release Clip */
- w = gtk_menu_item_new_with_mnemonic(_("Release C_lip"));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_release_clip), item);
- if (item && item->clip_ref && item->clip_ref->getObject()) {
- gtk_widget_set_sensitive(w, TRUE);
- } else {
- gtk_widget_set_sensitive(w, FALSE);
- }
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
-
-}
-
-static void sp_item_properties(GtkMenuItem *menuitem, SPItem *item)
-{
- SPDesktop *desktop;
-
- g_assert(SP_IS_ITEM(item));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- sp_desktop_selection(desktop)->set(item);
-
- // sp_item_dialog();
- desktop->_dlg_mgr->showDialog("ObjectProperties");
-}
-
-
-static void sp_set_mask(GtkMenuItem *menuitem, SPItem *item)
-{
- SPDesktop *desktop;
-
- g_assert(SP_IS_ITEM(item));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- sp_selection_set_mask(desktop, false, false);
-}
-
-
-static void sp_release_mask(GtkMenuItem *menuitem, SPItem *item)
-{
- SPDesktop *desktop;
-
- g_assert(SP_IS_ITEM(item));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- sp_selection_unset_mask(desktop, false);
-}
-
-
-static void sp_set_clip(GtkMenuItem *menuitem, SPItem *item)
-{
- SPDesktop *desktop;
-
- g_assert(SP_IS_ITEM(item));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- sp_selection_set_mask(desktop, true, false);
-}
-
-
-static void sp_release_clip(GtkMenuItem *menuitem, SPItem *item)
-{
- SPDesktop *desktop;
-
- g_assert(SP_IS_ITEM(item));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- sp_selection_unset_mask(desktop, true);
-}
-
-
-static void sp_item_select_this(GtkMenuItem *menuitem, SPItem *item)
-{
- SPDesktop *desktop;
-
- g_assert(SP_IS_ITEM(item));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- sp_desktop_selection(desktop)->set(item);
-}
-
-static void sp_item_create_link(GtkMenuItem *menuitem, SPItem *item)
-{
- g_assert(SP_IS_ITEM(item));
- g_assert(!SP_IS_ANCHOR(item));
-
- SPDesktop *desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
- Inkscape::XML::Node *repr = xml_doc->createElement("svg:a");
- item->parent->getRepr()->addChild(repr, item->getRepr());
- SPObject *object = item->document->getObjectByRepr(repr);
- g_return_if_fail(SP_IS_ANCHOR(object));
-
- const char *id = item->getRepr()->attribute("id");
- Inkscape::XML::Node *child = item->getRepr()->duplicate(xml_doc);
- item->deleteObject(false);
- repr->addChild(child, NULL);
- child->setAttribute("id", id);
-
- Inkscape::GC::release(repr);
- Inkscape::GC::release(child);
-
- DocumentUndo::done(object->document, SP_VERB_NONE,
- _("Create link"));
-
- sp_desktop_selection(desktop)->set(SP_ITEM(object));
- desktop->_dlg_mgr->showDialog("ObjectAttributes");
-}
-
-/* SPGroup */
-
-static void sp_item_group_ungroup_activate(GtkMenuItem *menuitem, SPGroup *group);
-
-static void sp_group_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu)
-{
- SPItem *item=SP_ITEM(object);
- GtkWidget *w;
-
- /* "Ungroup" */
- w = gtk_menu_item_new_with_mnemonic(_("_Ungroup"));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_item_group_ungroup_activate), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(menu), w);
-}
-
-static void sp_item_group_ungroup_activate(GtkMenuItem *menuitem, SPGroup *group)
-{
- SPDesktop *desktop;
- GSList *children;
-
- g_assert(SP_IS_GROUP(group));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- children = NULL;
- sp_item_group_ungroup(group, &children);
-
- sp_desktop_selection(desktop)->setList(children);
- g_slist_free(children);
-}
-
-/* SPAnchor */
-
-static void sp_anchor_link_properties(GtkMenuItem *menuitem, SPAnchor *anchor);
-static void sp_anchor_link_follow(GtkMenuItem *menuitem, SPAnchor *anchor);
-static void sp_anchor_link_remove(GtkMenuItem *menuitem, SPAnchor *anchor);
-
-static void sp_anchor_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m)
-{
- SPItem *item;
- GtkWidget *w;
-
- item = (SPItem *) object;
-
- /* Link dialog */
- w = gtk_menu_item_new_with_mnemonic(_("Link _Properties..."));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_anchor_link_properties), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- /* Select item */
- w = gtk_menu_item_new_with_mnemonic(_("_Follow Link"));
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_anchor_link_follow), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- /* Reset transformations */
- w = gtk_menu_item_new_with_mnemonic(_("_Remove Link"));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_anchor_link_remove), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
-}
-
-static void sp_anchor_link_properties(GtkMenuItem *menuitem, SPAnchor */*anchor*/)
-{
- SPDesktop *desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
- desktop->_dlg_mgr->showDialog("ObjectAttributes");
-}
-
-static void sp_anchor_link_follow(GtkMenuItem */*menuitem*/, SPAnchor *anchor)
-{
- g_return_if_fail(anchor != NULL);
- g_return_if_fail(SP_IS_ANCHOR(anchor));
-
- /* shell out to an external browser here */
-}
-
-static void sp_anchor_link_remove(GtkMenuItem */*menuitem*/, SPAnchor *anchor)
-{
- GSList *children;
-
- g_return_if_fail(anchor != NULL);
- g_return_if_fail(SP_IS_ANCHOR(anchor));
-
- children = NULL;
- sp_item_group_ungroup(SP_GROUP(anchor), &children);
-
- g_slist_free(children);
-}
-
-/* Image */
-
-static void sp_image_image_properties(GtkMenuItem *menuitem, SPAnchor *anchor);
-static void sp_image_image_edit(GtkMenuItem *menuitem, SPAnchor *anchor);
-static void sp_image_image_embed(GtkMenuItem *menuitem, SPItem *item);
-static void sp_image_image_extract(GtkMenuItem *menuitem, SPItem *item);
-
-static void sp_image_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m)
-{
- SPItem *item = SP_ITEM(object);
- GtkWidget *w;
- Inkscape::XML::Node *ir = object->getRepr();
- const gchar *href = ir->attribute("xlink:href");
-
- /* Image properties */
- w = gtk_menu_item_new_with_mnemonic(_("Image _Properties..."));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_image_image_properties), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
-
- /* Edit externally */
- w = gtk_menu_item_new_with_mnemonic(_("Edit Externally..."));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_image_image_edit), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- if ( (!href) || ((strncmp(href, "data:", 5) == 0)) ) {
- gtk_widget_set_sensitive( w, FALSE );
- }
-
- /* Embed image */
- if (Inkscape::Verb::getbyid( "org.ekips.filter.embedselectedimages" )) {
- w = gtk_menu_item_new_with_mnemonic(C_("Context menu", "Embed Image"));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_image_image_embed), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- if ( (!href) || ((strncmp(href, "data:", 5) == 0)) ) {
- gtk_widget_set_sensitive( w, FALSE );
- }
- }
-
- /* Extract image */
- if (Inkscape::Verb::getbyid( "org.ekips.filter.extractimage" )) {
- w = gtk_menu_item_new_with_mnemonic(C_("Context menu", "Extract Image"));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_image_image_extract), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
- if ( (!href) || ((strncmp(href, "data:", 5) != 0)) ) {
- gtk_widget_set_sensitive( w, FALSE );
- }
- }
-}
-
-/* Image Properties entry */
-static void sp_image_image_properties(GtkMenuItem *menuitem, SPAnchor */*anchor*/)
-{
- SPDesktop *desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
- desktop->_dlg_mgr->showDialog("ObjectAttributes");
-}
-
-static gchar* getImageEditorName() {
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- gchar* value = 0;
- Glib::ustring choices = prefs->getString("/options/bitmapeditor/value");
- if (!choices.empty()) {
- value = g_strdup(choices.c_str());
- }
- if (!value) {
- value = g_strdup("gimp");
- }
- return value;
-}
-
-/* Edit Externally entry */
-static void sp_image_image_edit(GtkMenuItem *menuitem, SPAnchor *anchor)
-{
- SPObject* obj = anchor;
- Inkscape::XML::Node *ir = obj->getRepr();
- const gchar *href = ir->attribute("xlink:href");
-
- GError* errThing = 0;
- Glib::ustring cmdline = getImageEditorName();
- Glib::ustring name;
- Glib::ustring fullname;
-
-#ifdef WIN32
- // g_spawn_command_line_sync parsing is done according to Unix shell rules,
- // not Windows command interpreter rules. Thus we need to enclose the
- // executable path with sigle quotes.
- int index = cmdline.find(".exe");
- if ( index < 0 ) index = cmdline.find(".bat");
- if ( index < 0 ) index = cmdline.find(".com");
- if ( index >= 0 ) {
- Glib::ustring editorBin = cmdline.substr(0, index + 4).c_str();
- Glib::ustring args = cmdline.substr(index + 4, cmdline.length()).c_str();
- editorBin.insert(0, "'");
- editorBin.append("'");
- cmdline = editorBin;
- cmdline.append(args);
- } else {
- // Enclose the whole command line if no executable path can be extracted.
- cmdline.insert(0, "'");
- cmdline.append("'");
- }
-#endif
-
- if (strncmp (href,"file:",5) == 0) {
- // URI to filename conversion
- name = g_filename_from_uri(href, NULL, NULL);
- } else {
- name.append(href);
- }
-
- if (Glib::path_is_absolute(name)) {
- fullname = name;
- } else if (SP_ACTIVE_DOCUMENT->getBase()) {
- fullname = Glib::build_filename(SP_ACTIVE_DOCUMENT->getBase(), name);
- } else {
- fullname = Glib::build_filename(Glib::get_current_dir(), name);
- }
-
- cmdline.append(" '");
- cmdline.append(fullname.c_str());
- cmdline.append("'");
-
- //g_warning("##Command line: %s\n", cmdline.c_str());
-
- g_spawn_command_line_async(cmdline.c_str(),
- &errThing);
-
- if ( errThing ) {
- g_warning("Problem launching editor (%d). %s", errThing->code, errThing->message);
- SPDesktop *desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, errThing->message);
- g_error_free(errThing);
- errThing = 0;
- }
-}
-
-/* Embed Image entry */
-static void sp_image_image_embed(GtkMenuItem *menuitem, SPItem *item)
-{
- SPDesktop *desktop;
-
- g_assert(SP_IS_ITEM(item));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- if (sp_desktop_selection(desktop)->isEmpty()) {
- sp_desktop_selection(desktop)->set(item);
- }
-
- Inkscape::Verb *verb = Inkscape::Verb::getbyid( "org.ekips.filter.embedselectedimages" );
- if (verb) {
- SPAction *action = verb->get_action(desktop);
- if (action) {
- sp_action_perform(action, NULL);
- }
- }
-}
-
-/* Extract Image entry */
-static void sp_image_image_extract(GtkMenuItem *menuitem, SPItem *item)
-{
- SPDesktop *desktop;
-
- g_assert(SP_IS_ITEM(item));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- if (sp_desktop_selection(desktop)->isEmpty()) {
- sp_desktop_selection(desktop)->set(item);
- }
-
- Inkscape::Verb *verb = Inkscape::Verb::getbyid( "org.ekips.filter.extractimage" );
- if (verb) {
- SPAction *action = verb->get_action(desktop);
- if (action) {
- sp_action_perform(action, NULL);
- }
- }
-}
-
-/* Fill and Stroke entry */
-static void sp_fill_settings(GtkMenuItem *menuitem, SPItem *item)
-{
- SPDesktop *desktop;
-
- g_assert(SP_IS_ITEM(item));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- if (sp_desktop_selection(desktop)->isEmpty()) {
- sp_desktop_selection(desktop)->set(item);
- }
-
- desktop->_dlg_mgr->showDialog("FillAndStroke");
-}
-
-/* SPShape */
-static void sp_shape_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m)
-{
- SPItem *item;
- GtkWidget *w;
-
- item = (SPItem *) object;
-
- /* Item dialog */
- w = gtk_menu_item_new_with_mnemonic(_("_Fill and Stroke..."));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_fill_settings), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
-}
-
-/* Edit Text entry */
-static void sp_text_settings(GtkMenuItem *menuitem, SPItem *item)
-{
- SPDesktop *desktop;
-
- g_assert(SP_IS_ITEM(item));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- if (sp_desktop_selection(desktop)->isEmpty()) {
- sp_desktop_selection(desktop)->set(item);
- }
-
- desktop->_dlg_mgr->showDialog("TextFont");
-}
-
-/* Spellcheck entry */
-static void sp_spellcheck_settings(GtkMenuItem *menuitem, SPItem *item)
-{
- SPDesktop *desktop;
-
- g_assert(SP_IS_ITEM(item));
-
- desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop");
- g_return_if_fail(desktop != NULL);
-
- if (sp_desktop_selection(desktop)->isEmpty()) {
- sp_desktop_selection(desktop)->set(item);
- }
-
- desktop->_dlg_mgr->showDialog("SpellCheck");
-}
-
-/* SPText */
-static void sp_text_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m)
-{
- SPItem *item;
- GtkWidget *w;
-
- item = (SPItem *) object;
-
- /* Fill and Stroke dialog */
- w = gtk_menu_item_new_with_mnemonic(_("_Fill and Stroke..."));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_fill_settings), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
-
- /* Edit Text dialog */
- w = gtk_menu_item_new_with_mnemonic(_("_Text and Font..."));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_text_settings), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
-
- /* Spellcheck dialog */
- w = gtk_menu_item_new_with_mnemonic(_("Check Spellin_g..."));
- g_object_set_data(G_OBJECT(w), "desktop", desktop);
- g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_spellcheck_settings), item);
- gtk_widget_show(w);
- gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
-}
-/*
- 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 :
+/* + * Unser-interface related object extension + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * This code is in public domain + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "ui/dialog/dialog-manager.h" +#include "context-menu.h" +#include "../xml/repr.h" +#include "desktop.h" +#include "document.h" +#include "document-undo.h" +#include "helper/action.h" //sp_action_perform +#include "inkscape.h" +#include "message-stack.h" +#include "preferences.h" +#include "verbs.h" + +using Inkscape::DocumentUndo; + +static void sp_object_type_menu(GType type, SPObject *object, SPDesktop *desktop, GtkMenu *menu); + +/* Append object-specific part to context menu */ + +void sp_object_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu) +{ + GObjectClass *klass; + klass = G_OBJECT_GET_CLASS(object); + while (G_TYPE_CHECK_CLASS_TYPE((klass), SP_TYPE_OBJECT)) { + GType type; + type = G_TYPE_FROM_CLASS(klass); + sp_object_type_menu(type, object, desktop, menu); + klass = (GObjectClass*)g_type_class_peek_parent(klass); + } +} + +/* Implementation */ + +#include <gtk/gtk.h> +#include <glibmm/i18n.h> + +#include "selection.h" +#include "selection-chemistry.h" +#include "sp-anchor.h" +#include "sp-clippath.h" +#include "sp-image.h" +#include "sp-mask.h" +#include "sp-path.h" +#include "sp-text.h" +#include "desktop-handles.h" +#include "ui/dialog/object-attributes.h" +#include "ui/dialog/object-properties.h" +#include "ui/dialog/spellcheck.h" + + +static void sp_item_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu); +static void sp_group_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu); +static void sp_anchor_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu); +static void sp_image_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu); +static void sp_shape_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu); +static void sp_text_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu); + +static void sp_object_type_menu(GType type, SPObject *object, SPDesktop *desktop, GtkMenu *menu) +{ + static GHashTable *t2m = NULL; + void (* handler)(SPObject *object, SPDesktop *desktop, GtkMenu *menu); + if (!t2m) { + t2m = g_hash_table_new(NULL, NULL); + g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_ITEM), (void*)sp_item_menu); + g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_GROUP), (void*)sp_group_menu); + g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_ANCHOR), (void*)sp_anchor_menu); + g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_IMAGE), (void*)sp_image_menu); + g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_SHAPE), (void*)sp_shape_menu); + g_hash_table_insert(t2m, GUINT_TO_POINTER(SP_TYPE_TEXT), (void*)sp_text_menu); + } + handler = (void (*)(SPObject*, SPDesktop*, GtkMenu*))g_hash_table_lookup(t2m, GUINT_TO_POINTER(type)); + if (handler) handler(object, desktop, menu); +} + +/* SPItem */ + +static void sp_item_properties(GtkMenuItem *menuitem, SPItem *item); +static void sp_item_select_this(GtkMenuItem *menuitem, SPItem *item); +static void sp_item_create_link(GtkMenuItem *menuitem, SPItem *item); +static void sp_set_mask(GtkMenuItem *menuitem, SPItem *item); +static void sp_release_mask(GtkMenuItem *menuitem, SPItem *item); +static void sp_set_clip(GtkMenuItem *menuitem, SPItem *item); +static void sp_release_clip(GtkMenuItem *menuitem, SPItem *item); + +/* Generate context menu item section */ +static void sp_item_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m) +{ + SPItem *item; + GtkWidget *w; + + item = (SPItem *) object; + + /* Item dialog */ + w = gtk_menu_item_new_with_mnemonic(_("_Object Properties...")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_item_properties), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + /* Separator */ + w = gtk_menu_item_new(); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + /* Select item */ + w = gtk_menu_item_new_with_mnemonic(_("_Select This")); + if (sp_desktop_selection(desktop)->includes(item)) { + gtk_widget_set_sensitive(w, FALSE); + } else { + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_item_select_this), item); + } + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + /* Create link */ + w = gtk_menu_item_new_with_mnemonic(_("_Create Link")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_item_create_link), item); + gtk_widget_set_sensitive(w, !SP_IS_ANCHOR(item)); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + /* Set mask */ + w = gtk_menu_item_new_with_mnemonic(_("Set Mask")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_set_mask), item); + if ((item && item->mask_ref && item->mask_ref->getObject()) || (item->clip_ref && item->clip_ref->getObject())) { + gtk_widget_set_sensitive(w, FALSE); + } else { + gtk_widget_set_sensitive(w, TRUE); + } + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + /* Release mask */ + w = gtk_menu_item_new_with_mnemonic(_("Release Mask")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_release_mask), item); + if (item && item->mask_ref && item->mask_ref->getObject()) { + gtk_widget_set_sensitive(w, TRUE); + } else { + gtk_widget_set_sensitive(w, FALSE); + } + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + /* Set Clip */ + w = gtk_menu_item_new_with_mnemonic(_("Set _Clip")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_set_clip), item); + if ((item && item->mask_ref && item->mask_ref->getObject()) || (item->clip_ref && item->clip_ref->getObject())) { + gtk_widget_set_sensitive(w, FALSE); + } else { + gtk_widget_set_sensitive(w, TRUE); + } + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + /* Release Clip */ + w = gtk_menu_item_new_with_mnemonic(_("Release C_lip")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_release_clip), item); + if (item && item->clip_ref && item->clip_ref->getObject()) { + gtk_widget_set_sensitive(w, TRUE); + } else { + gtk_widget_set_sensitive(w, FALSE); + } + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + +} + +static void sp_item_properties(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + sp_desktop_selection(desktop)->set(item); + + // sp_item_dialog(); + desktop->_dlg_mgr->showDialog("ObjectProperties"); +} + + +static void sp_set_mask(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + sp_selection_set_mask(desktop, false, false); +} + + +static void sp_release_mask(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + sp_selection_unset_mask(desktop, false); +} + + +static void sp_set_clip(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + sp_selection_set_mask(desktop, true, false); +} + + +static void sp_release_clip(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + sp_selection_unset_mask(desktop, true); +} + + +static void sp_item_select_this(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + sp_desktop_selection(desktop)->set(item); +} + +static void sp_item_create_link(GtkMenuItem *menuitem, SPItem *item) +{ + g_assert(SP_IS_ITEM(item)); + g_assert(!SP_IS_ANCHOR(item)); + + SPDesktop *desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:a"); + item->parent->getRepr()->addChild(repr, item->getRepr()); + SPObject *object = item->document->getObjectByRepr(repr); + g_return_if_fail(SP_IS_ANCHOR(object)); + + const char *id = item->getRepr()->attribute("id"); + Inkscape::XML::Node *child = item->getRepr()->duplicate(xml_doc); + item->deleteObject(false); + repr->addChild(child, NULL); + child->setAttribute("id", id); + + Inkscape::GC::release(repr); + Inkscape::GC::release(child); + + DocumentUndo::done(object->document, SP_VERB_NONE, + _("Create link")); + + sp_desktop_selection(desktop)->set(SP_ITEM(object)); + desktop->_dlg_mgr->showDialog("ObjectAttributes"); +} + +/* SPGroup */ + +static void sp_item_group_ungroup_activate(GtkMenuItem *menuitem, SPGroup *group); + +static void sp_group_menu(SPObject *object, SPDesktop *desktop, GtkMenu *menu) +{ + SPItem *item=SP_ITEM(object); + GtkWidget *w; + + /* "Ungroup" */ + w = gtk_menu_item_new_with_mnemonic(_("_Ungroup")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_item_group_ungroup_activate), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), w); +} + +static void sp_item_group_ungroup_activate(GtkMenuItem *menuitem, SPGroup *group) +{ + SPDesktop *desktop; + GSList *children; + + g_assert(SP_IS_GROUP(group)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + children = NULL; + sp_item_group_ungroup(group, &children); + + sp_desktop_selection(desktop)->setList(children); + g_slist_free(children); +} + +/* SPAnchor */ + +static void sp_anchor_link_properties(GtkMenuItem *menuitem, SPAnchor *anchor); +static void sp_anchor_link_follow(GtkMenuItem *menuitem, SPAnchor *anchor); +static void sp_anchor_link_remove(GtkMenuItem *menuitem, SPAnchor *anchor); + +static void sp_anchor_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m) +{ + SPItem *item; + GtkWidget *w; + + item = (SPItem *) object; + + /* Link dialog */ + w = gtk_menu_item_new_with_mnemonic(_("Link _Properties...")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_anchor_link_properties), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + /* Select item */ + w = gtk_menu_item_new_with_mnemonic(_("_Follow Link")); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_anchor_link_follow), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + /* Reset transformations */ + w = gtk_menu_item_new_with_mnemonic(_("_Remove Link")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_anchor_link_remove), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); +} + +static void sp_anchor_link_properties(GtkMenuItem *menuitem, SPAnchor */*anchor*/) +{ + SPDesktop *desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + desktop->_dlg_mgr->showDialog("ObjectAttributes"); +} + +static void sp_anchor_link_follow(GtkMenuItem */*menuitem*/, SPAnchor *anchor) +{ + g_return_if_fail(anchor != NULL); + g_return_if_fail(SP_IS_ANCHOR(anchor)); + + /* shell out to an external browser here */ +} + +static void sp_anchor_link_remove(GtkMenuItem */*menuitem*/, SPAnchor *anchor) +{ + GSList *children; + + g_return_if_fail(anchor != NULL); + g_return_if_fail(SP_IS_ANCHOR(anchor)); + + children = NULL; + sp_item_group_ungroup(SP_GROUP(anchor), &children); + + g_slist_free(children); +} + +/* Image */ + +static void sp_image_image_properties(GtkMenuItem *menuitem, SPAnchor *anchor); +static void sp_image_image_edit(GtkMenuItem *menuitem, SPItem *item); +static void sp_image_image_embed(GtkMenuItem *menuitem, SPItem *item); +static void sp_image_image_extract(GtkMenuItem *menuitem, SPItem *item); + +static void sp_image_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m) +{ + SPItem *item = SP_ITEM(object); + GtkWidget *w; + Inkscape::XML::Node *ir = object->getRepr(); + const gchar *href = ir->attribute("xlink:href"); + + /* Image properties */ + w = gtk_menu_item_new_with_mnemonic(_("Image _Properties...")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_image_image_properties), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + + /* Edit externally */ + w = gtk_menu_item_new_with_mnemonic(_("Edit Externally...")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_image_image_edit), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + if ( (!href) || ((strncmp(href, "data:", 5) == 0)) ) { + gtk_widget_set_sensitive( w, FALSE ); + } + + /* Embed image */ + if (Inkscape::Verb::getbyid( "org.ekips.filter.embedselectedimages" )) { + w = gtk_menu_item_new_with_mnemonic(C_("Context menu", "Embed Image")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_image_image_embed), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + if ( (!href) || ((strncmp(href, "data:", 5) == 0)) ) { + gtk_widget_set_sensitive( w, FALSE ); + } + } + + /* Extract image */ + if (Inkscape::Verb::getbyid( "org.ekips.filter.extractimage" )) { + w = gtk_menu_item_new_with_mnemonic(C_("Context menu", "Extract Image...")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_image_image_extract), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + if ( (!href) || ((strncmp(href, "data:", 5) != 0)) ) { + gtk_widget_set_sensitive( w, FALSE ); + } + } +} + +/* Image Properties entry */ +static void sp_image_image_properties(GtkMenuItem *menuitem, SPAnchor */*anchor*/) +{ + SPDesktop *desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + desktop->_dlg_mgr->showDialog("ObjectAttributes"); +} + +static gchar* getImageEditorName() { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gchar* value = 0; + Glib::ustring choices = prefs->getString("/options/bitmapeditor/value"); + if (!choices.empty()) { + value = g_strdup(choices.c_str()); + } + if (!value) { + value = g_strdup("gimp"); + } + return value; +} + +/* Edit Externally entry */ +static void sp_image_image_edit(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop = NULL; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + if (sp_desktop_selection(desktop)->isEmpty()) { + sp_desktop_selection(desktop)->set(item); + } + + GSList const *selected = sp_desktop_selection(desktop)->itemList(); + + GError* errThing = 0; + Glib::ustring cmdline = getImageEditorName(); + Glib::ustring name; + Glib::ustring fullname; + +#ifdef WIN32 + // g_spawn_command_line_sync parsing is done according to Unix shell rules, + // not Windows command interpreter rules. Thus we need to enclose the + // executable path with sigle quotes. + int index = cmdline.find(".exe"); + if ( index < 0 ) index = cmdline.find(".bat"); + if ( index < 0 ) index = cmdline.find(".com"); + if ( index >= 0 ) { + Glib::ustring editorBin = cmdline.substr(0, index + 4).c_str(); + Glib::ustring args = cmdline.substr(index + 4, cmdline.length()).c_str(); + editorBin.insert(0, "'"); + editorBin.append("'"); + cmdline = editorBin; + cmdline.append(args); + } else { + // Enclose the whole command line if no executable path can be extracted. + cmdline.insert(0, "'"); + cmdline.append("'"); + } +#endif + + for (GSList const *iter = selected; iter != NULL; iter = iter->next) { + Inkscape::XML::Node *ir = SP_ITEM(iter->data)->getRepr(); + const gchar *href = ir->attribute("xlink:href"); + + if (strncmp (href,"file:",5) == 0) { + // URI to filename conversion + name = g_filename_from_uri(href, NULL, NULL); + } else { + name.append(href); + } + + if (Glib::path_is_absolute(name)) { + fullname = name; + } else if (SP_ACTIVE_DOCUMENT->getBase()) { + fullname = Glib::build_filename(SP_ACTIVE_DOCUMENT->getBase(), name); + } else { + fullname = Glib::build_filename(Glib::get_current_dir(), name); + } + + cmdline.append(" '"); + cmdline.append(fullname.c_str()); + cmdline.append("'"); + } + + //g_warning("##Command line: %s\n", cmdline.c_str()); + + g_spawn_command_line_async(cmdline.c_str(), + &errThing); + + if ( errThing ) { + g_warning("Problem launching editor (%d). %s", errThing->code, errThing->message); + SPDesktop *desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, errThing->message); + g_error_free(errThing); + errThing = 0; + } +} + +/* Embed Image entry */ +static void sp_image_image_embed(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + if (sp_desktop_selection(desktop)->isEmpty()) { + sp_desktop_selection(desktop)->set(item); + } + + Inkscape::Verb *verb = Inkscape::Verb::getbyid( "org.ekips.filter.embedselectedimages" ); + if (verb) { + SPAction *action = verb->get_action(desktop); + if (action) { + sp_action_perform(action, NULL); + } + } +} + +/* Extract Image entry */ +static void sp_image_image_extract(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + if (sp_desktop_selection(desktop)->isEmpty()) { + sp_desktop_selection(desktop)->set(item); + } + + Inkscape::Verb *verb = Inkscape::Verb::getbyid( "org.ekips.filter.extractimage" ); + if (verb) { + SPAction *action = verb->get_action(desktop); + if (action) { + sp_action_perform(action, NULL); + } + } +} + +/* Fill and Stroke entry */ +static void sp_fill_settings(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + if (sp_desktop_selection(desktop)->isEmpty()) { + sp_desktop_selection(desktop)->set(item); + } + + desktop->_dlg_mgr->showDialog("FillAndStroke"); +} + +/* SPShape */ +static void sp_shape_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m) +{ + SPItem *item; + GtkWidget *w; + + item = (SPItem *) object; + + /* Item dialog */ + w = gtk_menu_item_new_with_mnemonic(_("_Fill and Stroke...")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_fill_settings), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); +} + +/* Edit Text entry */ +static void sp_text_settings(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + if (sp_desktop_selection(desktop)->isEmpty()) { + sp_desktop_selection(desktop)->set(item); + } + + desktop->_dlg_mgr->showDialog("TextFont"); +} + +/* Spellcheck entry */ +static void sp_spellcheck_settings(GtkMenuItem *menuitem, SPItem *item) +{ + SPDesktop *desktop; + + g_assert(SP_IS_ITEM(item)); + + desktop = (SPDesktop*)g_object_get_data(G_OBJECT(menuitem), "desktop"); + g_return_if_fail(desktop != NULL); + + if (sp_desktop_selection(desktop)->isEmpty()) { + sp_desktop_selection(desktop)->set(item); + } + + desktop->_dlg_mgr->showDialog("SpellCheck"); +} + +/* SPText */ +static void sp_text_menu(SPObject *object, SPDesktop *desktop, GtkMenu *m) +{ + SPItem *item; + GtkWidget *w; + + item = (SPItem *) object; + + /* Fill and Stroke dialog */ + w = gtk_menu_item_new_with_mnemonic(_("_Fill and Stroke...")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_fill_settings), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + + /* Edit Text dialog */ + w = gtk_menu_item_new_with_mnemonic(_("_Text and Font...")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_text_settings), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); + + /* Spellcheck dialog */ + w = gtk_menu_item_new_with_mnemonic(_("Check Spellin_g...")); + g_object_set_data(G_OBJECT(w), "desktop", desktop); + g_signal_connect(G_OBJECT(w), "activate", G_CALLBACK(sp_spellcheck_settings), item); + gtk_widget_show(w); + gtk_menu_shell_append(GTK_MENU_SHELL(m), w); +} +/* + 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/Makefile_insert b/src/ui/dialog/Makefile_insert index 91b1ccaf6..f8b95247a 100644 --- a/src/ui/dialog/Makefile_insert +++ b/src/ui/dialog/Makefile_insert @@ -8,6 +8,8 @@ ink_common_sources += \ ui/dialog/behavior.h \ ui/dialog/calligraphic-profile-rename.h \ ui/dialog/calligraphic-profile-rename.cpp \ + ui/dialog/clonetiler.cpp \ + ui/dialog/clonetiler.h \ ui/dialog/color-item.cpp \ ui/dialog/color-item.h \ ui/dialog/debug.cpp \ diff --git a/src/ui/dialog/clonetiler.cpp b/src/ui/dialog/clonetiler.cpp new file mode 100644 index 000000000..36e79a682 --- /dev/null +++ b/src/ui/dialog/clonetiler.cpp @@ -0,0 +1,2833 @@ + +/** @file + * Clone tiling dialog + */ +/* Authors: + * bulia byak <buliabyak@users.sf.net> + * Johan Engelen <goejendaagh@zonnet.nl> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * Romain de Bossoreille + * + * Copyright (C) 2004-2011 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "clonetiler.h" + +#include <climits> + +#include <glib.h> +#include <glibmm/i18n.h> +#include <2geom/transforms.h> +#include <gtkmm/adjustment.h> + +#include "desktop.h" +#include "desktop-handles.h" +#include "display/cairo-utils.h" +#include "display/drawing.h" +#include "display/drawing-context.h" +#include "display/drawing-item.h" +#include "document.h" +#include "document-undo.h" +#include "filter-chemistry.h" +#include "helper/unit-menu.h" +#include "helper/units.h" +#include "helper/window.h" +#include "inkscape.h" +#include "interface.h" +#include "macros.h" +#include "message-stack.h" +#include "preferences.h" +#include "selection.h" +#include "sp-filter.h" +#include "sp-namedview.h" +#include "sp-use.h" +#include "style.h" +#include "svg/svg-color.h" +#include "svg/svg.h" +#include "ui/icon-names.h" +#include "ui/widget/spinbutton.h" +#include "unclump.h" +#include "verbs.h" +#include "widgets/icon.h" +#include "xml/repr.h" +#include "sp-root.h" + +using Inkscape::DocumentUndo; + +namespace Inkscape { +namespace UI { +namespace Dialog { + +#define SB_MARGIN 1 +#define VB_MARGIN 4 + +static Glib::ustring const prefs_path = "/dialogs/clonetiler/"; + +static Inkscape::Drawing *trace_drawing = NULL; +static unsigned trace_visionkey; +static gdouble trace_zoom; +static SPDocument *trace_doc = NULL; + + +CloneTiler::CloneTiler (void) : + UI::Widget::Panel ("", "/dialogs/clonetiler/", SP_VERB_DIALOG_CLONETILER), + dlg(NULL), + desktop(NULL), + deskTrack(), + table_row_labels(NULL), + selectChangedConn(), + subselChangedConn(), + selectModifiedConn() +{ + Gtk::Box *contents = _getContents(); + contents->set_spacing(0); + + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + dlg = GTK_WIDGET(gobj()); + + GtkWidget *mainbox = gtk_vbox_new(FALSE, 4); + gtk_container_set_border_width (GTK_CONTAINER (mainbox), 6); + + contents->pack_start (*Gtk::manage(Glib::wrap(mainbox)), true, true, 0); + + GtkWidget *nb = gtk_notebook_new (); + gtk_box_pack_start (GTK_BOX (mainbox), nb, FALSE, FALSE, 0); + + + // Symmetry + { + GtkWidget *vb = clonetiler_new_tab (nb, _("_Symmetry")); + + /* TRANSLATORS: For the following 17 symmetry groups, see + * http://www.bib.ulb.ac.be/coursmath/doc/17.htm (visual examples); + * http://www.clarku.edu/~djoyce/wallpaper/seventeen.html (English vocabulary); or + * http://membres.lycos.fr/villemingerard/Geometri/Sym1D.htm (French vocabulary). + */ + struct SymGroups { + gint group; + gchar const *label; + } const sym_groups[] = { + // TRANSLATORS: "translation" means "shift" / "displacement" here. + {TILE_P1, _("<b>P1</b>: simple translation")}, + {TILE_P2, _("<b>P2</b>: 180° rotation")}, + {TILE_PM, _("<b>PM</b>: reflection")}, + // TRANSLATORS: "glide reflection" is a reflection and a translation combined. + // For more info, see http://mathforum.org/sum95/suzanne/symsusan.html + {TILE_PG, _("<b>PG</b>: glide reflection")}, + {TILE_CM, _("<b>CM</b>: reflection + glide reflection")}, + {TILE_PMM, _("<b>PMM</b>: reflection + reflection")}, + {TILE_PMG, _("<b>PMG</b>: reflection + 180° rotation")}, + {TILE_PGG, _("<b>PGG</b>: glide reflection + 180° rotation")}, + {TILE_CMM, _("<b>CMM</b>: reflection + reflection + 180° rotation")}, + {TILE_P4, _("<b>P4</b>: 90° rotation")}, + {TILE_P4M, _("<b>P4M</b>: 90° rotation + 45° reflection")}, + {TILE_P4G, _("<b>P4G</b>: 90° rotation + 90° reflection")}, + {TILE_P3, _("<b>P3</b>: 120° rotation")}, + {TILE_P31M, _("<b>P31M</b>: reflection + 120° rotation, dense")}, + {TILE_P3M1, _("<b>P3M1</b>: reflection + 120° rotation, sparse")}, + {TILE_P6, _("<b>P6</b>: 60° rotation")}, + {TILE_P6M, _("<b>P6M</b>: reflection + 60° rotation")}, + }; + + gint current = prefs->getInt(prefs_path + "symmetrygroup", 0); + + // Create a list structure containing all the data to be displayed in + // the symmetry group combo box. + GtkListStore *store = gtk_list_store_new (1, G_TYPE_STRING); + GtkTreeIter iter; + + for (unsigned j = 0; j < G_N_ELEMENTS(sym_groups); ++j) { + SymGroups const &sg = sym_groups[j]; + + // Add the description of the symgroup to a new row + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, sg.label, -1); + } + + // Add a new combo box widget with the list of symmetry groups to the vbox + GtkWidget *combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store)); + gtk_widget_set_tooltip_text (combo, _("Select one of the 17 symmetry groups for the tiling")); + gtk_box_pack_start (GTK_BOX (vb), combo, FALSE, FALSE, SB_MARGIN); + + // Specify the rendering of data from the list in a combo box cell + GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, FALSE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo), renderer, "markup", 0, NULL); + + gtk_combo_box_set_active (GTK_COMBO_BOX (combo), current); + + g_signal_connect(G_OBJECT(combo), "changed", + G_CALLBACK(clonetiler_symgroup_changed), NULL); + } + + table_row_labels = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); + + // Shift + { + GtkWidget *vb = clonetiler_new_tab (nb, _("S_hift")); + + GtkWidget *table = clonetiler_table_x_y_rand (3); + gtk_box_pack_start (GTK_BOX (vb), table, FALSE, FALSE, 0); + + // X + { + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "shift" means: the tiles will be shifted (offset) horizontally by this amount + // xgettext:no-c-format + gtk_label_set_markup (GTK_LABEL(l), _("<b>Shift X:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 2, 1); + } + + { + GtkWidget *l = clonetiler_spinbox ( + // xgettext:no-c-format + _("Horizontal shift per row (in % of tile width)"), "shiftx_per_j", + -10000, 10000, "%"); + clonetiler_table_attach (table, l, 0, 2, 2); + } + + { + GtkWidget *l = clonetiler_spinbox ( + // xgettext:no-c-format + _("Horizontal shift per column (in % of tile width)"), "shiftx_per_i", + -10000, 10000, "%"); + clonetiler_table_attach (table, l, 0, 2, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Randomize the horizontal shift by this percentage"), "shiftx_rand", + 0, 1000, "%"); + clonetiler_table_attach (table, l, 0, 2, 4); + } + + // Y + { + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "shift" means: the tiles will be shifted (offset) vertically by this amount + // xgettext:no-c-format + gtk_label_set_markup (GTK_LABEL(l), _("<b>Shift Y:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 3, 1); + } + + { + GtkWidget *l = clonetiler_spinbox ( + // xgettext:no-c-format + _("Vertical shift per row (in % of tile height)"), "shifty_per_j", + -10000, 10000, "%"); + clonetiler_table_attach (table, l, 0, 3, 2); + } + + { + GtkWidget *l = clonetiler_spinbox ( + // xgettext:no-c-format + _("Vertical shift per column (in % of tile height)"), "shifty_per_i", + -10000, 10000, "%"); + clonetiler_table_attach (table, l, 0, 3, 3); + } + + { + GtkWidget *l = clonetiler_spinbox ( + _("Randomize the vertical shift by this percentage"), "shifty_rand", + 0, 1000, "%"); + clonetiler_table_attach (table, l, 0, 3, 4); + } + + // Exponent + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<b>Exponent:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 4, 1); + } + + { + GtkWidget *l = clonetiler_spinbox ( + _("Whether rows are spaced evenly (1), converge (<1) or diverge (>1)"), "shifty_exp", + 0, 10, "", true); + clonetiler_table_attach (table, l, 0, 4, 2); + } + + { + GtkWidget *l = clonetiler_spinbox ( + _("Whether columns are spaced evenly (1), converge (<1) or diverge (>1)"), "shiftx_exp", + 0, 10, "", true); + clonetiler_table_attach (table, l, 0, 4, 3); + } + + { // alternates + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Alternate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("<small>Alternate:</small>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 5, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the sign of shifts for each row"), "shifty_alternate"); + clonetiler_table_attach (table, l, 0, 5, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the sign of shifts for each column"), "shiftx_alternate"); + clonetiler_table_attach (table, l, 0, 5, 3); + } + + { // Cumulate + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Cumulate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("<small>Cumulate:</small>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 6, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Cumulate the shifts for each row"), "shifty_cumulate"); + clonetiler_table_attach (table, l, 0, 6, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Cumulate the shifts for each column"), "shiftx_cumulate"); + clonetiler_table_attach (table, l, 0, 6, 3); + } + + { // Exclude tile width and height in shift + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Cumulate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("<small>Exclude tile:</small>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 7, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Exclude tile height in shift"), "shifty_excludeh"); + clonetiler_table_attach (table, l, 0, 7, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Exclude tile width in shift"), "shiftx_excludew"); + clonetiler_table_attach (table, l, 0, 7, 3); + } + + } + + + // Scale + { + GtkWidget *vb = clonetiler_new_tab (nb, _("Sc_ale")); + + GtkWidget *table = clonetiler_table_x_y_rand (2); + gtk_box_pack_start (GTK_BOX (vb), table, FALSE, FALSE, 0); + + // X + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<b>Scale X:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 2, 1); + } + + { + GtkWidget *l = clonetiler_spinbox ( + // xgettext:no-c-format + _("Horizontal scale per row (in % of tile width)"), "scalex_per_j", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 2, 2); + } + + { + GtkWidget *l = clonetiler_spinbox ( + // xgettext:no-c-format + _("Horizontal scale per column (in % of tile width)"), "scalex_per_i", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 2, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Randomize the horizontal scale by this percentage"), "scalex_rand", + 0, 1000, "%"); + clonetiler_table_attach (table, l, 0, 2, 4); + } + + // Y + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<b>Scale Y:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 3, 1); + } + + { + GtkWidget *l = clonetiler_spinbox ( + // xgettext:no-c-format + _("Vertical scale per row (in % of tile height)"), "scaley_per_j", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 3, 2); + } + + { + GtkWidget *l = clonetiler_spinbox ( + // xgettext:no-c-format + _("Vertical scale per column (in % of tile height)"), "scaley_per_i", + -100, 1000, "%"); + clonetiler_table_attach (table, l, 0, 3, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Randomize the vertical scale by this percentage"), "scaley_rand", + 0, 1000, "%"); + clonetiler_table_attach (table, l, 0, 3, 4); + } + + // Exponent + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<b>Exponent:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 4, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Whether row scaling is uniform (1), converge (<1) or diverge (>1)"), "scaley_exp", + 0, 10, "", true); + clonetiler_table_attach (table, l, 0, 4, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Whether column scaling is uniform (1), converge (<1) or diverge (>1)"), "scalex_exp", + 0, 10, "", true); + clonetiler_table_attach (table, l, 0, 4, 3); + } + + // Logarithmic (as in logarithmic spiral) + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<b>Base:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 5, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Base for a logarithmic spiral: not used (0), converge (<1), or diverge (>1)"), "scaley_log", + 0, 10, "", false); + clonetiler_table_attach (table, l, 0, 5, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Base for a logarithmic spiral: not used (0), converge (<1), or diverge (>1)"), "scalex_log", + 0, 10, "", false); + clonetiler_table_attach (table, l, 0, 5, 3); + } + + { // alternates + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Alternate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("<small>Alternate:</small>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 6, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the sign of scales for each row"), "scaley_alternate"); + clonetiler_table_attach (table, l, 0, 6, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the sign of scales for each column"), "scalex_alternate"); + clonetiler_table_attach (table, l, 0, 6, 3); + } + + { // Cumulate + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Cumulate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("<small>Cumulate:</small>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 7, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Cumulate the scales for each row"), "scaley_cumulate"); + clonetiler_table_attach (table, l, 0, 7, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Cumulate the scales for each column"), "scalex_cumulate"); + clonetiler_table_attach (table, l, 0, 7, 3); + } + + } + + + // Rotation + { + GtkWidget *vb = clonetiler_new_tab (nb, _("_Rotation")); + + GtkWidget *table = clonetiler_table_x_y_rand (1); + gtk_box_pack_start (GTK_BOX (vb), table, FALSE, FALSE, 0); + + // Angle + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<b>Angle:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 2, 1); + } + + { + GtkWidget *l = clonetiler_spinbox ( + // xgettext:no-c-format + _("Rotate tiles by this angle for each row"), "rotate_per_j", + -180, 180, "°"); + clonetiler_table_attach (table, l, 0, 2, 2); + } + + { + GtkWidget *l = clonetiler_spinbox ( + // xgettext:no-c-format + _("Rotate tiles by this angle for each column"), "rotate_per_i", + -180, 180, "°"); + clonetiler_table_attach (table, l, 0, 2, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Randomize the rotation angle by this percentage"), "rotate_rand", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 4); + } + + { // alternates + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Alternate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("<small>Alternate:</small>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 3, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the rotation direction for each row"), "rotate_alternatej"); + clonetiler_table_attach (table, l, 0, 3, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the rotation direction for each column"), "rotate_alternatei"); + clonetiler_table_attach (table, l, 0, 3, 3); + } + + { // Cumulate + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Cumulate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("<small>Cumulate:</small>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 4, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Cumulate the rotation for each row"), "rotate_cumulatej"); + clonetiler_table_attach (table, l, 0, 4, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Cumulate the rotation for each column"), "rotate_cumulatei"); + clonetiler_table_attach (table, l, 0, 4, 3); + } + + } + + + // Blur and opacity + { + GtkWidget *vb = clonetiler_new_tab (nb, _("_Blur & opacity")); + + GtkWidget *table = clonetiler_table_x_y_rand (1); + gtk_box_pack_start (GTK_BOX (vb), table, FALSE, FALSE, 0); + + + // Blur + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<b>Blur:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 2, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Blur tiles by this percentage for each row"), "blur_per_j", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Blur tiles by this percentage for each column"), "blur_per_i", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Randomize the tile blur by this percentage"), "blur_rand", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 4); + } + + { // alternates + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Alternate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("<small>Alternate:</small>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 3, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the sign of blur change for each row"), "blur_alternatej"); + clonetiler_table_attach (table, l, 0, 3, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the sign of blur change for each column"), "blur_alternatei"); + clonetiler_table_attach (table, l, 0, 3, 3); + } + + + + // Dissolve + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<b>Opacity:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 4, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Decrease tile opacity by this percentage for each row"), "opacity_per_j", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 4, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Decrease tile opacity by this percentage for each column"), "opacity_per_i", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 4, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Randomize the tile opacity by this percentage"), "opacity_rand", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 4, 4); + } + + { // alternates + GtkWidget *l = gtk_label_new (""); + // TRANSLATORS: "Alternate" is a verb here + gtk_label_set_markup (GTK_LABEL(l), _("<small>Alternate:</small>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 5, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the sign of opacity change for each row"), "opacity_alternatej"); + clonetiler_table_attach (table, l, 0, 5, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the sign of opacity change for each column"), "opacity_alternatei"); + clonetiler_table_attach (table, l, 0, 5, 3); + } + } + + + // Color + { + GtkWidget *vb = clonetiler_new_tab (nb, _("Co_lor")); + + { + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + + GtkWidget *l = gtk_label_new (_("Initial color: ")); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); + + guint32 rgba = 0x000000ff | sp_svg_read_color (prefs->getString(prefs_path + "initial_color").data(), 0x000000ff); + color_picker = new Inkscape::UI::Widget::ColorPicker (*new Glib::ustring(_("Initial color of tiled clones")), *new Glib::ustring(_("Initial color for clones (works only if the original has unset fill or stroke)")), rgba, false); + color_changed_connection = color_picker->connectChanged (sigc::ptr_fun(on_picker_color_changed)); + + gtk_box_pack_start (GTK_BOX (hb), reinterpret_cast<GtkWidget*>(color_picker->gobj()), FALSE, FALSE, 0); + + gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); + } + + + GtkWidget *table = clonetiler_table_x_y_rand (3); + gtk_box_pack_start (GTK_BOX (vb), table, FALSE, FALSE, 0); + + // Hue + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<b>H:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 2, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Change the tile hue by this percentage for each row"), "hue_per_j", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Change the tile hue by this percentage for each column"), "hue_per_i", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Randomize the tile hue by this percentage"), "hue_rand", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 2, 4); + } + + + // Saturation + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<b>S:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 3, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Change the color saturation by this percentage for each row"), "saturation_per_j", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 3, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Change the color saturation by this percentage for each column"), "saturation_per_i", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 3, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Randomize the color saturation by this percentage"), "saturation_rand", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 3, 4); + } + + // Lightness + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<b>L:</b>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 4, 1); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Change the color lightness by this percentage for each row"), "lightness_per_j", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 4, 2); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Change the color lightness by this percentage for each column"), "lightness_per_i", + -100, 100, "%"); + clonetiler_table_attach (table, l, 0, 4, 3); + } + + { + GtkWidget *l = clonetiler_spinbox (_("Randomize the color lightness by this percentage"), "lightness_rand", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0, 4, 4); + } + + + { // alternates + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<small>Alternate:</small>")); + gtk_size_group_add_widget(table_row_labels, l); + clonetiler_table_attach (table, l, 1, 5, 1); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the sign of color changes for each row"), "color_alternatej"); + clonetiler_table_attach (table, l, 0, 5, 2); + } + + { + GtkWidget *l = clonetiler_checkbox (_("Alternate the sign of color changes for each column"), "color_alternatei"); + clonetiler_table_attach (table, l, 0, 5, 3); + } + + } + + // Trace + { + GtkWidget *vb = clonetiler_new_tab (nb, _("_Trace")); + + + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + gtk_box_pack_start (GTK_BOX (vb), hb, FALSE, FALSE, 0); + + GtkWidget *b = gtk_check_button_new_with_label (_("Trace the drawing under the tiles")); + g_object_set_data (G_OBJECT(b), "uncheckable", GINT_TO_POINTER(TRUE)); + bool old = prefs->getBool(prefs_path + "dotrace"); + gtk_toggle_button_set_active ((GtkToggleButton *) b, old); + gtk_widget_set_tooltip_text (b, _("For each clone, pick a value from the drawing in that clone's location and apply it to the clone")); + gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(b), "toggled", + G_CALLBACK(clonetiler_do_pick_toggled), (gpointer)dlg); + } + + { + GtkWidget *vvb = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vb), vvb, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT(dlg), "dotrace", (gpointer) vvb); + + + { + GtkWidget *frame = gtk_frame_new (_("1. Pick from the drawing:")); + gtk_box_pack_start (GTK_BOX (vvb), frame, FALSE, FALSE, 0); + + GtkWidget *table = gtk_table_new (3, 3, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_add(GTK_CONTAINER(frame), table); + + + GtkWidget* radio; + { + radio = gtk_radio_button_new_with_label (NULL, _("Color")); + gtk_widget_set_tooltip_text (radio, _("Pick the visible color and opacity")); + clonetiler_table_attach (table, radio, 0.0, 1, 1); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (clonetiler_pick_switched), GINT_TO_POINTER(PICK_COLOR)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs->getInt(prefs_path + "pick", 0) == PICK_COLOR); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)), _("Opacity")); + gtk_widget_set_tooltip_text (radio, _("Pick the total accumulated opacity")); + clonetiler_table_attach (table, radio, 0.0, 2, 1); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (clonetiler_pick_switched), GINT_TO_POINTER(PICK_OPACITY)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs->getInt(prefs_path + "pick", 0) == PICK_OPACITY); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)), _("R")); + gtk_widget_set_tooltip_text (radio, _("Pick the Red component of the color")); + clonetiler_table_attach (table, radio, 0.0, 1, 2); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (clonetiler_pick_switched), GINT_TO_POINTER(PICK_R)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs->getInt(prefs_path + "pick", 0) == PICK_R); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)), _("G")); + gtk_widget_set_tooltip_text (radio, _("Pick the Green component of the color")); + clonetiler_table_attach (table, radio, 0.0, 2, 2); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (clonetiler_pick_switched), GINT_TO_POINTER(PICK_G)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs->getInt(prefs_path + "pick", 0) == PICK_G); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)), _("B")); + gtk_widget_set_tooltip_text (radio, _("Pick the Blue component of the color")); + clonetiler_table_attach (table, radio, 0.0, 3, 2); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (clonetiler_pick_switched), GINT_TO_POINTER(PICK_B)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs->getInt(prefs_path + "pick", 0) == PICK_B); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)), C_("Clonetiler color hue", "H")); + gtk_widget_set_tooltip_text (radio, _("Pick the hue of the color")); + clonetiler_table_attach (table, radio, 0.0, 1, 3); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (clonetiler_pick_switched), GINT_TO_POINTER(PICK_H)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs->getInt(prefs_path + "pick", 0) == PICK_H); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)), C_("Clonetiler color saturation", "S")); + gtk_widget_set_tooltip_text (radio, _("Pick the saturation of the color")); + clonetiler_table_attach (table, radio, 0.0, 2, 3); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (clonetiler_pick_switched), GINT_TO_POINTER(PICK_S)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs->getInt(prefs_path + "pick", 0) == PICK_S); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)), C_("Clonetiler color lightness", "L")); + gtk_widget_set_tooltip_text (radio, _("Pick the lightness of the color")); + clonetiler_table_attach (table, radio, 0.0, 3, 3); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (clonetiler_pick_switched), GINT_TO_POINTER(PICK_L)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), prefs->getInt(prefs_path + "pick", 0) == PICK_L); + } + + } + + { + GtkWidget *frame = gtk_frame_new (_("2. Tweak the picked value:")); + gtk_box_pack_start (GTK_BOX (vvb), frame, FALSE, FALSE, VB_MARGIN); + + GtkWidget *table = gtk_table_new (4, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_add(GTK_CONTAINER(frame), table); + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Gamma-correct:")); + clonetiler_table_attach (table, l, 1.0, 1, 1); + } + { + GtkWidget *l = clonetiler_spinbox (_("Shift the mid-range of the picked value upwards (>0) or downwards (<0)"), "gamma_picked", + -10, 10, ""); + clonetiler_table_attach (table, l, 0.0, 1, 2); + } + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Randomize:")); + clonetiler_table_attach (table, l, 1.0, 1, 3); + } + { + GtkWidget *l = clonetiler_spinbox (_("Randomize the picked value by this percentage"), "rand_picked", + 0, 100, "%"); + clonetiler_table_attach (table, l, 0.0, 1, 4); + } + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("Invert:")); + clonetiler_table_attach (table, l, 1.0, 2, 1); + } + { + GtkWidget *l = clonetiler_checkbox (_("Invert the picked value"), "invert_picked"); + clonetiler_table_attach (table, l, 0.0, 2, 2); + } + } + + { + GtkWidget *frame = gtk_frame_new (_("3. Apply the value to the clones':")); + gtk_box_pack_start (GTK_BOX (vvb), frame, FALSE, FALSE, 0); + + + GtkWidget *table = gtk_table_new (2, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_container_add(GTK_CONTAINER(frame), table); + + { + GtkWidget *b = gtk_check_button_new_with_label (_("Presence")); + bool old = prefs->getBool(prefs_path + "pick_to_presence", true); + gtk_toggle_button_set_active ((GtkToggleButton *) b, old); + gtk_widget_set_tooltip_text (b, _("Each clone is created with the probability determined by the picked value in that point")); + clonetiler_table_attach (table, b, 0.0, 1, 1); + g_signal_connect(G_OBJECT(b), "toggled", + G_CALLBACK(clonetiler_pick_to), (gpointer) "pick_to_presence"); + } + + { + GtkWidget *b = gtk_check_button_new_with_label (_("Size")); + bool old = prefs->getBool(prefs_path + "pick_to_size"); + gtk_toggle_button_set_active ((GtkToggleButton *) b, old); + gtk_widget_set_tooltip_text (b, _("Each clone's size is determined by the picked value in that point")); + clonetiler_table_attach (table, b, 0.0, 2, 1); + g_signal_connect(G_OBJECT(b), "toggled", + G_CALLBACK(clonetiler_pick_to), (gpointer) "pick_to_size"); + } + + { + GtkWidget *b = gtk_check_button_new_with_label (_("Color")); + bool old = prefs->getBool(prefs_path + "pick_to_color", 0); + gtk_toggle_button_set_active ((GtkToggleButton *) b, old); + gtk_widget_set_tooltip_text (b, _("Each clone is painted by the picked color (the original must have unset fill or stroke)")); + clonetiler_table_attach (table, b, 0.0, 1, 2); + g_signal_connect(G_OBJECT(b), "toggled", + G_CALLBACK(clonetiler_pick_to), (gpointer) "pick_to_color"); + } + + { + GtkWidget *b = gtk_check_button_new_with_label (_("Opacity")); + bool old = prefs->getBool(prefs_path + "pick_to_opacity", 0); + gtk_toggle_button_set_active ((GtkToggleButton *) b, old); + gtk_widget_set_tooltip_text (b, _("Each clone's opacity is determined by the picked value in that point")); + clonetiler_table_attach (table, b, 0.0, 2, 2); + g_signal_connect(G_OBJECT(b), "toggled", + G_CALLBACK(clonetiler_pick_to), (gpointer) "pick_to_opacity"); + } + } + gtk_widget_set_sensitive (vvb, prefs->getBool(prefs_path + "dotrace")); + } + } + + // Rows/columns, width/height + { + GtkWidget *table = gtk_table_new (2, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), VB_MARGIN); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + gtk_box_pack_start (GTK_BOX (mainbox), table, FALSE, FALSE, 0); + + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + g_object_set_data (G_OBJECT(dlg), "rowscols", (gpointer) hb); + + { + Gtk::Adjustment *a = new Gtk::Adjustment (0.0, 1, 500, 1, 10, 0); + int value = prefs->getInt(prefs_path + "jmax", 2); + a->set_value (value); + + Inkscape::UI::Widget::SpinButton *sb = new Inkscape::UI::Widget::SpinButton (*a, 1.0, 0); + sb->set_tooltip_text (_("How many rows in the tiling")); + sb->set_width_chars (7); + gtk_box_pack_start (GTK_BOX (hb), GTK_WIDGET(sb->gobj()), TRUE, TRUE, 0); + + // TODO: C++ification + g_signal_connect(G_OBJECT(a->gobj()), "value_changed", + G_CALLBACK(clonetiler_xy_changed), (gpointer) "jmax"); + } + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), "×"); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_box_pack_start (GTK_BOX (hb), l, TRUE, TRUE, 0); + } + + { + Gtk::Adjustment *a = new Gtk::Adjustment (0.0, 1, 500, 1, 10, 0); + int value = prefs->getInt(prefs_path + "imax", 2); + a->set_value (value); + + Inkscape::UI::Widget::SpinButton *sb = new Inkscape::UI::Widget::SpinButton (*a, 1.0, 0); + sb->set_tooltip_text (_("How many columns in the tiling")); + sb->set_width_chars (7); + gtk_box_pack_start (GTK_BOX (hb), GTK_WIDGET(sb->gobj()), TRUE, TRUE, 0); + + // TODO: C++ification + g_signal_connect(G_OBJECT(a->gobj()), "value_changed", + G_CALLBACK(clonetiler_xy_changed), (gpointer) "imax"); + } + + clonetiler_table_attach (table, hb, 0.0, 1, 2); + } + + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + g_object_set_data (G_OBJECT(dlg), "widthheight", (gpointer) hb); + + // unitmenu + GtkWidget *u = sp_unit_selector_new (SP_UNIT_ABSOLUTE | SP_UNIT_DEVICE); + sp_unit_selector_set_unit (SP_UNIT_SELECTOR(u), sp_desktop_namedview(SP_ACTIVE_DESKTOP)->doc_units); + + { + // Width spinbutton + Gtk::Adjustment *a = new Gtk::Adjustment (0.0, -1e6, 1e6, 1.0, 10.0, 0); + sp_unit_selector_add_adjustment (SP_UNIT_SELECTOR (u), GTK_ADJUSTMENT (a->gobj())); + + double value = prefs->getDouble(prefs_path + "fillwidth", 50.0); + SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(u)); + gdouble const units = sp_pixels_get_units (value, unit); + a->set_value (units); + + Inkscape::UI::Widget::SpinButton *e = new Inkscape::UI::Widget::SpinButton (*a, 1.0, 2); + e->set_tooltip_text (_("Width of the rectangle to be filled")); + e->set_width_chars (7); + e->set_digits (4); + gtk_box_pack_start (GTK_BOX (hb), GTK_WIDGET(e->gobj()), TRUE, TRUE, 0); + // TODO: C++ification + g_signal_connect(G_OBJECT(a->gobj()), "value_changed", + G_CALLBACK(clonetiler_fill_width_changed), u); + } + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), "×"); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5); + gtk_box_pack_start (GTK_BOX (hb), l, TRUE, TRUE, 0); + } + + { + // Height spinbutton + Gtk::Adjustment *a = new Gtk::Adjustment (0.0, -1e6, 1e6, 1.0, 10.0, 0); + sp_unit_selector_add_adjustment (SP_UNIT_SELECTOR (u), GTK_ADJUSTMENT (a->gobj())); + + double value = prefs->getDouble(prefs_path + "fillheight", 50.0); + SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(u)); + gdouble const units = sp_pixels_get_units (value, unit); + a->set_value (units); + + Inkscape::UI::Widget::SpinButton *e = new Inkscape::UI::Widget::SpinButton (*a, 1.0, 2); + e->set_tooltip_text (_("Height of the rectangle to be filled")); + e->set_width_chars (7); + e->set_digits (4); + gtk_box_pack_start (GTK_BOX (hb), GTK_WIDGET(e->gobj()), TRUE, TRUE, 0); + // TODO: C++ification + g_signal_connect(G_OBJECT(a->gobj()), "value_changed", + G_CALLBACK(clonetiler_fill_height_changed), u); + } + + gtk_box_pack_start (GTK_BOX (hb), u, TRUE, TRUE, 0); + clonetiler_table_attach (table, hb, 0.0, 2, 2); + + } + + // Switch + GtkWidget* radio; + { + radio = gtk_radio_button_new_with_label (NULL, _("Rows, columns: ")); + gtk_widget_set_tooltip_text (radio, _("Create the specified number of rows and columns")); + clonetiler_table_attach (table, radio, 0.0, 1, 1); + g_signal_connect (G_OBJECT (radio), "toggled", G_CALLBACK (clonetiler_switch_to_create), (gpointer) dlg); + } + if (!prefs->getBool(prefs_path + "fillrect")) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE); + gtk_toggle_button_toggled (GTK_TOGGLE_BUTTON (radio)); + } + { + radio = gtk_radio_button_new_with_label (gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)), _("Width, height: ")); + gtk_widget_set_tooltip_text (radio, _("Fill the specified width and height with the tiling")); + clonetiler_table_attach (table, radio, 0.0, 2, 1); + g_signal_connect (G_OBJECT (radio), "toggled", G_CALLBACK (clonetiler_switch_to_fill), (gpointer) dlg); + } + if (prefs->getBool(prefs_path + "fillrect")) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio), TRUE); + gtk_toggle_button_toggled (GTK_TOGGLE_BUTTON (radio)); + } + } + + + // Use saved pos + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + gtk_box_pack_start (GTK_BOX (mainbox), hb, FALSE, FALSE, 0); + + GtkWidget *b = gtk_check_button_new_with_label (_("Use saved size and position of the tile")); + bool keepbbox = prefs->getBool(prefs_path + "keepbbox", true); + gtk_toggle_button_set_active ((GtkToggleButton *) b, keepbbox); + gtk_widget_set_tooltip_text (b, _("Pretend that the size and position of the tile are the same as the last time you tiled it (if any), instead of using the current size")); + gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0); + + g_signal_connect(G_OBJECT(b), "toggled", + G_CALLBACK(clonetiler_keep_bbox_toggled), NULL); + } + + // Statusbar + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + gtk_box_pack_end (GTK_BOX (mainbox), hb, FALSE, FALSE, 0); + GtkWidget *l = gtk_label_new(""); + g_object_set_data (G_OBJECT(dlg), "status", (gpointer) l); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); + } + + // Buttons + { + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + gtk_box_pack_start (GTK_BOX (mainbox), hb, FALSE, FALSE, 0); + + { + GtkWidget *b = gtk_button_new (); + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup_with_mnemonic (GTK_LABEL(l), _(" <b>_Create</b> ")); + gtk_container_add (GTK_CONTAINER(b), l); + gtk_widget_set_tooltip_text (b, _("Create and tile the clones of the selection")); + g_signal_connect (G_OBJECT (b), "clicked", G_CALLBACK (clonetiler_apply), dlg); + gtk_box_pack_end (GTK_BOX (hb), b, FALSE, FALSE, 0); + } + + { // buttons which are enabled only when there are tiled clones + GtkWidget *sb = gtk_hbox_new(FALSE, 0); + gtk_box_pack_end (GTK_BOX (hb), sb, FALSE, FALSE, 0); + g_object_set_data (G_OBJECT(dlg), "buttons_on_tiles", (gpointer) sb); + { + // TRANSLATORS: if a group of objects are "clumped" together, then they + // are unevenly spread in the given amount of space - as shown in the + // diagrams on the left in the following screenshot: + // http://www.inkscape.org/screenshots/gallery/inkscape-0.42-CVS-tiles-unclump.png + // So unclumping is the process of spreading a number of objects out more evenly. + GtkWidget *b = gtk_button_new_with_mnemonic (_(" _Unclump ")); + gtk_widget_set_tooltip_text (b, _("Spread out clones to reduce clumping; can be applied repeatedly")); + g_signal_connect (G_OBJECT (b), "clicked", G_CALLBACK (clonetiler_unclump), NULL); + gtk_box_pack_end (GTK_BOX (sb), b, FALSE, FALSE, 0); + } + + { + GtkWidget *b = gtk_button_new_with_mnemonic (_(" Re_move ")); + gtk_widget_set_tooltip_text (b, _("Remove existing tiled clones of the selected object (siblings only)")); + g_signal_connect (G_OBJECT (b), "clicked", G_CALLBACK (clonetiler_remove), gpointer(dlg)); + gtk_box_pack_end (GTK_BOX (sb), b, FALSE, FALSE, 0); + } + + // connect to global selection changed signal (so we can change desktops) and + // external_change (so we're not fooled by undo) + g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (clonetiler_change_selection), dlg); + g_signal_connect (G_OBJECT (INKSCAPE), "external_change", G_CALLBACK (clonetiler_external_change), dlg); + g_signal_connect(G_OBJECT(dlg), "destroy", G_CALLBACK(clonetiler_disconnect_gsignal), G_OBJECT (INKSCAPE)); + + // update now + clonetiler_change_selection (NULL, sp_desktop_selection(SP_ACTIVE_DESKTOP), dlg); + } + + { + GtkWidget *b = gtk_button_new_with_mnemonic (_(" R_eset ")); + // TRANSLATORS: "change" is a noun here + gtk_widget_set_tooltip_text (b, _("Reset all shifts, scales, rotates, opacity and color changes in the dialog to zero")); + g_signal_connect (G_OBJECT (b), "clicked", G_CALLBACK (clonetiler_reset), dlg); + gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0); + } + } + + gtk_widget_show_all (mainbox); + + } + + show_all(); + + desktopChangeConn = deskTrack.connectDesktopChanged( sigc::mem_fun(*this, &CloneTiler::setTargetDesktop) ); + deskTrack.connect(GTK_WIDGET(gobj())); + +} + +CloneTiler::~CloneTiler (void) +{ + //subselChangedConn.disconnect(); + //selectChangedConn.disconnect(); + //selectModifiedConn.disconnect(); + desktopChangeConn.disconnect(); + deskTrack.disconnect(); + color_changed_connection.disconnect(); +} + +void CloneTiler::setDesktop(SPDesktop *desktop) +{ + Panel::setDesktop(desktop); + deskTrack.setBase(desktop); +} + +void CloneTiler::setTargetDesktop(SPDesktop *desktop) +{ + if (this->desktop != desktop) { + if (this->desktop) { + //selectModifiedConn.disconnect(); + //subselChangedConn.disconnect(); + //selectChangedConn.disconnect(); + } + this->desktop = desktop; + if (desktop && desktop->selection) { + //selectChangedConn = desktop->selection->connectChanged(sigc::hide(sigc::mem_fun(*this, &CloneTiler::clonetiler_change_selection))); + //subselChangedConn = desktop->connectToolSubselectionChanged(sigc::hide(sigc::mem_fun(*this, &CloneTiler::clonetiler_change_selection))); + //selectModifiedConn = desktop->selection->connectModified(sigc::hide<0>(sigc::mem_fun(*this, &CloneTiler::clonetiler_change_selection))); + } + } +} + +void CloneTiler::on_picker_color_changed(guint rgba) +{ + static bool is_updating = false; + if (is_updating || !SP_ACTIVE_DESKTOP) + return; + + is_updating = true; + + gchar c[32]; + sp_svg_write_color(c, sizeof(c), rgba); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(prefs_path + "initial_color", c); + + is_updating = false; +} + +void CloneTiler::clonetiler_change_selection(Inkscape::Application * /*inkscape*/, Inkscape::Selection *selection, GtkWidget *dlg) +{ + GtkWidget *buttons = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "buttons_on_tiles"); + GtkWidget *status = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "status"); + + if (selection->isEmpty()) { + gtk_widget_set_sensitive (buttons, FALSE); + gtk_label_set_markup (GTK_LABEL(status), _("<small>Nothing selected.</small>")); + return; + } + + if (g_slist_length ((GSList *) selection->itemList()) > 1) { + gtk_widget_set_sensitive (buttons, FALSE); + gtk_label_set_markup (GTK_LABEL(status), _("<small>More than one object selected.</small>")); + return; + } + + guint n = clonetiler_number_of_clones(selection->singleItem()); + if (n > 0) { + gtk_widget_set_sensitive (buttons, TRUE); + gchar *sta = g_strdup_printf (_("<small>Object has <b>%d</b> tiled clones.</small>"), n); + gtk_label_set_markup (GTK_LABEL(status), sta); + g_free (sta); + } else { + gtk_widget_set_sensitive (buttons, FALSE); + gtk_label_set_markup (GTK_LABEL(status), _("<small>Object has no tiled clones.</small>")); + } +} + +void CloneTiler::clonetiler_external_change(Inkscape::Application * /*inkscape*/, GtkWidget *dlg) +{ + clonetiler_change_selection (NULL, sp_desktop_selection(SP_ACTIVE_DESKTOP), dlg); +} + +void CloneTiler::clonetiler_disconnect_gsignal(GObject *widget, gpointer source) +{ + if (source && G_IS_OBJECT(source)) { + sp_signal_disconnect_by_data (source, widget); + } +} + +Geom::Affine CloneTiler::clonetiler_get_transform( + // symmetry group + int type, + + // row, column + int i, int j, + + // center, width, height of the tile + double cx, double cy, + double w, double h, + + // values from the dialog: + // Shift + double shiftx_per_i, double shifty_per_i, + double shiftx_per_j, double shifty_per_j, + double shiftx_rand, double shifty_rand, + double shiftx_exp, double shifty_exp, + int shiftx_alternate, int shifty_alternate, + int shiftx_cumulate, int shifty_cumulate, + int shiftx_excludew, int shifty_excludeh, + + // Scale + double scalex_per_i, double scaley_per_i, + double scalex_per_j, double scaley_per_j, + double scalex_rand, double scaley_rand, + double scalex_exp, double scaley_exp, + double scalex_log, double scaley_log, + int scalex_alternate, int scaley_alternate, + int scalex_cumulate, int scaley_cumulate, + + // Rotation + double rotate_per_i, double rotate_per_j, + double rotate_rand, + int rotate_alternatei, int rotate_alternatej, + int rotate_cumulatei, int rotate_cumulatej + ) +{ + + // Shift (in units of tile width or height) ------------- + double delta_shifti = 0.0; + double delta_shiftj = 0.0; + + if( shiftx_alternate ) { + delta_shifti = (double)(i%2); + } else { + if( shiftx_cumulate ) { // Should the delta shifts be cumulative (i.e. 1, 1+2, 1+2+3, ...) + delta_shifti = (double)(i*i); + } else { + delta_shifti = (double)i; + } + } + + if( shifty_alternate ) { + delta_shiftj = (double)(j%2); + } else { + if( shifty_cumulate ) { + delta_shiftj = (double)(j*j); + } else { + delta_shiftj = (double)j; + } + } + + // Random shift, only calculate if non-zero. + double delta_shiftx_rand = 0.0; + double delta_shifty_rand = 0.0; + if( shiftx_rand != 0.0 ) delta_shiftx_rand = shiftx_rand * g_random_double_range (-1, 1); + if( shifty_rand != 0.0 ) delta_shifty_rand = shifty_rand * g_random_double_range (-1, 1); + + + // Delta shift (units of tile width/height) + double di = shiftx_per_i * delta_shifti + shiftx_per_j * delta_shiftj + delta_shiftx_rand; + double dj = shifty_per_i * delta_shifti + shifty_per_j * delta_shiftj + delta_shifty_rand; + + // Shift in actual x and y, used below + double dx = w * di; + double dy = h * dj; + + double shifti = di; + double shiftj = dj; + + // Include tile width and height in shift if required + if( !shiftx_excludew ) shifti += i; + if( !shifty_excludeh ) shiftj += j; + + // Add exponential shift if necessary + if ( shiftx_exp != 1.0 ) shifti = pow( shifti, shiftx_exp ); + if ( shifty_exp != 1.0 ) shiftj = pow( shiftj, shifty_exp ); + + // Final shift + Geom::Affine rect_translate (Geom::Translate (w * shifti, h * shiftj)); + + // Rotation (in degrees) ------------ + double delta_rotationi = 0.0; + double delta_rotationj = 0.0; + + if( rotate_alternatei ) { + delta_rotationi = (double)(i%2); + } else { + if( rotate_cumulatei ) { + delta_rotationi = (double)(i*i + i)/2.0; + } else { + delta_rotationi = (double)i; + } + } + + if( rotate_alternatej ) { + delta_rotationj = (double)(j%2); + } else { + if( rotate_cumulatej ) { + delta_rotationj = (double)(j*j + j)/2.0; + } else { + delta_rotationj = (double)j; + } + } + + double delta_rotate_rand = 0.0; + if( rotate_rand != 0.0 ) delta_rotate_rand = rotate_rand * 180.0 * g_random_double_range (-1, 1); + + double dr = rotate_per_i * delta_rotationi + rotate_per_j * delta_rotationj + delta_rotate_rand; + + // Scale (times the original) ----------- + double delta_scalei = 0.0; + double delta_scalej = 0.0; + + if( scalex_alternate ) { + delta_scalei = (double)(i%2); + } else { + if( scalex_cumulate ) { // Should the delta scales be cumulative (i.e. 1, 1+2, 1+2+3, ...) + delta_scalei = (double)(i*i + i)/2.0; + } else { + delta_scalei = (double)i; + } + } + + if( scaley_alternate ) { + delta_scalej = (double)(j%2); + } else { + if( scaley_cumulate ) { + delta_scalej = (double)(j*j + j)/2.0; + } else { + delta_scalej = (double)j; + } + } + + // Random scale, only calculate if non-zero. + double delta_scalex_rand = 0.0; + double delta_scaley_rand = 0.0; + if( scalex_rand != 0.0 ) delta_scalex_rand = scalex_rand * g_random_double_range (-1, 1); + if( scaley_rand != 0.0 ) delta_scaley_rand = scaley_rand * g_random_double_range (-1, 1); + // But if random factors are same, scale x and y proportionally + if( scalex_rand == scaley_rand ) delta_scalex_rand = delta_scaley_rand; + + // Total delta scale + double scalex = 1.0 + scalex_per_i * delta_scalei + scalex_per_j * delta_scalej + delta_scalex_rand; + double scaley = 1.0 + scaley_per_i * delta_scalei + scaley_per_j * delta_scalej + delta_scaley_rand; + + if( scalex < 0.0 ) scalex = 0.0; + if( scaley < 0.0 ) scaley = 0.0; + + // Add exponential scale if necessary + if ( scalex_exp != 1.0 ) scalex = pow( scalex, scalex_exp ); + if ( scaley_exp != 1.0 ) scaley = pow( scaley, scaley_exp ); + + // Add logarithmic factor if necessary + if ( scalex_log > 0.0 ) scalex = pow( scalex_log, scalex - 1.0 ); + if ( scaley_log > 0.0 ) scaley = pow( scaley_log, scaley - 1.0 ); + // Alternative using rotation angle + //if ( scalex_log != 1.0 ) scalex *= pow( scalex_log, M_PI*dr/180 ); + //if ( scaley_log != 1.0 ) scaley *= pow( scaley_log, M_PI*dr/180 ); + + + // Calculate transformation matrices, translating back to "center of tile" (rotation center) before transforming + Geom::Affine drot_c = Geom::Translate(-cx, -cy) * Geom::Rotate (M_PI*dr/180) * Geom::Translate(cx, cy); + + Geom::Affine dscale_c = Geom::Translate(-cx, -cy) * Geom::Scale (scalex, scaley) * Geom::Translate(cx, cy); + + Geom::Affine d_s_r = dscale_c * drot_c; + + Geom::Affine rotate_180_c = Geom::Translate(-cx, -cy) * Geom::Rotate (M_PI) * Geom::Translate(cx, cy); + + Geom::Affine rotate_90_c = Geom::Translate(-cx, -cy) * Geom::Rotate (-M_PI/2) * Geom::Translate(cx, cy); + Geom::Affine rotate_m90_c = Geom::Translate(-cx, -cy) * Geom::Rotate ( M_PI/2) * Geom::Translate(cx, cy); + + Geom::Affine rotate_120_c = Geom::Translate(-cx, -cy) * Geom::Rotate (-2*M_PI/3) * Geom::Translate(cx, cy); + Geom::Affine rotate_m120_c = Geom::Translate(-cx, -cy) * Geom::Rotate ( 2*M_PI/3) * Geom::Translate(cx, cy); + + Geom::Affine rotate_60_c = Geom::Translate(-cx, -cy) * Geom::Rotate (-M_PI/3) * Geom::Translate(cx, cy); + Geom::Affine rotate_m60_c = Geom::Translate(-cx, -cy) * Geom::Rotate ( M_PI/3) * Geom::Translate(cx, cy); + + Geom::Affine flip_x = Geom::Translate(-cx, -cy) * Geom::Scale (-1, 1) * Geom::Translate(cx, cy); + Geom::Affine flip_y = Geom::Translate(-cx, -cy) * Geom::Scale (1, -1) * Geom::Translate(cx, cy); + + + // Create tile with required symmetry + const double cos60 = cos(M_PI/3); + const double sin60 = sin(M_PI/3); + const double cos30 = cos(M_PI/6); + const double sin30 = sin(M_PI/6); + + switch (type) { + + case TILE_P1: + return d_s_r * rect_translate; + break; + + case TILE_P2: + if (i % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * rotate_180_c * rect_translate; + } + break; + + case TILE_PM: + if (i % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + break; + + case TILE_PG: + if (j % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + break; + + case TILE_CM: + if ((i + j) % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + break; + + case TILE_PMM: + if (j % 2 == 0) { + if (i % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + } else { + if (i % 2 == 0) { + return d_s_r * flip_y * rect_translate; + } else { + return d_s_r * flip_x * flip_y * rect_translate; + } + } + break; + + case TILE_PMG: + if (j % 2 == 0) { + if (i % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * rotate_180_c * rect_translate; + } + } else { + if (i % 2 == 0) { + return d_s_r * flip_y * rect_translate; + } else { + return d_s_r * rotate_180_c * flip_y * rect_translate; + } + } + break; + + case TILE_PGG: + if (j % 2 == 0) { + if (i % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_y * rect_translate; + } + } else { + if (i % 2 == 0) { + return d_s_r * rotate_180_c * rect_translate; + } else { + return d_s_r * rotate_180_c * flip_y * rect_translate; + } + } + break; + + case TILE_CMM: + if (j % 4 == 0) { + if (i % 2 == 0) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + } else if (j % 4 == 1) { + if (i % 2 == 0) { + return d_s_r * flip_y * rect_translate; + } else { + return d_s_r * flip_x * flip_y * rect_translate; + } + } else if (j % 4 == 2) { + if (i % 2 == 1) { + return d_s_r * rect_translate; + } else { + return d_s_r * flip_x * rect_translate; + } + } else { + if (i % 2 == 1) { + return d_s_r * flip_y * rect_translate; + } else { + return d_s_r * flip_x * flip_y * rect_translate; + } + } + break; + + case TILE_P4: + { + Geom::Affine ori (Geom::Translate ((w + h) * pow((i/2), shiftx_exp) + dx, (h + w) * pow((j/2), shifty_exp) + dy)); + Geom::Affine dia1 (Geom::Translate (w/2 + h/2, -h/2 + w/2)); + Geom::Affine dia2 (Geom::Translate (-w/2 + h/2, h/2 + w/2)); + if (j % 2 == 0) { + if (i % 2 == 0) { + return d_s_r * ori; + } else { + return d_s_r * rotate_m90_c * dia1 * ori; + } + } else { + if (i % 2 == 0) { + return d_s_r * rotate_90_c * dia2 * ori; + } else { + return d_s_r * rotate_180_c * dia1 * dia2 * ori; + } + } + } + break; + + case TILE_P4M: + { + double max = MAX(w, h); + Geom::Affine ori (Geom::Translate ((max + max) * pow((i/4), shiftx_exp) + dx, (max + max) * pow((j/2), shifty_exp) + dy)); + Geom::Affine dia1 (Geom::Translate ( w/2 - h/2, h/2 - w/2)); + Geom::Affine dia2 (Geom::Translate (-h/2 + w/2, w/2 - h/2)); + if (j % 2 == 0) { + if (i % 4 == 0) { + return d_s_r * ori; + } else if (i % 4 == 1) { + return d_s_r * flip_y * rotate_m90_c * dia1 * ori; + } else if (i % 4 == 2) { + return d_s_r * rotate_m90_c * dia1 * Geom::Translate (h, 0) * ori; + } else if (i % 4 == 3) { + return d_s_r * flip_x * Geom::Translate (w, 0) * ori; + } + } else { + if (i % 4 == 0) { + return d_s_r * flip_y * Geom::Translate(0, h) * ori; + } else if (i % 4 == 1) { + return d_s_r * rotate_90_c * dia2 * Geom::Translate(0, h) * ori; + } else if (i % 4 == 2) { + return d_s_r * flip_y * rotate_90_c * dia2 * Geom::Translate(h, 0) * Geom::Translate(0, h) * ori; + } else if (i % 4 == 3) { + return d_s_r * flip_y * flip_x * Geom::Translate(w, 0) * Geom::Translate(0, h) * ori; + } + } + } + break; + + case TILE_P4G: + { + double max = MAX(w, h); + Geom::Affine ori (Geom::Translate ((max + max) * pow((i/4), shiftx_exp) + dx, (max + max) * pow(j, shifty_exp) + dy)); + Geom::Affine dia1 (Geom::Translate ( w/2 + h/2, h/2 - w/2)); + Geom::Affine dia2 (Geom::Translate (-h/2 + w/2, w/2 + h/2)); + if (((i/4) + j) % 2 == 0) { + if (i % 4 == 0) { + return d_s_r * ori; + } else if (i % 4 == 1) { + return d_s_r * rotate_m90_c * dia1 * ori; + } else if (i % 4 == 2) { + return d_s_r * rotate_90_c * dia2 * ori; + } else if (i % 4 == 3) { + return d_s_r * rotate_180_c * dia1 * dia2 * ori; + } + } else { + if (i % 4 == 0) { + return d_s_r * flip_y * Geom::Translate (0, h) * ori; + } else if (i % 4 == 1) { + return d_s_r * flip_y * rotate_m90_c * dia1 * Geom::Translate (-h, 0) * ori; + } else if (i % 4 == 2) { + return d_s_r * flip_y * rotate_90_c * dia2 * Geom::Translate (h, 0) * ori; + } else if (i % 4 == 3) { + return d_s_r * flip_x * Geom::Translate (w, 0) * ori; + } + } + } + break; + + case TILE_P3: + { + double width; + double height; + Geom::Affine dia1; + Geom::Affine dia2; + if (w > h) { + width = w + w * cos60; + height = 2 * w * sin60; + dia1 = Geom::Affine (Geom::Translate (w/2 + w/2 * cos60, -(w/2 * sin60))); + dia2 = dia1 * Geom::Affine (Geom::Translate (0, 2 * (w/2 * sin60))); + } else { + width = h * cos (M_PI/6); + height = h; + dia1 = Geom::Affine (Geom::Translate (h/2 * cos30, -(h/2 * sin30))); + dia2 = dia1 * Geom::Affine (Geom::Translate (0, h/2)); + } + Geom::Affine ori (Geom::Translate (width * pow((2*(i/3) + j%2), shiftx_exp) + dx, (height/2) * pow(j, shifty_exp) + dy)); + if (i % 3 == 0) { + return d_s_r * ori; + } else if (i % 3 == 1) { + return d_s_r * rotate_m120_c * dia1 * ori; + } else if (i % 3 == 2) { + return d_s_r * rotate_120_c * dia2 * ori; + } + } + break; + + case TILE_P31M: + { + Geom::Affine ori; + Geom::Affine dia1; + Geom::Affine dia2; + Geom::Affine dia3; + Geom::Affine dia4; + if (w > h) { + ori = Geom::Affine(Geom::Translate (w * pow((i/6) + 0.5*(j%2), shiftx_exp) + dx, (w * cos30) * pow(j, shifty_exp) + dy)); + dia1 = Geom::Affine (Geom::Translate (0, h/2) * Geom::Translate (w/2, 0) * Geom::Translate (w/2 * cos60, -w/2 * sin60) * Geom::Translate (-h/2 * cos30, -h/2 * sin30) ); + dia2 = dia1 * Geom::Affine (Geom::Translate (h * cos30, h * sin30)); + dia3 = dia2 * Geom::Affine (Geom::Translate (0, 2 * (w/2 * sin60 - h/2 * sin30))); + dia4 = dia3 * Geom::Affine (Geom::Translate (-h * cos30, h * sin30)); + } else { + ori = Geom::Affine (Geom::Translate (2*h * cos30 * pow((i/6 + 0.5*(j%2)), shiftx_exp) + dx, (2*h - h * sin30) * pow(j, shifty_exp) + dy)); + dia1 = Geom::Affine (Geom::Translate (0, -h/2) * Geom::Translate (h/2 * cos30, h/2 * sin30)); + dia2 = dia1 * Geom::Affine (Geom::Translate (h * cos30, h * sin30)); + dia3 = dia2 * Geom::Affine (Geom::Translate (0, h/2)); + dia4 = dia3 * Geom::Affine (Geom::Translate (-h * cos30, h * sin30)); + } + if (i % 6 == 0) { + return d_s_r * ori; + } else if (i % 6 == 1) { + return d_s_r * flip_y * rotate_m120_c * dia1 * ori; + } else if (i % 6 == 2) { + return d_s_r * rotate_m120_c * dia2 * ori; + } else if (i % 6 == 3) { + return d_s_r * flip_y * rotate_120_c * dia3 * ori; + } else if (i % 6 == 4) { + return d_s_r * rotate_120_c * dia4 * ori; + } else if (i % 6 == 5) { + return d_s_r * flip_y * Geom::Translate(0, h) * ori; + } + } + break; + + case TILE_P3M1: + { + double width; + double height; + Geom::Affine dia1; + Geom::Affine dia2; + Geom::Affine dia3; + Geom::Affine dia4; + if (w > h) { + width = w + w * cos60; + height = 2 * w * sin60; + dia1 = Geom::Affine (Geom::Translate (0, h/2) * Geom::Translate (w/2, 0) * Geom::Translate (w/2 * cos60, -w/2 * sin60) * Geom::Translate (-h/2 * cos30, -h/2 * sin30) ); + dia2 = dia1 * Geom::Affine (Geom::Translate (h * cos30, h * sin30)); + dia3 = dia2 * Geom::Affine (Geom::Translate (0, 2 * (w/2 * sin60 - h/2 * sin30))); + dia4 = dia3 * Geom::Affine (Geom::Translate (-h * cos30, h * sin30)); + } else { + width = 2 * h * cos (M_PI/6); + height = 2 * h; + dia1 = Geom::Affine (Geom::Translate (0, -h/2) * Geom::Translate (h/2 * cos30, h/2 * sin30)); + dia2 = dia1 * Geom::Affine (Geom::Translate (h * cos30, h * sin30)); + dia3 = dia2 * Geom::Affine (Geom::Translate (0, h/2)); + dia4 = dia3 * Geom::Affine (Geom::Translate (-h * cos30, h * sin30)); + } + Geom::Affine ori (Geom::Translate (width * pow((2*(i/6) + j%2), shiftx_exp) + dx, (height/2) * pow(j, shifty_exp) + dy)); + if (i % 6 == 0) { + return d_s_r * ori; + } else if (i % 6 == 1) { + return d_s_r * flip_y * rotate_m120_c * dia1 * ori; + } else if (i % 6 == 2) { + return d_s_r * rotate_m120_c * dia2 * ori; + } else if (i % 6 == 3) { + return d_s_r * flip_y * rotate_120_c * dia3 * ori; + } else if (i % 6 == 4) { + return d_s_r * rotate_120_c * dia4 * ori; + } else if (i % 6 == 5) { + return d_s_r * flip_y * Geom::Translate(0, h) * ori; + } + } + break; + + case TILE_P6: + { + Geom::Affine ori; + Geom::Affine dia1; + Geom::Affine dia2; + Geom::Affine dia3; + Geom::Affine dia4; + Geom::Affine dia5; + if (w > h) { + ori = Geom::Affine(Geom::Translate (w * pow((2*(i/6) + (j%2)), shiftx_exp) + dx, (2*w * sin60) * pow(j, shifty_exp) + dy)); + dia1 = Geom::Affine (Geom::Translate (w/2 * cos60, -w/2 * sin60)); + dia2 = dia1 * Geom::Affine (Geom::Translate (w/2, 0)); + dia3 = dia2 * Geom::Affine (Geom::Translate (w/2 * cos60, w/2 * sin60)); + dia4 = dia3 * Geom::Affine (Geom::Translate (-w/2 * cos60, w/2 * sin60)); + dia5 = dia4 * Geom::Affine (Geom::Translate (-w/2, 0)); + } else { + ori = Geom::Affine(Geom::Translate (2*h * cos30 * pow((i/6 + 0.5*(j%2)), shiftx_exp) + dx, (h + h * sin30) * pow(j, shifty_exp) + dy)); + dia1 = Geom::Affine (Geom::Translate (-w/2, -h/2) * Geom::Translate (h/2 * cos30, -h/2 * sin30) * Geom::Translate (w/2 * cos60, w/2 * sin60)); + dia2 = dia1 * Geom::Affine (Geom::Translate (-w/2 * cos60, -w/2 * sin60) * Geom::Translate (h/2 * cos30, -h/2 * sin30) * Geom::Translate (h/2 * cos30, h/2 * sin30) * Geom::Translate (-w/2 * cos60, w/2 * sin60)); + dia3 = dia2 * Geom::Affine (Geom::Translate (w/2 * cos60, -w/2 * sin60) * Geom::Translate (h/2 * cos30, h/2 * sin30) * Geom::Translate (-w/2, h/2)); + dia4 = dia3 * dia1.inverse(); + dia5 = dia3 * dia2.inverse(); + } + if (i % 6 == 0) { + return d_s_r * ori; + } else if (i % 6 == 1) { + return d_s_r * rotate_m60_c * dia1 * ori; + } else if (i % 6 == 2) { + return d_s_r * rotate_m120_c * dia2 * ori; + } else if (i % 6 == 3) { + return d_s_r * rotate_180_c * dia3 * ori; + } else if (i % 6 == 4) { + return d_s_r * rotate_120_c * dia4 * ori; + } else if (i % 6 == 5) { + return d_s_r * rotate_60_c * dia5 * ori; + } + } + break; + + case TILE_P6M: + { + + Geom::Affine ori; + Geom::Affine dia1, dia2, dia3, dia4, dia5, dia6, dia7, dia8, dia9, dia10; + if (w > h) { + ori = Geom::Affine(Geom::Translate (w * pow((2*(i/12) + (j%2)), shiftx_exp) + dx, (2*w * sin60) * pow(j, shifty_exp) + dy)); + dia1 = Geom::Affine (Geom::Translate (w/2, h/2) * Geom::Translate (-w/2 * cos60, -w/2 * sin60) * Geom::Translate (-h/2 * cos30, h/2 * sin30)); + dia2 = dia1 * Geom::Affine (Geom::Translate (h * cos30, -h * sin30)); + dia3 = dia2 * Geom::Affine (Geom::Translate (-h/2 * cos30, h/2 * sin30) * Geom::Translate (w * cos60, 0) * Geom::Translate (-h/2 * cos30, -h/2 * sin30)); + dia4 = dia3 * Geom::Affine (Geom::Translate (h * cos30, h * sin30)); + dia5 = dia4 * Geom::Affine (Geom::Translate (-h/2 * cos30, -h/2 * sin30) * Geom::Translate (-w/2 * cos60, w/2 * sin60) * Geom::Translate (w/2, -h/2)); + dia6 = dia5 * Geom::Affine (Geom::Translate (0, h)); + dia7 = dia6 * dia1.inverse(); + dia8 = dia6 * dia2.inverse(); + dia9 = dia6 * dia3.inverse(); + dia10 = dia6 * dia4.inverse(); + } else { + ori = Geom::Affine(Geom::Translate (4*h * cos30 * pow((i/12 + 0.5*(j%2)), shiftx_exp) + dx, (2*h + 2*h * sin30) * pow(j, shifty_exp) + dy)); + dia1 = Geom::Affine (Geom::Translate (-w/2, -h/2) * Geom::Translate (h/2 * cos30, -h/2 * sin30) * Geom::Translate (w/2 * cos60, w/2 * sin60)); + dia2 = dia1 * Geom::Affine (Geom::Translate (h * cos30, -h * sin30)); + dia3 = dia2 * Geom::Affine (Geom::Translate (-w/2 * cos60, -w/2 * sin60) * Geom::Translate (h * cos30, 0) * Geom::Translate (-w/2 * cos60, w/2 * sin60)); + dia4 = dia3 * Geom::Affine (Geom::Translate (h * cos30, h * sin30)); + dia5 = dia4 * Geom::Affine (Geom::Translate (w/2 * cos60, -w/2 * sin60) * Geom::Translate (h/2 * cos30, h/2 * sin30) * Geom::Translate (-w/2, h/2)); + dia6 = dia5 * Geom::Affine (Geom::Translate (0, h)); + dia7 = dia6 * dia1.inverse(); + dia8 = dia6 * dia2.inverse(); + dia9 = dia6 * dia3.inverse(); + dia10 = dia6 * dia4.inverse(); + } + if (i % 12 == 0) { + return d_s_r * ori; + } else if (i % 12 == 1) { + return d_s_r * flip_y * rotate_m60_c * dia1 * ori; + } else if (i % 12 == 2) { + return d_s_r * rotate_m60_c * dia2 * ori; + } else if (i % 12 == 3) { + return d_s_r * flip_y * rotate_m120_c * dia3 * ori; + } else if (i % 12 == 4) { + return d_s_r * rotate_m120_c * dia4 * ori; + } else if (i % 12 == 5) { + return d_s_r * flip_x * dia5 * ori; + } else if (i % 12 == 6) { + return d_s_r * flip_x * flip_y * dia6 * ori; + } else if (i % 12 == 7) { + return d_s_r * flip_y * rotate_120_c * dia7 * ori; + } else if (i % 12 == 8) { + return d_s_r * rotate_120_c * dia8 * ori; + } else if (i % 12 == 9) { + return d_s_r * flip_y * rotate_60_c * dia9 * ori; + } else if (i % 12 == 10) { + return d_s_r * rotate_60_c * dia10 * ori; + } else if (i % 12 == 11) { + return d_s_r * flip_y * Geom::Translate (0, h) * ori; + } + } + break; + + default: + break; + } + + return Geom::identity(); +} + +bool CloneTiler::clonetiler_is_a_clone_of(SPObject *tile, SPObject *obj) +{ + bool result = false; + char *id_href = NULL; + + if (obj) { + Inkscape::XML::Node *obj_repr = obj->getRepr(); + id_href = g_strdup_printf("#%s", obj_repr->attribute("id")); + } + + if (SP_IS_USE(tile) && + tile->getRepr()->attribute("xlink:href") && + (!id_href || !strcmp(id_href, tile->getRepr()->attribute("xlink:href"))) && + tile->getRepr()->attribute("inkscape:tiled-clone-of") && + (!id_href || !strcmp(id_href, tile->getRepr()->attribute("inkscape:tiled-clone-of")))) + { + result = true; + } else { + result = false; + } + if (id_href) { + g_free(id_href); + id_href = 0; + } + return result; +} + +void CloneTiler::clonetiler_trace_hide_tiled_clones_recursively(SPObject *from) +{ + if (!trace_drawing) + return; + + for (SPObject *o = from->firstChild(); o != NULL; o = o->next) { + if (SP_IS_ITEM(o) && clonetiler_is_a_clone_of (o, NULL)) + SP_ITEM(o)->invoke_hide(trace_visionkey); // FIXME: hide each tiled clone's original too! + clonetiler_trace_hide_tiled_clones_recursively (o); + } +} + +void CloneTiler::clonetiler_trace_setup(SPDocument *doc, gdouble zoom, SPItem *original) +{ + trace_drawing = new Inkscape::Drawing(); + /* Create ArenaItem and set transform */ + trace_visionkey = SPItem::display_key_new(1); + trace_doc = doc; + trace_drawing->setRoot(trace_doc->getRoot()->invoke_show(*trace_drawing, trace_visionkey, SP_ITEM_SHOW_DISPLAY)); + + // hide the (current) original and any tiled clones, we only want to pick the background + original->invoke_hide(trace_visionkey); + clonetiler_trace_hide_tiled_clones_recursively(trace_doc->getRoot()); + + trace_doc->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + trace_doc->ensureUpToDate(); + + trace_zoom = zoom; +} + +guint32 CloneTiler::clonetiler_trace_pick(Geom::Rect box) +{ + if (!trace_drawing) { + return 0; + } + + trace_drawing->root()->setTransform(Geom::Scale(trace_zoom)); + trace_drawing->update(); + + /* Item integer bbox in points */ + Geom::IntRect ibox = (box * Geom::Scale(trace_zoom)).roundOutwards(); + + /* Find visible area */ + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ibox.width(), ibox.height()); + Inkscape::DrawingContext ct(s, ibox.min()); + /* Render */ + trace_drawing->render(ct, ibox); + double R = 0, G = 0, B = 0, A = 0; + ink_cairo_surface_average_color(s, R, G, B, A); + cairo_surface_destroy(s); + + return SP_RGBA32_F_COMPOSE (R, G, B, A); +} + +void CloneTiler::clonetiler_trace_finish() +{ + if (trace_doc) { + trace_doc->getRoot()->invoke_hide(trace_visionkey); + delete trace_drawing; + trace_doc = NULL; + trace_drawing = NULL; + } +} + +void CloneTiler::clonetiler_unclump(GtkWidget */*widget*/, void *) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) { + return; + } + + Inkscape::Selection *selection = sp_desktop_selection(desktop); + + // check if something is selected + if (selection->isEmpty() || g_slist_length((GSList *) selection->itemList()) > 1) { + sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>one object</b> whose tiled clones to unclump.")); + return; + } + + SPObject *obj = selection->singleItem(); + SPObject *parent = obj->parent; + + GSList *to_unclump = NULL; // not including the original + + for (SPObject *child = parent->firstChild(); child != NULL; child = child->next) { + if (clonetiler_is_a_clone_of (child, obj)) { + to_unclump = g_slist_prepend (to_unclump, child); + } + } + + sp_desktop_document(desktop)->ensureUpToDate(); + + unclump (to_unclump); + + g_slist_free (to_unclump); + + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_DIALOG_CLONETILER, + _("Unclump tiled clones")); +} + +guint CloneTiler::clonetiler_number_of_clones(SPObject *obj) +{ + SPObject *parent = obj->parent; + + guint n = 0; + + for (SPObject *child = parent->firstChild(); child != NULL; child = child->next) { + if (clonetiler_is_a_clone_of (child, obj)) { + n ++; + } + } + + return n; +} + +void CloneTiler::clonetiler_remove(GtkWidget */*widget*/, GtkWidget *dlg, bool do_undo/* = true*/) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) { + return; + } + + Inkscape::Selection *selection = sp_desktop_selection(desktop); + + // check if something is selected + if (selection->isEmpty() || g_slist_length((GSList *) selection->itemList()) > 1) { + sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>one object</b> whose tiled clones to remove.")); + return; + } + + SPObject *obj = selection->singleItem(); + SPObject *parent = obj->parent; + +// remove old tiling + GSList *to_delete = NULL; + for (SPObject *child = parent->firstChild(); child != NULL; child = child->next) { + if (clonetiler_is_a_clone_of (child, obj)) { + to_delete = g_slist_prepend (to_delete, child); + } + } + for (GSList *i = to_delete; i; i = i->next) { + SP_OBJECT(i->data)->deleteObject(); + } + g_slist_free (to_delete); + + clonetiler_change_selection (NULL, selection, dlg); + + if (do_undo) { + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_DIALOG_CLONETILER, + _("Delete tiled clones")); + } +} + +Geom::Rect CloneTiler::transform_rect(Geom::Rect const &r, Geom::Affine const &m) +{ + using Geom::X; + using Geom::Y; + Geom::Point const p1 = r.corner(1) * m; + Geom::Point const p2 = r.corner(2) * m; + Geom::Point const p3 = r.corner(3) * m; + Geom::Point const p4 = r.corner(4) * m; + return Geom::Rect( + Geom::Point( + std::min(std::min(p1[X], p2[X]), std::min(p3[X], p4[X])), + std::min(std::min(p1[Y], p2[Y]), std::min(p3[Y], p4[Y]))), + Geom::Point( + std::max(std::max(p1[X], p2[X]), std::max(p3[X], p4[X])), + std::max(std::max(p1[Y], p2[Y]), std::max(p3[Y], p4[Y])))); +} + +/** +Randomizes \a val by \a rand, with 0 < val < 1 and all values (including 0, 1) having the same +probability of being displaced. + */ +double CloneTiler::randomize01(double val, double rand) +{ + double base = MIN (val - rand, 1 - 2*rand); + if (base < 0) { + base = 0; + } + val = base + g_random_double_range (0, MIN (2 * rand, 1 - base)); + return CLAMP(val, 0, 1); // this should be unnecessary with the above provisions, but just in case... +} + + +void CloneTiler::clonetiler_apply(GtkWidget */*widget*/, GtkWidget *dlg) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop == NULL) { + return; + } + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Inkscape::Selection *selection = sp_desktop_selection(desktop); + + // check if something is selected + if (selection->isEmpty()) { + sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select an <b>object</b> to clone.")); + return; + } + + // Check if more than one object is selected. + if (g_slist_length((GSList *) selection->itemList()) > 1) { + sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("If you want to clone several objects, <b>group</b> them and <b>clone the group</b>.")); + return; + } + + // set "busy" cursor + desktop->setWaitingCursor(); + + // set statusbar text + GtkWidget *status = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "status"); + gtk_label_set_markup (GTK_LABEL(status), _("<small>Creating tiled clones...</small>")); + gtk_widget_queue_draw(GTK_WIDGET(status)); + gdk_window_process_all_updates(); + + SPObject *obj = selection->singleItem(); + SPItem *item = SP_IS_ITEM(obj) ? SP_ITEM(obj) : 0; + Inkscape::XML::Node *obj_repr = obj->getRepr(); + const char *id_href = g_strdup_printf("#%s", obj_repr->attribute("id")); + SPObject *parent = obj->parent; + + clonetiler_remove (NULL, dlg, false); + + double shiftx_per_i = 0.01 * prefs->getDoubleLimited(prefs_path + "shiftx_per_i", 0, -10000, 10000); + double shifty_per_i = 0.01 * prefs->getDoubleLimited(prefs_path + "shifty_per_i", 0, -10000, 10000); + double shiftx_per_j = 0.01 * prefs->getDoubleLimited(prefs_path + "shiftx_per_j", 0, -10000, 10000); + double shifty_per_j = 0.01 * prefs->getDoubleLimited(prefs_path + "shifty_per_j", 0, -10000, 10000); + double shiftx_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "shiftx_rand", 0, 0, 1000); + double shifty_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "shifty_rand", 0, 0, 1000); + double shiftx_exp = prefs->getDoubleLimited(prefs_path + "shiftx_exp", 1, 0, 10); + double shifty_exp = prefs->getDoubleLimited(prefs_path + "shifty_exp", 1, 0, 10); + bool shiftx_alternate = prefs->getBool(prefs_path + "shiftx_alternate"); + bool shifty_alternate = prefs->getBool(prefs_path + "shifty_alternate"); + bool shiftx_cumulate = prefs->getBool(prefs_path + "shiftx_cumulate"); + bool shifty_cumulate = prefs->getBool(prefs_path + "shifty_cumulate"); + bool shiftx_excludew = prefs->getBool(prefs_path + "shiftx_excludew"); + bool shifty_excludeh = prefs->getBool(prefs_path + "shifty_excludeh"); + + double scalex_per_i = 0.01 * prefs->getDoubleLimited(prefs_path + "scalex_per_i", 0, -100, 1000); + double scaley_per_i = 0.01 * prefs->getDoubleLimited(prefs_path + "scaley_per_i", 0, -100, 1000); + double scalex_per_j = 0.01 * prefs->getDoubleLimited(prefs_path + "scalex_per_j", 0, -100, 1000); + double scaley_per_j = 0.01 * prefs->getDoubleLimited(prefs_path + "scaley_per_j", 0, -100, 1000); + double scalex_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "scalex_rand", 0, 0, 1000); + double scaley_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "scaley_rand", 0, 0, 1000); + double scalex_exp = prefs->getDoubleLimited(prefs_path + "scalex_exp", 1, 0, 10); + double scaley_exp = prefs->getDoubleLimited(prefs_path + "scaley_exp", 1, 0, 10); + double scalex_log = prefs->getDoubleLimited(prefs_path + "scalex_log", 0, 0, 10); + double scaley_log = prefs->getDoubleLimited(prefs_path + "scaley_log", 0, 0, 10); + bool scalex_alternate = prefs->getBool(prefs_path + "scalex_alternate"); + bool scaley_alternate = prefs->getBool(prefs_path + "scaley_alternate"); + bool scalex_cumulate = prefs->getBool(prefs_path + "scalex_cumulate"); + bool scaley_cumulate = prefs->getBool(prefs_path + "scaley_cumulate"); + + double rotate_per_i = prefs->getDoubleLimited(prefs_path + "rotate_per_i", 0, -180, 180); + double rotate_per_j = prefs->getDoubleLimited(prefs_path + "rotate_per_j", 0, -180, 180); + double rotate_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "rotate_rand", 0, 0, 100); + bool rotate_alternatei = prefs->getBool(prefs_path + "rotate_alternatei"); + bool rotate_alternatej = prefs->getBool(prefs_path + "rotate_alternatej"); + bool rotate_cumulatei = prefs->getBool(prefs_path + "rotate_cumulatei"); + bool rotate_cumulatej = prefs->getBool(prefs_path + "rotate_cumulatej"); + + double blur_per_i = 0.01 * prefs->getDoubleLimited(prefs_path + "blur_per_i", 0, 0, 100); + double blur_per_j = 0.01 * prefs->getDoubleLimited(prefs_path + "blur_per_j", 0, 0, 100); + bool blur_alternatei = prefs->getBool(prefs_path + "blur_alternatei"); + bool blur_alternatej = prefs->getBool(prefs_path + "blur_alternatej"); + double blur_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "blur_rand", 0, 0, 100); + + double opacity_per_i = 0.01 * prefs->getDoubleLimited(prefs_path + "opacity_per_i", 0, 0, 100); + double opacity_per_j = 0.01 * prefs->getDoubleLimited(prefs_path + "opacity_per_j", 0, 0, 100); + bool opacity_alternatei = prefs->getBool(prefs_path + "opacity_alternatei"); + bool opacity_alternatej = prefs->getBool(prefs_path + "opacity_alternatej"); + double opacity_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "opacity_rand", 0, 0, 100); + + Glib::ustring initial_color = prefs->getString(prefs_path + "initial_color"); + double hue_per_j = 0.01 * prefs->getDoubleLimited(prefs_path + "hue_per_j", 0, -100, 100); + double hue_per_i = 0.01 * prefs->getDoubleLimited(prefs_path + "hue_per_i", 0, -100, 100); + double hue_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "hue_rand", 0, 0, 100); + double saturation_per_j = 0.01 * prefs->getDoubleLimited(prefs_path + "saturation_per_j", 0, -100, 100); + double saturation_per_i = 0.01 * prefs->getDoubleLimited(prefs_path + "saturation_per_i", 0, -100, 100); + double saturation_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "saturation_rand", 0, 0, 100); + double lightness_per_j = 0.01 * prefs->getDoubleLimited(prefs_path + "lightness_per_j", 0, -100, 100); + double lightness_per_i = 0.01 * prefs->getDoubleLimited(prefs_path + "lightness_per_i", 0, -100, 100); + double lightness_rand = 0.01 * prefs->getDoubleLimited(prefs_path + "lightness_rand", 0, 0, 100); + bool color_alternatej = prefs->getBool(prefs_path + "color_alternatej"); + bool color_alternatei = prefs->getBool(prefs_path + "color_alternatei"); + + int type = prefs->getInt(prefs_path + "symmetrygroup", 0); + bool keepbbox = prefs->getBool(prefs_path + "keepbbox", true); + int imax = prefs->getInt(prefs_path + "imax", 2); + int jmax = prefs->getInt(prefs_path + "jmax", 2); + + bool fillrect = prefs->getBool(prefs_path + "fillrect"); + double fillwidth = prefs->getDoubleLimited(prefs_path + "fillwidth", 50, 0, 1e6); + double fillheight = prefs->getDoubleLimited(prefs_path + "fillheight", 50, 0, 1e6); + + bool dotrace = prefs->getBool(prefs_path + "dotrace"); + int pick = prefs->getInt(prefs_path + "pick"); + bool pick_to_presence = prefs->getBool(prefs_path + "pick_to_presence"); + bool pick_to_size = prefs->getBool(prefs_path + "pick_to_size"); + bool pick_to_color = prefs->getBool(prefs_path + "pick_to_color"); + bool pick_to_opacity = prefs->getBool(prefs_path + "pick_to_opacity"); + double rand_picked = 0.01 * prefs->getDoubleLimited(prefs_path + "rand_picked", 0, 0, 100); + bool invert_picked = prefs->getBool(prefs_path + "invert_picked"); + double gamma_picked = prefs->getDoubleLimited(prefs_path + "gamma_picked", 0, -10, 10); + + if (dotrace) { + clonetiler_trace_setup (sp_desktop_document(desktop), 1.0, item); + } + + Geom::Point center; + double w = 0; + double h = 0; + double x0 = 0; + double y0 = 0; + + if (keepbbox && + obj_repr->attribute("inkscape:tile-w") && + obj_repr->attribute("inkscape:tile-h") && + obj_repr->attribute("inkscape:tile-x0") && + obj_repr->attribute("inkscape:tile-y0") && + obj_repr->attribute("inkscape:tile-cx") && + obj_repr->attribute("inkscape:tile-cy")) { + + double cx = 0; + double cy = 0; + sp_repr_get_double (obj_repr, "inkscape:tile-cx", &cx); + sp_repr_get_double (obj_repr, "inkscape:tile-cy", &cy); + center = Geom::Point (cx, cy); + + sp_repr_get_double (obj_repr, "inkscape:tile-w", &w); + sp_repr_get_double (obj_repr, "inkscape:tile-h", &h); + sp_repr_get_double (obj_repr, "inkscape:tile-x0", &x0); + sp_repr_get_double (obj_repr, "inkscape:tile-y0", &y0); + } else { + bool prefs_bbox = prefs->getBool("/tools/bounding_box", false); + SPItem::BBoxType bbox_type = ( !prefs_bbox ? + SPItem::VISUAL_BBOX : SPItem::GEOMETRIC_BBOX ); + Geom::OptRect r = item->documentBounds(bbox_type); + if (r) { + w = r->dimensions()[Geom::X]; + h = r->dimensions()[Geom::Y]; + x0 = r->min()[Geom::X]; + y0 = r->min()[Geom::Y]; + center = desktop->dt2doc(item->getCenter()); + + sp_repr_set_svg_double(obj_repr, "inkscape:tile-cx", center[Geom::X]); + sp_repr_set_svg_double(obj_repr, "inkscape:tile-cy", center[Geom::Y]); + sp_repr_set_svg_double(obj_repr, "inkscape:tile-w", w); + sp_repr_set_svg_double(obj_repr, "inkscape:tile-h", h); + sp_repr_set_svg_double(obj_repr, "inkscape:tile-x0", x0); + sp_repr_set_svg_double(obj_repr, "inkscape:tile-y0", y0); + } else { + center = Geom::Point(0, 0); + w = h = 0; + x0 = y0 = 0; + } + } + + Geom::Point cur(0, 0); + Geom::Rect bbox_original (Geom::Point (x0, y0), Geom::Point (x0 + w, y0 + h)); + double perimeter_original = (w + h)/4; + + // The integers i and j are reserved for tile column and row. + // The doubles x and y are used for coordinates + for (int i = 0; + fillrect? + (fabs(cur[Geom::X]) < fillwidth && i < 200) // prevent "freezing" with too large fillrect, arbitrarily limit rows + : (i < imax); + i ++) { + for (int j = 0; + fillrect? + (fabs(cur[Geom::Y]) < fillheight && j < 200) // prevent "freezing" with too large fillrect, arbitrarily limit cols + : (j < jmax); + j ++) { + + // Note: We create a clone at 0,0 too, right over the original, in case our clones are colored + + // Get transform from symmetry, shift, scale, rotation + Geom::Affine t = clonetiler_get_transform (type, i, j, center[Geom::X], center[Geom::Y], w, h, + shiftx_per_i, shifty_per_i, + shiftx_per_j, shifty_per_j, + shiftx_rand, shifty_rand, + shiftx_exp, shifty_exp, + shiftx_alternate, shifty_alternate, + shiftx_cumulate, shifty_cumulate, + shiftx_excludew, shifty_excludeh, + scalex_per_i, scaley_per_i, + scalex_per_j, scaley_per_j, + scalex_rand, scaley_rand, + scalex_exp, scaley_exp, + scalex_log, scaley_log, + scalex_alternate, scaley_alternate, + scalex_cumulate, scaley_cumulate, + rotate_per_i, rotate_per_j, + rotate_rand, + rotate_alternatei, rotate_alternatej, + rotate_cumulatei, rotate_cumulatej ); + + cur = center * t - center; + if (fillrect) { + if ((cur[Geom::X] > fillwidth) || (cur[Geom::Y] > fillheight)) { // off limits + continue; + } + } + + gchar color_string[32]; *color_string = 0; + + // Color tab + if (!initial_color.empty()) { + guint32 rgba = sp_svg_read_color (initial_color.data(), 0x000000ff); + float hsl[3]; + sp_color_rgb_to_hsl_floatv (hsl, SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba)); + + double eff_i = (color_alternatei? (i%2) : (i)); + double eff_j = (color_alternatej? (j%2) : (j)); + + hsl[0] += hue_per_i * eff_i + hue_per_j * eff_j + hue_rand * g_random_double_range (-1, 1); + double notused; + hsl[0] = modf( hsl[0], ¬used ); // Restrict to 0-1 + hsl[1] += saturation_per_i * eff_i + saturation_per_j * eff_j + saturation_rand * g_random_double_range (-1, 1); + hsl[1] = CLAMP (hsl[1], 0, 1); + hsl[2] += lightness_per_i * eff_i + lightness_per_j * eff_j + lightness_rand * g_random_double_range (-1, 1); + hsl[2] = CLAMP (hsl[2], 0, 1); + + float rgb[3]; + sp_color_hsl_to_rgb_floatv (rgb, hsl[0], hsl[1], hsl[2]); + sp_svg_write_color(color_string, sizeof(color_string), SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1.0)); + } + + // Blur + double blur = 0.0; + { + int eff_i = (blur_alternatei? (i%2) : (i)); + int eff_j = (blur_alternatej? (j%2) : (j)); + blur = (blur_per_i * eff_i + blur_per_j * eff_j + blur_rand * g_random_double_range (-1, 1)); + blur = CLAMP (blur, 0, 1); + } + + // Opacity + double opacity = 1.0; + { + int eff_i = (opacity_alternatei? (i%2) : (i)); + int eff_j = (opacity_alternatej? (j%2) : (j)); + opacity = 1 - (opacity_per_i * eff_i + opacity_per_j * eff_j + opacity_rand * g_random_double_range (-1, 1)); + opacity = CLAMP (opacity, 0, 1); + } + + // Trace tab + if (dotrace) { + Geom::Rect bbox_t = transform_rect (bbox_original, t); + + guint32 rgba = clonetiler_trace_pick (bbox_t); + float r = SP_RGBA32_R_F(rgba); + float g = SP_RGBA32_G_F(rgba); + float b = SP_RGBA32_B_F(rgba); + float a = SP_RGBA32_A_F(rgba); + + float hsl[3]; + sp_color_rgb_to_hsl_floatv (hsl, r, g, b); + + gdouble val = 0; + switch (pick) { + case PICK_COLOR: + val = 1 - hsl[2]; // inverse lightness; to match other picks where black = max + break; + case PICK_OPACITY: + val = a; + break; + case PICK_R: + val = r; + break; + case PICK_G: + val = g; + break; + case PICK_B: + val = b; + break; + case PICK_H: + val = hsl[0]; + break; + case PICK_S: + val = hsl[1]; + break; + case PICK_L: + val = 1 - hsl[2]; + break; + default: + break; + } + + if (rand_picked > 0) { + val = randomize01 (val, rand_picked); + r = randomize01 (r, rand_picked); + g = randomize01 (g, rand_picked); + b = randomize01 (b, rand_picked); + } + + if (gamma_picked != 0) { + double power; + if (gamma_picked > 0) + power = 1/(1 + fabs(gamma_picked)); + else + power = 1 + fabs(gamma_picked); + + val = pow (val, power); + r = pow (r, power); + g = pow (g, power); + b = pow (b, power); + } + + if (invert_picked) { + val = 1 - val; + r = 1 - r; + g = 1 - g; + b = 1 - b; + } + + val = CLAMP (val, 0, 1); + r = CLAMP (r, 0, 1); + g = CLAMP (g, 0, 1); + b = CLAMP (b, 0, 1); + + // recompose tweaked color + rgba = SP_RGBA32_F_COMPOSE(r, g, b, a); + + if (pick_to_presence) { + if (g_random_double_range (0, 1) > val) { + continue; // skip! + } + } + if (pick_to_size) { + t = Geom::Translate(-center[Geom::X], -center[Geom::Y]) * Geom::Scale (val, val) * Geom::Translate(center[Geom::X], center[Geom::Y]) * t; + } + if (pick_to_opacity) { + opacity *= val; + } + if (pick_to_color) { + sp_svg_write_color(color_string, sizeof(color_string), rgba); + } + } + + if (opacity < 1e-6) { // invisibly transparent, skip + continue; + } + + if (fabs(t[0]) + fabs (t[1]) + fabs(t[2]) + fabs(t[3]) < 1e-6) { // too small, skip + continue; + } + + // Create the clone + Inkscape::XML::Node *clone = obj_repr->document()->createElement("svg:use"); + clone->setAttribute("x", "0"); + clone->setAttribute("y", "0"); + clone->setAttribute("inkscape:tiled-clone-of", id_href); + clone->setAttribute("xlink:href", id_href); + + Geom::Point new_center; + bool center_set = false; + if (obj_repr->attribute("inkscape:transform-center-x") || obj_repr->attribute("inkscape:transform-center-y")) { + new_center = desktop->dt2doc(item->getCenter()) * t; + center_set = true; + } + + gchar *affinestr=sp_svg_transform_write(t); + clone->setAttribute("transform", affinestr); + g_free(affinestr); + + if (opacity < 1.0) { + sp_repr_set_css_double(clone, "opacity", opacity); + } + + if (*color_string) { + clone->setAttribute("fill", color_string); + clone->setAttribute("stroke", color_string); + } + + // add the new clone to the top of the original's parent + parent->getRepr()->appendChild(clone); + + if (blur > 0.0) { + SPObject *clone_object = sp_desktop_document(desktop)->getObjectByRepr(clone); + double perimeter = perimeter_original * t.descrim(); + double radius = blur * perimeter; + // this is necessary for all newly added clones to have correct bboxes, + // otherwise filters won't work: + sp_desktop_document(desktop)->ensureUpToDate(); + // it's hard to figure out exact width/height of the tile without having an object + // that we can take bbox of; however here we only need a lower bound so that blur + // margins are not too small, and the perimeter should work + SPFilter *constructed = new_filter_gaussian_blur(sp_desktop_document(desktop), radius, t.descrim(), t.expansionX(), t.expansionY(), perimeter, perimeter); + sp_style_set_property_url (clone_object, "filter", constructed, false); + } + + if (center_set) { + SPObject *clone_object = sp_desktop_document(desktop)->getObjectByRepr(clone); + if (clone_object && SP_IS_ITEM(clone_object)) { + clone_object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + SP_ITEM(clone_object)->setCenter(desktop->doc2dt(new_center)); + clone_object->updateRepr(); + } + } + + Inkscape::GC::release(clone); + } + cur[Geom::Y] = 0; + } + + if (dotrace) { + clonetiler_trace_finish (); + } + + clonetiler_change_selection (NULL, selection, dlg); + + desktop->clearWaitingCursor(); + + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_DIALOG_CLONETILER, + _("Create tiled clones")); +} + +GtkWidget * CloneTiler::clonetiler_new_tab(GtkWidget *nb, const gchar *label) +{ + GtkWidget *l = gtk_label_new_with_mnemonic (label); + GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN); + gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN); + gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l); + return vb; +} + +void CloneTiler::clonetiler_checkbox_toggled(GtkToggleButton *tb, gpointer *data) +{ + const gchar *attr = (const gchar *) data; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(prefs_path + attr, gtk_toggle_button_get_active(tb)); +} + +GtkWidget * CloneTiler::clonetiler_checkbox(const char *tip, const char *attr) +{ + GtkWidget *hb = gtk_hbox_new(FALSE, VB_MARGIN); + + GtkWidget *b = gtk_check_button_new (); + gtk_widget_set_tooltip_text (b, tip); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool value = prefs->getBool(prefs_path + attr); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(b), value); + + gtk_box_pack_end (GTK_BOX (hb), b, FALSE, TRUE, 0); + g_signal_connect ( G_OBJECT (b), "clicked", + G_CALLBACK (clonetiler_checkbox_toggled), (gpointer) attr); + + g_object_set_data (G_OBJECT(b), "uncheckable", GINT_TO_POINTER(TRUE)); + + return hb; +} + +void CloneTiler::clonetiler_value_changed(GtkAdjustment *adj, gpointer data) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + const gchar *pref = (const gchar *) data; + prefs->setDouble(prefs_path + pref, gtk_adjustment_get_value (adj)); +} + +GtkWidget * CloneTiler::clonetiler_spinbox(const char *tip, const char *attr, double lower, double upper, const gchar *suffix, bool exponent/* = false*/) +{ + GtkWidget *hb = gtk_hbox_new(FALSE, 0); + + { + Gtk::Adjustment *a; + if (exponent) { + a = new Gtk::Adjustment (1.0, lower, upper, 0.01, 0.05, 0); + } else { + a = new Gtk::Adjustment (0.0, lower, upper, 0.1, 0.5, 0); + } + + Inkscape::UI::Widget::SpinButton *sb; + if (exponent) { + sb = new Inkscape::UI::Widget::SpinButton (*a, 0.01, 2); + } else { + sb = new Inkscape::UI::Widget::SpinButton (*a, 0.1, 1); + } + + sb->set_tooltip_text (tip); + sb->set_width_chars (5); + sb->set_digits(3); + gtk_box_pack_start (GTK_BOX (hb), GTK_WIDGET(sb->gobj()), FALSE, FALSE, SB_MARGIN); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double value = prefs->getDoubleLimited(prefs_path + attr, exponent? 1.0 : 0.0, lower, upper); + a->set_value (value); + // TODO: C++ification + g_signal_connect(G_OBJECT(a->gobj()), "value_changed", + G_CALLBACK(clonetiler_value_changed), (gpointer) attr); + + if (exponent) { + sb->set_data ("oneable", GINT_TO_POINTER(TRUE)); + } else { + sb->set_data ("zeroable", GINT_TO_POINTER(TRUE)); + } + } + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), suffix); + gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 0); + } + + return hb; +} + +void CloneTiler::clonetiler_symgroup_changed(GtkComboBox *cb, gpointer /*data*/) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint group_new = gtk_combo_box_get_active (cb); + prefs->setInt(prefs_path + "symmetrygroup", group_new); +} + +void CloneTiler::clonetiler_xy_changed(GtkAdjustment *adj, gpointer data) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + const gchar *pref = (const gchar *) data; + prefs->setInt(prefs_path + pref, (int) floor(gtk_adjustment_get_value (adj) + 0.5)); +} + +void CloneTiler::clonetiler_keep_bbox_toggled(GtkToggleButton *tb, gpointer /*data*/) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(prefs_path + "keepbbox", gtk_toggle_button_get_active(tb)); +} + +void CloneTiler::clonetiler_pick_to(GtkToggleButton *tb, gpointer data) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + const gchar *pref = (const gchar *) data; + prefs->setBool(prefs_path + pref, gtk_toggle_button_get_active(tb)); +} + + +void CloneTiler::clonetiler_reset_recursive(GtkWidget *w) +{ + if (w && GTK_IS_OBJECT(w)) { + { + int r = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(w), "zeroable")); + if (r && GTK_IS_SPIN_BUTTON(w)) { // spinbutton + GtkAdjustment *a = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(w)); + gtk_adjustment_set_value (a, 0); + } + } + { + int r = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(w), "oneable")); + if (r && GTK_IS_SPIN_BUTTON(w)) { // spinbutton + GtkAdjustment *a = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(w)); + gtk_adjustment_set_value (a, 1); + } + } + { + int r = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(w), "uncheckable")); + if (r && GTK_IS_TOGGLE_BUTTON(w)) { // checkbox + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(w), FALSE); + } + } + } + + if (GTK_IS_CONTAINER(w)) { + GList *ch = gtk_container_get_children (GTK_CONTAINER(w)); + for (GList *i = ch; i != NULL; i = i->next) { + clonetiler_reset_recursive (GTK_WIDGET(i->data)); + } + g_list_free (ch); + } +} + +void CloneTiler::clonetiler_reset(GtkWidget */*widget*/, GtkWidget *dlg) +{ + clonetiler_reset_recursive (dlg); +} + +void CloneTiler::clonetiler_table_attach(GtkWidget *table, GtkWidget *widget, float align, int row, int col) +{ + GtkWidget *a = gtk_alignment_new (align, 0, 0, 0); + gtk_container_add(GTK_CONTAINER(a), widget); + gtk_table_attach ( GTK_TABLE (table), a, col, col + 1, row, row + 1, (GtkAttachOptions)4, (GtkAttachOptions)0, 0, 0 ); +} + +GtkWidget * CloneTiler::clonetiler_table_x_y_rand(int values) +{ + GtkWidget *table = gtk_table_new (values + 2, 5, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), VB_MARGIN); + gtk_table_set_row_spacings (GTK_TABLE (table), 6); + gtk_table_set_col_spacings (GTK_TABLE (table), 8); + + { + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + + GtkWidget *i = sp_icon_new (Inkscape::ICON_SIZE_DECORATION, INKSCAPE_ICON("object-rows")); + gtk_box_pack_start (GTK_BOX (hb), i, FALSE, FALSE, 2); + + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<small>Per row:</small>")); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 2); + + clonetiler_table_attach (table, hb, 0, 1, 2); + } + + { + GtkWidget *hb = gtk_hbox_new (FALSE, 0); + + GtkWidget *i = sp_icon_new (Inkscape::ICON_SIZE_DECORATION, INKSCAPE_ICON("object-columns")); + gtk_box_pack_start (GTK_BOX (hb), i, FALSE, FALSE, 2); + + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<small>Per column:</small>")); + gtk_box_pack_start (GTK_BOX (hb), l, FALSE, FALSE, 2); + + clonetiler_table_attach (table, hb, 0, 1, 3); + } + + { + GtkWidget *l = gtk_label_new (""); + gtk_label_set_markup (GTK_LABEL(l), _("<small>Randomize:</small>")); + clonetiler_table_attach (table, l, 0, 1, 4); + } + + return table; +} + +void CloneTiler::clonetiler_pick_switched(GtkToggleButton */*tb*/, gpointer data) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint v = GPOINTER_TO_INT (data); + prefs->setInt(prefs_path + "pick", v); +} + + +void CloneTiler::clonetiler_switch_to_create(GtkToggleButton */*tb*/, GtkWidget *dlg) +{ + GtkWidget *rowscols = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "rowscols"); + GtkWidget *widthheight = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "widthheight"); + + if (rowscols) { + gtk_widget_set_sensitive (rowscols, TRUE); + } + if (widthheight) { + gtk_widget_set_sensitive (widthheight, FALSE); + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(prefs_path + "fillrect", false); +} + + +void CloneTiler::clonetiler_switch_to_fill(GtkToggleButton */*tb*/, GtkWidget *dlg) +{ + GtkWidget *rowscols = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "rowscols"); + GtkWidget *widthheight = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "widthheight"); + + if (rowscols) { + gtk_widget_set_sensitive (rowscols, FALSE); + } + if (widthheight) { + gtk_widget_set_sensitive (widthheight, TRUE); + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(prefs_path + "fillrect", true); +} + + + + +void CloneTiler::clonetiler_fill_width_changed(GtkAdjustment *adj, GtkWidget *u) +{ + gdouble const raw_dist = gtk_adjustment_get_value (adj); + SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(u)); + gdouble const pixels = sp_units_get_pixels (raw_dist, unit); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble(prefs_path + "fillwidth", pixels); +} + +void CloneTiler::clonetiler_fill_height_changed(GtkAdjustment *adj, GtkWidget *u) +{ + gdouble const raw_dist = gtk_adjustment_get_value (adj); + SPUnit const &unit = *sp_unit_selector_get_unit(SP_UNIT_SELECTOR(u)); + gdouble const pixels = sp_units_get_pixels (raw_dist, unit); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble(prefs_path + "fillheight", pixels); +} + + +void CloneTiler::clonetiler_do_pick_toggled(GtkToggleButton *tb, GtkWidget *dlg) +{ + GtkWidget *vvb = (GtkWidget *) g_object_get_data (G_OBJECT(dlg), "dotrace"); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(prefs_path + "dotrace", gtk_toggle_button_get_active (tb)); + + if (vvb) { + gtk_widget_set_sensitive (vvb, gtk_toggle_button_get_active (tb)); + } +} + + +} +} +} + + +/* + 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/clonetiler.h b/src/ui/dialog/clonetiler.h new file mode 100644 index 000000000..2cad30e9e --- /dev/null +++ b/src/ui/dialog/clonetiler.h @@ -0,0 +1,183 @@ +/** @file + * @brief Clone tiling dialog + */ +/* Authors: + * bulia byak <buliabyak@users.sf.net> + * + * Copyright (C) 2004 Authors + * Released under the GNU GPL, read the file 'COPYING' for more information + */ +#ifndef __SP_CLONE_TILER_H__ +#define __SP_CLONE_TILER_H__ + +#include "ui/widget/panel.h" +#include <glib.h> +#include <gtk/gtk.h> + +#include "ui/dialog/desktop-tracker.h" +#include "ui/widget/color-picker.h" +#include "sp-root.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + +class CloneTiler : public Widget::Panel { +public: + CloneTiler(); + virtual ~CloneTiler(); + + static CloneTiler &getInstance() { return *new CloneTiler(); } + +protected: + + GtkWidget * clonetiler_new_tab(GtkWidget *nb, const gchar *label); + GtkWidget * clonetiler_table_x_y_rand(int values); + GtkWidget * clonetiler_spinbox(const char *tip, const char *attr, double lower, double upper, const gchar *suffix, bool exponent = false); + GtkWidget * clonetiler_checkbox(const char *tip, const char *attr); + void clonetiler_table_attach(GtkWidget *table, GtkWidget *widget, float align, int row, int col); + + static void clonetiler_symgroup_changed(GtkComboBox *cb, gpointer /*data*/); + static void clonetiler_remove(GtkWidget */*widget*/, GtkWidget *dlg, bool do_undo = true); + static void on_picker_color_changed(guint rgba); + static void clonetiler_trace_hide_tiled_clones_recursively(SPObject *from); + static void clonetiler_checkbox_toggled(GtkToggleButton *tb, gpointer *data); + static void clonetiler_pick_switched(GtkToggleButton */*tb*/, gpointer data); + static void clonetiler_do_pick_toggled(GtkToggleButton *tb, GtkWidget *dlg); + static void clonetiler_pick_to(GtkToggleButton *tb, gpointer data); + static void clonetiler_xy_changed(GtkAdjustment *adj, gpointer data); + static void clonetiler_fill_width_changed(GtkAdjustment *adj, GtkWidget *u); + static void clonetiler_fill_height_changed(GtkAdjustment *adj, GtkWidget *u); + static void clonetiler_switch_to_create(GtkToggleButton */*tb*/, GtkWidget *dlg); + static void clonetiler_switch_to_fill(GtkToggleButton */*tb*/, GtkWidget *dlg); + static void clonetiler_keep_bbox_toggled(GtkToggleButton *tb, gpointer /*data*/); + static void clonetiler_apply(GtkWidget */*widget*/, GtkWidget *dlg); + static void clonetiler_unclump(GtkWidget */*widget*/, void *); + static void clonetiler_change_selection(Inkscape::Application * /*inkscape*/, Inkscape::Selection *selection, GtkWidget *dlg); + static void clonetiler_external_change(Inkscape::Application * /*inkscape*/, GtkWidget *dlg); + static void clonetiler_disconnect_gsignal(GObject *widget, gpointer source); + static void clonetiler_reset(GtkWidget */*widget*/, GtkWidget *dlg); + static guint clonetiler_number_of_clones(SPObject *obj); + static void clonetiler_trace_setup(SPDocument *doc, gdouble zoom, SPItem *original); + static guint32 clonetiler_trace_pick(Geom::Rect box); + static void clonetiler_trace_finish(); + static bool clonetiler_is_a_clone_of(SPObject *tile, SPObject *obj); + static Geom::Rect transform_rect(Geom::Rect const &r, Geom::Affine const &m); + static double randomize01(double val, double rand); + static void clonetiler_value_changed(GtkAdjustment *adj, gpointer data); + static void clonetiler_reset_recursive(GtkWidget *w); + + static Geom::Affine clonetiler_get_transform( // symmetry group + int type, + + // row, column + int i, int j, + + // center, width, height of the tile + double cx, double cy, + double w, double h, + + // values from the dialog: + // Shift + double shiftx_per_i, double shifty_per_i, + double shiftx_per_j, double shifty_per_j, + double shiftx_rand, double shifty_rand, + double shiftx_exp, double shifty_exp, + int shiftx_alternate, int shifty_alternate, + int shiftx_cumulate, int shifty_cumulate, + int shiftx_excludew, int shifty_excludeh, + + // Scale + double scalex_per_i, double scaley_per_i, + double scalex_per_j, double scaley_per_j, + double scalex_rand, double scaley_rand, + double scalex_exp, double scaley_exp, + double scalex_log, double scaley_log, + int scalex_alternate, int scaley_alternate, + int scalex_cumulate, int scaley_cumulate, + + // Rotation + double rotate_per_i, double rotate_per_j, + double rotate_rand, + int rotate_alternatei, int rotate_alternatej, + int rotate_cumulatei, int rotate_cumulatej + ); + + +private: + CloneTiler(CloneTiler const &d); + CloneTiler& operator=(CloneTiler const &d); + + GtkWidget *dlg; + SPDesktop *desktop; + DesktopTracker deskTrack; + Inkscape::UI::Widget::ColorPicker *color_picker; + GtkSizeGroup* table_row_labels; + + sigc::connection desktopChangeConn; + sigc::connection selectChangedConn; + sigc::connection subselChangedConn; + sigc::connection selectModifiedConn; + sigc::connection color_changed_connection; + + /** + * Can be invoked for setting the desktop. Currently not used. + */ + void setDesktop(SPDesktop *desktop); + + /** + * Is invoked by the desktop tracker when the desktop changes. + */ + void setTargetDesktop(SPDesktop *desktop); + +}; + + +enum { + PICK_COLOR, + PICK_OPACITY, + PICK_R, + PICK_G, + PICK_B, + PICK_H, + PICK_S, + PICK_L +}; + +enum { + TILE_P1, + TILE_P2, + TILE_PM, + TILE_PG, + TILE_CM, + TILE_PMM, + TILE_PMG, + TILE_PGG, + TILE_CMM, + TILE_P4, + TILE_P4M, + TILE_P4G, + TILE_P3, + TILE_P31M, + TILE_P3M1, + TILE_P6, + TILE_P6M +}; + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/dialog/dialog-manager.cpp b/src/ui/dialog/dialog-manager.cpp index 2e7ffa1c2..60011ca9d 100644 --- a/src/ui/dialog/dialog-manager.cpp +++ b/src/ui/dialog/dialog-manager.cpp @@ -51,6 +51,7 @@ #include "ui/dialog/spellcheck.h" #include "ui/dialog/export.h" #include "ui/dialog/xml-tree.h" +#include "ui/dialog/clonetiler.h" #ifdef ENABLE_SVG_FONTS #include "ui/dialog/svg-fonts-dialog.h" @@ -129,6 +130,7 @@ DialogManager::DialogManager() { registerFactory("SpellCheck", &create<SpellCheck, FloatingBehavior>); registerFactory("Export", &create<Export, FloatingBehavior>); registerFactory("XmlTree", &create<XmlTree, FloatingBehavior>); + registerFactory("CloneTiler", &create<CloneTiler, FloatingBehavior>); } else { @@ -163,6 +165,7 @@ DialogManager::DialogManager() { registerFactory("SpellCheck", &create<SpellCheck, DockBehavior>); registerFactory("Export", &create<Export, DockBehavior>); registerFactory("XmlTree", &create<XmlTree, DockBehavior>); + registerFactory("CloneTiler", &create<CloneTiler, DockBehavior>); } } diff --git a/src/ui/dialog/find.cpp b/src/ui/dialog/find.cpp index 9d3508c53..f5745e02b 100644 --- a/src/ui/dialog/find.cpp +++ b/src/ui/dialog/find.cpp @@ -24,12 +24,14 @@ #include "inkscape.h" #include "desktop.h" #include "document.h" +#include "document-undo.h" #include "selection.h" #include "desktop-handles.h" #include "dialogs/dialog-events.h" #include "verbs.h" #include "interface.h" +#include "preferences.h" #include "sp-text.h" #include "sp-flowtext.h" #include "text-editing.h" @@ -50,6 +52,8 @@ #include "sp-offset.h" #include "sp-root.h" #include "xml/repr.h" +#include "xml/node-iterators.h" +#include "xml/attribute-record.h" #include <glibmm/i18n.h> @@ -59,112 +63,278 @@ namespace Dialog { Find::Find() : UI::Widget::Panel("", "/dialogs/find", SP_VERB_DIALOG_FIND), - _entry_text(_("_Text:"), _("Find objects by their text content (exact or partial match)")), - _entry_id(_("_ID:"), _("Find objects by the value of the id attribute (exact or partial match)")), - _entry_style(_("_Style:"), _("Find objects by the value of the style attribute (exact or partial match)")), - _entry_attribute(_("_Attribute:"), _("Find objects by the name of an attribute (exact or partial match)")), - _check_search_selection(_("Search in s_election"), _("Limit search to the current selection")), - _check_search_layer(_("Search in current _layer"), _("Limit search to the current layer")), - _check_include_hidden(_("Include _hidden"), _("Include hidden objects in search")), - _check_include_locked(_("Include l_ocked"), _("Include locked objects in search")), - - _check_all(_("All types"), _("Search in all object types")), - _check_all_shapes(_("All shapes"), _("Search all shapes")), - _check_rects(_("Rectangles"), _("Search rectangles")), - _check_ellipses(_("Ellipses"), _("Search ellipses, arcs, circles")), - _check_stars(_("Stars"), _("Search stars and polygons")), - _check_spirals(_("Spirals"), _("Search spirals")), - _check_paths(_("Paths"), _("Search paths, lines, polylines")), - _check_texts(_("Texts"), _("Search text objects")), - _check_groups(_("Groups"), _("Search groups")), - _check_clones( + + entry_find(_("F_ind:"), _("Find objects by their content (exact or partial match)")), + entry_replace(_("Re_place:"), _("Replace found objects with this value ")), + + check_scope_all(_("_All"), _("Search in all layers")), + check_scope_layer(_("Current _layer"), _("Limit search to the current layer")), + check_scope_selection(_("S_election"), _("Limit search to the current selection")), + check_searchin_text(_("Te_xt"), _("Search in text objects")), + check_searchin_property(_("_Properties"), _("Search in object properties, styles, attributes and IDs")), + frame_searchin(_("Search in")), + frame_scope(_("Scope")), + + + check_case_sensitive(_("Case sensiti_ve"), _("Match upper/lower case"), false), + check_exact_match(_("E_xact match"), _("Match whole objects only"), false), + check_include_hidden(_("Include _hidden"), _("Include hidden objects in search"), false), + check_include_locked(_("Include loc_ked"), _("Include locked objects in search"), false), + expander_options(_("Options")), + frame_options(_("General")), + + check_ids(_("_ID"), _("Search id name"), true), + check_attributename(_("Attribute _Name"), _("Search attribute name"), false), + check_attributevalue(_("Attribute _Value"), _("Search attribute value"), true), + check_style(_("_Style"), _("Search style"), true), + check_font(_("_Font"), _("Search fonts"), false), + frame_properties(_("Properties")), + + check_alltypes(_("All types"), _("Search all object types"), true), + check_rects(_("Rectangles"), _("Search rectangles"), false), + check_ellipses(_("Ellipses"), _("Search ellipses, arcs, circles"), false), + check_stars(_("Stars"), _("Search stars and polygons"), false), + check_spirals(_("Spirals"), _("Search spirals"), false), + check_paths(_("Paths"), _("Search paths, lines, polylines"), false), + check_texts(_("Texts"), _("Search text objects"), false), + check_groups(_("Groups"), _("Search groups"), false), + check_clones( //TRANSLATORS: "Clones" is a noun indicating type of object to find - C_("Find dialog", "Clones"), _("Search clones")), - _check_images(_("Images"), _("Search images")), - _check_offsets(_("Offsets"), _("Search offset objects")), - - _button_clear(_("_Clear"), _("Clear values")), - _button_find(_("_Find"), _("Select objects matching all of the fields you filled in")) + C_("Find dialog", "Clones"), _("Search clones"), false), + + check_images(_("Images"), _("Search images"), false), + check_offsets(_("Offsets"), _("Search offset objects"), false), + frame_types(_("Object Types")), + + status(""), + button_find(_("_Find"), _("Select all objects matching the selected fields ")), + button_replace(_("_Replace All"), _("Replace all the matching objects")), + _action_replace(false), + blocked(false), + desktop(NULL), + deskTrack() + { + entry_find.getEntry()->set_width_chars(25); + entry_replace.getEntry()->set_width_chars(25); + + Gtk::RadioButtonGroup grp_searchin = check_searchin_text.get_group(); + check_searchin_property.set_group(grp_searchin); + vbox_searchin.pack_start(check_searchin_text, true, true); + vbox_searchin.pack_start(check_searchin_property, true, true); + frame_searchin.add(vbox_searchin); + + Gtk::RadioButtonGroup grp_scope = check_scope_all.get_group(); + check_scope_layer.set_group(grp_scope); + check_scope_selection.set_group(grp_scope); + vbox_scope.pack_start(check_scope_all, true, true); + vbox_scope.pack_start(check_scope_layer, true, true); + vbox_scope.pack_start(check_scope_selection, true, true); + frame_scope.add(vbox_scope); + + hbox_searchin.set_spacing(4); + hbox_searchin.pack_start(frame_searchin, true, true); + hbox_searchin.pack_start(frame_scope, true, true); + + vbox_options1.pack_start(check_case_sensitive, true, true); + vbox_options1.pack_start(check_include_hidden, true, true); + vbox_options2.pack_start(check_exact_match, true, true); + vbox_options2.pack_start(check_include_locked, true, true); + hbox_options.pack_start(vbox_options1, true, true, 4); + hbox_options.pack_start(vbox_options2, true, true, 4); + frame_options.add(hbox_options); + + hbox_properties1.set_homogeneous(false); + hbox_properties1.pack_start(check_ids, false, false, 4 ); + hbox_properties1.pack_start(check_style, false, false, 8); + hbox_properties1.pack_start(check_font, false, false, 8); + hbox_properties2.set_homogeneous(false); + hbox_properties2.pack_start(check_attributevalue, false, false, 4); + hbox_properties2.pack_start(check_attributename, false, false, 4); + vbox_properties.pack_start(hbox_properties1, true, true, 0); + vbox_properties.pack_start(hbox_properties2, true, true, 2); + frame_properties.add(vbox_properties); + + vbox_types1.pack_start(check_alltypes, true, true); + vbox_types1.pack_start(check_paths, true, true); + vbox_types1.pack_start(check_texts, true, true); + vbox_types1.pack_start(check_groups, true, true); + vbox_types1.pack_start(check_clones, true, true); + vbox_types1.pack_start(check_images, true, true); + vbox_types2.pack_start(check_offsets, true, true); + vbox_types2.pack_start(check_rects, true, true); + vbox_types2.pack_start(check_ellipses, true, true); + vbox_types2.pack_start(check_stars, true, true); + vbox_types2.pack_start(check_spirals, true, true); + hbox_types.pack_start(vbox_types1, true, true, 4); + hbox_types.pack_start(vbox_types2, true, true, 4); + frame_types.add(hbox_types); + + vbox_expander.pack_start(frame_options, true, true, 4); + vbox_expander.pack_start(frame_properties, true, true, 4); + vbox_expander.pack_start(frame_types, true, true, 4); + + expander_options.set_use_underline(); + expander_options.add(vbox_expander); + + box_buttons.set_layout(Gtk::BUTTONBOX_END); + box_buttons.set_spacing(4); + box_buttons.pack_start(button_find, true, true, 6); + box_buttons.pack_start(button_replace, true, true, 6); + hboxbutton_row.pack_start(status, true, true, 6); + hboxbutton_row.pack_end(box_buttons, true, true); + Gtk::Box *contents = _getContents(); - contents->set_spacing(4); - - contents->pack_start(_entry_text, true, true); - contents->pack_start(_entry_id, true, true); - contents->pack_start(_entry_style, true, true); - contents->pack_start(_entry_attribute, true, true); - - contents->pack_start(_check_all, true, true); - contents->pack_start(_check_all_shapes, true, true); - contents->pack_start(_check_rects, true, true); - contents->pack_start(_check_ellipses, true, true); - contents->pack_start(_check_stars, true, true); - contents->pack_start(_check_spirals, true, true); - contents->pack_start(_check_paths, true, true); - contents->pack_start(_check_texts, true, true); - contents->pack_start(_check_groups, true, true); - contents->pack_start(_check_clones, true, true); - contents->pack_start(_check_images, true, true); - contents->pack_start(_check_offsets, true, true); - - contents->pack_start(_check_search_selection, true, true); - contents->pack_start(_check_search_layer, true, true); - contents->pack_start(_check_include_hidden, true, true); - contents->pack_start(_check_include_locked, true, true); - - contents->pack_start(_button_clear, true, true); - contents->pack_start(_button_find, true, true); + contents->set_spacing(6); + contents->pack_start(entry_find, false, false); + contents->pack_start(entry_replace, false, false); + contents->pack_start(hbox_searchin, false, false); + contents->pack_start(expander_options, false, false); + contents->pack_end(hboxbutton_row, false, false); + + checkProperties.push_back(&check_ids); + checkProperties.push_back(&check_style); + checkProperties.push_back(&check_font); + checkProperties.push_back(&check_attributevalue); + checkProperties.push_back(&check_attributename); + + checkTypes.push_back(&check_paths); + checkTypes.push_back(&check_texts); + checkTypes.push_back(&check_groups); + checkTypes.push_back(&check_clones); + checkTypes.push_back(&check_images); + checkTypes.push_back(&check_offsets); + checkTypes.push_back(&check_rects); + checkTypes.push_back(&check_ellipses); + checkTypes.push_back(&check_stars); + checkTypes.push_back(&check_spirals); + checkTypes.push_back(&check_offsets); // set signals to handle clicks - _check_all.signal_clicked().connect(sigc::mem_fun(*this, &Find::onToggleAlltypes)); - _check_all_shapes.signal_clicked().connect(sigc::mem_fun(*this, &Find::onToggleShapes)); - _button_clear.signal_clicked().connect(sigc::mem_fun(*this, &Find::onClear)); - _button_find.signal_clicked().connect(sigc::mem_fun(*this, &Find::onFind)); + expander_options.property_expanded().signal_changed().connect(sigc::mem_fun(*this, &Find::onExpander)); + button_find.signal_clicked().connect(sigc::mem_fun(*this, &Find::onFind)); + button_replace.signal_clicked().connect(sigc::mem_fun(*this, &Find::onReplace)); + check_searchin_text.signal_clicked().connect(sigc::mem_fun(*this, &Find::onSearchinText)); + check_searchin_property.signal_clicked().connect(sigc::mem_fun(*this, &Find::onSearchinProperty)); + check_alltypes.signal_clicked().connect(sigc::mem_fun(*this, &Find::onToggleAlltypes)); + + for(size_t i = 0; i < checkProperties.size(); i++) { + checkProperties[i]->signal_clicked().connect(sigc::mem_fun(*this, &Find::onToggleCheck)); + } + + for(size_t i = 0; i < checkTypes.size(); i++) { + checkTypes[i]->signal_clicked().connect(sigc::mem_fun(*this, &Find::onToggleCheck)); + } + + onSearchinText(); + onToggleAlltypes(); - _button_find.set_can_default(); - // set_default (_button_find); // activatable by Enter - _entry_text.getEntry()->grab_focus(); + desktopChangeConn = deskTrack.connectDesktopChanged( sigc::mem_fun(*this, &Find::setTargetDesktop) ); + deskTrack.connect(GTK_WIDGET(gobj())); show_all_children(); - onClear(); + + Inkscape::Selection *selection = sp_desktop_selection (SP_ACTIVE_DESKTOP); + SPItem *item = selection->singleItem(); + if (item) { + if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { + gchar *str; + str = sp_te_get_string_multiline (item); + entry_find.getEntry()->set_text(str); + } + } + + button_find.set_can_default(); + //button_find.grab_default(); // activatable by Enter + entry_find.getEntry()->grab_focus(); } -Find::~Find() +Find::~Find() { + desktopChangeConn.disconnect(); + selectChangedConn.disconnect(); + deskTrack.disconnect(); } +void Find::setDesktop(SPDesktop *desktop) +{ + Panel::setDesktop(desktop); + deskTrack.setBase(desktop); +} + +void Find::setTargetDesktop(SPDesktop *desktop) +{ + if (this->desktop != desktop) { + if (this->desktop) { + selectChangedConn.disconnect(); + } + this->desktop = desktop; + if (desktop && desktop->selection) { + selectChangedConn = desktop->selection->connectChanged(sigc::hide(sigc::mem_fun(*this, &Find::onSelectionChange))); + } + } +} + +void Find::onSelectionChange(void) +{ + if (!blocked) { + status.set_text(""); + } +} /*######################################################################## # FIND helper functions ########################################################################*/ - -bool -Find::item_id_match (SPItem *item, const gchar *id, bool exact) +Glib::ustring Find::find_replace(const gchar *str, const gchar *find, const gchar *replace, bool exact, bool casematch, bool replaceall) { - if (item->getRepr() == NULL) { - return false; + Glib::ustring ustr = str; + Glib::ustring ufind = find; + if (!casematch) { + ufind = ufind.lowercase(); } - - if (SP_IS_STRING(item)) { // SPStrings have "on demand" ids which are useless for searching - return false; + gsize n = find_strcmp_pos(ustr.c_str(), ufind.c_str(), exact, casematch); + while (n != std::string::npos) { + ustr.replace(n, ufind.length(), replace); + if (!replaceall) { + return ustr; + } + // Start the next search after the last replace character to avoid infinite loops (replace "a" with "aaa" etc) + n = find_strcmp_pos(ustr.c_str(), ufind.c_str(), exact, casematch, n + strlen(replace) + 1); } + return ustr; +} - const gchar *item_id = item->getRepr()->attribute("id"); - if (item_id == NULL) { - return false; +gsize Find::find_strcmp_pos(const gchar *str, const gchar *find, bool exact, bool casematch, gsize start/*=0*/) +{ + Glib::ustring ustr = str; + Glib::ustring ufind = find; + + if (!casematch) { + ustr = ustr.lowercase(); + ufind = ufind.lowercase(); } + gsize pos = std::string::npos; if (exact) { - return ((bool) !strcmp(item_id, id)); + if (ustr == ufind) { + pos = 0; + } } else { -// g_print ("strstr: %s %s: %s\n", item_id, id, strstr(item_id, id) != NULL? "yes":"no"); - return ((bool) (strstr(item_id, id) != NULL)); + pos = ustr.find(ufind, start); } + + return pos; +} + + +bool Find::find_strcmp(const gchar *str, const gchar *find, bool exact, bool casematch) +{ + return (std::string::npos != find_strcmp_pos(str, find, exact, casematch)); } bool -Find::item_text_match (SPItem *item, const gchar *text, bool exact) +Find::item_text_match (SPItem *item, const gchar *find, bool exact, bool casematch, bool replace/*=false*/) { if (item->getRepr() == NULL) { return false; @@ -172,111 +342,317 @@ Find::item_text_match (SPItem *item, const gchar *text, bool exact) if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { const gchar *item_text = sp_te_get_string_multiline (item); - if (item_text == NULL) + if (item_text == NULL) { return false; - bool ret; - if (exact) { - ret = ((bool) !strcasecmp(item_text, text)); - } else { - //FIXME: strcasestr - ret = ((bool) (strstr(item_text, text) != NULL)); } - g_free ((void*) item_text); - return ret; + bool found = find_strcmp(item_text, find, exact, casematch); + + if (found && replace) { + Glib::ustring ufind = find; + if (!casematch) { + ufind = ufind.lowercase(); + } + + Inkscape::Text::Layout const *layout = te_get_layout (item); + if (!layout) { + return found; + } + + gchar* replace_text = g_strdup(entry_replace.getEntry()->get_text().c_str()); + gsize n = find_strcmp_pos(item_text, ufind.c_str(), exact, casematch); + static Inkscape::Text::Layout::iterator _begin_w; + static Inkscape::Text::Layout::iterator _end_w; + while (n != std::string::npos) { + _begin_w = layout->charIndexToIterator(n); + _end_w = layout->charIndexToIterator(n + strlen(find)); + sp_te_replace(item, _begin_w, _end_w, replace_text); + item_text = sp_te_get_string_multiline (item); + n = find_strcmp_pos(item_text, ufind.c_str(), exact, casematch, n + strlen(replace_text) + 1); + } + + g_free(replace_text); + } + + return found; } return false; } + bool -Find::item_style_match (SPItem *item, const gchar *text, bool exact) +Find::item_id_match (SPItem *item, const gchar *id, bool exact, bool casematch, bool replace/*=false*/) { if (item->getRepr() == NULL) { return false; } - const gchar *item_text = item->getRepr()->attribute("style"); - if (item_text == NULL) { + if (SP_IS_STRING(item)) { // SPStrings have "on demand" ids which are useless for searching + return false; + } + + const gchar *item_id = item->getRepr()->attribute("id"); + if (item_id == NULL) { + return false; + } + + bool found = find_strcmp(item_id, id, exact, casematch); + + if (found && replace) { + gchar * replace_text = g_strdup(entry_replace.getEntry()->get_text().c_str()); + Glib::ustring new_item_style = find_replace(item_id, id, replace_text , exact, casematch, true); + if (new_item_style != item_id) { + item->getRepr()->setAttribute("id", new_item_style.data()); + } + g_free(replace_text); + } + + return found; +} + +bool +Find::item_style_match (SPItem *item, const gchar *text, bool exact, bool casematch, bool replace/*=false*/) +{ + if (item->getRepr() == NULL) { + return false; + } + + gchar *item_style = g_strdup(item->getRepr()->attribute("style")); + if (item_style == NULL) { + return false; + } + + bool found = find_strcmp(item_style, text, exact, casematch); + + if (found && replace) { + gchar * replace_text = g_strdup(entry_replace.getEntry()->get_text().c_str()); + Glib::ustring new_item_style = find_replace(item_style, text, replace_text , exact, casematch, true); + if (new_item_style != item_style) { + item->getRepr()->setAttribute("style", new_item_style.data()); + } + g_free(replace_text); + } + + g_free(item_style); + return found; +} + +bool Find::item_attr_match(SPItem *item, const gchar *text, bool exact, bool casematch, bool replace/*=false*/) +{ + bool found = false; + + if (item->getRepr() == NULL) { return false; } + gchar *attr_value = g_strdup(item->getRepr()->attribute(text)); if (exact) { - return ((bool) !strcmp(item_text, text)); + found = (attr_value != NULL); } else { - return ((bool) (strstr(item_text, text) != NULL)); + found = item->getRepr()->matchAttributeName(text); } + g_free(attr_value); + + // TODO - Rename attribute name ? + if (found && replace) { + found = false; + } + + return found; } -bool Find::item_attr_match(SPItem *item, const gchar *name, bool exact) +bool Find::item_attrvalue_match(SPItem *item, const gchar *text, bool exact, bool casematch, bool replace/*=false*/) { - bool result = false; - if (item->getRepr()) { - if (exact) { - const gchar *attr_value = item->getRepr()->attribute(name); - result = (attr_value != NULL); - } else { - result = item->getRepr()->matchAttributeName(name); + bool ret = false; + + if (item->getRepr() == NULL) { + return false; + } + + Inkscape::Util::List<Inkscape::XML::AttributeRecord const> iter = item->getRepr()->attributeList(); + for (; iter; ++iter) { + const gchar* key = g_quark_to_string(iter->key); + gchar *attr_value = g_strdup(item->getRepr()->attribute(key)); + bool found = find_strcmp(attr_value, text, exact, casematch); + if (found) { + ret = true; + } + + if (found && replace) { + gchar * replace_text = g_strdup(entry_replace.getEntry()->get_text().c_str()); + Glib::ustring new_item_style = find_replace(attr_value, text, replace_text , exact, casematch, true); + if (new_item_style != attr_value) { + item->getRepr()->setAttribute(key, new_item_style.data()); + } + } + + g_free(attr_value); + } + + return ret; +} + + +bool Find::item_font_match(SPItem *item, const gchar *text, bool exact, bool casematch, bool replace/*=false*/) +{ + bool ret = false; + + if (item->getRepr() == NULL) { + return false; + } + + const gchar *item_style = item->getRepr()->attribute("style"); + if (item_style == NULL) { + return false; + } + + std::vector<Glib::ustring> vFontTokenNames; + vFontTokenNames.push_back("font-family:"); + vFontTokenNames.push_back("-inkscape-font-specification:"); + + std::vector<Glib::ustring> vStyleTokens = Glib::Regex::split_simple(";", item_style); + for(size_t i=0; i<vStyleTokens.size(); i++) { + Glib::ustring token = vStyleTokens[i]; + for(size_t j=0; j<vFontTokenNames.size(); j++) { + if ( token.find(vFontTokenNames[j]) != std::string::npos) { + Glib::ustring font1 = Glib::ustring(vFontTokenNames[j]).append(text); + bool found = find_strcmp(token.c_str(), font1.c_str(), exact, casematch); + if (found) { + ret = true; + if (_action_replace) { + gchar *replace_text = g_strdup(entry_replace.getEntry()->get_text().c_str()); + gchar *orig_str = g_strdup(token.c_str()); + // Exact match fails since the "font-family:" is in the token, since the find was exact it still works with false below + Glib::ustring new_item_style = find_replace(orig_str, text, replace_text , false /*exact*/, casematch, true); + if (new_item_style != orig_str) { + vStyleTokens.at(i) = new_item_style; + } + g_free(orig_str); + g_free(replace_text); + } + } + } } } - return result; + + if (ret && _action_replace) { + Glib::ustring new_item_style; + for(size_t i=0; i<vStyleTokens.size(); i++) { + new_item_style.append(vStyleTokens.at(i)).append(";"); + } + new_item_style.erase(new_item_style.size()-1); + item->getRepr()->setAttribute("style", new_item_style.data()); + } + + return ret; } GSList * -Find::filter_fields (GSList *l, bool exact) +Find::filter_fields (GSList *l, bool exact, bool casematch) { - const gchar* text = _entry_text.getEntry()->get_text().c_str(); - const gchar* id = _entry_id.getEntry()->get_text().c_str(); - const gchar* style = _entry_style.getEntry()->get_text().c_str(); - const gchar* attr = _entry_attribute.getEntry()->get_text().c_str(); - + Glib::ustring tmp = entry_find.getEntry()->get_text(); + if (tmp.empty()) { + return l; + } + gchar* text = g_strdup(tmp.c_str()); + GSList *in = l; GSList *out = NULL; - if (strlen (text) != 0) { + + if (check_searchin_text.get_active()) { for (GSList *i = in; i != NULL; i = i->next) { - if (item_text_match (SP_ITEM(i->data), text, exact)) { - out = g_slist_prepend (out, i->data); + if (item_text_match (SP_ITEM(i->data), text, exact, casematch)) { + if (!g_slist_find(out, i->data)) { + out = g_slist_prepend (out, i->data); + if (_action_replace) { + item_text_match (SP_ITEM(i->data), text, exact, casematch, _action_replace); + } + } } } - } else { - out = in; } - - in = out; - out = NULL; - if (strlen (id) != 0) { - for (GSList *i = in; i != NULL; i = i->next) { - if (item_id_match (SP_ITEM(i->data), id, exact)) { - out = g_slist_prepend (out, i->data); + else if (check_searchin_property.get_active()) { + + bool ids = check_ids.get_active(); + bool style = check_style.get_active(); + bool font = check_font.get_active(); + bool attrname = check_attributename.get_active(); + bool attrvalue = check_attributevalue.get_active(); + + if (ids) { + for (GSList *i = in; i != NULL; i = i->next) { + if (item_id_match (SP_ITEM(i->data), text, exact, casematch)) { + if (!g_slist_find(out, i->data)) { + out = g_slist_prepend (out, i->data); + if (_action_replace) { + item_id_match (SP_ITEM(i->data), text, exact, casematch, _action_replace); + } + } + } } } - } else { - out = in; - } - in = out; - out = NULL; - if (strlen (style) != 0) { - for (GSList *i = in; i != NULL; i = i->next) { - if (item_style_match (SP_ITEM(i->data), style, exact)) { - out = g_slist_prepend (out, i->data); + + if (style) { + for (GSList *i = in; i != NULL; i = i->next) { + if (item_style_match (SP_ITEM(i->data), text, exact, casematch)) { + if (!g_slist_find(out, i->data)) + if (!g_slist_find(out, i->data)) { + out = g_slist_prepend (out, i->data); + if (_action_replace) { + item_style_match (SP_ITEM(i->data), text, exact, casematch, _action_replace); + } + } + } } } - } else { - out = in; - } - in = out; - out = NULL; - if (strlen (attr) != 0) { - for (GSList *i = in; i != NULL; i = i->next) { - if (item_attr_match (SP_ITEM(i->data), attr, exact)) { - out = g_slist_prepend (out, i->data); + + if (attrname) { + for (GSList *i = in; i != NULL; i = i->next) { + if (item_attr_match (SP_ITEM(i->data), text, exact, casematch)) { + if (!g_slist_find(out, i->data)) { + out = g_slist_prepend (out, i->data); + if (_action_replace) { + item_attr_match (SP_ITEM(i->data), text, exact, casematch, _action_replace); + } + } + } } } - } else { - out = in; + + + if (attrvalue) { + for (GSList *i = in; i != NULL; i = i->next) { + if (item_attrvalue_match (SP_ITEM(i->data), text, exact, casematch)) { + if (!g_slist_find(out, i->data)) { + out = g_slist_prepend (out, i->data); + if (_action_replace) { + item_attrvalue_match (SP_ITEM(i->data), text, exact, casematch, _action_replace); + } + } + } + } + } + + + if (font) { + for (GSList *i = in; i != NULL; i = i->next) { + if (item_font_match (SP_ITEM(i->data), text, exact, casematch)) { + if (!g_slist_find(out, i->data)) { + out = g_slist_prepend (out, i->data); + if (_action_replace) { + item_font_match (SP_ITEM(i->data), text, exact, casematch, _action_replace); + } + } + } + } + } + } + g_free(text); + return out; } @@ -284,37 +660,37 @@ Find::filter_fields (GSList *l, bool exact) bool Find::item_type_match (SPItem *item) { - SPDesktop *desktop = getDesktop(); + bool all =check_alltypes.get_active(); - if (SP_IS_RECT(item)) { - return (_check_all_shapes.get_active() || _check_rects.get_active()); + if ( SP_IS_RECT(item)) { + return ( all ||check_rects.get_active()); } else if (SP_IS_GENERICELLIPSE(item) || SP_IS_ELLIPSE(item) || SP_IS_ARC(item) || SP_IS_CIRCLE(item)) { - return (_check_all_shapes.get_active() || _check_ellipses.get_active()); + return ( all || check_ellipses.get_active()); } else if (SP_IS_STAR(item) || SP_IS_POLYGON(item)) { - return (_check_all_shapes.get_active() || _check_stars.get_active()); + return ( all || check_stars.get_active()); } else if (SP_IS_SPIRAL(item)) { - return (_check_all_shapes.get_active() || _check_spirals.get_active()); + return ( all || check_spirals.get_active()); } else if (SP_IS_PATH(item) || SP_IS_LINE(item) || SP_IS_POLYLINE(item)) { - return (_check_paths.get_active()); + return (all || check_paths.get_active()); } else if (SP_IS_TEXT(item) || SP_IS_TSPAN(item) || SP_IS_TREF(item) || SP_IS_STRING(item)) { - return (_check_texts.get_active()); + return (all || check_texts.get_active()); } else if (SP_IS_GROUP(item) && !desktop->isLayer(item) ) { // never select layers! - return (_check_groups.get_active()); + return (all || check_groups.get_active()); } else if (SP_IS_USE(item)) { - return (_check_clones.get_active()); + return (all || check_clones.get_active()); } else if (SP_IS_IMAGE(item)) { - return (_check_images.get_active()); + return (all || check_images.get_active()); } else if (SP_IS_OFFSET(item)) { - return (_check_offsets.get_active()); + return (all || check_offsets.get_active()); } return false; @@ -323,8 +699,6 @@ Find::item_type_match (SPItem *item) GSList * Find::filter_types (GSList *l) { - if (_check_all.get_active()) return l; - GSList *n = NULL; for (GSList *i = l; i != NULL; i = i->next) { if (item_type_match (SP_ITEM(i->data))) { @@ -336,18 +710,16 @@ Find::filter_types (GSList *l) GSList * -Find::filter_list (GSList *l, bool exact) +Find::filter_list (GSList *l, bool exact, bool casematch) { - l = filter_fields (l, exact); l = filter_types (l); + l = filter_fields (l, exact, casematch); return l; } GSList * Find::all_items (SPObject *r, GSList *l, bool hidden, bool locked) { - SPDesktop *desktop = getDesktop(); - if (SP_IS_DEFS(r)) { return l; // we're not interested in items in defs } @@ -371,8 +743,6 @@ Find::all_items (SPObject *r, GSList *l, bool hidden, bool locked) GSList * Find::all_selection_items (Inkscape::Selection *s, GSList *l, SPObject *ancestor, bool hidden, bool locked) { - SPDesktop *desktop = getDesktop(); - for (GSList *i = (GSList *) s->itemList(); i != NULL; i = i->next) { if (SP_IS_ITEM (i->data) && !reinterpret_cast<SPItem *>(i->data)->cloned && !desktop->isLayer(SP_ITEM(i->data))) { SPItem *item = reinterpret_cast<SPItem *>(i->data); @@ -388,43 +758,56 @@ Find::all_selection_items (Inkscape::Selection *s, GSList *l, SPObject *ancestor } return l; } - - + + /*######################################################################## # BUTTON CLICK HANDLERS (callbacks) ########################################################################*/ void -Find::onClear() -{ - _entry_text.getEntry()->set_text(Glib::ustring("")); - _entry_id.getEntry()->set_text(Glib::ustring("")); - _entry_style.getEntry()->set_text(Glib::ustring("")); - _entry_attribute.getEntry()->set_text(Glib::ustring("")); - - _check_all.set_active(); +Find::onFind() +{ + _action_replace = false; + onAction(); + + // Return focus to the find entry + entry_find.getEntry()->grab_focus(); } - - void -Find::onFind() -{ - SPDesktop *desktop = getDesktop(); +Find::onReplace() +{ + if (entry_find.getEntry()->get_text().length() < 1) { + status.set_text(_("Nothing to replace")); + return; + } + _action_replace = true; + onAction(); - bool hidden = _check_include_hidden.get_active(); - bool locked = _check_include_locked.get_active(); + // Return focus to the find entry + entry_find.getEntry()->grab_focus(); +} + +void +Find::onAction() +{ + + bool hidden = check_include_hidden.get_active(); + bool locked = check_include_locked.get_active(); + bool exact = check_exact_match.get_active(); + bool casematch = check_case_sensitive.get_active(); + blocked = true; GSList *l = NULL; - if (_check_search_selection.get_active()) { - if (_check_search_layer.get_active()) { + if (check_scope_selection.get_active()) { + if (check_scope_layer.get_active()) { l = all_selection_items (desktop->selection, l, desktop->currentLayer(), hidden, locked); } else { l = all_selection_items (desktop->selection, l, NULL, hidden, locked); } } else { - if (_check_search_layer.get_active()) { + if (check_scope_layer.get_active()) { l = all_items (desktop->currentLayer(), l, hidden, locked); } else { l = all_items(sp_desktop_document(desktop)->getRoot(), l, hidden, locked); @@ -432,13 +815,7 @@ Find::onFind() } guint all = g_slist_length (l); - bool exact = true; - GSList *n = NULL; - n = filter_list (l, exact); - if (n == NULL) { - exact = false; - n = filter_list (l, exact); - } + GSList *n = filter_list (l, exact, casematch); if (n != NULL) { int count = g_slist_length (n); @@ -448,88 +825,124 @@ Find::onFind() "<b>%d</b> objects found (out of <b>%d</b>), %s match.", count), count, all, exact? _("exact") : _("partial")); + status.set_text(Glib::ustring::compose("%1 %2 %3", count, _("objects"), _action_replace? _("replaced") : _("found") )); Inkscape::Selection *selection = sp_desktop_selection (desktop); selection->clear(); selection->setList(n); scroll_to_show_item (desktop, SP_ITEM(n->data)); + + if (_action_replace) { + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, _("Text Replace")); + } + } else { + status.set_text(_("Not found")); + if (!check_scope_selection.get_active()) { + Inkscape::Selection *selection = sp_desktop_selection (desktop); + selection->clear(); + } desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No objects found")); } + blocked = false; + +} +void +Find::onToggleCheck () +{ + bool objectok = false; + status.set_text(""); + + if (check_alltypes.get_active()) { + objectok = true; + } + for(int i = 0; i < 11; i++) { + if (checkTypes[i]->get_active()) { + objectok = true; + } + } + + if (!objectok) { + status.set_text(_("Select an object")); + } + + + bool propertyok = false; + + if (!check_searchin_property.get_active()) { + propertyok = true; + } else { + + for(size_t i = 0; i < checkProperties.size(); i++) { + if (checkProperties[i]->get_active()) { + propertyok = true; + } + } + } + + if (!propertyok) { + status.set_text(_("Select a property")); + } + + // Can't replace attribute names + bool attributenameyok = !check_attributename.get_active(); + + button_find.set_sensitive(objectok && propertyok); + button_replace.set_sensitive(objectok && propertyok && attributenameyok); + } void Find::onToggleAlltypes () { - if (_check_all.get_active()) { - // explicit toggle to make sure its handler gets called, no matter what was the original state - _check_all_shapes.toggled(); - _check_all_shapes.set_active(); - _check_all_shapes.hide(); - _check_paths.hide(); - _check_texts.hide(); - _check_groups.hide(); - _check_clones.hide(); - _check_images.hide(); - _check_offsets.hide(); - } else { - // explicit toggle to make sure its handler gets called, no matter what was the original state - _check_all_shapes.toggled(); - _check_all_shapes.set_active(); - _check_all_shapes.show(); - - _check_paths.set_active(); - _check_paths.show(); - _check_texts.set_active(); - _check_texts.show(); - _check_groups.set_active(); - _check_groups.show(); - _check_clones.set_active(); - _check_clones.show(); - _check_images.set_active(); - _check_images.show(); - _check_offsets.set_active(); - _check_offsets.show(); - } - squeeze_window(); + bool all =check_alltypes.get_active(); + for(size_t i = 0; i < checkTypes.size(); i++) { + checkTypes[i]->set_sensitive(!all); + } + + onToggleCheck(); } void -Find::onToggleShapes () +Find::onSearchinText () { - if (_check_all_shapes.get_active()) { - _check_rects.hide(); - _check_ellipses.hide(); - _check_stars.hide(); - _check_spirals.hide(); - } else { - _check_rects.set_active(); - _check_rects.show(); - _check_ellipses.set_active(); - _check_ellipses.show(); - _check_stars.set_active(); - _check_stars.show(); - _check_spirals.set_active(); - _check_spirals.show(); - } - squeeze_window(); + searchinToggle(false); + onToggleCheck(); } +void +Find::onSearchinProperty () +{ + searchinToggle(true); + onToggleCheck(); +} + +void +Find::searchinToggle(bool on) +{ + for(size_t i = 0; i < checkProperties.size(); i++) { + checkProperties[i]->set_sensitive(on); + } +} + +void +Find::onExpander () +{ + if (!expander_options.get_expanded()) + squeeze_window(); +} /*######################################################################## # UTILITY ########################################################################*/ - - void Find::squeeze_window() { - // TO DO: make window as small as possible + // TODO: resize dialog window when the expander is closed + // set_size_request(-1, -1); } - - } // namespace Dialog } // namespace UI } // namespace Inkscape diff --git a/src/ui/dialog/find.h b/src/ui/dialog/find.h index 15263caac..62ecd1763 100644 --- a/src/ui/dialog/find.h +++ b/src/ui/dialog/find.h @@ -16,6 +16,10 @@ #include "ui/widget/button.h" #include "ui/widget/entry.h" #include <glib.h> +#include <gtkmm.h> + +#include "desktop.h" +#include "ui/dialog/desktop-tracker.h" class SPItem; class SPObject; @@ -26,66 +30,268 @@ class Selection; namespace UI { namespace Dialog { +/** + * The Find class defines the Find and replace dialog. + * + * The Find and replace dialog allows you to search within the + * current document for specific text or properties of items. + * Matches items are highlighted and can be replaced as well. + * Scope can be limited to the entire document, current layer or selected items. + * Other options allow searching on specific object types and properties. + */ + class Find : public UI::Widget::Panel { public: Find(); virtual ~Find(); + /** + * Helper function which returns a new instance of the dialog. + * getInstance is needed by the dialog manager (Inkscape::UI::Dialog::DialogManager). + */ static Find &getInstance() { return *new Find(); } protected: - // Widgets: - Inkscape::UI::Widget::Entry _entry_text; - Inkscape::UI::Widget::Entry _entry_id; - Inkscape::UI::Widget::Entry _entry_style; - Inkscape::UI::Widget::Entry _entry_attribute; - - Inkscape::UI::Widget::CheckButton _check_search_selection; - Inkscape::UI::Widget::CheckButton _check_search_layer; - Inkscape::UI::Widget::CheckButton _check_include_hidden; - Inkscape::UI::Widget::CheckButton _check_include_locked; - - // Type checkbutton widgets... - Inkscape::UI::Widget::CheckButton _check_all; - Inkscape::UI::Widget::CheckButton _check_all_shapes; - Inkscape::UI::Widget::CheckButton _check_rects; - Inkscape::UI::Widget::CheckButton _check_ellipses; - Inkscape::UI::Widget::CheckButton _check_stars; - Inkscape::UI::Widget::CheckButton _check_spirals; - Inkscape::UI::Widget::CheckButton _check_paths; - Inkscape::UI::Widget::CheckButton _check_texts; - Inkscape::UI::Widget::CheckButton _check_groups; - Inkscape::UI::Widget::CheckButton _check_clones; - Inkscape::UI::Widget::CheckButton _check_images; - Inkscape::UI::Widget::CheckButton _check_offsets; - - // Button-click handlers - void onClear(); + + + /** + * Callbacks for pressing the dialog buttons. + */ void onFind(); + void onReplace(); + void onExpander(); + void onAction(); void onToggleAlltypes(); - void onToggleShapes(); + void onToggleCheck(); + void onSearchinText(); + void onSearchinProperty(); + /** + * Toggle all the properties checkboxes + */ + void searchinToggle(bool on); - // onFind helper functions - bool item_id_match (SPItem *item, const gchar *id, bool exact); - bool item_text_match (SPItem *item, const gchar *text, bool exact); - bool item_style_match (SPItem *item, const gchar *text, bool exact); - bool item_attr_match (SPItem *item, const gchar *name, bool exact); - GSList * filter_fields (GSList *l, bool exact); + /** + * Returns true if the SPItem 'item' has the same id + * + * @param item the SPItem to check + * @param id the value to compare with + * @param exact do an exacty match + * @param casematch match the text case exactly + * @param replace replace the value if found + * + */ + bool item_id_match (SPItem *item, const gchar *id, bool exact, bool casematch, bool replace=false); + /** + * Returns true if the SPItem 'item' has the same text content + * + * @param item the SPItem to check + * @param name the value to compare with + * @param exact do an exacty match + * @param casematch match the text case exactly + * @param replace replace the value if found + * + * + */ + bool item_text_match (SPItem *item, const gchar *text, bool exact, bool casematch, bool replace=false); + /** + * Returns true if the SPItem 'item' has the same text in the style attribute + * + * @param item the SPItem to check + * @param name the value to compare with + * @param exact do an exacty match + * @param casematch match the text case exactly + * @param replace replace the value if found + * + */ + bool item_style_match (SPItem *item, const gchar *text, bool exact, bool casematch, bool replace=false); + /** + * Returns true if found the SPItem 'item' has the same attribute name + * + * @param item the SPItem to check + * @param name the value to compare with + * @param exact do an exacty match + * @param casematch match the text case exactly + * @param replace replace the value if found + * + */ + bool item_attr_match (SPItem *item, const gchar *name, bool exact, bool casematch, bool replace=false); + /** + * Returns true if the SPItem 'item' has the same attribute value + * + * @param item the SPItem to check + * @param name the value to compare with + * @param exact do an exacty match + * @param casematch match the text case exactly + * @param replace replace the value if found + * + */ + bool item_attrvalue_match (SPItem *item, const gchar *name, bool exact, bool casematch, bool replace=false); + /** + * Returns true if the SPItem 'item' has the same font values + * + * @param item the SPItem to check + * @param name the value to compare with + * @param exact do an exacty match + * @param casematch match the text case exactly + * @param replace replace the value if found + * + */ + bool item_font_match (SPItem *item, const gchar *name, bool exact, bool casematch, bool replace=false); + /** + * Function to filter a list of items based on the item type by calling each item_XXX_match function + */ + GSList * filter_fields (GSList *l, bool exact, bool casematch); bool item_type_match (SPItem *item); GSList * filter_types (GSList *l); - GSList * filter_list (GSList *l, bool exact); + GSList * filter_list (GSList *l, bool exact, bool casematch); + + /** + * Find a string within a string and returns true if found with options for exact and casematching + */ + bool find_strcmp(const gchar *str, const gchar *find, bool exact, bool casematch); + + /** + * Find a string within a string and return the position with options for exact, casematching and search start location + */ + gsize find_strcmp_pos(const gchar *str, const gchar *find, bool exact, bool casematch, gsize start=0); + + /** + * Replace a string with another string with options for exact and casematching and replace once/all + */ + Glib::ustring find_replace(const gchar *str, const gchar *find, const gchar *replace, bool exact, bool casematch, bool replaceall); + + /** + * recursive function to return a list of all the items in the SPObject tree + * + */ GSList * all_items (SPObject *r, GSList *l, bool hidden, bool locked); + /** + * to return a list of all the selected items + * + */ GSList * all_selection_items (Inkscape::Selection *s, GSList *l, SPObject *ancestor, bool hidden, bool locked); + /** + * Shrink the dialog size when the expander widget is closed + * Currently not working, no known way to do this + */ void squeeze_window(); + /** + * Can be invoked for setting the desktop. Currently not used. + */ + void setDesktop(SPDesktop *desktop); + /** + * Is invoked by the desktop tracker when the desktop changes. + */ + void setTargetDesktop(SPDesktop *desktop); + /** + * Called when desktop selection changes + */ + void onSelectionChange(void); + private: Find(Find const &d); Find& operator=(Find const &d); - - Inkscape::UI::Widget::Button _button_clear; - Inkscape::UI::Widget::Button _button_find; + + /* + * Find and replace combo box widgets + */ + Inkscape::UI::Widget::Entry entry_find; + Inkscape::UI::Widget::Entry entry_replace; + + /** + * Scope and search in widgets + */ + Inkscape::UI::Widget::RadioButton check_scope_all; + Inkscape::UI::Widget::RadioButton check_scope_layer; + Inkscape::UI::Widget::RadioButton check_scope_selection; + Inkscape::UI::Widget::RadioButton check_searchin_text; + Inkscape::UI::Widget::RadioButton check_searchin_property; + Gtk::HBox hbox_searchin; + Gtk::VBox vbox_scope; + Gtk::VBox vbox_searchin; + Gtk::Frame frame_searchin; + Gtk::Frame frame_scope; + + /** + * General option widgets + */ + Inkscape::UI::Widget::CheckButton check_case_sensitive; + Inkscape::UI::Widget::CheckButton check_exact_match; + Inkscape::UI::Widget::CheckButton check_include_hidden; + Inkscape::UI::Widget::CheckButton check_include_locked; + Gtk::VBox vbox_options1; + Gtk::VBox vbox_options2; + Gtk::HBox hbox_options; + Gtk::VBox vbox_expander; + Gtk::Expander expander_options; + Gtk::Frame frame_options; + + /** + * Property type widgets + */ + Inkscape::UI::Widget::CheckButton check_ids; + Inkscape::UI::Widget::CheckButton check_attributename; + Inkscape::UI::Widget::CheckButton check_attributevalue; + Inkscape::UI::Widget::CheckButton check_style; + Inkscape::UI::Widget::CheckButton check_font; + Gtk::VBox vbox_properties; + Gtk::HBox hbox_properties1; + Gtk::HBox hbox_properties2; + Gtk::Frame frame_properties; + + /** + * A vector of all the properties widgets for easy processing + */ + std::vector<Inkscape::UI::Widget::CheckButton *> checkProperties; + + /** + * Object type widgets + */ + Inkscape::UI::Widget::CheckButton check_alltypes; + Inkscape::UI::Widget::CheckButton check_rects; + Inkscape::UI::Widget::CheckButton check_ellipses; + Inkscape::UI::Widget::CheckButton check_stars; + Inkscape::UI::Widget::CheckButton check_spirals; + Inkscape::UI::Widget::CheckButton check_paths; + Inkscape::UI::Widget::CheckButton check_texts; + Inkscape::UI::Widget::CheckButton check_groups; + Inkscape::UI::Widget::CheckButton check_clones; + Inkscape::UI::Widget::CheckButton check_images; + Inkscape::UI::Widget::CheckButton check_offsets; + Gtk::VBox vbox_types1; + Gtk::VBox vbox_types2; + Gtk::HBox hbox_types; + Gtk::Frame frame_types; + + /** + * A vector of all the check option widgets for easy processing + */ + std::vector<Inkscape::UI::Widget::CheckButton *> checkTypes; + + //Gtk::HBox hbox_text; + + /** + * Action Buttons and status + */ + Gtk::Label status; + Inkscape::UI::Widget::Button button_find; + Inkscape::UI::Widget::Button button_replace; + Gtk::HButtonBox box_buttons; + Gtk::HBox hboxbutton_row; + + /** + * Finding or replacing + */ + bool _action_replace; + bool blocked; + + SPDesktop *desktop; + DesktopTracker deskTrack; + sigc::connection desktopChangeConn; + sigc::connection selectChangedConn; }; } // namespace Dialog diff --git a/src/ui/dialog/guides.cpp b/src/ui/dialog/guides.cpp index 61b4fe389..088290b6f 100644 --- a/src/ui/dialog/guides.cpp +++ b/src/ui/dialog/guides.cpp @@ -54,7 +54,7 @@ GuidelinePropertiesDialog::GuidelinePropertiesDialog(SPGuide *guide, SPDesktop * } bool GuidelinePropertiesDialog::_relative_toggle_status = false; // initialize relative checkbox status for when this dialog is opened for first time -Glib::ustring GuidelinePropertiesDialog::_angle_unit_status = "deg"; // initialize angle unit status +Glib::ustring GuidelinePropertiesDialog::_angle_unit_status = DEG; // initialize angle unit status GuidelinePropertiesDialog::~GuidelinePropertiesDialog() { // save current status @@ -87,7 +87,7 @@ void GuidelinePropertiesDialog::_modeChanged() _spin_button_x.setValue(0); } else { // absolute - _spin_angle.setValueKeepUnit(_oldangle, "deg"); + _spin_angle.setValueKeepUnit(_oldangle, DEG); _spin_button_x.setValueKeepUnit(_oldpos[Geom::X], "px"); _spin_button_y.setValueKeepUnit(_oldpos[Geom::Y], "px"); @@ -96,7 +96,7 @@ void GuidelinePropertiesDialog::_modeChanged() void GuidelinePropertiesDialog::_onApply() { - double deg_angle = _spin_angle.getValue("deg"); + double deg_angle = _spin_angle.getValue(DEG); if (!_mode) deg_angle += _oldangle; Geom::Point normal; diff --git a/src/ui/dialog/transformation.cpp b/src/ui/dialog/transformation.cpp index 9b65c1f68..1951dc92a 100644 --- a/src/ui/dialog/transformation.cpp +++ b/src/ui/dialog/transformation.cpp @@ -724,7 +724,7 @@ void Transformation::applyPageScale(Inkscape::Selection *selection) void Transformation::applyPageRotate(Inkscape::Selection *selection) { - double angle = _scalar_rotate.getValue("deg"); + double angle = _scalar_rotate.getValue(DEG); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (prefs->getBool("/dialogs/transformation/applyseparately")) { diff --git a/src/ui/dialog/xml-tree.cpp b/src/ui/dialog/xml-tree.cpp index cdbb2257c..ae1ebf5ca 100644 --- a/src/ui/dialog/xml-tree.cpp +++ b/src/ui/dialog/xml-tree.cpp @@ -233,7 +233,7 @@ XmlTree::XmlTree (void) : GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(attributes)); g_signal_connect (G_OBJECT(selection), "changed", G_CALLBACK (on_attr_select_row), this); - + g_signal_connect( G_OBJECT(attributes), "row-value-changed", G_CALLBACK(on_attr_row_changed), this); xml_element_new_button.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_new_element_node)); xml_text_new_button.signal_clicked().connect(sigc::mem_fun(*this, &XmlTree::cmd_new_text_node)); @@ -738,11 +738,12 @@ void XmlTree::on_attr_select_row(GtkTreeSelection *selection, gpointer data) GtkTreeModel *model; if (!gtk_tree_selection_get_selected (selection, &model, &iter)) { -/* self->selected_attr = 0; + // Nothing selected + self->selected_attr = 0; self->attr_reset_context(self->selected_attr); self->xml_attribute_delete_button.set_sensitive(false); - self->on_attr_unselect_row_clear_text();*/ + self->on_attr_unselect_row_clear_text(); return; } @@ -763,6 +764,22 @@ void XmlTree::on_attr_select_row(GtkTreeSelection *selection, gpointer data) } +void XmlTree::on_attr_row_changed(SPXMLViewAttrList *attributes, const gchar * name, gpointer data) +{ + // Reselect the selected row if the data changes to refresh the attribute and value edit boxes. + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(attributes)); + GtkTreeIter iter; + GtkTreeModel *model; + const gchar *attr_name; + if (gtk_tree_selection_get_selected (selection, &model, &iter)) { + gtk_tree_model_get (model, &iter, 0, &attr_name, -1); + if (!strcmp(name, attr_name)) { + gtk_tree_selection_unselect_all(selection); + gtk_tree_selection_select_iter(selection, &iter); + } + } +} + void XmlTree::on_attr_unselect_row_clear_text() { attr_name.set_text(""); diff --git a/src/ui/dialog/xml-tree.h b/src/ui/dialog/xml-tree.h index eeb828771..bf43f5ae8 100644 --- a/src/ui/dialog/xml-tree.h +++ b/src/ui/dialog/xml-tree.h @@ -126,11 +126,16 @@ private: static void after_tree_move(GtkCTree *tree, GtkCTreeNode *node, GtkCTreeNode *new_parent, GtkCTreeNode *new_sibling, gpointer data); /** - * Callback functions for when attribute selection changes + * Callback for when attribute selection changes */ static void on_attr_select_row(GtkTreeSelection *selection, gpointer data); /** + * Callback for when attribute list values change + */ + static void on_attr_row_changed(SPXMLViewAttrList *attributes, const gchar * name, gpointer data); + + /** * Enable widgets based on current selections */ void on_tree_select_row_enable(GtkCTreeNode *node); diff --git a/src/ui/widget/button.cpp b/src/ui/widget/button.cpp index 1ba531ddf..bac866920 100644 --- a/src/ui/widget/button.cpp +++ b/src/ui/widget/button.cpp @@ -31,6 +31,22 @@ CheckButton::CheckButton(Glib::ustring const &label, Glib::ustring const &toolti set_tooltip_text(tooltip); } +CheckButton::CheckButton(Glib::ustring const &label, Glib::ustring const &tooltip, bool active) +{ + set_use_underline (true); + set_label (label); + set_tooltip_text(tooltip); + set_active(active); +} + +RadioButton::RadioButton(Glib::ustring const &label, Glib::ustring const &tooltip) +{ + set_use_underline (true); + set_label (label); + set_tooltip_text(tooltip); +} + + } // namespace Widget } // namespace UI } // namespace Inkscape diff --git a/src/ui/widget/button.h b/src/ui/widget/button.h index 362cdf8ff..b219fc3db 100644 --- a/src/ui/widget/button.h +++ b/src/ui/widget/button.h @@ -11,6 +11,7 @@ #define INKSCAPE_UI_WIDGET_BUTTON_H #include <gtkmm/checkbutton.h> +#include <gtkmm/radiobutton.h> namespace Inkscape { namespace UI { @@ -34,6 +35,19 @@ class CheckButton : public Gtk::CheckButton public: CheckButton(); CheckButton(Glib::ustring const &label, Glib::ustring const &tooltip); + CheckButton(Glib::ustring const &label, Glib::ustring const &tooltip, bool active); + +}; + +/** + * RadioButton widget. + */ +class RadioButton : public Gtk::RadioButton +{ +public: + RadioButton(); + RadioButton(Glib::ustring const &label, Glib::ustring const &tooltip); + }; } // namespace Widget diff --git a/src/ui/widget/entry.cpp b/src/ui/widget/entry.cpp index 7ac8532fb..173e014d9 100644 --- a/src/ui/widget/entry.cpp +++ b/src/ui/widget/entry.cpp @@ -24,7 +24,6 @@ Entry::Entry( Glib::ustring const &label, Glib::ustring const &tooltip, : Labelled(label, tooltip, new Gtk::Entry(), suffix, icon, mnemonic) { } - } // namespace Widget } // namespace UI diff --git a/src/ui/widget/entry.h b/src/ui/widget/entry.h index d332dde1b..53b848fc9 100644 --- a/src/ui/widget/entry.h +++ b/src/ui/widget/entry.h @@ -11,8 +11,10 @@ #define INKSCAPE_UI_WIDGET_ENTRY__H #include "labelled.h" +#include <gtkmm.h> #include <gtkmm/entry.h> +#include <gtkmm/comboboxtext.h> namespace Inkscape { namespace UI { |
