home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 4 / Apprentice-Release4.iso / Source Code / PowerPlant / Hierarchical Lists / Libs / CTwistDownListBox.cp < prev    next >
Encoding:
Text File  |  1995-10-24  |  13.4 KB  |  430 lines  |  [TEXT/MMCC]

  1. // ===========================================================================
  2. //    CTwistDownListBox.h            ©1994 Jan Bruyndonckx All rights reserved.
  3. //    v1.0 18/6/94
  4. //  Inspired by M.Minow's article in Develop 18
  5. //
  6. //    A hierarchical listbox subclass.
  7. //  The default listbox contains only string data.
  8. // ===========================================================================
  9.  
  10. #include <string.h>
  11. #include <Palettes.h>
  12. #include <UDrawingState.h>
  13. #include "CTwistDownListBox.h"
  14.  
  15. #ifndef DEBUG_NEW            // MetroWerks 'new' leak-finder
  16.  #define NEW    new
  17. #endif
  18.  
  19. //----------------------------------------------------------------------------
  20. // The trianges
  21.  
  22. PolyHandle    CTwistDownListBox::sClosedPoly = NULL,
  23.               CTwistDownListBox::sOpenedPoly = NULL,
  24.               CTwistDownListBox::sIntermediatePoly = NULL ;
  25.  
  26. static void DrawTriangle (PolyHandle aPoly, const Boolean drawFilled,
  27.                           const short posV, const short posH) ;
  28.  
  29. // These macros simplify access to the flag word in the list element.
  30. #define SetTDFlag(flags, mask)        ((flags) |= (mask))
  31. #define ClearTDFlag(flags, mask)    ((flags) &= ~(mask))
  32. #define InvertTDFlag(flags, mask)    ((flags) ^= (mask))
  33. #define TestTDFlag(flags, mask)        (((flags) & (mask)) != 0)
  34.  
  35. //----------------------------------------------------------------------------
  36. // Creation methods
  37.  
  38. CTwistDownListBox* CTwistDownListBox::CreateFromStream(LStream *inStream)
  39. {
  40.   return (NEW CTwistDownListBox(inStream));
  41. }
  42.  
  43. CTwistDownListBox::CTwistDownListBox() : CCustomListBox()
  44. {
  45.   init () ;
  46. }
  47.  
  48. CTwistDownListBox::CTwistDownListBox(const SPaneInfo &inPaneInfo,
  49.                         Boolean inHasHorizScroll, Boolean inHasVertScroll,
  50.                         Boolean inHasGrow, Boolean inHasFocusBox,
  51.                         MessageT inDoubleClickMessage, Int16 inTextTraitsID,
  52.                         LCommander *inSuper) :
  53.                       CCustomListBox (inPaneInfo, inHasHorizScroll, inHasVertScroll,
  54.                                       inHasGrow, inHasFocusBox, inDoubleClickMessage, inTextTraitsID,
  55.                                       inSuper)
  56. {
  57.   init () ;
  58. }
  59.  
  60. CTwistDownListBox::CTwistDownListBox (const CTwistDownListBox &inOriginal) :
  61.                     CCustomListBox (inOriginal)
  62. {
  63.   init () ;
  64. }
  65.  
  66. CTwistDownListBox::CTwistDownListBox(LStream *inStream) : CCustomListBox (inStream)
  67. {
  68.   init () ;
  69. }
  70.  
  71. //----------------------------------------------------------------------------
  72. // Create the triangle buttons, if they didn't exist already
  73.  
  74. void CTwistDownListBox::init (void)
  75. {
  76.  // Create the twist down buttons
  77.  // Note that the port and text drawing characteristics must have been set.
  78.  
  79.   short        buttonSize;
  80.   short        halfSize;
  81.   short        intermediateSize;
  82.   FontInfo    info;
  83.  
  84.   ::GetFontInfo(&info);
  85.   buttonSize = info.ascent;                // The "show sublist" button
  86.   buttonSize &= ~1;                        // Round down to an even number
  87.   halfSize = buttonSize / 2;
  88.   intermediateSize = (buttonSize * 3) / 4;
  89.  
  90.   // note, we allocate the polygons once (they are static variables) for *all* lists, and
  91.   // we never dispose them
  92.  
  93.   if (sClosedPoly == NULL)
  94.       { sClosedPoly = ::OpenPoly();
  95.       ::MoveTo(halfSize, 0);
  96.       ::LineTo(buttonSize, halfSize);
  97.       ::LineTo(halfSize, buttonSize);
  98.       ::LineTo(halfSize, 0);
  99.       ::ClosePoly();
  100.       }
  101.   if (sOpenedPoly == NULL)
  102.       { sOpenedPoly = ::OpenPoly();
  103.       ::MoveTo(0, halfSize);
  104.       ::LineTo(buttonSize, halfSize);
  105.       ::LineTo(halfSize, buttonSize);
  106.       ::LineTo(0, halfSize);
  107.       ::ClosePoly();
  108.       ::OffsetPoly (sOpenedPoly, 1, -2) ;
  109.       }
  110.   if (sIntermediatePoly == NULL)
  111.       { sIntermediatePoly = ::OpenPoly();    
  112.       ::MoveTo(intermediateSize, 0);
  113.       ::LineTo(intermediateSize, intermediateSize);
  114.       ::LineTo(0, intermediateSize);
  115.       ::LineTo(intermediateSize, 0);
  116.       ::ClosePoly();
  117.       }
  118.   
  119.   // Remember the width of the "button" area.
  120.   triangleWidth = (**sOpenedPoly).polyBBox.right
  121.                     + kTriangleOutsideGap
  122.                     + kTriangleInsideGap;
  123. }
  124.  
  125. //----------------------------------------------------------------------------
  126. // If the data-field contains text (default case), then get/set it
  127.  
  128. StringPtr CTwistDownListBox::GetDescriptor(Str255 outDescriptor) const
  129. { Byte            buffer[260] ;
  130.   short            l = sizeof (buffer) ;
  131.   TwistDownRecPtr twistElement = (TwistDownRecPtr) buffer ;
  132.   
  133.   twistElement->data[0] = '\0' ;
  134.   GetSelection (twistElement, &l) ;
  135.   l -= TwistDownRecSize ;
  136.   ::memcpy (outDescriptor+1, twistElement->data, l) ;
  137.   outDescriptor[0] = l ;
  138.   
  139.   return outDescriptor ;
  140. }
  141.  
  142. void CTwistDownListBox::SetDescriptor(ConstStr255Param inDescriptor)
  143. { Byte            buffer[260] ;
  144.   short            l ;
  145.   TwistDownRecPtr twistElement = (TwistDownRecPtr) buffer ;
  146.   
  147.   l = *inDescriptor+1 ;
  148.   ::memcpy (twistElement->data, inDescriptor+1,  l) ;
  149.   twistElement->indent = 0 ;
  150.   twistElement->flags = 0 ;
  151.   l += TwistDownRecSize ;
  152.   SetSelection (twistElement, l) ;
  153. }
  154.  
  155. //----------------------------------------------------------------------------
  156. // Some info about the mouse location (local coordinates)
  157.  
  158. void CTwistDownListBox::MouseInfo (Point mousePt, Boolean *inButtonArea, Cell *theCell)
  159. {
  160.   ::GlobalToLocal (&mousePt) ;
  161.   Rect hitRect  = (*mMacListH)->rView ;
  162.   hitRect.right = (*mMacListH)->rView.left + triangleWidth ;
  163.   *inButtonArea = ::PtInRect (mousePt, &hitRect) ;            // yes, is it in a button?
  164.  
  165.   short visibleTop = (*mMacListH)->visible.top ;
  166.   short cellHeight = (*mMacListH)->cellSize.v ;
  167.                                                             // get the selected cell
  168.   theCell->v = (mousePt.v - hitRect.top) / cellHeight + visibleTop ;
  169.   theCell->h = 0 ;
  170. }
  171.  
  172. //----------------------------------------------------------------------------
  173. // The user clicked in the list
  174.  
  175. void CTwistDownListBox::ClickSelf (const SMouseDownEvent &inMouseDown)
  176. { long        l ;
  177.   Cell         theCell ;
  178.   Boolean    inButtonArea ;
  179.  
  180.   SwitchTarget(this);
  181.   FocusDraw();
  182.  
  183.   // is the mousedown in the twistDown button area?
  184.   MouseInfo (inMouseDown.macEvent.where, &inButtonArea, &theCell) ;
  185.  
  186.   Rect hitRect = (*mMacListH)->rView ;
  187.  
  188.   if (inButtonArea)                    // yes, is it in a button?
  189.     { short visibleTop = (*mMacListH)->visible.top ;
  190.       short cellHeight = (*mMacListH)->cellSize.v ;
  191.  
  192.          TwistDownRecPtr    twist = (TwistDownRecPtr) GetCellPtr (theCell) ;
  193.  
  194.          if ((twist == NULL) || (TestTDFlag(twist->flags, kHasSubList) == 0))
  195.              goto end ;
  196.  
  197.       SetTDFlag (twist->flags, kDrawFilled) ;                    // Draw the button
  198.          ::LDraw (theCell, mMacListH) ;                            // Draw the cell anew
  199.          hitRect.top += ((theCell.v - visibleTop) * cellHeight) ;    // set to the dimensions of the button
  200.          hitRect.bottom = hitRect.top + cellHeight ;
  201.          Boolean inHitRect = false ;
  202.          if (::StillDown())
  203.            { inHitRect = true ;
  204.              Point    mousePt ;
  205.              
  206.              while (::WaitMouseUp())    
  207.             { ::GetMouse (&mousePt);
  208.               Boolean newInHitRect = ::PtInRect(mousePt, &hitRect) ;
  209.               if (newInHitRect != inHitRect)
  210.                 { twist = (TwistDownRecPtr) GetCellPtr (theCell) ;
  211.                   InvertTDFlag (twist->flags, kDrawFilled) ;
  212.                   ::LDraw (theCell, mMacListH) ;
  213.                   inHitRect = newInHitRect ;
  214.                 }
  215.             }
  216.         }
  217.       
  218.          twist = (TwistDownRecPtr) GetCellPtr (theCell) ;            // the user released the mouse
  219.          if (inHitRect == false) 
  220.              { // make very sure the button is cleared
  221.                if (TestTDFlag (twist->flags, kDrawFilled))
  222.                  { ClearTDFlag (twist->flags, kDrawFilled) ;
  223.                    ::LDraw (theCell, mMacListH) ;
  224.                  }
  225.                return ;        // and get out…
  226.              }
  227.          
  228.          SetTDFlag (twist->flags, kDrawIntermediate) ;                // Draw the animation triangle
  229.          ::LDraw (theCell, mMacListH) ;
  230.          ::Delay (kAnimationDelay, &l) ;
  231.          twist = (TwistDownRecPtr) GetCellPtr (theCell) ;
  232.          ClearTDFlag (twist->flags, kDrawIntermediate) ;
  233.          
  234.          ::LSetDrawingMode (false, mMacListH) ;                    // here send message… 
  235.          if (TestTDFlag (twist->flags, kIsOpened))                    // …to expand/collapse sublist
  236.              { ClearTDFlag (twist->flags, kIsOpened) ;
  237.                CollapseElement (theCell) ;
  238.              }
  239.          else
  240.              { 
  241.                if (GetHandleSize ((*mMacListH)->cells) > 30000)
  242.                  { ListLimitReached () ;
  243.                  }
  244.                else
  245.                  { SetTDFlag (twist->flags, kIsOpened) ;
  246.                    ExpandElement (theCell) ;
  247.                  }
  248.              }
  249.          ::LSetDrawingMode (true, mMacListH) ;                        // redraw list again
  250.          twist = (TwistDownRecPtr) GetCellPtr (theCell) ;
  251.          ClearTDFlag (twist->flags, kDrawFilled) ;
  252.          ResizeFrameBy (0, 0, true) ;            // this works more correctly when collapsing & rows are 'above' the top
  253.          return ;
  254.     }
  255.   
  256. end:      // mousedown was not in a button…
  257.   inherited::ClickSelf (inMouseDown) ;
  258. }
  259.  
  260. //---------------------------------------------------------------------------------------
  261.  
  262. void CTwistDownListBox::FullyExpand (const unsigned short inMaxDepth)
  263. {
  264.   Cell    cell = { 0, 0 } ;
  265.  
  266.   ::LSetDrawingMode (false, mMacListH) ;                        // don't draw during expand
  267.   
  268.   for (;;)
  269.     { TwistDownRecPtr twist = (TwistDownRecPtr) GetCellPtr (cell) ;
  270.       if (twist == NULL)
  271.           break ;
  272.       
  273.       // element has no sublist or is already opened…
  274.       if ((TestTDFlag(twist->flags, kHasSubList) != 0)    &&    // has a sublist
  275.             (TestTDFlag(twist->flags, kIsOpened)   == 0)    &&    // not yet opened
  276.             (twist->indent < inMaxDepth))                        // don't go below this level
  277.         { // Expand the element
  278.           SetTDFlag (twist->flags, kIsOpened) ;
  279.              ExpandElement (cell) ;      
  280.           }
  281.  
  282.       // advance to next cell position
  283.       if (::LNextCell (true, true, &cell, mMacListH) == false)
  284.           break ;
  285.     }
  286.  
  287.   ::LSetDrawingMode (true, mMacListH) ;                        // redraw list again
  288.   ResizeFrameBy (0, 0, true) ;                                // force update
  289. }
  290.  
  291. //---------------------------------------------------------------------------------------
  292. // We reached the 32K limit of the list manager!
  293. // Override if you want to present a nice alert…
  294.  
  295. void CTwistDownListBox::ListLimitReached (void)
  296. {
  297.   SysBeep (1) ;
  298.   SysBeep (1) ;
  299.  #ifdef ASSERTION
  300.   DebugStr("\p32K Reached") ;
  301.  #endif
  302. }
  303.  
  304. //----------------------------------------------------------------------------
  305.  
  306. void CTwistDownListBox::DrawElementSelf (const Rect *lRect, 
  307.                                          const void *lElement, 
  308.                                          const short lDataLen)
  309.                                          
  310. // Draw a single list cell on the screen.
  311. // Checks flags and draws triangular button if needed;
  312. // calls DrawTwistedElement to draw cell contents.
  313.  
  314.   TwistDownRecPtr twistElement = (TwistDownRecPtr) lElement ;
  315.  
  316.   if (TestTDFlag (twistElement->flags, kHasSubList))
  317.       { PolyHandle    aPoly = NULL ;
  318.         
  319.         aPoly = TestTDFlag(twistElement->flags, kIsOpened) ? sOpenedPoly : sClosedPoly ;
  320.         if (TestTDFlag(twistElement->flags, kDrawIntermediate))
  321.             aPoly = sIntermediatePoly ;
  322.         if (aPoly)
  323.             DrawTriangle (aPoly,
  324.                           TestTDFlag(twistElement->flags, kDrawFilled),
  325.                             lRect->top+1,
  326.                           lRect->left+kTriangleOutsideGap) ;
  327.       }
  328.  
  329.   ::MoveTo (lRect->left + triangleWidth + 2 + twistElement->indent*kIndentOffset,
  330.             lRect->top + 10) ;
  331.   DrawTwistedElement (lRect, twistElement, lDataLen) ;
  332. }
  333.  
  334. //---------------------------------------------------------------------------------------
  335. // Since we have only one column of data, resize the cellWidth accordingly
  336.  
  337. void CTwistDownListBox::ResizeFrameBy (Int16 inWidthDelta, Int16 inHeightDelta, Boolean inRefresh)
  338. {
  339.   (*mMacListH)->cellSize.h += inWidthDelta ;
  340.   inherited::ResizeFrameBy (inWidthDelta, inHeightDelta, inRefresh) ;
  341. }
  342.  
  343. //----------------------------------------------------------------------------
  344. // Draw the data-field of the element.  Default is simple text
  345.  
  346. void CTwistDownListBox::DrawTwistedElement (const Rect *lRect, 
  347.                                             const TwistDownRecPtr twistElement, 
  348.                                             const short lDataLen)
  349.  
  350. // Draw contents of a single list element.
  351. // Default version just draws text; override for other types of data.
  352.  
  353. {
  354. #pragma unused (lRect) 
  355.   ::DrawText (twistElement->data, 0, lDataLen-TwistDownRecSize) ;
  356. }
  357.  
  358. //----------------------------------------------------------------------------
  359. // User closed a sublist.  Remove all sublists hanging from this element.
  360. // Override in case an element contains a pointer to some allocated memory.
  361.  
  362. void CTwistDownListBox::CollapseElement (const Cell theCell)
  363. { short                count = 0 ;
  364.   TwistDownRecPtr    twist = (TwistDownRecPtr) GetCellPtr (theCell) ;
  365.   short                headIndent = twist->indent ;
  366.   Cell                cell = theCell ;
  367.   
  368.   for (cell.v++ ; ; cell.v++)
  369.     { twist = (TwistDownRecPtr) GetCellPtr (cell) ;
  370.       if (twist == NULL)
  371.           break ;
  372.       if (twist->indent <= headIndent)
  373.           break ;
  374.       count++ ;
  375.     }
  376.    
  377.   if (count)
  378.        ::LDelRow (count, theCell.v+1, mMacListH) ;
  379. }
  380.  
  381. //----------------------------------------------------------------------------
  382. // You'd better override me!
  383.  
  384. void CTwistDownListBox::ExpandElement (const Cell theCell)
  385. {
  386. #pragma unused (theCell) 
  387. }
  388.  
  389. //----------------------------------------------------------------------------
  390.  
  391. static void DrawTriangle (PolyHandle aPoly, const Boolean drawFilled,
  392.                           const short posV, const short posH)
  393. { const short kGrayness = 4 ;            // 1 for intermediate gray, 8 for very light gray
  394.  
  395.   ::PenNormal () ;
  396.   ::OffsetPoly (aPoly, posH, posV);        // move poly to the right spot
  397.  
  398.   if (drawFilled)
  399.      ::FillPoly(aPoly, &qd.black);
  400.   else
  401.    { RGBColor     foreColor, backColor;
  402.      
  403.      ::GetForeColor(&foreColor);
  404.      ::GetBackColor(&backColor);
  405.  
  406.      // This loop sets foreColor to a very light gray.
  407.      for (short i = 0; i < kGrayness; i++)
  408.        if (::GetGray(GetGDevice(), &backColor, &foreColor) == FALSE)
  409.             break;
  410.  
  411.      if ((foreColor.red   == 0) &&
  412.           (foreColor.green == 0) &&
  413.           (foreColor.blue  == 0))
  414.        ::ErasePoly (aPoly) ;
  415.      else
  416.       { StColorState    saveColorState ;
  417.         ::RGBForeColor(&foreColor);
  418.         ::FillPoly(aPoly, &qd.black);
  419.       }
  420.  
  421.      ::FramePoly (aPoly) ;
  422.    }
  423.    
  424.   ::OffsetPoly (aPoly, -posH, -posV);
  425. }
  426.  
  427. //----------------------------------------------------------------------------
  428.  
  429.