Commit e921deff authored by Tomaz  Canabrava's avatar Tomaz Canabrava
Browse files

Start the work to move to QTabBar

parent b579ee5c
......@@ -82,7 +82,6 @@ target_sources(
katerunninginstanceinfo.cpp
katesavemodifieddialog.cpp
katetabbar.cpp
katetabbutton.cpp
kateviewmanager.cpp
kateviewspace.cpp
katewaiter.cpp
......
......@@ -19,7 +19,6 @@
*/
#include "katetabbar.h"
#include "katetabbutton.h"
#include <math.h> // ceil
......@@ -30,129 +29,33 @@
#include <QStyleOptionTab>
#include <QWheelEvent>
class KateTabBarPrivate
#include <KTextEditor/Document>
struct KateTabButtonData {
QUrl url;
KTextEditor::Document *doc = nullptr;
};
Q_DECLARE_METATYPE(KateTabButtonData);
class KateTabBar::KateTabBarPrivate
{
public:
// pointer to tabbar
KateTabBar *q = nullptr;
// minimum and maximum tab width
int minimumTabWidth = 0;
int maximumTabWidth = 0;
// current tab width: when closing tabs with the mouse, we keep
// the tab width fixed until the mouse leaves the tab bar. This
// way the user can keep clicking the close button without moving
// the ouse.
qreal currentTabWidth = 0;
bool keepTabWidth = false;
bool isActive = false;
QVector<KateTabButton *> tabButtons;
QHash<int, KateTabButton *> idToTab;
KateTabButton *activeButton = nullptr;
int nextID = 0;
public: // functions
/**
* Set tab geometry. The tabs are animated only if @p animate is @e true.
*/
void updateButtonPositions(bool animate = false)
{
// if there are no tabs there is nothing to do
if (tabButtons.count() == 0) {
return;
}
// check whether an animation is still running, in that case,
// continue animations to avoid jumping tabs
const int maxi = tabButtons.size();
if (!animate) {
for (int i = 0; i < maxi; ++i) {
if (tabButtons.value(i)->geometryAnimationRunning()) {
animate = true;
break;
}
}
}
const int barWidth = q->width();
const int maxCount = q->maxTabCount();
// how many tabs do we show?
const int visibleTabCount = qMin(q->count(), maxCount);
// new tab width of each tab
qreal tabWidth;
const bool keepWidth = keepTabWidth && ceil(currentTabWidth) * visibleTabCount < barWidth;
if (keepWidth) {
// only keep tab width if the tabs still fit
tabWidth = currentTabWidth;
} else {
tabWidth = qMin(static_cast<qreal>(barWidth) / visibleTabCount, static_cast<qreal>(maximumTabWidth));
}
// if the last tab was closed through the close button, make sure the
// close button of the new tab is again under the mouse
if (keepWidth) {
const int xPos = q->mapFromGlobal(QCursor::pos()).x();
if (tabWidth * visibleTabCount < xPos) {
tabWidth = qMin(static_cast<qreal>(tabWidth * (visibleTabCount + 1.0)) / visibleTabCount, static_cast<qreal>(maximumTabWidth));
}
}
// now save the current tab width for future adaptation
currentTabWidth = tabWidth;
// now set the sizes
const int w = ceil(tabWidth);
const int h = q->height();
for (int i = 0; i < maxi; ++i) {
KateTabButton *tabButton = tabButtons.value(i);
if (i >= maxCount) {
tabButton->hide();
} else {
QRect endGeometry(i * tabWidth, 0, w, h);
if (i > 0) {
// make sure the tab button starts exactly next to the previous tab (avoid rounding errors)
endGeometry.setLeft((i - 1) * tabWidth + w);
}
if (animate) {
const QRect startGeometry = tabButton->isVisible() ? tabButton->geometry() : QRect(i * tabWidth, 0, 0, h);
tabButton->setAnimatedGeometry(startGeometry, endGeometry);
} else {
// two times endGeometry. Takes care of stopping a running animation
tabButton->setAnimatedGeometry(endGeometry, endGeometry);
}
tabButton->show();
}
}
}
};
/**
* Creates a new tab bar with the given \a parent.
*/
KateTabBar::KateTabBar(QWidget *parent)
: QWidget(parent)
: QTabBar(parent)
, d(new KateTabBarPrivate())
{
d->q = this;
d->minimumTabWidth = 150;
d->maximumTabWidth = 350;
d->currentTabWidth = 350;
d->keepTabWidth = false;
d->isActive = false;
d->activeButton = nullptr;
d->nextID = 0;
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
setAcceptDrops(true);
}
......@@ -167,10 +70,11 @@ KateTabBar::~KateTabBar()
void KateTabBar::setActive(bool active)
{
if (active != d->isActive) {
d->isActive = active;
update();
if (active == d->isActive) {
return;
}
d->isActive = active;
update();
}
bool KateTabBar::isActive() const
......@@ -178,277 +82,43 @@ bool KateTabBar::isActive() const
return d->isActive;
}
int KateTabBar::addTab(const QString &text)
{
return insertTab(d->tabButtons.size(), text);
}
int KateTabBar::insertTab(int position, const QString &text)
{
Q_ASSERT(position <= d->tabButtons.size());
// -1 is append
if (position < 0) {
position = d->tabButtons.size();
}
KateTabButton *tabButton = new KateTabButton(text, this);
d->tabButtons.insert(position, tabButton);
d->idToTab[d->nextID] = tabButton;
connect(tabButton, &KateTabButton::activated, this, &KateTabBar::tabButtonActivated);
connect(tabButton, &KateTabButton::closeRequest, this, &KateTabBar::tabButtonCloseRequest);
// abort potential keeping of width
d->keepTabWidth = false;
d->updateButtonPositions(true);
return d->nextID++;
}
int KateTabBar::currentTab() const
{
return d->idToTab.key(d->activeButton, -1);
}
void KateTabBar::setCurrentTab(int id)
{
Q_ASSERT(d->idToTab.contains(id));
KateTabButton *tabButton = d->idToTab.value(id, nullptr);
if (d->activeButton == tabButton) {
return;
}
if (d->activeButton) {
d->activeButton->setChecked(false);
}
d->activeButton = tabButton;
if (d->activeButton) {
d->activeButton->setChecked(true);
}
}
int KateTabBar::prevTab() const
{
const int curId = currentTab();
if (curId >= 0) {
KateTabButton *tabButton = d->idToTab.value(curId, nullptr);
const int index = d->tabButtons.indexOf(tabButton);
Q_ASSERT(index >= 0);
if (index > 0) {
return d->idToTab.key(d->tabButtons[index - 1], -1);
} else if (count() > 1) {
// cycle through tabbar
return d->idToTab.key(d->tabButtons.last(), -1);
}
}
return -1;
return currentIndex() == 0 ? count() - 1 // first index, wrap to last
: currentIndex() - 1;
}
int KateTabBar::nextTab() const
{
const int curId = currentTab();
if (curId >= 0) {
KateTabButton *tabButton = d->idToTab.value(curId, nullptr);
const int index = d->tabButtons.indexOf(tabButton);
Q_ASSERT(index >= 0);
if (index < d->tabButtons.size() - 1) {
return d->idToTab.key(d->tabButtons[index + 1], -1);
} else if (count() > 1) {
// cycle through tabbar
return d->idToTab.key(d->tabButtons.first(), -1);
}
}
return -1;
}
int KateTabBar::removeTab(int id)
{
Q_ASSERT(d->idToTab.contains(id));
KateTabButton *tabButton = d->idToTab.value(id, nullptr);
if (tabButton == d->activeButton) {
d->activeButton = Q_NULLPTR;
}
const int position = d->tabButtons.indexOf(tabButton);
if (position != -1) {
d->idToTab.remove(id);
d->tabButtons.removeAt(position);
}
if (tabButton) {
// delete the button with deleteLater() because the button itself might
// have send a close-request. So the app-execution is still in the
// button, a delete tabButton; would lead to a crash.
tabButton->hide();
tabButton->deleteLater();
}
d->updateButtonPositions(true);
return position;
return currentIndex() == count() - 1 ? 0 // last index, wrap to first
: currentIndex() + 1;
}
bool KateTabBar::containsTab(int id) const
bool KateTabBar::containsTab(int index) const
{
return d->idToTab.contains(id);
return index >= 0 && index < count();
}
void KateTabBar::setTabText(int id, const QString &text)
QVariant KateTabBar::ensureValidTabData(int idx)
{
Q_ASSERT(d->idToTab.contains(id));
KateTabButton *tabButton = d->idToTab.value(id, nullptr);
if (tabButton) {
tabButton->setText(text);
if (!tabData(idx).isValid()) {
setTabData(idx, QVariant::fromValue(KateTabButtonData{}));
}
return tabData(idx);
}
QString KateTabBar::tabText(int id) const
void KateTabBar::setTabUrl(int idx, const QUrl &url)
{
Q_ASSERT(d->idToTab.contains(id));
KateTabButton *tabButton = d->idToTab.value(id, nullptr);
if (tabButton) {
return tabButton->text();
}
return QString();
}
void KateTabBar::setTabToolTip(int id, const QString &tip)
{
Q_ASSERT(d->idToTab.contains(id));
KateTabButton *tabButton = d->idToTab.value(id, nullptr);
if (tabButton) {
tabButton->setToolTip(tip);
}
QVariant data = ensureValidTabData(idx);
KateTabButtonData buttonData = data.value<KateTabButtonData>();
buttonData.url = url;
setTabData(idx, QVariant::fromValue(buttonData));
}
QString KateTabBar::tabToolTip(int id) const
QUrl KateTabBar::tabUrl(int idx)
{
Q_ASSERT(d->idToTab.contains(id));
KateTabButton *tabButton = d->idToTab.value(id, nullptr);
if (tabButton) {
return tabButton->toolTip();
}
return QString();
}
void KateTabBar::setTabUrl(int id, const QUrl &url)
{
Q_ASSERT(d->idToTab.contains(id));
KateTabButton *tabButton = d->idToTab.value(id, nullptr);
if (tabButton) {
tabButton->setUrl(url);
}
}
QUrl KateTabBar::tabUrl(int id) const
{
Q_ASSERT(d->idToTab.contains(id));
KateTabButton *tabButton = d->idToTab.value(id, nullptr);
if (tabButton) {
return tabButton->url();
}
return QUrl();
}
void KateTabBar::setTabIcon(int id, const QIcon &icon)
{
Q_ASSERT(d->idToTab.contains(id));
KateTabButton *tabButton = d->idToTab.value(id, nullptr);
if (tabButton) {
tabButton->setIcon(icon);
}
}
QIcon KateTabBar::tabIcon(int id) const
{
Q_ASSERT(d->idToTab.contains(id));
KateTabButton *tabButton = d->idToTab.value(id, nullptr);
if (tabButton) {
return tabButton->icon();
}
return QIcon();
}
int KateTabBar::count() const
{
return d->tabButtons.count();
}
void KateTabBar::tabButtonActivated(KateTabButton *tabButton)
{
if (!tabButton) {
return;
}
if (tabButton == d->activeButton) {
// make sure we are the currently active view space
if (!isActive()) {
emit activateViewSpaceRequested();
}
return;
}
if (d->activeButton) {
d->activeButton->setChecked(false);
}
d->activeButton = tabButton;
d->activeButton->setChecked(true);
const int id = d->idToTab.key(d->activeButton, -1);
Q_ASSERT(id >= 0);
if (id >= 0) {
emit currentChanged(id);
}
}
void KateTabBar::tabButtonCloseRequest(KateTabButton *tabButton)
{
// keep width
if (underMouse()) {
d->keepTabWidth = true;
}
const int id = d->idToTab.key(tabButton, -1);
Q_ASSERT(id >= 0);
if (id >= 0) {
emit closeTabRequested(id);
}
}
void KateTabBar::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event)
// fix button positions
if (!d->keepTabWidth || event->size().width() < event->oldSize().width()) {
d->updateButtonPositions();
}
const int tabDiff = maxTabCount() - d->tabButtons.size();
if (tabDiff > 0) {
emit moreTabsRequested(tabDiff);
} else if (tabDiff < 0) {
emit lessTabsRequested(-tabDiff);
}
}
int KateTabBar::maxTabCount() const
{
return qMax(1, width() / d->minimumTabWidth);
QVariant data = ensureValidTabData(idx);
return data.value<KateTabButtonData>().url;
}
void KateTabBar::mouseDoubleClickEvent(QMouseEvent *event)
......@@ -465,58 +135,10 @@ void KateTabBar::mousePressEvent(QMouseEvent *event)
QWidget::mousePressEvent(event);
}
void KateTabBar::leaveEvent(QEvent *event)
{
if (d->keepTabWidth) {
d->keepTabWidth = false;
d->updateButtonPositions(true);
}
QWidget::leaveEvent(event);
}
void KateTabBar::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
const int buttonCount = d->tabButtons.size();
if (buttonCount < 1) {
return;
}
// draw separators
QStyleOption option;
option.initFrom(d->tabButtons[0]);
option.state |= QStyle::State_Horizontal;
const int w = style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, nullptr, this);
const int offset = w / 2;
option.rect.setWidth(w);
option.rect.moveTop(0);
QPainter painter(this);
// first separator
option.rect.moveLeft(d->tabButtons[0]->geometry().left() - offset);
style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &option, &painter);
// all other separators
for (int i = 0; i < buttonCount; ++i) {
option.rect.moveLeft(d->tabButtons[i]->geometry().right() - offset);
style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &option, &painter);
}
}
void KateTabBar::contextMenuEvent(QContextMenuEvent *ev)
{
int id = -1;
for (KateTabButton *button : qAsConst(d->tabButtons)) {
if (button->rect().contains(button->mapFromGlobal(ev->globalPos()))) {
id = d->idToTab.key(button, -1);
Q_ASSERT(id >= 0);
break;
}
}
int id = tabAt(ev->pos());
if (id >= 0) {
emit contextMenuRequest(id, ev->globalPos());
}
......@@ -528,67 +150,47 @@ void KateTabBar::wheelEvent(QWheelEvent *event)
// cycle through the tabs
const int delta = event->angleDelta().x() + event->angleDelta().y();
const int id = (delta > 0) ? prevTab() : nextTab();
if (id >= 0) {
Q_ASSERT(d->idToTab.contains(id));
KateTabButton *tabButton = d->idToTab.value(id, nullptr);
if (tabButton) {
tabButtonActivated(tabButton);
}
}
const int idx = (delta > 0) ? prevTab() : nextTab();
setCurrentIndex(idx);
}
void KateTabBar::dragEnterEvent(QDragEnterEvent *event)
int KateTabBar::maxTabCount() const
{
const bool sameApplication = event->source() != nullptr;
if (sameApplication && event->mimeData()->hasFormat(QStringLiteral("application/x-dndkatetabbutton"))) {
if (event->source()->parent() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
// possibly dragged from another tabbar (not yet implemented)
event->ignore();
}
} else {
event->ignore();
}
return 10; // TODO: Fix this.
}
void KateTabBar::dragMoveEvent(QDragMoveEvent *event)
void KateTabBar::setTabDocument(int idx, KTextEditor::Document* doc)
{
// first of all, make sure the dragged button is from this tabbar
auto button = qobject_cast<KateTabButton *>(event->source());
if (!button) {
event->ignore();
return;
}
// save old tab position
const int oldButtonIndex = d->tabButtons.indexOf(button);
d->tabButtons.removeAt(oldButtonIndex);
// find new position
const qreal currentPos = event->pos().x();
int index = d->tabButtons.size();
for (int i = 0; i < d->tabButtons.size(); ++i) {
auto tab = d->tabButtons[i];
if (tab->geometry().center().x() > currentPos) {
index = i;
break;
}
}
d->tabButtons.insert(index, button);
QVariant data = ensureValidTabData(idx);
KateTabButtonData buttonData = data.value<KateTabButtonData>();
buttonData.doc = doc;
setTabData(idx, QVariant::fromValue(buttonData));
}
// trigger rearrange if required
if (oldButtonIndex != index) {
d->updateButtonPositions(true);
}
void KateTabBar::setCurrentDocument(KTextEditor::Document *doc)
{
setCurrentIndex(documentIdx(doc));
}
event->accept();
int KateTabBar::documentIdx(KTextEditor::Document *doc)
{
for (int idx = 0; idx < count(); idx++) {
QVariant data = tabData(idx);
if (!data.isValid()) {
continue;
}
if (data.value<KateTabButtonData>().doc != doc) {
continue;
}
return idx;
}
return -1;
}