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
180
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 Flickable
697 {
698 id: _linesFlickable
699 interactive: false
700 // contentY: _flickable.contentY
701
702 Binding on contentY
703 {
704 value: _flickable.contentY
705 restoreMode: Binding.RestoreBindingOrValue
706 }
707
708 Rectangle
709 {
710 anchors.fill: parent
711 anchors.topMargin: body.topPadding + body.textMargin
712
713 implicitHeight: Math.max(_linesCounterList.contentHeight, control.height)
714
715 color: Qt.darker(Maui.Theme.backgroundColor, 1)
716
717
718 Column
719 {
720 id: _linesCounterList
721 anchors.fill: parent
722
723
724
725 // Binding on currentIndex
726 // {
727 // value: document.currentLineIndex
728 // restoreMode: Binding.RestoreBindingOrValue
729 // }
730
731 // Timer
732 // {
733 // id: _lineIndexTimer
734 // interval: 250
735 // onTriggered: _linesCounterList.currentIndex = document.currentLineIndex
736 // }
737
738 // Connections
739 // {
740 // target: document
741 // function onLineCountChanged()
742 // {
743 // _lineIndexTimer.restart()
744 // }
745 // }
746
747 Repeater
748 {
749
750 model: TE.LineNumberModel
751 {
752 lineCount: body.text !== "" ? document.lineCount : 0
753 }
754
755 delegate: Row
756 {
757 id: _delegate
758
759 readonly property int line : index
760 property bool foldable : control.document.isFoldable(line)
761
762 width: parent.width
763 height: Math.max(Math.ceil(fontMetrics.lineSpacing), document.lineHeight(line))
764
765 readonly property bool isCurrentItem : document.currentLineIndex === index
766
767 Connections
768 {
769 target: control.body
770
771 function onContentHeightChanged()
772 {
773 if(body.wrapMode !== Text.NoWrap)
774 {
775 _delegate.height = control.document.lineHeight(_delegate.line)
776 }
777
778 if(_delegate.isCurrentItem)
779 {
780 console.log("Updating line height")
781 _delegate.foldable = control.document.isFoldable(_delegate.line)
782 }
783
784 _linesFlickable.contentY = _flickable.contentY
785 }
786
787 function onWrapModeChanged()
788 {
789 _delegate.height = control.document.lineHeight(_delegate.line)
790 }
791 }
792
793 Label
794 {
795 width: 32
796 height: parent.height
797 opacity: isCurrentItem ? 1 : 0.7
798 color: isCurrentItem ? control.Maui.Theme.highlightedTextColor : control.body.color
799 font.pointSize: Math.min(Maui.Style.fontSizes.medium, body.font.pointSize)
800 horizontalAlignment: Text.AlignHCenter
801 verticalAlignment: Text.AlignVCenter
802 // renderType: Text.NativeRendering
803 font.family: "Monospace"
804 text: index+1
805
806 background: Rectangle
807 {
808 visible: isCurrentItem
809 color: Maui.Theme.highlightColor
810 }
811 }
812
813 AbstractButton
814 {
815 visible: foldable
816 anchors.verticalCenter: parent.verticalCenter
817 height: 8
818 width: 8
819 //onClicked:
820 //{
821 //control.goToLine(_delegate.line)
822 //control.document.toggleFold(_delegate.line)
823 //}
824 contentItem: Maui.Icon
825 {
826 source: "go-down"
827 }
828 }
829 }
830 }
831 }
832 }
833 }
834 }
835
836 contentItem: Item
837 {
838 RowLayout
839 {
840 anchors.fill: parent
841 clip: false
842
843 Loader
844 {
845 id: _linesCounter
846 asynchronous: true
847 active: control.showLineNumbers && !document.isRich
848
849 Layout.fillHeight: true
850 Layout.preferredWidth: active ? fontMetrics.averageCharacterWidth
851 * (Math.floor(Math.log10(body.lineCount)) + 1) + 10 : 0
852
853
854 sourceComponent: _linesCounterComponent
855 }
856
858 {
859 id: _scrollView
860
861 Layout.fillHeight: true
862 Layout.fillWidth: true
863
864 clip: false
865
866 ScrollBar.horizontal.policy: ScrollBar.AsNeeded
867
868 Keys.enabled: true
869 Keys.forwardTo: body
870 Keys.onPressed: (event) =>
871 {
872 if((event.key === Qt.Key_F) && (event.modifiers & Qt.ControlModifier))
873 {
874 control.showFindBar = true
875
876 if(control.body.selectedText.length)
877 {
878 _findField.text = control.body.selectedText
879 }else
880 {
881 _findField.selectAll()
882 }
883
884 _findField.forceActiveFocus()
885 event.accepted = true
886 }
887
888 if((event.key === Qt.Key_R) && (event.modifiers & Qt.ControlModifier))
889 {
890 control.showFindBar = true
891 _replaceButton.checked = true
892 _findField.text = control.body.selectedText
893 _replaceField.forceActiveFocus()
894 event.accepted = true
895 }
896 }
897
898 Flickable
899 {
900 id: _flickable
901 clip: false
902
903 interactive: true
904 boundsBehavior : Flickable.StopAtBounds
905 boundsMovement : Flickable.StopAtBounds
906
907 TextArea.flickable: TextArea
908 {
909 id: body
910 Maui.Theme.inherit: true
911 text: document.text
912
913 placeholderText: i18nd("mauikittexteditor","Body")
914
915 textFormat: TextEdit.PlainText
916
917 leftPadding: _linesCounter.width + padding
918
919 tabStopDistance: fontMetrics.averageCharacterWidth * 4
920 renderType: Text.QtRendering
921 antialiasing: true
922
923 Keys.onPressed: (event) =>
924 {
925 if(event.key === Qt.Key_PageUp)
926 {
927 _flickable.flick(0, 60*Math.sqrt(_flickable.height))
928 event.accepted = true
929 }
930
931 if(event.key === Qt.Key_PageDown)
932 {
933 _flickable.flick(0, -60*Math.sqrt(_flickable.height))
934 event.accepted = true
935 } // TODO: Move cursor
936 }
937
938 onPressAndHold: (event) =>
939 {
940 // if(Maui.Handy.isMobile)
941 // {
942 // return
943 // }
944 //
945 documentMenu.targetClick(spellcheckhighlighterLoader, body.positionAt(event.x, event.y));
946 }
947
948 onPressed: (event) =>
949 {
950 if(Maui.Handy.isMobile)
951 {
952 return
953 }
954
955 if(event.button === Qt.RightButton)
956 {
957 documentMenu.targetClick(spellcheckhighlighterLoader, body.positionAt(event.x, event.y))
958 }
959 }
960 }
961 }
962 }
963 }
964
965 Loader
966 {
967 active: Maui.Handy.isTouch
968 asynchronous: true
969
970 anchors.bottom: parent.bottom
971 anchors.right: parent.right
972 anchors.margins: Maui.Style.space.big
973
974 sourceComponent: Maui.FloatingButton
975 {
976 icon.name: "edit-menu"
977 onClicked: documentMenu.targetClick(spellcheckhighlighterLoader, body.cursorPosition)
978 }
979 }
980 }
981
982 /**
983 * @brief Force to focus the text area for input
984 */
985 function forceActiveFocus()
986 {
987 body.forceActiveFocus()
988 }
989
990 /**
991 * @brief Position the view and cursor at the given line number
992 * @param line the line number
993 */
994 function goToLine(line)
995 {
996 if(line>0 && line <= body.lineCount)
997 {
998 body.cursorPosition = document.goToLine(line-1)
999 body.forceActiveFocus()
1000 }
1001 }
1002}
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)
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
QString arg(Args &&... args) const const
qsizetype length() 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 Fri Jul 26 2024 11:52:34 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.