File indexing completed on 2024-04-28 15:30:33

0001 /*
0002     SPDX-FileCopyrightText: 2019-2020 Nibaldo González S. <nibgonz@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "katemodemenulist.h"
0007 
0008 #include "kateconfig.h"
0009 #include "katedocument.h"
0010 #include "kateglobal.h"
0011 #include "katemodemanager.h"
0012 #include "katepartdebug.h"
0013 #include "katesyntaxmanager.h"
0014 #include "kateview.h"
0015 
0016 #include <QAbstractItemView>
0017 #include <QApplication>
0018 #include <QFrame>
0019 #include <QHBoxLayout>
0020 #include <QVBoxLayout>
0021 #include <QWidgetAction>
0022 
0023 #include <KLocalizedString>
0024 
0025 namespace
0026 {
0027 /**
0028  * Detect words delimiters:
0029  *      ! " # $ % & ' ( ) * + , - . / : ;
0030  *      < = > ? [ \ ] ^ ` { | } ~ « »
0031  */
0032 static bool isDelimiter(const ushort c)
0033 {
0034     return (c <= 126 && c >= 33 && (c >= 123 || c <= 47 || (c <= 96 && c >= 58 && c != 95 && (c >= 91 || c <= 63)))) || c == 171 || c == 187;
0035 }
0036 
0037 /**
0038  * Overlay scroll bar on the list according to the operating system
0039  * and/or the desktop environment. In some desktop themes the scroll bar
0040  * isn't transparent, so it's better not to overlap it on the list.
0041  * NOTE: Currently, in the Breeze theme, the scroll bar does not overlap
0042  * the content. See: https://phabricator.kde.org/T9126
0043  */
0044 inline static bool overlapScrollBar()
0045 {
0046     return false;
0047 }
0048 }
0049 
0050 void KateModeMenuList::init()
0051 {
0052     connect(this, &QMenu::aboutToShow, this, &KateModeMenuList::onAboutToShowMenu);
0053 }
0054 
0055 void KateModeMenuList::onAboutToShowMenu()
0056 {
0057     if (m_initialized) {
0058         return;
0059     }
0060     /*
0061      * Fix font size & font style: display the font correctly when changing it from the
0062      * KDE Plasma preferences. For example, the font type "Menu" is displayed, but "font()"
0063      * and "fontMetrics()" return the font type "General". Therefore, this overwrites the
0064      * "General" font. This makes it possible to correctly apply word wrapping on items,
0065      * when changing the font or its size.
0066      */
0067     QFont font = this->font();
0068     font.setFamily(font.family());
0069     font.setStyle(font.style());
0070     font.setStyleName(font.styleName());
0071     font.setBold(font.bold());
0072     font.setItalic(font.italic());
0073     font.setUnderline(font.underline());
0074     font.setStrikeOut(font.strikeOut());
0075     font.setPointSize(font.pointSize());
0076     setFont(font);
0077 
0078     /*
0079      * Calculate the size of the list and the checkbox icon (in pixels) according
0080      * to the font size. From font 12pt to 26pt increase the list size.
0081      */
0082     int menuWidth = 266;
0083     int menuHeight = 428;
0084     const int fontSize = font.pointSize();
0085     if (fontSize >= 12) {
0086         const int increaseSize = (fontSize - 11) * 10;
0087         if (increaseSize >= 150) { // Font size: 26pt
0088             menuWidth += 150;
0089             menuHeight += 150;
0090         } else {
0091             menuWidth += increaseSize;
0092             menuHeight += increaseSize;
0093         }
0094 
0095         if (fontSize >= 22) {
0096             m_iconSize = 32;
0097         } else if (fontSize >= 18) {
0098             m_iconSize = 24;
0099         } else if (fontSize >= 14) {
0100             m_iconSize = 22;
0101         } else if (fontSize >= 12) {
0102             m_iconSize = 18;
0103         }
0104     }
0105 
0106     // Create list and search bar
0107     m_list = KateModeMenuListData::Factory::createListView(this);
0108     m_searchBar = KateModeMenuListData::Factory::createSearchLine(this);
0109 
0110     // Empty icon for items.
0111     QPixmap emptyIconPixmap(m_iconSize, m_iconSize);
0112     emptyIconPixmap.fill(Qt::transparent);
0113     m_emptyIcon = QIcon(emptyIconPixmap);
0114 
0115     /*
0116      * Load list widget, scroll bar and items.
0117      */
0118     if (overlapScrollBar()) {
0119         // The vertical scroll bar will be added in another layout
0120         m_scroll = new QScrollBar(Qt::Vertical, this);
0121         m_list->setVerticalScrollBar(m_scroll);
0122         m_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0123         m_list->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0124     } else {
0125         m_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0126         m_list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0127     }
0128     m_list->setIconSize(QSize(m_iconSize, m_iconSize));
0129     m_list->setResizeMode(QListView::Adjust);
0130     // Size of the list widget and search bar.
0131     setSizeList(menuHeight, menuWidth);
0132 
0133     // Data model (items).
0134     // couple model to view to let it be deleted with the view
0135     m_model = new QStandardItemModel(0, 0, m_list);
0136     loadHighlightingModel();
0137 
0138     /*
0139      * Search bar widget.
0140      */
0141     m_searchBar->setPlaceholderText(i18nc("Placeholder in search bar", "Search..."));
0142     m_searchBar->setToolTip(i18nc("ToolTip of the search bar of modes of syntax highlighting",
0143                                   "Search for syntax highlighting modes by language name or file extension (for example, C++ or .cpp)"));
0144     m_searchBar->setMaxLength(200);
0145 
0146     m_list->setFocusProxy(m_searchBar);
0147 
0148     /*
0149      * Set layouts and widgets.
0150      * container (QWidget)
0151      * └── layoutContainer (QVBoxLayout)
0152      *      ├── m_layoutList (QGridLayout)
0153      *      │   ├── m_list (ListView)
0154      *      │   ├── layoutScrollBar (QHBoxLayout) --> m_scroll (QScrollBar)
0155      *      │   └── m_emptyListMsg (QLabel)
0156      *      └── layoutSearchBar (QHBoxLayout) --> m_searchBar (SearchLine)
0157      */
0158     QWidget *container = new QWidget(this);
0159     QVBoxLayout *layoutContainer = new QVBoxLayout(container);
0160     m_layoutList = new QGridLayout();
0161     QHBoxLayout *layoutSearchBar = new QHBoxLayout();
0162 
0163     m_layoutList->addWidget(m_list, 0, 0, Qt::AlignLeft);
0164 
0165     // Add scroll bar and set margin.
0166     // Overlap scroll bar above the list widget.
0167     if (overlapScrollBar()) {
0168         QHBoxLayout *layoutScrollBar = new QHBoxLayout();
0169         layoutScrollBar->addWidget(m_scroll);
0170         layoutScrollBar->setContentsMargins(1, 2, 2, 2); // ScrollBar Margin = 2, Also see: KateModeMenuListData::ListView::getContentWidth()
0171         m_layoutList->addLayout(layoutScrollBar, 0, 0, Qt::AlignRight);
0172     }
0173 
0174     layoutSearchBar->addWidget(m_searchBar);
0175     layoutContainer->addLayout(m_layoutList);
0176     layoutContainer->addLayout(layoutSearchBar);
0177 
0178     QWidgetAction *widAct = new QWidgetAction(this);
0179     widAct->setDefaultWidget(container);
0180     addAction(widAct);
0181 
0182     /*
0183      * Detect selected item with one click.
0184      * This also applies to double-clicks.
0185      */
0186     connect(m_list, &KateModeMenuListData::ListView::clicked, this, &KateModeMenuList::selectHighlighting);
0187 
0188     m_initialized = true;
0189 }
0190 
0191 void KateModeMenuList::reloadItems()
0192 {
0193     // We aren't initialized, nothing to reload
0194     if (!m_initialized) {
0195         return;
0196     }
0197 
0198     const QString searchText = m_searchBar->text().trimmed();
0199     m_searchBar->m_bestResults.clear();
0200     if (!isHidden()) {
0201         hide();
0202     }
0203     /*
0204      * Clear model.
0205      * NOTE: This deletes the item objects and widgets indexed to items.
0206      * That is, the QLabel & QFrame objects of the section titles are also deleted.
0207      * See: QAbstractItemView::setIndexWidget(), QObject::deleteLater()
0208      */
0209     m_model->clear();
0210     m_list->selectionModel()->clear();
0211     m_selectedItem = nullptr;
0212 
0213     loadHighlightingModel();
0214 
0215     // Restore search text, if there is.
0216     m_searchBar->m_bSearchStateAutoScroll = false;
0217     if (!searchText.isEmpty()) {
0218         selectHighlightingFromExternal();
0219         m_searchBar->updateSearch(searchText);
0220         m_searchBar->setText(searchText);
0221     }
0222 }
0223 
0224 void KateModeMenuList::loadHighlightingModel()
0225 {
0226     m_list->setModel(m_model);
0227 
0228     QString *prevHlSection = nullptr;
0229     /*
0230      * The width of the text container in the item, in pixels. This is used to make
0231      * a custom word wrap and prevent the item's text from passing under the scroll bar.
0232      * NOTE: 8 = Icon margin
0233      */
0234     const int maxWidthText = m_list->getContentWidth(1, 8) - m_iconSize - 8;
0235 
0236     // Transparent color used as background in the sections.
0237     QPixmap transparentPixmap = QPixmap(m_iconSize / 2, m_iconSize / 2);
0238     transparentPixmap.fill(Qt::transparent);
0239     QBrush transparentBrush(transparentPixmap);
0240 
0241     /*
0242      * The first item on the list is the "Best Search Matches" section,
0243      * which will remain hidden and will only be shown when necessary.
0244      */
0245     createSectionList(QString(), transparentBrush, false);
0246     m_defaultHeightItemSection = m_list->visualRect(m_model->index(0, 0)).height();
0247     m_list->setRowHidden(0, true);
0248 
0249     /*
0250      * Get list of modes from KateModeManager::list().
0251      * We assume that the modes are arranged according to sections, alphabetically;
0252      * and the attribute "translatedSection" isn't empty if "section" has a value.
0253      */
0254     for (auto *hl : KTextEditor::EditorPrivate::self()->modeManager()->list()) {
0255         if (hl->name.isEmpty()) {
0256             continue;
0257         }
0258 
0259         // Detects a new section.
0260         if (!hl->translatedSection.isEmpty() && (prevHlSection == nullptr || hl->translatedSection != *prevHlSection)) {
0261             createSectionList(hl->sectionTranslated(), transparentBrush);
0262         }
0263         prevHlSection = hl->translatedSection.isNull() ? nullptr : &hl->translatedSection;
0264 
0265         // Create item in the list with the language name.
0266         KateModeMenuListData::ListItem *item = KateModeMenuListData::Factory::createListItem();
0267         /*
0268          * NOTE:
0269          *  - (If the scroll bar is not overlapped) In QListView::setWordWrap(),
0270          *    when the scroll bar is hidden, the word wrap changes, but the size
0271          *    of the items is keeped, causing display problems in some items.
0272          *    KateModeMenuList::setWordWrap() applies a fixed word wrap.
0273          *  - Search names generated in: KateModeMenuListData::SearchLine::updateSearch()
0274          */
0275         item->setText(setWordWrap(hl->nameTranslated(), maxWidthText, m_list->fontMetrics()));
0276         item->setMode(hl);
0277 
0278         item->setIcon(m_emptyIcon);
0279         item->setEditable(false);
0280         // Add item
0281         m_model->appendRow(item);
0282     }
0283 }
0284 
0285 KateModeMenuListData::ListItem *KateModeMenuList::createSectionList(const QString &sectionName, const QBrush &background, bool bSeparator, int modelPosition)
0286 {
0287     /*
0288      * Add a separator to the list.
0289      */
0290     if (bSeparator) {
0291         KateModeMenuListData::ListItem *separator = KateModeMenuListData::Factory::createListItem();
0292         separator->setFlags(Qt::NoItemFlags);
0293         separator->setEnabled(false);
0294         separator->setEditable(false);
0295         separator->setSelectable(false);
0296 
0297         separator->setSizeHint(QSize(separator->sizeHint().width() - 2, 4));
0298         separator->setBackground(background);
0299 
0300         QFrame *line = new QFrame(m_list);
0301         line->setFrameStyle(QFrame::HLine);
0302 
0303         if (modelPosition < 0) {
0304             m_model->appendRow(separator);
0305         } else {
0306             m_model->insertRow(modelPosition, separator);
0307         }
0308         m_list->setIndexWidget(m_model->index(separator->row(), 0), line);
0309         m_list->selectionModel()->select(separator->index(), QItemSelectionModel::Deselect);
0310     }
0311 
0312     /*
0313      * Add the section name to the list.
0314      */
0315     KateModeMenuListData::ListItem *section = KateModeMenuListData::Factory::createListItem();
0316     section->setFlags(Qt::NoItemFlags);
0317     section->setEnabled(false);
0318     section->setEditable(false);
0319     section->setSelectable(false);
0320 
0321     QLabel *label = new QLabel(sectionName, m_list);
0322     if (m_list->layoutDirection() == Qt::RightToLeft) {
0323         label->setAlignment(Qt::AlignRight);
0324     }
0325     label->setTextFormat(Qt::PlainText);
0326     label->setIndent(6);
0327 
0328     /*
0329      * NOTE: Names of sections in bold. The font color
0330      * should change according to Kate's color theme.
0331      */
0332     QFont font = label->font();
0333     font.setWeight(QFont::Bold);
0334     label->setFont(font);
0335 
0336     section->setBackground(background);
0337 
0338     if (modelPosition < 0) {
0339         m_model->appendRow(section);
0340     } else {
0341         m_model->insertRow(modelPosition + 1, section);
0342     }
0343     m_list->setIndexWidget(m_model->index(section->row(), 0), label);
0344     m_list->selectionModel()->select(section->index(), QItemSelectionModel::Deselect);
0345 
0346     // Apply word wrap in sections, for long labels.
0347     const int containerTextWidth = m_list->getContentWidth(2, 4);
0348     int heightSectionMargin = m_list->visualRect(m_model->index(section->row(), 0)).height() - label->sizeHint().height();
0349 
0350     if (label->sizeHint().width() > containerTextWidth) {
0351         label->setText(setWordWrap(label->text(), containerTextWidth - label->indent(), label->fontMetrics()));
0352         if (heightSectionMargin < 2) {
0353             heightSectionMargin = 2;
0354         }
0355         section->setSizeHint(QSize(section->sizeHint().width(), label->sizeHint().height() + heightSectionMargin));
0356     } else if (heightSectionMargin < 2) {
0357         section->setSizeHint(QSize(section->sizeHint().width(), label->sizeHint().height() + 2));
0358     }
0359 
0360     return section;
0361 }
0362 
0363 void KateModeMenuList::setButton(QPushButton *button, AlignmentHButton positionX, AlignmentVButton positionY, AutoUpdateTextButton autoUpdateTextButton)
0364 {
0365     if (positionX == AlignHInverse) {
0366         if (layoutDirection() == Qt::RightToLeft) {
0367             m_positionX = KateModeMenuList::AlignLeft;
0368         } else {
0369             m_positionX = KateModeMenuList::AlignRight;
0370         }
0371     } else if (positionX == AlignLeft && layoutDirection() != Qt::RightToLeft) {
0372         m_positionX = KateModeMenuList::AlignHDefault;
0373     } else {
0374         m_positionX = positionX;
0375     }
0376 
0377     m_positionY = positionY;
0378     m_pushButton = button;
0379     m_autoUpdateTextButton = autoUpdateTextButton;
0380 }
0381 
0382 void KateModeMenuList::setSizeList(const int height, const int width)
0383 {
0384     m_list->setSizeList(height, width);
0385     m_searchBar->setWidth(width);
0386 }
0387 
0388 void KateModeMenuList::autoScroll()
0389 {
0390     if (m_selectedItem && m_autoScroll == ScrollToSelectedItem) {
0391         m_list->setCurrentItem(m_selectedItem->row());
0392         m_list->scrollToItem(m_selectedItem->row(), QAbstractItemView::PositionAtCenter);
0393     } else {
0394         m_list->scrollToFirstItem();
0395     }
0396 }
0397 
0398 void KateModeMenuList::showEvent(QShowEvent *event)
0399 {
0400     Q_UNUSED(event);
0401     /*
0402      * TODO: Put the menu on the bottom-edge of the window if the status bar is hidden,
0403      * to show the menu with keyboard shortcuts. To do this, it's preferable to add a new
0404      * function/slot to display the menu, correcting the position. If the trigger button
0405      * isn't set or is destroyed, there may be problems detecting Right-to-left layouts.
0406      */
0407 
0408     // Set the menu position
0409     if (m_pushButton && m_pushButton->isVisible()) {
0410         /*
0411          * Get vertical position.
0412          * NOTE: In KDE Plasma with Wayland, the reference point of the position
0413          * is the main window, not the desktop. Therefore, if the window is vertically
0414          * smaller than the menu, it will be positioned on the upper edge of the window.
0415          */
0416         int newMenu_y; // New vertical menu position
0417         if (m_positionY == AlignTop) {
0418             newMenu_y = m_pushButton->mapToGlobal(QPoint(0, 0)).y() - geometry().height();
0419             if (newMenu_y < 0) {
0420                 newMenu_y = 0;
0421             }
0422         } else {
0423             newMenu_y = pos().y();
0424         }
0425 
0426         // Set horizontal position.
0427         if (m_positionX == AlignRight) {
0428             // New horizontal menu position
0429             int newMenu_x = pos().x() - geometry().width() + m_pushButton->geometry().width();
0430             // Get position of the right edge of the toggle button
0431             const int buttonPositionRight = m_pushButton->mapToGlobal(QPoint(0, 0)).x() + m_pushButton->geometry().width();
0432             if (newMenu_x < 0) {
0433                 newMenu_x = 0;
0434             } else if (newMenu_x + geometry().width() < buttonPositionRight) {
0435                 newMenu_x = buttonPositionRight - geometry().width();
0436             }
0437             move(newMenu_x, newMenu_y);
0438         } else if (m_positionX == AlignLeft) {
0439             move(m_pushButton->mapToGlobal(QPoint(0, 0)).x(), newMenu_y);
0440         } else if (m_positionY == AlignTop) {
0441             // Set vertical position, use the default horizontal position
0442             move(pos().x(), newMenu_y);
0443         }
0444     }
0445 
0446     // Select text from the search bar
0447     if (!m_searchBar->text().isEmpty()) {
0448         if (m_searchBar->text().trimmed().isEmpty()) {
0449             m_searchBar->clear();
0450         } else {
0451             m_searchBar->selectAll();
0452         }
0453     }
0454 
0455     // Set focus on the list. The list widget uses focus proxy to the search bar.
0456     m_list->setFocus(Qt::ActiveWindowFocusReason);
0457 
0458     KTextEditor::DocumentPrivate *doc = m_doc;
0459     if (!doc) {
0460         return;
0461     }
0462 
0463     // First show or if an external changed the current syntax highlighting.
0464     if (!m_selectedItem || (m_selectedItem->hasMode() && m_selectedItem->getMode()->name != doc->fileType())) {
0465         if (!selectHighlightingFromExternal(doc->fileType())) {
0466             // Strange case: if the current syntax highlighting does not exist in the list.
0467             if (m_selectedItem) {
0468                 m_selectedItem->setIcon(m_emptyIcon);
0469             }
0470             if ((m_selectedItem || !m_list->currentItem()) && m_searchBar->text().isEmpty()) {
0471                 m_list->scrollToFirstItem();
0472             }
0473             m_selectedItem = nullptr;
0474         }
0475     }
0476 }
0477 
0478 void KateModeMenuList::updateSelectedItem(KateModeMenuListData::ListItem *item)
0479 {
0480     // Change the previously selected item to empty icon
0481     if (m_selectedItem) {
0482         m_selectedItem->setIcon(m_emptyIcon);
0483     }
0484 
0485     // Update the selected item
0486     item->setIcon(m_checkIcon);
0487     m_selectedItem = item;
0488     m_list->setCurrentItem(item->row());
0489 
0490     // Change text of the trigger button
0491     if (bool(m_autoUpdateTextButton) && m_pushButton && item->hasMode()) {
0492         m_pushButton->setText(item->getMode()->nameTranslated());
0493     }
0494 }
0495 
0496 void KateModeMenuList::selectHighlightingSetVisibility(QStandardItem *pItem, const bool bHideMenu)
0497 {
0498     if (!pItem || !pItem->isSelectable() || !pItem->isEnabled()) {
0499         return;
0500     }
0501 
0502     KateModeMenuListData::ListItem *item = static_cast<KateModeMenuListData::ListItem *>(pItem);
0503 
0504     if (!item->text().isEmpty()) {
0505         updateSelectedItem(item);
0506     }
0507     if (bHideMenu) {
0508         hide();
0509     }
0510 
0511     // Apply syntax highlighting
0512     KTextEditor::DocumentPrivate *doc = m_doc;
0513     if (doc && item->hasMode()) {
0514         doc->updateFileType(item->getMode()->name, true);
0515     }
0516 }
0517 
0518 void KateModeMenuList::selectHighlighting(const QModelIndex &index)
0519 {
0520     selectHighlightingSetVisibility(m_model->item(index.row(), 0), true);
0521 }
0522 
0523 bool KateModeMenuList::selectHighlightingFromExternal(const QString &nameMode)
0524 {
0525     for (int i = 0; i < m_model->rowCount(); ++i) {
0526         KateModeMenuListData::ListItem *item = static_cast<KateModeMenuListData::ListItem *>(m_model->item(i, 0));
0527 
0528         if (!item->hasMode() || m_model->item(i, 0)->text().isEmpty()) {
0529             continue;
0530         }
0531         if (item->getMode()->name == nameMode || (nameMode.isEmpty() && item->getMode()->name == QLatin1String("Normal"))) {
0532             updateSelectedItem(item);
0533 
0534             // Clear search
0535             if (!m_searchBar->text().isEmpty()) {
0536                 // Prevent the empty list message from being seen over the items for a short time
0537                 if (m_emptyListMsg) {
0538                     m_emptyListMsg->hide();
0539                 }
0540 
0541                 // NOTE: This calls updateSearch(), it's scrolled to the selected item or the first item.
0542                 m_searchBar->clear();
0543             } else if (m_autoScroll == ScrollToSelectedItem) {
0544                 m_list->scrollToItem(i);
0545             } else {
0546                 // autoScroll()
0547                 m_list->scrollToFirstItem();
0548             }
0549             return true;
0550         }
0551     }
0552     return false;
0553 }
0554 
0555 bool KateModeMenuList::selectHighlightingFromExternal()
0556 {
0557     KTextEditor::DocumentPrivate *doc = m_doc;
0558     if (doc) {
0559         return selectHighlightingFromExternal(doc->fileType());
0560     }
0561     return false;
0562 }
0563 
0564 void KateModeMenuList::loadEmptyMsg()
0565 {
0566     m_emptyListMsg = new QLabel(i18nc("A search yielded no results", "No items matching your search"), this);
0567     m_emptyListMsg->setMargin(15);
0568     m_emptyListMsg->setWordWrap(true);
0569 
0570     const int fontSize = font().pointSize() > 10 ? font().pointSize() + 4 : 14;
0571 
0572     QColor color = m_emptyListMsg->palette().color(QPalette::Text);
0573     m_emptyListMsg->setStyleSheet(QLatin1String("font-size: ") + QString::number(fontSize) + QLatin1String("pt; color: rgba(") + QString::number(color.red())
0574                                   + QLatin1Char(',') + QString::number(color.green()) + QLatin1Char(',') + QString::number(color.blue())
0575                                   + QLatin1String(", 0.3);"));
0576 
0577     m_emptyListMsg->setAlignment(Qt::AlignCenter);
0578     m_layoutList->addWidget(m_emptyListMsg, 0, 0, Qt::AlignCenter);
0579 }
0580 
0581 QString KateModeMenuList::setWordWrap(const QString &text, const int maxWidth, const QFontMetrics &fontMetrics) const
0582 {
0583     // Get the length of the text, in pixels, and compare it with the container
0584     if (fontMetrics.horizontalAdvance(text) <= maxWidth) {
0585         return text;
0586     }
0587 
0588     // Add line breaks in the text to fit in the container
0589     QStringList words = text.split(QLatin1Char(' '));
0590     if (words.count() < 1) {
0591         return text;
0592     }
0593     QString newText = QString();
0594     QString tmpLineText = QString();
0595 
0596     for (int i = 0; i < words.count() - 1; ++i) {
0597         // Elide mode in long words
0598         if (fontMetrics.horizontalAdvance(words[i]) > maxWidth) {
0599             if (!tmpLineText.isEmpty()) {
0600                 newText += tmpLineText + QLatin1Char('\n');
0601                 tmpLineText.clear();
0602             }
0603             newText +=
0604                 fontMetrics.elidedText(words[i], m_list->layoutDirection() == Qt::RightToLeft ? Qt::ElideLeft : Qt::ElideRight, maxWidth) + QLatin1Char('\n');
0605             continue;
0606         } else {
0607             tmpLineText += words[i];
0608         }
0609 
0610         // This prevents the last line of text from having only one word with 1 or 2 chars
0611         if (i == words.count() - 3 && words[i + 2].length() <= 2
0612             && fontMetrics.horizontalAdvance(tmpLineText + QLatin1Char(' ') + words[i + 1] + QLatin1Char(' ') + words[i + 2]) > maxWidth) {
0613             newText += tmpLineText + QLatin1Char('\n');
0614             tmpLineText.clear();
0615         }
0616         // Add line break if the maxWidth is exceeded with the next word
0617         else if (fontMetrics.horizontalAdvance(tmpLineText + QLatin1Char(' ') + words[i + 1]) > maxWidth) {
0618             newText += tmpLineText + QLatin1Char('\n');
0619             tmpLineText.clear();
0620         } else {
0621             tmpLineText.append(QLatin1Char(' '));
0622         }
0623     }
0624 
0625     // Add line breaks in delimiters, if the last word is greater than the container
0626     bool bElidedLastWord = false;
0627     if (fontMetrics.horizontalAdvance(words[words.count() - 1]) > maxWidth) {
0628         bElidedLastWord = true;
0629         const int lastw = words.count() - 1;
0630         for (int c = words[lastw].length() - 1; c >= 0; --c) {
0631             if (isDelimiter(words[lastw][c].unicode()) && fontMetrics.horizontalAdvance(words[lastw].mid(0, c + 1)) <= maxWidth) {
0632                 bElidedLastWord = false;
0633                 if (fontMetrics.horizontalAdvance(words[lastw].mid(c + 1)) > maxWidth) {
0634                     words[lastw] = words[lastw].mid(0, c + 1) + QLatin1Char('\n')
0635                         + fontMetrics.elidedText(words[lastw].mid(c + 1),
0636                                                  m_list->layoutDirection() == Qt::RightToLeft ? Qt::ElideLeft : Qt::ElideRight,
0637                                                  maxWidth);
0638                 } else {
0639                     words[lastw].insert(c + 1, QLatin1Char('\n'));
0640                 }
0641                 break;
0642             }
0643         }
0644     }
0645 
0646     if (!tmpLineText.isEmpty()) {
0647         newText += tmpLineText;
0648     }
0649     if (bElidedLastWord) {
0650         newText += fontMetrics.elidedText(words[words.count() - 1], m_list->layoutDirection() == Qt::RightToLeft ? Qt::ElideLeft : Qt::ElideRight, maxWidth);
0651     } else {
0652         newText += words[words.count() - 1];
0653     }
0654     return newText;
0655 }
0656 
0657 void KateModeMenuListData::SearchLine::setWidth(const int width)
0658 {
0659     setMinimumWidth(width);
0660     setMaximumWidth(width);
0661 }
0662 
0663 void KateModeMenuListData::ListView::setSizeList(const int height, const int width)
0664 {
0665     setMinimumWidth(width);
0666     setMaximumWidth(width);
0667     setMinimumHeight(height);
0668     setMaximumHeight(height);
0669 }
0670 
0671 int KateModeMenuListData::ListView::getWidth() const
0672 {
0673     // Equivalent to: sizeHint().width()
0674     // But "sizeHint().width()" returns an incorrect value when the menu is large.
0675     return size().width() - 4;
0676 }
0677 
0678 int KateModeMenuListData::ListView::getContentWidth(const int overlayScrollbarMargin, const int classicScrollbarMargin) const
0679 {
0680     if (overlapScrollBar()) {
0681         return getWidth() - m_parentMenu->m_scroll->sizeHint().width() - 2 - overlayScrollbarMargin; // ScrollBar Margin = 2
0682     }
0683     return getWidth() - verticalScrollBar()->sizeHint().width() - classicScrollbarMargin;
0684 }
0685 
0686 int KateModeMenuListData::ListView::getContentWidth() const
0687 {
0688     return getContentWidth(0, 0);
0689 }
0690 
0691 bool KateModeMenuListData::ListItem::generateSearchName(const QString &itemName)
0692 {
0693     QString searchName = QString(itemName);
0694     bool bNewName = false;
0695 
0696     // Replace word delimiters with spaces
0697     for (int i = searchName.length() - 1; i >= 0; --i) {
0698         if (isDelimiter(searchName[i].unicode())) {
0699             searchName.replace(i, 1, QLatin1Char(' '));
0700             if (!bNewName) {
0701                 bNewName = true;
0702             }
0703         }
0704         // Avoid duplicate delimiters/spaces
0705         if (bNewName && i < searchName.length() - 1 && searchName[i].isSpace() && searchName[i + 1].isSpace()) {
0706             searchName.remove(i + 1, 1);
0707         }
0708     }
0709 
0710     if (bNewName) {
0711         if (searchName[searchName.length() - 1].isSpace()) {
0712             searchName.remove(searchName.length() - 1, 1);
0713         }
0714         if (searchName[0].isSpace()) {
0715             searchName.remove(0, 1);
0716         }
0717         m_searchName = searchName;
0718         return true;
0719     } else {
0720         m_searchName = itemName;
0721     }
0722     return false;
0723 }
0724 
0725 bool KateModeMenuListData::ListItem::matchExtension(const QString &text) const
0726 {
0727     if (!hasMode() || m_type->wildcards.count() == 0) {
0728         return false;
0729     }
0730 
0731     /*
0732      * Only file extensions and full names are matched. Files like "Kconfig*"
0733      * aren't considered. It's also assumed that "text" doesn't contain '*'.
0734      */
0735     for (const auto &ext : m_type->wildcards) {
0736         // File extension
0737         if (ext.startsWith(QLatin1String("*."))) {
0738             if (text.length() == ext.length() - 2 && text.compare(QStringView(ext).mid(2), Qt::CaseInsensitive) == 0) {
0739                 return true;
0740             }
0741         } else if (text.length() != ext.length() || ext.endsWith(QLatin1Char('*'))) {
0742             continue;
0743             // Full name
0744         } else if (text.compare(ext, Qt::CaseInsensitive) == 0) {
0745             return true;
0746         }
0747     }
0748     return false;
0749 }
0750 
0751 void KateModeMenuListData::ListView::keyPressEvent(QKeyEvent *event)
0752 {
0753     // Ctrl/Alt/Shift/Meta + Return/Enter selects an item, but without hiding the menu
0754     if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)
0755         && (event->modifiers().testFlag(Qt::ControlModifier) || event->modifiers().testFlag(Qt::AltModifier) || event->modifiers().testFlag(Qt::ShiftModifier)
0756             || event->modifiers().testFlag(Qt::MetaModifier))) {
0757         m_parentMenu->selectHighlightingSetVisibility(m_parentMenu->m_list->currentItem(), false);
0758     }
0759     // Return/Enter selects an item and hide the menu
0760     else if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
0761         m_parentMenu->selectHighlightingSetVisibility(m_parentMenu->m_list->currentItem(), true);
0762     } else {
0763         QListView::keyPressEvent(event);
0764     }
0765 }
0766 
0767 void KateModeMenuListData::SearchLine::keyPressEvent(QKeyEvent *event)
0768 {
0769     if (m_parentMenu->m_list
0770         && (event->matches(QKeySequence::MoveToNextLine) || event->matches(QKeySequence::SelectNextLine) || event->matches(QKeySequence::MoveToPreviousLine)
0771             || event->matches(QKeySequence::SelectPreviousLine) || event->matches(QKeySequence::MoveToNextPage) || event->matches(QKeySequence::SelectNextPage)
0772             || event->matches(QKeySequence::MoveToPreviousPage) || event->matches(QKeySequence::SelectPreviousPage) || event->key() == Qt::Key_Return
0773             || event->key() == Qt::Key_Enter)) {
0774         QApplication::sendEvent(m_parentMenu->m_list, event);
0775     } else {
0776         QLineEdit::keyPressEvent(event);
0777     }
0778 }
0779 
0780 void KateModeMenuListData::SearchLine::init()
0781 {
0782     connect(this, &KateModeMenuListData::SearchLine::textChanged, this, &KateModeMenuListData::SearchLine::_k_queueSearch);
0783 
0784     setEnabled(true);
0785     setClearButtonEnabled(true);
0786 }
0787 
0788 void KateModeMenuListData::SearchLine::clear()
0789 {
0790     m_queuedSearches = 0;
0791     m_bSearchStateAutoScroll = (text().trimmed().isEmpty()) ? false : true;
0792     /*
0793      * NOTE: This calls "SearchLine::_k_queueSearch()" with an empty string.
0794      * The search clearing should be done without delays.
0795      */
0796     QLineEdit::clear();
0797 }
0798 
0799 void KateModeMenuListData::SearchLine::_k_queueSearch(const QString &s)
0800 {
0801     m_queuedSearches++;
0802     m_search = s;
0803 
0804     if (m_search.isEmpty()) {
0805         _k_activateSearch(); // Clear search without delay
0806     } else {
0807         QTimer::singleShot(m_searchDelay, this, &KateModeMenuListData::SearchLine::_k_activateSearch);
0808     }
0809 }
0810 
0811 void KateModeMenuListData::SearchLine::_k_activateSearch()
0812 {
0813     m_queuedSearches--;
0814 
0815     if (m_queuedSearches <= 0) {
0816         updateSearch(m_search);
0817         m_queuedSearches = 0;
0818     }
0819 }
0820 
0821 void KateModeMenuListData::SearchLine::updateSearch(const QString &s)
0822 {
0823     if (m_parentMenu->m_emptyListMsg) {
0824         m_parentMenu->m_emptyListMsg->hide();
0825     }
0826     if (m_parentMenu->m_scroll && m_parentMenu->m_scroll->isHidden()) {
0827         m_parentMenu->m_scroll->show();
0828     }
0829 
0830     KateModeMenuListData::ListView *listView = m_parentMenu->m_list;
0831     QStandardItemModel *listModel = m_parentMenu->m_model;
0832 
0833     const QString searchText = (s.isNull() ? text() : s).simplified();
0834 
0835     /*
0836      * Clean "Best Search Matches" section, move items to their original places.
0837      */
0838     if (!listView->isRowHidden(0)) {
0839         listView->setRowHidden(0, true);
0840     }
0841     if (!m_bestResults.isEmpty()) {
0842         const int sizeBestResults = m_bestResults.size();
0843         for (int i = 0; i < sizeBestResults; ++i) {
0844             listModel->takeRow(m_bestResults.at(i).first->index().row());
0845             listModel->insertRow(m_bestResults.at(i).second + sizeBestResults - i - 1, m_bestResults.at(i).first);
0846         }
0847         m_bestResults.clear();
0848     }
0849 
0850     /*
0851      * Empty search bar.
0852      * Show all items and scroll to the selected item or to the first item.
0853      */
0854     if (searchText.isEmpty() || (searchText.size() == 1 && searchText[0].isSpace())) {
0855         for (int i = 1; i < listModel->rowCount(); ++i) {
0856             if (listView->isRowHidden(i)) {
0857                 listView->setRowHidden(i, false);
0858             }
0859         }
0860 
0861         // Don't auto-scroll if the search is already clear
0862         if (m_bSearchStateAutoScroll) {
0863             m_parentMenu->autoScroll();
0864         }
0865         m_bSearchStateAutoScroll = false;
0866         return;
0867     }
0868 
0869     /*
0870      * Prepare item filter.
0871      */
0872     int lastItem = -1;
0873     int lastSection = -1;
0874     int firstSection = -1;
0875     bool bEmptySection = true;
0876     bool bSectionSeparator = false;
0877     bool bSectionName = false;
0878     bool bNotShowBestResults = false;
0879     bool bSearchExtensions = true;
0880     bool bExactMatch = false; // If the search name will not be used
0881     /*
0882      * It's used for two purposes, it's true if searchText is a
0883      * single alphanumeric character or if it starts with a point.
0884      * Both cases don't conflict, so a single bool is used.
0885      */
0886     bool bIsAlphaOrPointExt = false;
0887 
0888     /*
0889      * Don't search for extensions if the search text has only one character,
0890      * to avoid unwanted results. In this case, the items that start with
0891      * that character are displayed.
0892      */
0893     if (searchText.length() < 2) {
0894         bSearchExtensions = false;
0895         if (searchText[0].isLetterOrNumber()) {
0896             bIsAlphaOrPointExt = true;
0897         }
0898     }
0899     // If the search text has a point at the beginning, match extensions
0900     else if (searchText.length() > 1 && searchText[0].toLatin1() == 46) {
0901         bIsAlphaOrPointExt = true;
0902         bSearchExtensions = true;
0903         bExactMatch = true;
0904     }
0905     // Two characters: search using the normal name of the items
0906     else if (searchText.length() == 2) {
0907         bExactMatch = true;
0908         // if it contains the '*' character, don't match extensions
0909         if (searchText[1].toLatin1() == 42 || searchText[0].toLatin1() == 42) {
0910             bSearchExtensions = false;
0911         }
0912     }
0913     /*
0914      * Don't use the search name if the search text has delimiters.
0915      * Don't search in extensions if it contains the '*' character.
0916      */
0917     else {
0918         QString::const_iterator srcText = searchText.constBegin();
0919         QString::const_iterator endText = searchText.constEnd();
0920 
0921         for (int it = 0; it < searchText.length() / 2 + searchText.length() % 2; ++it) {
0922             --endText;
0923             const ushort ucsrc = srcText->unicode();
0924             const ushort ucend = endText->unicode();
0925 
0926             // If searchText contains "*"
0927             if (ucsrc == 42 || ucend == 42) {
0928                 bSearchExtensions = false;
0929                 bExactMatch = true;
0930                 break;
0931             }
0932             if (!bExactMatch && (isDelimiter(ucsrc) || (ucsrc != ucend && isDelimiter(ucend)))) {
0933                 bExactMatch = true;
0934             }
0935             ++srcText;
0936         }
0937     }
0938 
0939     /*
0940      * Filter items.
0941      */
0942     for (int i = 1; i < listModel->rowCount(); ++i) {
0943         QString itemName = listModel->item(i, 0)->text();
0944 
0945         /*
0946          * Hide/show the name of the section. If the text of the item
0947          * is empty, then it corresponds to the name of the section.
0948          */
0949         if (itemName.isEmpty()) {
0950             listView->setRowHidden(i, false);
0951 
0952             if (bSectionSeparator) {
0953                 bSectionName = true;
0954             } else {
0955                 bSectionSeparator = true;
0956             }
0957 
0958             /*
0959              * This hides the name of the previous section
0960              * (and the separator) if this section has no items.
0961              */
0962             if (bSectionName && bEmptySection && lastSection > 0) {
0963                 listView->setRowHidden(lastSection, true);
0964                 listView->setRowHidden(lastSection - 1, true);
0965             }
0966 
0967             // Find the section name
0968             if (bSectionName) {
0969                 bSectionName = false;
0970                 bSectionSeparator = false;
0971                 bEmptySection = true;
0972                 lastSection = i;
0973             }
0974             continue;
0975         }
0976 
0977         /*
0978          * Start filtering items.
0979          */
0980         KateModeMenuListData::ListItem *item = static_cast<KateModeMenuListData::ListItem *>(listModel->item(i, 0));
0981 
0982         if (!item->hasMode()) {
0983             listView->setRowHidden(i, true);
0984             continue;
0985         }
0986         if (item->getSearchName().isEmpty()) {
0987             item->generateSearchName(item->getMode()->translatedName.isEmpty() ? item->getMode()->name : item->getMode()->translatedName);
0988         }
0989 
0990         /*
0991          * Add item to the "Best Search Matches" section if there is an exact match in the search.
0992          * However, if the "exact match" is already the first search result, that section will not
0993          * be displayed, as it isn't necessary.
0994          */
0995         if (!bNotShowBestResults
0996             && (item->getSearchName().compare(searchText, m_caseSensitivity) == 0
0997                 || (bExactMatch && item->getMode()->nameTranslated().compare(searchText, m_caseSensitivity) == 0))) {
0998             if (lastItem == -1) {
0999                 bNotShowBestResults = true;
1000             } else {
1001                 m_bestResults.append(qMakePair(item, i));
1002                 continue;
1003             }
1004         }
1005 
1006         // Only a character is written in the search bar
1007         if (searchText.length() == 1) {
1008             if (bIsAlphaOrPointExt) {
1009                 /*
1010                  * Add item to the "Best Search Matches" section, if there is a single letter.
1011                  * Also look for coincidence in the raw name, some translations use delimiters
1012                  * instead of spaces and this can lead to inaccurate results.
1013                  */
1014                 bool bMatchCharDel = true;
1015                 if (item->getMode()->name.startsWith(searchText + QLatin1Char(' '), m_caseSensitivity)) {
1016                     if (QString(QLatin1Char(' ') + item->getSearchName() + QLatin1Char(' '))
1017                             .contains(QLatin1Char(' ') + searchText + QLatin1Char(' '), m_caseSensitivity)) {
1018                         m_bestResults.append(qMakePair(item, i));
1019                         continue;
1020                     } else {
1021                         bMatchCharDel = false;
1022                     }
1023                 }
1024 
1025                 // CASE 1: All the items that start with that character will be displayed.
1026                 if (item->getSearchName().startsWith(searchText, m_caseSensitivity)) {
1027                     setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1028                     continue;
1029                 }
1030 
1031                 // CASE 2: Matches considering delimiters. For example, when writing "c",
1032                 //         "Objective-C" will be displayed in the results, but not "Yacc/Bison".
1033                 if (bMatchCharDel
1034                     && QString(QLatin1Char(' ') + item->getSearchName() + QLatin1Char(' '))
1035                            .contains(QLatin1Char(' ') + searchText + QLatin1Char(' '), m_caseSensitivity)) {
1036                     setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1037                     continue;
1038                 }
1039             }
1040             // CASE 3: The character isn't a letter or number, do an exact search.
1041             else if (item->getMode()->nameTranslated().contains(searchText[0], m_caseSensitivity)) {
1042                 setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1043                 continue;
1044             }
1045         }
1046         // CASE 4: Search text, using the search name or the normal name.
1047         else if (!bExactMatch && item->getSearchName().contains(searchText, m_caseSensitivity)) {
1048             setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1049             continue;
1050         } else if (bExactMatch && item->getMode()->nameTranslated().contains(searchText, m_caseSensitivity)) {
1051             setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1052             continue;
1053         }
1054 
1055         // CASE 5: Exact matches in extensions.
1056         if (bSearchExtensions) {
1057             if (bIsAlphaOrPointExt && item->matchExtension(searchText.mid(1))) {
1058                 setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1059                 continue;
1060             } else if (item->matchExtension(searchText)) {
1061                 setSearchResult(i, bEmptySection, lastSection, firstSection, lastItem);
1062                 continue;
1063             }
1064         }
1065 
1066         // Item not found, hide
1067         listView->setRowHidden(i, true);
1068     }
1069 
1070     // Remove last section name, if it's empty.
1071     if (bEmptySection && lastSection > 0 && !listModel->item(listModel->rowCount() - 1, 0)->text().isEmpty()) {
1072         listView->setRowHidden(lastSection, true);
1073         listView->setRowHidden(lastSection - 1, true);
1074     }
1075 
1076     // Hide the separator line in the name of the first section.
1077     if (m_bestResults.isEmpty()) {
1078         listView->setRowHidden(0, true);
1079         if (firstSection > 0) {
1080             listView->setRowHidden(firstSection - 1, true);
1081         }
1082     } else {
1083         /*
1084          * Show "Best Search Matches" section, if there are items.
1085          */
1086 
1087         // Show title in singular or plural, depending on the number of items.
1088         QLabel *labelSection = static_cast<QLabel *>(listView->indexWidget(listModel->index(0, 0)));
1089         if (m_bestResults.size() == 1) {
1090             labelSection->setText(
1091                 i18nc("Title (in singular) of the best result in an item search. Please, that the translation doesn't have more than 34 characters, since the "
1092                       "menu where it's displayed is small and fixed.",
1093                       "Best Search Match"));
1094         } else {
1095             labelSection->setText(
1096                 i18nc("Title (in plural) of the best results in an item search. Please, that the translation doesn't have more than 34 characters, since the "
1097                       "menu where it's displayed is small and fixed.",
1098                       "Best Search Matches"));
1099         }
1100 
1101         int heightSectionMargin = m_parentMenu->m_defaultHeightItemSection - labelSection->sizeHint().height();
1102         if (heightSectionMargin < 2) {
1103             heightSectionMargin = 2;
1104         }
1105         int maxWidthText = listView->getContentWidth(1, 3);
1106         // NOTE: labelSection->sizeHint().width() == labelSection->indent() + labelSection->fontMetrics().horizontalAdvance(labelSection->text())
1107         const bool bSectionMultiline = labelSection->sizeHint().width() > maxWidthText;
1108         maxWidthText -= labelSection->indent();
1109         if (!bSectionMultiline) {
1110             listModel->item(0, 0)->setSizeHint(QSize(listModel->item(0, 0)->sizeHint().width(), labelSection->sizeHint().height() + heightSectionMargin));
1111             listView->setRowHidden(0, false);
1112         }
1113 
1114         /*
1115          * Show items in "Best Search Matches" section.
1116          */
1117         int rowModelBestResults = 0; // New position in the model
1118 
1119         // Special Case: always show the "R Script" mode first by typing "r" in the search box
1120         if (searchText.length() == 1 && searchText.compare(QLatin1String("r"), m_caseSensitivity) == 0) {
1121             for (const QPair<ListItem *, int> &itemBestResults : std::as_const(m_bestResults)) {
1122                 listModel->takeRow(itemBestResults.second);
1123                 ++rowModelBestResults;
1124                 if (itemBestResults.first->getMode()->name == QLatin1String("R Script")) {
1125                     listModel->insertRow(1, itemBestResults.first);
1126                     listView->setRowHidden(1, false);
1127                 } else {
1128                     listModel->insertRow(rowModelBestResults, itemBestResults.first);
1129                     listView->setRowHidden(rowModelBestResults, false);
1130                 }
1131             }
1132         } else {
1133             // Move items to the "Best Search Matches" section
1134             for (const QPair<ListItem *, int> &itemBestResults : std::as_const(m_bestResults)) {
1135                 listModel->takeRow(itemBestResults.second);
1136                 listModel->insertRow(++rowModelBestResults, itemBestResults.first);
1137                 listView->setRowHidden(rowModelBestResults, false);
1138             }
1139         }
1140         if (lastItem == -1) {
1141             lastItem = rowModelBestResults;
1142         }
1143 
1144         // Add word wrap in long section titles.
1145         if (bSectionMultiline) {
1146             if (listView->visualRect(listModel->index(lastItem, 0)).bottom() + labelSection->sizeHint().height() + heightSectionMargin
1147                     > listView->geometry().height()
1148                 || labelSection->sizeHint().width() > listView->getWidth() - 1) {
1149                 labelSection->setText(m_parentMenu->setWordWrap(labelSection->text(), maxWidthText, labelSection->fontMetrics()));
1150             }
1151             listModel->item(0, 0)->setSizeHint(QSize(listModel->item(0, 0)->sizeHint().width(), labelSection->sizeHint().height() + heightSectionMargin));
1152             listView->setRowHidden(0, false);
1153         }
1154 
1155         m_parentMenu->m_list->setCurrentItem(1);
1156     }
1157 
1158     listView->scrollToTop();
1159 
1160     // Show message of empty list
1161     if (lastItem == -1) {
1162         if (m_parentMenu->m_emptyListMsg == nullptr) {
1163             m_parentMenu->loadEmptyMsg();
1164         }
1165         if (m_parentMenu->m_scroll) {
1166             m_parentMenu->m_scroll->hide();
1167         }
1168         m_parentMenu->m_emptyListMsg->show();
1169     }
1170     // Hide scroll bar if it isn't necessary
1171     else if (m_parentMenu->m_scroll && listView->visualRect(listModel->index(lastItem, 0)).bottom() <= listView->geometry().height()) {
1172         m_parentMenu->m_scroll->hide();
1173     }
1174 
1175     m_bSearchStateAutoScroll = true;
1176 }
1177 
1178 void KateModeMenuListData::SearchLine::setSearchResult(const int rowItem, bool &bEmptySection, int &lastSection, int &firstSection, int &lastItem)
1179 {
1180     if (lastItem == -1) {
1181         /*
1182          * Detect the first result of the search and "select" it.
1183          * This allows you to scroll through the list using
1184          * the Up/Down keys after entering a search.
1185          */
1186         m_parentMenu->m_list->setCurrentItem(rowItem);
1187 
1188         // Position of the first section visible.
1189         if (lastSection > 0) {
1190             firstSection = lastSection;
1191         }
1192     }
1193     if (bEmptySection) {
1194         bEmptySection = false;
1195     }
1196 
1197     lastItem = rowItem;
1198     if (m_parentMenu->m_list->isRowHidden(rowItem)) {
1199         m_parentMenu->m_list->setRowHidden(rowItem, false);
1200     }
1201 }
1202 
1203 void KateModeMenuList::updateMenu(KTextEditor::Document *doc)
1204 {
1205     m_doc = static_cast<KTextEditor::DocumentPrivate *>(doc);
1206 }
1207 
1208 #include "moc_katemodemenulist.cpp"