pgadmin4/runtime/TabWindow.cpp
2017-07-17 10:58:17 +01:00

714 lines
22 KiB
C++

//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// TabWindow.cpp - Implementation of the custom tab widget
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
// App headers
#include "TabWindow.h"
#ifdef PGADMIN4_USE_WEBENGINE
#include <QWebEngineHistory>
#else
#include <QWebHistory>
#endif
DockTabWidget *DockTabWidget::mainTabWidget = NULL;
DockTabWidget::DockTabWidget(QWidget *parent) :
QTabWidget(parent)
{
floatingWidget = NULL;
floatingEnabled = false;
setParent(parent);
setTabsClosable(false);
setElideMode(Qt::ElideRight);
// set custom tab bar in tab widget to receive events for docking.
setTabBar(new DockTabBar(this));
setDocumentMode(true);
setAcceptDrops(true);
// Get the system colours we need
QPalette palette = QApplication::palette("QPushButton");
QColor activebg = palette.color(QPalette::Button);
QColor activefg = palette.color(QPalette::ButtonText);
QColor inactivebg = palette.color(QPalette::Dark);
QColor inactivefg = palette.color(QPalette::ButtonText);
QColor border = palette.color(QPalette::Mid);
setStyleSheet(
"QTabBar::tab { "
"background-color: " + inactivebg.name() + "; "
"color: " + inactivefg.name() + "; "
"border: 1px solid " + border.name() + "; "
"padding: 1px 0px; "
"margin-left: 0px; "
"margin-top: 1px; "
#ifndef __APPLE__
"width: 15em; "
"height: 1.5em; "
#endif
"} "
"QTabBar::tab:selected { "
"background-color: " + activebg.name() + "; "
"color: " + activefg.name() + "; "
"border-bottom-style: none; "
"} "
"QTabWidget::pane { "
"border: 0; "
"} "
"QTabWidget::tab-bar {"
"alignment: left; "
"}"
);
if (mainTabWidget == NULL)
mainTabWidget = this;
}
DockTabWidget::DockTabWidget(DockTabWidget *other, QWidget *parent) :
QTabWidget(parent)
{
setFloatingBaseWidget(other->floatingBaseWidget());
setFloatingEnabled(other->isFloatingEnabled());
resize(other->size());
// set custom tab bar in tab widget to receive events for docking.
setTabBar(new DockTabBar(this));
setDocumentMode(true);
setAcceptDrops(true);
// Get the system colours we need
QPalette palette = QApplication::palette("QPushButton");
QColor activebg = palette.color(QPalette::Button);
QColor activefg = palette.color(QPalette::ButtonText);
QColor inactivebg = palette.color(QPalette::Dark);
QColor inactivefg = palette.color(QPalette::ButtonText);
QColor border = palette.color(QPalette::Mid);
setStyleSheet(
"QTabBar::tab { "
"background-color: " + inactivebg.name() + "; "
"color: " + inactivefg.name() + "; "
"border: 1px solid " + border.name() + "; "
"padding: 1px 0px; "
"margin-left: 0px; "
"margin-top: 1px; "
#ifndef __APPLE__
"width: 15em; "
"height: 1.5em; "
#else
"font: 11pt; "
"width: 19em; "
"height: 1.5em; "
#endif
"} "
"QTabBar::tab:selected { "
"background-color: " + activebg.name() + "; "
"color: " + activefg.name() + "; "
"border-bottom-style: none; "
"} "
"QTabWidget::pane { "
"border: 0; "
"} "
"QTabWidget::tab-bar {"
"alignment: left; "
"}"
);
}
void DockTabWidget::setFloatingBaseWidget(QWidget *widget)
{
floatingWidget = widget;
if (floatingEnabled && parentWidget() == 0)
setParent(widget);
}
void DockTabWidget::setFloatingEnabled(bool x)
{
floatingEnabled = x;
if (parent() == 0)
{
if (x)
setWindowFlags(Qt::Tool);
else
setWindowFlags(Qt::Window);
}
}
// Slot: go back to page and enable/disable toolbutton
void DockTabWidget::dockGoBackPage()
{
WebViewWindow *webviewPtr = NULL;
QWidget *tab = this->widget(this->currentIndex());
if (tab != NULL)
{
QList<QWidget*> widgetList = tab->findChildren<QWidget*>();
foreach( QWidget* widgetPtr, widgetList )
{
if (widgetPtr != NULL)
{
webviewPtr = dynamic_cast<WebViewWindow*>(widgetPtr);
if (webviewPtr != NULL)
webviewPtr->back();
}
}
}
}
// Slot: go forward to page and enable/disable toolbutton
void DockTabWidget::dockGoForwardPage()
{
WebViewWindow *webviewPtr = NULL;
QWidget *tab = this->widget(this->currentIndex());
if (tab != NULL)
{
QList<QWidget*> widgetList = tab->findChildren<QWidget*>();
foreach( QWidget* widgetPtr, widgetList )
{
if (widgetPtr != NULL)
{
webviewPtr = dynamic_cast<WebViewWindow*>(widgetPtr);
if (webviewPtr != NULL)
webviewPtr->forward();
}
}
}
}
// Close the tab and remove the memory of the given index tab
void DockTabWidget::dockClosetabs()
{
int totalTabs = 0;
QToolButton *btn = NULL;
QWidget *tab = NULL;
DockTabWidget *l_tab_widget = NULL;
QObject *senderPtr = QObject::sender();
if (senderPtr != NULL)
{
btn = dynamic_cast<QToolButton*>(senderPtr);
if (btn != NULL)
{
l_tab_widget = dynamic_cast<DockTabWidget*>(btn->parent()->parent());
int current_tab_index = 0;
if (l_tab_widget != NULL)
{
totalTabs = l_tab_widget->count();
for (int loopCount = l_tab_widget->count();loopCount >= 0;loopCount--)
{
QWidget *l_tab = l_tab_widget->tabBar()->tabButton(loopCount, QTabBar::RightSide);
if (l_tab != NULL)
{
QToolButton *nextBtnPtr = dynamic_cast<QToolButton*>(l_tab);
if (nextBtnPtr != NULL && btn != NULL && nextBtnPtr == btn)
current_tab_index = loopCount;
}
}
QList<QWidget*> widgetList = l_tab_widget->tabBar()->findChildren<QWidget*>();
foreach(QWidget* widgetPtr, widgetList)
{
if (widgetPtr != NULL)
{
QToolButton *toolBtnPtr = dynamic_cast<QToolButton*>(widgetPtr);
if (toolBtnPtr != NULL && toolBtnPtr == btn)
{
tab = l_tab_widget->widget(current_tab_index);
break;
}
}
}
}
if (tab != NULL)
tab->deleteLater();
// If user close the last tab then close the parent tab widget also.
if (totalTabs == 1 && l_tab_widget != NULL)
l_tab_widget->deleteLater();
}
}
if (tab != NULL)
{
WebViewWindow *webviewPtr = NULL;
QList<QWidget*> widgetList = tab->findChildren<QWidget*>();
foreach (QWidget* widgetPtr, widgetList)
{
if (widgetPtr != NULL)
{
webviewPtr = dynamic_cast<WebViewWindow*>(widgetPtr);
if (webviewPtr != NULL)
{
/* Trigger the action for tab window close so unload event will be called and
* resources will be freed properly.
* Trigger 'RequestClose' action from Qt5 onwards. Here we have triggerred the action
* 'ToggleVideoFullscreen + 1' because we do not know from which webkit
* version 'RequestClose' action was added so increment with previous enum value so that
* it will be backward webkit version compatible.
*/
#if QT_VERSION >= 0x050000
#ifndef PGADMIN4_USE_WEBENGINE
webviewPtr->page()->triggerAction(static_cast<QWebPage::WebAction>(QWebPage::ToggleVideoFullscreen + 1));
#endif
#endif
}
}
}
}
// Check if main pgAdmin4 application has only one tab then close tab bar.
// Here - check for count 2 because tab will be deleted later.
DockTabWidget *mainTab = DockTabWidget::getMainTabWidget();
if (mainTab != NULL && l_tab_widget != NULL && l_tab_widget == mainTab && mainTab->count() == 2)
mainTab->tabBar()->setVisible(false);
}
// This function is used to set back/forward/close buttons on new tabbar.
void DockTabWidget::setButtonsNewTabbar(int index)
{
QWidget *m_widget = new QWidget();
QToolButton *m_toolBtnBack = new QToolButton(m_widget);
m_toolBtnBack->setFixedHeight(PGA_BTN_SIZE);
m_toolBtnBack->setFixedWidth(PGA_BTN_SIZE);
m_toolBtnBack->setIcon(QIcon(":/back.png"));
m_toolBtnBack->setToolTip(tr("Go back"));
QToolButton *m_toolBtnForward = new QToolButton(m_widget);
m_toolBtnForward->setFixedHeight(PGA_BTN_SIZE);
m_toolBtnForward->setFixedWidth(PGA_BTN_SIZE);
m_toolBtnForward->setIcon(QIcon(":/forward.png"));
m_toolBtnForward->setToolTip(tr("Go forward"));
QToolButton *m_btnClose = new QToolButton(m_widget);
m_btnClose->setFixedHeight(PGA_BTN_SIZE);
m_btnClose->setFixedWidth(PGA_BTN_SIZE);
m_btnClose->setIcon(QIcon(":/close.png"));
m_btnClose->setToolTip(tr("Close tab"));
QHBoxLayout *m_horizontalLayout = new QHBoxLayout(m_widget);
m_horizontalLayout->setContentsMargins(0,1,0,0);
m_horizontalLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
m_horizontalLayout->setSpacing(1);
m_horizontalLayout->addWidget(m_toolBtnBack);
m_horizontalLayout->addWidget(m_toolBtnForward);
// Register the slot on toolbutton to show the previous history of web
connect(m_toolBtnBack, SIGNAL(clicked()), this, SLOT(dockGoBackPage()));
// Register the slot on toolbutton to show the next history of web
connect(m_toolBtnForward, SIGNAL(clicked()), this, SLOT(dockGoForwardPage()));
// Register the slot on close button , added manually
connect(m_btnClose, SIGNAL(clicked()), SLOT(dockClosetabs()));
// Register the slot on tab index change
connect(this, SIGNAL(currentChanged(int )), this,SLOT(tabIndexChanged(int )));
// Set the back and forward button on tab
this->tabBar()->setTabButton(index, QTabBar::LeftSide, m_widget);
this->tabBar()->setTabButton(index, QTabBar::RightSide, m_btnClose);
// find the webview and hide/show button depending on flag set with web view.
QWidget *tab = this->widget(index);
if (tab != NULL)
{
QList<QWidget*> widgetList = tab->findChildren<QWidget*>();
foreach( QWidget* widgetPtr, widgetList )
{
if (widgetPtr != NULL)
{
WebViewWindow *webViewPtr = dynamic_cast<WebViewWindow*>(widgetPtr);
if (webViewPtr != NULL)
{
// If user open any file in query tool then "Query -" name will not appear
// but it is still query tool so hide the tool button.
if (!webViewPtr->getBackForwardButtonHidden())
{
m_toolBtnBack->show();
m_toolBtnForward->show();
}
else
{
m_toolBtnBack->hide();
m_toolBtnForward->hide();
}
break;
}
}
}
}
}
// This function is used to move to old tab widget to new tab widget.
void DockTabWidget::moveTab(DockTabWidget *source, int sourceIndex, DockTabWidget *dest, int destIndex)
{
if (source == dest && sourceIndex < destIndex)
destIndex--;
QWidget *widget = source->widget(sourceIndex);
QString text = source->tabText(sourceIndex);
source->removeTab(sourceIndex);
dest->insertTab(destIndex, widget, text);
dest->setCurrentIndex(destIndex);
}
// This function is used to decode actual drop event on tab widget.
void DockTabWidget::decodeTabDropEvent(QDropEvent *event, DockTabWidget **p_tabWidget, int *p_index)
{
DockTabBar *tabBar = qobject_cast<DockTabBar *>(event->source());
if (!tabBar)
{
*p_tabWidget = NULL;
*p_index = 0;
return;
}
QByteArray data = event->mimeData()->data(MIMETYPE_TABINDEX);
QDataStream stream(&data, QIODevice::ReadOnly);
int index;
stream >> index;
*p_tabWidget = tabBar->tabWidget();
*p_index = index;
}
// This function is used to check event is actually drop event or not.
bool DockTabWidget::eventIsTabDrag(QDragEnterEvent *event)
{
return event->mimeData()->hasFormat(MIMETYPE_TABINDEX) && qobject_cast<DockTabBar *>(event->source());
}
// This function is used to delete tab widget when there is no tab inside.
void DockTabWidget::deleteIfEmpty()
{
if (count() == 0)
{
emit willBeAutomaticallyDeleted(this);
deleteLater();
}
}
// This is function is used to create another tab widget from parent window.
DockTabWidget *DockTabWidget::createAnotherTabWidget(QWidget *parent)
{
DockTabWidget *tab_widget = new DockTabWidget(this, parent);
tab_widget->tabBar()->setVisible(true);
return tab_widget;
}
// Check wether tab is insertable or not.
bool DockTabWidget::isInsertable(QWidget *widget)
{
Q_UNUSED(widget)
return true;
}
// Hide the close button of given index displayed on right side of tab
void DockTabWidget::enableDisableToolButton(const int &index)
{
QToolButton *toolBtnPtr = NULL;
WebViewWindow *tmpwebviewPtr = NULL;
WebViewWindow *webviewPtr = NULL;
// Enable/disable the toolbutton based on the history
QWidget *tab1 = this->widget(index);
if (tab1 != NULL)
{
QList<QWidget*> widgetList = tab1->findChildren<QWidget*>();
foreach( QWidget* widgetPtr, widgetList )
{
if (widgetPtr != NULL)
tmpwebviewPtr = dynamic_cast<WebViewWindow*>(widgetPtr);
if (tmpwebviewPtr != NULL)
webviewPtr = tmpwebviewPtr;
}
}
QWidget *tab = tabBar()->tabButton(index, QTabBar::LeftSide);
if (tab != NULL)
{
QList<QWidget*> widgetList = tab->findChildren<QWidget*>();
foreach( QWidget* widgetPtr, widgetList )
{
if (widgetPtr != NULL)
{
toolBtnPtr = dynamic_cast<QToolButton*>(widgetPtr);
if (webviewPtr != NULL && toolBtnPtr != NULL)
{
if (!QString::compare(toolBtnPtr->toolTip(), tr("Go back"), Qt::CaseInsensitive))
{
if (webviewPtr->page()->history()->canGoBack())
toolBtnPtr->setDisabled(false);
else
toolBtnPtr->setDisabled(true);
}
if (!QString::compare(toolBtnPtr->toolTip(), tr("Go forward"), Qt::CaseInsensitive))
{
if (webviewPtr->page()->history()->canGoForward())
toolBtnPtr->setDisabled(false);
else
toolBtnPtr->setDisabled(true);
}
}
}
}
}
}
// Slot: When the tab index change, hide/show the toolbutton displayed on tab
void DockTabWidget::tabIndexChanged(int index)
{
int tabCount = 0;
WebViewWindow *webViewPtr = NULL;
for (tabCount = 0; tabCount < this->count(); tabCount++)
{
// if main pgAdmin4 application tab then do nothing.
if (!QString::compare(this->tabText(tabCount), tr("pgAdmin 4"), Qt::CaseInsensitive))
continue;
QWidget *tab = this->widget(tabCount);
if (tab != NULL)
{
QList<QWidget*> widgetList = tab->findChildren<QWidget*>();
foreach( QWidget* widgetPtr, widgetList )
{
if (widgetPtr != NULL)
{
webViewPtr = dynamic_cast<WebViewWindow*>(widgetPtr);
if (webViewPtr != NULL)
break;
}
}
}
if (tabCount != index)
this->showHideToolButton(tabCount, 0);
else
{
if (!webViewPtr->getBackForwardButtonHidden())
this->showHideToolButton(tabCount, 1);
else
this->showHideToolButton(tabCount, 0);
}
}
// paint the tab text again as index of the tab widget changed.
this->tabBar()->update();
}
// Show and Hide the toolbutton once the tab is deselected depending on the option
// option 0: Hide the toolButton
// option 1: Show the toolButton
void DockTabWidget::showHideToolButton(const int &index, const int &option)
{
QToolButton *toolBtnPtr = NULL;
QWidget *tab = tabBar()->tabButton(index, QTabBar::LeftSide);
if (tab != NULL)
{
QList<QWidget*> widgetList = tab->findChildren<QWidget*>();
foreach( QWidget* widgetPtr, widgetList )
{
if (widgetPtr != NULL)
{
toolBtnPtr = dynamic_cast<QToolButton*>(widgetPtr);
if (toolBtnPtr != NULL)
{
if (!option)
toolBtnPtr->hide();
else
toolBtnPtr->show();
}
}
}
}
}
// Set the tab tool tip text
void DockTabWidget::setTabToolTipText(const int &index, const QString &toolTipString)
{
tabBar()->setTabToolTip(index, toolTipString);
}
// Implementation of custom tab bar for docking window.
DockTabBar::DockTabBar(DockTabWidget *tabWidget, QWidget *parent) :
QTabBar(parent),
tab_widget(tabWidget)
{
isStartingDrag = false;
setAcceptDrops(true);
}
// Insert new tab at specified index.
int DockTabBar::insertionIndexAt(const QPoint &pos)
{
int index = count();
for (int i = 0; i < count(); ++i)
{
QRect rect = tabRect(i);
QRect rect1(rect.x(), rect.y(), rect.width() / 2, rect.height());
QRect rect2(rect.x() + rect1.width(), rect.y(), rect.width() - rect1.width(), rect.height());
if (rect1.contains(pos))
{
index = i;
break;
}
if (rect2.contains(pos))
{
index = i + 1;
break;
}
}
return index;
}
// Mouse press event handler for tab drag.
void DockTabBar::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
dragStartPos = event->pos();
isStartingDrag = true;
}
QTabBar::mousePressEvent(event);
}
// Mouse move event handler for tab drag.
void DockTabBar::mouseMoveEvent(QMouseEvent *event)
{
if (!isStartingDrag)
return;
if ((!event->buttons()) && Qt::LeftButton)
return;
if ((event->pos() - dragStartPos).manhattanLength() < QApplication::startDragDistance())
return;
int index = tabAt(event->pos());
if (index < 0)
return;
// Don't allow to drag the pgAdmin4 main tab.
if (!QString::compare(tab_widget->tabText(index), tr("pgAdmin 4"), Qt::CaseInsensitive))
{
return;
}
// create data
QMimeData *mimeData = new QMimeData;
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << index;
mimeData->setData(MIMETYPE_TABINDEX, data);
// create pixmap
QRect rect = tabRect(index);
QPixmap pixmap(rect.size());
render(&pixmap, QPoint(), QRegion(rect));
// exec drag
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(pixmap);
QPoint offset = dragStartPos - rect.topLeft();
drag->setHotSpot(offset);
Qt::DropAction dropAction = drag->exec(Qt::MoveAction | Qt::IgnoreAction);
if (dropAction != Qt::MoveAction)
{
DockTabWidget *newTabWidget = tab_widget->createAnotherTabWidget();
if (!newTabWidget->isInsertable(tab_widget, index))
{
newTabWidget->deleteLater();
return;
}
DockTabWidget::moveTab(tab_widget, index, newTabWidget, 0);
newTabWidget->setButtonsNewTabbar(0);
newTabWidget->enableDisableToolButton(0);
QRect newGeometry = newTabWidget->geometry();
newGeometry.moveTopLeft(QCursor::pos() - offset);
newTabWidget->setGeometry(newGeometry);
newTabWidget->show();
// Check if main pgAdmin4 application has only one tab then close tab bar.
// Here - check for count 2 because tab will be deleted later.
DockTabWidget *mainTab = DockTabWidget::getMainTabWidget();
if (mainTab != NULL && tab_widget != NULL && tab_widget == mainTab && mainTab->count() == 1)
mainTab->tabBar()->setVisible(false);
}
tab_widget->deleteIfEmpty();
isStartingDrag = false;
}
// Actual tab drag started.
void DockTabBar::dragEnterEvent(QDragEnterEvent *event)
{
if (DockTabWidget::eventIsTabDrag(event))
event->acceptProposedAction();
}
// Drag event leave the actual area.
void DockTabBar::dragLeaveEvent(QDragLeaveEvent * event)
{
Q_UNUSED(event)
}
// Drop event handler for tabbar.
void DockTabBar::dropEvent(QDropEvent *event)
{
DockTabWidget *oldTabWidget = NULL;
int oldIndex;
DockTabWidget::decodeTabDropEvent(event, &oldTabWidget, &oldIndex);
if (oldTabWidget && tab_widget && tab_widget->isInsertable(oldTabWidget, oldIndex))
{
int newIndex = insertionIndexAt(event->pos());
DockTabWidget::moveTab(oldTabWidget, oldIndex, tab_widget, newIndex);
// create new back/forward/close buttons and register its events.
tab_widget->setButtonsNewTabbar(newIndex);
tab_widget->enableDisableToolButton(newIndex);
// Check if main pgAdmin4 application has only one tab then close tab bar.
// Here - check for count 2 because tab will be deleted later.
DockTabWidget *mainTab = DockTabWidget::getMainTabWidget();
if (mainTab != NULL && oldTabWidget != NULL && oldTabWidget == mainTab && mainTab->count() == 1)
mainTab->tabBar()->setVisible(false);
event->acceptProposedAction();
}
}