PageRenderTime 54ms CodeModel.GetById 5ms RepoModel.GetById 0ms app.codeStats 0ms

/src/ui/gui/find-dialog.c

#
C | 756 lines | 513 code | 187 blank | 56 comment | 40 complexity | a453800d22473842835a0a9c4689c6b7 MD5 | raw file
Possible License(s): GPL-3.0, AGPL-1.0, LGPL-2.1
  1. /* PSPPIRE - a graphical user interface for PSPP.
  2. Copyright (C) 2007, 2009, 2011, 2012 Free Software Foundation
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 3 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>. */
  13. /* This module implements the "Find" dialog; a dialog box to locate cases
  14. which match particular strings */
  15. #include <config.h>
  16. #include <ctype.h>
  17. #include <gtk/gtk.h>
  18. #include <regex.h>
  19. #include <stdlib.h>
  20. #include <sys/types.h>
  21. #include "data/data-in.h"
  22. #include "data/datasheet.h"
  23. #include "data/format.h"
  24. #include "data/value.h"
  25. #include "libpspp/cast.h"
  26. #include "libpspp/message.h"
  27. #include "ui/gui/builder-wrapper.h"
  28. #include "ui/gui/dict-display.h"
  29. #include "ui/gui/find-dialog.h"
  30. #include "ui/gui/helper.h"
  31. #include "ui/gui/psppire-data-sheet.h"
  32. #include "ui/gui/psppire-data-store.h"
  33. #include "ui/gui/psppire-data-window.h"
  34. #include "ui/gui/psppire-dialog.h"
  35. #include "ui/gui/psppire-selector.h"
  36. #include "gl/xalloc.h"
  37. #include <gettext.h>
  38. #define _(msgid) gettext (msgid)
  39. #define N_(msgid) msgid
  40. struct find_dialog
  41. {
  42. GtkBuilder *xml;
  43. PsppireDict *dict;
  44. struct datasheet *data;
  45. PsppireDataWindow *de;
  46. GtkWidget *variable_entry;
  47. GtkWidget *value_entry;
  48. GtkWidget *value_labels_checkbox;
  49. GtkWidget *match_regexp_checkbox;
  50. GtkWidget *match_substring_checkbox;
  51. };
  52. static void
  53. find_value (const struct find_dialog *fd, casenumber current_row,
  54. casenumber *row, int *column);
  55. /* A callback which occurs whenever the "Refresh" button is clicked,
  56. and when the dialog pops up.
  57. It restores the dialog to its default state.
  58. */
  59. static void
  60. refresh (GObject *obj, const struct find_dialog *fd)
  61. {
  62. gtk_toggle_button_set_active
  63. (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")),
  64. FALSE);
  65. gtk_toggle_button_set_active
  66. (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")),
  67. FALSE);
  68. gtk_entry_set_text (GTK_ENTRY (fd->variable_entry), "");
  69. gtk_entry_set_text (GTK_ENTRY (fd->value_entry), "");
  70. gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox),
  71. FALSE);
  72. gtk_toggle_button_set_active
  73. (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
  74. gtk_toggle_button_set_active
  75. (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
  76. }
  77. /* Callback on the "Find" button */
  78. static void
  79. do_find (GObject *obj, const struct find_dialog *fd)
  80. {
  81. PsppireDataSheet *data_sheet;
  82. casenumber x = -1;
  83. gint column = -1;
  84. glong row;
  85. data_sheet = psppire_data_editor_get_active_data_sheet (fd->de->data_editor);
  86. row = psppire_data_sheet_get_selected_case (data_sheet);
  87. if ( row < 0 )
  88. row = 0;
  89. find_value (fd, row, &x, &column);
  90. if ( x != -1)
  91. {
  92. gtk_notebook_set_current_page (GTK_NOTEBOOK (fd->de->data_editor),
  93. PSPPIRE_DATA_EDITOR_DATA_VIEW);
  94. psppire_data_sheet_goto_case (data_sheet, x);
  95. psppire_data_sheet_goto_variable (data_sheet, column);
  96. }
  97. }
  98. /* Callback on the selector.
  99. It gets invoked whenever a variable is selected */
  100. static void
  101. on_select (GtkEntry *entry, gpointer data)
  102. {
  103. struct find_dialog *fd = data;
  104. const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
  105. struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
  106. gboolean search_labels ;
  107. g_return_if_fail (var);
  108. gtk_widget_set_sensitive (fd->value_labels_checkbox,
  109. var_has_value_labels (var));
  110. search_labels =
  111. gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox));
  112. gtk_widget_set_sensitive (fd->match_regexp_checkbox,
  113. var_is_alpha (var) || search_labels);
  114. gtk_widget_set_sensitive (fd->match_substring_checkbox,
  115. var_is_alpha (var) || search_labels);
  116. }
  117. /* Callback on the selector.
  118. It gets invoked whenever a variable is unselected */
  119. static void
  120. on_deselect (GtkEntry *entry, gpointer data)
  121. {
  122. struct find_dialog *fd = data;
  123. gtk_widget_set_sensitive (fd->value_labels_checkbox, FALSE);
  124. gtk_widget_set_sensitive (fd->match_substring_checkbox, FALSE);
  125. gtk_widget_set_sensitive (fd->match_regexp_checkbox, FALSE);
  126. }
  127. static void
  128. value_labels_toggled (GtkToggleButton *tb, gpointer data)
  129. {
  130. struct find_dialog *fd = data;
  131. const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
  132. const struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
  133. gboolean active = gtk_toggle_button_get_active (tb) ;
  134. gtk_widget_set_sensitive (fd->match_substring_checkbox,
  135. active || (var && var_is_alpha (var)));
  136. gtk_widget_set_sensitive (fd->match_regexp_checkbox,
  137. active || (var && var_is_alpha (var)));
  138. }
  139. /* Pops up the Find dialog box
  140. */
  141. void
  142. find_dialog (PsppireDataWindow *de)
  143. {
  144. struct find_dialog fd;
  145. GtkWidget *dialog ;
  146. GtkWidget *source ;
  147. GtkWidget *selector;
  148. GtkWidget *find_button;
  149. GtkWidget *buttonbox;
  150. PsppireDataStore *ds ;
  151. fd.xml = builder_new ("find.ui");
  152. fd.de = de;
  153. find_button = gtk_button_new_from_stock (GTK_STOCK_FIND);
  154. gtk_widget_show (find_button);
  155. buttonbox = get_widget_assert (fd.xml, "find-buttonbox");
  156. psppire_box_pack_start_defaults (GTK_BOX (buttonbox), find_button);
  157. gtk_box_reorder_child (GTK_BOX (buttonbox), find_button, 0);
  158. dialog = get_widget_assert (fd.xml, "find-dialog");
  159. source = get_widget_assert (fd.xml, "find-variable-treeview");
  160. selector = get_widget_assert (fd.xml, "find-selector");
  161. g_object_get (de->data_editor,
  162. "dictionary", &fd.dict,
  163. "data-store", &ds,
  164. NULL);
  165. fd.data = ds->datasheet;
  166. fd.variable_entry = get_widget_assert (fd.xml, "find-variable-entry");
  167. fd.value_entry = get_widget_assert (fd.xml, "find-value-entry");
  168. fd.value_labels_checkbox =
  169. get_widget_assert (fd.xml,
  170. "find-value-labels-checkbutton");
  171. fd.match_regexp_checkbox =
  172. get_widget_assert (fd.xml,
  173. "find-match-regexp-checkbutton");
  174. fd.match_substring_checkbox =
  175. get_widget_assert (fd.xml,
  176. "find-match-substring-checkbutton");
  177. gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
  178. g_object_set (source, "model", fd.dict,
  179. "selection-mode", GTK_SELECTION_SINGLE,
  180. NULL);
  181. psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector),
  182. is_currently_in_entry);
  183. g_signal_connect (dialog, "refresh", G_CALLBACK (refresh), &fd);
  184. g_signal_connect (find_button, "clicked", G_CALLBACK (do_find), &fd);
  185. g_signal_connect (selector, "selected",
  186. G_CALLBACK (on_select), &fd);
  187. g_signal_connect (selector, "de-selected",
  188. G_CALLBACK (on_deselect), &fd);
  189. g_signal_connect (fd.value_labels_checkbox, "toggled",
  190. G_CALLBACK (value_labels_toggled), &fd);
  191. psppire_dialog_run (PSPPIRE_DIALOG (dialog));
  192. g_object_unref (fd.xml);
  193. }
  194. /* Iterators */
  195. static void
  196. forward (casenumber *i, struct datasheet *data UNUSED)
  197. {
  198. ++*i;
  199. }
  200. static void
  201. forward_wrap (casenumber *i, struct datasheet *data)
  202. {
  203. if ( ++*i >= datasheet_get_n_rows (data) ) *i = 0;
  204. }
  205. static void
  206. backward (casenumber *i, struct datasheet *data UNUSED)
  207. {
  208. --*i;
  209. }
  210. static void
  211. backward_wrap (casenumber *i, struct datasheet *data)
  212. {
  213. if ( --*i < 0 )
  214. *i = datasheet_get_n_rows (data) - 1;
  215. }
  216. /* Current plus one */
  217. static casenumber
  218. cp1 (casenumber current, struct datasheet *data)
  219. {
  220. return current + 1;
  221. }
  222. /* Current plus one, circular */
  223. static casenumber
  224. cp1c (casenumber current, struct datasheet *data)
  225. {
  226. casenumber next = current;
  227. forward_wrap (&next, data);
  228. return next;
  229. }
  230. /* Current minus one */
  231. static casenumber
  232. cm1 (casenumber current, struct datasheet *data)
  233. {
  234. return current - 1;
  235. }
  236. /* Current minus one, circular */
  237. static casenumber
  238. cm1c (casenumber current, struct datasheet *data)
  239. {
  240. casenumber next = current;
  241. backward_wrap (&next, data);
  242. return next;
  243. }
  244. static casenumber
  245. last (casenumber current, struct datasheet *data)
  246. {
  247. return datasheet_get_n_rows (data) ;
  248. }
  249. static casenumber
  250. minus1 (casenumber current, struct datasheet *data)
  251. {
  252. return -1;
  253. }
  254. /* An type to facilitate iterating through casenumbers */
  255. struct casenum_iterator
  256. {
  257. /* returns the first case to access */
  258. casenumber (*start) (casenumber, struct datasheet *);
  259. /* Returns one past the last case to access */
  260. casenumber (*end) (casenumber, struct datasheet *);
  261. /* Sets the first arg to the next case to access */
  262. void (*next) (casenumber *, struct datasheet *);
  263. };
  264. enum iteration_type{
  265. FORWARD = 0,
  266. FORWARD_WRAP,
  267. REVERSE,
  268. REVERSE_WRAP,
  269. n_iterators
  270. };
  271. static const struct casenum_iterator ip[n_iterators] =
  272. {
  273. {cp1, last, forward},
  274. {cp1c, cm1, forward_wrap},
  275. {cm1, minus1, backward},
  276. {cm1c, cp1, backward_wrap}
  277. };
  278. /* A factory returning an iterator according to the dialog box's settings */
  279. static const struct casenum_iterator *
  280. get_iteration_params (const struct find_dialog *fd)
  281. {
  282. gboolean wrap = gtk_toggle_button_get_active
  283. (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")));
  284. gboolean reverse = gtk_toggle_button_get_active
  285. (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")));
  286. if ( wrap )
  287. {
  288. if ( reverse )
  289. return &ip[REVERSE_WRAP];
  290. else
  291. return &ip[FORWARD_WRAP];
  292. }
  293. else
  294. {
  295. if ( reverse )
  296. return &ip[REVERSE];
  297. else
  298. return &ip[FORWARD];
  299. }
  300. }
  301. enum string_cmp_flags
  302. {
  303. STR_CMP_SUBSTR = 0x01, /* Find strings which are substrings of the
  304. values */
  305. STR_CMP_REGEXP = 0x02, /* Match against a regular expression */
  306. STR_CMP_LABELS = 0x04 /* Match against the values' labels instead
  307. of the data */
  308. };
  309. /* An abstract base type for comparing union values against a reference */
  310. struct comparator
  311. {
  312. const struct variable *var;
  313. enum string_cmp_flags flags;
  314. bool (*compare) (const struct comparator *,
  315. const union value *);
  316. void (*destroy) (struct comparator *);
  317. };
  318. /* A comparator which operates on the unadulterated union values */
  319. struct value_comparator
  320. {
  321. struct comparator parent;
  322. union value pattern;
  323. };
  324. /* A comparator which matches string values or parts thereof */
  325. struct string_comparator
  326. {
  327. struct comparator parent;
  328. const char *pattern;
  329. };
  330. /* A comparator to match string values against a POSIX.2 regular expression */
  331. struct regexp_comparator
  332. {
  333. struct comparator parent;
  334. regex_t re;
  335. };
  336. static bool
  337. value_compare (const struct comparator *cmptr,
  338. const union value *v)
  339. {
  340. const struct value_comparator *vc = (const struct value_comparator *) cmptr;
  341. return 0 == value_compare_3way (v, &vc->pattern, var_get_width (cmptr->var));
  342. }
  343. /* Return true if the label of VAL matches the reference string*/
  344. static bool
  345. string_label_compare (const struct comparator *cmptr,
  346. const union value *val)
  347. {
  348. const struct string_comparator *ssc =
  349. (const struct string_comparator *) cmptr;
  350. int width;
  351. const char *text = var_lookup_value_label (cmptr->var, val);
  352. if (text == NULL)
  353. return false;
  354. width = strlen (text);
  355. assert ( cmptr->flags & STR_CMP_LABELS);
  356. g_return_val_if_fail (width > 0, false);
  357. if ( cmptr->flags & STR_CMP_SUBSTR)
  358. return (NULL != g_strstr_len (text, width, ssc->pattern));
  359. else
  360. return (0 == strncmp (text, ssc->pattern, width));
  361. }
  362. /* Return true if VAL matches the reference string*/
  363. static bool
  364. string_value_compare (const struct comparator *cmptr,
  365. const union value *val)
  366. {
  367. bool found;
  368. char *text;
  369. const struct string_comparator *ssc =
  370. (const struct string_comparator *) cmptr;
  371. int width = var_get_width (cmptr->var);
  372. g_return_val_if_fail (width > 0, false);
  373. assert ( ! (cmptr->flags & STR_CMP_LABELS));
  374. text = value_to_text (*val, cmptr->var);
  375. if ( cmptr->flags & STR_CMP_SUBSTR)
  376. found = (NULL != g_strstr_len (text, width, ssc->pattern));
  377. else
  378. found = (0 == strncmp (text, ssc->pattern, width));
  379. free (text);
  380. return found;
  381. }
  382. /* Return true if VAL matched the regexp */
  383. static bool
  384. regexp_value_compare (const struct comparator *cmptr,
  385. const union value *val)
  386. {
  387. char *text;
  388. bool retval;
  389. const struct regexp_comparator *rec =
  390. (const struct regexp_comparator *) cmptr;
  391. int width = var_get_width (cmptr->var);
  392. assert ( ! (cmptr->flags & STR_CMP_LABELS) );
  393. g_return_val_if_fail (width > 0, false);
  394. text = value_to_text (*val, cmptr->var);
  395. /* We must remove trailing whitespace, otherwise $ will not match where
  396. one would expect */
  397. g_strchomp (text);
  398. retval = (0 == regexec (&rec->re, text, 0, 0, 0));
  399. g_free (text);
  400. return retval;
  401. }
  402. /* Return true if the label of VAL matched the regexp */
  403. static bool
  404. regexp_label_compare (const struct comparator *cmptr,
  405. const union value *val)
  406. {
  407. const char *text;
  408. const struct regexp_comparator *rec =
  409. (const struct regexp_comparator *) cmptr;
  410. int width ;
  411. assert ( cmptr->flags & STR_CMP_LABELS);
  412. text = var_lookup_value_label (cmptr->var, val);
  413. width = strlen (text);
  414. g_return_val_if_fail (width > 0, false);
  415. return (0 == regexec (&rec->re, text, 0, 0, 0));
  416. }
  417. static void
  418. regexp_destroy (struct comparator *cmptr)
  419. {
  420. struct regexp_comparator *rec
  421. = UP_CAST (cmptr, struct regexp_comparator, parent);
  422. regfree (&rec->re);
  423. }
  424. static void
  425. cmptr_value_destroy (struct comparator *cmptr)
  426. {
  427. struct value_comparator *vc
  428. = UP_CAST (cmptr, struct value_comparator, parent);
  429. value_destroy (&vc->pattern, var_get_width (cmptr->var));
  430. }
  431. static struct comparator *
  432. value_comparator_create (const struct variable *var, const char *target)
  433. {
  434. struct value_comparator *vc = xzalloc (sizeof (*vc));
  435. struct comparator *cmptr = &vc->parent;
  436. cmptr->flags = 0;
  437. cmptr->var = var;
  438. cmptr->compare = value_compare ;
  439. cmptr->destroy = cmptr_value_destroy;
  440. text_to_value (target, var, &vc->pattern);
  441. return cmptr;
  442. }
  443. static struct comparator *
  444. string_comparator_create (const struct variable *var, const char *target,
  445. enum string_cmp_flags flags)
  446. {
  447. struct string_comparator *ssc = xzalloc (sizeof (*ssc));
  448. struct comparator *cmptr = &ssc->parent;
  449. cmptr->flags = flags;
  450. cmptr->var = var;
  451. if ( flags & STR_CMP_LABELS)
  452. cmptr->compare = string_label_compare;
  453. else
  454. cmptr->compare = string_value_compare;
  455. ssc->pattern = target;
  456. return cmptr;
  457. }
  458. static struct comparator *
  459. regexp_comparator_create (const struct variable *var, const char *target,
  460. enum string_cmp_flags flags)
  461. {
  462. int code;
  463. struct regexp_comparator *rec = xzalloc (sizeof (*rec));
  464. struct comparator *cmptr = &rec->parent;
  465. cmptr->flags = flags;
  466. cmptr->var = var;
  467. cmptr->compare = (flags & STR_CMP_LABELS)
  468. ? regexp_label_compare : regexp_value_compare ;
  469. cmptr->destroy = regexp_destroy;
  470. code = regcomp (&rec->re, target, 0);
  471. if ( code != 0 )
  472. {
  473. char *errbuf = NULL;
  474. size_t errbuf_size = regerror (code, &rec->re, errbuf, 0);
  475. errbuf = xmalloc (errbuf_size);
  476. regerror (code, &rec->re, errbuf, errbuf_size);
  477. msg (ME, _("Bad regular expression: %s"), errbuf);
  478. free ( cmptr);
  479. free (errbuf);
  480. return NULL;
  481. }
  482. return cmptr;
  483. }
  484. /* Compare V against CMPTR's reference */
  485. static bool
  486. comparator_compare (const struct comparator *cmptr,
  487. const union value *v)
  488. {
  489. return cmptr->compare (cmptr, v);
  490. }
  491. /* Destroy CMPTR */
  492. static void
  493. comparator_destroy (struct comparator *cmptr)
  494. {
  495. if ( ! cmptr )
  496. return ;
  497. if ( cmptr->destroy )
  498. cmptr->destroy (cmptr);
  499. free (cmptr);
  500. }
  501. static struct comparator *
  502. comparator_factory (const struct variable *var, const char *str,
  503. enum string_cmp_flags flags)
  504. {
  505. if ( flags & STR_CMP_REGEXP )
  506. return regexp_comparator_create (var, str, flags);
  507. if ( flags & (STR_CMP_SUBSTR | STR_CMP_LABELS) )
  508. return string_comparator_create (var, str, flags);
  509. return value_comparator_create (var, str);
  510. }
  511. /* Find the row and column specified by the dialog FD, starting at CURRENT_ROW.
  512. After the function returns, *ROW contains the row and *COLUMN the column.
  513. If no such case is found, then *ROW will be set to -1
  514. */
  515. static void
  516. find_value (const struct find_dialog *fd, casenumber current_row,
  517. casenumber *row, int *column)
  518. {
  519. int width;
  520. const struct variable *var;
  521. const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
  522. const char *target_string = gtk_entry_get_text (GTK_ENTRY (fd->value_entry));
  523. enum string_cmp_flags flags = 0;
  524. g_assert (current_row >= 0);
  525. var = dict_lookup_var (fd->dict->dict, var_name);
  526. if ( ! var )
  527. return ;
  528. width = var_get_width (var);
  529. *column = var_get_dict_index (var);
  530. *row = -1;
  531. if ( gtk_toggle_button_get_active
  532. (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox)))
  533. flags |= STR_CMP_SUBSTR;
  534. if ( gtk_toggle_button_get_active
  535. (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox)))
  536. flags |= STR_CMP_REGEXP;
  537. if ( gtk_toggle_button_get_active
  538. (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox)))
  539. flags |= STR_CMP_LABELS;
  540. {
  541. union value val;
  542. casenumber i;
  543. const struct casenum_iterator *ip = get_iteration_params (fd);
  544. struct comparator *cmptr =
  545. comparator_factory (var, target_string, flags);
  546. value_init (&val, width);
  547. if ( ! cmptr)
  548. goto finish;
  549. for (i = ip->start (current_row, fd->data);
  550. i != ip->end (current_row, fd->data);
  551. ip->next (&i, fd->data))
  552. {
  553. datasheet_get_value (fd->data, i, var_get_case_index (var), &val);
  554. if ( comparator_compare (cmptr, &val))
  555. {
  556. *row = i;
  557. break;
  558. }
  559. }
  560. finish:
  561. comparator_destroy (cmptr);
  562. value_destroy (&val, width);
  563. }
  564. }