File indexing completed on 2024-05-05 05:51:26

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2010 Thomas Fjellstrom <thomas@fjellstrom.ca>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "katefiletreemodel.h"
0008 
0009 #include <QDir>
0010 #include <QGuiApplication>
0011 #include <QIcon>
0012 #include <QList>
0013 #include <QMimeData>
0014 #include <QStack>
0015 #include <QWidget>
0016 
0017 #include <KColorScheme>
0018 #include <KColorUtils>
0019 #include <KLocalizedString>
0020 
0021 #include <KTextEditor/MainWindow>
0022 #include <ktexteditor/application.h>
0023 #include <ktexteditor/document.h>
0024 #include <ktexteditor/editor.h>
0025 
0026 #include "katefiletreedebug.h"
0027 #include "ktexteditor_utils.h"
0028 
0029 #include <variant>
0030 
0031 static constexpr int MaxHistoryItems = 10;
0032 
0033 class FileTreeMimeData : public QMimeData
0034 {
0035     Q_OBJECT
0036 public:
0037     FileTreeMimeData(const QModelIndex &index)
0038         : m_index(index)
0039     {
0040     }
0041 
0042     QModelIndex index() const
0043     {
0044         return m_index;
0045     }
0046 
0047 private:
0048     QPersistentModelIndex m_index;
0049 };
0050 
0051 class ProxyItemDir;
0052 class ProxyItem
0053 {
0054     friend class KateFileTreeModel;
0055 
0056 public:
0057     enum Flag {
0058         None = 0,
0059         Dir = 1,
0060         Modified = 2,
0061         ModifiedExternally = 4,
0062         DeletedExternally = 8,
0063         Empty = 16,
0064         ShowFullPath = 32,
0065         Host = 64,
0066         Widget = 128,
0067     };
0068     Q_DECLARE_FLAGS(Flags, Flag)
0069 
0070     ProxyItem(const QString &n, ProxyItemDir *p = nullptr, Flags f = ProxyItem::None);
0071     ~ProxyItem();
0072 
0073     int addChild(ProxyItem *p);
0074     void removeChild(ProxyItem *p);
0075 
0076     ProxyItemDir *parent() const;
0077 
0078     ProxyItem *child(int idx) const;
0079     int childCount() const;
0080 
0081     int row() const;
0082 
0083     const QString &display() const;
0084     const QString &documentName() const;
0085 
0086     const QString &path() const;
0087     void setPath(const QString &str);
0088 
0089     void setHost(const QString &host);
0090     const QString &host() const;
0091 
0092     void setIcon(const QIcon &i);
0093     const QIcon &icon() const;
0094 
0095     const std::vector<ProxyItem *> &children() const;
0096     std::vector<ProxyItem *> &children();
0097 
0098     void setDoc(KTextEditor::Document *doc);
0099     KTextEditor::Document *doc() const;
0100 
0101     void setWidget(QWidget *);
0102     QWidget *widget() const;
0103 
0104     /**
0105      * the view uses this to close all the documents under the folder
0106      * @returns list of all the (nested) documents under this node
0107      */
0108     QList<KTextEditor::Document *> docTree() const;
0109 
0110     void setFlags(Flags flags);
0111     void setFlag(Flag flag);
0112     void clearFlag(Flag flag);
0113     bool flag(Flag flag) const;
0114 
0115 private:
0116     QString m_path;
0117     QString m_documentName;
0118     ProxyItemDir *m_parent;
0119     std::vector<ProxyItem *> m_children;
0120     int m_row;
0121     Flags m_flags;
0122 
0123     QString m_display;
0124     QIcon m_icon;
0125     std::variant<KTextEditor::Document *, QWidget *> m_object;
0126     QString m_host;
0127 
0128 protected:
0129     void updateDisplay();
0130     void updateDocumentName();
0131 };
0132 
0133 QDebug operator<<(QDebug dbg, ProxyItem *item)
0134 {
0135     if (!item) {
0136         dbg.nospace() << "ProxyItem(0x0) ";
0137         return dbg.maybeSpace();
0138     }
0139 
0140     const void *parent = static_cast<void *>(item->parent());
0141 
0142     dbg.nospace() << "ProxyItem(" << item << ",";
0143     dbg.nospace() << parent << "," << item->row() << ",";
0144     dbg.nospace() << item->doc() << "," << item->path() << ") ";
0145     return dbg.maybeSpace();
0146 }
0147 
0148 class ProxyItemDir : public ProxyItem
0149 {
0150 public:
0151     ProxyItemDir(const QString &n, ProxyItemDir *p = nullptr)
0152         : ProxyItem(n, p)
0153     {
0154         setFlag(ProxyItem::Dir);
0155         updateDisplay();
0156 
0157         setIcon(QIcon::fromTheme(QStringLiteral("folder")));
0158     }
0159 };
0160 
0161 QDebug operator<<(QDebug dbg, ProxyItemDir *item)
0162 {
0163     if (!item) {
0164         dbg.nospace() << "ProxyItemDir(0x0) ";
0165         return dbg.maybeSpace();
0166     }
0167 
0168     const void *parent = static_cast<void *>(item->parent());
0169 
0170     dbg.nospace() << "ProxyItemDir(" << item << ",";
0171     dbg.nospace() << parent << "," << item->row() << ",";
0172     dbg.nospace() << item->path() << ", children:" << item->childCount() << ") ";
0173     return dbg.maybeSpace();
0174 }
0175 
0176 Q_DECLARE_OPERATORS_FOR_FLAGS(ProxyItem::Flags)
0177 
0178 // BEGIN ProxyItem
0179 ProxyItem::ProxyItem(const QString &d, ProxyItemDir *p, ProxyItem::Flags f)
0180     : m_path(d)
0181     , m_parent(Q_NULLPTR)
0182     , m_row(-1)
0183     , m_flags(f)
0184 {
0185     updateDisplay();
0186 
0187     if (f.testFlag(Widget) && f.testFlag(Dir)) {
0188         m_documentName = display();
0189     }
0190 
0191     /**
0192      * add to parent, if parent passed
0193      * we assigned above nullptr to parent to not trigger
0194      * remove from old parent here!
0195      */
0196     if (p) {
0197         p->addChild(this);
0198     }
0199 }
0200 
0201 ProxyItem::~ProxyItem()
0202 {
0203     qDeleteAll(m_children);
0204 }
0205 
0206 void ProxyItem::updateDisplay()
0207 {
0208     // triggers only if this is a top level node and the root has the show full path flag set.
0209     if (flag(ProxyItem::Dir) && m_parent && !m_parent->m_parent && m_parent->flag(ProxyItem::ShowFullPath)) {
0210         m_display = m_path;
0211         if (m_display.startsWith(QDir::homePath())) {
0212             m_display.replace(0, QDir::homePath().length(), QStringLiteral("~"));
0213         }
0214     } else {
0215         m_display = m_path.section(QLatin1Char('/'), -1, -1);
0216         if (flag(ProxyItem::Host) && (!m_parent || (m_parent && !m_parent->m_parent))) {
0217             const QString hostPrefix = QStringLiteral("[%1]").arg(host());
0218             if (hostPrefix != m_display) {
0219                 m_display = hostPrefix + m_display;
0220             }
0221         }
0222     }
0223 }
0224 
0225 int ProxyItem::addChild(ProxyItem *item)
0226 {
0227     // remove from old parent, is any
0228     if (item->m_parent) {
0229         item->m_parent->removeChild(item);
0230     }
0231 
0232     const int item_row = m_children.size();
0233     item->m_row = item_row;
0234     m_children.push_back(item);
0235     item->m_parent = static_cast<ProxyItemDir *>(this);
0236 
0237     item->updateDisplay();
0238 
0239     return item_row;
0240 }
0241 
0242 void ProxyItem::removeChild(ProxyItem *item)
0243 {
0244     auto it = std::find(m_children.begin(), m_children.end(), item);
0245     Q_ASSERT(it != m_children.end());
0246     m_children.erase(it);
0247 
0248     auto idx = std::distance(m_children.begin(), it);
0249     for (size_t i = idx; i < m_children.size(); i++) {
0250         m_children[i]->m_row = i;
0251     }
0252 
0253     item->m_parent = nullptr;
0254 }
0255 
0256 ProxyItemDir *ProxyItem::parent() const
0257 {
0258     return m_parent;
0259 }
0260 
0261 ProxyItem *ProxyItem::child(int idx) const
0262 {
0263     return (size_t(idx) >= m_children.size()) ? nullptr : m_children[idx];
0264 }
0265 
0266 int ProxyItem::childCount() const
0267 {
0268     return m_children.size();
0269 }
0270 
0271 int ProxyItem::row() const
0272 {
0273     return m_row;
0274 }
0275 
0276 const QIcon &ProxyItem::icon() const
0277 {
0278     return m_icon;
0279 }
0280 
0281 void ProxyItem::setIcon(const QIcon &i)
0282 {
0283     m_icon = i;
0284 }
0285 
0286 const QString &ProxyItem::documentName() const
0287 {
0288     return m_documentName;
0289 }
0290 
0291 const QString &ProxyItem::display() const
0292 {
0293     return m_display;
0294 }
0295 
0296 const QString &ProxyItem::path() const
0297 {
0298     return m_path;
0299 }
0300 
0301 void ProxyItem::setPath(const QString &p)
0302 {
0303     m_path = p;
0304     updateDisplay();
0305 }
0306 
0307 const std::vector<ProxyItem *> &ProxyItem::children() const
0308 {
0309     return m_children;
0310 }
0311 
0312 std::vector<ProxyItem *> &ProxyItem::children()
0313 {
0314     return m_children;
0315 }
0316 
0317 void ProxyItem::setDoc(KTextEditor::Document *doc)
0318 {
0319     Q_ASSERT(doc);
0320     m_object = doc;
0321     updateDocumentName();
0322 }
0323 
0324 void ProxyItem::setWidget(QWidget *w)
0325 {
0326     Q_ASSERT(w);
0327     m_object = w;
0328     updateDocumentName();
0329 }
0330 
0331 QWidget *ProxyItem::widget() const
0332 {
0333     if (!std::holds_alternative<QWidget *>(m_object))
0334         return nullptr;
0335     return std::get<QWidget *>(m_object);
0336 }
0337 
0338 KTextEditor::Document *ProxyItem::doc() const
0339 {
0340     if (!std::holds_alternative<KTextEditor::Document *>(m_object))
0341         return nullptr;
0342     return std::get<KTextEditor::Document *>(m_object);
0343 }
0344 
0345 QList<KTextEditor::Document *> ProxyItem::docTree() const
0346 {
0347     QList<KTextEditor::Document *> result;
0348 
0349     if (doc()) {
0350         result.append(doc());
0351         return result;
0352     }
0353 
0354     for (const ProxyItem *item : m_children) {
0355         result.append(item->docTree());
0356     }
0357 
0358     return result;
0359 }
0360 
0361 bool ProxyItem::flag(Flag f) const
0362 {
0363     return m_flags & f;
0364 }
0365 
0366 void ProxyItem::setFlag(Flag f)
0367 {
0368     m_flags |= f;
0369 }
0370 
0371 void ProxyItem::setFlags(Flags f)
0372 {
0373     m_flags = f;
0374 }
0375 
0376 void ProxyItem::clearFlag(Flag f)
0377 {
0378     m_flags &= ~f;
0379 }
0380 
0381 void ProxyItem::setHost(const QString &host)
0382 {
0383     m_host = host;
0384 
0385     if (host.isEmpty()) {
0386         clearFlag(Host);
0387     } else {
0388         setFlag(Host);
0389     }
0390 
0391     updateDocumentName();
0392     updateDisplay();
0393 }
0394 
0395 const QString &ProxyItem::host() const
0396 {
0397     return m_host;
0398 }
0399 
0400 void ProxyItem::updateDocumentName()
0401 {
0402     QString name;
0403     if (doc()) {
0404         name = doc()->documentName();
0405     } else if (widget()) {
0406         name = widget()->windowTitle();
0407     }
0408 
0409     if (flag(ProxyItem::Host)) {
0410         m_documentName = QStringLiteral("[%1]%2").arg(m_host, name);
0411     } else {
0412         m_documentName = name;
0413     }
0414 }
0415 
0416 // END ProxyItem
0417 
0418 KateFileTreeModel::KateFileTreeModel(KTextEditor::MainWindow *mainWindow, QObject *p)
0419     : QAbstractItemModel(p)
0420     , m_mainWindow(mainWindow)
0421     , m_root(new ProxyItemDir(QStringLiteral("m_root"), nullptr))
0422 {
0423     // setup default settings
0424     // session init will set these all soon
0425     const KColorScheme colors(QPalette::Active);
0426     const QColor bg = colors.background().color();
0427     m_editShade = KColorUtils::tint(bg, colors.foreground(KColorScheme::ActiveText).color(), 0.5);
0428     m_viewShade = KColorUtils::tint(bg, colors.foreground(KColorScheme::VisitedText).color(), 0.5);
0429     m_inactiveDocColor = colors.foreground(KColorScheme::InactiveText).color();
0430     m_shadingEnabled = true;
0431     m_listMode = false;
0432 
0433     initModel();
0434 
0435     // ensure palette change updates the colors properly
0436     connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, [this]() {
0437         m_inactiveDocColor = KColorScheme(QPalette::Active).foreground(KColorScheme::InactiveText).color();
0438         updateBackgrounds(true);
0439     });
0440 }
0441 
0442 KateFileTreeModel::~KateFileTreeModel()
0443 {
0444     delete m_root;
0445 }
0446 
0447 void KateFileTreeModel::setShadingEnabled(bool se)
0448 {
0449     if (m_shadingEnabled != se) {
0450         updateBackgrounds(true);
0451         m_shadingEnabled = se;
0452     }
0453 
0454     if (!se) {
0455         m_viewHistory.clear();
0456         m_editHistory.clear();
0457         m_brushes.clear();
0458     }
0459 }
0460 
0461 void KateFileTreeModel::setEditShade(const QColor &es)
0462 {
0463     m_editShade = es;
0464 }
0465 
0466 void KateFileTreeModel::setViewShade(const QColor &vs)
0467 {
0468     m_viewShade = vs;
0469 }
0470 
0471 bool KateFileTreeModel::showFullPathOnRoots(void) const
0472 {
0473     return m_root->flag(ProxyItem::ShowFullPath);
0474 }
0475 
0476 void KateFileTreeModel::setShowFullPathOnRoots(bool s)
0477 {
0478     if (s) {
0479         m_root->setFlag(ProxyItem::ShowFullPath);
0480     } else {
0481         m_root->clearFlag(ProxyItem::ShowFullPath);
0482     }
0483 
0484     const auto rootChildren = m_root->children();
0485     for (ProxyItem *root : rootChildren) {
0486         root->updateDisplay();
0487     }
0488 }
0489 
0490 void KateFileTreeModel::initModel()
0491 {
0492     beginInsertRows(QModelIndex(), 0, 0);
0493     Q_ASSERT(!m_widgetsRoot);
0494     m_widgetsRoot = new ProxyItem(i18nc("Open here is a description, i.e. 'list of widgets that are open' not a verb", "Open Widgets"),
0495                                   nullptr,
0496                                   ProxyItem::Flags(ProxyItem::Dir | ProxyItem::Widget));
0497     m_widgetsRoot->setFlags(ProxyItem::Flags(ProxyItem::Dir | ProxyItem::Widget));
0498     m_widgetsRoot->setIcon(QIcon::fromTheme(QStringLiteral("folder-windows")));
0499     m_root->addChild(m_widgetsRoot);
0500     endInsertRows();
0501 
0502     // add already existing documents
0503     const auto documents = KTextEditor::Editor::instance()->application()->documents();
0504     for (KTextEditor::Document *doc : documents) {
0505         documentOpened(doc);
0506     }
0507 
0508     if (m_mainWindow) {
0509         QWidgetList widgets;
0510         QMetaObject::invokeMethod(m_mainWindow->window(), "widgets", Q_RETURN_ARG(QWidgetList, widgets));
0511         for (auto *w : std::as_const(widgets)) {
0512             addWidget(w);
0513         }
0514     }
0515 }
0516 
0517 void KateFileTreeModel::clearModel()
0518 {
0519     // remove all items
0520     // can safely ignore documentClosed here
0521 
0522     beginResetModel();
0523 
0524     delete m_root;
0525     m_root = new ProxyItemDir(QStringLiteral("m_root"), nullptr);
0526 
0527     m_widgetsRoot = nullptr;
0528 
0529     m_docmap.clear();
0530     m_viewHistory.clear();
0531     m_editHistory.clear();
0532     m_brushes.clear();
0533 
0534     endResetModel();
0535 }
0536 
0537 void KateFileTreeModel::connectDocument(const KTextEditor::Document *doc)
0538 {
0539     connect(doc, &KTextEditor::Document::documentNameChanged, this, &KateFileTreeModel::documentNameChanged);
0540     connect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateFileTreeModel::documentNameChanged);
0541     connect(doc, &KTextEditor::Document::modifiedChanged, this, &KateFileTreeModel::documentModifiedChanged);
0542     connect(doc, &KTextEditor::Document::modifiedOnDisk, this, &KateFileTreeModel::documentModifiedOnDisc);
0543 }
0544 
0545 QModelIndex KateFileTreeModel::docIndex(const KTextEditor::Document *doc) const
0546 {
0547     auto it = m_docmap.find(doc);
0548     if (it == m_docmap.end()) {
0549         return {};
0550     }
0551     auto item = it.value();
0552     return createIndex(item->row(), 0, item);
0553 }
0554 
0555 QModelIndex KateFileTreeModel::widgetIndex(QWidget *widget) const
0556 {
0557     ProxyItem *item = nullptr;
0558     const auto items = m_widgetsRoot->children();
0559     for (auto *it : items) {
0560         if (it->widget() == widget) {
0561             item = it;
0562             break;
0563         }
0564     }
0565     if (!item) {
0566         return {};
0567     }
0568 
0569     return createIndex(item->row(), 0, item);
0570 }
0571 
0572 Qt::ItemFlags KateFileTreeModel::flags(const QModelIndex &index) const
0573 {
0574     Qt::ItemFlags flags = Qt::ItemIsEnabled;
0575 
0576     if (!index.isValid()) {
0577         return Qt::NoItemFlags | Qt::ItemIsDropEnabled;
0578     }
0579 
0580     const ProxyItem *item = static_cast<ProxyItem *>(index.internalPointer());
0581     if (item) {
0582         if (!item->flag(ProxyItem::Dir)) {
0583             flags |= Qt::ItemIsSelectable;
0584         }
0585 
0586         if (item->flag(ProxyItem::Dir) && !item->flag(ProxyItem::Widget)) {
0587             flags |= Qt::ItemIsDropEnabled;
0588         }
0589 
0590         if (item->doc() && item->doc()->url().isValid()) {
0591             flags |= Qt::ItemIsDragEnabled;
0592         }
0593     }
0594 
0595     return flags;
0596 }
0597 
0598 QVariant KateFileTreeModel::data(const QModelIndex &index, int role) const
0599 {
0600     if (!index.isValid()) {
0601         return QVariant();
0602     }
0603 
0604     ProxyItem *item = static_cast<ProxyItem *>(index.internalPointer());
0605     if (!item) {
0606         return QVariant();
0607     }
0608 
0609     switch (role) {
0610     case KateFileTreeModel::PathRole:
0611         // allow to sort with hostname + path, bug 271488
0612         return (item->doc() && !item->doc()->url().isEmpty()) ? item->doc()->url().toString() : item->path();
0613 
0614     case KateFileTreeModel::DocumentRole:
0615         return QVariant::fromValue(item->doc());
0616 
0617     case KateFileTreeModel::WidgetRole:
0618         return QVariant::fromValue(item->widget());
0619 
0620     case KateFileTreeModel::OpeningOrderRole:
0621         return item->row();
0622 
0623     case KateFileTreeModel::DocumentTreeRole:
0624         return QVariant::fromValue(item->docTree());
0625 
0626     case Qt::DisplayRole:
0627         // in list mode we want to use kate's fancy names.
0628         if (index.column() == 0) {
0629             if (m_listMode) {
0630                 return item->documentName();
0631             } else {
0632                 return item->display();
0633             }
0634         }
0635         break;
0636     case Qt::DecorationRole:
0637         if (index.column() == 0) {
0638             return item->icon();
0639         }
0640         break;
0641     case Qt::ToolTipRole: {
0642         QString tooltip = item->path();
0643         if (item->flag(ProxyItem::DeletedExternally) || item->flag(ProxyItem::ModifiedExternally)) {
0644             tooltip = i18nc("%1 is the full path", "<p><b>%1</b></p><p>The document has been modified by another application.</p>", item->path());
0645         }
0646 
0647         return tooltip;
0648     }
0649 
0650     case Qt::ForegroundRole: {
0651         if (!item->flag(ProxyItem::Widget) && !item->flag(ProxyItem::Dir) && (!item->doc() || item->doc()->openingError())) {
0652             return m_inactiveDocColor;
0653         }
0654     } break;
0655 
0656     case Qt::BackgroundRole:
0657         // TODO: do that funky shading the file list does...
0658         if (m_shadingEnabled) {
0659             if (auto it = m_brushes.find(item); it != m_brushes.end()) {
0660                 return it->second;
0661             }
0662         }
0663         break;
0664     }
0665 
0666     return QVariant();
0667 }
0668 
0669 Qt::DropActions KateFileTreeModel::supportedDropActions() const
0670 {
0671     Qt::DropActions a = QAbstractItemModel::supportedDropActions();
0672     a |= Qt::MoveAction;
0673     return a;
0674 }
0675 
0676 QMimeData *KateFileTreeModel::mimeData(const QModelIndexList &indexes) const
0677 {
0678     if (indexes.size() != columnCount()) {
0679         return nullptr;
0680     }
0681 
0682     ProxyItem *item = static_cast<ProxyItem *>(indexes.at(0).internalPointer());
0683     QList<QUrl> urls;
0684     if (!item || !item->doc() || !item->doc()->url().isValid()) {
0685         return nullptr;
0686     }
0687     urls.append(item->doc()->url());
0688 
0689     FileTreeMimeData *mimeData = new FileTreeMimeData(indexes.at(0));
0690     mimeData->setUrls(urls);
0691     return mimeData;
0692 }
0693 
0694 bool KateFileTreeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int, int, const QModelIndex &parent) const
0695 {
0696     if (auto md = qobject_cast<const FileTreeMimeData *>(data)) {
0697         return action == Qt::MoveAction && md->index().parent() == parent;
0698     }
0699     return false;
0700 }
0701 
0702 bool KateFileTreeModel::dropMimeData(const QMimeData *data, Qt::DropAction, int row, int, const QModelIndex &parent)
0703 {
0704     auto md = qobject_cast<const FileTreeMimeData *>(data);
0705     if (!md) {
0706         return false;
0707     }
0708 
0709     const auto index = md->index();
0710     Q_ASSERT(parent == index.parent());
0711     if (!index.isValid() || index.row() > rowCount(parent) || index.row() == row) {
0712         return false;
0713     }
0714 
0715     auto parentItem = parent.isValid() ? static_cast<ProxyItemDir *>(parent.internalPointer()) : m_root;
0716     auto &childs = parentItem->children();
0717     int sourceRow = index.row();
0718 
0719     beginMoveRows(index.parent(), index.row(), index.row(), parent, row);
0720     childs.insert(childs.begin() + row, childs.at(index.row()));
0721     if (sourceRow > row) {
0722         sourceRow++;
0723     }
0724     childs.erase(childs.begin() + sourceRow);
0725     endMoveRows();
0726     return true;
0727 }
0728 
0729 QVariant KateFileTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
0730 {
0731     Q_UNUSED(orientation);
0732     Q_UNUSED(role);
0733 
0734     if (section == 0) {
0735         return QLatin1String("name");
0736     }
0737 
0738     return QVariant();
0739 }
0740 
0741 int KateFileTreeModel::rowCount(const QModelIndex &parent) const
0742 {
0743     if (!parent.isValid()) {
0744         return m_root->childCount();
0745     }
0746 
0747     // we only have children for column 0
0748     if (parent.column() != 0) {
0749         return 0;
0750     }
0751 
0752     const ProxyItem *item = static_cast<ProxyItem *>(parent.internalPointer());
0753     if (!item) {
0754         return 0;
0755     }
0756 
0757     return item->childCount();
0758 }
0759 
0760 int KateFileTreeModel::columnCount(const QModelIndex &parent) const
0761 {
0762     Q_UNUSED(parent);
0763     return 2;
0764 }
0765 
0766 QModelIndex KateFileTreeModel::parent(const QModelIndex &index) const
0767 {
0768     if (!index.isValid()) {
0769         return QModelIndex();
0770     }
0771 
0772     const ProxyItem *item = static_cast<ProxyItem *>(index.internalPointer());
0773     if (!item) {
0774         return QModelIndex();
0775     }
0776 
0777     if (!item->parent()) {
0778         return QModelIndex();
0779     }
0780 
0781     if (item->parent() == m_root) {
0782         return QModelIndex();
0783     }
0784 
0785     return createIndex(item->parent()->row(), 0, item->parent());
0786 }
0787 
0788 QModelIndex KateFileTreeModel::index(int row, int column, const QModelIndex &parent) const
0789 {
0790     const ProxyItem *p = nullptr;
0791     if (column != 0 && column != 1) {
0792         return QModelIndex();
0793     }
0794 
0795     if (!parent.isValid()) {
0796         p = m_root;
0797     } else {
0798         p = static_cast<ProxyItem *>(parent.internalPointer());
0799     }
0800 
0801     if (!p) {
0802         return QModelIndex();
0803     }
0804 
0805     if (row < 0 || row >= p->childCount()) {
0806         return QModelIndex();
0807     }
0808 
0809     return createIndex(row, column, p->child(row));
0810 }
0811 
0812 bool KateFileTreeModel::hasChildren(const QModelIndex &parent) const
0813 {
0814     if (!parent.isValid()) {
0815         return m_root->childCount() > 0;
0816     }
0817 
0818     // we only have children for column 0
0819     if (parent.column() != 0) {
0820         return false;
0821     }
0822 
0823     const ProxyItem *item = static_cast<ProxyItem *>(parent.internalPointer());
0824     if (!item) {
0825         return false;
0826     }
0827 
0828     return item->childCount() > 0;
0829 }
0830 
0831 ProxyItem *KateFileTreeModel::itemForIndex(const QModelIndex &index) const
0832 {
0833     if (!index.isValid()) {
0834         return m_root;
0835     }
0836 
0837     ProxyItem *item = static_cast<ProxyItem *>(index.internalPointer());
0838     if (!item) {
0839         return nullptr;
0840     }
0841     return item;
0842 }
0843 
0844 bool KateFileTreeModel::isDir(const QModelIndex &index) const
0845 {
0846     if (auto item = itemForIndex(index)) {
0847         return item->flag(ProxyItem::Dir) && !item->flag(ProxyItem::Widget);
0848     }
0849     return false;
0850 }
0851 
0852 bool KateFileTreeModel::isWidgetDir(const QModelIndex &index) const
0853 {
0854     if (auto item = itemForIndex(index)) {
0855         return item->flag(ProxyItem::Dir) && item->flag(ProxyItem::Widget);
0856     }
0857     return false;
0858 }
0859 
0860 bool KateFileTreeModel::listMode() const
0861 {
0862     return m_listMode;
0863 }
0864 
0865 void KateFileTreeModel::setListMode(bool lm)
0866 {
0867     if (lm != m_listMode) {
0868         m_listMode = lm;
0869 
0870         clearModel();
0871         initModel();
0872     }
0873 }
0874 
0875 void KateFileTreeModel::documentOpened(KTextEditor::Document *doc)
0876 {
0877     ProxyItem *item = new ProxyItem(QString());
0878     item->setDoc(doc);
0879 
0880     updateItemPathAndHost(item);
0881     setupIcon(item);
0882     handleInsert(item);
0883     m_docmap[doc] = item;
0884     connectDocument(doc);
0885 }
0886 
0887 void KateFileTreeModel::documentsOpened(const QList<KTextEditor::Document *> &docs)
0888 {
0889     for (KTextEditor::Document *doc : docs) {
0890         if (m_docmap.contains(doc)) {
0891             documentNameChanged(doc);
0892         } else {
0893             documentOpened(doc);
0894         }
0895     }
0896 }
0897 
0898 void KateFileTreeModel::documentModifiedChanged(KTextEditor::Document *doc)
0899 {
0900     auto it = m_docmap.find(doc);
0901     if (it == m_docmap.end()) {
0902         return;
0903     }
0904 
0905     ProxyItem *item = it.value();
0906 
0907     if (doc->isModified()) {
0908         item->setFlag(ProxyItem::Modified);
0909     } else {
0910         item->clearFlag(ProxyItem::Modified);
0911         item->clearFlag(ProxyItem::ModifiedExternally);
0912         item->clearFlag(ProxyItem::DeletedExternally);
0913     }
0914 
0915     setupIcon(item);
0916 
0917     const QModelIndex idx = createIndex(item->row(), 0, item);
0918     Q_EMIT dataChanged(idx, idx);
0919 }
0920 
0921 void KateFileTreeModel::documentModifiedOnDisc(KTextEditor::Document *doc, bool modified, KTextEditor::Document::ModifiedOnDiskReason reason)
0922 {
0923     Q_UNUSED(modified);
0924     auto it = m_docmap.find(doc);
0925     if (it == m_docmap.end()) {
0926         return;
0927     }
0928 
0929     ProxyItem *item = it.value();
0930 
0931     // This didn't do what I thought it did, on an ignore
0932     // we'd get !modified causing the warning icons to disappear
0933     if (!modified) {
0934         item->clearFlag(ProxyItem::ModifiedExternally);
0935         item->clearFlag(ProxyItem::DeletedExternally);
0936     } else {
0937         if (reason == KTextEditor::Document::OnDiskDeleted) {
0938             item->setFlag(ProxyItem::DeletedExternally);
0939         } else if (reason == KTextEditor::Document::OnDiskModified) {
0940             item->setFlag(ProxyItem::ModifiedExternally);
0941         } else if (reason == KTextEditor::Document::OnDiskCreated) {
0942             // with out this, on "reload" we don't get the icons removed :(
0943             item->clearFlag(ProxyItem::ModifiedExternally);
0944             item->clearFlag(ProxyItem::DeletedExternally);
0945         }
0946     }
0947 
0948     setupIcon(item);
0949 
0950     const QModelIndex idx = createIndex(item->row(), 0, item);
0951     Q_EMIT dataChanged(idx, idx);
0952 }
0953 
0954 void KateFileTreeModel::documentActivated(const KTextEditor::Document *doc)
0955 {
0956     if (!m_shadingEnabled) {
0957         return;
0958     }
0959 
0960     auto it = m_docmap.find(doc);
0961     if (it == m_docmap.end()) {
0962         return;
0963     }
0964 
0965     ProxyItem *item = it.value();
0966 
0967     m_viewHistory.erase(std::remove(m_viewHistory.begin(), m_viewHistory.end(), item), m_viewHistory.end());
0968     m_viewHistory.insert(m_viewHistory.begin(), item);
0969 
0970     while (m_viewHistory.size() > MaxHistoryItems) {
0971         m_viewHistory.pop_back();
0972     }
0973 
0974     updateBackgrounds();
0975 }
0976 
0977 void KateFileTreeModel::documentEdited(const KTextEditor::Document *doc)
0978 {
0979     if (!m_shadingEnabled) {
0980         return;
0981     }
0982 
0983     auto it = m_docmap.find(doc);
0984     if (it == m_docmap.end()) {
0985         return;
0986     }
0987 
0988     ProxyItem *item = it.value();
0989 
0990     m_editHistory.erase(std::remove(m_editHistory.begin(), m_editHistory.end(), item), m_editHistory.end());
0991     m_editHistory.insert(m_editHistory.begin(), item);
0992 
0993     while (m_editHistory.size() > MaxHistoryItems) {
0994         m_editHistory.pop_back();
0995     }
0996 
0997     updateBackgrounds();
0998 }
0999 
1000 class EditViewCount
1001 {
1002 public:
1003     EditViewCount() = default;
1004     int edit = 0;
1005     int view = 0;
1006 };
1007 
1008 void KateFileTreeModel::updateBackgrounds(bool force)
1009 {
1010     if (!m_shadingEnabled && !force) {
1011         return;
1012     }
1013 
1014     std::unordered_map<ProxyItem *, EditViewCount> helper;
1015     helper.reserve(m_viewHistory.size() + m_editHistory.size());
1016 
1017     int i = 1;
1018     for (ProxyItem *item : qAsConst(m_viewHistory)) {
1019         helper[item].view = i;
1020         i++;
1021     }
1022 
1023     i = 1;
1024     for (ProxyItem *item : qAsConst(m_editHistory)) {
1025         helper[item].edit = i;
1026         i++;
1027     }
1028 
1029     std::unordered_map<ProxyItem *, QBrush> oldBrushes = std::move(m_brushes);
1030 
1031     const int hc = (int)m_viewHistory.size();
1032     const int ec = (int)m_editHistory.size();
1033     const QColor base = QPalette().color(QPalette::Base);
1034 
1035     for (const auto &[item, editViewCount] : helper) {
1036         QColor shade(m_viewShade);
1037         QColor eshade(m_editShade);
1038 
1039         if (editViewCount.edit > 0) {
1040             int v = hc - editViewCount.view;
1041             int e = ec - editViewCount.edit + 1;
1042 
1043             e = e * e;
1044 
1045             const int n = qMax(v + e, 1);
1046 
1047             shade.setRgb(((shade.red() * v) + (eshade.red() * e)) / n,
1048                          ((shade.green() * v) + (eshade.green() * e)) / n,
1049                          ((shade.blue() * v) + (eshade.blue() * e)) / n);
1050         }
1051 
1052         // blend in the shade color; latest is most colored.
1053         const double t = double(hc - editViewCount.view + 1) / double(hc);
1054 
1055         m_brushes[item] = QBrush(KColorUtils::mix(base, shade, t));
1056     }
1057 
1058     for (const auto &[item, brush] : m_brushes) {
1059         oldBrushes.erase(item);
1060         const QModelIndex idx = createIndex(item->row(), 0, item);
1061         dataChanged(idx, idx);
1062     }
1063 
1064     for (const auto &[item, brush] : oldBrushes) {
1065         const QModelIndex idx = createIndex(item->row(), 0, item);
1066         dataChanged(idx, idx);
1067     }
1068 }
1069 
1070 void KateFileTreeModel::handleEmptyParents(ProxyItemDir *item)
1071 {
1072     Q_ASSERT(item != nullptr);
1073 
1074     if (!item->parent()) {
1075         return;
1076     }
1077 
1078     ProxyItemDir *parent = item->parent();
1079 
1080     while (parent) {
1081         if (!item->childCount()) {
1082             const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent);
1083             beginRemoveRows(parent_index, item->row(), item->row());
1084             parent->removeChild(item);
1085             endRemoveRows();
1086             delete item;
1087         } else {
1088             // breakout early, if this node isn't empty, theres no use in checking its parents
1089             return;
1090         }
1091 
1092         item = parent;
1093         parent = item->parent();
1094     }
1095 }
1096 
1097 void KateFileTreeModel::documentClosed(KTextEditor::Document *doc)
1098 {
1099     disconnect(doc, &KTextEditor::Document::documentNameChanged, this, &KateFileTreeModel::documentNameChanged);
1100     disconnect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateFileTreeModel::documentNameChanged);
1101     disconnect(doc, &KTextEditor::Document::modifiedChanged, this, &KateFileTreeModel::documentModifiedChanged);
1102     disconnect(doc, &KTextEditor::Document::modifiedOnDisk, this, &KateFileTreeModel::documentModifiedOnDisc);
1103 
1104     auto it = m_docmap.find(doc);
1105     if (it == m_docmap.end()) {
1106         return;
1107     }
1108 
1109     if (m_shadingEnabled) {
1110         ProxyItem *toRemove = it.value();
1111         m_brushes.erase(toRemove);
1112         m_viewHistory.erase(std::remove(m_viewHistory.begin(), m_viewHistory.end(), toRemove), m_viewHistory.end());
1113         m_editHistory.erase(std::remove(m_editHistory.begin(), m_editHistory.end(), toRemove), m_editHistory.end());
1114     }
1115 
1116     ProxyItem *node = it.value();
1117     ProxyItemDir *parent = node->parent();
1118 
1119     const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent);
1120     beginRemoveRows(parent_index, node->row(), node->row());
1121     node->parent()->removeChild(node);
1122     endRemoveRows();
1123 
1124     delete node;
1125     handleEmptyParents(parent);
1126 
1127     m_docmap.erase(it);
1128 }
1129 
1130 void KateFileTreeModel::documentNameChanged(KTextEditor::Document *doc)
1131 {
1132     auto it = m_docmap.find(doc);
1133     if (it == m_docmap.end()) {
1134         return;
1135     }
1136 
1137     handleNameChange(it.value());
1138     Q_EMIT triggerViewChangeAfterNameChange(); // FIXME: heh, non-standard signal?
1139 }
1140 
1141 ProxyItemDir *KateFileTreeModel::findRootNode(const QString &name, const int r) const
1142 {
1143     const auto rootChildren = m_root->children();
1144     for (ProxyItem *item : rootChildren) {
1145         if (!item->flag(ProxyItem::Dir)) {
1146             continue;
1147         }
1148 
1149         // make sure we're actually matching against the right dir,
1150         // previously the check below would match /foo/xy against /foo/x
1151         // and return /foo/x rather than /foo/xy
1152         // this seems a bit hackish, but is the simplest way to solve the
1153         // current issue.
1154         QString path = item->path().section(QLatin1Char('/'), 0, -r) + QLatin1Char('/');
1155 
1156         if (name.startsWith(path)) {
1157             return static_cast<ProxyItemDir *>(item);
1158         }
1159     }
1160 
1161     return nullptr;
1162 }
1163 
1164 ProxyItemDir *KateFileTreeModel::findChildNode(const ProxyItemDir *parent, const QString &name)
1165 {
1166     Q_ASSERT(parent != nullptr);
1167     Q_ASSERT(!name.isEmpty());
1168 
1169     if (!parent->childCount()) {
1170         return nullptr;
1171     }
1172 
1173     const auto children = parent->children();
1174     for (ProxyItem *item : children) {
1175         if (!item->flag(ProxyItem::Dir)) {
1176             continue;
1177         }
1178 
1179         if (item->display() == name) {
1180             return static_cast<ProxyItemDir *>(item);
1181         }
1182     }
1183 
1184     return nullptr;
1185 }
1186 
1187 void KateFileTreeModel::insertItemInto(ProxyItemDir *root, ProxyItem *item, bool move, ProxyItemDir **moveDest)
1188 {
1189     Q_ASSERT(root != nullptr);
1190     Q_ASSERT(item != nullptr);
1191 
1192     QString tail = item->path();
1193     tail.remove(0, root->path().length());
1194     QStringList parts = tail.split(QLatin1Char('/'), Qt::SkipEmptyParts);
1195     ProxyItemDir *ptr = root;
1196     QStringList current_parts;
1197     current_parts.append(root->path());
1198 
1199     // seems this can be empty, see bug 286191
1200     if (!parts.isEmpty()) {
1201         parts.pop_back();
1202     }
1203 
1204     for (const QString &part : qAsConst(parts)) {
1205         current_parts.append(part);
1206         ProxyItemDir *find = findChildNode(ptr, part);
1207         if (!find) {
1208             // One of child's parent dir didn't exist, create it
1209             // This is like you have a folder:
1210             // folder/dir/dir2/a.c
1211             // folder/b.c
1212             // if only a.c is opened, then we only show dir2,
1213             // but if you open b.c, we now need to create a new root i.e., "folder"
1214             // and since a.c lives in a child dir, we create "dir" as well.
1215             const QString new_name = current_parts.join(QLatin1Char('/'));
1216             const QModelIndex parent_index = (ptr == m_root) ? QModelIndex() : createIndex(ptr->row(), 0, ptr);
1217             beginInsertRows(parent_index, ptr->childCount(), ptr->childCount());
1218             ptr = new ProxyItemDir(new_name, ptr);
1219             endInsertRows();
1220         } else {
1221             ptr = find;
1222         }
1223     }
1224 
1225     if (!move) {
1226         // We are not moving rows, this is all new stuff
1227         const QModelIndex parent_index = (ptr == m_root) ? QModelIndex() : createIndex(ptr->row(), 0, ptr);
1228         beginInsertRows(parent_index, ptr->childCount(), ptr->childCount());
1229         ptr->addChild(item);
1230         endInsertRows();
1231     } else {
1232         // We are moving
1233         *moveDest = ptr;
1234     }
1235 }
1236 
1237 void KateFileTreeModel::handleInsert(ProxyItem *item)
1238 {
1239     Q_ASSERT(item != nullptr);
1240 
1241     if (m_listMode || item->flag(ProxyItem::Empty)) {
1242         beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount());
1243         m_root->addChild(item);
1244         endInsertRows();
1245         return;
1246     }
1247 
1248     // case (item.path > root.path)
1249     ProxyItemDir *root = findRootNode(item->path());
1250     if (root) {
1251         insertItemInto(root, item);
1252         return;
1253     }
1254 
1255     // trim off trailing file and dir
1256     QString base = item->path().section(QLatin1Char('/'), 0, -2);
1257 
1258     // create new root
1259     ProxyItemDir *new_root = new ProxyItemDir(base);
1260     new_root->setHost(item->host());
1261 
1262     // add new root to m_root
1263     beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount());
1264     m_root->addChild(new_root);
1265     endInsertRows();
1266 
1267     // same fix as in findRootNode, try to match a full dir, instead of a partial path
1268     base += QLatin1Char('/');
1269 
1270     // try and merge existing roots with the new root node (new_root.path < root.path)
1271     const auto rootChildren = m_root->children();
1272     for (ProxyItem *root : rootChildren) {
1273         if (root == new_root || !root->flag(ProxyItem::Dir)) {
1274             continue;
1275         }
1276 
1277         if (root->path().startsWith(base)) {
1278             // We can't move directly because this items parent directories might not be in the model yet
1279             // so check and insert them first. Then find out where we need to move
1280             ProxyItemDir *moveDest = nullptr;
1281             insertItemInto(new_root, root, true, &moveDest);
1282 
1283             const QModelIndex destParent = (moveDest == m_root) ? QModelIndex() : createIndex(moveDest->row(), 0, moveDest);
1284             // We are moving from topLevel root to maybe some child node
1285             // We MUST move, otherwise if "root" was expanded, it will be collapsed if we did a remove + insert instead.
1286             // This is the reason for added complexity in insertItemInto
1287             beginMoveRows(QModelIndex(), root->row(), root->row(), destParent, moveDest->childCount());
1288             m_root->removeChild(root);
1289             moveDest->addChild(root);
1290             endMoveRows();
1291         }
1292     }
1293 
1294     // add item to new root
1295     // have to call begin/endInsertRows here, or the new item won't show up.
1296     const QModelIndex new_root_index = createIndex(new_root->row(), 0, new_root);
1297     beginInsertRows(new_root_index, new_root->childCount(), new_root->childCount());
1298     new_root->addChild(item);
1299     endInsertRows();
1300 
1301     handleDuplicitRootDisplay(new_root);
1302 }
1303 
1304 void KateFileTreeModel::handleDuplicitRootDisplay(ProxyItemDir *init)
1305 {
1306     QStack<ProxyItemDir *> rootsToCheck;
1307     rootsToCheck.push(init);
1308 
1309     // make sure the roots don't match (recursively)
1310     while (!rootsToCheck.isEmpty()) {
1311         ProxyItemDir *check_root = rootsToCheck.pop();
1312 
1313         if (check_root->parent() != m_root) {
1314             continue;
1315         }
1316 
1317         const auto rootChildren = m_root->children();
1318         for (ProxyItem *root : rootChildren) {
1319             if (root == check_root || !root->flag(ProxyItem::Dir)) {
1320                 continue;
1321             }
1322 
1323             if (check_root->display() == root->display()) {
1324                 bool changed = false;
1325                 bool check_root_removed = false;
1326 
1327                 const QString rdir = root->path().section(QLatin1Char('/'), 0, -2);
1328                 if (!rdir.isEmpty()) {
1329                     beginRemoveRows(QModelIndex(), root->row(), root->row());
1330                     m_root->removeChild(root);
1331                     endRemoveRows();
1332 
1333                     ProxyItemDir *irdir = new ProxyItemDir(rdir);
1334                     beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount());
1335                     m_root->addChild(irdir);
1336                     endInsertRows();
1337 
1338                     insertItemInto(irdir, root);
1339 
1340                     const auto children = m_root->children();
1341                     for (ProxyItem *node : children) {
1342                         if (node == irdir || !root->flag(ProxyItem::Dir)) {
1343                             continue;
1344                         }
1345 
1346                         const QString xy = rdir + QLatin1Char('/');
1347                         if (node->path().startsWith(xy)) {
1348                             beginRemoveRows(QModelIndex(), node->row(), node->row());
1349                             // check_root_removed must be sticky
1350                             check_root_removed = check_root_removed || (node == check_root);
1351                             m_root->removeChild(node);
1352                             endRemoveRows();
1353                             insertItemInto(irdir, node);
1354                         }
1355                     }
1356 
1357                     rootsToCheck.push(irdir);
1358                     changed = true;
1359                 }
1360 
1361                 if (!check_root_removed) {
1362                     const QString nrdir = check_root->path().section(QLatin1Char('/'), 0, -2);
1363                     if (!nrdir.isEmpty()) {
1364                         beginRemoveRows(QModelIndex(), check_root->row(), check_root->row());
1365                         m_root->removeChild(check_root);
1366                         endRemoveRows();
1367 
1368                         ProxyItemDir *irdir = new ProxyItemDir(nrdir);
1369                         beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount());
1370                         m_root->addChild(irdir);
1371                         endInsertRows();
1372 
1373                         insertItemInto(irdir, check_root);
1374 
1375                         rootsToCheck.push(irdir);
1376                         changed = true;
1377                     }
1378                 }
1379 
1380                 if (changed) {
1381                     break; // restart
1382                 }
1383             }
1384         } // for root
1385     }
1386 }
1387 
1388 void KateFileTreeModel::handleNameChange(ProxyItem *item)
1389 {
1390     Q_ASSERT(item != nullptr);
1391     Q_ASSERT(item->parent());
1392 
1393     updateItemPathAndHost(item);
1394 
1395     if (m_listMode) {
1396         const QModelIndex idx = createIndex(item->row(), 0, item);
1397         setupIcon(item);
1398         Q_EMIT dataChanged(idx, idx);
1399         return;
1400     }
1401 
1402     // in either case (new/change) we want to remove the item from its parent
1403     ProxyItemDir *parent = item->parent();
1404 
1405     const QModelIndex parent_index = (parent == m_root) ? QModelIndex() : createIndex(parent->row(), 0, parent);
1406     beginRemoveRows(parent_index, item->row(), item->row());
1407     parent->removeChild(item);
1408     endRemoveRows();
1409 
1410     handleEmptyParents(parent);
1411 
1412     // clear all but Empty flag
1413     if (item->flag(ProxyItem::Empty)) {
1414         item->setFlags(ProxyItem::Empty);
1415     } else {
1416         item->setFlags(ProxyItem::None);
1417     }
1418 
1419     setupIcon(item);
1420     handleInsert(item);
1421 }
1422 
1423 void KateFileTreeModel::updateItemPathAndHost(ProxyItem *item)
1424 {
1425     const KTextEditor::Document *doc = item->doc();
1426     Q_ASSERT(doc); // this method should not be called at directory items
1427 
1428     QString path = doc->url().path();
1429     QString host;
1430     if (doc->url().isEmpty()) {
1431         path = doc->documentName();
1432         item->setFlag(ProxyItem::Empty);
1433     } else {
1434         item->clearFlag(ProxyItem::Empty);
1435         host = doc->url().host();
1436         if (!host.isEmpty()) {
1437             path = QStringLiteral("[%1]%2").arg(host, path);
1438         }
1439     }
1440 
1441     // for some reason we get useless name changes [should be fixed in 5.0]
1442     if (item->path() == path) {
1443         return;
1444     }
1445 
1446     item->setPath(path);
1447     item->setHost(host);
1448 }
1449 
1450 void KateFileTreeModel::setupIcon(ProxyItem *item)
1451 {
1452     Q_ASSERT(item != nullptr);
1453     Q_ASSERT(item->doc() != nullptr);
1454 
1455     // use common method as e.g. in tabbar, too
1456     item->setIcon(Utils::iconForDocument(item->doc()));
1457 }
1458 
1459 void KateFileTreeModel::resetHistory()
1460 {
1461     QSet<ProxyItem *> list{m_viewHistory.begin(), m_viewHistory.end()};
1462     list += QSet<ProxyItem *>{m_editHistory.begin(), m_editHistory.end()};
1463 
1464     m_viewHistory.clear();
1465     m_editHistory.clear();
1466     m_brushes.clear();
1467 
1468     for (ProxyItem *item : qAsConst(list)) {
1469         QModelIndex idx = createIndex(item->row(), 0, item);
1470         dataChanged(idx, idx, QList<int>(1, Qt::BackgroundRole));
1471     }
1472 }
1473 
1474 void KateFileTreeModel::addWidget(QWidget *w)
1475 {
1476     if (!w) {
1477         return;
1478     }
1479 
1480     const QModelIndex parentIdx = createIndex(m_widgetsRoot->row(), 0, m_widgetsRoot);
1481     beginInsertRows(parentIdx, m_widgetsRoot->childCount(), m_widgetsRoot->childCount());
1482     auto item = new ProxyItem(w->windowTitle());
1483     item->setFlag(ProxyItem::Widget);
1484     item->setIcon(w->windowIcon());
1485     item->setWidget(w);
1486     m_widgetsRoot->addChild(item);
1487     endInsertRows();
1488 }
1489 
1490 void KateFileTreeModel::removeWidget(QWidget *w)
1491 {
1492     ProxyItem *item = nullptr;
1493     const auto items = m_widgetsRoot->children();
1494     for (auto *it : items) {
1495         if (it->widget() == w) {
1496             item = it;
1497             break;
1498         }
1499     }
1500     if (!item) {
1501         return;
1502     }
1503 
1504     const QModelIndex parentIdx = createIndex(m_widgetsRoot->row(), 0, m_widgetsRoot);
1505     beginRemoveRows(parentIdx, item->row(), item->row());
1506     m_widgetsRoot->removeChild(item);
1507     endRemoveRows();
1508     delete item;
1509 }
1510 
1511 #include "katefiletreemodel.moc"
1512 #include "moc_katefiletreemodel.cpp"