source: trunk/src/qt3support/widgets/q3dockarea.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 44.1 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 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 Qt3Support module 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 "q3dockarea.h"
43
44#ifndef QT_NO_MAINWINDOW
45#include "qsplitter.h"
46#include "qevent.h"
47#include "qlayout.h"
48#include "qapplication.h"
49#include "qpainter.h"
50#include "qmap.h"
51#include "q3mainwindow.h"
52#include "q3toolbar.h"
53
54QT_BEGIN_NAMESPACE
55
56//#define QDOCKAREA_DEBUG
57
58struct Q3DockData
59{
60 Q3DockData() : w(0), rect() {}
61 Q3DockData(Q3DockWindow *dw, const QRect &r) : w(dw), rect(r) {}
62 Q3DockWindow *w;
63 QRect rect;
64
65 Q_DUMMY_COMPARISON_OPERATOR(Q3DockData)
66};
67
68static int fix_x(Q3DockWindow* w, int width = -1) {
69 if (QApplication::reverseLayout()) {
70 if (width < 0)
71 width = w->width();
72 return w->parentWidget()->width() - w->x() - width;
73 }
74 return w->x();
75}
76static int fix_x(Q3DockWindow* w, int x, int width = -1) {
77 if (QApplication::reverseLayout()) {
78 if (width < 0)
79 width = w->width();
80 return w->parentWidget()->width() - x - width;
81 }
82 return x;
83}
84
85static QPoint fix_pos(Q3DockWindow* w) {
86 if (QApplication::reverseLayout()) {
87 QPoint p = w->pos();
88 p.rx() = w->parentWidget()->width() - p.x() - w->width();
89 return p;
90 }
91 return w->pos();
92}
93
94
95void Q3DockAreaLayout::setGeometry(const QRect &r)
96{
97 QLayout::setGeometry(r);
98 layoutItems(r);
99}
100
101QLayoutItem *Q3DockAreaLayout::itemAt(int) const
102{
103 return 0; //###
104}
105
106QLayoutItem *Q3DockAreaLayout::takeAt(int)
107{
108 return 0; //###
109}
110
111int Q3DockAreaLayout::count() const
112{
113 return 0; //###
114}
115
116
117QSize Q3DockAreaLayout::sizeHint() const
118{
119 if (dockWindows->isEmpty())
120 return QSize(0, 0);
121
122 if (dirty) {
123 Q3DockAreaLayout *that = (Q3DockAreaLayout *) this;
124 that->layoutItems(geometry());
125 }
126
127 int w = 0;
128 int h = 0;
129 int y = -1;
130 int x = -1;
131 int ph = 0;
132 int pw = 0;
133 for (int i = 0; i < dockWindows->size(); ++i) {
134 Q3DockWindow *dw = dockWindows->at(i);
135 int plush = 0, plusw = 0;
136 if (dw->isHidden())
137 continue;
138 if (hasHeightForWidth()) {
139 if (y != dw->y())
140 plush = ph;
141 y = dw->y();
142 ph = dw->height();
143 } else {
144 if (x != dw->x())
145 plusw = pw;
146 x = dw->x();
147 pw = dw->width();
148 }
149 h = qMax(h, dw->height() + plush);
150 w = qMax(w, dw->width() + plusw);
151 }
152
153 if (hasHeightForWidth())
154 return QSize(0, h);
155 return QSize(w, 0);
156}
157
158bool Q3DockAreaLayout::hasHeightForWidth() const
159{
160 return orient == Qt::Horizontal;
161}
162
163void Q3DockAreaLayout::init()
164{
165 dirty = true;
166 cached_width = 0;
167 cached_height = 0;
168 cached_hfw = -1;
169 cached_wfh = -1;
170}
171
172void Q3DockAreaLayout::invalidate()
173{
174 dirty = true;
175 cached_width = 0;
176 cached_height = 0;
177 QLayout::invalidate();
178}
179
180static int start_pos(const QRect &r, Qt::Orientation o)
181{
182 if (o == Qt::Horizontal) {
183 return qMax(0, r.x());
184 } else {
185 return qMax(0, r.y());
186 }
187}
188
189static void add_size(int s, int &pos, Qt::Orientation o)
190{
191 if (o == Qt::Horizontal) {
192 pos += s;
193 } else {
194 pos += s;
195 }
196}
197
198static int space_left(const QRect &r, int pos, Qt::Orientation o)
199{
200 if (o == Qt::Horizontal) {
201 return (r.x() + r.width()) - pos;
202 } else {
203 return (r.y() + r.height()) - pos;
204 }
205}
206
207static int dock_extent(Q3DockWindow *w, Qt::Orientation o, int maxsize)
208{
209 if (o == Qt::Horizontal)
210 return qMin(maxsize, qMax(w->sizeHint().width(), w->fixedExtent().width()));
211 else
212 return qMin(maxsize, qMax(w->sizeHint().height(), w->fixedExtent().height()));
213}
214
215static int dock_strut(Q3DockWindow *w, Qt::Orientation o)
216{
217 if (o != Qt::Horizontal) {
218 int wid;
219 if ((wid = w->fixedExtent().width()) != -1)
220 return qMax(wid, qMax(w->minimumSize().width(), w->minimumSizeHint().width()));
221 return qMax(w->sizeHint().width(), qMax(w->minimumSize().width(), w->minimumSizeHint().width()));
222 } else {
223 int hei;
224 if ((hei = w->fixedExtent().height()) != -1)
225 return qMax(hei, qMax(w->minimumSizeHint().height(), w->minimumSize().height()));
226 return qMax(w->sizeHint().height(), qMax(w->minimumSizeHint().height(), w->minimumSize().height()));
227 }
228}
229
230static void set_geometry(Q3DockWindow *w, int pos, int sectionpos, int extent, int strut, Qt::Orientation o)
231{
232 if (o == Qt::Horizontal)
233 w->setGeometry(fix_x(w, pos, extent), sectionpos, extent, strut);
234 else
235 w->setGeometry(sectionpos, pos, strut, extent);
236}
237
238static int size_extent(const QSize &s, Qt::Orientation o, bool swap = false)
239{
240 return o == Qt::Horizontal ? (swap ? s.height() : s.width()) : (swap ? s.width() : s.height());
241}
242
243static int point_pos(const QPoint &p, Qt::Orientation o, bool swap = false)
244{
245 return o == Qt::Horizontal ? (swap ? p.y() : p.x()) : (swap ? p.x() : p.y());
246}
247
248static void shrink_extend(Q3DockWindow *dw, int &dockExtend, int /*spaceLeft*/, Qt::Orientation o)
249{
250 Q3ToolBar *tb = qobject_cast<Q3ToolBar*>(dw);
251 if (o == Qt::Horizontal) {
252 int mw = 0;
253 if (!tb)
254 mw = dw->minimumWidth();
255 else
256 mw = dw->sizeHint().width();
257 dockExtend = mw;
258 } else {
259 int mh = 0;
260 if (!tb)
261 mh = dw->minimumHeight();
262 else
263 mh = dw->sizeHint().height();
264 dockExtend = mh;
265 }
266}
267
268static void place_line(QList<Q3DockData> &lastLine, Qt::Orientation o, int linestrut, int fullextent, int tbstrut, int maxsize, Q3DockAreaLayout *)
269{
270 Q3DockWindow *last = 0;
271 QRect lastRect;
272 for (QList<Q3DockData>::Iterator it = lastLine.begin(); it != lastLine.end(); ++it) {
273 if (tbstrut != -1 && qobject_cast<Q3ToolBar*>((*it).w))
274 (*it).rect.setHeight(tbstrut);
275 if (!last) {
276 last = (*it).w;
277 lastRect = (*it).rect;
278 continue;
279 }
280 if (!last->isStretchable()) {
281 int w = qMin(lastRect.width(), maxsize);
282 set_geometry(last, lastRect.x(), lastRect.y(), w, lastRect.height(), o);
283 } else {
284 int w = qMin((*it).rect.x() - lastRect.x(), maxsize);
285 set_geometry(last, lastRect.x(), lastRect.y(), w,
286 last->isResizeEnabled() ? linestrut : lastRect.height(), o);
287 }
288 last = (*it).w;
289 lastRect = (*it).rect;
290 }
291 if (!last)
292 return;
293 if (!last->isStretchable()) {
294 int w = qMin(lastRect.width(), maxsize);
295 set_geometry(last, lastRect.x(), lastRect.y(), w, lastRect.height(), o);
296 } else {
297 int w = qMin(fullextent - lastRect.x() - (o == Qt::Vertical ? 1 : 0), maxsize);
298 set_geometry(last, lastRect.x(), lastRect.y(), w,
299 last->isResizeEnabled() ? linestrut : lastRect.height(), o);
300 }
301}
302
303QSize Q3DockAreaLayout::minimumSize() const
304{
305 if (dockWindows->isEmpty())
306 return QSize(0, 0);
307
308 if (dirty) {
309 Q3DockAreaLayout *that = (Q3DockAreaLayout *) this;
310 that->layoutItems(geometry());
311 }
312
313 int s = 0;
314
315 for (int i = 0; i < dockWindows->size(); ++i) {
316 Q3DockWindow *dw = dockWindows->at(i);
317 if (dw->isHidden())
318 continue;
319 s = qMax(s, dock_strut(dw, orientation()));
320 }
321
322 return orientation() == Qt::Horizontal ? QSize(0, s ? s+2 : 0) : QSize(s, 0);
323}
324
325
326
327int Q3DockAreaLayout::layoutItems(const QRect &rect, bool testonly)
328{
329 if (dockWindows->isEmpty())
330 return 0;
331
332 dirty = false;
333
334 // some corrections
335 QRect r = rect;
336 if (orientation() == Qt::Vertical)
337 r.setHeight(r.height() - 3);
338
339 // init
340 lines.clear();
341 ls.clear();
342 int start = start_pos(r, orientation());
343 int pos = start;
344 int sectionpos = 0;
345 int linestrut = 0;
346 QList<Q3DockData> lastLine;
347 int tbstrut = -1;
348 int maxsize = size_extent(rect.size(), orientation());
349 int visibleWindows = 0;
350
351 // go through all widgets in the dock
352 for (int i = 0; i < dockWindows->size(); ++i) {
353 Q3DockWindow *dw = dockWindows->at(i);
354 if (dw->isHidden())
355 continue;
356 ++visibleWindows;
357 // find position for the widget: This is the maximum of the
358 // end of the previous widget and the offset of the widget. If
359 // the position + the width of the widget dosn't fit into the
360 // dock, try moving it a bit back, if possible.
361 int op = pos;
362 int dockExtend = dock_extent(dw, orientation(), maxsize);
363 if (!dw->isStretchable()) {
364 pos = qMax(pos, dw->offset());
365 if (pos + dockExtend > size_extent(r.size(), orientation()) - 1)
366 pos = qMax(op, size_extent(r.size(), orientation()) - 1 - dockExtend);
367 }
368 if (!lastLine.isEmpty() && !dw->newLine() && space_left(rect, pos, orientation()) < dockExtend)
369 shrink_extend(dw, dockExtend, space_left(rect, pos, orientation()), orientation());
370 // if the current widget doesn't fit into the line anymore and it is not the first widget of the line
371 if (!lastLine.isEmpty() &&
372 (space_left(rect, pos, orientation()) < dockExtend || dw->newLine())) {
373 if (!testonly) // place the last line, if not in test mode
374 place_line(lastLine, orientation(), linestrut, size_extent(r.size(), orientation()), tbstrut, maxsize, this);
375 // remember the line coordinats of the last line
376 if (orientation() == Qt::Horizontal)
377 lines.append(QRect(0, sectionpos, r.width(), linestrut));
378 else
379 lines.append(QRect(sectionpos, 0, linestrut, r.height()));
380 // do some clearing for the next line
381 lastLine.clear();
382 sectionpos += linestrut;
383 linestrut = 0;
384 pos = start;
385 tbstrut = -1;
386 }
387
388 // remember first widget of a line
389 if (lastLine.isEmpty()) {
390 ls.append(dw);
391 // try to make the best position
392 int op = pos;
393 if (!dw->isStretchable())
394 pos = qMax(pos, dw->offset());
395 if (pos + dockExtend > size_extent(r.size(), orientation()) - 1)
396 pos = qMax(op, size_extent(r.size(), orientation()) - 1 - dockExtend);
397 }
398 // do some calculations and add the remember the rect which the docking widget requires for the placing
399 QRect dwRect(pos, sectionpos, dockExtend, dock_strut(dw, orientation() ));
400 lastLine.append(Q3DockData(dw, dwRect));
401 if (qobject_cast<Q3ToolBar*>(dw))
402 tbstrut = qMax(tbstrut, dock_strut(dw, orientation()));
403 linestrut = qMax(dock_strut(dw, orientation()), linestrut);
404 add_size(dockExtend, pos, orientation());
405 }
406
407 // if some stuff was not placed/stored yet, do it now
408 if (!testonly)
409 place_line(lastLine, orientation(), linestrut, size_extent(r.size(), orientation()), tbstrut, maxsize, this);
410 if (orientation() == Qt::Horizontal)
411 lines.append(QRect(0, sectionpos, r.width(), linestrut));
412 else
413 lines.append(QRect(sectionpos, 0, linestrut, r.height()));
414 if (lines.size() >= 2 && *(--lines.end()) == *(--(--lines.end())))
415 lines.removeLast();
416
417 bool hadResizable = false;
418 for (int i = 0; i < dockWindows->size(); ++i) {
419 Q3DockWindow *dw = dockWindows->at(i);
420 if (!dw->isVisibleTo(parentWidget))
421 continue;
422 hadResizable = hadResizable || dw->isResizeEnabled();
423 dw->updateSplitterVisibility(visibleWindows > 1); //!dw->area()->isLastDockWindow(dw));
424 if (Q3ToolBar *tb = qobject_cast<Q3ToolBar *>(dw))
425 tb->checkForExtension(dw->size());
426 }
427 return sectionpos + linestrut;
428}
429
430int Q3DockAreaLayout::heightForWidth(int w) const
431{
432 if (dockWindows->isEmpty() && parentWidget)
433 return parentWidget->minimumHeight();
434
435 if (cached_width != w) {
436 Q3DockAreaLayout * mthis = (Q3DockAreaLayout*)this;
437 mthis->cached_width = w;
438 int h = mthis->layoutItems(QRect(0, 0, w, 0), true);
439 mthis->cached_hfw = h;
440 return h;
441 }
442
443 return cached_hfw;
444}
445
446int Q3DockAreaLayout::widthForHeight(int h) const
447{
448 if (cached_height != h) {
449 Q3DockAreaLayout * mthis = (Q3DockAreaLayout*)this;
450 mthis->cached_height = h;
451 int w = mthis->layoutItems(QRect(0, 0, 0, h), true);
452 mthis->cached_wfh = w;
453 return w;
454 }
455 return cached_wfh;
456}
457
458
459
460
461/*!
462 \class Q3DockArea
463 \brief The Q3DockArea class manages and lays out Q3DockWindows.
464
465 \compat
466
467 A Q3DockArea is a container which manages a list of
468 \l{Q3DockWindow}s which it lays out within its area. In cooperation
469 with the \l{Q3DockWindow}s it is responsible for the docking and
470 undocking of \l{Q3DockWindow}s and moving them inside the dock
471 area. Q3DockAreas also handle the wrapping of \l{Q3DockWindow}s to
472 fill the available space as compactly as possible. Q3DockAreas can
473 contain Q3ToolBars since Q3ToolBar is a Q3DockWindow subclass.
474
475 QMainWindow contains four Q3DockAreas which you can use for your
476 Q3ToolBars and Q3DockWindows, so in most situations you do not
477 need to use the Q3DockArea class directly. Although QMainWindow
478 contains support for its own dock areas, you can't add new ones.
479 You also can't add a Q3DockArea to your own subclass of QWidget.
480 It won't be shown.
481
482 \img qmainwindow-qdockareas.png QMainWindow's Q3DockAreas
483
484 \target lines
485 \section1 Lines.
486
487 Q3DockArea uses the concept of lines. A line is a horizontal
488 region which may contain dock windows side-by-side. A dock area
489 may have room for more than one line. When dock windows are docked
490 into a dock area they are usually added at the right hand side of
491 the top-most line that has room (unless manually placed by the
492 user). When users move dock windows they may leave empty lines or
493 gaps in non-empty lines. Qt::Dock windows can be lined up to
494 minimize wasted space using the lineUp() function.
495
496 The Q3DockArea class maintains a position list of all its child
497 dock windows. Qt::Dock windows are added to a dock area from position
498 0 onwards. Qt::Dock windows are laid out sequentially in position
499 order from left to right, and in the case of multiple lines of
500 dock windows, from top to bottom. If a dock window is floated it
501 still retains its position since this is where the window will
502 return if the user double clicks its caption. A dock window's
503 position can be determined with hasDockWindow(). The position can
504 be changed with moveDockWindow().
505
506 To dock or undock a dock window use Q3DockWindow::dock() and
507 Q3DockWindow::undock() respectively. If you want to control which
508 dock windows can dock in a dock area use setAcceptDockWindow(). To
509 see if a dock area contains a particular dock window use
510 \l{hasDockWindow()}; to see how many dock windows a dock area
511 contains use count().
512
513 The streaming operators can write the positions of the dock
514 windows in the dock area to a QTextStream. The positions can be
515 read back later to restore the saved positions.
516
517 Save the positions to a QTextStream:
518 \snippet doc/src/snippets/code/src_qt3support_widgets_q3dockarea.cpp 0
519
520 Restore the positions from a QTextStream:
521 \snippet doc/src/snippets/code/src_qt3support_widgets_q3dockarea.cpp 1
522*/
523
524/*!
525 \property Q3DockArea::handlePosition
526 \brief where the dock window splitter handle is placed in the dock
527 area
528
529 The default position is \c Normal.
530*/
531
532/*!
533 \property Q3DockArea::orientation
534 \brief the dock area's orientation
535
536 There is no default value; the orientation is specified in the
537 constructor.
538*/
539
540/*!
541 \enum Q3DockArea::HandlePosition
542
543 A dock window has two kinds of handles, the dock window handle
544 used for dragging the dock window, and the splitter handle used to
545 resize the dock window in relation to other dock windows using a
546 splitter. (The splitter handle is only visible for docked
547 windows.)
548
549 This enum specifies where the dock window splitter handle is
550 placed in the dock area.
551
552 \value Normal The splitter handles of dock windows are placed at
553 the right or bottom.
554
555 \value Reverse The splitter handles of dock windows are placed at
556 the left or top.
557*/
558
559/*!
560 Constructs a Q3DockArea with orientation \a o, HandlePosition \a h,
561 parent \a parent and called \a name.
562*/
563
564Q3DockArea::Q3DockArea(Qt::Orientation o, HandlePosition h, QWidget *parent, const char *name)
565 : QWidget(parent, name), orient(o), layout(0), hPos(h)
566{
567 layout = new Q3DockAreaLayout(this, o, &dockWindows, 0, 0, "toollayout");
568 installEventFilter(this);
569}
570
571/*!
572 Destroys the dock area and all the dock windows docked in the dock
573 area.
574
575 Does not affect any floating dock windows or dock windows in other
576 dock areas, even if they first appeared in this dock area.
577 Floating dock windows are effectively top level windows and are
578 not child windows of the dock area. When a floating dock window is
579 docked (dragged into a dock area) its parent becomes the dock
580 area.
581*/
582
583Q3DockArea::~Q3DockArea()
584{
585 while (!dockWindows.isEmpty())
586 delete dockWindows.takeFirst();
587}
588
589/*!
590 Moves the Q3DockWindow \a w within the dock area. If \a w is not
591 already docked in this area, \a w is docked first. If \a index is
592 -1 or larger than the number of docked widgets, \a w is appended
593 at the end, otherwise it is inserted at the position \a index.
594*/
595
596void Q3DockArea::moveDockWindow(Q3DockWindow *w, int index)
597{
598 invalidateFixedSizes();
599 Q3DockWindow *dockWindow = 0;
600 int dockWindowIndex = findDockWindow(w);
601 if (dockWindowIndex == -1) {
602 dockWindow = w;
603 bool vis = dockWindow->isVisible();
604 dockWindow->setParent(this);
605 dockWindow->move(0, 0);
606 if(vis)
607 dockWindow->show();
608 w->installEventFilter(this);
609 updateLayout();
610 setSizePolicy(QSizePolicy(orientation() == Qt::Horizontal ? QSizePolicy::Expanding : QSizePolicy::Minimum,
611 orientation() == Qt::Vertical ? QSizePolicy::Expanding : QSizePolicy::Minimum));
612 dockWindows.append(w);
613 } else {
614 if (w->parent() != this) {
615 bool vis = w->isVisible();
616 w->setParent(this);
617 w->move(0, 0);
618 if(vis)
619 w->show();
620 }
621 if (index == -1) {
622 dockWindows.removeAll(w);
623 dockWindows.append(w);
624 }
625 }
626
627 w->dockArea = this;
628 w->curPlace = Q3DockWindow::InDock;
629 w->updateGui();
630
631 if (index != -1 && index < (int)dockWindows.count()) {
632 dockWindows.removeAll(w);
633 dockWindows.insert(index, w);
634 }
635}
636
637/*!
638 Returns true if the dock area contains the dock window \a w;
639 otherwise returns false. If \a index is not 0 it will be set as
640 follows: if the dock area contains the dock window *\a{index} is
641 set to \a w's index position; otherwise *\a{index} is set to -1.
642*/
643
644bool Q3DockArea::hasDockWindow(Q3DockWindow *w, int *index)
645{
646 int i = dockWindows.indexOf(w);
647 if (index)
648 *index = i;
649 return i != -1;
650}
651
652int Q3DockArea::lineOf(int index)
653{
654 QList<Q3DockWindow *> lineStarts = layout->lineStarts();
655 int i = 0;
656 for (; i < lineStarts.size(); ++i) {
657 Q3DockWindow *w = lineStarts.at(i);
658 if (dockWindows.indexOf(w) >= index)
659 return i;
660 }
661 return i;
662}
663
664/*!
665 \overload
666
667 Moves the dock window \a w inside the dock area where \a p is the
668 new position (in global screen coordinates), \a r is the suggested
669 rectangle of the dock window and \a swap specifies whether or not
670 the orientation of the docked widget needs to be changed.
671
672 This function is used internally by Q3DockWindow. You shouldn't
673 need to call it yourself.
674*/
675
676void Q3DockArea::moveDockWindow(Q3DockWindow *w, const QPoint &p, const QRect &r, bool swap)
677{
678 invalidateFixedSizes();
679 int mse = -10;
680 bool hasResizable = false;
681 for (int i = 0; i < dockWindows.size(); ++i) {
682 Q3DockWindow *dw = dockWindows.at(i);
683 if (dw->isHidden())
684 continue;
685 if (dw->isResizeEnabled())
686 hasResizable = true;
687 if (orientation() != Qt::Horizontal)
688 mse = qMax(qMax(dw->fixedExtent().width(), dw->width()), mse);
689 else
690 mse = qMax(qMax(dw->fixedExtent().height(), dw->height()), mse);
691 }
692 if (!hasResizable && w->isResizeEnabled()) {
693 if (orientation() != Qt::Horizontal)
694 mse = qMax(w->fixedExtent().width(), mse);
695 else
696 mse = qMax(w->fixedExtent().height(), mse);
697 }
698
699 Q3DockWindow *dockWindow = 0;
700 int dockWindowIndex = findDockWindow(w);
701 QList<Q3DockWindow *> lineStarts = layout->lineStarts();
702 QList<QRect> lines = layout->lineList();
703 bool wasAloneInLine = false;
704 QPoint pos = mapFromGlobal(p);
705 int line = lineOf(dockWindowIndex);
706 QRect lr;
707 if (line < lines.size())
708 lr = lines.at(line);
709 if (dockWindowIndex != -1) {
710 if (lineStarts.contains(w)
711 && ((dockWindowIndex < dockWindows.count() - 1
712 && lineStarts.contains(dockWindows.at(dockWindowIndex + 1)))
713 || dockWindowIndex == dockWindows.count() - 1))
714 wasAloneInLine = true;
715 dockWindow = dockWindows.takeAt(dockWindowIndex);
716 if (!wasAloneInLine) { // only do the pre-layout if the widget isn't the only one in its line
717 if (lineStarts.contains(dockWindow) && dockWindowIndex < dockWindows.count())
718 dockWindows.at(dockWindowIndex)->setNewLine(true);
719 layout->layoutItems(QRect(0, 0, width(), height()), true);
720 }
721 } else {
722 dockWindow = w;
723 bool vis = dockWindow->isVisible();
724 dockWindow->setParent(this);
725 dockWindow->move(0, 0);
726 if(vis)
727 dockWindow->show();
728 if (swap)
729 dockWindow->resize(dockWindow->height(), dockWindow->width());
730 w->installEventFilter(this);
731 }
732
733 lineStarts = layout->lineStarts();
734 lines = layout->lineList();
735
736 QRect rect = QRect(mapFromGlobal(r.topLeft()), r.size());
737 if (orientation() == Qt::Horizontal && QApplication::reverseLayout()) {
738 rect = QRect(width() - rect.x() - rect.width(), rect.y(), rect.width(), rect.height());
739 pos.rx() = width() - pos.x();
740 }
741 dockWindow->setOffset(point_pos(rect.topLeft(), orientation()));
742 if (orientation() == Qt::Horizontal) {
743 int offs = dockWindow->offset();
744 if (width() - offs < dockWindow->minimumWidth())
745 dockWindow->setOffset(width() - dockWindow->minimumWidth());
746 } else {
747 int offs = dockWindow->offset();
748 if (height() - offs < dockWindow->minimumHeight())
749 dockWindow->setOffset(height() - dockWindow->minimumHeight());
750 }
751
752 if (dockWindows.isEmpty()) {
753 dockWindows.append(dockWindow);
754 } else {
755 int dockLine = -1;
756 bool insertLine = false;
757 int i = 0;
758 QRect lineRect;
759 // find the line which we touched with the mouse
760 for (QList<QRect>::Iterator it = lines.begin(); it != lines.end(); ++it, ++i) {
761 if (point_pos(pos, orientation(), true) >= point_pos((*it).topLeft(), orientation(), true) &&
762 point_pos(pos, orientation(), true) <= point_pos((*it).topLeft(), orientation(), true) +
763 size_extent((*it).size(), orientation(), true)) {
764 dockLine = i;
765 lineRect = *it;
766 break;
767 }
768 }
769 if (dockLine == -1) { // outside the dock...
770 insertLine = true;
771 if (point_pos(pos, orientation(), true) < 0) // insert as first line
772 dockLine = 0;
773 else
774 dockLine = (int)lines.count(); // insert after the last line ### size_t/int cast
775 } else { // inside the dock (we have found a dockLine)
776 if (point_pos(pos, orientation(), true) <
777 point_pos(lineRect.topLeft(), orientation(), true) + 4) { // mouse was at the very beginning of the line
778 insertLine = true; // insert a new line before that with the docking widget
779 } else if (point_pos(pos, orientation(), true) >
780 point_pos(lineRect.topLeft(), orientation(), true) +
781 size_extent(lineRect.size(), orientation(), true) - 4) { // mouse was at the very and of the line
782 insertLine = true; // insert a line after that with the docking widget
783 dockLine++;
784 }
785 }
786
787 if (!insertLine && wasAloneInLine && lr.contains(pos)) // if we are alone in a line and just moved in there, re-insert it
788 insertLine = true;
789
790#if defined(QDOCKAREA_DEBUG)
791 qDebug("insert in line %d, and insert that line: %d", dockLine, insertLine);
792 qDebug(" (btw, we have %d lines)", lines.count());
793#endif
794 Q3DockWindow *dw = 0;
795 if (dockLine >= (int)lines.count()) { // insert after last line
796 dockWindows.append(dockWindow);
797 dockWindow->setNewLine(true);
798#if defined(QDOCKAREA_DEBUG)
799 qDebug("insert at the end");
800#endif
801 } else if (dockLine == 0 && insertLine) { // insert before first line
802 dockWindows.insert(0, dockWindow);
803 dockWindows.at(1)->setNewLine(true);
804#if defined(QDOCKAREA_DEBUG)
805 qDebug("insert at the begin");
806#endif
807 } else { // insert somewhere in between
808 // make sure each line start has a new line
809 for (int i = 0; i < lineStarts.size(); ++i) {
810 dw = lineStarts.at(i);
811 dw->setNewLine(true);
812 }
813
814 // find the index of the first widget in the search line
815 int searchLine = dockLine;
816#if defined(QDOCKAREA_DEBUG)
817 qDebug("search line start of %d", searchLine);
818#endif
819 Q3DockWindow *lsw = lineStarts.at(searchLine);
820 int index = dockWindows.indexOf(lsw);
821 if (index == -1) { // the linestart widget hasn't been found, try to find it harder
822 if (lsw == w && dockWindowIndex <= dockWindows.count())
823 index = dockWindowIndex;
824 else
825 index = 0;
826 }
827#if defined(QDOCKAREA_DEBUG)
828 qDebug(" which starts at %d", index);
829#endif
830 if (!insertLine) { // if we insert the docking widget in the existing line
831 // find the index for the widget
832 bool inc = true;
833 bool firstTime = true;
834 for (int i = index; i < dockWindows.size(); ++i) {
835 dw = dockWindows.at(i);
836 if (orientation() == Qt::Horizontal)
837 dw->setFixedExtentWidth(-1);
838 else
839 dw->setFixedExtentHeight(-1);
840 if (!firstTime && lineStarts.contains(dw)) // we are in the next line, so break
841 break;
842 if (point_pos(pos, orientation()) <
843 point_pos(fix_pos(dw), orientation()) + size_extent(dw->size(), orientation()) / 2) {
844 inc = false;
845 }
846 if (inc)
847 index++;
848 firstTime = false;
849 }
850#if defined(QDOCKAREA_DEBUG)
851 qDebug("insert at index: %d", index);
852#endif
853 // if we insert it just before a widget which has a new line, transfer the newline to the docking widget
854 // but not if we didn't only mave a widget in its line which was alone in the line before
855 if (!(wasAloneInLine && lr.contains(pos))
856 && index >= 0 && index < dockWindows.count() &&
857 dockWindows.at(index)->newLine() && lineOf(index) == dockLine) {
858#if defined(QDOCKAREA_DEBUG)
859 qDebug("get rid of the old newline and get me one");
860#endif
861 dockWindows.at(index)->setNewLine(false);
862 dockWindow->setNewLine(true);
863 } else if (wasAloneInLine && lr.contains(pos)) {
864 dockWindow->setNewLine(true);
865 } else { // if we are somewhere in a line, get rid of the newline
866 dockWindow->setNewLine(false);
867 }
868 } else { // insert in a new line, so make sure the dock widget and the widget which will be after it have a newline
869#if defined(QDOCKAREA_DEBUG)
870 qDebug("insert a new line");
871#endif
872 if (index < dockWindows.count()) {
873#if defined(QDOCKAREA_DEBUG)
874 qDebug("give the widget at %d a newline", index);
875#endif
876 Q3DockWindow* nldw = dockWindows.at(index);
877 if (nldw)
878 nldw->setNewLine(true);
879 }
880#if defined(QDOCKAREA_DEBUG)
881 qDebug("give me a newline");
882#endif
883 dockWindow->setNewLine(true);
884 }
885 // finally insert the widget
886 dockWindows.insert(index, dockWindow);
887 }
888 }
889
890 if (mse != -10 && w->isResizeEnabled()) {
891 if (orientation() != Qt::Horizontal)
892 w->setFixedExtentWidth(qMin(qMax(w->minimumWidth(), mse), w->sizeHint().width()));
893 else
894 w->setFixedExtentHeight(qMin(qMax(w->minimumHeight(), mse), w->sizeHint().height()));
895 }
896
897 updateLayout();
898 setSizePolicy(QSizePolicy(orientation() == Qt::Horizontal ? QSizePolicy::Expanding : QSizePolicy::Minimum,
899 orientation() == Qt::Vertical ? QSizePolicy::Expanding : QSizePolicy::Minimum));
900}
901
902/*!
903 Removes the dock window \a w from the dock area. If \a
904 makeFloating is true, \a w gets floated, and if \a swap is true,
905 the orientation of \a w gets swapped. If \a fixNewLines is true
906 (the default) newlines in the area will be fixed.
907
908 You should never need to call this function yourself. Use
909 Q3DockWindow::dock() and Q3DockWindow::undock() instead.
910*/
911
912void Q3DockArea::removeDockWindow(Q3DockWindow *w, bool makeFloating, bool swap, bool fixNewLines)
913{
914 w->removeEventFilter(this);
915 Q3DockWindow *dockWindow = 0;
916 int i = findDockWindow(w);
917 if (i == -1)
918 return;
919 dockWindow = dockWindows.at(i);
920 dockWindows.removeAt(i);
921 QList<Q3DockWindow *> lineStarts = layout->lineStarts();
922 if (fixNewLines && lineStarts.contains(dockWindow) && i < dockWindows.count())
923 dockWindows.at(i)->setNewLine(true);
924 if (makeFloating) {
925 QWidget *p = parentWidget() ? parentWidget() : window();
926 dockWindow->setParent(p, Qt::WType_Dialog | Qt::WStyle_Customize | Qt::WStyle_NoBorder | Qt::WStyle_Tool);
927 dockWindow->move(0, 0);
928 }
929 if (swap)
930 dockWindow->resize(dockWindow->height(), dockWindow->width());
931 updateLayout();
932 if (dockWindows.isEmpty())
933 setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred));
934}
935
936int Q3DockArea::findDockWindow(Q3DockWindow *w)
937{
938 return dockWindows.indexOf(w);
939}
940
941void Q3DockArea::updateLayout()
942{
943 layout->invalidate();
944 layout->activate();
945}
946
947/*! \reimp
948 */
949
950bool Q3DockArea::eventFilter(QObject *o, QEvent *e)
951{
952 if (e->type() == QEvent::Close) {
953 if (qobject_cast<Q3DockWindow*>(o)) {
954 o->removeEventFilter(this);
955 QApplication::sendEvent(o, e);
956 if (((QCloseEvent*)e)->isAccepted())
957 removeDockWindow((Q3DockWindow*)o, false, false);
958 return true;
959 }
960 }
961 return false;
962}
963
964/*! \internal
965
966 Invalidates the offset of the next dock window in the dock area.
967 */
968
969void Q3DockArea::invalidNextOffset(Q3DockWindow *dw)
970{
971 int i = dockWindows.indexOf(dw);
972 if (i == -1 || i >= (int)dockWindows.count() - 1)
973 return;
974 if ((dw = dockWindows.at(++i)))
975 dw->setOffset(0);
976}
977
978/*!
979 \property Q3DockArea::count
980 \brief the number of dock windows in the dock area
981*/
982int Q3DockArea::count() const
983{
984 return dockWindows.count();
985}
986
987/*!
988 \property Q3DockArea::empty
989 \brief whether the dock area is empty
990*/
991
992bool Q3DockArea::isEmpty() const
993{
994 return dockWindows.isEmpty();
995}
996
997
998/*!
999 Returns a list of the dock windows in the dock area.
1000*/
1001
1002QList<Q3DockWindow *> Q3DockArea::dockWindowList() const
1003{
1004 return dockWindows;
1005}
1006
1007/*!
1008 Lines up the dock windows in this dock area to minimize wasted
1009 space. If \a keepNewLines is true, only space within lines is
1010 cleaned up. If \a keepNewLines is false the number of lines might
1011 be changed.
1012*/
1013
1014void Q3DockArea::lineUp(bool keepNewLines)
1015{
1016 for (int i = 0; i < dockWindows.size(); ++i) {
1017 Q3DockWindow *dw = dockWindows.at(i);
1018 dw->setOffset(0);
1019 if (!keepNewLines)
1020 dw->setNewLine(false);
1021 }
1022 layout->invalidate();
1023 layout->activate();
1024}
1025
1026Q3DockArea::DockWindowData *Q3DockArea::dockWindowData(Q3DockWindow *w)
1027{
1028 DockWindowData *data = new DockWindowData;
1029 data->index = findDockWindow(w);
1030 if (data->index == -1) {
1031 delete data;
1032 return 0;
1033 }
1034 QList<Q3DockWindow *> lineStarts = layout->lineStarts();
1035 int i = -1;
1036 for (int j = 0; j < dockWindows.size(); ++j) {
1037 Q3DockWindow *dw = dockWindows.at(j);
1038 if (lineStarts.contains(dw))
1039 ++i;
1040 if (dw == w)
1041 break;
1042 }
1043 data->line = i;
1044 data->offset = point_pos(QPoint(fix_x(w), w->y()), orientation());
1045 data->area = this;
1046 data->fixedExtent = w->fixedExtent();
1047 return data;
1048}
1049
1050void Q3DockArea::dockWindow(Q3DockWindow *dockWindow, DockWindowData *data)
1051{
1052 if (!data)
1053 return;
1054
1055 dockWindow->setParent(this);
1056 dockWindow->move(0, 0);
1057
1058 dockWindow->installEventFilter(this);
1059 dockWindow->dockArea = this;
1060 dockWindow->updateGui();
1061
1062 if (dockWindows.isEmpty()) {
1063 dockWindows.append(dockWindow);
1064 } else {
1065 QList<Q3DockWindow *> lineStarts = layout->lineStarts();
1066 int index = 0;
1067 if (lineStarts.count() > data->line)
1068 index = dockWindows.indexOf(lineStarts.at(data->line));
1069 if (index == -1)
1070 index = 0;
1071 bool firstTime = true;
1072 int offset = data->offset;
1073 for (int i = index; i < dockWindows.size(); ++i) {
1074 Q3DockWindow *dw = dockWindows.at(i);
1075 if (!firstTime && lineStarts.contains(dw))
1076 break;
1077 if (offset <
1078 point_pos(fix_pos(dw), orientation()) + size_extent(dw->size(), orientation()) / 2)
1079 break;
1080 index++;
1081 firstTime = false;
1082 }
1083 if (index >= 0 && index < dockWindows.count() &&
1084 dockWindows.at(index)->newLine() && lineOf(index) == data->line) {
1085 dockWindows.at(index)->setNewLine(false);
1086 dockWindow->setNewLine(true);
1087 } else {
1088 dockWindow->setNewLine(false);
1089 }
1090
1091 dockWindows.insert(index, dockWindow);
1092 }
1093 dockWindow->show();
1094
1095 dockWindow->setFixedExtentWidth(data->fixedExtent.width());
1096 dockWindow->setFixedExtentHeight(data->fixedExtent.height());
1097
1098 updateLayout();
1099 setSizePolicy(QSizePolicy(orientation() == Qt::Horizontal ? QSizePolicy::Expanding : QSizePolicy::Minimum,
1100 orientation() == Qt::Vertical ? QSizePolicy::Expanding : QSizePolicy::Minimum));
1101
1102}
1103
1104/*!
1105 Returns true if dock window \a dw could be docked into the dock
1106 area; otherwise returns false.
1107
1108 \sa setAcceptDockWindow()
1109*/
1110
1111bool Q3DockArea::isDockWindowAccepted(Q3DockWindow *dw)
1112{
1113 if (!dw)
1114 return false;
1115 if (forbiddenWidgets.contains(dw))
1116 return false;
1117
1118 Q3MainWindow *mw = qobject_cast<Q3MainWindow*>(parentWidget());
1119 if (!mw)
1120 return true;
1121 if (!mw->hasDockWindow(dw))
1122 return false;
1123 if (!mw->isDockEnabled(this))
1124 return false;
1125 if (!mw->isDockEnabled(dw, this))
1126 return false;
1127 return true;
1128}
1129
1130/*!
1131 If \a accept is true, dock window \a dw can be docked in the dock
1132 area. If \a accept is false, dock window \a dw cannot be docked in
1133 the dock area.
1134
1135 \sa isDockWindowAccepted()
1136*/
1137
1138void Q3DockArea::setAcceptDockWindow(Q3DockWindow *dw, bool accept)
1139{
1140 if (accept)
1141 forbiddenWidgets.removeAll(dw);
1142 else if (!forbiddenWidgets.contains(dw))
1143 forbiddenWidgets.append(dw);
1144}
1145
1146void Q3DockArea::invalidateFixedSizes()
1147{
1148 for (int i = 0; i < dockWindows.size(); ++i) {
1149 Q3DockWindow *dw = dockWindows.at(i);
1150 if (orientation() == Qt::Horizontal)
1151 dw->setFixedExtentWidth(-1);
1152 else
1153 dw->setFixedExtentHeight(-1);
1154 }
1155}
1156
1157int Q3DockArea::maxSpace(int hint, Q3DockWindow *dw)
1158{
1159 int index = findDockWindow(dw);
1160 if (index == -1 || index + 1 >= (int)dockWindows.count()) {
1161 if (orientation() == Qt::Horizontal)
1162 return dw->width();
1163 return dw->height();
1164 }
1165
1166 Q3DockWindow *w = 0;
1167 int i = 0;
1168 do {
1169 w = dockWindows.at(index + (++i));
1170 } while (i + 1 < (int)dockWindows.count() && (!w || w->isHidden()));
1171 if (!w || !w->isResizeEnabled() || i >= (int)dockWindows.count()) {
1172 if (orientation() == Qt::Horizontal)
1173 return dw->width();
1174 return dw->height();
1175 }
1176 int min = 0;
1177 Q3ToolBar *tb = qobject_cast<Q3ToolBar*>(w);
1178 if (orientation() == Qt::Horizontal) {
1179 w->setFixedExtentWidth(-1);
1180 if (!tb)
1181 min = qMax(w->minimumSize().width(), w->minimumSizeHint().width());
1182 else
1183 min = w->sizeHint().width();
1184 } else {
1185 w->setFixedExtentHeight(-1);
1186 if (!tb)
1187 min = qMax(w->minimumSize().height(), w->minimumSizeHint().height());
1188 else
1189 min = w->sizeHint().height();
1190 }
1191
1192 int diff = hint - (orientation() == Qt::Horizontal ? dw->width() : dw->height());
1193
1194 if ((orientation() == Qt::Horizontal ? w->width() : w->height()) - diff < min)
1195 hint = (orientation() == Qt::Horizontal ? dw->width() : dw->height()) + (orientation() == Qt::Horizontal ? w->width() : w->height()) - min;
1196
1197 diff = hint - (orientation() == Qt::Horizontal ? dw->width() : dw->height());
1198 if (orientation() == Qt::Horizontal)
1199 w->setFixedExtentWidth(w->width() - diff);
1200 else
1201 w->setFixedExtentHeight(w->height() - diff);
1202 return hint;
1203}
1204
1205void Q3DockArea::setFixedExtent(int d, Q3DockWindow *dw)
1206{
1207 QList<Q3DockWindow *> lst;
1208 for (int i = 0; i < dockWindows.size(); ++i) {
1209 Q3DockWindow *w = dockWindows.at(i);
1210 if (w->isHidden())
1211 continue;
1212 if (orientation() == Qt::Horizontal) {
1213 if (dw->y() != w->y())
1214 continue;
1215 } else {
1216 if (dw->x() != w->x())
1217 continue;
1218 }
1219 if (orientation() == Qt::Horizontal)
1220 d = qMax(d, w->minimumHeight());
1221 else
1222 d = qMax(d, w->minimumWidth());
1223 if (w->isResizeEnabled())
1224 lst.append(w);
1225 }
1226 for (int i = 0; i < lst.size(); ++i) {
1227 Q3DockWindow *w = lst.at(i);
1228 if (orientation() == Qt::Horizontal)
1229 w->setFixedExtentHeight(d);
1230 else
1231 w->setFixedExtentWidth(d);
1232 }
1233}
1234
1235bool Q3DockArea::isLastDockWindow(Q3DockWindow *dw)
1236{
1237 int i = dockWindows.indexOf(dw);
1238 if (i == -1 || i >= (int)dockWindows.count() - 1)
1239 return true;
1240 Q3DockWindow *w = 0;
1241 if ((w = dockWindows.at(++i))) {
1242 if (orientation() == Qt::Horizontal && dw->y() < w->y())
1243 return true;
1244 if (orientation() == Qt::Vertical && dw->x() < w->x())
1245 return true;
1246 } else {
1247 return true;
1248 }
1249 return false;
1250}
1251
1252#ifndef QT_NO_TEXTSTREAM
1253
1254/*!
1255 \relates Q3DockArea
1256
1257 Writes the layout of the dock windows in dock area \a dockArea to
1258 the text stream \a ts.
1259*/
1260
1261QTextStream &operator<<(QTextStream &ts, const Q3DockArea &dockArea)
1262{
1263 QString str;
1264 QList<Q3DockWindow *> l = dockArea.dockWindowList();
1265
1266 for (int i = 0; i < l.size(); ++i) {
1267 Q3DockWindow *dw = l.at(i);
1268 str += QLatin1Char('[') + QString(dw->windowTitle()) + QLatin1Char(',') + QString::number((int)dw->offset()) +
1269 QLatin1Char(',') + QString::number((int)dw->newLine()) + QLatin1Char(',') + QString::number(dw->fixedExtent().width()) +
1270 QLatin1Char(',') + QString::number(dw->fixedExtent().height()) + QLatin1Char(',') + QString::number((int)!dw->isHidden()) + QLatin1Char(']');
1271 }
1272 ts << str << endl;
1273
1274 return ts;
1275}
1276
1277/*!
1278 \relates Q3DockArea
1279
1280 Reads the layout description of the dock windows in dock area \a
1281 dockArea from the text stream \a ts and restores it. The layout
1282 description must have been previously written by the operator<<()
1283 function.
1284*/
1285
1286QTextStream &operator>>(QTextStream &ts, Q3DockArea &dockArea)
1287{
1288 QString s = ts.readLine();
1289
1290 QString name, offset, newLine, width, height, visible;
1291
1292 enum State { Pre, Name, Offset, NewLine, Width, Height, Visible, Post };
1293 int state = Pre;
1294 QChar c;
1295 QList<Q3DockWindow *> l = dockArea.dockWindowList();
1296
1297 for (int i = 0; i < s.length(); ++i) {
1298 c = s[i];
1299 if (state == Pre && c == QLatin1Char('[')) {
1300 state++;
1301 continue;
1302 }
1303 if (c == QLatin1Char(',') &&
1304 (state == Name || state == Offset || state == NewLine || state == Width || state == Height)) {
1305 state++;
1306 continue;
1307 }
1308 if (state == Visible && c == QLatin1Char(']')) {
1309 for (int j = 0; j < l.size(); ++j) {
1310 Q3DockWindow *dw = l.at(j);
1311 if (QString(dw->windowTitle()) == name) {
1312 dw->setNewLine((bool)newLine.toInt());
1313 dw->setOffset(offset.toInt());
1314 dw->setFixedExtentWidth(width.toInt());
1315 dw->setFixedExtentHeight(height.toInt());
1316 if (!(bool)visible.toInt())
1317 dw->hide();
1318 else
1319 dw->show();
1320 break;
1321 }
1322 }
1323
1324 name = offset = newLine = width = height = visible = QLatin1String("");
1325
1326 state = Pre;
1327 continue;
1328 }
1329 if (state == Name)
1330 name += c;
1331 else if (state == Offset)
1332 offset += c;
1333 else if (state == NewLine)
1334 newLine += c;
1335 else if (state == Width)
1336 width += c;
1337 else if (state == Height)
1338 height += c;
1339 else if (state == Visible)
1340 visible += c;
1341 }
1342
1343 dockArea.QWidget::layout()->invalidate();
1344 dockArea.QWidget::layout()->activate();
1345 return ts;
1346}
1347#endif
1348
1349QT_END_NAMESPACE
1350
1351#endif //QT_NO_MAINWINDOW
Note: See TracBrowser for help on using the repository browser.