MauiKit TextEditor

TextEditor.qml
1import QtQuick
2import QtQml
3import QtQuick.Controls
4import QtQuick.Layouts
5
6import org.mauikit.controls as Maui
7import org.mauikit.texteditor as TE
8
9import org.kde.sonnet as Sonnet
10
11/**
12 * @since org.mauikit.texteditor 1.0
13 * @brief Integrated text editor component
14 *
15 * A text area for editing text with convinient functions.
16 * The Editor is controlled by the DocumentHandler which controls the files I/O, the syntax highlighting styles, and many more text editing properties.
17 *
18 * @section features Features
19 *
20 * The TextEditor control comes with a set of built-in features, such as find & replace, syntax highlighting support, line number sidebar, I/O capabilities, file document alerts, and syntax corrector.
21 *
22 * @subsection io I/O
23 * Opening a local text file is handle by the DocumentHandler via the `fileUrl` property. The document contents will be loaded by the FileLoader and made available to the TextEditor for drawing.
24 *
25 * @see DocumentHandler::fileUrl
26 *
27 * @warning Opening large contents will cause the app to freeze, since it is not optimized to dynamically allocate the contents by chunks and instead all of the content will be rendered at once. A solution with a different backend is being implemented.
28 *
29 * Once an existing document is opened or created, it will also be watched for any external changes, such as modifications to its contents or its removal, those changes will be notified via the alert bars, exposing the avaliable options.
30 * @see DocumentHandler::autoReload
31 *
32 * @image html alert_bars.png
33 *
34 * To save any changes made to an existing document or to save a new one manually use the exposed method DocumentHandler::saveAs, which will take as parameter the location where to save the file at, if you mean to save the changes to an already existing file, simply pass the DocumentHandler::fileUrl value.
35 * The changes made could be automatically saved every few seconds if the DocumentHandler::autoSave property is enabled.
36 *
37 * @code
38 * ToolButton
39 * {
40 * icon.name: "folder-open"
41 * onClicked: _editor.fileUrl = "file:///home/camiloh/nota/CMakeLists.txt"
42 * }
43 *
44 * ...
45 *
46 * TE.TextEditor
47 * {
48 * id: _editor
49 * anchors.fill: parent
50 * body.wrapMode: Text.NoWrap
51 * document.enableSyntaxHighlighting: true
52 * }
53 * @endcode
54 *
55 * @subsection syntax_highlighting Syntax Highlighting
56 *
57 * To enable the syntax highlighting enable the DocumentHandler::enableSyntaxHighlighting property.
58 *
59 * @note If the language is not detected automatically or if you desire to change it, use the `showSyntaxHighlightingLanguages` property to toggle the selection combobox to allow the user to select a custom language, and bind it to the DocumentHandler::formatName property.
60 *
61 * There are different color schemes available, those can be set using the DocumentHandler::theme property. You can also use the ColorSchemesPage control which lists all the available options.
62 *
63 * @subsection other Others
64 *
65 * The find & replace bars can be toggled using the `showFindBar` property.
66 * @see DocumentHandler::findWholeWords
67 * @see DocumentHandler::findCaseSensitively
68 *
69 * To enable the line number sidebar use the `showLineNumbers` property.
70 *
71 * Spell checking can be anbled using the `spellcheckEnabled` property. For this Sonnet must be available.
72 *
73 * @subsection fonts Fonts & Colors
74 *
75 * For tweaking the font properties and colors use the DocumentHandler::textColor and DocumentHandler::backgroundColor, etc.
76 *
77 * For more details and properties check the own DocumentHandler properties.
78 */
79Page
80{
81 id: control
82
83 padding: 0
84 focus: false
85 clip: false
86 title: document.fileName + (document.modified ? "*" : "")
87
88 /**
89 * @brief
90 */
91 property bool showFindBar: false
92
93 onShowFindBarChanged:
94 {
95 if(showFindBar)
96 {
97 _findField.forceActiveFocus()
98 }else
99 {
100 body.forceActiveFocus()
101 }
103
104 onWidthChanged: body.update()
105
106 onHeightChanged: body.update()
107
108 /**
109 * @brief Access to the editor text area.
110 * @property TextArea TextEditor::body
111 */
112 readonly property alias body : body
113
114 /**
115 * @brief Alias to access the DocumentHandler
116 * @property DocumentHandler TextEditor::document
117 */
118 readonly property alias document : document
120 /**
121 * @brief Alias to the ScrollView
122 * @property ScrollView TextEditor::scrollView
123 */
124 readonly property alias scrollView: _scrollView
125
126 /**
127 * @brief Alias to the contextual menu. This menu is loaded asynchronous.
128 * @property Menu TextEditor::documentMenu
129 */
130 readonly property alias documentMenu : _documentMenuLoader.item
131
132 /**
133 * @brief Alias to the text area text content
134 * @property string TextEditor::text
135 */
136 property alias text: body.text
137
138 /**
139 * @see DocumentHandler::uppercase
140 * @property bool TextEditor::uppercase
141 */
142 property alias uppercase: document.uppercase
143
144 /**
145 * @see DocumentHandler::underline
146 * @property bool TextEditor::underline
147 */
148 property alias underline: document.underline
149
150 /**
151 * @see DocumentHandler::italic
152 * @property bool TextEditor::italic
153 */
154 property alias italic: document.italic
156 /**
157 * @see DocumentHandler::bold
158 * @property bool TextEditor::bold
159 */
160 property alias bold: document.bold
161
162 /**
163 * @brief Whether there are modifications to the document that can be redo. Alias to the TextArea::canRedo
164 * @property bool TextEditor::canRedo
165 */
166 property alias canRedo: body.canRedo
167
168 /**
169 * @brief If a file url is provided the DocumentHandler will try to open its contents and display it
170 * @see DocumentHandler::fileUrl
171 * @property url TextEditor::fileUrl
172 */
173 property alias fileUrl : document.fileUrl
174
175 /**
176 * @brief If a sidebar listing each line number should be visible.
177 * By default this is set to `false`
178 */
179 property bool showLineNumbers : false
181 /**
182 * @brief Whether to enable the spell checker.
183 * By default this is set to `false`
184 */
185 property bool spellcheckEnabled: false
186
187 FontMetrics
188 {
189 id: fontMetrics
190 font: body.font
191 }
192
193 TE.DocumentHandler
194 {
195 id: document
196 document: body.textDocument
197 cursorPosition: body.cursorPosition
198 selectionStart: body.selectionStart
199 selectionEnd: body.selectionEnd
200 backgroundColor: control.Maui.Theme.backgroundColor
201 enableSyntaxHighlighting: false
202 findCaseSensitively: _findCaseSensitively.checked
203 findWholeWords: _findWholeWords.checked
204
205 onSearchFound: (start, end) =>
206 {
207 body.select(start, end)
208 }
209 }
210
211 Loader
212 {
213 id: spellcheckhighlighterLoader
214 property bool activable: control.spellcheckEnabled
215 property Sonnet.Settings settings: Sonnet.Settings {}
216 active: activable && settings.checkerEnabledByDefault
217 onActiveChanged:
218 {
219 if (active)
220 {
221 item.active = true;
222 }
223 }
224
225 sourceComponent: Sonnet.SpellcheckHighlighter
226 {
227 id: spellcheckhighlighter
228 document: body.textDocument
229 cursorPosition: body.cursorPosition
230 selectionStart: body.selectionStart
231 selectionEnd: body.selectionEnd
232 misspelledColor: Maui.Theme.negativeTextColor
233 active: spellcheckhighlighterLoader.activable && settings.checkerEnabledByDefault
234
235 onChangeCursorPosition: (start, end) =>
236 {
237 body.cursorPosition = start;
238 body.moveCursorSelection(end, TextEdit.SelectCharacters);
239 }
240 }
241 }
242
243 Loader
244 {
245 id: _documentMenuLoader
246
247 asynchronous: true
248 sourceComponent: Maui.ContextualMenu
249 {
250 property var spellcheckhighlighter: null
251 property var spellcheckhighlighterLoader: null
252 property int restoredCursorPosition: 0
253 property int restoredSelectionStart
254 property int restoredSelectionEnd
255 property var suggestions: []
256 property bool deselectWhenMenuClosed: true
257 property var runOnMenuClose: () => {}
258 property bool persistentSelectionSetting
259
260 Component.onCompleted:
261 {
262 persistentSelectionSetting = body.persistentSelection
263 }
264
265 Maui.MenuItemActionRow
266 {
267 Action
268 {
269 icon.name: "edit-undo-symbolic"
270 text: i18n("Undo")
271 shortcut: StandardKey.Undo
272
273 onTriggered:
274 {
275 documentMenu.deselectWhenMenuClosed = false;
276 documentMenu.runOnMenuClose = () => body.undo();
277 }
278 }
279
280 Action
281 {
282 icon.name: "edit-redo-symbolic"
283 text: i18n("Redo")
284 shortcut: StandardKey.Redo
285
286 onTriggered:
287 {
288 documentMenu.deselectWhenMenuClosed = false;
289 documentMenu.runOnMenuClose = () => body.redo();
290 }
291 }
292 }
293
294 MenuItem
295 {
296 action: Action
297 {
298 icon.name: "edit-copy-symbolic"
299 text: i18n("Copy")
300 shortcut: StandardKey.Copy
301 }
302
303 onTriggered:
304 {
305 documentMenu.deselectWhenMenuClosed = false;
306 documentMenu.runOnMenuClose = () => control.body.copy();
307 }
308
309 enabled: body.selectedText.length
310 }
311
312 MenuItem
313 {
314 action: Action {
315 icon.name: "edit-cut-symbolic"
316 text: i18n("Cut")
317 shortcut: StandardKey.Cut
318 }
319 onTriggered:
320 {
321 documentMenu.deselectWhenMenuClosed = false;
322 documentMenu.runOnMenuClose = () => control.body.cut();
323 }
324 enabled: !body.readOnly && body.selectedText.length
325 }
326
327 MenuItem
328 {
329 action: Action
330 {
331 icon.name: "edit-paste-symbolic"
332 text: i18n("Paste")
333 shortcut: StandardKey.Paste
334 }
335
336 onTriggered:
337 {
338 documentMenu.deselectWhenMenuClosed = false;
339 documentMenu.runOnMenuClose = () => control.body.paste();
340 }
341
342 enabled: !body.readOnly
343 }
344
345 MenuItem
346 {
347 action: Action
348 {
349 icon.name: "edit-select-all-symbolic"
350 text: i18n("Select All")
351 shortcut: StandardKey.SelectAll
352 }
353
354 onTriggered:
355 {
356 documentMenu.deselectWhenMenuClosed = false
357 documentMenu.runOnMenuClose = () => control.body.selectAll();
358 }
359 }
360
361 MenuItem
362 {
363 text: i18nd("mauikittexteditor","Search Selected Text on Google...")
364 onTriggered: Qt.openUrlExternally("https://www.google.com/search?q="+body.selectedText)
365 enabled: body.selectedText.length
366 }
367
368 MenuItem
369 {
370 enabled: !control.body.readOnly && control.body.selectedText
371 action: Action
372 {
373 icon.name: "edit-delete-symbolic"
374 text: i18n("Delete")
375 shortcut: StandardKey.Delete
376 }
377
378 onTriggered:
379 {
380 documentMenu.deselectWhenMenuClosed = false;
381 documentMenu.runOnMenuClose = () => control.body.remove(control.body.selectionStart, control.body.selectionEnd);
382 }
383 }
384
385 MenuSeparator
386 {
387 }
388
389 Menu
390 {
391 id: _spellingMenu
392 title: i18nd("mauikittexteditor","Spelling")
393 enabled: control.spellcheckEnabled
394
395 Instantiator
396 {
397 id: _suggestions
398 active: !control.body.readOnly && documentMenu.spellcheckhighlighter !== null && documentMenu.spellcheckhighlighter.active && documentMenu.spellcheckhighlighter.wordIsMisspelled
399 model: documentMenu.suggestions
400 delegate: MenuItem
401 {
402 text: modelData
403 onClicked:
404 {
405 documentMenu.deselectWhenMenuClosed = false;
406 documentMenu.runOnMenuClose = () => documentMenu.spellcheckhighlighter.replaceWord(modelData);
407 }
408 }
409
410 onObjectAdded: (index, object) =>
411 {
412 _spellingMenu.insertItem(0, object)
413 }
414
415 onObjectRemoved: (index, object) =>
416 {
417 _spellingMenu.removeItem(_spellingMenu.itemAt(0))
418 }
419 }
420
421 MenuSeparator
422 {
423 enabled: !control.body.readOnly && ((documentMenu.spellcheckhighlighter !== null && documentMenu.spellcheckhighlighter.active && documentMenu.spellcheckhighlighter.wordIsMisspelled) || (documentMenu.spellcheckhighlighterLoader && documentMenu.spellcheckhighlighterLoader.activable))
424 }
425
426 MenuItem
427 {
428 enabled: !control.body.readOnly && documentMenu.spellcheckhighlighter !== null && documentMenu.spellcheckhighlighter.active && documentMenu.spellcheckhighlighter.wordIsMisspelled && documentMenu.suggestions.length === 0
429 action: Action
430 {
431 text: documentMenu.spellcheckhighlighter ? i18n("No suggestions for \"%1\"").arg(documentMenu.spellcheckhighlighter.wordUnderMouse) : ''
432 enabled: false
433 }
434 }
435
436 MenuItem
437 {
438 enabled: !control.body.readOnly && documentMenu.spellcheckhighlighter !== null && documentMenu.spellcheckhighlighter.active && documentMenu.spellcheckhighlighter.wordIsMisspelled
439 action: Action
440 {
441 text: documentMenu.spellcheckhighlighter ? i18n("Add \"%1\" to dictionary").arg(documentMenu.spellcheckhighlighter.wordUnderMouse) : ''
442 onTriggered:
443 {
444 documentMenu.deselectWhenMenuClosed = false;
445 documentMenu.runOnMenuClose = () => spellcheckhighlighter.addWordToDictionary(documentMenu.spellcheckhighlighter.wordUnderMouse);
446 }
447 }
448 }
449
450 MenuItem
451 {
452 enabled: !control.body.readOnly && documentMenu.spellcheckhighlighter !== null && documentMenu.spellcheckhighlighter.active && documentMenu.spellcheckhighlighter.wordIsMisspelled
453 action: Action
454 {
455 text: i18n("Ignore")
456 onTriggered:
457 {
458 documentMenu.deselectWhenMenuClosed = false;
459 documentMenu.runOnMenuClose = () => documentMenu.spellcheckhighlighter.ignoreWord(documentMenu.spellcheckhighlighter.wordUnderMouse);
460 }
461 }
462 }
463
464 MenuItem
465 {
466 enabled: !control.body.readOnly && documentMenu.spellcheckhighlighterLoader && documentMenu.spellcheckhighlighterLoader.activable
467 checkable: true
468 checked: documentMenu.spellcheckhighlighter ? documentMenu.spellcheckhighlighter.active : false
469 text: i18n("Enable Spellchecker")
470 onCheckedChanged:
471 {
472 spellcheckhighlighterLoader.active = checked;
473 documentMenu.spellcheckhighlighter = documentMenu.spellcheckhighlighterLoader.item;
474 }
475 }
476 }
477
478 function targetClick(spellcheckhighlighter, mousePosition)
479 {
480 control.body.persistentSelection = true; // persist selection when menu is opened
481 documentMenu.spellcheckhighlighterLoader = spellcheckhighlighter;
482 if (spellcheckhighlighter && spellcheckhighlighter.active) {
483 documentMenu.spellcheckhighlighter = spellcheckhighlighter.item;
484 documentMenu.suggestions = mousePosition ? spellcheckhighlighter.item.suggestions(mousePosition) : [];
485 } else {
486 documentMenu.spellcheckhighlighter = null;
487 documentMenu.suggestions = [];
488 }
489
490 storeCursorAndSelection();
491 documentMenu.show()
492 }
493
494 function storeCursorAndSelection()
495 {
496 documentMenu.restoredCursorPosition = control.body.cursorPosition;
497 documentMenu.restoredSelectionStart = control.body.selectionStart;
498 documentMenu.restoredSelectionEnd = control.body.selectionEnd;
499 }
500
501 onOpened:
502 {
503 runOnMenuClose = () => {};
504 }
505
506 onClosed:
507 {
508 // restore text field's original persistent selection setting
509 body.persistentSelection = documentMenu.persistentSelectionSetting
510 // deselect text field text if menu is closed not because of a right click on the text field
511 if (documentMenu.deselectWhenMenuClosed)
512 {
513 body.deselect();
514 }
515 documentMenu.deselectWhenMenuClosed = true;
516
517 // restore cursor position
518 body.forceActiveFocus();
519 body.cursorPosition = documentMenu.restoredCursorPosition;
520 body.select(documentMenu.restoredSelectionStart, documentMenu.restoredSelectionEnd);
521
522 // run action, and free memory
523 runOnMenuClose();
524 runOnMenuClose = () => {};
525 }
526 }
527 }
528
529 footer: Column
530 {
531 width: parent.width
532
533 Maui.ToolBar
534 {
535 id: _findToolBar
536 visible: showFindBar
537 width: parent.width
538 position: ToolBar.Footer
539
540 rightContent: ToolButton
541 {
542 id: _replaceButton
543 icon.name: "edit-find-replace"
544 checkable: true
545 checked: false
546 }
547
548 leftContent: Maui.ToolButtonMenu
549 {
550 icon.name: "overflow-menu"
551
552 MenuItem
553 {
554 id: _findCaseSensitively
555 checkable: true
556 text: i18nd("mauikittexteditor","Case Sensitive")
557 }
558
559 MenuItem
560 {
561 id: _findWholeWords
562 checkable: true
563 text: i18nd("mauikittexteditor","Whole Words Only")
564 }
565 }
566
567 middleContent: Maui.SearchField
568 {
569 id: _findField
570 Layout.fillWidth: true
571 Layout.maximumWidth: 500
572 Layout.alignment: Qt.AlignCenter
573 placeholderText: i18nd("mauikittexteditor","Find")
574
575 onAccepted:
576 {
577 document.find(text)
578 }
579
580 actions:[
581
582 Action
583 {
584 enabled: _findField.text.length
585 icon.name: "arrow-up"
586 onTriggered: document.find(_findField.text, false)
587 }
588 ]
589 }
590 }
591
592 Maui.ToolBar
593 {
594 id: _replaceToolBar
595 position: ToolBar.Footer
596 visible: _replaceButton.checked && _findToolBar.visible
597 width: parent.width
598 enabled: !body.readOnly
599 forceCenterMiddleContent: false
600
601 middleContent: Maui.SearchField
602 {
603 id: _replaceField
604 placeholderText: i18nd("mauikittexteditor","Replace")
605 Layout.fillWidth: true
606 Layout.maximumWidth: 500
607 Layout.alignment: Qt.AlignCenter
608 icon.source: "edit-find-replace"
609 actions: Action
610 {
611 text: i18nd("mauikittexteditor","Replace")
612 enabled: _replaceField.text.length
613 icon.name: "checkmark"
614 onTriggered: document.replace(_findField.text, _replaceField.text)
615 }
616 }
617
618 rightContent: Button
619 {
620 enabled: _replaceField.text.length
621 text: i18nd("mauikittexteditor","Replace All")
622 onClicked: document.replaceAll(_findField.text, _replaceField.text)
623 }
624 }
625 }
626
627 header: Column
628 {
629 width: parent.width
630
631 Repeater
632 {
633 model: document.alerts
634
635 Maui.ToolBar
636 {
637 id: _alertBar
638 property var alert : model.alert
639 readonly property int index_ : index
640 width: parent.width
641
642 Maui.Theme.backgroundColor:
643 {
644 switch(alert.level)
645 {
646 case 0: return Maui.Theme.positiveBackgroundColor
647 case 1: return Maui.Theme.neutralBackgroundColor
648 case 2: return Maui.Theme.negativeBackgroundColor
649 }
650 }
651
652 Maui.Theme.textColor:
653 {
654 switch(alert.level)
655 {
656 case 0: return Maui.Theme.positiveTextColor
657 case 1: return Maui.Theme.neutralTextColor
658 case 2: return Maui.Theme.negativeTextColor
659 }
660 }
661
662 forceCenterMiddleContent: false
663 middleContent: Maui.ListItemTemplate
664 {
665 Maui.Theme.inherit: true
666 Layout.fillWidth: true
667 Layout.fillHeight: true
668 label1.text: alert.title
669 label2.text: alert.body
670 }
671
672 rightContent: Repeater
673 {
674 model: alert.actionLabels
675
676 Button
677 {
678 id: _alertAction
679 property int index_ : index
680 text: modelData
681 onClicked: alert.triggerAction(_alertAction.index_, _alertBar.index_)
682
683 Maui.Theme.backgroundColor: Qt.lighter(_alertBar.Maui.Theme.backgroundColor, 1.2)
684 Maui.Theme.hoverColor: Qt.lighter(_alertBar.Maui.Theme.backgroundColor, 1)
685 Maui.Theme.textColor: Qt.darker(Maui.Theme.backgroundColor)
686 }
687 }
688 }
689 }
690 }
691
692 Component
693 {
694 id: _linesCounterComponent
695
696 Rectangle
697 {
698 anchors.fill: parent
699 anchors.topMargin: body.topPadding + body.textMargin
700
701 color: Qt.darker(Maui.Theme.backgroundColor, 1.2)
702
703 ListView
704 {
705 id: _linesCounterList
706 anchors.fill: parent
707 interactive: false
708 enabled: false
709
710 Binding on contentY
711 {
712 value: _flickable.contentY
713 restoreMode: Binding.RestoreBindingOrValue
714 }
715
716 model: TE.LineNumberModel
717 {
718 lineCount: body.text !== "" ? document.lineCount : 0
719 }
720
721 delegate: RowLayout
722 {
723 id: _delegate
724
725 readonly property int line : index
726 // property bool foldable : control.document.isFoldable(line)
727
728 width: ListView.view.width
729 height: Math.max(Math.ceil(fontMetrics.lineSpacing), document.lineHeight(line))
730
731 readonly property bool isCurrentItem : document.currentLineIndex === index
732
733 Connections
734 {
735 target: control.body
736
737 function onContentHeightChanged()
738 {
739 if(body.wrapMode !== Text.NoWrap)
740 {
741 _delegate.height = control.document.lineHeight(_delegate.line)
742 }
743
744 if(_delegate.isCurrentItem)
745 {
746 console.log("Updating line height")
747 // _delegate.foldable = control.document.isFoldable(_delegate.line)
748 }
749
750 _linesCounterList.contentY = _flickable.contentY
751 }
752
753 function onWrapModeChanged()
754 {
755 _delegate.height = control.document.lineHeight(_delegate.line)
756 }
757 }
758
759 Label
760 {
761 Layout.fillWidth: true
762 Layout.fillHeight: true
763
764 opacity: isCurrentItem ? 1 : 0.7
765 color: isCurrentItem ? control.Maui.Theme.highlightedTextColor : control.body.color
766 font.pointSize: Math.min(Maui.Style.fontSizes.medium, body.font.pointSize)
767 horizontalAlignment: Text.AlignHCenter
768 verticalAlignment: Text.AlignTop
769 // renderType: Text.NativeRendering
770 font.family: "Monospace"
771 text: index+1
772
773 background: Rectangle
774 {
775 visible: isCurrentItem
776 color: Maui.Theme.highlightColor
777 }
778 }
779
780 // AbstractButton
781 // {
782 // visible: foldable
783 // Layout.alignment: Qt.AlignVCenter
784 // implicitHeight: 8
785 // implicitWidth: 8
786 // //onClicked:
787 // //{
788 // //control.goToLine(_delegate.line)
789 // //control.document.toggleFold(_delegate.line)
790 // //}
791 // contentItem: Maui.Icon
792 // {
793 // source: "go-down"
794 // color: isCurrentItem ? control.Maui.Theme.highlightedTextColor : control.body.color
795 // }
796 // }
797 }
798
799 }
800 }
801
802 }
803
804 contentItem: Item
805 {
806 RowLayout
807 {
808 anchors.fill: parent
809 clip: false
810
811 Loader
812 {
813 id: _linesCounter
814 asynchronous: true
815 active: control.showLineNumbers && !document.isRich && body.lineCount > 1 && body.wrapMode === Text.NoWrap
816
817 Layout.fillHeight: true
818 Layout.preferredWidth: active ? fontMetrics.averageCharacterWidth
819 * (Math.floor(Math.log10(body.lineCount)) + 1) + 10 : 0
820
821
822 sourceComponent: _linesCounterComponent
823 }
824
826 {
827 id: _scrollView
828
829 Layout.fillHeight: true
830 Layout.fillWidth: true
831
832 clip: false
833
834 ScrollBar.horizontal.policy: ScrollBar.AsNeeded
835
836 Keys.enabled: true
837 Keys.forwardTo: body
838 Keys.onPressed: (event) =>
839 {
840 if((event.key === Qt.Key_F) && (event.modifiers & Qt.ControlModifier))
841 {
842 control.showFindBar = true
843
844 if(control.body.selectedText.length)
845 {
846 _findField.text = control.body.selectedText
847 }else
848 {
849 _findField.selectAll()
850 }
851
852 _findField.forceActiveFocus()
853 event.accepted = true
854 }
855
856 if((event.key === Qt.Key_R) && (event.modifiers & Qt.ControlModifier))
857 {
858 control.showFindBar = true
859 _replaceButton.checked = true
860 _findField.text = control.body.selectedText
861 _replaceField.forceActiveFocus()
862 event.accepted = true
863 }
864 }
865
866 Flickable
867 {
868 id: _flickable
869 clip: false
870
871 interactive: true
872 boundsBehavior : Flickable.StopAtBounds
873 boundsMovement : Flickable.StopAtBounds
874
875 TextArea.flickable: TextArea
876 {
877 id: body
878 Maui.Theme.inherit: true
879 text: document.text
880
881 placeholderText: i18nd("mauikittexteditor","Body")
882
883 textFormat: TextEdit.PlainText
884
885 tabStopDistance: fontMetrics.averageCharacterWidth * 4
886 renderType: Text.QtRendering
887 antialiasing: true
888 activeFocusOnPress: true
889 focusPolicy: Qt.StrongFocus
890
891 Keys.onReturnPressed: (event) =>
892 {
893 body.insert(body.cursorPosition, "\n")
894 if(Maui.Handy.isAndroid)//workaround for Android, since pressing return/enter will close the keyboard after inserting the break
895 /*The fix to this workaround has been introduced into Qt 6.8
896 see: https://doc.qt.io/qt-6/qml-qtquick-virtualkeyboard-settings-virtualkeyboardsettings.html#closeOnReturn-prop
897 */
898 {
899 Qt.inputMethod.show();
900 event.accepted = true
901 }
902 }
903
904 Keys.onPressed: (event) =>
905 {
906 if(event.key === Qt.Key_PageUp)
907 {
908 _flickable.flick(0, 60*Math.sqrt(_flickable.height))
909 event.accepted = true
910 }
911
912 if(event.key === Qt.Key_PageDown)
913 {
914 _flickable.flick(0, -60*Math.sqrt(_flickable.height))
915 event.accepted = true
916 } // TODO: Move cursor
917 }
918
919 onPressAndHold: (event) =>
920 {
921 if(Maui.Handy.isAndroid)
922 {
923 return
924 }
925
926 if(Maui.Handy.isMobile || Maui.Handy.isTouch)
927 {
928 documentMenu.targetClick(spellcheckhighlighterLoader, body.positionAt(event.x, event.y))
929 event.accepted = true
930 return
931 }
932 event.accepted = false
933 }
934
935 onPressed: (event) =>
936 {
937 if(Maui.Handy.isMobile)
938 {
939 return
940 }
941
942 if(event.button === Qt.RightButton)
943 {
944 documentMenu.targetClick(spellcheckhighlighterLoader, body.positionAt(event.x, event.y))
945 event.accepted = true
946 }
947 }
948 }
949 }
950 }
951 }
952
953 Loader
954 {
955 active: Maui.Handy.isTouch
956 asynchronous: true
957
958 anchors.bottom: parent.bottom
959 anchors.right: parent.right
960 anchors.margins: Maui.Style.space.big
961
962 sourceComponent: Maui.FloatingButton
963 {
964 icon.name: "edit-menu"
965 onClicked: documentMenu.targetClick(spellcheckhighlighterLoader, body.cursorPosition)
966 }
967 }
968 }
969
970 /**
971 * @brief Force to focus the text area for input
972 */
973 function forceActiveFocus()
974 {
975 body.forceActiveFocus()
976 }
977
978 /**
979 * @brief Position the view and cursor at the given line number
980 * @param line the line number
981 */
982 function goToLine(line)
983 {
984 if(line>0 && line <= body.lineCount)
985 {
986 body.cursorPosition = document.goToLine(line-1)
987 body.forceActiveFocus()
988 }
989 }
990}
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nd(const char *domain, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
const QList< QKeySequence > & end()
void forceActiveFocus()
QString arg(Args &&... args) const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:58:09 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.