Commit 14e0da66 authored by Christoph Cullmann's avatar Christoph Cullmann 🐮
Browse files

implement optional limit for number of tabs and LRU replacement

this allows to have the "old" behavior of pre 20.08 as option

it is not exactly the same as in e.g. 20.04, as we still use the normal QTabBar,
but it should feel equivalent enough

as positive side effect, the user is no able to configure how many
tabs he wants to have as maximum, default is 0, aka unlimited,
before this was based on same heuristics

BUG: 426520
parent 29ba5b01
......@@ -354,6 +354,23 @@ public Q_SLOTS:
*/
void remoteMessageReceived(const QString &message, QObject *socket);
public:
/**
* trigger emission of configurationChanged
*/
void emitConfigurationChanged()
{
emit configurationChanged();
}
Q_SIGNALS:
/**
* Emitted when the configuration got changed via the global config dialog.
* Allows widgets to listen on this and adapt, the KSharedConfig::openConfig()
* is already updated.
*/
void configurationChanged();
protected:
/**
* Event filter for QApplication to handle mac os like file open
......
......@@ -179,6 +179,23 @@ KateConfigDialog::KateConfigDialog(KateMainWindow *parent, KTextEditor::View *vi
vbox->addLayout(hlayout);
layout->addWidget(buttonGroup);
// tabbar => we allow to configure some limit on number of tabs to show
buttonGroup = new QGroupBox(i18n("&Tabbar"), generalFrame);
vbox = new QVBoxLayout;
buttonGroup->setLayout(vbox);
hlayout = new QHBoxLayout;
label = new QLabel(i18n("&Limit Tabbar to a fixed number of tabs:"), buttonGroup);
hlayout->addWidget(label);
m_tabLimit = new QSpinBox(buttonGroup);
hlayout->addWidget(m_tabLimit);
label->setBuddy(m_tabLimit);
m_tabLimit->setRange(0, 256);
m_tabLimit->setSpecialValueText(i18n("Unlimited"));
m_tabLimit->setValue(cgGeneral.readEntry("Tabbar Tab Limit", 0));
connect(m_tabLimit, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KateConfigDialog::slotChanged);
vbox->addLayout(hlayout);
layout->addWidget(buttonGroup);
layout->addStretch(1); // :-] works correct without autoadd
// END General page
......@@ -391,6 +408,8 @@ void KateConfigDialog::slotApply()
cg.writeEntry("Quick Open List Mode", m_cmbQuickOpenListMode->currentData().toInt());
m_mainWindow->setQuickOpenListMode(static_cast<KateQuickOpenModel::List>(m_cmbQuickOpenListMode->currentData().toInt()));
cg.writeEntry("Tabbar Tab Limit", m_tabLimit->value());
// patch document modified warn state
const QList<KTextEditor::Document *> &docs = KateApp::self()->documentManager()->documentList();
for (KTextEditor::Document *doc : docs)
......@@ -427,6 +446,11 @@ void KateConfigDialog::slotApply()
config->sync();
// emit config change
if (m_dataChanged) {
KateApp::self()->emitConfigurationChanged();
}
m_dataChanged = false;
buttonBox()->button(QDialogButtonBox::Apply)->setEnabled(false);
}
......
......@@ -96,6 +96,7 @@ private:
KPluralHandlingSpinBox *m_daysMetaInfos;
QComboBox *m_cmbQuickOpenMatchMode;
QComboBox *m_cmbQuickOpenListMode;
QSpinBox *m_tabLimit;
// Sessions Page
Ui::SessionConfigWidget *sessionConfigUi;
......
......@@ -18,6 +18,7 @@
Boston, MA 02110-1301, USA.
*/
#include "kateapp.h"
#include "katetabbar.h"
#include <math.h> // ceil
......@@ -29,6 +30,9 @@
#include <QStyleOptionTab>
#include <QWheelEvent>
#include <KSharedConfig>
#include <KConfigGroup>
#include <KTextEditor/Document>
struct KateTabButtonData {
......@@ -60,6 +64,37 @@ KateTabBar::KateTabBar(QWidget *parent)
// allow users to re-arrange the tabs
setMovable(true);
// enforce configured limit
readTabCountLimitConfig();
// handle config changes
connect(KateApp::self(), &KateApp::configurationChanged, this, &KateTabBar::readTabCountLimitConfig);
}
void KateTabBar::readTabCountLimitConfig()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup cgGeneral = KConfigGroup(config, "General");
// 0 == unlimited, normalized other inputs
const int tabCountLimit = cgGeneral.readEntry("Tabbar Tab Limit", 0);
m_tabCountLimit = (tabCountLimit <= 0) ? 0 : tabCountLimit;
// use scroll buttons if we have no limit
setUsesScrollButtons(m_tabCountLimit == 0);
// elide if we have some limit
setElideMode((m_tabCountLimit == 0) ? Qt::ElideNone : Qt::ElideMiddle);
// if we enforce a limit: purge tabs that violate it
if (m_tabCountLimit > 0 && (count() > m_tabCountLimit)) {
// just purge last X tabs, this isn't that clever but happens only on config changes!
while (count() > m_tabCountLimit) {
removeTab(count() - 1);
}
setCurrentIndex(0);
}
}
void KateTabBar::setActive(bool active)
......@@ -157,7 +192,60 @@ void KateTabBar::setTabDocument(int idx, KTextEditor::Document *doc)
void KateTabBar::setCurrentDocument(KTextEditor::Document *doc)
{
setCurrentIndex(documentIdx(doc));
// in any case: update lru counter for this document, might add new element to hash
m_docToLruCounter[doc] = ++m_lruCounter;
// do we have a tab for this document?
// if yes => just set as current one
const int existingIndex = documentIdx(doc);
if (existingIndex != -1) {
setCurrentIndex(existingIndex);
return;
}
// else: if we are still inside the allowed number of tabs or have no limit
// => create new tab and be done
if ((m_tabCountLimit == 0) || count() < m_tabCountLimit) {
m_beingAdded = doc;
insertTab(-1, doc->documentName());
return;
}
// ok, we have already the limit of tabs reached:
// replace the tab with the lowest lru counter => the least recently used
// search for the right tab
quint64 minCounter = static_cast<quint64>(-1);
int indexToReplace = 0;
for (int idx = 0; idx < count(); idx++) {
QVariant data = tabData(idx);
if (!data.isValid()) {
continue;
}
const quint64 currentCounter = m_docToLruCounter[data.value<KateTabButtonData>().doc];
if (currentCounter <= minCounter) {
minCounter = currentCounter;
indexToReplace = idx;
}
}
// replace it's data + set it as active
setTabText(indexToReplace, doc->documentName());
setTabDocument(indexToReplace, doc);
setTabToolTip(indexToReplace, doc->url().toDisplayString());
setCurrentIndex(indexToReplace);
}
void KateTabBar::removeDocument(KTextEditor::Document *doc)
{
// remove document if needed, we might have no tab for it, if tab count is limited!
const int idx = documentIdx(doc);
if (idx != -1) {
removeTab(idx);
}
// purge LRU storage, must work
Q_ASSERT(m_docToLruCounter.erase(doc) == 1);
}
int KateTabBar::documentIdx(KTextEditor::Document *doc)
......@@ -194,12 +282,6 @@ KTextEditor::Document *KateTabBar::tabDocument(int idx)
return doc;
}
int KateTabBar::insertTab(int idx, KTextEditor::Document *doc)
{
m_beingAdded = doc;
return insertTab(idx, doc->documentName());
}
void KateTabBar::tabInserted(int idx)
{
if (m_beingAdded) {
......
......@@ -24,6 +24,8 @@
#include <QTabBar>
#include <QUrl>
#include <unordered_map>
namespace KTextEditor
{
class Document;
......@@ -43,7 +45,12 @@ class KateTabBar : public QTabBar
public:
explicit KateTabBar(QWidget *parent = nullptr);
int insertTab(int idx, KTextEditor::Document *doc);
/**
* Read and apply tab limit as configured
*/
void readTabCountLimitConfig();
void tabInserted(int idx) override;
/**
......@@ -84,6 +91,7 @@ public:
int documentIdx(KTextEditor::Document *doc);
void setTabDocument(int idx, KTextEditor::Document *doc);
KTextEditor::Document *tabDocument(int idx);
void removeDocument(KTextEditor::Document *doc);
/**
* Marks this tabbar as active. That is, current-tab indicators are
......@@ -148,6 +156,24 @@ private:
bool m_isActive = false;
KTextEditor::Document *m_beingAdded = nullptr;
/**
* limit of number of tabs we should keep
* default unlimited == 0
*/
int m_tabCountLimit = 0;
/**
* next lru counter value for a tab
*/
quint64 m_lruCounter = 0;
/**
* LRU counter storage, to determine which document has which age
* simple 64-bit counter, worst thing that can happen on 64-bit wraparound
* is a bit strange tab replacement a few times
*/
std::unordered_map<const KTextEditor::Document *, quint64> m_docToLruCounter;
};
#endif // KATE_TAB_BAR_H
......@@ -242,11 +242,8 @@ bool KateViewSpace::showView(KTextEditor::Document *document)
/**
* follow current view
* we have tabs available for all registered documents!
*/
const auto idx = m_tabBar->documentIdx(document);
Q_ASSERT(idx != -1);
m_tabBar->setCurrentIndex(idx);
m_tabBar->setCurrentDocument(document);
// track tab changes again
connect(m_tabBar, &KateTabBar::currentChanged, this, &KateViewSpace::changeView);
......@@ -325,11 +322,9 @@ void KateViewSpace::registerDocument(KTextEditor::Document *doc)
disconnect(m_tabBar, &KateTabBar::currentChanged, this, &KateViewSpace::changeView);
/**
* we want a tab for each known document
* if we arrive here, there shall be no existing tab!
* create the tab for this document, if necessary
*/
Q_ASSERT(m_tabBar->documentIdx(doc) == -1);
m_tabBar->insertTab(-1, doc);
m_tabBar->setCurrentDocument(doc);
updateDocumentState(doc);
......@@ -362,12 +357,9 @@ void KateViewSpace::documentDestroyed(QObject *doc)
disconnect(doc, nullptr, this, nullptr);
/**
* remove the tab for this document
* as we only handle registered documents here, there shall be a tab!
* remove the tab for this document, if existing
*/
const int idx = m_tabBar->documentIdx(invalidDoc);
Q_ASSERT(idx != -1);
m_tabBar->removeTab(idx);
m_tabBar->removeDocument(invalidDoc);
}
void KateViewSpace::updateDocumentName(KTextEditor::Document *doc)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment