|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the QtGui module of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include "qscrollarea.h" |
|
43 #include "private/qscrollarea_p.h" |
|
44 |
|
45 #ifndef QT_NO_SCROLLAREA |
|
46 |
|
47 #include "qscrollbar.h" |
|
48 #include "qlayout.h" |
|
49 #include "qstyle.h" |
|
50 #include "qapplication.h" |
|
51 #include "qvariant.h" |
|
52 #include "qdebug.h" |
|
53 #include "private/qlayoutengine_p.h" |
|
54 |
|
55 QT_BEGIN_NAMESPACE |
|
56 |
|
57 /*! |
|
58 \class QScrollArea |
|
59 |
|
60 \brief The QScrollArea class provides a scrolling view onto |
|
61 another widget. |
|
62 |
|
63 \ingroup basicwidgets |
|
64 |
|
65 |
|
66 A scroll area is used to display the contents of a child widget |
|
67 within a frame. If the widget exceeds the size of the frame, the |
|
68 view can provide scroll bars so that the entire area of the child |
|
69 widget can be viewed. The child widget must be specified with |
|
70 setWidget(). For example: |
|
71 |
|
72 \snippet doc/src/snippets/code/src_gui_widgets_qscrollarea.cpp 0 |
|
73 |
|
74 The code above creates a scroll area (shown in the images below) |
|
75 containing an image label. When scaling the image, the scroll area |
|
76 can provide the necessary scroll bars: |
|
77 |
|
78 \table |
|
79 \row |
|
80 \o \inlineimage qscrollarea-noscrollbars.png |
|
81 \o \inlineimage qscrollarea-onescrollbar.png |
|
82 \o \inlineimage qscrollarea-twoscrollbars.png |
|
83 \endtable |
|
84 |
|
85 The scroll bars appearance depends on the currently set \l |
|
86 {Qt::ScrollBarPolicy}{scroll bar policies}. You can control the |
|
87 appearance of the scroll bars using the inherited functionality |
|
88 from QAbstractScrollArea. |
|
89 |
|
90 For example, you can set the |
|
91 QAbstractScrollArea::horizontalScrollBarPolicy and |
|
92 QAbstractScrollArea::verticalScrollBarPolicy properties. Or if you |
|
93 want the scroll bars to adjust dynamically when the contents of |
|
94 the scroll area changes, you can use the \l |
|
95 {QAbstractScrollArea::horizontalScrollBar()}{horizontalScrollBar()} |
|
96 and \l |
|
97 {QAbstractScrollArea::verticalScrollBar()}{verticalScrollBar()} |
|
98 functions (which enable you to access the scroll bars) and set the |
|
99 scroll bars' values whenever the scroll area's contents change, |
|
100 using the QScrollBar::setValue() function. |
|
101 |
|
102 You can retrieve the child widget using the widget() function. The |
|
103 view can be made to be resizable with the setWidgetResizable() |
|
104 function. The alignment of the widget can be specified with |
|
105 setAlignment(). |
|
106 |
|
107 Two convenience functions ensureVisible() and |
|
108 ensureWidgetVisible() ensure a certain region of the contents is |
|
109 visible inside the viewport, by scrolling the contents if |
|
110 necessary. |
|
111 |
|
112 \section1 Size Hints and Layouts |
|
113 |
|
114 When using a scroll area to display the contents of a custom |
|
115 widget, it is important to ensure that the |
|
116 \l{QWidget::sizeHint}{size hint} of the child widget is set to a |
|
117 suitable value. If a standard QWidget is used for the child |
|
118 widget, it may be necessary to call QWidget::setMinimumSize() to |
|
119 ensure that the contents of the widget are shown correctly within |
|
120 the scroll area. |
|
121 |
|
122 If a scroll area is used to display the contents of a widget that |
|
123 contains child widgets arranged in a layout, it is important to |
|
124 realise that the size policy of the layout will also determine the |
|
125 size of the widget. This is especially useful to know if you intend |
|
126 to dynamically change the contents of the layout. In such cases, |
|
127 setting the layout's \l{QLayout::sizeConstraint}{size constraint} |
|
128 property to one which provides constraints on the minimum and/or |
|
129 maximum size of the layout (e.g., QLayout::SetMinAndMaxSize) will |
|
130 cause the size of the scroll area to be updated whenever the |
|
131 contents of the layout changes. |
|
132 |
|
133 For a complete example using the QScrollArea class, see the \l |
|
134 {widgets/imageviewer}{Image Viewer} example. The example shows how |
|
135 to combine QLabel and QScrollArea to display an image. |
|
136 |
|
137 \sa QAbstractScrollArea, QScrollBar, {Image Viewer Example} |
|
138 */ |
|
139 |
|
140 |
|
141 /*! |
|
142 Constructs an empty scroll area with the given \a parent. |
|
143 |
|
144 \sa setWidget() |
|
145 */ |
|
146 QScrollArea::QScrollArea(QWidget *parent) |
|
147 : QAbstractScrollArea(*new QScrollAreaPrivate,parent) |
|
148 { |
|
149 Q_D(QScrollArea); |
|
150 d->viewport->setBackgroundRole(QPalette::NoRole); |
|
151 d->vbar->setSingleStep(20); |
|
152 d->hbar->setSingleStep(20); |
|
153 d->layoutChildren(); |
|
154 } |
|
155 |
|
156 /*! |
|
157 \internal |
|
158 */ |
|
159 QScrollArea::QScrollArea(QScrollAreaPrivate &dd, QWidget *parent) |
|
160 : QAbstractScrollArea(dd, parent) |
|
161 { |
|
162 Q_D(QScrollArea); |
|
163 d->viewport->setBackgroundRole(QPalette::NoRole); |
|
164 d->vbar->setSingleStep(20); |
|
165 d->hbar->setSingleStep(20); |
|
166 d->layoutChildren(); |
|
167 } |
|
168 |
|
169 /*! |
|
170 Destroys the scroll area and its child widget. |
|
171 |
|
172 \sa setWidget() |
|
173 */ |
|
174 QScrollArea::~QScrollArea() |
|
175 { |
|
176 } |
|
177 |
|
178 void QScrollAreaPrivate::updateWidgetPosition() |
|
179 { |
|
180 Q_Q(QScrollArea); |
|
181 Qt::LayoutDirection dir = q->layoutDirection(); |
|
182 QRect scrolled = QStyle::visualRect(dir, viewport->rect(), QRect(QPoint(-hbar->value(), -vbar->value()), widget->size())); |
|
183 QRect aligned = QStyle::alignedRect(dir, alignment, widget->size(), viewport->rect()); |
|
184 widget->move(widget->width() < viewport->width() ? aligned.x() : scrolled.x(), |
|
185 widget->height() < viewport->height() ? aligned.y() : scrolled.y()); |
|
186 } |
|
187 |
|
188 void QScrollAreaPrivate::updateScrollBars() |
|
189 { |
|
190 Q_Q(QScrollArea); |
|
191 if (!widget) |
|
192 return; |
|
193 QSize p = viewport->size(); |
|
194 QSize m = q->maximumViewportSize(); |
|
195 |
|
196 QSize min = qSmartMinSize(widget); |
|
197 QSize max = qSmartMaxSize(widget); |
|
198 |
|
199 if (resizable) { |
|
200 if ((widget->layout() ? widget->layout()->hasHeightForWidth() : widget->sizePolicy().hasHeightForWidth())) { |
|
201 QSize p_hfw = p.expandedTo(min).boundedTo(max); |
|
202 int h = widget->heightForWidth( p_hfw.width() ); |
|
203 min = QSize(p_hfw.width(), qMax(p_hfw.height(), h)); |
|
204 } |
|
205 } |
|
206 |
|
207 if ((resizable && m.expandedTo(min) == m && m.boundedTo(max) == m) |
|
208 || (!resizable && m.expandedTo(widget->size()) == m)) |
|
209 p = m; // no scroll bars needed |
|
210 |
|
211 if (resizable) |
|
212 widget->resize(p.expandedTo(min).boundedTo(max)); |
|
213 QSize v = widget->size(); |
|
214 |
|
215 hbar->setRange(0, v.width() - p.width()); |
|
216 hbar->setPageStep(p.width()); |
|
217 vbar->setRange(0, v.height() - p.height()); |
|
218 vbar->setPageStep(p.height()); |
|
219 updateWidgetPosition(); |
|
220 |
|
221 } |
|
222 |
|
223 /*! |
|
224 Returns the scroll area's widget, or 0 if there is none. |
|
225 |
|
226 \sa setWidget() |
|
227 */ |
|
228 |
|
229 QWidget *QScrollArea::widget() const |
|
230 { |
|
231 Q_D(const QScrollArea); |
|
232 return d->widget; |
|
233 } |
|
234 |
|
235 /*! |
|
236 \fn void QScrollArea::setWidget(QWidget *widget) |
|
237 |
|
238 Sets the scroll area's \a widget. |
|
239 |
|
240 The \a widget becomes a child of the scroll area, and will be |
|
241 destroyed when the scroll area is deleted or when a new widget is |
|
242 set. |
|
243 |
|
244 The widget's \l{QWidget::setAutoFillBackground()}{autoFillBackground} |
|
245 property will be set to \c{true}. |
|
246 |
|
247 If the scroll area is visible when the \a widget is |
|
248 added, you must \l{QWidget::}{show()} it explicitly. |
|
249 |
|
250 Note that You must add the layout of \a widget before you call |
|
251 this function; if you add it later, the \a widget will not be |
|
252 visible - regardless of when you \l{QWidget::}{show()} the scroll |
|
253 area. In this case, you can also not \l{QWidget::}{show()} the \a |
|
254 widget later. |
|
255 |
|
256 \sa widget() |
|
257 */ |
|
258 void QScrollArea::setWidget(QWidget *widget) |
|
259 { |
|
260 Q_D(QScrollArea); |
|
261 if (widget == d->widget || !widget) |
|
262 return; |
|
263 |
|
264 delete d->widget; |
|
265 d->widget = 0; |
|
266 d->hbar->setValue(0); |
|
267 d->vbar->setValue(0); |
|
268 if (widget->parentWidget() != d->viewport) |
|
269 widget->setParent(d->viewport); |
|
270 if (!widget->testAttribute(Qt::WA_Resized)) |
|
271 widget->resize(widget->sizeHint()); |
|
272 d->widget = widget; |
|
273 d->widget->setAutoFillBackground(true); |
|
274 widget->installEventFilter(this); |
|
275 d->widgetSize = QSize(); |
|
276 d->updateScrollBars(); |
|
277 d->widget->show(); |
|
278 |
|
279 } |
|
280 |
|
281 /*! |
|
282 Removes the scroll area's widget, and passes ownership of the |
|
283 widget to the caller. |
|
284 |
|
285 \sa widget() |
|
286 */ |
|
287 QWidget *QScrollArea::takeWidget() |
|
288 { |
|
289 Q_D(QScrollArea); |
|
290 QWidget *w = d->widget; |
|
291 d->widget = 0; |
|
292 if (w) |
|
293 w->setParent(0); |
|
294 return w; |
|
295 } |
|
296 |
|
297 /*! |
|
298 \reimp |
|
299 */ |
|
300 bool QScrollArea::event(QEvent *e) |
|
301 { |
|
302 Q_D(QScrollArea); |
|
303 if (e->type() == QEvent::StyleChange || e->type() == QEvent::LayoutRequest) { |
|
304 d->updateScrollBars(); |
|
305 } |
|
306 #ifdef QT_KEYPAD_NAVIGATION |
|
307 else if (QApplication::keypadNavigationEnabled()) { |
|
308 if (e->type() == QEvent::Show) |
|
309 QApplication::instance()->installEventFilter(this); |
|
310 else if (e->type() == QEvent::Hide) |
|
311 QApplication::instance()->removeEventFilter(this); |
|
312 } |
|
313 #endif |
|
314 return QAbstractScrollArea::event(e); |
|
315 } |
|
316 |
|
317 |
|
318 /*! |
|
319 \reimp |
|
320 */ |
|
321 bool QScrollArea::eventFilter(QObject *o, QEvent *e) |
|
322 { |
|
323 Q_D(QScrollArea); |
|
324 #ifdef QT_KEYPAD_NAVIGATION |
|
325 if (d->widget && o != d->widget && e->type() == QEvent::FocusIn |
|
326 && QApplication::keypadNavigationEnabled()) { |
|
327 if (o->isWidgetType()) |
|
328 ensureWidgetVisible(static_cast<QWidget *>(o)); |
|
329 } |
|
330 #endif |
|
331 if (o == d->widget && e->type() == QEvent::Resize) |
|
332 d->updateScrollBars(); |
|
333 |
|
334 return false; |
|
335 } |
|
336 |
|
337 /*! |
|
338 \reimp |
|
339 */ |
|
340 void QScrollArea::resizeEvent(QResizeEvent *) |
|
341 { |
|
342 Q_D(QScrollArea); |
|
343 d->updateScrollBars(); |
|
344 |
|
345 } |
|
346 |
|
347 |
|
348 /*!\reimp |
|
349 */ |
|
350 void QScrollArea::scrollContentsBy(int, int) |
|
351 { |
|
352 Q_D(QScrollArea); |
|
353 if (!d->widget) |
|
354 return; |
|
355 d->updateWidgetPosition(); |
|
356 } |
|
357 |
|
358 |
|
359 /*! |
|
360 \property QScrollArea::widgetResizable |
|
361 \brief whether the scroll area should resize the view widget |
|
362 |
|
363 If this property is set to false (the default), the scroll area |
|
364 honors the size of its widget. Regardless of this property, you |
|
365 can programmatically resize the widget using widget()->resize(), |
|
366 and the scroll area will automatically adjust itself to the new |
|
367 size. |
|
368 |
|
369 If this property is set to true, the scroll area will |
|
370 automatically resize the widget in order to avoid scroll bars |
|
371 where they can be avoided, or to take advantage of extra space. |
|
372 */ |
|
373 bool QScrollArea::widgetResizable() const |
|
374 { |
|
375 Q_D(const QScrollArea); |
|
376 return d->resizable; |
|
377 } |
|
378 |
|
379 void QScrollArea::setWidgetResizable(bool resizable) |
|
380 { |
|
381 Q_D(QScrollArea); |
|
382 d->resizable = resizable; |
|
383 updateGeometry(); |
|
384 d->updateScrollBars(); |
|
385 } |
|
386 |
|
387 /*! |
|
388 \reimp |
|
389 */ |
|
390 QSize QScrollArea::sizeHint() const |
|
391 { |
|
392 Q_D(const QScrollArea); |
|
393 int f = 2 * d->frameWidth; |
|
394 QSize sz(f, f); |
|
395 int h = fontMetrics().height(); |
|
396 if (d->widget) { |
|
397 if (!d->widgetSize.isValid()) |
|
398 d->widgetSize = d->resizable ? d->widget->sizeHint() : d->widget->size(); |
|
399 sz += d->widgetSize; |
|
400 } else { |
|
401 sz += QSize(12 * h, 8 * h); |
|
402 } |
|
403 if (d->vbarpolicy == Qt::ScrollBarAlwaysOn) |
|
404 sz.setWidth(sz.width() + d->vbar->sizeHint().width()); |
|
405 if (d->hbarpolicy == Qt::ScrollBarAlwaysOn) |
|
406 sz.setHeight(sz.height() + d->hbar->sizeHint().height()); |
|
407 return sz.boundedTo(QSize(36 * h, 24 * h)); |
|
408 } |
|
409 |
|
410 |
|
411 |
|
412 /*! |
|
413 \reimp |
|
414 */ |
|
415 bool QScrollArea::focusNextPrevChild(bool next) |
|
416 { |
|
417 if (QWidget::focusNextPrevChild(next)) { |
|
418 if (QWidget *fw = focusWidget()) |
|
419 ensureWidgetVisible(fw); |
|
420 return true; |
|
421 } |
|
422 return false; |
|
423 } |
|
424 |
|
425 /*! |
|
426 Scrolls the contents of the scroll area so that the point (\a x, \a y) is visible |
|
427 inside the region of the viewport with margins specified in pixels by \a xmargin and |
|
428 \a ymargin. If the specified point cannot be reached, the contents are scrolled to |
|
429 the nearest valid position. The default value for both margins is 50 pixels. |
|
430 */ |
|
431 void QScrollArea::ensureVisible(int x, int y, int xmargin, int ymargin) |
|
432 { |
|
433 Q_D(QScrollArea); |
|
434 |
|
435 int logicalX = QStyle::visualPos(layoutDirection(), d->viewport->rect(), QPoint(x, y)).x(); |
|
436 |
|
437 if (logicalX - xmargin < d->hbar->value()) { |
|
438 d->hbar->setValue(qMax(0, logicalX - xmargin)); |
|
439 } else if (logicalX > d->hbar->value() + d->viewport->width() - xmargin) { |
|
440 d->hbar->setValue(qMin(logicalX - d->viewport->width() + xmargin, d->hbar->maximum())); |
|
441 } |
|
442 |
|
443 if (y - ymargin < d->vbar->value()) { |
|
444 d->vbar->setValue(qMax(0, y - ymargin)); |
|
445 } else if (y > d->vbar->value() + d->viewport->height() - ymargin) { |
|
446 d->vbar->setValue(qMin(y - d->viewport->height() + ymargin, d->vbar->maximum())); |
|
447 } |
|
448 } |
|
449 |
|
450 /*! |
|
451 \since 4.2 |
|
452 |
|
453 Scrolls the contents of the scroll area so that the \a childWidget |
|
454 of QScrollArea::widget() is visible inside the viewport with |
|
455 margins specified in pixels by \a xmargin and \a ymargin. If the |
|
456 specified point cannot be reached, the contents are scrolled to |
|
457 the nearest valid position. The default value for both margins is |
|
458 50 pixels. |
|
459 |
|
460 */ |
|
461 void QScrollArea::ensureWidgetVisible(QWidget *childWidget, int xmargin, int ymargin) |
|
462 { |
|
463 Q_D(QScrollArea); |
|
464 |
|
465 if (!d->widget->isAncestorOf(childWidget)) |
|
466 return; |
|
467 |
|
468 const QRect microFocus = childWidget->inputMethodQuery(Qt::ImMicroFocus).toRect(); |
|
469 const QRect defaultMicroFocus = |
|
470 childWidget->QWidget::inputMethodQuery(Qt::ImMicroFocus).toRect(); |
|
471 QRect focusRect = (microFocus != defaultMicroFocus) |
|
472 ? QRect(childWidget->mapTo(d->widget, microFocus.topLeft()), microFocus.size()) |
|
473 : QRect(childWidget->mapTo(d->widget, QPoint(0,0)), childWidget->size()); |
|
474 const QRect visibleRect(-d->widget->pos(), d->viewport->size()); |
|
475 |
|
476 if (visibleRect.contains(focusRect)) |
|
477 return; |
|
478 |
|
479 focusRect.adjust(-xmargin, -ymargin, xmargin, ymargin); |
|
480 |
|
481 if (focusRect.width() > visibleRect.width()) |
|
482 d->hbar->setValue(focusRect.center().x() - d->viewport->width() / 2); |
|
483 else if (focusRect.right() > visibleRect.right()) |
|
484 d->hbar->setValue(focusRect.right() - d->viewport->width()); |
|
485 else |
|
486 d->hbar->setValue(focusRect.left()); |
|
487 |
|
488 if (focusRect.height() > visibleRect.height()) |
|
489 d->vbar->setValue(focusRect.center().y() - d->viewport->height() / 2); |
|
490 else if (focusRect.bottom() > visibleRect.bottom()) |
|
491 d->vbar->setValue(focusRect.bottom() - d->viewport->height()); |
|
492 else |
|
493 d->vbar->setValue(focusRect.top()); |
|
494 } |
|
495 |
|
496 |
|
497 /*! |
|
498 \property QScrollArea::alignment |
|
499 \brief the alignment of the scroll area's widget |
|
500 \since 4.2 |
|
501 |
|
502 By default, the widget stays rooted to the top-left corner of the |
|
503 scroll area. |
|
504 */ |
|
505 |
|
506 void QScrollArea::setAlignment(Qt::Alignment alignment) |
|
507 { |
|
508 Q_D(QScrollArea); |
|
509 d->alignment = alignment; |
|
510 if (d->widget) |
|
511 d->updateWidgetPosition(); |
|
512 } |
|
513 |
|
514 Qt::Alignment QScrollArea::alignment() const |
|
515 { |
|
516 Q_D(const QScrollArea); |
|
517 return d->alignment; |
|
518 } |
|
519 |
|
520 QT_END_NAMESPACE |
|
521 |
|
522 #endif // QT_NO_SCROLLAREA |