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"