1 /*
2  * Copyright (C) 2009 - 2011 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (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 
21 #include <glib/gi18n-lib.h>
22 #include <string.h>
23 #include <gdk/gdkkeysyms.h>
24 #ifdef HAVE_GTKSOURCEVIEW
25   #ifdef GTK_DISABLE_SINGLE_INCLUDES
26     #undef GTK_DISABLE_SINGLE_INCLUDES
27   #endif
28 
29   #include <gtksourceview/gtksourceview.h>
30   #include <gtksourceview/gtksourcelanguagemanager.h>
31   #include <gtksourceview/gtksourcebuffer.h>
32   #include <gtksourceview/gtksourcestyleschememanager.h>
33   #include <gtksourceview/gtksourcestylescheme.h>
34 #endif
35 #include "query-editor.h"
36 #include <binreloc/gda-binreloc.h>
37 #include "../browser-connection.h"
38 #include "../browser-window.h"
39 #include <libgda-ui/internal/popup-container.h>
40 #include "../support.h"
41 #include "../common/widget-overlay.h"
42 
43 #define QUERY_EDITOR_LANGUAGE_SQL "gda-sql"
44 #define COLOR_ALTER_FACTOR 1.8
45 #define MAX_HISTORY_BATCH_ITEMS 20
46 #define STATES_ARRAY_SIZE 32
47 
48 static void query_editor_history_batch_add_item (QueryEditorHistoryBatch *qib,
49 						 QueryEditorHistoryItem *qih);
50 static void query_editor_history_batch_del_item (QueryEditor *editor, QueryEditorHistoryBatch *qib,
51 						 QueryEditorHistoryItem *qih);
52 
53 typedef void (* CreateTagsFunc) (QueryEditor *editor, const gchar *language);
54 
55 typedef struct {
56 	QueryEditorHistoryBatch *batch; /* ref held here */
57 	QueryEditorHistoryItem *item; /* ref held here */
58 	GtkTextTag *tag; /* ref held here */
59 	GtkTextMark *start_mark; /* ref NOT held here */
60 	GtkTextMark *end_mark; /* ref NOT held here */
61 
62 	gint ref_count;
63 } HistItemData;
64 static HistItemData *hist_item_data_new (void);
65 static HistItemData *hist_item_data_ref (HistItemData *hdata);
66 static void          hist_item_data_unref (HistItemData *hdata);
67 
68 struct _QueryEditorPrivate {
69 	QueryEditorMode mode;
70 	GtkWidget *scrolled_window;
71 	GtkWidget *text;
72 
73 	GtkTextTag *indent_tag;
74 	GArray *states; /* array of strings, pos 0 => oldest state */
75 	gint current_state;
76 	gchar *current_state_text;
77 
78 	/* HISTORY mode */
79 	guint ts_timeout_id;
80 	GSList *batches_list; /* list of QueryEditorHistoryBatch, in reverse order, refs held here */
81 	GHashTable *hash; /* crossed references:
82 			   * QueryEditorHistoryBatch --> HistItemData on @batch
83 			   * QueryEditorHistoryItem --> HistItemData on @item
84 			   * GtkTextTag --> HistItemData on @tag
85 			   *
86 			   * hash table holds references to all HistItemData
87 			   */
88 	QueryEditorHistoryBatch *insert_into_batch; /* hold ref here */
89 	HistItemData *hist_focus; /* ref held here */
90 
91 	/* completion popup */
92 	GtkWidget *completion_popup;
93 	GtkTreeView *completion_treeview;
94 	GtkCellRenderer *completion_renderer;
95 	GtkWidget *completion_sw;
96 
97 	/* tooltip */
98 	GtkWidget *ovl;
99 	GtkWidget *tooltip_widget;
100 };
101 
102 static void query_editor_class_init (QueryEditorClass *klass);
103 static void query_editor_init       (QueryEditor *editor, QueryEditorClass *klass);
104 static void query_editor_finalize   (GObject *object);
105 
106 static void query_editor_map       (GtkWidget *widget);
107 static void query_editor_grab_focus (GtkWidget *widget);
108 
109 
110 static GObjectClass *parent_class = NULL;
111 static GHashTable *supported_languages = NULL;
112 static gint number_of_objects = 0;
113 
114 static void focus_on_hist_data (QueryEditor *editor, HistItemData *hdata);
115 static HistItemData *get_next_hist_data (QueryEditor *editor, HistItemData *hdata);
116 static HistItemData *get_prev_hist_data (QueryEditor *editor, HistItemData *hdata);
117 
118 static gboolean timestamps_update_cb (QueryEditor *editor);
119 
120 /* signals */
121 enum
122 {
123         CHANGED,
124 	HISTORY_ITEM_REMOVED,
125 	HISTORY_CLEARED,
126 	EXECUTE_REQUEST,
127         LAST_SIGNAL
128 };
129 
130 static gint query_editor_signals[LAST_SIGNAL] = { 0, 0, 0, 0 };
131 
132 
133 /*
134  * Private functions
135  */
136 static void
create_tags_for_sql(QueryEditor * editor,const gchar * language)137 create_tags_for_sql (QueryEditor *editor, const gchar *language)
138 {
139 #ifdef HAVE_GTKSOURCEVIEW
140 	GtkTextBuffer *buffer;
141 	GtkSourceLanguageManager *mgr;
142 	GtkSourceLanguage *lang;
143 	gchar ** current_search_path;
144 	gint len;
145 	gchar ** search_path;
146 
147 	GtkSourceStyleSchemeManager* sch_mgr;
148 	GtkSourceStyleScheme *sch;
149 #endif
150 
151 	g_return_if_fail (language != NULL);
152 	g_return_if_fail (!strcmp (language, QUERY_EDITOR_LANGUAGE_SQL));
153 
154 #ifdef HAVE_GTKSOURCEVIEW
155 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
156 	mgr = gtk_source_language_manager_new ();
157 
158 	/* alter search path */
159 	current_search_path = (gchar **) gtk_source_language_manager_get_search_path (mgr);
160 	len = g_strv_length (current_search_path);
161 	search_path = g_new0 (gchar*, len + 2);
162 	memcpy (search_path, current_search_path, sizeof (gchar*) * len);
163 	search_path [len] = gda_gbr_get_file_path (GDA_DATA_DIR, LIBGDA_ABI_NAME, "language-specs", NULL);
164 	gtk_source_language_manager_set_search_path (mgr, search_path);
165 	g_free (search_path [len]);
166 	g_free (search_path);
167 
168 	lang = gtk_source_language_manager_get_language (mgr, "gda-sql");
169 
170 	if (!lang) {
171 		gchar *tmp;
172 		tmp = gda_gbr_get_file_path (GDA_DATA_DIR, LIBGDA_ABI_NAME, "language-spec", NULL);
173 		g_print ("Could not find the gda-sql.lang file in %s,\nusing the default SQL highlighting rules.\n",
174 			 tmp);
175 		g_free (tmp);
176 		lang = gtk_source_language_manager_get_language (mgr, "sql");
177 	}
178 	if (lang)
179 		gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (buffer), lang);
180 
181 	g_object_unref (mgr);
182 
183 	sch_mgr = gtk_source_style_scheme_manager_get_default ();
184 	sch = gtk_source_style_scheme_manager_get_scheme (sch_mgr, "tango");
185 	if (sch)
186 		gtk_source_buffer_set_style_scheme (GTK_SOURCE_BUFFER (buffer), sch);
187 
188 #endif
189 }
190 
191 /*
192  * QueryEditor class implementation
193  */
194 static void
query_editor_class_init(QueryEditorClass * klass)195 query_editor_class_init (QueryEditorClass *klass)
196 {
197 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
198 
199 	parent_class = g_type_class_peek_parent (klass);
200 
201 	query_editor_signals[CHANGED] =
202                 g_signal_new ("changed",
203                               G_TYPE_FROM_CLASS (object_class),
204                               G_SIGNAL_RUN_FIRST,
205                               G_STRUCT_OFFSET (QueryEditorClass, changed),
206                               NULL, NULL,
207                               g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
208 
209 	query_editor_signals[EXECUTE_REQUEST] =
210                 g_signal_new ("execute-request",
211                               G_TYPE_FROM_CLASS (object_class),
212                               G_SIGNAL_RUN_FIRST,
213                               G_STRUCT_OFFSET (QueryEditorClass, execute_request),
214                               NULL, NULL,
215                               g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
216 
217 	query_editor_signals[HISTORY_ITEM_REMOVED] =
218                 g_signal_new ("history-item-removed",
219                               G_TYPE_FROM_CLASS (object_class),
220                               G_SIGNAL_RUN_FIRST,
221                               G_STRUCT_OFFSET (QueryEditorClass, history_item_removed),
222                               NULL, NULL,
223                               g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
224 
225 	query_editor_signals[HISTORY_CLEARED] =
226                 g_signal_new ("history-cleared",
227                               G_TYPE_FROM_CLASS (object_class),
228                               G_SIGNAL_RUN_FIRST,
229                               G_STRUCT_OFFSET (QueryEditorClass, history_cleared),
230                               NULL, NULL,
231                               g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
232 
233 	object_class->finalize = query_editor_finalize;
234 	GTK_WIDGET_CLASS (object_class)->map = query_editor_map;
235 	GTK_WIDGET_CLASS (object_class)->grab_focus = query_editor_grab_focus;
236 }
237 
238 static void
text_buffer_changed_cb(G_GNUC_UNUSED GtkTextBuffer * buffer,QueryEditor * editor)239 text_buffer_changed_cb (G_GNUC_UNUSED GtkTextBuffer *buffer, QueryEditor *editor)
240 {
241 	if (editor->priv->mode != QUERY_EDITOR_HISTORY)
242 		g_signal_emit (editor, query_editor_signals[CHANGED], 0);
243 }
244 
245 static void
popup_container_position_func(PopupContainer * cont,gint * out_x,gint * out_y)246 popup_container_position_func (PopupContainer *cont, gint *out_x, gint *out_y)
247 {
248 	QueryEditor *editor;
249 	GdkWindow *wind;
250         gint x, y, ex, ey;
251 	GtkTextBuffer *buffer;
252 	GtkTextIter iter;
253 	GdkRectangle rect;
254 	GtkTextView *textview;
255 
256         editor = g_object_get_data (G_OBJECT (cont), "editor");
257 	textview = GTK_TEXT_VIEW (editor->priv->text);
258 	buffer = gtk_text_view_get_buffer (textview);
259 	gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
260 	gtk_text_view_get_iter_location (textview, &iter, &rect);
261 	gtk_text_view_buffer_to_window_coords (textview, GTK_TEXT_WINDOW_WIDGET,
262 					       rect.x, rect.y, &ex, &ey);
263 
264 	wind = gtk_text_view_get_window (textview, GTK_TEXT_WINDOW_WIDGET);
265         gdk_window_get_origin (wind, &x, &y);
266 
267         x += ex + rect.width;
268         y += ey + rect.height;
269 
270         if (x < 0)
271                 x = 0;
272 
273         if (y < 0)
274                 y = 0;
275 
276         *out_x = x;
277         *out_y = y;
278 }
279 
280 static gchar *
get_string_to_complete(QueryEditor * editor,gchar ** out_start_pos)281 get_string_to_complete (QueryEditor *editor, gchar **out_start_pos)
282 {
283 	GtkTextBuffer *buffer;
284 	GtkTextIter start, end;
285 	GtkTextMark *mark;
286 
287 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
288 	mark = gtk_text_buffer_get_insert (buffer);
289 	gtk_text_buffer_get_iter_at_mark (buffer, &end, mark);
290 	gtk_text_buffer_get_start_iter (buffer, &start);
291 
292 	gchar *str, *ptr;
293 
294 	str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
295 	if (!*str) {
296 		g_free (str);
297 		*out_start_pos = NULL;
298 		return NULL;
299 	}
300 
301 	for (ptr = str + strlen (str) - 1; ptr > str; ptr--) {
302 		if (! g_ascii_isalnum (*ptr) &&
303 		    (*ptr != '_') && (*ptr != '.')) {
304 			ptr++;
305 			break;
306 		}
307 	}
308 	if ((ptr == str) &&
309 	    ! g_ascii_isalnum (*ptr) &&
310 	    (*ptr != '_'))
311 		ptr++;
312 	/*g_print ("completing [%s]\n", ptr);*/
313 
314 	*out_start_pos = ptr;
315 	return str;
316 }
317 
318 static void
completion_row_activated_cb(G_GNUC_UNUSED GtkTreeView * treeview,GtkTreePath * path,G_GNUC_UNUSED GtkTreeViewColumn * column,QueryEditor * editor)319 completion_row_activated_cb (G_GNUC_UNUSED GtkTreeView *treeview, GtkTreePath *path,
320 			     G_GNUC_UNUSED GtkTreeViewColumn *column, QueryEditor *editor)
321 {
322 	GtkTreeModel *model;
323 	GtkTreeIter iter;
324 
325 	gtk_widget_hide (editor->priv->completion_popup);
326 	model = gtk_tree_view_get_model (editor->priv->completion_treeview);
327 	if (gtk_tree_model_get_iter (model, &iter, path)) {
328 		gchar *compl;
329 		gchar *str, *ptr;
330 
331 		str = get_string_to_complete (editor, &ptr);
332 		if (!str)
333 			return;
334 
335 		gtk_tree_model_get (model, &iter, 0, &compl, -1);
336 
337 		GtkTextBuffer *buffer;
338 		GtkTextIter start, end;
339 
340 		buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
341 		gtk_text_buffer_get_iter_at_mark (buffer, &end, gtk_text_buffer_get_insert (buffer));
342 		start = end;
343 		if (gtk_text_iter_backward_chars (&start, strlen (ptr))) {
344 			gtk_text_buffer_delete (buffer, &start, &end);
345 			gtk_text_buffer_insert (buffer, &end, compl, -1);
346 			gtk_text_buffer_insert_at_cursor (buffer, " ", 1);
347 		}
348 
349 		g_free (str);
350 		g_free (compl);
351 	}
352 }
353 
354 static void
display_completions(QueryEditor * editor)355 display_completions (QueryEditor *editor)
356 {
357 	gchar *str, *ptr;
358 
359 	str = get_string_to_complete (editor, &ptr);
360 	if (!str)
361 		return;
362 
363 	BrowserConnection *bcnc;
364 	gchar **compl;
365 
366 	bcnc = browser_window_get_connection ((BrowserWindow*) gtk_widget_get_toplevel ((GtkWidget*) editor));
367 	compl = browser_connection_get_completions (bcnc, str, ptr - str, strlen (str));
368 	g_free (str);
369 
370 	if (compl) {
371 		GtkListStore *model;
372 		if (! editor->priv->completion_popup) {
373 			GtkWidget *treeview;
374 			GtkCellRenderer *renderer;
375 			GtkTreeViewColumn *column;
376 			GtkWidget *popup, *sw;
377 
378 			model = gtk_list_store_new (1, G_TYPE_STRING);
379 			treeview = browser_make_tree_view (GTK_TREE_MODEL (model));
380 			gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
381 			gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (treeview), GTK_TREE_VIEW_GRID_LINES_NONE);
382 			gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)),
383 						     GTK_SELECTION_BROWSE);
384 			g_object_unref (model);
385 
386 			renderer = gtk_cell_renderer_text_new ();
387 			g_object_set (G_OBJECT (renderer), "scale", 0.8,
388 				      "background", "#ffff82", NULL);
389 			column = gtk_tree_view_column_new_with_attributes ("", renderer,
390 									   "text", 0, NULL);
391 			gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
392 
393 			sw = gtk_scrolled_window_new (NULL, NULL);
394 			gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
395 							     GTK_SHADOW_NONE);
396 			gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
397 							GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
398 			gtk_container_add (GTK_CONTAINER (sw), treeview);
399 
400 			popup = popup_container_new_with_func (popup_container_position_func);
401 			g_object_set_data ((GObject*) popup, "editor", editor);
402 			gtk_container_set_border_width (GTK_CONTAINER (popup), 0);
403 			gtk_container_add (GTK_CONTAINER (popup), sw);
404 			editor->priv->completion_popup = popup;
405 			editor->priv->completion_treeview = GTK_TREE_VIEW (treeview);
406 			editor->priv->completion_renderer = renderer;
407 			editor->priv->completion_sw = sw;
408 
409 			g_signal_connect (treeview, "row-activated",
410 					  G_CALLBACK (completion_row_activated_cb), editor);
411 
412 		}
413 		else {
414 			model = GTK_LIST_STORE (gtk_tree_view_get_model (editor->priv->completion_treeview));
415 			gtk_list_store_clear (model);
416 		}
417 
418 		/* fill in the model */
419 		GtkTreeIter iter;
420 		gint w, width = 0, h, height = 0;
421 		gint i;
422 
423 		for (i = 0; ; i++) {
424 			if (! compl[i])
425 				break;
426 			/*g_print ("==> [%s]\n", compl[i]);*/
427 			gtk_list_store_append (model, &iter);
428 			gtk_list_store_set (model, &iter, 0, compl[i], -1);
429 			if (i == 0)
430 				gtk_tree_selection_select_iter (gtk_tree_view_get_selection (editor->priv->completion_treeview),
431 								&iter);
432 
433 			GtkRequisition nat_req;
434 			g_object_set ((GObject*) editor->priv->completion_renderer, "text", compl[i], NULL);
435 			gtk_cell_renderer_get_preferred_size (editor->priv->completion_renderer,
436 							      (GtkWidget*) editor->priv->completion_treeview,
437 							      NULL, &nat_req);
438 			width = MAX (width, nat_req.width);
439 			height += nat_req.height + 2;
440 		}
441 		g_strfreev (compl);
442 
443 		gtk_widget_set_size_request (editor->priv->completion_sw,
444 					     MIN (width + 30, 400), MIN (height, 400));
445 		gtk_widget_show_all (editor->priv->completion_popup);
446 	}
447 }
448 
449 /*
450  * Returns: -1 if none focussed
451  */
452 static gboolean
event(GtkWidget * text_view,GdkEvent * ev,QueryEditor * editor)453 event (GtkWidget *text_view, GdkEvent *ev, QueryEditor *editor)
454 {
455 	GtkTextIter start, end, iter;
456 	GtkTextBuffer *buffer;
457 	GdkEventButton *event;
458 	gint x, y;
459 
460 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
461 	if ((editor->priv->mode == QUERY_EDITOR_HISTORY) &&
462 	    (ev->type == GDK_BUTTON_RELEASE)) {
463 		event = (GdkEventButton *)ev;
464 
465 		if (event->button != 1)
466 			return FALSE;
467 
468 		/* we shouldn't follow a link if the user has selected something */
469 		gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
470 		if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end))
471 			return FALSE;
472 
473 		gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
474 						       GTK_TEXT_WINDOW_WIDGET,
475 						       event->x, event->y, &x, &y);
476 
477 		gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, x, y);
478 
479 		/* go through tags */
480 		GSList *tags = NULL, *tagp = NULL;
481 		HistItemData *hist_focus = NULL;
482 		tags = gtk_text_iter_get_tags (&iter);
483 		for (tagp = tags;  tagp;  tagp = tagp->next) {
484 			hist_focus = g_hash_table_lookup (editor->priv->hash, tagp->data);
485 			if (hist_focus)
486 				break;
487 		}
488 		focus_on_hist_data (editor, hist_focus);
489 
490 		if (tags)
491 			g_slist_free (tags);
492 	}
493 	else if (ev->type == GDK_KEY_PRESS) {
494 		GdkEventKey *evkey = ((GdkEventKey*) ev);
495 		if ((editor->priv->mode == QUERY_EDITOR_HISTORY) &&
496 		    ((evkey->keyval == GDK_KEY_Up) || (evkey->keyval == GDK_KEY_Down))) {
497 			HistItemData *nfocus = NULL;
498 			if (editor->priv->hist_focus) {
499 				if (((GdkEventKey*) ev)->keyval == GDK_KEY_Up)
500 					nfocus = get_prev_hist_data (editor, editor->priv->hist_focus);
501 				else
502 					nfocus = get_next_hist_data (editor, editor->priv->hist_focus);
503 				if (!nfocus)
504 					nfocus = editor->priv->hist_focus;
505 			}
506 
507 			focus_on_hist_data (editor, nfocus);
508 			return TRUE;
509 		}
510 		else if ((editor->priv->mode == QUERY_EDITOR_HISTORY) &&
511 			 ((evkey->keyval == GDK_KEY_Delete) && editor->priv->hist_focus)) {
512 			if (editor->priv->hist_focus->item)
513 				query_editor_del_current_history_item (editor);
514 			else if (editor->priv->hist_focus->batch)
515 				query_editor_del_history_batch (editor, editor->priv->hist_focus->batch);
516 			return TRUE;
517 		}
518 		else if ((editor->priv->mode == QUERY_EDITOR_READWRITE) &&
519 			 (evkey->state & GDK_CONTROL_MASK) &&
520 			 ((evkey->keyval == GDK_KEY_L) || (evkey->keyval == GDK_KEY_l))) {
521 			GtkTextIter start, end;
522 			gtk_text_buffer_get_start_iter (buffer, &start);
523 			gtk_text_buffer_get_end_iter (buffer, &end);
524 			gtk_text_buffer_delete (buffer, &start, &end);
525 			return TRUE;
526 		}
527 		else if ((editor->priv->mode == QUERY_EDITOR_READWRITE) &&
528 			 (evkey->state & GDK_CONTROL_MASK) &&
529 			 (evkey->keyval == GDK_KEY_Return)) {
530 			g_signal_emit (editor, query_editor_signals[EXECUTE_REQUEST], 0);
531 			return TRUE;
532 		}
533 		else if ((editor->priv->mode == QUERY_EDITOR_READWRITE) &&
534 			 (evkey->state & GDK_CONTROL_MASK) &&
535 			 (evkey->keyval == GDK_KEY_Up) &&
536 			 editor->priv->states) {
537 			if (editor->priv->states->len > 0) {
538 				gint i = -1;
539 				if (editor->priv->current_state == G_MAXINT) {
540 					i = editor->priv->states->len - 1;
541 					g_free (editor->priv->current_state_text);
542 					editor->priv->current_state_text = query_editor_get_all_text (editor);
543 				}
544 				else if (editor->priv->current_state >= (gint)editor->priv->states->len)
545 					i = editor->priv->states->len - 1; /* last stored state */
546 				else if (editor->priv->current_state > 0)
547 					i = editor->priv->current_state - 1;
548 				gchar *ctext;
549 				ctext = query_editor_get_all_text (editor);
550 				while (i >= 0) {
551 					gchar *tmp;
552 					editor->priv->current_state = i;
553 					tmp = g_array_index (editor->priv->states, gchar*, i);
554 					if (strcmp (tmp, ctext)) {
555 						query_editor_set_text (editor, tmp);
556 						break;
557 					}
558 					i--;
559 				}
560 				g_free (ctext);
561 			}
562 			return TRUE;
563 		}
564 		else if ((editor->priv->mode == QUERY_EDITOR_READWRITE) &&
565 			 (evkey->state & GDK_CONTROL_MASK) &&
566 			 (evkey->keyval == GDK_KEY_Down) &&
567 			 editor->priv->states) {
568 			if (editor->priv->states->len > 0) {
569 				if (editor->priv->current_state < (gint)editor->priv->states->len - 1) {
570 					gchar *tmp;
571 					editor->priv->current_state ++;
572 					tmp = g_array_index (editor->priv->states, gchar*, editor->priv->current_state);
573 					query_editor_set_text (editor, tmp);
574 				}
575 				else if (editor->priv->current_state_text) {
576 					editor->priv->current_state = G_MAXINT;
577 					query_editor_set_text (editor, editor->priv->current_state_text);
578 					g_free (editor->priv->current_state_text);
579 					editor->priv->current_state_text = NULL;
580 				}
581 			}
582 			return TRUE;
583 		}
584 		else if ((editor->priv->mode == QUERY_EDITOR_READWRITE) &&
585 			 (evkey->state & GDK_CONTROL_MASK) &&
586 			 (evkey->keyval == GDK_KEY_space)) {
587 			display_completions (editor);
588 			return TRUE;
589 		}
590 	}
591 	else
592 		return FALSE;
593 
594 	return FALSE;
595 }
596 
597 static gboolean
text_view_draw(GtkTextView * tv,cairo_t * cr,QueryEditor * editor)598 text_view_draw (GtkTextView *tv, cairo_t *cr, QueryEditor *editor)
599 {
600 	if (!editor->priv->hist_focus)
601 		return FALSE;
602 
603 	GdkRectangle visible_rect;
604 	GdkRectangle redraw_rect;
605 	GtkTextIter cur;
606 	gint y, ye;
607 	gint height, heighte;
608 	gint win_y;
609 	gint margin;
610 	GtkTextBuffer *tbuffer;
611 
612 	tbuffer = gtk_text_view_get_buffer (tv);
613 	gtk_text_buffer_get_iter_at_mark (tbuffer, &cur, editor->priv->hist_focus->start_mark);
614 	gtk_text_view_get_line_yrange (tv, &cur, &y, &height);
615 
616 	gtk_text_buffer_get_iter_at_mark (tbuffer, &cur, editor->priv->hist_focus->end_mark);
617 	gtk_text_view_get_line_yrange (tv, &cur, &ye, &heighte);
618 	height = ye - y;
619 
620 	if (!editor->priv->hist_focus->item) {
621 		GSList *list;
622 		HistItemData *hdata;
623 		list = g_slist_last (editor->priv->hist_focus->batch->hist_items);
624 		if (list) {
625 			hdata = g_hash_table_lookup (editor->priv->hash, list->data);
626 			gtk_text_buffer_get_iter_at_mark (tbuffer, &cur, hdata->end_mark);
627 			gtk_text_view_get_line_yrange (tv, &cur, &ye, &heighte);
628 			height = ye - y;
629 		}
630 	}
631 
632 	gtk_text_view_get_visible_rect (tv, &visible_rect);
633 	gtk_text_view_buffer_to_window_coords (tv,
634 					       GTK_TEXT_WINDOW_TEXT,
635 					       visible_rect.x,
636 					       visible_rect.y,
637 					       &redraw_rect.x,
638 					       &redraw_rect.y);
639 	gtk_text_view_buffer_to_window_coords (tv,
640 					       GTK_TEXT_WINDOW_TEXT,
641 					       0,
642 					       y,
643 					       NULL,
644 					       &win_y);
645 
646 	redraw_rect.width = visible_rect.width;
647 	redraw_rect.height = visible_rect.height;
648 
649 	GdkRectangle rect;
650 	margin = gtk_text_view_get_left_margin (tv);
651 	rect.x = redraw_rect.x + MAX (0, margin - 1);
652 	rect.y = win_y;
653 	rect.width = redraw_rect.width;
654 	rect.height = height;
655 	cairo_set_source_rgba (cr, .5, .5, .5, .3);
656 	gdk_cairo_rectangle (cr, &rect);
657 	cairo_fill (cr);
658 
659 	return FALSE;
660 }
661 
662 static void
copy_all_in_signle_line_cb(GtkMenuItem * mitem,QueryEditor * editor)663 copy_all_in_signle_line_cb (GtkMenuItem *mitem, QueryEditor *editor)
664 {
665 	gchar *all, *ptr;
666 	GString *string;
667 	GtkClipboard *clipboard;
668 
669 	all = query_editor_get_all_text (editor);
670 	if (!all)
671 		return;
672 
673 	string = g_string_new ("");
674 	for (ptr = all; *ptr; ptr++) {
675 		if (*ptr == '\n') {
676 			if ((ptr > all) && (ptr[-1] != ' '))
677 				g_string_append_c (string, ' ');
678 		}
679 		else {
680 			if ((*ptr == '-') && (ptr[1] == '-')) {
681 				/* comment => skip till end of line */
682 				for (; *ptr && *ptr != '\n'; ptr++);
683 			}
684 			else
685 				g_string_append_c (string, *ptr);
686 		}
687 	}
688 	g_free (all);
689 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
690 	gtk_clipboard_set_text (clipboard, string->str, -1);
691 	g_string_free (string, TRUE);
692 }
693 
694 static void
text_view_populate_popup_cb(GtkTextView * entry,GtkMenu * menu,QueryEditor * editor)695 text_view_populate_popup_cb (GtkTextView *entry, GtkMenu *menu, QueryEditor *editor)
696 {
697 	GtkWidget *mitem;
698 	mitem = gtk_menu_item_new_with_label (_("Copy all in a single line"));
699 	g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (copy_all_in_signle_line_cb),
700 			  editor);
701         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mitem);
702         gtk_widget_show (mitem);
703 }
704 
705 static void
query_editor_init(QueryEditor * editor,G_GNUC_UNUSED QueryEditorClass * klass)706 query_editor_init (QueryEditor *editor, G_GNUC_UNUSED QueryEditorClass *klass)
707 {
708 	int tab = 8;
709 	gboolean highlight = TRUE;
710 	gboolean showlinesno = FALSE;
711 	GtkWidget *ovl, *wid;
712 
713 	g_return_if_fail (QUERY_IS_EDITOR (editor));
714 
715 	gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), GTK_ORIENTATION_VERTICAL);
716 
717 	/* allocate private structure */
718 	editor->priv = g_new0 (QueryEditorPrivate, 1);
719 	editor->priv->mode = QUERY_EDITOR_READWRITE;
720 	editor->priv->batches_list = NULL;
721 	editor->priv->hash = NULL;
722 	editor->priv->hist_focus = NULL;
723 
724 	editor->priv->states = NULL;
725 	editor->priv->current_state = G_MAXINT;
726 	editor->priv->current_state_text = NULL;
727 
728 	editor->priv->completion_popup = NULL;
729 
730 	/* set up widgets */
731 	ovl = widget_overlay_new ();
732 	editor->priv->ovl = ovl;
733 	gtk_box_pack_start (GTK_BOX (editor), ovl, TRUE, TRUE, 2);
734 
735 	editor->priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL);
736         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (editor->priv->scrolled_window),
737 					     GTK_SHADOW_ETCHED_OUT);
738 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (editor->priv->scrolled_window),
739 					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
740 	gtk_container_add (GTK_CONTAINER (ovl), editor->priv->scrolled_window);
741 	widget_overlay_set_child_props (WIDGET_OVERLAY (ovl), editor->priv->scrolled_window,
742 					WIDGET_OVERLAY_CHILD_HALIGN, WIDGET_OVERLAY_ALIGN_FILL,
743 					WIDGET_OVERLAY_CHILD_VALIGN, WIDGET_OVERLAY_ALIGN_FILL,
744 					-1);
745 
746 	wid = gtk_label_new ("");
747 	editor->priv->tooltip_widget = wid;
748 	gtk_label_set_markup (GTK_LABEL (wid), QUERY_EDITOR_TOOLTIP);
749 	gtk_container_add (GTK_CONTAINER (ovl), wid);
750 	widget_overlay_set_child_props (WIDGET_OVERLAY (ovl), wid,
751 					WIDGET_OVERLAY_CHILD_HALIGN, WIDGET_OVERLAY_ALIGN_CENTER,
752 					WIDGET_OVERLAY_CHILD_VALIGN, WIDGET_OVERLAY_ALIGN_CENTER,
753 					WIDGET_OVERLAY_CHILD_ALPHA, .8,
754 					WIDGET_OVERLAY_CHILD_TOOLTIP, TRUE,
755 					-1);
756 
757 #ifdef HAVE_GTKSOURCEVIEW
758 	editor->priv->text = gtk_source_view_new ();
759 	gtk_source_buffer_set_highlight_syntax (GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text))),
760 					 highlight);
761 	gtk_source_view_set_show_line_numbers (GTK_SOURCE_VIEW (editor->priv->text), showlinesno);
762 	gtk_source_view_set_tab_width (GTK_SOURCE_VIEW (editor->priv->text), tab);
763 #else
764 	editor->priv->text = gtk_text_view_new ();
765 #endif
766 
767 	gtk_container_add (GTK_CONTAINER (editor->priv->scrolled_window), editor->priv->text);
768 	g_signal_connect (editor->priv->text, "event",
769 			  G_CALLBACK (event), editor);
770 	g_signal_connect (gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text)), "changed",
771 			  G_CALLBACK (text_buffer_changed_cb), editor);
772 	g_signal_connect (editor->priv->text, "draw",
773 			  G_CALLBACK (text_view_draw), editor);
774 	g_signal_connect (editor->priv->text, "populate-popup",
775 			  G_CALLBACK (text_view_populate_popup_cb), editor);
776 
777 	/* create some tags */
778 	GtkTextBuffer *buffer;
779 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
780 	gtk_text_buffer_create_tag (buffer, "h0",
781 				    "foreground", "#474A8F",
782 				    "weight", PANGO_WEIGHT_BOLD,
783 				    "variant", PANGO_VARIANT_SMALL_CAPS,
784 				    "scale", PANGO_SCALE_LARGE,
785 				    "underline", PANGO_UNDERLINE_SINGLE,
786 				    NULL);
787 
788 	gtk_text_buffer_create_tag (buffer, "note",
789 				    "left-margin", 50,
790 				    "foreground", "black",
791 				    "weight", PANGO_WEIGHT_NORMAL,
792 				    "style", PANGO_STYLE_ITALIC,
793 				    NULL);
794 
795 	/* initialize common data */
796 	number_of_objects++;
797 	if (!supported_languages) {
798 		supported_languages = g_hash_table_new (g_str_hash, g_str_equal);
799 
800 		/* add the built-in languages */
801 		g_hash_table_insert (supported_languages, QUERY_EDITOR_LANGUAGE_SQL, create_tags_for_sql);
802 	}
803 
804 	create_tags_for_sql (editor, QUERY_EDITOR_LANGUAGE_SQL);
805 
806 	gtk_widget_show_all (ovl);
807 
808 	/* timeout function to update timestamps */
809 	editor->priv->ts_timeout_id = 0;
810 }
811 
812 static void
query_editor_map(GtkWidget * widget)813 query_editor_map (GtkWidget *widget)
814 {
815 	GTK_WIDGET_CLASS (parent_class)->map (widget);
816 	if (QUERY_EDITOR (widget)->priv->mode == QUERY_EDITOR_HISTORY) {
817 		GtkStyleContext* style_context = gtk_widget_get_style_context (widget);
818 		GdkRGBA color;
819 		gtk_style_context_get_background_color (style_context, GTK_STATE_FLAG_NORMAL, &color);
820 		color.red += (1.0 - color.red) / COLOR_ALTER_FACTOR;
821 		color.green += (1.0 - color.green) / COLOR_ALTER_FACTOR;
822 		color.blue += (1.0 - color.blue) / COLOR_ALTER_FACTOR;
823 		gtk_widget_override_background_color (QUERY_EDITOR (widget)->priv->text, GTK_STATE_FLAG_NORMAL, &color);
824 	}
825 }
826 
827 
828 static void
query_editor_grab_focus(GtkWidget * widget)829 query_editor_grab_focus (GtkWidget *widget)
830 {
831 	gtk_widget_grab_focus (QUERY_EDITOR (widget)->priv->text);
832 }
833 
834 
835 static void
hist_data_free_all(QueryEditor * editor)836 hist_data_free_all (QueryEditor *editor)
837 {
838 	if (editor->priv->hist_focus) {
839 		hist_item_data_unref (editor->priv->hist_focus);
840 		editor->priv->hist_focus = NULL;
841 	}
842 
843 	if (editor->priv->ts_timeout_id) {
844 		g_source_remove (editor->priv->ts_timeout_id);
845 		editor->priv->ts_timeout_id = 0;
846 	}
847 
848 	if (editor->priv->hash) {
849 		g_hash_table_destroy (editor->priv->hash);
850 		editor->priv->hash = NULL;
851 	}
852 
853 	if (editor->priv->insert_into_batch) {
854 		query_editor_history_batch_unref (editor->priv->insert_into_batch);
855 		editor->priv->insert_into_batch = NULL;
856 	}
857 	if (editor->priv->batches_list) {
858 		g_slist_foreach (editor->priv->batches_list, (GFunc) query_editor_history_batch_unref, NULL);
859 		g_slist_free (editor->priv->batches_list);
860 		editor->priv->batches_list = NULL;
861 	}
862 }
863 
864 static void
query_editor_finalize(GObject * object)865 query_editor_finalize (GObject *object)
866 {
867 	QueryEditor *editor = (QueryEditor *) object;
868 
869 	g_return_if_fail (QUERY_IS_EDITOR (editor));
870 
871 	/* free memory */
872 	hist_data_free_all (editor);
873 	if (editor->priv->states) {
874 		gsize i;
875 		for (i = 0; i < editor->priv->states->len; i++) {
876 			gchar *str;
877 			str = g_array_index (editor->priv->states, gchar*, i);
878 			g_free (str);
879 		}
880 		g_array_free (editor->priv->states, TRUE);
881 	}
882 	g_free (editor->priv->current_state_text);
883 	if (editor->priv->completion_popup)
884 		gtk_widget_destroy (editor->priv->completion_popup);
885 
886 	g_free (editor->priv);
887 	editor->priv = NULL;
888 
889 	parent_class->finalize (object);
890 
891 	/* update common data */
892 	number_of_objects--;
893 	if (number_of_objects == 0) {
894 		g_hash_table_destroy (supported_languages);
895 		supported_languages = NULL;
896 	}
897 }
898 
899 GType
query_editor_get_type(void)900 query_editor_get_type (void)
901 {
902 	static GType type = 0;
903 
904 	if (G_UNLIKELY (type == 0)) {
905 		static const GTypeInfo info = {
906 			sizeof (QueryEditorClass),
907 			(GBaseInitFunc) NULL,
908 			(GBaseFinalizeFunc) NULL,
909 			(GClassInitFunc) query_editor_class_init,
910 			NULL,
911 			NULL,
912 			sizeof (QueryEditor),
913 			0,
914 			(GInstanceInitFunc) query_editor_init,
915 			0
916 		};
917 		type = g_type_register_static (GTK_TYPE_BOX, "QueryEditor", &info, 0);
918 	}
919 	return type;
920 }
921 
922 /**
923  * query_editor_new
924  *
925  * Create a new #QueryEditor widget, which is a multiline text widget
926  * with support for several languages used to write database stored
927  * procedures and functions. If libgnomedb was compiled with gtksourceview
928  * in, this widget will support syntax highlighting for all supported
929  * languages.
930  *
931  * Returns: the newly created widget.
932  */
933 GtkWidget *
query_editor_new(void)934 query_editor_new (void)
935 {
936 	QueryEditor *editor;
937 
938 	editor = g_object_new (QUERY_TYPE_EDITOR, NULL);
939 
940 	return GTK_WIDGET (editor);
941 }
942 
943 /**
944  * query_editor_get_mode
945  * @editor: a #QueryEditor widget.
946  *
947  * Get @editor's mode
948  *
949  * Returns: @editor's mode
950  */
951 QueryEditorMode
query_editor_get_mode(QueryEditor * editor)952 query_editor_get_mode (QueryEditor *editor)
953 {
954 	g_return_val_if_fail (QUERY_IS_EDITOR (editor), 0);
955 	return editor->priv->mode;
956 }
957 
958 /**
959  * query_editor_set_mode
960  * @editor: a #QueryEditor widget.
961  * @mode: new mode
962  *
963  * A mode change will empty the buffer.
964  */
965 void
query_editor_set_mode(QueryEditor * editor,QueryEditorMode mode)966 query_editor_set_mode (QueryEditor *editor, QueryEditorMode mode)
967 {
968 	GtkTextBuffer *buffer;
969 	gboolean clean = TRUE;
970 
971 	g_return_if_fail (QUERY_IS_EDITOR (editor));
972 	if (editor->priv->mode == mode)
973 		return;
974 
975 	if (((editor->priv->mode == QUERY_EDITOR_READWRITE) && (mode == QUERY_EDITOR_READONLY)) ||
976 	    ((editor->priv->mode == QUERY_EDITOR_READONLY) && (mode == QUERY_EDITOR_READWRITE)))
977 		clean = FALSE;
978 	else if (editor->priv->mode == QUERY_EDITOR_HISTORY)
979 		hist_data_free_all (editor);
980 
981 	editor->priv->mode = mode;
982 
983 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
984 	if (clean) {
985 		GtkTextIter start, end;
986 		gtk_text_buffer_get_start_iter (buffer, &start);
987 		gtk_text_buffer_get_end_iter (buffer, &end);
988 		gtk_text_buffer_delete (buffer, &start, &end);
989 	}
990 
991 	switch (mode) {
992 	case QUERY_EDITOR_READWRITE:
993 		gtk_widget_set_tooltip_markup (editor->priv->text, QUERY_EDITOR_TOOLTIP);
994 		gtk_text_view_set_editable (GTK_TEXT_VIEW (editor->priv->text), TRUE);
995 		gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (editor->priv->text), TRUE);
996 		break;
997 	case QUERY_EDITOR_READONLY:
998 	case QUERY_EDITOR_HISTORY:
999 		gtk_widget_set_tooltip_markup (editor->priv->text, NULL);
1000 		gtk_text_view_set_editable (GTK_TEXT_VIEW (editor->priv->text), FALSE);
1001 		gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (editor->priv->text), FALSE);
1002 		gtk_widget_destroy (editor->priv->tooltip_widget);
1003 		editor->priv->tooltip_widget = NULL;
1004 		break;
1005 	default:
1006 		g_assert_not_reached ();
1007 	}
1008 
1009 	if (mode == QUERY_EDITOR_HISTORY) {
1010 		GtkStyleContext *style_context = gtk_widget_get_style_context (GTK_WIDGET (editor));
1011 		GdkRGBA color;
1012 		gtk_style_context_get_background_color (style_context, GTK_STATE_FLAG_NORMAL, &color);
1013 		color.red += (1.0 - color.red) / COLOR_ALTER_FACTOR;
1014 		color.green += (1.0 - color.green) / COLOR_ALTER_FACTOR;
1015 		color.blue += (1.0 - color.blue) / COLOR_ALTER_FACTOR;
1016 		gtk_widget_override_background_color (editor->priv->text, GTK_STATE_FLAG_NORMAL, &color);
1017 
1018 		editor->priv->hash = g_hash_table_new_full (NULL, NULL, NULL,
1019 							    (GDestroyNotify) hist_item_data_unref);
1020 	}
1021 	else {
1022 		gtk_widget_override_background_color (editor->priv->text,
1023 					GTK_STATE_FLAG_NORMAL, NULL);
1024 	}
1025 }
1026 
1027 /**
1028  * query_editor_set_text
1029  * @editor: a #QueryEditor widget.
1030  * @text: text to display in the editor.
1031  *
1032  * Set @editor's text, removing any previous one
1033  */
1034 void
query_editor_set_text(QueryEditor * editor,const gchar * text)1035 query_editor_set_text (QueryEditor *editor, const gchar *text)
1036 {
1037 	GtkTextBuffer *buffer;
1038 	GtkTextIter start, end;
1039 
1040 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1041 	g_return_if_fail (editor->priv->mode != QUERY_EDITOR_HISTORY);
1042 
1043 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
1044 	gtk_text_buffer_get_start_iter (buffer, &start);
1045 	gtk_text_buffer_get_end_iter (buffer, &end);
1046 	gtk_text_buffer_delete (buffer, &start, &end);
1047 
1048 	if (text) {
1049 		gtk_text_buffer_get_end_iter (buffer, &end);
1050 		gtk_text_buffer_insert (buffer, &end, text, -1);
1051 	}
1052 }
1053 
1054 /**
1055  * query_editor_append_text
1056  * @editor: a #QueryEditor widget.
1057  * @text: text to display in the editor.
1058  *
1059  * Appends some text to @editor.
1060  */
1061 void
query_editor_append_text(QueryEditor * editor,const gchar * text)1062 query_editor_append_text (QueryEditor *editor, const gchar *text)
1063 {
1064 	GtkTextBuffer *buffer;
1065 
1066 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1067 	g_return_if_fail (editor->priv->mode != QUERY_EDITOR_HISTORY);
1068 
1069 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
1070 
1071 	if (text) {
1072 		GtkTextIter end;
1073 		gint len;
1074 		len = strlen (text);
1075 		gtk_text_buffer_get_end_iter (buffer, &end);
1076 		gtk_text_buffer_insert (buffer, &end, text, -1);
1077 
1078 		if ((len >= 1) && (text [len - 1] != '\n'))
1079 			gtk_text_buffer_insert (buffer, &end, "\n", 1);
1080 	}
1081 }
1082 
1083 /**
1084  * query_editor_keep_current_state
1085  * @editor:  #QueryEditor widget.
1086  *
1087  * Makes @editor keep the current text as a past state, which can be brought back
1088  * using CTRL-Up or Gtrl-Down
1089  */
1090 void
query_editor_keep_current_state(QueryEditor * editor)1091 query_editor_keep_current_state (QueryEditor *editor)
1092 {
1093 	gchar *text, *tmp;
1094 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1095 	g_return_if_fail (editor->priv->mode != QUERY_EDITOR_HISTORY);
1096 
1097 	if (!editor->priv->states)
1098 		editor->priv->states = g_array_sized_new (FALSE, FALSE, sizeof (gchar*), STATES_ARRAY_SIZE);
1099 
1100 	editor->priv->current_state = G_MAXINT;
1101 
1102 	text = query_editor_get_all_text (editor);
1103 	if (editor->priv->states->len > 0) {
1104 		tmp = g_array_index (editor->priv->states, gchar *, editor->priv->states->len-1);
1105 		if (!strcmp (tmp, text)) {
1106 			g_free (text);
1107 			return;
1108 		}
1109 	}
1110 
1111 	if (editor->priv->states->len == STATES_ARRAY_SIZE) {
1112 		tmp = g_array_index (editor->priv->states, gchar *, 0);
1113 		g_free (tmp);
1114 		g_array_remove_index (editor->priv->states, 0);
1115 	}
1116 	g_array_append_val (editor->priv->states, text);
1117 }
1118 
1119 /**
1120  * query_editor_append_note
1121  * @editor: a #QueryEditor widget.
1122  * @text: text to display in the editor.
1123  * @level: 0 for header, 1 for text with marging and in italics
1124  *
1125  * Appends some text to @editor.
1126  */
1127 void
query_editor_append_note(QueryEditor * editor,const gchar * text,gint level)1128 query_editor_append_note (QueryEditor *editor, const gchar *text, gint level)
1129 {
1130 	GtkTextBuffer *buffer;
1131 
1132 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1133 	g_return_if_fail (editor->priv->mode != QUERY_EDITOR_HISTORY);
1134 
1135 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
1136 	if (text) {
1137 		GtkTextIter end;
1138 		gchar *str;
1139 
1140 		switch (level) {
1141 		case 0:
1142 			str = g_strdup_printf ("%s\n", text);
1143 			gtk_text_buffer_get_end_iter (buffer, &end);
1144 			gtk_text_buffer_insert_with_tags_by_name (buffer, &end,
1145 								  str, -1,
1146 								  "h0", NULL);
1147 			g_free (str);
1148 			break;
1149 		case 1:
1150 			str = g_strdup_printf ("%s\n", text);
1151 			gtk_text_buffer_get_end_iter (buffer, &end);
1152 			gtk_text_buffer_insert_with_tags_by_name (buffer, &end,
1153 								  str, -1,
1154 								  "note", NULL);
1155 			g_free (str);
1156 			break;
1157 		default:
1158 			g_assert_not_reached ();
1159 		}
1160 	}
1161 }
1162 
1163 /**
1164  * query_editor_show_tooltip
1165  * @editor: a #QueryEditor
1166  * @show_tooltip:
1167  *
1168  * Defines if tooltip can be shown or not
1169  */
1170 void
query_editor_show_tooltip(QueryEditor * editor,gboolean show_tooltip)1171 query_editor_show_tooltip (QueryEditor *editor, gboolean show_tooltip)
1172 {
1173 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1174 	g_return_if_fail (editor->priv->mode == QUERY_EDITOR_READWRITE);
1175 
1176 	if (show_tooltip)
1177 		gtk_widget_set_tooltip_markup (editor->priv->text, QUERY_EDITOR_TOOLTIP);
1178 	else
1179 		gtk_widget_set_tooltip_markup (editor->priv->text, NULL);
1180 }
1181 
1182 static void
focus_on_hist_data(QueryEditor * editor,HistItemData * hdata)1183 focus_on_hist_data (QueryEditor *editor, HistItemData *hdata)
1184 {
1185 	if (editor->priv->hist_focus) {
1186 		if (editor->priv->hist_focus == hdata)
1187 			return;
1188 		if (editor->priv->hist_focus->item)
1189 			g_object_set (G_OBJECT (editor->priv->hist_focus->tag),
1190 				      "foreground-set", TRUE, NULL);
1191 		else {
1192 			/* un-highlight all the batch */
1193 			GSList *list;
1194 			for (list = editor->priv->hist_focus->batch->hist_items; list; list = list->next) {
1195 				HistItemData *hd;
1196 				hd = g_hash_table_lookup (editor->priv->hash, list->data);
1197 				g_object_set (G_OBJECT (hd->tag),
1198 					      "foreground-set", TRUE, NULL);
1199 			}
1200 		}
1201 		hist_item_data_unref (editor->priv->hist_focus);
1202 		editor->priv->hist_focus = NULL;
1203 	}
1204 
1205 	if (hdata) {
1206 		GtkTextBuffer *buffer;
1207 		GtkTextIter iter;
1208 
1209 		buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
1210 		if (editor->priv->hist_focus)
1211 			hist_item_data_unref (editor->priv->hist_focus);
1212 		editor->priv->hist_focus = hist_item_data_ref (hdata);
1213 		if (hdata->item)
1214 			g_object_set (G_OBJECT (hdata->tag),
1215 				      "foreground-set", FALSE, NULL);
1216 		else {
1217 			/* highlight all the batch */
1218 			GSList *list;
1219 			for (list = hdata->batch->hist_items; list; list = list->next) {
1220 				HistItemData *hd;
1221 				hd = g_hash_table_lookup (editor->priv->hash, list->data);
1222 				g_object_set (G_OBJECT (hd->tag),
1223 					      "foreground-set", FALSE, NULL);
1224 			}
1225 		}
1226 
1227 		gtk_text_buffer_get_iter_at_mark (buffer, &iter, hdata->start_mark);
1228 		gtk_text_buffer_place_cursor (buffer, &iter);
1229 		gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (editor->priv->text), hdata->start_mark);
1230 	}
1231 
1232 	g_signal_emit (editor, query_editor_signals[CHANGED], 0);
1233 }
1234 
1235 const char *
get_date_format(time_t time)1236 get_date_format (time_t time)
1237 {
1238         static char timebuf[200];
1239 	GTimeVal now;
1240 	unsigned long diff, tmp;
1241 
1242 	g_get_current_time (&now);
1243 
1244 	if (now.tv_sec < time)
1245 		return _("In the future:\n");
1246 
1247 	diff = now.tv_sec - time;
1248 	if (diff < 60)
1249 		return _("Less than a minute ago:\n");
1250 
1251 	/* Turn it into minutes */
1252 	tmp = diff / 60;
1253 	if (tmp < 60) {
1254 		snprintf (timebuf, sizeof(timebuf), ngettext ("%lu minute ago:\n",
1255 							      "%lu minutes ago:\n", tmp), tmp);
1256 		return timebuf;
1257 	}
1258 	/* Turn it into hours */
1259 	tmp = diff / 3600;
1260 	if (tmp < 24) {
1261 		snprintf (timebuf, sizeof(timebuf), ngettext ("%lu hour ago\n",
1262 							      "%lu hours ago\n", tmp), tmp);
1263 		return timebuf;
1264 	}
1265 	/* Turn it into days */
1266 	tmp = diff / 86400;
1267 	snprintf (timebuf, sizeof(timebuf), ngettext ("%lu day ago\n",
1268 						      "%lu days ago\n", tmp), tmp);
1269 
1270 	return timebuf;
1271 }
1272 
1273 /**
1274  * query_editor_start_history_batch
1275  * @editor: a #QueryEditor widget.
1276  * @hist_batch: (allow-none): a #QueryEditorHistoryBatch to add, or %NULL
1277  *
1278  * @hist_hash ref is _NOT_ stolen here
1279  */
1280 void
query_editor_start_history_batch(QueryEditor * editor,QueryEditorHistoryBatch * hist_batch)1281 query_editor_start_history_batch (QueryEditor *editor, QueryEditorHistoryBatch *hist_batch)
1282 {
1283 	GtkTextIter iter;
1284 	GtkTextBuffer *buffer;
1285 	GtkTextTag *tag;
1286 	HistItemData *hdata;
1287 	gboolean empty = FALSE;
1288 
1289 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1290 	g_return_if_fail (editor->priv->mode == QUERY_EDITOR_HISTORY);
1291 
1292 	if (!hist_batch) {
1293 		GTimeVal run_time = {0, 0};
1294 		empty = TRUE;
1295 
1296 		hist_batch = query_editor_history_batch_new (run_time, NULL);
1297 	}
1298 	else {
1299 		if (g_slist_find (editor->priv->batches_list, hist_batch))
1300 			return;
1301 	}
1302 
1303 	/* update editor->priv->insert_into_batch */
1304 	if (editor->priv->insert_into_batch)
1305 		query_editor_history_batch_unref (editor->priv->insert_into_batch);
1306 	editor->priv->insert_into_batch = query_editor_history_batch_ref (hist_batch);
1307 	editor->priv->batches_list = g_slist_prepend (editor->priv->batches_list,
1308 						      query_editor_history_batch_ref (hist_batch));
1309 
1310 	/* new HistItemData */
1311 	hdata = hist_item_data_new ();
1312 	hdata->batch = query_editor_history_batch_ref (hist_batch);
1313 	hdata->item = NULL;
1314 	g_hash_table_insert (editor->priv->hash, hist_batch, hdata);
1315 
1316 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
1317 	gtk_text_buffer_get_end_iter (buffer, &iter);
1318 
1319 	/* mark start of insertion */
1320 	hdata->start_mark = gtk_text_buffer_create_mark (buffer, NULL, &iter, TRUE);
1321 
1322 	/* new tag */
1323 	tag = gtk_text_buffer_create_tag (buffer, NULL,
1324 					  "pixels-below-lines", 3,
1325 					  "foreground", "black",
1326 					  "scale", PANGO_SCALE_SMALL,
1327 					  "weight", PANGO_WEIGHT_BOLD, NULL);
1328 	hdata->tag = g_object_ref (tag);
1329 	g_hash_table_insert (editor->priv->hash, tag, hist_item_data_ref (hdata));
1330 
1331 	if (!empty) {
1332 		/* insert text */
1333 		gtk_text_buffer_insert_with_tags (buffer, &iter,
1334 						  get_date_format (hist_batch->run_time.tv_sec),
1335 						  -1, tag, NULL);
1336 	}
1337 
1338 	/* mark end of insertion */
1339 	hdata->end_mark = gtk_text_buffer_create_mark (buffer, NULL, &iter, TRUE);
1340 
1341 	if (empty)
1342 		query_editor_history_batch_unref (hist_batch);
1343 
1344 	/* add timout to 1 sec. */
1345 	if (editor->priv->ts_timeout_id == 0)
1346 		editor->priv->ts_timeout_id  = g_timeout_add_seconds (60,
1347 								      (GSourceFunc) timestamps_update_cb,
1348 								      editor);
1349 
1350 	/* remove too old batches */
1351 	if (g_slist_length (editor->priv->batches_list) > MAX_HISTORY_BATCH_ITEMS) {
1352 		QueryEditorHistoryBatch *obatch;
1353 		obatch = g_slist_last (editor->priv->batches_list)->data;
1354 		query_editor_del_history_batch (editor, obatch);
1355 	}
1356 }
1357 
1358 static gboolean
timestamps_update_cb(QueryEditor * editor)1359 timestamps_update_cb (QueryEditor *editor)
1360 {
1361 	GSList *list;
1362 	GtkTextBuffer *buffer;
1363 	GtkTextIter start, end;
1364 
1365 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
1366 	for (list = editor->priv->batches_list; list; list = list->next) {
1367 		QueryEditorHistoryBatch *batch;
1368 		batch = (QueryEditorHistoryBatch*) list->data;
1369 		if (batch->run_time.tv_sec != 0) {
1370 			HistItemData *hdata;
1371 			hdata = g_hash_table_lookup (editor->priv->hash, batch);
1372 
1373 			/* delete current text */
1374 			gtk_text_buffer_get_iter_at_mark (buffer, &start, hdata->start_mark);
1375 			gtk_text_buffer_get_iter_at_mark (buffer, &end, hdata->end_mark);
1376 			gtk_text_buffer_delete (buffer, &start, &end);
1377 
1378 			/* insert text */
1379 			gtk_text_buffer_get_iter_at_mark (buffer, &start, hdata->start_mark);
1380 			gtk_text_buffer_insert_with_tags (buffer, &start,
1381 							  get_date_format (batch->run_time.tv_sec),
1382 							  -1, hdata->tag, NULL);
1383 			gtk_text_buffer_delete_mark (buffer, hdata->end_mark);
1384 			hdata->end_mark = gtk_text_buffer_create_mark (buffer, NULL, &start, TRUE);
1385 		}
1386 	}
1387 	return TRUE; /* don't remove timeout */
1388 }
1389 
1390 /**
1391  * query_editor_add_history_item
1392  * @editor: a #QueryEditor widget.
1393  * @hist_item: (allow-none): a #QueryEditorHistoryItem to add, or %NULL
1394  *
1395  * Adds some text. @text_data is useful only if @editor's mode is HISTORY, it will be ignored
1396  * otherwise.
1397  *
1398  * Returns: the position of the added text chunk, or %0 if mode is not HISTORY
1399  */
1400 void
query_editor_add_history_item(QueryEditor * editor,QueryEditorHistoryItem * hist_item)1401 query_editor_add_history_item (QueryEditor *editor, QueryEditorHistoryItem *hist_item)
1402 {
1403 	GtkTextIter iter;
1404 	GtkTextBuffer *buffer;
1405 	GtkTextTag *tag;
1406 	HistItemData *hdata;
1407 
1408 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1409 	g_return_if_fail (editor->priv->mode == QUERY_EDITOR_HISTORY);
1410 	g_return_if_fail (hist_item);
1411 	g_return_if_fail (hist_item->sql);
1412 
1413 	/* new HistItemData */
1414 	hdata = hist_item_data_new ();
1415 	hdata->batch = NULL;
1416 	hdata->item = query_editor_history_item_ref (hist_item);
1417 	g_hash_table_insert (editor->priv->hash, hist_item, hdata);
1418 
1419 	/* remove leading and trailing spaces */
1420 	g_strstrip (hist_item->sql);
1421 
1422 	if (!editor->priv->insert_into_batch)
1423 		query_editor_start_history_batch (editor, NULL);
1424 
1425 	query_editor_history_batch_add_item (editor->priv->insert_into_batch, hist_item);
1426 	hdata->batch = query_editor_history_batch_ref (editor->priv->insert_into_batch);
1427 
1428 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
1429 	gtk_text_buffer_get_end_iter (buffer, &iter);
1430 
1431 
1432 	tag = gtk_text_buffer_create_tag (buffer, NULL,
1433 					  "scale", 0.75,
1434 					  "foreground", "gray",
1435 					  "foreground-set", TRUE, NULL);
1436 	if (hist_item->within_transaction)
1437 		g_object_set (G_OBJECT (tag), "left-margin", 15, NULL);
1438 	hdata->tag = g_object_ref (tag);
1439 	g_hash_table_insert (editor->priv->hash, tag, hist_item_data_ref (hdata));
1440 
1441 	/* mark start of insertion */
1442 	hdata->start_mark = gtk_text_buffer_create_mark (buffer, NULL, &iter, TRUE);
1443 
1444 	/* insert text */
1445 	gchar *sql, *tmp1, *tmp2;
1446 	sql = g_strdup (hist_item->sql);
1447 	for (tmp1 = sql; (*tmp1 == ' ') || (*tmp1 == '\n') || (*tmp1 == '\t'); tmp1 ++);
1448 	for (tmp2 = sql + (strlen (sql) - 1);
1449 	     (tmp2 > tmp1) && ((*tmp2 == ' ') || (*tmp2 == '\n') || (*tmp2 == '\t'));
1450 	     tmp2--)
1451 		*tmp2 = 0;
1452 	gtk_text_buffer_insert_with_tags (buffer, &iter, tmp1, -1, tag, NULL);
1453 	gtk_text_buffer_insert_with_tags (buffer, &iter, "\n", 1, tag, NULL);
1454 	g_free (sql);
1455 
1456 	/* mark end of insertion */
1457 	gtk_text_buffer_get_end_iter (buffer, &iter);
1458 	hdata->end_mark = gtk_text_buffer_create_mark (buffer, NULL, &iter, TRUE);
1459 
1460 	focus_on_hist_data (editor, hdata);
1461 	gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (editor->priv->text),
1462 				      &iter, 0., FALSE, 0., 0.);
1463 }
1464 
1465 /**
1466  * query_editor_get_current_history_item
1467  * @editor: a #QueryEditor widget.
1468  * @out_in_batch: (allow-none): a pointer to store the #QueryEditorHistoryBatch the returned item is in, or %NULL
1469  *
1470  * Get the current selected #QueryEditorHistoryItem
1471  * passed to query_editor_add_history_item().
1472  *
1473  * Returns: a #QueryEditorHistoryItem pointer, or %NULL
1474  */
1475 QueryEditorHistoryItem *
query_editor_get_current_history_item(QueryEditor * editor,QueryEditorHistoryBatch ** out_in_batch)1476 query_editor_get_current_history_item (QueryEditor *editor, QueryEditorHistoryBatch **out_in_batch)
1477 {
1478 	g_return_val_if_fail (QUERY_IS_EDITOR (editor), NULL);
1479 	g_return_val_if_fail (editor->priv->mode == QUERY_EDITOR_HISTORY, NULL);
1480 
1481 	if (out_in_batch)
1482 		*out_in_batch = NULL;
1483 
1484 	if (editor->priv->hist_focus) {
1485 		if (out_in_batch)
1486 			*out_in_batch = editor->priv->hist_focus->batch;
1487 		return editor->priv->hist_focus->item;
1488 	}
1489 	else
1490 		return NULL;
1491 }
1492 
1493 /**
1494  * query_editor_get_current_history_batch
1495  * @editor: a #QueryEditor widget.
1496  *
1497  * Get the current selected #QueryEditorHistoryBatch if the selection is not on
1498  * a #QueryEditorHistoryItem, but on the #QueryEditorHistoryBatch which was last
1499  * set by a call to query_editor_start_history_batch().
1500  *
1501  * Returns: a #QueryEditorHistoryBatch pointer, or %NULL
1502  */
1503 QueryEditorHistoryBatch *
query_editor_get_current_history_batch(QueryEditor * editor)1504 query_editor_get_current_history_batch (QueryEditor *editor)
1505 {
1506 	g_return_val_if_fail (QUERY_IS_EDITOR (editor), NULL);
1507 	g_return_val_if_fail (editor->priv->mode == QUERY_EDITOR_HISTORY, NULL);
1508 
1509 	if (editor->priv->hist_focus && !editor->priv->hist_focus->item)
1510 		return editor->priv->hist_focus->batch;
1511 	else
1512 		return NULL;
1513 }
1514 
1515 /**
1516  * query_editor_history_is_empty
1517  * @editor: a #QueryEditor widget.
1518  *
1519  * Returns: %TRUE if @editor does not have any history item
1520  */
1521 gboolean
query_editor_history_is_empty(QueryEditor * editor)1522 query_editor_history_is_empty (QueryEditor *editor)
1523 {
1524 	g_return_val_if_fail (QUERY_IS_EDITOR (editor), FALSE);
1525 	g_return_val_if_fail (editor->priv->mode == QUERY_EDITOR_HISTORY, FALSE);
1526 
1527 	return editor->priv->batches_list ? FALSE : TRUE;
1528 }
1529 
1530 /**
1531  * query_editor_del_current_history_item
1532  * @editor: a #QueryEditor widget.
1533  *
1534  * Deletes the text associated to the current selection, useful only if @editor's mode is HISTORY
1535  */
1536 void
query_editor_del_current_history_item(QueryEditor * editor)1537 query_editor_del_current_history_item (QueryEditor *editor)
1538 {
1539 	HistItemData *hdata, *focus;
1540 	GtkTextBuffer *buffer;
1541 
1542 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1543 	g_return_if_fail (editor->priv->mode == QUERY_EDITOR_HISTORY);
1544 
1545 	if (!editor->priv->hist_focus)
1546 		return;
1547 	hdata = editor->priv->hist_focus;
1548 	if (!hdata->item)
1549 		return;
1550 
1551 	focus = get_next_hist_data (editor, hdata);
1552 	if (focus)
1553 		focus_on_hist_data (editor, focus);
1554 	else
1555 		focus_on_hist_data (editor, get_prev_hist_data (editor, hdata));
1556 
1557 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
1558 
1559 	/* handle GtkTextBuffer's deletion */
1560 	GtkTextIter start, end;
1561 	gtk_text_buffer_get_iter_at_mark (buffer, &start, hdata->start_mark);
1562 	gtk_text_buffer_get_iter_at_mark (buffer, &end, hdata->end_mark);
1563 	gtk_text_buffer_delete (buffer, &start, &end);
1564 	gtk_text_buffer_delete_mark (buffer, hdata->start_mark);
1565 	gtk_text_buffer_delete_mark (buffer, hdata->end_mark);
1566 
1567 	hist_item_data_ref (hdata);
1568 	g_hash_table_remove (editor->priv->hash, hdata->item);
1569 	g_hash_table_remove (editor->priv->hash, hdata->tag);
1570 
1571 	g_assert (hdata->batch);
1572 	query_editor_history_batch_del_item (editor, hdata->batch, hdata->item);
1573 
1574 	if (! hdata->batch->hist_items) {
1575 		/* remove hdata->batch */
1576 		HistItemData *remhdata;
1577 
1578 		editor->priv->batches_list = g_slist_remove (editor->priv->batches_list, hdata->batch);
1579 		query_editor_history_batch_unref (hdata->batch);
1580 
1581 		remhdata = g_hash_table_lookup (editor->priv->hash, hdata->batch);
1582 		gtk_text_buffer_get_iter_at_mark (buffer, &start, remhdata->start_mark);
1583 		gtk_text_buffer_get_iter_at_mark (buffer, &end, remhdata->end_mark);
1584 		gtk_text_buffer_delete (buffer, &start, &end);
1585 		gtk_text_buffer_delete_mark (buffer, remhdata->start_mark);
1586 		gtk_text_buffer_delete_mark (buffer, remhdata->end_mark);
1587 
1588 		g_hash_table_remove (editor->priv->hash, remhdata->batch);
1589 		g_hash_table_remove (editor->priv->hash, remhdata->tag);
1590 
1591 		if (editor->priv->insert_into_batch == hdata->batch) {
1592 			query_editor_history_batch_unref (editor->priv->insert_into_batch);
1593 			editor->priv->insert_into_batch = NULL;
1594 		}
1595 	}
1596 	hist_item_data_unref (hdata);
1597 }
1598 
1599 /**
1600  * query_editor_del_all_history_items
1601  * @editor: a #QueryEditor
1602  *
1603  * Clears all the history
1604  */
1605 void
query_editor_del_all_history_items(QueryEditor * editor)1606 query_editor_del_all_history_items (QueryEditor *editor)
1607 {
1608 	GtkTextBuffer *buffer;
1609 	GtkTextIter start, end;
1610 
1611 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1612 	g_return_if_fail (editor->priv->mode == QUERY_EDITOR_HISTORY);
1613 
1614 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
1615 	hist_data_free_all (editor);
1616 
1617 	gtk_text_buffer_get_start_iter (buffer, &start);
1618 	gtk_text_buffer_get_end_iter (buffer, &end);
1619 	gtk_text_buffer_delete (buffer, &start, &end);
1620 
1621 	editor->priv->hash = g_hash_table_new_full (NULL, NULL, NULL,
1622 						    (GDestroyNotify) hist_item_data_unref);
1623 	g_signal_emit (editor, query_editor_signals[CHANGED], 0);
1624 	g_signal_emit (editor, query_editor_signals[HISTORY_CLEARED], 0);
1625 }
1626 
1627 /**
1628  * query_editor_del_history_batch
1629  * @editor: a #QueryEditor
1630  * @batch: a #QueryEditorHistoryBatch
1631  *
1632  * Deletes all regarding @batch.
1633  */
1634 void
query_editor_del_history_batch(QueryEditor * editor,QueryEditorHistoryBatch * batch)1635 query_editor_del_history_batch (QueryEditor *editor, QueryEditorHistoryBatch *batch)
1636 {
1637 	GtkTextBuffer *buffer;
1638 	GSList *list;
1639 	HistItemData *hdata;
1640 	GtkTextIter start, end;
1641 	HistItemData *focus;
1642 	gint i;
1643 
1644 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1645 	g_return_if_fail (editor->priv->mode == QUERY_EDITOR_HISTORY);
1646 	g_return_if_fail (batch);
1647 	i = g_slist_index (editor->priv->batches_list, batch);
1648 	g_return_if_fail (i >= 0);
1649 
1650 	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
1651 
1652 	/* compute new focus */
1653 	focus = NULL;
1654 	if (i > 0) {
1655 		list = g_slist_nth (editor->priv->batches_list, i - 1);
1656 		focus = g_hash_table_lookup (editor->priv->hash, list->data);
1657 	}
1658 	focus_on_hist_data (editor, focus);
1659 
1660 	/* remove all history items */
1661 	for (list =  batch->hist_items; list; list = batch->hist_items) {
1662 		hdata = g_hash_table_lookup (editor->priv->hash, list->data);
1663 		g_assert (hdata);
1664 
1665 		gtk_text_buffer_get_iter_at_mark (buffer, &start, hdata->start_mark);
1666 		gtk_text_buffer_get_iter_at_mark (buffer, &end, hdata->end_mark);
1667 		gtk_text_buffer_delete (buffer, &start, &end);
1668 		gtk_text_buffer_delete_mark (buffer, hdata->start_mark);
1669 		gtk_text_buffer_delete_mark (buffer, hdata->end_mark);
1670 
1671 		g_hash_table_remove (editor->priv->hash, hdata->item);
1672 		g_hash_table_remove (editor->priv->hash, hdata->tag);
1673 		query_editor_history_batch_del_item (editor, batch, (QueryEditorHistoryItem*) list->data);
1674 	}
1675 
1676 	/* remove batch */
1677 	editor->priv->batches_list = g_slist_remove (editor->priv->batches_list, batch);
1678 	query_editor_history_batch_unref (batch);
1679 
1680 	hdata = g_hash_table_lookup (editor->priv->hash, batch);
1681 	gtk_text_buffer_get_iter_at_mark (buffer, &start, hdata->start_mark);
1682 	gtk_text_buffer_get_iter_at_mark (buffer, &end, hdata->end_mark);
1683 	gtk_text_buffer_delete (buffer, &start, &end);
1684 	gtk_text_buffer_delete_mark (buffer, hdata->start_mark);
1685 	gtk_text_buffer_delete_mark (buffer, hdata->end_mark);
1686 
1687 	if (editor->priv->insert_into_batch == batch) {
1688 		query_editor_history_batch_unref (editor->priv->insert_into_batch);
1689 		editor->priv->insert_into_batch = NULL;
1690 	}
1691 
1692 	g_hash_table_remove (editor->priv->hash, hdata->batch);
1693 	g_hash_table_remove (editor->priv->hash, hdata->tag);
1694 }
1695 
1696 static HistItemData *
get_next_hist_data(QueryEditor * editor,HistItemData * hdata)1697 get_next_hist_data (QueryEditor *editor, HistItemData *hdata)
1698 {
1699 	GSList *node;
1700 	g_return_val_if_fail (hdata, NULL);
1701 	g_assert (hdata->batch);
1702 
1703 	if (hdata->item) {
1704 		node = g_slist_find (hdata->batch->hist_items, hdata->item);
1705 		g_assert (node);
1706 		node = node->next;
1707 		if (node)
1708 			return g_hash_table_lookup (editor->priv->hash, node->data);
1709 	}
1710 	else
1711 		if (hdata->batch->hist_items)
1712 			return g_hash_table_lookup (editor->priv->hash, hdata->batch->hist_items->data);
1713 
1714 	/* move on to the next batch if any */
1715 	gint i;
1716 	i = g_slist_index (editor->priv->batches_list, hdata->batch);
1717 	if (i > 0) {
1718 		node = g_slist_nth (editor->priv->batches_list, i-1);
1719 		hdata = g_hash_table_lookup (editor->priv->hash, node->data);
1720 		return get_next_hist_data (editor, hdata);
1721 	}
1722 	return NULL;
1723 }
1724 
1725 static HistItemData *
get_prev_hist_data(QueryEditor * editor,HistItemData * hdata)1726 get_prev_hist_data (QueryEditor *editor, HistItemData *hdata)
1727 {
1728 	GSList *node;
1729 	gint i;
1730 	g_return_val_if_fail (hdata, NULL);
1731 	g_assert (hdata->batch);
1732 
1733 	if (hdata->item) {
1734 		node = g_slist_find (hdata->batch->hist_items, hdata->item);
1735 		g_assert (node);
1736 		i = g_slist_position (hdata->batch->hist_items, node);
1737 		if (i > 0) {
1738 			node = g_slist_nth (hdata->batch->hist_items, i - 1);
1739 			return g_hash_table_lookup (editor->priv->hash, node->data);
1740 		}
1741 	}
1742 
1743 	/* move to the previous batch, if any */
1744 	node = g_slist_find (editor->priv->batches_list, hdata->batch);
1745 	node = node->next;
1746 	while (node) {
1747 		QueryEditorHistoryBatch *b;
1748 		b = (QueryEditorHistoryBatch*) node->data;
1749 		GSList *l;
1750 		l = g_slist_last (b->hist_items);
1751 		if (l)
1752 			return g_hash_table_lookup (editor->priv->hash, l->data);
1753 		node = node->next;
1754 	}
1755 
1756 	return NULL;
1757 }
1758 
1759 /**
1760  * query_editor_get_all_text
1761  * @editor: a #QueryEditor widget.
1762  *
1763  * Retrieve the full contents of the given editor widget.
1764  *
1765  * Returns: the current contents of the editor buffer. You must free
1766  * the returned value when no longer needed.
1767  */
1768 gchar *
query_editor_get_all_text(QueryEditor * editor)1769 query_editor_get_all_text (QueryEditor *editor)
1770 {
1771 	g_return_val_if_fail (QUERY_IS_EDITOR (editor), NULL);
1772 
1773 	GtkTextBuffer *buffer;
1774         GtkTextIter start;
1775         GtkTextIter end;
1776 
1777         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text));
1778 
1779 	gtk_text_buffer_get_start_iter (buffer, &start);
1780 	gtk_text_buffer_get_end_iter (buffer, &end);
1781 
1782         return gtk_text_buffer_get_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text)),
1783                                          &start, &end, FALSE);
1784 }
1785 
1786 /**
1787  * query_editor_load_from_file
1788  * @editor: a #QueryEditor widget.
1789  * @filename: the file to be loaded.
1790  *
1791  * Load the given filename into the editor widget.
1792  *
1793  * Returns: TRUE if successful, FALSE otherwise.
1794  */
1795 gboolean
query_editor_load_from_file(QueryEditor * editor,const gchar * filename)1796 query_editor_load_from_file (QueryEditor *editor, const gchar *filename)
1797 {
1798 	gboolean retval = TRUE;
1799 	gchar *contents;
1800 
1801 	g_return_val_if_fail (QUERY_IS_EDITOR (editor), FALSE);
1802 	g_return_val_if_fail (filename != NULL, FALSE);
1803 
1804 	if (g_file_get_contents (filename, &contents, NULL, NULL)) {
1805 		query_editor_set_text (editor, contents);
1806 		g_free (contents);
1807 	}
1808 	else
1809 		retval = FALSE;
1810 
1811 	return retval;
1812 }
1813 
1814 /**
1815  * query_editor_save_to_file
1816  * @editor: a #QueryEditor widget.
1817  * @filename: the file to save to.
1818  *
1819  * Save the current editor contents to the given file.
1820  *
1821  * Returns: TRUE if successful, FALSE otherwise.
1822  */
1823 gboolean
query_editor_save_to_file(QueryEditor * editor,const gchar * filename)1824 query_editor_save_to_file (QueryEditor *editor, const gchar *filename)
1825 {
1826 	gchar *contents;
1827 	gboolean retval = TRUE;
1828 
1829 	g_return_val_if_fail (QUERY_IS_EDITOR (editor), FALSE);
1830 	g_return_val_if_fail (filename != NULL, FALSE);
1831 
1832 	contents = query_editor_get_all_text (editor);
1833 	if (!g_file_set_contents (filename, contents, strlen (contents), NULL))
1834 		retval = FALSE;
1835 
1836 	g_free (contents);
1837 
1838 	return retval;
1839 }
1840 
1841 /**
1842  * query_editor_copy_clipboard
1843  * @editor: a #QueryEditor widget.
1844  *
1845  * Copy currently selected text in the given editor widget to the clipboard.
1846  */
1847 void
query_editor_copy_clipboard(QueryEditor * editor)1848 query_editor_copy_clipboard (QueryEditor *editor)
1849 {
1850 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1851 	gtk_text_buffer_copy_clipboard (gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text)),
1852                                         gtk_clipboard_get (GDK_SELECTION_CLIPBOARD));
1853 }
1854 
1855 /**
1856  * query_editor_cut_clipboard
1857  * @editor: a #QueryEditor widget.
1858  *
1859  * Moves currently selected text in the given editor widget to the clipboard.
1860  */
1861 void
query_editor_cut_clipboard(QueryEditor * editor)1862 query_editor_cut_clipboard (QueryEditor *editor)
1863 {
1864 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1865 
1866 	gtk_text_buffer_cut_clipboard (gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text)),
1867                                        gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
1868 				       gtk_text_view_get_editable (GTK_TEXT_VIEW (editor->priv->text)));
1869 }
1870 
1871 /**
1872  * query_editor_paste_clipboard
1873  * @editor: a #QueryEditor widget.
1874  *
1875  * Paste clipboard contents into editor widget, at the current position.
1876  */
1877 void
query_editor_paste_clipboard(QueryEditor * editor)1878 query_editor_paste_clipboard (QueryEditor *editor)
1879 {
1880 	g_return_if_fail (QUERY_IS_EDITOR (editor));
1881 
1882 	gtk_text_buffer_paste_clipboard (gtk_text_view_get_buffer (GTK_TEXT_VIEW (editor->priv->text)),
1883                                          gtk_clipboard_get (GDK_SELECTION_CLIPBOARD),
1884                                          NULL,
1885                                          gtk_text_view_get_editable (GTK_TEXT_VIEW (editor->priv->text)));
1886 }
1887 
1888 /*
1889  * QueryEditorHistoryBatch
1890  */
1891 QueryEditorHistoryBatch *
query_editor_history_batch_new(GTimeVal run_time,GdaSet * params)1892 query_editor_history_batch_new (GTimeVal run_time, GdaSet *params)
1893 {
1894 	QueryEditorHistoryBatch *qib;
1895 
1896 	qib = g_new0 (QueryEditorHistoryBatch, 1);
1897 	qib->ref_count = 1;
1898 	qib->run_time = run_time;
1899 	if (params)
1900 		qib->params = gda_set_copy (params);
1901 
1902 #ifdef QUERY_EDITOR_DEBUG
1903 	g_print ("\t%s() => %p\n", __FUNCTION__, qib);
1904 #endif
1905 	return qib;
1906 }
1907 
1908 QueryEditorHistoryBatch *
query_editor_history_batch_ref(QueryEditorHistoryBatch * qib)1909 query_editor_history_batch_ref (QueryEditorHistoryBatch *qib)
1910 {
1911 	g_return_val_if_fail (qib, NULL);
1912 	qib->ref_count++;
1913 #ifdef QUERY_EDITOR_DEBUG
1914 	g_print ("%s (%p) ref count:%d\n", __FUNCTION__, qib, qib->ref_count);
1915 #endif
1916 	return qib;
1917 }
1918 
1919 void
query_editor_history_batch_unref(QueryEditorHistoryBatch * qib)1920 query_editor_history_batch_unref (QueryEditorHistoryBatch *qib)
1921 {
1922 	g_return_if_fail (qib);
1923 	qib->ref_count--;
1924 #ifdef QUERY_EDITOR_DEBUG
1925 	g_print ("%s (%p) ref count:%d\n", __FUNCTION__, qib, qib->ref_count);
1926 #endif
1927 	if (qib->ref_count <= 0) {
1928 		if (qib->hist_items) {
1929 			g_slist_foreach (qib->hist_items, (GFunc) query_editor_history_item_unref, NULL);
1930 			g_slist_free (qib->hist_items);
1931 		}
1932 		if (qib->params)
1933 			g_object_unref (qib->params);
1934 
1935 		g_free (qib);
1936 #ifdef QUERY_EDITOR_DEBUG
1937 		g_print ("\t%s() => %p\n", __FUNCTION__, qib);
1938 #endif
1939 	}
1940 #ifdef QUERY_EDITOR_DEBUG
1941 	else
1942 		g_print ("\tFAILED %s() => %p:%d\n", __FUNCTION__, qib, qib->ref_count);
1943 #endif
1944 }
1945 
1946 static void
query_editor_history_batch_add_item(QueryEditorHistoryBatch * qib,QueryEditorHistoryItem * qih)1947 query_editor_history_batch_add_item (QueryEditorHistoryBatch *qib, QueryEditorHistoryItem *qih)
1948 {
1949 	g_return_if_fail (qib);
1950 	g_return_if_fail (qih);
1951 	qib->hist_items = g_slist_append (qib->hist_items, query_editor_history_item_ref (qih));
1952 }
1953 
1954 static void
query_editor_history_batch_del_item(QueryEditor * editor,QueryEditorHistoryBatch * qib,QueryEditorHistoryItem * qih)1955 query_editor_history_batch_del_item (QueryEditor *editor, QueryEditorHistoryBatch *qib, QueryEditorHistoryItem *qih)
1956 {
1957 	g_return_if_fail (qib);
1958 	g_return_if_fail (qih);
1959 	qib->hist_items = g_slist_remove (qib->hist_items, qih);
1960 
1961 	g_signal_emit (editor, query_editor_signals[HISTORY_ITEM_REMOVED], 0, qih);
1962 	query_editor_history_item_unref (qih);
1963 }
1964 
1965 /*
1966  * QueryEditorHistoryItem
1967  */
1968 QueryEditorHistoryItem *
query_editor_history_item_new(const gchar * sql,GObject * result,GError * error)1969 query_editor_history_item_new (const gchar *sql, GObject *result, GError *error)
1970 {
1971 	QueryEditorHistoryItem *qih;
1972 
1973 	g_return_val_if_fail (sql, NULL);
1974 
1975 	qih = g_new0 (QueryEditorHistoryItem, 1);
1976 	qih->ref_count = 1;
1977 	qih->sql = g_strdup (sql);
1978 	if (result)
1979 		qih->result = g_object_ref (result);
1980 	if (error)
1981 		qih->exec_error = g_error_copy (error);
1982 
1983 #ifdef QUERY_EDITOR_DEBUG
1984 	g_print ("\t%s() => %p\n", __FUNCTION__, qih);
1985 #endif
1986 	return qih;
1987 }
1988 
1989 QueryEditorHistoryItem *
query_editor_history_item_ref(QueryEditorHistoryItem * qih)1990 query_editor_history_item_ref (QueryEditorHistoryItem *qih)
1991 {
1992 	g_return_val_if_fail (qih, NULL);
1993 	qih->ref_count++;
1994 #ifdef QUERY_EDITOR_DEBUG
1995 	g_print ("%s (%p) ref count:%d\n", __FUNCTION__, qih, qih->ref_count);
1996 #endif
1997 	return qih;
1998 }
1999 
2000 void
query_editor_history_item_unref(QueryEditorHistoryItem * qih)2001 query_editor_history_item_unref (QueryEditorHistoryItem *qih)
2002 {
2003 	g_return_if_fail (qih);
2004 	qih->ref_count--;
2005 #ifdef QUERY_EDITOR_DEBUG
2006 	g_print ("%s (%p) ref count:%d\n", __FUNCTION__, qih, qih->ref_count);
2007 #endif
2008 	if (qih->ref_count <= 0) {
2009 		g_free (qih->sql);
2010 		if (qih->result)
2011 			g_object_unref (qih->result);
2012 		if (qih->exec_error)
2013 			g_error_free (qih->exec_error);
2014 		g_free (qih);
2015 #ifdef QUERY_EDITOR_DEBUG
2016 		g_print ("\t%s() => %p\n", __FUNCTION__, qih);
2017 #endif
2018 	}
2019 #ifdef QUERY_EDITOR_DEBUG
2020 	else
2021 		g_print ("\tFAILED %s() => %p:%d\n", __FUNCTION__, qih, qih->ref_count);
2022 #endif
2023 }
2024 
2025 /*
2026  * HistItemData
2027  */
2028 static HistItemData *
hist_item_data_new(void)2029 hist_item_data_new (void)
2030 {
2031 	HistItemData *hdata;
2032 	hdata = g_new0 (HistItemData, 1);
2033 	hdata->ref_count = 1;
2034 	return hdata;
2035 }
2036 
2037 static HistItemData *
hist_item_data_ref(HistItemData * hdata)2038 hist_item_data_ref (HistItemData *hdata)
2039 {
2040 	g_return_val_if_fail (hdata, NULL);
2041 	hdata->ref_count ++;
2042 #ifdef QUERY_EDITOR_DEBUG
2043 	g_print ("%s (%p) ref count:%d\n", __FUNCTION__, hdata, hdata->ref_count);
2044 #endif
2045 	return hdata;
2046 }
2047 
2048 static void
hist_item_data_unref(HistItemData * hdata)2049 hist_item_data_unref (HistItemData *hdata)
2050 {
2051 	g_return_if_fail (hdata);
2052 	hdata->ref_count --;
2053 #ifdef QUERY_EDITOR_DEBUG
2054 	g_print ("%s (%p) ref count:%d\n", __FUNCTION__, hdata, hdata->ref_count);
2055 #endif
2056 	if (hdata->ref_count <= 0) {
2057 		if (hdata->batch)
2058 			query_editor_history_batch_unref (hdata->batch);
2059 		if (hdata->item)
2060 			query_editor_history_item_unref (hdata->item);
2061 		if (hdata->tag)
2062 			g_object_unref (hdata->tag);
2063 		g_free (hdata);
2064 	}
2065 }
2066