Browse Source

latest merges from Schell Games

David Rose 21 years ago
parent
commit
28d62c55ef

+ 9 - 4
direct/src/ffi/DoGenPyCode.py

@@ -38,6 +38,7 @@ Options:
   -r          remove the default library list; instrument only named libraries
   -O          no C++ comments or assertion statements
   -n          Don't use squeezeTool to squeeze the result into one .pyz file
+  -s          Don't delete source files after squeezing
 
 Any additional names listed on the command line are taken to be names
 of libraries that are to be instrumented.
@@ -50,7 +51,8 @@ extensionsDir = ''
 interrogateLib = ''
 codeLibs = []
 etcPath = []
-doSqueeze = 1
+doSqueeze = True
+deleteSourceAfterSqueeze = True
 
 def doGetopts():
     global outputDir
@@ -58,6 +60,7 @@ def doGetopts():
     global interrogateLib
     global codeLibs
     global doSqueeze
+    global deleteSourceAfterSqueeze
     global etcPath
 
     # These options are allowed but are flagged as warnings (they are
@@ -73,7 +76,7 @@ def doGetopts():
 
     # Extract the args the user passed in
     try:
-        opts, pargs = getopt.getopt(sys.argv[1:], 'hvOd:x:i:e:rngtpo')
+        opts, pargs = getopt.getopt(sys.argv[1:], 'hvOd:x:i:e:rnsgtpo')
     except Exception, e:
         # User passed in a bad option, print the error and the help, then exit
         print e
@@ -105,7 +108,9 @@ def doGetopts():
             FFIConstants.wantComments = 0
             FFIConstants.wantTypeChecking = 0
         elif (flag == '-n'):
-            doSqueeze = 0
+            doSqueeze = False
+        elif (flag == '-s'):
+            deleteSourceAfterSqueeze = False
         elif (flag in ['-g', '-t', '-p', '-o']):
             FFIConstants.notify.warning("option is deprecated: %s" % (flag))
             
@@ -191,4 +196,4 @@ def run():
     db.generateCode(outputDir, extensionsDir)
 
     if doSqueeze:
-        db.squeezeGeneratedCode(outputDir)
+        db.squeezeGeneratedCode(outputDir, deleteSourceAfterSqueeze)

+ 5 - 4
direct/src/ffi/FFIInterrogateDatabase.py

@@ -733,7 +733,7 @@ class FFIInterrogateDatabase:
         file = open(init, 'w')
         file.close()
 
-    def squeezeGeneratedCode(self, outputDir):
+    def squeezeGeneratedCode(self, outputDir, deleteSource=True):
 
         # Since we will be squeezing the importModuleName file, rename
         # the original to something we can import from within the
@@ -762,9 +762,10 @@ class FFIInterrogateDatabase:
         pandaSqueezeTool.squeeze(squeezedName, unsqueezedName,
                                  files, outputDir)
 
-        # Remove the now-squeezed source files.
-        for file in files:
-            os.remove(file)
+        if( deleteSource ):
+            # Remove the now-squeezed source files.
+            for file in files:
+                os.remove(file)
         
 
     def generateCodeLib(self, codeDir, extensionsDir, CModuleName):

+ 24 - 2
dtool/pptempl/Template.nmake.pp

@@ -39,7 +39,7 @@
 // All of the Sources.pp files in the current source hierarchy
 // $DTOOL/pptempl/Global.pp
 // $DTOOL/pptempl/Global.nmake.pp
-// $DTOOL/pptempl/Depends.pp, once for each Sources.pp filem
+// $DTOOL/pptempl/Depends.pp, once for each Sources.pp file
 // Template.nmake.pp (this file), once for each Sources.pp file
 
 #if $[ne $[CTPROJS],]
@@ -106,6 +106,14 @@
   #define lxx_st_sources $[sort $[lxx_sources(metalib_target lib_target noinst_lib_target static_lib_target ss_lib_target bin_target noinst_bin_target test_bin_target test_lib_target)]]
   #define dep_sources_1  $[sort $[get_sources(metalib_target lib_target noinst_lib_target static_lib_target ss_lib_target bin_target noinst_bin_target test_bin_target test_lib_target)]]
 
