Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / ui / dialog / find.cpp
1 /**
2  * \brief Find dialog
3  *
4  * Authors:
5  *   Bryce W. Harrington <bryce@bryceharrington.org>
6  *   Johan Engelen <goejendaagh@zonnet.nl>
7  *   Jon A. Cruz <jon@joncruz.org>
8  *   Abhishek Sharma
9  *
10  * Copyright (C) 2004-2006 Authors
11  *
12  * Released under GNU GPL.  Read the file 'COPYING' for more information.
13  */
15 #ifdef HAVE_CONFIG_H
16 # include <config.h>
17 #endif
19 #include <gtkmm/widget.h>
20 #include "find.h"
21 #include "verbs.h"
23 #include "message-stack.h"
24 #include "helper/window.h"
25 #include "macros.h"
26 #include "inkscape.h"
27 #include "desktop.h"
28 #include "document.h"
29 #include "selection.h"
30 #include "desktop-handles.h"
32 #include "dialogs/dialog-events.h"
33 #include "verbs.h"
34 #include "interface.h"
35 #include "sp-text.h"
36 #include "sp-flowtext.h"
37 #include "text-editing.h"
38 #include "sp-tspan.h"
39 #include "sp-tref.h"
40 #include "selection-chemistry.h"
41 #include "sp-defs.h"
42 #include "sp-rect.h"
43 #include "sp-ellipse.h"
44 #include "sp-star.h"
45 #include "sp-spiral.h"
46 #include "sp-path.h"
47 #include "sp-line.h"
48 #include "sp-polyline.h"
49 #include "sp-item-group.h"
50 #include "sp-use.h"
51 #include "sp-image.h"
52 #include "sp-offset.h"
53 #include "xml/repr.h"
56 namespace Inkscape {
57 namespace UI {
58 namespace Dialog {
60 Find::Find()
61     : UI::Widget::Panel("", "/dialogs/find", SP_VERB_DIALOG_FIND),
62       _entry_text(_("_Text:"), _("Find objects by their text content (exact or partial match)")),
63       _entry_id(_("_ID:"), _("Find objects by the value of the id attribute (exact or partial match)")),
64       _entry_style(_("_Style:"), _("Find objects by the value of the style attribute (exact or partial match)")),
65       _entry_attribute(_("_Attribute:"), _("Find objects by the name of an attribute (exact or partial match)")),
66       _check_search_selection(_("Search in s_election"), _("Limit search to the current selection")),
67       _check_search_layer(_("Search in current _layer"), _("Limit search to the current layer")),
68       _check_include_hidden(_("Include _hidden"), _("Include hidden objects in search")),
69       _check_include_locked(_("Include l_ocked"), _("Include locked objects in search")),
71       _check_all(_("All types"), _("Search in all object types")),
72       _check_all_shapes(_("All shapes"), _("Search all shapes")),
73       _check_rects(_("Rectangles"), _("Search rectangles")),
74       _check_ellipses(_("Ellipses"), _("Search ellipses, arcs, circles")),
75       _check_stars(_("Stars"), _("Search stars and polygons")),
76       _check_spirals(_("Spirals"), _("Search spirals")),
77       _check_paths(_("Paths"), _("Search paths, lines, polylines")),
78       _check_texts(_("Texts"), _("Search text objects")),
79       _check_groups(_("Groups"), _("Search groups")),
80       _check_clones(
81                     //TRANSLATORS: "Clones" is a noun indicating type of object to find
82                     C_("Find dialog", "Clones"), _("Search clones")),
83       _check_images(_("Images"), _("Search images")),
84       _check_offsets(_("Offsets"), _("Search offset objects")),
85     
86       _button_clear(_("_Clear"), _("Clear values")),
87       _button_find(_("_Find"), _("Select objects matching all of the fields you filled in"))
88 {
89     Gtk::Box *contents = _getContents();
90     contents->set_spacing(4);
91     
92     contents->pack_start(_entry_text, true, true);
93     contents->pack_start(_entry_id, true, true);
94     contents->pack_start(_entry_style, true, true);
95     contents->pack_start(_entry_attribute, true, true);
97     contents->pack_start(_check_all, true, true);
98     contents->pack_start(_check_all_shapes, true, true);
99     contents->pack_start(_check_rects, true, true);
100     contents->pack_start(_check_ellipses, true, true);
101     contents->pack_start(_check_stars, true, true);
102     contents->pack_start(_check_spirals, true, true);
103     contents->pack_start(_check_paths, true, true);
104     contents->pack_start(_check_texts, true, true);
105     contents->pack_start(_check_groups, true, true);
106     contents->pack_start(_check_clones, true, true);
107     contents->pack_start(_check_images, true, true);
108     contents->pack_start(_check_offsets, true, true);
110     contents->pack_start(_check_search_selection, true, true);
111     contents->pack_start(_check_search_layer, true, true);
112     contents->pack_start(_check_include_hidden, true, true);
113     contents->pack_start(_check_include_locked, true, true);
115     contents->pack_start(_button_clear, true, true);
116     contents->pack_start(_button_find, true, true);
118     // set signals to handle clicks
119     _check_all.signal_clicked().connect(sigc::mem_fun(*this, &Find::onToggleAlltypes));
120     _check_all_shapes.signal_clicked().connect(sigc::mem_fun(*this, &Find::onToggleShapes));
121     _button_clear.signal_clicked().connect(sigc::mem_fun(*this, &Find::onClear));
122     _button_find.signal_clicked().connect(sigc::mem_fun(*this, &Find::onFind));
124     _button_find.set_flags(Gtk::CAN_DEFAULT);
125     // set_default (_button_find); // activatable by Enter
126     _entry_text.getEntry()->grab_focus();
128     show_all_children();
129     onClear();
132 Find::~Find() 
137 /*########################################################################
138 # FIND helper functions
139 ########################################################################*/
141                    
142 bool
143 Find::item_id_match (SPItem *item, const gchar *id, bool exact)
145     if (SP_OBJECT_REPR (item) == NULL)
146         return false;
148     if (SP_IS_STRING(item)) // SPStrings have "on demand" ids which are useless for searching
149         return false;
151     const gchar *item_id = (SP_OBJECT_REPR (item))->attribute("id");
152     if (item_id == NULL)
153         return false;
155     if (exact) {
156         return ((bool) !strcmp(item_id, id));
157     } else {
158 //        g_print ("strstr: %s %s: %s\n", item_id, id, strstr(item_id, id) != NULL? "yes":"no");
159         return ((bool) (strstr(item_id, id) != NULL));
160     }
163 bool
164 Find::item_text_match (SPItem *item, const gchar *text, bool exact)
166     if (SP_OBJECT_REPR (item) == NULL)
167         return false;
169     if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
170         const gchar *item_text = sp_te_get_string_multiline (item);
171         if (item_text == NULL)
172             return false;
173         bool ret;
174         if (exact) {
175             ret = ((bool) !strcasecmp(item_text, text));
176         } else {
177             //FIXME: strcasestr
178             ret = ((bool) (strstr(item_text, text) != NULL));
179         }
180         g_free ((void*) item_text);
181         return ret;
182     }
183     return false;
186 bool
187 Find::item_style_match (SPItem *item, const gchar *text, bool exact)
189     if (SP_OBJECT_REPR (item) == NULL)
190         return false;
192     const gchar *item_text = (SP_OBJECT_REPR (item))->attribute("style");
193     if (item_text == NULL)
194         return false;
196     if (exact) {
197         return ((bool) !strcmp(item_text, text));
198     } else {
199         return ((bool) (strstr(item_text, text) != NULL));
200     }
203 bool
204 Find::item_attr_match (SPItem *item, const gchar *name, bool exact)
206     if (SP_OBJECT_REPR (item) == NULL)
207         return false;
209     if (exact) {
210         const gchar *attr_value = (SP_OBJECT_REPR (item))->attribute(name);
211         return ((bool) (attr_value != NULL));
212     } else {
213         return SP_OBJECT_REPR (item)->matchAttributeName(name);
214     }
218 GSList *
219 Find::filter_fields (GSList *l, bool exact)
221     const gchar* text = _entry_text.getEntry()->get_text().c_str();
222     const gchar* id = _entry_id.getEntry()->get_text().c_str();
223     const gchar* style = _entry_style.getEntry()->get_text().c_str();
224     const gchar* attr = _entry_attribute.getEntry()->get_text().c_str();
225     
226     GSList *in = l;
227     GSList *out = NULL;
228     if (strlen (text) != 0) {
229         for (GSList *i = in; i != NULL; i = i->next) {
230             if (item_text_match (SP_ITEM(i->data), text, exact)) {
231                 out = g_slist_prepend (out, i->data);
232             }
233         }
234     } else {
235         out = in;
236     }
237     
238     in = out;
239     out = NULL;
240     if (strlen (id) != 0) {
241         for (GSList *i = in; i != NULL; i = i->next) {
242             if (item_id_match (SP_ITEM(i->data), id, exact)) {
243                 out = g_slist_prepend (out, i->data);
244             }
245         }
246     } else {
247         out = in;
248     }
250     in = out;
251     out = NULL;
252     if (strlen (style) != 0) {
253         for (GSList *i = in; i != NULL; i = i->next) {
254             if (item_style_match (SP_ITEM(i->data), style, exact)) {
255                 out = g_slist_prepend (out, i->data);
256             }
257         }
258     } else {
259         out = in;
260     }
262     in = out;
263     out = NULL;
264     if (strlen (attr) != 0) {
265         for (GSList *i = in; i != NULL; i = i->next) {
266             if (item_attr_match (SP_ITEM(i->data), attr, exact)) {
267                 out = g_slist_prepend (out, i->data);
268             }
269         }
270     } else {
271         out = in;
272     }
274     return out;
278 bool
279 Find::item_type_match (SPItem *item)
281     SPDesktop *desktop = getDesktop();
283     if (SP_IS_RECT(item)) {
284         return (_check_all_shapes.get_active() || _check_rects.get_active());
286     } else if (SP_IS_GENERICELLIPSE(item) || SP_IS_ELLIPSE(item) || SP_IS_ARC(item) || SP_IS_CIRCLE(item)) {
287         return (_check_all_shapes.get_active() || _check_ellipses.get_active());
289     } else if (SP_IS_STAR(item) || SP_IS_POLYGON(item)) {
290         return (_check_all_shapes.get_active() || _check_stars.get_active());
292     } else if (SP_IS_SPIRAL(item)) {
293         return (_check_all_shapes.get_active() || _check_spirals.get_active());
295     } else if (SP_IS_PATH(item) || SP_IS_LINE(item) || SP_IS_POLYLINE(item)) {
296         return (_check_paths.get_active());
298     } else if (SP_IS_TEXT(item) || SP_IS_TSPAN(item) || SP_IS_TREF(item) || SP_IS_STRING(item)) {
299         return (_check_texts.get_active());
301     } else if (SP_IS_GROUP(item) && !desktop->isLayer(item) ) { // never select layers!
302         return (_check_groups.get_active());
304     } else if (SP_IS_USE(item)) {
305         return (_check_clones.get_active());
307     } else if (SP_IS_IMAGE(item)) {
308         return (_check_images.get_active());
310     } else if (SP_IS_OFFSET(item)) {
311         return (_check_offsets.get_active());
312     }
314     return false;
317 GSList *
318 Find::filter_types (GSList *l)
320     if (_check_all.get_active()) return l;
322     GSList *n = NULL;
323     for (GSList *i = l; i != NULL; i = i->next) {
324         if (item_type_match (SP_ITEM(i->data))) {
325             n = g_slist_prepend (n, i->data);
326         }
327     }
328     return n;
332 GSList *
333 Find::filter_list (GSList *l, bool exact)
335     l = filter_fields (l, exact);
336     l = filter_types (l);
337     return l;
340 GSList *
341 Find::all_items (SPObject *r, GSList *l, bool hidden, bool locked)
343     SPDesktop *desktop = getDesktop();
345     if (SP_IS_DEFS(r))
346         return l; // we're not interested in items in defs
348     if (!strcmp (SP_OBJECT_REPR (r)->name(), "svg:metadata"))
349         return l; // we're not interested in metadata
351     for (SPObject *child = r->firstChild(); child; child = child->getNext()) {
352         if (SP_IS_ITEM (child) && !SP_OBJECT_IS_CLONED (child) && !desktop->isLayer(SP_ITEM(child))) {
353                 if ((hidden || !desktop->itemIsHidden(SP_ITEM(child))) && (locked || !SP_ITEM(child)->isLocked())) {
354                     l = g_slist_prepend (l, child);
355                 }
356         }
357         l = all_items (child, l, hidden, locked);
358     }
359     return l;
362 GSList *
363 Find::all_selection_items (Inkscape::Selection *s, GSList *l, SPObject *ancestor, bool hidden, bool locked)
365     SPDesktop *desktop = getDesktop();
367     for (GSList *i = (GSList *) s->itemList(); i != NULL; i = i->next) {
368         if (SP_IS_ITEM (i->data) && !SP_OBJECT_IS_CLONED (i->data) && !desktop->isLayer(SP_ITEM(i->data))) {
369             if (!ancestor || ancestor->isAncestorOf(SP_OBJECT (i->data))) {
370                 if ((hidden || !desktop->itemIsHidden(SP_ITEM(i->data))) && (locked || !SP_ITEM(i->data)->isLocked())) {
371                     l = g_slist_prepend (l, i->data);
372                 }
373             }
374         }
375         if (!ancestor || ancestor->isAncestorOf(SP_OBJECT (i->data))) {
376             l = all_items (SP_OBJECT (i->data), l, hidden, locked);
377         }
378     }
379     return l;
381                    
382                    
384 /*########################################################################
385 # BUTTON CLICK HANDLERS    (callbacks)
386 ########################################################################*/
388 void
389 Find::onClear()
390 {         
391     _entry_text.getEntry()->set_text(Glib::ustring(""));
392     _entry_id.getEntry()->set_text(Glib::ustring(""));
393     _entry_style.getEntry()->set_text(Glib::ustring(""));
394     _entry_attribute.getEntry()->set_text(Glib::ustring(""));
396     _check_all.set_active();
398     
399     
401 void
402 Find::onFind()
403 {   
404     SPDesktop *desktop = getDesktop();
406     bool hidden = _check_include_hidden.get_active();
407     bool locked = _check_include_locked.get_active();
409     GSList *l = NULL;
410     if (_check_search_selection.get_active()) {
411         if (_check_search_layer.get_active()) {
412             l = all_selection_items (desktop->selection, l, desktop->currentLayer(), hidden, locked);
413         } else {
414             l = all_selection_items (desktop->selection, l, NULL, hidden, locked);
415         }
416     } else {
417         if (_check_search_layer.get_active()) {
418             l = all_items (desktop->currentLayer(), l, hidden, locked);
419         } else {
420             l = all_items(sp_desktop_document(desktop)->getRoot(), l, hidden, locked);
421         }
422     }
423     guint all = g_slist_length (l);
425     bool exact = true;
426     GSList *n = NULL;
427     n = filter_list (l, exact);
428     if (n == NULL) {
429         exact = false;
430         n = filter_list (l, exact);
431     }
433     if (n != NULL) {
434         int count = g_slist_length (n);
435         desktop->messageStack()->flashF(Inkscape::NORMAL_MESSAGE,
436                                         // TRANSLATORS: "%s" is replaced with "exact" or "partial" when this string is displayed
437                                         ngettext("<b>%d</b> object found (out of <b>%d</b>), %s match.",
438                                                  "<b>%d</b> objects found (out of <b>%d</b>), %s match.",
439                                                  count),
440                                         count, all, exact? _("exact") : _("partial"));
442         Inkscape::Selection *selection = sp_desktop_selection (desktop);
443         selection->clear();
444         selection->setList(n);
445         scroll_to_show_item (desktop, SP_ITEM(n->data));
446     } else {
447         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No objects found"));
448     }
451 void
452 Find::onToggleAlltypes ()
454     if (_check_all.get_active()) {
455         // explicit toggle to make sure its handler gets called, no matter what was the original state
456         _check_all_shapes.toggled();
457         _check_all_shapes.set_active();
458         _check_all_shapes.hide();
459         _check_paths.hide();
460         _check_texts.hide();
461         _check_groups.hide();
462         _check_clones.hide();
463         _check_images.hide();
464         _check_offsets.hide();
465     } else {
466         // explicit toggle to make sure its handler gets called, no matter what was the original state
467         _check_all_shapes.toggled();
468         _check_all_shapes.set_active();
469         _check_all_shapes.show();
471         _check_paths.set_active();
472         _check_paths.show();
473         _check_texts.set_active();
474         _check_texts.show();
475         _check_groups.set_active();
476         _check_groups.show();
477         _check_clones.set_active();
478         _check_clones.show();
479         _check_images.set_active();
480         _check_images.show();
481         _check_offsets.set_active();
482         _check_offsets.show();
483     }
484     squeeze_window();
487 void
488 Find::onToggleShapes ()
490     if (_check_all_shapes.get_active()) {
491         _check_rects.hide();
492         _check_ellipses.hide();
493         _check_stars.hide();
494         _check_spirals.hide();
495     } else {
496         _check_rects.set_active();
497         _check_rects.show();
498         _check_ellipses.set_active();
499         _check_ellipses.show();
500         _check_stars.set_active();
501         _check_stars.show();
502         _check_spirals.set_active();
503         _check_spirals.show();
504     }
505     squeeze_window();
509 /*########################################################################
510 # UTILITY
511 ########################################################################*/
515 void
516 Find::squeeze_window()
518     // TO DO: make window as small as possible
523 } // namespace Dialog
524 } // namespace UI
525 } // namespace Inkscape