1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2002 CodeFactory AB
4 * Copyright (C) 2002 Mikael Hallendal <micke@imendio.com>
5 * Copyright (C) 2008 Imendio AB
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public
18 * License along with this program; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 */
22
23 #include "config.h"
24 #include <gtk/gtk.h>
25 #include <string.h>
26
27 #include "dh-link.h"
28 #include "dh-book.h"
29 #include "dh-keyword-model.h"
30
31 struct _DhKeywordModelPriv {
32 DhBookManager *book_manager;
33
34 GList *keyword_words;
35 gint keyword_words_length;
36
37 gint stamp;
38 };
39
40 #define G_LIST(x) ((GList *) x)
41 #define MAX_HITS 100
42
43 static void dh_keyword_model_init (DhKeywordModel *list_store);
44 static void dh_keyword_model_class_init (DhKeywordModelClass *class);
45 static void dh_keyword_model_tree_model_init (GtkTreeModelIface *iface);
46
47 G_DEFINE_TYPE_WITH_CODE (DhKeywordModel, dh_keyword_model, G_TYPE_OBJECT,
48 G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
49 dh_keyword_model_tree_model_init));
50
51 static void
keyword_model_dispose(GObject * object)52 keyword_model_dispose (GObject *object)
53 {
54 DhKeywordModel *model = DH_KEYWORD_MODEL (object);
55 DhKeywordModelPriv *priv = model->priv;
56
57 if (priv->book_manager) {
58 g_object_unref (priv->book_manager);
59 priv->book_manager = NULL;
60 }
61
62 G_OBJECT_CLASS (dh_keyword_model_parent_class)->dispose (object);
63 }
64
65 static void
keyword_model_finalize(GObject * object)66 keyword_model_finalize (GObject *object)
67 {
68 DhKeywordModel *model = DH_KEYWORD_MODEL (object);
69 DhKeywordModelPriv *priv = model->priv;
70
71 g_list_free (priv->keyword_words);
72
73 g_free (model->priv);
74
75 G_OBJECT_CLASS (dh_keyword_model_parent_class)->finalize (object);
76 }
77
78 static void
dh_keyword_model_class_init(DhKeywordModelClass * klass)79 dh_keyword_model_class_init (DhKeywordModelClass *klass)
80 {
81 GObjectClass *object_class = G_OBJECT_CLASS (klass);;
82
83 object_class->finalize = keyword_model_finalize;
84 object_class->dispose = keyword_model_dispose;
85 }
86
87 static void
dh_keyword_model_init(DhKeywordModel * model)88 dh_keyword_model_init (DhKeywordModel *model)
89 {
90 DhKeywordModelPriv *priv;
91
92 priv = g_new0 (DhKeywordModelPriv, 1);
93 model->priv = priv;
94
95 do {
96 priv->stamp = g_random_int ();
97 } while (priv->stamp == 0);
98 }
99
100 static GtkTreeModelFlags
keyword_model_get_flags(GtkTreeModel * tree_model)101 keyword_model_get_flags (GtkTreeModel *tree_model)
102 {
103 return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
104 }
105
106 static gint
keyword_model_get_n_columns(GtkTreeModel * tree_model)107 keyword_model_get_n_columns (GtkTreeModel *tree_model)
108 {
109 return DH_KEYWORD_MODEL_NUM_COLS;
110 }
111
112 static GType
keyword_model_get_column_type(GtkTreeModel * tree_model,gint column)113 keyword_model_get_column_type (GtkTreeModel *tree_model,
114 gint column)
115 {
116 switch (column) {
117 case DH_KEYWORD_MODEL_COL_NAME:
118 return G_TYPE_STRING;
119 break;
120 case DH_KEYWORD_MODEL_COL_LINK:
121 return G_TYPE_POINTER;
122 default:
123 return G_TYPE_INVALID;
124 }
125 }
126
127 static gboolean
keyword_model_get_iter(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreePath * path)128 keyword_model_get_iter (GtkTreeModel *tree_model,
129 GtkTreeIter *iter,
130 GtkTreePath *path)
131 {
132 DhKeywordModel *model;
133 DhKeywordModelPriv *priv;
134 GList *node;
135 const gint *indices;
136
137 model = DH_KEYWORD_MODEL (tree_model);
138 priv = model->priv;
139
140 indices = gtk_tree_path_get_indices (path);
141
142 if (indices == NULL) {
143 return FALSE;
144 }
145
146 if (indices[0] >= priv->keyword_words_length) {
147 return FALSE;
148 }
149
150 node = g_list_nth (priv->keyword_words, indices[0]);
151
152 iter->stamp = priv->stamp;
153 iter->user_data = node;
154
155 return TRUE;
156 }
157
158 static GtkTreePath *
keyword_model_get_path(GtkTreeModel * tree_model,GtkTreeIter * iter)159 keyword_model_get_path (GtkTreeModel *tree_model,
160 GtkTreeIter *iter)
161 {
162 DhKeywordModel *model = DH_KEYWORD_MODEL (tree_model);
163 DhKeywordModelPriv *priv;
164 GtkTreePath *path;
165 gint i = 0;
166
167 g_return_val_if_fail (iter->stamp == model->priv->stamp, NULL);
168
169 priv = model->priv;
170
171 i = g_list_position (priv->keyword_words, iter->user_data);
172 if (i < 0) {
173 return NULL;
174 }
175
176 path = gtk_tree_path_new ();
177 gtk_tree_path_append_index (path, i);
178
179 return path;
180 }
181
182 static void
keyword_model_get_value(GtkTreeModel * tree_model,GtkTreeIter * iter,gint column,GValue * value)183 keyword_model_get_value (GtkTreeModel *tree_model,
184 GtkTreeIter *iter,
185 gint column,
186 GValue *value)
187 {
188 DhLink *link;
189
190 link = G_LIST (iter->user_data)->data;
191
192 switch (column) {
193 case DH_KEYWORD_MODEL_COL_NAME:
194 g_value_init (value, G_TYPE_STRING);
195 g_value_set_string (value, dh_link_get_name (link));
196 break;
197 case DH_KEYWORD_MODEL_COL_LINK:
198 g_value_init (value, G_TYPE_POINTER);
199 g_value_set_pointer (value, link);
200 break;
201 default:
202 g_warning ("Bad column %d requested", column);
203 }
204 }
205
206 static gboolean
keyword_model_iter_next(GtkTreeModel * tree_model,GtkTreeIter * iter)207 keyword_model_iter_next (GtkTreeModel *tree_model,
208 GtkTreeIter *iter)
209 {
210 DhKeywordModel *model = DH_KEYWORD_MODEL (tree_model);
211
212 g_return_val_if_fail (model->priv->stamp == iter->stamp, FALSE);
213
214 iter->user_data = G_LIST (iter->user_data)->next;
215
216 return (iter->user_data != NULL);
217 }
218
219 static gboolean
keyword_model_iter_children(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent)220 keyword_model_iter_children (GtkTreeModel *tree_model,
221 GtkTreeIter *iter,
222 GtkTreeIter *parent)
223 {
224 DhKeywordModelPriv *priv;
225
226 priv = DH_KEYWORD_MODEL (tree_model)->priv;
227
228 /* This is a list, nodes have no children. */
229 if (parent) {
230 return FALSE;
231 }
232
233 /* But if parent == NULL we return the list itself as children of
234 * the "root".
235 */
236 if (priv->keyword_words) {
237 iter->stamp = priv->stamp;
238 iter->user_data = priv->keyword_words;
239 return TRUE;
240 }
241
242 return FALSE;
243 }
244
245 static gboolean
keyword_model_iter_has_child(GtkTreeModel * tree_model,GtkTreeIter * iter)246 keyword_model_iter_has_child (GtkTreeModel *tree_model,
247 GtkTreeIter *iter)
248 {
249 return FALSE;
250 }
251
252 static gint
keyword_model_iter_n_children(GtkTreeModel * tree_model,GtkTreeIter * iter)253 keyword_model_iter_n_children (GtkTreeModel *tree_model,
254 GtkTreeIter *iter)
255 {
256 DhKeywordModelPriv *priv;
257
258 priv = DH_KEYWORD_MODEL (tree_model)->priv;
259
260 if (iter == NULL) {
261 return priv->keyword_words_length;
262 }
263
264 g_return_val_if_fail (priv->stamp == iter->stamp, -1);
265
266 return 0;
267 }
268
269 static gboolean
keyword_model_iter_nth_child(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * parent,gint n)270 keyword_model_iter_nth_child (GtkTreeModel *tree_model,
271 GtkTreeIter *iter,
272 GtkTreeIter *parent,
273 gint n)
274 {
275 DhKeywordModelPriv *priv;
276 GList *child;
277
278 priv = DH_KEYWORD_MODEL (tree_model)->priv;
279
280 if (parent) {
281 return FALSE;
282 }
283
284 child = g_list_nth (priv->keyword_words, n);
285
286 if (child) {
287 iter->stamp = priv->stamp;
288 iter->user_data = child;
289 return TRUE;
290 }
291
292 return FALSE;
293 }
294
295 static gboolean
keyword_model_iter_parent(GtkTreeModel * tree_model,GtkTreeIter * iter,GtkTreeIter * child)296 keyword_model_iter_parent (GtkTreeModel *tree_model,
297 GtkTreeIter *iter,
298 GtkTreeIter *child)
299 {
300 return FALSE;
301 }
302
303 static void
dh_keyword_model_tree_model_init(GtkTreeModelIface * iface)304 dh_keyword_model_tree_model_init (GtkTreeModelIface *iface)
305 {
306 iface->get_flags = keyword_model_get_flags;
307 iface->get_n_columns = keyword_model_get_n_columns;
308 iface->get_column_type = keyword_model_get_column_type;
309 iface->get_iter = keyword_model_get_iter;
310 iface->get_path = keyword_model_get_path;
311 iface->get_value = keyword_model_get_value;
312 iface->iter_next = keyword_model_iter_next;
313 iface->iter_children = keyword_model_iter_children;
314 iface->iter_has_child = keyword_model_iter_has_child;
315 iface->iter_n_children = keyword_model_iter_n_children;
316 iface->iter_nth_child = keyword_model_iter_nth_child;
317 iface->iter_parent = keyword_model_iter_parent;
318 }
319
320 DhKeywordModel *
dh_keyword_model_new(void)321 dh_keyword_model_new (void)
322 {
323 DhKeywordModel *model;
324
325 model = g_object_new (DH_TYPE_KEYWORD_MODEL, NULL);
326
327 return model;
328 }
329
330 void
dh_keyword_model_set_words(DhKeywordModel * model,DhBookManager * book_manager)331 dh_keyword_model_set_words (DhKeywordModel *model,
332 DhBookManager *book_manager)
333 {
334 g_return_if_fail (DH_IS_KEYWORD_MODEL (model));
335
336 model->priv->book_manager = g_object_ref (book_manager);
337 }
338
339 static GList *
keyword_model_search(DhKeywordModel * model,const gchar * string,gchar ** stringv,const gchar * book_id,gboolean case_sensitive,DhLink ** exact_link)340 keyword_model_search (DhKeywordModel *model,
341 const gchar *string,
342 gchar **stringv,
343 const gchar *book_id,
344 gboolean case_sensitive,
345 DhLink **exact_link)
346 {
347 DhKeywordModelPriv *priv;
348 GList *new_list = NULL, *b;
349 gint hits = 0;
350 gchar *page_id = NULL;
351 gchar *page_filename_prefix = NULL;
352
353 priv = model->priv;
354
355 /* The search string may be prefixed by a page:foobar qualifier, it
356 * will be matched against the filenames of the hits to limit the
357 * search to pages whose filename is prefixed by "foobar.
358 */
359 if (stringv && g_str_has_prefix(stringv[0], "page:")) {
360 page_id = stringv[0] + 5;
361 page_filename_prefix = g_strdup_printf("%s.", page_id);
362 stringv++;
363 }
364
365 for (b = dh_book_manager_get_books (priv->book_manager);
366 b && hits < MAX_HITS;
367 b = g_list_next (b)) {
368 DhBook *book;
369 GList *l;
370
371 book = DH_BOOK (b->data);
372
373 for (l = dh_book_get_keywords (book);
374 l && hits < MAX_HITS;
375 l = g_list_next (l)) {
376 DhLink *link;
377 gboolean found;
378 gchar *name;
379
380 link = l->data;
381 found = FALSE;
382
383 if (book_id &&
384 dh_link_get_book_id (link) &&
385 strcmp (dh_link_get_book_id (link), book_id) != 0) {
386 continue;
387 }
388
389 if (page_id &&
390 (dh_link_get_link_type (link) != DH_LINK_TYPE_PAGE &&
391 !g_str_has_prefix (dh_link_get_file_name (link), page_filename_prefix))) {
392 continue;
393 }
394
395 if (!case_sensitive) {
396 name = g_ascii_strdown (dh_link_get_name (link), -1);
397 } else {
398 name = g_strdup (dh_link_get_name (link));
399 }
400
401 if (!found) {
402 gint i;
403
404 if (stringv[0] == NULL) {
405 /* means only a page was specified, no keyword */
406 if (g_strrstr (dh_link_get_name(link), page_id))
407 found = TRUE;
408 } else {
409 found = TRUE;
410 for (i = 0; stringv[i] != NULL; i++) {
411 if (!g_strrstr (name, stringv[i])) {
412 found = FALSE;
413 break;
414 }
415 }
416 }
417 }
418
419 g_free (name);
420
421 if (found) {
422 /* Include in the new list. */
423 new_list = g_list_prepend (new_list, link);
424 hits++;
425
426 if (!*exact_link &&
427 dh_link_get_name (link) && (
428 (dh_link_get_link_type (link) == DH_LINK_TYPE_PAGE &&
429 page_id && strcmp (dh_link_get_name (link), page_id) == 0) ||
430 (strcmp (dh_link_get_name (link), string) == 0))) {
431 *exact_link = link;
432 }
433 }
434 }
435 }
436
437 g_free (page_filename_prefix);
438
439 return g_list_sort (new_list, dh_link_compare);
440 }
441
442 DhLink *
dh_keyword_model_filter(DhKeywordModel * model,const gchar * string,const gchar * book_id)443 dh_keyword_model_filter (DhKeywordModel *model,
444 const gchar *string,
445 const gchar *book_id)
446 {
447 DhKeywordModelPriv *priv;
448 GList *new_list = NULL;
449 gint old_length;
450 DhLink *exact_link = NULL;
451 gint hits;
452 gint i;
453 GtkTreePath *path;
454 GtkTreeIter iter;
455
456 g_return_val_if_fail (DH_IS_KEYWORD_MODEL (model), NULL);
457 g_return_val_if_fail (string != NULL, NULL);
458
459 priv = model->priv;
460
461 /* Do the minimum amount of work: call update on all rows that are
462 * kept and remove the rest.
463 */
464 old_length = priv->keyword_words_length;
465 new_list = NULL;
466 hits = 0;
467
468 if (string[0] != '\0') {
469 gchar **stringv;
470 gboolean case_sensitive;
471
472 stringv = g_strsplit (string, " ", -1);
473
474 case_sensitive = FALSE;
475
476 /* Search for any parameters and position search cursor to
477 * the next element in the search string.
478 */
479 for (i = 0; stringv[i] != NULL; i++) {
480 gchar *lower;
481
482 /* Searches are case sensitive when any uppercase
483 * letter is used in the search terms, matching vim
484 * smartcase behaviour.
485 */
486 lower = g_ascii_strdown (stringv[i], -1);
487 if (strcmp (lower, stringv[i]) != 0) {
488 case_sensitive = TRUE;
489 g_free (lower);
490 break;
491 }
492 g_free (lower);
493 }
494
495 new_list = keyword_model_search (model,
496 string,
497 stringv,
498 book_id,
499 case_sensitive,
500 &exact_link);
501 hits = g_list_length (new_list);
502
503 g_strfreev (stringv);
504 }
505
506 /* Update the list of hits. */
507 g_list_free (priv->keyword_words);
508 priv->keyword_words = new_list;
509 priv->keyword_words_length = hits;
510
511 /* Update model: rows 0 -> hits. */
512 for (i = 0; i < hits; ++i) {
513 path = gtk_tree_path_new ();
514 gtk_tree_path_append_index (path, i);
515 keyword_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
516 gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter);
517 gtk_tree_path_free (path);
518 }
519
520 if (old_length > hits) {
521 /* Update model: remove rows hits -> old_length. */
522 for (i = old_length - 1; i >= hits; i--) {
523 path = gtk_tree_path_new ();
524 gtk_tree_path_append_index (path, i);
525 gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path);
526 gtk_tree_path_free (path);
527 }
528 }
529 else if (old_length < hits) {
530 /* Update model: add rows old_length -> hits. */
531 for (i = old_length; i < hits; i++) {
532 path = gtk_tree_path_new ();
533 gtk_tree_path_append_index (path, i);
534 keyword_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
535 gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter);
536 gtk_tree_path_free (path);
537 }
538 }
539
540 if (hits == 1) {
541 return priv->keyword_words->data;
542 }
543
544 return exact_link;
545 }
546