1 /*
2 * ao_tasks.c - this file is part of Addons, a Geany plugin
3 *
4 * Copyright 2009-2011 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 * $Id$
21 */
22
23
24 #include <string.h>
25 #include <gtk/gtk.h>
26 #include <glib-object.h>
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31 #include <geanyplugin.h>
32
33 #include "addons.h"
34 #include "ao_tasks.h"
35
36 #include <gdk/gdkkeysyms.h>
37
38
39 typedef struct _AoTasksPrivate AoTasksPrivate;
40
41 #define AO_TASKS_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), \
42 AO_TASKS_TYPE, AoTasksPrivate))
43
44 struct _AoTasks
45 {
46 GObject parent;
47 };
48
49 struct _AoTasksClass
50 {
51 GObjectClass parent_class;
52 };
53
54 struct _AoTasksPrivate
55 {
56 gboolean enable_tasks;
57 gboolean active;
58
59 GtkListStore *store;
60 GtkWidget *tree;
61
62 GtkWidget *page;
63 GtkWidget *popup_menu;
64 GtkWidget *popup_menu_delete_button;
65
66 gchar **tokens;
67
68 gboolean scan_all_documents;
69
70 GHashTable *selected_tasks;
71 gint selected_task_line;
72 GeanyDocument *selected_task_doc;
73 gboolean ignore_selection_changed;
74 };
75
76 enum
77 {
78 PROP_0,
79 PROP_ENABLE_TASKS,
80 PROP_TOKENS,
81 PROP_SCAN_ALL_DOCUMENTS
82 };
83
84 enum
85 {
86 TLIST_COL_FILENAME,
87 TLIST_COL_DISPLAY_FILENAME,
88 TLIST_COL_LINE,
89 TLIST_COL_TOKEN,
90 TLIST_COL_NAME,
91 TLIST_COL_TOOLTIP,
92 TLIST_COL_MAX
93 };
94
95 static void ao_tasks_finalize (GObject *object);
96 static void ao_tasks_show (AoTasks *t);
97 static void ao_tasks_hide (AoTasks *t);
98
G_DEFINE_TYPE(AoTasks,ao_tasks,G_TYPE_OBJECT)99 G_DEFINE_TYPE(AoTasks, ao_tasks, G_TYPE_OBJECT)
100
101
102 static void ao_tasks_set_property(GObject *object, guint prop_id,
103 const GValue *value, GParamSpec *pspec)
104 {
105 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(object);
106
107 switch (prop_id)
108 {
109 case PROP_ENABLE_TASKS:
110 {
111 gboolean new_val = g_value_get_boolean(value);
112 if (new_val && ! priv->enable_tasks)
113 ao_tasks_show(AO_TASKS(object));
114 if (! new_val && priv->enable_tasks)
115 ao_tasks_hide(AO_TASKS(object));
116
117 priv->enable_tasks = new_val;
118 if (priv->enable_tasks && main_is_realized() && ! priv->active)
119 ao_tasks_set_active(AO_TASKS(object));
120 break;
121 }
122 case PROP_SCAN_ALL_DOCUMENTS:
123 {
124 priv->scan_all_documents = g_value_get_boolean(value);
125 break;
126 }
127 case PROP_TOKENS:
128 {
129 const gchar *t = g_value_get_string(value);
130 if (EMPTY(t))
131 t = "TODO;FIXME"; /* fallback */
132 g_strfreev(priv->tokens);
133 priv->tokens = g_strsplit(t, ";", -1);
134 ao_tasks_update(AO_TASKS(object), NULL);
135 break;
136 }
137 default:
138 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
139 break;
140 }
141 }
142
143
ao_tasks_class_init(AoTasksClass * klass)144 static void ao_tasks_class_init(AoTasksClass *klass)
145 {
146 GObjectClass *g_object_class;
147
148 g_object_class = G_OBJECT_CLASS(klass);
149 g_object_class->finalize = ao_tasks_finalize;
150 g_object_class->set_property = ao_tasks_set_property;
151 g_type_class_add_private(klass, sizeof(AoTasksPrivate));
152
153 g_object_class_install_property(g_object_class,
154 PROP_SCAN_ALL_DOCUMENTS,
155 g_param_spec_boolean(
156 "scan-all-documents",
157 "scan-all-documents",
158 "Whether to show tasks for all open documents",
159 TRUE,
160 G_PARAM_WRITABLE));
161
162 g_object_class_install_property(g_object_class,
163 PROP_ENABLE_TASKS,
164 g_param_spec_boolean(
165 "enable-tasks",
166 "enable-tasks",
167 "Whether to show list of defined tasks",
168 TRUE,
169 G_PARAM_WRITABLE));
170
171 g_object_class_install_property(g_object_class,
172 PROP_TOKENS,
173 g_param_spec_string(
174 "tokens",
175 "tokens",
176 "The tokens to scan documents for",
177 NULL,
178 G_PARAM_WRITABLE));
179 }
180
181
ao_tasks_finalize(GObject * object)182 static void ao_tasks_finalize(GObject *object)
183 {
184 AoTasksPrivate *priv;
185
186 g_return_if_fail(object != NULL);
187 g_return_if_fail(IS_AO_TASKS(object));
188
189 priv = AO_TASKS_GET_PRIVATE(object);
190 g_strfreev(priv->tokens);
191
192 ao_tasks_hide(AO_TASKS(object));
193
194 if (priv->selected_tasks != NULL)
195 g_hash_table_destroy(priv->selected_tasks);
196
197 G_OBJECT_CLASS(ao_tasks_parent_class)->finalize(object);
198 }
199
200
ao_tasks_selection_changed_cb(gpointer t)201 static gboolean ao_tasks_selection_changed_cb(gpointer t)
202 {
203 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
204 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->tree));
205 GtkTreeIter iter;
206 GtkTreeModel *model;
207
208 if (gtk_tree_selection_get_selected(selection, &model, &iter))
209 {
210 gint line;
211 gchar *filename, *locale_filename;
212 GeanyDocument *doc;
213
214 gtk_tree_model_get(model, &iter,
215 TLIST_COL_LINE, &line,
216 TLIST_COL_FILENAME, &filename,
217 -1);
218 locale_filename = utils_get_locale_from_utf8(filename);
219 doc = document_open_file(locale_filename, FALSE, NULL, NULL);
220 if (doc != NULL)
221 {
222 sci_goto_line(doc->editor->sci, line - 1, TRUE);
223 gtk_widget_grab_focus(GTK_WIDGET(doc->editor->sci));
224 }
225
226 /* remember the selected line for this document to restore the selection after an update */
227 if (priv->scan_all_documents)
228 {
229 priv->selected_task_doc = doc;
230 priv->selected_task_line = line;
231 }
232 else
233 g_hash_table_insert(priv->selected_tasks, doc, GINT_TO_POINTER(line));
234
235 g_free(filename);
236 g_free(locale_filename);
237 }
238 return FALSE;
239 }
240
241
ao_tasks_button_press_cb(GtkWidget * widget,GdkEventButton * event,gpointer data)242 static gboolean ao_tasks_button_press_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
243 {
244 if (event->button == 1)
245 { /* allow reclicking of a treeview item */
246 g_idle_add(ao_tasks_selection_changed_cb, data);
247 }
248 else if (event->button == 3)
249 {
250 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(data);
251 gboolean has_selection = gtk_tree_selection_get_selected(
252 gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->tree)), NULL, NULL);
253 gtk_widget_set_sensitive(priv->popup_menu_delete_button, has_selection);
254
255 gtk_menu_popup(GTK_MENU(priv->popup_menu), NULL, NULL, NULL, NULL,
256 event->button, event->time);
257 /* don't return TRUE here, otherwise the selection won't be changed */
258 }
259 return FALSE;
260 }
261
262
ao_tasks_key_press_cb(GtkWidget * widget,GdkEventKey * event,gpointer data)263 static gboolean ao_tasks_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
264 {
265 if (event->keyval == GDK_Return ||
266 event->keyval == GDK_ISO_Enter ||
267 event->keyval == GDK_KP_Enter ||
268 event->keyval == GDK_space)
269 {
270 g_idle_add(ao_tasks_selection_changed_cb, data);
271 }
272 if ((event->keyval == GDK_F10 && event->state & GDK_SHIFT_MASK) || event->keyval == GDK_Menu)
273 {
274 GdkEventButton button_event;
275
276 button_event.time = event->time;
277 button_event.button = 3;
278
279 ao_tasks_button_press_cb(widget, &button_event, data);
280 return TRUE;
281 }
282 return FALSE;
283 }
284
285
ao_tasks_hide(AoTasks * t)286 static void ao_tasks_hide(AoTasks *t)
287 {
288 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
289
290 if (priv->page)
291 {
292 gtk_widget_destroy(priv->page);
293 priv->page = NULL;
294 }
295 if (priv->popup_menu)
296 {
297 g_object_unref(priv->popup_menu);
298 priv->popup_menu = NULL;
299 }
300 }
301
302
popup_delete_item_click_cb(GtkWidget * button,AoTasks * t)303 static void popup_delete_item_click_cb(GtkWidget *button, AoTasks *t)
304 {
305 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
306 GtkTreeIter iter;
307 GtkTreeModel *model;
308 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->tree));
309 gchar *filename;
310 gint line, start, end;
311 GeanyDocument *doc;
312
313 if (! gtk_tree_selection_get_selected(selection, &model, &iter))
314 return;
315
316 /* get task information */
317 gtk_tree_model_get(model, &iter,
318 TLIST_COL_FILENAME, &filename,
319 TLIST_COL_LINE, &line,
320 -1);
321
322 /* find the document of this task item */
323 doc = document_find_by_filename(filename);
324 g_free(filename);
325
326 if (doc == NULL)
327 return;
328
329 line--; /* display line vs. document line */
330
331 start = sci_get_position_from_line(doc->editor->sci, line);
332 end = sci_get_position_from_line(doc->editor->sci, line + 1);
333 if (end == -1)
334 end = sci_get_length(doc->editor->sci);
335
336 /* create a selection over this line and then delete it */
337 scintilla_send_message(doc->editor->sci, SCI_SETSEL, start, end);
338 scintilla_send_message(doc->editor->sci, SCI_CLEAR, 0, 0);
339 /* update the task list */
340 ao_tasks_update(t, doc);
341 }
342
343
popup_update_item_click_cb(GtkWidget * button,AoTasks * t)344 static void popup_update_item_click_cb(GtkWidget *button, AoTasks *t)
345 {
346 ao_tasks_update(t, NULL);
347 }
348
349
popup_hide_item_click_cb(GtkWidget * button,AoTasks * t)350 static void popup_hide_item_click_cb(GtkWidget *button, AoTasks *t)
351 {
352 keybindings_send_command(GEANY_KEY_GROUP_VIEW, GEANY_KEYS_VIEW_MESSAGEWINDOW);
353 }
354
355
create_popup_menu(AoTasks * t)356 static GtkWidget *create_popup_menu(AoTasks *t)
357 {
358 GtkWidget *item, *menu;
359 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
360
361 menu = gtk_menu_new();
362
363 item = gtk_image_menu_item_new_from_stock(GTK_STOCK_DELETE, NULL);
364 priv->popup_menu_delete_button = item;
365 gtk_widget_show(item);
366 gtk_container_add(GTK_CONTAINER(menu), item);
367 g_signal_connect(item, "activate", G_CALLBACK(popup_delete_item_click_cb), t);
368
369 item = gtk_separator_menu_item_new();
370 gtk_widget_show(item);
371 gtk_container_add(GTK_CONTAINER(menu), item);
372
373 item = ui_image_menu_item_new(GTK_STOCK_REFRESH, _("_Update"));
374 gtk_widget_show(item);
375 gtk_container_add(GTK_CONTAINER(menu), item);
376 g_signal_connect(item, "activate", G_CALLBACK(popup_update_item_click_cb), t);
377
378 item = gtk_separator_menu_item_new();
379 gtk_widget_show(item);
380 gtk_container_add(GTK_CONTAINER(menu), item);
381
382 item = gtk_menu_item_new_with_mnemonic(_("_Hide Message Window"));
383 gtk_widget_show(item);
384 gtk_container_add(GTK_CONTAINER(menu), item);
385 g_signal_connect(item, "activate", G_CALLBACK(popup_hide_item_click_cb), t);
386
387 return menu;
388 }
389
390
ao_tasks_show(AoTasks * t)391 static void ao_tasks_show(AoTasks *t)
392 {
393 GtkCellRenderer *text_renderer;
394 GtkTreeViewColumn *column;
395 GtkTreeSelection *selection;
396 GtkTreeSortable *sortable;
397 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
398
399 priv->store = gtk_list_store_new(TLIST_COL_MAX,
400 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
401 priv->tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(priv->store));
402
403 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->tree));
404 gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
405
406 /* selection handling */
407 g_signal_connect(priv->tree, "button-press-event", G_CALLBACK(ao_tasks_button_press_cb), t);
408 g_signal_connect(priv->tree, "key-press-event", G_CALLBACK(ao_tasks_key_press_cb), t);
409
410 text_renderer = gtk_cell_renderer_text_new();
411 column = gtk_tree_view_column_new();
412 gtk_tree_view_column_set_title(column, _("File"));
413 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
414 gtk_tree_view_column_set_attributes(column, text_renderer, "text",
415 TLIST_COL_DISPLAY_FILENAME, NULL);
416 gtk_tree_view_column_set_sort_indicator(column, FALSE);
417 gtk_tree_view_column_set_sort_column_id(column, TLIST_COL_DISPLAY_FILENAME);
418 gtk_tree_view_column_set_resizable(column, TRUE);
419 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->tree), column);
420
421 text_renderer = gtk_cell_renderer_text_new();
422 column = gtk_tree_view_column_new();
423 gtk_tree_view_column_set_title(column, _("Line"));
424 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
425 gtk_tree_view_column_set_attributes(column, text_renderer, "text", TLIST_COL_LINE, NULL);
426 gtk_tree_view_column_set_sort_indicator(column, FALSE);
427 gtk_tree_view_column_set_sort_column_id(column, TLIST_COL_LINE);
428 gtk_tree_view_column_set_resizable(column, TRUE);
429 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->tree), column);
430
431 text_renderer = gtk_cell_renderer_text_new();
432 g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
433 column = gtk_tree_view_column_new();
434 gtk_tree_view_column_set_title(column, _("Type"));
435 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
436 gtk_tree_view_column_set_attributes(column, text_renderer, "text", TLIST_COL_TOKEN, NULL);
437 gtk_tree_view_column_set_sort_indicator(column, FALSE);
438 gtk_tree_view_column_set_sort_column_id(column, TLIST_COL_TOKEN);
439 gtk_tree_view_column_set_resizable(column, TRUE);
440 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->tree), column);
441
442 text_renderer = gtk_cell_renderer_text_new();
443 g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
444 column = gtk_tree_view_column_new();
445 gtk_tree_view_column_set_title(column, _("Task"));
446 gtk_tree_view_column_pack_start(column, text_renderer, TRUE);
447 gtk_tree_view_column_set_attributes(column, text_renderer, "text", TLIST_COL_NAME, NULL);
448 gtk_tree_view_column_set_sort_indicator(column, FALSE);
449 gtk_tree_view_column_set_sort_column_id(column, TLIST_COL_NAME);
450 gtk_tree_view_column_set_resizable(column, TRUE);
451 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->tree), column);
452
453 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(priv->tree), TRUE);
454 gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(priv->tree), TRUE);
455 gtk_tree_view_set_search_column(GTK_TREE_VIEW(priv->tree), TLIST_COL_DISPLAY_FILENAME);
456
457 /* sorting */
458 sortable = GTK_TREE_SORTABLE(GTK_TREE_MODEL(priv->store));
459 gtk_tree_sortable_set_sort_column_id(sortable, TLIST_COL_DISPLAY_FILENAME, GTK_SORT_ASCENDING);
460
461 ui_widget_modify_font_from_string(priv->tree, geany->interface_prefs->tagbar_font);
462
463 /* GTK 2.12 tooltips */
464 if (gtk_check_version(2, 12, 0) == NULL)
465 g_object_set(priv->tree, "has-tooltip", TRUE, "tooltip-column", TLIST_COL_TOOLTIP, NULL);
466
467 /* scrolled window */
468 priv->page = gtk_scrolled_window_new(NULL, NULL);
469 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(priv->page),
470 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
471
472 gtk_container_add(GTK_CONTAINER(priv->page), priv->tree);
473
474 gtk_widget_show_all(priv->page);
475 gtk_notebook_append_page(
476 GTK_NOTEBOOK(ui_lookup_widget(geany->main_widgets->window, "notebook_info")),
477 priv->page,
478 gtk_label_new(_("Tasks")));
479
480 priv->popup_menu = create_popup_menu(t);
481 g_object_ref_sink(priv->popup_menu);
482 }
483
484
ao_tasks_activate(AoTasks * t)485 void ao_tasks_activate(AoTasks *t)
486 {
487 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
488
489 if (priv->enable_tasks)
490 {
491 GtkNotebook *notebook = GTK_NOTEBOOK(geany->main_widgets->message_window_notebook);
492 gint page_number = gtk_notebook_page_num(notebook, priv->page);
493
494 gtk_notebook_set_current_page(notebook, page_number);
495 gtk_widget_grab_focus(priv->tree);
496 }
497 }
498
499
ao_tasks_remove(AoTasks * t,GeanyDocument * cur_doc)500 void ao_tasks_remove(AoTasks *t, GeanyDocument *cur_doc)
501 {
502 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
503 GtkTreeModel *model = GTK_TREE_MODEL(priv->store);
504 GtkTreeIter iter;
505 gchar *filename;
506
507 if (! priv->active || ! priv->enable_tasks)
508 return;
509
510 if (gtk_tree_model_get_iter_first(model, &iter))
511 {
512 gboolean has_next;
513
514 do
515 {
516 gtk_tree_model_get(model, &iter, TLIST_COL_FILENAME, &filename, -1);
517
518 if (utils_str_equal(filename, cur_doc->file_name))
519 { /* gtk_list_store_remove() manages the iter and set it to the next row */
520 has_next = gtk_list_store_remove(priv->store, &iter);
521 }
522 else
523 { /* if we didn't delete the row, we need to manage the iter manually */
524 has_next = gtk_tree_model_iter_next(model, &iter);
525 }
526 g_free(filename);
527 }
528 while (has_next);
529 }
530 }
531
532
create_task(AoTasks * t,GeanyDocument * doc,gint line,const gchar * token,const gchar * line_buf,const gchar * task_start,const gchar * display_name)533 static void create_task(AoTasks *t, GeanyDocument *doc, gint line, const gchar *token,
534 const gchar *line_buf, const gchar *task_start, const gchar *display_name)
535 {
536 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
537 gchar *context, *tooltip;
538
539 /* retrieve the following line and use it for the tooltip */
540 context = g_strstrip(sci_get_line(doc->editor->sci, line + 1));
541 setptr(context, g_strconcat(
542 _("Context:"), "\n", line_buf, "\n", context, NULL));
543 tooltip = g_markup_escape_text(context, -1);
544
545 /* add the task into the list */
546 gtk_list_store_insert_with_values(priv->store, NULL, -1,
547 TLIST_COL_FILENAME, DOC_FILENAME(doc),
548 TLIST_COL_DISPLAY_FILENAME, display_name,
549 TLIST_COL_LINE, line + 1,
550 TLIST_COL_TOKEN, token,
551 TLIST_COL_NAME, task_start,
552 TLIST_COL_TOOLTIP, tooltip,
553 -1);
554 g_free(context);
555 g_free(tooltip);
556 }
557
558
update_tasks_for_doc(AoTasks * t,GeanyDocument * doc)559 static void update_tasks_for_doc(AoTasks *t, GeanyDocument *doc)
560 {
561 gint lexer, lines, line, last_pos = 0, style;
562 gchar *line_buf, *display_name, *task_start, *closing_comment = NULL;
563 gchar **token;
564 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
565
566 if (doc->is_valid)
567 {
568 display_name = document_get_basename_for_display(doc, -1);
569 lexer = sci_get_lexer(doc->editor->sci);
570 lines = sci_get_line_count(doc->editor->sci);
571 for (line = 0; line < lines; line++)
572 {
573 line_buf = sci_get_line(doc->editor->sci, line);
574 for (token = priv->tokens; *token != NULL; ++token)
575 {
576 if (EMPTY(*token))
577 continue;
578 if ((task_start = strstr(line_buf, *token)) == NULL)
579 continue;
580 style = sci_get_style_at(doc->editor->sci, last_pos + (task_start - line_buf));
581 if (!highlighting_is_comment_style(lexer, style))
582 continue;
583
584 /* skip the token and additional whitespace */
585 task_start = strstr(g_strstrip(line_buf), *token) + strlen(*token);
586 while (*task_start == ' ' || *task_start == ':')
587 task_start++;
588 /* reset task_start in case there is no text following */
589 if (EMPTY(task_start))
590 task_start = line_buf;
591 else if ((EMPTY(doc->file_type->comment_single) ||
592 strstr(line_buf, doc->file_type->comment_single) == NULL) &&
593 !EMPTY(doc->file_type->comment_close) &&
594 (closing_comment = strstr(task_start, doc->file_type->comment_close)) != NULL)
595 *closing_comment = '\0';
596 /* create the task */
597 create_task(t, doc, line, *token, line_buf, task_start, display_name);
598 /* if we found a token, continue on next line */
599 break;
600 }
601 g_free(line_buf);
602 last_pos = sci_get_line_end_position(doc->editor->sci, line) + 1;
603 }
604 g_free(display_name);
605 }
606 }
607
608
ao_tasks_update_single(AoTasks * t,GeanyDocument * cur_doc)609 void ao_tasks_update_single(AoTasks *t, GeanyDocument *cur_doc)
610 {
611 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
612
613 if (! priv->active || ! priv->enable_tasks)
614 return;
615
616 if (! priv->scan_all_documents)
617 {
618 /* update */
619 gtk_list_store_clear(priv->store);
620 ao_tasks_update(t, cur_doc);
621 }
622 }
623
624
ao_tasks_select_task(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)625 static gboolean ao_tasks_select_task(GtkTreeModel *model, GtkTreePath *path,
626 GtkTreeIter *iter, gpointer data)
627 {
628 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(data);
629 gint line, selected_line;
630 gchar *filename = NULL;
631 const gchar *selected_filename = NULL;
632 gboolean ret = FALSE;
633
634 if (priv->scan_all_documents)
635 {
636 gtk_tree_model_get(model, iter, TLIST_COL_LINE, &line, TLIST_COL_FILENAME, &filename, -1);
637 selected_line = priv->selected_task_line;
638 selected_filename = DOC_FILENAME(priv->selected_task_doc);
639 }
640 else
641 {
642 gtk_tree_model_get(model, iter, TLIST_COL_LINE, &line, -1);
643 selected_line = GPOINTER_TO_INT(g_hash_table_lookup(
644 priv->selected_tasks, priv->selected_task_doc));
645 }
646
647 if (line == selected_line && utils_str_equal(filename, selected_filename))
648 {
649 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->tree));
650
651 gtk_tree_selection_select_iter(selection, iter);
652 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(priv->tree), path, NULL, FALSE, 0.0, 0.0);
653
654 ret = TRUE;
655 }
656 g_free(filename);
657 return ret;
658 }
659
660
ao_tasks_set_active(AoTasks * t)661 void ao_tasks_set_active(AoTasks *t)
662 {
663 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
664
665 if (priv->enable_tasks)
666 {
667 priv->active = TRUE;
668 ao_tasks_update(t, NULL);
669 }
670 }
671
672
ao_tasks_update(AoTasks * t,GeanyDocument * cur_doc)673 void ao_tasks_update(AoTasks *t, GeanyDocument *cur_doc)
674 {
675 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(t);
676
677 if (! priv->active || ! priv->enable_tasks)
678 return;
679
680 if (! priv->scan_all_documents && cur_doc == NULL)
681 {
682 /* clear all */
683 gtk_list_store_clear(priv->store);
684 /* get the current document */
685 cur_doc = document_get_current();
686 }
687
688 if (cur_doc != NULL)
689 {
690 /* TODO handle renaming of files, probably we need a new signal for this */
691 ao_tasks_remove(t, cur_doc);
692 update_tasks_for_doc(t, cur_doc);
693 }
694 else
695 {
696 guint i = 0;
697 /* clear all */
698 gtk_list_store_clear(priv->store);
699 /* iterate over all docs */
700 foreach_document(i)
701 {
702 update_tasks_for_doc(t, documents[i]);
703 }
704 }
705 /* restore selection */
706 priv->ignore_selection_changed = TRUE;
707 if (priv->scan_all_documents && priv->selected_task_doc != NULL)
708 {
709 gtk_tree_model_foreach(GTK_TREE_MODEL(priv->store), ao_tasks_select_task, t);
710 }
711 else if (cur_doc != NULL && g_hash_table_lookup(priv->selected_tasks, cur_doc) != NULL)
712 {
713 priv->selected_task_doc = cur_doc;
714 gtk_tree_model_foreach(GTK_TREE_MODEL(priv->store), ao_tasks_select_task, t);
715 }
716 priv->ignore_selection_changed = FALSE;
717 }
718
719
ao_tasks_init(AoTasks * self)720 static void ao_tasks_init(AoTasks *self)
721 {
722 AoTasksPrivate *priv = AO_TASKS_GET_PRIVATE(self);
723
724 priv->page = NULL;
725 priv->popup_menu = NULL;
726 priv->tokens = NULL;
727 priv->active = FALSE;
728 priv->ignore_selection_changed = FALSE;
729
730 priv->selected_task_line = 0;
731 priv->selected_task_doc = 0;
732 if (priv->scan_all_documents)
733 priv->selected_tasks = NULL;
734 else
735 priv->selected_tasks = g_hash_table_new(g_direct_hash, g_direct_equal);
736 }
737
738
ao_tasks_new(gboolean enable,const gchar * tokens,gboolean scan_all_documents)739 AoTasks *ao_tasks_new(gboolean enable, const gchar *tokens, gboolean scan_all_documents)
740 {
741 return g_object_new(AO_TASKS_TYPE,
742 "scan-all-documents", scan_all_documents,
743 "tokens", tokens,
744 "enable-tasks", enable, NULL);
745 }
746