1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of Qt Creator.
7**
8** Commercial License Usage
9** Licensees holding valid commercial Qt licenses may use this file in
10** accordance with the commercial license agreement provided with the
11** Software or, alternatively, in accordance with the terms contained in
12** a written agreement between you and The Qt Company. For licensing terms
13** and conditions see https://www.qt.io/terms-conditions. For further
14** information use the contact form at https://www.qt.io/contact-us.
15**
16** GNU General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU
18** General Public License version 3 as published by the Free Software
19** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20** included in the packaging of this file. Please review the following
21** information to ensure the GNU General Public License requirements will
22** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23**
24****************************************************************************/
25
26#include "sidebar.h"
27#include "sidebarwidget.h"
28
29#include "actionmanager/command.h"
30#include <utils/algorithm.h>
31#include <utils/qtcassert.h>
32#include <utils/utilsicons.h>
33
34#include <QSettings>
35#include <QPointer>
36#include <QToolButton>
37
38namespace Core {
39
40SideBarItem::SideBarItem(QWidget *widget, const QString &id) :
41 m_id(id), m_widget(widget)
42{
43}
44
45SideBarItem::~SideBarItem()
46{
47 delete m_widget;
48}
49
50QWidget *SideBarItem::widget() const
51{
52 return m_widget;
53}
54
55QString SideBarItem::id() const
56{
57 return m_id;
58}
59
60QString SideBarItem::title() const
61{
62 return m_widget->windowTitle();
63}
64
65QList<QToolButton *> SideBarItem::createToolBarWidgets()
66{
67 return QList<QToolButton *>();
68}
69
70struct SideBarPrivate {
71 SideBarPrivate() = default;
72
73 QList<Internal::SideBarWidget*> m_widgets;
74 QMap<QString, QPointer<SideBarItem> > m_itemMap;
75 QStringList m_availableItemIds;
76 QStringList m_availableItemTitles;
77 QStringList m_unavailableItemIds;
78 QStringList m_defaultVisible;
79 QMap<QString, Core::Command*> m_shortcutMap;
80 bool m_closeWhenEmpty = false;
81};
82
83SideBar::SideBar(QList<SideBarItem*> itemList,
84 QList<SideBarItem*> defaultVisible) :
85 d(new SideBarPrivate)
86{
87 setOrientation(Qt::Vertical);
88 foreach (SideBarItem *item, itemList) {
89 d->m_itemMap.insert(item->id(), item);
90 d->m_availableItemIds.append(item->id());
91 d->m_availableItemTitles.append(item->title());
92 }
93
94 foreach (SideBarItem *item, defaultVisible) {
95 if (!itemList.contains(item))
96 continue;
97 d->m_defaultVisible.append(item->id());
98 }
99}
100
101SideBar::~SideBar()
102{
103 foreach (const QPointer<SideBarItem> &i, d->m_itemMap)
104 if (!i.isNull())
105 delete i.data();
106 delete d;
107}
108
109QString SideBar::idForTitle(const QString &title) const
110{
111 for (auto iter = d->m_itemMap.cbegin(), end = d->m_itemMap.cend(); iter != end; ++iter) {
112 if (iter.value().data()->title() == title)
113 return iter.key();
114 }
115 return QString();
116}
117
118QStringList SideBar::availableItemIds() const
119{
120 return d->m_availableItemIds;
121}
122
123QStringList SideBar::availableItemTitles() const
124{
125 return d->m_availableItemTitles;
126}
127
128QStringList SideBar::unavailableItemIds() const
129{
130 return d->m_unavailableItemIds;
131}
132
133bool SideBar::closeWhenEmpty() const
134{
135 return d->m_closeWhenEmpty;
136}
137void SideBar::setCloseWhenEmpty(bool value)
138{
139 d->m_closeWhenEmpty = value;
140}
141
142void SideBar::makeItemAvailable(SideBarItem *item)
143{
144 auto cend = d->m_itemMap.constEnd();
145 for (auto it = d->m_itemMap.constBegin(); it != cend ; ++it) {
146 if (it.value().data() == item) {
147 d->m_availableItemIds.append(it.key());
148 d->m_availableItemTitles.append(it.value().data()->title());
149 d->m_unavailableItemIds.removeAll(it.key());
150 Utils::sort(d->m_availableItemTitles);
151 emit availableItemsChanged();
152 //updateWidgets();
153 break;
154 }
155 }
156}
157
158// sets a list of externally used, unavailable items. For example,
159// another sidebar could set
160void SideBar::setUnavailableItemIds(const QStringList &itemIds)
161{
162 // re-enable previous items
163 foreach (const QString &id, d->m_unavailableItemIds) {
164 d->m_availableItemIds.append(id);
165 d->m_availableItemTitles.append(d->m_itemMap.value(id).data()->title());
166 }
167
168 d->m_unavailableItemIds.clear();
169
170 foreach (const QString &id, itemIds) {
171 if (!d->m_unavailableItemIds.contains(id))
172 d->m_unavailableItemIds.append(id);
173 d->m_availableItemIds.removeAll(id);
174 d->m_availableItemTitles.removeAll(d->m_itemMap.value(id).data()->title());
175 }
176 Utils::sort(d->m_availableItemTitles);
177 updateWidgets();
178}
179
180SideBarItem *SideBar::item(const QString &id)
181{
182 if (d->m_itemMap.contains(id)) {
183 d->m_availableItemIds.removeAll(id);
184 d->m_availableItemTitles.removeAll(d->m_itemMap.value(id).data()->title());
185
186 if (!d->m_unavailableItemIds.contains(id))
187 d->m_unavailableItemIds.append(id);
188
189 emit availableItemsChanged();
190 return d->m_itemMap.value(id).data();
191 }
192 return nullptr;
193}
194
195Internal::SideBarWidget *SideBar::insertSideBarWidget(int position, const QString &id)
196{
197 if (!d->m_widgets.isEmpty())
198 d->m_widgets.at(0)->setCloseIcon(Utils::Icons::CLOSE_SPLIT_BOTTOM.icon());
199
200 auto item = new Internal::SideBarWidget(this, id);
201 connect(item, &Internal::SideBarWidget::splitMe, this, &SideBar::splitSubWidget);
202 connect(item, &Internal::SideBarWidget::closeMe, this, &SideBar::closeSubWidget);
203 connect(item, &Internal::SideBarWidget::currentWidgetChanged, this, &SideBar::updateWidgets);
204 insertWidget(position, item);
205 d->m_widgets.insert(position, item);
206 if (d->m_widgets.size() == 1)
207 d->m_widgets.at(0)->setCloseIcon(d->m_widgets.size() == 1
208 ? Utils::Icons::CLOSE_SPLIT_LEFT.icon()
209 : Utils::Icons::CLOSE_SPLIT_TOP.icon());
210 updateWidgets();
211 return item;
212}
213
214void SideBar::removeSideBarWidget(Internal::SideBarWidget *widget)
215{
216 widget->removeCurrentItem();
217 d->m_widgets.removeOne(widget);
218 widget->hide();
219 widget->deleteLater();
220}
221
222void SideBar::splitSubWidget()
223{
224 auto original = qobject_cast<Internal::SideBarWidget*>(sender());
225 int pos = indexOf(original) + 1;
226 insertSideBarWidget(pos);
227 updateWidgets();
228}
229
230void SideBar::closeSubWidget()
231{
232 if (d->m_widgets.count() != 1) {
233 auto widget = qobject_cast<Internal::SideBarWidget*>(sender());
234 if (!widget)
235 return;
236 removeSideBarWidget(widget);
237 // update close button of top item
238 if (d->m_widgets.size() == 1)
239 d->m_widgets.at(0)->setCloseIcon(d->m_widgets.size() == 1
240 ? Utils::Icons::CLOSE_SPLIT_LEFT.icon()
241 : Utils::Icons::CLOSE_SPLIT_TOP.icon());
242 updateWidgets();
243 } else {
244 if (d->m_closeWhenEmpty) {
245 setVisible(false);
246 emit sideBarClosed();
247 }
248 }
249}
250
251void SideBar::updateWidgets()
252{
253 foreach (Internal::SideBarWidget *i, d->m_widgets)
254 i->updateAvailableItems();
255}
256
257void SideBar::saveSettings(QSettings *settings, const QString &name)
258{
259 const QString prefix = name.isEmpty() ? name : (name + QLatin1Char('/'));
260
261 QStringList views;
262 for (int i = 0; i < d->m_widgets.count(); ++i) {
263 QString currentItemId = d->m_widgets.at(i)->currentItemId();
264 if (!currentItemId.isEmpty())
265 views.append(currentItemId);
266 }
267 if (views.isEmpty() && !d->m_itemMap.isEmpty())
268 views.append(d->m_itemMap.cbegin().key());
269
270 settings->setValue(prefix + QLatin1String("Views"), views);
271 settings->setValue(prefix + QLatin1String("Visible"),
272 parentWidget() ? isVisibleTo(parentWidget()) : true);
273 settings->setValue(prefix + QLatin1String("VerticalPosition"), saveState());
274 settings->setValue(prefix + QLatin1String("Width"), width());
275}
276
277void SideBar::closeAllWidgets()
278{
279 foreach (Internal::SideBarWidget *widget, d->m_widgets)
280 removeSideBarWidget(widget);
281}
282
283void SideBar::readSettings(QSettings *settings, const QString &name)
284{
285 const QString prefix = name.isEmpty() ? name : (name + QLatin1Char('/'));
286
287 closeAllWidgets();
288
289 const QString viewsKey = prefix + QLatin1String("Views");
290 if (settings->contains(viewsKey)) {
291 QStringList views = settings->value(viewsKey).toStringList();
292 if (views.count()) {
293 foreach (const QString &id, views)
294 if (availableItemIds().contains(id))
295 insertSideBarWidget(d->m_widgets.count(), id);
296
297 } else {
298 insertSideBarWidget(0);
299 }
300 }
301 if (d->m_widgets.size() == 0) {
302 foreach (const QString &id, d->m_defaultVisible)
303 insertSideBarWidget(d->m_widgets.count(), id);
304 }
305
306 const QString visibleKey = prefix + QLatin1String("Visible");
307 if (settings->contains(visibleKey))
308 setVisible(settings->value(visibleKey).toBool());
309
310 const QString positionKey = prefix + QLatin1String("VerticalPosition");
311 if (settings->contains(positionKey))
312 restoreState(settings->value(positionKey).toByteArray());
313
314 const QString widthKey = prefix + QLatin1String("Width");
315 if (settings->contains(widthKey)) {
316 QSize s = size();
317 s.setWidth(settings->value(widthKey).toInt());
318 resize(s);
319 }
320}
321
322void SideBar::activateItem(const QString &id)
323{
324 QTC_ASSERT(d->m_itemMap.contains(id), return);
325 for (int i = 0; i < d->m_widgets.count(); ++i) {
326 if (d->m_widgets.at(i)->currentItemId() == id) {
327 d->m_itemMap.value(id)->widget()->setFocus();
328 return;
329 }
330 }
331
332 Internal::SideBarWidget *widget = d->m_widgets.first();
333 widget->setCurrentItem(id);
334 updateWidgets();
335 d->m_itemMap.value(id)->widget()->setFocus();
336}
337
338void SideBar::setShortcutMap(const QMap<QString, Command*> &shortcutMap)
339{
340 d->m_shortcutMap = shortcutMap;
341}
342
343QMap<QString, Command*> SideBar::shortcutMap() const
344{
345 return d->m_shortcutMap;
346}
347} // namespace Core
348
349