0a0538c30ab4efcf512b455bcd9af609dd2cc79c
1 /**
2 * \brief Find dialog
3 *
4 * Authors:
5 * Bryce W. Harrington <bryce@bryceharrington.org>
6 * Johan Engelen <goejendaagh@zonnet.nl>
7 *
8 * Copyright (C) 2004-2006 Authors
9 *
10 * Released under GNU GPL. Read the file 'COPYING' for more information.
11 */
13 #ifdef HAVE_CONFIG_H
14 # include <config.h>
15 #endif
17 #include <gtkmm/widget.h>
18 #include "find.h"
19 #include "verbs.h"
21 #include "message-stack.h"
22 #include "helper/window.h"
23 #include "macros.h"
24 #include "inkscape.h"
25 #include "document.h"
26 #include "desktop.h"
27 #include "selection.h"
28 #include "desktop-handles.h"
30 #include "dialogs/dialog-events.h"
31 #include "prefs-utils.h"
32 #include "verbs.h"
33 #include "interface.h"
34 #include "sp-text.h"
35 #include "sp-flowtext.h"
36 #include "text-editing.h"
37 #include "sp-tspan.h"
38 #include "sp-tref.h"
39 #include "selection-chemistry.h"
40 #include "sp-defs.h"
41 #include "sp-rect.h"
42 #include "sp-ellipse.h"
43 #include "sp-star.h"
44 #include "sp-spiral.h"
45 #include "sp-path.h"
46 #include "sp-line.h"
47 #include "sp-polyline.h"
48 #include "sp-item-group.h"
49 #include "sp-use.h"
50 #include "sp-image.h"
51 #include "sp-offset.h"
52 #include <xml/repr.h>
55 namespace Inkscape {
56 namespace UI {
57 namespace Dialog {
59 Find::Find(Behavior::BehaviorFactory behavior_factory)
60 : Dialog (behavior_factory, "dialogs.find", SP_VERB_DIALOG_FIND),
61 _entry_text(_("_Text: "), _("Find objects by their text content (exact or partial match)")),
62 _entry_id(_("_ID: "), _("Find objects by the value of the id attribute (exact or partial match)")),
63 _entry_style(_("_Style: "), _("Find objects by the value of the style attribute (exact or partial match)")),
64 _entry_attribute(_("_Attribute: "), _("Find objects by the name of an attribute (exact or partial match)")),
65 _check_search_selection(_("Search in s_election"), _("Limit search to the current selection")),
66 _check_search_layer(_("Search in current _layer"), _("Limit search to the current layer")),
67 _check_include_hidden(_("Include _hidden"), _("Include hidden objects in search")),
68 _check_include_locked(_("Include l_ocked"), _("Include locked objects in search")),
70 _check_all(_("All types"), _("Search in all object types")),
71 _check_all_shapes(_("All shapes"), _("Search all shapes")),
72 _check_rects(_("Rectangles"), _("Search rectangles")),
73 _check_ellipses(_("Ellipses"), _("Search ellipses, arcs, circles")),
74 _check_stars(_("Stars"), _("Search stars and polygons")),
75 _check_spirals(_("Spirals"), _("Search spirals")),
76 _check_paths(_("Paths"), _("Search paths, lines, polylines")),
77 _check_texts(_("Texts"), _("Search text objects")),
78 _check_groups(_("Groups"), _("Search groups")),
79 _check_clones(_("Clones"), _("Search clones")),
80 _check_images(_("Images"), _("Search images")),
81 _check_offsets(_("Offsets"), _("Search offset objects")),
83 _button_clear(_("_Clear"), _("Clear values")),
84 _button_find(_("_Find"), _("Select objects matching all of the fields you filled in"))
85 {
86 // Top level vbox
87 Gtk::VBox *vbox = get_vbox();
88 vbox->set_spacing(4);
90 vbox->pack_start(_entry_text, true, true);
91 vbox->pack_start(_entry_id, true, true);
92 vbox->pack_start(_entry_style, true, true);
93 vbox->pack_start(_entry_attribute, true, true);
95 vbox->pack_start(_check_all, true, true);
96 vbox->pack_start(_check_all_shapes, true, true);
97 vbox->pack_start(_check_rects, true, true);
98 vbox->pack_start(_check_ellipses, true, true);
99 vbox->pack_start(_check_stars, true, true);
100 vbox->pack_start(_check_spirals, true, true);
101 vbox->pack_start(_check_paths, true, true);
102 vbox->pack_start(_check_texts, true, true);
103 vbox->pack_start(_check_groups, true, true);
104 vbox->pack_start(_check_clones, true, true);
105 vbox->pack_start(_check_images, true, true);
106 vbox->pack_start(_check_offsets, true, true);
108 vbox->pack_start(_check_search_selection, true, true);
109 vbox->pack_start(_check_search_layer, true, true);
110 vbox->pack_start(_check_include_hidden, true, true);
111 vbox->pack_start(_check_include_locked, true, true);
113 vbox->pack_start(_button_clear, true, true);
114 vbox->pack_start(_button_find, true, true);
116 // set signals to handle clicks
117 _check_all.signal_clicked().connect(sigc::mem_fun(*this, &Find::onToggleAlltypes));
118 _check_all_shapes.signal_clicked().connect(sigc::mem_fun(*this, &Find::onToggleShapes));
119 _button_clear.signal_clicked().connect(sigc::mem_fun(*this, &Find::onClear));
120 _button_find.signal_clicked().connect(sigc::mem_fun(*this, &Find::onFind));
122 _button_find.set_flags(Gtk::CAN_DEFAULT);
123 set_default (_button_find); // activatable by Enter
124 _entry_text.getEntry()->grab_focus();
126 show_all_children();
127 onClear();
128 }
130 Find::~Find()
131 {
132 }
135 /*########################################################################
136 # FIND helper functions
137 ########################################################################*/
140 bool
141 Find::item_id_match (SPItem *item, const gchar *id, bool exact)
142 {
143 if (SP_OBJECT_REPR (item) == NULL)
144 return false;
146 if (SP_IS_STRING(item)) // SPStrings have "on demand" ids which are useless for searching
147 return false;
149 const gchar *item_id = (SP_OBJECT_REPR (item))->attribute("id");
150 if (item_id == NULL)
151 return false;
153 if (exact) {
154 return ((bool) !strcmp(item_id, id));
155 } else {
156 // g_print ("strstr: %s %s: %s\n", item_id, id, strstr(item_id, id) != NULL? "yes":"no");
157 return ((bool) (strstr(item_id, id) != NULL));
158 }
159 }
161 bool
162 Find::item_text_match (SPItem *item, const gchar *text, bool exact)
163 {
164 if (SP_OBJECT_REPR (item) == NULL)
165 return false;
167 if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
168 const gchar *item_text = sp_te_get_string_multiline (item);
169 if (item_text == NULL)
170 return false;
171 bool ret;
172 if (exact) {
173 ret = ((bool) !strcasecmp(item_text, text));
174 } else {
175 //FIXME: strcasestr
176 ret = ((bool) (strstr(item_text, text) != NULL));
177 }
178 g_free ((void*) item_text);
179 return ret;
180 }
181 return false;
182 }
184 bool
185 Find::item_style_match (SPItem *item, const gchar *text, bool exact)
186 {
187 if (SP_OBJECT_REPR (item) == NULL)
188 return false;
190 const gchar *item_text = (SP_OBJECT_REPR (item))->attribute("style");
191 if (item_text == NULL)
192 return false;
194 if (exact) {
195 return ((bool) !strcmp(item_text, text));
196 } else {
197 return ((bool) (strstr(item_text, text) != NULL));
198 }
199 }
201 bool
202 Find::item_attr_match (SPItem *item, const gchar *name, bool exact)
203 {
204 if (SP_OBJECT_REPR (item) == NULL)
205 return false;
207 if (exact) {
208 const gchar *attr_value = (SP_OBJECT_REPR (item))->attribute(name);
209 return ((bool) (attr_value != NULL));
210 } else {
211 return SP_OBJECT_REPR (item)->matchAttributeName(name);
212 }
213 }
216 GSList *
217 Find::filter_fields (GSList *l, bool exact)
218 {
219 const gchar* text = _entry_text.getEntry()->get_text().c_str();
220 const gchar* id = _entry_id.getEntry()->get_text().c_str();
221 const gchar* style = _entry_style.getEntry()->get_text().c_str();
222 const gchar* attr = _entry_attribute.getEntry()->get_text().c_str();
224 GSList *in = l;
225 GSList *out = NULL;
226 if (strlen (text) != 0) {
227 for (GSList *i = in; i != NULL; i = i->next) {
228 if (item_text_match (SP_ITEM(i->data), text, exact)) {
229 out = g_slist_prepend (out, i->data);
230 }
231 }
232 } else {
233 out = in;
234 }
236 in = out;
237 out = NULL;
238 if (strlen (id) != 0) {
239 for (GSList *i = in; i != NULL; i = i->next) {
240 if (item_id_match (SP_ITEM(i->data), id, exact)) {
241 out = g_slist_prepend (out, i->data);
242 }
243 }
244 } else {
245 out = in;
246 }
248 in = out;
249 out = NULL;
250 if (strlen (style) != 0) {
251 for (GSList *i = in; i != NULL; i = i->next) {
252 if (item_style_match (SP_ITEM(i->data), style, exact)) {
253 out = g_slist_prepend (out, i->data);
254 }
255 }
256 } else {
257 out = in;
258 }
260 in = out;
261 out = NULL;
262 if (strlen (attr) != 0) {
263 for (GSList *i = in; i != NULL; i = i->next) {
264 if (item_attr_match (SP_ITEM(i->data), attr, exact)) {
265 out = g_slist_prepend (out, i->data);
266 }
267 }
268 } else {
269 out = in;
270 }
272 return out;
273 }
276 bool
277 Find::item_type_match (SPItem *item)
278 {
279 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
281 if (SP_IS_RECT(item)) {
282 return (_check_all_shapes.get_active() || _check_rects.get_active());
284 } else if (SP_IS_GENERICELLIPSE(item) || SP_IS_ELLIPSE(item) || SP_IS_ARC(item) || SP_IS_CIRCLE(item)) {
285 return (_check_all_shapes.get_active() || _check_ellipses.get_active());
287 } else if (SP_IS_STAR(item) || SP_IS_POLYGON(item)) {
288 return (_check_all_shapes.get_active() || _check_stars.get_active());
290 } else if (SP_IS_SPIRAL(item)) {
291 return (_check_all_shapes.get_active() || _check_spirals.get_active());
293 } else if (SP_IS_PATH(item) || SP_IS_LINE(item) || SP_IS_POLYLINE(item)) {
294 return (_check_paths.get_active());
296 } else if (SP_IS_TEXT(item) || SP_IS_TSPAN(item) || SP_IS_TREF(item) || SP_IS_STRING(item)) {
297 return (_check_texts.get_active());
299 } else if (SP_IS_GROUP(item) && !desktop->isLayer(item) ) { // never select layers!
300 return (_check_groups.get_active());
302 } else if (SP_IS_USE(item)) {
303 return (_check_clones.get_active());
305 } else if (SP_IS_IMAGE(item)) {
306 return (_check_images.get_active());
308 } else if (SP_IS_OFFSET(item)) {
309 return (_check_offsets.get_active());
310 }
312 return false;
313 }
315 GSList *
316 Find::filter_types (GSList *l)
317 {
318 if (_check_all.get_active()) return l;
320 GSList *n = NULL;
321 for (GSList *i = l; i != NULL; i = i->next) {
322 if (item_type_match (SP_ITEM(i->data))) {
323 n = g_slist_prepend (n, i->data);
324 }
325 }
326 return n;
327 }
330 GSList *
331 Find::filter_list (GSList *l, bool exact)
332 {
333 l = filter_fields (l, exact);
334 l = filter_types (l);
335 return l;
336 }
338 GSList *
339 Find::all_items (SPObject *r, GSList *l, bool hidden, bool locked)
340 {
341 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
343 if (SP_IS_DEFS(r))
344 return l; // we're not interested in items in defs
346 if (!strcmp (SP_OBJECT_REPR (r)->name(), "svg:metadata"))
347 return l; // we're not interested in metadata
349 for (SPObject *child = sp_object_first_child(r); child; child = SP_OBJECT_NEXT (child)) {
350 if (SP_IS_ITEM (child) && !SP_OBJECT_IS_CLONED (child) && !desktop->isLayer(SP_ITEM(child))) {
351 if ((hidden || !desktop->itemIsHidden(SP_ITEM(child))) && (locked || !SP_ITEM(child)->isLocked())) {
352 l = g_slist_prepend (l, child);
353 }
354 }
355 l = all_items (child, l, hidden, locked);
356 }
357 return l;
358 }
360 GSList *
361 Find::all_selection_items (Inkscape::Selection *s, GSList *l, SPObject *ancestor, bool hidden, bool locked)
362 {
363 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
365 for (GSList *i = (GSList *) s->itemList(); i != NULL; i = i->next) {
366 if (SP_IS_ITEM (i->data) && !SP_OBJECT_IS_CLONED (i->data) && !desktop->isLayer(SP_ITEM(i->data))) {
367 if (!ancestor || ancestor->isAncestorOf(SP_OBJECT (i->data))) {
368 if ((hidden || !desktop->itemIsHidden(SP_ITEM(i->data))) && (locked || !SP_ITEM(i->data)->isLocked())) {
369 l = g_slist_prepend (l, i->data);
370 }
371 }
372 }
373 if (!ancestor || ancestor->isAncestorOf(SP_OBJECT (i->data))) {
374 l = all_items (SP_OBJECT (i->data), l, hidden, locked);
375 }
376 }
377 return l;
378 }
382 /*########################################################################
383 # BUTTON CLICK HANDLERS (callbacks)
384 ########################################################################*/
386 void
387 Find::onClear()
388 {
389 _entry_text.getEntry()->set_text(Glib::ustring(""));
390 _entry_id.getEntry()->set_text(Glib::ustring(""));
391 _entry_style.getEntry()->set_text(Glib::ustring(""));
392 _entry_attribute.getEntry()->set_text(Glib::ustring(""));
394 _check_all.set_active();
395 }
399 void
400 Find::onFind()
401 {
402 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
404 bool hidden = _check_include_hidden.get_active();
405 bool locked = _check_include_locked.get_active();
407 GSList *l = NULL;
408 if (_check_search_selection.get_active()) {
409 if (_check_search_layer.get_active()) {
410 l = all_selection_items (desktop->selection, l, desktop->currentLayer(), hidden, locked);
411 } else {
412 l = all_selection_items (desktop->selection, l, NULL, hidden, locked);
413 }
414 } else {
415 if (_check_search_layer.get_active()) {
416 l = all_items (desktop->currentLayer(), l, hidden, locked);
417 } else {
418 l = all_items (SP_DOCUMENT_ROOT (sp_desktop_document (desktop)), l, hidden, locked);
419 }
420 }
421 guint all = g_slist_length (l);
423 bool exact = true;
424 GSList *n = NULL;
425 n = filter_list (l, exact);
426 if (n == NULL) {
427 exact = false;
428 n = filter_list (l, exact);
429 }
431 if (n != NULL) {
432 int count = g_slist_length (n);
433 desktop->messageStack()->flashF(Inkscape::NORMAL_MESSAGE,
434 // TRANSLATORS: "%s" is replaced with "exact" or "partial" when this string is displayed
435 ngettext("<b>%d</b> object found (out of <b>%d</b>), %s match.",
436 "<b>%d</b> objects found (out of <b>%d</b>), %s match.",
437 count),
438 count, all, exact? _("exact") : _("partial"));
440 Inkscape::Selection *selection = sp_desktop_selection (desktop);
441 selection->clear();
442 selection->setList(n);
443 scroll_to_show_item (desktop, SP_ITEM(n->data));
444 } else {
445 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No objects found"));
446 }
447 }
449 void
450 Find::onToggleAlltypes ()
451 {
452 if (_check_all.get_active()) {
453 // explicit toggle to make sure its handler gets called, no matter what was the original state
454 _check_all_shapes.toggled();
455 _check_all_shapes.set_active();
456 _check_all_shapes.hide();
457 _check_paths.hide();
458 _check_texts.hide();
459 _check_groups.hide();
460 _check_clones.hide();
461 _check_images.hide();
462 _check_offsets.hide();
463 } else {
464 // explicit toggle to make sure its handler gets called, no matter what was the original state
465 _check_all_shapes.toggled();
466 _check_all_shapes.set_active();
467 _check_all_shapes.show();
469 _check_paths.set_active();
470 _check_paths.show();
471 _check_texts.set_active();
472 _check_texts.show();
473 _check_groups.set_active();
474 _check_groups.show();
475 _check_clones.set_active();
476 _check_clones.show();
477 _check_images.set_active();
478 _check_images.show();
479 _check_offsets.set_active();
480 _check_offsets.show();
481 }
482 squeeze_window();
483 }
485 void
486 Find::onToggleShapes ()
487 {
488 if (_check_all_shapes.get_active()) {
489 _check_rects.hide();
490 _check_ellipses.hide();
491 _check_stars.hide();
492 _check_spirals.hide();
493 } else {
494 _check_rects.set_active();
495 _check_rects.show();
496 _check_ellipses.set_active();
497 _check_ellipses.show();
498 _check_stars.set_active();
499 _check_stars.show();
500 _check_spirals.set_active();
501 _check_spirals.show();
502 }
503 squeeze_window();
504 }
507 /*########################################################################
508 # UTILITY
509 ########################################################################*/
513 void
514 Find::squeeze_window()
515 {
516 // TO DO: make window as small as possible
517 }
521 } // namespace Dialog
522 } // namespace UI
523 } // namespace Inkscape