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