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: */