Bug 357725, support minimum and maximum size constraints on windows and popups, r=mats,jmathies,karlt,smichaud,sr=neil

This commit is contained in:
Neil Deakin 2012-07-30 20:43:29 -04:00
parent 8ba0d842dd
commit 28413e223c
15 changed files with 274 additions and 30 deletions

View File

@ -7488,7 +7488,7 @@ PresShell::DoReflow(nsIFrame* target, bool aInterruptible)
target->GetView(),
boundsRelativeToTarget);
nsContainerFrame::SyncWindowProperties(mPresContext, target,
target->GetView());
target->GetView(), rcx);
target->DidReflow(mPresContext, nullptr, NS_FRAME_REFLOW_FINISHED);
if (target == rootFrame && size.height == NS_UNCONSTRAINEDSIZE) {

View File

@ -34,6 +34,8 @@
#include "nsListControlFrame.h"
#include "nsIBaseWindow.h"
#include "nsThemeConstants.h"
#include "nsBoxLayoutState.h"
#include "nsRenderingContext.h"
#include "nsCSSFrameConstructor.h"
#include "mozilla/dom/Element.h"
@ -632,7 +634,8 @@ IsTopLevelWidget(nsIWidget* aWidget)
void
nsContainerFrame::SyncWindowProperties(nsPresContext* aPresContext,
nsIFrame* aFrame,
nsIView* aView)
nsIView* aView,
nsRenderingContext* aRC)
{
#ifdef MOZ_XUL
if (!aView || !nsCSSRendering::IsCanvasFrame(aFrame) || !aView->HasWidget())
@ -676,9 +679,47 @@ nsContainerFrame::SyncWindowProperties(nsPresContext* aPresContext,
nsIWidget* viewWidget = aView->GetWidget();
viewWidget->SetTransparencyMode(mode);
windowWidget->SetWindowShadowStyle(rootFrame->GetStyleUIReset()->mWindowShadow);
if (!aRC)
return;
nsBoxLayoutState aState(aPresContext, aRC);
nsSize minSize = rootFrame->GetMinSize(aState);
nsSize maxSize = rootFrame->GetMaxSize(aState);
SetSizeConstraints(aPresContext, windowWidget, minSize, maxSize);
#endif
}
void nsContainerFrame::SetSizeConstraints(nsPresContext* aPresContext,
nsIWidget* aWidget,
const nsSize& aMinSize,
const nsSize& aMaxSize)
{
nsIntSize devMinSize(aPresContext->AppUnitsToDevPixels(aMinSize.width),
aPresContext->AppUnitsToDevPixels(aMinSize.height));
nsIntSize devMaxSize(aMaxSize.width == NS_INTRINSICSIZE ? NS_MAXSIZE :
aPresContext->AppUnitsToDevPixels(aMaxSize.width),
aMaxSize.height == NS_INTRINSICSIZE ? NS_MAXSIZE :
aPresContext->AppUnitsToDevPixels(aMaxSize.height));
widget::SizeConstraints constraints(devMinSize, devMaxSize);
// The sizes are in inner window sizes, so convert them into outer window sizes.
// Use a size of (200, 200) as only the difference between the inner and outer
// size is needed.
nsIntSize windowSize = aWidget->ClientToWindowSize(nsIntSize(200, 200));
if (constraints.mMinSize.width)
constraints.mMinSize.width += windowSize.width - 200;
if (constraints.mMinSize.height)
constraints.mMinSize.height += windowSize.height - 200;
if (constraints.mMaxSize.width != NS_MAXSIZE)
constraints.mMaxSize.width += windowSize.width - 200;
if (constraints.mMaxSize.height != NS_MAXSIZE)
constraints.mMaxSize.height += windowSize.height - 200;
aWidget->SetSizeConstraints(constraints);
}
void
nsContainerFrame::SyncFrameViewAfterReflow(nsPresContext* aPresContext,
nsIFrame* aFrame,

View File

@ -141,7 +141,8 @@ public:
// shadow.
static void SyncWindowProperties(nsPresContext* aPresContext,
nsIFrame* aFrame,
nsIView* aView);
nsIView* aView,
nsRenderingContext* aRC = nullptr);
// Sets the view's attributes from the frame style.
// - visibility
@ -155,6 +156,20 @@ public:
nsIView* aView,
PRUint32 aFlags = 0);
/**
* Converts the minimum and maximum sizes given in inner window app units to
* outer window device pixel sizes and assigns these constraints to the widget.
*
* @param aPresContext pres context
* @param aWidget widget for this frame
* @param minimum size of the window in app units
* @param maxmimum size of the window in app units
*/
static void SetSizeConstraints(nsPresContext* aPresContext,
nsIWidget* aWidget,
const nsSize& aMinSize,
const nsSize& aMaxSize);
// Used by both nsInlineFrame and nsFirstLetterFrame.
void DoInlineIntrinsicWidth(nsRenderingContext *aRenderingContext,
InlineIntrinsicWidthData *aData,

View File

@ -450,6 +450,14 @@ nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu, b
nsRect rect = GetRect();
rect.x = rect.y = 0;
if (sizeChanged) {
// If the size of the popup changed, apply any size constraints.
nsIWidget* widget = view->GetWidget();
if (widget) {
SetSizeConstraints(pc, widget, minSize, maxSize);
}
}
viewManager->ResizeView(view, rect);
viewManager->SetViewVisibility(view, nsViewVisibility_kShow);

View File

@ -29,6 +29,7 @@
#include "mozilla/dom/Element.h"
#include "nsContentErrors.h"
using namespace mozilla;
//
// NS_NewResizerFrame
@ -186,8 +187,19 @@ nsResizerFrame::HandleEvent(nsPresContext* aPresContext,
}
nsIntRect rect = mMouseDownRect;
AdjustDimensions(&rect.x, &rect.width, mouseMove.x, direction.mHorizontal);
AdjustDimensions(&rect.y, &rect.height, mouseMove.y, direction.mVertical);
// Check if there are any size constraints on this window.
widget::SizeConstraints sizeConstraints;
if (window) {
nsCOMPtr<nsIWidget> widget;
window->GetMainWidget(getter_AddRefs(widget));
sizeConstraints = widget->GetSizeConstraints();
}
AdjustDimensions(&rect.x, &rect.width, sizeConstraints.mMinSize.width,
sizeConstraints.mMaxSize.width, mouseMove.x, direction.mHorizontal);
AdjustDimensions(&rect.y, &rect.height, sizeConstraints.mMinSize.height,
sizeConstraints.mMaxSize.height, mouseMove.y, direction.mVertical);
// Don't allow resizing a window or a popup past the edge of the screen,
// so adjust the rectangle to fit within the available screen area.
@ -249,6 +261,10 @@ nsResizerFrame::HandleEvent(nsPresContext* aPresContext,
ResizeContent(contentToResize, direction, sizeInfo, &originalSizeInfo);
MaybePersistOriginalSize(contentToResize, originalSizeInfo);
// Move the popup to the new location unless it is anchored, since
// the position shouldn't change. nsMenuPopupFrame::SetPopupPosition
// will instead ensure that the popup's position is anchored at the
// right place.
if (weakFrame.IsAlive() &&
(oldRect.x != rect.x || oldRect.y != rect.y) &&
(!menuPopupFrame->IsAnchored() ||
@ -365,25 +381,25 @@ nsResizerFrame::GetContentToResize(nsIPresShell* aPresShell, nsIBaseWindow** aWi
return aPresShell->GetDocument()->GetElementById(elementid);
}
/* adjust the window position and size according to the mouse movement and
* the resizer direction
*/
void
nsResizerFrame::AdjustDimensions(PRInt32* aPos, PRInt32* aSize,
PRInt32 aMinSize, PRInt32 aMaxSize,
PRInt32 aMovement, PRInt8 aResizerDirection)
{
switch(aResizerDirection)
{
case -1:
// only move the window when the direction is top and/or left
*aPos+= aMovement;
// falling through: the window is resized in both cases
case 1:
*aSize+= aResizerDirection*aMovement;
// use one as a minimum size or the element could disappear
if (*aSize < 1)
*aSize = 1;
}
PRInt32 oldSize = *aSize;
*aSize += aResizerDirection * aMovement;
// use one as a minimum size or the element could disappear
if (*aSize < 1)
*aSize = 1;
// Constrain the size within the minimum and maximum size.
*aSize = NS_MAX(aMinSize, NS_MIN(aMaxSize, *aSize));
// For left and top resizers, the window must be moved left by the same
// amount that the window was resized.
if (aResizerDirection == -1)
*aPos += oldSize - *aSize;
}
/* static */ void

View File

@ -35,8 +35,22 @@ protected:
nsIContent* GetContentToResize(nsIPresShell* aPresShell, nsIBaseWindow** aWindow);
Direction GetDirection();
/**
* Adjust the window position and size in a direction according to the mouse
* movement and the resizer direction. The minimum and maximum size is used
* to constrain the size.
*
* @param aPos left or top position
* @param aSize width or height
* @param aMinSize minimum width or height
* @param aMacSize maximum width or height
* @param aMovement the amount the mouse was moved
* @param aResizerDirection resizer direction returned by GetDirection
*/
static void AdjustDimensions(PRInt32* aPos, PRInt32* aSize,
PRInt32 aMovement, PRInt8 aResizerDirection);
PRInt32 aMinSize, PRInt32 aMaxSize,
PRInt32 aMovement, PRInt8 aResizerDirection);
struct SizeInfo {
nsString width, height;

View File

@ -224,6 +224,7 @@ public:
NS_IMETHOD ConstrainPosition(bool aAllowSlop,
PRInt32 *aX, PRInt32 *aY);
virtual void SetSizeConstraints(const SizeConstraints& aConstraints);
NS_IMETHOD Move(PRInt32 aX, PRInt32 aY);
NS_IMETHOD PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
nsIWidget *aWidget, bool aActivate);

View File

@ -1072,6 +1072,32 @@ NS_IMETHODIMP nsCocoaWindow::ConstrainPosition(bool aAllowSlop,
return NS_OK;
}
void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
// Popups can be smaller than (60, 60)
NSRect rect =
(mWindowType == eWindowType_popup) ? NSZeroRect : NSMakeRect(0.0, 0.0, 60, 60);
rect = [mWindow frameRectForContentRect:rect];
SizeConstraints c = aConstraints;
c.mMinSize.width = NS_MAX(PRInt32(rect.size.width), c.mMinSize.width);
c.mMinSize.height = NS_MAX(PRInt32(rect.size.height), c.mMinSize.height);
NSSize minSize = { static_cast<CGFloat>(c.mMinSize.width),
static_cast<CGFloat>(c.mMinSize.height) };
[mWindow setMinSize:minSize];
NSSize maxSize = { c.mMaxSize.width == NS_MAXSIZE ? FLT_MAX : c.mMaxSize.width,
c.mMaxSize.height == NS_MAXSIZE ? FLT_MAX : c.mMaxSize.height };
[mWindow setMaxSize:maxSize];
nsBaseWidget::SetSizeConstraints(c);
NS_OBJC_END_TRY_ABORT_BLOCK;
}
NS_IMETHODIMP nsCocoaWindow::Move(PRInt32 aX, PRInt32 aY)
{
if (!mWindow || (mBounds.x == aX && mBounds.y == aY))
@ -1250,6 +1276,8 @@ NS_IMETHODIMP nsCocoaWindow::Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRIn
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
ConstrainSize(&aWidth, &aHeight);
nsIntRect newBounds = nsIntRect(aX, aY, aWidth, aHeight);
FitRectToVisibleAreaForScreen(newBounds, [mWindow screen]);
@ -1617,11 +1645,6 @@ nsIntSize nsCocoaWindow::ClientToWindowSize(const nsIntSize& aClientSize)
if (!mWindow)
return nsIntSize(0, 0);
// this is only called for popups currently. If needed, expand this to support
// other types of windows
if (!IsPopupWithTitleBar())
return aClientSize;
NSRect rect(NSMakeRect(0.0, 0.0, aClientSize.width, aClientSize.height));
NSRect inflatedRect = [mWindow frameRectForContentRect:rect];

View File

@ -944,6 +944,23 @@ nsWindow::ConstrainPosition(bool aAllowSlop, PRInt32 *aX, PRInt32 *aY)
return NS_OK;
}
void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
{
if (mShell) {
GdkGeometry geometry;
geometry.min_width = aConstraints.mMinSize.width;
geometry.min_height = aConstraints.mMinSize.height;
geometry.max_width = aConstraints.mMaxSize.width;
geometry.max_height = aConstraints.mMaxSize.height;
PRUint32 hints = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE;
gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr,
&geometry, GdkWindowHints(hints));
}
nsBaseWidget::SetSizeConstraints(aConstraints);
}
NS_IMETHODIMP
nsWindow::Show(bool aState)
{
@ -1002,6 +1019,8 @@ nsWindow::Show(bool aState)
NS_IMETHODIMP
nsWindow::Resize(PRInt32 aWidth, PRInt32 aHeight, bool aRepaint)
{
ConstrainSize(&aWidth, &aHeight);
// For top-level windows, aWidth and aHeight should possibly be
// interpreted as frame bounds, but NativeResize treats these as window
// bounds (Bug 581866).
@ -1079,6 +1098,8 @@ NS_IMETHODIMP
nsWindow::Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight,
bool aRepaint)
{
ConstrainSize(&aWidth, &aHeight);
mBounds.x = aX;
mBounds.y = aY;
mBounds.SizeTo(GetSafeWindowSize(nsIntSize(aWidth, aHeight)));

View File

@ -111,6 +111,7 @@ public:
NS_IMETHOD ConstrainPosition(bool aAllowSlop,
PRInt32 *aX,
PRInt32 *aY);
virtual void SetSizeConstraints(const SizeConstraints& aConstraints);
NS_IMETHOD Move(PRInt32 aX,
PRInt32 aY);
NS_IMETHOD Show (bool aState);

View File

@ -163,7 +163,6 @@ enum nsTopLevelWidgetZPlacement { // for PlaceBehind()
eZPlacementTop // top of the window stack
};
/**
* Preference for receiving IME updates
*
@ -351,6 +350,27 @@ struct InputContextAction {
}
};
/**
* Size constraints for setting the minimum and maximum size of a widget.
* Values are in device pixels.
*/
struct SizeConstraints {
SizeConstraints()
: mMaxSize(NS_MAXSIZE, NS_MAXSIZE)
{
}
SizeConstraints(nsIntSize aMinSize,
nsIntSize aMaxSize)
: mMinSize(aMinSize),
mMaxSize(aMaxSize)
{
}
nsIntSize mMinSize;
nsIntSize mMaxSize;
};
} // namespace widget
} // namespace mozilla
@ -369,6 +389,7 @@ class nsIWidget : public nsISupports {
typedef mozilla::widget::IMEState IMEState;
typedef mozilla::widget::InputContext InputContext;
typedef mozilla::widget::InputContextAction InputContextAction;
typedef mozilla::widget::SizeConstraints SizeConstraints;
// Used in UpdateThemeGeometries.
struct ThemeGeometry {
@ -658,7 +679,8 @@ class nsIWidget : public nsISupports {
NS_IMETHOD MoveClient(PRInt32 aX, PRInt32 aY) = 0;
/**
* Resize this widget.
* Resize this widget. Any size constraints set for the window by a
* previous call to SetSizeConstraints will be applied.
*
* @param aWidth the new width expressed in the parent's coordinate system
* @param aHeight the new height expressed in the parent's coordinate system
@ -670,7 +692,8 @@ class nsIWidget : public nsISupports {
bool aRepaint) = 0;
/**
* Move or resize this widget.
* Move or resize this widget. Any size constraints set for the window by
* a previous call to SetSizeConstraints will be applied.
*
* @param aX the new x position expressed in the parent's coordinate system
* @param aY the new y position expressed in the parent's coordinate system
@ -1588,6 +1611,26 @@ class nsIWidget : public nsISupports {
return bounds;
}
/**
* Set size constraints on the window size such that it is never less than
* the specified minimum size and never larger than the specified maximum
* size. The size constraints are sizes of the outer rectangle including
* the window frame and title bar. Use 0 for an unconstrained minimum size
* and NS_MAXSIZE for an unconstrained maximum size. Note that this method
* does not necessarily change the size of a window to conform to this size,
* thus Resize should be called afterwards.
*
* @param aConstraints: the size constraints in device pixels
*/
virtual void SetSizeConstraints(const SizeConstraints& aConstraints) = 0;
/**
* Return the size constraints currently observed by the widget.
*
* @return the constraints in device pixels
*/
virtual const SizeConstraints& GetSizeConstraints() const = 0;
protected:
// keep the list of children. We also keep track of our siblings.

View File

@ -1286,6 +1286,18 @@ BOOL CALLBACK nsWindow::UnregisterTouchForDescendants(HWND aWnd, LPARAM aMsg) {
*
**************************************************************/
void
nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
{
SizeConstraints c = aConstraints;
if (mWindowType != eWindowType_popup) {
c.mMinSize.width = NS_MAX(PRInt32(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width);
c.mMinSize.height = NS_MAX(PRInt32(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height);
}
nsBaseWidget::SetSizeConstraints(c);
}
// Move this component
NS_METHOD nsWindow::Move(PRInt32 aX, PRInt32 aY)
{
@ -1356,6 +1368,7 @@ NS_METHOD nsWindow::Resize(PRInt32 aWidth, PRInt32 aHeight, bool aRepaint)
{
NS_ASSERTION((aWidth >=0 ) , "Negative width passed to nsWindow::Resize");
NS_ASSERTION((aHeight >=0 ), "Negative height passed to nsWindow::Resize");
ConstrainSize(&aWidth, &aHeight);
// Avoid unnecessary resizing calls
if (mBounds.width == aWidth && mBounds.height == aHeight && !aRepaint)
@ -1394,6 +1407,7 @@ NS_METHOD nsWindow::Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeig
{
NS_ASSERTION((aWidth >=0 ), "Negative width passed to nsWindow::Resize");
NS_ASSERTION((aHeight >=0 ), "Negative height passed to nsWindow::Resize");
ConstrainSize(&aWidth, &aHeight);
// Avoid unnecessary resizing calls
if (mBounds.x == aX && mBounds.y == aY &&
@ -2883,7 +2897,7 @@ nsIntPoint nsWindow::WidgetToScreenOffset()
nsIntSize nsWindow::ClientToWindowSize(const nsIntSize& aClientSize)
{
if (!IsPopupWithTitleBar())
if (mWindowType == eWindowType_popup && !IsPopupWithTitleBar())
return aClientSize;
// just use (200, 200) as the position
@ -5063,6 +5077,22 @@ bool nsWindow::ProcessMessage(UINT msg, WPARAM &wParam, LPARAM &lParam,
}
break;
case WM_GETMINMAXINFO:
{
MINMAXINFO* mmi = (MINMAXINFO*)lParam;
// Set the constraints. The minimum size should also be constrained to the
// default window maximum size so that it fits on screen.
mmi->ptMinTrackSize.x =
NS_MIN((PRInt32)mmi->ptMaxTrackSize.x,
NS_MAX((PRInt32)mmi->ptMinTrackSize.x, mSizeConstraints.mMinSize.width));
mmi->ptMinTrackSize.y =
NS_MIN((PRInt32)mmi->ptMaxTrackSize.y,
NS_MAX((PRInt32)mmi->ptMinTrackSize.y, mSizeConstraints.mMinSize.height));
mmi->ptMaxTrackSize.x = NS_MIN((PRInt32)mmi->ptMaxTrackSize.x, mSizeConstraints.mMaxSize.width);
mmi->ptMaxTrackSize.y = NS_MIN((PRInt32)mmi->ptMaxTrackSize.y, mSizeConstraints.mMaxSize.height);
}
break;
case WM_SETFOCUS:
// If previous focused window isn't ours, it must have received the
// redirected message. So, we should forget it.

View File

@ -94,6 +94,7 @@ public:
NS_IMETHOD Show(bool bState);
virtual bool IsVisible() const;
NS_IMETHOD ConstrainPosition(bool aAllowSlop, PRInt32 *aX, PRInt32 *aY);
virtual void SetSizeConstraints(const SizeConstraints& aConstraints);
NS_IMETHOD Move(PRInt32 aX, PRInt32 aY);
NS_IMETHOD Resize(PRInt32 aWidth, PRInt32 aHeight, bool aRepaint);
NS_IMETHOD Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, bool aRepaint);

View File

@ -1323,6 +1323,18 @@ nsBaseWidget::GetGLFrameBufferFormat()
return LOCAL_GL_NONE;
}
void nsBaseWidget::SetSizeConstraints(const SizeConstraints& aConstraints)
{
mSizeConstraints = aConstraints;
// We can't ensure that the size is honored at this point because we're
// probably in the middle of a reflow.
}
const widget::SizeConstraints& nsBaseWidget::GetSizeConstraints() const
{
return mSizeConstraints;
}
#ifdef DEBUG
//////////////////////////////////////////////////////////////
//

View File

@ -179,6 +179,9 @@ public:
virtual PRUint32 GetGLFrameBufferFormat() MOZ_OVERRIDE;
virtual const SizeConstraints& GetSizeConstraints() const;
virtual void SetSizeConstraints(const SizeConstraints& aConstraints);
/**
* Use this when GetLayerManager() returns a BasicLayerManager
* (nsBaseWidget::GetLayerManager() does). This sets up the widget's
@ -283,6 +286,20 @@ protected:
}
}
/**
* Apply the current size constraints to the given size.
*
* @param aWidth width to constrain
* @param aHeight height to constrain
*/
void ConstrainSize(PRInt32* aWidth, PRInt32* aHeight) const
{
*aWidth = NS_MAX(mSizeConstraints.mMinSize.width,
NS_MIN(mSizeConstraints.mMaxSize.width, *aWidth));
*aHeight = NS_MAX(mSizeConstraints.mMinSize.height,
NS_MIN(mSizeConstraints.mMaxSize.height, *aHeight));
}
protected:
/**
* Starts the OMTC compositor destruction sequence.
@ -322,6 +339,7 @@ protected:
nsSizeMode mSizeMode;
nsPopupLevel mPopupLevel;
nsPopupType mPopupType;
SizeConstraints mSizeConstraints;
// the last rolled up popup. Only set this when an nsAutoRollup is in scope,
// so it can be cleared automatically.