home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / ACLINF.ZIP / ACCEL
Text File  |  1992-09-10  |  23KB  |  544 lines

  1.                      Accelerator table decoding for OS/2
  2.  
  3. [PM has an affinity for accelerator tables. It loves to translate the WM_CHAR
  4. message into a WM_COMMAND or WM_SYSCOMMAND or WM_HELP. It looks everywhere
  5. for the translation table.]
  6.  
  7. Since there seems to be little information about the accelerator tables used 
  8. for OS/2, I am supplying what information that I have collected. Some of this
  9. is documented by Microsoft [and almost none by IBM], some is deduced with the
  10. aid of monitor programs such as SPY, and some is simply deduced (make that
  11. "intelligently guessed" . . . .)
  12.  
  13. However, it is not all black. The procedures mentioned here and the tables
  14. are documented. If IBM later changes the accelerator processing then they
  15. will break their own programs and rules. There are no secret addresses
  16. mentioned here. All structures are public. All procedures are public. This is
  17. safe PM programming.
  18.  
  19.  
  20.                     Accelerator tables come in two forms.
  21.  
  22. 1. You can create them with the aid of the resource compiler and store them
  23. in your resources; or
  24.  
  25. 2. You can simply build them from the structures supplied by OS/2.
  26.  
  27. There is no reason that a single application NEED use only one accelerator
  28. table. If you have an accelerator table to translate some key combinations to
  29. various commands then the most common approach is to give it the ID of your
  30. client window and place the accelerator table in the resource file. Then by
  31. using the frame control flag, FCF_ACCELTABLE, in creating the standard
  32. window, the accelerator table is loaded and used.
  33.  
  34. However, if you wish to have a different accelerator table for various states
  35. of the program then you may have more than one accelerator table in the
  36. resource file. The only difficulty is that you have only one chance to call
  37. the accelerator table the same as the client window ID and thereby have the
  38. system load and manage the accelerator table. The others are left to you to
  39. manage.
  40.  
  41. You can do the management yourself by processing the WM_TRANSLATEACCEL
  42. messages or by using WinSetAccelTable to set the default accelerator table
  43. for your application.
  44.  
  45. If you wish to store multiple accelerator tables in the resource file then
  46. you can use the WinLoadAccelTable procedure to load them. The result is a
  47. handle which may be given to the WinTranslateAccel procedure.
  48. WinDestroyAccelTable will remove the accelerator table handle which was
  49. loaded by WinLoadAccelTable.
  50.  
  51. If you wish to create the accelerator tables programmatically, or even
  52. statically, then use the WinCreateAccelTable procedure to turn the list of
  53. structures into an accelerator table handle. WinDestroyAccelTable again will
  54. remove the handle association when you no longer need it.
  55.  
  56.  
  57.                            Accelerator translations
  58.  
  59. The last accelerator procedure is the WinTranslateAccel. This is the most
  60. useful of the lot.
  61.  
  62. ----
  63.  
  64. The Microsoft quick help information for version 1 has this for
  65. WinTranslateAccel():
  66.  
  67. Generally, applications do not have to call this function. It is normally
  68. called automatically by WinGetMsg and WinPeekMsg when a WM_CHAR message is
  69. received, with the window handle of the active window as the first parameter.
  70. The standard frame window procedure always passes WM_COMMAND messages to the
  71. FID_CLIENT window. Since the message is physically changed by
  72. WinTranslateAccel, applications will not receive the WM_CHAR messages that
  73. resulted in WM_COMMAND, WM_SYSCOMMAND, or WM_HELP messages.
  74.  
  75. ----
  76.  
  77. It takes an accelerator table handle and a message pointer. The result is the
  78. translated accelerator based upon the WM_CHAR message. It works as follows:
  79.  
  80. Lets assume that you have an accelerator table which you have stored in the
  81. resource file. It was defined as:
  82.  
  83. #define MY_UNSHIFTED_FUNCTION   101
  84. #define MY_SHIFTED_FUNCTION     102
  85.  
  86. ACCELTABLE 523
  87. BEGIN
  88.     "a", MY_UNSHIFTED_FUNCTION,         ALT
  89.     "a", MY_SHIFTED_FUNCTION,   SHIFT | ALT
  90. END
  91.  
  92. When you press a key on the keyboard, the hardware generates an interrupt.
  93. The interrupt suspends OS/2. OS/2 reads the keyboard and determines that the
  94. key has been pressed. Let's assume that this is the post with the label "A".
  95. OS/2 looks in the keyboard translation table for the proper shift state and
  96. determines that this is the shift letter "a". (You had the shift key down.)
  97.  
  98. The shifted letter "a" is placed into the system queue as a WM_CHAR message.
  99. (First guess: I am only guessing that this is a WM_CHAR -- nowhere is it
  100. documented to the general public. However, it must be some event. I will call
  101. this the WM_CHAR message for the lack of a better name.)
  102.  
  103. Sometime later PM looks at the system queue and fetches the WM_CHAR message.
  104. It looks at the active window and places it in to the message queue for that
  105. application. If the application was waiting for a message, then it clears the
  106. generates the event for the semaphore at the same time.  (OS/2 will then move
  107. the thread from BLOCKED to READY and it will be dispatched at the next
  108. appropriate time. If you want more information about the dispatcher then
  109. there are several good books on OS theory.)
  110.  
  111. The application sometime later calls WinGetMsg (or it was in WinGetMsg and
  112. was blocked on that previous semaphore). WinGetMsg peeks into the application
  113. queue and sees a WM_CHAR message for the shift "a".
  114.  
  115. The next step that WinGetMsg does before it returns is to do any accelerator
  116. translations. This is done by sending a WM_TRANSLATEACCEL message to the
  117. active window. As part of the message is a pointer to the WM_CHAR message
  118. that it will supply should the translation not be successful.
  119.  
  120. Since most applications do not process the WM_TRANSLATEACCEL message it is
  121. given to our old friend the WinDefWindowProc. The WinDefWindowProc knows what
  122. to do with the WM_TRANSLATEACCEL message. It passes the buck. It does this by
  123. doing a WinSendMessage to the parent. (Next Guess: I believe that it uses the
  124. parent. The docs are contradict the help information. However, since there is
  125. a parent/child relationship between the frame window and the active window
  126. while there is not necessarily a owner relationship, it is a good guess that
  127. it follows the parent rather than the owner relationship.)
  128.  
  129. This message is the WM_TRANSLATEACCEL message that it just received.
  130.  
  131. Eventually, the WM_TRANSLATEACCEL message will filter to the frame window.
  132. The frame window will finally do something with the message.
  133.  
  134. The message processing is done with the aid of a procedure called
  135. WinTranslateAccel() This procedure takes the message pointer given in mp1 and
  136. returns TRUE or FALSE depending upon wether or not it found the WM_CHAR data
  137. in the accelerator table. If the procedure returns TRUE, the frame window
  138. returns TRUE.
  139.  
  140.  
  141. [Moral: If you want to override any system accelerator, you need only put it
  142. into your application accelerator table with the proper value.
  143.  
  144. This override applies to all windows for the application. If you have an
  145. accelerator entry for the enter key, then it applies to every window
  146. (including dialog entry fields -- isn't that just great! How do you expect to
  147. complete the field without a return key? If you override F1 then you have
  148. overridden F1 for all windows, menus, dialogs, etc.)]
  149.  
  150.  
  151. If the WinTranslateAccel returns FALSE then the frame window looks in the
  152. system translation queue for a match. If the WinTranslateAccel returns TRUE
  153. then the frame window returns TRUE.
  154.  
  155. [Another grey area . . . .]
  156. [A similar effect may be done if the frame window passed it to its parent. 
  157. The parent of the application frame window is the desktop. It may be that the
  158. system translation is done in the desktop window procedure rather than the
  159. frame window. I don't know. I can't see a message sent to the desktop but
  160. that only means that SPY was not able to capture the event if it occurred.]
  161.  
  162. Since you have a translator for the shift "a" in your table the
  163. WinTranslateAccel found the entry. It changed the message from WM_CHAR to
  164. WM_COMMAND and gave the command value 102. It then returned TRUE. Now your
  165. shift "a" has been destroyed in favor of a WM_COMMAND message.
  166.  
  167. The return linkage is then un-rolled and the user gets a WM_COMMAND to be
  168. given to the WinDispatchMsg procedure.
  169.  
  170. If, instead the character had been a "b" (which is not in your table . . .
  171. you like "b"s) then the WinTranslateAccel would not have found the entry. In
  172. this case, WinTranslateAccel does not change the message and returns FALSE.
  173.  
  174. The frame window sees the FALSE and then uses the system accelerator table to
  175. do the translation. There is no translation in the system table entry for a
  176. shift "b", so it returns FALSE again.
  177.  
  178. Ok, true or false the thread then unwinds. The frame window returns to the
  179. WinDefWindowProc which returns to the WinDefWindowProc which returns . . . .
  180.  
  181. Eventually, you will end up back in WinGetMsg. The result of the translated
  182. message is then given to the caller to give to the WinDispatchMsg procedure.
  183. This is the untranslatable "b" or the WM_COMMAND of 102 when you used the
  184. shift "a".
  185.  
  186.                                  Moral Advice
  187.  
  188. It is not a good idea to simply process the WM_TRANSLATEACCEL message to see
  189. if it is some key combination and if it is to return FALSE without having
  190. changed the message. That is cheating. The false return from the message
  191. means that you have processed the message.  But you made no changes!  If you
  192. wish to have your own table then do it by the rules. Use the WinCreateAccel
  193. or WinLoadAccel procedures.
  194.  
  195.  
  196.  
  197.                              Accelerator Tables.
  198.  
  199. The WinTranslateAccel procedure is fairly simple. It does a top down scan for
  200. the first character which matches the appropriate shift combinations.
  201.  
  202. A common mistake is to have two entries.
  203.  
  204. Entry one is ALT "a".
  205. Entry two is SHIFT ALT "a".
  206.  
  207. If you place the items in the table in this order:
  208.  
  209.     "a", MY_UNSHIFTED_FUNCTION,         ALT
  210.     "a", MY_SHIFTED_FUNCTION,   SHIFT | ALT
  211.  
  212. Then you will never, never, see the translator event for the SHIFT ALT "a".
  213. This is because the system sees the ALT "a" combination and finds a match on
  214. this combination. The shift key will become irrelevant in this case.
  215.  
  216. The proper order is
  217.  
  218.     "a", MY_SHIFTED_FUNCTION,   SHIFT | ALT
  219.     "a", MY_UNSHIFTED_FUNCTION,         ALT
  220.  
  221. Which will cause the event without the shift to fail and find the ALT "a" in
  222. the second position. The shift key needs to be pressed to get the first
  223. translation.
  224.  
  225. Rule of thumb: Place the most complicated combinations of key events FIRST.
  226. The first item in the table should be THE most complicated. Work down from
  227. there to the unshifted, un-control, un-alt, key codes. Those should be at the
  228. end of the table.
  229.  
  230. The translated item may have one of three valid modes (it is stored in two
  231. bits). By default the translated item will be a WM_COMMAND. If you wish a
  232. WM_SYSCOMMAND then that selection is available also. However, the
  233. WM_SYSCOMMAND usually means that the entry was found in the system table and
  234. not in your table. But, if you like F4 to be WM_SYSCOMMAND with SC_CLOSE then
  235. put in the entry
  236.  
  237.   VK_F4, SC_CLOSE, SYSCOMMAND | VIRTUALKEY
  238.  
  239. and you too can close your application with a simple F4 keypress.
  240.  
  241.  
  242. The only modifier which should be avoided is the HELP modifier. This will
  243. generate a WM_HELP message and that is another whole story of cascaded
  244. messages.
  245.  
  246.  
  247.  
  248.                                   Questions
  249.  
  250.  
  251. Assume that you have windows such as:
  252.  
  253. Standard window S1
  254.   Client of S1 - C1
  255.     Standard Window S2 (exactly covers C1 -- a MDI configuration)
  256.       Client of S2 - C2
  257.         A custom window D1 that covers C2
  258.           A dozen windows that are children of D1, including
  259.             X1 - a custom window we have created
  260.             E1 - a WC_ENTRYFIELD window
  261.             B1, B2 - WC_PUSHBUTTON windows
  262.             Z1 - a custom window, child of X1
  263.  
  264.  
  265. >> Who sends the WM_TRANSLATEACCEL messages--PM or the default window
  266. >> procedure? 
  267.  
  268. PM sends the message (or rather you do from your thread calling WinGetMsg)
  269.  
  270.  
  271. >> Is that done before or after WM_CHAR is sent to the window
  272. >> with the focus?
  273.  
  274. The message is sent BEFORE the WM_CHAR message. You will only get a WM_CHAR
  275. message if there are no accelerators for your entry.
  276.  
  277.  
  278. >> Is the message passed up the owner chain?  If so, by
  279. >> whom?
  280.  
  281. I believe that it is passed up the PARENT chain. I may be wrong, but it is an
  282. educated guess. WinDefWindowProc does the passing. (Again another educated
  283. guess.)
  284.  
  285.  
  286. >> Or is it sent specifically to each window in the owner chain? 
  287.  
  288. No, it is not "broadcast". The message should eventually find the frame
  289. window which will do the standard translations unless you intercept the
  290. message.
  291.  
  292.  
  293. >> When (if) the accelerator table on S2 is found, who is the message sent
  294. >> to?  S2, the window with the focus, or ?
  295.  
  296. WM_CHAR, WM_COMMAND, and WM_SYSCOMMANDS are always sent to the window with
  297. the current focus. WM_HELP messages filter though the help hook and start
  298. another cascade of messages to eventually display the IPF pannel or be
  299. ignored.
  300.  
  301. The result of the translation is one of the four type of messages. It must be
  302. a WM_COMMAND, WM_SYSCOMMAND, WM_HELP if it is found in the table. If it is
  303. not found in the table, the message is WM_CHAR. (It probably started as
  304. WM_CHAR and was simply left as such)
  305.  
  306.  
  307. >> The specific problem I have is that an accelerator table entry
  308. >> (Ctrl+F4) attached to S2 is apparently not being processed.
  309.  
  310. The problem with MDI is that the frame window for the MDI is not really the
  311. frame with the accelerator tables. The accelerator tables are loaded against
  312. the frame window of the client window created by WinCreateStdWindow().
  313.  
  314. Therefore, when the first frame window "up" from the active window is not
  315. really the owner of the accelerator tables, it will not find the entry in
  316. your accelerator tables.
  317.  
  318. If you have access to a PM toolkit for version 1 of OS/2 (from Microsoft)
  319. then you will find a sample program called MDI. This is a Multiple Document
  320. Interface. The work done in the MDIDOC.C procedure (near line 510) describes
  321. the method which must be performed. You must subclass the frame window to the
  322. MDI client.
  323.  
  324.  
  325. THE FOLLOWING CODE IS FROM THE SAMPLE MDIDOC.C. IT IS COPYRIGHTED BY
  326. MICROSOFT.
  327.  
  328.     ctlData = FCF_TITLEBAR   | FCF_MINMAX | FCF_SIZEBORDER |
  329.               FCF_VERTSCROLL | FCF_HORZSCROLL;
  330.  
  331.     hwndS2 = WinCreateStdWindow(hwndMDI,
  332.             FS_ICON | FS_ACCELTABLE,
  333.             (VOID FAR *)&ctlData,
  334.             pszClassName, szDocTitle,
  335.             WS_VISIBLE,
  336.             (HMODULE)0, IDR_MDIDOC,
  337.             (HWND FAR *)&hwndC2);
  338.  
  339.     pfnFrameWndProc = WinSubclassWindow(hwndS2,
  340.             (PFNWP)DocFrameWndProc);
  341.  
  342. If you forget to create the MDI frame windows with FS_ACCELTABLE then you
  343. will not have accelerators in your application. Even if your outer frame
  344. window does have the accelerator table defined.
  345.  
  346.  
  347. Then in the DocFrameWndProc, the following is performed:
  348.  
  349.  
  350.  
  351. MRESULT EXPENTRY DocFrameWndProc(HWND hwnd, USHORT msg, MPARAM mp1,
  352.                                  MPARAM mp2)
  353.     {
  354.     MRESULT mres;
  355.     USHORT cFrameCtls;
  356.     HWND hwndParent, hwndClient;
  357.     register NPDOC npdoc;
  358.     RECTL rclClient;
  359.  
  360.     switch (msg) {
  361.  
  362.     case WM_SYSCOMMAND:
  363.         if (SHORT1FROMMP(mp2) == CMDSRC_ACCELERATOR)
  364.             {
  365.  
  366.             /*
  367.              * If the command was sent because of an accelerator
  368.              * we need to see if it goes to the document or the main
  369.              * frame window.
  370.              */
  371.             if ((WinGetKeyState(HWND_DESKTOP, VK_CTRL) & 0x8000))
  372.                 {
  373.  
  374.                 /*
  375.                  * If the control key is down we'll send it
  376.                  * to the document's frame since that means
  377.                  * it's either ctl-esc or one of the document
  378.                  * window's accelerators.
  379.                  */
  380.                 return (*pfnFrameWndProc)(hwnd, msg, mp1, mp2);
  381.                 }
  382.             else
  383.                 if (SHORT1FROMMP(mp1) == SC_DOCSYSMENU)
  384.                     {
  385.  
  386.                 /*
  387.                  * If the window is maximized then we want
  388.                  * to pull down the system menu on the main
  389.                  * menu bar.
  390.                  */
  391.                     if ((WinQueryWindowULong(hwnd, QWL_STYLE) & WS_MAXIMIZED)
  392.                         &&
  393.                         (SHORT1FROMMP(mp1) == SC_DOCSYSMENU))
  394.                         {
  395.                         WinPostMsg(miAabSysMenu.hwndSubMenu, MM_STARTMENUMODE,
  396.                                   MPFROM2SHORT(TRUE, FALSE), 0L);
  397.                         return ((MRESULT) 0);
  398.                         }
  399.                     else
  400.                         {
  401.                         WinPostMsg(WinWindowFromID(hwnd, FID_SYSMENU),
  402.                            MM_STARTMENUMODE, MPFROM2SHORT(TRUE, FALSE), 0L);
  403.                         }
  404.                     }
  405.                 else
  406.                     {
  407.                 /*
  408.                  * Control isn't down so send it the main
  409.                  * frame window.
  410.                  */
  411.                     return WinSendMsg(hwndMDIFrame, msg, mp1, mp2);
  412.                     }
  413.                 }
  414.             else
  415.                 {
  416.             /*
  417.              * WM_SYSCOMMAND not caused by an accelerator
  418.              * so hwnd is the window we want to send the
  419.              * message to.
  420.              */
  421.                 return (*pfnFrameWndProc)(hwnd, msg, mp1, mp2);
  422.                 }
  423.         break;
  424.  
  425.         ....
  426.  
  427.  
  428.                                 Documentation
  429.  
  430. >> I'm asking this question generally because I'm not sure where the problem
  431. >> is, or whether it is in the code or in my understanding of some detail. If this
  432. >> is documented especially clearly anywhere, please tell me where!
  433.  
  434. A good source of this information is (or was before the divorce) Microsoft
  435. Online. That was/is an additional cost service if Microsoft. However, now all
  436. that is present in the databases is information relating to Windows. The OS/2
  437. information seems to have been archived.
  438.  
  439. You might find information in the Microsoft Knowledge Base (GO MSKB) on
  440. Compuserve. There is no guarantee that they are there. They were on MSONLINE
  441. where I accessed them.
  442.  
  443. The articles which I found most useful are identified as
  444.  
  445. Q46125 Accelerator Translation Flow of Control
  446. Q39339 Using Accelerator Keys within a dialog box within a DLL
  447. Q58076 Processing Accelerator keystokes when a Dialog Box is Up
  448.  
  449. and something called "ACCEL".
  450.  
  451. S12433 PM Accelerators Sample Program
  452.  
  453. That may be in the Microsoft Library (GO MSL). If you don't find it there
  454. then you might try Microsoft Developer Relations to see if they can supply
  455. you with a copy. [No guarantees.]
  456.  
  457.  
  458. Additionally, you can find information documented under the version 1 quick
  459. help database, strangely enough at the end of the "Window Procedures
  460. Overview". It talks about the WM_TRANSLATEACCEL message.
  461.  
  462. Note that much of the documentation contridicts what SPY tells you is
  463. *reallly* going on the system. So, take it with a grain of salt.
  464.  
  465. If you can find out where it is fully documented then TELL ME. I have a
  466. curiosity as to the correctness of the guesswork (educated as it is).
  467.  
  468.  
  469.  
  470.             New Accelerator tables for dialog procedures in a DLL
  471.  
  472. >> I need to load an accelerator table for a dialog box. I have a DLL
  473. >> that creates a dialog box when it is called. In the dialog box are several
  474. >> buttons. I would like to take the accelerator table table defined in the
  475. >> DLL's resource file and have it send messages to the dialog procedure.
  476. >> How?
  477.  
  478. (This answer comes from originated from a message from Microsoft. It is
  479. basically obvious, given the statements earlier, so I have restated it
  480. below.)
  481.  
  482. This method will work for buttons only. I have no other method for any other
  483. control. If you find a way then let us all know.
  484.  
  485. Use the WinLoadAccelTable() procedure to read the accelerator table from your
  486. DLL's resource file.
  487.  
  488. To obtain the previous accelerator table, send the parent window a message
  489. WM_GETACCELTABLE. There are no parameters to this message. The result is the
  490. accelerator table of the frame. There is no procedure to directly read this
  491. information, but the message will suffice.
  492.  
  493. Then use WinSetAccelTable to set the new accelerator table into place.
  494.  
  495. At the end of the dialog procedure you should restore the previous
  496. accelerator table using the WinSetAccelTable() procedure.
  497.  
  498. WinDestroyAccelTable() will destory the accelerator table when you are no
  499. longer using it. (After you have restore the previous accelerator table.)
  500.  
  501.  
  502.  
  503.  
  504.                                Dialog Mnemonics
  505.  
  506. Dialogs call WinDefDlgProc(). The processing for mnemonics is done by the
  507. control windows itself. They respond to the WM_MATCHMNEMONIC message to match
  508. the input character to the mnemonic text. This is a kind of accelerator but
  509. not really one. They are mnemonics. Do not get them confused.
  510.  
  511. The processing of a mnemonic is strange indead. It varies depending upon the
  512. type of control being used. A list box may select the item in the list if it
  513. matches the first character of an item text. If there is no entry then the
  514. list box does something stupid. It passes it along. The result may be pushing
  515. a button to delete the item in the list!
  516.  
  517.  
  518.  
  519.                          The ugly word:  Windows 3.x
  520.  
  521. Windows does not combine the accelerator processing with the WinGetMsg
  522. procedure. It is a seperate procedure to translate the accelerators. If you
  523. don't wish to translate the functions in the manner described then it is up
  524. to you to do your own translation and not call the standard translate message
  525. procedure.
  526.  
  527. The messaage loop for windows is something like:
  528.  
  529.    while (GetMessage (&msg, NULL, 0, 0))
  530.       {
  531.       TranslateMessage (&msg);          // Accelerators et al translated here
  532.       DispatchMessage  (&msg);          // The standard dispatch routine
  533.       }
  534.  
  535. The accelerators are translated by the TranslateMessage() procedure. If you
  536. don't wish accelerators translated then don't call TranslateMessage(). If you
  537. want something else done to the message then do it yourself.
  538.  
  539. PM merged the GetMessage and the TranslateMessage routines.
  540.  
  541.  
  542. A. Longyear
  543. Compuserve: 70165,725
  544.