Mercurial > hg > octave-nkf
view src/graphics/fltk_backend/fltk_backend.cc @ 7839:71eb1793f0db
fltk_backend can now handle figure.position changes
author | Shai Ayal <shaiay@sourceforge.net> |
---|---|
date | Sat, 23 Feb 2008 20:53:23 +0200 |
parents | caab78e7e377 |
children | 2c8f693c32fd |
line wrap: on
line source
/* Copyright (C) 2007 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/>. */ #include <map> #include <set> #include <sstream> #include <iostream> #ifdef HAVE_CONFIG_H #include <config.h> #endif #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 "oct.h" #include "parse.h" #include "graphics.h" 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 x, int y, int w, int h, double num) : Fl_Gl_Window (x, y, w, h, 0), number (num) { // ask for double buffering and a depth buffer mode(FL_DEPTH | FL_DOUBLE ); }; ~OpenGL_fltk () {}; private: double number; opengl_renderer renderer; void setup_viewport (int _w, int _h) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); glViewport (0, 0, _w, _h); // glOrtho (0.0, 1, 0.0, 1, -1.0, 1.0); } void draw () { if (!valid ()) { valid (1); setup_viewport (w (), h ()); } #ifndef MESA glDrawBuffer (GL_FRONT_AND_BACK); #endif // !MESA renderer.draw (gh_manager::lookup (number)); #ifndef MESA glDrawBuffer (GL_BACK); #endif // !MESA }; void resize (int _x,int _y,int _w,int _h) { Fl_Gl_Window::resize (_x, _y, _w, _h); setup_viewport (_w, _h); redraw (); }; 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, double num) : Fl_Window (_x, _y, _w, _h, "octave"), _number (num), err_props (graphics_handle (), graphics_handle ()) { callback (window_close, static_cast<void*> (this)); begin(); { canvas = new OpenGL_fltk (0, 0, _w , _h - status_h, num); autoscale = new Fl_Button (0, _h - status_h, status_h, status_h, "A"); autoscale->callback (button_callback, static_cast<void*> (this)); help = new Fl_Button (status_h, _h - status_h, status_h, status_h, "H"); help->callback (button_callback, static_cast<void*> (this)); status = new Fl_Output (2*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 (); resizable (canvas); size_range (4*status_h, 2*status_h); std::stringstream name; name << "octave: figure " << number (); label (name.str ().c_str ()); } ~plot_window () { canvas->hide(); status->hide(); this->hide(); delete canvas; delete status; }; double number () { return _number;}; void mark_modified () { damage (FL_DAMAGE_ALL); } private: // figure number double _number; figure::properties err_props; // status area height static const int status_h = 20; // window callback static void window_close (Fl_Widget* w, 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* w, void* data) { static_cast<plot_window*> (data)-> button_press (w); }; void button_press (Fl_Widget* widg) { if (widg == autoscale) std::cout << "AS " << number () << " pressed\n"; if (widg == help) fl_message (help_text); } OpenGL_fltk* canvas; Fl_Button* autoscale; Fl_Button* help; Fl_Output* status; figure::properties& get_figure_props () { graphics_object obj = gh_manager::get_object (_number); if (obj && obj.isa ("figure")) { figure::properties& fprops = dynamic_cast<figure::properties&> (obj.get_properties ()); return fprops; } error("OpenGL_fltk:: Internal error -- Not associated with figure!"); return err_props; }; void pixel2pos (int px, int py, double& x, double& y) const { x = static_cast<double> (px) / w (); y = 1. - static_cast<double> (py) / (h () - status_h); } graphics_handle pixel2axes (int px, int py) { double x,y; pixel2pos (px, py, x, y); figure::properties pp = get_figure_props (); Matrix children = (get_figure_props ()).get_children (); for (octave_idx_type n = 0; n < children.numel (); n++) { graphics_object ax = gh_manager::get_object (children (n)); if (ax) { if (ax.isa ("axes")) { axes::properties& props = dynamic_cast<axes::properties&> (ax.get_properties ()); Matrix pos = props.get_position (). matrix_value (); if (x >= pos(0) && x <= pos(0) + pos(2) && y >= pos(1) && y <= pos(1) + pos(3) ) return props.get___myhandle__ (); } } } return graphics_handle (); } void pixel2status (int px, int py) { double x,y; std::stringstream cbuf; pixel2pos (px, py, x, y); cbuf << "[" << x << ", " << y <<"]"; 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; octave_value_list args; args(0) = number (); args(1) = "position"; args(2) = pos; feval("set",args); } void draw (void) { figure::properties& fp = get_figure_props (); Matrix pos = fp.get_position ().matrix_value (); Fl_Window::resize (pos(0), pos(1) , pos(2), pos(3)); return Fl_Window::draw (); } int handle (int event) { static double x0,y0; static graphics_handle h0 = graphics_handle (); static bool in_drag = false; 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_MOVE: pixel2status (Fl::event_x (), Fl::event_y ()); break; case FL_PUSH: if (Fl::event_button () == 1) { pixel2pos (Fl::event_x (), Fl::event_y (), x0, y0); h0 = pixel2axes (Fl::event_x (), Fl::event_y ()); return 1; } break; case FL_DRAG: pixel2status (Fl::event_x (), Fl::event_y ()); if (Fl::event_button () == 1) { in_drag = true; return 1; } break; case FL_RELEASE: if (Fl::event_button () == 1) { // end of drag -- zoom if (in_drag) { in_drag = false; } // one click -- select axes else if ( Fl::event_clicks () == 0) { if (h0.ok ()) get_figure_props ().set_currentaxes (h0. value()); return 1; } } break; } return retval; } }; class figure_manager { public: static figure_manager& Instance () { static figure_manager fm; return fm; } ~figure_manager () { close_all (); } void close_all () { wm_iterator win; for (win = windows.begin (); win != windows.end (); win++) delete (*win).second; windows.clear (); } void new_window (double num) { if (windows.find (num) != windows.end ()) return; int x,y,w,h; default_size(x,y,w,h); windows[num] = new plot_window (x, y, w, h, num); }; void delete_window (double num) { wm_iterator win; if ( (win=windows.find (num)) != windows.end ()) { delete (*win).second; windows.erase (win); } }; void mark_modified (double num) { wm_iterator win; if ( (win=windows.find (num)) != windows.end ()) { (*win).second->mark_modified (); } }; Matrix get_size (double num) { Matrix sz (1, 2, 0.0); wm_iterator win; if ( (win=windows.find (num)) != windows.end ()) { sz(0) = (*win).second->w (); sz(1) = (*win).second->h (); } return sz; } private: figure_manager () {}; figure_manager (const figure_manager& ) {}; figure_manager& operator = (const figure_manager&) {return *this;}; // singelton -- hide all of the above typedef std::map<double, plot_window*> window_map; typedef window_map::iterator wm_iterator;; window_map windows; void default_size (int& x, int& y, int& w, int& h) { x = 10; y = 10; w = 400; h = 300; } }; #define FLTK_BACKEND_NAME "fltk" 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& fh) const { if (fh.is_real_scalar ()) figure_manager::Instance ().delete_window (fh.double_value ()); } void redraw_figure (const graphics_handle& fh) const { figure_manager::Instance ().mark_modified (fh.value ()); } 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.value ()); } 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__, args, nargout,"") { 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__, args, nargout,"") { figure_manager::Instance ().close_all (); graphics_backend::unregister_backend (FLTK_BACKEND_NAME); backend_registered = false; // 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, nargout,"") { 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__, args, nargout,\ "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 (children (n)); // put figure handle in __plot_stream__ so we know // which window to close. Only do this if necesarry // since it modifies the figure and causes a redraw octave_value ps = fp.get___plot_stream__ (); if (ps.is_real_scalar ()) if (ps.double_value () == fp.get___myhandle__ ().value ()) continue; octave_value_list oargs; oargs(0) = fp.get___myhandle__ ().value (); oargs(1) = "__plot_stream__"; oargs(2) = fp.get___myhandle__ ().value (); feval ("set" , oargs); } } } } Fl::wait(fltk_maxtime); return retval; } /* to init autoload("__init_fltk__",[pwd(),"/fltk_backend.oct"]) autoload("__remove_fltk__",[pwd(),"/fltk_backend.oct"]) autoload("__fltk_redraw__",[pwd(),"/fltk_backend.oct"]) autoload("__fltk_maxtime__",[pwd(),"/fltk_backend.oct"]) input_event_hook ("__fltk_redraw__"); __init_fltk__ (); set(gcf(),"__backend__","fltk") plot(randn(1e3,1)); */