1 /************************************************************************
2  *
3  * Copyright 2012 Jakob Leben (jakob.leben@gmail.com)
4  *
5  * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
6  * All rights reserved.
7  * Contact: Nokia Corporation (qt-info@nokia.com)
8  *
9  * This file is part of SuperCollider Qt GUI.
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23  *
24  ************************************************************************/
25 
26 #include "stack_layout.hpp"
27 
28 #include <QWidget>
29 
30 namespace QtCollider {
31 
32 StackLayout::StackLayout(): _index(-1), _mode(StackOne), _gotParent(false) {}
33 
34 StackLayout::~StackLayout() { qDeleteAll(_list); }
35 
36 int StackLayout::addWidget(QWidget* widget) { return insertWidget(_list.count(), widget); }
37 
38 int StackLayout::insertWidget(int index, QWidget* widget) {
39     addChildWidget(widget);
40     index = qMin(index, _list.count());
41     if (index < 0)
42         index = _list.count();
43     QWidgetItem* wi = new QWidgetItem(widget);
44     _list.insert(index, wi);
45     invalidate();
46     if (_index < 0) {
47         setCurrentIndex(index);
48     } else {
49         if (index <= _index)
50             ++_index;
51         if (_mode == StackOne)
52             widget->hide();
53         widget->lower();
54     }
55     return index;
56 }
57 
58 QLayoutItem* StackLayout::itemAt(int index) const { return _list.value(index); }
59 
60 QLayoutItem* StackLayout::takeAt(int index) {
61     if (index < 0 || index >= _list.size())
62         return 0;
63     QLayoutItem* item = _list.takeAt(index);
64     if (index == _index) {
65         _index = -1;
66         if (_list.count() > 0) {
67             int newIndex = (index == _list.count()) ? index - 1 : index;
68             setCurrentIndex(newIndex);
69         }
70     } else if (index < _index) {
71         --_index;
72     }
73     // NOTE: Here, the Qt implementation hides item->widget() if it exists and is not being
74     // deleted. We can't reproduce this, because we can't access private info on whether the
75     // widget is being deleted.
76     return item;
77 }
78 
79 void StackLayout::setCurrentIndex(int index) {
80     QWidget* prev = currentWidget();
81     QWidget* next = widget(index);
82     if (!next || next == prev)
83         return;
84 
85     _index = index;
86 
87     if (!parent())
88         return;
89 
90     bool reenableUpdates = false;
91     QWidget* parent = parentWidget();
92 
93     if (parent && parent->updatesEnabled()) {
94         reenableUpdates = true;
95         parent->setUpdatesEnabled(false);
96     }
97 
98     QWidget* fw = parent ? parent->window()->focusWidget() : 0;
99     if (prev) {
100         prev->clearFocus();
101         if (_mode == StackOne)
102             prev->hide();
103     }
104 
105     next->raise();
106     next->show();
107 
108     // try to move focus onto the incoming widget if focus
109     // was somewhere on the outgoing widget.
110 
111     if (parent) {
112         if (fw && (prev && prev->isAncestorOf(fw))) { // focus was on old page
113             // look for the best focus widget we can find
114             if (QWidget* nfw = next->focusWidget())
115                 nfw->setFocus();
116             else {
117                 // second best: first child widget in the focus chain
118                 QWidget* i = fw;
119                 while ((i = i->nextInFocusChain()) != fw) {
120                     if (((i->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && !i->focusProxy() && i->isVisibleTo(next)
121                         && i->isEnabled() && next->isAncestorOf(i)) {
122                         i->setFocus();
123                         break;
124                     }
125                 }
126                 // third best: incoming widget
127                 if (i == fw)
128                     next->setFocus();
129             }
130         }
131     }
132     if (reenableUpdates)
133         parent->setUpdatesEnabled(true);
134 
135     if (_mode == StackOne)
136         // expandingDirections() might have changed, so invalidate():
137         invalidate();
138 }
139 
140 int StackLayout::currentIndex() const { return _index; }
141 
142 void StackLayout::setCurrentWidget(QWidget* widget) {
143     int index = indexOf(widget);
144     if (index == -1) {
145         qWarning("StackLayout::setCurrentWidget: Widget %p not contained in stack", widget);
146         return;
147     }
148     setCurrentIndex(index);
149 }
150 
151 QWidget* StackLayout::currentWidget() const { return _index >= 0 ? _list.at(_index)->widget() : 0; }
152 
153 QWidget* StackLayout::widget(int index) const {
154     if (index < 0 || index >= _list.size())
155         return 0;
156     return _list.at(index)->widget();
157 }
158 
159 int StackLayout::count() const { return _list.size(); }
160 
161 void StackLayout::addItem(QLayoutItem* item) {
162     QWidget* widget = item->widget();
163     if (widget) {
164         addWidget(widget);
165         delete item;
166     } else {
167         qWarning("StackLayout::addItem: Only widgets can be added");
168     }
169 }
170 
171 QSize StackLayout::sizeHint() const {
172     QSize s(0, 0);
173 
174     switch (_mode) {
175     case StackOne:
176         if (_index >= 0)
177             if (QWidget* w = _list.at(_index)->widget()) {
178                 if (w->sizePolicy().horizontalPolicy() != QSizePolicy::Ignored)
179                     s.setWidth(w->sizeHint().width());
180                 if (w->sizePolicy().verticalPolicy() != QSizePolicy::Ignored)
181                     s.setHeight(w->sizeHint().height());
182             }
183         break;
184 
185     case StackAll: {
186         int n = _list.count();
187         for (int i = 0; i < n; ++i)
188             if (QWidget* w = _list.at(i)->widget()) {
189                 QSize ws(w->sizeHint());
190                 if (w->sizePolicy().horizontalPolicy() == QSizePolicy::Ignored)
191                     ws.setWidth(0);
192                 if (w->sizePolicy().verticalPolicy() == QSizePolicy::Ignored)
193                     ws.setHeight(0);
194                 s = s.expandedTo(ws);
195             }
196         break;
197     }
198     }
199 
200     return s;
201 }
202 
203 static QSize smartMinSize(const QSize& sizeHint, const QSize& minSizeHint, const QSize& minSize, const QSize& maxSize,
204                           const QSizePolicy& sizePolicy) {
205     QSize s(0, 0);
206 
207     if (sizePolicy.horizontalPolicy() != QSizePolicy::Ignored) {
208         if (sizePolicy.horizontalPolicy() & QSizePolicy::ShrinkFlag)
209             s.setWidth(minSizeHint.width());
210         else
211             s.setWidth(qMax(sizeHint.width(), minSizeHint.width()));
212     }
213 
214     if (sizePolicy.verticalPolicy() != QSizePolicy::Ignored) {
215         if (sizePolicy.verticalPolicy() & QSizePolicy::ShrinkFlag) {
216             s.setHeight(minSizeHint.height());
217         } else {
218             s.setHeight(qMax(sizeHint.height(), minSizeHint.height()));
219         }
220     }
221 
222     s = s.boundedTo(maxSize);
223     if (minSize.width() > 0)
224         s.setWidth(minSize.width());
225     if (minSize.height() > 0)
226         s.setHeight(minSize.height());
227 
228     return s.expandedTo(QSize(0, 0));
229 }
230 
231 QSize StackLayout::minimumSize() const {
232     QSize s(0, 0);
233 
234     switch (_mode) {
235     case StackOne:
236         if (_index >= 0)
237             if (QWidget* w = _list.at(_index)->widget())
238                 s = smartMinSize(w->sizeHint(), w->minimumSizeHint(), w->minimumSize(), w->maximumSize(),
239                                  w->sizePolicy());
240         break;
241 
242     case StackAll: {
243         int n = _list.count();
244         for (int i = 0; i < n; ++i)
245             if (QWidget* w = _list.at(i)->widget())
246                 s = s.expandedTo(smartMinSize(w->sizeHint(), w->minimumSizeHint(), w->minimumSize(), w->maximumSize(),
247                                               w->sizePolicy()));
248         break;
249     }
250     }
251 
252     return s;
253 }
254 
255 Qt::Orientations StackLayout::expandingDirections() const {
256     Qt::Orientations directions;
257 
258     switch (_mode) {
259     case StackOne:
260         directions = _index >= 0 ? _list.at(_index)->expandingDirections() : (Qt::Orientations)0;
261         break;
262 
263     case StackAll: {
264         Qt::Orientations directions = 0;
265         int n = _list.count();
266         for (int i = 0; i < n; ++i)
267             directions |= _list.at(i)->expandingDirections();
268         break;
269     }
270     }
271 
272     return directions;
273 }
274 
275 void StackLayout::setGeometry(const QRect& rect) {
276     switch (_mode) {
277     case StackOne:
278         if (QWidget* widget = currentWidget())
279             widget->setGeometry(rect);
280         break;
281     case StackAll:
282         if (const int n = _list.count())
283             for (int i = 0; i < n; ++i)
284                 if (QWidget* widget = _list.at(i)->widget())
285                     widget->setGeometry(rect);
286         break;
287     }
288 }
289 
290 StackLayout::StackingMode StackLayout::stackingMode() const { return _mode; }
291 
292 void StackLayout::setStackingMode(StackingMode stackingMode) {
293     if (_mode == stackingMode)
294         return;
295     _mode = stackingMode;
296 
297     if (!parent())
298         return;
299 
300     const int n = _list.count();
301     if (n == 0)
302         return;
303 
304     switch (_mode) {
305     case StackOne: {
306         const int idx = currentIndex();
307         if (idx >= 0)
308             for (int i = 0; i < n; ++i)
309                 if (QWidget* widget = _list.at(i)->widget())
310                     widget->setVisible(i == idx);
311         break;
312     }
313     case StackAll: { // Turn overlay on: Make sure all widgets are the same size
314         QRect geometry;
315         if (const QWidget* widget = currentWidget())
316             geometry = widget->geometry();
317         for (int i = 0; i < n; ++i)
318             if (QWidget* widget = _list.at(i)->widget()) {
319                 if (!geometry.isNull())
320                     widget->setGeometry(geometry);
321                 widget->setVisible(true);
322             }
323     } break;
324     }
325 
326     invalidate();
327 }
328 
329 void StackLayout::invalidate() {
330     QWidget* pw = parentWidget();
331 
332     if (pw && !_gotParent) {
333         _gotParent = true;
334 
335         const int n = _list.count();
336         if (n == 0)
337             return;
338 
339         QWidget* cw = currentWidget();
340 
341         switch (_mode) {
342         case StackOne: {
343             if (cw)
344                 for (int i = 0; i < n; ++i)
345                     if (QWidget* widget = _list.at(i)->widget())
346                         widget->setVisible(widget == cw);
347             break;
348         }
349         case StackAll: {
350             for (int i = 0; i < n; ++i)
351                 if (QWidget* widget = _list.at(i)->widget())
352                     widget->setVisible(true);
353             break;
354         }
355         }
356 
357         // NOTE: re-order the widgets after setting visibility, since the latter
358         // may affect the order itself (at least on Mac OS X)
359         if (cw) {
360             for (int i = 0; i < n; ++i) {
361                 QWidget* w = _list.at(i)->widget();
362                 if (w && w != cw)
363                     w->lower();
364             }
365         }
366     }
367 
368     QLayout::invalidate();
369 }
370 
371 } // namespace QtCollider
372