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

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