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