Files
jami-client-qt/src/app/commoncomponents/LineEditContextMenu.qml
pmagnier-slimani ceec1f95b9 spellcheck: windows and macos
Implement the hunspell spellchecker for Windows and MacOS. It also
changes the base implementation for Linux. The system dictionaries
(if any) are aggregated with those installed from the LibreOffice
repository via Jami's dictionary management interface.

This commit implements a major refactoring of the spellcheck system
to improve UI responsiveness and user experience:

Core Changes:
- Used QAbstractListModel to represent the list of dictionaries
- Added new QML components:
  - DictionaryInstallView.qml
  - ManageDictionariesDialog.qml
  - SpellCheckLanguageComboBox.qml
- Updated property names for clarity
- Fixed a bug in the settings combo box custom component that caused
  out-of-range errors for filtered models

GitLab: #1997
Change-Id: Ibd0879f957f27f4c7c5720762ace553ca84e2bc3
2025-06-03 19:24:16 -04:00

176 lines
5.0 KiB
QML

/*
* Copyright (C) 2020-2025 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import net.jami.Adapters 1.1
import net.jami.Constants 1.1
import net.jami.Enums 1.1
import net.jami.Models 1.1
import "contextmenu"
import "../mainview"
import "../mainview/components"
ContextMenuAutoLoader {
id: root
// lineEdit (TextEdit) selection will be lost when menu is opened
property var lineEditObj
property var selectionStart
property var selectionEnd
property bool customizePaste: false
property bool selectOnly: false
property bool spellCheckEnabled: false
property var suggestionList
property var menuItemsLength
property var language
signal contextMenuRequirePaste
SpellLanguageContextMenu {
id: spellLanguageContextMenu
active: spellCheckEnabled
}
property list<GeneralMenuItem> menuItems: [
GeneralMenuItem {
id: cut
canTrigger: true
isActif: lineEditObj.selectedText.length && !selectOnly
itemName: JamiStrings.cut
hasIcon: false
onClicked: lineEditObj.cut()
},
GeneralMenuItem {
id: copy
canTrigger: true
isActif: lineEditObj.selectedText.length
itemName: JamiStrings.copy
hasIcon: false
onClicked: lineEditObj.copy()
},
GeneralMenuItem {
id: paste
canTrigger: !selectOnly
itemName: JamiStrings.paste
hasIcon: false
onClicked: {
if (customizePaste)
root.contextMenuRequirePaste();
else
lineEditObj.paste();
}
},
GeneralMenuItem {
id: textLanguage
canTrigger: spellCheckEnabled && SpellCheckAdapter.installedDictionaryCount > 0
itemName: JamiStrings.textLanguage
hasIcon: false
onClicked: {
spellLanguageContextMenu.openMenu();
}
},
GeneralMenuItem {
id: manageLanguages
itemName: JamiStrings.manageDictionaries
canTrigger: spellCheckEnabled
hasIcon: false
onClicked: {
viewCoordinator.presentDialog(appWindow, "commoncomponents/ManageDictionariesDialog.qml");
}
}
]
ListView {
model: ListModel {
id: suggestionListModel
}
Instantiator {
model: suggestionListModel
delegate: GeneralMenuItem {
id: suggestion
canTrigger: true
isActif: true
itemName: model.name
bold: true
hasIcon: false
onClicked: {
replaceWord(model.name);
}
}
onObjectAdded: {
menuItems.push(object);
}
onObjectRemoved: {
menuItems.splice(menuItemsLength, suggestionList.length);
}
}
}
function removeItems() {
suggestionListModel.clear();
suggestionList.length = 0;
}
function addMenuItem(wordList) {
menuItemsLength = menuItems.length; // Keep initial number of items for easier removal
suggestionList = wordList;
for (var i = 0; i < suggestionList.length; ++i) {
suggestionListModel.append({
"name": suggestionList[i]
});
}
}
function replaceWord(word) {
lineEditObj.remove(selectionStart, selectionEnd);
lineEditObj.insert(lineEditObj.cursorPosition, word);
}
function openMenuAt(mouseEvent) {
if (lineEditObj.selectedText.length === 0 && selectOnly)
return;
x = mouseEvent.x;
y = mouseEvent.y;
selectionStart = lineEditObj.selectionStart;
selectionEnd = lineEditObj.selectionEnd;
root.openMenu();
lineEditObj.select(selectionStart, selectionEnd);
}
Connections {
target: root.item
enabled: root.status === Loader.Ready
function onOpened() {
lineEditObj.select(selectionStart, selectionEnd);
}
function onClosed() {
if (!suggestionList || suggestionList.length === 0) {
return;
}
removeItems();
}
}
Component.onCompleted: menuItemsToLoad = menuItems
}