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 §ionName, 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"