diff --git a/src/mode/katemodemanager.cpp b/src/mode/katemodemanager.cpp --- a/src/mode/katemodemanager.cpp +++ b/src/mode/katemodemanager.cpp @@ -21,6 +21,7 @@ // BEGIN Includes #include "katemodemanager.h" #include "katewildcardmatcher.h" +#include "katemodemenulist.h" #include "kateconfig.h" #include "katedocument.h" @@ -70,6 +71,7 @@ QStringList g(config.groupList()); + KateFileType *normalType = nullptr; qDeleteAll(m_types); m_types.clear(); m_name2Type.clear(); @@ -79,7 +81,6 @@ KateFileType *type = new KateFileType(); type->number = z; type->name = g[z]; - type->section = cg.readEntry(QStringLiteral("Section")); type->wildcards = cg.readXdgListEntry(QStringLiteral("Wildcards")); type->mimetypes = cg.readXdgListEntry(QStringLiteral("Mimetypes")); type->priority = cg.readEntry(QStringLiteral("Priority"), 0); @@ -90,19 +91,31 @@ // only for generated types... type->hlGenerated = cg.readEntry(QStringLiteral("Highlighting Generated"), false); - type->version = cg.readEntry(QStringLiteral("Highlighting Version")); - // insert into the list + hash... - m_types.append(type); + // the "Normal" mode will be added later + if (type->name == QLatin1String("Normal")) { + if (!normalType) { + normalType = type; + } + } else { + type->section = cg.readEntry(QStringLiteral("Section")); + type->version = cg.readEntry(QStringLiteral("Highlighting Version")); + } + + // insert into the hash... + // NOTE: "katemoderc" could have modes that do not exist or are invalid (for example, custom + // XML files that were deleted or renamed), so they will be added to the list "m_types" later m_name2Type.insert(type->name, type); } // try if the hl stuff is up to date... const auto modes = KateHlManager::self()->modeList(); for (int i = 0; i < modes.size(); ++i) { // filter out hidden languages; and - // filter out "None" hl, we add that later as "normal" mode - if (modes[i].isHidden() || modes[i].name() == QLatin1String("None")) { + // filter out "None" hl, we add that later as "normal" mode. + // hl with empty names will also be filtered. The + // KTextEditor::DocumentPrivate::updateFileType() function considers hl with empty names as invalid. + if (modes[i].isHidden() || modes[i].name().isEmpty() || modes[i].name() == QLatin1String("None")) { continue; } @@ -115,9 +128,10 @@ type = new KateFileType(); type->name = modes[i].name(); type->priority = 0; - m_types.append(type); m_name2Type.insert(type->name, type); } + // only the types that exist or are valid are added + m_types.append(type); if (newType || type->version != QString::number(modes[i].version())) { type->name = modes[i].name(); @@ -139,15 +153,20 @@ std::sort(m_types.begin(), m_types.end(), compareKateFileType); // add the none type... - KateFileType *t = new KateFileType(); + if (!normalType) { + normalType = new KateFileType(); + } // marked by hlGenerated - t->name = QStringLiteral("Normal"); - t->translatedName = i18n("Normal"); - t->hl = QStringLiteral("None"); - t->hlGenerated = true; + normalType->name = QStringLiteral("Normal"); + normalType->translatedName = i18n("Normal"); + normalType->hl = QStringLiteral("None"); + normalType->hlGenerated = true; + + m_types.prepend(normalType); - m_types.prepend(t); + // update the mode menu of the status bar + KateModeMenuList::reloadAll(); } // diff --git a/src/mode/katemodemenu.cpp b/src/mode/katemodemenu.cpp --- a/src/mode/katemodemenu.cpp +++ b/src/mode/katemodemenu.cpp @@ -60,6 +60,9 @@ QString hlName = KTextEditor::EditorPrivate::self()->modeManager()->list().at(z)->nameTranslated(); QString hlSection = KTextEditor::EditorPrivate::self()->modeManager()->list().at(z)->sectionTranslated(); + if (hlName.isEmpty()) { + continue; + } if (!hlSection.isEmpty() && !names.contains(hlName)) { if (!subMenusName.contains(hlSection)) { subMenusName << hlSection; diff --git a/src/mode/katemodemenulist.h b/src/mode/katemodemenulist.h --- a/src/mode/katemodemenulist.h +++ b/src/mode/katemodemenulist.h @@ -137,27 +137,32 @@ { init(Bottom); } - ~KateModeMenuList() - { - } + ~KateModeMenuList(); + + /** + * Reload items from all instances. + */ + static void reloadAll(); /** * Update the selected item in the list widget, but without changing * the syntax highlighting in the document. * This is useful for updating this menu, when changing the syntax highlighting * from another menu, or from an external one. This doesn't hide or show the menu. * @param nameMode Raw name of the syntax highlight definition. If it's empty, * the "Normal" mode will be used. + * @return True if @p nameMode exists and is selected. */ - void selectHighlightingFromExternal(const QString &nameMode); + bool selectHighlightingFromExternal(const QString &nameMode); /** * Update the selected item in the list widget, but without changing * the syntax highlighting in the document. This doesn't hide or show the menu. * The menu is kept updated according to the active syntax highlighting, * obtained from the KTextEditor::DocumentPrivate class. + * @return True if the item is selected correctly. * @see KTextEditor::DocumentPrivate::fileType() */ - void selectHighlightingFromExternal(); + bool selectHighlightingFromExternal(); /** * Set the button that shows this menu. It allows to update the label @@ -259,14 +264,14 @@ /** * Load message when the list is empty in the search. */ - inline void loadEmptyMsg(); + void loadEmptyMsg(); AutoScroll m_autoScroll = ScrollToSelectedItem; AlignmentHButton m_positionX; AlignmentVButton m_positionY; AutoUpdateTextButton m_autoUpdateTextButton; - QPushButton *m_pushButton = nullptr; + QPointer m_pushButton = nullptr; QLabel *m_emptyListMsg = nullptr; QGridLayout *m_layoutList = nullptr; QScrollBar *m_scroll = nullptr; @@ -294,6 +299,8 @@ QPointer m_doc; + static QSet m_instances; + private Q_SLOTS: /** * Action when selecting a item in the list. This also applies @@ -358,6 +365,7 @@ { selectionModel()->setCurrentIndex(m_parentMenu->m_model->index(rowItem, 0), QItemSelectionModel::ClearAndSelect); } + inline QStandardItem *currentItem() const { return m_parentMenu->m_model->item(currentIndex().row(), 0); @@ -368,6 +376,12 @@ scrollTo(m_parentMenu->m_model->index(rowItem, 0), hint); } + inline void scrollToFirstItem() + { + setCurrentItem(1); + scrollToTop(); + } + protected: /** * Override from QListView. @@ -479,7 +493,11 @@ */ static const int m_searchDelay = 170; + /** + * This prevents auto-scrolling when the search is kept clean. + */ bool m_bSearchStateAutoScroll = false; + QString m_search = QString(); int m_queuedSearches = 0; Qt::CaseSensitivity m_caseSensitivity = Qt::CaseInsensitive; @@ -493,6 +511,7 @@ KateModeMenuList *m_parentMenu = nullptr; friend Factory; + friend void KateModeMenuList::reloadAll(); protected: /** diff --git a/src/mode/katemodemenulist.cpp b/src/mode/katemodemenulist.cpp --- a/src/mode/katemodemenulist.cpp +++ b/src/mode/katemodemenulist.cpp @@ -63,6 +63,13 @@ } } +QSet KateModeMenuList::m_instances; + +KateModeMenuList::~KateModeMenuList() +{ + m_instances.remove(this); +} + void KateModeMenuList::init(const SearchBarPosition searchBarPos) { /* @@ -139,8 +146,12 @@ setSizeList(menuHeight, menuWidth); // Data model (items). + // couple model to view to let it be deleted with the view + m_model = new QStandardItemModel(0, 0, m_list); loadHighlightingModel(); + m_instances.insert(this); + /* * Search bar widget. */ @@ -212,10 +223,38 @@ connect(m_list, &KateModeMenuListData::ListView::clicked, this, &KateModeMenuList::selectHighlighting); } +void KateModeMenuList::reloadAll() +{ + for (auto *menu : m_instances) { + const QString searchText = menu->m_searchBar->text().trimmed(); + menu->m_searchBar->m_bestResults.clear(); + if (!menu->isHidden()) { + menu->hide(); + } + /* + * Clear model. + * NOTE: This deletes the objects of item and widget indexed to items. + * That is, the QLabel & QFrame objects of the section titles are also deleted. + * See: QAbstractItemView::setIndexWidget(), QObject::deleteLater() + */ + menu->m_model->clear(); + menu->m_list->selectionModel()->clear(); + menu->m_selectedItem = nullptr; + + menu->loadHighlightingModel(); + + // Restore search text, if there is. + menu->m_searchBar->m_bSearchStateAutoScroll = false; + if (!searchText.isEmpty()) { + menu->selectHighlightingFromExternal(); + menu->m_searchBar->updateSearch(searchText); + menu->m_searchBar->setText(searchText); + } + } +} + void KateModeMenuList::loadHighlightingModel() { - // couple model to view to let it be deleted with the view - m_model = new QStandardItemModel(0, 0, m_list); m_list->setModel(m_model); QString *prevHlSection = nullptr; @@ -245,6 +284,10 @@ * and the attribute "translatedSection" isn't empty if "section" has a value. */ for (auto *hl : KTextEditor::EditorPrivate::self()->modeManager()->list()) { + if (hl->name.isEmpty()) { + continue; + } + // Detects a new section. if (!hl->translatedSection.isEmpty() && (prevHlSection == nullptr || hl->translatedSection != *prevHlSection)) { createSectionList(hl->sectionTranslated(), transparentBrush); @@ -374,12 +417,11 @@ void KateModeMenuList::autoScroll() { - if (m_autoScroll == ScrollToSelectedItem) { + if (m_selectedItem && m_autoScroll == ScrollToSelectedItem) { m_list->setCurrentItem(m_selectedItem->row()); m_list->scrollToItem(m_selectedItem->row(), QAbstractItemView::PositionAtCenter); } else { - m_list->setCurrentItem(0); - m_list->scrollToTop(); + m_list->scrollToFirstItem(); } } @@ -433,7 +475,7 @@ // Select text from the search bar if (!m_searchBar->text().isEmpty()) { - if (m_searchBar->text().simplified().isEmpty()) { + if (m_searchBar->text().trimmed().isEmpty()) { m_searchBar->clear(); } else { m_searchBar->selectAll(); @@ -450,7 +492,16 @@ // First show or if an external changed the current syntax highlighting. if (!m_selectedItem || (m_selectedItem->hasMode() && m_selectedItem->getMode()->name != doc->fileType())) { - selectHighlightingFromExternal(doc->fileType()); + if (!selectHighlightingFromExternal(doc->fileType())) { + // Strange case: if the current syntax highlighting does not exist in the list. + if (m_selectedItem) { + m_selectedItem->setIcon(m_emptyIcon); + } + if ((m_selectedItem || !m_list->currentItem()) && m_searchBar->text().isEmpty()) { + m_list->scrollToFirstItem(); + } + m_selectedItem = nullptr; + } } } @@ -474,7 +525,7 @@ void KateModeMenuList::selectHighlightingSetVisibility(QStandardItem *pItem, const bool bHideMenu) { - if (!pItem->isSelectable() || !pItem->isEnabled()) { + if (!pItem || !pItem->isSelectable() || !pItem->isEnabled()) { return; } @@ -499,7 +550,7 @@ selectHighlightingSetVisibility(m_model->item(index.row(), 0), true); } -void KateModeMenuList::selectHighlightingFromExternal(const QString &nameMode) +bool KateModeMenuList::selectHighlightingFromExternal(const QString &nameMode) { for (int i = 0; i < m_model->rowCount(); ++i) { KateModeMenuListData::ListItem *item = static_cast(m_model->item(i, 0)); @@ -523,20 +574,21 @@ m_list->scrollToItem(i); } else { // autoScroll() - m_list->setCurrentItem(0); - m_list->scrollToTop(); + m_list->scrollToFirstItem(); } - return; + return true; } } + return false; } -void KateModeMenuList::selectHighlightingFromExternal() +bool KateModeMenuList::selectHighlightingFromExternal() { KTextEditor::DocumentPrivate *doc = m_doc; if (doc) { - selectHighlightingFromExternal(doc->fileType()); + return selectHighlightingFromExternal(doc->fileType()); } + return false; } void KateModeMenuList::loadEmptyMsg() diff --git a/src/syntax/katehighlightmenu.cpp b/src/syntax/katehighlightmenu.cpp --- a/src/syntax/katehighlightmenu.cpp +++ b/src/syntax/katehighlightmenu.cpp @@ -56,7 +56,7 @@ if (hlName == QLatin1String("None")) hlName = i18n("None"); - if (!hl.isHidden()) { + if (!hl.isHidden() && !hlName.isEmpty()) { if (!hlSection.isEmpty() && !names.contains(hlName)) { if (!subMenusName.contains(hlSection)) { subMenusName << hlSection;