1 /*-- identify_ui.c --*/
2 /*
3 * ggobi
4 * Copyright (C) AT&T, Duncan Temple Lang, Dianne Cook 1999-2005
5 *
6 * ggobi is free software; you may use, redistribute, and/or modify it
7 * under the terms of the Eclipse Public License, which is distributed
8 * with the source code and displayed on the ggobi web site,
9 * www.ggobi.org. For more information, contact the authors:
10 *
11 * Deborah F. Swayne dfs@research.att.com
12 * Di Cook dicook@iastate.edu
13 * Duncan Temple Lang duncan@wald.ucdavis.edu
14 * Andreas Buja andreas.buja@wharton.upenn.edu
15 */
16
17 #include <gdk/gdkkeysyms.h>
18 #include <gtk/gtk.h>
19 #ifdef USE_STRINGS_H
20 #include <strings.h>
21 #endif
22
23 #include "vars.h"
24 #include "externs.h"
25
26 static void notebook_current_page_set (displayd *, GtkWidget *, ggobid *);
27
28
29 static void
identify_target_cb(GtkWidget * w,ggobid * gg)30 identify_target_cb (GtkWidget * w, ggobid * gg)
31 {
32 displayd *display = gg->current_display;
33 cpaneld *cpanel = &display->cpanel;
34 gboolean edges_displayed = (display->e != NULL &&
35 (display->options.edges_directed_show_p ||
36 display->options.edges_undirected_show_p ||
37 display->options.edges_arrowheads_show_p));
38
39 cpanel->id_target_type =
40 (enum idtargetd) gtk_combo_box_get_active (GTK_COMBO_BOX (w));
41
42 if (!edges_displayed && cpanel->id_target_type == identify_edges) {
43 quick_message ("Sorry, need to display edges before labeling them.",
44 false);
45 gtk_combo_box_set_active (GTK_COMBO_BOX (w), (gint) identify_points);
46 } else {
47 GtkWidget *pnl, *notebook;
48 pnl = mode_panel_get_by_name (GGOBI (getIModeName) (IDENT), gg);
49 if (pnl) {
50 notebook = widget_find_by_name (pnl, "IDENTIFY:notebook");
51 if (notebook)
52 notebook_current_page_set (display, notebook, gg);
53 }
54 }
55
56 displays_plot (NULL, QUICK, gg);
57 }
58
59 static void
recenter_cb(GtkWidget * w,ggobid * gg)60 recenter_cb (GtkWidget * w, ggobid * gg)
61 {
62 GGobiData *d = gg->current_display->d;
63 gint k = -1;
64 if (g_slist_length (d->sticky_ids) >= 1) {
65 k = GPOINTER_TO_INT (d->sticky_ids->data);
66 }
67 recenter_data (k, d, gg);
68 }
69
70 static void
id_remove_labels_cb(GtkWidget * w,ggobid * gg)71 id_remove_labels_cb (GtkWidget * w, ggobid * gg)
72 {
73 displayd *dsp = gg->current_display;
74 cpaneld *cpanel = &gg->current_display->cpanel;
75 GGobiData *d;
76 gboolean ok = true;
77
78 if (cpanel->id_target_type == identify_points)
79 d = dsp->d;
80 else {
81 d = dsp->e;
82 ok = (d != NULL);
83 }
84
85 g_slist_free (d->sticky_ids);
86 d->sticky_ids = (GSList *) NULL;
87
88 /* This will become an event on the datad when we move to
89 Gtk objects (soon now!) */
90 g_signal_emit (G_OBJECT (gg), GGobiSignals[STICKY_POINT_REMOVED_SIGNAL], 0,
91 -1, (gint) UNSTICKY, d);
92
93 displays_plot (NULL, QUICK, gg);
94 }
95
96 static void
id_all_sticky_cb(GtkWidget * w,ggobid * gg)97 id_all_sticky_cb (GtkWidget * w, ggobid * gg)
98 {
99 gint i, m;
100 GGobiData *d = NULL;
101 displayd *dsp = gg->current_display;
102 cpaneld *cpanel = &dsp->cpanel;
103
104 if (cpanel->id_target_type == identify_edges) {
105 /* Make sure there is an edge set and it is displayed */
106 if (dsp->e != NULL &&
107 (dsp->options.edges_directed_show_p ||
108 dsp->options.edges_undirected_show_p ||
109 dsp->options.edges_arrowheads_show_p)) {
110 d = dsp->e;
111 }
112 }
113 else
114 d = dsp->d;
115
116 if (d) {
117
118 /*-- clear the list before adding to avoid redundant entries --*/
119 g_slist_free (d->sticky_ids);
120 d->sticky_ids = (GSList *) NULL;
121 for (m = 0; m < d->nrows_in_plot; m++) {
122 i = d->rows_in_plot.els[m];
123 d->sticky_ids = g_slist_append (d->sticky_ids, GINT_TO_POINTER (i));
124 }
125
126 /* This will become an event on the datad when we move to
127 Gtk objects (soon now!) */
128 g_signal_emit (G_OBJECT (gg),
129 GGobiSignals[STICKY_POINT_ADDED_SIGNAL], 0, -1,
130 (gint) STICKY, d);
131 displays_plot (NULL, QUICK, gg);
132 }
133 }
134
135 enum
136 { RECORD_ID_INDEX = -3, RECORD_LABEL_INDEX, RECORD_NUMBER_INDEX };
137
138 static void
label_selected_cb(GtkTreeSelection * treesel,ggobid * gg)139 label_selected_cb (GtkTreeSelection * treesel, ggobid * gg)
140 {
141 gint *vars, nvars, i;
142 displayd *display = gg->current_display;
143
144 if (display) {
145 cpaneld *cpanel = &display->cpanel;
146 vars =
147 get_selections_from_tree_view (GTK_WIDGET
148 (gtk_tree_selection_get_tree_view
149 (treesel)), &nvars);
150 cpanel->id_display_type = 0;
151 for (i = 0; i < nvars; i++) {
152 if (vars[i] < 0)
153 cpanel->id_display_type |= 1 << -vars[i];
154 else
155 cpanel->id_display_type |= 1;
156 }
157 displays_plot (NULL, QUICK, gg);
158 }
159 }
160
161 /*--------------------------------------------------------------------*/
162 /* Handling keyboard and mouse events in the plot window */
163 /*--------------------------------------------------------------------*/
164
165 static gint
key_press_cb(GtkWidget * w,GdkEventKey * event,splotd * sp)166 key_press_cb (GtkWidget * w, GdkEventKey * event, splotd * sp)
167 {
168 ggobid *gg = GGobiFromSPlot (sp);
169 cpaneld *cpanel = &gg->current_display->cpanel;
170
171 /*-- add a key_press_cb in each mode, and let it begin with these lines --*/
172 if (splot_event_handled (w, event, cpanel, sp, gg))
173 return true;
174
175 /*-- insert mode-specific key presses (if any) here --*/
176
177 return false;
178 }
179
180
181 static gint
motion_notify_cb(GtkWidget * w,GdkEventMotion * event,splotd * sp)182 motion_notify_cb (GtkWidget * w, GdkEventMotion * event, splotd * sp)
183 {
184 gint k;
185 ggobid *gg = GGobiFromSPlot (sp);
186 GGobiData *d = gg->current_display->d;
187 gboolean button1_p, button2_p;
188 gint nd = g_slist_length (gg->d);
189 cpaneld *cpanel = &gg->current_display->cpanel;
190 GGobiPointMoveEvent ev;
191
192 /*
193 * w = sp->da
194 * sp = g_object_get_data(G_OBJECT (w), "splotd"));
195 */
196
197 mousepos_get_motion (w, event, &button1_p, &button2_p, sp);
198
199 if (GGOBI_IS_EXTENDED_SPLOT (sp)) {
200 gboolean changed;
201 gboolean (*f) (icoords, splotd * sp, GGobiData *, ggobid *);
202
203 f = GGOBI_EXTENDED_SPLOT_GET_CLASS (sp)->identify_notify;
204 if (f) {
205 changed = f (sp->mousepos, sp, d, gg);
206 if (changed) {
207 displays_plot (NULL, QUICK, gg);
208 }
209 return (true);
210 }
211 }
212
213 if (cpanel->id_target_type == identify_points) {
214 k = find_nearest_point (&sp->mousepos, sp, d, gg);
215 d->nearest_point = k;
216
217 /*-- link by id --*/
218 if (nd > 1)
219 identify_link_by_id (k, d, gg);
220 /*-- --*/
221
222 if (k != d->nearest_point_prev) {
223 displays_plot (NULL, QUICK, gg);
224
225 #ifdef EXPLICIT_IDENTIFY_HANDLER
226 if (gg->identify_handler.handler) {
227 (gg->identify_handler.handler) (gg->identify_handler.user_data,
228 k, sp, w, gg);
229 }
230 #endif
231
232 if (k != d->nearest_point_prev) {
233 ev.d = d;
234 ev.id = k;
235 /* This will become an event on the datad when we move to
236 Gtk objects (soon now!) - note: this came long ago */
237 g_signal_emit (G_OBJECT (gg), GGobiSignals[IDENTIFY_POINT_SIGNAL], 0,
238 sp, k, d);
239
240 displays_plot (NULL, QUICK, gg);
241 d->nearest_point_prev = k;
242 }
243 }
244 }
245 else {
246 GGobiData *e = gg->current_display->e;
247 if (e && e->edge.n) {
248 k = find_nearest_edge (sp, gg->current_display, gg);
249 e->nearest_point = k;
250 if (e->nearest_point != e->nearest_point_prev) {
251 ev.d = e;
252 ev.id = k;
253 /*-- perhaps this should be an IDENTIFY_EDGE_SIGNAL ... --*/
254 g_signal_emit (G_OBJECT (gg), GGobiSignals[IDENTIFY_POINT_SIGNAL], 0,
255 sp, k, e);
256
257 displays_plot (NULL, QUICK, gg);
258 e->nearest_point_prev = k;
259 }
260 }
261 }
262
263 return true; /* no need to propagate the event */
264 }
265
266 static gint
button_press_cb(GtkWidget * w,GdkEventButton * event,splotd * sp)267 button_press_cb (GtkWidget * w, GdkEventButton * event, splotd * sp)
268 {
269 /*
270 * If nearest_point is a member of gg->sticky_ids, remove it; if
271 * it isn't, add it.
272 */
273 ggobid *gg = GGobiFromSPlot (sp);
274 displayd *dsp = sp->displayptr;
275 cpaneld *cpanel = &dsp->cpanel;
276 GGobiData *d = NULL;
277
278 if (cpanel->id_target_type == identify_edges) {
279 if (dsp->e != NULL)
280 d = dsp->e;
281 }
282 else
283 d = dsp->d;
284
285 if (!d)
286 return false;
287
288 sticky_id_toggle (d, gg);
289
290 return true;
291 }
292
293 static gint
button_release_cb(GtkWidget * w,GdkEventButton * event,splotd * sp)294 button_release_cb (GtkWidget * w, GdkEventButton * event, splotd * sp)
295 {
296 ggobid *gg = GGobiFromSPlot (sp);
297 gg->buttondown = 0;
298 return true;
299 }
300
301 void
identify_event_handlers_toggle(splotd * sp,gboolean state)302 identify_event_handlers_toggle (splotd * sp, gboolean state)
303 {
304 displayd *display = (displayd *) sp->displayptr;
305
306 if (state == on) {
307 if (GGOBI_IS_WINDOW_DISPLAY (display) && GGOBI_WINDOW_DISPLAY(display)->useWindow)
308 sp->key_press_id =
309 g_signal_connect (G_OBJECT (GGOBI_WINDOW_DISPLAY (display)->window),
310 "key_press_event", G_CALLBACK (key_press_cb),
311 (gpointer) sp);
312
313 sp->press_id = g_signal_connect (G_OBJECT (sp->da),
314 "button_press_event",
315 G_CALLBACK (button_press_cb),
316 (gpointer) sp);
317 sp->release_id =
318 g_signal_connect (G_OBJECT (sp->da), "button_release_event",
319 G_CALLBACK (button_release_cb), (gpointer) sp);
320 sp->motion_id =
321 g_signal_connect (G_OBJECT (sp->da), "motion_notify_event",
322 G_CALLBACK (motion_notify_cb), (gpointer) sp);
323 }
324 else {
325 disconnect_key_press_signal (sp);
326 disconnect_button_press_signal (sp);
327 disconnect_button_release_signal (sp);
328 disconnect_motion_signal (sp);
329 }
330 }
331
332 static const gchar *label_prefices[] = {
333 "<i>Record ID</i>",
334 "<i>Record Label</i>",
335 "<i>Record Number</i>"
336 };
337
338 static const gchar **
label_prefix_func(GtkWidget * notebook,GGobiData * d,gint * sel_prefix,gint * n_prefices)339 label_prefix_func (GtkWidget * notebook, GGobiData * d, gint * sel_prefix,
340 gint * n_prefices)
341 {
342 gint offset = d->rowIds ? 0 : 1;
343 *n_prefices = G_N_ELEMENTS (label_prefices) - offset;
344 *sel_prefix = 1 - offset;
345 return (label_prefices + offset);
346 }
347
348 /*----------------------------------------------------------------------*/
349 /* Making the control panel */
350 /*----------------------------------------------------------------------*/
351
352
353 static gchar *target_lbl[] = {
354 "Points",
355 "Edges",
356 };
357 void
cpanel_identify_make(ggobid * gg)358 cpanel_identify_make (ggobid * gg)
359 {
360 modepaneld *panel;
361 GtkWidget *btn, *opt;
362 GtkWidget *notebook;
363 GtkWidget *frame, *framevb;
364
365 panel = (modepaneld *) g_malloc (sizeof (modepaneld));
366 gg->control_panels = g_list_append (gg->control_panels, (gpointer) panel);
367 panel->name = g_strdup (GGOBI (getIModeName) (IDENT));
368 panel->w = gtk_vbox_new (false, VBOX_SPACING);
369 gtk_container_set_border_width (GTK_CONTAINER (panel->w), 5);
370
371 /*-- option menu --*/
372 opt = gtk_combo_box_new_text ();
373 gtk_widget_set_name (opt, "IDENTIFY:target_option_menu");
374 gtk_tooltips_set_tip (GTK_TOOLTIPS (gg->tips), opt,
375 "Label points or edges", NULL);
376 gtk_box_pack_start (GTK_BOX (panel->w), opt, false, false, 0);
377 populate_combo_box (opt, target_lbl, G_N_ELEMENTS (target_lbl),
378 G_CALLBACK (identify_target_cb), gg);
379
380 /*-- provide a variable list so that any variable can be the label --*/
381 notebook = create_prefixed_variable_notebook (panel->w,
382 GTK_SELECTION_MULTIPLE,
383 all_vartypes, all_datatypes,
384 G_CALLBACK
385 (label_selected_cb), NULL, gg,
386 label_prefix_func);
387 gtk_widget_set_name (notebook, "IDENTIFY:notebook");
388 // experiment -- dfs
389 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), false);
390 g_object_set_data (G_OBJECT (panel->w), "notebook", notebook);
391
392 /*-- button for removing all labels --*/
393 btn = gtk_button_new_with_mnemonic ("_Remove labels");
394 gtk_widget_set_name (btn, "IDENTIFY:remove_sticky_labels");
395 gtk_tooltips_set_tip (GTK_TOOLTIPS (gg->tips),
396 btn, "Remove all labels", NULL);
397 g_signal_connect (G_OBJECT (btn), "clicked",
398 G_CALLBACK (id_remove_labels_cb), gg);
399 gtk_box_pack_start (GTK_BOX (panel->w), btn, false, false, 1);
400
401 /*
402 * button for making all labels sticky
403 */
404 btn = gtk_button_new_with_mnemonic ("Label all");
405 gtk_tooltips_set_tip (GTK_TOOLTIPS (gg->tips), btn,
406 "Make all labels sticky, or persistent (to make the nearest point label sticky, click middle or right in the plot)",
407 NULL);
408 g_signal_connect (G_OBJECT (btn), "clicked",
409 G_CALLBACK (id_all_sticky_cb), (gpointer) gg);
410 gtk_box_pack_start (GTK_BOX (panel->w), btn, false, false, 1);
411
412
413 /*-- frame around button for resetting center --*/
414 frame = gtk_frame_new ("Recenter data");
415 //gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_OUT);
416 gtk_box_pack_start (GTK_BOX (panel->w), frame, false, false, 3);
417
418 framevb = gtk_vbox_new (false, VBOX_SPACING);
419 gtk_container_set_border_width (GTK_CONTAINER (framevb), 4);
420 gtk_container_add (GTK_CONTAINER (frame), framevb);
421
422 btn = gtk_button_new_with_mnemonic ("Re_center");
423 gtk_widget_set_name (btn, "IDENT:recenter_btn");
424 gtk_tooltips_set_tip (GTK_TOOLTIPS (gg->tips), btn,
425 "Make one point sticky, and then click here to recenter the data around that point. (If there are no sticky labels, restore default centering.)",
426 NULL);
427 g_signal_connect (G_OBJECT (btn), "clicked",
428 G_CALLBACK (recenter_cb), (gpointer) gg);
429 gtk_box_pack_start (GTK_BOX (framevb), btn, false, false, 0);
430 /*-- --*/
431
432 gtk_widget_show_all (panel->w);
433 }
434
435
436 /*--------------------------------------------------------------------*/
437 /* Control panel section */
438 /*--------------------------------------------------------------------*/
439
440 void
cpanel_identify_init(cpaneld * cpanel,ggobid * gg)441 cpanel_identify_init (cpaneld * cpanel, ggobid * gg)
442 {
443 cpanel->id_display_type = ID_RECORD_LABEL;
444 }
445
446 /* called from cpanel_identify_set and when id_target_type is selected */
447 static void
notebook_current_page_set(displayd * display,GtkWidget * notebook,ggobid * gg)448 notebook_current_page_set (displayd * display, GtkWidget * notebook,
449 ggobid * gg)
450 {
451 GtkWidget *swin;
452 GGobiData *d = display->d, *paged, *e = display->e;
453 gint page_num;
454 cpaneld *cpanel = &display->cpanel;
455
456 if (notebook == NULL) {
457 return;
458 }
459
460 /*
461 * For each page of the notebook, get its child, the scrolled
462 * window. Get the datad that the scrolled window knows about,
463 * and compare it with display->d
464 */
465 page_num = 0;
466 swin = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), page_num);
467 while (swin) {
468 paged = (GGobiData *) g_object_get_data (G_OBJECT (swin), "datad");
469
470 if (paged == d && cpanel->id_target_type == identify_points) {
471 gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page_num);
472 break;
473 } else if (e != NULL && paged == e && cpanel->id_target_type == identify_edges) {
474 gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), page_num);
475 break;
476 }
477 page_num += 1;
478 swin = gtk_notebook_get_nth_page (GTK_NOTEBOOK (notebook), page_num);
479 }
480
481 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (notebook), false);
482 }
483
484 void
cpanel_identify_set(displayd * display,cpaneld * cpanel,ggobid * gg)485 cpanel_identify_set (displayd * display, cpaneld * cpanel, ggobid * gg)
486 {
487 GtkWidget *w;
488 GtkWidget *pnl = mode_panel_get_by_name (GGOBI (getIModeName) (IDENT), gg);
489
490 if (pnl == (GtkWidget *) NULL)
491 return;
492
493 w = widget_find_by_name (pnl, "IDENTIFY:notebook");
494 notebook_current_page_set (display, w, gg);
495
496 w = widget_find_by_name (pnl, "IDENTIFY:target_option_menu");
497 gtk_combo_box_set_active (GTK_COMBO_BOX (w), (gint) cpanel->id_target_type);
498 }
499