// ================================================================================= // CBetterListBox.h ©1995 J. Rodden, DD/MF & Associates. All rights reserved // // Based on: // CListBox ©1994 Harold Ekstrom, AG Group, Inc. All rights reserved // CDDxList ©1995 Malcolm Pradhan. All rights reserved // ================================================================================= #include "CBetterListBox.h" #include #include #include #include #include #include // --------------------------------------------------------------------------------- // € CreateFromStream // --------------------------------------------------------------------------------- CBetterListBox* CBetterListBox::CreateFromStream( LStream *inStream ) { return (new CBetterListBox(inStream)); } // --------------------------------------------------------------------------------- // € CBetterListBox // --------------------------------------------------------------------------------- CBetterListBox::CBetterListBox() { mLDEFProc = nil; mAllowDeleteKey = true; mBroadcastDblClkModifiers = true; } // --------------------------------------------------------------------------------- // € CBetterListBox(LStream*) // --------------------------------------------------------------------------------- CBetterListBox::CBetterListBox( LStream *inStream ) : LListBox(inStream) { mLDEFProc = nil; mAllowDeleteKey = true; mBroadcastDblClkModifiers = true; } // --------------------------------------------------------------------------------- // € ~CBetterListBox // --------------------------------------------------------------------------------- CBetterListBox::~CBetterListBox() { // Dispose Toolbox ListHandle before LListBox gets a chance. // That way, we're still around to get the lCloseMsg. if (mMacListH != nil) { LDispose( mMacListH ); mMacListH = nil; } } // --------------------------------------------------------------------------------- // € FinishCreateSelf // --------------------------------------------------------------------------------- // This code could have gone in the constructor for CBetterListBox, but then // derived class's ldef initialization code wouldn't get called. void CBetterListBox::FinishCreateSelf() { // Store a pointer to this object in the list handle's userHandle field. (**mMacListH).userHandle = (Handle) this; // Call the ldef with the lInitMsg. Cell dummyCell = {0,0}; CBetterListBoxLDEF( lInitMsg, false, nil, dummyCell, 0, 0, mMacListH ); // we want at least one column of data if ((*mMacListH)->dataBounds.right == 0)::LAddColumn (1, 0, mMacListH); } // --------------------------------------------------------------------------------- // € LDEFInitialize // --------------------------------------------------------------------------------- void CBetterListBox::LDEFInitialize() { short fontHeight; Point tCell; FontInfo fInfo; ::GetFontInfo( &fInfo ); (**mMacListH).indent.h = 2; fontHeight = fInfo.ascent+fInfo.descent+fInfo.leading; if (fontHeight >= 16) { tCell.h = (*mMacListH)->cellSize.h; tCell.v = fontHeight + 2; ::LCellSize (tCell, mMacListH); } (**mMacListH).indent.v = ((**mMacListH).cellSize.v - fontHeight) / 2 + fInfo.ascent+fInfo.leading; } // --------------------------------------------------------------------------------- // € LDEFDraw // --------------------------------------------------------------------------------- void CBetterListBox::LDEFDraw( Boolean lSelect, Rect *lRect, Cell lCell, short lDataOffset, short lDataLen ) { FocusDraw(); UTextTraits::SetPortTextTraits(mTextTraitsID); DrawElementSelf( lSelect, lRect, lCell, lDataLen ); FocusDraw(); if ( lSelect ) { if (IsOnDuty ()) { LDEFHilite( lSelect, lRect, lCell, lDataOffset, lDataLen ); } else { ::LMSetHiliteMode( LMGetHiliteMode() & ~(1 << hiliteBit) ); // now do a lot of work to make it pretty! RgnHandle outerRgn = ::NewRgn(); // Make region containing item ::InsetRect(lRect,1,1); ::RectRgn(outerRgn, lRect); RgnHandle innerRgn = ::NewRgn(); // Carve out interior of region so ::CopyRgn(outerRgn, innerRgn); // that it's just a one-pixel thick ::InsetRgn(innerRgn, 1, 1); // outline of the item rectangle ::DiffRgn(outerRgn, innerRgn, outerRgn); ::InvertRgn (outerRgn); ::DisposeRgn(innerRgn); ::DisposeRgn(outerRgn); } } } // --------------------------------------------------------------------------------- // € LDEFHilite // --------------------------------------------------------------------------------- void CBetterListBox::LDEFHilite( Boolean lSelect, Rect *lRect, Cell lCell, short lDataOffset, short lDataLen ) { FocusDraw(); ::LMSetHiliteMode( LMGetHiliteMode() & ~(1 << hiliteBit) ); ::InsetRect(lRect,1,1); ::InvertRect(lRect); } // --------------------------------------------------------------------------------- // € SwapListItems // --------------------------------------------------------------------------------- // Swaps the items in the list, updates the selection if necessary. void CBetterListBox::SwapListItems (Cell& toCell, Cell& fromCell, short inDataLen) { short lDataLen; void* data1 = (void*) new char(inDataLen); void* data2 = (void*) new char(inDataLen); if ( data1 != nil && data2 != nil ) { Boolean hasSelection = false; Cell selectedCell = {0,0}; if ( LGetSelect( true, &selectedCell, mMacListH) ) { hasSelection = true; ::LSetSelect (false, selectedCell, mMacListH); } ::LSetDrawingMode( false, mMacListH); // fromCell data -> data1 lDataLen = inDataLen; ::LGetCell( data1, &lDataLen, fromCell, mMacListH ); // toCell data -> data2 lDataLen = inDataLen; ::LGetCell( data2, &lDataLen, toCell, mMacListH ); // data2 -> fromCell data lDataLen = inDataLen; ::LSetCell (data2, lDataLen, fromCell, mMacListH); // data1 -> toCell data lDataLen = inDataLen; ::LSetCell (data1, lDataLen, toCell, mMacListH); // reset selection if ( hasSelection ) { ::LSetSelect (true, selectedCell, mMacListH); } ::LSetDrawingMode( true, mMacListH); // redraw cells ::LDraw (toCell, mMacListH); ::LDraw (fromCell, mMacListH); // scroll the list if necessary ::LAutoScroll (mMacListH); } } // --------------------------------------------------------------------------------- // € ClearCell // --------------------------------------------------------------------------------- // Override to "clear" the given cell (when delete is hit and inCell is selected), // whatever that may semantically mean for your class. void CBetterListBox::ClearCell(Cell inCell) { } // --------------------------------------------------------------------------------- // € DeleteRow // --------------------------------------------------------------------------------- void CBetterListBox::DeleteRow(short inRowNum) { if ( inRowNum < 0 || inRowNum > (*mMacListH)->dataBounds.bottom ) inRowNum = (*mMacListH)->dataBounds.bottom; ::LDelRow( 1, inRowNum, mMacListH); } // --------------------------------------------------------------------------------- // € DeleteCol // --------------------------------------------------------------------------------- void CBetterListBox::DeleteCol(short inColNum) { if ( inColNum < 0 || inColNum > (*mMacListH)->dataBounds.right ) inColNum = (*mMacListH)->dataBounds.right; ::LDelColumn( 1, inColNum, mMacListH); } // --------------------------------------------------------------------------------- // € AddRowElement // --------------------------------------------------------------------------------- // Adds a new item to the end of the list. void CBetterListBox::AddRowElement ( const void *lElement, const short lDataLen, Cell& inCell) { if ( inCell.v < 0 ) inCell.v = (*mMacListH)->dataBounds.bottom + 1; if ( inCell.h < 0 || inCell.h > (*mMacListH)->dataBounds.right ) inCell.h = (*mMacListH)->dataBounds.right; inCell.v = ::LAddRow( 1, inCell.v, mMacListH); if ( inCell.h == (*mMacListH)->dataBounds.right ) inCell.h--; if ( inCell.v == (*mMacListH)->dataBounds.bottom ) inCell.v--; ::LSetCell( lElement, lDataLen, inCell, mMacListH); } // --------------------------------------------------------------------------------- // € AddColElement // --------------------------------------------------------------------------------- // Adds a new item to the end of the list. void CBetterListBox::AddColElement ( const void *lElement, const short lDataLen, Cell& inCell) { if ( inCell.h < 0 ) inCell.h = (*mMacListH)->dataBounds.right + 1; if ( inCell.v < 0 || inCell.v > (*mMacListH)->dataBounds.bottom ) inCell.v = (*mMacListH)->dataBounds.bottom; inCell.h = ::LAddColumn( 1, inCell.h, mMacListH); if ( inCell.h == (*mMacListH)->dataBounds.right ) inCell.h--; if ( inCell.v == (*mMacListH)->dataBounds.bottom ) inCell.v--; ::LSetCell( lElement, lDataLen, inCell, mMacListH); } // --------------------------------------------------------------------------------- // € CopyCell // --------------------------------------------------------------------------------- void CBetterListBox::CopyCell( Cell inSrcCell, Cell inDestCell, short inDataLen) { if ( IsValidCell(inSrcCell) && IsValidCell(inDestCell) ) { void* data = (void*) new char(inDataLen); ::LGetCell( data, &inDataLen, inSrcCell, mMacListH); ::LSetCell( data, inDataLen, inDestCell, mMacListH); delete data; } } // --------------------------------------------------------------------------- // € HandleKeyPress // --------------------------------------------------------------------------- // ListBox supports keyboard navigation and type selection Boolean CBetterListBox::HandleKeyPress( const EventRecord &inKeyEvent) { Boolean keyHandled = true; Char16 theKey = inKeyEvent.message & charCodeMask; if (mAllowDeleteKey && UKeyFilters::IsTEDeleteKey(theKey)) { DoDeleteKey(); } else if ( theKey == char_Tab ) { EventRecord newKeyEvent = inKeyEvent; Boolean shiftKeyDown = (inKeyEvent.modifiers & shiftKey) != 0; Int16 numColumns = (**mMacListH).dataBounds.right; theKey = ( numColumns == 1 ) ? ( shiftKeyDown ? char_UpArrow : char_DownArrow ) : ( shiftKeyDown ? char_LeftArrow : char_RightArrow ); newKeyEvent.modifiers = inKeyEvent.modifiers & ~shiftKey; newKeyEvent.message = (newKeyEvent.message & ~charCodeMask) | theKey; DoNavigationKey(newKeyEvent); } else { keyHandled = LListBox::HandleKeyPress(inKeyEvent); } return keyHandled; } // --------------------------------------------------------------------------------- // € DrawElementSelf // --------------------------------------------------------------------------------- // Draw the contents of an element. Default assumes text data void CBetterListBox::DrawElementSelf( Boolean lSelect, Rect *lRect, Cell lCell, short lDataLen ) { Str255 theString; ::LGetCell(theString + 1, &lDataLen, lCell, mMacListH); theString[0] = lDataLen; ::MoveTo( lRect->left + kTextGap, lRect->bottom - kTextGap); ::DrawString(theString); } // --------------------------------------------------------------------------- // € TakeOffDuty // --------------------------------------------------------------------------- void CBetterListBox::TakeOffDuty() { Cell theCell = {0, 0}; if (::LGetSelect(true, &theCell, mMacListH)) { ::LDraw (theCell, mMacListH); } } // --------------------------------------------------------------------------- // € PutOnDuty // --------------------------------------------------------------------------- void CBetterListBox::PutOnDuty() { Cell theCell = {0, 0}; if (::LGetSelect(true, &theCell, mMacListH)) { ::LDraw (theCell, mMacListH); } } // --------------------------------------------------------------------------------- // € ClickSelf // --------------------------------------------------------------------------------- // We need to check for drags so we have to do the mouse handling ourselves void CBetterListBox::ClickSelf( const SMouseDownEvent &inMouseDown) { Cell theCell; Point mousePt; short cellHeight; short cellWidth; short visibleTop; short visibleLeft; Rect itemRect; if (SwitchTarget(this)) { FocusDraw(); mousePt = inMouseDown.whereLocal; visibleTop = (**mMacListH).visible.top; visibleLeft = (**mMacListH).visible.left; cellHeight = (**mMacListH).cellSize.v; cellWidth = (**mMacListH).cellSize.h; theCell.h = ((mousePt.h - (**mMacListH).rView.left) / cellWidth) + visibleLeft; theCell.v = ((mousePt.v - (**mMacListH).rView.top) / cellHeight) + visibleTop; // is it in the scroll bar? Provide hook to handle it if (mousePt.h >= (**mMacListH).rView.right) { ClickInScrollBar(inMouseDown.whereLocal, inMouseDown.macEvent.modifiers); return; } // has it hit on a valid cell? Rect cells = (**mMacListH).dataBounds; if (theCell.v <= cells.bottom - 1 && theCell.v >= cells.top && theCell.h <= cells.right - 1 && theCell.h >= cells.left) { ::LRect (&itemRect, theCell, mMacListH); // Is it inside? if (::PtInRect(mousePt, &itemRect)) { // Let subclass grab click, otherwise check for double click Boolean clickHandled = ClickInCell( inMouseDown, itemRect, theCell); if (!clickHandled) { SelectOneCell(theCell); if (GetClickCount() == 2) HandleDoubleClick(theCell,inMouseDown); } } } else { theCell.v = theCell.h = 0; if( ::LGetSelect( true, &theCell, mMacListH)) { ::LSetSelect( false, theCell, mMacListH); } } } } //---------------------------------------------------------------------------- // € ClickInCell //---------------------------------------------------------------------------- // Hook to let subclasses do something (drag & drop, expand hierarchical item, etc) // with a mouse click within a cell Boolean CBetterListBox::ClickInCell(const SMouseDownEvent &inMouseDown, Rect& inRect, Cell& inCell) { return false; } // --------------------------------------------------------------------------------- // € ClickInScrollBar // --------------------------------------------------------------------------------- // Hook to do something when scrollbar is clicked on, // default lets List Manager handle click in scrollbar void CBetterListBox::ClickInScrollBar(Point& inLocalPt, short inModifiers) { ::LClick(inLocalPt, inModifiers, mMacListH); } // --------------------------------------------------------------------------------- // € HandleDoubleClick // --------------------------------------------------------------------------------- // Hook to do something when a cell is double clicked on void CBetterListBox::HandleDoubleClick( const Cell& inCell, const SMouseDownEvent& inMouseDown) { MessageT theMessage = mDoubleClickMessage; if (mBroadcastDblClkModifiers) PackModifiersInMessage(theMessage,inMouseDown); BroadcastMessage(theMessage, this); } // --------------------------------------------------------------------------- // € DoDeleteKey // --------------------------------------------------------------------------- void CBetterListBox::DoDeleteKey() { Int16 numColumns = (**mMacListH).dataBounds.right; Int16 numRows = (**mMacListH).dataBounds.bottom; Cell theSelection = {0, 0}; if (::LGetSelect(true, &theSelection, mMacListH)) { if ( numColumns == 1 ) DeleteRow(theSelection.v); else if ( numRows == 1 ) DeleteCol(theSelection.h); else ClearCell(theSelection); } } // --------------------------------------------------------------------------------- // € IsValidCell // --------------------------------------------------------------------------------- Boolean CBetterListBox::IsValidCell(Cell& inCell) { return ( inCell.v >= 0 && inCell.v <= ((**mMacListH).dataBounds.bottom - 1) && inCell.h >= 0 && inCell.h <= ((**mMacListH).dataBounds.right - 1) ); } // ================================================================================= // ================================================================================= // --------------------------------------------------------------------------------- // € CBetterListBoxLDEF // --------------------------------------------------------------------------------- pascal void CBetterListBoxLDEF( short lMessage, Boolean lSelect, Rect *lRect, Cell lCell, short lDataOffset, short lDataLen, ListHandle lList ) { // Get the real object out of the list handle's userHandle field. CBetterListBox *theListBox = (CBetterListBox *) (**lList).userHandle; if ( theListBox ) { switch ( lMessage ) { case lInitMsg: // Set up the callback mechanism by placing a UPP or ProcPtr in // the list's refCon. The ldef stub will call this routine back // for other messages. (The lInitMsg was called manually.) #ifdef __powerc theListBox->mLDEFProc = (ListDefProcPtr) NewListDefProc( CBetterListBoxLDEF ); #else theListBox->mLDEFProc = (ListDefProcPtr) CBetterListBoxLDEF; #endif (**lList).refCon = (long) theListBox->mLDEFProc; // Calling through to the subobject's initialize routine // is ok since it should have been created by now, or our own // will be called anyway. theListBox->LDEFInitialize(); break; case lDrawMsg: // Call the real object's draw routine. theListBox->LDEFDraw( lSelect, lRect, lCell, lDataOffset, lDataLen ); break; case lHiliteMsg: // Call the real object's hilite routine. theListBox->LDEFHilite( lSelect, lRect, lCell, lDataOffset, lDataLen ); break; case lCloseMsg: // Unfortunately, any derived object has probably been // destroyed at this point, so we can't call through // to it for the lCloseMsg. #ifdef __powerc // Dispose of the ldef's routine descriptor. DisposeRoutineDescriptor( (UniversalProcPtr) theListBox->mLDEFProc ); theListBox->mLDEFProc = nil; #endif break; } } }