1 #include "titledpanel.h"
2 #include "utilsSystem.h"
3
4 Q_DECLARE_METATYPE(QAction *)
5
6 QHash<QString, TitledPanelPage *> TitledPanelPage::allPages;
7
TitledPanelPage(QWidget * widget,const QString & id,const QString & text,const QIcon & icon)8 TitledPanelPage::TitledPanelPage(QWidget *widget, const QString &id, const QString &text, const QIcon &icon) :
9 m_widget(nullptr), m_visibleAction(nullptr), m_selectAction(nullptr), m_toolbarActions(nullptr)
10 {
11 #ifndef QT_NO_DEBUG
12 if (allPages.contains(id)) {
13 qDebug() << "Duplicate TitledPanelPage ID:" << id;
14 }
15 if (!widget) qDebug() << "Page does not have widget!" << id;
16 #endif
17 allPages.insert(id, this);
18
19 m_widget = widget;
20 Q_ASSERT(m_widget);
21 m_widget->setProperty("containingPage", QVariant::fromValue<TitledPanelPage *>(this));
22 auto *f = qobject_cast<QFrame *>(m_widget); // remove frame form widget if it has one
23 if (f) f->setFrameShape(QFrame::NoFrame);
24
25 m_id = id;
26 m_title = text;
27 m_icon = icon;
28
29 m_visibleAction = new QAction(text, this);
30 m_visibleAction->setCheckable(true);
31 m_visibleAction->setChecked(true);
32 m_visibleAction->setData(id);
33
34 // select action
35 // TODO check
36 m_selectAction = new QAction(text, this);
37 m_selectAction->setData(id);
38 m_selectAction->setIcon(icon);
39 m_selectAction->setToolTip(text);
40 m_selectAction->setCheckable(true);
41
42 m_toolbarActions = new QList<QAction *>();
43 }
44
~TitledPanelPage()45 TitledPanelPage::~TitledPanelPage()
46 {
47 allPages.remove(m_id);
48 delete m_toolbarActions; // only deletes the list. The actions themselves are not owned by the page
49 }
50
addToolbarAction(QAction * act)51 void TitledPanelPage::addToolbarAction(QAction *act)
52 {
53 m_toolbarActions->append(act);
54 }
55
addToolbarActions(const QList<QAction * > & actions)56 void TitledPanelPage::addToolbarActions(const QList<QAction *>& actions)
57 {
58 m_toolbarActions->append(actions);
59 }
60
id() const61 QString TitledPanelPage::id() const
62 {
63 return m_id;
64 }
65
widget()66 QWidget *TitledPanelPage::widget()
67 {
68 return m_widget;
69 }
70
visible() const71 bool TitledPanelPage::visible() const
72 {
73 return m_visibleAction->isChecked();
74 }
75
fromId(const QString & id)76 TitledPanelPage *TitledPanelPage::fromId(const QString &id)
77 {
78 TitledPanelPage *page = allPages.value(id);
79 if (!page) qDebug() << "Requested TitledPanelPage does not exist:" << id;
80 return page;
81 }
82
updatePageTitle(const QString & id,const QString & newTitle)83 void TitledPanelPage::updatePageTitle(const QString &id, const QString& newTitle)
84 {
85 TitledPanelPage *page = allPages.value(id);
86 if (page) page->setTitle(newTitle);
87 }
88
setTitle(const QString & title)89 void TitledPanelPage::setTitle(const QString &title)
90 {
91 if (m_title != title) {
92 m_title = title;
93 m_visibleAction->setText(title);
94 m_selectAction->setText(title);
95 m_selectAction->setToolTip(title);
96 emit titleChanged(title);
97 }
98 }
99
setIcon(const QIcon & icon)100 void TitledPanelPage::setIcon(const QIcon &icon)
101 {
102 m_icon = icon;
103 m_selectAction->setIcon(icon);
104 emit iconChanged(icon);
105 }
106
107
108 /*** class TitledPanel ***/
109
TitledPanel(QWidget * parent)110 TitledPanel::TitledPanel(QWidget *parent) :
111 QFrame(parent), mToggleViewAction(nullptr), closeAction(nullptr), pageSelectActions(nullptr), selectorStyle(ComboSelector), vLayout(nullptr),
112 topbar(nullptr), lbTopbarLabel(nullptr), cbTopbarSelector(nullptr), tbTopbarSelector(nullptr), stack(nullptr)
113 {
114 setFrameShape(QFrame::Box);
115 setFrameShadow(QFrame::Plain);
116
117 mToggleViewAction = new QAction(this);
118 mToggleViewAction->setCheckable(true);
119 mToggleViewAction->setChecked(true);
120 connect(mToggleViewAction, SIGNAL(toggled(bool)), this, SLOT(viewToggled(bool)));
121
122 pageSelectActions = new QActionGroup(this);
123
124 closeAction = new QAction(this);
125 closeAction->setIcon(getRealIcon("close"));
126 closeAction->setToolTip(tr("Close"));
127 connect(closeAction, SIGNAL(triggered()), this, SLOT(hide()));
128
129 stack = new QStackedWidget();
130
131 vLayout = new QVBoxLayout(this);
132 vLayout->setSpacing(0);
133 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
134 vLayout->setMargin(0);
135 #else
136 vLayout->setContentsMargins(0,0,0,0);
137 #endif
138 vLayout->addWidget(stack);
139 setLayout(vLayout);
140
141 updateTopbar();
142 }
143
144 /* note: the panel takes ownership of the page */
appendPage(TitledPanelPage * page,bool guiUpdate)145 void TitledPanel::appendPage(TitledPanelPage *page, bool guiUpdate)
146 {
147 page->setParent(this);
148 pages.append(page);
149 stack->addWidget(page->m_widget);
150 page->m_widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding);
151
152 // TODO adapt for horizontal or vertical panels
153 page->m_widget->setMinimumWidth(0);
154
155 connect(page, SIGNAL(titleChanged(QString)), this, SLOT(onPageTitleChange()));
156 connect(page, SIGNAL(iconChanged(QIcon)), this, SLOT(onPageIconChange()));
157 connect(page->m_selectAction, SIGNAL(toggled(bool)), this, SLOT(setActivePageFromAction()));
158 connect(page->m_visibleAction, SIGNAL(toggled(bool)), this, SLOT(togglePageVisibleFromAction(bool)));
159
160 if (guiUpdate) updateTopbar();
161 }
162
163 /* note: the panel doesn't have a parent anymore. You are responsible for deleting */
removePage(TitledPanelPage * page,bool guiUpdate)164 void TitledPanel::removePage(TitledPanelPage *page, bool guiUpdate)
165 {
166 disconnect(page, SIGNAL(titleChanged()), this, SLOT(onPageTitleChange()));
167 disconnect(page, SIGNAL(iconChanged()), this, SLOT(onPageIconChange()));
168 disconnect(page->m_selectAction, SIGNAL(toggled(bool)), this, SLOT(onPageSelectRequest()));
169 disconnect(page->m_visibleAction, SIGNAL(toggled(bool)), this, SLOT(togglePageVisibleFromAction(bool)));
170
171 stack->removeWidget(page->m_widget);
172 page->setParent(nullptr);
173
174 int i = pages.indexOf(page);
175 if (i >= 0) pages.removeAt(i);
176
177 if (guiUpdate) updateTopbar();
178 }
179
pageCount() const180 int TitledPanel::pageCount() const
181 {
182 return pages.count();
183 }
184
pageFromId(const QString & id)185 TitledPanelPage *TitledPanel::pageFromId(const QString &id)
186 {
187 TitledPanelPage *page = TitledPanelPage::fromId(id);
188 if (!page || !pages.contains(page))
189 return nullptr;
190 return page;
191 }
192
193 // TODO
setHiddenPageIds(const QStringList & hiddenIds)194 void TitledPanel::setHiddenPageIds(const QStringList &hiddenIds)
195 {
196 mHiddenPageIds = hiddenIds;
197 qDebug() << "hidden pages not yet implemented";
198 }
199
200 // TODO
hiddenPageIds() const201 QStringList TitledPanel::hiddenPageIds() const
202 {
203 qDebug() << "hidden pages not yet implemented";
204 return mHiddenPageIds;
205 }
206
setCurrentPage(const QString & id)207 void TitledPanel::setCurrentPage(const QString &id)
208 {
209 TitledPanelPage *page = TitledPanelPage::fromId(id);
210 if (!page) {
211 qDebug() << "TitledPanel: trying to access invalid page" << id;
212 return;
213 }
214 if (currentPage() == page)
215 return;
216
217 stack->setCurrentWidget(page->m_widget);
218 page->m_selectAction->setChecked(true);
219
220 updateTopbar();
221
222 emit pageChanged(id);
223 }
224
currentPage() const225 TitledPanelPage *TitledPanel::currentPage() const
226 {
227 QWidget *w = stack->currentWidget();
228 if (!w) return nullptr;
229 return qvariant_cast<TitledPanelPage *>(w->property("containingPage"));
230 }
231
currentPageId() const232 QString TitledPanel::currentPageId() const
233 {
234 TitledPanelPage *page = currentPage();
235 if (page) return page->id();
236 return QString();
237 }
238
showPage(const QString & id)239 void TitledPanel::showPage(const QString &id)
240 {
241 setCurrentPage(id);
242 show();
243 }
244
toggleViewAction() const245 QAction *TitledPanel::toggleViewAction() const
246 {
247 return mToggleViewAction;
248 }
249
setSelectorStyle(TitledPanel::PageSelectorStyle style)250 void TitledPanel::setSelectorStyle(TitledPanel::PageSelectorStyle style)
251 {
252 selectorStyle = style;
253 updateTopbar();
254 }
255
setVisible(bool visible)256 void TitledPanel::setVisible(bool visible)
257 {
258 QFrame::setVisible(visible);
259 mToggleViewAction->setChecked(visible);
260 }
261
updateTopbar()262 void TitledPanel::updateTopbar()
263 {
264 QToolBar *oldTopbar = topbar;
265
266 // did not manage to remove and add again widgets to the toolbar properly
267 // widgets added by toolbar->addWidget seem to have a bit weird behaviour
268 // on toolbar->clear() or reparenting
269 // so we create a new toolbar
270 // alternatively do not use a toolbar, but a widget styled like one, as QtCreator does
271
272 // dpi aware icon scaling
273 // screen dpi is read and the icon are scaled up in reference to 96 dpi
274 // this should be helpful on X11 (Xresouces) and possibly windows
275 double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
276 double scale=dpi/96;
277
278 topbar = new QToolBar(this);
279 topbar->setOrientation(Qt::Horizontal);
280 topbar->setFloatable(false);
281 topbar->setMovable(false);
282 topbar->setIconSize(QSize(qRound(16*scale), qRound(16*scale)));
283 topbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
284
285 QList<QAction *> acts = pageSelectActions->actions();
286 foreach (QAction *act, acts) {
287 pageSelectActions->removeAction(act);
288 }
289
290 int visiblePageCount = 0;
291 TitledPanelPage *firstVisiblePage = nullptr;
292 foreach (TitledPanelPage *p, pages) {
293 if (p->visible()) {
294 if (!firstVisiblePage) firstVisiblePage = p;
295 visiblePageCount++;
296 }
297 }
298
299 // will be deleted together with oldTopbar because they are children of it
300 lbTopbarLabel = nullptr;
301 cbTopbarSelector = nullptr;
302 tbTopbarSelector = nullptr;
303
304 if (visiblePageCount == 1) {
305 lbTopbarLabel = new QLabel(firstVisiblePage->m_title);
306 lbTopbarLabel->setIndent(4);
307 topbar->addWidget(lbTopbarLabel);
308 } else if (visiblePageCount > 1) {
309 switch (selectorStyle) {
310 case ComboSelector:
311 cbTopbarSelector = new QComboBox(topbar);
312 cbTopbarSelector->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
313 cbTopbarSelector->setMinimumWidth(0);
314 cbTopbarSelector->setMaxVisibleItems(25);
315 connect(cbTopbarSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(setActivePageFromComboBox(int)));
316 break;
317 case TabSelector:
318 tbTopbarSelector = new QTabBar(topbar);
319 connect(tbTopbarSelector, SIGNAL(currentChanged(int)), this, SLOT(setActivePageFromTabBar(int)));
320 }
321
322 foreach (TitledPanelPage *p, pages) {
323 if (p->visible()) {
324 pageSelectActions->addAction(p->m_selectAction);
325
326 // create selector (items)
327 if (cbTopbarSelector) {
328 bool old = cbTopbarSelector->blockSignals(true);
329 cbTopbarSelector->addItem(p->m_title, p->id());
330 cbTopbarSelector->blockSignals(old);
331 } else if (tbTopbarSelector) {
332 bool old = tbTopbarSelector->blockSignals(true);
333 int idx = tbTopbarSelector->addTab(p->m_title);
334 tbTopbarSelector->setTabData(idx, p->id());
335 tbTopbarSelector->blockSignals(old);
336 }
337 }
338 }
339
340 // update selector
341 if (cbTopbarSelector) {
342 int index = cbTopbarSelector->findData(currentPageId());
343 if (index >= 0) {
344 cbTopbarSelector->setCurrentIndex(index);
345 } else {
346 QString id = cbTopbarSelector->itemData(0).toString();
347 stack->setCurrentWidget(TitledPanelPage::fromId(id)->m_widget);
348 }
349 topbar->addWidget(cbTopbarSelector);
350 } else if (tbTopbarSelector) {
351 int index;
352 for (index = 0; index < tbTopbarSelector->count(); index++) {
353 if (tbTopbarSelector->tabData(index).toString() == currentPageId()) {
354 tbTopbarSelector->setCurrentIndex(index);
355 break;
356 }
357 }
358 if (index >= tbTopbarSelector->count()) { // currentPage not visible any more: fall back to first page
359 QString id = tbTopbarSelector->tabData(0).toString();
360 stack->setCurrentWidget(TitledPanelPage::fromId(id)->m_widget);
361 }
362 topbar->addWidget(tbTopbarSelector);
363 #ifdef Q_OS_MAC
364 topbar->layout()->itemAt(topbar->layout()->count() - 1)->setAlignment(Qt::AlignVCenter);
365 #else
366 topbar->layout()->itemAt(topbar->layout()->count() - 1)->setAlignment(Qt::AlignBottom);
367 #endif
368 }
369
370
371 /* TODO alternative selectors:
372
373 QActionGroup *sel = new QActionGroup(this);
374 QAction *act = sel->addAction("Hallo");
375 act->setCheckable(true);
376 topbar->addAction(act);
377 act = sel->addAction("Welt");
378 act->setCheckable(true);
379 topbar->addAction(act);
380 act = sel->addAction("Fuzzy");
381 act->setCheckable(true);
382 topbar->addAction(act); */
383 }
384
385 if (currentPage()) {
386 foreach (QAction *act, *currentPage()->m_toolbarActions) {
387 topbar->addAction(act);
388 }
389 }
390
391 QWidget *topbarSpacer = new QWidget(topbar);
392 topbarSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
393 topbar->addWidget(topbarSpacer);
394 topbar->addAction(closeAction);
395
396 if (oldTopbar) {
397 oldTopbar->hide();
398 vLayout->removeWidget(oldTopbar);
399 oldTopbar->deleteLater();
400 }
401 vLayout->insertWidget(0, topbar);
402 }
403
updatePageSelector(TitledPanelPage * page)404 void TitledPanel::updatePageSelector(TitledPanelPage *page)
405 {
406 if (!page) {
407 foreach (TitledPanelPage *p, pages) {
408 updatePageSelector(p);
409 }
410 return;
411 }
412
413 if (lbTopbarLabel) {
414 lbTopbarLabel->setText(page->m_title);
415 }
416
417 if (cbTopbarSelector) {
418 int index = cbTopbarSelector->findData(page->id());
419 if (index >= 0) {
420 cbTopbarSelector->setItemText(index, page->m_title);
421 }
422 }
423 }
424
onPageTitleChange()425 void TitledPanel::onPageTitleChange()
426 {
427 auto *page = qobject_cast<TitledPanelPage *>(sender());
428 if (!page) return;
429
430 updatePageSelector(page);
431 }
432
onPageIconChange()433 void TitledPanel::onPageIconChange()
434 {
435 auto *act = qobject_cast<QAction *>(sender());
436 if (act) {
437 // TODO update page select controls
438 qDebug() << "Page icon change not yet implemented";
439 }
440 }
441
setActivePageFromAction()442 void TitledPanel::setActivePageFromAction()
443 {
444 auto *act = qobject_cast<QAction *>(sender());
445 if (!act) return;
446 setCurrentPage(act->data().toString());
447 }
448
setActivePageFromComboBox(int index)449 void TitledPanel::setActivePageFromComboBox(int index)
450 {
451 auto *box = qobject_cast<QComboBox *>(sender());
452 if (box)
453 setCurrentPage(box->itemData(index).toString());
454 }
455
setActivePageFromTabBar(int index)456 void TitledPanel::setActivePageFromTabBar(int index)
457 {
458 auto *tabBar = qobject_cast<QTabBar *>(sender());
459 if (tabBar)
460 setCurrentPage(tabBar->tabData(index).toString());
461 }
462
togglePageVisibleFromAction(bool on)463 void TitledPanel::togglePageVisibleFromAction(bool on)
464 {
465 Q_UNUSED(on)
466 auto *act = qobject_cast<QAction *>(sender());
467 if (!act || act->data().toString() == "") return;
468
469 // TODO maybe just remove(id)
470 //updateTopbar();
471 }
472
473 // TODO check: can't we directly set the context menu on the widget?
customContextMenuRequested(const QPoint & localPosition)474 void TitledPanel::customContextMenuRequested(const QPoint &localPosition)
475 {
476 QWidget *widget = stack->currentWidget();
477 if (widget && widget->underMouse()) { //todo?: use a more reliable function than underMouse (see qt bug 260000)
478 emit widgetContextMenuRequested(widget, mapToGlobal(localPosition));
479 } else {
480 QMenu menu;
481 foreach (TitledPanelPage *page, pages) {
482 menu.addAction(page->m_visibleAction);
483 }
484 menu.exec(mapToGlobal(localPosition));
485 }
486 }
487
488
489
490
491
492
493
494
495