+  // If there is an __init__.py in the directory, then all Python
+  // files in the directory just get installed without having to be
+  // named.
+  #if $[and $[INSTALL_PYTHON_SOURCE],$[wildcard $[TOPDIR]/$[DIRPREFIX]__init__.py]]
+    #define py_sources $[wildcard $[TOPDIR]/$[DIRPREFIX]*.py]
+  #endif
+  #define install_py $[py_sources:$[TOPDIR]/$[DIRPREFIX]%=%]
+
   // These are the source files that our dependency cache file will
   // depend on.  If it's an empty list, we won't bother writing rules to
   // freshen the cache file.
@@ -171,6 +179,7 @@
     $[if $[install_data],$[install_data_dir]] \
     $[if $[install_config],$[install_config_dir]] \
     $[if $[install_igatedb],$[install_igatedb_dir]] \
+    $[if $[install_py],$[install_py_dir] $[install_py_package_dir]] \
     ]
 
 // Similarly, we need to ensure that $[ODIR] exists.  Trying to make
@@ -285,7 +294,8 @@ $[TAB] if exist $[file] del /f $[file]
      $[INSTALL_HEADERS:%=$[install_headers_dir]/%] \
      $[INSTALL_PARSER_INC:%=$[install_parser_inc_dir]/%] \
      $[INSTALL_DATA:%=$[install_data_dir]/%] \
-     $[INSTALL_CONFIG:%=$[install_config_dir]/%]
+     $[INSTALL_CONFIG:%=$[install_config_dir]/%] \
+     $[if $[install_py],$[install_py:%=$[install_py_dir]/%] $[install_py_package_dir]/__init__.py]
 
 #define installed_igate_files \
      $[get_igatedb(metalib_target lib_target ss_lib_target):$[ODIR]/%=$[install_igatedb_dir]/%]
