diff src/DLD-FUNCTIONS/fltk_backend.cc @ 7874:e3a502930e2a

eliminate src/graphics subdirectory
author John W. Eaton <jwe@octave.org>
date Thu, 05 Jun 2008 18:00:37 -0400
parents src/graphics/fltk_backend/fltk_backend.cc@e6d5532f760e
children 3ffc34caec65
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/DLD-FUNCTIONS/fltk_backend.cc	Thu Jun 05 18:00:37 2008 -0400
@@ -0,0 +1,811 @@
+/*
+
+Copyright (C) 2007, 2008 Shai Ayal
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+/*
+
+To initialize:
+
+  input_event_hook ("__fltk_redraw__");
+  __init_fltk__ ();
+  set (gcf (), "__backend__", "fltk");
+  plot (randn (1e3, 1));
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <map>
+#include <set>
+#include <sstream>
+#include <iostream>
+
+#include "gl-render.h"
+
+#include <FL/Fl.H>
+#include <FL/Fl_Window.H>
+#include <FL/Fl_Output.H>
+#include <FL/Fl_Button.H>
+#include <FL/Fl_Gl_Window.H>
+#include <FL/fl_ask.H>
+#include <FL/fl_draw.H>
+#include <FL/gl.h>
+
+#include "oct.h"
+#include "parse.h"
+#include "graphics.h"
+
+#define FLTK_BACKEND_NAME "fltk"
+
+const char* help_text = "\
+Keyboard Shortcuts\n\
+a - autoscale\n\
+g - toggle grid\n\
+\n\
+Mouse\n\
+left drag - zoom\n\
+right click - unzoom\n\
+double click - copy coordinates to clipboard\
+";
+
+class OpenGL_fltk : public Fl_Gl_Window
+{
+public:
+  OpenGL_fltk (int xx, int yy, int ww, int hh, double num)
+    : Fl_Gl_Window (xx, yy, ww, hh, 0), number (num), in_zoom (false)
+  {
+    // ask for double buffering and a depth buffer
+    mode (FL_DEPTH | FL_DOUBLE);
+  }
+
+  ~OpenGL_fltk (void) { }
+
+  void zoom (bool z) { in_zoom = z; }
+  bool zoom (void) { return in_zoom; }
+  void set_zoom_box (const Matrix& zb) { zoom_box = zb; }
+
+private:
+  double number;
+  opengl_renderer renderer;
+  bool in_zoom;
+
+  // (x1,y1,x2,y2)
+  Matrix zoom_box;
+
+  void setup_viewport (int _w, int _h)
+  {
+    glMatrixMode (GL_PROJECTION);
+    glLoadIdentity ();
+    glViewport (0, 0, _w, _h);
+  }
+
+  void draw (void)
+  {
+    if (!valid ())
+      {
+	valid (1);
+	setup_viewport (w (), h ());
+      }
+
+    renderer.draw (gh_manager::lookup (number));
+  }
+
+  void resize (int _x,int _y,int _w,int _h)
+  {
+    Fl_Gl_Window::resize (_x, _y, _w, _h);
+    setup_viewport (_w, _h);
+    redraw ();
+  }
+
+  void draw_overlay (void)
+  {
+    if (!in_zoom)
+      return;
+
+    if (!valid())
+      {
+	valid(1);
+	setup_viewport (w (), h ());
+      }
+
+    glPushMatrix ();
+
+    glMatrixMode (GL_MODELVIEW);
+    glLoadIdentity ();
+
+    glMatrixMode (GL_PROJECTION);
+    glLoadIdentity ();
+    gluOrtho2D (0.0, w (), 0.0, h ());
+
+    glPushAttrib (GL_DEPTH_BUFFER_BIT | GL_CURRENT_BIT);
+    glDisable (GL_DEPTH_TEST);
+
+    glLineWidth (1);
+    glBegin (GL_LINE_STRIP);
+    gl_color(0);
+    glVertex2d (zoom_box(0), h () - zoom_box(1));
+    glVertex2d (zoom_box(0), h () - zoom_box(3));
+    glVertex2d (zoom_box(2), h () - zoom_box(3));
+    glVertex2d (zoom_box(2), h () - zoom_box(1));
+    glVertex2d (zoom_box(0), h () - zoom_box(1));
+    glEnd ();
+
+    glPopAttrib ();
+    glPopMatrix ();
+  }
+
+  int handle (int event)
+  {
+    int retval = Fl_Gl_Window::handle (event);
+
+    switch (event)
+      {
+      case FL_ENTER:
+	window ()->cursor (FL_CURSOR_CROSS);
+	return 1;
+
+      case FL_LEAVE:
+	window ()->cursor (FL_CURSOR_DEFAULT);
+	return 1;
+      }
+
+    return retval;
+  }
+};
+
+class plot_window : public Fl_Window
+{
+public:
+  plot_window (int _x, int _y, int _w, int _h, figure::properties& _fp)
+    : Fl_Window (_x, _y, _w, _h, "octave"), fp (_fp)
+  {
+    callback (window_close, static_cast<void*> (this));
+
+    begin ();
+    {
+      canvas = new
+	OpenGL_fltk (0, 0, _w , _h - status_h, number ());
+
+      autoscale = new
+	Fl_Button (0,
+		   _h - status_h,
+		   status_h,
+		   status_h,
+		   "A");
+      autoscale->callback (button_callback, static_cast<void*> (this));
+
+      togglegrid = new
+	Fl_Button (status_h,
+		   _h - status_h,
+		   status_h,
+		   status_h,
+		   "G");
+      togglegrid->callback (button_callback, static_cast<void*> (this));
+
+      help = new
+	Fl_Button (2*status_h,
+		   _h - status_h,
+		   status_h,
+		   status_h,
+		   "H");
+      help->callback (button_callback, static_cast<void*> (this));
+
+      status = new
+	Fl_Output (3*status_h,
+		   _h - status_h,
+		   _w > 2*status_h ? _w - status_h : 0,
+		   status_h, "");
+
+      status->textcolor (FL_BLACK);
+      status->color (FL_GRAY);
+      status->textfont (FL_COURIER);
+      status->textsize (10);
+      status->box (FL_ENGRAVED_BOX);
+
+      // This allows us to have a valid OpenGL context right away
+      canvas->mode (FL_DEPTH | FL_DOUBLE );
+      show ();
+      canvas->show ();
+      canvas->make_current ();
+    }
+    end ();
+
+    status->show ();
+    autoscale->show ();
+    togglegrid->show ();
+
+    resizable (canvas);
+    size_range (4*status_h, 2*status_h);
+
+    std::stringstream name;
+    name << "octave: figure " << number ();
+    label (name.str ().c_str ());
+  }
+
+  ~plot_window (void)
+  {
+    canvas->hide ();
+    status->hide ();
+    this->hide ();
+    delete canvas;
+    delete status;
+  }
+
+  // FIXME -- this could change
+  double number (void) { return fp.get___myhandle__ ().value (); }
+
+  void mark_modified (void)
+  {
+    damage (FL_DAMAGE_ALL);
+    canvas->damage (FL_DAMAGE_ALL);
+  }
+
+private:
+  // figure properties
+  figure::properties& fp;
+
+  // status area height
+  static const int status_h = 20;
+
+  // window callback
+  static void window_close (Fl_Widget*, void* data)
+  {
+    octave_value_list args;
+    args(0) = static_cast<plot_window*> (data)->number ();
+    feval ("close", args);
+  }
+
+  // button callbacks
+  static void button_callback (Fl_Widget* ww, void* data)
+  {
+    static_cast<plot_window*> (data)->button_press (ww);
+  }
+
+  void button_press (Fl_Widget* widg)
+  {
+    if (widg == autoscale)
+      axis_auto ();
+
+    if (widg == togglegrid)
+      toggle_grid ();
+
+    if (widg == help)
+      fl_message (help_text);
+  }
+
+  OpenGL_fltk* canvas;
+  Fl_Button* autoscale;
+  Fl_Button* togglegrid;
+  Fl_Button* help;
+  Fl_Output* status;
+
+  void axis_auto (void)
+  {
+    octave_value_list args;
+    args(0) = "auto";
+    feval ("axis",args);
+    mark_modified ();
+  }
+
+  void toggle_grid (void)
+  {
+    feval ("grid");
+    mark_modified ();
+  }
+
+  void pixel2pos (int px, int py, double& xx, double& yy) const
+  {
+    graphics_object ax = gh_manager::get_object (fp.get_currentaxes ());
+
+    if (ax && ax.isa ("axes"))
+      {
+	axes::properties& ap =
+	  dynamic_cast<axes::properties&> (ax.get_properties ());
+	ColumnVector pp = ap.pixel2coord (px, py);
+	xx = pp(0);
+	yy = pp(1);
+      }
+  }
+
+  graphics_handle pixel2axes (int /* px */, int /* py */)
+  {
+    Matrix kids = fp.get_children ();
+
+    for (octave_idx_type n = 0; n < kids.numel (); n++)
+      {
+	graphics_object ax = gh_manager::get_object (kids (n));
+	if (ax && ax.isa ("axes"))
+	  {
+#if 0
+	     axes::properties& ap =
+	       dynamic_cast<axes::properties&> (ax.get_properties ());
+
+	     // std::cout << "\npixpos="<<pixpos<<"(px,py)=("<<px<<","<<py<<")\n";
+	     if (px >= pixpos(0) && px <= pixpos(2)
+		 && py >= pixpos(1) && py <= pixpos(3))
+	       return ap.get___myhandle__ ();
+#endif
+	  }
+      }
+
+    return graphics_handle ();
+  }
+
+  void pixel2status (int px0, int py0, int px1 = -1, int py1 = -1)
+  {
+    double x0, y0, x1, y1;
+    std::stringstream cbuf;
+
+    pixel2pos (px0, py0, x0, y0);
+    cbuf << "[" << x0 << ", " << y0 << "]";
+    if (px1 >= 0)
+      {
+	pixel2pos (px1, py1, x1, y1);
+	cbuf << " -> ["<< x1 << ", " << y1 << "]";
+      }
+
+    status->value (cbuf.str ().c_str ());
+    status->redraw ();
+  }
+
+  void resize (int _x,int _y,int _w,int _h)
+  {
+    Fl_Window::resize (_x, _y, _w, _h);
+
+    Matrix pos (1,4,0);
+    pos(0) = _x;
+    pos(1) = _y;
+    pos(2) = _w;
+    pos(3) = _h - status_h;
+
+    fp.set_position (pos);
+  }
+
+  void draw (void)
+  {
+    Matrix pos = fp.get_position ().matrix_value ();
+    Fl_Window::resize (pos(0), pos(1) , pos(2), pos(3) + status_h);
+
+    return Fl_Window::draw ();
+  }
+
+  int handle (int event)
+  {
+    static int px0,py0;
+    static graphics_handle h0 = graphics_handle ();
+
+    int retval = Fl_Window::handle (event);
+
+    // we only handle events which are in the canvas area
+    if (Fl::event_y () >= h() - status_h)
+      return retval;
+
+    switch (event)
+      {
+      case FL_KEYDOWN:
+	switch(Fl::event_key ())
+	  {
+	  case 'a':
+	  case 'A':
+	    axis_auto ();
+	    break;
+
+	  case 'g':
+	  case 'G':
+	    toggle_grid ();
+	    break;
+	  }
+	break;
+
+      case FL_MOVE:
+	pixel2status (Fl::event_x (), Fl::event_y ());
+	break;
+
+      case FL_PUSH:
+	if (Fl::event_button () == 1)
+	  {
+	    px0 = Fl::event_x ();
+	    py0 = Fl::event_y ();
+	    h0 = pixel2axes (Fl::event_x (), Fl::event_y ());
+	    return 1;
+	  }
+	break;
+
+      case FL_DRAG:
+	pixel2status (px0, py0, Fl::event_x (), Fl::event_y ());
+	if (Fl::event_button () == 1)
+	  {
+	    canvas->zoom (true);
+	    Matrix zoom_box (1,4,0);
+	    zoom_box (0) = px0;
+	    zoom_box (1) = py0;
+	    zoom_box (2) =  Fl::event_x ();
+	    zoom_box (3) =  Fl::event_y ();
+	    canvas->set_zoom_box (zoom_box);
+	    canvas->redraw_overlay ();
+	    return 1;
+	  }
+	break;
+
+      case FL_RELEASE:
+	if (Fl::event_button () == 1)
+	  {
+	    // end of drag -- zoom
+	    if (canvas->zoom ())
+	      {
+		canvas->zoom (false);
+		double x0,y0,x1,y1;
+		graphics_object ax =
+		  gh_manager::get_object (fp.get_currentaxes ());
+		if (ax && ax.isa ("axes"))
+		  {
+		    axes::properties& ap =
+		      dynamic_cast<axes::properties&> (ax.get_properties ());
+		    pixel2pos (px0, py0, x0, y0);
+		    pixel2pos (Fl::event_x (), Fl::event_y (), x1, y1);
+		    Matrix xl (1,2,0);
+		    Matrix yl (1,2,0);
+		    if (x0 < x1)
+		      {
+			xl(0) = x0;
+			xl(1) = x1;
+		      }
+		    else
+		      {
+			xl(0) = x1;
+			xl(1) = x0;
+		      }
+
+		    if (y0 < y1)
+		      {
+			yl(0) = y0;
+			yl(1) = y1;
+		      }
+		    else
+		      {
+			yl(0) = y1;
+			yl(1) = y0;
+		      }
+		    ap.zoom (xl, yl);
+		    mark_modified ();
+		  }
+	      }
+	    // one click -- select axes
+	    else if ( Fl::event_clicks () == 0)
+	      {
+		std::cout << "ca="<< h0.value ()<<"\n";
+		if (h0.ok ())
+		  fp.set_currentaxes (h0.value());
+		return 1;
+	      }
+	  }
+	else if (Fl::event_button () == 3)
+	  {
+	    graphics_object ax =
+	      gh_manager::get_object (fp.get_currentaxes ());
+	    if (ax && ax.isa ("axes"))
+	      {
+		axes::properties& ap =
+		  dynamic_cast<axes::properties&> (ax.get_properties ());
+		ap.unzoom ();
+		mark_modified ();
+	      }
+	  }
+	break;
+      }
+
+    return retval;
+  }
+};
+
+class figure_manager
+{
+public:
+
+  static figure_manager& Instance (void)
+  {
+    static figure_manager fm;
+    return fm;
+  }
+
+  ~figure_manager (void)
+  {
+    close_all ();
+  }
+
+  void close_all (void)
+  {
+    wm_iterator win;
+    for (win = windows.begin (); win != windows.end (); win++)
+      delete (*win).second;
+    windows.clear ();
+  }
+
+  void new_window (figure::properties& fp)
+  {
+    int x, y, w, h;
+
+    int idx = figprops2idx (fp);
+    if (idx >= 0 && windows.find (idx) == windows.end ())
+      {
+	default_size(x,y,w,h);
+	idx2figprops (curr_index , fp);
+	windows[curr_index++] = new plot_window (x, y, w, h, fp);
+      }
+  }
+
+  void delete_window (int idx)
+  {
+    wm_iterator win;
+    if ((win = windows.find (idx)) != windows.end ())
+      {
+	delete (*win).second;
+	windows.erase (win);
+      }
+  }
+
+  void delete_window (std::string idx_str)
+  {
+    delete_window (str2idx (idx_str));
+  }
+
+  void mark_modified (int idx)
+  {
+    wm_iterator win;
+    if ((win=windows.find (idx)) != windows.end ())
+      {
+	(*win).second->mark_modified ();
+      }
+  }
+
+  void mark_modified (const graphics_handle& gh)
+  {
+    mark_modified (hnd2idx (gh));
+  }
+
+  Matrix get_size (int idx)
+  {
+    Matrix sz (1, 2, 0.0);
+
+    wm_iterator win;
+    if ((win = windows.find (idx)) != windows.end ())
+      {
+	sz(0) = (*win).second->w ();
+	sz(1) = (*win).second->h ();
+      }
+
+    return sz;
+  }
+
+  Matrix get_size (const graphics_handle& gh)
+  {
+    return get_size (hnd2idx (gh));
+  }
+
+private:
+  figure_manager (void) { }
+  figure_manager (const figure_manager&) { }
+  figure_manager& operator = (const figure_manager&) { return *this; }
+  // singelton -- hide all of the above
+
+  static int curr_index;
+  typedef std::map<int, plot_window*> window_map;
+  typedef window_map::iterator wm_iterator;;
+  window_map windows;
+
+  static std::string fltk_idx_header;
+
+  void default_size (int& x, int& y, int& w, int& h)
+  {
+    x = 10;
+    y = 10;
+    w = 400;
+    h = 300;
+  }
+
+  int str2idx (const caseless_str clstr)
+  {
+    int ind;
+    if (clstr.find (fltk_idx_header,0) == 0)
+      {
+	std::istringstream istr (clstr.substr (fltk_idx_header.size ()));
+	if (istr >> ind)
+	  return ind;
+      }
+    error ("fltk_backend: could not recognize fltk index");
+    return -1;
+  }
+
+  void idx2figprops (int idx, figure::properties& fp)
+  {
+    std::ostringstream ind_str;
+    ind_str << fltk_idx_header << idx;
+    fp.set___plot_stream__ (ind_str.str ());
+  }
+
+  int figprops2idx (const figure::properties& fp)
+  {
+    if (fp.get___backend__ () == FLTK_BACKEND_NAME)
+      {
+	octave_value ps = fp.get___plot_stream__ ();
+	if (ps.is_string ())
+	  return str2idx (ps.string_value ());
+	else
+	  return 0;
+      }
+    error ("fltk_backend:: figure is not fltk");
+    return -1;
+  }
+
+  int hnd2idx (const graphics_handle& fh)
+  {
+    return hnd2idx (fh.value ());
+  }
+
+  int hnd2idx (const double h)
+  {
+    graphics_object fobj = gh_manager::get_object (h);
+    if (fobj &&  fobj.isa ("figure"))
+      {
+	figure::properties& fp =
+	  dynamic_cast<figure::properties&> (fobj.get_properties ());
+	return figprops2idx (fp);
+      }
+    error ("fltk_backend:: not a figure");
+    return -1;
+  }
+};
+
+std::string figure_manager::fltk_idx_header="fltk index=";
+int figure_manager::curr_index = 1;
+
+class fltk_backend : public base_graphics_backend
+{
+public:
+  fltk_backend (void)
+    : base_graphics_backend (FLTK_BACKEND_NAME) { }
+
+  ~fltk_backend (void) { }
+
+  bool is_valid (void) const { return true; }
+
+  void close_figure (const octave_value& ov) const
+  {
+    if (ov.is_string ())
+      figure_manager::Instance ().delete_window (ov.string_value ());
+  }
+
+  void redraw_figure (const graphics_handle& fh) const
+  {
+    figure_manager::Instance ().mark_modified (fh);
+  }
+
+  void print_figure (const graphics_handle& /*fh*/,
+		     const std::string& /*term*/,
+		     const std::string& /*file*/, bool /*mono*/,
+		     const std::string& /*debug_file*/) const { }
+
+  Matrix get_canvas_size (const graphics_handle& fh) const
+  {
+    return figure_manager::Instance ().get_size (fh);
+  }
+
+  double get_screen_resolution (void) const
+  {
+    // FLTK doesn't give this info
+    return 72.0;
+  }
+
+  Matrix get_screen_size (void) const
+  {
+    Matrix sz (1, 2, 0.0);
+    sz(0) = Fl::w ();
+    sz(1) = Fl::h ();
+    return sz;
+  }
+};
+
+static bool backend_registered = false;
+// call this to init the fltk backend
+DEFUN_DLD (__init_fltk__, , , "")
+{
+  graphics_backend::register_backend (new fltk_backend);
+  backend_registered = true;
+
+  octave_value retval;
+  return retval;
+}
+
+
+// call this to delete the fltk backend
+DEFUN_DLD (__remove_fltk__, , , "")
+{
+  figure_manager::Instance ().close_all ();
+  graphics_backend::unregister_backend (FLTK_BACKEND_NAME);
+  backend_registered = false;
+
+  // FIXME ???
+  // give FLTK 10 seconds to wrap it up
+  Fl::wait(10);	
+  octave_value retval;
+  return retval;	
+}
+
+// give FLTK no more than 0.01 sec to do it's stuff
+static double fltk_maxtime = 1e-2;
+
+// call this to delete the fltk backend
+DEFUN_DLD (__fltk_maxtime__, args, ,"")
+{
+  octave_value retval = fltk_maxtime;
+
+  if (args.length () == 1)
+    {
+      if (args(0).is_real_scalar ())
+      fltk_maxtime = args(0).double_value ();
+    else
+      error ("argument must be a real scalar");
+    }
+
+  return retval;
+}
+
+// call this from the idle_callback to refresh windows
+DEFUN_DLD (__fltk_redraw__, , ,
+  "internal function for the fltk backend")
+{
+  octave_value retval;
+
+  if (!backend_registered)
+    return retval;
+
+  // we scan all figures and add those which use FLTK as a backend
+  graphics_object obj = gh_manager::get_object (0);
+  if (obj && obj.isa ("root_figure"))
+    {
+      base_properties& props = obj.get_properties ();
+      Matrix children = props.get_children ();
+
+      for (octave_idx_type n = 0; n < children.numel (); n++)
+        {
+          graphics_object fobj = gh_manager::get_object (children (n));
+          if (fobj && fobj.isa ("figure"))
+	    {
+	      figure::properties& fp =
+		dynamic_cast<figure::properties&> (fobj.get_properties ());
+	      if (fp.get___backend__ () == FLTK_BACKEND_NAME)
+		figure_manager::Instance ().new_window (fp);
+	    }
+        }
+    }
+
+  Fl::wait (fltk_maxtime);	
+
+  return retval;	
+}
+
+/*
+;;; Local Variables: ***
+;;; mode: C++ ***
+;;; End: ***
+*/