1 /*
2  * psitabwidget.cpp - Customised QTabWidget for Psi
3  * Copyright (C) 2006  Kevin Smith
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18  *
19  */
20 
21 #include "psitabwidget.h"
22 #include "psitabbar.h"
23 #include "common.h"
24 #include "psioptions.h"
25 #include <QWidget>
26 #include <QVBoxLayout>
27 #include <QHBoxLayout>
28 #include <QToolButton>
29 #include <QStackedLayout>
30 #include <QStyle>
31 #include <QApplication>
32 #include <QMenu>
33 
34 /**
35  * Constructor
36  */
PsiTabWidget(QWidget * parent)37 PsiTabWidget::PsiTabWidget(QWidget *parent)
38 		: QWidget(parent) {
39 	tabsPosition_ = QTabWidget::East; // impossible => uninitialised state
40 	tabBar_ = new PsiTabBar(this);
41 	tabBar_->setUsesScrollButtons(true);
42 	layout_ = new QVBoxLayout(this);
43 	layout_->setMargin(0);
44 	layout_->setSpacing(0);
45 	barLayout_ = new QHBoxLayout;
46 	layout_->addLayout(barLayout_);
47 	barLayout_->setMargin(0);
48 	barLayout_->setSpacing(0);
49 	barLayout_->addWidget(tabBar_, 2);
50 	barLayout_->setAlignment(Qt::AlignLeft);
51 
52 	int buttonwidth = qMax(tabBar_->style()->pixelMetric(QStyle::PM_TabBarScrollButtonWidth, 0, tabBar_),
53 		QApplication::globalStrut().width());
54 
55 	downButton_ = new QToolButton(this);
56 	downButton_->setMinimumSize(3,3);
57 	downButton_->setFixedWidth(buttonwidth);
58 	downButton_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
59 	menu_ = new QMenu(this);
60 	downButton_->setMenu(menu_);
61 	downButton_->setStyleSheet(" QToolButton::menu-indicator { image:none } ");
62 	connect(menu_, SIGNAL(aboutToShow()), SLOT(menu_aboutToShow()));
63 	connect(menu_, SIGNAL(triggered(QAction*)), SLOT(menu_triggered(QAction*)));
64 	barLayout_->addWidget(downButton_);
65 
66 	closeButton_ = new QToolButton(this);
67 	closeButton_->setMinimumSize(3,3);
68 	closeButton_->setFixedWidth(buttonwidth);
69 	closeButton_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
70 	barLayout_->addWidget(closeButton_);
71 	closeButton_->setText("x");
72 	downButton_->setArrowType(Qt::DownArrow);
73 	downButton_->setPopupMode(QToolButton::InstantPopup);
74 	stacked_ = new QStackedLayout(layout_);
75 
76 	setTabPosition(QTabWidget::North);
77 	setLooks();
78 
79 	if (!PsiOptions::instance()->getOption("options.ui.tabs.show-tab-close-buttons").toBool()){
80 		tabBar_->setTabsClosable(false);
81 	}
82 	connect(tabBar_, SIGNAL(mouseDoubleClickTab(int)), SLOT(mouseDoubleClickTab(int)));
83 	connect(tabBar_, SIGNAL(mouseMiddleClickTab(int)), SLOT(mouseMiddleClickTab(int)));
84 	connect(tabBar_, SIGNAL( currentChanged(int)), SLOT(tab_currentChanged(int)));
85 	connect(tabBar_, SIGNAL( contextMenu(QContextMenuEvent*,int)), SLOT( tab_contextMenu(QContextMenuEvent*,int)));
86 	connect(tabBar_, SIGNAL(tabMoved(int,int)),SLOT(widgetMoved(int,int)));
87 	connect(tabBar_, SIGNAL(tabCloseRequested(int)),SIGNAL(tabCloseRequested(int)));
88 	connect(closeButton_, SIGNAL(clicked()), SIGNAL(closeButtonClicked()));
89 }
90 
setCloseIcon(const QIcon & icon)91 void PsiTabWidget::setCloseIcon(const QIcon& icon) {
92 	closeButton_->setIcon(icon);
93 	closeButton_->setText("");
94 }
95 
96 /**
97  * Destructor
98  */
~PsiTabWidget()99 PsiTabWidget::~PsiTabWidget() {
100 }
101 
102 /**
103  * Set the color of text on a tab.
104  * \param tab Widget for the tab to change.
105  * \param color Color to set text.
106  */
setTabTextColor(QWidget * tab,const QColor & color)107 void PsiTabWidget::setTabTextColor( QWidget* tab, const QColor& color) {
108 	for (int i = 0; i < count(); i++) {
109 		if (widget(i) == tab) {
110 			tabBar_->setTabTextColor(i, color);
111 		}
112 	}
113 }
114 
115 /**
116  * Returns the specified widget.
117  * \param index Widget to return.
118  * \return Specified widget.
119  */
widget(int index)120 QWidget *PsiTabWidget::widget(int index) {
121 	return widgets_[index];
122 }
123 
mouseDoubleClickTab(int tab)124 void PsiTabWidget::mouseDoubleClickTab(int tab) {
125 	emit mouseDoubleClickTab(widget(tab));
126 }
127 
mouseMiddleClickTab(int tab)128 void PsiTabWidget::mouseMiddleClickTab(int tab) {
129 	emit mouseMiddleClickTab(widget(tab));
130 }
131 
132 /**
133  * Number of tabs/widgets
134  */
count()135 int PsiTabWidget::count() {
136 	return tabBar_->count();
137 }
138 
139 /**
140  * Returns the widget of the current page
141  */
currentPage()142 QWidget *PsiTabWidget::currentPage() {
143 	if (currentPageIndex() == -1)
144 		return 0;
145 	return widgets_[currentPageIndex()];
146 }
147 
tab_currentChanged(int tab)148 void PsiTabWidget::tab_currentChanged(int tab) {
149 	// qt 4.4 sends -1 i case of an empty QTabbar, ignore that case.
150 	if (tab == -1) return;
151 	setCurrentPage(tab);
152 	emit currentChanged(currentPage());
153 }
154 
155 /**
156  * Returns the index of the current page
157  */
currentPageIndex()158 int PsiTabWidget::currentPageIndex() {
159 	return tabBar_->currentIndex();
160 }
161 
162 /**
163  * Add the Widget to the tab stack.
164  */
addTab(QWidget * widget,QString name,const QIcon & icon)165 void PsiTabWidget::addTab(QWidget *widget, QString name, const QIcon &icon)
166 {
167 	Q_ASSERT(widget);
168 	if (widgets_.contains(widget)) {
169 		return;
170 	}
171 	widgets_.append(widget);
172 	stacked_->addWidget(widget);
173 	if (PsiOptions::instance()->getOption("options.ui.tabs.show-tab-icons").toBool())
174 		tabBar_->addTab(icon, name);
175 	else
176 		tabBar_->addTab(name);
177 	setLooks();
178 	showPage(currentPage());
179 }
180 
setLooks()181 void PsiTabWidget::setLooks()
182 {
183 	const QString css = PsiOptions::instance()->getOption("options.ui.chat.css").toString();
184 	if (!css.isEmpty()) {
185 		setStyleSheet(css);
186 	}
187 }
188 
189 /**
190  * Selects the page for the specified widget.
191  */
showPage(QWidget * widget)192 void PsiTabWidget::showPage(QWidget* widget) {
193 	for (int i = 0; i < count(); i++) {
194 		if (widgets_[i] == widget) {
195 			showPageDirectly(widget);
196 			tabBar_->setCurrentIndex(i);
197 		}
198 	}
199 
200 }
201 
202 /**
203  * Selects the page for the specified widget (internal helper).
204  */
showPageDirectly(QWidget * widget)205 void PsiTabWidget::showPageDirectly(QWidget* widget) {
206 	// FIXME move this back into showPage? should this be in the public interface?
207 	for (int i=0; i < count(); i++) {
208 		if (widgets_[i] == widget) {
209 			stacked_->setCurrentWidget(widget);
210 			// currentChanged is handled by tabBar_
211 			return;
212 		}
213 	}
214 }
215 
216 /**
217  * Removes the page for the specified widget.
218  */
removePage(QWidget * widget)219 void PsiTabWidget::removePage(QWidget* widget) {
220 	for (int i=0; i < count(); i++) {
221 		if (widgets_[i] == widget) {
222 			stacked_->removeWidget(widget);
223 			widgets_.remove(i);
224 			tabBar_->removeTab(i);
225 			// tabBar_ emits current changed if needed
226 		}
227 	}
228 }
229 
230 /**
231  * Finds the index of the widget (or -1 if missing).
232  */
getIndex(QWidget * widget)233 int PsiTabWidget::getIndex(QWidget* widget) {
234 	for (int i = 0; i < count(); i++) {
235 		if (widgets_[i] == widget) {
236 			return i;
237 		}
238 	}
239 	return -1;
240 }
241 
242 /**
243  * Set the text of the tab.
244  */
setTabText(QWidget * widget,const QString & label)245 void PsiTabWidget::setTabText(QWidget* widget, const QString& label) {
246 	int index = getIndex(widget);
247 	if (index == -1) {
248 		return;
249 	}
250 	tabBar_->setTabText(index, label);
251 }
252 
253 /**
254  * Set the icon of the tab.
255  */
setTabIcon(QWidget * widget,const QIcon & icon)256 void PsiTabWidget::setTabIcon(QWidget *widget, const QIcon &icon)
257 {
258 	int index = getIndex(widget);
259 	if (index == -1 || !PsiOptions::instance()->getOption("options.ui.tabs.show-tab-icons").toBool()) {
260 		return;
261 	}
262 	tabBar_->setTabIcon(index, icon);
263 }
264 
setCurrentPage(int index)265 void PsiTabWidget::setCurrentPage(int index) {
266 	if (index >= 0 && index < count()) {
267 		showPage(widgets_[index]);
268 	}
269 }
270 
removeCurrentPage()271 void PsiTabWidget::removeCurrentPage() {
272 	removePage(currentPage());
273 }
274 
setTabPosition(QTabWidget::TabPosition pos)275 void PsiTabWidget::setTabPosition(QTabWidget::TabPosition pos) {
276 	if (tabsPosition_ == pos) {
277 		return;
278 	}
279 
280 	tabsPosition_ = pos;
281 	tabBar_->setShape(tabsPosition_ == QTabWidget::North ? QTabBar::RoundedNorth : QTabBar::RoundedSouth);
282 
283 	layout_->removeItem(barLayout_);
284 	layout_->removeItem(stacked_);
285 
286 	// addLayout sets parent and complains if it's already set
287 	barLayout_->setParent(0);
288 	stacked_->setParent(0);
289 	if (tabsPosition_ == QTabWidget::North) {
290 		layout_->addLayout(barLayout_);
291 		layout_->addLayout(stacked_);
292 	} else {
293 		layout_->addLayout(stacked_);
294 		layout_->addLayout(barLayout_);
295 	}
296 }
297 
menu_aboutToShow()298 void PsiTabWidget::menu_aboutToShow() {
299 	clearMenu(menu_);
300 	bool vis = false;
301 	for (int i = 0; i < tabBar_->count(); i++) {
302 		QRect r = tabBar_->tabRect(i);
303 		bool newvis = tabBar_->rect().contains(r);
304 		if (newvis != vis) {
305 			menu_->addSeparator ();
306 			vis = newvis;
307 		}
308 		menu_->addAction(tabBar_->tabText(i))->setData(i+1);
309 	}
310 	emit aboutToShowMenu(menu_);
311 }
312 
menu_triggered(QAction * act)313 void PsiTabWidget::menu_triggered(QAction *act) {
314 	int idx = act->data().toInt();
315 	if (idx <= 0 || idx > tabBar_->count()) {
316 		// out of range
317 		// emit signal?
318 	} else {
319 		setCurrentPage(idx-1);
320 	}
321 }
322 
tab_contextMenu(QContextMenuEvent * event,int tab)323 void PsiTabWidget::tab_contextMenu( QContextMenuEvent * event, int tab) {
324 	emit tabContextMenu(tab, tabBar_->mapToGlobal(event->pos()), event);
325 }
326 
page(int index)327 QWidget* PsiTabWidget::page(int index) {
328 	Q_ASSERT(index >=0 && index < count());
329 	return widgets_[index];
330 }
331 
332 /**
333  * Show/hide the tab bar of this widget
334  */
setTabBarShown(bool shown)335 void PsiTabWidget::setTabBarShown(bool shown) {
336 	if (shown && tabBar_->isHidden()) {
337 		tabBar_->show();
338 	} else if (!shown && !tabBar_->isHidden()) {
339 		tabBar_->hide();
340 	}
341 }
342 
343 /**
344  * Show/hide the menu and close buttons that appear next to the tab bar
345  */
setTabButtonsShown(bool shown)346 void PsiTabWidget::setTabButtonsShown(bool shown) {
347 	if (shown && downButton_->isHidden()) {
348 		downButton_->show();
349 		closeButton_->show();
350 	} else if (!shown && !downButton_->isHidden()) {
351 		downButton_->hide();
352 		closeButton_->hide();
353 	}
354 }
355 
356 /**
357  * Enable/disable dragging of tabs
358  */
setDragsEnabled(bool enabled)359 void PsiTabWidget::setDragsEnabled(bool enabled) {
360 	((PsiTabBar *)tabBar_)->setDragsEnabled(enabled);
361 }
362 
widgetMoved(int from,int to)363 void PsiTabWidget::widgetMoved(int from, int to)
364 {
365 	if (from > to) {
366 		stacked_->removeWidget(widgets_[from]);
367 		widgets_.insert(to, 1, widgets_[from]);
368 		widgets_.remove(from+1);
369 		stacked_->insertWidget(to,widgets_[to]);
370 	}
371 	else {
372 		stacked_->removeWidget(widgets_[from]);
373 		widgets_.insert(to+1, 1, widgets_[from]);
374 		widgets_.remove(from,1);
375 		stacked_->insertWidget(to,widgets_[to]);
376 	}
377 
378 	emit currentChanged(currentPage());
379 
380 };
381