home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C++ / Applications / Nuntius 1.2 / src / Nuntius / UGroupTree.cp < prev    next >
Encoding:
Text File  |  1994-03-13  |  30.1 KB  |  1,151 lines  |  [TEXT/MPS ]

  1. // Copyright © 1992 Peter Speck, speck@dat.ruc.dk. All rights reserved.
  2. // UGroupTree.cp
  3.  
  4. #include "UGroupTree.h"
  5. #include "Tools.h"
  6. #include "UThread.h"
  7. #include "UProgress.h"
  8. #include "UDynDynArray.h"
  9. #include "UGrouplist.h"
  10. #include "StreamTools.h"
  11. #include "NetAsciiTools.h"
  12.  
  13. #include <RsrcGlobals.h>
  14. #include <ErrorGlobals.h>
  15.  
  16. #include <ToolUtils.h>
  17.  
  18. #pragma segment MyGroupList
  19.  
  20. const long kCurrentGroupTreeVersion = 2;
  21. const long kMinGroupTreeVersion = 2;
  22.  
  23. #define qDebugCreateBrief            qDebug & 0
  24. #define qDebugCreate                    qDebugCreateBrief & 0
  25. #define qDebugCreateVerbose        qDebugCreate & 0
  26. #define qDebugHelp                        qDebug & 0
  27. #define qDebugHelpVerbose     qDebugHelp & 0
  28. #define qDebugIterator qDebug & 0
  29. #define qDebugInfo qDebug & 0
  30.  
  31. char gDebugNodeName[300];
  32.  
  33. #define WNE { EventRecord event; WaitNextEvent(everyEvent, event, 1, nil); }
  34.  
  35. struct Node
  36. {
  37.     ArrayIndex fNextNodeIndex;
  38.     ArrayIndex fFirstSubNodeIndex;
  39.     ArrayIndex fParentIndex;
  40.     Boolean fIsFolder;
  41.     Boolean fFiller1;
  42.     short fSubNoRealGroups;
  43.     CRect fWindowFrame;
  44.     long fLastReadArticleID;
  45.     long fLastUpdatedArticleID;
  46. };
  47.  
  48.  
  49. struct NodeInfo
  50. {
  51.     CRect fFrame;
  52.     long fLastReadArticleID;
  53.     long fLastUpdatedArticleID;
  54. };
  55.  
  56. typedef NodeInfo *NodeInfoPtr;
  57.  
  58. #if 0
  59.   #if qDebugCreate | qDebugIterator
  60.  
  61.   #define stderr mystderr
  62.   #define mystderr mystderr
  63. FILE *mystderr;
  64. void OpenOut()
  65. {
  66.     char filename[] = "HD2:Desktop Folder:GroupTreeLog";
  67.     mystderr = fopen(filename, "w");
  68.     fsetfileinfo(filename, 'MPS ', 'TEXT'); 
  69. }
  70.   #endif
  71. #endif
  72.  
  73. //************************************************************************************************
  74. inline long RoundToLong(long x) { return (x + 3) & ~3; }
  75. //************************************************************************************************
  76. CFindSubGroupIterator::CFindSubGroupIterator(TGroupTree *groupTree, TLongintList *expandData, ArrayIndex windowFolderIndex, ArrayIndex line)
  77. {
  78.     fGroupTree = groupTree;
  79.     fExpandData = expandData;
  80.     fLine = line;
  81.     fOffset = 0;
  82.     fIndex = kEmptyIndex;
  83.     fWindowFolderIndex = windowFolderIndex;
  84. #if qDebugIterator
  85.     fprintf(stderr, "\n");
  86.     fprintf(stderr, "Construct of CFSGI: line = %ld, wfi = %ld\n", line, windowFolderIndex);
  87.     fPrevGroupLock = fGroupTree->Lock(true);
  88. #endif
  89. }
  90.  
  91. CFindSubGroupIterator::~CFindSubGroupIterator()
  92. {
  93. #if qDebugIterator
  94.     fprintf(stderr, "Destruct of CFSGI\n");
  95.     fGroupTree->Lock(fPrevGroupLock);
  96. #endif
  97. }
  98.  
  99. ArrayIndex CFindSubGroupIterator::FirstGroup()
  100. {
  101.     fOffset = fLine;
  102.     fIndex = fGroupTree->ComputeNodeAddress(fWindowFolderIndex)->fFirstSubNodeIndex;
  103. #if qDebugIterator
  104.     fprintf(stderr, "CFSGI::First (fOffset = %ld), returned index = %ld\n", fOffset, fIndex);
  105. #endif
  106.     return fIndex;
  107. }
  108.  
  109. Boolean CFindSubGroupIterator::More()
  110. {
  111.     if (fIndex == kEmptyIndex)
  112.     {
  113. #if qDebugIterator
  114.         fprintf(stderr, "CFSGI::More returns false as fIndex == kEmptyIndex\n");
  115. #endif
  116.         return false;
  117.     }
  118.     if (!fOffset)
  119.     {
  120. #if qDebugIterator
  121.         fprintf(stderr, "CFSGI::More returns false as fOffset == 0\n");
  122. #endif
  123.         return false;
  124.     }
  125. #if qDebugIterator
  126.     fprintf(stderr, "CFSGI::More is true  (fOffset = %ld)\n", fOffset);
  127. #endif
  128.     return true;
  129. }
  130.  
  131. ArrayIndex CFindSubGroupIterator::NextGroup()
  132. {
  133.     Advance();
  134. #if qDebugIterator
  135.     fprintf(stderr, "CFSGI::NextGroup returns %ld\n", fIndex);
  136. #endif
  137.     return fIndex;
  138. }
  139.  
  140. void CFindSubGroupIterator::Advance()
  141. {
  142. #if qDebugIterator
  143.     fprintf(stderr, "CFSGI::Advance at entry: fIndex = %ld, fOffset = %ld\n", fIndex, fOffset);
  144. #endif
  145.     NodePtr nodeP;
  146.     NodePtr nodeIndex1P = fGroupTree->ComputeNodeAddress(1);
  147.     while (true) // name loop
  148.     {
  149.         nodeP = nodeIndex1P + (fIndex - 1);
  150.         fOffset--; // count line
  151.         if (!fOffset)
  152.         {
  153. #if qDebugIterator
  154.             fGroupTree->FetchDebugNodeName(fIndex);
  155.             fprintf(stderr, "CFSGI::Advance, Found _the_ group %s as fOffset == 0 (isFolder = %hd)\n", gDebugNodeName, nodeP->fIsFolder);
  156. #endif
  157.             break; // found it!!!
  158.         }
  159.         if (!fOffset)
  160.             return;
  161.         ArrayIndex subLines;
  162.         subLines = fExpandData->At(fIndex);
  163.         if (fOffset <= subLines)
  164.         {
  165.             fIndex = nodeP->fFirstSubNodeIndex;
  166. #if qDebugIterator
  167.             fGroupTree->FetchDebugNodeName(fIndex);
  168.             fprintf(stderr, "CFSGI::Advance, Found super-group (%s) for the group, returned sub index = %ld\n", gDebugNodeName, fIndex);
  169. #endif
  170.             break;
  171.         }
  172.         fOffset -= subLines;
  173. #if qDebugIterator
  174.         if (nodeP->fNextNodeIndex == kEmptyIndex)
  175.         {
  176.             ProgramBreak("Ups, nodeP->fNextNodeIndex == kEmptyIndex");
  177.             return;
  178.         }
  179.         fGroupTree->FetchDebugNodeName(fIndex);
  180.         fprintf(stderr, "CFSGI::Advance (%ld), skipped past %s with index %ld\n", fOffset, gDebugNodeName, fIndex);
  181. #endif
  182.         fIndex = nodeP->fNextNodeIndex;
  183.     }
  184. #if qDebugIterator
  185.     fGroupTree->FetchDebugNodeName(fIndex);
  186.     fprintf(stderr, "CFSGI::Advance at return: fOffset = %ld, index = %ld, isFolder = %hd, name = %s\n", fOffset, fIndex, nodeP->fIsFolder, gDebugNodeName);
  187. #endif
  188. }
  189. //************************************************************************************************
  190. CRealGroupIterator::CRealGroupIterator(TGroupTree *groupTree, ArrayIndex startIndex)
  191. {
  192.     fGroupTree = groupTree;
  193.     fStartIndex = startIndex;
  194.     fIndex = kEmptyIndex;
  195. #if qDebugIterator
  196.     fprintf(stderr, "Construct of CRealGroupIterator: startIndex = %ld\n", startIndex);
  197. #endif
  198. }
  199.  
  200. CRealGroupIterator::~CRealGroupIterator()
  201. {
  202. #if qDebugIterator
  203.     fprintf(stderr, "Destruct of CRealGroupIterator\n");
  204. #endif
  205. }
  206.  
  207. ArrayIndex CRealGroupIterator::FirstGroup()
  208. {
  209. #if qDebug
  210.     fPrevGroupDebugLock = fGroupTree->Lock(true);
  211. #endif
  212.     fIndex = fStartIndex;
  213.     if (fGroupTree->ComputeNodeAddress(fIndex)->fIsFolder)
  214.         Advance();
  215. #if qDebugIterator
  216.     fprintf(stderr, "CRealGroupIterator::First, returned index = %ld\n", fIndex);
  217. #endif
  218.     return fIndex;
  219. }
  220.  
  221. Boolean CRealGroupIterator::More()
  222. {
  223.     if (fIndex == kEmptyIndex)
  224.     {
  225. #if qDebug
  226.         fGroupTree->Lock(fPrevGroupDebugLock);
  227. #endif
  228.         return false;
  229.     }
  230. #if qDebugIterator
  231.     fprintf(stderr, "CRealGroupIterator::More (fIndex = %ld) is true\n", fIndex);
  232. #endif
  233.     return true;
  234. }
  235.  
  236. ArrayIndex CRealGroupIterator::NextGroup()
  237. {
  238.     Advance();
  239.     return fIndex;
  240. }
  241.  
  242. void CRealGroupIterator::Advance()
  243. {
  244.     NodePtr nodeP;
  245.     NodePtr nodeIndex1P = fGroupTree->ComputeNodeAddress(1);
  246.     nodeP = nodeIndex1P + (fIndex - 1);
  247. #if qDebugIterator
  248.     fGroupTree->FetchDebugNodeName(fIndex);
  249.     fprintf(stderr, "\nCRealGroupIterator::Advance, Current item is: index = %ld, name = %s\n", fIndex, gDebugNodeName);
  250. #endif
  251.     if (nodeP->fFirstSubNodeIndex != kEmptyIndex)
  252.     {
  253.         fIndex = nodeP->fFirstSubNodeIndex;
  254.         nodeP = nodeIndex1P + (fIndex - 1);
  255. #if qDebugIterator
  256.         fGroupTree->FetchDebugNodeName(fIndex);
  257.         fprintf(stderr, "CRealGroupIterator::Advance, found sub-item: index = %ld, isFolder = %hd, name = %s\n", fIndex, nodeP->fIsFolder, gDebugNodeName);
  258.         if (fIndex == kEmptyIndex)
  259.             ProgramBreak("UPS");
  260. #endif
  261.         if (nodeP->fIsFolder)
  262.             Advance();
  263.         return;
  264.     }
  265.     while (true)
  266.     {
  267.         if (fIndex == fStartIndex && !nodeP->fIsFolder)
  268.         {
  269. #if qDebugIterator
  270.             fprintf(stderr, "CRealGroupIterator::Advance, current was real entry-group, returning kEmptyIndex\n");
  271. #endif
  272.             fIndex = kEmptyIndex;
  273.             return;
  274.         }
  275.         if (nodeP->fNextNodeIndex != kEmptyIndex)
  276.         {
  277.             // next group in list
  278.             fIndex = nodeP->fNextNodeIndex;
  279.             nodeP = nodeIndex1P + (fIndex - 1);
  280. #if qDebugIterator
  281.             fGroupTree->FetchDebugNodeName(fIndex);
  282.             fprintf(stderr, "CRealGroupIterator::Advance, checking next item: index = %ld, isFolder = %hd, name = %s\n", fIndex, nodeP->fIsFolder, gDebugNodeName);
  283.             if (fIndex == kEmptyIndex)
  284.                 ProgramBreak("UPS");
  285. #endif
  286.             if (nodeP->fIsFolder)
  287.                 Advance();
  288.             return;
  289.         }
  290.         fIndex = nodeP->fParentIndex;
  291.         nodeP = nodeIndex1P + (fIndex - 1);
  292.         if (fIndex == fStartIndex)
  293.         {
  294. #if qDebugIterator
  295.             fprintf(stderr, "CRealGroupIterator::Advance, parent was entry-group, returning kEmptyIndex\n");
  296. #endif
  297.             fIndex = kEmptyIndex;
  298.             return;
  299.         }
  300. #if qDebugIterator
  301.         fGroupTree->FetchDebugNodeName(fIndex);
  302.         fprintf(stderr, "CRealGroupIterator::Advance, escaping to parent: index = %ld, isFolder = %hd, name = %s\n", fIndex, nodeP->fIsFolder, gDebugNodeName);
  303.         if (fIndex == kEmptyIndex)
  304.             ProgramBreak("UPS");
  305. #endif
  306.     }    
  307. }
  308. //************************************************************************************************
  309. TGroupTree::TGroupTree()
  310. {
  311. }
  312.  
  313. pascal void TGroupTree::Initialize()
  314. {
  315. #ifdef mystderr
  316.     OpenOut();
  317. #endif
  318.     inherited::Initialize();
  319.     fGroupNameList = nil;
  320.     fHelpTextList = nil;
  321. }
  322.  
  323. void TGroupTree::IGroupTree()
  324. {
  325. #if qDebug
  326.     if (sizeof(Node) != 32)
  327.     {
  328.         fprintf(stderr, "sizeof(Node) is %ld, and not 32\n", long(sizeof(Node)));
  329.         ProgramBreak(gEmptyString);
  330.     }
  331. #endif
  332.     inherited::IDynamicArray(0, sizeof(Node));
  333.     FailInfo fi;
  334.     if (fi.Try())
  335.     {
  336.         fAllocationIncrement = 512;
  337.         fGroupNameList = NewDynDynArray(32 * 1024);
  338.         fHelpTextList = NewDynDynArray(32 * 1024);
  339.         fi.Success();
  340.     }
  341.     else // fail
  342.     {
  343.         Free();
  344.         fi.ReSignal();
  345.     }
  346. }
  347.  
  348. pascal void TGroupTree::Free()
  349. {
  350.     delete fGroupNameList; fGroupNameList = nil;
  351.     delete fHelpTextList; fHelpTextList = nil;
  352.     inherited::Free();
  353. }
  354.  
  355. Boolean TGroupTree::TreeIsEmpty()
  356. {
  357.     return fSize <= 1; // head-fake-node
  358. }
  359.  
  360. void TGroupTree::DoRead(TStream *aStream)
  361. {
  362.     long version = aStream->ReadLong();
  363.     if (!MyCheckVersion(version, kMinGroupTreeVersion, kCurrentGroupTreeVersion, "TGroupTree"))
  364.         return;
  365.     ReadDynamicArray(aStream, this);
  366.     fGroupNameList->DoRead(aStream);
  367.     fHelpTextList->DoRead(aStream);
  368. }
  369.  
  370. void TGroupTree::DoWrite(TStream *aStream)
  371. {
  372.     aStream->WriteLong(kCurrentGroupTreeVersion);
  373.     WriteDynamicArray(aStream, this);
  374.     fGroupNameList->DoWrite(aStream);
  375.     fHelpTextList->DoWrite(aStream);
  376. }
  377.  
  378. void TGroupTree::DoNeedDiskSpace(long &dataForkBytes)
  379. {
  380.     dataForkBytes += sizeof(long); // version
  381.     dataForkBytes += MyStreamSizeOfDynamicArray(this);
  382.     dataForkBytes += fGroupNameList->NeededDiskSpace();
  383.     dataForkBytes += fHelpTextList->NeededDiskSpace();
  384. }
  385.  
  386. void TGroupTree::WriteTreeInfo(TStream *aStream)
  387. {
  388.     Handle h = nil;
  389.     VOLATILE(h);
  390.     FailInfo fi;
  391.     if (fi.Try())
  392.     {
  393.         h = NewPermHandle(fSize * sizeof(NodeInfo));
  394.         HLock(h);
  395.         NodePtr nodeP = ComputeNodeAddress(1);
  396.         NodeInfoPtr nodeInfoP = NodeInfoPtr(*h);
  397.         for (ArrayIndex i = 1; i <= fSize; i++)
  398.         {
  399.             nodeInfoP->fFrame = nodeP->fWindowFrame;
  400.             nodeInfoP->fLastReadArticleID = nodeP->fLastReadArticleID;
  401.             nodeInfoP->fLastUpdatedArticleID = nodeP->fLastUpdatedArticleID;
  402.             nodeP++;
  403.             nodeInfoP++;
  404.         }
  405.         aStream->WriteHandle(h);
  406.         h = DisposeIfHandle(h);
  407.         fi.Success();
  408.     }
  409.     else // fail
  410.     {
  411.         h = DisposeIfHandle(h);
  412.         // ignore errors, just don't saved info
  413.     }
  414. }
  415.  
  416. void TGroupTree::ReadTreeInfo(TStream *aStream)
  417. {
  418.     Handle h = nil;
  419.     VOLATILE(h);
  420.     FailInfo fi;
  421.     if (fi.Try())
  422.     {
  423.         h = aStream->ReadHandle();
  424.         if (GetHandleSize(h) != fSize * sizeof(NodeInfo))
  425.         {
  426. #if qDebug
  427.             fprintf(stderr, "Handle had wrong size!!! Does not read Tree Info\n");
  428. #endif
  429.             Failure(0, 0); // it have been modified, just ignore saved info
  430.         }
  431.         HLock(h);
  432.         NodePtr nodeP = ComputeNodeAddress(1);
  433.         NodeInfoPtr nodeInfoP = NodeInfoPtr(*h);
  434.         for (ArrayIndex i = 1; i <= fSize; i++)
  435.         {
  436.             nodeP->fWindowFrame = nodeInfoP->fFrame;
  437.             nodeP->fLastReadArticleID = nodeInfoP->fLastReadArticleID;
  438.             nodeP->fLastUpdatedArticleID = nodeInfoP->fLastUpdatedArticleID;
  439.             nodeP++;
  440.             nodeInfoP++;
  441.         }
  442.         h = DisposeIfHandle(h);
  443.         fi.Success();
  444.     }
  445.     else // fail
  446.     {
  447.         h = DisposeIfHandle(h);
  448.         // ignore errors, just forget saved info
  449.     }
  450. }
  451.  
  452. //========= REAL THING HERE =====================================================
  453.  
  454. #if qDebug
  455. Node *TGroupTree::ComputeNodeAddress(ArrayIndex index)
  456. {
  457.     if (index < 1 || index > fSize)
  458.     {
  459.         fprintf(stderr, "TGroupTree, index = %ld out of range, fSize = %ld\n", index, fSize);
  460.         ProgramBreak(gEmptyString);
  461.         Failure(minErr, 0);
  462.     }
  463.     return NodePtr(inherited::ComputeAddress(index));
  464. }
  465. #endif
  466.  
  467. const CStr255 &TGroupTree::GetNodeName(ArrayIndex index)
  468. {
  469. #if qDebug & 0
  470.     if (!VerboseIsObject(fGroupNameList))
  471.         ProgramBreak("fGroupNameList is not object");
  472. #endif
  473.     return *(const CStr255 *) fGroupNameList->ComputeAddress(index);
  474. }
  475.  
  476. void TGroupTree::FetchDebugNodeName(ArrayIndex index)
  477. {
  478.     CStr255 name = GetNodeName(index);
  479.     BytesMove(&name[1], gDebugNodeName, name.Length());
  480.     gDebugNodeName[name.Length()] = 0;
  481. }
  482.  
  483. Boolean TGroupTree::GetNextSubName(const CStr255 &dotName, short &index, CStr255 &subName)
  484. {
  485.     if (index > dotName.Length())
  486.         return false;
  487.     subName = "";
  488.     index++;
  489.     while (index <= dotName.Length() && dotName[index] != '.')
  490.         subName += dotName[index++];
  491.     return true;
  492. }
  493.  
  494. void TGroupTree::AddGroupEntry(CStr255 &subName, ArrayIndex parentIndex, Boolean isFolder, 
  495.                                                             ArrayIndex &firstIndex, 
  496.                                                             ArrayIndex &index, Boolean &newSubNode, ArrayIndex &deltaGroups)
  497. {
  498.     newSubNode = false;
  499. #if qDebugCreate
  500.     Lock(true);
  501. #endif
  502.     index = firstIndex;
  503.     NodePtr checkNodeP = nil;
  504.     NodePtr prevNodeP = nil;
  505.     Boolean insertAsFirst = true;
  506.     Boolean insertBefore = false;
  507.     if (index != kEmptyIndex)
  508.     {
  509.         while (true)
  510.         {
  511.             checkNodeP = ComputeNodeAddress(index);
  512. #if qDebugCreateVerbose
  513.             CStr255 debugName(GetNodeName(index));
  514.             fprintf(stderr, "Comparing subgroup with: %s\n", (char*)debugName);
  515. #endif
  516.             short cmp = StrCmp(subName, GetNodeName(index));
  517.             if (cmp == kItem1LessThanItem2)
  518.             {
  519. #if qDebugCreateVerbose
  520.                 FetchDebugNodeName(index);
  521.                 fprintf(stderr, "Inserting before the subgroup: %s\n", gDebugNodeName);
  522. #endif
  523.                 insertBefore = true;
  524.                 break; // insert it before checkNodeP
  525.             }
  526.             if (cmp == kItem1EqualItem2)
  527.             {
  528.                 if (isFolder != checkNodeP->fIsFolder)
  529.                 {
  530. #if qDebugCreateVerbose
  531.                     fprintf(stderr, "Found the group, but 'folder' state did not match\n");
  532. #endif
  533.                     if (!isFolder)
  534.                         break; // insert the group x before the folder x
  535.                 }
  536.                 else
  537.                 {
  538. #if qDebugCreateVerbose
  539.                     fprintf(stderr, "Found the subgroup: %s\n", (char*) subName);
  540. #endif
  541.                     return; // found it
  542.                 }
  543.             }
  544.             insertAsFirst = false;
  545.             if (checkNodeP->fNextNodeIndex == kEmptyIndex)
  546.             {
  547. #if qDebugCreateVerbose
  548.                 fprintf(stderr, "Did not find the subgroup: %s\n", (char*) subName);
  549. #endif
  550.                 insertBefore = false;
  551.                 break; // at end of list
  552.             }
  553.             index = checkNodeP->fNextNodeIndex;
  554.             prevNodeP = checkNodeP;
  555.         }
  556.     }
  557.     ArrayIndex newIndex = fSize + 1; // new element
  558.     newSubNode = true;
  559.     Node newNode;
  560.     BlockSet(Ptr(&newNode), sizeof(newNode), 0x88);
  561.  
  562.     newNode.fFirstSubNodeIndex = kEmptyIndex;
  563.     newNode.fSubNoRealGroups = 0;
  564.     newNode.fParentIndex = parentIndex;
  565.     newNode.fIsFolder = isFolder;
  566.     newNode.fWindowFrame = CRect(0, 0, 0, 0);
  567.     newNode.fLastReadArticleID = 1;
  568.     newNode.fLastUpdatedArticleID = 1;
  569.     newNode.fFiller1 = 0;
  570.  
  571.  
  572.     if (!isFolder)
  573.         deltaGroups++;
  574.  
  575.     newNode.fNextNodeIndex = kEmptyIndex;
  576.     if (insertAsFirst)
  577.     {
  578.         firstIndex = newIndex;
  579.         newNode.fNextNodeIndex = index;
  580. #if qDebugCreate
  581.         fprintf(stderr, "%s is inserted as first in this list\n", (char*) subName);
  582. #endif
  583.     } 
  584.     else if (insertBefore)
  585.     {
  586.         prevNodeP->fNextNodeIndex = newIndex;
  587.         newNode.fNextNodeIndex = index;
  588. #if qDebugCreate
  589.         FetchDebugNodeName(index);
  590.         fprintf(stderr, "New subgroup inserted before: %s\n", gDebugNodeName);
  591. #endif
  592.     }
  593.     else // insert after existing group
  594.     {
  595. #if qDebugCreate
  596.         FetchDebugNodeName(index);
  597.         fprintf(stderr, "New subgroup chained after: %s\n", gDebugNodeName);
  598. #endif
  599.         newNode.fNextNodeIndex = checkNodeP->fNextNodeIndex;
  600.         checkNodeP->fNextNodeIndex = newIndex;
  601.     }
  602. #if qDebugCreate
  603.     Lock(false);
  604. #endif
  605. #if qDebug
  606.     if (newIndex != fSize + 1)
  607.         ProgramBreak("Trouble with newIndex for new element at InsertElementBefore");
  608. #endif
  609.     fGroupNameList->InsertLast(&subName, RoundToLong(subName.Length() + 1));
  610.     fHelpTextList->CreateNewElement(0);
  611.     InsertElementsBefore(newIndex, &newNode, 1);
  612.     index = newIndex;
  613. #if qDebugCreate
  614.     DumpTree();
  615. #endif
  616. }
  617.  
  618. void TGroupTree::AddGroup(const CStr255 &dotName, Boolean &isNewGroup)
  619. {
  620. #if qDebugCreate
  621.     fprintf(stderr, "\n");
  622. #endif
  623. #if qDebugCreateBrief
  624.     CStr255 s(dotName);
  625.     fprintf(stderr, "Adding new group:     ===> %s <===\n", (char*) s);
  626.     WNE;
  627. #endif
  628.     short dotNameIndex = 0;
  629.     ArrayIndex deltaGroups = 0;
  630.     AddSubGroup(dotName, dotNameIndex, kFirstIndex, deltaGroups);
  631.     if (deltaGroups)
  632.     {
  633. #if qDebugCreateVerbose
  634.         fprintf(stderr, "Got %ld subDeltaGroups for group %s\n", deltaGroups, (char*)dotName);
  635. #endif
  636.         ComputeNodeAddress(kFirstIndex)->fSubNoRealGroups += short(deltaGroups);
  637.     }
  638.     isNewGroup = (deltaGroups != 0);
  639. }
  640.  
  641. void TGroupTree::AddSubGroup(const CStr255 &dotName, short dotNameIndex, ArrayIndex parentIndex, ArrayIndex &deltaGroups)
  642. {
  643.     CStr255 subName;
  644.     if (GetNextSubName(dotName, dotNameIndex, subName))
  645.     {
  646.         ArrayIndex firstSubIndex = ComputeNodeAddress(parentIndex)->fFirstSubNodeIndex;
  647.         ArrayIndex index;
  648.         Boolean newSubNode;
  649.         Boolean isFolder = dotNameIndex <= dotName.Length();
  650.         AddGroupEntry(subName, parentIndex, isFolder, firstSubIndex, index, newSubNode, deltaGroups);
  651.         if (newSubNode)
  652.         {
  653. #if qDebugCreateVerbose
  654.             fprintf(stderr, "A new sub-group was created: %s with ",  (char*) subName);
  655.             fprintf(stderr, "index = %ld and parent = %ld ", index, parentIndex);
  656.             fprintf(stderr, "(firstSub = %ld)\n", firstSubIndex);
  657. #endif
  658.             NodePtr parentP = ComputeNodeAddress(parentIndex);
  659.             parentP->fFirstSubNodeIndex = firstSubIndex;
  660.         }
  661.         ArrayIndex subDeltaGroups = 0;
  662.         AddSubGroup(dotName, dotNameIndex, index, subDeltaGroups);
  663.         if (subDeltaGroups)
  664.         {
  665. #if qDebugCreateVerbose
  666.             fprintf(stderr, "Got %ld subDeltaGroups for group %s\n", subDeltaGroups, (char*)subName);
  667. #endif
  668.             ComputeNodeAddress(index)->fSubNoRealGroups += short(subDeltaGroups);
  669.             deltaGroups += subDeltaGroups;
  670.         }
  671.     }
  672. }
  673.  
  674. void TGroupTree::DoInitialState()
  675. {
  676.     DeleteAll();
  677.     Node newNode;
  678.     BlockSet(Ptr(&newNode), sizeof(newNode), 0x88);
  679.     CStr255 rootName;
  680.     MyGetIndString(rootName, kGroupTreeRootName);
  681.     fGroupNameList->InsertLast(&rootName, RoundToLong(rootName.Length() + 1));
  682.     fHelpTextList->CreateNewElement(0);
  683.     newNode.fNextNodeIndex = kEmptyIndex;
  684.     newNode.fFirstSubNodeIndex = kEmptyIndex;
  685.     newNode.fParentIndex = kEmptyIndex;
  686.     newNode.fSubNoRealGroups = 0;
  687.     newNode.fIsFolder = true;
  688.     newNode.fWindowFrame = CRect(0, 0, 0, 0);
  689.     newNode.fLastReadArticleID = 1;
  690.     newNode.fFiller1 = 0;
  691.     newNode.fLastUpdatedArticleID = 0;
  692.     InsertElementsBefore(fSize + 1, &newNode, 1);
  693. }
  694.  
  695. void TGroupTree::GetLineFromText(Handle h, long size, long &offset, CStr255 &line)
  696. // The parsing in this proc assumes that the text in h is terminated with a CR
  697. {
  698.     line = "";
  699.     Ptr p = *h + offset;
  700.     while (*p != 13)
  701.         line += *p++;
  702.     p++; // past CR
  703.     offset = p - *h;
  704.     if (offset < size && *p == 10)
  705.         p++, offset++;
  706. }
  707.  
  708. void TGroupTree::UpdateGroupTree(Handle groupListH, TGroupList *newGroups)
  709. // The parsing in this proc assumes that the text in groupListH (if any) is terminated with a CR
  710. {
  711. #if qDebug
  712.     if (!fSize)
  713.         ProgramBreak("UpdateGroupTree called while tree is missing head-fake-node");
  714. #endif
  715.     long size = GetHandleSize(groupListH);
  716.     gCurProgress->SetWorkToDo(size);
  717.     long offset = 0;
  718.     ArrayIndex lineNo = 0;
  719.     while (offset <= size)
  720.     {
  721.         CStr255 line, dotName;
  722.         dotName = "";
  723.         GetLineFromText(groupListH, size, offset, line);
  724.         while (line.Length() && line[line.Length()] < 32)
  725.             line.Length()--;
  726.         char ch = line[line.Length()];
  727.         if (ch == 'y' || ch == 'n' || ch == 'm')
  728.         {
  729.             short i = 1;
  730.             while (i <= line.Length() && line[i] > 32)
  731.                 dotName += line[i++];
  732.             if (dotName.Length())
  733.             {
  734.                 Boolean isNew;
  735.                 AddGroup(dotName, isNew);
  736.                 if (newGroups && isNew)
  737.                     newGroups->AppendGroup(dotName);
  738. #if qDebugCreateVerbose
  739.                 DumpTree();
  740. #endif
  741.             }
  742.             if ((lineNo++ & 7) == 0)
  743.             {
  744.                 gCurProgress->SetWorkDone(offset);
  745.                 gCurThread->CheckYield();
  746.             }
  747.         }
  748.     }
  749.     SetArraySize(fSize);
  750.     fGroupNameList->SizeAllocToFit();
  751.     fHelpTextList->SizeAllocToFit();
  752.     gCurProgress->SetWorkDone(size);
  753. #if qDebugCreate
  754.     DumpTree();
  755. #endif
  756. }
  757.  
  758. void TGroupTree::UpdateDescriptions(Handle h)
  759. // The parsing in this proc assumes that the text in groupListH (if any) is terminated with a CR
  760. {
  761. #if qDebug
  762.     if (!fSize)
  763.         ProgramBreak("UpdateDescriptions called while tree is missing head-fake-node");
  764. #endif
  765.     long size = GetHandleSize(h);
  766.     gCurProgress->SetWorkToDo(size);
  767.     long offset = 0;
  768.     ArrayIndex lineNo = 0;
  769.     while (offset <= size)
  770.     {
  771.         CStr255 line, dotName, help;
  772.         dotName = help = "";
  773.         GetLineFromText(h, size, offset, line);
  774.         short i = 1;
  775.         while (i <= line.Length() && line[i] > 32)
  776.             dotName += line[i++];
  777.         while (i <= line.Length() && line[i] <= 32)
  778.             i++;
  779.         while (i <= line.Length())
  780.             help += line[i++];
  781.         if (dotName.Length() && help.Length() && help[1] != '?')
  782.         {
  783.             ArrayIndex index = GetNodeIndexFromDotName(dotName);
  784.             if (index)
  785.             {
  786.                 long helpSize = RoundToLong(help.Length() + 1);
  787.                 fHelpTextList->SetElementSize(index, helpSize);
  788.                 fHelpTextList->ReplaceElementsAt(index, &help, 1);
  789. #if qDebugHelpVerbose
  790.                 DumpTree();
  791. #endif
  792.             }
  793.         }
  794.         if ((lineNo++ & 7) == 0)
  795.         {
  796.             gCurProgress->SetWorkDone(offset);
  797.             gCurThread->CheckYield();
  798.         }
  799.     }
  800.     gCurProgress->SetWorkDone(size);
  801. #if qDebugHelpVerbose
  802.     DumpTree();
  803. #endif
  804. }
  805.  
  806. void TGroupTree::DumpAllNodes()
  807. {
  808. #if qDebug
  809.     Boolean prevLock = Lock(true);
  810.     fprintf(stderr, "index   next   fsub   parn   real   type   name\n");
  811.     for (ArrayIndex index = 1; index <= fSize; index++)
  812.     {
  813.         fprintf(stderr, "%4ldi", index);
  814.         NodePtr nodeP = ComputeNodeAddress(index);
  815.         fprintf(stderr, "%6ldn", nodeP->fNextNodeIndex);
  816.         fprintf(stderr, "%6lds", nodeP->fFirstSubNodeIndex);
  817.         fprintf(stderr, "%6ldp", nodeP->fParentIndex);
  818.         fprintf(stderr, "%6ldr", long(nodeP->fSubNoRealGroups));
  819.         fprintf(stderr, nodeP->fIsFolder ? "   fldr" : "   grup");
  820.         FetchDebugNodeName(index);
  821.         fprintf(stderr, "   '%s'", gDebugNodeName);
  822.         CStr255 help;
  823.         GetHelpText(index, help);
  824.         if (help.Length())
  825.             fprintf(stderr, ", '%s'", (char*)help);
  826.         fprintf(stderr, "\n");
  827.     }
  828.     Lock(prevLock);
  829. #endif
  830. }
  831.  
  832. void TGroupTree::DumpTreePart(ArrayIndex index, ArrayIndex level)
  833. {
  834. #if qDebug
  835.     while (index != kEmptyIndex)
  836.     {
  837.         Node node;
  838.         node = *ComputeNodeAddress(index);
  839.         fprintf(stderr, "%3ldg %hdf  ", long(node.fSubNoRealGroups), node.fIsFolder);
  840.         CStr255 s("");
  841.         for (short i = 1; i <= level; i++)
  842.             s += "•    ";
  843.         FetchDebugNodeName(index);
  844.         CStr255 as = gDebugNodeName;
  845.         s += as;
  846.         fprintf(stderr, "%s\n", (char*)s);
  847.         if (node.fFirstSubNodeIndex == index || node.fFirstSubNodeIndex < 0 || node.fFirstSubNodeIndex > fSize)
  848.             fprintf(stderr, "*** INVALID SUB LINK ***\n");
  849.         else
  850.             DumpTreePart(node.fFirstSubNodeIndex, level + 1);
  851.         if (node.fNextNodeIndex == index || node.fNextNodeIndex < 0 || node.fNextNodeIndex > fSize)
  852.         {
  853.             fprintf(stderr, "*** INVALID NEXT LINK ***\n");
  854.             break;
  855.         }
  856.         index = node.fNextNodeIndex;
  857.     }
  858. #else
  859.     index = level; // pragma unused
  860. #endif
  861. }
  862.  
  863. void TGroupTree::DumpTree()
  864. {
  865. #if qDebug
  866.     fprintf(stderr, "Dumping all nodes:\n");
  867.     DumpAllNodes();
  868.     fprintf(stderr, "Dumping group table as tree:\n");
  869.     if (kFirstIndex < 0 || kFirstIndex > fSize)
  870.         fprintf(stderr, "*** kFirstIndex IS INVALID (%ld) ***\n", kFirstIndex);
  871.     else
  872.         DumpTreePart(kFirstIndex, 0);
  873.     fprintf(stderr, "End of dump\n");
  874. #endif
  875. }
  876.  
  877. void TGroupTree::GetDrawInfo(ArrayIndex index, long &level, CStr255 &text, Boolean &isFolder)
  878. {
  879.     level = 0;
  880.     NodePtr nodeP = ComputeNodeAddress(index);
  881.     text = GetNodeName(index);
  882.     isFolder = nodeP->fIsFolder;
  883.     ArrayIndex levelIndex = index;
  884.     while (levelIndex != kEmptyIndex)
  885.     {
  886.         NodePtr nodeP = ComputeNodeAddress(levelIndex);
  887.         level++;
  888.         levelIndex = nodeP->fParentIndex;
  889.     }    
  890. #if qDebugIterator
  891.     fprintf(stderr, "GetDrawInfo: Got index: %ld at level %ld\n", index, level);
  892.     fprintf(stderr, "   name = %s, isFolder = %hd\n", (char*)text, isFolder);
  893. #endif
  894. }
  895.  
  896. void TGroupTree::GetHelpText(ArrayIndex index, CStr255 &help)
  897. {
  898.     help = "";
  899.     fHelpTextList->GetElementsAt(index, &help, 1);
  900. }
  901.  
  902. //............................................................
  903.  
  904. void TGroupTree::GetDotNameFromNodeIndex(ArrayIndex index, CStr255 &name)
  905. {
  906.     if (index == kFirstIndex)
  907.     {
  908.         MyGetIndString(name, kGroupTreeRootName);
  909.         return;
  910.     }
  911.     name = "";
  912.     while (index != kFirstIndex)
  913.     {
  914.         NodePtr nodeP = ComputeNodeAddress(index);
  915.         if (name.Length())
  916.         {
  917.             name.Insert(".", 1);
  918.             name.Insert(GetNodeName(index), 1);
  919.         }
  920.         else
  921.             name = GetNodeName(index);
  922.         index = nodeP->fParentIndex;
  923.     }
  924. }
  925.  
  926. ArrayIndex TGroupTree::GetNodeIndexFromDotName(const CStr255 &dotName)
  927. {
  928.     short dotNameIndex = 0;
  929.     CStr255 subName;
  930.     ArrayIndex index = ComputeNodeAddress(kFirstIndex)->fFirstSubNodeIndex;
  931.     while (true)
  932.     {
  933.         if (!GetNextSubName(dotName, dotNameIndex, subName))
  934.             return kEmptyIndex;
  935.         Boolean isLastPart = (dotNameIndex > dotName.Length());
  936.         NodePtr nodeP = ComputeNodeAddress(index);
  937.         while (!StrEqual(subName, GetNodeName(index)) || nodeP->fIsFolder == isLastPart)
  938.         {
  939.             index = nodeP->fNextNodeIndex;
  940.             if (index == kEmptyIndex)
  941.             {
  942. #if qDebug & 0
  943.                 fprintf(stderr, "Did not find the group '%s' in the tree\n", (char*)dotName);
  944. #endif
  945.                 return kEmptyIndex;
  946.             }
  947.             nodeP = ComputeNodeAddress(index);
  948.         }
  949.         if (isLastPart)
  950.             return index;
  951.         index = nodeP->fFirstSubNodeIndex;
  952.     }
  953. }
  954. //............................................................
  955. ArrayIndex TGroupTree::SubGetNoItems(TLongintList *expandData, ArrayIndex parentIndex)
  956. {
  957.     long noItems = 0;
  958.     ArrayIndex index = ComputeNodeAddress(parentIndex)->fFirstSubNodeIndex;
  959.     while (index != parentIndex)
  960.     {
  961.         NodePtr nodeP = ComputeNodeAddress(index);
  962.         noItems++;
  963.         if (nodeP->fIsFolder && expandData->At(index))
  964.             noItems += SubGetNoItems(expandData, index);
  965.         if (nodeP->fNextNodeIndex == kEmptyIndex)
  966.             return noItems;
  967.         index = nodeP->fNextNodeIndex;
  968.     }
  969. }
  970.  
  971. ArrayIndex TGroupTree::GetNoItems(TLongintList *expandData, ArrayIndex startIndex)
  972. {
  973.     ArrayIndex noItems = SubGetNoItems(expandData, startIndex);
  974. #if qDebugInfo
  975.     fprintf(stderr, "TGroupTree::GetNoItems, startIndex = %ld -> noItems = %ld\n", startIndex, noItems);
  976. #endif
  977.     return noItems;
  978. }
  979.  
  980. ArrayIndex TGroupTree::FindSubGroupIndexFromLine(TLongintList *expandData, ArrayIndex windowFolderIndex, ArrayIndex line)
  981. {
  982. #if qDebug
  983.     if (windowFolderIndex < 1 || windowFolderIndex > fSize)
  984.         ProgramBreak("Invalid windowFolderIndex");
  985.     if (line < 1 || line > 32000)
  986.         ProgramBreak("Invalid line");
  987. #endif
  988.     CFindSubGroupIterator iter(this, expandData, windowFolderIndex, line);
  989.     for (ArrayIndex index = iter.FirstGroup(); iter.More(); index = iter.NextGroup())
  990.         ;
  991. #if qDebugInfo
  992.     fprintf(stderr, "TGroupTree::FindSubGroupIndexFromLine line = %hd -> index = %ld\n", line, index);
  993. #endif
  994.     return index;    
  995. }
  996.  
  997. ArrayIndex TGroupTree::GetParentIndex(ArrayIndex index)
  998. {
  999.     if (index == kFirstIndex)
  1000.     {
  1001. #if qDebug
  1002.         ProgramBreak("TGroupTree::GetParentIndex called with index = kFirstIndex");
  1003. #endif
  1004.         return kFirstIndex;
  1005.     }
  1006.     return ComputeNodeAddress(index)->fParentIndex;
  1007. }
  1008.  
  1009. CRect TGroupTree::GetWindowFrame(ArrayIndex index)
  1010. {
  1011.     return ComputeNodeAddress(index)->fWindowFrame;
  1012. }
  1013.  
  1014. void TGroupTree::SetWindowFrame(ArrayIndex index, const CRect &frame)
  1015. {
  1016.     ComputeNodeAddress(index)->fWindowFrame = frame;
  1017. }
  1018.  
  1019. Boolean TGroupTree::HasGroup(const CStr255 &dotName)
  1020. {
  1021.     return GetNodeIndexFromDotName(dotName) != kEmptyIndex;
  1022. }
  1023.  
  1024. long TGroupTree::GetLastReadArticleID(const CStr255 &dotName)
  1025. {
  1026.     ArrayIndex index = GetNodeIndexFromDotName(dotName);
  1027.     if (index == kEmptyIndex)
  1028.         return 0;
  1029.     return ComputeNodeAddress(index)->fLastReadArticleID;
  1030. }
  1031.  
  1032. void TGroupTree::SetLastReadArticleID(const CStr255 &dotName, long lastID)
  1033. {
  1034.     ArrayIndex index = GetNodeIndexFromDotName(dotName);
  1035.     if (index != kEmptyIndex)
  1036.         ComputeNodeAddress(index)->fLastReadArticleID = lastID;
  1037. }
  1038.  
  1039. long TGroupTree::GetLastUpdatedArticleID(const CStr255 &dotName)
  1040. {
  1041.     ArrayIndex index = GetNodeIndexFromDotName(dotName);
  1042.     if (index == kEmptyIndex)
  1043.         return 0;
  1044.     return ComputeNodeAddress(index)->fLastUpdatedArticleID;
  1045. }
  1046.  
  1047. void TGroupTree::SetLastUpdatedArticleID(const CStr255 &dotName, long lastID)
  1048. {
  1049.     ArrayIndex index = GetNodeIndexFromDotName(dotName);
  1050.     if (index != kEmptyIndex)
  1051.         ComputeNodeAddress(index)->fLastUpdatedArticleID = lastID;
  1052. }
  1053.  
  1054. //******************************** EXPAND ******************************************
  1055. //------------------------------------------------------------------
  1056. void TGroupTree::Expand(TLongintList *expandData, Boolean doExpand, Boolean wayDown, 
  1057.                                                                                         ArrayIndex index, ArrayIndex &deltaLines)
  1058. {
  1059. #if qDebugExpand
  1060.     Lock(true);
  1061.     fprintf(stderr, "\n");
  1062.     fprintf(stderr, "Expand: <> <> <> <>\n");
  1063. #endif
  1064.     deltaLines = 0;
  1065. // find the line
  1066.     if (doExpand && expandData->At(index))
  1067.     {
  1068. #if qDebugExpand
  1069.         fprintf(stderr, "TGroupTree::Expand: Is expanded with %ld sublines\n", expandData->At(index));
  1070. #endif
  1071.         return; // is expanded
  1072.     }
  1073.     if (!doExpand && !expandData->At(index))
  1074.     {
  1075. #if qDebugExpand
  1076.         fprintf(stderr, "TGroupTree::Expand: Was unexpanded \n");
  1077. #endif
  1078.         return; // is unexpanded
  1079.     }
  1080.     
  1081.     Boolean groupsIsVisible = (expandData->At(index) > 0);
  1082.     NodePtr nodeP = ComputeNodeAddress(index);
  1083.     NodePtr nodeIndex1P = ComputeNodeAddress(1);
  1084.     DoExpand(expandData, nodeIndex1P, nodeP->fFirstSubNodeIndex, doExpand, wayDown, deltaLines, groupsIsVisible);
  1085.  
  1086. // adjust parents
  1087.     while (index != kEmptyIndex)
  1088.     {
  1089.         nodeP = ComputeNodeAddress(index);
  1090. #if qDebugExpand
  1091.         FetchDebugNodeName(index);
  1092.         fprintf(stderr, "Adjusted %s with %ld (%ld to %ld) lines\n", gDebugNodeName, deltaLines, nodeP->fSubNoShownLines, deltaLines + expandData->At(index));
  1093. #endif        
  1094.         ArrayIndex *edP = (ArrayIndex*) expandData->ComputeAddress(index);
  1095.         *edP += deltaLines;
  1096.         index = nodeP->fParentIndex;
  1097.     }
  1098. #if qDebugExpand
  1099.     Lock(false);
  1100.     DumpTree();
  1101. #endif
  1102. }
  1103.  
  1104. // TGroupTree must be locked in debug mode!
  1105. void TGroupTree::DoExpand(TLongintList *expandData, NodePtr nodeIndex1P, ArrayIndex index, Boolean doExpand, 
  1106.                                     Boolean wayDown, ArrayIndex &deltaLines, Boolean groupsIsVisible)
  1107. {
  1108. #if qDebugExpand
  1109.     NodePtr XnodeP = nodeIndex1P + (index - 1);
  1110.     FetchDebugNodeName(index);
  1111.     fprintf(stderr, "DoExpand   (x, %ld, %hd, %hd, %hd) for %s\n",
  1112.         index, doExpand, wayDown, deltaLines, gDebugNodeName);
  1113. #endif
  1114. // mark all sub as unexpanded and count deltaLines
  1115.     while (index != kEmptyIndex)
  1116.     {
  1117.         NodePtr nodeP = nodeIndex1P + (index - 1); // much faster when doing this alot
  1118.         if (doExpand && !groupsIsVisible)
  1119.             deltaLines++;
  1120.         if (!doExpand && groupsIsVisible)
  1121.             deltaLines--;
  1122. #if qDebugExpand
  1123.     FetchDebugNodeName(index);
  1124.     NodePtr XnodeP = nodeIndex1P + (index - 1);
  1125.     fprintf(stderr, "--->DoExpand(x, %ld, %hd, %ld, %hd) for %s\n",
  1126.         index, doExpand, wayDown, deltaLines, gDebugNodeName);
  1127. #endif
  1128.         if (wayDown)
  1129.         {
  1130.             Boolean subGroupsVisible = groupsIsVisible && expandData->At(index);
  1131.             ArrayIndex subDeltaLines = 0;
  1132.             ArrayIndex subIndex = nodeP->fFirstSubNodeIndex;
  1133.             if (subIndex != kEmptyIndex) // optimize
  1134.                 DoExpand(expandData, nodeIndex1P, subIndex, doExpand, wayDown, subDeltaLines, subGroupsVisible);
  1135.             if (doExpand)
  1136.                 expandData->AtPut(index, subDeltaLines);
  1137.             else
  1138.                 expandData->AtPut(index, 0);
  1139.             deltaLines += subDeltaLines;
  1140.         }
  1141.         else
  1142.         {
  1143.             if (doExpand)
  1144.                 deltaLines += expandData->At(index);
  1145.             else
  1146.                 deltaLines -= expandData->At(index);
  1147.         }
  1148.         index = nodeP->fNextNodeIndex;
  1149.     }
  1150. }
  1151.