GNU Octave  3.8.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
__init_fltk__.cc
Go to the documentation of this file.
1 /*
2 
3 Copyright (C) 2007-2013 Shai Ayal
4 
5 This file is part of Octave.
6 
7 Octave is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by the
9 Free Software Foundation; either version 3 of the License, or (at your
10 option) any later version.
11 
12 Octave is distributed in the hope that it will be useful, but WITHOUT
13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Octave; see the file COPYING. If not, see
19 <http://www.gnu.org/licenses/>.
20 
21 */
22 
23 /*
24 
25 To initialize:
26 
27  graphics_toolkit ("fltk");
28  plot (randn (1e3, 1));
29 
30 */
31 
32 // PKG_ADD: if (__have_fltk__ () && have_window_system ()) register_graphics_toolkit ("fltk"); endif
33 
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
37 
38 #include "builtin-defun-decls.h"
39 #include "defun-dld.h"
40 #include "error.h"
41 #include "ov-fcn-handle.h"
42 
43 #ifdef HAVE_FLTK
44 
45 #include <map>
46 #include <set>
47 #include <sstream>
48 #include <iostream>
49 
50 #ifdef WIN32
51 #define WIN32_LEAN_AND_MEAN
52 #endif
53 
54 #include <FL/Fl.H>
55 #include <FL/Fl_Box.H>
56 #include <FL/Fl_Button.H>
57 #include <FL/Fl_Choice.H>
58 #include <FL/Fl_File_Chooser.H>
59 #include <FL/Fl_Gl_Window.H>
60 #include <FL/Fl_Menu_Bar.H>
61 #include <FL/Fl_Menu_Button.H>
62 #include <FL/Fl_Output.H>
63 #include <FL/Fl_Window.H>
64 #include <FL/fl_ask.H>
65 #include <FL/fl_draw.H>
66 #include <FL/gl.h>
67 
68 // FLTK headers may include X11/X.h which defines Complex, and that
69 // conflicts with Octave's Complex typedef. We don't need the X11
70 // Complex definition in this file, so remove it before including Octave
71 // headers which may require Octave's Complex typedef.
72 #undef Complex
73 
74 #include "cmd-edit.h"
75 #include "lo-ieee.h"
76 
77 #include "display.h"
78 #include "file-ops.h"
79 #include "gl-render.h"
80 #include "gl2ps-renderer.h"
81 #include "graphics.h"
82 #include "parse.h"
83 #include "sysdep.h"
84 #include "toplev.h"
85 #include "variables.h"
86 
87 #define FLTK_GRAPHICS_TOOLKIT_NAME "fltk"
88 
89 // Give FLTK no more than 0.01 sec to do its stuff.
90 static double fltk_maxtime = 1e-2;
91 
92 const char* help_text = "\
93 Keyboard Shortcuts\n\
94 a - autoscale\n\
95 p - pan/zoom\n\
96 r - rotate\n\
97 g - toggle grid\n\
98 \n\
99 Mouse\n\
100 left drag - pan\n\
101 mouse wheel - zoom\n\
102 right drag - rectangle zoom\n\
103 left double click - autoscale\n\
104 ";
105 
106 class OpenGL_fltk : public Fl_Gl_Window
107 {
108 public:
109  OpenGL_fltk (int xx, int yy, int ww, int hh, double num)
110  : Fl_Gl_Window (xx, yy, ww, hh, 0), number (num), renderer (),
111  in_zoom (false), zoom_box (), print_mode (false)
112  {
113  // Ask for double buffering and a depth buffer.
114  mode (FL_DEPTH | FL_DOUBLE);
115  }
116 
117  ~OpenGL_fltk (void) { }
118 
119  void zoom (bool z)
120  {
121  in_zoom = z;
122  if (! in_zoom)
123  hide_overlay ();
124  }
125 
126  bool zoom (void) { return in_zoom; }
127  void set_zoom_box (const Matrix& zb) { zoom_box = zb; }
128 
129  void print (const std::string& cmd, const std::string& term)
130  {
131  print_mode = true;
132  print_cmd = cmd;
133  print_term = term;
134  }
135 
136  void resize (int xx, int yy, int ww, int hh)
137  {
138  Fl_Gl_Window::resize (xx, yy, ww, hh);
139  setup_viewport (ww, hh);
140  redraw ();
141  }
142 
143  bool renumber (double new_number)
144  {
145  bool retval = false;
146 
147  if (number != new_number)
148  {
149  number = new_number;
150  retval = true;
151  }
152 
153  return retval;
154  }
155 
156 private:
157  double number;
158  opengl_renderer renderer;
159  bool in_zoom;
160  // (x1,y1,x2,y2)
161  Matrix zoom_box;
162 
163  bool print_mode;
164  std::string print_cmd;
165  std::string print_term;
166 
167  void setup_viewport (int ww, int hh)
168  {
169  glMatrixMode (GL_PROJECTION);
170  glLoadIdentity ();
171  glViewport (0, 0, ww, hh);
172  }
173 
174  void draw (void)
175  {
176  if (! valid ())
177  {
178  valid (1);
179  setup_viewport (w (), h ());
180  }
181 
182  if (print_mode)
183  {
184 #ifdef HAVE_GL2PS_H
185  FILE *fp = octave_popen (print_cmd.c_str (), "w");
186  glps_renderer rend (fp, print_term);
187 
188  rend.draw (gh_manager::get_object (number), print_cmd);
189 
190  octave_pclose (fp);
191  print_mode = false;
192 #else
193  print_mode = false;
194  error ("fltk: printing not available without gl2ps library");
195  return;
196 #endif
197  }
198  else
199  {
200  renderer.draw (gh_manager::get_object (number));
201 
202  if (zoom ())
203  overlay ();
204  }
205  }
206 
207  void zoom_box_vertex (void)
208  {
209  glVertex2d (zoom_box(0), h () - zoom_box(1));
210  glVertex2d (zoom_box(0), h () - zoom_box(3));
211  glVertex2d (zoom_box(2), h () - zoom_box(3));
212  glVertex2d (zoom_box(2), h () - zoom_box(1));
213  glVertex2d (zoom_box(0), h () - zoom_box(1));
214  }
215 
216  void overlay (void)
217  {
218  glPushMatrix ();
219 
220  glMatrixMode (GL_MODELVIEW);
221  glLoadIdentity ();
222 
223  glMatrixMode (GL_PROJECTION);
224  glLoadIdentity ();
225  gluOrtho2D (0.0, w (), 0.0, h ());
226 
227  glPushAttrib (GL_DEPTH_BUFFER_BIT | GL_CURRENT_BIT);
228  glDisable (GL_DEPTH_TEST);
229 
230  glBegin (GL_POLYGON);
231  glColor4f (0.45, 0.62, 0.81, 0.1);
232  zoom_box_vertex ();
233  glEnd ();
234 
235  glBegin (GL_LINE_STRIP);
236  glLineWidth (1.5);
237  glColor4f (0.45, 0.62, 0.81, 0.9);
238  zoom_box_vertex ();
239  glEnd ();
240 
241  glPopAttrib ();
242  glPopMatrix ();
243  }
244 
245  int handle (int event)
246  {
247  int retval = Fl_Gl_Window::handle (event);
248 
249  switch (event)
250  {
251  case FL_ENTER:
252  window ()->cursor (FL_CURSOR_CROSS);
253  return 1;
254 
255  case FL_LEAVE:
256  window ()->cursor (FL_CURSOR_DEFAULT);
257  return 1;
258  }
259 
260  return retval;
261  }
262 };
263 
264 // Parameter controlling how fast we zoom when using the scrool wheel.
265 static double Vwheel_zoom_speed = 0.05;
266 // Parameter controlling the GUI mode.
267 static enum { pan_zoom, rotate_zoom, none } gui_mode;
268 
269 void script_cb (Fl_Widget*, void* data)
270 {
271  static_cast<uimenu::properties*> (data)->execute_callback ();
272 }
273 
274 
275 class fltk_uimenu
276 {
277 public:
278  fltk_uimenu (int xx, int yy, int ww, int hh)
279  {
280  menubar = new
281  Fl_Menu_Bar (xx, yy, ww, hh);
282  }
283 
284  int items_to_show (void)
285  {
286  //returns the number of visible menu items
287  int len = menubar->size ();
288  int n = 0;
289  for (int t = 0; t < len; t++ )
290  {
291  const Fl_Menu_Item *m = static_cast<const Fl_Menu_Item*> (&
292  (menubar->menu ()[t]));
293  if (m->label () && m->visible ())
294  n++;
295  }
296 
297  return n;
298  }
299 
300  void show (void)
301  {
302  menubar->show ();
303  }
304 
305  void hide (void)
306  {
307  menubar->hide ();
308  }
309 
310  bool is_visible (void)
311  {
312  return menubar->visible ();
313  }
314 
315  int find_index_by_name (const std::string& findname)
316  {
317  // This function is derived from Greg Ercolano's function
318  // int GetIndexByName(...), see:
319  // http://seriss.com/people/erco/fltk/#Menu_ChangeLabel
320  // He agreed via PM that it can be included in octave using GPLv3
321  // Kai Habel (14.10.2010)
322 
323  std::string menupath;
324  for (int t = 0; t < menubar->size (); t++ )
325  {
326  Fl_Menu_Item *m = const_cast<Fl_Menu_Item*> (&(menubar->menu ()[t]));
327  if (m->submenu ())
328  {
329  // item has submenu
330  if (!menupath.empty ())
331  menupath += "/";
332  menupath += m->label ();
333 
334  if (menupath.compare (findname) == 0 )
335  return (t);
336  }
337  else
338  {
339  // End of submenu? Pop back one level.
340  if (! m->label ())
341  {
342  std::size_t idx = menupath.find_last_of ("/");
343  if (idx != std::string::npos)
344  menupath.erase (idx);
345  else
346  menupath.clear ();
347  continue;
348  }
349  // Menu item?
350  std::string itempath = menupath;
351  if (!itempath.empty ())
352  itempath += "/";
353  itempath += m->label ();
354 
355  if (itempath.compare (findname) == 0)
356  return (t);
357  }
358  }
359  return (-1);
360  }
361 
362  Matrix find_uimenu_children (uimenu::properties& uimenup) const
363  {
364  Matrix uimenu_childs = uimenup.get_all_children ();
365  Matrix retval = do_find_uimenu_children (uimenu_childs);
366  return retval;
367  }
368 
369  Matrix find_uimenu_children (figure::properties& figp) const
370  {
371  Matrix uimenu_childs = figp.get_all_children ();
372  Matrix retval = do_find_uimenu_children (uimenu_childs);
373  return retval;
374  }
375 
376  Matrix do_find_uimenu_children (Matrix uimenu_childs) const
377  {
378  octave_idx_type k = 0;
379 
380 
381  Matrix pos = Matrix (uimenu_childs.numel (), 1);
382 
383  for (octave_idx_type ii = 0; ii < uimenu_childs.numel (); ii++)
384  {
385  graphics_object kidgo = gh_manager::get_object (uimenu_childs (ii));
386 
387  if (kidgo.valid_object () && kidgo.isa ("uimenu"))
388  {
389  uimenu_childs(k) = uimenu_childs(ii);
390  pos(k++) =
391  dynamic_cast<uimenu::properties&>
392  (kidgo.get_properties ()).get_position ();
393  }
394  }
395 
396  uimenu_childs.resize (k, 1);
397  pos.resize (k, 1);
398  Matrix retval = Matrix (k, 1);
399  // Don't know if this is the best method to sort.
400  // Can we avoid the for loop?
402  for (octave_idx_type ii = 0; ii < k; ii++)
403  retval(ii) = uimenu_childs (sidx(ii));
404 
405  return retval;
406  }
407 
408  void delete_entry (uimenu::properties& uimenup)
409  {
410  std::string fltk_label = uimenup.get_fltk_label ();
411  int idx = find_index_by_name (fltk_label.c_str ());
412 
413  if (idx >= 0)
414  menubar->remove (idx);
415  }
416 
417  void update_accelerator (uimenu::properties& uimenup)
418  {
419  std::string fltk_label = uimenup.get_fltk_label ();
420  if (!fltk_label.empty ())
421  {
422  Fl_Menu_Item* item = const_cast<Fl_Menu_Item*> (menubar->find_item (
423  fltk_label.c_str ()));
424  if (item)
425  {
426  std::string acc = uimenup.get_accelerator ();
427  if (acc.length () > 0)
428  {
429  int key = FL_CTRL + acc[0];
430  item->shortcut (key);
431  }
432  }
433  }
434  }
435 
436  void update_callback (uimenu::properties& uimenup)
437  {
438  std::string fltk_label = uimenup.get_fltk_label ();
439  if (!fltk_label.empty ())
440  {
441  Fl_Menu_Item* item = const_cast<Fl_Menu_Item*> (menubar->find_item (
442  fltk_label.c_str ()));
443  if (item)
444  {
445  if (!uimenup.get_callback ().is_empty ())
446  item->callback (static_cast<Fl_Callback*> (script_cb),
447  static_cast<void*> (&uimenup));
448  else
449  item->callback (0, static_cast<void*> (0));
450  }
451  }
452  }
453 
454  void update_enable (uimenu::properties& uimenup)
455  {
456  std::string fltk_label = uimenup.get_fltk_label ();
457  if (!fltk_label.empty ())
458  {
459  Fl_Menu_Item* item = const_cast<Fl_Menu_Item*> (menubar->find_item (
460  fltk_label.c_str ()));
461  if (item)
462  {
463  if (uimenup.is_enable ())
464  item->activate ();
465  else
466  item->deactivate ();
467  }
468  }
469  }
470 
471  void update_foregroundcolor (uimenu::properties& uimenup)
472  {
473  std::string fltk_label = uimenup.get_fltk_label ();
474  if (!fltk_label.empty ())
475  {
476  Fl_Menu_Item* item = const_cast<Fl_Menu_Item*> (menubar->find_item (
477  fltk_label.c_str ()));
478  if (item)
479  {
480  Matrix rgb = uimenup.get_foregroundcolor_rgb ();
481 
482  uchar r = static_cast<uchar> (gnulib::floor (rgb (0) * 255));
483  uchar g = static_cast<uchar> (gnulib::floor (rgb (1) * 255));
484  uchar b = static_cast<uchar> (gnulib::floor (rgb (2) * 255));
485 
486  item->labelcolor (fl_rgb_color (r, g, b));
487  }
488  }
489  }
490 
491  void update_seperator (const uimenu::properties& uimenup)
492  {
493  // Matlab places the separator before the current
494  // menu entry, while fltk places it after. So we need to find
495  // the previous item in this menu/submenu. (Kai)
496  std::string fltk_label = uimenup.get_fltk_label ();
497  if (!fltk_label.empty ())
498  {
499  int itemflags = 0, idx;
500  int curr_idx = find_index_by_name (fltk_label.c_str ());
501 
502  for (idx = curr_idx - 1; idx >= 0; idx--)
503  {
504  Fl_Menu_Item* item
505  = const_cast<Fl_Menu_Item*> (&menubar->menu () [idx]);
506  itemflags = item->flags;
507  if (item->label ())
508  break;
509  }
510 
511  if (idx >= 0 && idx < menubar->size ())
512  {
513  if (uimenup.is_separator ())
514  {
515  if (idx >= 0 && !(itemflags & FL_SUBMENU))
516  menubar->mode (idx, itemflags | FL_MENU_DIVIDER);
517  }
518  else
519  menubar->mode (idx, itemflags & (~FL_MENU_DIVIDER));
520  }
521  }
522  }
523 
524  void update_visible (uimenu::properties& uimenup)
525  {
526  std::string fltk_label = uimenup.get_fltk_label ();
527  if (!fltk_label.empty ())
528  {
529  Fl_Menu_Item* item
530  = const_cast<Fl_Menu_Item*> (menubar->find_item (fltk_label.c_str ()));
531  if (item)
532  {
533  if (uimenup.is_visible ())
534  item->show ();
535  else
536  item->hide ();
537  }
538  }
539  }
540 
541  void add_entry (uimenu::properties& uimenup)
542  {
543 
544  std::string fltk_label = uimenup.get_fltk_label ();
545 
546  if (!fltk_label.empty ())
547  {
548  bool item_added = false;
549  do
550  {
551  const Fl_Menu_Item* item
552  = menubar->find_item (fltk_label.c_str ());
553 
554  if (item)
555  {
556  //avoid duplicate menulabels
557  std::size_t idx1 = fltk_label.find_last_of ("(");
558  std::size_t idx2 = fltk_label.find_last_of (")");
559  int len = idx2 - idx1;
560  int val = 1;
561  if (len > 0)
562  {
563  std::string valstr = fltk_label.substr (idx1 + 1, len - 1);
564  fltk_label.erase (idx1, len + 1);
565  val = atoi (valstr.c_str ());
566  if (val > 0 && val < 99)
567  val++;
568  }
569  std::ostringstream valstream;
570  valstream << val;
571  fltk_label += "(" + valstream.str () + ")";
572  }
573  else
574  {
575  Matrix uimenu_ch = find_uimenu_children (uimenup);
576  int len = uimenu_ch.numel ();
577  int flags = 0;
578  if (len > 0)
579  flags = FL_SUBMENU;
580  if (len == 0 && uimenup.is_checked ())
581  flags += FL_MENU_TOGGLE + FL_MENU_VALUE;
582  menubar->add (fltk_label.c_str (), 0, 0, 0, flags);
583  item_added = true;
584  }
585  }
586  while (!item_added);
587  uimenup.set_fltk_label (fltk_label);
588  }
589  }
590 
591  void add_to_menu (uimenu::properties& uimenup)
592  {
593  Matrix kids = find_uimenu_children (uimenup);
594  int len = kids.length ();
595  std::string fltk_label = uimenup.get_fltk_label ();
596 
597  add_entry (uimenup);
598  update_foregroundcolor (uimenup);
599  update_callback (uimenup);
600  update_accelerator (uimenup);
601  update_enable (uimenup);
602  update_visible (uimenup);
603  update_seperator (uimenup);
604 
605  for (octave_idx_type ii = 0; ii < len; ii++)
606  {
607  graphics_object kgo = gh_manager::get_object (kids (len - (ii + 1)));
608  if (kgo.valid_object ())
609  {
610  uimenu::properties& kprop = dynamic_cast<uimenu::properties&>
611  (kgo.get_properties ());
612  add_to_menu (kprop);
613  }
614  }
615  }
616 
617  void add_to_menu (figure::properties& figp)
618  {
619  Matrix kids = find_uimenu_children (figp);
620  int len = kids.length ();
621  menubar->clear ();
622  for (octave_idx_type ii = 0; ii < len; ii++)
623  {
624  graphics_object kgo = gh_manager::get_object (kids (len - (ii + 1)));
625 
626  if (kgo.valid_object ())
627  {
628  uimenu::properties& kprop = dynamic_cast<uimenu::properties&>
629  (kgo.get_properties ());
630  add_to_menu (kprop);
631  }
632  }
633  }
634 
635  template <class T_prop>
636  void remove_from_menu (T_prop& prop)
637  {
638  Matrix kids;
639  std::string type = prop.get_type ();
640  kids = find_uimenu_children (prop);
641  int len = kids.length ();
642 
643  for (octave_idx_type ii = 0; ii < len; ii++)
644  {
645  graphics_object kgo = gh_manager::get_object (kids (len - (ii + 1)));
646 
647  if (kgo.valid_object ())
648  {
649  uimenu::properties kprop = dynamic_cast<uimenu::properties&>
650  (kgo.get_properties ());
651  remove_from_menu (kprop);
652  }
653  }
654 
655  if (type.compare ("uimenu") == 0)
656  delete_entry (dynamic_cast<uimenu::properties&> (prop));
657  else if (type.compare ("figure") == 0)
658  menubar->clear ();
659  }
660 
661  ~fltk_uimenu (void)
662  {
663  delete menubar;
664  }
665 
666 private:
667 
668  // No copying!
669 
670  fltk_uimenu (const fltk_uimenu&);
671 
672  fltk_uimenu operator = (const fltk_uimenu&);
673 
674  Fl_Menu_Bar* menubar;
675 };
676 
677 class plot_window : public Fl_Window
678 {
679  friend class fltk_uimenu;
680 public:
681  plot_window (int xx, int yy, int ww, int hh, figure::properties& xfp)
682  : Fl_Window (xx, yy - menu_h, ww, hh + menu_h + status_h, "octave"),
683  window_label (), shift (0), ndim (2), fp (xfp), canvas (0),
684  autoscale (0), togglegrid (0), panzoom (0), rotate (0), help (0),
685  status (0), ax_obj (), pos_x (0), pos_y (0)
686  {
687  callback (window_close, static_cast<void*> (this));
688  size_range (4*status_h, 2*status_h);
689 
690  // FIXME: The function below is only available in FLTK >= 1.3
691  // At some point support for FLTK 1.1 will be dropped in Octave.
692  // At that point this function should be uncommented.
693  // The current solution is to call xclass() before show() for each window.
694  // Set WM_CLASS which allows window managers to properly group related
695  // windows. Otherwise, the class is just "FLTK"
696  //default_xclass ("Octave");
697 
698  begin ();
699  {
700  // bbox of plot canvas = [xx, yy, ww, hh];
701  // (xx, yy) = UL coordinate relative to UL window.
702 
703  canvas = new OpenGL_fltk (0, menu_h, ww, hh, number ());
704 
705  uimenu = new fltk_uimenu (0, 0, ww, menu_h);
706  uimenu->hide ();
707 
708  // Toolbar is a composite of "bottom", "autoscale", "togglegrid",
709  // "panzoom", "rotate", "help", and "status".
710 
711  yy = hh + menu_h;
712  bottom = new Fl_Box (0, yy, ww, status_h);
713  bottom->box (FL_FLAT_BOX);
714 
715  ndim = calc_dimensions (gh_manager::get_object (fp.get___myhandle__ ()));
716 
717  autoscale = new Fl_Button (0, yy, status_h, status_h, "A");
718  autoscale->callback (button_callback, static_cast<void*> (this));
719  autoscale->tooltip ("Autoscale");
720 
721  togglegrid = new Fl_Button (status_h, yy, status_h,
722  status_h, "G");
723  togglegrid->callback (button_callback, static_cast<void*> (this));
724  togglegrid->tooltip ("Toggle Grid");
725 
726  panzoom = new Fl_Button (2 * status_h, yy, status_h,
727  status_h, "P");
728  panzoom->callback (button_callback, static_cast<void*> (this));
729  panzoom->tooltip ("Mouse Pan/Zoom");
730 
731  rotate = new Fl_Button (3 * status_h, yy, status_h,
732  status_h, "R");
733  rotate->callback (button_callback, static_cast<void*> (this));
734  rotate->tooltip ("Mouse Rotate");
735 
736  if (ndim == 2)
737  rotate->deactivate ();
738 
739  help = new Fl_Button (4 * status_h, yy, status_h,
740  status_h, "?");
741  help->callback (button_callback, static_cast<void*> (this));
742  help->tooltip ("Help");
743 
744  status = new Fl_Output (5 * status_h, yy,
745  ww > 2*status_h ? ww - status_h : 0,
746  status_h, "");
747 
748  status->textcolor (FL_BLACK);
749  status->color (FL_GRAY);
750  status->textfont (FL_COURIER);
751  status->textsize (10);
752  status->box (FL_ENGRAVED_BOX);
753 
754  // This allows us to have a valid OpenGL context right away.
755  canvas->mode (FL_DEPTH | FL_DOUBLE );
756  if (fp.is_visible ())
757  {
758  // FIXME: This code should be removed when Octave drops support
759  // for FLTK 1.1. Search for default_xclass in this file to find
760  // code that should be uncommented to take its place.
761  //
762  // Set WM_CLASS which allows window managers to properly group
763  // related windows. Otherwise, the class is just "FLTK"
764  xclass ("Octave");
765  show ();
766  if (fp.get_currentaxes ().ok ())
767  show_canvas ();
768  else
769  hide_canvas ();
770  }
771  }
772  end ();
773 
774  status->show ();
775  autoscale->show ();
776  togglegrid->show ();
777  panzoom->show ();
778  rotate->show ();
779 
780  set_name ();
781  resizable (canvas);
782  gui_mode = (ndim == 3 ? rotate_zoom : pan_zoom);
783  uimenu->add_to_menu (fp);
784  if (uimenu->items_to_show ())
785  show_menubar ();
786  else
787  hide_menubar ();
788  }
789 
790  ~plot_window (void)
791  {
792  canvas->hide ();
793  status->hide ();
794  uimenu->hide ();
795  this->hide ();
796  }
797 
798  double number (void) { return fp.get___myhandle__ ().value (); }
799 
800  void renumber (double new_number)
801  {
802  if (canvas)
803  {
804  if (canvas->renumber (new_number))
805  mark_modified ();
806  }
807  else
808  error ("unable to renumber figure");
809  }
810 
811  void print (const std::string& cmd, const std::string& term)
812  {
813  canvas->print (cmd, term);
814 
815  // Print immediately so the output file will exist when the drawnow
816  // command is done.
817  mark_modified ();
818  Fl::wait (fltk_maxtime);
819  }
820 
821  void show_menubar (void)
822  {
823  if (!uimenu->is_visible ())
824  {
825  // FIXME: Toolbar and menubar do not update
826  uimenu->show ();
827  mark_modified ();
828  }
829  }
830 
831  void hide_menubar (void)
832  {
833  if (uimenu->is_visible ())
834  {
835  // FIXME: Toolbar and menubar do not update
836  uimenu->hide ();
837  mark_modified ();
838  }
839  }
840 
841  void uimenu_update (const graphics_handle& gh, int id)
842  {
843  graphics_object uimenu_obj = gh_manager::get_object (gh);
844 
845  if (uimenu_obj.valid_object () && uimenu_obj.isa ("uimenu"))
846  {
847  uimenu::properties& uimenup =
848  dynamic_cast<uimenu::properties&> (uimenu_obj.get_properties ());
849  std::string fltk_label = uimenup.get_fltk_label ();
850  graphics_object fig = uimenu_obj.get_ancestor ("figure");
851  figure::properties& figp =
852  dynamic_cast<figure::properties&> (fig.get_properties ());
853 
854  switch (id)
855  {
857  uimenu->remove_from_menu (uimenup);
858  break;
859 
861  uimenu->update_visible (uimenup);
862  break;
863 
865  uimenu->update_accelerator (uimenup);
866  break;
867 
869  uimenu->update_callback (uimenup);
870  break;
871 
873  uimenu->add_to_menu (figp);//rebuilding entire menu
874  break;
875 
877  uimenu->update_enable (uimenup);
878  break;
879 
881  uimenu->update_foregroundcolor (uimenup);
882  break;
883 
885  uimenu->add_to_menu (figp);//rebuilding entire menu
886  break;
887 
889  uimenu->add_to_menu (figp);//rebuilding entire menu
890  break;
891 
893  uimenu->update_seperator (uimenup);
894  break;
895  }
896 
897  if (uimenu->items_to_show ())
898  show_menubar ();
899  else
900  hide_menubar ();
901 
902  mark_modified ();
903  }
904  }
905 
906  void show_canvas (void)
907  {
908  if (fp.is_visible ())
909  {
910  canvas->show ();
911  canvas->make_current ();
912  }
913  }
914 
915  void hide_canvas (void)
916  {
917  canvas->hide ();
918  }
919 
920  void mark_modified (void)
921  {
922  damage (FL_DAMAGE_ALL);
923  canvas->damage (FL_DAMAGE_ALL);
924  ndim = calc_dimensions (gh_manager::get_object (fp.get___myhandle__ ()));
925 
926  if (ndim == 3)
927  rotate->activate ();
928  else if (ndim == 2 && gui_mode == rotate_zoom)
929  {
930  rotate->deactivate ();
931  gui_mode = pan_zoom;
932  }
933  }
934 
935  void set_name (void)
936  {
937  window_label = fp.get_title ();
938  label (window_label.c_str ());
939  }
940 
941 private:
942 
943  // No copying!
944 
945  plot_window (const plot_window&);
946 
947  plot_window& operator = (const plot_window&);
948 
949  // window name -- this must exists for the duration of the window's
950  // life
951  std::string window_label;
952 
953  // Mod keys status
954  int shift;
955 
956  // Number of dimensions, 2 or 3.
957  int ndim;
958 
959  // Figure properties.
960  figure::properties& fp;
961 
962  // Status area height.
963  static const int status_h = 20;
964 
965  // Menu height
966  static const int menu_h = 20;
967 
968  // Window callback.
969  static void window_close (Fl_Widget*, void* data)
970  {
971  octave_value_list args;
972  args(0) = static_cast<plot_window*> (data)->number ();
973  feval ("close", args);
974  }
975 
976  // Button callbacks.
977  static void button_callback (Fl_Widget* ww, void* data)
978  {
979  static_cast<plot_window*> (data)->button_press (ww, data);
980  }
981 
982  void button_press (Fl_Widget* widg, void*)
983  {
984  if (widg == autoscale)
985  axis_auto ();
986 
987  if (widg == togglegrid)
988  toggle_grid ();
989 
990  if (widg == panzoom)
991  gui_mode = pan_zoom;
992 
993  if (widg == rotate && ndim == 3)
994  gui_mode = rotate_zoom;
995 
996  if (widg == help)
997  fl_message ("%s", help_text);
998  }
999 
1000  fltk_uimenu* uimenu;
1001  OpenGL_fltk* canvas;
1002  Fl_Box* bottom;
1003  Fl_Button* autoscale;
1004  Fl_Button* togglegrid;
1005  Fl_Button* panzoom;
1006  Fl_Button* rotate;
1007  Fl_Button* help;
1008  Fl_Output* status;
1009  graphics_object ax_obj;
1010  int pos_x;
1011  int pos_y;
1012 
1013  void axis_auto (void)
1014  {
1015  octave_value_list args;
1016  args(0) = fp.get_currentaxes ().as_octave_value ();
1017  args(1) = "auto";
1018  feval ("axis", args);
1019  mark_modified ();
1020  }
1021 
1022  void toggle_grid (void)
1023  {
1024  octave_value_list args;
1025  if (fp.get_currentaxes ().ok ())
1026  args(0) = fp.get_currentaxes ().as_octave_value ();
1027 
1028  feval ("grid", args);
1029  mark_modified ();
1030  }
1031 
1032  void pixel2pos (const graphics_handle& ax, int px, int py, double& xx,
1033  double& yy) const
1034  {
1035  pixel2pos ( gh_manager::get_object (ax), px, py, xx, yy);
1036  }
1037 
1038  void pixel2pos (graphics_object ax, int px, int py, double& xx,
1039  double& yy) const
1040  {
1041  if (ax && ax.isa ("axes"))
1042  {
1043  axes::properties& ap =
1044  dynamic_cast<axes::properties&> (ax.get_properties ());
1045  ColumnVector pp = ap.pixel2coord (px, py);
1046  xx = pp(0);
1047  yy = pp(1);
1048  }
1049  }
1050 
1051  graphics_handle pixel2axes_or_ca (int px, int py )
1052  {
1053  Matrix kids = fp.get_children ();
1054  int len = kids.length ();
1055 
1056  for (int k = 0; k < len; k++)
1057  {
1058  graphics_handle hnd = gh_manager::lookup (kids(k));
1059 
1060  if (hnd.ok ())
1061  {
1063 
1064  if (kid.valid_object () && kid.isa ("axes"))
1065  {
1066  Matrix bb = kid.get_properties ().get_boundingbox (true);
1067 
1068  if (bb(0) <= px && px < (bb(0)+bb(2))
1069  && bb(1) <= py && py < (bb(1)+bb(3)))
1070  {
1071  return hnd;
1072  }
1073  }
1074  }
1075  }
1076  return fp.get_currentaxes ();
1077  }
1078 
1079  void pixel2status (const graphics_handle& ax, int px0, int py0,
1080  int px1 = -1, int py1 = -1)
1081  {
1082  pixel2status (gh_manager::get_object (ax), px0, py0, px1, py1);
1083  }
1084 
1085  void pixel2status (graphics_object ax, int px0, int py0,
1086  int px1 = -1, int py1 = -1)
1087  {
1088  double x0, y0, x1, y1;
1089  x0 = y0 = x1 = y1 = octave_NaN;
1090  std::stringstream cbuf;
1091  cbuf.precision (4);
1092  cbuf.width (6);
1093  pixel2pos (ax, px0, py0, x0, y0);
1094  cbuf << "[" << x0 << ", " << y0 << "]";
1095  if (px1 >= 0)
1096  {
1097  pixel2pos (ax, px1, py1, x1, y1);
1098  cbuf << " -> ["<< x1 << ", " << y1 << "]";
1099  }
1100 
1101  status->value (cbuf.str ().c_str ());
1102  status->redraw ();
1103  }
1104 
1105  void view2status (graphics_object ax)
1106  {
1107  if (ax && ax.isa ("axes"))
1108  {
1109  axes::properties& ap =
1110  dynamic_cast<axes::properties&> (ax.get_properties ());
1111  std::stringstream cbuf;
1112  cbuf.precision (4);
1113  cbuf.width (6);
1114  Matrix v (1,2,0);
1115  v = ap.get ("view").matrix_value ();
1116  cbuf << "[azimuth: " << v(0) << ", elevation: " << v(1) << "]";
1117 
1118  status->value (cbuf.str ().c_str ());
1119  status->redraw ();
1120  }
1121  }
1122 
1123  void set_currentpoint (int px, int py)
1124  {
1125  if (!fp.is_beingdeleted ())
1126  {
1127  Matrix pos (1,2,0);
1128  pos(0) = px;
1129  pos(1) = h () - (py + status_h + menu_dy ());
1130  fp.set_currentpoint (pos);
1131  graphics_object robj = gh_manager::get_object (fp.get_parent ());
1133  dynamic_cast<root_figure::properties&> (robj.get_properties ());
1134  rp.set_currentfigure (fp.get___myhandle__ ().value ());
1135  }
1136  }
1137 
1138  void set_axes_currentpoint (graphics_object ax, int px, int py)
1139  {
1140  if (ax.valid_object ())
1141  {
1142  axes::properties& ap =
1143  dynamic_cast<axes::properties&> (ax.get_properties ());
1144 
1145  double xx, yy;
1146  pixel2pos (ax, px, py, xx, yy);
1147 
1148  Matrix pos (2,3,0);
1149  pos(0,0) = xx;
1150  pos(1,0) = yy;
1151  pos(0,1) = xx;
1152  pos(1,1) = yy;
1153 
1154  ap.set_currentpoint (pos);
1155  fp.set_currentaxes (ap.get___myhandle__ ().value ());
1156  }
1157  }
1158 
1159  int menu_dy ()
1160  {
1161  if (uimenu->is_visible ())
1162  return menu_h;
1163  else
1164  return 0;
1165  }
1166 
1167  int key2shift (int key)
1168  {
1169  if (key == FL_Shift_L || key == FL_Shift_R)
1170  return FL_SHIFT;
1171 
1172  if (key == FL_Control_L || key == FL_Control_R)
1173  return FL_CTRL;
1174 
1175  if (key == FL_Alt_L || key == FL_Alt_R)
1176  return FL_ALT;
1177 
1178  if (key == FL_Meta_L || key == FL_Meta_R)
1179  return FL_META;
1180 
1181  return 0;
1182  }
1183 
1184  int key2ascii (int key)
1185  {
1186  if (key < 256) return key;
1187  if (key == FL_Tab) return '\t';
1188  if (key == FL_Enter) return 0x0a;
1189  if (key == FL_BackSpace) return 0x08;
1190  if (key == FL_Escape) return 0x1b;
1191 
1192  return 0;
1193  }
1194 
1195  Cell modifier2cell ()
1196  {
1198 
1199  if (shift & FL_SHIFT)
1200  mod.append (std::string ("shift"));
1201  if (shift & FL_CTRL)
1202  mod.append (std::string ("control"));
1203  if (shift & FL_ALT || shift & FL_META)
1204  mod.append (std::string ("alt"));
1205 
1206  return Cell (mod);
1207  }
1208 
1209  void resize (int xx,int yy,int ww,int hh)
1210  {
1211  Fl_Window::resize (xx, yy, ww, hh);
1212 
1213  Matrix pos (1,4,0);
1214  pos(0) = xx;
1215  pos(1) = yy + menu_dy ();
1216  pos(2) = ww;
1217  pos(3) = hh - menu_dy () - status_h;
1218 
1219  fp.set_boundingbox (pos, true);
1220  }
1221 
1222  void draw (void)
1223  {
1224  // FIXME: Toolbar and menubar do not update properly
1225  Matrix pos = fp.get_boundingbox (true);
1226  int canvas_h = pos(3);
1227  int canvas_w = pos(2);
1228  int canvas_y = menu_dy ();
1229  int toolbar_y = menu_dy () + canvas_h;
1230  pos(1) = pos(1) - menu_dy ();
1231  pos(3) = pos(3) + menu_dy () + status_h;
1232 
1233  Fl_Window::resize (pos(0), pos(1), pos(2), pos(3));
1234 
1235  bottom->resize (0, toolbar_y, status_h, status_h);
1236  autoscale->resize (0, toolbar_y, status_h, status_h);
1237  togglegrid->resize (status_h, toolbar_y, status_h, status_h);
1238  panzoom->resize (2 * status_h, toolbar_y, status_h, status_h);
1239  rotate->resize (3 * status_h, toolbar_y, status_h, status_h);
1240  help->resize (4 * status_h, toolbar_y, status_h, status_h);
1241  status->resize (5 * status_h, toolbar_y, pos(2) - 4 * status_h, status_h);
1242  if (canvas->valid ())
1243  canvas->resize (0, canvas_y, canvas_w, canvas_h);
1244 
1245  return Fl_Window::draw ();
1246  }
1247 
1248  int handle (int event)
1249  {
1250  graphics_handle gh;
1251 
1252  graphics_object fig = gh_manager::get_object (fp.get___myhandle__ ());
1253  int retval = Fl_Window::handle (event);
1254 
1255  // We only handle events which are in the canvas area.
1256  if (!Fl::event_inside (canvas))
1257  return retval;
1258 
1259  if (!fp.is_beingdeleted ())
1260  {
1261  switch (event)
1262  {
1263  case FL_KEYDOWN:
1264  {
1265  int key = Fl::event_key ();
1266 
1267  shift |= key2shift (key);
1268  int key_a = key2ascii (key);
1269  if (key_a && fp.get_keypressfcn ().is_defined ())
1270  {
1271  octave_scalar_map evt;
1272  evt.assign ("Character", octave_value (key_a));
1273  evt.assign ("Key", octave_value (std::tolower (key_a)));
1274  evt.assign ("Modifier", octave_value (modifier2cell ()));
1275  fp.execute_keypressfcn (evt);
1276  }
1277  switch (key)
1278  {
1279  case 'a':
1280  case 'A':
1281  axis_auto ();
1282  break;
1283 
1284  case 'g':
1285  case 'G':
1286  toggle_grid ();
1287  break;
1288 
1289  case 'p':
1290  case 'P':
1291  gui_mode = pan_zoom;
1292  break;
1293 
1294  case 'r':
1295  case 'R':
1296  gui_mode = rotate_zoom;
1297  break;
1298  }
1299  }
1300  break;
1301 
1302  case FL_KEYUP:
1303  {
1304  int key = Fl::event_key ();
1305 
1306  shift &= (~key2shift (key));
1307  int key_a = key2ascii (key);
1308  if (key_a && fp.get_keyreleasefcn ().is_defined ())
1309  {
1310  octave_scalar_map evt;
1311  evt.assign ("Character", octave_value (key_a));
1312  evt.assign ("Key", octave_value (std::tolower (key_a)));
1313  evt.assign ("Modifier", octave_value (modifier2cell ()));
1314  fp.execute_keyreleasefcn (evt);
1315  }
1316  }
1317  break;
1318 
1319  case FL_MOVE:
1320  pixel2status (pixel2axes_or_ca (Fl::event_x (),
1321  Fl::event_y () - menu_dy ()),
1322  Fl::event_x (), Fl::event_y () - menu_dy ());
1323  break;
1324 
1325  case FL_PUSH:
1326  pos_x = Fl::event_x ();
1327  pos_y = Fl::event_y () - menu_dy ();
1328 
1329  set_currentpoint (Fl::event_x (), Fl::event_y () - menu_dy ());
1330 
1331  gh = pixel2axes_or_ca (pos_x, pos_y);
1332 
1333  if (gh.ok ())
1334  {
1335  ax_obj = gh_manager::get_object (gh);
1336  set_axes_currentpoint (ax_obj, pos_x, pos_y);
1337  }
1338 
1339  fp.execute_windowbuttondownfcn (Fl::event_button());
1340 
1341  if (Fl::event_button () == 1 || Fl::event_button () == 3)
1342  return 1;
1343 
1344  break;
1345 
1346  case FL_DRAG:
1347  if (fp.get_windowbuttonmotionfcn ().is_defined ())
1348  {
1349  set_currentpoint (Fl::event_x (), Fl::event_y () - menu_dy ());
1350  fp.execute_windowbuttonmotionfcn ();
1351  }
1352 
1353  if (Fl::event_button () == 1)
1354  {
1355  if (ax_obj && ax_obj.isa ("axes"))
1356  {
1357  if (gui_mode == pan_zoom)
1358  pixel2status (ax_obj, pos_x, pos_y,
1359  Fl::event_x (),
1360  Fl::event_y () - menu_dy ());
1361  else
1362  view2status (ax_obj);
1363  axes::properties& ap =
1364  dynamic_cast<axes::properties&>
1365  (ax_obj.get_properties ());
1366 
1367  double x0, y0, x1, y1;
1368  Matrix pos = fp.get_boundingbox (true);
1369  pixel2pos (ax_obj, pos_x, pos_y, x0, y0);
1370  pixel2pos (ax_obj, Fl::event_x (),
1371  Fl::event_y () - menu_dy (),
1372  x1, y1);
1373 
1374  if (gui_mode == pan_zoom)
1375  ap.translate_view (x0, x1, y0, y1);
1376  else if (gui_mode == rotate_zoom)
1377  {
1378  double daz, del;
1379  daz = (Fl::event_x () - pos_x) / pos(2) * 360;
1380  del = (Fl::event_y () - menu_dy () - pos_y)
1381  / pos(3) * 360;
1382  ap.rotate_view (del, daz);
1383  }
1384 
1385  pos_x = Fl::event_x ();
1386  pos_y = Fl::event_y () - menu_dy ();
1387  mark_modified ();
1388  }
1389  return 1;
1390  }
1391  else if (Fl::event_button () == 3)
1392  {
1393  pixel2status (ax_obj, pos_x, pos_y,
1394  Fl::event_x (), Fl::event_y () - menu_dy ());
1395  Matrix zoom_box (1,4,0);
1396  zoom_box (0) = pos_x;
1397  zoom_box (1) = pos_y;
1398  zoom_box (2) = Fl::event_x ();
1399  zoom_box (3) = Fl::event_y () - menu_dy ();
1400  canvas->set_zoom_box (zoom_box);
1401  canvas->zoom (true);
1402  canvas->redraw ();
1403  }
1404 
1405  break;
1406 
1407  case FL_MOUSEWHEEL:
1408  {
1409  graphics_object ax =
1410  gh_manager::get_object (pixel2axes_or_ca (Fl::event_x (),
1411  Fl::event_y ()
1412  - menu_dy ()));
1413  if (ax && ax.isa ("axes"))
1414  {
1415  axes::properties& ap =
1416  dynamic_cast<axes::properties&> (ax.get_properties ());
1417 
1418  // Determine if we're zooming in or out.
1419  const double factor =
1420  (Fl::event_dy () > 0) ? 1 / (1.0 - Vwheel_zoom_speed)
1421  : 1.0 - Vwheel_zoom_speed;
1422 
1423  // Get the point we're zooming about.
1424  double x1, y1;
1425  pixel2pos (ax, Fl::event_x (), Fl::event_y () - menu_dy (),
1426  x1, y1);
1427 
1428  ap.zoom_about_point (x1, y1, factor, false);
1429  mark_modified ();
1430  }
1431  }
1432  return 1;
1433 
1434  case FL_RELEASE:
1435  if (fp.get_windowbuttonupfcn ().is_defined ())
1436  {
1437  set_currentpoint (Fl::event_x (), Fl::event_y () - menu_dy ());
1438  fp.execute_windowbuttonupfcn ();
1439  }
1440 
1441  if (Fl::event_button () == 1)
1442  {
1443  if ( Fl::event_clicks () == 1)
1444  {
1445  if (ax_obj && ax_obj.isa ("axes"))
1446  {
1447  axes::properties& ap = dynamic_cast<axes::properties&>
1448  (ax_obj.get_properties ());
1449  ap.set_xlimmode ("auto");
1450  ap.set_ylimmode ("auto");
1451  ap.set_zlimmode ("auto");
1452  mark_modified ();
1453  }
1454  }
1455  }
1456  if (Fl::event_button () == 3)
1457  {
1458  // End of drag -- zoom.
1459  if (canvas->zoom ())
1460  {
1461  canvas->zoom (false);
1462  double x0,y0,x1,y1;
1463  if (ax_obj && ax_obj.isa ("axes"))
1464  {
1465  axes::properties& ap = dynamic_cast<axes::properties&>
1466  (ax_obj.get_properties ());
1467  pixel2pos (ax_obj, pos_x, pos_y, x0, y0);
1468  int pos_x1 = Fl::event_x ();
1469  int pos_y1 = Fl::event_y () - menu_dy ();
1470  pixel2pos (ax_obj, pos_x1, pos_y1, x1, y1);
1471  Matrix xl (1,2,0);
1472  Matrix yl (1,2,0);
1473  int dx = abs (pos_x - pos_x1);
1474  int dy = abs (pos_y - pos_y1);
1475  // Smallest zoom box must be 4 pixels square
1476  if ((dx > 4) && (dy > 4))
1477  {
1478  if (x0 < x1)
1479  {
1480  xl(0) = x0;
1481  xl(1) = x1;
1482  }
1483  else
1484  {
1485  xl(0) = x1;
1486  xl(1) = x0;
1487  }
1488  if (y0 < y1)
1489  {
1490  yl(0) = y0;
1491  yl(1) = y1;
1492  }
1493  else
1494  {
1495  yl(0) = y1;
1496  yl(1) = y0;
1497  }
1498  ap.zoom (xl, yl);
1499  }
1500  mark_modified ();
1501  }
1502  }
1503  }
1504  break;
1505  }
1506  }
1507 
1508  return retval;
1509  }
1510 };
1511 
1512 class figure_manager
1513 {
1514 public:
1515 
1516  static bool instance_ok (void)
1517  {
1518  bool retval = true;
1519 
1520  if (! instance)
1521  instance = new figure_manager ();
1522 
1523  if (! instance)
1524  {
1525  ::error ("unable to create figure_manager object!");
1526 
1527  retval = false;
1528  }
1529 
1530  return retval;
1531  }
1532 
1533  ~figure_manager (void)
1534  {
1535  close_all ();
1536  }
1537 
1538  static void close_all (void)
1539  {
1540  if (instance_ok ())
1541  instance->do_close_all ();
1542  }
1543 
1544  static void new_window (figure::properties& fp)
1545  {
1546  if (instance_ok ())
1547  instance->do_new_window (fp);
1548  }
1549 
1550  static void delete_window (int idx)
1551  {
1552  if (instance_ok ())
1553  instance->do_delete_window (idx);
1554  }
1555 
1556  static void delete_window (const std::string& idx_str)
1557  {
1558  delete_window (str2idx (idx_str));
1559  }
1560 
1561  static void renumber_figure (const std::string& idx_str, double new_number)
1562  {
1563  if (instance_ok ())
1564  instance->do_renumber_figure (str2idx (idx_str), new_number);
1565  }
1566 
1567  static void toggle_window_visibility (int idx, bool is_visible)
1568  {
1569  if (instance_ok ())
1570  instance->do_toggle_window_visibility (idx, is_visible);
1571  }
1572 
1573  static void toggle_window_visibility (const std::string& idx_str,
1574  bool is_visible)
1575  {
1576  toggle_window_visibility (str2idx (idx_str), is_visible);
1577  }
1578 
1579  static void mark_modified (int idx)
1580  {
1581  if (instance_ok ())
1582  instance->do_mark_modified (idx);
1583  }
1584 
1585  static void mark_modified (const graphics_handle& gh)
1586  {
1587  mark_modified (hnd2idx (gh));
1588  }
1589 
1590  static void set_name (int idx)
1591  {
1592  if (instance_ok ())
1593  instance->do_set_name (idx);
1594  }
1595 
1596  static void set_name (const std::string& idx_str)
1597  {
1598  set_name (str2idx (idx_str));
1599  }
1600 
1601  static Matrix get_size (int idx)
1602  {
1603  return instance_ok () ? instance->do_get_size (idx) : Matrix ();
1604  }
1605 
1606  static Matrix get_size (const graphics_handle& gh)
1607  {
1608  return get_size (hnd2idx (gh));
1609  }
1610 
1611  static void print (const graphics_handle& gh, const std::string& cmd,
1612  const std::string& term)
1613  {
1614  if (instance_ok ())
1615  instance->do_print (hnd2idx (gh), cmd, term);
1616  }
1617 
1618  static void uimenu_update (const graphics_handle& figh,
1619  const graphics_handle& uimenuh, int id)
1620  {
1621  if (instance_ok ())
1622  instance->do_uimenu_update (hnd2idx (figh), uimenuh, id);
1623  }
1624 
1625  static void update_canvas (const graphics_handle& gh,
1626  const graphics_handle& ca)
1627  {
1628  if (instance_ok ())
1629  instance->do_update_canvas (hnd2idx (gh), ca);
1630  }
1631 
1632  static void toggle_menubar_visibility (int fig_idx, bool menubar_is_figure)
1633  {
1634  if (instance_ok ())
1635  instance->do_toggle_menubar_visibility (fig_idx, menubar_is_figure);
1636  }
1637 
1638  static void toggle_menubar_visibility (const std::string& fig_idx_str,
1639  bool menubar_is_figure)
1640  {
1641  toggle_menubar_visibility (str2idx (fig_idx_str), menubar_is_figure);
1642  }
1643 
1644 private:
1645 
1646  static figure_manager *instance;
1647 
1648  figure_manager (void) { }
1649 
1650  // No copying!
1651  figure_manager (const figure_manager&);
1652  figure_manager& operator = (const figure_manager&);
1653 
1654  // Singelton -- hide all of the above.
1655 
1656  static int curr_index;
1657  typedef std::map<int, plot_window*> window_map;
1658  typedef window_map::iterator wm_iterator;;
1659  window_map windows;
1660 
1661  static std::string fltk_idx_header;
1662 
1663  void do_close_all (void)
1664  {
1665  wm_iterator win;
1666  for (win = windows.begin (); win != windows.end (); win++)
1667  delete win->second;
1668  windows.clear ();
1669  }
1670 
1671  void do_new_window (figure::properties& fp)
1672  {
1673  int idx = figprops2idx (fp);
1674 
1675  if (idx >= 0 && windows.find (idx) == windows.end ())
1676  {
1677  Matrix pos = fp.get_boundingbox (true);
1678 
1679  int x = pos(0);
1680  int y = pos(1);
1681  int w = pos(2);
1682  int h = pos(3);
1683 
1684  idx2figprops (curr_index, fp);
1685 
1686  windows[curr_index++] = new plot_window (x, y, w, h, fp);
1687  }
1688  }
1689 
1690  void do_delete_window (int idx)
1691  {
1692  wm_iterator win = windows.find (idx);
1693 
1694  if (win != windows.end ())
1695  {
1696  delete win->second;
1697  windows.erase (win);
1698  }
1699  }
1700 
1701  void do_renumber_figure (int idx, double new_number)
1702  {
1703  wm_iterator win = windows.find (idx);
1704 
1705  if (win != windows.end ())
1706  win->second->renumber (new_number);
1707  }
1708 
1709  void do_toggle_window_visibility (int idx, bool is_visible)
1710  {
1711  wm_iterator win = windows.find (idx);
1712 
1713  if (win != windows.end ())
1714  {
1715  if (is_visible)
1716  win->second->show ();
1717  else
1718  win->second->hide ();
1719 
1720  win->second->redraw ();
1721  }
1722  }
1723 
1724  void do_toggle_menubar_visibility (int fig_idx, bool menubar_is_figure)
1725  {
1726  wm_iterator win = windows.find (fig_idx);
1727 
1728  if (win != windows.end ())
1729  {
1730  if (menubar_is_figure)
1731  win->second->show_menubar ();
1732  else
1733  win->second->hide_menubar ();
1734 
1735  win->second->redraw ();
1736  }
1737  }
1738 
1739  void do_mark_modified (int idx)
1740  {
1741  wm_iterator win = windows.find (idx);
1742 
1743  if (win != windows.end ())
1744  win->second->mark_modified ();
1745  }
1746 
1747  void do_set_name (int idx)
1748  {
1749  wm_iterator win = windows.find (idx);
1750 
1751  if (win != windows.end ())
1752  win->second->set_name ();
1753  }
1754 
1755  Matrix do_get_size (int idx)
1756  {
1757  Matrix sz (1, 2, 0.0);
1758 
1759  wm_iterator win = windows.find (idx);
1760 
1761  if (win != windows.end ())
1762  {
1763  sz(0) = win->second->w ();
1764  sz(1) = win->second->h ();
1765  }
1766 
1767  return sz;
1768  }
1769 
1770  void do_print (int idx, const std::string& cmd, const std::string& term)
1771  {
1772  wm_iterator win = windows.find (idx);
1773 
1774  if (win != windows.end ())
1775  win->second->print (cmd, term);
1776  }
1777 
1778  void do_uimenu_update (int idx, const graphics_handle& gh, int id)
1779  {
1780  wm_iterator win = windows.find (idx);
1781 
1782  if (win != windows.end ())
1783  win->second->uimenu_update (gh, id);
1784  }
1785 
1786  void do_update_canvas (int idx, const graphics_handle& ca)
1787  {
1788  wm_iterator win = windows.find (idx);
1789 
1790  if (win != windows.end ())
1791  {
1792  if (ca.ok ())
1793  win->second->show_canvas ();
1794  else
1795  win->second->hide_canvas ();
1796  }
1797  }
1798 
1799  static int str2idx (const caseless_str& clstr)
1800  {
1801  int ind;
1802  if (clstr.find (fltk_idx_header,0) == 0)
1803  {
1804  std::istringstream istr (clstr.substr (fltk_idx_header.size ()));
1805  if (istr >> ind)
1806  return ind;
1807  }
1808  error ("figure_manager: could not recognize fltk index");
1809  return -1;
1810  }
1811 
1812  void idx2figprops (int idx, figure::properties& fp)
1813  {
1814  std::ostringstream ind_str;
1815  ind_str << fltk_idx_header << idx;
1816  fp.set___plot_stream__ (ind_str.str ());
1817  }
1818 
1819  static int figprops2idx (const figure::properties& fp)
1820  {
1821  if (fp.get___graphics_toolkit__ () == FLTK_GRAPHICS_TOOLKIT_NAME)
1822  {
1823  octave_value ps = fp.get___plot_stream__ ();
1824  if (ps.is_string ())
1825  return str2idx (ps.string_value ());
1826  else
1827  return 0;
1828  }
1829  error ("figure_manager: figure is not fltk");
1830  return -1;
1831  }
1832 
1833  static int hnd2idx (double h)
1834  {
1836  if (fobj && fobj.isa ("figure"))
1837  {
1838  figure::properties& fp =
1839  dynamic_cast<figure::properties&> (fobj.get_properties ());
1840  return figprops2idx (fp);
1841  }
1842  error ("figure_manager: H (= %g) is not a figure", h);
1843  return -1;
1844  }
1845 
1846  static int hnd2idx (const graphics_handle& fh)
1847  {
1848  return hnd2idx (fh.value ());
1849  }
1850 };
1851 
1852 figure_manager *figure_manager::instance = 0;
1853 
1854 std::string figure_manager::fltk_idx_header="fltk index=";
1855 int figure_manager::curr_index = 1;
1856 
1857 static bool toolkit_loaded = false;
1858 
1859 static int
1860 __fltk_redraw__ (void)
1861 {
1862  if (toolkit_loaded)
1863  {
1864  // We scan all figures and add those which use FLTK.
1866  if (obj && obj.isa ("root"))
1867  {
1868  base_properties& props = obj.get_properties ();
1869  Matrix children = props.get_all_children ();
1870 
1871  for (octave_idx_type n = 0; n < children.numel (); n++)
1872  {
1873  graphics_object fobj = gh_manager::get_object (children (n));
1874  if (fobj && fobj.isa ("figure"))
1875  {
1876  figure::properties& fp =
1877  dynamic_cast<figure::properties&> (fobj.get_properties ());
1878  if (fp.get___graphics_toolkit__ ()
1879  == FLTK_GRAPHICS_TOOLKIT_NAME)
1880  figure_manager::new_window (fp);
1881  }
1882  }
1883  }
1884 
1885  // it seems that we have to call Fl::check twice to get everything drawn
1886  Fl::check ();
1887  Fl::check ();
1888  }
1889 
1890  return 0;
1891 }
1892 
1893 class fltk_graphics_toolkit : public base_graphics_toolkit
1894 {
1895 public:
1896  fltk_graphics_toolkit (void)
1897  : base_graphics_toolkit (FLTK_GRAPHICS_TOOLKIT_NAME),
1898  input_event_hook_fcn_id ()
1899  { }
1900 
1901  ~fltk_graphics_toolkit (void) { }
1902 
1903  bool is_valid (void) const { return true; }
1904 
1905  bool initialize (const graphics_object& go)
1906  {
1907  if (go.isa ("figure")
1908  || go.isa ("uimenu"))
1909  {
1910  if (go.isa ("uimenu"))
1911  update (go, uimenu::properties::ID_LABEL);
1912 
1913  return true;
1914  }
1915 
1916  return false;
1917  }
1918 
1919  void finalize (const graphics_object& go)
1920  {
1921  if (go.isa ("figure"))
1922  {
1923  octave_value ov = go.get (caseless_str ("__plot_stream__"));
1924 
1925  if (! ov.is_empty ())
1926  figure_manager::delete_window (ov.string_value ());
1927  }
1928  }
1929 
1930  void uimenu_set_fltk_label (graphics_object uimenu_obj)
1931  {
1932  if (uimenu_obj.valid_object ())
1933  {
1934  uimenu::properties& uimenup =
1935  dynamic_cast<uimenu::properties&> (uimenu_obj.get_properties ());
1936  std::string fltk_label = uimenup.get_label ();
1937  graphics_object go = gh_manager::get_object (uimenu_obj.get_parent ());
1938  if (go.isa ("uimenu"))
1939  fltk_label = dynamic_cast<const uimenu::properties&>
1940  (go.get_properties ()).get_fltk_label ()
1941  + "/"
1942  + fltk_label;
1943  else if (go.isa ("figure"))
1944  ;
1945  else
1946  error ("unexpected parent object\n");
1947 
1948  uimenup.set_fltk_label (fltk_label);
1949  }
1950  }
1951 
1952  void update (const graphics_object& go, int id)
1953  {
1954  if (go.isa ("figure"))
1955  {
1956  octave_value ov = go.get (caseless_str ("__plot_stream__"));
1957 
1958  if (! ov.is_empty ())
1959  {
1960  const figure::properties& fp =
1961  dynamic_cast<const figure::properties&> (go.get_properties ());
1962 
1963  switch (id)
1964  {
1966  figure_manager::toggle_window_visibility (ov.string_value (),
1967  fp.is_visible ());
1968  break;
1969 
1971  figure_manager::toggle_menubar_visibility
1972  (ov.string_value (), fp.menubar_is ("figure"));
1973  break;
1974 
1976  figure_manager::update_canvas (go.get_handle (),
1977  fp.get_currentaxes ());
1978  break;
1979 
1982  figure_manager::set_name (ov.string_value ());
1983  break;
1984 
1986  {
1987  std::string tmp = ov.string_value ();
1988  graphics_handle gh = fp.get___myhandle__ ();
1989  figure_manager::renumber_figure (tmp, gh.value ());
1990  figure_manager::set_name (tmp);
1991  }
1992  break;
1993  }
1994  }
1995  }
1996  else if (go.isa ("uimenu"))
1997  {
1998  if (id == uimenu::properties::ID_LABEL)
1999  uimenu_set_fltk_label (go);
2000 
2001  graphics_object fig = go.get_ancestor ("figure");
2002  figure_manager::uimenu_update (fig.get_handle (), go.get_handle (), id);
2003  }
2004  }
2005 
2006  void redraw_figure (const graphics_object& go) const
2007  {
2008  figure_manager::mark_modified (go.get_handle ());
2009 
2010  __fltk_redraw__ ();
2011  }
2012 
2013  void print_figure (const graphics_object& go,
2014  const std::string& term,
2015  const std::string& file_cmd, bool /*mono*/,
2016  const std::string& /*debug_file*/) const
2017  {
2018  figure_manager::print (go.get_handle (), file_cmd, term);
2019  redraw_figure (go);
2020  }
2021 
2022  Matrix get_canvas_size (const graphics_handle& fh) const
2023  {
2024  return figure_manager::get_size (fh);
2025  }
2026 
2027  double get_screen_resolution (void) const
2028  {
2029  // FLTK doesn't give this info.
2030  return 72.0;
2031  }
2032 
2033  Matrix get_screen_size (void) const
2034  {
2035  Matrix sz (1, 2, 0.0);
2036  sz(0) = Fl::w ();
2037  sz(1) = Fl::h ();
2038  return sz;
2039  }
2040 
2041  void close (void)
2042  {
2043  if (toolkit_loaded)
2044  {
2045  munlock ("__init_fltk__");
2046 
2047  figure_manager::close_all ();
2048 
2049  octave_value_list args = input_event_hook_fcn_id;
2050  args.append (false);
2051  Fremove_input_event_hook (args, 0);
2052 
2053  input_event_hook_fcn_id = octave_value_list ();
2054 
2055  // FIXME: ???
2056  Fl::wait (fltk_maxtime);
2057  }
2058  }
2059 
2060  void set_input_event_hook_id (const octave_value_list& id)
2061  {
2062  input_event_hook_fcn_id = id;
2063  }
2064 
2065 private:
2066  octave_value_list input_event_hook_fcn_id;
2067 };
2068 
2069 #endif
2070 
2071 DEFUN_DLD (__fltk_redraw__, , ,
2072  "-*- texinfo -*-\n\
2073 @deftypefn {Loadable Function} {} __fltk_redraw__ ()\n\
2074 Undocumented internal function.\n\
2075 @end deftypefn")
2076 {
2077 #ifdef HAVE_FLTK
2078  __fltk_redraw__ ();
2079 #else
2080  error ("__fltk_redraw__: not available without OpenGL and FLTK libraries");
2081 #endif
2082 
2083  return octave_value ();
2084 }
2085 
2086 // Initialize the fltk graphics toolkit.
2087 
2088 DEFUN_DLD (__init_fltk__, , ,
2089  "-*- texinfo -*-\n\
2090 @deftypefn {Loadable Function} {} __init_fltk__ ()\n\
2091 Undocumented internal function.\n\
2092 @end deftypefn")
2093 {
2094 #ifdef HAVE_FLTK
2096  error ("__init_fltk__: no graphics DISPLAY available");
2097  else if (! toolkit_loaded)
2098  {
2099  mlock ();
2100 
2101  fltk_graphics_toolkit *fltk = new fltk_graphics_toolkit ();
2102  graphics_toolkit tk (fltk);
2104  toolkit_loaded = true;
2105 
2106  octave_value fcn (new octave_builtin (F__fltk_redraw__));
2107  octave_value fcn_handle (new octave_fcn_handle (fcn, "@__fltk_redraw__"));
2108  octave_value_list id = Fadd_input_event_hook (fcn_handle, 1);
2109 
2110  fltk->set_input_event_hook_id (id);
2111  }
2112 #else
2113  error ("__init_fltk__: not available without OpenGL and FLTK libraries");
2114 #endif
2115 
2116  return octave_value ();
2117 }
2118 
2119 DEFUN_DLD (__fltk_maxtime__, args, ,
2120  "-*- texinfo -*-\n\
2121 @deftypefn {Loadable Function} {@var{maxtime} =} __fltk_maxtime__ ()\n\
2122 @deftypefnx {Loadable Function} {} __fltk_maxtime__ (@var{maxtime})\n\
2123 Undocumented internal function.\n\
2124 @end deftypefn")
2125 {
2126 #ifdef HAVE_FLTK
2127  octave_value retval = fltk_maxtime;
2128 
2129  if (args.length () == 1)
2130  {
2131  if (args(0).is_real_scalar ())
2132  fltk_maxtime = args(0).double_value ();
2133  else
2134  error ("argument must be a real scalar");
2135  }
2136 
2137  return retval;
2138 #else
2139  error ("__fltk_maxtime__: not available without OpenGL and FLTK libraries");
2140  return octave_value ();
2141 #endif
2142 }
2143 
2144 DEFUN_DLD (__have_fltk__, , ,
2145  "-*- texinfo -*-\n\
2146 @deftypefn {Loadable Function} {@var{FLTK_available} =} __have_fltk__ ()\n\
2147 Undocumented internal function.\n\
2148 @end deftypefn")
2149 {
2150  octave_value retval;
2151 
2152 #ifdef HAVE_FLTK
2153  retval = true;
2154 #else
2155  retval = false;
2156 #endif
2157 
2158  return retval;
2159 }
2160 
2161 // FIXME: This function should be abstracted and made potentially
2162 // available to all graphics toolkits. This suggests putting it in
2163 // graphics.cc as is done for drawnow() and having the master
2164 // mouse_wheel_zoom function call fltk_mouse_wheel_zoom. The same
2165 // should be done for gui_mode and fltk_gui_mode. For now (2011.01.30),
2166 // just changing function names and docstrings.
2167 
2168 DEFUN_DLD (mouse_wheel_zoom, args, nargout,
2169  "-*- texinfo -*-\n\
2170 @deftypefn {Loadable Function} {@var{val} =} mouse_wheel_zoom ()\n\
2171 @deftypefnx {Loadable Function} {@var{old_val} =} mouse_wheel_zoom (@var{new_val})\n\
2172 @deftypefnx {Loadable Function} {} mouse_wheel_zoom (@var{new_val}, \"local\")\n\
2173 Query or set the mouse wheel zoom factor.\n\
2174 \n\
2175 The zoom factor is a number in the range (0,1) which is the percentage of the\n\
2176 current axis limits that will be used when zooming. For example, if the\n\
2177 current x-axis limits are [0, 50] and @code{mouse_wheel_zoom} is 0.4 (40%),\n\
2178 then a zoom operation will change the limits by 20.\n\
2179 \n\
2180 When called from inside a function with the @qcode{\"local\"} option, the\n\
2181 variable is changed locally for the function and any subroutines it calls. \n\
2182 The original variable value is restored when exiting the function.\n\
2183 \n\
2184 This function is currently implemented only for the FLTK graphics toolkit.\n\
2185 @seealso{gui_mode}\n\
2186 @end deftypefn")
2187 {
2188 #ifdef HAVE_FLTK
2189  return SET_INTERNAL_VARIABLE_WITH_LIMITS(wheel_zoom_speed, 0.0001, 0.9999);
2190 #else
2191  error ("mouse_wheel_zoom: not available without OpenGL and FLTK libraries");
2192  return octave_value ();
2193 #endif
2194 }
2195 
2196 DEFUN_DLD (gui_mode, args, ,
2197  "-*- texinfo -*-\n\
2198 @deftypefn {Built-in Function} {@var{mode} =} gui_mode ()\n\
2199 @deftypefnx {Built-in Function} {} gui_mode (@var{mode})\n\
2200 Query or set the GUI mode for the current graphics toolkit.\n\
2201 The @var{mode} argument can be one of the following strings:\n\
2202 \n\
2203 @table @asis\n\
2204 @item @qcode{\"2d\"}\n\
2205 Allows panning and zooming of current axes.\n\
2206 \n\
2207 @item @qcode{\"3d\"}\n\
2208 Allows rotating and zooming of current axes.\n\
2209 \n\
2210 @item @qcode{\"none\"}\n\
2211 Mouse inputs have no effect.\n\
2212 @end table\n\
2213 \n\
2214 This function is currently implemented only for the FLTK graphics toolkit.\n\
2215 @seealso{mouse_wheel_zoom}\n\
2216 @end deftypefn")
2217 {
2218 #ifdef HAVE_FLTK
2219  caseless_str mode_str;
2220 
2221  if (gui_mode == pan_zoom)
2222  mode_str = "2d";
2223  else if (gui_mode == rotate_zoom)
2224  mode_str = "3d";
2225  else
2226  mode_str = "none";
2227 
2228  bool failed = false;
2229 
2230  if (args.length () == 1)
2231  {
2232  if (args(0).is_string ())
2233  {
2234  mode_str = args(0).string_value ();
2235 
2236  if (mode_str.compare ("2d"))
2237  gui_mode = pan_zoom;
2238  else if (mode_str.compare ("3d"))
2239  gui_mode = rotate_zoom;
2240  else if (mode_str.compare ("none"))
2241  gui_mode = none;
2242  else
2243  failed = true;
2244  }
2245  else
2246  failed = true;
2247  }
2248 
2249  if (failed)
2250  error ("MODE must be one of the strings: \"2D\", \"3D\", or \"none\"");
2251 
2252  return octave_value (mode_str);
2253 #else
2254  error ("gui_mode: not available without OpenGL and FLTK libraries");
2255  return octave_value ();
2256 #endif
2257 }