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