source: trunk/tools/designer/src/lib/shared/layout.cpp@ 651

Last change on this file since 651 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

File size: 42.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the Qt Designer of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
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** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "layout_p.h"
43#include "qdesigner_utils_p.h"
44#include "qlayout_widget_p.h"
45#include "spacer_widget_p.h"
46#include "layoutdecoration.h"
47#include "widgetfactory_p.h"
48#include "qdesigner_widgetitem_p.h"
49
50#include <QtDesigner/QDesignerFormEditorInterface>
51#include <QtDesigner/QDesignerFormWindowInterface>
52#include <QtDesigner/QDesignerContainerExtension>
53#include <QtDesigner/QExtensionManager>
54#include <QtDesigner/QDesignerPropertySheetExtension>
55#include <QtDesigner/QDesignerWidgetDataBaseInterface>
56#include <QtDesigner/QDesignerMetaDataBaseInterface>
57
58#include <QtCore/qdebug.h>
59#include <QtCore/QVector>
60
61#include <QtGui/qevent.h>
62#include <QtGui/QGridLayout>
63#include <QtGui/QPainter>
64#include <QtGui/QBitmap>
65#include <QtGui/QSplitter>
66#include <QtGui/QMainWindow>
67#include <QtGui/QApplication>
68#include <QtGui/QScrollArea>
69#include <QtGui/QFormLayout>
70#include <QtGui/QLabel>
71#include <QtGui/QWizardPage>
72#include <QtGui/QWizard>
73#include <QtCore/QDebug>
74#include <QtCore/QSet>
75
76QT_BEGIN_NAMESPACE
77
78enum { FormLayoutColumns = 2 };
79
80namespace qdesigner_internal {
81
82/* The wizard has a policy of setting a size policy of its external children
83 * according to the page being expanding or not (in the latter case, the
84 * page will be pushed to the top). When setting/breaking layouts, this needs
85 * to be updated, which happens via a fake style change event. */
86
87void updateWizardLayout(QWidget *layoutBase);
88
89class FriendlyWizardPage : public QWizardPage {
90 friend void updateWizardLayout(QWidget *);
91};
92
93void updateWizardLayout(QWidget *layoutBase)
94{
95 if (QWizardPage *wizardPage = qobject_cast<QWizardPage*>(layoutBase))
96 if (QWizard *wizard = static_cast<FriendlyWizardPage*>(wizardPage)->wizard()) {
97 QEvent event(QEvent::StyleChange);
98 QApplication::sendEvent(wizard, &event);
99 }
100}
101
102/*!
103 \class Layout layout.h
104 \brief Baseclass for layouting widgets in the Designer (Helper for Layout commands)
105 \internal
106
107 Classes derived from this abstract base class are used for layouting
108 operations in the Designer (creating/breaking layouts).
109
110 Instances live in the Layout/BreakLayout commands.
111*/
112
113/*! \a p specifies the parent of the layoutBase \a lb. The parent
114 might be changed in setup(). If the layoutBase is a
115 container, the parent and the layoutBase are the same. Also they
116 always have to be a widget known to the designer (e.g. in the case
117 of the tabwidget parent and layoutBase are the tabwidget and not the
118 page which actually gets laid out. For actual usage the correct
119 widget is found later by Layout.)
120 */
121
122Layout::Layout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, LayoutInfo::Type layoutType) :
123 m_widgets(wl),
124 m_parentWidget(p),
125 m_layoutBase(lb),
126 m_formWindow(fw),
127 m_layoutType(layoutType),
128 m_reparentLayoutWidget(true),
129 m_isBreak(false)
130{
131 if (m_layoutBase)
132 m_oldGeometry = m_layoutBase->geometry();
133}
134
135Layout::~Layout()
136{
137}
138
139/*! The widget list we got in the constructor might contain too much
140 widgets (like widgets with different parents, already laid out
141 widgets, etc.). Here we set up the list and so the only the "best"
142 widgets get laid out.
143*/
144
145void Layout::setup()
146{
147 m_startPoint = QPoint(32767, 32767);
148
149 // Go through all widgets of the list we got. As we can only
150 // layout widgets which have the same parent, we first do some
151 // sorting which means create a list for each parent containing
152 // its child here. After that we keep working on the list of
153 // children which has the most entries.
154 // Widgets which are already laid out are thrown away here too
155
156 QMultiMap<QWidget*, QWidget*> lists;
157 foreach (QWidget *w, m_widgets) {
158 QWidget *p = w->parentWidget();
159
160 if (p && LayoutInfo::layoutType(m_formWindow->core(), p) != LayoutInfo::NoLayout
161 && m_formWindow->core()->metaDataBase()->item(p->layout()) != 0)
162 continue;
163
164 lists.insert(p, w);
165 }
166
167 QWidgetList lastList;
168 QWidgetList parents = lists.keys();
169 foreach (QWidget *p, parents) {
170 QWidgetList children = lists.values(p);
171
172 if (children.count() > lastList.count())
173 lastList = children;
174 }
175
176
177 // If we found no list (because no widget did fit at all) or the
178 // best list has only one entry and we do not layout a container,
179 // we leave here.
180 QDesignerWidgetDataBaseInterface *widgetDataBase = m_formWindow->core()->widgetDataBase();
181 if (lastList.count() < 2 &&
182 (!m_layoutBase ||
183 (!widgetDataBase->isContainer(m_layoutBase, false) &&
184 m_layoutBase != m_formWindow->mainContainer()))
185 ) {
186 m_widgets.clear();
187 m_startPoint = QPoint(0, 0);
188 return;
189 }
190
191 // Now we have a new and clean widget list, which makes sense
192 // to layout
193 m_widgets = lastList;
194 // Also use the only correct parent later, so store it
195
196 Q_ASSERT(m_widgets.isEmpty() == false);
197
198 m_parentWidget = m_formWindow->core()->widgetFactory()->widgetOfContainer(m_widgets.first()->parentWidget());
199 // Now calculate the position where the layout-meta-widget should
200 // be placed and connect to widgetDestroyed() signals of the
201 // widgets to get informed if one gets deleted to be able to
202 // handle that and do not crash in this case
203 foreach (QWidget *w, m_widgets) {
204 connect(w, SIGNAL(destroyed()), this, SLOT(widgetDestroyed()));
205 m_startPoint = QPoint(qMin(m_startPoint.x(), w->x()), qMin(m_startPoint.y(), w->y()));
206 const QRect rc(w->geometry());
207
208 m_geometries.insert(w, rc);
209 // Change the Z-order, as saving/loading uses the Z-order for
210 // writing/creating widgets and this has to be the same as in
211 // the layout. Else saving + loading will give different results
212 w->raise();
213 }
214
215 sort();
216}
217
218void Layout::widgetDestroyed()
219{
220 if (QWidget *w = qobject_cast<QWidget *>(sender())) {
221 m_widgets.removeAt(m_widgets.indexOf(w));
222 m_geometries.remove(w);
223 }
224}
225
226bool Layout::prepareLayout(bool &needMove, bool &needReparent)
227{
228 foreach (QWidget *widget, m_widgets) {
229 widget->raise();
230 }
231
232 needMove = !m_layoutBase;
233 needReparent = needMove || (m_reparentLayoutWidget && qobject_cast<QLayoutWidget*>(m_layoutBase)) || qobject_cast<QSplitter*>(m_layoutBase);
234
235 QDesignerWidgetFactoryInterface *widgetFactory = m_formWindow->core()->widgetFactory();
236 QDesignerMetaDataBaseInterface *metaDataBase = m_formWindow->core()->metaDataBase();
237
238 if (m_layoutBase == 0) {
239 const bool useSplitter = m_layoutType == LayoutInfo::HSplitter || m_layoutType == LayoutInfo::VSplitter;
240 const QString baseWidgetClassName = useSplitter ? QLatin1String("QSplitter") : QLatin1String("QLayoutWidget");
241 m_layoutBase = widgetFactory->createWidget(baseWidgetClassName, widgetFactory->containerOfWidget(m_parentWidget));
242 if (useSplitter) {
243 m_layoutBase->setObjectName(QLatin1String("splitter"));
244 m_formWindow->ensureUniqueObjectName(m_layoutBase);
245 }
246 } else {
247 LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase);
248 }
249
250 metaDataBase->add(m_layoutBase);
251
252 Q_ASSERT(m_layoutBase->layout() == 0 || metaDataBase->item(m_layoutBase->layout()) == 0);
253
254 return true;
255}
256
257static bool isMainContainer(QDesignerFormWindowInterface *fw, const QWidget *w)
258{
259 return w && (w == fw || w == fw->mainContainer());
260}
261
262static bool isPageOfContainerWidget(QDesignerFormWindowInterface *fw, QWidget *widget)
263{
264 QDesignerContainerExtension *c = qt_extension<QDesignerContainerExtension*>(
265 fw->core()->extensionManager(), widget->parentWidget());
266
267 if (c != 0) {
268 for (int i = 0; i<c->count(); ++i) {
269 if (widget == c->widget(i))
270 return true;
271 }
272 }
273
274 return false;
275}
276void Layout::finishLayout(bool needMove, QLayout *layout)
277{
278 if (m_parentWidget == m_layoutBase) {
279 QWidget *widget = m_layoutBase;
280 m_oldGeometry = widget->geometry();
281
282 bool done = false;
283 while (!isMainContainer(m_formWindow, widget) && !done) {
284 if (!m_formWindow->isManaged(widget)) {
285 widget = widget->parentWidget();
286 continue;
287 } else if (LayoutInfo::isWidgetLaidout(m_formWindow->core(), widget)) {
288 widget = widget->parentWidget();
289 continue;
290 } else if (isPageOfContainerWidget(m_formWindow, widget)) {
291 widget = widget->parentWidget();
292 continue;
293 } else if (widget->parentWidget()) {
294 QScrollArea *area = qobject_cast<QScrollArea*>(widget->parentWidget()->parentWidget());
295 if (area && area->widget() == widget) {
296 widget = area;
297 continue;
298 }
299 }
300
301 done = true;
302 }
303 updateWizardLayout(m_layoutBase);
304 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
305 // We don't want to resize the form window
306 if (!Utils::isCentralWidget(m_formWindow, widget))
307 widget->adjustSize();
308
309 return;
310 }
311
312 if (needMove)
313 m_layoutBase->move(m_startPoint);
314
315 const QRect g(m_layoutBase->pos(), m_layoutBase->size());
316
317 if (LayoutInfo::layoutType(m_formWindow->core(), m_layoutBase->parentWidget()) == LayoutInfo::NoLayout && !m_isBreak)
318 m_layoutBase->adjustSize();
319 else if (m_isBreak)
320 m_layoutBase->setGeometry(m_oldGeometry);
321
322 m_oldGeometry = g;
323 if (layout)
324 layout->invalidate();
325 m_layoutBase->show();
326
327 if (qobject_cast<QLayoutWidget*>(m_layoutBase) || qobject_cast<QSplitter*>(m_layoutBase)) {
328 m_formWindow->clearSelection(false);
329 m_formWindow->manageWidget(m_layoutBase);
330 m_formWindow->selectWidget(m_layoutBase);
331 }
332}
333
334void Layout::undoLayout()
335{
336 if (!m_widgets.count())
337 return;
338
339 m_formWindow->selectWidget(m_layoutBase, false);
340
341 QDesignerWidgetFactoryInterface *widgetFactory = m_formWindow->core()->widgetFactory();
342 QHashIterator<QWidget *, QRect> it(m_geometries);
343 while (it.hasNext()) {
344 it.next();
345
346 if (!it.key())
347 continue;
348
349 QWidget* w = it.key();
350 const QRect rc = it.value();
351
352 const bool showIt = w->isVisibleTo(m_formWindow);
353 QWidget *container = widgetFactory->containerOfWidget(m_parentWidget);
354
355 // ### remove widget here
356 QWidget *parentWidget = w->parentWidget();
357 QDesignerFormEditorInterface *core = m_formWindow->core();
358 QDesignerLayoutDecorationExtension *deco = qt_extension<QDesignerLayoutDecorationExtension*>(core->extensionManager(), parentWidget);
359
360 if (deco)
361 deco->removeWidget(w);
362
363 w->setParent(container);
364 w->setGeometry(rc);
365
366 if (showIt)
367 w->show();
368 }
369
370 LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase);
371
372 if (m_parentWidget != m_layoutBase && !qobject_cast<QMainWindow*>(m_layoutBase)) {
373 m_formWindow->unmanageWidget(m_layoutBase);
374 m_layoutBase->hide();
375 } else {
376 QMainWindow *mw = qobject_cast<QMainWindow*>(m_formWindow->mainContainer());
377 if (m_layoutBase != m_formWindow->mainContainer() &&
378 (!mw || mw->centralWidget() != m_layoutBase))
379 m_layoutBase->setGeometry(m_oldGeometry);
380 }
381}
382
383void Layout::breakLayout()
384{
385 typedef QMap<QWidget *, QRect> WidgetRectMap;
386 WidgetRectMap rects;
387 /* Store the geometry of the widgets. The idea is to give the user space
388 * to rearrange them, so, we do a adjustSize() on them, unless they want
389 * to grow (expanding widgets like QTextEdit), in which the geometry is
390 * preserved. Note that historically, geometries were re-applied
391 * only after breaking splitters. */
392 foreach (QWidget *w, m_widgets) {
393 const QRect geom = w->geometry();
394 const QSize sizeHint = w->sizeHint();
395 const bool restoreGeometry = sizeHint.isEmpty() || sizeHint.width() > geom.width() || sizeHint.height() > geom.height();
396 rects.insert(w, restoreGeometry ? w->geometry() : QRect(geom.topLeft(), QSize()));
397 }
398 const QPoint m_layoutBasePos = m_layoutBase->pos();
399 QDesignerWidgetDataBaseInterface *widgetDataBase = m_formWindow->core()->widgetDataBase();
400
401 LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase);
402
403 const bool needReparent = (m_reparentLayoutWidget && qobject_cast<QLayoutWidget*>(m_layoutBase)) ||
404 qobject_cast<QSplitter*>(m_layoutBase) ||
405 (!widgetDataBase->isContainer(m_layoutBase, false) &&
406 m_layoutBase != m_formWindow->mainContainer());
407 const bool add = m_geometries.isEmpty();
408
409 QMapIterator<QWidget*, QRect> it(rects);
410 while (it.hasNext()) {
411 it.next();
412
413 QWidget *w = it.key();
414 if (needReparent) {
415 w->setParent(m_layoutBase->parentWidget(), 0);
416 w->move(m_layoutBasePos + it.value().topLeft());
417 w->show();
418 }
419
420 const QRect oldGeometry = it.value();
421 if (oldGeometry.isEmpty()) {
422 w->adjustSize();
423 } else {
424 w->resize(oldGeometry.size());
425 }
426
427 if (add)
428 m_geometries.insert(w, QRect(w->pos(), w->size()));
429 }
430
431 if (needReparent) {
432 m_layoutBase->hide();
433 m_parentWidget = m_layoutBase->parentWidget();
434 m_formWindow->unmanageWidget(m_layoutBase);
435 } else {
436 m_parentWidget = m_layoutBase;
437 }
438 updateWizardLayout(m_layoutBase);
439
440 if (!m_widgets.isEmpty() && m_widgets.first() && m_widgets.first()->isVisibleTo(m_formWindow))
441 m_formWindow->selectWidget(m_widgets.first());
442 else
443 m_formWindow->selectWidget(m_formWindow);
444}
445
446static QString suggestLayoutName(const char *className)
447{
448 // Legacy
449 if (!qstrcmp(className, "QHBoxLayout"))
450 return QLatin1String("horizontalLayout");
451 if (!qstrcmp(className, "QVBoxLayout"))
452 return QLatin1String("verticalLayout");
453 if (!qstrcmp(className, "QGridLayout"))
454 return QLatin1String("gridLayout");
455
456 return qtify(QString::fromUtf8(className));
457}
458QLayout *Layout::createLayout(int type)
459{
460 Q_ASSERT(m_layoutType != LayoutInfo::HSplitter && m_layoutType != LayoutInfo::VSplitter);
461 QLayout *layout = m_formWindow->core()->widgetFactory()->createLayout(m_layoutBase, 0, type);
462 // set a name
463 layout->setObjectName(suggestLayoutName(layout->metaObject()->className()));
464 m_formWindow->ensureUniqueObjectName(layout);
465 // QLayoutWidget
466 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(m_formWindow->core()->extensionManager(), layout);
467 if (sheet && qobject_cast<QLayoutWidget*>(m_layoutBase)) {
468 sheet->setProperty(sheet->indexOf(QLatin1String("leftMargin")), 0);
469 sheet->setProperty(sheet->indexOf(QLatin1String("topMargin")), 0);
470 sheet->setProperty(sheet->indexOf(QLatin1String("rightMargin")), 0);
471 sheet->setProperty(sheet->indexOf(QLatin1String("bottomMargin")), 0);
472 }
473 return layout;
474}
475
476void Layout::reparentToLayoutBase(QWidget *w)
477{
478 if (w->parent() != m_layoutBase) {
479 w->setParent(m_layoutBase, 0);
480 w->move(QPoint(0,0));
481 }
482}
483
484namespace { // within qdesigner_internal
485
486// ----- PositionSortPredicate: Predicate to be usable as LessThan function to sort widgets by position
487class PositionSortPredicate {
488public:
489 PositionSortPredicate(Qt::Orientation orientation) : m_orientation(orientation) {}
490 bool operator()(const QWidget* w1, const QWidget* w2) {
491 return m_orientation == Qt::Horizontal ? w1->x() < w2->x() : w1->y() < w2->y();
492 }
493 private:
494 const Qt::Orientation m_orientation;
495};
496
497// -------- BoxLayout
498class BoxLayout : public Layout
499{
500public:
501 BoxLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb,
502 Qt::Orientation orientation);
503
504 virtual void doLayout();
505 virtual void sort();
506
507private:
508 const Qt::Orientation m_orientation;
509};
510
511BoxLayout::BoxLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb,
512 Qt::Orientation orientation) :
513 Layout(wl, p, fw, lb, orientation == Qt::Horizontal ? LayoutInfo::HBox : LayoutInfo::VBox),
514 m_orientation(orientation)
515{
516}
517
518void BoxLayout::sort()
519{
520 QWidgetList wl = widgets();
521 qStableSort(wl.begin(), wl.end(), PositionSortPredicate(m_orientation));
522 setWidgets(wl);
523}
524
525void BoxLayout::doLayout()
526{
527 bool needMove, needReparent;
528 if (!prepareLayout(needMove, needReparent))
529 return;
530
531 QBoxLayout *layout = static_cast<QBoxLayout *>(createLayout(m_orientation == Qt::Horizontal ? LayoutInfo::HBox : LayoutInfo::VBox));
532
533 QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem.
534
535 const QWidgetList::const_iterator cend = widgets().constEnd();
536 for (QWidgetList::const_iterator it = widgets().constBegin(); it != cend; ++it) {
537 QWidget *w = *it;
538 if (needReparent)
539 reparentToLayoutBase(w);
540
541 if (const Spacer *spacer = qobject_cast<const Spacer*>(w))
542 layout->addWidget(w, 0, spacer->alignment());
543 else
544 layout->addWidget(w);
545 w->show();
546 }
547 finishLayout(needMove, layout);
548}
549
550// -------- SplitterLayout
551class SplitterLayout : public Layout
552{
553public:
554 SplitterLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb,
555 Qt::Orientation orientation);
556
557 virtual void doLayout();
558 virtual void sort();
559
560private:
561 const Qt::Orientation m_orientation;
562};
563
564SplitterLayout::SplitterLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb,
565 Qt::Orientation orientation) :
566 Layout(wl, p, fw, lb, orientation == Qt::Horizontal ? LayoutInfo::HSplitter : LayoutInfo::VSplitter),
567 m_orientation(orientation)
568{
569}
570
571void SplitterLayout::sort()
572{
573 QWidgetList wl = widgets();
574 qStableSort(wl.begin(), wl.end(), PositionSortPredicate(m_orientation));
575 setWidgets(wl);
576}
577
578void SplitterLayout::doLayout()
579{
580 bool needMove, needReparent;
581 if (!prepareLayout(needMove, needReparent))
582 return;
583
584 QSplitter *splitter = qobject_cast<QSplitter*>(layoutBaseWidget());
585 Q_ASSERT(splitter != 0);
586
587
588 const QWidgetList::const_iterator cend = widgets().constEnd();
589 for (QWidgetList::const_iterator it = widgets().constBegin(); it != cend; ++it) {
590 QWidget *w = *it;
591 if (needReparent)
592 reparentToLayoutBase(w);
593 splitter->addWidget(w);
594 w->show();
595 }
596
597 splitter->setOrientation(m_orientation);
598 finishLayout(needMove);
599}
600
601// ---------- Grid: Helper for laying out grids
602
603class Grid
604{
605public:
606 enum Mode {
607 GridLayout, // Arbitrary size/supports span
608 FormLayout // 2-column/no span
609 };
610
611 Grid(Mode mode);
612 void resize(int nrows, int ncols);
613
614 ~Grid();
615
616 QWidget* cell(int row, int col) const { return m_cells[ row * m_ncols + col]; }
617
618 void setCells(const QRect &c, QWidget* w);
619
620 bool empty() const { return m_nrows * m_ncols; }
621 int numRows() const { return m_nrows; }
622 int numCols() const { return m_ncols; }
623
624 void simplify();
625 bool locateWidget(QWidget* w, int& row, int& col, int& rowspan, int& colspan) const;
626
627 QDebug debug(QDebug str) const;
628
629private:
630 void setCell(int row, int col, QWidget* w) { m_cells[ row * m_ncols + col] = w; }
631 void swapCells(int r1, int c1, int r2, int c2);
632 void shrink();
633 void reallocFormLayout();
634 int countRow(int r, int c) const;
635 int countCol(int r, int c) const;
636 void setRow(int r, int c, QWidget* w, int count);
637 void setCol(int r, int c, QWidget* w, int count);
638 bool isWidgetStartCol(int c) const;
639 bool isWidgetEndCol(int c) const;
640 bool isWidgetStartRow(int r) const;
641 bool isWidgetEndRow(int r) const;
642 bool isWidgetTopLeft(int r, int c) const;
643 void extendLeft();
644 void extendRight();
645 void extendUp();
646 void extendDown();
647 bool shrinkFormLayoutSpans();
648
649 const Mode m_mode;
650 int m_nrows;
651 int m_ncols;
652
653 QWidget** m_cells; // widget matrix w11, w12, w21...
654};
655
656Grid::Grid(Mode mode) :
657 m_mode(mode),
658 m_nrows(0),
659 m_ncols(0),
660 m_cells(0)
661{
662}
663
664Grid::~Grid()
665{
666 delete [] m_cells;
667}
668
669void Grid::resize(int nrows, int ncols)
670{
671 delete [] m_cells;
672 m_cells = 0;
673 m_nrows = nrows;
674 m_ncols = ncols;
675 if (const int allocSize = m_nrows * m_ncols) {
676 m_cells = new QWidget*[allocSize];
677 qFill(m_cells, m_cells + allocSize, static_cast<QWidget *>(0));
678 }
679}
680
681QDebug Grid::debug(QDebug str) const
682{
683 str << m_nrows << 'x' << m_ncols << '\n';
684 QSet<QWidget *> widgets;
685 const int cellCount = m_nrows * m_ncols;
686 int row, col, rowspan, colspan;
687 for (int c = 0; c < cellCount; c++)
688 if (QWidget *w = m_cells[c])
689 if (!widgets.contains(w)) {
690 widgets.insert(w);
691 locateWidget(w, row, col, rowspan, colspan);
692 str << w << " at " << row << col << rowspan << 'x' << colspan << '\n';
693 }
694 for (int r = 0; r < m_nrows; r++)
695 for (int c = 0; c < m_ncols; c++)
696 str << "At " << r << c << cell(r, c) << '\n';
697
698 return str;
699}
700
701static inline QDebug operator<<(QDebug str, const Grid &g) { return g.debug(str); }
702
703void Grid::setCells(const QRect &c, QWidget* w)
704{
705 const int bottom = c.top() + c.height();
706 const int width = c.width();
707
708 for (int r = c.top(); r < bottom; r++) {
709 QWidget **pos = m_cells + r * m_ncols + c.left();
710 qFill(pos, pos + width, w);
711 }
712}
713
714
715void Grid::swapCells(int r1, int c1, int r2, int c2)
716{
717 QWidget *w1 = cell(r1, c1);
718 setCell(r1, c1, cell(r2, c2));
719 setCell(r2, c2, w1);
720}
721
722int Grid::countRow(int r, int c) const
723{
724 QWidget* w = cell(r, c);
725 int i = c + 1;
726 while (i < m_ncols && cell(r, i) == w)
727 i++;
728 return i - c;
729}
730
731int Grid::countCol(int r, int c) const
732{
733 QWidget* w = cell(r, c);
734 int i = r + 1;
735 while (i < m_nrows && cell(i, c) == w)
736 i++;
737 return i - r;
738}
739
740void Grid::setCol(int r, int c, QWidget* w, int count)
741{
742 for (int i = 0; i < count; i++)
743 setCell(r + i, c, w);
744}
745
746void Grid::setRow(int r, int c, QWidget* w, int count)
747{
748 for (int i = 0; i < count; i++)
749 setCell(r, c + i, w);
750}
751
752bool Grid::isWidgetStartCol(int c) const
753{
754 for (int r = 0; r < m_nrows; r++) {
755 if (cell(r, c) && ((c==0) || (cell(r, c) != cell(r, c-1)))) {
756 return true;
757 }
758 }
759 return false;
760}
761
762bool Grid::isWidgetEndCol(int c) const
763{
764 for (int r = 0; r < m_nrows; r++) {
765 if (cell(r, c) && ((c == m_ncols-1) || (cell(r, c) != cell(r, c+1))))
766 return true;
767 }
768 return false;
769}
770
771bool Grid::isWidgetStartRow(int r) const
772{
773 for ( int c = 0; c < m_ncols; c++) {
774 if (cell(r, c) && ((r==0) || (cell(r, c) != cell(r-1, c))))
775 return true;
776 }
777 return false;
778}
779
780bool Grid::isWidgetEndRow(int r) const
781{
782 for (int c = 0; c < m_ncols; c++) {
783 if (cell(r, c) && ((r == m_nrows-1) || (cell(r, c) != cell(r+1, c))))
784 return true;
785 }
786 return false;
787}
788
789
790bool Grid::isWidgetTopLeft(int r, int c) const
791{
792 QWidget* w = cell(r, c);
793 if (!w)
794 return false;
795 return (!r || cell(r-1, c) != w) && (!c || cell(r, c-1) != w);
796}
797
798void Grid::extendLeft()
799{
800 for (int c = 1; c < m_ncols; c++) {
801 for (int r = 0; r < m_nrows; r++) {
802 QWidget* w = cell(r, c);
803 if (!w)
804 continue;
805
806 const int cc = countCol(r, c);
807 int stretch = 0;
808 for (int i = c-1; i >= 0; i--) {
809 if (cell(r, i))
810 break;
811 if (countCol(r, i) < cc)
812 break;
813 if (isWidgetEndCol(i))
814 break;
815 if (isWidgetStartCol(i)) {
816 stretch = c - i;
817 break;
818 }
819 }
820 if (stretch) {
821 for (int i = 0; i < stretch; i++)
822 setCol(r, c-i-1, w, cc);
823 }
824 }
825 }
826}
827
828
829void Grid::extendRight()
830{
831 for (int c = m_ncols - 2; c >= 0; c--) {
832 for (int r = 0; r < m_nrows; r++) {
833 QWidget* w = cell(r, c);
834 if (!w)
835 continue;
836 const int cc = countCol(r, c);
837 int stretch = 0;
838 for (int i = c+1; i < m_ncols; i++) {
839 if (cell(r, i))
840 break;
841 if (countCol(r, i) < cc)
842 break;
843 if (isWidgetStartCol(i))
844 break;
845 if (isWidgetEndCol(i)) {
846 stretch = i - c;
847 break;
848 }
849 }
850 if (stretch) {
851 for (int i = 0; i < stretch; i++)
852 setCol(r, c+i+1, w, cc);
853 }
854 }
855 }
856
857}
858
859void Grid::extendUp()
860{
861 for (int r = 1; r < m_nrows; r++) {
862 for (int c = 0; c < m_ncols; c++) {
863 QWidget* w = cell(r, c);
864 if (!w)
865 continue;
866 const int cr = countRow(r, c);
867 int stretch = 0;
868 for (int i = r-1; i >= 0; i--) {
869 if (cell(i, c))
870 break;
871 if (countRow(i, c) < cr)
872 break;
873 if (isWidgetEndRow(i))
874 break;
875 if (isWidgetStartRow(i)) {
876 stretch = r - i;
877 break;
878 }
879 }
880 if (stretch) {
881 for (int i = 0; i < stretch; i++)
882 setRow(r-i-1, c, w, cr);
883 }
884 }
885 }
886}
887
888void Grid::extendDown()
889{
890 for (int r = m_nrows - 2; r >= 0; r--) {
891 for (int c = 0; c < m_ncols; c++) {
892 QWidget* w = cell(r, c);
893 if (!w)
894 continue;
895 const int cr = countRow(r, c);
896 int stretch = 0;
897 for (int i = r+1; i < m_nrows; i++) {
898 if (cell(i, c))
899 break;
900 if (countRow(i, c) < cr)
901 break;
902 if (isWidgetStartRow(i))
903 break;
904 if (isWidgetEndRow(i)) {
905 stretch = i - r;
906 break;
907 }
908 }
909 if (stretch) {
910 for (int i = 0; i < stretch; i++)
911 setRow(r+i+1, c, w, cr);
912 }
913 }
914 }
915}
916
917void Grid::simplify()
918{
919 switch (m_mode) {
920 case GridLayout:
921 // Grid: Extend all widgets to occupy most space and delete
922 // rows/columns that are not bordering on a widget
923 extendLeft();
924 extendRight();
925 extendUp();
926 extendDown();
927 shrink();
928 break;
929 case FormLayout:
930 // Form: First treat it as a grid to get the same behaviour
931 // regarding spanning and shrinking. Then restrict the span to
932 // the horizontal span possible in the form, simplify again
933 // and spread the widgets over a 2-column layout
934 extendLeft();
935 extendRight();
936 extendUp();
937 extendDown();
938 shrink();
939 if (shrinkFormLayoutSpans())
940 shrink();
941 reallocFormLayout();
942 break;
943 }
944
945}
946
947void Grid::shrink()
948{
949 // tick off the occupied cols/rows (bordering on widget edges)
950 QVector<bool> columns(m_ncols, false);
951 QVector<bool> rows(m_nrows, false);
952
953 for (int c = 0; c < m_ncols; c++)
954 for (int r = 0; r < m_nrows; r++)
955 if (isWidgetTopLeft(r, c))
956 rows[r] = columns[c] = true;
957
958 // remove empty cols/rows
959 const int simplifiedNCols = columns.count(true);
960 const int simplifiedNRows = rows.count(true);
961 if (simplifiedNCols == m_ncols && simplifiedNRows == m_nrows)
962 return;
963 // reallocate and copy omitting the empty cells
964 QWidget **simplifiedCells = new QWidget*[simplifiedNCols * simplifiedNRows];
965 qFill(simplifiedCells, simplifiedCells + simplifiedNCols * simplifiedNRows, static_cast<QWidget *>(0));
966 QWidget **simplifiedPtr = simplifiedCells;
967
968 for (int r = 0; r < m_nrows; r++)
969 if (rows[r])
970 for (int c = 0; c < m_ncols; c++)
971 if (columns[c]) {
972 if (QWidget *w = cell(r, c))
973 *simplifiedPtr = w;
974 simplifiedPtr++;
975 }
976 Q_ASSERT(simplifiedPtr == simplifiedCells + simplifiedNCols * simplifiedNRows);
977 delete [] m_cells;
978 m_cells = simplifiedCells;
979 m_nrows = simplifiedNRows;
980 m_ncols = simplifiedNCols;
981}
982
983bool Grid::shrinkFormLayoutSpans()
984{
985 bool shrunk = false;
986 typedef QSet<QWidget*> WidgetSet;
987 // Determine unique set of widgets
988 WidgetSet widgets;
989 QWidget **end = m_cells + m_ncols * m_nrows;
990 for (QWidget **wptr = m_cells; wptr < end; wptr++)
991 if (QWidget *w = *wptr)
992 widgets.insert(w);
993 // Restrict the widget span: max horizontal span at column 0: 2, anything else: 1
994 const int maxRowSpan = 1;
995 const WidgetSet::const_iterator cend = widgets.constEnd();
996 for (WidgetSet::const_iterator it = widgets.constBegin(); it != cend ; ++it) {
997 QWidget *w = *it;
998 int row, col, rowspan, colspan;
999 locateWidget(w, row, col, rowspan, colspan);
1000 const int maxColSpan = col == 0 ? 2 : 1;
1001 const int newColSpan = qMin(colspan, maxColSpan);
1002 const int newRowSpan = qMin(rowspan, maxRowSpan);
1003 if (newColSpan != colspan || newRowSpan != rowspan) {
1004 setCells(QRect(col, row, colspan, rowspan), 0);
1005 setCells(QRect(col, row, newColSpan, newRowSpan), w);
1006 shrunk = true;
1007 }
1008 }
1009 return shrunk;
1010}
1011
1012void Grid::reallocFormLayout()
1013{
1014 // Columns matching? -> happy!
1015 if (m_ncols == FormLayoutColumns)
1016 return;
1017
1018 // If there are offset columns (starting past the field column),
1019 // move them to the left and squeeze them. This also prevents the
1020 // following reallocation from creating empty form rows.
1021 int pastRightWidgetCount = 0;
1022 if (m_ncols > FormLayoutColumns) {
1023 for (int r = 0; r < m_nrows; r++) {
1024 // Try to find a column where the form columns are empty and
1025 // there are widgets further to the right.
1026 if (cell(r, 0) == 0 && cell(r, 1) == 0) {
1027 int sourceCol = FormLayoutColumns;
1028 QWidget *firstWidget = 0;
1029 for ( ; sourceCol < m_ncols; sourceCol++)
1030 if (QWidget *w = cell(r, sourceCol)) {
1031 firstWidget = w;
1032 break;
1033 }
1034 if (firstWidget) {
1035 // Move/squeeze. Copy to beginning of column if it is a label, else field
1036 int targetCol = qobject_cast<QLabel*>(firstWidget) ? 0 : 1;
1037 for ( ; sourceCol < m_ncols; sourceCol++)
1038 if (QWidget *w = cell(r, sourceCol))
1039 setCell(r, targetCol++, w);
1040 // Pad with zero
1041 for ( ; targetCol < m_ncols; targetCol++)
1042 setCell(r, targetCol, 0);
1043 }
1044 }
1045 // Any protruding widgets left on that row?
1046 for (int c = FormLayoutColumns; c < m_ncols; c++)
1047 if (cell(r, c))
1048 pastRightWidgetCount++;
1049 }
1050 }
1051 // Reallocate with 2 columns. Just insert the protruding ones as fields.
1052 const int formNRows = m_nrows + pastRightWidgetCount;
1053 QWidget **formCells = new QWidget*[FormLayoutColumns * formNRows];
1054 qFill(formCells, formCells + FormLayoutColumns * formNRows, static_cast<QWidget *>(0));
1055 QWidget **formPtr = formCells;
1056 const int matchingColumns = qMin(m_ncols, static_cast<int>(FormLayoutColumns));
1057 for (int r = 0; r < m_nrows; r++) {
1058 int c = 0;
1059 for ( ; c < matchingColumns; c++) // Just copy over matching columns
1060 *formPtr++ = cell(r, c);
1061 formPtr += FormLayoutColumns - matchingColumns; // In case old format was 1 column
1062 // protruding widgets: Insert as single-field rows
1063 for ( ; c < m_ncols; c++)
1064 if (QWidget *w = cell(r, c)) {
1065 formPtr++;
1066 *formPtr++ = w;
1067 }
1068 }
1069 Q_ASSERT(formPtr == formCells + FormLayoutColumns * formNRows);
1070 delete [] m_cells;
1071 m_cells = formCells;
1072 m_nrows = formNRows;
1073 m_ncols = FormLayoutColumns;
1074}
1075
1076bool Grid::locateWidget(QWidget *w, int &row, int &col, int &rowspan, int &colspan) const
1077{
1078 const int end = m_nrows * m_ncols;
1079 const int startIndex = qFind(m_cells, m_cells + end, w) - m_cells;
1080 if (startIndex == end)
1081 return false;
1082
1083 row = startIndex / m_ncols;
1084 col = startIndex % m_ncols;
1085 for (rowspan = 1; row + rowspan < m_nrows && cell(row + rowspan, col) == w; rowspan++) {}
1086 for (colspan = 1; col + colspan < m_ncols && cell(row, col + colspan) == w; colspan++) {}
1087 return true;
1088}
1089
1090// QGridLayout/QFormLayout Helpers: get item position/add item (overloads to make templates work)
1091
1092void getGridItemPosition(QGridLayout *gridLayout, int index, int *row, int *column, int *rowspan, int *colspan)
1093{
1094 gridLayout->getItemPosition(index, row, column, rowspan, colspan);
1095}
1096
1097void addWidgetToGrid(QGridLayout *lt, QWidget * widget, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment)
1098{
1099 lt->addWidget(widget, row, column, rowSpan, columnSpan, alignment);
1100}
1101
1102inline void getGridItemPosition(QFormLayout *formLayout, int index, int *row, int *column, int *rowspan, int *colspan)
1103{
1104 getFormLayoutItemPosition(formLayout, index, row, column, rowspan, colspan);
1105}
1106
1107inline void addWidgetToGrid(QFormLayout *lt, QWidget * widget, int row, int column, int, int columnSpan, Qt::Alignment)
1108{
1109 formLayoutAddWidget(lt, widget, QRect(column, row, columnSpan, 1), false);
1110}
1111
1112// ----------- Base template for grid like layouts
1113template <class GridLikeLayout, int LayoutType, int GridMode>
1114class GridLayout : public Layout
1115{
1116public:
1117 GridLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb);
1118
1119 virtual void doLayout();
1120 virtual void sort() { setWidgets(buildGrid(widgets())); }
1121
1122protected:
1123 QWidget *widgetAt(GridLikeLayout *layout, int row, int column) const;
1124
1125protected:
1126 QWidgetList buildGrid(const QWidgetList &);
1127 Grid m_grid;
1128};
1129
1130template <class GridLikeLayout, int LayoutType, int GridMode>
1131GridLayout<GridLikeLayout, LayoutType, GridMode>::GridLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb) :
1132 Layout(wl, p, fw, lb, LayoutInfo::Grid),
1133 m_grid(static_cast<Grid::Mode>(GridMode))
1134{
1135}
1136
1137template <class GridLikeLayout, int LayoutType, int GridMode>
1138QWidget *GridLayout<GridLikeLayout, LayoutType, GridMode>::widgetAt(GridLikeLayout *layout, int row, int column) const
1139{
1140 int index = 0;
1141 while (QLayoutItem *item = layout->itemAt(index)) {
1142 if (item->widget()) {
1143 int r, c, rowspan, colspan;
1144 getGridItemPosition(layout, index, &r, &c, &rowspan, &colspan);
1145 if (row == r && column == c)
1146 return item->widget();
1147 }
1148 ++index;
1149 }
1150 return 0;
1151}
1152
1153template <class GridLikeLayout, int LayoutType, int GridMode>
1154void GridLayout<GridLikeLayout, LayoutType, GridMode>::doLayout()
1155{
1156 bool needMove, needReparent;
1157 if (!prepareLayout(needMove, needReparent))
1158 return;
1159
1160 GridLikeLayout *layout = static_cast<GridLikeLayout *>(createLayout(LayoutType));
1161
1162 if (m_grid.empty())
1163 sort();
1164
1165 QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem.
1166
1167 const QWidgetList::const_iterator cend = widgets().constEnd();
1168 for (QWidgetList::const_iterator it = widgets().constBegin(); it != cend; ++it) {
1169 QWidget *w = *it;
1170 int r = 0, c = 0, rs = 0, cs = 0;
1171
1172 if (m_grid.locateWidget(w, r, c, rs, cs)) {
1173 if (needReparent)
1174 reparentToLayoutBase(w);
1175
1176 Qt::Alignment alignment = Qt::Alignment(0);
1177 if (const Spacer *spacer = qobject_cast<const Spacer*>(w))
1178 alignment = spacer->alignment();
1179
1180 if (rs * cs == 1) {
1181 addWidgetToGrid(layout, w, r, c, 1, 1, alignment);
1182 } else {
1183 addWidgetToGrid(layout, w, r, c, rs, cs, alignment);
1184 }
1185
1186 w->show();
1187 } else {
1188 qDebug("ooops, widget '%s' does not fit in layout", w->objectName().toUtf8().constData());
1189 }
1190 }
1191
1192 QLayoutSupport::createEmptyCells(layout);
1193
1194 finishLayout(needMove, layout);
1195}
1196
1197// Remove duplicate entries (Remove next, if equal to current)
1198void removeIntVecDuplicates(QVector<int> &v)
1199{
1200 if (v.size() < 2)
1201 return;
1202
1203 for (QVector<int>::iterator current = v.begin() ; (current != v.end()) && ((current+1) != v.end()) ; )
1204 if ( (*current == *(current+1)) )
1205 v.erase(current+1);
1206 else
1207 ++current;
1208}
1209
1210// Ensure a non-zero size for a widget geometry (squeezed spacers)
1211inline QRect expandGeometry(const QRect &rect)
1212{
1213 return rect.isEmpty() ? QRect(rect.topLeft(), rect.size().expandedTo(QSize(1, 1))) : rect;
1214}
1215
1216template <class GridLikeLayout, int LayoutType, int GridMode>
1217QWidgetList GridLayout<GridLikeLayout, LayoutType, GridMode>::buildGrid(const QWidgetList &widgetList)
1218{
1219 if (widgetList.empty())
1220 return QWidgetList();
1221
1222 // Pixel to cell conversion:
1223 // By keeping a list of start'n'stop values (x & y) for each widget,
1224 // it is possible to create a very small grid of cells to represent
1225 // the widget layout.
1226 // -----------------------------------------------------------------
1227
1228 // We need a list of both start and stop values for x- & y-axis
1229 const int widgetCount = widgetList.size();
1230 QVector<int> x( widgetCount * 2 );
1231 QVector<int> y( widgetCount * 2 );
1232
1233 // Using push_back would look nicer, but operator[] is much faster
1234 int index = 0;
1235 for (int i = 0; i < widgetCount; ++i) {
1236 const QRect widgetPos = expandGeometry(widgetList.at(i)->geometry());
1237 x[index] = widgetPos.left();
1238 x[index+1] = widgetPos.right();
1239 y[index] = widgetPos.top();
1240 y[index+1] = widgetPos.bottom();
1241 index += 2;
1242 }
1243
1244 qSort(x);
1245 qSort(y);
1246
1247 // Remove duplicate x entries (Remove next, if equal to current)
1248 removeIntVecDuplicates(x);
1249 removeIntVecDuplicates(y);
1250
1251 // Note that left == right and top == bottom for size 1 items; reserve
1252 // enough space
1253 m_grid.resize(y.size(), x.size());
1254
1255 const QWidgetList::const_iterator cend = widgetList.constEnd();
1256 for (QWidgetList::const_iterator it = widgetList.constBegin(); it != cend; ++it) {
1257 QWidget *w = *it;
1258 // Mark the cells in the grid that contains a widget
1259 const QRect widgetPos = expandGeometry(w->geometry());
1260 QRect c(0, 0, 0, 0); // rect of columns/rows
1261
1262 // From left til right (not including)
1263 const int leftIdx = x.indexOf(widgetPos.left());
1264 Q_ASSERT(leftIdx != -1);
1265 c.setLeft(leftIdx);
1266 c.setRight(leftIdx);
1267 for (int cw=leftIdx; cw<x.size(); cw++)
1268 if (x[cw] < widgetPos.right())
1269 c.setRight(cw);
1270 else
1271 break;
1272 // From top til bottom (not including)
1273 const int topIdx = y.indexOf(widgetPos.top());
1274 Q_ASSERT(topIdx != -1);
1275 c.setTop(topIdx);
1276 c.setBottom(topIdx);
1277 for (int ch=topIdx; ch<y.size(); ch++)
1278 if (y[ch] < widgetPos.bottom())
1279 c.setBottom(ch);
1280 else
1281 break;
1282 m_grid.setCells(c, w); // Mark cellblock
1283 }
1284
1285 m_grid.simplify();
1286
1287 QWidgetList ordered;
1288 for (int i = 0; i < m_grid.numRows(); i++)
1289 for (int j = 0; j < m_grid.numCols(); j++) {
1290 QWidget *w = m_grid.cell(i, j);
1291 if (w && !ordered.contains(w))
1292 ordered.append(w);
1293 }
1294 return ordered;
1295}
1296} // anonymous
1297
1298Layout* Layout::createLayout(const QWidgetList &widgets, QWidget *parentWidget,
1299 QDesignerFormWindowInterface *fw,
1300 QWidget *layoutBase, LayoutInfo::Type layoutType)
1301{
1302 switch (layoutType) {
1303 case LayoutInfo::Grid:
1304 return new GridLayout<QGridLayout, LayoutInfo::Grid, Grid::GridLayout>(widgets, parentWidget, fw, layoutBase);
1305 case LayoutInfo::HBox:
1306 case LayoutInfo::VBox: {
1307 const Qt::Orientation orientation = layoutType == LayoutInfo::HBox ? Qt::Horizontal : Qt::Vertical;
1308 return new BoxLayout(widgets, parentWidget, fw, layoutBase, orientation);
1309 }
1310 case LayoutInfo::HSplitter:
1311 case LayoutInfo::VSplitter: {
1312 const Qt::Orientation orientation = layoutType == LayoutInfo::HSplitter ? Qt::Horizontal : Qt::Vertical;
1313 return new SplitterLayout(widgets, parentWidget, fw, layoutBase, orientation);
1314 }
1315 case LayoutInfo::Form:
1316 return new GridLayout<QFormLayout, LayoutInfo::Form, Grid::FormLayout>(widgets, parentWidget, fw, layoutBase);
1317 default:
1318 break;
1319 }
1320 Q_ASSERT(0);
1321 return 0;
1322}
1323
1324} // namespace qdesigner_internal
1325
1326QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.