/*************************************************************************** * Copyright (C) 2006 by Vladimir Kuznetsov * * vovanec@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include #include #include #include #include #include "mainwindow.h" #include "termwidgetholder.h" #include "tabbar.h" #include "tabwidget.h" #include "config.h" #include "properties.h" #include "qterminalapp.h" #define TAB_INDEX_PROPERTY "tab_index" #define TAB_CUSTOM_NAME_PROPERTY "custom_name" TabWidget::TabWidget(QWidget* parent) : QTabWidget(parent), tabNumerator(0), mTabBar(new TabBar(this)) { // Insert our own tab bar which overrides tab width and eliding setTabBar(mTabBar); setFocusPolicy(Qt::NoFocus); /* On Mac OS X this will look similar to * the tabs in Safari or Leopard's Terminal.app . * I love this! */ setDocumentMode(true); tabBar()->setUsesScrollButtons(true); setMovable(true); setUsesScrollButtons(true); tabBar()->installEventFilter(this); connect(this, &TabWidget::tabCloseRequested, this, &TabWidget::removeTab); connect(tabBar(), &QTabBar::tabMoved, this, &TabWidget::updateTabIndices); connect(this, &TabWidget::tabRenameRequested, this, &TabWidget::renameSession); connect(this, &TabWidget::tabTitleColorChangeRequested, this, &TabWidget::setTitleColor); } TermWidgetHolder * TabWidget::terminalHolder() { return reinterpret_cast(widget(currentIndex())); } int TabWidget::addNewTab(TerminalConfig config) { tabNumerator++; QString label = QString(tr("Shell No. %1")).arg(tabNumerator); TermWidgetHolder *ch = terminalHolder(); if (ch) config.provideCurrentDirectory(ch->currentTerminal()->impl()->workingDirectory()); TermWidgetHolder *console = new TermWidgetHolder(config, this); console->setWindowTitle(label); connect(console, &TermWidgetHolder::finished, this, &TabWidget::removeFinished); connect(console, &TermWidgetHolder::lastTerminalClosed, this, &TabWidget::removeFinished); connect(console, &TermWidgetHolder::termTitleChanged, this, &TabWidget::onTermTitleChanged); connect(this, &QTabWidget::currentChanged, this, &TabWidget::currentTitleChanged); int index = addTab(console, label); console->setProperty(TAB_CUSTOM_NAME_PROPERTY, false); updateTabIndices(); setCurrentIndex(index); console->setInitialFocus(); showHideTabBar(); return index; } void TabWidget::switchLeftSubterminal() { terminalHolder()->directionalNavigation(NavigationDirection::Left); } void TabWidget::switchRightSubterminal() { terminalHolder()->directionalNavigation(NavigationDirection::Right); } void TabWidget::switchTopSubterminal() { terminalHolder()->directionalNavigation(NavigationDirection::Top); } void TabWidget::switchBottomSubterminal() { terminalHolder()->directionalNavigation(NavigationDirection::Bottom); } void TabWidget::splitHorizontally() { terminalHolder()->splitHorizontal(terminalHolder()->currentTerminal()); findParent(this)->updateDisabledActions(); } void TabWidget::splitVertically() { terminalHolder()->splitVertical(terminalHolder()->currentTerminal()); findParent(this)->updateDisabledActions(); } void TabWidget::splitCollapse() { terminalHolder()->splitCollapse(terminalHolder()->currentTerminal()); findParent(this)->updateDisabledActions(); } void TabWidget::copySelection() { terminalHolder()->currentTerminal()->impl()->copyClipboard(); } void TabWidget::pasteClipboard() { terminalHolder()->currentTerminal()->impl()->pasteClipboard(); } void TabWidget::pasteSelection() { terminalHolder()->currentTerminal()->impl()->pasteSelection(); } void TabWidget::zoomIn() { terminalHolder()->currentTerminal()->impl()->zoomIn(); } void TabWidget::zoomOut() { terminalHolder()->currentTerminal()->impl()->zoomOut(); } void TabWidget::zoomReset() { terminalHolder()->currentTerminal()->impl()->zoomReset(); } void TabWidget::updateTabIndices() { for(int i = 0; i < count(); i++) widget(i)->setProperty(TAB_INDEX_PROPERTY, i); } void TabWidget::onTermTitleChanged(QString title, QString icon) { TermWidgetHolder * console = qobject_cast(sender()); const bool custom_name = console->property(TAB_CUSTOM_NAME_PROPERTY).toBool(); if (!custom_name) { const int index = console->property(TAB_INDEX_PROPERTY).toInt(); setTabIcon(index, QIcon::fromTheme(icon)); setTabText(index, title); if (currentIndex() == index) emit currentTitleChanged(index); } } void TabWidget::renameSession(int index) { bool ok = false; QString text = QInputDialog::getText(this, tr("Tab name"), tr("New tab name:"), QLineEdit::Normal, QString(), &ok); if(ok && !text.isEmpty()) { setTabIcon(index, QIcon{}); setTabText(index, text); widget(index)->setProperty(TAB_CUSTOM_NAME_PROPERTY, true); if (currentIndex() == index) emit currentTitleChanged(index); } } void TabWidget::renameCurrentSession() { renameSession(currentIndex()); } void TabWidget::setTitleColor(int index) { QColor current = tabBar()->tabTextColor(index); QColor color = QColorDialog::getColor(current, this, tr("Select new tab title color")); if (color.isValid()) tabBar()->setTabTextColor(index, color); } void TabWidget::renameTabsAfterRemove() { // it breaks custom names - it replaces original/custom title with shell no # #if 0 for(int i = 0; i < count(); i++) { setTabText(i, QString(tr("Shell No. %1")).arg(i+1)); } #endif } void TabWidget::contextMenuEvent(QContextMenuEvent *event) { QMenu menu(this); QMap< QString, QAction * > actions = findParent(this)->leaseActions(); QAction *close = menu.addAction(QIcon::fromTheme("document-close"), tr("Close session")); QAction *rename = menu.addAction(actions[RENAME_SESSION]->text()); QAction *changeColor = menu.addAction(QIcon::fromTheme("color-management"), tr("Change title color")); rename->setShortcut(actions[RENAME_SESSION]->shortcut()); rename->blockSignals(true); int tabIndex = tabBar()->tabAt(tabBar()->mapFrom(this,event->pos())); QAction *action = menu.exec(event->globalPos()); if (action == close) { emit tabCloseRequested(tabIndex); } else if (action == rename) { emit tabRenameRequested(tabIndex); } else if (action == changeColor) { emit tabTitleColorChangeRequested(tabIndex); } } bool TabWidget::eventFilter(QObject *obj, QEvent *event) { QMouseEvent *e = reinterpret_cast(event); if (e->button() == Qt::MidButton) { if (event->type() == QEvent::MouseButtonRelease) { // close the tab on middle clicking int index = tabBar()->tabAt(e->pos()); if (index > -1){ removeTab(index); return true; } } } else if (event->type() == QEvent::MouseButtonDblClick) { // if user doubleclicks on tab button - rename it. If user // clicks on free space - open new tab int index = tabBar()->tabAt(e->pos()); if (index == -1) { TerminalConfig defaultConfig; addNewTab(defaultConfig); } else renameSession(index); return true; } return QTabWidget::eventFilter(obj, event); } void TabWidget::removeFinished() { QObject* term = sender(); QVariant prop = term->property(TAB_INDEX_PROPERTY); if(prop.isValid() && prop.canConvert(QVariant::Int)) { int index = prop.toInt(); removeTab(index); // if (count() == 0) // emit closeTabNotification(); } } void TabWidget::removeTab(int index) { if (count() > 1) { setUpdatesEnabled(false); QWidget * w = widget(index); QTabWidget::removeTab(index); w->deleteLater(); updateTabIndices(); int current = currentIndex(); if (current >= 0 ) { qobject_cast(widget(current))->setInitialFocus(); } // do not decrease it as renaming is disabled in renameTabsAfterRemove // tabNumerator--; setUpdatesEnabled(true); } else { emit closeTabNotification(true); } renameTabsAfterRemove(); showHideTabBar(); } void TabWidget::removeCurrentTab() { // question disabled due user requests. Yes I agree it was anoying. // if (QMessageBox::question(this, // tr("Close current session"), // tr("Are you sure you want to close current sesstion?"), // QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) // { if (count() > 1) { removeTab(currentIndex()); } else { emit closeTabNotification(false); } } int TabWidget::switchToRight() { int next_pos = currentIndex() + 1; if (next_pos < count()) setCurrentIndex(next_pos); else setCurrentIndex(0); findParent(this)->updateDisabledActions(); return currentIndex(); } int TabWidget::switchToLeft() { int previous_pos = currentIndex() - 1; if (previous_pos < 0) setCurrentIndex(count() - 1); else setCurrentIndex(previous_pos); findParent(this)->updateDisabledActions(); return currentIndex(); } void TabWidget::move(Direction dir) { if(count() > 1) { int index = currentIndex(); QWidget* child = widget(index); QString label = tabText(index); QString toolTip = tabToolTip(index); QIcon icon = tabIcon(index); int newIndex = 0; if(dir == Left) if(index == 0) newIndex = count() -1; else newIndex = index - 1; else if(index == count() - 1) newIndex = 0; else newIndex = index + 1; setUpdatesEnabled(false); QTabWidget::removeTab(index); newIndex = insertTab(newIndex, child, label); setTabToolTip(newIndex, toolTip); setTabIcon(newIndex, icon); setUpdatesEnabled(true); setCurrentIndex(newIndex); child->setFocus(); updateTabIndices(); } } void TabWidget::moveLeft() { move(Left); } void TabWidget::moveRight() { move(Right); } void TabWidget::changeScrollPosition(QAction *triggered) { QActionGroup *scrollPosition = static_cast(sender()); if(!scrollPosition) qFatal("scrollPosition is NULL"); Properties::Instance()->scrollBarPos = scrollPosition->actions().indexOf(triggered); Properties::Instance()->saveSettings(); propertiesChanged(); } void TabWidget::changeTabPosition(QAction *triggered) { QActionGroup *tabPosition = static_cast(sender()); if(!tabPosition) qFatal("tabPosition is NULL"); Properties *prop = Properties::Instance(); /* order is dictated from mainwindow.cpp */ QTabWidget::TabPosition position = (QTabWidget::TabPosition)tabPosition->actions().indexOf(triggered); setTabPosition(position); prop->tabsPos = position; prop->saveSettings(); } void TabWidget::changeKeyboardCursorShape(QAction *triggered) { QActionGroup *keyboardCursorShape = static_cast(sender()); if(!keyboardCursorShape) qFatal("keyboardCursorShape is NULL"); Properties::Instance()->keyboardCursorShape = keyboardCursorShape->actions().indexOf(triggered); Properties::Instance()->saveSettings(); propertiesChanged(); } void TabWidget::propertiesChanged() { for (int i = 0; i < count(); ++i) { TermWidgetHolder *console = static_cast(widget(i)); console->propertiesChanged(); } showHideTabBar(); setTabsClosable(Properties::Instance()->showCloseTabButton); // Update the tab widths mTabBar->setLimitWidth(Properties::Instance()->limitTabWidth); mTabBar->setLimitWidthValue(Properties::Instance()->limitTabWidthValue); mTabBar->updateWidth(); } void TabWidget::clearActiveTerminal() { reinterpret_cast(widget(currentIndex()))->clearActiveTerminal(); } void TabWidget::saveSession() { int ix = currentIndex(); reinterpret_cast(widget(ix))->saveSession(tabText(ix)); } void TabWidget::loadSession() { reinterpret_cast(widget(currentIndex()))->loadSession(); } void TabWidget::preset2Horizontal() { TerminalConfig defaultConfig; int ix = TabWidget::addNewTab(defaultConfig); TermWidgetHolder* term = reinterpret_cast(widget(ix)); term->splitHorizontal(term->currentTerminal()); // switch to the 1st terminal term->directionalNavigation(NavigationDirection::Left); } void TabWidget::preset2Vertical() { TerminalConfig defaultConfig; int ix = TabWidget::addNewTab(defaultConfig); TermWidgetHolder* term = reinterpret_cast(widget(ix)); term->splitVertical(term->currentTerminal()); // switch to the 1st terminal term->directionalNavigation(NavigationDirection::Left); } void TabWidget::preset4Terminals() { TerminalConfig defaultConfig; int ix = TabWidget::addNewTab(defaultConfig); TermWidgetHolder* term = reinterpret_cast(widget(ix)); term->splitVertical(term->currentTerminal()); term->splitHorizontal(term->currentTerminal()); term->directionalNavigation(NavigationDirection::Left); term->splitHorizontal(term->currentTerminal()); // switch to the 1st terminal term->directionalNavigation(NavigationDirection::Top); } void TabWidget::showHideTabBar() { if (Properties::Instance()->tabBarless) tabBar()->setVisible(false); else tabBar()->setVisible(!Properties::Instance()->hideTabBarWithOneTab || count() > 1); }