1 /* 2 * Copyright (C) 2009 - 2012 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 * Copyright (C) 2012 Daniel Mustieles <daniel.mustieles@gmail.com> 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public main(int,char **)18 * License along with this library; if not, write to the 19 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, 20 * Boston, MA 02110-1301, USA. 21 */ 22 23 #include <string.h> 24 #include <glib/gi18n-lib.h> 25 #include <libgda/libgda.h> 26 #include "gdaui-data-proxy.h" 27 #include "gdaui-data-filter.h" 28 29 static void gdaui_data_filter_class_init (GdauiDataFilterClass * class); 30 static void gdaui_data_filter_init (GdauiDataFilter *wid); 31 static void gdaui_data_filter_dispose (GObject *object); 32 33 static void gdaui_data_filter_set_property (GObject *object, 34 guint param_id, 35 const GValue *value, 36 GParamSpec *pspec); 37 static void gdaui_data_filter_get_property (GObject *object, 38 guint param_id, 39 GValue *value, 40 GParamSpec *pspec); 41 42 43 /* callbacks */ 44 static void proxy_filter_changed_cb (GdaDataProxy *proxy, GdauiDataFilter *filter); 45 static void release_proxy (GdauiDataFilter *filter); 46 static void data_widget_destroyed_cb (GdauiDataProxy *wid, GdauiDataFilter *filter); 47 static void data_widget_proxy_changed_cb (GdauiDataProxy *data_widget, 48 GdaDataProxy *proxy, GdauiDataFilter *filter); 49 50 static void clear_filter_cb (GtkButton *button, GdauiDataFilter *filter); 51 static void apply_filter_cb (GtkButton *button, GdauiDataFilter *filter); 52 53 struct _GdauiDataFilterPriv 54 { 55 GdauiDataProxy *data_widget; 56 GdaDataProxy *proxy; 57 58 GtkWidget *filter_entry; 59 GtkWidget *notice; 60 }; 61 62 /* get a pointer to the parents to be able to call their destructor */ 63 static GObjectClass *parent_class = NULL; 64 65 /* properties */ 66 enum { 67 PROP_0, 68 PROP_DATA_WIDGET 69 }; 70 71 GType 72 gdaui_data_filter_get_type (void) 73 { 74 static GType type = 0; 75 76 if (G_UNLIKELY (type == 0)) { 77 static const GTypeInfo filter = { 78 sizeof (GdauiDataFilterClass), 79 (GBaseInitFunc) NULL, 80 (GBaseFinalizeFunc) NULL, 81 (GClassInitFunc) gdaui_data_filter_class_init, 82 NULL, 83 NULL, 84 sizeof (GdauiDataFilter), 85 0, 86 (GInstanceInitFunc) gdaui_data_filter_init, 87 0 88 }; 89 90 type = g_type_register_static (GTK_TYPE_BOX, "GdauiDataFilter", &filter, 0); 91 } 92 93 return type; 94 } 95 96 static void 97 gdaui_data_filter_class_init (GdauiDataFilterClass * class) 98 { 99 GObjectClass *object_class = G_OBJECT_CLASS (class); 100 101 parent_class = g_type_class_peek_parent (class); 102 103 object_class->dispose = gdaui_data_filter_dispose; 104 105 /* Properties */ 106 object_class->set_property = gdaui_data_filter_set_property; 107 object_class->get_property = gdaui_data_filter_get_property; 108 g_object_class_install_property (object_class, PROP_DATA_WIDGET, 109 g_param_spec_object ("data-widget", NULL, NULL, GDAUI_TYPE_DATA_PROXY, 110 G_PARAM_READABLE | G_PARAM_WRITABLE)); 111 } 112 113 static void 114 set_wait_cursor (GtkWidget *w) 115 { 116 GtkWidget *parent; 117 118 parent = gtk_widget_get_toplevel (w); 119 if (parent) { 120 GdkCursor* cursor; 121 cursor = gdk_cursor_new (GDK_WATCH); 122 gdk_window_set_cursor (gtk_widget_get_window (parent), cursor); 123 g_object_unref (cursor); 124 } 125 } 126 127 static void 128 unset_wait_cursor (GtkWidget *w) 129 { 130 GtkWidget *parent; 131 132 parent = gtk_widget_get_toplevel (w); 133 if (parent) 134 gdk_window_set_cursor (gtk_widget_get_window (parent), NULL); 135 } 136 137 static void 138 apply_filter_cb (G_GNUC_UNUSED GtkButton *button, GdauiDataFilter *filter) 139 { 140 const gchar *expr; 141 gchar *err = NULL; 142 143 expr = gtk_entry_get_text (GTK_ENTRY (filter->priv->filter_entry)); 144 if (expr && !*expr) 145 expr = NULL; 146 147 gtk_widget_hide (filter->priv->notice); 148 if (filter->priv->proxy) { 149 GError *error = NULL; 150 151 g_signal_handlers_block_by_func (G_OBJECT (filter->priv->proxy), 152 G_CALLBACK (proxy_filter_changed_cb), filter); 153 set_wait_cursor ((GtkWidget*) filter); 154 while (g_main_context_pending (NULL)) 155 g_main_context_iteration (NULL, FALSE); 156 157 if (!gda_data_proxy_set_filter_expr (filter->priv->proxy, expr, &error)) { 158 if (error && error->message) 159 err = g_strdup (error->message); 160 else 161 err = g_strdup (_("No detail")); 162 if (error) 163 g_error_free (error); 164 } 165 166 unset_wait_cursor ((GtkWidget*) filter); 167 g_signal_handlers_unblock_by_func (G_OBJECT (filter->priv->proxy), 168 G_CALLBACK (proxy_filter_changed_cb), filter); 169 } 170 171 if (err) { 172 gchar *esc, *markup; 173 174 esc = g_markup_escape_text (err, -1); 175 markup = g_strdup_printf ("<small><span foreground=\"#FF0000\"><b>%s</b>: %s</span></small>", 176 _("Filter failed:"), esc); 177 g_free (esc); 178 gtk_label_set_line_wrap (GTK_LABEL (filter->priv->notice), TRUE); 179 gtk_label_set_line_wrap_mode (GTK_LABEL (filter->priv->notice), PANGO_WRAP_WORD); 180 gtk_label_set_selectable (GTK_LABEL (filter->priv->notice), TRUE); 181 gtk_label_set_markup (GTK_LABEL (filter->priv->notice), markup); 182 g_free (markup); 183 gtk_widget_show (filter->priv->notice); 184 } 185 } 186 187 static void 188 clear_filter_cb (GtkButton *button, GdauiDataFilter *filter) 189 { 190 gtk_entry_set_text (GTK_ENTRY (filter->priv->filter_entry), ""); 191 apply_filter_cb (button, filter); 192 } 193 194 static void 195 gdaui_data_filter_init (GdauiDataFilter * wid) 196 { 197 GtkWidget *grid, *label, *entry, *button, *bbox; 198 gchar *str; 199 200 wid->priv = g_new0 (GdauiDataFilterPriv, 1); 201 wid->priv->data_widget = NULL; 202 wid->priv->proxy = NULL; 203 204 gtk_orientable_set_orientation (GTK_ORIENTABLE (wid), GTK_ORIENTATION_VERTICAL); 205 206 grid = gtk_grid_new (); 207 gtk_box_pack_start (GTK_BOX (wid), grid, TRUE, TRUE, 0); 208 209 label = gtk_label_new (""); 210 str = g_strdup_printf ("<b>%s</b>\n(<small>%s</small>):", _("Filter"), _("any valid SQL expression")); 211 gtk_label_set_markup (GTK_LABEL (label), str); 212 g_free (str); 213 214 gtk_widget_set_tooltip_markup (label, _("Columns can be referenced by their name or more easily " 215 "using <b><tt>_<column number></tt></b>. For example a valid " 216 "expression can be: <b><tt>_2 like 'doe%'</tt></b> " 217 "to filter rows where the 2nd column starts with <tt>doe</tt>.")); 218 219 gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1); 220 entry = gtk_entry_new (); 221 gtk_grid_attach (GTK_GRID (grid), entry, 1, 0, 1, 1); 222 g_signal_connect (G_OBJECT (entry), "activate", 223 G_CALLBACK (apply_filter_cb), wid); 224 225 label = gtk_label_new (""); 226 wid->priv->notice = label; 227 gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 2, 1); 228 229 bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); 230 gtk_grid_attach (GTK_GRID (grid), bbox, 0, 2, 2, 1); 231 button = gtk_button_new_with_label (_("Set filter")); 232 gtk_container_add (GTK_CONTAINER (bbox), button); 233 g_signal_connect (G_OBJECT (button), "clicked", 234 G_CALLBACK (apply_filter_cb), wid); 235 236 button = gtk_button_new_with_label (_("Clear filter")); 237 gtk_container_add (GTK_CONTAINER (bbox), button); 238 g_signal_connect (G_OBJECT (button), "clicked", 239 G_CALLBACK (clear_filter_cb), wid); 240 241 gtk_widget_show_all (grid); 242 gtk_widget_hide (wid->priv->notice); 243 244 wid->priv->filter_entry = entry; 245 } 246 247 /** 248 * gdaui_data_filter_new: 249 * @data_widget: a widget implementing the #GdauiDataProxy interface 250 * 251 * Creates a new #GdauiDataFilter widget suitable to change the filter expression 252 * for @data_widget's displayed rows 253 * 254 * Returns: (transfer full): the new widget 255 * 256 * Since: 4.2 257 */ 258 GtkWidget * 259 gdaui_data_filter_new (GdauiDataProxy *data_widget) 260 { 261 GtkWidget *filter; 262 263 g_return_val_if_fail (!data_widget || GDAUI_IS_DATA_PROXY (data_widget), NULL); 264 265 filter = (GtkWidget *) g_object_new (GDAUI_TYPE_DATA_FILTER, 266 "data-widget", data_widget, NULL); 267 268 return filter; 269 } 270 271 static void 272 data_widget_destroyed_cb (GdauiDataProxy *wid, GdauiDataFilter *filter) 273 { 274 g_assert (wid == filter->priv->data_widget); 275 g_signal_handlers_disconnect_by_func (G_OBJECT (wid), 276 G_CALLBACK (data_widget_destroyed_cb), filter); 277 g_signal_handlers_disconnect_by_func (G_OBJECT (wid), 278 G_CALLBACK (data_widget_proxy_changed_cb), filter); 279 280 filter->priv->data_widget = NULL; 281 } 282 283 static void 284 proxy_filter_changed_cb (GdaDataProxy *proxy, GdauiDataFilter *filter) 285 { 286 const gchar *expr; 287 288 g_assert (proxy == filter->priv->proxy); 289 expr = gda_data_proxy_get_filter_expr (proxy); 290 gtk_entry_set_text (GTK_ENTRY (filter->priv->filter_entry), expr ? expr : ""); 291 } 292 293 static void 294 release_proxy (GdauiDataFilter *filter) 295 { 296 g_signal_handlers_disconnect_by_func (G_OBJECT (filter->priv->proxy), 297 G_CALLBACK (proxy_filter_changed_cb), filter); 298 g_object_unref (filter->priv->proxy); 299 filter->priv->proxy = NULL; 300 } 301 302 static void 303 data_widget_proxy_changed_cb (GdauiDataProxy *data_widget, G_GNUC_UNUSED GdaDataProxy *proxy, GdauiDataFilter *filter) 304 { 305 g_object_set (G_OBJECT (filter), "data-widget", data_widget, NULL); 306 } 307 308 static void 309 gdaui_data_filter_dispose (GObject *object) 310 { 311 GdauiDataFilter *filter; 312 313 g_return_if_fail (object != NULL); 314 g_return_if_fail (GDAUI_IS_DATA_FILTER (object)); 315 filter = GDAUI_DATA_FILTER (object); 316 317 if (filter->priv) { 318 if (filter->priv->proxy) 319 release_proxy (filter); 320 if (filter->priv->data_widget) 321 data_widget_destroyed_cb (filter->priv->data_widget, filter); 322 323 /* the private area itself */ 324 g_free (filter->priv); 325 filter->priv = NULL; 326 } 327 328 /* for the parent class */ 329 parent_class->dispose (object); 330 } 331 332 static void 333 gdaui_data_filter_set_property (GObject *object, 334 guint param_id, 335 const GValue *value, 336 GParamSpec *pspec) 337 { 338 GdauiDataFilter *filter; 339 340 filter = GDAUI_DATA_FILTER (object); 341 if (filter->priv) { 342 switch (param_id) { 343 case PROP_DATA_WIDGET: 344 if (filter->priv->data_widget) 345 data_widget_destroyed_cb (filter->priv->data_widget, filter); 346 if (filter->priv->proxy) 347 release_proxy (filter); 348 349 filter->priv->data_widget = GDAUI_DATA_PROXY (g_value_get_object (value)); 350 if (filter->priv->data_widget) { 351 GdaDataProxy *proxy; 352 353 /* data widget */ 354 g_signal_connect (filter->priv->data_widget, "destroy", 355 G_CALLBACK (data_widget_destroyed_cb), filter); 356 g_signal_connect (filter->priv->data_widget, "proxy-changed", 357 G_CALLBACK (data_widget_proxy_changed_cb), filter); 358 359 /* proxy */ 360 proxy = gdaui_data_proxy_get_proxy (filter->priv->data_widget); 361 if (proxy) { 362 filter->priv->proxy = proxy; 363 g_object_ref (filter->priv->proxy); 364 g_signal_connect (G_OBJECT (proxy), "filter_changed", 365 G_CALLBACK (proxy_filter_changed_cb), filter); 366 proxy_filter_changed_cb (proxy, filter); 367 } 368 } 369 break; 370 default: 371 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); 372 break; 373 } 374 } 375 } 376 377 static void 378 gdaui_data_filter_get_property (GObject *object, 379 guint param_id, 380 GValue *value, 381 GParamSpec *pspec) 382 { 383 GdauiDataFilter *filter; 384 385 filter = GDAUI_DATA_FILTER (object); 386 if (filter->priv) { 387 switch (param_id) { 388 case PROP_DATA_WIDGET: 389 g_value_set_pointer (value, filter->priv->data_widget); 390 break; 391 default: 392 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); 393 break; 394 } 395 } 396 } 397