File indexing completed on 2024-04-14 03:55:12

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 
0007 /*
0008  *  NOTE: The KateModeMenuListData::SearchLine class is based on
0009  *  KListWidgetSearchLine, by Scott Wheeler <wheeler@kde.org> and
0010  *  Gustavo Sverzut Barbieri <gsbarbieri@users.sourceforge.net>.
0011  *  See: https://api.kde.org/frameworks/kitemviews/html/classKListWidgetSearchLine.html
0012  *
0013  *  TODO: Add keyboard shortcut to show the menu.
0014  *  See: KateModeMenuList::showEvent()
0015  */
0016 
0017 #ifndef KATEMODEMENULIST_H
0018 #define KATEMODEMENULIST_H
0019 
0020 #include <QGridLayout>
0021 #include <QIcon>
0022 #include <QKeyEvent>
0023 #include <QLabel>
0024 #include <QLineEdit>
0025 #include <QListView>
0026 #include <QMenu>
0027 #include <QPointer>
0028 #include <QPushButton>
0029 #include <QScrollBar>
0030 #include <QStandardItemModel>
0031 #include <QString>
0032 
0033 namespace KTextEditor
0034 {
0035 class DocumentPrivate;
0036 class Document;
0037 }
0038 
0039 namespace KateModeMenuListData
0040 {
0041 class ListView;
0042 class ListItem;
0043 class SearchLine;
0044 class Factory;
0045 }
0046 
0047 class KateFileType;
0048 /**
0049  * Class of menu to select the
0050  * syntax highlighting language (mode menu).
0051  * Provides a menu with a scrollable list plus search bar.
0052  *
0053  * This is an alternative to the classic mode menu of the KateModeMenu class.
0054  *
0055  * @see KateModeManager, KateFileType, KateModeMenu
0056  */
0057 class KateModeMenuList : public QMenu
0058 {
0059 public:
0060     /**
0061      * Horizontal Alignment with respect to the trigger button.
0062      * "AlignHDefault" is the normal alignment.
0063      * "AlignHInverse" uses right alignment in Left-to-right layouts and
0064      * left alignmentnin Right-to-left layouts (used in some languages).
0065      * "AlignLeft" and "AlignRight" forces the alignment, regardless of the layout direction.
0066      * @see setButton(), QWidget::layoutDirection(), Qt::LayoutDirection
0067      */
0068     enum AlignmentHButton { AlignHDefault, AlignHInverse, AlignLeft, AlignRight };
0069     /**
0070      * Vertical Alignment with respect to the trigger button.
0071      * "AlignVDefault" uses normal alignment (below the button) and "AlignTop"
0072      * forces the alignment above the trigger button.
0073      * @see setButton(), KateStatusBarOpenUpMenu::setVisible()
0074      */
0075     enum AlignmentVButton { AlignVDefault, AlignTop };
0076     /**
0077      * Define if the trigger button label must be updated when selecting an item.
0078      * @see setButton()
0079      */
0080     enum class AutoUpdateTextButton : bool;
0081     /**
0082      * Search bar position, above or below the list.
0083      */
0084     enum SearchBarPosition { Top, Bottom };
0085     /**
0086      * Defines where the list will scroll after clearing the search or changing the view.
0087      * @see setAutoScroll(), autoScroll()
0088      */
0089     enum AutoScroll { ScrollToSelectedItem, ScrollToTop };
0090 
0091     KateModeMenuList(const QString &title, QWidget *parent)
0092         : QMenu(title, parent)
0093     {
0094         init();
0095     }
0096 
0097     /**
0098      * Reload all items.
0099      * @see KateModeManager::update()
0100      */
0101     void reloadItems();
0102 
0103     /**
0104      * Update the selected item in the list widget, but without changing
0105      * the syntax highlighting in the document.
0106      * This is useful for updating this menu, when changing the syntax highlighting
0107      * from another menu, or from an external one. This doesn't hide or show the menu.
0108      * @param nameMode Raw name of the syntax highlight definition. If it's empty,
0109      *                 the "Normal" mode will be used.
0110      * @return True if @p nameMode exists and is selected.
0111      */
0112     bool selectHighlightingFromExternal(const QString &nameMode);
0113     /**
0114      * Update the selected item in the list widget, but without changing
0115      * the syntax highlighting in the document. This doesn't hide or show the menu.
0116      * The menu is kept updated according to the active syntax highlighting,
0117      * obtained from the KTextEditor::DocumentPrivate class.
0118      * @return True if the item is selected correctly.
0119      * @see KTextEditor::DocumentPrivate::fileType()
0120      */
0121     bool selectHighlightingFromExternal();
0122 
0123     /**
0124      * Set the button that shows this menu. It allows to update the label
0125      * of the button and define the alignment of the menu with respect to it.
0126      * This function doesn't call QPushButton::setMenu().
0127      * @param button Trigger button.
0128      * @param positionX Horizontal position of the menu with respect to the trigger button.
0129      * @param positionY Vertical position of the menu with respect to the trigger button.
0130      * @param autoUpdateTextButton Determines whether the text of the button should be
0131      *        changed when selecting an item from the menu.
0132      *
0133      * @see AlignmentHButton, AlignmentVButton, AutoUpdateTextButton
0134      */
0135     void setButton(QPushButton *button,
0136                    AlignmentHButton positionX = AlignHDefault,
0137                    AlignmentVButton positionY = AlignTop,
0138                    AutoUpdateTextButton autoUpdateTextButton = AutoUpdateTextButton(false));
0139 
0140     /**
0141      * Define the scroll when cleaning the search or changing the view.
0142      * The default value is AutoScroll::ScrollToSelectedItem.
0143      * @see AutoScroll
0144      */
0145     void setAutoScroll(AutoScroll scroll)
0146     {
0147         m_autoScroll = scroll;
0148     }
0149 
0150     /**
0151      * Set document to apply the syntax highlighting.
0152      * @see KTextEditor::DocumentPrivate
0153      */
0154     void updateMenu(KTextEditor::Document *doc);
0155 
0156 protected:
0157     friend KateModeMenuListData::ListView;
0158     friend KateModeMenuListData::ListItem;
0159     friend KateModeMenuListData::SearchLine;
0160 
0161     /**
0162      * Action when displaying the menu.
0163      * Override from QWidget.
0164      */
0165     void showEvent(QShowEvent *event) override;
0166 
0167 private:
0168     void init();
0169 
0170     void onAboutToShowMenu();
0171 
0172     /**
0173      * Define the size of the list widget, in pixels. The @p width is also
0174      * applied to the search bar. This does not recalculate the word wrap in items.
0175      */
0176     inline void setSizeList(const int height, const int width = 266);
0177 
0178     /**
0179      * Load the data model with the syntax highlighting definitions to show in the list.
0180      */
0181     void loadHighlightingModel();
0182 
0183     /**
0184      * Scroll the list, according to AutoScroll.
0185      * @see AutoScroll
0186      */
0187     void autoScroll();
0188 
0189     /**
0190      * Set a custom word wrap on a text line, according to a maximum width (in pixels).
0191      * @param text Line of text
0192      * @param maxWidth Width of the text container, in pixels.
0193      * @param fontMetrics Font metrics. See QWidget::fontMetrics()
0194      */
0195     QString setWordWrap(const QString &text, const int maxWidth, const QFontMetrics &fontMetrics) const;
0196 
0197     /**
0198      * Update the selected item in the list, with the active syntax highlighting.
0199      * This method only changes the selected item, with the checkbox icon, doesn't apply
0200      * syntax highlighting in the document or hides the menu.
0201      * @see selectHighlighting(), selectHighlightingFromExternal(), selectHighlightingSetVisibility()
0202      */
0203     void updateSelectedItem(KateModeMenuListData::ListItem *item);
0204 
0205     /**
0206      * Select an item from the list and apply the syntax highlighting in the document.
0207      * This is equivalent to the slot: KateModeMenuList::selectHighlighting().
0208      * @param bHideMenu If the menu should be hidden after applying the highlight.
0209      * @see selectHighlighting()
0210      */
0211     void selectHighlightingSetVisibility(QStandardItem *pItem, const bool bHideMenu);
0212 
0213     /**
0214      * Create a new section in the list of items and add it to the model.
0215      * It corresponds to a separator line and a title.
0216      * @param sectionName Section title.
0217      * @param background Background color is generally transparent.
0218      * @param bSeparator True if a separation line will also be created before the section title.
0219      * @param modelPosition Position in the model where to insert the new section. If the value is
0220      *                      less than zero, the section is added to the end of the list/model.
0221      * @return A pointer to the item created with the section title.
0222      */
0223     KateModeMenuListData::ListItem *createSectionList(const QString &sectionName, const QBrush &background, bool bSeparator = true, int modelPosition = -1);
0224 
0225     /**
0226      * Load message when the list is empty in the search.
0227      */
0228     void loadEmptyMsg();
0229 
0230     AutoScroll m_autoScroll = ScrollToSelectedItem;
0231     AlignmentHButton m_positionX;
0232     AlignmentVButton m_positionY;
0233     AutoUpdateTextButton m_autoUpdateTextButton;
0234 
0235     QPointer<QPushButton> m_pushButton = nullptr;
0236     QLabel *m_emptyListMsg = nullptr;
0237     QGridLayout *m_layoutList = nullptr;
0238     QScrollBar *m_scroll = nullptr;
0239 
0240     KateModeMenuListData::SearchLine *m_searchBar = nullptr;
0241     KateModeMenuListData::ListView *m_list = nullptr;
0242     QStandardItemModel *m_model = nullptr;
0243 
0244     /**
0245      * Item with active syntax highlighting.
0246      */
0247     KateModeMenuListData::ListItem *m_selectedItem = nullptr;
0248 
0249     /**
0250      * Icon for selected/active item (checkbox).
0251      * NOTE: Selected and inactive items show an icon with incorrect color,
0252      * however, this isn't a problem, since the list widget is never inactive.
0253      */
0254     const QIcon m_checkIcon = QIcon::fromTheme(QStringLiteral("checkbox"));
0255     QIcon m_emptyIcon;
0256     int m_iconSize = 16;
0257 
0258     int m_defaultHeightItemSection;
0259 
0260     QPointer<KTextEditor::DocumentPrivate> m_doc;
0261 
0262     bool m_initialized = false;
0263 
0264 private:
0265     /**
0266      * Action when selecting a item in the list. This also applies
0267      * the syntax highlighting in the document and hides the menu.
0268      * This is equivalent to KateModeMenuList::selectHighlightingSetVisibility().
0269      * @see selectHighlightingSetVisibility(), updateSelectedItem()
0270      */
0271     void selectHighlighting(const QModelIndex &index);
0272 };
0273 
0274 namespace KateModeMenuListData
0275 {
0276 /**
0277  * Class of List Widget.
0278  */
0279 class ListView : public QListView
0280 {
0281 private:
0282     ListView(KateModeMenuList *menu)
0283         : QListView(menu)
0284     {
0285         m_parentMenu = menu;
0286     }
0287 
0288 public:
0289     ~ListView() override
0290     {
0291     }
0292 
0293     /**
0294      * Define the size of the widget list.
0295      * @p height and @p width are values in pixels.
0296      */
0297     void setSizeList(const int height, const int width = 266);
0298 
0299     /**
0300      * Get the width of the list, in pixels.
0301      * @see QAbstractScrollArea::sizeHint()
0302      */
0303     inline int getWidth() const;
0304 
0305     /**
0306      * Get the width of the contents of the list (in pixels), that is,
0307      * the list minus the scroll bar and margins.
0308      */
0309     int getContentWidth() const;
0310 
0311     /**
0312      * Get the width of the contents of the list (in pixels), that is, the list minus
0313      * the scroll bar and margins. The parameter allows you to specify additional margins
0314      * according to the scroll bar, which can be superimposed or fixed depending to the
0315      * desktop environment or operating system.
0316      * @param overlayScrollbarMargin Additional margin for the scroll bar, if it is
0317      *                               superimposed on the list.
0318      * @param classicScrollbarMargin Additional margin for the scroll bar, if fixed in the list.
0319      */
0320     inline int getContentWidth(const int overlayScrollbarMargin, const int classicScrollbarMargin) const;
0321 
0322     inline void setCurrentItem(const int rowItem)
0323     {
0324         selectionModel()->setCurrentIndex(m_parentMenu->m_model->index(rowItem, 0), QItemSelectionModel::ClearAndSelect);
0325     }
0326 
0327     inline QStandardItem *currentItem() const
0328     {
0329         return m_parentMenu->m_model->item(currentIndex().row(), 0);
0330     }
0331 
0332     inline void scrollToItem(const int rowItem, QAbstractItemView::ScrollHint hint = QAbstractItemView::PositionAtCenter)
0333     {
0334         scrollTo(m_parentMenu->m_model->index(rowItem, 0), hint);
0335     }
0336 
0337     inline void scrollToFirstItem()
0338     {
0339         setCurrentItem(1);
0340         scrollToTop();
0341     }
0342 
0343 protected:
0344     /**
0345      * Override from QListView.
0346      */
0347     void keyPressEvent(QKeyEvent *event) override;
0348 
0349 private:
0350     KateModeMenuList *m_parentMenu = nullptr;
0351     friend Factory;
0352 };
0353 
0354 /**
0355  * Class of an Item of the Data Model of the List.
0356  * @see KateModeMenuListData::ListView, KateFileType, QStandardItemModel
0357  */
0358 class ListItem : public QStandardItem
0359 {
0360 private:
0361     ListItem()
0362         : QStandardItem()
0363     {
0364     }
0365 
0366     const KateFileType *m_type = nullptr;
0367     QString m_searchName;
0368 
0369     friend Factory;
0370 
0371 public:
0372     ~ListItem() override
0373     {
0374     }
0375 
0376     /**
0377      * Associate this item with a KateFileType object.
0378      */
0379     inline void setMode(KateFileType *type)
0380     {
0381         m_type = type;
0382     }
0383     const KateFileType *getMode() const
0384     {
0385         return m_type;
0386     }
0387     bool hasMode() const
0388     {
0389         return m_type;
0390     }
0391 
0392     /**
0393      * Generate name of the item used for the search.
0394      * @param itemName The item name.
0395      * @return True if a new name is generated for the search.
0396      */
0397     bool generateSearchName(const QString &itemName);
0398 
0399     /**
0400      * Find matches in the extensions of the item mode, with a @p text.
0401      * @param text Text to match, without dots or asterisks. For example, in
0402      *             a common extension, it corresponds to the text after "*."
0403      * @return True if a match is found, false if not.
0404      */
0405     bool matchExtension(const QString &text) const;
0406 
0407     const QString &getSearchName() const
0408     {
0409         return m_searchName;
0410     }
0411 };
0412 
0413 /**
0414  * Class of Search Bar.
0415  * Based on the KListWidgetSearchLine class.
0416  */
0417 class SearchLine : public QLineEdit
0418 {
0419 public:
0420     ~SearchLine() override
0421     {
0422         m_bestResults.clear();
0423     }
0424 
0425     /**
0426      * Define the width of the search bar, in pixels.
0427      */
0428     void setWidth(const int width);
0429 
0430 private:
0431     SearchLine(KateModeMenuList *menu)
0432         : QLineEdit(menu)
0433     {
0434         m_parentMenu = menu;
0435         init();
0436     }
0437 
0438     void init();
0439 
0440     /**
0441      * Select result of the items search.
0442      * Used only by KateModeMenuListData::SearchLine::updateSearch().
0443      */
0444     void setSearchResult(const int rowItem, bool &bEmptySection, int &lastSection, int &firstSection, int &lastItem);
0445 
0446     /**
0447      * Delay in search results after typing, in milliseconds.
0448      * Default value: 200
0449      */
0450     static const int m_searchDelay = 170;
0451 
0452     /**
0453      * This prevents auto-scrolling when the search is kept clean.
0454      */
0455     bool m_bSearchStateAutoScroll = false;
0456 
0457     QString m_search = QString();
0458     int m_queuedSearches = 0;
0459     Qt::CaseSensitivity m_caseSensitivity = Qt::CaseInsensitive;
0460 
0461     /**
0462      * List of items to display in the "Best Search Matches" section. The integer value
0463      * corresponds to the original position of the item in the model. The purpose of this
0464      * is to restore the position of the items when starting or cleaning a search.
0465      */
0466     QList<QPair<ListItem *, int>> m_bestResults;
0467 
0468     KateModeMenuList *m_parentMenu = nullptr;
0469     friend Factory;
0470     friend void KateModeMenuList::reloadItems();
0471 
0472 protected:
0473     /**
0474      * Override from QLineEdit. This allows you to navigate through
0475      * the menu and write in the search bar simultaneously with the keyboard.
0476      */
0477     void keyPressEvent(QKeyEvent *event) override;
0478 
0479 public:
0480     virtual void clear();
0481     virtual void updateSearch(const QString &s = QString());
0482 
0483 private:
0484     void _k_queueSearch(const QString &s);
0485     void _k_activateSearch();
0486 };
0487 
0488 class Factory
0489 {
0490 private:
0491     friend KateModeMenuList;
0492     Factory(){};
0493     static ListView *createListView(KateModeMenuList *parentMenu)
0494     {
0495         return new ListView(parentMenu);
0496     }
0497     static ListItem *createListItem()
0498     {
0499         return new ListItem();
0500     }
0501     static SearchLine *createSearchLine(KateModeMenuList *parentMenu)
0502     {
0503         return new SearchLine(parentMenu);
0504     }
0505 };
0506 }
0507 
0508 #endif // KATEMODEMENULIST_H