core/vgui/impl/glut/menu_hack_X11.cxx
Go to the documentation of this file.
00001 /*
00002   fsm
00003 */
00004 #include "menu_hack.h"
00005 
00006 #include <vcl_iostream.h>
00007 #include <vcl_vector.h>
00008 
00009 #include <vgui/vgui_gl.h>
00010 #include <vgui/vgui_glut.h>
00011 
00012 #include <X11/Xlib.h>
00013 
00014 // The purpose of this class is to fool GLUT into popping up
00015 // menus on more special conditions than mouse presses. I
00016 // works by intercepting the desired event (modifier+button),
00017 // registering a GLUT menu on that button and then putting a
00018 // fake X button event back onto the event stream.
00019 //
00020 // This works with both shared and non-shared GLUT libraries,
00021 // but it may break when linked against with newer versions of
00022 // GLUT.
00023 
00024 //--------------------------------------------------------------------------------
00025 
00026 // see glutint.h (taken from glut source distribution) for the
00027 // rest of this structure.
00028 struct GLUTwindow
00029 {
00030   int num;              /* Small integer window id (0-based). */
00031 
00032   /* Window system related state. */
00033 #if defined(_WIN32) && !defined(__CYGWIN__)
00034   int pf;               /* Pixel format. */
00035   HDC hdc;              /* Window's Win32 device context. */
00036 #endif
00037   Window win;           /* X window for GLUT window */
00038   // *** there's more, but we don't need it..... ***
00039 };
00040 
00041 // we use these extern global variables in libGLUT to do the trickery :
00042 extern    Display *__glutDisplay;
00043 extern GLUTwindow *__glutCurrentWindow;
00044 extern GLUTwindow *__glutMenuWindow;
00045 
00046 //--------------------------------------------------------------------------------
00047 
00048 int  vgui_glut_menu_hack::glut_button = 0;
00049 bool vgui_glut_menu_hack::active = false;
00050 void (*vgui_glut_menu_hack::last_minute_change_callback)(int menu_id) = 0;
00051 
00052 //--------------------------------------------------------------------------------
00053 
00054 struct vgui_glut_menu_hack_bind_entry
00055 {
00056   int button;
00057   int mods;
00058   int menu_id;
00059 };
00060 
00061 static vgui_glut_menu_hack_bind_entry default_entries[] = {
00062   {GLUT_LEFT_BUTTON  ,0                ,0},
00063   {GLUT_LEFT_BUTTON  ,GLUT_ACTIVE_SHIFT,0},
00064   {GLUT_LEFT_BUTTON  ,GLUT_ACTIVE_CTRL ,0},
00065   {GLUT_LEFT_BUTTON  ,GLUT_ACTIVE_ALT  ,0},
00066   {GLUT_MIDDLE_BUTTON,0                ,0},
00067   {GLUT_MIDDLE_BUTTON,GLUT_ACTIVE_SHIFT,0},
00068   {GLUT_MIDDLE_BUTTON,GLUT_ACTIVE_CTRL ,0},
00069   {GLUT_MIDDLE_BUTTON,GLUT_ACTIVE_ALT  ,0},
00070   {GLUT_RIGHT_BUTTON ,0                ,0},
00071   {GLUT_RIGHT_BUTTON ,GLUT_ACTIVE_SHIFT,0},
00072   {GLUT_RIGHT_BUTTON ,GLUT_ACTIVE_CTRL ,0},
00073   {GLUT_RIGHT_BUTTON ,GLUT_ACTIVE_ALT  ,0},
00074 };
00075 
00076 const unsigned int table_size = sizeof(default_entries)/sizeof(default_entries[0]);
00077 
00078 struct vgui_glut_menu_hack::per_window_record
00079 {
00080   vgui_glut_menu_hack_bind_entry entries[ table_size ];
00081   per_window_record() {
00082     for (unsigned i=0;i<table_size; ++i)
00083       entries[i] = default_entries[i];
00084   }
00085 };
00086 
00087 static vgui_glut_menu_hack::per_window_record * get_current_record()
00088 {
00089   static vcl_vector<vgui_glut_menu_hack::per_window_record *> records;
00090 
00091   unsigned win = glutGetWindow();
00092   if (win == 0)
00093     return 0;
00094   while (win >= records.size())
00095     records.push_back( (vgui_glut_menu_hack::per_window_record*)0 ); // gcc 2.7 needs this cast
00096 
00097   if (records[win] == 0) {
00098 #ifdef DEBUG
00099     vcl_cerr << __FILE__ " : create record for window " << win << vcl_endl;
00100 #endif
00101     records[win] = new vgui_glut_menu_hack::per_window_record;
00102   }
00103 
00104   return records[win];
00105 }
00106 
00107 //--------------------------------------------------------------------------------
00108 
00109 int vgui_glut_menu_hack::find_index(int button, int mods)
00110 {
00111   const per_window_record *rec = get_current_record();
00112   if (!rec)
00113     return -1;
00114   for (unsigned i=0; i<table_size; i++)
00115     if (button==rec->entries[i].button && mods==rec->entries[i].mods)
00116       return i;
00117   vcl_cerr << __FILE__ " : invalid button/modifier combination " << button << ' ' << mods << vcl_endl;
00118   return -1;
00119 }
00120 
00121 bool vgui_glut_menu_hack::mouse(int button, int state, int x, int y)
00122 {
00123   if (state != GLUT_DOWN)
00124     return false;
00125 
00126   int mods = glutGetModifiers();
00127 
00128   int index = find_index(button, mods);
00129 
00130   if (index<0)
00131     return false; // invalid modifier combination.
00132 
00133   const per_window_record *rec = get_current_record();
00134   if (!rec)
00135     return false;
00136 
00137   if (rec->entries[index].menu_id==0)
00138     return false; // no menu bound.
00139 
00140   // allow client to change the menu if desired :
00141   if (last_minute_change_callback)
00142     last_minute_change_callback(rec->entries[index].menu_id);
00143 
00144   active = true;
00145   glut_button = button;
00146 #ifdef DEBUG
00147   vcl_cerr << "active\n";
00148 #endif
00149 
00150   // attach the required button to the menu
00151   {
00152     int old_id = glutGetMenu();
00153     int tmp_id = rec->entries[index].menu_id;
00154     glutSetMenu(tmp_id);
00155     glutAttachMenu(glut_button);
00156     if (old_id)
00157       glutSetMenu(old_id);
00158   }
00159 
00160   { // push another button press onto the event stream :
00161     static XEvent event;
00162     event.xbutton.type = ButtonPress;
00163     event.xbutton.serial = 0x12345678; // ?
00164     event.xbutton.send_event = 0;
00165     event.xbutton.display = __glutDisplay;
00166     event.xbutton.window = __glutCurrentWindow->win;
00167     event.xbutton.root = 0; // ?
00168     event.xbutton.subwindow = 0; // ?
00169     event.xbutton.time = 0; // ?
00170     event.xbutton.x = x;
00171     event.xbutton.y = y;
00172     event.xbutton.x_root = glutGet(GLenum(GLUT_WINDOW_X)) + x; // ?? but this
00173     event.xbutton.y_root = glutGet(GLenum(GLUT_WINDOW_Y)) + y; // works ??
00174     event.xbutton.state = 0;
00175     if (glut_button == GLUT_LEFT_BUTTON)
00176       event.xbutton.button = Button1;
00177     if (glut_button == GLUT_MIDDLE_BUTTON)
00178       event.xbutton.button = Button2;
00179     if (glut_button == GLUT_RIGHT_BUTTON)
00180       event.xbutton.button = Button3;
00181     event.xbutton.same_screen = 1; // ?
00182 
00183     XPutBackEvent(__glutDisplay, &event);
00184   }
00185   // the button press we pushed should bring up the menu.
00186 
00187   return true;
00188 }
00189 
00190 void vgui_glut_menu_hack::menustatus(int status,int /*x*/,int /*y*/)
00191 {
00192   if (active  &&  status == GLUT_MENU_NOT_IN_USE) {
00193     glutDetachMenu(glut_button);
00194 #ifdef DEBUG
00195     vcl_cerr << "purged\n";
00196 #endif
00197     active = false;
00198   }
00199 }
00200 
00201 //--------------------------------------------------------------------------------
00202 
00203 void vgui_glut_menu_hack::bind  (int button, int mods, int menu_id)
00204 {
00205   int index = find_index(button, mods);
00206   if (index<0)
00207     return;
00208   else {
00209     per_window_record *rec = get_current_record();
00210     if (!rec)
00211       return;
00212 #ifdef DEBUG
00213     vcl_cerr << "bind : " << glutGetWindow() << ' ' <<  button << ' ' << mods << ' ' << menu_id <<  vcl_endl;
00214 #endif
00215     rec->entries[index].menu_id = menu_id;
00216   }
00217 }