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