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