home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Developer Kits / Arrange Developer Kit / Examples / Meeting Maker / MeetingMaker.cp < prev    next >
Encoding:
Text File  |  1994-07-15  |  33.2 KB  |  1,107 lines  |  [TEXT/MPS ]

  1. #include "ArrangeCallbacks.h"
  2. #include "PluginLibrary.h"
  3.  
  4. #include <Dialogs.h>
  5. #include <Files.h>
  6. #include <Memory.h>
  7. #include <String.h>
  8. #include <TextUtils.h>
  9. #include <OSUtils.h>
  10.  
  11.  
  12. /* This file contains the code for an Import/Export filter plugin that is 
  13.  * specific to a tab-delimited export format generated by MeetingMaker. 
  14.  * This plugin understands this custom format, will create note and field 
  15.  * definitions in the target document to store the imported info into, will 
  16.  * perform simple data transformations on the incoming data (and their inverses
  17.  * on the outgoing data) to make the data fit the Arrange model more closely, and 
  18.  * will filter for redundant information in the target document, and suppress
  19.  * duplicated notes. This is documented in more detail below.
  20.  * 
  21.  * This file also contains a very simple read stream class that will cache disk
  22.  * access in 4K chunks.
  23.  * 
  24.  * Based on GenericPlugin.cp as of 7/15/94.
  25.  */
  26.  
  27. #define ModuleRsrcID 0xFFFF8400
  28.  
  29. #define baseCmdCode 0x12000200 // replace this with a value obtained from
  30.                                          // Common Knowledge.
  31.  
  32. #define aboutCmdCode    (baseCmdCode + 0)
  33.  
  34.  
  35. // STR# resource IDs and indicies
  36. #define rFieldNameStrings ModuleRsrcID
  37.     #define rNoteTypeName 10
  38.     
  39. #define rMMImportExportNames ModuleRsrcID + 1
  40.     #define rImportName 1
  41.     #define rExportName 2
  42.  
  43.  
  44. class SimpleStream;
  45.  
  46. /* This is the list of fields in the standard format that we are importing. 
  47.  * The values of the enum correspond to indicies in an STR# resource 
  48.  * that contain the actual names of the Arrange fields that correspond 
  49.  * to each MeetingMaker field. The ID of this STR# is rFieldNameStrings.
  50.  */
  51. enum MeetingMakerField
  52.     {
  53.     mmTitle = 0,
  54.     mmLocation,
  55.     mmDate,
  56.     mmStartTime,
  57.     mmDuration,
  58.     mmPrivate,
  59.     mmFlexible,
  60.     mmLabel,
  61.     mmNotes
  62.     };
  63.  
  64.  
  65. class Plugin
  66.     {
  67. public:
  68.     Plugin(const ArrangeCallbackTbl* theCalls);
  69.     ~Plugin();
  70.     
  71.     arHookResult ClickNotify( arClickLocation loc, Point where, Short modifiers,
  72.                                       Short clickCount, arNoteID note, arFieldID field,
  73.                                       arPathID path );
  74.     arHookResult KeyNotify  ( Short theChar, Short key, Short modifiers );
  75.     arHookResult MenuNotify ( Integer commandCode, Integer commandParam,
  76.                                       Short modifiers );
  77.     arHookResult FieldNotify( arNoteID note, arFieldID field, arFieldAction action,
  78.                                       const char* choiceText );
  79.     void         TopicNotify( arTopicID newTopic, arWindowID newWindow,
  80.                                       arTopicAction action );
  81.     void             TickNotify (  );
  82.     arHookResult FileNotify ( arFileAction action );
  83.     arHookResult QuitNotify (  );
  84.     void         ATMNotify  (  );
  85.     
  86.     Integer ImportNotify( Short fRefnum, Integer formatID,
  87.                                  Boolean sniff, arTopicID destTopic );
  88.                                  
  89.     Integer ExportNotify( Short fRefnum,  arTopicID srcTopic );
  90.     
  91. private:
  92.     const ArrangeCallbackTbl* calls; // callback table
  93.     
  94.     arNoteID GenerateMMNote(SimpleStream *stream, arTypeID type, char *readBuffer);
  95.     arTypeID AllocateFieldsAndTypes();
  96.     arFieldID GetArrangeFieldForMMField(MeetingMakerField field);
  97.     
  98.     Boolean MMNoteExists(arNoteID newNote);
  99.     void WriteMMNote(arNoteID note,Short fRefNum);
  100.     }; // Plugin
  101.  
  102.  
  103. /*************************************************************************/
  104. /**************************** Main entry point ***************************/
  105. /*************************************************************************/
  106.  
  107. /* Root entry point for the module - must be the first function in the
  108.  * file, so that the linker will place it first in the code segment.
  109.  * This is the routine which Arrange calls at application startup time
  110.  * (and again at quit time).
  111.  * 
  112.  * Most plug-ins will not need to modify this routine or the Hook functions
  113.  * immediately following.  All customization can be done in the "Plugin"
  114.  * section (below).
  115.  */
  116. extern "C" Integer OurModuleRoot( ModuleParamBlock *pb, ModuleRootAction action,
  117.                                             Integer /*p1*/ )
  118.     {
  119.     /* Extract the callback table from our parameter block, and coerce it
  120.      * to the extended Arrange table.
  121.      */
  122.     ArrangeCallbackTbl* calls = (ArrangeCallbackTbl*)(pb->calls);
  123.     
  124.     switch (action)
  125.         {
  126.         case mrLoad:
  127.             {
  128.             // Allocate memory and create a new Plugin object.
  129.             void* storage = calls->mem->AllocMem( sizeof(Plugin),
  130.                                                               amFreeStore | amErrIfNoMem );
  131.             pb->moduleRefcon = uInteger(new(storage) Plugin(calls));
  132.             return 1;
  133.             }
  134.         
  135.         case mrUnload:
  136.             {
  137.             /* Dispose of the Plugin object and release the memory
  138.              * it occupies.
  139.              */
  140.             delete (Plugin*)(pb->moduleRefcon);
  141.             calls->mem->DeallocMem((void*)(pb->moduleRefcon), amFreeStore);
  142.             return 0;
  143.             }
  144.         
  145.         case mrFindEntry:
  146.         default:
  147.             {
  148.             /* This module contains no extended entry points, so we always
  149.              * return 0 for the mrFindEntry message.  Most plug-ins will not
  150.              * use extended entry points.
  151.              */
  152.             return 0;
  153.             }
  154.         
  155.         } // switch (action)
  156.     
  157.     } // OurModuleRoot
  158.  
  159.  
  160. /* These hook functions simply pass control to the appropriate function
  161.  * in the Plugin object.
  162.  */
  163. static arHookResult OurClickHook( ModuleParamBlock* pb, arClickLocation loc,
  164.                                              Point where, pShort modifiers, pShort clickCount,
  165.                                              arNoteID note, arFieldID field,
  166.                                              arPathID path ENDP )
  167.     {
  168.     return ((Plugin*)(pb->moduleRefcon))->ClickNotify( loc, where, modifiers,
  169.                                                                         clickCount, note, field,
  170.                                                                         path );
  171.     }
  172.  
  173.  
  174. static arHookResult OurKeyHook( ModuleParamBlock* pb, pShort theChar, pShort key,
  175.                                           pShort modifiers ENDP )
  176.     {
  177.     return ((Plugin*)(pb->moduleRefcon))->KeyNotify(theChar, key, modifiers);
  178.     }
  179.  
  180.  
  181. static arHookResult OurMenuHook( ModuleParamBlock* pb, Integer commandCode,
  182.                                             Integer commandParam, pShort modifiers ENDP )
  183.     {
  184.     return ((Plugin*)(pb->moduleRefcon))->MenuNotify( commandCode, commandParam,
  185.                                                                      modifiers );
  186.     }
  187.  
  188.  
  189. static arHookResult OurFieldHook( ModuleParamBlock* pb, arNoteID note,
  190.                                              arFieldID field, arFieldAction action,
  191.                                              const char* choiceText ENDP )
  192.     {
  193.     return ((Plugin*)(pb->moduleRefcon))->FieldNotify( note, field, action,
  194.                                                                         choiceText );
  195.     }
  196.  
  197.  
  198. static void OurTopicHook( ModuleParamBlock* pb, arTopicID newTopic,
  199.                                   arWindowID newWindow, arTopicAction action ENDP )
  200.     {
  201.     ((Plugin*)(pb->moduleRefcon))->TopicNotify(newTopic, newWindow, action);
  202.     }
  203.  
  204.  
  205. static void OurTickHook(ModuleParamBlock* pb ENDP)
  206.     {
  207.     ((Plugin*)(pb->moduleRefcon))->TickNotify();
  208.     }
  209.  
  210.  
  211. static arHookResult OurFileHook(ModuleParamBlock* pb, arFileAction action ENDP)
  212.     {
  213.     return ((Plugin*)(pb->moduleRefcon))->FileNotify(action);
  214.     }
  215.  
  216.  
  217. static arHookResult OurQuitHook(ModuleParamBlock* pb ENDP)
  218.     {
  219.     return ((Plugin*)(pb->moduleRefcon))->QuitNotify();
  220.     }
  221.  
  222.  
  223. static void OurATMHook(ModuleParamBlock* pb ENDP)
  224.     {
  225.     ((Plugin*)(pb->moduleRefcon))->ATMNotify();
  226.     }
  227.  
  228.  
  229. /* These are the actual hooks that we post. They just call back to the 
  230.  * class object. 
  231.  */
  232. static Integer OurImportHook( ModuleParamBlock* pb, const FSSpec* /*file*/,
  233.                                        pShort fRefnum, Integer formatID,
  234.                                        Boolean sniff, arTopicID destTopic ENDP )
  235.     {
  236.     return ((Plugin*)(pb->moduleRefcon))->ImportNotify( fRefnum, formatID,
  237.                                                                          sniff, destTopic );
  238.     } // OurImportHook
  239.  
  240.  
  241. static Integer OurExportHook( ModuleParamBlock* pb, const FSSpec* /*file*/,
  242.                                         pShort fRefnum, Integer /*formatID*/,
  243.                                         arEFType /*type*/, arTopicID srcTopic ENDP )
  244.     {
  245.     return ((Plugin*)(pb->moduleRefcon))->ExportNotify(fRefnum, srcTopic);
  246.     } // OurExportHook
  247.  
  248.  
  249. /*************************************************************************/
  250. /****************************** SimpleStream *****************************/
  251. /*************************************************************************/
  252.  
  253. /* This is a simple read stream class for reading from a file. All it 
  254.  * really does is provide a 4K read buffer so that multiple small reads are
  255.  * more efficient. You must pass it the refnum of a file that is already open.
  256.  * It will not close the file. 
  257.  */
  258. class SimpleStream
  259.     {
  260. public:
  261.     SimpleStream(Short fRefnum, char *buffer);
  262.     
  263.     char ReadByte();
  264.     void ReadBlock(char *buffer, Integer length);
  265.     
  266.     Integer BytesRemaining();
  267.     void Reset();
  268.     
  269. protected:
  270.     short fRefnum;     //The HFS Ref num of the file we are reading from.
  271.     char *readBuffer;    //This class reads bytes from the file 4K at a time. 
  272.                             //This is the physical buffer.
  273.     char *bufEnd;        //Pointer to the first byte after the end of the buffer
  274.     char *nextByte;    //Pointer to the next byte in the buffer to be read.
  275.     
  276.     Integer bufferReadOffset; //Index in the file of the next byte to be buffered.
  277.     Integer fileSize;            //Size of the file itself.
  278.     
  279.     void AdvanceBuffer();
  280.     }; // SimpleStream
  281.  
  282.  
  283. /* Create the SimpleStream object.Read the length of the file. Read the first 4K.
  284.  * This assumes that the file is already open for read. This class will
  285.  * not open or close the file. It does not own the buffer passed in.
  286.  */
  287. SimpleStream::SimpleStream(Short newFRefnum,char *newBuffer)
  288.     {
  289.     fRefnum = newFRefnum;
  290.     readBuffer = newBuffer;
  291.     bufEnd = readBuffer + 4096;
  292.     nextByte = &readBuffer[0];
  293.     bufferReadOffset = 0;
  294.     
  295.     long temp;
  296.     GetEOF(fRefnum,&temp);
  297.     fileSize = (Integer)temp;
  298.     
  299.     //Reset the file to 0.
  300.     SetFPos(fRefnum,fsFromStart,0);
  301.     AdvanceBuffer();
  302.     } // SimpleStream constructor
  303.  
  304.  
  305. /* Reset the file and the cache to the beginning. */
  306. void SimpleStream::Reset()
  307.     {
  308.     //Reset the file
  309.     SetFPos(fRefnum,fsFromStart,0);
  310.     
  311.     //Reset the cache
  312.     nextByte = &readBuffer[0];
  313.     bufferReadOffset = 0;
  314.     AdvanceBuffer();
  315.     } // Reset
  316.  
  317.  
  318. /* Advance the buffer by 4K or to the end of the file, if there are no 
  319.  * error conditions. Reset the next byte pointer.
  320.  */
  321. void SimpleStream::AdvanceBuffer()
  322.     {
  323.     if (fileSize < 1)
  324.         return;
  325.         
  326.     long bytesToRead = Min(4096, fileSize - bufferReadOffset);
  327.     
  328.     if (bytesToRead == 0)
  329.         {
  330.         fileSize = -1;
  331.         return;
  332.         }
  333.         
  334.     if (FSRead(fRefnum,&bytesToRead,readBuffer) != noErr)
  335.         fileSize = -1;
  336.     
  337.  
  338.     nextByte = readBuffer;
  339.     bufferReadOffset += (Integer)bytesToRead;
  340.     bufEnd = readBuffer + bytesToRead;
  341.     } // AdvanceBuffer
  342.  
  343.  
  344. /* Return the number of bytes remaining in the file, after the current mark. */
  345. Integer SimpleStream::BytesRemaining()
  346.     {
  347.     if (fileSize < 1)
  348.         return 0;
  349.         
  350.     //return the  amount left to read, plus what's left in the buffer.
  351.     return (fileSize - bufferReadOffset) + (bufEnd - nextByte);
  352.     } // BytesRemaining
  353.  
  354.  
  355. /* Read the next byte from the file. Advance the pointer if needed. This may
  356.  * leave the buffer empty and not advance. 
  357.  */
  358. char SimpleStream::ReadByte()
  359.     {
  360.     if (nextByte >= bufEnd)
  361.         AdvanceBuffer();
  362.     
  363.     return *(nextByte++);
  364.     } // ReadByte
  365.  
  366.  
  367. /* Read a block of the given length from the file. The buffer must be at least as 
  368.  * long as the passed length. The length can be any size.
  369.  */
  370. void SimpleStream::ReadBlock(char *buffer, Integer length)
  371.     {
  372.     Integer bytesToRead = length;
  373.     
  374.     while (bytesToRead > 0)
  375.         {
  376.         Integer bytesInCurBuffer = bufEnd - nextByte;
  377.         Integer bytesOnThisRead = Min(bytesToRead,bytesInCurBuffer);
  378.         if (bytesOnThisRead > 0)
  379.             {
  380.             BlockMove(nextByte,buffer,bytesOnThisRead);
  381.             nextByte += bytesOnThisRead;
  382.             buffer += bytesOnThisRead;
  383.             bytesToRead -= bytesOnThisRead;
  384.             }
  385.         
  386.         if (nextByte >= bufEnd)
  387.             AdvanceBuffer();
  388.         
  389.         } // while (bytesToRead > 0)
  390.     
  391.     } // ReadBlock
  392.  
  393.  
  394. /*************************************************************************/
  395. /********************************* Plugin ********************************/
  396. /*************************************************************************/
  397.  
  398. /* Construct a Plugin object.  This is called once, from the OurModuleRoot
  399.  * function, when the module is initialized (i.e. at application startup time).
  400.  */
  401. Plugin::Plugin(const ArrangeCallbackTbl* theCalls)
  402.     {
  403.     // Record the callback table for future use.
  404.     calls = theCalls;
  405.     
  406.     /* These commands, if un-commented-out, register this plugin to be called
  407.      * by Arrange when various events occur.  For example, if you un-comment-out
  408.      * the call to SetClickHook, then Plugin::ClickNotify will be called
  409.      * whenever the user clicks in any of the locations described in the
  410.      * arClickLocation enum.
  411.      */
  412.     // calls->ui->SetClickHook(OurClickHook, 0, true);
  413.     // calls->ui->SetKeyHook  (OurKeyHook,   0, true, charFilter, keyFilter, modFilter);
  414.     // calls->ui->SetMenuHook (OurMenuHook,  0, true, whichCommand);
  415.     // calls->ui->SetFieldHook(OurFieldHook, 0, true, whichField);
  416.     // calls->ui->SetTopicHook(OurTopicHook, 0, true);
  417.     // calls->ui->SetTickHook (OurTickHook,  0, true);
  418.     // calls->ui->SetFileHook (OurFileHook,  0, true);
  419.     // calls->ui->SetQuitHook (OurQuitHook,  0, true);
  420.     // calls->ui->SetATMHook  (OurATMHook,   0, true);
  421.     
  422.     // Add an item to the About Plugins menu for this plugin.
  423.     calls->ui->AddMenuItem(mPluginAbout, "About Meeting Maker", 0, aboutCmdCode, 0);
  424.     
  425.     // Register ourselves to be called when our About command is chosen.
  426.     calls->ui->SetMenuHook(OurMenuHook, 0, true, aboutCmdCode);
  427.     
  428.     // Register hooks for our import and export formats.
  429.     OSType textType = 'TEXT';
  430.     
  431.     char buffer[256];
  432.     getindstring(&buffer[0],rMMImportExportNames,rImportName); 
  433.     calls->ui->RegisterImportFormat( buffer, 'MMkr', &textType, 1,
  434.                                                 OurImportHook, 0 );
  435.                                                 
  436.     getindstring(&buffer[0],rMMImportExportNames,rExportName); 
  437.     calls->ui->RegisterExportFormat( buffer, 'MMkr', efExport,
  438.                                                 OurExportHook, 0, 'TEXT', 'MMss' );
  439.     } // Plugin constructor
  440.  
  441.  
  442. /* Dispose of a Plugin object.  This is called when Arrange terminates.  You
  443.  * should free up any data structures which you allocation in the Plugin
  444.  * constructor (above).
  445.  */
  446. Plugin::~Plugin()
  447.     {
  448.     } // ~Plugin
  449.  
  450.  
  451. /* This function is called whenever the user clicks in an "interesting place"
  452.  * in a document window.  It should return true if we handle the click, false
  453.  * (the normal case) when the click should be passed along to other plug-ins
  454.  * or to Arrange's normal event processing.  See the documentation for
  455.  * SetClickHook for more details.
  456.  */
  457. arHookResult Plugin::ClickNotify( arClickLocation /*loc*/, Point /*where*/,
  458.                                              Short /*modifiers*/, Short /*clickCount*/,
  459.                                              arNoteID /*note*/, arFieldID /*field*/,
  460.                                              arPathID /*path*/ )
  461.     {
  462.     return false; // Let Arrange handle the event
  463.     } // ClickNotify
  464.  
  465.  
  466. /* This function is called whenever the user types a key which matches the
  467.  * filters we pass to SetKeyHook in Plugin's constructor.  It should return
  468.  * true if we handle the event, false (the normal case) when the event
  469.  * should be passed along to other plug-ins or to Arrange's normal event
  470.  * processing.  See the documentation for SetKeyHook for more details.
  471.  */
  472. arHookResult Plugin::KeyNotify( Short /*theChar*/, Short /*key*/,
  473.                                           Short /*modifiers*/ )
  474.     {
  475.     return false; // Let Arrange handle the event
  476.     } // KeyNotify
  477.  
  478.  
  479. /* This function is called whenever the user chooses a menu item for which
  480.  * we have registered (via the SetMenuHook function).  It should return true
  481.  * if we handle the command, false (the normal case) when the command should
  482.  * be passed along to other plug-ins or to Arrange's normal event processing.
  483.  * See the documentation for SetMenuHook for more details.
  484.  */
  485. arHookResult Plugin::MenuNotify( Integer commandCode, Integer /*commandParam*/,
  486.                                             Short /*modifiers*/ )
  487.     {
  488.     /* If this is our About menu item, display our about-box dialog and return
  489.      * true to indicate we handled the command.
  490.      */
  491.     if (commandCode == aboutCmdCode)
  492.         {
  493.         Alert(ModuleRsrcID, nil);
  494.         return true;
  495.         }
  496.     else
  497.         return false; // Let Arrange handle the event
  498.     
  499.     } // MenuNotify
  500.  
  501.  
  502. /* If we register to recieve events for a field by calling SetFieldHook, this
  503.  * function will be called for any events in that field.  It should return
  504.  * false in almost all cases.  See the documentation for SetFieldHook for
  505.  * more details.
  506.  */
  507. arHookResult Plugin::FieldNotify( arNoteID /*note*/, arFieldID /*field*/,
  508.                                              arFieldAction /*action*/,
  509.                                              const char* /*choiceText*/ )
  510.     {
  511.     return false;
  512.     } // FieldNotify
  513.  
  514.  
  515. /* This function is called whenever the user switches windows or changes
  516.  * the current folder/topic/view in the front window.  See the documentation
  517.  * for SetTopicHook for more details.
  518.  */
  519. void Plugin::TopicNotify( arTopicID /*newTopic*/, arWindowID /*newWindow*/,
  520.                                   arTopicAction /*action*/ )
  521.     {
  522.     } // TopicNotify
  523.  
  524.  
  525. /* This function is called periodically, whenever Arrange recieves a null
  526.  * event from the Event Manager.
  527.  */
  528. void Plugin::TickNotify()
  529.     {
  530.     } // TickNotify
  531.  
  532.  
  533. /* This function is called whenever various file-level events occur.  It
  534.  * should return true in almost all cases.  See the documentation for
  535.  * SetFileHook for more details.
  536.  */
  537. arHookResult Plugin::FileNotify(arFileAction /*action*/)
  538.     {
  539.     return true;
  540.     } // FileNotify
  541.  
  542.  
  543. /* This function is called if the user voluntarily quits Arrange.  It should
  544.  * return true to allow the user to quit, false to prevent it.  See the
  545.  * documentation for SetQuitHook for more details.
  546.  */
  547. arHookResult Plugin::QuitNotify()
  548.     {
  549.     return true;
  550.     } // QuitNotify
  551.  
  552.  
  553. /* This function is called whenever the user clicks in the menu bar or types
  554.  * a command key, just before processing the event.  It should do any fixing
  555.  * up of menus which might be necessary based on the current state of affairs.
  556.  * See the documentation for SetATMHook for more details.
  557.  */
  558. void Plugin::ATMNotify()
  559.     {
  560.     } // ATMNotify
  561.  
  562.  
  563. /*************************************************************************/
  564. /********************************* Import ********************************/
  565. /*************************************************************************/
  566.  
  567. /* Read to the next tab or CR. Load the data, minus 
  568.  * delimiter, into the buffer, and return the length read (minus the delimiter). 
  569.  * Signal true if the delimiter was the tab char. EOF counts as CR. The buffer must
  570.  * be at least 4K long. Note that this will read the delimiter character, although 
  571.  * it will not return it in the buffer. So, repeated calls to this routine will 
  572.  * return all the data inbetween the delimiters in the stream, but none of the 
  573.  * delimiters: 
  574.  * 
  575.  * while (ReadToNextDelimiter(stream,buffer,wasTab) != 0)
  576.  *  ... no delimiters in the buffer ...
  577.  *
  578.  * Note that this routine will always read until either the end of the file or the 
  579.  * next delimiter, but it will store no more than bufferLength in the buffer.
  580.  */
  581. Integer ReadToNextDelimiter( SimpleStream *stream, OUT char *buffer, 
  582.                                       Integer bufferLength, OUT Boolean &wasTab )
  583.     {
  584.     char *startBuffer = buffer;
  585.     while (stream->BytesRemaining() > 0)
  586.         {
  587.         char nextChar = stream->ReadByte();
  588.         if (nextChar == '\t')
  589.             {
  590.             wasTab = true;
  591.             return buffer - startBuffer;
  592.             }
  593.         else if (nextChar == '\n')
  594.             {
  595.             wasTab = false;
  596.             return buffer - startBuffer;
  597.             }
  598.         
  599.         if (buffer - startBuffer < bufferLength)
  600.             {
  601.             buffer[0] = nextChar;
  602.             buffer++;
  603.             }
  604.         }
  605.     
  606.     wasTab = false;
  607.     return buffer - startBuffer;
  608.     } // ReadToNextDelimiter
  609.  
  610.  
  611. /* Lookup the name of the Arrange field that corresponds to the field 
  612.  * in the MM file.
  613.  */
  614. static void GetArrangeNameForMMField(MeetingMakerField theField, OUT char *name)
  615.     {
  616.     name[0] = 0;
  617.     getindstring(&name[0],rFieldNameStrings,theField + 1); 
  618.     } // GetArrangeNameForMMField
  619.     
  620.  
  621. /* Return the arrange field for the given meeting maker field, or nil if it 
  622.  * doesn't exist. You should call AllocateFieldsAndTypes below before 
  623.  * calling this to make the field exist. This actually looks the arFieldID up
  624.  * in the current document.
  625.  */
  626. arFieldID Plugin::GetArrangeFieldForMMField(MeetingMakerField field)
  627.     {
  628.     char buffer[256];
  629.     
  630.     GetArrangeNameForMMField(field,buffer);
  631.     if (buffer[0] != '<')
  632.         {
  633.         //Lookup the field
  634.         return calls->sysObj->LookupObjectName(arField,buffer,false);
  635.         }
  636.         
  637.     return 0;
  638.     } // GetArrangeFieldForMMField
  639.  
  640.  
  641. // Create the note type and fields for the MM import, if they don't already exist.
  642. arTypeID Plugin::AllocateFieldsAndTypes()
  643.     {
  644.     char buffer[256];
  645.     
  646.     //See if the type exists
  647.     getindstring(&buffer[0],rFieldNameStrings,rNoteTypeName); 
  648.     arTypeID type = calls->sysObj->LookupObjectName(arNoteType,buffer,false);
  649.     
  650.     if (type == 0)
  651.         {
  652.         type = calls->sysObj->CreateNoteType(buffer,true);
  653.         
  654.         //Remove the note text field
  655.         calls->notes->RemoveField(type, calls->sysObj->GetBuiltInObject(boNoteTextField));
  656.         }
  657.         
  658.     for (Integer index = mmTitle; index < mmNotes + 1; index++)
  659.         {
  660.         //Create each of these if needed, and add it to the type if not present.
  661.         GetArrangeNameForMMField((MeetingMakerField)index,buffer);
  662.         if (buffer[0] != '<')
  663.             {
  664.             //Lookup the field
  665.             arFieldID field = calls->sysObj->LookupObjectName(arField,buffer,false);
  666.             if (field == 0)
  667.                 {
  668.                 arFieldType theFieldType = arFTText;
  669.                 if (index == mmDate || index == mmStartTime || index == mmDuration)
  670.                     theFieldType = arFTDateTime;
  671.  
  672.                 field = calls->sysObj->CreateField(buffer,theFieldType,true);
  673.                 }
  674.             
  675.             if (!calls->notes->NoteHasField(type,field,true))
  676.                 calls->notes->AddField(type,field,0);
  677.             }
  678.         }
  679.         
  680.     return type;
  681.     } // AllocateFieldsAndTypes
  682.  
  683.  
  684. /* This assumes that the file is already open for read. This will not close the
  685.  * file.  Import the file using the Meeting Maker format. 
  686.  */
  687. Integer Plugin::ImportNotify( Short fRefnum, Integer formatID,
  688.                                         Boolean sniff, arTopicID destTopic )
  689.     {
  690.     //Is this the right format?
  691.     if (formatID != 'MMkr')
  692.         return -1;
  693.         
  694.     arTopicInfo info; 
  695.     calls->sysObj->GetTopicInfo(destTopic,&info);
  696.     
  697.     if (info.type == arFolder)
  698.         return -1; //Can't import into a folder
  699.     else if (info.type == arView)
  700.         destTopic = info.parent; //When in a view, import into the parent topic.
  701.         
  702.     //Allocate our buffers and the read stream.
  703.     char *readBuffer = (char*)calls->mem->AllocMem(4096,amPtr);
  704.     if (readBuffer == nil)
  705.         return -1;
  706.         
  707.     SimpleStream stream(fRefnum,readBuffer);
  708.     char *buffer = (char*)calls->mem->AllocMem(4096,amPtr);
  709.     if (buffer == nil)
  710.         {
  711.         calls->mem->DeallocMem(readBuffer,amPtr);
  712.         return -1;
  713.         }
  714.         
  715.     Boolean wasTab;
  716.     char *tempBuffer = buffer;
  717.     
  718.     //Read past the first row, this is just the labels
  719.     while (stream.BytesRemaining() > 0)
  720.         {
  721.         tempBuffer += ReadToNextDelimiter(&stream,tempBuffer,4096,wasTab);
  722.         tempBuffer[0] = 0;
  723.         if (!wasTab)
  724.             break;
  725.         }
  726.             
  727.     //If we are sniffing the file, check to see if the first line is what 
  728.     //we expect it to be.
  729.     if (sniff)
  730.         {
  731.         //The first line should look like this if this is the right format.
  732.         char *firstLine = "TitleLocationDateStart TimeDurationPrivateFlexibleLabelAgenda/Notes";
  733.             
  734.         Boolean isGood = strcmp(firstLine,buffer) == 0;
  735.         calls->mem->DeallocMem(readBuffer,amPtr);
  736.         calls->mem->DeallocMem(buffer,amPtr);
  737.         
  738.         if (isGood)
  739.             return 10000;
  740.         else
  741.             return -1;
  742.         }
  743.  
  744.     //Make sure the Arrange fields and types are allocated before the actual import.
  745.     arTypeID type = AllocateFieldsAndTypes();
  746.     arFieldID topicListField = calls->sysObj->GetBuiltInObject(boTopicContentsField);
  747.     
  748.     //Generate notes until we run out of file.
  749.     while (stream.BytesRemaining() > 0)
  750.         {
  751.         //Create the note
  752.         arNoteID note = GenerateMMNote(&stream,type,buffer);
  753.         
  754.         if (note != 0)
  755.             {
  756.             //See if there is already a meeting with the same title, location and
  757.             //time. If so, destroy the new note.
  758.             if (MMNoteExists(note))
  759.                 calls->notes->DestroyNote(note);
  760.             else
  761.                 calls->data->AddListFieldEntry(destTopic,topicListField,0,note,nullSFF);
  762.             }
  763.         }
  764.         
  765.     //Deallocate the buffers.
  766.     calls->mem->DeallocMem(readBuffer,amPtr);
  767.     calls->mem->DeallocMem(buffer,amPtr);
  768.     return 0;
  769.     } // ImportNotify
  770.  
  771.  
  772. /* Utility used during import. Reading from the current position of the stream, 
  773.  * create a current note (assume that the file is currently at the beginning of 
  774.  * a line. Return 0 if there is an error, otherwise return the note.
  775.  * This assumes that AllocateFieldsAndTypes has been called. 
  776.  */
  777. arNoteID Plugin::GenerateMMNote(SimpleStream *stream,arTypeID type, char *buffer)
  778.     {
  779.     //Make the note.
  780.     arNoteID resultNote = calls->notes->CreateNote(type,true);
  781.     Boolean wasTab; 
  782.     
  783.     //Loop through the fields on the line, importing data. There are some 
  784.     //special cases: the 'Start Date' and 'Time' field get packed together into
  785.     //one date/time field, and the duration field gets converted into an end time 
  786.     //based on the start date/time.
  787.     for (Integer index = mmTitle; index <= mmNotes; index++)
  788.         {
  789.         //Read the next column.
  790.         Integer fieldSize = ReadToNextDelimiter(stream,buffer,4096,wasTab);
  791.         buffer[fieldSize] = 0;
  792.         
  793.         //Find the field to put the data in.
  794.         arFieldID field = GetArrangeFieldForMMField((MeetingMakerField)index);
  795.         
  796.         //If the field for this note is not present, then we just skip the col. 
  797.         //This happens when the name <skip> is present in the field name resource list.
  798.         if (field == 0)
  799.             continue;
  800.             
  801.         //Make sure the note has the target field
  802.         if (!calls->notes->NoteHasField(resultNote,field,true))
  803.             calls->notes->AddField(resultNote,field,0);
  804.  
  805.         //Add the data to the field. 
  806.         //Special cases for the date, start, and duration fields.
  807.         if ((MeetingMakerField)index == mmDate && wasTab)
  808.             {
  809.             //The 'Date' field is actually stored in two columns in the list, the 
  810.             //'date' col and the 'start' col. We concatenate, with an '@' in the middle,
  811.             //so Arrange can parse the date, and then store, advancing past the 
  812.             //next col.
  813.             buffer[fieldSize++] = '@';
  814.             Integer timeSize = ReadToNextDelimiter(stream,&buffer[fieldSize],4096,wasTab);
  815.             buffer[fieldSize + timeSize] = 0;
  816.             
  817.             //Make sure to advance the index to the next column, since we have read two 
  818.             //columns at once.
  819.             index++;
  820.             
  821.             //Store the date as text, and tell Arrange to parse it.
  822.             calls->data->SetFieldText(resultNote,field,buffer,
  823.                                               fieldSize + timeSize,sfDoParse);
  824.             }
  825.         else    if ((MeetingMakerField)index == mmDuration)
  826.             {
  827.             //The duration field gets turned into an end date relative to the 
  828.             //already stored start date. We dig it out, parse into seconds, and then
  829.             //store the offsetted value.
  830.             
  831.             arFieldID startDateField = GetArrangeFieldForMMField(mmDate);
  832.             arDate startDate = calls->data->GetFieldDate(resultNote,startDateField);
  833.                         
  834.             //Find the ':' in the middle
  835.             char *colonLoc = buffer;
  836.             while (colonLoc[0] != ':' && colonLoc[0] != 0)
  837.                 colonLoc++;
  838.             
  839.             //Change it to a 0, and parse the two numbers on either side.
  840.             colonLoc[0] = 0;
  841.             long hours,minutes;
  842.             
  843.             stringtonum(buffer,&hours);
  844.             stringtonum(colonLoc+1,&minutes);
  845.             Integer duration = (Integer)(hours*60*60 + minutes*60); //In seconds
  846.             
  847.             //Set the field, and let Arrange generate the text.
  848.             calls->data->SetFieldDate(resultNote,field,startDate + duration,sfDoFormat);
  849.             }
  850.         else
  851.             //All other fields are just stored as text.
  852.             calls->data->SetFieldText(resultNote,field,buffer,fieldSize,nullSFF);
  853.         
  854.         //If the current field was the end of the line, stop reading and return what 
  855.         //we have.
  856.         if (!wasTab)
  857.             return resultNote;
  858.         }
  859.     
  860.     //Return the note
  861.     return resultNote;
  862.     } // GenerateMMNote
  863.  
  864.  
  865. /* Look through the document for another note that has the same Title, Location and 
  866.  * Start Time. Return true if you find one, false if not.
  867.  */
  868. Boolean Plugin::MMNoteExists(arNoteID newNote)
  869.     {
  870.     if (newNote == 0)
  871.         return false;
  872.         
  873.     arFilterClause clauseList[3];
  874.     
  875.     clauseList[0].type = fEquals;
  876.     clauseList[0].field = GetArrangeFieldForMMField(mmTitle);
  877.     calls->data->GetFieldText(newNote,clauseList[0].field,128,clauseList[0].text);
  878.     
  879.     clauseList[1].type = fEquals;
  880.     clauseList[1].field = GetArrangeFieldForMMField(mmLocation);
  881.     calls->data->GetFieldText(newNote,clauseList[1].field,128,clauseList[1].text);
  882.     
  883.     clauseList[2].type = fEquals;
  884.     clauseList[2].field = GetArrangeFieldForMMField(mmDate);
  885.     clauseList[2].date = calls->data->GetFieldDate(newNote,clauseList[2].field);
  886.     
  887.     arTypeID type = calls->notes->GetNoteType(newNote);
  888.     OWN arListID matches = calls->search->FilterNotes(type,(arFilterFlags)0,3,clauseList);
  889.     
  890.     //There will be at least 1 entry in the list, the newNote. If there is more
  891.     //than 1, then there is a match, and we return true.
  892.     Boolean result = calls->list->GetListLen(matches) > 1;
  893.     calls->list->DisposeList(matches);
  894.     return result;
  895.     } // MMNoteExists
  896.  
  897.  
  898. /*************************************************************************/
  899. /********************************* Export ********************************/
  900. /*************************************************************************/
  901.     
  902. /* Write the col list header out into the given file. Assume the file is 
  903.  * set to pos 0.
  904.  */
  905. static void WriteMMHeader(Short fRefNum)
  906.     {
  907.     char *buffer = "Title\tLocation\tDate\tStart Time\tDuration\tPrivate\tFlexible\tLabel\tAgenda/Notes\n";
  908.     long bufCount = strlen(buffer);
  909.     FSWrite(fRefNum,&bufCount,buffer);
  910.     } // WriteMMHeader
  911.  
  912.  
  913. /* Export the current topic to the given file. Assumes that the file is already
  914.  * open, and will leave the file open and unflushed. Note that this doesn't care if 
  915.  * the notes in the topic are meeting notes. It will export the data as best it can.
  916.  */
  917. Integer Plugin::ExportNotify(Short fRefnum, arTopicID srcTopic )
  918.     {
  919.     if (SetFPos(fRefnum,fsFromStart,0) != noErr)
  920.         return 1;
  921.     
  922.     WriteMMHeader(fRefnum);
  923.     
  924.     //Iterate through the notes
  925.     arFieldID topicContentsField = calls->sysObj->GetBuiltInObject(boTopicContentsField);
  926.     Integer topicCount = calls->data->GetFieldListLen(srcTopic,topicContentsField);
  927.     for (Integer index = 0; index < topicCount; index++)
  928.         {
  929.         //For each note, call the write routine
  930.         arNoteID curNote = calls->data->GetFieldListEntry(srcTopic,topicContentsField,index);
  931.         WriteMMNote(curNote,fRefnum);
  932.         }
  933.     
  934.     return 0;
  935.     } // ExportNotify
  936.  
  937.  
  938. /* Generate a string based on the given date. Generate either the date 
  939.  * portion of the string or the time portion. 
  940.  */
  941. static void GetDateAsString(arDate date, Boolean doTime, OUT char *buffer)
  942.     {
  943.     buffer[0] = 0;
  944.     Handle theIUHandle;
  945.     LongDateTime secs;
  946.     
  947.     ((LongDateCvt*)&secs)->hl.lLow = date;
  948.     ((LongDateCvt*)&secs)->hl.lHigh = 0;
  949.     if (!doTime) 
  950.         {
  951.         theIUHandle = Handle(IUGetIntl(0)); 
  952.         iuldatestring(    &secs,
  953.                             shortDate, 
  954.                             buffer,
  955.                             theIUHandle);
  956.         }
  957.     else
  958.         {
  959.         theIUHandle = Handle(IUGetIntl(0));
  960.         iultimestring(&secs,false,buffer,theIUHandle); 
  961.         }
  962.     
  963.     } // GetDateAsString
  964.  
  965.  
  966. /* Write the given note out as a MM tabline.  If any of the fields are 'skip'
  967.  * fields, or are not present in the note, just output an empty column.  The
  968.  * startDate field will be split into two columns (date, time), and the end
  969.  * date gets converted to a duration.
  970.  */
  971. void Plugin::WriteMMNote(arNoteID note,Short fRefNum)
  972.     {
  973.     //go through the fields. If the cur field is not a skip field, and the note
  974.     //has the field, write out the text. If the cur field is the date field, 
  975.     //replace the ampersand with a tab, and output, skipping the next col. If the 
  976.     //cur field is the duration field, calc the duration, and output HH:MM. Output
  977.     //tab for blank or missing fields.
  978.     char buffer[4096];
  979.     long bufCount; 
  980.     for (Integer index = mmTitle; index <=     mmNotes; index++)
  981.         {
  982.         arFieldID curField = GetArrangeFieldForMMField((MeetingMakerField)index);
  983.         if (curField != 0 && calls->notes->NoteHasField(note,curField,false) )
  984.             {
  985.             if ((MeetingMakerField)index == mmDate)
  986.                 {
  987.                 #ifdef OLDCODE
  988.                 
  989.                 bufCount = Min(4096,calls->data->GetFieldTextLen(note,curField));
  990.                 calls->data->GetFieldText(note,curField,4096,buffer);
  991.                 
  992.                 //Find the '@', replace it with tab (so this field fills two cols, date and start),
  993.                 //and advance the index 1 (to skip the 'start' part which we just wrote out.
  994.                 char *tempBuffer = buffer;
  995.                 while (tempBuffer[0] != 0)
  996.                     {
  997.                     if (tempBuffer[0] == '@')
  998.                         {
  999.                         tempBuffer[0] = '\t';
  1000.                         index++;
  1001.                         break;
  1002.                         }
  1003.                         
  1004.                     tempBuffer++;
  1005.                     }
  1006.                 
  1007.                 //If we didn't find the '@', tack a tab on the end anyway, to advance.
  1008.                 if (tempBuffer[0] == 0)
  1009.                     {
  1010.                     tempBuffer[0] = '\t';
  1011.                     index++;
  1012.                     bufCount++;
  1013.                     }
  1014.                 #endif
  1015.                 
  1016.                 arDate date = calls->data->GetFieldDate(note,curField);
  1017.                 if (date == missingDateToken)
  1018.                     {
  1019.                     //Missing date, just output blanks
  1020.                     buffer[0] = ' ';
  1021.                     buffer[1] = '\t';
  1022.                     buffer[2] = ' ';
  1023.                     index++; 
  1024.                     bufCount = 3;
  1025.                     }
  1026.                 else
  1027.                     {
  1028.                     buffer[0] = 0;
  1029.                     GetDateAsString(date, false, buffer);
  1030.                     Integer dateLen = strlen(buffer);
  1031.                     GetDateAsString(date,true,&buffer[dateLen]);
  1032.                     buffer[dateLen] = '\t';
  1033.                     index++;
  1034.                     bufCount = strlen(buffer);
  1035.                     }
  1036.                 }
  1037.             else if ((MeetingMakerField)index == mmDuration)
  1038.                 {
  1039.                 //Calculate the duration from the start and end fields.
  1040.                 //Default the duration to 1 hour if either field is missing.
  1041.                 arFieldID startField = GetArrangeFieldForMMField(mmDate);
  1042.                 
  1043.                 long durationSeconds;
  1044.                 if (startField == 0)
  1045.                     durationSeconds = 3600; 
  1046.                 else
  1047.                     {
  1048.                     arDate startDate = calls->data->GetFieldDate(note,startField);
  1049.                     arDate endDate = calls->data->GetFieldDate(note,curField);
  1050.                     
  1051.                     if (startDate == missingDateToken || endDate == missingDateToken)
  1052.                         durationSeconds = 3600;
  1053.                     else
  1054.                         durationSeconds = endDate - startDate;
  1055.                     }
  1056.                     
  1057.                 long durationHours = durationSeconds/3600;
  1058.                 long durationMinutes = (durationSeconds - (durationHours*3600))/60;
  1059.                 
  1060.                 buffer[0] = 0;
  1061.                 if (durationHours == 0)
  1062.                     {
  1063.                     buffer[0] = '0';
  1064.                     buffer[1] = '0';
  1065.                     buffer[2] = 0;
  1066.                     }
  1067.                 else
  1068.                     numtostring(durationHours,buffer);
  1069.                 
  1070.                 Integer hoursLen = strlen(buffer);
  1071.                 buffer[hoursLen] = ':';
  1072.                 buffer[hoursLen + 1] = 0;
  1073.                 if (durationHours == 0)
  1074.                     {
  1075.                     buffer[hoursLen + 1] = '0';
  1076.                     buffer[hoursLen + 2] = '0';
  1077.                     buffer[hoursLen + 3] = 0;
  1078.                     }
  1079.                 else
  1080.                     numtostring(durationMinutes,&buffer[hoursLen + 1]);
  1081.                 
  1082.                 bufCount = strlen(buffer);
  1083.                 }
  1084.             else
  1085.                 {
  1086.                 bufCount = Min(4096,calls->data->GetFieldTextLen(note,curField));
  1087.                 calls->data->GetFieldText(note,curField,4096,buffer);
  1088.                 }
  1089.             
  1090.             if (bufCount > 0)
  1091.                 FSWrite(fRefNum,&bufCount,buffer);
  1092.             }
  1093.         
  1094.         //Write out the tab at the end of the column.
  1095.         bufCount = 1;
  1096.         buffer[0] = '\t';
  1097.         buffer[1] = 0;
  1098.         FSWrite(fRefNum,&bufCount,buffer);
  1099.         }
  1100.         
  1101.     //End the line
  1102.     bufCount = 1;
  1103.     buffer[0] = '\n';
  1104.     buffer[1] = 0;
  1105.     FSWrite(fRefNum,&bufCount,buffer);
  1106.     } // WriteMMNote
  1107.