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);
execution_batch_free(ExecutionBatch * ebatch)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;
execution_statement_free(ExecutionStatement * estmt)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
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
query_console_page_class_init(QueryConsolePageClass * klass)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 */
query_console_page_show_all(GtkWidget * widget)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) {
query_console_page_page_init(BrowserPageIface * iface)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)
query_console_page_init(QueryConsolePage * tconsole,G_GNUC_UNUSED QueryConsolePageClass * klass)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_console_page_dispose(GObject * object)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
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
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 }
query_console_page_new(BrowserConnection * bcnc)279
280 static gchar *
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
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
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
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
connection_busy_cb(G_GNUC_UNUSED BrowserConnection * bcnc,gboolean is_busy,G_GNUC_UNUSED gchar * reason,QueryConsolePage * tconsole)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
history_changed_cb(G_GNUC_UNUSED QueryEditor * history,QueryConsolePage * tconsole)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);
history_clear_clicked_cb(G_GNUC_UNUSED GtkButton * button,QueryConsolePage * tconsole)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) &&
history_copy_clicked_cb(G_GNUC_UNUSED GtkButton * button,QueryConsolePage * tconsole)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);
params_form_activated_cb(GdauiBasicForm * form,QueryConsolePage * tconsole)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
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);
editor_changed_cb(G_GNUC_UNUSED QueryEditor * editor,QueryConsolePage * tconsole)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,
editor_execute_request_cb(G_GNUC_UNUSED QueryEditor * editor,QueryConsolePage * tconsole)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);
sql_variables_clicked_cb(GtkToggleButton * button,QueryConsolePage * tconsole)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;
sql_clear_clicked_cb(G_GNUC_UNUSED GtkButton * button,QueryConsolePage * tconsole)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
sql_indent_clicked_cb(G_GNUC_UNUSED GtkButton * button,QueryConsolePage * tconsole)662 static void
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
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),
sql_favorite_clicked_cb(G_GNUC_UNUSED GtkButton * button,QueryConsolePage * tconsole)700 editor);
701 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mitem);
702 gtk_widget_show (mitem);
703 }
704
705 static void
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",
fav_form_name_activated_cb(G_GNUC_UNUSED GtkWidget * form,GtkWidget * dlg)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",
sql_favorite_new_mitem_cb(G_GNUC_UNUSED GtkMenuItem * mitem,QueryConsolePage * tconsole)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
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
829 query_editor_grab_focus (GtkWidget *widget)
830 {
831 gtk_widget_grab_focus (QUERY_EDITOR (widget)->priv->text);
sql_favorite_modify_mitem_cb(G_GNUC_UNUSED GtkMenuItem * mitem,QueryConsolePage * tconsole)832 }
833
834
835 static void
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
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)
params_form_changed_cb(GdauiBasicForm * form,G_GNUC_UNUSED GdaHolder * param,G_GNUC_UNUSED gboolean is_user_modif,QueryConsolePage * tconsole)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 }
sql_execute_clicked_cb(G_GNUC_UNUSED GtkButton * button,QueryConsolePage * tconsole)898
899 GType
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 *
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
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
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
query_exec_done_cb(BrowserConnection * bcnc,guint exec_id,GObject * out_result,GdaSet * out_last_inserted_row,GError * error,QueryConsolePage * tconsole)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
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
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
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
actually_execute(QueryConsolePage * tconsole,const gchar * sql,GdaSet * params,gboolean add_editor_history)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
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
1171 query_editor_show_tooltip (QueryEditor *editor, gboolean show_tooltip)
1172 {
1173 g_return_if_fail (QUERY_IS_EDITOR (editor));
rerun_requested_cb(QueryResult * qres,QueryEditorHistoryBatch * batch,QueryEditorHistoryItem * item,QueryConsolePage * tconsole)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
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);
query_console_page_set_text(QueryConsolePage * console,const gchar * text,gint fav_id)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;
query_execute_cb(G_GNUC_UNUSED GtkAction * action,QueryConsolePage * tconsole)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),
editor_undo_cb(G_GNUC_UNUSED GtkAction * action,G_GNUC_UNUSED QueryConsolePage * tconsole)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 *
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);
query_console_page_page_get_actions_group(BrowserPage * page)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) {
query_console_page_page_get_actions_ui(G_GNUC_UNUSED BrowserPage * page)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;
query_console_page_page_get_tab_label(BrowserPage * page,GtkWidget ** out_close_button)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 *
query_console_page_grab_focus(GtkWidget * widget)1278 * @hist_hash ref is _NOT_ stolen here
1279 */
1280 void
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
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
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 *
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 *
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
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
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
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
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 *
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 *
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 *
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
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
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
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
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
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 *
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 *
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
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
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
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 *
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 *
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
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 *
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 *
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
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