@@ -923,6 +933,18 @@ $[osfilename $[install_config_dir]/$[file]] : $[patsubst %,$[osfilename %],$[fil
 $[TAB] xcopy /I/Y $[osfilename $[local]] $[osfilename $[dest]]
 #end file
 
+#foreach file $[install_py]
+$[osfilename $[install_py_dir]/$[file]] : $[patsubst %,$[osfilename %],$[file]]
+#define local $[file]
+#define dest $[install_py_dir]
+$[TAB] xcopy /I/Y $[osfilename $[local]] $[osfilename $[dest]]
+#end file
+   
+#if $[install_py]
+$[osfilename $[install_py_package_dir]/__init__.py] :
+$[TAB] echo. > $[osfilename $[install_py_package_dir]/__init__.py]
+#endif
+
 // Finally, all the special targets.  These are commands that just need
 // to be invoked; we don't pretend to know what they are.
 #forscopes special_target

+ 61 - 6
panda/src/audiotraits/fmodAudioManager.cxx

@@ -66,6 +66,17 @@ FmodAudioManager() {
   _cache_limit = audio_cache_limit;
   _concurrent_sound_limit = 0;
 
+  // RobCode
+  // Fill list of supported types
+  // Order of this list (first is most important) determines
+  // the search order for sound files without an extension.
+  _supported_types.push_back("wav");
+  _supported_types.push_back("ogg");
+  _supported_types.push_back("mp3");
+  _supported_types.push_back("mid");
+  _supported_types.push_back("midi");
+  _supported_types.push_back("rmi");
+
   // Initialize FMOD, if this is the first manager created.
   _is_valid = true;
   if (_active_managers == 0) {
@@ -168,11 +179,55 @@ get_sound(const string &file_name, bool positional) {
   nassertr(is_valid(), NULL);
   Filename path = file_name;
 
-  if (use_vfs) {
-    VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-    vfs->resolve_filename(path, get_sound_path());
-  } else {
-    path.resolve_filename(get_sound_path());
+  // RobCode
+  // test for an invalid suffix type
+  string suffix = downcase(path.get_extension());
+  if (!suffix.empty()) {
+    SupportedTypes::iterator type_i=find(_supported_types.begin(), _supported_types.end(), suffix);
+    // if suffix not found in list of supported types
+    if (type_i == _supported_types.end()) {
+        // print error and return
+        audio_error("FmodAudioManager::get_sound: \""<<path<<"\" is not a supported sound file format.");
+        audio_error("Supported formats are: WAV, OGG, MP3, MID, MIDI, RMI");
+        return get_null_sound();
+    } else { // the suffix is of a supported type
+      audio_debug("FmodAudioManager::get_sound: \""<<path<<"\" is a supported sound file format.");
+      // resolve the path normally
+      if (use_vfs) {
+        VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+        vfs->resolve_filename(path, get_sound_path());
+      } else {
+        path.resolve_filename(get_sound_path());
+      }
+    }
+  } else { // no suffix given. Search for supported file types of the same name.
+    audio_debug("FmodAudioManager::get_sound: \""<<path<<"\" has no extension. Searching for supported files with the same name.");
+    // look for each type of file 
+    SupportedTypes::const_iterator type_i; 
+    for (type_i = _supported_types.begin(); type_i != _supported_types.end(); ++type_i) { 
+      path.set_extension(*type_i); // set extension as supported type
+      
+      if (use_vfs) { // check virtual file system
+        VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+        if (vfs->resolve_filename(path, get_sound_path())) {
+          break; // break out of loop with a valid type_i value and path with correct extension
+        }
+      } else { // check regular file system
+        if (path.resolve_filename(get_sound_path())) {
+          break; // break out of loop with a valid type_i value and path with correct extension
+        }
+      }
+    } // end for loop
+    // if no valid file found
+    if (type_i == _supported_types.end() ) {
+      // just print a warning for now
+      audio_debug("FmodAudioManager::get_sound: \""<<file_name<<"\" does not exist, even with default sound extensions.");
+      // reset path to no extension
+      path.set_extension("");
+    } else {
+        audio_debug("FmodAudioManager::get_sound: \""<<path<<"\" found using default sound extensions.");
+        suffix = downcase(path.get_extension()); // update suffix (used below when loading file)
+    }
   }
 
   audio_debug("  resolved file_name is '"<<path<<"'");
@@ -241,7 +296,7 @@ get_sound(const string &file_name, bool positional) {
   }
 
   string os_path = path.to_os_specific();
-  string suffix = downcase(path.get_extension());
+  //string suffix = downcase(path.get_extension()); // declared above by RobCode
   
   if (suffix == "mid" || suffix == "rmi" || suffix == "midi") {
     stream = FSOUND_Stream_Open(os_path.c_str(), 0, 0, 0);

+ 5 - 0
panda/src/audiotraits/fmodAudioManager.h

@@ -136,6 +136,11 @@ private:
   typedef pdeque<string> LRU;
   LRU _lru;
 
+  // RobCode
+  // List of supported sound formats
+  typedef pvector<string> SupportedTypes;
+  SupportedTypes _supported_types;
+
   void release_sound(FmodAudioSound *audioSound);
 
   int _cache_limit;

+ 17 - 0
panda/src/putil/buttonHandle.I

@@ -92,6 +92,23 @@ get_ascii_equivalent() const {
   return has_ascii_equivalent() ? (char)_index : '\0';
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonHandle::matches
+//       Access: Published
+//  Description: Returns true if this ButtonHandle is the same as the
+//               other one, or if the other one is an alias for this
+//               one.  (Does not return true if this button is an
+//               alias for the other one, however.)
+//
+//               This is a more general comparison than operator ==.
+////////////////////////////////////////////////////////////////////
+INLINE bool ButtonHandle::
+matches(const ButtonHandle &other) const {
+  return ((*this) == other ||
+          (other != ButtonHandle::none() &&
+           get_alias() == other));
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ButtonHandle::get_index
 //       Access: Published

+ 22 - 0
panda/src/putil/buttonHandle.cxx

@@ -36,3 +36,25 @@ get_name() const {
     return ButtonRegistry::ptr()->get_name(*this);
   }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonHandle::get_alias
+//       Access: Published
+//  Description: Returns the alias (alternate name) associated with
+//               the button, if any, or ButtonHandle::none() if the
+//               button has no alias.
+//
+//               Each button is allowed to have one alias, and
+//               multiple different buttons can refer to the same
+//               alias.  The alias should be the more general name for
+//               the button, for instance, shift is an alias for
+//               lshift, but not vice-versa.
+////////////////////////////////////////////////////////////////////
+ButtonHandle ButtonHandle::
+get_alias() const {
+  if ((*this) == ButtonHandle::none()) {
+    return ButtonHandle::none();
+  } else {
+    return ButtonRegistry::ptr()->get_alias(*this);
+  }
+}

+ 4 - 0
panda/src/putil/buttonHandle.h

@@ -43,6 +43,10 @@ PUBLISHED:
   INLINE bool has_ascii_equivalent() const;
   INLINE char get_ascii_equivalent() const;
 
+  ButtonHandle get_alias() const;
+
+  INLINE bool matches(const ButtonHandle &other) const;
+
   INLINE int get_index() const;
   INLINE void output(ostream &out) const;
   INLINE static ButtonHandle none();

+ 18 - 3
panda/src/putil/buttonRegistry.I

@@ -24,13 +24,14 @@
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE ButtonRegistry::RegistryNode::
-RegistryNode(ButtonHandle handle, const string &name) :
-  _handle(handle), _name(name) {
+RegistryNode(ButtonHandle handle, ButtonHandle alias, const string &name) :
+  _handle(handle), _alias(alias), _name(name) 
+{
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: ButtonRegistry::ptr
-//       Access: Public, Static
+//       Access: Published, Static
 //  Description: Returns the pointer to the global ButtonRegistry
 //               object.
 ////////////////////////////////////////////////////////////////////
@@ -53,3 +54,17 @@ get_name(ButtonHandle button) const {
   nassertr(rnode != (RegistryNode *)NULL, "");
   return rnode->_name;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: ButtonRegistry::get_alias
+//       Access: Public
+//  Description: Returns the alias for the indicated button, or
+//               ButtonHandle::none() if the button has no specified
+//               alias.
+////////////////////////////////////////////////////////////////////
+INLINE ButtonHandle ButtonRegistry::
+get_alias(ButtonHandle button) const {
+  RegistryNode *rnode = look_up(button);
+  nassertr(rnode != (RegistryNode *)NULL, ButtonHandle::none());
+  return rnode->_alias;
+}

+ 19 - 7
panda/src/putil/buttonRegistry.cxx

@@ -38,13 +38,21 @@ ButtonRegistry *ButtonRegistry::_global_pointer = NULL;
 //               it was already registered; in either case, the new
 //               ButtonHandle is loaded into the first parameter.
 //
+//               If the alias is not ButtonHandle::none(), it
+//               indicates an alias (alternate name) for the same
+//               button.  Each button is allowed to have one alias,
+//               and multiple different buttons can refer to the same
+//               alias.  The alias should be the more general name for
+//               the button, for instance, shift is an alias for
+//               lshift, but not vice-versa.
+//
 //               This defines a new kind of button matching the
 //               indicated name.  The ButtonHandle can then be passed
 //               around to devices as a button in its own right.
 ////////////////////////////////////////////////////////////////////
 bool ButtonRegistry::
 register_button(ButtonHandle &button_handle, const string &name,
-                char ascii_equivalent) {
+                ButtonHandle alias, char ascii_equivalent) {
   NameRegistry::iterator ri;
   ri = _name_registry.find(name);
 
@@ -81,7 +89,7 @@ register_button(ButtonHandle &button_handle, const string &name,
     ButtonHandle new_handle;
     new_handle._index = index;
 
-    RegistryNode *rnode = new RegistryNode(new_handle, name);
+    RegistryNode *rnode = new RegistryNode(new_handle, alias, name);
     _handle_registry[index] = rnode;
     _name_registry[name] = rnode;
 
@@ -108,7 +116,7 @@ register_button(ButtonHandle &button_handle, const string &name,
 
 ////////////////////////////////////////////////////////////////////
 //     Function: ButtonRegistry::get_button
-//       Access: Public
+//       Access: Published
 //  Description: Finds a ButtonHandle in the registry matching the
 //               indicated name.  If there is no such ButtonHandle,
 //               registers a new one and returns it.
@@ -129,7 +137,7 @@ get_button(const string &name) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: ButtonRegistry::find_ascii_button
-//       Access: Public
+//       Access: Published
 //  Description: Finds a ButtonHandle in the registry matching the
 //               indicated ASCII equivalent character.  If there is no
 //               such ButtonHandle, returns ButtonHandle::none().
@@ -142,10 +150,9 @@ find_ascii_button(char ascii_equivalent) const {
   return _handle_registry[ascii_equivalent]->_handle;
 }
 
-
 ////////////////////////////////////////////////////////////////////
 //     Function: ButtonRegistry::write
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 void ButtonRegistry::
@@ -165,11 +172,16 @@ write(ostream &out) const {
   NameRegistry::const_iterator ri;
   for (ri = _name_registry.begin(); ri != _name_registry.end(); ++ri) {
     if (!(*ri).second->_handle.has_ascii_equivalent()) {
-      out << "  " << (*ri).second->_name << "\n";
+      out << "  " << (*ri).second->_name;
+      if ((*ri).second->_alias != ButtonHandle::none()) {
+        out << " (alias " << (*ri).second->_alias << ")";
+      }
+      out << "\n";
     }
   }
 }
 
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ButtonRegistry::Constructor
 //       Access: Private

+ 7 - 3
panda/src/putil/buttonRegistry.h

@@ -37,27 +37,31 @@ class EXPCL_PANDA ButtonRegistry {
 protected:
   class EXPCL_PANDA RegistryNode {
   public:
-    INLINE RegistryNode(ButtonHandle handle, const string &name);
+    INLINE RegistryNode(ButtonHandle handle, ButtonHandle alias,
+                        const string &name);
 
     ButtonHandle _handle;
+    ButtonHandle _alias;
     string _name;
   };
 
 public:
   bool register_button(ButtonHandle &button_handle, const string &name,
+                       ButtonHandle alias = ButtonHandle::none(),
                        char ascii_equivalent = '\0');
 
 PUBLISHED:
   ButtonHandle get_button(const string &name);
   ButtonHandle find_ascii_button(char ascii_equivalent) const;
 
+  void write(ostream &out) const;
+
   // ptr() returns the pointer to the global ButtonRegistry object.
   INLINE static ButtonRegistry *ptr();
 
 public:
   INLINE string get_name(ButtonHandle button) const;
-
-  void write(ostream &out) const;
+  INLINE ButtonHandle get_alias(ButtonHandle button) const;
 
 private:
   // The ButtonRegistry class should never be constructed by user code.

+ 27 - 7
panda/src/putil/keyboardButton.cxx

@@ -91,6 +91,12 @@ DEFINE_KEYBD_BUTTON_HANDLE(print_screen)
 DEFINE_KEYBD_BUTTON_HANDLE(shift)
 DEFINE_KEYBD_BUTTON_HANDLE(control)
 DEFINE_KEYBD_BUTTON_HANDLE(alt)
+DEFINE_KEYBD_BUTTON_HANDLE(lshift)
+DEFINE_KEYBD_BUTTON_HANDLE(rshift)
+DEFINE_KEYBD_BUTTON_HANDLE(lcontrol)
+DEFINE_KEYBD_BUTTON_HANDLE(rcontrol)
+DEFINE_KEYBD_BUTTON_HANDLE(lalt)
+DEFINE_KEYBD_BUTTON_HANDLE(ralt)
 
 
 ////////////////////////////////////////////////////////////////////
@@ -101,12 +107,18 @@ DEFINE_KEYBD_BUTTON_HANDLE(alt)
 ////////////////////////////////////////////////////////////////////
 void KeyboardButton::
 init_keyboard_buttons() {
-  ButtonRegistry::ptr()->register_button(_space, "space", ' ');
-  ButtonRegistry::ptr()->register_button(_backspace, "backspace", '\x08');
-  ButtonRegistry::ptr()->register_button(_tab, "tab", '\x09');
-  ButtonRegistry::ptr()->register_button(_enter, "enter", '\x0d');
-  ButtonRegistry::ptr()->register_button(_escape, "escape", '\x1b');
-  ButtonRegistry::ptr()->register_button(_del, "delete", '\x7f');
+  ButtonRegistry::ptr()->register_button(_space, "space", 
+                                         ButtonHandle::none(), ' ');
+  ButtonRegistry::ptr()->register_button(_backspace, "backspace", 
+                                         ButtonHandle::none(), '\x08');
+  ButtonRegistry::ptr()->register_button(_tab, "tab", 
+                                         ButtonHandle::none(), '\x09');
+  ButtonRegistry::ptr()->register_button(_enter, "enter", 
+                                         ButtonHandle::none(), '\x0d');
+  ButtonRegistry::ptr()->register_button(_escape, "escape", 
+                                         ButtonHandle::none(), '\x1b');
+  ButtonRegistry::ptr()->register_button(_del, "delete", 
+                                         ButtonHandle::none(), '\x7f');
 
   ButtonRegistry::ptr()->register_button(_f1, "f1");
   ButtonRegistry::ptr()->register_button(_f2, "f2");
@@ -141,11 +153,19 @@ init_keyboard_buttons() {
   ButtonRegistry::ptr()->register_button(_scroll_lock, "scroll_lock");
   ButtonRegistry::ptr()->register_button(_print_screen, "print_screen");
 
+  ButtonRegistry::ptr()->register_button(_lshift, "lshift", _shift);
+  ButtonRegistry::ptr()->register_button(_rshift, "rshift", _shift);
+  ButtonRegistry::ptr()->register_button(_lcontrol, "lcontrol", _control);
+  ButtonRegistry::ptr()->register_button(_rcontrol, "rcontrol", _control);
+  ButtonRegistry::ptr()->register_button(_lalt, "lalt", _alt);
+  ButtonRegistry::ptr()->register_button(_ralt, "ralt", _alt);
+
   // Also register all of the visible ASCII characters.
   for (int i = 32; i < 127; i++) {
     if (isgraph(i)) {
       ButtonHandle key;
-      ButtonRegistry::ptr()->register_button(key, string(1, (char)i), i);
+      ButtonRegistry::ptr()->register_button(key, string(1, (char)i), 
+                                             ButtonHandle::none(), i);
     }
   }
 }

+ 7 - 0
panda/src/putil/keyboardButton.h

@@ -74,6 +74,13 @@ PUBLISHED:
   static ButtonHandle scroll_lock();
   static ButtonHandle print_screen();
 
+  static ButtonHandle lshift();
+  static ButtonHandle rshift();
+  static ButtonHandle lcontrol();
+  static ButtonHandle rcontrol();
+  static ButtonHandle lalt();
+  static ButtonHandle ralt();
+
 public:
   static void init_keyboard_buttons();
 };

+ 8 - 4
panda/src/putil/modifierButtons.cxx

@@ -235,7 +235,7 @@ bool ModifierButtons::
 has_button(ButtonHandle button) const {
   PTA(ButtonHandle)::const_iterator bi;
   for (bi = _button_list.begin(); bi != _button_list.end(); ++bi) {
-    if (button == (*bi)) {
+    if (button.matches(*bi)) {
       return true;
     }
   }
@@ -250,6 +250,10 @@ has_button(ButtonHandle button) const {
 //               being monitored.  Returns true if the button was
 //               removed, false if it was not being monitored in the
 //               first place.
+//
+//               Unlike the other methods, you cannot remove a button
+//               by removing its alias; you have to remove exactly the
+//               button itself.
 ////////////////////////////////////////////////////////////////////
 bool ModifierButtons::
 remove_button(ButtonHandle button) {
@@ -289,7 +293,7 @@ remove_button(ButtonHandle button) {
 bool ModifierButtons::
 button_down(ButtonHandle button) {
   for (int i = 0; i < (int)_button_list.size(); i++) {
-    if (button == _button_list[i]) {
+    if (button.matches(_button_list[i])) {
       _state |= ((BitmaskType)1 << i);
       return true;
     }
@@ -311,7 +315,7 @@ button_down(ButtonHandle button) {
 bool ModifierButtons::
 button_up(ButtonHandle button) {
   for (int i = 0; i < (int)_button_list.size(); i++) {
-    if (button == _button_list[i]) {
+    if (button.matches(_button_list[i])) {
       _state &= ~((BitmaskType)1 << i);
       return true;
     }
@@ -330,7 +334,7 @@ button_up(ButtonHandle button) {
 bool ModifierButtons::
 is_down(ButtonHandle button) const {
   for (int i = 0; i < (int)_button_list.size(); i++) {
-    if (button == _button_list[i]) {
+    if (button.matches(_button_list[i])) {
       return ((_state & ((BitmaskType)1 << i)) != 0);
     }
   }

+ 150 - 14
panda/src/windisplay/winGraphicsWindow.cxx

@@ -93,6 +93,12 @@ WinGraphicsWindow(GraphicsPipe *pipe, GraphicsStateGuardian *gsg,
   _cursor = 0;
   memset(_keyboard_state, 0, sizeof(BYTE) * num_virtual_keys);
   _lost_keypresses = false;
+  _lshift_down = false;
+  _rshift_down = false;
+  _lcontrol_down = false;
+  _rcontrol_down = false;
+  _lalt_down = false;
+  _ralt_down = false;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1406,6 +1412,27 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
           ScreenToClient(hwnd, &point);
           handle_keypress(lookup_key(wparam), point.x, point.y, 
                           get_message_time());
+
+          // wparam does not contain left/right information for SHIFT,
+          // CONTROL, or ALT, so we have to track their status and do
+          // the right thing.  We'll send the left/right specific key
+          // event along with the general key event.
+          //
+          // Key repeating is not being handled consistently for LALT
+          // and RALT, but from comments below, it's only being handled
+          // the way it is for backspace, so we'll leave it as is.
+          if (wparam == VK_MENU) {
+            if ((GetKeyState(VK_LMENU) & 0x8000) != 0 && ! _lalt_down) {
+              handle_keypress(KeyboardButton::lalt(), point.x, point.y,
+                              get_message_time());
+              _lalt_down = true;
+            }
+            if ((GetKeyState(VK_RMENU) & 0x8000) != 0 && ! _ralt_down) {
+              handle_keypress(KeyboardButton::ralt(), point.x, point.y,
+                              get_message_time());
+              _ralt_down = true;
+            }
+          }
           if (wparam == VK_F10) {
             // bypass default windproc F10 behavior (it activates the main
             // menu, but we have none)
@@ -1446,7 +1473,35 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
           ScreenToClient(hwnd, &point);
           handle_keypress(lookup_key(wparam), point.x, point.y,
                           get_message_time());
-    
+
+          // wparam does not contain left/right information for SHIFT,
+          // CONTROL, or ALT, so we have to track their status and do
+          // the right thing.  We'll send the left/right specific key
+          // event along with the general key event.
+          if (wparam == VK_SHIFT) {
+            if ((GetKeyState(VK_LSHIFT) & 0x8000) != 0 && ! _lshift_down) {
+              handle_keypress(KeyboardButton::lshift(), point.x, point.y,
+                              get_message_time());
+              _lshift_down = true;
+            }
+            if ((GetKeyState(VK_RSHIFT) & 0x8000) != 0 && ! _rshift_down) {
+              handle_keypress(KeyboardButton::rshift(), point.x, point.y,
+                              get_message_time());
+              _rshift_down = true;
+            }
+          } else if(wparam == VK_CONTROL) {
+            if ((GetKeyState(VK_LCONTROL) & 0x8000) != 0 && ! _lcontrol_down) {
+              handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
+                              get_message_time());
+              _lcontrol_down = true;
+            }
+            if ((GetKeyState(VK_RCONTROL) & 0x8000) != 0 && ! _rcontrol_down) {
+              handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
+                              get_message_time());
+              _rcontrol_down = true;
+            }
+          }
+
           // Handle Cntrl-V paste from clipboard.  Is there a better way
           // to detect this hotkey?
           if ((wparam=='V') && (GetKeyState(VK_CONTROL) < 0) &&
@@ -1471,7 +1526,6 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
               CloseClipboard();
             }
           }
-
         } else {
           // Actually, for now we'll respect the repeat anyway, just
           // so we support backspace properly.  Rethink later.
@@ -1480,6 +1534,58 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
           ScreenToClient(hwnd, &point);
           handle_keypress(lookup_key(wparam), point.x, point.y,
                           get_message_time());
+
+          // wparam does not contain left/right information for SHIFT,
+          // CONTROL, or ALT, so we have to track their status and do
+          // the right thing.  We'll send the left/right specific key
+          // event along with the general key event.
+          //
+          // If the user presses LSHIFT and then RSHIFT, the RSHIFT event
+          // will come in with the keyrepeat flag on (i.e. it will end up
+          // in this block).  The logic below should detect this correctly
+          // and only send the RSHIFT event.  Note that the CONTROL event
+          // will be sent twice, once for each keypress.  Since keyrepeats
+          // are currently being sent simply as additional keypress events,
+          // that should be okay for now.
+          if (wparam == VK_SHIFT) {
+            if (((GetKeyState(VK_LSHIFT) & 0x8000) != 0) && ! _lshift_down ) {
+              handle_keypress(KeyboardButton::lshift(), point.x, point.y,
+                              get_message_time());
+              _lshift_down = true;
+            } else if (((GetKeyState(VK_RSHIFT) & 0x8000) != 0) && ! _rshift_down ) {
+              handle_keypress(KeyboardButton::rshift(), point.x, point.y,
+                              get_message_time());
+              _rshift_down = true;
+            } else {
+              if ((GetKeyState(VK_LSHIFT) & 0x8000) != 0) {
+                handle_keypress(KeyboardButton::lshift(), point.x, point.y,
+                                get_message_time());
+              }
+              if ((GetKeyState(VK_RSHIFT) & 0x8000) != 0) {
+                handle_keypress(KeyboardButton::rshift(), point.x, point.y,
+                                get_message_time());
+              }
+            }
+          } else if(wparam == VK_CONTROL) {
+            if (((GetKeyState(VK_LCONTROL) & 0x8000) != 0) && ! _lcontrol_down ) {
+              handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
+                              get_message_time());
+              _lcontrol_down = true;
+            } else if (((GetKeyState(VK_RCONTROL) & 0x8000) != 0) && ! _rcontrol_down ) {
+              handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
+                              get_message_time());
+              _rcontrol_down = true;
+            } else {
+              if ((GetKeyState(VK_LCONTROL) & 0x8000) != 0) {
+                handle_keypress(KeyboardButton::lcontrol(), point.x, point.y,
+                                get_message_time());
+              }
+              if ((GetKeyState(VK_RCONTROL) & 0x8000) != 0) {
+                handle_keypress(KeyboardButton::rcontrol(), point.x, point.y,
+                                get_message_time());
+              }
+            }
+          }
         }
         break;
     
@@ -1493,6 +1599,39 @@ window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
             << "keyup: " << wparam << " (" << lookup_key(wparam) << ")\n";
         }
         handle_keyrelease(lookup_key(wparam), get_message_time());
+
+        // wparam does not contain left/right information for SHIFT,
+        // CONTROL, or ALT, so we have to track their status and do
+        // the right thing.  We'll send the left/right specific key
+        // event along with the general key event.
+        if (wparam == VK_SHIFT) {
+          if ((GetKeyState(VK_LSHIFT) & 0x8000) == 0 && _lshift_down) {
+            handle_keyrelease(KeyboardButton::lshift(), get_message_time());
+            _lshift_down = false;
+          }
+          if ((GetKeyState(VK_RSHIFT) & 0x8000) == 0 && _rshift_down) {
+            handle_keyrelease(KeyboardButton::rshift(), get_message_time());
+            _rshift_down = false;
+          }
+        } else if(wparam == VK_CONTROL) {
+          if ((GetKeyState(VK_LCONTROL) & 0x8000) == 0 && _lcontrol_down) {
+            handle_keyrelease(KeyboardButton::lcontrol(), get_message_time());
+            _lcontrol_down = false;
+          }
+          if ((GetKeyState(VK_RCONTROL) & 0x8000) == 0 && _rcontrol_down) {
+            handle_keyrelease(KeyboardButton::rcontrol(), get_message_time());
+            _rcontrol_down = false;
+          }
+        } else if(wparam == VK_MENU) {
+          if ((GetKeyState(VK_LMENU) & 0x8000) == 0 && _lalt_down) {
+            handle_keyrelease(KeyboardButton::lalt(), get_message_time());
+            _lalt_down = false;
+          }
+          if ((GetKeyState(VK_RMENU) & 0x8000) == 0 && _ralt_down) {
+            handle_keyrelease(KeyboardButton::ralt(), get_message_time());
+            _ralt_down = false;
+          }
+        }
         break;
     
       case WM_KILLFOCUS: 
@@ -1870,20 +2009,17 @@ lookup_key(WPARAM wparam) const {
   case VK_SCROLL: return KeyboardButton::scroll_lock();
   case VK_SNAPSHOT: return KeyboardButton::print_screen();
 
-  case VK_SHIFT:
-  case VK_LSHIFT:
-  case VK_RSHIFT:
-    return KeyboardButton::shift();
+  case VK_SHIFT: return KeyboardButton::shift();
+  case VK_LSHIFT: return KeyboardButton::lshift();
+  case VK_RSHIFT: return KeyboardButton::rshift();
 
-  case VK_CONTROL:
-  case VK_LCONTROL:
-  case VK_RCONTROL:
-    return KeyboardButton::control();
+  case VK_CONTROL: return KeyboardButton::control();
+  case VK_LCONTROL: return KeyboardButton::lcontrol();
+  case VK_RCONTROL: return KeyboardButton::rcontrol();
 
-  case VK_MENU:
-  case VK_LMENU:
-  case VK_RMENU:
-    return KeyboardButton::alt();
+  case VK_MENU: return KeyboardButton::alt();
+  case VK_LMENU: return KeyboardButton::lalt();
+  case VK_RMENU: return KeyboardButton::ralt();
 
   default:
     int key = MapVirtualKey(wparam, 2);

+ 12 - 0
panda/src/windisplay/winGraphicsWindow.h

@@ -139,6 +139,18 @@ private:
   BYTE _keyboard_state[num_virtual_keys];
   bool _lost_keypresses;
 
+  // These are used to store the status of the individual left and right
+  // shift, control, and alt keys.  Keyboard events are not sent for
+  // these individual keys, but for each pair as a whole.  The status
+  // of each key must be checked as keypress and keyrelease events are
+  // received.
+  bool _lshift_down;
+  bool _rshift_down;
+  bool _lcontrol_down;
+  bool _rcontrol_down;
+  bool _lalt_down;
+  bool _ralt_down;
+
 private:
   // We need this map to support per-window calls to window_proc().
   typedef map<HWND, WinGraphicsWindow *> WindowHandles;