2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 2000 Simon Hausmann <hausmann@kde.org>
4 * (C) 2000 Stefan Schimanski (1Stein@gmx.de)
5 * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
25 #include "core/layout/LayoutFrameSet.h"
27 #include "core/dom/Document.h"
28 #include "core/events/MouseEvent.h"
29 #include "core/frame/LocalFrame.h"
30 #include "core/html/HTMLDimension.h"
31 #include "core/html/HTMLFrameSetElement.h"
32 #include "core/input/EventHandler.h"
33 #include "core/layout/LayoutFrame.h"
34 #include "core/layout/LayoutView.h"
35 #include "core/paint/FrameSetPainter.h"
36 #include "platform/Cursor.h"
37 #include "platform/graphics/GraphicsContext.h"
41 LayoutFrameSet::LayoutFrameSet(HTMLFrameSetElement
* frameSet
)
44 , m_isChildResizing(false)
49 LayoutFrameSet::~LayoutFrameSet()
53 LayoutFrameSet::GridAxis::GridAxis()
54 : m_splitBeingResized(noSplit
)
58 HTMLFrameSetElement
* LayoutFrameSet::frameSet() const
60 return toHTMLFrameSetElement(node());
63 void LayoutFrameSet::paint(const PaintInfo
& paintInfo
, const LayoutPoint
& paintOffset
)
65 FrameSetPainter(*this).paint(paintInfo
, paintOffset
);
68 void LayoutFrameSet::computePreferredLogicalWidths()
70 m_minPreferredLogicalWidth
= 0;
71 m_maxPreferredLogicalWidth
= 0;
72 clearPreferredLogicalWidthsDirty();
75 void LayoutFrameSet::GridAxis::resize(int size
)
78 m_deltas
.resize(size
);
81 // To track edges for resizability and borders, we need to be (size + 1). This is because a parent frameset
82 // may ask us for information about our left/top/right/bottom edges in order to make its own decisions about
83 // what to do. We are capable of tainting that parent frameset's borders, so we have to cache this info.
84 m_preventResize
.resize(size
+ 1);
85 m_allowBorder
.resize(size
+ 1);
88 void LayoutFrameSet::layOutAxis(GridAxis
& axis
, const Vector
<HTMLDimension
>& grid
, int availableLen
)
90 availableLen
= max(availableLen
, 0);
92 int* gridLayout
= axis
.m_sizes
.data();
95 gridLayout
[0] = availableLen
;
99 int gridLen
= axis
.m_sizes
.size();
102 int totalRelative
= 0;
104 int totalPercent
= 0;
105 int countRelative
= 0;
107 int countPercent
= 0;
109 // First we need to investigate how many columns of each type we have and
110 // how much space these columns are going to require.
111 for (int i
= 0; i
< gridLen
; ++i
) {
112 // Count the total length of all of the fixed columns/rows -> totalFixed
113 // Count the number of columns/rows which are fixed -> countFixed
114 if (grid
[i
].isAbsolute()) {
115 gridLayout
[i
] = max
<int>(grid
[i
].value(), 0);
116 totalFixed
+= gridLayout
[i
];
120 // Count the total percentage of all of the percentage columns/rows -> totalPercent
121 // Count the number of columns/rows which are percentages -> countPercent
122 if (grid
[i
].isPercentage()) {
123 gridLayout
[i
] = max
<int>(grid
[i
].value() * availableLen
/ 100., 0);
124 totalPercent
+= gridLayout
[i
];
128 // Count the total relative of all the relative columns/rows -> totalRelative
129 // Count the number of columns/rows which are relative -> countRelative
130 if (grid
[i
].isRelative()) {
131 totalRelative
+= max
<int>(grid
[i
].value(), 1);
136 int remainingLen
= availableLen
;
138 // Fixed columns/rows are our first priority. If there is not enough space to fit all fixed
139 // columns/rows we need to proportionally adjust their size.
140 if (totalFixed
> remainingLen
) {
141 int remainingFixed
= remainingLen
;
143 for (int i
= 0; i
< gridLen
; ++i
) {
144 if (grid
[i
].isAbsolute()) {
145 gridLayout
[i
] = (gridLayout
[i
] * remainingFixed
) / totalFixed
;
146 remainingLen
-= gridLayout
[i
];
150 remainingLen
-= totalFixed
;
153 // Percentage columns/rows are our second priority. Divide the remaining space proportionally
154 // over all percentage columns/rows. IMPORTANT: the size of each column/row is not relative
155 // to 100%, but to the total percentage. For example, if there are three columns, each of 75%,
156 // and the available space is 300px, each column will become 100px in width.
157 if (totalPercent
> remainingLen
) {
158 int remainingPercent
= remainingLen
;
160 for (int i
= 0; i
< gridLen
; ++i
) {
161 if (grid
[i
].isPercentage()) {
162 gridLayout
[i
] = (gridLayout
[i
] * remainingPercent
) / totalPercent
;
163 remainingLen
-= gridLayout
[i
];
167 remainingLen
-= totalPercent
;
170 // Relative columns/rows are our last priority. Divide the remaining space proportionally
171 // over all relative columns/rows. IMPORTANT: the relative value of 0* is treated as 1*.
173 int lastRelative
= 0;
174 int remainingRelative
= remainingLen
;
176 for (int i
= 0; i
< gridLen
; ++i
) {
177 if (grid
[i
].isRelative()) {
178 gridLayout
[i
] = (max(grid
[i
].value(), 1.) * remainingRelative
) / totalRelative
;
179 remainingLen
-= gridLayout
[i
];
184 // If we could not evenly distribute the available space of all of the relative
185 // columns/rows, the remainder will be added to the last column/row.
186 // For example: if we have a space of 100px and three columns (*,*,*), the remainder will
187 // be 1px and will be added to the last column: 33px, 33px, 34px.
189 gridLayout
[lastRelative
] += remainingLen
;
194 // If we still have some left over space we need to divide it over the already existing
197 // Our first priority is to spread if over the percentage columns. The remaining
198 // space is spread evenly, for example: if we have a space of 100px, the columns
199 // definition of 25%,25% used to result in two columns of 25px. After this the
200 // columns will each be 50px in width.
201 if (countPercent
&& totalPercent
) {
202 int remainingPercent
= remainingLen
;
203 int changePercent
= 0;
205 for (int i
= 0; i
< gridLen
; ++i
) {
206 if (grid
[i
].isPercentage()) {
207 changePercent
= (remainingPercent
* gridLayout
[i
]) / totalPercent
;
208 gridLayout
[i
] += changePercent
;
209 remainingLen
-= changePercent
;
212 } else if (totalFixed
) {
213 // Our last priority is to spread the remaining space over the fixed columns.
214 // For example if we have 100px of space and two column of each 40px, both
215 // columns will become exactly 50px.
216 int remainingFixed
= remainingLen
;
219 for (int i
= 0; i
< gridLen
; ++i
) {
220 if (grid
[i
].isAbsolute()) {
221 changeFixed
= (remainingFixed
* gridLayout
[i
]) / totalFixed
;
222 gridLayout
[i
] += changeFixed
;
223 remainingLen
-= changeFixed
;
229 // If we still have some left over space we probably ended up with a remainder of
230 // a division. We cannot spread it evenly anymore. If we have any percentage
231 // columns/rows simply spread the remainder equally over all available percentage columns,
232 // regardless of their size.
233 if (remainingLen
&& countPercent
) {
234 int remainingPercent
= remainingLen
;
235 int changePercent
= 0;
237 for (int i
= 0; i
< gridLen
; ++i
) {
238 if (grid
[i
].isPercentage()) {
239 changePercent
= remainingPercent
/ countPercent
;
240 gridLayout
[i
] += changePercent
;
241 remainingLen
-= changePercent
;
244 } else if (remainingLen
&& countFixed
) {
245 // If we don't have any percentage columns/rows we only have
246 // fixed columns. Spread the remainder equally over all fixed
248 int remainingFixed
= remainingLen
;
251 for (int i
= 0; i
< gridLen
; ++i
) {
252 if (grid
[i
].isAbsolute()) {
253 changeFixed
= remainingFixed
/ countFixed
;
254 gridLayout
[i
] += changeFixed
;
255 remainingLen
-= changeFixed
;
260 // Still some left over. Add it to the last column, because it is impossible
261 // spread it evenly or equally.
263 gridLayout
[gridLen
- 1] += remainingLen
;
265 // now we have the final layout, distribute the delta over it
267 int* gridDelta
= axis
.m_deltas
.data();
268 for (int i
= 0; i
< gridLen
; ++i
) {
269 if (gridLayout
[i
] && gridLayout
[i
] + gridDelta
[i
] <= 0)
271 gridLayout
[i
] += gridDelta
[i
];
273 // if the deltas broke something, undo them
275 for (int i
= 0; i
< gridLen
; ++i
)
276 gridLayout
[i
] -= gridDelta
[i
];
277 axis
.m_deltas
.fill(0);
281 void LayoutFrameSet::notifyFrameEdgeInfoChanged()
285 // FIXME: We should only recompute the edge info with respect to the frame that changed
286 // and its adjacent frame(s) instead of recomputing the edge info for the entire frameset.
290 void LayoutFrameSet::fillFromEdgeInfo(const FrameEdgeInfo
& edgeInfo
, int r
, int c
)
292 if (edgeInfo
.allowBorder(LeftFrameEdge
))
293 m_cols
.m_allowBorder
[c
] = true;
294 if (edgeInfo
.allowBorder(RightFrameEdge
))
295 m_cols
.m_allowBorder
[c
+ 1] = true;
296 if (edgeInfo
.preventResize(LeftFrameEdge
))
297 m_cols
.m_preventResize
[c
] = true;
298 if (edgeInfo
.preventResize(RightFrameEdge
))
299 m_cols
.m_preventResize
[c
+ 1] = true;
301 if (edgeInfo
.allowBorder(TopFrameEdge
))
302 m_rows
.m_allowBorder
[r
] = true;
303 if (edgeInfo
.allowBorder(BottomFrameEdge
))
304 m_rows
.m_allowBorder
[r
+ 1] = true;
305 if (edgeInfo
.preventResize(TopFrameEdge
))
306 m_rows
.m_preventResize
[r
] = true;
307 if (edgeInfo
.preventResize(BottomFrameEdge
))
308 m_rows
.m_preventResize
[r
+ 1] = true;
311 void LayoutFrameSet::computeEdgeInfo()
313 m_rows
.m_preventResize
.fill(frameSet()->noResize());
314 m_rows
.m_allowBorder
.fill(false);
315 m_cols
.m_preventResize
.fill(frameSet()->noResize());
316 m_cols
.m_allowBorder
.fill(false);
318 LayoutObject
* child
= firstChild();
322 size_t rows
= m_rows
.m_sizes
.size();
323 size_t cols
= m_cols
.m_sizes
.size();
324 for (size_t r
= 0; r
< rows
; ++r
) {
325 for (size_t c
= 0; c
< cols
; ++c
) {
326 FrameEdgeInfo edgeInfo
;
327 if (child
->isFrameSet())
328 edgeInfo
= toLayoutFrameSet(child
)->edgeInfo();
330 edgeInfo
= toLayoutFrame(child
)->edgeInfo();
331 fillFromEdgeInfo(edgeInfo
, r
, c
);
332 child
= child
->nextSibling();
339 FrameEdgeInfo
LayoutFrameSet::edgeInfo() const
341 FrameEdgeInfo
result(frameSet()->noResize(), true);
343 int rows
= frameSet()->totalRows();
344 int cols
= frameSet()->totalCols();
346 result
.setPreventResize(LeftFrameEdge
, m_cols
.m_preventResize
[0]);
347 result
.setAllowBorder(LeftFrameEdge
, m_cols
.m_allowBorder
[0]);
348 result
.setPreventResize(RightFrameEdge
, m_cols
.m_preventResize
[cols
]);
349 result
.setAllowBorder(RightFrameEdge
, m_cols
.m_allowBorder
[cols
]);
350 result
.setPreventResize(TopFrameEdge
, m_rows
.m_preventResize
[0]);
351 result
.setAllowBorder(TopFrameEdge
, m_rows
.m_allowBorder
[0]);
352 result
.setPreventResize(BottomFrameEdge
, m_rows
.m_preventResize
[rows
]);
353 result
.setAllowBorder(BottomFrameEdge
, m_rows
.m_allowBorder
[rows
]);
359 void LayoutFrameSet::layout()
361 ASSERT(needsLayout());
363 if (!parent()->isFrameSet() && !document().printing()) {
364 setWidth(view()->viewWidth());
365 setHeight(view()->viewHeight());
368 unsigned cols
= frameSet()->totalCols();
369 unsigned rows
= frameSet()->totalRows();
371 if (m_rows
.m_sizes
.size() != rows
|| m_cols
.m_sizes
.size() != cols
) {
376 LayoutUnit borderThickness
= frameSet()->border();
377 layOutAxis(m_rows
, frameSet()->rowLengths(), size().height() - (rows
- 1) * borderThickness
);
378 layOutAxis(m_cols
, frameSet()->colLengths(), size().width() - (cols
- 1) * borderThickness
);
386 updateLayerTransformAfterLayout();
391 static void clearNeedsLayoutOnHiddenFrames(LayoutBox
* frame
)
393 for (; frame
; frame
= frame
->nextSiblingBox()) {
396 frame
->clearNeedsLayout();
397 clearNeedsLayoutOnHiddenFrames(frame
->firstChildBox());
401 void LayoutFrameSet::positionFrames()
403 LayoutBox
* child
= firstChildBox();
407 int rows
= frameSet()->totalRows();
408 int cols
= frameSet()->totalCols();
410 int borderThickness
= frameSet()->border();
412 LayoutPoint position
;
413 for (int r
= 0; r
< rows
; r
++) {
415 size
.setHeight(m_rows
.m_sizes
[r
]);
416 for (int c
= 0; c
< cols
; c
++) {
417 child
->setLocation(position
);
418 size
.setWidth(m_cols
.m_sizes
[c
]);
420 // has to be resized and itself resize its contents
421 if (size
!= child
->size()) {
422 child
->setSize(size
);
423 child
->setNeedsLayoutAndFullPaintInvalidation(LayoutInvalidationReason::SizeChanged
);
427 position
.setX(position
.x() + size
.width() + borderThickness
);
429 child
= child
->nextSiblingBox();
433 position
.setY(position
.y() + size
.height() + borderThickness
);
436 // All the remaining frames are hidden to avoid ugly spurious unflowed frames.
437 clearNeedsLayoutOnHiddenFrames(child
);
440 void LayoutFrameSet::startResizing(GridAxis
& axis
, int position
)
442 int split
= hitTestSplit(axis
, position
);
443 if (split
== noSplit
|| axis
.m_preventResize
[split
]) {
444 axis
.m_splitBeingResized
= noSplit
;
447 axis
.m_splitBeingResized
= split
;
448 axis
.m_splitResizeOffset
= position
- splitPosition(axis
, split
);
451 void LayoutFrameSet::continueResizing(GridAxis
& axis
, int position
)
455 if (axis
.m_splitBeingResized
== noSplit
)
457 int currentSplitPosition
= splitPosition(axis
, axis
.m_splitBeingResized
);
458 int delta
= (position
- currentSplitPosition
) - axis
.m_splitResizeOffset
;
461 axis
.m_deltas
[axis
.m_splitBeingResized
- 1] += delta
;
462 axis
.m_deltas
[axis
.m_splitBeingResized
] -= delta
;
463 setNeedsLayoutAndFullPaintInvalidation(LayoutInvalidationReason::SizeChanged
);
466 bool LayoutFrameSet::userResize(MouseEvent
* evt
)
471 if (evt
->type() == EventTypeNames::mousedown
&& evt
->button() == LeftButton
) {
472 FloatPoint localPos
= absoluteToLocal(FloatPoint(evt
->absoluteLocation()), UseTransforms
);
473 startResizing(m_cols
, localPos
.x());
474 startResizing(m_rows
, localPos
.y());
475 if (m_cols
.m_splitBeingResized
!= noSplit
|| m_rows
.m_splitBeingResized
!= noSplit
) {
481 if (evt
->type() == EventTypeNames::mousemove
|| (evt
->type() == EventTypeNames::mouseup
&& evt
->button() == LeftButton
)) {
482 FloatPoint localPos
= absoluteToLocal(FloatPoint(evt
->absoluteLocation()), UseTransforms
);
483 continueResizing(m_cols
, localPos
.x());
484 continueResizing(m_rows
, localPos
.y());
485 if (evt
->type() == EventTypeNames::mouseup
&& evt
->button() == LeftButton
) {
486 setIsResizing(false);
495 void LayoutFrameSet::setIsResizing(bool isResizing
)
497 m_isResizing
= isResizing
;
498 for (LayoutObject
* ancestor
= parent(); ancestor
; ancestor
= ancestor
->parent()) {
499 if (ancestor
->isFrameSet())
500 toLayoutFrameSet(ancestor
)->m_isChildResizing
= isResizing
;
502 if (LocalFrame
* frame
= this->frame())
503 frame
->eventHandler().setResizingFrameSet(isResizing
? frameSet() : 0);
506 bool LayoutFrameSet::canResizeRow(const IntPoint
& p
) const
508 int r
= hitTestSplit(m_rows
, p
.y());
509 return r
!= noSplit
&& !m_rows
.m_preventResize
[r
];
512 bool LayoutFrameSet::canResizeColumn(const IntPoint
& p
) const
514 int c
= hitTestSplit(m_cols
, p
.x());
515 return c
!= noSplit
&& !m_cols
.m_preventResize
[c
];
518 int LayoutFrameSet::splitPosition(const GridAxis
& axis
, int split
) const
523 int borderThickness
= frameSet()->border();
525 int size
= axis
.m_sizes
.size();
530 for (int i
= 0; i
< split
&& i
< size
; ++i
)
531 position
+= axis
.m_sizes
[i
] + borderThickness
;
532 return position
- borderThickness
;
535 int LayoutFrameSet::hitTestSplit(const GridAxis
& axis
, int position
) const
540 int borderThickness
= frameSet()->border();
541 if (borderThickness
<= 0)
544 size_t size
= axis
.m_sizes
.size();
548 int splitPosition
= axis
.m_sizes
[0];
549 for (size_t i
= 1; i
< size
; ++i
) {
550 if (position
>= splitPosition
&& position
< splitPosition
+ borderThickness
)
552 splitPosition
+= borderThickness
+ axis
.m_sizes
[i
];
557 bool LayoutFrameSet::isChildAllowed(LayoutObject
* child
, const ComputedStyle
&) const
559 return child
->isFrame() || child
->isFrameSet();
562 CursorDirective
LayoutFrameSet::getCursor(const LayoutPoint
& point
, Cursor
& cursor
) const
564 IntPoint roundedPoint
= roundedIntPoint(point
);
565 if (canResizeRow(roundedPoint
)) {
566 cursor
= rowResizeCursor();
569 if (canResizeColumn(roundedPoint
)) {
570 cursor
= columnResizeCursor();
573 return LayoutBox::getCursor(point
, cursor
);