home *** CD-ROM | disk | FTP | other *** search
/ Power GUI Programming with VisualAge C++ / powergui.iso / powergui / dm / lboxdrag / lboxitem.cpp < prev    next >
Encoding:
Text File  |  1996-10-29  |  17.5 KB  |  630 lines

  1. //************************************************************
  2. // Direct Manipulation - List Box Example
  3. //
  4. // Copyright (C) 1994, Law, Leong, Love, Olson, Tsuji.
  5. // Copyright (c) 1997 John Wiley & Sons, Inc. 
  6. // All Rights Reserved.
  7. //************************************************************
  8. extern "C"
  9.   {
  10. #ifdef __OS2__
  11.   #define INCL_WINSYS
  12.   #include <os2.h>
  13. #endif
  14. #ifdef __WINDOWS__
  15.   #include <windows.h>
  16. #endif
  17.   }
  18.  
  19. #include <ilistbox.hpp>
  20. #include <istring.hpp>
  21. #include <ipoint.hpp>
  22. #include <ifont.hpp>
  23. #include <idmimage.hpp>
  24. #include <idmevent.hpp>
  25. #include <ihandle.hpp>
  26. #include <itrace.hpp>
  27. #include <igline.hpp>
  28. #include <igpyline.hpp>
  29. #include <igrafctx.hpp>
  30. #include <icolor.hpp>
  31. #include <itrace.hpp>
  32. #include <igrect.hpp>
  33. #include <icoordsy.hpp>
  34.  
  35. #include "lboxitem.hpp"
  36.  
  37. static const unsigned
  38.   nil = 0xffffffffu;
  39.  
  40. ListBoxItem :: ListBoxItem ( IDMSourceOperation* srcOp,
  41.                              IListBox*           srcLB,
  42.                              unsigned            index )
  43.   : IDMItem( srcOp,
  44.              IDM::text,
  45.              IDMItem:: moveable | IDMItem::copyable,
  46.              none )
  47.   {
  48.   IMODTRACE_DEVELOP("ListBoxItem::ListBoxItem");
  49.  
  50.   // Item contents is the list-box's item text.
  51.   this -> setContents( srcLB->itemText( index ) );
  52.   // Item object is the item index.
  53.   ITRACE_DEVELOP("Selected text is " + srcLB->itemText(index) );
  54.  
  55.   this -> setObject( (void*)index );
  56.   // Try to use rfText.
  57.   IString
  58.     name = this -> generateSourceName(),
  59.     rfs   = rfForThisProcess();
  60.   if ( name.length() )
  61.     { // Text fits; use rfText.
  62.     this -> setSourceName( name );
  63.     rfs += IString( "," ) + IDM::rfText;
  64.     }
  65.   else
  66.     { // Text doesn't fit; use rfSharedMem instead.
  67.     rfs += IString( "," ) + IDM::rfSharedMem;
  68.     this -> setRequiresPreparation();
  69.     }
  70.   ITRACE_DEVELOP( "Rmfs is " + rfs );
  71.  
  72.   // Set up the RMFs we support.
  73.   this -> setRMFs( rmfsFrom( IDM::rmLibrary, rfs ) );
  74.  
  75. #ifdef IC_PM
  76.   // We support dropping on the shredder in OS/2.
  77.   this -> addRMF( IDM::rmDiscard, IDM::rfUnknown );
  78. #endif
  79.  
  80.   // Use text icon when a user drags the item.
  81.   ISystemPointerHandle
  82.     icon( ISystemPointerHandle::text );
  83.   IDMImage
  84.     image( icon );
  85.   this -> setImage( image );
  86.   }
  87.  
  88. ListBoxItem :: ListBoxItem ( const IDMItem::Handle& dragItem )
  89.   : IDMItem( dragItem )
  90.   {
  91.   IMODTRACE_DEVELOP("ListBoxItem::ListBoxItem(Handle)");
  92.   // We only support copy and move.
  93.   this -> enableLink( false );
  94.   }
  95.  
  96. ListBoxItem :: ~ListBoxItem ( )
  97.   {
  98.   IMODTRACE_DEVELOP("ListBoxItem::~ListBoxItem");
  99.   }
  100.  
  101. Boolean ListBoxItem :: generateSourceItems( IDMSourceOperation* srcOp )
  102.   {
  103.   IMODTRACE_DEVELOP("ListBoxItem::generateSourceItems");
  104.   Boolean
  105.     result = false;
  106.   IListBox
  107.    *srcLB = (IListBox*)( srcOp->sourceWindow() );
  108.   // Get index of dragged item:
  109.   unsigned
  110.     index = sourceIndex( srcLB, srcOp->position() );
  111.   if ( index != nil )
  112.     { // User not dragging from white space; add appropriate item.
  113.     srcOp -> addItem( new ListBoxItem( srcOp, srcLB, index ) );
  114.     srcOp -> setImageStyle( IDM::stack3AndFade );
  115.     result = true;
  116.     }
  117.   return result;
  118.   }
  119.  
  120. Boolean ListBoxItem :: targetDrop ( IDMTargetDropEvent& event )
  121.   {
  122.   IMODTRACE_DEVELOP("ListBoxItem::targetDrop");
  123.   IListBox
  124.    *tgtLB = (IListBox*)( event.window() );
  125.  
  126.   // Turn off target emphasis.
  127.   ListBoxItemProvider
  128.    *provider = (ListBoxItemProvider*)( tgtLB->itemProvider() );
  129.   provider -> drawEmphasis( tgtLB, event, TgtLocation( after, nil ) );
  130.  
  131.   // Calculate where the object is dropped.
  132.   TgtLocation
  133.     dropLoc = targetLocation( tgtLB, event.dropPosition() );
  134.   // Add or replace the list item, based on drop location.
  135.   switch ( dropLoc.type )
  136.     {
  137.     case before:
  138.       tgtLB -> add( dropLoc.index, contents() );
  139.       break;
  140.     case on:
  141.       tgtLB -> setItemText( dropLoc.index, contents() );
  142.       break;
  143.     case after:
  144.       tgtLB -> add( dropLoc.index + 1, contents() );
  145.       break;
  146.     }
  147.   // If source and target are the same and the item is moved
  148.   // forward, update source index.
  149.   IDMTargetOperation::Handle
  150.     tgtOp = IDMTargetOperation::targetOperation();
  151.   if ( tgtOp->sourceWindow() == event.window()
  152.        &&
  153.        tgtOp->operation() == IDMOperation::move )
  154.     {
  155.     IDMItem::Handle
  156.       srcItem = IDMItem::sourceItemFor( tgtOp->item( 1 ) );
  157.     unsigned
  158.       srcIndex = (unsigned)( srcItem->object() );
  159.     if ( dropLoc.type != on
  160.          &&
  161.          dropLoc.index < srcIndex )
  162.       srcItem->setObject( (void*)( srcIndex + 1 ) );
  163.     }
  164.  
  165.   return true;
  166.   }
  167.  
  168. Boolean ListBoxItem :: sourceEnd ( IDMSourceEndEvent& event )
  169.   {
  170.   IMODTRACE_DEVELOP("ListBoxItem::sourceEnd");
  171.   // If the move is completed and not sent to the shredder,
  172.   // delete the source item.
  173.   if ( event.wasTargetSuccessful()
  174.        &&
  175.        (unsigned long)( object() ) != nil
  176.        &&
  177.        event.dragItem()->sourceOperation()->operation() != IDMOperation::copy )
  178.     {
  179.     unsigned
  180.       index = (unsigned)( this->object() );
  181.     ( (IListBox*)( event.window() ) ) -> remove( index );
  182.     }
  183.   return true;
  184.   }
  185.  
  186. #ifdef IC_PM
  187. Boolean ListBoxItem :: sourceDiscard ( IDMSourceDiscardEvent& event )
  188.   {
  189.   IListBox
  190.    *srcLB = (IListBox*)( event.window() );
  191.  
  192.   // Get index of the dragged item.
  193.   unsigned
  194.     index = (unsigned)( this->object() );
  195.  
  196.   // Delete that item.
  197.   srcLB->remove( index );
  198.  
  199.   // Mark deleted so sourceEnd doesn't delete it again.
  200.   setObject( (void*)nil );
  201.  
  202.   return true;
  203.   }
  204. #endif //IC_PM
  205.  
  206. unsigned ListBoxItem :: sourceIndex ( IListBox* pLB,
  207.                                       const IPoint& pt )
  208.   {
  209.   IMODTRACE_DEVELOP("ListBoxItems::sourceIndex");
  210.  
  211.   // If there are no items, indicate no match.
  212.   if ( pLB->isEmpty() )
  213.     return nil;
  214.  
  215.   // Calculate index of dragged item:
  216.   unsigned
  217.     dy, i, row, index;
  218.  
  219.   if ( ICoordinateSystem::applicationOrientation() ==
  220.            ICoordinateSystem::originUpperLeft )
  221.     dy = pt.y();
  222.   else
  223.     dy = pLB->rect().height() - pt.y();
  224.  
  225.   i  = itemHeight(pLB);
  226.   row = dy / i;
  227.   index = pLB->top() + row;
  228.  
  229.   ITRACE_DEVELOP("Source index is " + IString(index) + " row is " +
  230.     IString(row) + " item height is " +  IString(i) + " dy " +
  231.     IString(dy) + " " + pLB->rect().asString() + pt.asString());
  232.  
  233.   // Indicate if dragging below the last item.
  234.   if ( index >= pLB->count() )
  235.     index = nil;
  236.  
  237.   return index;
  238.   }
  239.  
  240. ListBoxItem::TgtLocation
  241.   ListBoxItem :: targetLocation ( IListBox* pLB, const IPoint& pt )
  242.   {
  243.   // Target position is in desktop coordinates.
  244.   // We must map this to listbox window coordinates.
  245.   IPoint
  246.     lbPt = IWindow::mapPoint( pt,
  247.                               IWindow::desktopWindow()->handle(),
  248.                               pLB->handle() );
  249. #ifdef IC_WIN
  250.   // fix for mapping problem on windows.
  251.   lbPt += IPoint(1,1);
  252. #endif
  253.  
  254.   // Get index of target item:
  255.   unsigned
  256.     index = sourceIndex( pLB, lbPt );
  257.   LocType
  258.     type = on;
  259.   if ( index != nil )
  260.     { // Drop at item, see if before or after:
  261.      unsigned dy;
  262.       if ( ICoordinateSystem::applicationOrientation() ==
  263.               ICoordinateSystem::originUpperLeft )
  264.        dy = lbPt.y();
  265.      else
  266.        dy = pLB->rect().height() - lbPt.y();
  267.     unsigned
  268.       i   = itemHeight(pLB),
  269.       rem = dy % i;
  270.     if ( rem < i/4 )
  271.       type = before;
  272.     else if ( rem > 3*i/4 )
  273.       type = after;
  274.     }
  275.   else
  276.     { // Drop off end.
  277.     type = after;
  278.     index = pLB->count() - 1;
  279.     }
  280.   return TgtLocation( type, index );
  281.   }
  282.  
  283. unsigned ListBoxItem :: itemHeight( IListBox* pLB )
  284.   {
  285.   // Get item height presuming standard use
  286.   // based on current font.  Note that this
  287.   // may be wrong if the listbox is owner-draw
  288.   // or if setHeight() is called.
  289.   IFont
  290.     font( pLB );
  291.   unsigned
  292.     result = font.maxCharHeight() + font.externalLeading();
  293.   // Don't return 0 since that would cause divide-by-zero error.
  294.   if ( !result )
  295.     result++;
  296.   return result;
  297.   }
  298.  
  299. unsigned ListBoxItem :: verticalScrollWidth( IListBox* pLB )
  300.   {
  301.   // Get the vertical scroll bar's width.  For the OS/2 list box,
  302.   // the vertical scrollbar is always visible.  For the Windows
  303.   // list box, it is only visible when needed:  when the number of
  304.   // items exceed the viewing area.  Note that the LBS_DISABLENOSCROLL
  305.   // style can be included to always display the vertical scroll bar, but
  306.   // the Open Class Library does not expose this style.
  307.   unsigned
  308.     result;
  309. #ifdef IC_PM
  310.     result = WinQuerySysValue( HWND_DESKTOP, SV_CXVSCROLL ) + 1;
  311. #endif //IC_PM
  312. #ifdef IC_WIN
  313.     unsigned
  314.       totalItemHeight = ListBoxItem::itemHeight( pLB ) * pLB->count(),
  315.       hscrollHeight = ListBoxItem::horizontalScrollHeight( );
  316.  
  317.     if ( totalItemHeight >= (pLB->rect().top() - hscrollHeight) )
  318.       {
  319.         result = GetSystemMetrics( SM_CXVSCROLL ) +
  320.                  GetSystemMetrics( SM_CXFRAME );
  321.       }
  322.       else
  323.         result = GetSystemMetrics( SM_CXFRAME );
  324. #endif //IC_WIN
  325.  
  326.   return result;
  327.   }
  328.  
  329. #ifdef IC_WIN
  330. unsigned ListBoxItem :: horizontalScrollHeight( )
  331.   {
  332.   // Get the horizontal scroll bar's height for Windows.
  333.   return( GetSystemMetrics( SM_CYHSCROLL ) +
  334.           GetSystemMetrics( SM_CYFRAME ) - 3 );
  335.  
  336.   }
  337. #endif //IC_WIN
  338.  
  339. ListBoxItem::TgtLocation :: TgtLocation ( LocType type,
  340.                                           unsigned index )
  341.   : type( type ),
  342.     index( index )
  343.   {
  344.   }
  345.  
  346. Boolean ListBoxItem::TgtLocation ::
  347.   operator == ( const TgtLocation& loc ) const
  348.   {
  349.   if ( index == loc.index )
  350.     return type == loc.type;
  351.   else if ( index == loc.index-1 )
  352.     return ( type == after && loc.type == before );
  353.   else if ( index == loc.index+1 )
  354.     return ( type == before && loc.type == after );
  355.   else
  356.     return false;
  357.   }
  358.  
  359. ListBoxItemProvider :: ListBoxItemProvider ( IListBox* listBox )
  360.   {
  361.   if ( listBox )
  362.     this -> provideItemsFor( listBox );
  363.   }
  364.  
  365. ListBoxItemProvider
  366.  &ListBoxItemProvider :: provideItemsFor( IListBox* listBox )
  367.   {
  368.   listBox -> setItemProvider( this );
  369.   return *this;
  370.   }
  371.  
  372.  
  373. typedef ListBoxItem::TgtLocation TgtLocation;
  374.  
  375. static TgtLocation
  376.   lastTarget( ListBoxItem::after, nil );
  377.  
  378.  
  379. static void draw ( IGraphicContext&   gc,
  380.                    IListBox*          lb,
  381.                    const TgtLocation& target )
  382.   {
  383.   if ( target.index != nil )
  384.     {
  385.     // First, get offset from top of listbox:
  386.     unsigned
  387.       offset = target.index - lb->top() + 1,
  388.       height = ListBoxItem::itemHeight( lb );
  389.  
  390.     // Next, adjust it if before this item:
  391.     if ( target.type == ListBoxItem::before )
  392.       offset--;
  393.  
  394.     // Calculate that item's rectangle's bottom, taking into account
  395.     // the platform's coordinate system:
  396.     unsigned
  397.       bottom;
  398.     if ( ICoordinateSystem::applicationOrientation()
  399.          == ICoordinateSystem::originUpperLeft )
  400.       {
  401.       bottom = height * offset;
  402.       }
  403.     else
  404.       {
  405.       bottom = lb->rect().height() - height * offset;
  406.       // Decrease bottom by 2 pels to align the emphasis.
  407.       bottom -= 2;
  408.       }
  409.  
  410.     // Get the width of the vertical scroll bar.
  411.     unsigned
  412.       vscrollWidth =
  413.         ListBoxItem::verticalScrollWidth( lb );
  414. #ifdef IC_WIN
  415.     // Get the height of the horizontal scroll bar.
  416.     unsigned
  417.       hscrollHeight =
  418.         ListBoxItem::horizontalScrollHeight( );
  419. #endif
  420.  
  421.     // Draw line or box:
  422.     IPoint
  423.       origin( 0, bottom );
  424.     if ( target.type == ListBoxItem::on )
  425.       {
  426.       IPoint
  427.         topRight;
  428.       if ( ICoordinateSystem::applicationOrientation()
  429.            == ICoordinateSystem::originUpperLeft )
  430.         {
  431.         topRight = IPoint( lb->rect().width(), bottom - height + 2 );
  432.         }
  433.       else
  434.         {
  435.         topRight = IPoint( lb->rect().width(), bottom + height );
  436.         }
  437.  
  438.       // Adjust the origin so the left side of the box is visible.
  439.         origin+= IPoint( 1, 0 );
  440.  
  441.       // Adjust the end point if the vertical scroll bar is visible.
  442.       topRight -= IPoint( vscrollWidth, 1 );
  443.  
  444. #ifdef IC_WIN
  445.       // Do not draw emphasis over the horizontal scroll bar.
  446.       IPoint
  447.         bottomLeft( origin.x(),
  448.                     lb->rect().top() - hscrollHeight - 1 );
  449.       if ( origin.y() >= bottomLeft.y() )
  450.         {
  451.         IGPolyline myLine( IPointArray(4) );
  452.         myLine.setPoint( 0, bottomLeft );
  453.         myLine.setPoint( 1, IPoint( origin.x(), topRight.y() ) );
  454.         myLine.setPoint( 2, topRight );
  455.         myLine.setPoint( 3, IPoint( topRight.x(), bottomLeft.y() ) );
  456.         myLine.drawOn( gc );
  457.         return;
  458.         }
  459. #endif //IC_WIN
  460.  
  461.       IRectangle theBox( origin, topRight );
  462.       IGRectangle myBox( theBox  );
  463.       myBox.drawOn( gc );
  464.       }
  465.     else
  466.       {
  467. #ifdef IC_WIN
  468.       // Do not draw emphasis over the horizontal scroll bar.
  469.       if ( bottom >= (lb->rect().top() - hscrollHeight - 1) )
  470.         return;
  471. #endif //IC_WIN
  472.  
  473.       // Adjust the end point if the vertical scroll bar is visible.
  474.       IPoint
  475.           end( lb->rect().width() - vscrollWidth + 1, bottom );
  476.       IGPolyline myLine( IPointArray(2) );
  477.       myLine.setPoint( 0, origin );
  478.       myLine.setPoint( 1, end );
  479.       myLine.drawOn( gc );
  480.       }
  481.     }
  482.   }
  483.  
  484. ListBoxItemProvider
  485.  &ListBoxItemProvider :: drawEmphasis ( IListBox*          listBox,
  486.                                         IDMTargetEvent&    event,
  487.                                         const TgtLocation& target )
  488.   {
  489.   // If the same target, it's already drawn.
  490.   if ( target == lastTarget )
  491.     return *this;
  492.  
  493.   // Get the graphic context and set the drawing attributes.
  494.   IGraphicContext
  495.     gc( event.presSpace() );
  496.  
  497.   gc.setMixMode( IGraphicBundle::xor );
  498.   gc.setDrawOperation( IGraphicBundle::frame );
  499.   gc.setPenColor( IColor::white );
  500.   gc.setPenWidth( 1 );
  501.  
  502.   // "Undraw" current target emphasis.
  503.   draw( gc, listBox, lastTarget );
  504.  
  505.   // Set new target and draw it.
  506.   lastTarget = target;
  507.   draw( gc , listBox, lastTarget );
  508.  
  509.   event.releasePresSpace();
  510.   return *this;
  511.   }
  512.  
  513. Boolean ListBoxItemProvider ::
  514.   provideLeaveSupport ( IDMTargetLeaveEvent& event )
  515.   {
  516.   IListBox
  517.    *listBox = (IListBox*)( event.window() );
  518.   this -> drawEmphasis( listBox,
  519.                         event,
  520.                         TgtLocation( ListBoxItem::after, nil ) );
  521.   return false;
  522.   }
  523.  
  524. Boolean ListBoxItemProvider ::
  525.   provideEnterSupport ( IDMTargetEnterEvent& event )
  526.  
  527. {
  528.   IMODTRACE_DEVELOP("ListBoxItemProvider::provideEnterSupport");
  529.   // Get default dragover result.
  530.   Inherited::provideEnterSupport( event );
  531.  
  532.   IDMTargetOperation::Handle
  533.     tgtOp = IDMTargetOperation::targetOperation();
  534.  
  535.   IListBox
  536.    *lb = (IListBox*)( event.window() );
  537.  
  538. #ifdef IC_WIN
  539.   // Do not allow drops over the scroll bars.
  540.   // The scroll bars in Windows are not controls;
  541.   // thus, we calculate the scroll rectangles and
  542.   // test for hits over the scroll areas.
  543.  
  544.   // Target position is in desktop coordinates.
  545.   // We must map this to listbox window coordinates.
  546.   IPoint
  547.     lbPt = IWindow::mapPoint( event.position(),
  548.                               IWindow::desktopWindow()->handle(),
  549.                               lb->handle() );
  550.   // fix for mapping problem on windows.
  551.   lbPt += IPoint(1,1);
  552.  
  553.   unsigned
  554.     hscrollHeight = ListBoxItem::horizontalScrollHeight( ),
  555.     vscrollWidth = ListBoxItem::verticalScrollWidth( lb );
  556.  
  557.   IRectangle hscrollRect( lb->rect().left(),
  558.                           lb->rect().top() - hscrollHeight,
  559.                           lb->rect().right(),
  560.                           lb->rect().top() ),
  561.              vscrollRect( lb->rect().right() - vscrollWidth + 3,
  562.                           lb->rect().bottom(),
  563.                           lb->rect().right(),
  564.                           lb->rect().top() - hscrollHeight );
  565.   if ( hscrollRect.contains( lbPt ) ||
  566.        vscrollRect.contains( lbPt ) )
  567.     {
  568.     event.setDropIndicator( IDM::notOk );
  569.     // Undraw any existing target emphasis.
  570.     drawEmphasis( lb,
  571.                   event,
  572.                   TgtLocation( ListBoxItem::after, nil ) );
  573.     return true;
  574.     }
  575. #endif //IC_WIN
  576.  
  577.   ListBoxItem::TgtLocation
  578.     tgtLocation
  579.       = ListBoxItem::targetLocation( lb, event.position() );
  580.  
  581.   if ( event.dropIndicator() == IDM::ok
  582.        &&
  583.        tgtOp->sourceWindow() == event.window() )
  584.   { // If source equals target, prohibit dropping on same item.
  585.     IDMItem::Handle
  586.       srcItem = IDMItem::sourceItemFor( tgtOp->item( 1 ) );
  587.     unsigned
  588.       srcIndex = (unsigned)( srcItem->object() );
  589.  
  590.     // Disable conflicting drop on source window:
  591.     unsigned long
  592.       op = tgtOp->operation();
  593.     if ( op == IDMOperation::drag )
  594.       op = IDMOperation::move; // Default;
  595.  
  596.     if ( op == IDMOperation::copy )
  597.     { // Can't copy to self.
  598.       if ( (srcIndex == tgtLocation.index)
  599.            &&
  600.            tgtLocation.type == ListBoxItem::on )
  601.         event.setDropIndicator( IDM::notOk );
  602.     }
  603.     else if ( op == IDMOperation::move )
  604.     { // No sense moving to same place.
  605.       if ( (srcIndex == tgtLocation.index)
  606.            ||
  607.            ( tgtLocation.type == ListBoxItem::before
  608.              &&
  609.              srcIndex == tgtLocation.index - 1 )
  610.            ||
  611.            ( tgtLocation.type == ListBoxItem::after
  612.              &&
  613.              srcIndex == tgtLocation.index + 1 ) )
  614.       {
  615.         event.setDropIndicator( IDM::notOk );
  616.       }
  617.     }
  618.   }
  619.  
  620.   // Draw target emphasis:
  621.   drawEmphasis( lb, event, tgtLocation );
  622.  
  623.   return true;
  624.   }
  625.  
  626. ListBoxItemProvider *ListBoxItemProvider :: operator & ()
  627.   {
  628.   return this;
  629.   }
  630.