зеркало из https://github.com/mozilla/pjs.git
970 строки
26 KiB
C++
970 строки
26 KiB
C++
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|||
|
*
|
|||
|
* The contents of this file are subject to the Netscape Public License
|
|||
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|||
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|||
|
* http://www.mozilla.org/NPL/
|
|||
|
*
|
|||
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|||
|
* for the specific language governing rights and limitations under the
|
|||
|
* NPL.
|
|||
|
*
|
|||
|
* The Initial Developer of this code under the NPL is Netscape
|
|||
|
* Communications Corporation. Portions created by Netscape are
|
|||
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|||
|
* Reserved.
|
|||
|
*/
|
|||
|
|
|||
|
|
|||
|
/*
|
|||
|
Features (not including the look/feel/behavior from inherited classes):
|
|||
|
|
|||
|
(1) LMenu based. This allows flexible use of the class for both
|
|||
|
resource-based menus and menus built dynamically. Additionally,
|
|||
|
the benefits of attaching commands to menu items (using Mcmd and/or
|
|||
|
dynamically) is gained.
|
|||
|
(2) The "current item" can either be marked or not. If marking is specified,
|
|||
|
a mark character can be specified as well.
|
|||
|
(3) Popup or Popdown behavior.
|
|||
|
(4) A delay before popup value is supported. This gives rise to the "quick click"
|
|||
|
feature described below.
|
|||
|
(5) Quick clicks are supported (ala Greg's Browser and others). This allows a
|
|||
|
value or command to be specified such that if the button is released before
|
|||
|
the popup delay value, either the value will change to the specified value
|
|||
|
or the specified command will be sent. Note that if the quick click is
|
|||
|
command-based, the command does not have to coincide with a menu command
|
|||
|
for a menu item (it can be any arbitrary command). Quick click commands are
|
|||
|
supported even when there is no menu (as long as the quick click command
|
|||
|
is an enabled command).
|
|||
|
(6) Use of factory methods for menu management allows subclasses to add menu
|
|||
|
caching, etc.
|
|||
|
(7) Use of template methods (or hooks) for handling "quick clicks" and value
|
|||
|
changes allow subclasses to further customize the behavior. For example,
|
|||
|
a subclass may implement a "smart" button which will perform an action
|
|||
|
directly (instead of relying on broadcaster/listener delegation) by overriding
|
|||
|
HandleNewValue. For example, CGuideMenuPopup (which is a subclass of
|
|||
|
CPatternButtonPopup) can be inserted into a PPob window resource and
|
|||
|
It Will Just Work<EFBFBD> without additional code to link broadcasters to listeners
|
|||
|
and additional ListenToMessage cases sprinkled about.
|
|||
|
(8) Fully configurable through Constructor PPobs and accessor methods.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
CAVEAT: Although we do correctly save and restore the system font, if Mercutio
|
|||
|
MDEF is being used in the app, then it must be used in all menus which
|
|||
|
change the system font in order to not mess up the system font. It is
|
|||
|
probably a wise idea to use Mercutio MDEF in *all* menus throughout the
|
|||
|
app to be safe.
|
|||
|
*/
|
|||
|
|
|||
|
#include "CPatternButtonPopup.h"
|
|||
|
|
|||
|
#include <UMemoryMgr.h>
|
|||
|
|
|||
|
#include "StSetBroadcasting.h"
|
|||
|
#include "CDrawingState.h"
|
|||
|
#include "CApplicationEventAttachment.h"
|
|||
|
#include "CTargetedUpdateMenuRegistry.h"
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> CPatternButtonPopup
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// Stream-based ctor
|
|||
|
|
|||
|
CPatternButtonPopup::CPatternButtonPopup(
|
|||
|
LStream* inStream)
|
|||
|
|
|||
|
: mMenu(nil),
|
|||
|
|
|||
|
mPopupMenuID(0),
|
|||
|
mPopupTextTraitsID(0),
|
|||
|
mInitialCurrentItem(0),
|
|||
|
mTicksBeforeDisplayingPopup(9),
|
|||
|
mQuickClickValueOrCommand(0),
|
|||
|
mQuickClickIsCommandBased(false),
|
|||
|
mResourceBasedMenu(false),
|
|||
|
mPopdownBehavior(false),
|
|||
|
mMarkCurrentItem(false),
|
|||
|
mMarkCharacter(0),
|
|||
|
mDetachResource(false),
|
|||
|
|
|||
|
mOwnsMenu(false),
|
|||
|
mPopUpMenuSelectWasCalled(true), // Start out true to ensure proper behavior
|
|||
|
// even if super::HandlePopupMenuSelect()
|
|||
|
// is called instead of our own
|
|||
|
|
|||
|
super(inStream)
|
|||
|
{
|
|||
|
// Read in the data from the stream
|
|||
|
|
|||
|
ThrowIfNil_(inStream);
|
|||
|
|
|||
|
LStream& theStream = *inStream;
|
|||
|
|
|||
|
theStream >> mPopupMenuID;
|
|||
|
theStream >> mPopupTextTraitsID;
|
|||
|
theStream >> mInitialCurrentItem;
|
|||
|
theStream >> mTicksBeforeDisplayingPopup;
|
|||
|
theStream >> mQuickClickValueOrCommand;
|
|||
|
theStream >> mQuickClickIsCommandBased;
|
|||
|
theStream >> mResourceBasedMenu;
|
|||
|
theStream >> mPopdownBehavior;
|
|||
|
theStream >> mMarkCurrentItem;
|
|||
|
theStream >> mMarkCharacter;
|
|||
|
theStream >> mDetachResource;
|
|||
|
|
|||
|
// Ignore the ticks before displaying popup value specified in the resource.
|
|||
|
// That was a mistake.
|
|||
|
|
|||
|
mTicksBeforeDisplayingPopup = GetDblTime();
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> ~CPatternButtonPopup
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// dtor
|
|||
|
|
|||
|
CPatternButtonPopup::~CPatternButtonPopup()
|
|||
|
{
|
|||
|
CPatternButtonPopup::EliminatePreviousMenu();
|
|||
|
}
|
|||
|
|
|||
|
#pragma mark -
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> FinishCreateSelf
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::FinishCreateSelf()
|
|||
|
{
|
|||
|
super::FinishCreateSelf();
|
|||
|
|
|||
|
MakeNewMenu();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#pragma mark -
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> GetMenu
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// The menu factory methods (GetMenu, OwnsMenu, SetMenu, AdoptMenu,
|
|||
|
// MakeNewMenu, and EliminatePreviousMenu) *must* be overriden as a group.
|
|||
|
// The main reason for overriding this group of methods would be to
|
|||
|
// do some form of menu caching.
|
|||
|
|
|||
|
LMenu*
|
|||
|
CPatternButtonPopup::GetMenu() const
|
|||
|
{
|
|||
|
return mMenu;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> OwnsMenu
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// The menu factory methods (GetMenu, OwnsMenu, SetMenu, AdoptMenu,
|
|||
|
// MakeNewMenu, and EliminatePreviousMenu) *must* be overriden as a group.
|
|||
|
// The main reason for overriding this group of methods would be to
|
|||
|
// do some form of menu caching.
|
|||
|
|
|||
|
Boolean
|
|||
|
CPatternButtonPopup::OwnsMenu() const
|
|||
|
{
|
|||
|
return mOwnsMenu;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> SetMenu
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// The menu factory methods (GetMenu, OwnsMenu, SetMenu, AdoptMenu,
|
|||
|
// MakeNewMenu, and EliminatePreviousMenu) *must* be overriden as a group.
|
|||
|
// The main reason for overriding this group of methods would be to
|
|||
|
// do some form of menu caching.
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::SetMenu(LMenu* inMenu)
|
|||
|
{
|
|||
|
EliminatePreviousMenu();
|
|||
|
|
|||
|
mMenu = inMenu;
|
|||
|
mOwnsMenu = false;
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> AdoptMenu
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// The menu factory methods (GetMenu, OwnsMenu, SetMenu, AdoptMenu,
|
|||
|
// MakeNewMenu, and EliminatePreviousMenu) *must* be overriden as a group.
|
|||
|
// The main reason for overriding this group of methods would be to
|
|||
|
// do some form of menu caching.
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::AdoptMenu(
|
|||
|
LMenu* inMenuToAdopt)
|
|||
|
{
|
|||
|
EliminatePreviousMenu();
|
|||
|
|
|||
|
mMenu = inMenuToAdopt;
|
|||
|
mOwnsMenu = true;
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> MakeNewMenu
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// The menu factory methods (GetMenu, OwnsMenu, SetMenu, AdoptMenu,
|
|||
|
// MakeNewMenu, and EliminatePreviousMenu) *must* be overriden as a group.
|
|||
|
// The main reason for overriding this group of methods would be to
|
|||
|
// do some form of menu caching.
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::MakeNewMenu()
|
|||
|
{
|
|||
|
// Create popup menu if appropriate
|
|||
|
|
|||
|
if (mResourceBasedMenu)
|
|||
|
{
|
|||
|
if (mPopupMenuID)
|
|||
|
{
|
|||
|
// Resource-based menu (via ::GetMenu)
|
|||
|
|
|||
|
AdoptMenu(new LMenu(mPopupMenuID));
|
|||
|
mValue = 0;
|
|||
|
SetPopupMinMaxValues();
|
|||
|
|
|||
|
// Detach the resource as specified
|
|||
|
|
|||
|
if (mDetachResource)
|
|||
|
{
|
|||
|
::DetachResource((Handle) GetMenu()->GetMacMenuH());
|
|||
|
}
|
|||
|
|
|||
|
// For resource-based menus, set the initial current item
|
|||
|
|
|||
|
StSetBroadcasting setBroadcasting(this, false);
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
SetValue(mInitialCurrentItem);
|
|||
|
}
|
|||
|
catch (const CValueRangeException&) { }
|
|||
|
catch (const CAttemptToSetDisabledValueException&) { }
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (mPopupMenuID)
|
|||
|
{
|
|||
|
// Dynamic menu (via ::NewMenu)
|
|||
|
|
|||
|
AdoptMenu(new LMenu(mPopupMenuID, "\p"));
|
|||
|
mValue = 0;
|
|||
|
SetPopupMinMaxValues();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> EliminatePreviousMenu
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// The menu factory methods (GetMenu, OwnsMenu, SetMenu, AdoptMenu,
|
|||
|
// MakeNewMenu, and EliminatePreviousMenu) *must* be overriden as a group.
|
|||
|
// The main reason for overriding this group of methods would be to
|
|||
|
// do some form of menu caching.
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::EliminatePreviousMenu()
|
|||
|
{
|
|||
|
if (mMenu)
|
|||
|
{
|
|||
|
if (mOwnsMenu)
|
|||
|
{
|
|||
|
delete mMenu;
|
|||
|
}
|
|||
|
|
|||
|
mMenu = nil;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#pragma mark -
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> TrackHotSpot
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
|
|||
|
Boolean
|
|||
|
CPatternButtonPopup::TrackHotSpot(
|
|||
|
Int16 inHotSpot,
|
|||
|
Point inPoint,
|
|||
|
Int16 inModifiers)
|
|||
|
{
|
|||
|
Boolean pointInHotSpot;
|
|||
|
// The value we set mPopUpMenuSelectWasCalled
|
|||
|
// is not important. We just want to restore
|
|||
|
// the value when this function returns.
|
|||
|
StValueChanger<Boolean> theValueChanger(mPopUpMenuSelectWasCalled, true);
|
|||
|
|
|||
|
// If no menu, then allow normal tracking and
|
|||
|
// perform a quick click if button is released
|
|||
|
// in the hot spot.
|
|||
|
if (!GetMenu() || !GetMenu()->GetMacMenuH())
|
|||
|
{
|
|||
|
pointInHotSpot = super::TrackHotSpot(inHotSpot, inPoint, inModifiers);
|
|||
|
|
|||
|
if (pointInHotSpot)
|
|||
|
{
|
|||
|
if (mQuickClickIsCommandBased)
|
|||
|
{
|
|||
|
HandleQuickClick();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return pointInHotSpot;
|
|||
|
}
|
|||
|
|
|||
|
Point currPt = inPoint;
|
|||
|
UInt32 endTicks = sWhenLastMouseDown + mTicksBeforeDisplayingPopup;
|
|||
|
|
|||
|
// For the initial mouse down, the
|
|||
|
// mouse is currently inside the HotSpot
|
|||
|
// when it was previously outside
|
|||
|
Boolean currInside = true;
|
|||
|
Boolean prevInside = false;
|
|||
|
HotSpotAction(inHotSpot, currInside, prevInside);
|
|||
|
|
|||
|
// If the controlKey is down then we bypass quick click tracking in favor of
|
|||
|
// the popup menu. Additionally, we don't even bother tracking unless
|
|||
|
// mQuickClickValueOrCommand is non-zero
|
|||
|
|
|||
|
if (!CApplicationEventAttachment::CurrentEventHasModifiers(controlKey) && mQuickClickValueOrCommand)
|
|||
|
{
|
|||
|
// Track the mouse while it is down
|
|||
|
Point currPt = inPoint;
|
|||
|
while (::StillDown() && ::TickCount() < endTicks)
|
|||
|
{
|
|||
|
::GetMouse(&currPt); // Must keep track if mouse moves from
|
|||
|
prevInside = currInside; // In-to-Out or Out-To-In
|
|||
|
currInside = PointInHotSpot(currPt, inHotSpot);
|
|||
|
|
|||
|
if (!currInside)
|
|||
|
{
|
|||
|
// Reset endTicks if mouse moves outside
|
|||
|
endTicks = ::TickCount() + mTicksBeforeDisplayingPopup;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Quick Click tracking
|
|||
|
if (!::StillDown())
|
|||
|
{
|
|||
|
// Set button in "up" state
|
|||
|
HotSpotAction(inHotSpot, false, true);
|
|||
|
|
|||
|
// Check if MouseUp occurred in HotSpot
|
|||
|
::GetMouse(&currPt);
|
|||
|
pointInHotSpot = PointInHotSpot(currPt, inHotSpot);
|
|||
|
|
|||
|
if (pointInHotSpot)
|
|||
|
{
|
|||
|
HandleQuickClick();
|
|||
|
}
|
|||
|
|
|||
|
return pointInHotSpot;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
HotSpotAction(inHotSpot, currInside, prevInside);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!::StillDown())
|
|||
|
{
|
|||
|
// Set button in "up" state
|
|||
|
HotSpotAction(inHotSpot, false, true);
|
|||
|
|
|||
|
// Check if MouseUp occurred in HotSpot
|
|||
|
::GetMouse(&currPt);
|
|||
|
pointInHotSpot = PointInHotSpot(currPt, inHotSpot);
|
|||
|
|
|||
|
if (pointInHotSpot)
|
|||
|
{
|
|||
|
HandleQuickClick();
|
|||
|
}
|
|||
|
|
|||
|
return pointInHotSpot;
|
|||
|
}
|
|||
|
|
|||
|
// We tracked inside the button longer than
|
|||
|
// mTicksBeforeDisplayingPopup and the mouse is
|
|||
|
// still down, so we now skip normal tracking
|
|||
|
// to display the popup menu
|
|||
|
Int16 menuID = 0;
|
|||
|
Int16 menuItem = GetValue();
|
|||
|
Point popLocation;
|
|||
|
|
|||
|
GetPopupMenuPosition(popLocation);
|
|||
|
|
|||
|
// Handle user interaction with the menu
|
|||
|
HandlePopupMenuSelect(popLocation, mPopdownBehavior ? 1 : menuItem, menuID, menuItem);
|
|||
|
|
|||
|
if (!mPopUpMenuSelectWasCalled && mQuickClickValueOrCommand)
|
|||
|
{
|
|||
|
// If the popup menu was not displayed,
|
|||
|
// then we will check once more if MouseUp
|
|||
|
// occurred in HotSpot so we can do
|
|||
|
// the quick click
|
|||
|
::GetMouse(&currPt);
|
|||
|
pointInHotSpot = PointInHotSpot(currPt, inHotSpot);
|
|||
|
|
|||
|
if (pointInHotSpot)
|
|||
|
{
|
|||
|
HandleQuickClick();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Rect localFrame;
|
|||
|
Point mousePt;
|
|||
|
|
|||
|
FocusDraw();
|
|||
|
CalcLocalFrameRect(localFrame);
|
|||
|
::GetMouse(&mousePt);
|
|||
|
|
|||
|
mMouseInFrame = ::PtInRect(mousePt, &localFrame);
|
|||
|
|
|||
|
// Set the new item, if specified. NOTE: We set the
|
|||
|
// value here so that this control acts the same as
|
|||
|
// LStdControl and LGAPopup
|
|||
|
if (menuItem > 0)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SetValue(menuItem);
|
|||
|
}
|
|||
|
catch (const CValueRangeException&) { }
|
|||
|
catch (const CAttemptToSetDisabledValueException&) { }
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Set button in "up" state
|
|||
|
HotSpotAction(inHotSpot, false, true);
|
|||
|
}
|
|||
|
|
|||
|
return (menuItem > 0);
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> HandlePopupMenuSelect
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// Handle user interaction with the popup menu
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::HandlePopupMenuSelect(
|
|||
|
Point inPopupLoc,
|
|||
|
Int16 inCurrentItem,
|
|||
|
Int16& outMenuID,
|
|||
|
Int16& outMenuItem)
|
|||
|
{
|
|||
|
ThrowIfNil_(GetMenu());
|
|||
|
ThrowIfNil_(GetMenu()->GetMacMenuH());
|
|||
|
|
|||
|
mPopUpMenuSelectWasCalled = false;
|
|||
|
|
|||
|
// Handle the actual insertion into the hierarchical menubar.
|
|||
|
//
|
|||
|
// From Apple Sample Code (PopupMenuWithCurFont.c):
|
|||
|
// "Believe it or not, it's important to insert
|
|||
|
// the menu before diddling the font characteristics."
|
|||
|
//
|
|||
|
// Note that this is also what LGAPopup.cp does. Unfortunately,
|
|||
|
// neither this nor the StMercutioMDEFTextState seems to help
|
|||
|
// with the font metrics problems we see with Mercurtio MDEF.
|
|||
|
|
|||
|
::InsertMenu(GetMenu()->GetMacMenuH(), hierMenu);
|
|||
|
|
|||
|
Int16 saveFont = ::LMGetSysFontFam();
|
|||
|
Int16 saveSize = ::LMGetSysFontSize();
|
|||
|
|
|||
|
StMercutioMDEFTextState theMercutioMDEFTextState;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
// Reconfigure the system font so that the menu will be drawn in our desired
|
|||
|
// font and size.
|
|||
|
|
|||
|
if (mPopupTextTraitsID)
|
|||
|
{
|
|||
|
FocusDraw();
|
|||
|
|
|||
|
TextTraitsH traitsH = UTextTraits::LoadTextTraits(mPopupTextTraitsID);
|
|||
|
|
|||
|
if (traitsH)
|
|||
|
{
|
|||
|
// Bug #64133 kellys
|
|||
|
// If setting to application font, get the application font for current script
|
|||
|
if((**traitsH).fontNumber == 1)
|
|||
|
::LMSetSysFontFam ( ::GetScriptVariable(::FontToScript(1), smScriptAppFond) );
|
|||
|
else
|
|||
|
::LMSetSysFontFam ( (**traitsH).fontNumber );
|
|||
|
::LMSetSysFontSize((**traitsH).size);
|
|||
|
::LMSetLastSPExtra(-1L);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Adjust the contents of the menu
|
|||
|
|
|||
|
AdjustMenuContents();
|
|||
|
|
|||
|
// Setup the currently selected menu item
|
|||
|
|
|||
|
SetupCurrentMenuItem(GetMenu()->GetMacMenuH(), GetValue());
|
|||
|
|
|||
|
// Call PopupMenuSelect and wait for it to return
|
|||
|
|
|||
|
Int32 result = 0;
|
|||
|
|
|||
|
if (::StillDown())
|
|||
|
{
|
|||
|
mPopUpMenuSelectWasCalled = true;
|
|||
|
|
|||
|
result = ::PopUpMenuSelect(
|
|||
|
GetMenu()->GetMacMenuH(),
|
|||
|
inPopupLoc.v,
|
|||
|
inPopupLoc.h,
|
|||
|
inCurrentItem);
|
|||
|
}
|
|||
|
|
|||
|
// Extract the values from the returned result these are then passed
|
|||
|
// back out to the caller
|
|||
|
|
|||
|
outMenuID = HiWord(result);
|
|||
|
outMenuItem = LoWord(result);
|
|||
|
}
|
|||
|
catch (...)
|
|||
|
{
|
|||
|
// Ignore errors
|
|||
|
}
|
|||
|
|
|||
|
// Restore the system font
|
|||
|
|
|||
|
::LMSetSysFontFam(saveFont);
|
|||
|
::LMSetSysFontSize(saveSize);
|
|||
|
::LMSetLastSPExtra(-1L);
|
|||
|
|
|||
|
// Finally get the menu removed
|
|||
|
|
|||
|
::DeleteMenu(mPopupMenuID);
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> HotSpotResult
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// Note that actual hot spot result processing occurs in TrackHotSpot. This
|
|||
|
// is because the information necessary to perform the correct action is
|
|||
|
// only available while tracking.
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::HotSpotResult(Int16 inHotSpot)
|
|||
|
{
|
|||
|
if (!IsBehaviourToggle())
|
|||
|
{
|
|||
|
// Make sure that non-toggle buttons return to the "up" state
|
|||
|
|
|||
|
HotSpotAction(inHotSpot, false, true);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#pragma mark -
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> AdjustMenuContents
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// Do stuff to the menu (add or remove menu items). The default implementation
|
|||
|
// is intentionally a noop. Override to add interesting behavior.
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::AdjustMenuContents()
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> SetupCurrentMenuItem
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// Mark new item and unmark old item
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::SetupCurrentMenuItem(MenuHandle inMenuH, Int16 inCurrentItem)
|
|||
|
{
|
|||
|
ThrowIfNil_(inMenuH);
|
|||
|
|
|||
|
if (mMarkCurrentItem)
|
|||
|
{
|
|||
|
if (GetValue() != inCurrentItem)
|
|||
|
{
|
|||
|
Int16 oldItem = GetValue();
|
|||
|
|
|||
|
if (oldItem > 0)
|
|||
|
{
|
|||
|
::SetItemMark(inMenuH, oldItem, noMark);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (mMarkCharacter && (inCurrentItem > 0))
|
|||
|
{
|
|||
|
::SetItemMark(inMenuH, inCurrentItem, mMarkCharacter);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
::SetItemMark(inMenuH, inCurrentItem, noMark);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> GetPopupMenuPosition
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// Get the position for displaying the popup menu in global coordinates
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::GetPopupMenuPosition(Point &outPopupLoc)
|
|||
|
{
|
|||
|
Rect popupRect;
|
|||
|
|
|||
|
CalcLocalFrameRect(popupRect);
|
|||
|
|
|||
|
if (mPopdownBehavior)
|
|||
|
{
|
|||
|
outPopupLoc.v = popupRect.bottom + 1;
|
|||
|
outPopupLoc.h = popupRect.left + 2;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
outPopupLoc.v = popupRect.top;
|
|||
|
outPopupLoc.h = popupRect.left;
|
|||
|
}
|
|||
|
|
|||
|
LocalToPortPoint(outPopupLoc);
|
|||
|
PortToGlobalPoint(outPopupLoc);
|
|||
|
}
|
|||
|
|
|||
|
#pragma mark -
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> SetValue
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// This is overridden to adjust the mark on the previous/new menu items and
|
|||
|
// to ensure that the value is always broadcast (even if it hasn't really
|
|||
|
// changed) and to call a "hook" for derived classes to perform some action
|
|||
|
// on value changes.
|
|||
|
//
|
|||
|
// Note that this implementation should be considered "final" (i.e. no further
|
|||
|
// overrides allowed) in order to preserve the semantics of this class.
|
|||
|
//
|
|||
|
// Exceptions thrown: CValueRangeException, CAttemptToSetDisabledValueException
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::SetValue(Int32 inValue)
|
|||
|
{
|
|||
|
ThrowIfNil_(GetMenu());
|
|||
|
ThrowIfNil_(GetMenu()->GetMacMenuH());
|
|||
|
|
|||
|
if (inValue < 0 || inValue > ::CountMItems(GetMenu()->GetMacMenuH()))
|
|||
|
{
|
|||
|
throw CValueRangeException();
|
|||
|
}
|
|||
|
|
|||
|
if (!GetMenu()->ItemIsEnabled(inValue))
|
|||
|
{
|
|||
|
throw CAttemptToSetDisabledValueException();
|
|||
|
}
|
|||
|
|
|||
|
// If mMarkCurrentItem is false, it is assumed that we want to
|
|||
|
// be able to select the same command several times in a row.
|
|||
|
// By hacking mValue to 0, we make sure that the menu command
|
|||
|
// will always be broadcast.
|
|||
|
|
|||
|
if (!mMarkCurrentItem)
|
|||
|
{
|
|||
|
mValue = 0;
|
|||
|
}
|
|||
|
|
|||
|
if (mValue != inValue)
|
|||
|
{
|
|||
|
// Setup the menu item
|
|||
|
|
|||
|
SetupCurrentMenuItem(GetMenu()->GetMacMenuH(), inValue);
|
|||
|
|
|||
|
// Set the new value. If the HandleNewValue hook indicates that
|
|||
|
// the value was handled, then we make sure that the call to
|
|||
|
// the base class SetValue does not broadcast the new value.
|
|||
|
|
|||
|
if (HandleNewValue(inValue))
|
|||
|
{
|
|||
|
StSetBroadcasting setBroadcasting(this, false);
|
|||
|
|
|||
|
super::SetValue(inValue);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
super::SetValue(inValue);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> SetPopupMinMaxValues
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// Setup the minimum and maximum values for the control based on the
|
|||
|
// current menu state.
|
|||
|
//
|
|||
|
// NOTE: It is important for clients to call this method after inserting/removing
|
|||
|
// menu items dynamically.
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::SetPopupMinMaxValues()
|
|||
|
{
|
|||
|
if (GetMenu() && GetMenu()->GetMacMenuH())
|
|||
|
{
|
|||
|
mMinValue = 0;
|
|||
|
mMaxValue = ::CountMItems(GetMenu()->GetMacMenuH());
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
mMaxValue = mMinValue = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#pragma mark -
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> OKToSendCommand
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
|
|||
|
Boolean
|
|||
|
CPatternButtonPopup::OKToSendCommand(
|
|||
|
Int32 inCommand)
|
|||
|
{
|
|||
|
LCommander* theTarget = LCommander::GetTarget();
|
|||
|
Boolean enabled = false;
|
|||
|
Boolean usesMark = false;
|
|||
|
Str255 outName;
|
|||
|
Char16 outMark;
|
|||
|
|
|||
|
if (inCommand && theTarget)
|
|||
|
{
|
|||
|
theTarget->ProcessCommandStatus(inCommand, enabled, usesMark, outMark, outName);
|
|||
|
}
|
|||
|
|
|||
|
return enabled;
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> BroadcastValueMessage
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// Broadcast the menu command if there is one, otherwise broadcast the value
|
|||
|
|
|||
|
void CPatternButtonPopup::BroadcastValueMessage()
|
|||
|
{
|
|||
|
ThrowIfNil_(GetMenu());
|
|||
|
ThrowIfNil_(GetMenu()->GetMacMenuH());
|
|||
|
|
|||
|
if (mValueMessage != msg_Nothing)
|
|||
|
{
|
|||
|
CommandT theCommand = GetMenu()->CommandFromIndex(GetValue());
|
|||
|
|
|||
|
if (theCommand != cmd_Nothing)
|
|||
|
{
|
|||
|
if (OKToSendCommand(theCommand))
|
|||
|
{
|
|||
|
BroadcastMessage(theCommand, nil);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Int32 value = mValue;
|
|||
|
|
|||
|
BroadcastMessage(mValueMessage, &value);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#pragma mark -
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> HandleQuickClick
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// Hook for handling quick clicks.
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::HandleQuickClick()
|
|||
|
{
|
|||
|
if (mQuickClickValueOrCommand)
|
|||
|
{
|
|||
|
if (mQuickClickIsCommandBased)
|
|||
|
{
|
|||
|
if (OKToSendCommand(mQuickClickValueOrCommand))
|
|||
|
{
|
|||
|
BroadcastMessage(mQuickClickValueOrCommand, nil);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SetValue(mQuickClickValueOrCommand);
|
|||
|
}
|
|||
|
catch (const CValueRangeException&) { }
|
|||
|
catch (const CAttemptToSetDisabledValueException&) { }
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> HandleNewValue
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// Hook for handling value changes. Called by SetValue.
|
|||
|
//
|
|||
|
// Some CPatternButtonPopups may override this to perform an action rather
|
|||
|
// than using broadcaster/listener. Returns true if HandleNewValue handled the new
|
|||
|
// value (this will cause the new value to not be broadcast).
|
|||
|
//
|
|||
|
// Note that the setting of the new value is done by CPatternButtonPopup::SetValue.
|
|||
|
// Therefore, GetValue() will still return the old value here, so the old value is
|
|||
|
// still available in this method.
|
|||
|
|
|||
|
Boolean
|
|||
|
CPatternButtonPopup::HandleNewValue(Int32 inNewValue)
|
|||
|
{
|
|||
|
#pragma unused(inNewValue)
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
#pragma mark -
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// <09> HandleEnablingPolicy
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// This is our required implementation of the MPaneEnablerPolicy interface.
|
|||
|
//
|
|||
|
// The enabling policy is as follows:
|
|||
|
//
|
|||
|
// - If all of the menu items associated with the menu items are disabled
|
|||
|
// then disable the button. Note that a menu item without a command
|
|||
|
// is automatically enabled and a menu item with a command is enabled
|
|||
|
// only if the command associated with it is enabled.
|
|||
|
// - If there is (no menu or no menu items in the menu) and
|
|||
|
// mQuickClickIsCommandBased is true and it is *not OK* to send the
|
|||
|
// mQuickClickValueOrCommand command then disable the button.
|
|||
|
// - Otherwise enable the buton.
|
|||
|
|
|||
|
void
|
|||
|
CPatternButtonPopup::HandleEnablingPolicy()
|
|||
|
{
|
|||
|
LCommander* theTarget = LCommander::GetTarget();
|
|||
|
MenuHandle macMenuH = nil;
|
|||
|
MessageT buttonCommand = GetValueMessage();
|
|||
|
Boolean enabled = false;
|
|||
|
Boolean usesMark = false;
|
|||
|
Str255 outName;
|
|||
|
Char16 outMark;
|
|||
|
|
|||
|
if (!CTargetedUpdateMenuRegistry::UseRegistryToUpdateMenus() ||
|
|||
|
CTargetedUpdateMenuRegistry::CommandInRegistry(buttonCommand))
|
|||
|
{
|
|||
|
if (!IsActive() || !IsVisible())
|
|||
|
return;
|
|||
|
|
|||
|
if (!theTarget)
|
|||
|
return;
|
|||
|
|
|||
|
if (GetMenu() && GetMenu()->GetMacMenuH() && ::CountMItems(GetMenu()->GetMacMenuH()))
|
|||
|
{
|
|||
|
macMenuH = GetMenu()->GetMacMenuH();
|
|||
|
}
|
|||
|
|
|||
|
if (macMenuH)
|
|||
|
{
|
|||
|
// We have a menu
|
|||
|
|
|||
|
CommandT theCommand;
|
|||
|
Boolean allDisabled = true;
|
|||
|
Int16 menuItem = 0;
|
|||
|
|
|||
|
while (GetMenu()->FindNextCommand(menuItem, theCommand))
|
|||
|
{
|
|||
|
if (theCommand)
|
|||
|
{
|
|||
|
outName[0] = 0;
|
|||
|
|
|||
|
// Call ProcessCommandStatus() to determine whether to enable the menu items.
|
|||
|
// If they are all disabled then disable the button itself.
|
|||
|
|
|||
|
theTarget->ProcessCommandStatus(theCommand, enabled, usesMark, outMark, outName);
|
|||
|
|
|||
|
if (outName[0] != 0)
|
|||
|
{
|
|||
|
::SetMenuItemText(macMenuH, menuItem, outName);
|
|||
|
}
|
|||
|
|
|||
|
if (enabled)
|
|||
|
{
|
|||
|
allDisabled = false;
|
|||
|
::EnableItem(macMenuH, menuItem);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
::DisableItem(macMenuH, menuItem);
|
|||
|
}
|
|||
|
|
|||
|
if (usesMark)
|
|||
|
{
|
|||
|
if ( outMark && mMarkCharacter )
|
|||
|
outMark = mMarkCharacter;
|
|||
|
::SetItemMark(macMenuH, menuItem, outMark);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
allDisabled = false;
|
|||
|
::EnableItem(macMenuH, menuItem);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (menuItem == 0)
|
|||
|
{
|
|||
|
// The menu does not have any commands, so we check the command attached to
|
|||
|
// the button itself.
|
|||
|
|
|||
|
theTarget->ProcessCommandStatus(buttonCommand, enabled, usesMark, outMark, outName);
|
|||
|
allDisabled = (!enabled);
|
|||
|
}
|
|||
|
|
|||
|
if (allDisabled)
|
|||
|
{
|
|||
|
Disable();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Enable();
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// We don't have a menu
|
|||
|
|
|||
|
if (mQuickClickIsCommandBased && OKToSendCommand(mQuickClickValueOrCommand))
|
|||
|
{
|
|||
|
Enable();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Disable();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|