1/*
2 SPDX-FileCopyrightText: 2019-2020 Nibaldo González S. <nibgonz@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7/*
8 * NOTE: The KateModeMenuListData::SearchLine class is based on
9 * KListWidgetSearchLine, by Scott Wheeler <wheeler@kde.org> and
10 * Gustavo Sverzut Barbieri <gsbarbieri@users.sourceforge.net>.
11 * See: https://api.kde.org/frameworks/kitemviews/html/classKListWidgetSearchLine.html
12 *
13 * TODO: Add keyboard shortcut to show the menu.
14 * See: KateModeMenuList::showEvent()
15 */
16
17#ifndef KATEMODEMENULIST_H
18#define KATEMODEMENULIST_H
19
20#include <QGridLayout>
21#include <QIcon>
22#include <QKeyEvent>
23#include <QLabel>
24#include <QLineEdit>
25#include <QListView>
26#include <QMenu>
27#include <QPointer>
28#include <QPushButton>
29#include <QScrollBar>
30#include <QStandardItemModel>
31#include <QString>
32
33namespace KTextEditor
34{
35class DocumentPrivate;
36class Document;
37}
38
39namespace KateModeMenuListData
40{
41class ListView;
42class ListItem;
43class SearchLine;
44class Factory;
45}
46
47class KateFileType;
48/**
49 * Class of menu to select the
50 * syntax highlighting language (mode menu).
51 * Provides a menu with a scrollable list plus search bar.
52 *
53 * This is an alternative to the classic mode menu of the KateModeMenu class.
54 *
55 * @see KateModeManager, KateFileType, KateModeMenu
56 */
57class KateModeMenuList : public QMenu
58{
59public:
60 /**
61 * Horizontal Alignment with respect to the trigger button.
62 * "AlignHDefault" is the normal alignment.
63 * "AlignHInverse" uses right alignment in Left-to-right layouts and
64 * left alignmentnin Right-to-left layouts (used in some languages).
65 * "AlignLeft" and "AlignRight" forces the alignment, regardless of the layout direction.
66 * @see setButton(), QWidget::layoutDirection(), Qt::LayoutDirection
67 */
68 enum AlignmentHButton {
69 AlignHDefault,
70 AlignHInverse,
71 AlignLeft,
72 AlignRight
73 };
74 /**
75 * Vertical Alignment with respect to the trigger button.
76 * "AlignVDefault" uses normal alignment (below the button) and "AlignTop"
77 * forces the alignment above the trigger button.
78 * @see setButton(), KateStatusBarOpenUpMenu::setVisible()
79 */
80 enum AlignmentVButton {
81 AlignVDefault,
82 AlignTop
83 };
84 /**
85 * Define if the trigger button label must be updated when selecting an item.
86 * @see setButton()
87 */
88 enum class AutoUpdateTextButton : bool;
89 /**
90 * Search bar position, above or below the list.
91 */
92 enum SearchBarPosition {
93 Top,
94 Bottom
95 };
96 /**
97 * Defines where the list will scroll after clearing the search or changing the view.
98 * @see setAutoScroll(), autoScroll()
99 */
100 enum AutoScroll {
101 ScrollToSelectedItem,
102 ScrollToTop
103 };
104
105 KateModeMenuList(const QString &title, QWidget *parent)
106 : QMenu(title, parent)
107 {
108 init();
109 }
110
111 /**
112 * Reload all items.
113 * @see KateModeManager::update()
114 */
115 void reloadItems();
116
117 /**
118 * Update the selected item in the list widget, but without changing
119 * the syntax highlighting in the document.
120 * This is useful for updating this menu, when changing the syntax highlighting
121 * from another menu, or from an external one. This doesn't hide or show the menu.
122 * @param nameMode Raw name of the syntax highlight definition. If it's empty,
123 * the "Normal" mode will be used.
124 * @return True if @p nameMode exists and is selected.
125 */
126 bool selectHighlightingFromExternal(const QString &nameMode);
127 /**
128 * Update the selected item in the list widget, but without changing
129 * the syntax highlighting in the document. This doesn't hide or show the menu.
130 * The menu is kept updated according to the active syntax highlighting,
131 * obtained from the KTextEditor::DocumentPrivate class.
132 * @return True if the item is selected correctly.
133 * @see KTextEditor::DocumentPrivate::fileType()
134 */
135 bool selectHighlightingFromExternal();
136
137 /**
138 * Set the button that shows this menu. It allows to update the label
139 * of the button and define the alignment of the menu with respect to it.
140 * This function doesn't call QPushButton::setMenu().
141 * @param button Trigger button.
142 * @param positionX Horizontal position of the menu with respect to the trigger button.
143 * @param positionY Vertical position of the menu with respect to the trigger button.
144 * @param autoUpdateTextButton Determines whether the text of the button should be
145 * changed when selecting an item from the menu.
146 *
147 * @see AlignmentHButton, AlignmentVButton, AutoUpdateTextButton
148 */
149 void setButton(QPushButton *button,
150 AlignmentHButton positionX = AlignHDefault,
151 AlignmentVButton positionY = AlignTop,
152 AutoUpdateTextButton autoUpdateTextButton = AutoUpdateTextButton(false));
153
154 /**
155 * Define the scroll when cleaning the search or changing the view.
156 * The default value is AutoScroll::ScrollToSelectedItem.
157 * @see AutoScroll
158 */
159 void setAutoScroll(AutoScroll scroll)
160 {
161 m_autoScroll = scroll;
162 }
163
164 /**
165 * Set document to apply the syntax highlighting.
166 * @see KTextEditor::DocumentPrivate
167 */
168 void updateMenu(KTextEditor::Document *doc);
169
170protected:
171 friend KateModeMenuListData::ListView;
172 friend KateModeMenuListData::ListItem;
173 friend KateModeMenuListData::SearchLine;
174
175 /**
176 * Action when displaying the menu.
177 * Override from QWidget.
178 */
179 void showEvent(QShowEvent *event) override;
180
181private:
182 void init();
183
184 void onAboutToShowMenu();
185
186 /**
187 * Define the size of the list widget, in pixels. The @p width is also
188 * applied to the search bar. This does not recalculate the word wrap in items.
189 */
190 inline void setSizeList(const int height, const int width = 266);
191
192 /**
193 * Load the data model with the syntax highlighting definitions to show in the list.
194 */
195 void loadHighlightingModel();
196
197 /**
198 * Scroll the list, according to AutoScroll.
199 * @see AutoScroll
200 */
201 void autoScroll();
202
203 /**
204 * Set a custom word wrap on a text line, according to a maximum width (in pixels).
205 * @param text Line of text
206 * @param maxWidth Width of the text container, in pixels.
207 * @param fontMetrics Font metrics. See QWidget::fontMetrics()
208 */
209 QString setWordWrap(const QString &text, const int maxWidth, const QFontMetrics &fontMetrics) const;
210
211 /**
212 * Update the selected item in the list, with the active syntax highlighting.
213 * This method only changes the selected item, with the checkbox icon, doesn't apply
214 * syntax highlighting in the document or hides the menu.
215 * @see selectHighlighting(), selectHighlightingFromExternal(), selectHighlightingSetVisibility()
216 */
217 void updateSelectedItem(KateModeMenuListData::ListItem *item);
218
219 /**
220 * Select an item from the list and apply the syntax highlighting in the document.
221 * This is equivalent to the slot: KateModeMenuList::selectHighlighting().
222 * @param bHideMenu If the menu should be hidden after applying the highlight.
223 * @see selectHighlighting()
224 */
225 void selectHighlightingSetVisibility(QStandardItem *pItem, const bool bHideMenu);
226
227 /**
228 * Create a new section in the list of items and add it to the model.
229 * It corresponds to a separator line and a title.
230 * @param sectionName Section title.
231 * @param background Background color is generally transparent.
232 * @param bSeparator True if a separation line will also be created before the section title.
233 * @param modelPosition Position in the model where to insert the new section. If the value is
234 * less than zero, the section is added to the end of the list/model.
235 * @return A pointer to the item created with the section title.
236 */
237 KateModeMenuListData::ListItem *createSectionList(const QString &sectionName, const QBrush &background, bool bSeparator = true, int modelPosition = -1);
238
239 /**
240 * Load message when the list is empty in the search.
241 */
242 void loadEmptyMsg();
243
244 AutoScroll m_autoScroll = ScrollToSelectedItem;
245 AlignmentHButton m_positionX;
246 AlignmentVButton m_positionY;
247 AutoUpdateTextButton m_autoUpdateTextButton;
248
249 QPointer<QPushButton> m_pushButton = nullptr;
250 QLabel *m_emptyListMsg = nullptr;
251 QGridLayout *m_layoutList = nullptr;
252 QScrollBar *m_scroll = nullptr;
253
254 KateModeMenuListData::SearchLine *m_searchBar = nullptr;
255 KateModeMenuListData::ListView *m_list = nullptr;
256 QStandardItemModel *m_model = nullptr;
257
258 /**
259 * Item with active syntax highlighting.
260 */
261 KateModeMenuListData::ListItem *m_selectedItem = nullptr;
262
263 /**
264 * Icon for selected/active item (checkbox).
265 * NOTE: Selected and inactive items show an icon with incorrect color,
266 * however, this isn't a problem, since the list widget is never inactive.
267 */
268 const QIcon m_checkIcon = QIcon::fromTheme(QStringLiteral("checkbox"));
269 QIcon m_emptyIcon;
270 int m_iconSize = 16;
271
272 int m_defaultHeightItemSection;
273
274 QPointer<KTextEditor::DocumentPrivate> m_doc;
275
276 bool m_initialized = false;
277
278private:
279 /**
280 * Action when selecting a item in the list. This also applies
281 * the syntax highlighting in the document and hides the menu.
282 * This is equivalent to KateModeMenuList::selectHighlightingSetVisibility().
283 * @see selectHighlightingSetVisibility(), updateSelectedItem()
284 */
285 void selectHighlighting(const QModelIndex &index);
286};
287
288namespace KateModeMenuListData
289{
290/**
291 * Class of List Widget.
292 */
293class ListView : public QListView
294{
295private:
296 ListView(KateModeMenuList *menu)
297 : QListView(menu)
298 {
299 m_parentMenu = menu;
300 }
301
302public:
303 ~ListView() override
304 {
305 }
306
307 /**
308 * Define the size of the widget list.
309 * @p height and @p width are values in pixels.
310 */
311 void setSizeList(const int height, const int width = 266);
312
313 /**
314 * Get the width of the list, in pixels.
315 * @see QAbstractScrollArea::sizeHint()
316 */
317 inline int getWidth() const;
318
319 /**
320 * Get the width of the contents of the list (in pixels), that is,
321 * the list minus the scroll bar and margins.
322 */
323 int getContentWidth() const;
324
325 /**
326 * Get the width of the contents of the list (in pixels), that is, the list minus
327 * the scroll bar and margins. The parameter allows you to specify additional margins
328 * according to the scroll bar, which can be superimposed or fixed depending to the
329 * desktop environment or operating system.
330 * @param overlayScrollbarMargin Additional margin for the scroll bar, if it is
331 * superimposed on the list.
332 * @param classicScrollbarMargin Additional margin for the scroll bar, if fixed in the list.
333 */
334 inline int getContentWidth(const int overlayScrollbarMargin, const int classicScrollbarMargin) const;
335
336 inline void setCurrentItem(const int rowItem)
337 {
338 selectionModel()->setCurrentIndex(index: m_parentMenu->m_model->index(row: rowItem, column: 0), command: QItemSelectionModel::ClearAndSelect);
339 }
340
341 inline QStandardItem *currentItem() const
342 {
343 return m_parentMenu->m_model->item(row: currentIndex().row(), column: 0);
344 }
345
346 inline void scrollToItem(const int rowItem, QAbstractItemView::ScrollHint hint = QAbstractItemView::PositionAtCenter)
347 {
348 scrollTo(index: m_parentMenu->m_model->index(row: rowItem, column: 0), hint);
349 }
350
351 inline void scrollToFirstItem()
352 {
353 setCurrentItem(1);
354 scrollToTop();
355 }
356
357protected:
358 /**
359 * Override from QListView.
360 */
361 void keyPressEvent(QKeyEvent *event) override;
362
363private:
364 KateModeMenuList *m_parentMenu = nullptr;
365 friend Factory;
366};
367
368/**
369 * Class of an Item of the Data Model of the List.
370 * @see KateModeMenuListData::ListView, KateFileType, QStandardItemModel
371 */
372class ListItem : public QStandardItem
373{
374private:
375 ListItem()
376 : QStandardItem()
377 {
378 }
379
380 const KateFileType *m_type = nullptr;
381 QString m_searchName;
382
383 friend Factory;
384
385public:
386 ~ListItem() override
387 {
388 }
389
390 /**
391 * Associate this item with a KateFileType object.
392 */
393 inline void setMode(KateFileType *type)
394 {
395 m_type = type;
396 }
397 const KateFileType *getMode() const
398 {
399 return m_type;
400 }
401 bool hasMode() const
402 {
403 return m_type;
404 }
405
406 /**
407 * Generate name of the item used for the search.
408 * @param itemName The item name.
409 * @return True if a new name is generated for the search.
410 */
411 bool generateSearchName(const QString &itemName);
412
413 /**
414 * Find matches in the extensions of the item mode, with a @p text.
415 * @param text Text to match, without dots or asterisks. For example, in
416 * a common extension, it corresponds to the text after "*."
417 * @return True if a match is found, false if not.
418 */
419 bool matchExtension(const QString &text) const;
420
421 const QString &getSearchName() const
422 {
423 return m_searchName;
424 }
425};
426
427/**
428 * Class of Search Bar.
429 * Based on the KListWidgetSearchLine class.
430 */
431class SearchLine : public QLineEdit
432{
433public:
434 ~SearchLine() override
435 {
436 m_bestResults.clear();
437 }
438
439 /**
440 * Define the width of the search bar, in pixels.
441 */
442 void setWidth(const int width);
443
444private:
445 SearchLine(KateModeMenuList *menu)
446 : QLineEdit(menu)
447 {
448 m_parentMenu = menu;
449 init();
450 }
451
452 void init();
453
454 /**
455 * Select result of the items search.
456 * Used only by KateModeMenuListData::SearchLine::updateSearch().
457 */
458 void setSearchResult(const int rowItem, bool &bEmptySection, int &lastSection, int &firstSection, int &lastItem);
459
460 /**
461 * Delay in search results after typing, in milliseconds.
462 * Default value: 200
463 */
464 static const int m_searchDelay = 170;
465
466 /**
467 * This prevents auto-scrolling when the search is kept clean.
468 */
469 bool m_bSearchStateAutoScroll = false;
470
471 QString m_search = QString();
472 int m_queuedSearches = 0;
473 Qt::CaseSensitivity m_caseSensitivity = Qt::CaseInsensitive;
474
475 /**
476 * List of items to display in the "Best Search Matches" section. The integer value
477 * corresponds to the original position of the item in the model. The purpose of this
478 * is to restore the position of the items when starting or cleaning a search.
479 */
480 QList<QPair<ListItem *, int>> m_bestResults;
481
482 KateModeMenuList *m_parentMenu = nullptr;
483 friend Factory;
484 friend void KateModeMenuList::reloadItems();
485
486protected:
487 /**
488 * Override from QLineEdit. This allows you to navigate through
489 * the menu and write in the search bar simultaneously with the keyboard.
490 */
491 void keyPressEvent(QKeyEvent *event) override;
492
493public:
494 virtual void clear();
495 virtual void updateSearch(const QString &s = QString());
496
497private:
498 void _k_queueSearch(const QString &s);
499 void _k_activateSearch();
500};
501
502class Factory
503{
504private:
505 friend KateModeMenuList;
506 Factory() { };
507 static ListView *createListView(KateModeMenuList *parentMenu)
508 {
509 return new ListView(parentMenu);
510 }
511 static ListItem *createListItem()
512 {
513 return new ListItem();
514 }
515 static SearchLine *createSearchLine(KateModeMenuList *parentMenu)
516 {
517 return new SearchLine(parentMenu);
518 }
519};
520}
521
522#endif // KATEMODEMENULIST_H
523

source code of ktexteditor/src/mode/katemodemenulist.h