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 <string.h>
22 #include <glib/gi18n-lib.h>
23 #include <libgda/binreloc/gda-binreloc.h>
24 #include "browser-connections-list.h"
25 #include "browser-core.h"
26 #include "browser-window.h"
27 #include "browser-connection.h"
28 #include <libgda-ui/gdaui-login.h>
29 #include "support.h"
30 #include "login-dialog.h"
31 
32 /*
33  * Main static functions
34  */
35 static void browser_connections_list_class_init (BrowserConnectionsListClass *klass);
36 static void browser_connections_list_init (BrowserConnectionsList *stmt);
37 static void browser_connections_list_dispose (GObject *object);
38 
39 static void connection_added_cb (BrowserCore *bcore, BrowserConnection *bcnc, BrowserConnectionsList *clist);
40 static void connection_removed_cb (BrowserCore *bcore, BrowserConnection *bcnc, BrowserConnectionsList *clist);
41 
42 struct _BrowserConnectionsListPrivate
43 {
44 	GtkGrid     *layout_grid;
45 	GtkTreeView *treeview;
46 	gulong       cnc_added_sigid;
47 	gulong       cnc_removed_sigid;
48 
49 	GtkWidget   *cnc_params_editor;
50 
51 	GtkWidget   *close_cnc_button;
52 };
53 
54 /* get a pointer to the parents to be able to call their destructor */
55 static GObjectClass  *parent_class = NULL;
56 static BrowserConnectionsList *_clist = NULL;
57 
58 GType
browser_connections_list_get_type(void)59 browser_connections_list_get_type (void)
60 {
61 	static GType type = 0;
62 
63 	if (G_UNLIKELY (type == 0)) {
64 		static GMutex registering;
65 		static const GTypeInfo info = {
66 			sizeof (BrowserConnectionsListClass),
67 			(GBaseInitFunc) NULL,
68 			(GBaseFinalizeFunc) NULL,
69 			(GClassInitFunc) browser_connections_list_class_init,
70 			NULL,
71 			NULL,
72 			sizeof (BrowserConnectionsList),
73 			0,
74 			(GInstanceInitFunc) browser_connections_list_init,
75 			0
76 		};
77 
78 		g_mutex_lock (&registering);
79 		if (type == 0)
80 			type = g_type_register_static (GTK_TYPE_WINDOW, "BrowserConnectionsList", &info, 0);
81 		g_mutex_unlock (&registering);
82 	}
83 	return type;
84 }
85 
86 static void
browser_connections_list_class_init(BrowserConnectionsListClass * klass)87 browser_connections_list_class_init (BrowserConnectionsListClass * klass)
88 {
89 	GObjectClass   *object_class = G_OBJECT_CLASS (klass);
90 	parent_class = g_type_class_peek_parent (klass);
91 
92 	object_class->dispose = browser_connections_list_dispose;
93 }
94 
95 static void
browser_connections_list_init(BrowserConnectionsList * clist)96 browser_connections_list_init (BrowserConnectionsList *clist)
97 {
98 	clist->priv = g_new0 (BrowserConnectionsListPrivate, 1);
99 	clist->priv->treeview = NULL;
100 	clist->priv->cnc_params_editor = NULL;
101 	clist->priv->cnc_added_sigid = 0;
102 	clist->priv->cnc_removed_sigid = 0;
103 }
104 
105 static void
browser_connections_list_dispose(GObject * object)106 browser_connections_list_dispose (GObject *object)
107 {
108 	BrowserConnectionsList *clist;
109 
110 	g_return_if_fail (object != NULL);
111 	g_return_if_fail (BROWSER_IS_CONNECTIONS_LIST (object));
112 
113 	clist = BROWSER_CONNECTIONS_LIST (object);
114 
115 	if (clist->priv) {
116 		if (clist->priv->cnc_added_sigid > 0)
117 			g_signal_handler_disconnect (browser_core_get (), clist->priv->cnc_added_sigid);
118 		if (clist->priv->cnc_removed_sigid > 0)
119 			g_signal_handler_disconnect (browser_core_get (), clist->priv->cnc_removed_sigid);
120 
121 		g_free (clist->priv);
122 		clist->priv = NULL;
123 	}
124 
125 	/* parent class */
126 	parent_class->dispose (object);
127 }
128 
129 enum
130 {
131 	COLUMN_BCNC,
132 	NUM_COLUMNS
133 };
134 
135 static gboolean
delete_event(GtkWidget * widget,G_GNUC_UNUSED GdkEvent * event,G_GNUC_UNUSED gpointer data)136 delete_event (GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, G_GNUC_UNUSED gpointer data)
137 {
138 	gtk_widget_hide (widget);
139 	return TRUE;
140 }
141 
cell_name_data_func(G_GNUC_UNUSED GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,G_GNUC_UNUSED gpointer data)142 static void cell_name_data_func (G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
143 				 GtkCellRenderer *cell,
144 				 GtkTreeModel *tree_model,
145 				 GtkTreeIter *iter,
146 				 G_GNUC_UNUSED gpointer data)
147 {
148 	BrowserConnection *bcnc;
149 	gchar *str, *cncstr = NULL;
150 	gchar *markup;
151 	const GdaDsnInfo *cncinfo;
152 
153 	gtk_tree_model_get (tree_model, iter, COLUMN_BCNC, &bcnc, -1);
154 	cncinfo = browser_connection_get_information (bcnc);
155 	if (cncinfo) {
156 		if (cncinfo->name)
157 			cncstr = g_strdup_printf (_("DSN: %s"), cncinfo->name);
158 		else if (cncinfo->provider)
159 			cncstr = g_strdup_printf (_("Provider: %s"), cncinfo->provider);
160 	}
161 
162 	markup = g_markup_escape_text (browser_connection_get_name (bcnc), -1);
163 	if (cncstr)
164 		str = g_strdup_printf ("%s\n<small>%s</small>",
165 				       markup, cncstr);
166 	else
167 		str = g_strdup (markup);
168 	g_free (cncstr);
169 	g_free (markup);
170 
171 	g_object_set ((GObject*) cell, "markup", str, NULL);
172 	g_free (str);
173 	g_object_unref (bcnc);
174 }
175 
176 static void
selection_changed_cb(GtkTreeSelection * select,BrowserConnectionsList * clist)177 selection_changed_cb (GtkTreeSelection *select, BrowserConnectionsList *clist)
178 {
179 	GtkTreeModel *model;
180 	GtkTreeIter iter;
181 	BrowserConnection *bcnc = NULL;
182 	const GdaDsnInfo *cncinfo = NULL;
183 
184 	if (gtk_tree_selection_get_selected (select, &model, &iter)) {
185 		gtk_tree_model_get (model, &iter, COLUMN_BCNC, &bcnc, -1);
186 		cncinfo = browser_connection_get_information (bcnc);
187 		g_object_unref (bcnc);
188 
189 		gtk_widget_set_sensitive (_clist->priv->close_cnc_button, TRUE);
190 	}
191 	else
192 		gtk_widget_set_sensitive (_clist->priv->close_cnc_button, FALSE);
193 
194 	if (clist->priv->cnc_params_editor) {
195 		gtk_widget_destroy (clist->priv->cnc_params_editor);
196 		clist->priv->cnc_params_editor = NULL;
197 	}
198 
199 	if (cncinfo && cncinfo->provider) {
200 		/* create GdaSet for parameters to display */
201 		GdaSet *dset;
202 		GdaHolder *holder;
203 		dset = gda_set_new_inline (1, "PROVIDER_NAME", G_TYPE_STRING, cncinfo->provider);
204 		holder = GDA_HOLDER (dset->holders->data);
205 		g_object_set (G_OBJECT (holder), "name", _("Database provider"), NULL);
206 
207 		GdaProviderInfo *pinfo;
208 		pinfo = gda_config_get_provider_info (cncinfo->provider);
209 		if (pinfo && pinfo->dsn_params)
210 			gda_set_merge_with_set (dset, pinfo->dsn_params);
211 
212 		holder = gda_holder_new_inline (G_TYPE_STRING, "GDA_BROWSER_DICT_FILE", _("In memory"));
213 		g_object_set (G_OBJECT (holder),
214 			      "name", _("Dictionary file"),
215 			      "description", _("File used to store any information associated\n"
216 					       "to this connection (favorites, descriptions, ...)"), NULL);
217 		gda_set_add_holder (dset, holder);
218 		g_object_unref (holder);
219 		if (bcnc) {
220 			const gchar *dict_file_name;
221 			dict_file_name = browser_connection_get_dictionary_file (bcnc);
222 
223 			if (dict_file_name)
224 				gda_set_set_holder_value (dset, NULL, "GDA_BROWSER_DICT_FILE",
225 							  dict_file_name);
226 		}
227 
228 		/* create form */
229 		GtkWidget *wid;
230 		wid = gdaui_basic_form_new (dset);
231 		g_object_set ((GObject*) wid, "show-actions", FALSE, NULL);
232 		gdaui_basic_form_entry_set_editable (GDAUI_BASIC_FORM (wid), NULL, FALSE);
233 		gtk_grid_attach (clist->priv->layout_grid, wid, 1, 2, 1, 1);
234 		gtk_widget_show (wid);
235 		clist->priv->cnc_params_editor = wid;
236 
237 		/* fill GdaSet's parameters with values */
238 		if (cncinfo->cnc_string) {
239                         gchar **array = NULL;
240 
241                         array = g_strsplit (cncinfo->cnc_string, ";", 0);
242                         if (array) {
243                                 gint index = 0;
244                                 gchar *tok;
245                                 gchar *value;
246                                 gchar *name;
247 
248                                 for (index = 0; array[index]; index++) {
249                                         name = strtok_r (array [index], "=", &tok);
250                                         if (name)
251                                                 value = strtok_r (NULL, "=", &tok);
252                                         else
253                                                 value = NULL;
254                                         if (name && value) {
255                                                 GdaHolder *param;
256                                                 gda_rfc1738_decode (name);
257                                                 gda_rfc1738_decode (value);
258 
259                                                 param = gda_set_get_holder (dset, name);
260                                                 if (param)
261                                                         g_assert (gda_holder_set_value_str (param, NULL, value, NULL));
262                                         }
263                                 }
264 
265                                 g_strfreev (array);
266                         }
267                 }
268 
269 		g_object_unref (dset);
270 	}
271 }
272 
273 static void
connection_close_cb(G_GNUC_UNUSED GtkButton * button,BrowserConnectionsList * clist)274 connection_close_cb (G_GNUC_UNUSED GtkButton *button, BrowserConnectionsList *clist)
275 {
276 	GtkTreeSelection *select;
277 	GtkTreeModel *model;
278 	GtkTreeIter iter;
279 
280 	select = gtk_tree_view_get_selection (clist->priv->treeview);
281 	if (gtk_tree_selection_get_selected (select, &model, &iter)) {
282 		BrowserConnection *bcnc;
283 		gtk_tree_model_get (model, &iter, COLUMN_BCNC, &bcnc, -1);
284 		g_object_unref (bcnc);
285 		browser_connection_close (NULL, bcnc);
286 	}
287 }
288 
289 static void
connection_new_cb(G_GNUC_UNUSED GtkButton * button,G_GNUC_UNUSED BrowserConnectionsList * clist)290 connection_new_cb (G_GNUC_UNUSED GtkButton *button, G_GNUC_UNUSED BrowserConnectionsList *clist)
291 {
292 	LoginDialog *dialog;
293 	GdaConnection *cnc;
294 	GError *error = NULL;
295 	dialog = login_dialog_new (NULL);
296 
297 	cnc = login_dialog_run (dialog, TRUE, &error);
298 	if (cnc) {
299 		BrowserConnection *bcnc;
300 		BrowserWindow *nbwin;
301 		bcnc = browser_connection_new (cnc);
302 		nbwin = browser_window_new (bcnc, NULL);
303 
304 		browser_core_take_window (nbwin);
305 		browser_core_take_connection (bcnc);
306 	}
307 }
308 
309 /**
310  * browser_connections_list_show
311  * @current: (allow-none): a connection to select for displaed properties, or %NULL
312  *
313  * Creates a new #BrowserConnectionsList widget and displays it.
314  * Only one is created and shown (singleton)
315  *
316  * Returns: the new object
317  */
318 void
browser_connections_list_show(BrowserConnection * current)319 browser_connections_list_show (BrowserConnection *current)
320 {
321 	if (!_clist) {
322 		GtkWidget *clist, *sw, *grid, *treeview, *label, *wid;
323 		gchar *str;
324 		clist = GTK_WIDGET (g_object_new (BROWSER_TYPE_CONNECTIONS_LIST,
325 						  NULL));
326 		gtk_window_set_default_size ((GtkWindow*) clist, 550, 450);
327 		_clist = (BrowserConnectionsList *) clist;
328 		gtk_window_set_title (GTK_WINDOW (clist), _("Opened connections"));
329 		gtk_container_set_border_width (GTK_CONTAINER (clist), 6);
330 		g_signal_connect (G_OBJECT (clist), "delete-event",
331 				  G_CALLBACK (delete_event), NULL);
332 
333 		str = gda_gbr_get_file_path (GDA_DATA_DIR, LIBGDA_ABI_NAME, "pixmaps", "gda-browser-connected.png", NULL);
334 		gtk_window_set_icon_from_file (GTK_WINDOW (clist), str, NULL);
335 		g_free (str);
336 
337 		/* table layout */
338 		grid = gtk_grid_new ();
339 		gtk_grid_set_column_spacing (GTK_GRID (grid), 10);
340 		gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
341 		gtk_container_add (GTK_CONTAINER (clist), grid);
342 		_clist->priv->layout_grid = GTK_GRID (grid);
343 
344 		/* image and explaining label */
345 		GtkWidget *hbox;
346 		hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
347 		gtk_grid_attach (GTK_GRID (grid), hbox, 0, 0, 3, 1);
348 
349 		str = gda_gbr_get_file_path (GDA_DATA_DIR, LIBGDA_ABI_NAME, "pixmaps", "gda-browser-connected-big.png", NULL);
350 		wid = gtk_image_new_from_file (str);
351 		g_free (str);
352 		gtk_box_pack_start (GTK_BOX (hbox), wid, FALSE, FALSE, 0);
353 
354 		wid = gtk_label_new ("");
355 		str = g_strdup_printf ("<big><b>%s:\n</b></big>%s",
356 				       _("List of opened connections"),
357 				       "The connection properties are read-only.");
358 		gtk_label_set_markup (GTK_LABEL (wid), str);
359 		g_free (str);
360 		gtk_misc_set_alignment (GTK_MISC (wid), 0., -1);
361 		gtk_box_pack_start (GTK_BOX (hbox), wid, TRUE, FALSE, 6);
362 
363 		/* left column */
364 		label = gtk_label_new ("");
365 		str = g_strdup_printf ("<b>%s:</b>", _("Connections"));
366 		gtk_label_set_markup (GTK_LABEL (label), str);
367 		g_free (str);
368 		gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
369 		gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1);
370 
371 		sw = gtk_scrolled_window_new (NULL, NULL);
372 		gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
373 						     GTK_SHADOW_ETCHED_IN);
374 		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
375 						GTK_POLICY_NEVER,
376 						GTK_POLICY_AUTOMATIC);
377 		gtk_grid_attach (GTK_GRID (grid), sw, 0, 2, 1, 2);
378 
379 		/* connection's properties */
380 		label = gtk_label_new ("");
381 		str = g_strdup_printf ("<b>%s:</b>", _("Connection's properties"));
382 		gtk_label_set_markup (GTK_LABEL (label), str);
383 		g_free (str);
384 		gtk_misc_set_alignment (GTK_MISC (label), 0., -1);
385 		gtk_grid_attach (GTK_GRID (grid), label, 1, 1, 1, 1);
386 
387 		/* buttons at the bottom*/
388 		GtkWidget *bbox, *button;
389 		bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
390 		gtk_grid_attach (GTK_GRID (grid), bbox, 1, 3, 1, 1);
391 		gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
392 		button = gtk_button_new_with_label (_("Close connection"));
393 		gtk_box_pack_start (GTK_BOX (bbox), button, TRUE, TRUE, 0);
394 		g_signal_connect (button, "clicked",
395 				  G_CALLBACK (connection_close_cb), clist);
396 		gtk_widget_set_tooltip_text (button, _("Close selected connection"));
397 		_clist->priv->close_cnc_button = button;
398 
399 		button = gtk_button_new_with_label (_("Connect"));
400 		gtk_box_pack_start (GTK_BOX (bbox), button, TRUE, TRUE, 0);
401 		g_signal_connect (button, "clicked",
402 				  G_CALLBACK (connection_new_cb), clist);
403 		gtk_widget_set_tooltip_text (button, _("Open a new connection"));
404 
405 		/* GtkTreeModel and view */
406 		GtkListStore *store;
407 		store = gtk_list_store_new (NUM_COLUMNS,
408 					    BROWSER_TYPE_CONNECTION);
409 
410 		treeview = browser_make_tree_view (GTK_TREE_MODEL (store));
411 		_clist->priv->treeview = GTK_TREE_VIEW (treeview);
412 		gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
413 		gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (treeview), TRUE);
414 		g_object_unref (G_OBJECT (store));
415 		gtk_container_add (GTK_CONTAINER (sw), treeview);
416 
417 		/* treeview's columns */
418 		GtkTreeViewColumn *col;
419 		GtkCellRenderer *cell;
420 		cell = gtk_cell_renderer_text_new ();
421 		col = gtk_tree_view_column_new ();
422 		gtk_tree_view_column_pack_start (col, cell, TRUE);
423 		gtk_tree_view_column_set_cell_data_func (col, cell,
424 							 (GtkTreeCellDataFunc) cell_name_data_func, NULL, NULL);
425 		gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), col);
426 
427 		/* selection handling */
428 		GtkTreeSelection *select;
429 		select = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
430 		gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE);
431 		g_signal_connect (G_OBJECT (select), "changed",
432 				  G_CALLBACK (selection_changed_cb), clist);
433 
434 
435 		/* initial filling */
436 		GSList *connections, *list;
437 		connections =  browser_core_get_connections ();
438 		for (list = connections; list; list = list->next)
439 			connection_added_cb (browser_core_get(), BROWSER_CONNECTION (list->data),
440 					     (BrowserConnectionsList*) clist);
441 		g_slist_free (connections);
442 
443 		_clist->priv->cnc_added_sigid = g_signal_connect (browser_core_get (), "connection-added",
444 								  G_CALLBACK (connection_added_cb), _clist);
445 		_clist->priv->cnc_removed_sigid = g_signal_connect (browser_core_get (), "connection-removed",
446 								    G_CALLBACK (connection_removed_cb), _clist);
447 
448 		gtk_widget_show_all (clist);
449 	}
450 	else {
451 		gtk_window_set_screen (GTK_WINDOW (_clist), gdk_screen_get_default ()); /* FIXME: specify GdkScreen */
452 		gtk_window_present (GTK_WINDOW (_clist));
453 	}
454 
455 	if (current) {
456 		GtkTreeModel *model;
457 		GtkTreeIter iter;
458 		model = gtk_tree_view_get_model (GTK_TREE_VIEW (_clist->priv->treeview));
459 		if (gtk_tree_model_get_iter_first (model, &iter)) {
460 			do {
461 				BrowserConnection *bcnc;
462 				gtk_tree_model_get (model, &iter, COLUMN_BCNC, &bcnc, -1);
463 				g_object_unref (bcnc);
464 				if (bcnc == current) {
465 					GtkTreeSelection *select;
466 					select = gtk_tree_view_get_selection (GTK_TREE_VIEW (_clist->priv->treeview));
467 					gtk_tree_selection_select_iter (select, &iter);
468 					break;
469 				}
470 			} while (gtk_tree_model_iter_next (model, &iter));
471 		}
472 	}
473 	else {
474 		/* select the 1st available */
475 		GtkTreeModel *model;
476 		GtkTreeIter iter;
477 		model = gtk_tree_view_get_model (GTK_TREE_VIEW (_clist->priv->treeview));
478 		if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter)) {
479 			GtkTreeSelection *select;
480                         select = gtk_tree_view_get_selection (GTK_TREE_VIEW (_clist->priv->treeview));
481 			gtk_tree_selection_select_iter (select, &iter);
482 		}
483 	}
484 }
485 
486 static void
connection_added_cb(G_GNUC_UNUSED BrowserCore * bcore,BrowserConnection * bcnc,BrowserConnectionsList * clist)487 connection_added_cb (G_GNUC_UNUSED BrowserCore *bcore, BrowserConnection *bcnc, BrowserConnectionsList *clist)
488 {
489 	GtkListStore *store;
490 	GtkTreeIter iter;
491 
492 	store = GTK_LIST_STORE (gtk_tree_view_get_model (clist->priv->treeview));
493 	gtk_list_store_append (store, &iter);
494 	gtk_list_store_set (store, &iter,
495 			    COLUMN_BCNC, bcnc, -1);
496 }
497 
498 static void
connection_removed_cb(G_GNUC_UNUSED BrowserCore * bcore,BrowserConnection * bcnc,BrowserConnectionsList * clist)499 connection_removed_cb (G_GNUC_UNUSED BrowserCore *bcore, BrowserConnection *bcnc, BrowserConnectionsList *clist)
500 {
501 	GtkTreeModel *model;
502 	GtkTreeIter iter;
503 	GtkTreeSelection *select;
504 	gboolean is_selected_item = FALSE;
505 
506 	select = gtk_tree_view_get_selection (clist->priv->treeview);
507 	model = gtk_tree_view_get_model (clist->priv->treeview);
508 	g_assert (gtk_tree_model_get_iter_first (model, &iter));
509 
510 	do {
511 		BrowserConnection *mbcnc;
512 		gtk_tree_model_get (model, &iter, COLUMN_BCNC, &mbcnc, -1);
513 		g_object_unref (mbcnc);
514 		if (mbcnc == bcnc) {
515 			is_selected_item = gtk_tree_selection_iter_is_selected (select, &iter);
516 			gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
517 			break;
518 		}
519 	} while (gtk_tree_model_iter_next (model, &iter));
520 
521 	/* select the 1st available */
522 	if (is_selected_item && gtk_tree_model_get_iter_first (model, &iter))
523 		gtk_tree_selection_select_iter (select, &iter);
524 }
525