LoMainWindow.C

Go to the documentation of this file.
00001 /**
00002    \file  Robots/LoBot/ui/LoMainWindow.C
00003    \brief The Lobot/Robolocust main window.
00004 */
00005 
00006 // //////////////////////////////////////////////////////////////////// //
00007 // The iLab Neuromorphic Vision C++ Toolkit - Copyright (C) 2000-2005   //
00008 // by the University of Southern California (USC) and the iLab at USC.  //
00009 // See http://iLab.usc.edu for information about this project.          //
00010 // //////////////////////////////////////////////////////////////////// //
00011 // Major portions of the iLab Neuromorphic Vision Toolkit are protected //
00012 // under the U.S. patent ``Computation of Intrinsic Perceptual Saliency //
00013 // in Visual Environments, and Applications'' by Christof Koch and      //
00014 // Laurent Itti, California Institute of Technology, 2001 (patent       //
00015 // pending; application number 09/912,225 filed July 23, 2001; see      //
00016 // http://pair.uspto.gov/cgi-bin/final/home.pl for current status).     //
00017 // //////////////////////////////////////////////////////////////////// //
00018 // This file is part of the iLab Neuromorphic Vision C++ Toolkit.       //
00019 //                                                                      //
00020 // The iLab Neuromorphic Vision C++ Toolkit is free software; you can   //
00021 // redistribute it and/or modify it under the terms of the GNU General  //
00022 // Public License as published by the Free Software Foundation; either  //
00023 // version 2 of the License, or (at your option) any later version.     //
00024 //                                                                      //
00025 // The iLab Neuromorphic Vision C++ Toolkit is distributed in the hope  //
00026 // that it will be useful, but WITHOUT ANY WARRANTY; without even the   //
00027 // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      //
00028 // PURPOSE.  See the GNU General Public License for more details.       //
00029 //                                                                      //
00030 // You should have received a copy of the GNU General Public License    //
00031 // along with the iLab Neuromorphic Vision C++ Toolkit; if not, write   //
00032 // to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,   //
00033 // Boston, MA 02111-1307 USA.                                           //
00034 // //////////////////////////////////////////////////////////////////// //
00035 //
00036 // Primary maintainer for this file: mviswana usc edu
00037 // $HeadURL: svn://isvn.usc.edu/software/invt/trunk/saliency/src/Robots/LoBot/ui/LoMainWindow.C $
00038 // $Id: LoMainWindow.C 13967 2010-09-18 08:00:07Z mviswana $
00039 //
00040 
00041 //---------------------- ALTERNATIVE DEFINITION -------------------------
00042 
00043 // In case OpenGL and/or GLUT are missing
00044 //
00045 // NOTE: Don't really need to check INVT_HAVE_LIBGL and INVT_HAVE_LIBGLU
00046 // as well because it ought to be a pretty rare/broken installation that
00047 // has GLUT but not the OpenGL libraries...
00048 #ifndef INVT_HAVE_LIBGLUT
00049 
00050 #include "Robots/LoBot/ui/LoMainWindow.H"
00051 #include "Robots/LoBot/misc/LoExcept.H"
00052 
00053 namespace lobot {
00054 
00055 MainWindow::MainWindow()
00056 {
00057    throw missing_libs(MISSING_OPENGL) ;
00058 }
00059 
00060 // NOTE: Don't need empty definitions of the remaining member functions
00061 // because they don't get used at all if OpenGL and/or GLUT are missing,
00062 // which means that the linker won't complain about missing functions
00063 // (since the compiler doesn't generate code for these functions).
00064 
00065 } // end of namespace encapsulating above empty definition
00066 
00067 #else // OpenGL and GLUT available ==> the real McCoy
00068 
00069 //------------------------------ HEADERS --------------------------------
00070 
00071 // lobot headers
00072 #include "Robots/LoBot/ui/LoMainWindow.H"
00073 #include "Robots/LoBot/ui/LoRenderBuffer.H"
00074 
00075 #include "Robots/LoBot/LoApp.H"
00076 #include "Robots/LoBot/config/LoConfigHelpers.H"
00077 
00078 #include "Robots/LoBot/thread/LoShutdown.H"
00079 #include "Robots/LoBot/thread/LoPause.H"
00080 
00081 #include "Robots/LoBot/util/LoFile.H"
00082 #include "Robots/LoBot/util/LoString.H"
00083 #include "Robots/LoBot/util/LoMath.H"
00084 #include "Robots/LoBot/util/LoTime.H"
00085 
00086 #include "Robots/LoBot/misc/LoExcept.H"
00087 #include "Robots/LoBot/misc/singleton.hh"
00088 
00089 // INVT utilities
00090 #include "Util/log.H"
00091 
00092 // DevIL headers
00093 #ifdef INVT_HAVE_LIBDEVIL
00094 #include <IL/il.h>
00095 #endif
00096 
00097 // OpenGL headers
00098 #include <GL/glut.h>
00099 
00100 // Boost headers
00101 #include <boost/bind.hpp>
00102 
00103 // Standard C++ headers
00104 #include <iomanip>
00105 #include <sstream>
00106 #include <string>
00107 #include <algorithm>
00108 #include <functional>
00109 
00110 // System/Unix headers
00111 #include <sys/stat.h> // for mkdir
00112 
00113 //----------------------------- NAMESPACE -------------------------------
00114 
00115 namespace lobot {
00116 
00117 //-------------------------- KNOB TWIDDLING -----------------------------
00118 
00119 namespace {
00120 
00121 /// This inner class encapsulates various parameters that can be used to
00122 /// tweak different aspects of the Robolocust UI and the visualizations.
00123 class Params : public singleton<Params> {
00124    /// Private constructor because this is a singleton.
00125    Params() ;
00126    friend class singleton<Params> ;
00127 
00128    /// Users can specify whatever window title they want for the
00129    /// Robolocust UI.
00130    std::string m_title ;
00131 
00132    /// The Robolocust UI supports taking screenshots of itself after each
00133    /// iteration of the render cycle and then saving these screenshots as
00134    /// JPEG or PNG files. By default, the screen capture functionality is
00135    /// off. This flag turns it on.
00136    bool m_screen_capture ;
00137 
00138    /// When screen capturing is turned on, Robolocust will write each
00139    /// frame to the directory specified by this setting. A time-stamp
00140    /// corresponding to when the Robolocust application was launched will
00141    /// be automatically to appended to this setting's value. The format
00142    /// of the time-stamp is "YYYYmmdd-HHMMSS". Thus, if the value of this
00143    /// setting is "/tmp/foo-" and the application was launched at
00144    /// midnight on January 1st, 2000, all frames will be written to
00145    /// "/tmp/foo-20000101-000000".
00146    std::string m_sc_dir ;
00147 
00148    /// Each frame saved by the screen capturing process will be named
00149    /// frame000.png, frame001.png, frame002.png, and so on. This setting
00150    /// specifies the number of digits in the numeric part of the frame
00151    /// name. The default is six digits. Thus, by default, frames will be
00152    /// named frame000000.png, frame000001.png, and so on.
00153    int m_sc_len ;
00154 
00155    /// Robolocust uses the OpenIL library (aka libdevil) to save frames
00156    /// to disk. Thus, it can write the individual frames in any of the
00157    /// file formats supported by OpenIL. This setting specifies the
00158    /// format to use for the individual screen capture frames. It should
00159    /// be a 3-letter string such "png", "jpg", "pnm", "tif", etc.
00160    ///
00161    /// NOTE: Depending on how OpenIL is compiled and installed, some
00162    /// image file formats may not be supported. Generally, it is best to
00163    /// stick to the "png" or "jpg" formats. By default, Robolocust saves
00164    /// its frames as PNG files.
00165    std::string m_sc_fmt ;
00166 
00167    // A flag to check if the screen capture file format is PNG or not.
00168    bool m_sc_png ;
00169 
00170    /// This setting specifies the initial zoom level for drawables that
00171    /// support zoom/pan operations. Its value should be a floating point
00172    /// number. Here's how it works: a value of 1 (the default) will
00173    /// result in things being shown as-is. Fractional values zoom out
00174    /// the drawables, i.e., make it smaller; for example, a value of 0.5
00175    /// would show the drawable in half-size. Values greater than unity
00176    /// zoom into the drawable, e.g., 2 would double the drawable's size.
00177    float m_initial_zoom ;
00178 
00179    /// We can speed up or slow down the zoom by adjusting this factor.
00180    /// Higher values will result in amplifying mouse motion so that
00181    /// even a small movement results in a large zoom in or out; lower
00182    /// values will damp the mouse motion so that more dragging is
00183    /// required to achieve the desired zoom level.
00184    float m_zoom_drag_factor ;
00185 
00186    /// This setting specifies the frequency with which the Robolocust UI
00187    /// is updated. It is expected to be a time expressed in milliseconds.
00188    /// Thus, for some value N, the update will be performed once every N
00189    /// milliseconds.
00190    int m_update_frequency ;
00191 
00192 public:
00193    // Accessing the various parameters
00194    //@{
00195    static const std::string& title()  {return instance().m_title  ;}
00196    static const std::string& sc_dir() {return instance().m_sc_dir ;}
00197    static const std::string& sc_fmt() {return instance().m_sc_fmt ;}
00198    static int   sc_len()              {return instance().m_sc_len ;}
00199    static bool  sc_png()              {return instance().m_sc_png ;}
00200    static bool  screen_capture()      {return instance().m_screen_capture   ;}
00201    static float initial_zoom()        {return instance().m_initial_zoom     ;}
00202    static float zoom_drag_factor()    {return instance().m_zoom_drag_factor ;}
00203    static int   update_frequency()    {return instance().m_update_frequency ;}
00204    //@}
00205 } ;
00206 
00207 // Parameters initialization
00208 Params::Params()
00209    : m_title(ui_conf<std::string>("title", "Robolocust")),
00210      m_screen_capture(ui_conf("screen_capture", false)),
00211      m_sc_dir(ui_conf<std::string>("screen_capture_dir", "/tmp/lobot-frames-")
00212                  + startup_timestamp_str()),
00213      m_sc_len(clamp(ui_conf("screen_capture_len", 6), 3, 9)),
00214      m_sc_fmt(downstring(ui_conf<std::string>("screen_capture_fmt", "png"))),
00215      m_sc_png(m_sc_fmt == "png"),
00216      m_initial_zoom(clamp(ui_conf("initial_zoom", 1.0f), 0.1f, 5.0f)),
00217      m_zoom_drag_factor(clamp(ui_conf("zoom_drag_factor", 0.1f), 0.01f, 2.5f)),
00218      m_update_frequency(clamp(ui_conf("update_frequency", 250), 100, 60000))
00219 {
00220    if (m_screen_capture && (mkdir(m_sc_dir.c_str(), 0755) != 0))
00221       LERROR("failed to create directory \"%s\"", m_sc_dir.c_str()) ;
00222 }
00223 
00224 } // end of local anonymous namespace encapsulating above helpers
00225 
00226 //-------------------------- INITIALIZATION -----------------------------
00227 
00228 // Since the Robolocust UI relies on GLUT (which implements its own main
00229 // loop), we need to run it in a separate thread from the rest of the
00230 // Robolocust system. Furthermore, since GLUT implements its own main
00231 // loop, this thread will never return. Therefore, we need to specify
00232 // that this thread's run() method won't return when starting up the
00233 // thread by passing false to the second parameter of Thread::start().
00234 MainWindow::MainWindow()
00235    : m_window(0), m_render_buffer(0), m_width(-1), m_height(-1),
00236      m_frame_number(0),
00237      m_drag_button(-1), m_drag_modifiers(-1)
00238 {
00239    start("lobot_ui", false) ;
00240 }
00241 
00242 // Helper function object to compute the total size of the main window
00243 // using the geometry specifications of all its drawables. It works by
00244 // computing the union of all the rectangles described by the geometry
00245 // specs.
00246 namespace {
00247 
00248 // DEVNOTE: This function object is meant to be used with the STL
00249 // for_each algorithm. Unlike the usual invocation of for_each, wherein
00250 // we simply discard for_each's return value, when using this function
00251 // object, for_each's return value should be used to retrieve the UI
00252 // dimensions computed and stored by this function object.
00253 class calc_size {
00254    int left, right, bottom, top ;
00255 public:
00256    calc_size() ;
00257    void operator()(Drawable*) ;
00258 
00259    int width()  const {return right - left ;}
00260    int height() const {return bottom - top ;}
00261 } ;
00262 
00263 calc_size::calc_size()
00264    : left  (std::numeric_limits<int>::max()),
00265      right (std::numeric_limits<int>::min()),
00266      bottom(std::numeric_limits<int>::min()),
00267      top   (std::numeric_limits<int>::max())
00268 {}
00269 
00270 void calc_size::operator()(Drawable* d)
00271 {
00272    if (d->visible()) {
00273       Drawable::Geometry g = d->geometry() ;
00274       left   = std::min(left,   g.x) ;
00275       right  = std::max(right,  g.x + g.width) ;
00276       bottom = std::max(bottom, g.y + g.height) ;
00277       top    = std::min(top,    g.y) ;
00278    }
00279 }
00280 
00281 } // end of local namespace encapsulating above helper function object
00282 
00283 // The UI thread's run method will initialize GLUT, perform some other
00284 // rendering set up operations and then enter the GLUT main loop.
00285 void MainWindow::run()
00286 {
00287    try
00288    {
00289       App::wait_for_init() ;
00290 
00291       int argc = App::argc() ;
00292       glutInit(& argc, const_cast<char**>(App::argv())) ;
00293       glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE) ;
00294 
00295 #ifdef INVT_HAVE_LIBDEVIL
00296       ilInit() ;
00297 #endif
00298 
00299       // Inner block for window creation to ensure that its associated
00300       // mutex is released as soon as it is no longer required.
00301       {
00302          AutoMutex M(m_window_mutex) ;
00303          m_window = glutCreateWindow(Params::title().c_str()) ;
00304       }
00305 
00306       typedef MainWindow me ; // typing shortcut
00307       m_keymap['r'] = & me::reset_zoom_pan ;
00308       m_keymap['p'] = & me::pause ;
00309       m_keymap['q'] = & me::quit ;
00310       m_keymap['Q'] = & me::quit ;
00311       m_keymap[27]  = & me::quit ; // ESC (assuming ASCII encoding)
00312       m_keymap[ 3]  = & me::quit ; // Ctrl-C (assuming ASCII encoding)
00313       m_keymap[ 4]  = & me::quit ; // Ctrl-D (assuming ASCII encoding)
00314       m_keymap[17]  = & me::quit ; // Ctrl-Q (assuming ASCII encoding)
00315       m_keymap[24]  = & me::quit ; // Ctrl-X (assuming ASCII encoding)
00316 
00317       m_drag_prev[0] = m_drag_prev[1] = -1 ;
00318 
00319       calc_size s =
00320          std::for_each(m_drawables.begin(), m_drawables.end(), calc_size()) ;
00321       m_width  = s.width()  ;
00322       m_height = s.height() ;
00323 
00324       m_render_buffer = new RenderBuffer(m_width, m_height) ;
00325 
00326       glutReshapeFunc(reshape_callback) ;
00327       glutDisplayFunc(render_callback) ;
00328       glutKeyboardFunc(keyboard_callback) ;
00329       glutMouseFunc(click_callback) ;
00330       glutMotionFunc(drag_callback) ;
00331       glutIdleFunc(idle_callback) ;
00332       setup_timer() ;
00333 
00334       // Now that the GL rendering context is up, we should trigger the GL
00335       // initializations for all the drawables.
00336       //
00337       // NOTE: Inner block to ensure mutex release ASAP.
00338       {
00339          AutoMutex M(m_drawables_mutex) ;
00340          std::for_each(m_drawables.begin(), m_drawables.end(),
00341                        std::mem_fun(& Drawable::gl_init)) ;
00342       }
00343 
00344       glutMainLoop() ;
00345    }
00346    catch (std::exception& e)
00347    {
00348       LERROR("%s", e.what()) ;
00349       quit() ;
00350    }
00351 }
00352 
00353 // Since the Robolocust UI operates in a separate thread, the main thread
00354 // only needs to setup the main window object by adding drawables to it.
00355 // After that, the UI works asynchronously w.r.t. and independently of
00356 // the main thread.
00357 void MainWindow::push_back(Drawable* d)
00358 {
00359    if (d)
00360    {
00361       // Before adding a new drawable to the drawables list and starting
00362       // its rendering cycles, we should perform any GL related
00363       // initialization that drawable might have. But only if the GL
00364       // rendering context is ready for action; otherwise: segfault.
00365       //
00366       // NOTE: Inner block to ensure window mutex is released as soon as
00367       // it is no longer required.
00368       {
00369          AutoMutex W(m_window_mutex) ;
00370          if (m_window) // GL rendering context is up and running
00371             d->gl_init() ;
00372       }
00373 
00374       AutoMutex D(m_drawables_mutex) ;
00375       m_drawables.push_back(d) ;
00376    }
00377 }
00378 
00379 //----------------------------- RENDERING -------------------------------
00380 
00381 // Setup update timer
00382 void MainWindow::setup_timer()
00383 {
00384    glutTimerFunc(Params::update_frequency(), timer_callback, 0) ;
00385 }
00386 
00387 // When update timer fires, invalidate GLUT window in order to trigger
00388 // main window's rendering operation.
00389 void MainWindow::update()
00390 {
00391    glutPostRedisplay() ;
00392    if (Pause::is_clear())
00393       setup_timer() ;
00394 }
00395 
00396 // Helper function object for setting up a drawable's drawing area and
00397 // then rendering it.
00398 namespace {
00399 
00400 class render_drawable {
00401    int W, H ;
00402 public:
00403    render_drawable(int ui_width, int ui_height) ;
00404    void operator()(Drawable*) const ;
00405 } ;
00406 
00407 render_drawable::render_drawable(int w, int h)
00408    : W(w), H(h)
00409 {}
00410 
00411 void render_drawable::operator()(Drawable* d) const
00412 {
00413    if (d->invisible())
00414       return ;
00415 
00416    Drawable::Geometry g = d->geometry() ;
00417    glViewport(g.x, H - (g.y + g.height), g.width, g.height) ;
00418 
00419    d->render() ;
00420 
00421    // Draw a border to demarcate the drawable's drawing area
00422    if (d->border()) {
00423       glMatrixMode(GL_PROJECTION) ;
00424       glPushMatrix() ;
00425       glLoadIdentity() ;
00426       gluOrtho2D(0, g.width, 0, g.height) ;
00427 
00428       glMatrixMode(GL_MODELVIEW) ;
00429       glPushMatrix() ;
00430       glLoadIdentity() ;
00431 
00432       glPushAttrib(GL_CURRENT_BIT) ;
00433          glColor3fv(d->border_color().rgb()) ;
00434          glBegin(GL_LINE_LOOP) ;
00435             glVertex2i(1, 1) ;
00436             glVertex2i(g.width - 1, 1) ;
00437             glVertex2i(g.width - 1, g.height - 1) ;
00438             glVertex2i(1, g.height - 1) ;
00439          glEnd() ;
00440       glPopAttrib() ;
00441 
00442       glMatrixMode(GL_PROJECTION) ;
00443       glPopMatrix() ;
00444       glMatrixMode(GL_MODELVIEW) ;
00445       glPopMatrix() ;
00446    }
00447 }
00448 
00449 } // end of local namespace encapsulating above helper
00450 
00451 // Render all the drawables: first to the off-screen buffer and then to
00452 // the on-screen buffer.
00453 void MainWindow::render()
00454 {
00455    m_render_buffer->setup() ;
00456 
00457    static bool first_time = true ;
00458    if (first_time)
00459    {
00460       reset_zoom_pan() ;
00461       AutoMutex M(m_drawables_mutex) ;
00462       std::for_each(m_drawables.begin(), m_drawables.end(),
00463                     std::bind2nd(std::mem_fun(& Drawable::zoom_by),
00464                                  Params::initial_zoom() - 1)) ;
00465       first_time = false ;
00466    }
00467 
00468    glClear(GL_COLOR_BUFFER_BIT) ;
00469 
00470    // Use block to ensure mutex is released when no longer needed
00471    {
00472       AutoMutex M(m_drawables_mutex) ;
00473       std::for_each(m_drawables.begin(), m_drawables.end(),
00474                     render_drawable(m_width, m_height)) ;
00475    }
00476 
00477    // If screen caturing is enabled, retrieve the pixel data from render
00478    // buffer and queue it for later writing in the GLUT idle handler so
00479    // that rendering and user interaction continue to take precedence.
00480    if (Params::screen_capture())
00481       m_capture_queue.push(new ScreenCapture(m_frame_number++,
00482                                              m_width, m_height,
00483                                              m_render_buffer->pixels(),
00484                                              m_render_buffer->size())) ;
00485 
00486    m_render_buffer->to_screen() ;
00487    glutSwapBuffers() ;
00488 }
00489 
00490 //------------------------- SCREEN CAPTURING ----------------------------
00491 
00492 // Save a screenshot to the specified file
00493 void MainWindow::save_screenshot(const std::string& file_name) const
00494 {
00495    ScreenCapture sc(file_name, m_width, m_height,
00496                     m_render_buffer->pixels(), m_render_buffer->size()) ;
00497    sc.save() ;
00498 }
00499 
00500 // Helper function to fix the alpha values passed by lobot::RenderBuffer
00501 // so that they're all 255 (fully opaque). Otherwise, the OpenIL/DevIL
00502 // library produces an image with a transparent background when saving as
00503 // PNG.
00504 //
00505 // The function should be passed pointers to the start and end of the
00506 // screen capture's data buffer.
00507 static void fix_alpha_values(unsigned char* start, unsigned char* end)
00508 {
00509    for (unsigned char* p = start + 3; p < end; p += 4)
00510       *p = 255 ;
00511 }
00512 
00513 // ScreenCapture constructor for saving single frames to client-supplied
00514 // file names.
00515 MainWindow::ScreenCapture::
00516 ScreenCapture(const std::string& file_name,
00517               int w, int h, const unsigned char* data, int n)
00518    : m_name(file_name), m_width(w), m_height(h), m_data(data, data + n)
00519 {
00520    if (downstring(extension(m_name)) == "png")
00521       fix_alpha_values(&m_data[0], &m_data[0] + n) ;
00522 }
00523 
00524 // ScreenCapture constructor for saving multiple frames with the file
00525 // name being derived from the client-supplied frame number.
00526 MainWindow::ScreenCapture::
00527 ScreenCapture(int frame_number, int w, int h, const unsigned char* data, int n)
00528    : m_width(w), m_height(h), m_data(data, data + n)
00529 {
00530    using std::setfill ; using std::setw ;
00531    std::ostringstream file_name ;
00532    file_name << Params::sc_dir() << "/frame"
00533              << setfill('0') << setw(Params::sc_len()) << frame_number
00534              << '.' << Params::sc_fmt() ;
00535    m_name = file_name.str() ;
00536 
00537    if (Params::sc_png())
00538       fix_alpha_values(&m_data[0], &m_data[0] + n) ;
00539 }
00540 
00541 // This function uses the OpenIL/DevIL library to save a frame to disk.
00542 // If OpenIL/DevIL is not installed, we throw an exception so that the
00543 // first attempt at saving a frame will result in the application
00544 // quitting with a reasonable error message to inform the user of what
00545 // went wrong.
00546 void MainWindow::ScreenCapture::save() const
00547 {
00548 #ifdef INVT_HAVE_LIBDEVIL
00549    ILuint image ;
00550    ilGenImages(1, &image) ;
00551    ilBindImage(image) ;
00552 
00553    unsigned char* data = const_cast<unsigned char*>(&m_data[0]) ;
00554    ilTexImage(m_width, m_height, 1, 4, IL_BGRA, IL_UNSIGNED_BYTE, data) ;
00555    ilSaveImage(const_cast<char*>(m_name.c_str())) ;
00556    if (ilGetError() != IL_NO_ERROR)
00557       LERROR("error writing \"%s\"", m_name.c_str()) ;
00558 
00559    ilDeleteImages(1, &image) ;
00560 #else
00561    throw missing_libs(MISSING_LIBDEVIL) ;
00562 #endif
00563 }
00564 
00565 // To not tie up the visualization thread too much with screen capture
00566 // saving, we queue captured frames and use GLUT's idling mechanism to
00567 // periodically write these frames to disk. Thus, when the application is
00568 // busy with rendering or user interaction, saving frames to disk will be
00569 // put temporarily on hold.
00570 //
00571 // This function pops the next frame from the screen capture queue and
00572 // saves it to disk.
00573 void MainWindow::dump_next_frame()
00574 {
00575    ScreenCapture* frame = m_capture_queue.front() ;
00576    m_capture_queue.pop() ;
00577    frame->save() ;
00578    delete frame ;
00579 }
00580 
00581 //--------------------------- WINDOW EVENTS -----------------------------
00582 
00583 // The Robolocust UI decides its own size based on the geometry specs in
00584 // the config file. We really don't want users messing around with the
00585 // main window by resizing it. Therefore, we respond to such events by
00586 // simply resizing the window back to its old/original size.
00587 void MainWindow::reshape(int W, int H)
00588 {
00589    if (W == m_width && H == m_height) // window is of "ideal" size
00590       return ;
00591    glutReshapeWindow(m_width, m_height) ;
00592 }
00593 
00594 //-------------------------- KEYBOARD INPUT -----------------------------
00595 
00596 // Use keymap to invoke appropriate handler for key pressed by user
00597 void MainWindow::handle_key(unsigned char key)
00598 {
00599    // Main window gets dibs on key presses
00600    KeyMap::iterator handler = m_keymap.find(key) ;
00601    if (handler != m_keymap.end())
00602       (this->*(handler->second))() ;
00603 
00604    // It then lets each drawable take a crack at the event
00605    // NOTE: Use block to ensure mutex is released when no longer required
00606    {
00607       AutoMutex M(m_drawables_mutex) ;
00608       std::for_each(m_drawables.begin(), m_drawables.end(),
00609                     std::bind2nd(std::mem_fun(&Drawable::keypress), key)) ;
00610    }
00611 
00612    // Finally, when everyone is done handling the key press, we repaint
00613    glutPostRedisplay() ;
00614 }
00615 
00616 void MainWindow::reset_zoom_pan()
00617 {
00618    AutoMutex M(m_drawables_mutex) ;
00619    std::for_each(m_drawables.begin(), m_drawables.end(),
00620                  std::mem_fun(& Drawable::reset_zoom_pan)) ;
00621 }
00622 
00623 void MainWindow::pause()
00624 {
00625    Pause::toggle() ;
00626    if (Pause::is_clear())
00627       setup_timer() ;
00628 }
00629 
00630 void MainWindow::quit()
00631 {
00632    AutoMutex M(m_drawables_mutex) ;
00633    std::for_each(m_drawables.begin(), m_drawables.end(),
00634                  std::mem_fun(& Drawable::gl_cleanup)) ;
00635 
00636    glutDestroyWindow(m_window) ;
00637    Shutdown::signal() ;
00638    pthread_exit(0) ;
00639 }
00640 
00641 //---------------------------- MOUSE INPUT ------------------------------
00642 
00643 void MainWindow::left_click(int state, int modifiers, int x, int y)
00644 {
00645    switch (state)
00646    {
00647       case GLUT_DOWN:
00648          m_drag_button    = GLUT_LEFT_BUTTON ;
00649          m_drag_modifiers = modifiers ;
00650          m_drag_prev[0]   = x ;
00651          m_drag_prev[1]   = y ;
00652          break ;
00653       case GLUT_UP:
00654          m_drag_button    = -1 ;
00655          m_drag_modifiers = -1 ;
00656          m_drag_prev[0]   = -1 ;
00657          m_drag_prev[1]   = -1 ;
00658          break ;
00659    }
00660 }
00661 
00662 void MainWindow::middle_click(int state, int modifiers, int x, int y)
00663 {
00664    switch (state)
00665    {
00666       case GLUT_DOWN:
00667          m_drag_button    = GLUT_MIDDLE_BUTTON ;
00668          m_drag_modifiers = modifiers ;
00669          m_drag_prev[0]   = x ;
00670          m_drag_prev[1]   = y ;
00671          break ;
00672       case GLUT_UP:
00673          m_drag_button    = -1 ;
00674          m_drag_modifiers = -1 ;
00675          m_drag_prev[0]   = -1 ;
00676          m_drag_prev[1]   = -1 ;
00677          break ;
00678    }
00679 }
00680 
00681 void MainWindow::right_click(int state, int modifiers, int x, int y)
00682 {
00683    switch (state)
00684    {
00685       case GLUT_DOWN:
00686          m_drag_button    = GLUT_RIGHT_BUTTON ;
00687          m_drag_modifiers = modifiers ;
00688          m_drag_prev[0]   = x ;
00689          m_drag_prev[1]   = y ;
00690          break ;
00691       case GLUT_UP:
00692          m_drag_button    = -1 ;
00693          m_drag_modifiers = -1 ;
00694          m_drag_prev[0]   = -1 ;
00695          m_drag_prev[1]   = -1 ;
00696          break ;
00697    }
00698 }
00699 
00700 void MainWindow::left_drag(int x, int y)
00701 {
00702    if (m_drag_modifiers & GLUT_ACTIVE_SHIFT) // zoom
00703    {
00704       const float dz = (y - m_drag_prev[1]) * Params::zoom_drag_factor() ;
00705       AutoMutex M(m_drawables_mutex) ;
00706       std::for_each(m_drawables.begin(), m_drawables.end(),
00707                     std::bind2nd(std::mem_fun(& Drawable::zoom_by), -dz)) ;
00708    }
00709    else // pan
00710    {
00711       AutoMutex M(m_drawables_mutex) ;
00712       std::for_each(m_drawables.begin(), m_drawables.end(),
00713                     boost::bind(& Drawable::pan, _1,
00714                                 x, y, m_drag_prev[0], m_drag_prev[1])) ;
00715    }
00716 
00717    m_drag_prev[0] = x ;
00718    m_drag_prev[1] = y ;
00719 
00720    glutPostRedisplay() ;
00721 }
00722 
00723 void MainWindow::middle_drag(int x, int y)
00724 {
00725    // Block to ensure mutex is released when no longer required
00726    {
00727       const float dz = (y - m_drag_prev[1]) * Params::zoom_drag_factor() ;
00728       AutoMutex M(m_drawables_mutex) ;
00729       std::for_each(m_drawables.begin(), m_drawables.end(),
00730                     std::bind2nd(std::mem_fun(& Drawable::zoom_by), -dz)) ;
00731    }
00732 
00733    m_drag_prev[0] = x ;
00734    m_drag_prev[1] = y ;
00735 
00736    glutPostRedisplay() ;
00737 }
00738 
00739 void MainWindow::right_drag(int, int)
00740 {
00741 }
00742 
00743 //-------------------------- GLUT CALLBACKS -----------------------------
00744 
00745 void MainWindow::reshape_callback(int width, int height)
00746 {
00747    instance().reshape(width, height) ;
00748 }
00749 
00750 void MainWindow::render_callback()
00751 {
00752    instance().render() ;
00753 }
00754 
00755 void MainWindow::keyboard_callback(unsigned char key, int, int)
00756 {
00757    instance().handle_key(key) ;
00758 }
00759 
00760 void MainWindow::click_callback(int button, int state, int x, int y)
00761 {
00762    switch (button)
00763    {
00764       case GLUT_LEFT_BUTTON:
00765          instance().left_click(state, glutGetModifiers(), x, y) ;
00766          break ;
00767       case GLUT_MIDDLE_BUTTON:
00768          instance().middle_click(state, glutGetModifiers(), x, y) ;
00769          break ;
00770       case GLUT_RIGHT_BUTTON:
00771          instance().right_click(state, glutGetModifiers(), x, y) ;
00772          break ;
00773    }
00774 }
00775 
00776 void MainWindow::drag_callback(int x, int y)
00777 {
00778    switch (instance().m_drag_button)
00779    {
00780       case GLUT_LEFT_BUTTON:
00781          instance().left_drag(x, y) ;
00782          break ;
00783       case GLUT_MIDDLE_BUTTON:
00784          instance().middle_drag(x, y) ;
00785          break ;
00786       case GLUT_RIGHT_BUTTON:
00787          instance().right_drag(x, y) ;
00788          break ;
00789    }
00790 }
00791 
00792 // We use a timer to continuously update the Robolocust UI because GLUT
00793 // runs its own main loop independent of the rest of the Robolocust
00794 // system (which means that we can't perform regular updates as part of
00795 // lobot::App's main loop).
00796 void MainWindow::timer_callback(int)
00797 {
00798    instance().update() ;
00799 }
00800 
00801 // When there are no messages to respond to, check the application
00802 // shutdown signal. This is useful for a clean termination of the
00803 // Robolocust UI when the user switches input focus from the main window
00804 // back to the terminal that launched the lobot program and terminates it
00805 // using Ctrl-C or something similar.
00806 //
00807 // If the shutdown signal is not active, then take this opportunity to
00808 // write the next screen capture frame to disk. Hopefully, doing this in
00809 // the idle callback prevents the visualization thread from getting too
00810 // bogged down handling screen captures.
00811 void MainWindow::idle_callback()
00812 {
00813    MainWindow& W = instance() ;
00814    if (Shutdown::signaled())
00815    {
00816       AutoMutex M(W.m_drawables_mutex) ;
00817       std::for_each(W.m_drawables.begin(), W.m_drawables.end(),
00818                     std::mem_fun(& Drawable::gl_cleanup)) ;
00819 
00820       glutDestroyWindow(W.m_window) ;
00821       pthread_exit(0) ;
00822    }
00823    if (! W.m_capture_queue.empty())
00824       W.dump_next_frame() ;
00825 }
00826 
00827 //----------------------------- CLEAN-UP --------------------------------
00828 
00829 MainWindow::~MainWindow()
00830 {
00831    delete m_render_buffer ;
00832    while (! m_capture_queue.empty())
00833       dump_next_frame() ;
00834 }
00835 
00836 //-----------------------------------------------------------------------
00837 
00838 } // end of namespace encapsulating this file's definitions
00839 
00840 #endif // #ifndef INVT_HAVE_LIBGLUT
00841 
00842 /* So things look consistent in everyone's emacs... */
00843 /* Local Variables: */
00844 /* indent-tabs-mode: nil */
00845 /* End: */
Generated on Sun May 8 08:05:56 2011 for iLab Neuromorphic Vision Toolkit by  doxygen 1.6.3