1 line
41 KiB
C
Executable File
1 line
41 KiB
C
Executable File
/* Copyright (c) 2017, Computer History Museum
|
|
All rights reserved.
|
|
Redistribution and use in source and binary forms, with or without modification, are permitted (subject to
|
|
the limitations in the disclaimer below) provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
|
disclaimer in the documentation and/or other materials provided with the distribution.
|
|
* Neither the name of Computer History Museum nor the names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE
|
|
COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
DAMAGE. */
|
|
|
|
#include "stickypopup.h"
|
|
|
|
#define FILE_NUM 94
|
|
#pragma segment StickyPopup
|
|
|
|
|
|
/* MJN *//* new file */
|
|
/*
|
|
|
|
stickypopup.c
|
|
|
|
|
|
stickypopup.c contains code to do a "sticky" popup menu. It is basically identical to
|
|
a normal popup menu, except that it doesn't require the mouse button to stay down in
|
|
order to stay open, and it allows keyboard navigation to select items.
|
|
|
|
Use NewStickyPopup to create a new, empty sticky popup. Use AddEntriesToStickyPopup to
|
|
add items to the popup, and RemoveEntriesFromStickyPopup to remove items. Call
|
|
StickyPopupSelect to bring up the sticky popup and allow the user to select a choice
|
|
from it; this function is comperable to PopUpMenuSelect. Use DisposeStickyPopup to
|
|
dispose of the sticky popup when you are through with it.
|
|
|
|
WARNING: The sticky popup menu routines are not compatible with the Menu Manager routines
|
|
for working with menus and standard popup menus. The data types (i.e. StickyPopupHdl
|
|
and MenuHandle) are not interchangeable.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#pragma mark Constants & defines
|
|
|
|
#define STICKY_POPUP_MEM_TEST_SIZE 16384 /* 16 K */
|
|
#define MAX_LIST_DATA_SIZE 32000 /* safely less than 32 K */
|
|
#define CELL_WIDTH_EXTRA 4 /* number of extra pixels to leave on right hand side in list */
|
|
#define STICKY_SELECT_EVENT_MASK (mDownMask + mUpMask + keyDownMask + autoKeyMask + keyUpMask)
|
|
|
|
#define RETURN_CHAR_CODE 0x0D /* ASCII code for Carriage Return */
|
|
#define ENTER_CHAR_CODE 0x03 /* ASCII code for Enter */
|
|
#define PERIOD_CHAR_CODE 0x2E /* ASCII code for Period */
|
|
#define ESCAPE_KEY_CODE 0x35 /* virtual key code for Escape */
|
|
#define BACKSPACE_CHAR_CODE 0x08 /* ASCII code for Backspace/Delete */
|
|
#define CLEAR_KEY_CODE 0x47 /* virtual key code for Clear key on numeric keypad */
|
|
#define FWD_DEL_CHAR_CODE 0x7F /* ASCII code for Forward Delete key on extended keyboard */
|
|
#define UP_ARROW_CHAR_CODE 0x1E /* ASCII code for Up Arrow */
|
|
#define DOWN_ARROW_CHAR_CODE 0x1F /* ASCII code for Down Arrow */
|
|
#define LEFT_ARROW_CHAR_CODE 0x1C /* ASCII code for Left Arrow */
|
|
#define RIGHT_ARROW_CHAR_CODE 0x1D /* ASCII code for Right Arrow */
|
|
#define HOME_CHAR_CODE 0x01 /* ASCII code for Home key on extended keyboard */
|
|
#define END_CHAR_CODE 0x04 /* ASCII code for End key on extended keyboard */
|
|
#define SPACE_CHAR_CODE 0x20 /* ASCII code for space character */
|
|
#define DASH_CHAR_CODE 0x2D /* ASCII code for dash character */
|
|
#define STICKY_SPACE_CHAR_CODE 0xCA /* ASCII code for sticky (non-breaking) space (em-space) character */
|
|
#define STICKY_DASH_CHAR_CODE 0xD0 /* ASCII code for sticky (non-breaking) dash (en-dash) character */
|
|
|
|
|
|
|
|
#pragma mark Globals
|
|
|
|
static StickyPopupHdl CurStickyPopup = nil; /* sticky popup currently being "run" by StickyPopupSelect */
|
|
static long StickyPopupKeyUpdateDelay; /* time, in ticks, to wait after a key stroke before updating the list selection in response to typing */
|
|
static Str255 StickyPopupTypingStr; /* string for accumulating the text that the user is typing for purpose of selection in list */
|
|
static long StickyPopupLastKeyTicks; /* system ticks (TickCount) of last key stroke being used for purpose of selection in list */
|
|
static pascal Boolean StickyPopupClickLoop(void);
|
|
|
|
|
|
/* FINISH *//* move to stringutil.c (and its prototype, too) */
|
|
/*********************************************************************************************
|
|
* StripStickyCharacters - strips out sticky spaces and sticky dashes from the Pascal
|
|
* string passed in theString (sticky meaning non-breaking).
|
|
*
|
|
* Note that the usage of the word "sticky" in this routine's name has nothing to do with
|
|
* sticky popups.
|
|
*
|
|
* This routine will not move or purge memory.
|
|
*********************************************************************************************/
|
|
|
|
void StripStickyCharacters(Str255 theString)
|
|
|
|
{
|
|
long numChars;
|
|
long i;
|
|
unsigned char* curChar;
|
|
|
|
|
|
numChars = theString[0];
|
|
for (i = 0, curChar = theString + 1; i < numChars; i++, curChar++)
|
|
switch (*curChar)
|
|
{
|
|
case STICKY_SPACE_CHAR_CODE:
|
|
*curChar = SPACE_CHAR_CODE;
|
|
break;
|
|
case STICKY_DASH_CHAR_CODE:
|
|
*curChar = DASH_CHAR_CODE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* StickyPopupNormalizeString - normalize a string for purposes of string comparison. This
|
|
* routine is provided so that you can set up a sticky popup menu in alpha-numeric-sorted
|
|
* order, so that typing to select an item will work; since the sticky popup routines use
|
|
* this routine also, you are guaranteed to be sorting using the same methods as the sticky
|
|
* popup code.
|
|
*
|
|
* Currently, normalization does the following:
|
|
*
|
|
* - Converts all lowercase characters to uppercase
|
|
* - Strips out all diacritical markings
|
|
* - Converts all sticky spaces to normal spaces, and converts all sticky dashes to
|
|
* normal dashes (via StripStickyCharacters)
|
|
*********************************************************************************************/
|
|
|
|
void StickyPopupNormalizeString(Str255 theString)
|
|
|
|
{
|
|
/* FINISH *//* support scripts other than smRoman */
|
|
if (FurrinSort) UppercaseStripDiacritics(theString + 1, theString[0], smRoman);
|
|
else MyUpperText(theString+1,theString[0]);
|
|
StripStickyCharacters(theString);
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* StickyPopupCompareString - string comparison routine. Return value depends on the
|
|
* comparison of the two strings:
|
|
*
|
|
* Relationship Return value
|
|
*
|
|
* stringA < stringB -1
|
|
* stringA = stringB 0
|
|
* stringA > stringB 1
|
|
*
|
|
* The return value is identical to that returned by both RelString and CompareString.
|
|
* This routine is provided so that you can set up a sticky popup menu in
|
|
* alpha-numeric-sorted order, so that typing to select an item will work; since the sticky
|
|
* popup routines use this routine also, you are guaranteed to be sorting using the same
|
|
* methods as the sticky popup code.
|
|
*
|
|
* If you pass in true for needNormalize, this routine will make local copies of the two
|
|
* strings, and call StickyPopupNormalizeString on the local copies before doing the string
|
|
* compare; the original strings you pass in are left unmodified. If you pass in false for
|
|
* needNormalize, it is assumed that you have already called StickyPopupNormalizeString on
|
|
* the strings yourself, or that you have some reason to do the comparison without first
|
|
* normalizing the strings.
|
|
*********************************************************************************************/
|
|
|
|
short StickyPopupCompareString(Str255 stringA, Str255 stringB, Boolean needNormalize)
|
|
|
|
{
|
|
Str255 stringA_;
|
|
Str255 stringB_;
|
|
|
|
|
|
if (!needNormalize)
|
|
return StringComp(stringA,stringB); // calls CompareString or RelString depending on FurrinSort SD
|
|
|
|
BlockMoveData(stringA, stringA_, stringA[0] + 1);
|
|
BlockMoveData(stringB, stringB_, stringB[0] + 1);
|
|
StickyPopupNormalizeString(stringA_);
|
|
StickyPopupNormalizeString(stringB_);
|
|
return StringComp(stringA_, stringB_); // calls CompareString or RelString depending on FurrinSort SD
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* MouseLocInList - returns true if mouse location passed in curMouseLoc is inside
|
|
* the boundaries of the sticky popup menu specified by stickyPopup; the popup
|
|
* should be currently popped (isPopped = true). curMouseLoc is in global coordinates.
|
|
*********************************************************************************************/
|
|
|
|
static Boolean MouseLocInList(Point curMouseLoc, StickyPopupHdl stickyPopup)
|
|
|
|
{
|
|
return PtInRgn(curMouseLoc,MyGetWindowContentRegion((**stickyPopup).theWindow));
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* StickyPopupClickLoop - lClikLoop routine for the list used in sticky popups.
|
|
* It always returns true (i.e. it never canceles LClick). It's only purpose
|
|
* is to unhilite cells while the mouse is outside of the list's boundaries.
|
|
*********************************************************************************************/
|
|
|
|
static pascal Boolean StickyPopupClickLoop(void)
|
|
|
|
{
|
|
ListHandle theList;
|
|
Point curMouseLoc;
|
|
Cell curSel;
|
|
Boolean selectionExists;
|
|
|
|
|
|
theList = (**CurStickyPopup).theList;
|
|
GetMouse(&curMouseLoc);
|
|
LocalToGlobal(&curMouseLoc);
|
|
if (MouseLocInList(curMouseLoc, CurStickyPopup))
|
|
{
|
|
curSel.v = curSel.h = 0;
|
|
selectionExists = LGetSelect(true, &curSel, theList);
|
|
if (!selectionExists)
|
|
{
|
|
curSel.v = (**theList).mouseLoc.v / (**theList).cellSize.v; /* WARNING *//* assumes cellSize.v has already been set to a valid non-zero value */
|
|
curSel.h = 0;
|
|
LSetDrawingMode(true, theList);
|
|
LSetSelect(true, curSel, theList);
|
|
}
|
|
return true;
|
|
}
|
|
curSel.v = curSel.h = 0;
|
|
selectionExists = LGetSelect(true, &curSel, theList);
|
|
if (selectionExists)
|
|
{
|
|
LSetSelect(false, curSel, theList);
|
|
LSetDrawingMode(false, theList);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*********************************************************************************************
|
|
* NewStickyPopup - create a new sticky popup menu. Pass in the font family ID
|
|
* and font size to use in fontNum and fontSize. Pass a pointer to a StickyPopupHdl
|
|
* in stickyPopup; the created sticky popup will be stored in this handle. The
|
|
* function returns noErr if it succeeds, or a non-zero error code if it fails.
|
|
*********************************************************************************************/
|
|
|
|
OSErr NewStickyPopup(short fontNum, short fontSize, StickyPopupHdl *stickyPopup)
|
|
|
|
{
|
|
OSErr err;
|
|
StickyPopupHdl popupHdl;
|
|
StickyPopupPtr popupPtr;
|
|
GrafPtr origPort;
|
|
Ptr p;
|
|
WindowPtr win;
|
|
Rect r;
|
|
ListBounds listDataBounds;
|
|
Point listCellSize;
|
|
ListHandle listHdl;
|
|
Str255 scratchStr;
|
|
DECLARE_UPP(StickyPopupClickLoop,ListClickLoop);
|
|
|
|
*stickyPopup = nil;
|
|
|
|
GetFontName(fontNum, &scratchStr);
|
|
if (!scratchStr[0])
|
|
return paramErr;
|
|
if (fontSize < 4)
|
|
return paramErr;
|
|
|
|
p = NewPtr(STICKY_POPUP_MEM_TEST_SIZE);
|
|
if (!p)
|
|
return memFullErr;
|
|
DisposePtr(p);
|
|
|
|
GetPort(&origPort);
|
|
|
|
popupHdl = (StickyPopupHdl)NewHandleClear(sizeof(StickyPopupRec));
|
|
if (!popupHdl)
|
|
return memFullErr;
|
|
|
|
r.top = r.left = 0;
|
|
r.bottom = r.right = 1;
|
|
win = NewWindow(nil, &r, "\p", false, HaveOSX () ? dBoxProc : altDBoxProc, (WindowPtr)-1, false, 0);
|
|
if (!win)
|
|
{
|
|
err = memFullErr;
|
|
goto BadExit1;
|
|
}
|
|
SetWindowKind (win, dialogKind); /* extra precaution to prevent full-context switch-out */
|
|
|
|
SetPort(GetWindowPort(win));
|
|
TextFont(fontNum);
|
|
TextSize(fontSize);
|
|
|
|
listDataBounds.top = listDataBounds.left = listDataBounds.bottom = 0;
|
|
listDataBounds.right = 1;
|
|
listCellSize.v = listCellSize.h = 0;
|
|
listHdl = LNew(&r, &listDataBounds, listCellSize, 0, win, false, false, false, false);
|
|
SetPort(origPort);
|
|
if (!listHdl)
|
|
{
|
|
err = MemError();
|
|
if (!err)
|
|
err = memFullErr;
|
|
goto BadExit2;
|
|
}
|
|
(**listHdl).selFlags = lOnlyOne;
|
|
INIT_UPP(StickyPopupClickLoop,ListClickLoop);
|
|
(**listHdl).lClickLoop = StickyPopupClickLoopUPP;
|
|
|
|
popupPtr = *popupHdl;
|
|
popupPtr->theWindow = win;
|
|
popupPtr->fontNum = fontNum;
|
|
popupPtr->fontSize = fontSize;
|
|
popupPtr->theList = listHdl;
|
|
popupPtr->maxVisItems = 0;
|
|
popupPtr->isPopped = false;
|
|
popupPtr->refCon = 0;
|
|
|
|
*stickyPopup = popupHdl;
|
|
return noErr;
|
|
|
|
|
|
BadExit2:
|
|
DisposeWindow_(win);
|
|
BadExit1:
|
|
DisposeHandle((Handle)popupHdl);
|
|
BadExit0:
|
|
return err;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* DisposeStickyPopup - disposes of the sticky popup menu passed in stickyPopup.
|
|
*********************************************************************************************/
|
|
|
|
void DisposeStickyPopup(StickyPopupHdl stickyPopup)
|
|
|
|
{
|
|
StickyPopupPtr popupPtr;
|
|
|
|
|
|
if (!stickyPopup)
|
|
return;
|
|
|
|
HLock((Handle)stickyPopup);
|
|
popupPtr = *stickyPopup;
|
|
|
|
if (popupPtr->theList)
|
|
LDispose(popupPtr->theList);
|
|
if (popupPtr->theWindow)
|
|
DisposeWindow_(popupPtr->theWindow);
|
|
DisposeHandle((Handle)stickyPopup);
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* AddEntriesToStickyPopup - add entries to the sticky popup menu passed in stickyPopup.
|
|
* The routine can be used for adding multiple entries. dataPtr points to the start of
|
|
* the data. entryOffset is the offset, in bytes, between entries. entryCount is the
|
|
* number of entries to add to the popup. beforeIndex is the index (1-based) of the entry
|
|
* to insert the new entries in front of; pass -1 for beforeIndex to append to the end.
|
|
*
|
|
* The data being added is always a Pascal-style string. If you just wanted to add one
|
|
* entry, dataPtr would point to the start of the Pascal string (length byte), entryOffset
|
|
* would be ignored (set it to zero), and entryCount would be 1. If you had an array of
|
|
* 16 Str255's to add, you would set dataPtr to the start of the first Pascal string in
|
|
* the array, entryOffset would be 256, and entryCount would be 16.
|
|
*
|
|
* This function doesn't attempt to do any kind of sorting of the items you're adding.
|
|
* Any special sorting, such as alphabetizing, must be handled by the caller. The items
|
|
* in the sticky popup menu must be alphabetized in order for selection by typing to
|
|
* work (i.e. typing G to select the first item in the popup that starts with a G).
|
|
*
|
|
* Unlike AddMenu, this function ignores menu metacharacters.
|
|
*
|
|
* You cannot add entries to a sticky popup menu while the menu is popped (isPopped = true).
|
|
*
|
|
* The function returns noErr if it succeeds, or a non-zero error code if it fails.
|
|
*********************************************************************************************/
|
|
|
|
OSErr AddEntriesToStickyPopup(StickyPopupHdl stickyPopup, StringPtr dataPtr, long entryOffset, short entryCount, short beforeIndex)
|
|
|
|
{
|
|
OSErr err;
|
|
ListHandle theList;
|
|
ListBounds listDataBounds;
|
|
Handle listCellDataHdl;
|
|
Cell curCell;
|
|
short endIndex;
|
|
StringPtr p;
|
|
short origCount, newCount;
|
|
Size origSize, newSize;
|
|
short scratch;
|
|
|
|
|
|
if (!stickyPopup)
|
|
return paramErr;
|
|
if ((**stickyPopup).isPopped)
|
|
return paramErr;
|
|
if (!entryCount || ((entryCount > 1) && !entryOffset) || !dataPtr)
|
|
return paramErr;
|
|
|
|
p = NewPtr(STICKY_POPUP_MEM_TEST_SIZE);
|
|
if (!p)
|
|
return memFullErr;
|
|
DisposePtr(p);
|
|
|
|
theList = (**stickyPopup).theList;
|
|
listCellDataHdl = (Handle)(**theList).cells;
|
|
listDataBounds = (**theList).dataBounds;
|
|
if ((beforeIndex < 1) || (beforeIndex > listDataBounds.bottom))
|
|
beforeIndex = listDataBounds.bottom;
|
|
else
|
|
beforeIndex -= 1;
|
|
|
|
origCount = listDataBounds.bottom;
|
|
scratch = LAddRow(entryCount, beforeIndex, theList);
|
|
listDataBounds = (**theList).dataBounds;
|
|
newCount = listDataBounds.bottom;
|
|
if (newCount != (origCount + entryCount))
|
|
return memFullErr;
|
|
|
|
curCell.h = 0;
|
|
endIndex = beforeIndex + entryCount;
|
|
err = noErr;
|
|
for (p = dataPtr, curCell.v = beforeIndex; !err && (curCell.v < endIndex); p += entryOffset, curCell.v++)
|
|
{
|
|
origSize = GetHandleSize(listCellDataHdl);
|
|
if ((origSize + p[0] + 1) > MAX_LIST_DATA_SIZE)
|
|
{
|
|
err = memFullErr;
|
|
continue;
|
|
}
|
|
LSetCell(p + 1, p[0], curCell, theList);
|
|
newSize = GetHandleSize(listCellDataHdl);
|
|
if (newSize == origSize)
|
|
{
|
|
err = memFullErr;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* RemoveEntriesFromStickyPopup - remove entries from the sticky popup menu passed in
|
|
* stickyPopup. The index (1-based) of the first entry to remove is passed in index
|
|
* and the number of entries removed is passed in count.
|
|
*
|
|
* You cannot remove entries from a sticky popup menu while the menu is popped
|
|
* (isPopped = true).
|
|
*
|
|
* The function returns noErr if it succeeds, or a non-zero error code if it fails.
|
|
*********************************************************************************************/
|
|
|
|
OSErr RemoveEntriesFromStickyPopup(StickyPopupHdl stickyPopup, short index, short count)
|
|
|
|
{
|
|
if (!stickyPopup)
|
|
return paramErr;
|
|
if ((**stickyPopup).isPopped)
|
|
return paramErr;
|
|
if (!count)
|
|
return noErr;
|
|
LDelRow(count, index - 1, (**stickyPopup).theList);
|
|
return noErr;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* ClearListSelection - deselect all cells in the popup's list
|
|
*********************************************************************************************/
|
|
|
|
|
|
static void ClearListSelection(ListHandle lHandle)
|
|
|
|
{
|
|
ListBounds dataBounds;
|
|
short maxV, maxH;
|
|
Cell theCell;
|
|
|
|
|
|
dataBounds = (**lHandle).dataBounds;
|
|
maxV = dataBounds.bottom;
|
|
maxH = dataBounds.right;
|
|
for (theCell.h = dataBounds.left; theCell.h < maxH; theCell.h++)
|
|
for (theCell.v = dataBounds.top; theCell.v < maxV; theCell.v++)
|
|
LSetSelect(false, theCell, lHandle);
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* SelectListItem - move the selection in a sticky popup's list to the specific
|
|
* item specified by itemNo (one-based). You can pass in -1 for itemNo to specify
|
|
* the last item in the list. The popup is specified in stickyPopup; the popup
|
|
* should be currently popped (isPopped = true).
|
|
*********************************************************************************************/
|
|
|
|
static void SelectListItem(StickyPopupHdl stickyPopup, short itemNo)
|
|
|
|
{
|
|
ListHandle theList;
|
|
Cell theCell;
|
|
Boolean selectionExists;
|
|
ListBounds dataBounds;
|
|
|
|
|
|
theList = (**stickyPopup).theList;
|
|
dataBounds = (**theList).dataBounds;
|
|
|
|
if (itemNo == -1)
|
|
itemNo = dataBounds.bottom - 1;
|
|
else
|
|
itemNo--;
|
|
|
|
theCell.v = theCell.h = 0;
|
|
selectionExists = LGetSelect(true, &theCell, theList);
|
|
if (selectionExists && (theCell.v == itemNo))
|
|
return;
|
|
if ((itemNo < dataBounds.top) || (itemNo >= dataBounds.bottom))
|
|
return;
|
|
if (itemNo >= (**stickyPopup).maxVisItems)
|
|
return;
|
|
|
|
if (selectionExists)
|
|
LSetSelect(false, theCell, theList);
|
|
theCell.v = itemNo;
|
|
LSetSelect(true, theCell, theList);
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* AdjustListSelection - move the selection in a sticky popup's list. The
|
|
* popup is specified in stickyPopup; the popup should be currently popped
|
|
* (isPopped = true).
|
|
*********************************************************************************************/
|
|
|
|
static void AdjustListSelection(StickyPopupHdl stickyPopup, long adjustFactor)
|
|
|
|
{
|
|
ListHandle theList;
|
|
Cell theCell;
|
|
Boolean selectionExists;
|
|
short newCellV;
|
|
ListBounds dataBounds;
|
|
|
|
|
|
if (!adjustFactor)
|
|
return;
|
|
|
|
theList = (**stickyPopup).theList;
|
|
dataBounds = (**theList).dataBounds;
|
|
|
|
theCell.v = theCell.h = 0;
|
|
selectionExists = LGetSelect(true, &theCell, theList);
|
|
newCellV = theCell.v + adjustFactor;
|
|
if ((newCellV < dataBounds.top) || (newCellV >= dataBounds.bottom))
|
|
return;
|
|
if (newCellV >= (**stickyPopup).maxVisItems)
|
|
return;
|
|
|
|
if (selectionExists)
|
|
LSetSelect(false, theCell, theList);
|
|
theCell.v = newCellV;
|
|
LSetSelect(true, theCell, theList);
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* SelectItemByString - select an item in the popup stickyPopup based on the
|
|
* string passed in searchStr; the popup should be currently popped
|
|
* (isPopped = true). The string compare used in the search/selection
|
|
* process is insensitive to case and diacritical marks.
|
|
*
|
|
* In order for this to be useful, the items in the sticky popup menu must
|
|
* be in alphabetical order.
|
|
*
|
|
* This routine selects an item, given that you already have the string to
|
|
* search for and select. The actual handling of the user's typing is done
|
|
* in HandleStickyPopupKeyDown and HandleStickyPopupIdle.
|
|
*********************************************************************************************/
|
|
|
|
static void SelectItemByString(StickyPopupHdl stickyPopup, Str255 searchStr)
|
|
|
|
{
|
|
ListHandle theList;
|
|
Str255 typingStr;
|
|
Cell theCell;
|
|
Cell curSelected;
|
|
Boolean selectionExists;
|
|
ListBounds dataBounds;
|
|
Boolean found;
|
|
Str255 cellStr;
|
|
short cellDataLen;
|
|
|
|
|
|
theList = (**stickyPopup).theList;
|
|
BlockMoveData(searchStr, typingStr, searchStr[0] + 1);
|
|
StickyPopupNormalizeString(typingStr);
|
|
dataBounds = (**theList).dataBounds;
|
|
curSelected.v = curSelected.h = 0;
|
|
selectionExists = LGetSelect(true, &curSelected, theList);
|
|
|
|
theCell.v = theCell.h = 0;
|
|
found = false;
|
|
while (!found && (theCell.v < dataBounds.bottom))
|
|
{
|
|
cellDataLen = sizeof(cellStr) - 1;
|
|
LGetCell(cellStr + 1, &cellDataLen, theCell, theList);
|
|
cellStr[0] = cellDataLen;
|
|
StickyPopupNormalizeString(cellStr);
|
|
if (StickyPopupCompareString(typingStr, cellStr, false) <= 0)
|
|
found = true;
|
|
else
|
|
theCell.v++;
|
|
}
|
|
if (!found || (theCell.v == curSelected.v))
|
|
return;
|
|
if (theCell.v >= (**stickyPopup).maxVisItems)
|
|
return;
|
|
|
|
if (selectionExists)
|
|
LSetSelect(false, curSelected, theList);
|
|
LSetSelect(true, theCell, theList);
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* ResetStickyPopupTyping - resets the global variables used to track the user's typing
|
|
* when the user is typing to select an item in a sticky popup menu. This routine
|
|
* should be called any time we need to "start over" with determining what the user
|
|
* is trying to type.
|
|
*
|
|
* LMGetKeyThresh() (word at $18E) returns the delay, in ticks, to wait before
|
|
* initiating auto-repeat typing; it is set in the "Delay Until Repeat" section of
|
|
* the Keyboard control panel. Apple recommends using this value for this kind of
|
|
* update delay. We re-read the value from low memory each time this function is
|
|
* called because the user can change this setting in between calls to
|
|
* StickyPopupSelect. We could set this value just once at the start of each call
|
|
* to StickyPopupSelect, but I do it here so that all the typing-related globals
|
|
* are reset in the same spot.
|
|
*********************************************************************************************/
|
|
|
|
static void ResetStickyPopupTyping(void)
|
|
|
|
{
|
|
StickyPopupKeyUpdateDelay = LMGetKeyThresh();
|
|
StickyPopupTypingStr[0] = 0;
|
|
StickyPopupLastKeyTicks = 0;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* GetStickyPopupMonitorRect - calculates the Rect, in global coordinates, of the monitor
|
|
* on which the sticky popup will be displayed. If the monitor contains the menu bar, that
|
|
* area is subtracted from the Rect before it is returned. The Rect returned in
|
|
* *listMonitorRect contains the global Rect within which it is okay to dispaly. Pass in
|
|
* the coordinates (global) of the top left corner of the popup in top and left; this point
|
|
* is used to decide which monitor to target.
|
|
*********************************************************************************************/
|
|
|
|
static void GetStickyPopupMonitorRect(short top, short left, Rect *listMonitorRect)
|
|
|
|
{
|
|
Point topLeft;
|
|
Rect screenRect;
|
|
short menuBarHeight;
|
|
Boolean hasCQD;
|
|
GDHandle gd;
|
|
Boolean found;
|
|
long response;
|
|
|
|
|
|
menuBarHeight = GetMBarHeight();
|
|
hasCQD = !Gestalt(gestaltQuickdrawVersion, &response) && (response >= gestalt8BitQD);
|
|
if (hasCQD)
|
|
{
|
|
topLeft.v = top;
|
|
topLeft.h = left;
|
|
gd = GetDeviceList();
|
|
found = false;
|
|
while (!found && gd)
|
|
{
|
|
if (TestDeviceAttribute(gd, screenDevice) && PtInRect(topLeft, &(**gd).gdRect))
|
|
found = true;
|
|
else
|
|
gd = GetNextDevice(gd);
|
|
}
|
|
if (!found)
|
|
gd = GetMainDevice();
|
|
screenRect = (**gd).gdRect;
|
|
if (TestDeviceAttribute(gd, mainScreen))
|
|
screenRect.top += menuBarHeight;
|
|
}
|
|
else
|
|
{
|
|
GetQDGlobalsScreenBitsBounds(&screenRect);
|
|
screenRect.top += menuBarHeight;
|
|
}
|
|
*listMonitorRect = screenRect;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* CalcStickyPopupSize - for the popup specified in stickyPopup, makes the following
|
|
* calculations: calculates the height and width for the popup, based on the number
|
|
* of entries and the width of each entry; adjusts the location of the top-left corner
|
|
* of the popup as necessary to insure that the popup stays entirely on the monitor, and
|
|
* also to adjust the popup so that the item specified in curItem shows up in the top left
|
|
* location; sets (**stickyPopup).maxVisItems to the number of items which will fit in the
|
|
* popup without making it go off the bottom of the screen. Pass in the initial values for
|
|
* the top-left corner of the popup in top and left (global coordinates); these values
|
|
* may get adjusted. The height and width of the popup are returned in popupHeight and
|
|
* popupWidth.
|
|
*
|
|
* (jp) Added code to support a "teflon" rect. This is a rectangle of the screen that the
|
|
* sticky popup should never intersect. For example, the already-typed portion of a
|
|
* nickname during nickname completion.
|
|
*********************************************************************************************/
|
|
|
|
static void CalcStickyPopupSize(StickyPopupHdl stickyPopup, short curItem, Rect *teflonRect, short *top, short *left, short *popupHeight, short *popupWidth)
|
|
|
|
{
|
|
GrafPtr origPort;
|
|
ListHandle theList;
|
|
ListPtr listPtr;
|
|
short height;
|
|
short width;
|
|
short maxVisItems;
|
|
short cellCount;
|
|
short cellHeight;
|
|
short cellIndent;
|
|
Cell curCell;
|
|
short curCellWidth, largestCellWidth;
|
|
Str255 cellStr;
|
|
short cellDataLen;
|
|
Rect listMonitorRect,
|
|
stickyPopupRect,
|
|
dstRect;
|
|
short listBottom;
|
|
|
|
|
|
theList = (**stickyPopup).theList;
|
|
listPtr = *theList;
|
|
cellCount = listPtr->dataBounds.bottom;
|
|
cellHeight = listPtr->cellSize.v;
|
|
cellIndent = listPtr->indent.h;
|
|
|
|
GetPort(&origPort);
|
|
SetPort(GetWindowPort((**stickyPopup).theWindow));
|
|
largestCellWidth = 1;
|
|
curCell.h = 0;
|
|
for (curCell.v = 0; curCell.v < cellCount; curCell.v++)
|
|
{
|
|
cellDataLen = sizeof(cellStr) - 1;
|
|
LGetCell(cellStr + 1, &cellDataLen, curCell, theList);
|
|
cellStr[0] = cellDataLen;
|
|
curCellWidth = cellIndent + StringWidth(cellStr);
|
|
if (curCellWidth > largestCellWidth)
|
|
largestCellWidth = curCellWidth;
|
|
}
|
|
SetPort(origPort);
|
|
|
|
height = cellCount * cellHeight;
|
|
width = largestCellWidth + CELL_WIDTH_EXTRA;
|
|
maxVisItems = cellCount;
|
|
|
|
if (curItem != 1)
|
|
*top -= cellHeight * (curItem - 1);
|
|
|
|
GetStickyPopupMonitorRect(*top, *left, &listMonitorRect);
|
|
listMonitorRect.top += 2;
|
|
listMonitorRect.left += 2;
|
|
listMonitorRect.bottom -= 4;
|
|
listMonitorRect.right -= 4;
|
|
|
|
/* (jp) Replaced this with code to allow the popup to "drift" past the teflon rect
|
|
if (*top < listMonitorRect.top)
|
|
*top = listMonitorRect.top;
|
|
if (*left < listMonitorRect.left)
|
|
*left = listMonitorRect.left;
|
|
if ((*left + width) > listMonitorRect.right)
|
|
*left = listMonitorRect.left;
|
|
|
|
listBottom = *top + height;
|
|
if (listBottom > listMonitorRect.bottom)
|
|
{
|
|
*top -= (listBottom - listMonitorRect.bottom);
|
|
if (*top < listMonitorRect.top)
|
|
{
|
|
*top = listMonitorRect.top;
|
|
maxVisItems = (listMonitorRect.bottom - listMonitorRect.top) / cellHeight;
|
|
height = maxVisItems * cellHeight;
|
|
}
|
|
}
|
|
*/
|
|
|
|
// (jp) Here's the new stuff, featuring Teflon (tm). All we're really doing is
|
|
// "drifting" the popup in one direction or another if it intersects with
|
|
// the teflon rect. This is certainly not fool-proof and takes advantage
|
|
// of the way _we_ use sticky popups (as opposed to the way they could
|
|
// conceivably be used as a general UI element).
|
|
SetRect (&stickyPopupRect, *left, *top, *left + width, *top + height);
|
|
if (*top < listMonitorRect.top) {
|
|
*top = listMonitorRect.top;
|
|
SetRect (&stickyPopupRect, *left, *top, *left + width, *top + height);
|
|
if (SectRect (&stickyPopupRect, teflonRect, &dstRect)) {
|
|
*top = teflonRect->bottom + 2;
|
|
SetRect (&stickyPopupRect, *left, *top, *left + width, *top + height);
|
|
}
|
|
}
|
|
|
|
if (*left < listMonitorRect.left) {
|
|
*left = listMonitorRect.left;
|
|
SetRect (&stickyPopupRect, *left, *top, *left + width, *top + height);
|
|
if (SectRect (&stickyPopupRect, teflonRect, &dstRect)) {
|
|
*left = teflonRect->right + 2;
|
|
SetRect (&stickyPopupRect, *left, *top, *left + width, *top + height);
|
|
}
|
|
}
|
|
|
|
if ((*left + width) > listMonitorRect.right) {
|
|
*left = listMonitorRect.right - width;
|
|
SetRect (&stickyPopupRect, *left, *top, *left + width, *top + height);
|
|
if (SectRect (&stickyPopupRect, teflonRect, &dstRect)) {
|
|
*left = teflonRect->left - 2 - width;
|
|
SetRect (&stickyPopupRect, *left, *top, *left + width, *top + height);
|
|
}
|
|
}
|
|
|
|
listBottom = *top + height;
|
|
if (listBottom > listMonitorRect.bottom)
|
|
{
|
|
*top -= (listBottom - listMonitorRect.bottom);
|
|
if (*top < listMonitorRect.top)
|
|
{
|
|
*top = listMonitorRect.top;
|
|
maxVisItems = (listMonitorRect.bottom - listMonitorRect.top) / cellHeight;
|
|
height = maxVisItems * cellHeight;
|
|
}
|
|
SetRect (&stickyPopupRect, *left, *top, *left + width, *top + height);
|
|
if (SectRect (&stickyPopupRect, teflonRect, &dstRect)) {
|
|
*top = teflonRect->top - 2 - height;
|
|
SetRect (&stickyPopupRect, *left, *top, *left + width, *top + height);
|
|
}
|
|
}
|
|
// (jp) End of replacement stuff
|
|
|
|
if ((*left + width) > listMonitorRect.right)
|
|
width = listMonitorRect.right - *left;
|
|
|
|
*popupHeight = height;
|
|
*popupWidth = width;
|
|
(**stickyPopup).maxVisItems = maxVisItems;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* HandleStickyPopupMouseDown - handle a mouseDown event in our internal event loop
|
|
* for sticky popup menus. stickyPopup is the popup being run; the popup should be
|
|
* currently popped (isPopped = true). Pass a pointer to the EventRecord in
|
|
* popupEvent.
|
|
*
|
|
* A Boolean value is returned via validSel. This indicates whether the currently
|
|
* selected cell in the popup's list should be considered to be the user's selection.
|
|
*
|
|
* The function returns true if StickyPopupSelect should terminate its event loop
|
|
* and return a result to the caller, or false if it should keep going and wait for
|
|
* the user to do more actions. If the function returns true and *validSel is set
|
|
* to true, then the currently selected cell in the popup's list is the user's choice.
|
|
*********************************************************************************************/
|
|
|
|
static Boolean HandleStickyPopupMouseDown(StickyPopupHdl stickyPopup, EventRecord* popupEvent, Boolean *validSel)
|
|
|
|
{
|
|
GrafPtr origPort;
|
|
ListHandle theList;
|
|
Point clickLoc;
|
|
Point endMouseLoc;
|
|
WindowPtr theWindow;
|
|
Cell origSel;
|
|
Cell newSel;
|
|
Boolean selectionExists;
|
|
|
|
|
|
*validSel = false;
|
|
ResetStickyPopupTyping();
|
|
|
|
theList = (**stickyPopup).theList;
|
|
theWindow = (**stickyPopup).theWindow;
|
|
clickLoc = popupEvent->where;
|
|
if (!MouseLocInList(clickLoc, stickyPopup))
|
|
return true;
|
|
origSel.v = origSel.h = 0;
|
|
selectionExists = LGetSelect(true, &origSel, theList);
|
|
GetPort(&origPort);
|
|
SetPort(GetWindowPort(theWindow));
|
|
GlobalToLocal(&clickLoc);
|
|
SetPort(origPort);
|
|
LClick(clickLoc, popupEvent->modifiers, theList);
|
|
LSetDrawingMode(true, theList);
|
|
GetMouse(&endMouseLoc);
|
|
LocalToGlobal(&endMouseLoc);
|
|
newSel.v = newSel.h = 0;
|
|
selectionExists = LGetSelect(true, &newSel, theList);
|
|
if (!MouseLocInList(endMouseLoc, stickyPopup))
|
|
{
|
|
if (selectionExists)
|
|
LSetSelect(false, newSel, theList);
|
|
LSetSelect(true, origSel, theList);
|
|
return false;
|
|
}
|
|
*validSel = selectionExists;
|
|
return true;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* HandleStickyPopupKeyDown - handle a keyDown event in our internal event loop
|
|
* for sticky popup menus. stickyPopup is the popup being run; the popup should be
|
|
* currently popped (isPopped = true). Pass a pointer to the EventRecord in
|
|
* popupEvent.
|
|
*
|
|
* A Boolean value is returned via validSel. This indicates whether the currently
|
|
* selected cell in the popup's list should be considered to be the user's selection.
|
|
*
|
|
* The Boolean parameter allowTyping indicates whether or not the user is allowed to
|
|
* select entries in the popup's list by typing characters (i.e. typing a G to select
|
|
* the first item in the list that begins with a G). In order for this to work, the
|
|
* items in the popup's list must be in alphabetical order. If you pass in false for
|
|
* allowTyping, the user can still use the arrow keys to move the selection up and
|
|
* down, and use Return or Enter to accept the current selection, or Command-Period or
|
|
* Escape to cancel the popup.
|
|
*
|
|
* The function returns true if StickyPopupSelect should terminate its event loop
|
|
* and return a result to the caller, or false if it should keep going and wait for
|
|
* the user to do more actions. If the function returns true and *validSel is set
|
|
* to true, then the currently selected cell in the popup's list is the user's choice.
|
|
*********************************************************************************************/
|
|
|
|
static Boolean HandleStickyPopupKeyDown(StickyPopupHdl stickyPopup, EventRecord* popupEvent, Boolean *validSel, Boolean allowTyping)
|
|
|
|
{
|
|
unsigned char keyChar; /* ASCII of keystroke */
|
|
unsigned char keyCode; /* virtual key code of keystroke */
|
|
short adjust;
|
|
|
|
|
|
*validSel = false;
|
|
|
|
keyChar = popupEvent->message & charCodeMask;
|
|
keyCode = (popupEvent->message & keyCodeMask) >> 8;
|
|
|
|
/* if the user pressed Command-Period, Escape, Backspace/Delete, the Clear key on the numeric
|
|
keypad, or the Forward Delete key on the extended keyboard, then cancel the popup */
|
|
if (
|
|
((popupEvent->modifiers & cmdKey) && (keyChar == PERIOD_CHAR_CODE))
|
|
|| (keyCode == ESCAPE_KEY_CODE)
|
|
|| (keyChar == BACKSPACE_CHAR_CODE)
|
|
|| (keyCode == CLEAR_KEY_CODE)
|
|
|| (keyChar == FWD_DEL_CHAR_CODE)
|
|
)
|
|
{
|
|
ResetStickyPopupTyping();
|
|
return true;
|
|
}
|
|
|
|
/* if the uesr pressed Return or Enter, then exit the popup with the current selection */
|
|
if ((keyChar == RETURN_CHAR_CODE) || (keyChar == ENTER_CHAR_CODE))
|
|
{
|
|
*validSel = true;
|
|
ResetStickyPopupTyping();
|
|
return true;
|
|
}
|
|
|
|
/* ignore Left Arrow and Right Arrow, and reset the typing globals */
|
|
if ((keyChar == LEFT_ARROW_CHAR_CODE) || (keyChar == RIGHT_ARROW_CHAR_CODE))
|
|
{
|
|
ResetStickyPopupTyping();
|
|
return false;
|
|
}
|
|
|
|
/* if the user pressed Up Arrow or Down Arrow, use them to navigate up and down the list */
|
|
if ((keyChar == UP_ARROW_CHAR_CODE) || (keyChar == DOWN_ARROW_CHAR_CODE))
|
|
{
|
|
if (keyChar == UP_ARROW_CHAR_CODE)
|
|
adjust = -1;
|
|
else
|
|
adjust = 1;
|
|
AdjustListSelection(stickyPopup, adjust);
|
|
ResetStickyPopupTyping();
|
|
return false;
|
|
}
|
|
|
|
/* if the user pressed the Home or End keys, move the selection to the top or bottom of the list, respectively */
|
|
if (keyChar == HOME_CHAR_CODE)
|
|
{
|
|
SelectListItem(stickyPopup, 1);
|
|
ResetStickyPopupTyping();
|
|
return false;
|
|
}
|
|
if (keyChar == END_CHAR_CODE)
|
|
{
|
|
SelectListItem(stickyPopup, -1);
|
|
ResetStickyPopupTyping();
|
|
return false;
|
|
}
|
|
|
|
if (!allowTyping)
|
|
return false;
|
|
|
|
/* if the user tried a Command key while the popup was running, beep and ignore it, and reset the typing globals */
|
|
if (popupEvent->modifiers & cmdKey)
|
|
{
|
|
SysBeep(5);
|
|
ResetStickyPopupTyping();
|
|
return false;
|
|
}
|
|
|
|
/* Accumulate keystrokes for purposes of selecting an item in the popup via typing. The actual selection
|
|
of the item is performed by HandleStickyPopupIdle. */
|
|
StickyPopupLastKeyTicks = popupEvent->when;
|
|
if (StickyPopupTypingStr[0] < 255)
|
|
StickyPopupTypingStr[++StickyPopupTypingStr[0]] = keyChar;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* HandleStickyPopupIdle - handle a null event in our internal event loop
|
|
* for sticky popup menus. stickyPopup is the popup being run; the popup should be
|
|
* currently popped (isPopped = true).
|
|
*
|
|
* This is where we check to see if we should adjust the currently selected
|
|
* cell in the popup's list based on what the user has been typing.
|
|
*
|
|
* If a MenuHook proc is present (long at $A30), it does not get called.
|
|
*********************************************************************************************/
|
|
|
|
static void HandleStickyPopupIdle(StickyPopupHdl stickyPopup)
|
|
|
|
{
|
|
if (!StickyPopupTypingStr[0])
|
|
return;
|
|
if (TickCount() < (StickyPopupLastKeyTicks + StickyPopupKeyUpdateDelay))
|
|
return;
|
|
SelectItemByString(stickyPopup, StickyPopupTypingStr);
|
|
ResetStickyPopupTyping();
|
|
}
|
|
|
|
|
|
/*********************************************************************************************
|
|
* StickyPopupSelect - display a sticky popup menu and allow the user to make a
|
|
* selection. This routine is comparable to the Mac toolbox routine PopUpMenuSelect.
|
|
*
|
|
* stickyPopup is a StickyPopupHdl for a sticky popup you have already created. Before
|
|
* calling this routine, call NewStickyPopup to create the popup, and
|
|
* AddEntriesToStickyPopup to add items to the popup.
|
|
*
|
|
* The top-left corner of the popup is placed in the location specified by top and
|
|
* left (global coordinates). The popup comes up with the item whose index (1-based) is
|
|
* specified in curItem as the currently selected item.
|
|
*
|
|
* The Boolean parameter allowTyping indicates whether or not the user is allowed to
|
|
* select entries in the popup's list by typing characters (i.e. typing a G to select
|
|
* the first item in the list that begins with a G). In order for this to work, the
|
|
* items in the popup's list must be in alphabetical order. If you pass in false for
|
|
* allowTyping, the user can still use the arrow keys to move the selection up and
|
|
* down, and use Return or Enter to accept the current selection, or Command-Period or
|
|
* Escape to cancel the popup.
|
|
*
|
|
* The function returns a longword to indicate the user's selection. The high word of
|
|
* this value is always zero. The low word is zero if the user made no selection (i.e.
|
|
* canceled the popup or let go of the mouse outside of the popup's boundaries). If the
|
|
* user made a valid selection, the low word contains the index (1-based) of the item
|
|
* which the user selected.
|
|
*
|
|
* This function is designed to work the same as PopUpMenuSelect (and to handle the
|
|
* parameters the same way), except that this function has an additional parameter for
|
|
* allowTyping, and it doesn't return a menu ID in the high word of the function result.
|
|
*
|
|
* If a MenuHook proc is present (ProcPtr at $A30), it does not get called.
|
|
*********************************************************************************************/
|
|
|
|
long StickyPopupSelect(StickyPopupHdl stickyPopup, short top, short left, short curItem, Boolean allowTyping, Rect *teflonRect)
|
|
|
|
{
|
|
long result;
|
|
StickyPopupPtr popupPtr;
|
|
SignedByte origState;
|
|
WindowPtr popupWindow;
|
|
ListHandle theList;
|
|
Point cellSize;
|
|
Cell theCell;
|
|
short popupHeight, popupWidth;
|
|
Boolean done, validSel;
|
|
EventRecord popupEvent;
|
|
|
|
|
|
if (!stickyPopup)
|
|
return 0;
|
|
if ((**stickyPopup).isPopped || CurStickyPopup)
|
|
return 0;
|
|
|
|
origState = HGetState((Handle)stickyPopup);
|
|
MoveHHi((Handle)stickyPopup); /* ignore failure */
|
|
HLock((Handle)stickyPopup);
|
|
popupPtr = *stickyPopup;
|
|
popupWindow = popupPtr->theWindow;
|
|
theList = popupPtr->theList;
|
|
|
|
popupPtr->isPopped = true;
|
|
CurStickyPopup = stickyPopup;
|
|
ResetStickyPopupTyping();
|
|
|
|
CalcStickyPopupSize(stickyPopup, curItem, teflonRect, &top, &left, &popupHeight, &popupWidth);
|
|
MoveWindow(popupWindow, left, top, false);
|
|
SizeWindow(popupWindow, popupWidth, popupHeight, false);
|
|
LSize(popupWidth, popupHeight, theList);
|
|
cellSize.v = (**theList).cellSize.v;
|
|
cellSize.h = popupWidth;
|
|
LCellSize(cellSize, theList);
|
|
ClearListSelection(theList);
|
|
theCell.v = curItem - 1;
|
|
theCell.h = 0;
|
|
LSetSelect(true, theCell, theList);
|
|
BringToFront(popupWindow);
|
|
ShowHide(popupWindow, true);
|
|
LSetDrawingMode(true, theList);
|
|
LUpdate(MyGetPortVisibleRegion(GetWindowPort(popupWindow)), theList);
|
|
|
|
done = false;
|
|
validSel = false;
|
|
while (!done)
|
|
{
|
|
if (!GetNextEvent(STICKY_SELECT_EVENT_MASK, &popupEvent))
|
|
HandleStickyPopupIdle(stickyPopup);
|
|
else switch (popupEvent.what)
|
|
{
|
|
case mouseDown:
|
|
if (HandleStickyPopupMouseDown(stickyPopup, &popupEvent, &validSel))
|
|
done = true;
|
|
break;
|
|
case keyDown:
|
|
case autoKey:
|
|
if (HandleStickyPopupKeyDown(stickyPopup, &popupEvent, &validSel, allowTyping))
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
if (validSel)
|
|
{
|
|
theCell.v = theCell.h = 0;
|
|
LGetSelect(true, &theCell, theList);
|
|
result = theCell.v + 1;
|
|
}
|
|
else
|
|
result = 0;
|
|
|
|
LSetDrawingMode(false, theList);
|
|
ShowHide(popupWindow, false);
|
|
|
|
popupPtr->maxVisItems = 0;
|
|
popupPtr->isPopped = false;
|
|
CurStickyPopup = nil;
|
|
HSetState((Handle)stickyPopup, origState);
|
|
|
|
return result;
|
|
}
|