зеркало из https://github.com/mozilla/pjs.git
990 строки
27 KiB
C++
990 строки
27 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||
*
|
||
* ***** BEGIN LICENSE BLOCK *****
|
||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||
*
|
||
* The contents of this file are subject to the Mozilla Public License Version
|
||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||
* the License. You may obtain a copy of the License at
|
||
* http://www.mozilla.org/MPL/
|
||
*
|
||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||
* for the specific language governing rights and limitations under the
|
||
* License.
|
||
*
|
||
* The Original Code is mozilla.org code.
|
||
*
|
||
* The Initial Developer of the Original Code is
|
||
* Netscape Communications Corporation.
|
||
* Portions created by the Initial Developer are Copyright (C) 1998
|
||
* the Initial Developer. All Rights Reserved.
|
||
*
|
||
* Contributor(s):
|
||
*
|
||
* Alternatively, the contents of this file may be used under the terms of
|
||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||
* of those above. If you wish to allow use of your version of this file only
|
||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||
* use your version of this file under the terms of the MPL, indicate your
|
||
* decision by deleting the provisions above and replace them with the notice
|
||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||
* the provisions above, a recipient may use your version of this file under
|
||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||
*
|
||
* ***** END LICENSE BLOCK ***** */
|
||
|
||
|
||
/*
|
||
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<72> 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();
|
||
}
|
||
}
|
||
}
|
||
}
|