home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Developer Kits / Arrange Developer Kit / Examples / Auto-completion / AutoCompletion.cp < prev    next >
Encoding:
Text File  |  1994-07-15  |  18.2 KB  |  617 lines  |  [TEXT/MPS ]

  1. #include "ArrangeCallbacks.h"
  2. #include "PluginLibrary.h"
  3.  
  4. #include <Dialogs.h>
  5. #include <Windows.h>
  6. #include <Resources.h>
  7. #include <Memory.h>
  8. #include <Events.h>
  9.  
  10. // AutoCompletion.cp - based on GenericPlugin.cp as of 7/15/94
  11.  
  12.  
  13. #define ModuleRsrcID 0xFFFF8000
  14.  
  15. #define baseCmdCode 0x12000100 // replace this with a value obtained from
  16.                                          // Common Knowledge.
  17.  
  18. #define aboutCmdCode    (baseCmdCode + 0)
  19.  
  20. #define autoCompletionAction 0x11000006
  21.  
  22.  
  23. class Plugin
  24.     {
  25. public:
  26.     Plugin(const ArrangeCallbackTbl* theCalls);
  27.     ~Plugin();
  28.     
  29.     arHookResult ClickNotify( arClickLocation loc, Point where, Short modifiers,
  30.                                       Short clickCount, arNoteID note, arFieldID field,
  31.                                       arPathID path );
  32.     arHookResult KeyNotify  ( Short theChar, Short key, Short modifiers );
  33.     arHookResult MenuNotify ( Integer commandCode, Integer commandParam,
  34.                                       Short modifiers );
  35.     arHookResult FieldNotify( arNoteID note, arFieldID field, arFieldAction action,
  36.                                       const char* choiceText );
  37.     void         TopicNotify( arTopicID newTopic, arWindowID newWindow,
  38.                                       arTopicAction action );
  39.     void             TickNotify (  );
  40.     arHookResult FileNotify ( arFileAction action );
  41.     arHookResult QuitNotify (  );
  42.     void         ATMNotify  (  );
  43.     
  44. private:
  45.     const ArrangeCallbackTbl* calls; // callback table
  46.     
  47.     arNoteID  selNote;  // Note containing the current selection; nil if there
  48.                               // is no active text selection, or if the selected
  49.                               // field is not a popup field.
  50.     
  51.     // These variables are only valid if selNote is non-nil.
  52.     arFieldID selField;              // Field in which the selection is made.
  53.     Integer lastKeyTime;              // TickCount when the user last typed a key.
  54.     Boolean lastKeyWasBackspace; // True if last key event was a backspace.
  55.     Boolean textIsDirty;              // True if the user has made any editing
  56.                                           // change since the last call to RunCompletion.
  57.     
  58.     void EnterField(arNoteID note, arFieldID field);
  59.     void ExitField();
  60.     void NoteUserAction(Boolean isBackspace, Boolean isDirty);
  61.     void Tick();
  62.     void RunCompletion();
  63.     
  64.     Boolean FindPopupMatch( const char* matchText, Integer matchTextLen,
  65.                                     const char* popupText, Integer popupTextLen,
  66.                                     OUT const char* &matchStart, OUT Integer &matchLen );
  67.     }; // Plugin
  68.  
  69.  
  70. /*************************************************************************/
  71. /**************************** Main entry point ***************************/
  72. /*************************************************************************/
  73.  
  74. /* Root entry point for the module - must be the first function in the
  75.  * file, so that the linker will place it first in the code segment.
  76.  * This is the routine which Arrange calls at application startup time
  77.  * (and again at quit time).
  78.  * 
  79.  * Most plug-ins will not need to modify this routine or the Hook functions
  80.  * immediately following.  All customization can be done in the "Plugin"
  81.  * section (below).
  82.  */
  83. extern "C" Integer OurModuleRoot( ModuleParamBlock *pb, ModuleRootAction action,
  84.                                              Integer /*p1*/ )
  85.     {
  86.     /* Extract the callback table from our parameter block, and coerce it
  87.      * to the extended Arrange table.
  88.      */
  89.     ArrangeCallbackTbl* calls = (ArrangeCallbackTbl*)(pb->calls);
  90.     
  91.     switch (action)
  92.         {
  93.         case mrLoad:
  94.             {
  95.             // Allocate memory and create a new Plugin object.
  96.             void* storage = calls->mem->AllocMem( sizeof(Plugin),
  97.                                                               amFreeStore | amErrIfNoMem );
  98.             pb->moduleRefcon = uInteger(new(storage) Plugin(calls));
  99.             return 1;
  100.             }
  101.         
  102.         case mrUnload:
  103.             {
  104.             /* Dispose of the Plugin object and release the memory
  105.              * it occupies.
  106.              */
  107.             delete (Plugin*)(pb->moduleRefcon);
  108.             calls->mem->DeallocMem((void*)(pb->moduleRefcon), amFreeStore);
  109.             return 0;
  110.             }
  111.         
  112.         case mrFindEntry:
  113.         default:
  114.             {
  115.             /* This module contains no extended entry points, so we always
  116.              * return 0 for the mrFindEntry message.  Most plug-ins will not
  117.              * use extended entry points.
  118.              */
  119.             return 0;
  120.             }
  121.         
  122.         } // switch (action)
  123.     
  124.     } // OurModuleRoot
  125.  
  126.  
  127. /* These hook functions simply pass control to the appropriate function
  128.  * in the Plugin object.
  129.  */
  130. static arHookResult OurClickHook( ModuleParamBlock* pb, arClickLocation loc,
  131.                                              Point where, pShort modifiers, pShort clickCount,
  132.                                              arNoteID note, arFieldID field,
  133.                                              arPathID path ENDP )
  134.     {
  135.     return ((Plugin*)(pb->moduleRefcon))->ClickNotify( loc, where, modifiers,
  136.                                                                         clickCount, note, field,
  137.                                                                         path );
  138.     }
  139.  
  140.  
  141. static arHookResult OurKeyHook( ModuleParamBlock* pb, pShort theChar, pShort key,
  142.                                           pShort modifiers ENDP )
  143.     {
  144.     return ((Plugin*)(pb->moduleRefcon))->KeyNotify(theChar, key, modifiers);
  145.     }
  146.  
  147.  
  148. static arHookResult OurMenuHook( ModuleParamBlock* pb, Integer commandCode,
  149.                                             Integer commandParam, pShort modifiers ENDP )
  150.     {
  151.     return ((Plugin*)(pb->moduleRefcon))->MenuNotify( commandCode, commandParam,
  152.                                                                      modifiers );
  153.     }
  154.  
  155.  
  156. static arHookResult OurFieldHook( ModuleParamBlock* pb, arNoteID note,
  157.                                              arFieldID field, arFieldAction action,
  158.                                              const char* choiceText ENDP )
  159.     {
  160.     return ((Plugin*)(pb->moduleRefcon))->FieldNotify( note, field, action,
  161.                                                                         choiceText );
  162.     }
  163.  
  164.  
  165. static void OurTopicHook( ModuleParamBlock* pb, arTopicID newTopic,
  166.                                   arWindowID newWindow, arTopicAction action ENDP )
  167.     {
  168.     ((Plugin*)(pb->moduleRefcon))->TopicNotify(newTopic, newWindow, action);
  169.     }
  170.  
  171.  
  172. static void OurTickHook(ModuleParamBlock* pb ENDP)
  173.     {
  174.     ((Plugin*)(pb->moduleRefcon))->TickNotify();
  175.     }
  176.  
  177.  
  178. static arHookResult OurFileHook(ModuleParamBlock* pb, arFileAction action ENDP)
  179.     {
  180.     return ((Plugin*)(pb->moduleRefcon))->FileNotify(action);
  181.     }
  182.  
  183.  
  184. static arHookResult OurQuitHook(ModuleParamBlock* pb ENDP)
  185.     {
  186.     return ((Plugin*)(pb->moduleRefcon))->QuitNotify();
  187.     }
  188.  
  189.  
  190. static void OurATMHook(ModuleParamBlock* pb ENDP)
  191.     {
  192.     ((Plugin*)(pb->moduleRefcon))->ATMNotify();
  193.     }
  194.  
  195.  
  196. /*************************************************************************/
  197. /********************************* Plugin ********************************/
  198. /*************************************************************************/
  199.  
  200. /* Number of ticks after a standard keydown event before we try to complete
  201.  * the text.
  202.  */
  203. #define completionDelay 15
  204.  
  205.  
  206. /* Number of ticks after a backspace event before we try to complete the
  207.  * text.
  208.  */
  209. #define backspaceDelay 60
  210.  
  211.  
  212. /* Construct a Plugin object.  This is called once, from the OurModuleRoot
  213.  * function, when the module is initialized (i.e. at application startup time).
  214.  */
  215. Plugin::Plugin(const ArrangeCallbackTbl* theCalls)
  216.     {
  217.     // Record the callback table for future use.
  218.     calls = theCalls;
  219.     
  220.     /* These commands, if un-commented-out, register this plugin to be called
  221.      * by Arrange when various events occur.  For example, if you un-comment-out
  222.      * the call to SetClickHook, then Plugin::ClickNotify will be called
  223.      * whenever the user clicks in any of the locations described in the
  224.      * arClickLocation enum.
  225.      */
  226.     // calls->ui->SetClickHook(OurClickHook, 0, true);
  227.     // calls->ui->SetKeyHook  (OurKeyHook,   0, true, charFilter, keyFilter, modFilter);
  228.     // calls->ui->SetMenuHook (OurMenuHook,  0, true, whichCommand);
  229.     // calls->ui->SetFieldHook(OurFieldHook, 0, true, whichField);
  230.     // calls->ui->SetTopicHook(OurTopicHook, 0, true);
  231.     calls->ui->SetTickHook (OurTickHook,  0, true);
  232.     // calls->ui->SetFileHook (OurFileHook,  0, true);
  233.     // calls->ui->SetQuitHook (OurQuitHook,  0, true);
  234.     // calls->ui->SetATMHook  (OurATMHook,   0, true);
  235.     
  236.     calls->ui->SetFieldHook(OurFieldHook, 0, true, 0);
  237.     
  238.     calls->ui->AddMenuItem(mPluginAbout, "About Auto-completion", 0, aboutCmdCode, 0);
  239.     calls->ui->SetMenuHook(OurMenuHook, 0, true, aboutCmdCode);
  240.     
  241.     calls->ui->RegisterFieldProc( "Auto-completion", autoCompletionAction, fpAction,
  242.                                             1 << arFTPopup, OurFieldHook, autoCompletionAction );
  243.     
  244.     selNote = nil;
  245.     } // Plugin constructor
  246.  
  247.  
  248. /* Dispose of a Plugin object.  This is called when Arrange terminates.  You
  249.  * should free up any data structures which you allocation in the Plugin
  250.  * constructor (above).
  251.  */
  252. Plugin::~Plugin()
  253.     {
  254.     } // ~Plugin
  255.  
  256.  
  257. /* This function is called whenever the user clicks in an "interesting place"
  258.  * in a document window.  It should return true if we handle the click, false
  259.  * (the normal case) when the click should be passed along to other plug-ins
  260.  * or to Arrange's normal event processing.  See the documentation for
  261.  * SetClickHook for more details.
  262.  */
  263. arHookResult Plugin::ClickNotify( arClickLocation /*loc*/, Point /*where*/,
  264.                                              Short /*modifiers*/, Short /*clickCount*/,
  265.                                              arNoteID /*note*/, arFieldID /*field*/,
  266.                                              arPathID /*path*/ )
  267.     {
  268.     return false; // Let Arrange handle the event
  269.     } // ClickNotify
  270.  
  271.  
  272. /* This function is called whenever the user types a key which matches the
  273.  * filters we pass to SetKeyHook in Plugin's constructor.  It should return
  274.  * true if we handle the event, false (the normal case) when the event
  275.  * should be passed along to other plug-ins or to Arrange's normal event
  276.  * processing.  See the documentation for SetKeyHook for more details.
  277.  */
  278. arHookResult Plugin::KeyNotify( Short /*theChar*/, Short /*key*/,
  279.                                           Short /*modifiers*/ )
  280.     {
  281.     return false; // Let Arrange handle the event
  282.     } // KeyNotify
  283.  
  284.  
  285. /* This function is called whenever the user chooses a menu item for which
  286.  * we have registered (via the SetMenuHook function).  It should return true
  287.  * if we handle the command, false (the normal case) when the command should
  288.  * be passed along to other plug-ins or to Arrange's normal event processing.
  289.  * See the documentation for SetMenuHook for more details.
  290.  */
  291. arHookResult Plugin::MenuNotify( Integer commandCode, Integer /*commandParam*/,
  292.                                             Short /*modifiers*/ )
  293.     {
  294.     /* If this is our About menu item, display our about-box dialog and return
  295.      * true to indicate we handled the command.
  296.      */
  297.     if (commandCode == aboutCmdCode)
  298.         {
  299.         Alert(ModuleRsrcID, nil);
  300.         return true;
  301.         }
  302.     else
  303.         return false; // Let Arrange handle the event
  304.     
  305.     } // MenuNotify
  306.  
  307.  
  308. /* If we register to recieve events for a field by calling SetFieldHook, this
  309.  * function will be called for any events in that field.  It should return
  310.  * false in almost all cases.  See the documentation for SetFieldHook for
  311.  * more details.
  312.  */
  313. arHookResult Plugin::FieldNotify( arNoteID note, arFieldID field,
  314.                                              arFieldAction action,
  315.                                              const char* /*choiceText*/ )
  316.     {
  317.     switch (action)
  318.         {
  319.         case faEntry:
  320.             EnterField(note,field);
  321.             break;
  322.         
  323.         case faExit:
  324.             ExitField();
  325.             break;
  326.         
  327.         case faUpdate:
  328.             RunCompletion();
  329.             break;
  330.         
  331.         case faKeystroke:
  332.         case faEditCmd:
  333.             NoteUserAction(false, true);
  334.             break;
  335.         
  336.         case faBackspace:
  337.             NoteUserAction(true, true);
  338.             break;
  339.         
  340.         case faSelChange:
  341.             NoteUserAction(false, false);
  342.             break;
  343.         
  344.         } // switch (action)
  345.     
  346.     return false;
  347.     } // FieldNotify
  348.  
  349.  
  350. /* This function is called whenever the user switches windows or changes
  351.  * the current folder/topic/view in the front window.  See the documentation
  352.  * for SetTopicHook for more details.
  353.  */
  354. void Plugin::TopicNotify( arTopicID /*newTopic*/, arWindowID /*newWindow*/,
  355.                                   arTopicAction /*action*/ )
  356.     {
  357.     } // TopicNotify
  358.  
  359.  
  360. /* This function is called periodically, whenever Arrange recieves a null
  361.  * event from the Event Manager.
  362.  */
  363. void Plugin::TickNotify()
  364.     {
  365.     /* Exit if there is no active selection in a popup field, or if the
  366.      * user hasn't typed anything for us to complete.
  367.      */
  368.     if (selNote == nil || !textIsDirty)
  369.         return;
  370.     
  371.     /* Determine whether enough time has elapsed since the last user
  372.      * activity.
  373.      */
  374.     Integer delay;
  375.     if (lastKeyWasBackspace)
  376.         delay = backspaceDelay;
  377.     else
  378.         delay = completionDelay;
  379.     
  380.     /* If enough time has elapsed, then attempt to complete what the user
  381.      * has typed.
  382.      */
  383.     if (TickCount() >= lastKeyTime + delay)
  384.         RunCompletion();
  385.     
  386.     } // TickNotify
  387.  
  388.  
  389. /* This function is called whenever various file-level events occur.  It
  390.  * should return true in almost all cases.  See the documentation for
  391.  * SetFileHook for more details.
  392.  */
  393. arHookResult Plugin::FileNotify(arFileAction /*action*/)
  394.     {
  395.     return true;
  396.     } // FileNotify
  397.  
  398.  
  399. /* This function is called if the user voluntarily quits Arrange.  It should
  400.  * return true to allow the user to quit, false to prevent it.  See the
  401.  * documentation for SetQuitHook for more details.
  402.  */
  403. arHookResult Plugin::QuitNotify()
  404.     {
  405.     return true;
  406.     } // QuitNotify
  407.  
  408.  
  409. /* This function is called whenever the user clicks in the menu bar or types
  410.  * a command key, just before processing the event.  It should do any fixing
  411.  * up of menus which might be necessary based on the current state of affairs.
  412.  * See the documentation for SetATMHook for more details.
  413.  */
  414. void Plugin::ATMNotify()
  415.     {
  416.     } // ATMNotify
  417.  
  418.  
  419. // This function is called when the user clicks in a field of some note.
  420. void Plugin::EnterField(arNoteID note, arFieldID field)
  421.     {
  422.     /* If the field is a popup field, and the Auto-completion action is selected
  423.      * for that field, set our selNote and selField variables accordingly;
  424.      * otherwise set selNote to nil.
  425.      */
  426.     arFieldInfo fieldInfo;
  427.     fieldInfo.versNum = 1;
  428.     calls->sysObj->GetFieldInfo(field, &fieldInfo);
  429.     
  430.     if ( fieldInfo.type == arFTPopup &&
  431.           calls->ui->TestFieldProc(field, fpAction, autoCompletionAction) )
  432.         {
  433.         selNote                 = note;
  434.         selField                = field;
  435.         lastKeyTime              = Integer(TickCount());
  436.         lastKeyWasBackspace = false;
  437.         textIsDirty              = false;
  438.         }
  439.     else
  440.         selNote = nil;
  441.     
  442.     } // EnterField
  443.  
  444.  
  445. // This function is called when the user clicks out of a field.
  446. void Plugin::ExitField()
  447.     {
  448.     selNote = nil;
  449.     } // ExitField
  450.  
  451.  
  452. /* This function is called when the user clicks, types, or takes other
  453.  * action in the active field.  isBackspace is true if the event is a press
  454.  * of the backspace key or equivalent, false otherwise (we wait longer after
  455.  * a backspace than after other keys to finish what the user is typing).
  456.  * isDirty is true if the action is one that changes the contents of the
  457.  * field (i.e. typing or cut/paste but not Copy, a mouse click, or an arrow
  458.  * key).
  459.  */
  460. void Plugin::NoteUserAction(Boolean isBackspace, Boolean isDirty)
  461.     {
  462.     lastKeyTime = Integer(TickCount());
  463.     lastKeyWasBackspace = isBackspace;
  464.     textIsDirty = textIsDirty || isDirty;
  465.     } // NoteUserAction
  466.  
  467.  
  468. /* Search through the list of entries in popupText for one which has a prefix
  469.  * equal (up to case differences) to matchText.  If we find such an entry,
  470.  * place its address in matchStart and its length in matchLen and return true.
  471.  * Otherwise, return false (in which case matchStart and matchLen are
  472.  * undefined).
  473.  */
  474. Boolean Plugin::FindPopupMatch( const char* matchText, Integer matchTextLen,
  475.                                           const char* popupText, Integer popupTextLen,
  476.                                           OUT const char* &matchStart,
  477.                                           OUT Integer &matchLen )
  478.     {
  479.     // Loop until the list of popup entries has been exhausted.
  480.     while (popupTextLen > 0)
  481.         {
  482.         // Find the length of the current entry.
  483.         Integer curEntryLen = 0;
  484.         while (curEntryLen < popupTextLen && popupText[curEntryLen] != '\n')
  485.             curEntryLen++;
  486.         
  487.         // Check whether this entry is a match.
  488.         if (curEntryLen >= matchTextLen)
  489.             {
  490.             char matchBuf[1], popupBuf[1];
  491.             
  492.             Boolean isAMatch = true;
  493.             for (Integer i=0; i<matchTextLen; i++)
  494.                 {
  495.                 matchBuf[0] = matchText[i];
  496.                 popupBuf[0] = popupText[i];
  497.                 calls->util->LowerString(matchBuf, 1);
  498.                 calls->util->LowerString(popupBuf, 1);
  499.                 if (matchBuf[0] != popupBuf[0])
  500.                     {
  501.                     isAMatch = false;
  502.                     break;
  503.                     }
  504.                 
  505.                 }
  506.             
  507.             if (isAMatch)
  508.                 {
  509.                 matchStart = popupText;
  510.                 matchLen   = curEntryLen;
  511.                 return true;
  512.                 }
  513.             
  514.             } // if (curEntryLen >= matchTextLen)
  515.         
  516.         // Advance to the next entry.
  517.         popupText    += curEntryLen + 1;
  518.         popupTextLen -= curEntryLen + 1;
  519.         } // while (popupTextLen > 0)
  520.     
  521.     return false;
  522.     } // FindPopupMatch
  523.  
  524.  
  525. /* Scan the text in the active field to see if we can finish what the user
  526.  * has typed by inserting an entry from the popup list.  This is called when
  527.  * someone tries to flush the contents of the field, or from our Tick method.
  528.  * 
  529.  * FUTURE NOTE: need to modify this routine to not just silently fail when
  530.  * text lengths exceed 2000 characters.  Either allocate a dynamic buffer,
  531.  * or beep, or something.
  532.  */
  533. void Plugin::RunCompletion()
  534.     {
  535.     // Exit if the user hasn't typed anything for us to complete.
  536.     if (selNote == nil || !textIsDirty)
  537.         return;
  538.     
  539.     textIsDirty = false;
  540.     
  541.     /* Get the current contents of the active field.  Exit if they are
  542.      * too large to fit in our buffer.
  543.      */
  544.     char textBuf[2000];
  545.     Integer selStart,selEnd;
  546.     Integer textLen = calls->sel->GetSelText( sizeof(textBuf), textBuf,
  547.                                                             &selStart, &selEnd );
  548.     if (textLen >= sizeof(textBuf))
  549.         return;
  550.     
  551.     /* Find the start of the "last entry" in the text.  This is all text after
  552.      * the last comma character, if any, and excluding any leading spaces.
  553.      */
  554.     char* scanPtr = textBuf;
  555.     char* stopPtr = textBuf + textLen;
  556.     char* entryStart = textBuf;
  557.     while (scanPtr < stopPtr)
  558.         {
  559.         if (*scanPtr == ',')
  560.             entryStart = scanPtr+1;
  561.         
  562.         scanPtr++;
  563.         }
  564.     
  565.     while (*entryStart == ' ' || *entryStart == '\t' || *entryStart == '\n')
  566.         entryStart++;
  567.     
  568.     // Exit if no text has been entered yet.
  569.     if (entryStart >= textBuf + textLen)
  570.         return;
  571.     
  572.     /* Get the list of entries in the popup menu for this field.  Remember
  573.      * that this list is stored in what is nominally the "default value"
  574.      * field of the field definition.  Exit if the text is too long to hold
  575.      * in our buffer.
  576.      */
  577.     char popupBuf[2000];
  578.     arFieldID defaultValueField = arFieldID( calls->sysObj->
  579.                                                 GetBuiltInObject(boDefaultValueField) );
  580.     Integer popupTextLen = calls->data->GetFieldText( selField, defaultValueField,
  581.                                                                       sizeof(popupBuf), popupBuf );
  582.     if (popupTextLen >= sizeof(popupBuf))
  583.         return;
  584.     
  585.     /* Search for a match between the user's text and an entry in the popup
  586.      * menu.
  587.      */
  588.     const char* matchStart;
  589.     Integer matchLen;
  590.     Integer entryPos = entryStart - textBuf;
  591.     if (!FindPopupMatch( entryStart, textLen - entryPos,
  592.                                 popupBuf, popupTextLen,
  593.                                 matchStart, matchLen ))
  594.         return;
  595.     
  596.     /* We found a match; exit if it is too large to append onto our text
  597.      * buffer.
  598.      */
  599.     if (entryPos + matchLen >= sizeof(textBuf))
  600.         return;
  601.     
  602.     /* Finish what the user has typed with the entry from the popup menu, and
  603.      * select the text we are inserting.
  604.      */
  605.     Integer copyLen = matchLen - (textLen - entryPos);
  606.     if (copyLen > 0)
  607.         {
  608.         selStart = textLen;
  609.         selEnd   = selStart + copyLen;
  610.         for (Integer i=0; i<copyLen; i++)
  611.             textBuf[textLen+i] = matchStart[textLen-entryPos+i];
  612.         
  613.         calls->sel->SetSelText(textBuf, textLen+copyLen, selStart, selEnd);
  614.         }
  615.     
  616.     } // RunCompletion
  617.