Keyboard.qml 82 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843
  1. // Copyright (C) 2016 The Qt Company Ltd.
  2. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
  3. import QtQuick
  4. // Deliberately imported after QtQuick to avoid missing restoreMode property in Binding. Fix in Qt 6.
  5. import QtQml
  6. import QtQuick.Layouts
  7. import QtQuick.Window
  8. import QtQuick.VirtualKeyboard
  9. import QtQuick.VirtualKeyboard.Styles
  10. import QtQuick.VirtualKeyboard.Settings
  11. import QtQuick.VirtualKeyboard.Plugins
  12. import Qt.labs.folderlistmodel
  13. Item {
  14. id: keyboard
  15. objectName: "keyboard"
  16. property alias style: styleLoader.item
  17. property alias wordCandidateView: wordCandidateView
  18. property alias shadowInputControl: shadowInputControl
  19. property alias alternativeKeys: alternativeKeys
  20. property alias characterPreview: characterPreview
  21. property alias wordCandidateContextMenu: wordCandidateContextMenu
  22. property alias fullScreenModeSelectionControl: fullScreenModeSelectionControl
  23. property alias navigationHighlight: navigationHighlight
  24. property alias keyboardInputArea: keyboardInputArea
  25. property Item activeKey: null
  26. property TouchPoint activeTouchPoint
  27. property int localeIndex: -1
  28. property var availableLocaleIndices: []
  29. property var availableCustomLocaleIndices: []
  30. property string locale: localeIndex >= 0 && localeIndex < layoutsModel.count ? layoutsModel.get(localeIndex, "fileName") : ""
  31. property string inputLocale
  32. property int defaultLocaleIndex: -1
  33. readonly property bool latinOnly: InputContext.inputMethodHints & (Qt.ImhLatinOnly | Qt.ImhEmailCharactersOnly | Qt.ImhUrlCharactersOnly)
  34. readonly property bool preferNumbers: InputContext.inputMethodHints & Qt.ImhPreferNumbers
  35. readonly property bool dialableCharactersOnly: InputContext.inputMethodHints & Qt.ImhDialableCharactersOnly
  36. readonly property bool formattedNumbersOnly: InputContext.inputMethodHints & Qt.ImhFormattedNumbersOnly
  37. readonly property bool digitsOnly: InputContext.inputMethodHints & Qt.ImhDigitsOnly
  38. property string layout
  39. property string layoutType: {
  40. if (keyboard.handwritingMode) return "handwriting"
  41. if (keyboard.dialableCharactersOnly) return "dialpad"
  42. if (keyboard.formattedNumbersOnly) return "numbers"
  43. if (keyboard.digitsOnly) return "digits"
  44. if (keyboard.symbolMode) return "symbols"
  45. return "main"
  46. }
  47. property bool active: Qt.inputMethod.visible
  48. property bool handwritingMode
  49. property bool fullScreenHandwritingMode
  50. property bool symbolMode
  51. property bool fullScreenMode: VirtualKeyboardSettings.fullScreenMode
  52. property var defaultInputMethod: initDefaultInputMethod()
  53. property var plainInputMethod: PlainInputMethod {}
  54. property var customInputMethod: null
  55. property var customInputMethodSharedLayouts: []
  56. property int defaultInputMode: InputEngine.InputMode.Latin
  57. property bool inputMethodNeedsReset: true
  58. property bool inputModeNeedsReset: true
  59. property bool navigationModeActive: false
  60. readonly property bool languagePopupListActive: languagePopupList.enabled
  61. property alias soundEffect: soundEffect
  62. property alias keyboardLayoutLoader: keyboardLayoutLoader
  63. property real screenHeight: parent.parent ? parent.parent.height : Screen.height
  64. property bool noAnimations
  65. property int pressAndHoldDelay: 500
  66. function initDefaultInputMethod() {
  67. try {
  68. return Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard.Plugins; DefaultInputMethod {}', keyboard, "defaultInputMethod")
  69. } catch (e) { }
  70. return plainInputMethod
  71. }
  72. Component.onCompleted: InputContext.priv.registerInputPanel(parent)
  73. width: keyboardBackground.width
  74. height: keyboardBackground.height
  75. onActiveChanged: {
  76. hideLanguagePopup()
  77. if (active && symbolMode && !preferNumbers)
  78. symbolMode = false
  79. keyboardInputArea.reset()
  80. wordCandidateViewAutoHideTimer.stop()
  81. }
  82. onActiveKeyChanged: {
  83. if (InputContext.inputEngine.activeKey !== Qt.Key_unknown)
  84. InputContext.inputEngine.virtualKeyCancel()
  85. }
  86. Connections {
  87. target: VirtualKeyboardSettings
  88. function onLocaleChanged() {
  89. updateDefaultLocale()
  90. localeIndex = defaultLocaleIndex
  91. }
  92. function onActiveLocalesChanged() {
  93. updateDefaultLocale()
  94. if (!isValidLocale(localeIndex) || VirtualKeyboardSettings.locale)
  95. localeIndex = defaultLocaleIndex
  96. }
  97. function onDefaultInputMethodDisabledChanged() {
  98. updateInputMethod()
  99. }
  100. }
  101. onAvailableLocaleIndicesChanged: hideLanguagePopup()
  102. onAvailableCustomLocaleIndicesChanged: hideLanguagePopup()
  103. onLocaleChanged: {
  104. hideLanguagePopup()
  105. inputMethodNeedsReset = true
  106. inputModeNeedsReset = true
  107. updateLayout()
  108. }
  109. onInputLocaleChanged: {
  110. if (Qt.locale(inputLocale).name !== "C")
  111. InputContext.priv.locale = inputLocale
  112. }
  113. onLayoutChanged: hideLanguagePopup()
  114. onLayoutTypeChanged: {
  115. updateAvailableLocaleIndices()
  116. updateLayout()
  117. }
  118. onLatinOnlyChanged: inputModeNeedsReset = true
  119. onPreferNumbersChanged: {
  120. keyboard.symbolMode = !keyboard.handwritingMode && preferNumbers
  121. inputModeNeedsReset = true
  122. }
  123. onDialableCharactersOnlyChanged: inputModeNeedsReset = true
  124. onFormattedNumbersOnlyChanged: inputModeNeedsReset = true
  125. onDigitsOnlyChanged: inputModeNeedsReset = true
  126. onHandwritingModeChanged: if (!keyboard.handwritingMode) keyboard.fullScreenHandwritingMode = false
  127. onFullScreenHandwritingModeChanged: if (keyboard.fullScreenHandwritingMode) keyboard.handwritingMode = true
  128. onLanguagePopupListActiveChanged: {
  129. if (languagePopupListActive && navigationModeActive)
  130. keyboardInputArea.initialKey = null
  131. }
  132. Connections {
  133. target: InputContext
  134. function onInputMethodHintsChanged() {
  135. if (InputContext.priv.focus)
  136. updateInputMethod()
  137. }
  138. }
  139. Connections {
  140. target: InputContext.priv
  141. function onInputItemChanged() {
  142. keyboard.hideLanguagePopup()
  143. if (active && symbolMode && !preferNumbers)
  144. symbolMode = false
  145. }
  146. function onFocusChanged() {
  147. if (InputContext.priv.focus)
  148. updateInputMethod()
  149. }
  150. function onNavigationKeyPressed(key, isAutoRepeat) {
  151. var initialKey
  152. var direction = wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight ? 1 : -1
  153. switch (key) {
  154. case Qt.Key_Left:
  155. if (keyboard.navigationModeActive && !keyboardInputArea.initialKey) {
  156. if (languagePopupListActive) {
  157. hideLanguagePopup()
  158. keyboardInputArea.setActiveKey(null)
  159. keyboardInputArea.navigateToNextKey(0, 0, false)
  160. break
  161. }
  162. if (alternativeKeys.active) {
  163. if (alternativeKeys.listView.currentIndex > 0) {
  164. alternativeKeys.listView.decrementCurrentIndex()
  165. } else {
  166. alternativeKeys.close()
  167. keyboardInputArea.setActiveKey(null)
  168. keyboardInputArea.navigateToNextKey(0, 0, false)
  169. }
  170. break
  171. }
  172. if (functionPopupList.active) {
  173. if (functionPopupList.listView.currentIndex > 0) {
  174. functionPopupList.listView.decrementCurrentIndex()
  175. } else {
  176. functionPopupList.close()
  177. keyboardInputArea.setActiveKey(null)
  178. keyboardInputArea.navigateToNextKey(0, 0, false)
  179. }
  180. break
  181. }
  182. if (wordCandidateContextMenu.active) {
  183. hideWordCandidateContextMenu()
  184. break
  185. }
  186. if (wordCandidateView.count) {
  187. if (wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight &&
  188. wordCandidateView.currentIndex > 0) {
  189. wordCandidateView.decrementCurrentIndex()
  190. } else if (wordCandidateView.effectiveLayoutDirection == Qt.RightToLeft &&
  191. wordCandidateView.currentIndex + 1 < wordCandidateView.count) {
  192. wordCandidateView.incrementCurrentIndex()
  193. } else {
  194. keyboardInputArea.navigateToNextKey(0, 0, false)
  195. initialKey = keyboardInputArea.initialKey
  196. while (keyboardInputArea.navigateToNextKey(0, 1 * direction, false))
  197. initialKey = keyboardInputArea.initialKey
  198. while (keyboardInputArea.navigateToNextKey(1, 0, false))
  199. initialKey = keyboardInputArea.initialKey
  200. keyboardInputArea.initialKey = initialKey
  201. keyboardInputArea.navigateToNextKey(0, 0, false)
  202. }
  203. break
  204. }
  205. }
  206. initialKey = keyboardInputArea.initialKey
  207. if (!keyboardInputArea.navigateToNextKey(-1 * direction, 0, false)) {
  208. keyboardInputArea.initialKey = initialKey
  209. if (!keyboardInputArea.navigateToNextKey(0, -1 * direction, false)) {
  210. if (wordCandidateView.count) {
  211. wordCandidateView.currentIndex =
  212. wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight ?
  213. (wordCandidateView.count - 1) : 0
  214. break
  215. }
  216. keyboardInputArea.initialKey = initialKey
  217. keyboardInputArea.navigateToNextKey(0, -1 * direction, true)
  218. }
  219. keyboardInputArea.navigateToNextKey(-1 * direction, 0, true)
  220. }
  221. break
  222. case Qt.Key_Up:
  223. if (languagePopupListActive) {
  224. if (languagePopupList.currentIndex > 0) {
  225. languagePopupList.decrementCurrentIndex()
  226. } else if (languagePopupList.keyNavigationWraps) {
  227. languagePopupList.currentIndex = languagePopupList.count - 1
  228. } else {
  229. hideLanguagePopup()
  230. keyboardInputArea.setActiveKey(null)
  231. keyboardInputArea.navigateToNextKey(0, 0, false)
  232. }
  233. } else if (alternativeKeys.active) {
  234. alternativeKeys.close()
  235. keyboardInputArea.setActiveKey(null)
  236. keyboardInputArea.navigateToNextKey(0, 0, false)
  237. } else if (functionPopupList.active) {
  238. functionPopupList.close()
  239. keyboardInputArea.setActiveKey(null)
  240. keyboardInputArea.navigateToNextKey(0, 0, false)
  241. } else if (wordCandidateContextMenu.active) {
  242. if (wordCandidateContextMenuList.currentIndex > 0) {
  243. wordCandidateContextMenuList.decrementCurrentIndex()
  244. } else if (wordCandidateContextMenuList.keyNavigationWraps && wordCandidateContextMenuList.count > 1) {
  245. wordCandidateContextMenuList.currentIndex = wordCandidateContextMenuList.count - 1
  246. } else {
  247. hideWordCandidateContextMenu()
  248. }
  249. } else if (keyboard.navigationModeActive && !keyboardInputArea.initialKey && wordCandidateView.count) {
  250. keyboardInputArea.navigateToNextKey(0, 0, false)
  251. initialKey = keyboardInputArea.initialKey
  252. if (!keyboardInputArea.navigateToNextKey(0, -1, false)) {
  253. keyboardInputArea.initialKey = initialKey
  254. keyboardInputArea.navigateToNextKey(0, -1, true)
  255. } else {
  256. keyboardInputArea.navigateToNextKey(0, 1, false)
  257. }
  258. } else if (!keyboardInputArea.navigateToNextKey(0, -1, !keyboard.navigationModeActive || !keyboardInputArea.initialKey || wordCandidateView.count == 0)) {
  259. if (wordCandidateView.currentIndex === -1)
  260. wordCandidateView.incrementCurrentIndex()
  261. }
  262. break
  263. case Qt.Key_Right:
  264. if (keyboard.navigationModeActive && !keyboardInputArea.initialKey) {
  265. if (languagePopupListActive) {
  266. hideLanguagePopup()
  267. keyboardInputArea.setActiveKey(null)
  268. keyboardInputArea.navigateToNextKey(0, 0, false)
  269. break
  270. }
  271. if (alternativeKeys.active) {
  272. if (alternativeKeys.listView.currentIndex + 1 < alternativeKeys.listView.count) {
  273. alternativeKeys.listView.incrementCurrentIndex()
  274. } else {
  275. alternativeKeys.close()
  276. keyboardInputArea.setActiveKey(null)
  277. keyboardInputArea.navigateToNextKey(0, 0, false)
  278. }
  279. break
  280. }
  281. if (functionPopupList.active) {
  282. if (functionPopupList.listView.currentIndex + 1 < functionPopupList.listView.count) {
  283. functionPopupList.listView.incrementCurrentIndex()
  284. } else {
  285. functionPopupList.close()
  286. keyboardInputArea.setActiveKey(null)
  287. keyboardInputArea.navigateToNextKey(0, 0, false)
  288. }
  289. break
  290. }
  291. if (wordCandidateContextMenu.active) {
  292. hideWordCandidateContextMenu()
  293. break
  294. }
  295. if (wordCandidateView.count) {
  296. if (wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight &&
  297. wordCandidateView.currentIndex + 1 < wordCandidateView.count) {
  298. wordCandidateView.incrementCurrentIndex()
  299. } else if (wordCandidateView.effectiveLayoutDirection == Qt.RightToLeft &&
  300. wordCandidateView.currentIndex > 0) {
  301. wordCandidateView.decrementCurrentIndex()
  302. } else {
  303. keyboardInputArea.navigateToNextKey(0, 0, false)
  304. initialKey = keyboardInputArea.initialKey
  305. while (keyboardInputArea.navigateToNextKey(0, -1 * direction, false))
  306. initialKey = keyboardInputArea.initialKey;
  307. while (keyboardInputArea.navigateToNextKey(-1, 0, false))
  308. initialKey = keyboardInputArea.initialKey;
  309. keyboardInputArea.initialKey = initialKey
  310. keyboardInputArea.navigateToNextKey(0, 0, false)
  311. }
  312. break
  313. }
  314. }
  315. initialKey = keyboardInputArea.initialKey
  316. if (!keyboardInputArea.navigateToNextKey(1 * direction, 0, false)) {
  317. keyboardInputArea.initialKey = initialKey
  318. if (!keyboardInputArea.navigateToNextKey(0, 1 * direction, false)) {
  319. if (wordCandidateView.count) {
  320. wordCandidateView.currentIndex =
  321. wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight ?
  322. 0 : (wordCandidateView.count - 1)
  323. break
  324. }
  325. keyboardInputArea.initialKey = initialKey
  326. keyboardInputArea.navigateToNextKey(0, 1 * direction, true)
  327. }
  328. keyboardInputArea.navigateToNextKey(1 * direction, 0, true)
  329. }
  330. break
  331. case Qt.Key_Down:
  332. if (languagePopupListActive) {
  333. if (languagePopupList.currentIndex + 1 < languagePopupList.count) {
  334. languagePopupList.incrementCurrentIndex()
  335. } else if (languagePopupList.keyNavigationWraps) {
  336. languagePopupList.currentIndex = 0
  337. } else {
  338. hideLanguagePopup()
  339. keyboardInputArea.setActiveKey(null)
  340. keyboardInputArea.navigateToNextKey(0, 0, false)
  341. }
  342. } else if (alternativeKeys.active) {
  343. alternativeKeys.close()
  344. keyboardInputArea.setActiveKey(null)
  345. keyboardInputArea.navigateToNextKey(0, 0, false)
  346. } else if (functionPopupList.active) {
  347. functionPopupList.close()
  348. keyboardInputArea.setActiveKey(null)
  349. keyboardInputArea.navigateToNextKey(0, 0, false)
  350. } else if (wordCandidateContextMenu.active) {
  351. if (wordCandidateContextMenuList.currentIndex + 1 < wordCandidateContextMenuList.count) {
  352. wordCandidateContextMenuList.incrementCurrentIndex()
  353. } else if (wordCandidateContextMenuList.keyNavigationWraps && wordCandidateContextMenuList.count > 1) {
  354. wordCandidateContextMenuList.currentIndex = 0
  355. } else {
  356. hideWordCandidateContextMenu()
  357. keyboardInputArea.setActiveKey(null)
  358. keyboardInputArea.navigateToNextKey(0, 0, false)
  359. }
  360. } else if (keyboard.navigationModeActive && !keyboardInputArea.initialKey && wordCandidateView.count) {
  361. keyboardInputArea.navigateToNextKey(0, 0, false)
  362. initialKey = keyboardInputArea.initialKey
  363. if (!keyboardInputArea.navigateToNextKey(0, 1, false)) {
  364. keyboardInputArea.initialKey = initialKey
  365. keyboardInputArea.navigateToNextKey(0, 1, true)
  366. } else {
  367. keyboardInputArea.navigateToNextKey(0, -1, false)
  368. }
  369. } else if (!keyboardInputArea.navigateToNextKey(0, 1, !keyboard.navigationModeActive || !keyboardInputArea.initialKey || wordCandidateView.count == 0)) {
  370. if (wordCandidateView.currentIndex === -1)
  371. wordCandidateView.incrementCurrentIndex()
  372. }
  373. break
  374. case Qt.Key_Return:
  375. if (!keyboard.navigationModeActive)
  376. break
  377. if (languagePopupListActive) {
  378. if (!isAutoRepeat) {
  379. languagePopupList.model.selectItem(languagePopupList.currentIndex)
  380. keyboardInputArea.reset()
  381. keyboardInputArea.navigateToNextKey(0, 0, false)
  382. }
  383. } else if (keyboardInputArea.initialKey) {
  384. if (!isAutoRepeat) {
  385. pressAndHoldTimer.restart()
  386. keyboardInputArea.setActiveKey(keyboardInputArea.initialKey)
  387. keyboardInputArea.press(keyboardInputArea.initialKey, true)
  388. }
  389. } else if (!wordCandidateContextMenu.active && wordCandidateView.count > 0) {
  390. if (!isAutoRepeat) {
  391. pressAndHoldTimer.restart()
  392. }
  393. }
  394. break
  395. default:
  396. break
  397. }
  398. }
  399. function onNavigationKeyReleased(key, isAutoRepeat) {
  400. switch (key) {
  401. case Qt.Key_Return:
  402. if (!keyboard.navigationModeActive) {
  403. if (languagePopupListActive)
  404. languagePopupList.model.selectItem(languagePopupList.currentIndex)
  405. break
  406. }
  407. if (isAutoRepeat)
  408. break
  409. if (!languagePopupListActive && !alternativeKeys.active && !functionPopupList.active && !wordCandidateContextMenu.active && keyboard.activeKey) {
  410. keyboardInputArea.release(keyboard.activeKey)
  411. pressAndHoldTimer.stop()
  412. alternativeKeys.close()
  413. functionPopupList.close()
  414. keyboardInputArea.setActiveKey(null)
  415. if (!languagePopupListActive && keyboardInputArea.navigationCursor !== Qt.point(-1, -1))
  416. keyboardInputArea.navigateToNextKey(0, 0, false)
  417. } else if (wordCandidateContextMenu.active) {
  418. if (!wordCandidateContextMenu.openedByNavigationKeyLongPress) {
  419. wordCandidateContextMenu.selectCurrentItem()
  420. keyboardInputArea.navigateToNextKey(0, 0, false)
  421. } else {
  422. wordCandidateContextMenu.openedByNavigationKeyLongPress = false
  423. }
  424. } else if (alternativeKeys.active) {
  425. if (!alternativeKeys.openedByNavigationKeyLongPress) {
  426. alternativeKeys.clicked()
  427. alternativeKeys.close()
  428. keyboardInputArea.navigateToNextKey(0, 0, false)
  429. keyboardInputArea.reset()
  430. } else {
  431. alternativeKeys.openedByNavigationKeyLongPress = false
  432. }
  433. } else if (functionPopupList.active) {
  434. if (!functionPopupList.openedByNavigationKeyLongPress) {
  435. functionPopupList.clicked()
  436. functionPopupList.close()
  437. keyboardInputArea.navigateToNextKey(0, 0, false)
  438. keyboardInputArea.reset()
  439. } else {
  440. functionPopupList.openedByNavigationKeyLongPress = false
  441. }
  442. } else if (!wordCandidateContextMenu.active && wordCandidateView.count > 0) {
  443. wordCandidateView.model.selectItem(wordCandidateView.currentIndex)
  444. if (!InputContext.preeditText.length)
  445. keyboardInputArea.navigateToNextKey(0, 1, true)
  446. }
  447. break
  448. default:
  449. break
  450. }
  451. }
  452. }
  453. Connections {
  454. target: InputContext.inputEngine
  455. function onVirtualKeyClicked(key, text, modifiers, isAutoRepeat) {
  456. if (isAutoRepeat && keyboard.activeKey)
  457. soundEffect.play(keyboard.activeKey.soundEffect)
  458. if (key !== Qt.Key_unknown && keyboardInputArea.dragSymbolMode) {
  459. keyboardInputArea.dragSymbolMode = false
  460. keyboard.symbolMode = false
  461. } else if (key === Qt.Key_Space) {
  462. var surroundingText = InputContext.surroundingText.trim()
  463. if (InputContext.priv.shiftHandler.sentenceEndingCharacters.indexOf(surroundingText.charAt(surroundingText.length-1)) >= 0)
  464. keyboard.symbolMode = false
  465. }
  466. }
  467. }
  468. FolderListModel {
  469. id: layoutsModel
  470. nameFilters: ["$"]
  471. folder: VirtualKeyboardSettings.layoutPath
  472. }
  473. Connections {
  474. target: layoutsModel
  475. function onCountChanged() {
  476. updateDefaultLocale()
  477. localeIndex = defaultLocaleIndex
  478. }
  479. }
  480. AlternativeKeys {
  481. id: alternativeKeys
  482. objectName: "alternativeKeys"
  483. // Add some extra margin for decoration
  484. property real horizontalMargin: style.alternateKeysListItemWidth
  485. property real verticalMargin: style.alternateKeysListItemHeight
  486. property rect previewRect: Qt.rect(keyboard.x + alternativeKeys.listView.x - horizontalMargin,
  487. keyboard.y + alternativeKeys.listView.y - verticalMargin,
  488. alternativeKeys.listView.width + horizontalMargin * 2,
  489. alternativeKeys.listView.height + verticalMargin * 2)
  490. property bool openedByNavigationKeyLongPress
  491. onVisibleChanged: {
  492. if (visible)
  493. InputContext.priv.previewRectangle = Qt.binding(function() {return previewRect})
  494. else
  495. openedByNavigationKeyLongPress = false
  496. InputContext.priv.previewVisible = visible
  497. }
  498. }
  499. FunctionPopupList {
  500. id: functionPopupList
  501. property bool openedByNavigationKeyLongPress
  502. }
  503. Timer {
  504. id: pressAndHoldTimer
  505. interval: keyboard.pressAndHoldDelay
  506. onTriggered: {
  507. if (keyboard.activeKey && keyboard.activeKey === keyboardInputArea.initialKey) {
  508. var origin = keyboard.mapFromItem(activeKey, activeKey.width / 2, 0)
  509. if (keyboard.activeKey.smallText === "\u2699" &&
  510. functionPopupList.open(keyboard.activeKey, origin.x, origin.y)) {
  511. InputContext.inputEngine.virtualKeyCancel()
  512. keyboardInputArea.initialKey = null
  513. functionPopupList.openedByNavigationKeyLongPress = keyboard.navigationModeActive
  514. } else if (alternativeKeys.open(keyboard.activeKey, origin.x, origin.y)) {
  515. InputContext.inputEngine.virtualKeyCancel()
  516. keyboardInputArea.initialKey = null
  517. alternativeKeys.openedByNavigationKeyLongPress = keyboard.navigationModeActive
  518. } else if (keyboard.activeKey.key === Qt.Key_Context1 && !keyboard.symbolMode) {
  519. InputContext.inputEngine.virtualKeyCancel()
  520. keyboardInputArea.dragSymbolMode = true
  521. keyboard.symbolMode = true
  522. keyboardInputArea.initialKey = null
  523. if (keyboardInputArea.navigationCursor !== Qt.point(-1, -1))
  524. keyboardInputArea.navigateToNextKey(0, 0, false)
  525. }
  526. } else if (keyboardInputArea.dragSymbolMode &&
  527. keyboard.activeKey &&
  528. keyboard.activeKey.functionKey &&
  529. !keyboard.activeKey.repeat) {
  530. InputContext.inputEngine.virtualKeyCancel()
  531. keyboardInputArea.click(keyboard.activeKey)
  532. keyboardInputArea.initialKey = null
  533. if (keyboardInputArea.navigationCursor !== Qt.point(-1, -1))
  534. keyboardInputArea.navigateToNextKey(0, 0, false)
  535. } else if (!wordCandidateContextMenu.active && keyboard.navigationModeActive) {
  536. wordCandidateContextMenu.show(wordCandidateView.currentIndex)
  537. wordCandidateContextMenu.openedByNavigationKeyLongPress = keyboard.navigationModeActive
  538. }
  539. }
  540. }
  541. Timer {
  542. id: releaseInaccuracyTimer
  543. interval: 500
  544. onTriggered: {
  545. if (keyboardInputArea.pressed && activeTouchPoint && !alternativeKeys.active && !keyboardInputArea.dragSymbolMode && !functionPopupList.active) {
  546. var key = keyboardInputArea.keyOnPoint(activeTouchPoint.x, activeTouchPoint.y)
  547. if (key !== keyboard.activeKey) {
  548. InputContext.inputEngine.virtualKeyCancel()
  549. keyboardInputArea.setActiveKey(key)
  550. keyboardInputArea.press(key, false)
  551. }
  552. }
  553. }
  554. }
  555. CharacterPreviewBubble {
  556. id: characterPreview
  557. objectName: "characterPreviewBubble"
  558. active: keyboardInputArea.pressed && !alternativeKeys.active && !functionPopupList.active
  559. property rect previewRect: Qt.rect(keyboard.x + characterPreview.x,
  560. keyboard.y + characterPreview.y,
  561. characterPreview.width,
  562. characterPreview.height)
  563. }
  564. Binding {
  565. target: InputContext.priv
  566. property: "previewRectangle"
  567. value: characterPreview.previewRect
  568. when: characterPreview.visible
  569. restoreMode: Binding.RestoreBinding
  570. }
  571. Binding {
  572. target: InputContext.priv
  573. property: "previewRectangle"
  574. value: languagePopupList.previewRect
  575. when: languagePopupListActive
  576. restoreMode: Binding.RestoreBinding
  577. }
  578. Binding {
  579. target: InputContext.priv
  580. property: "previewVisible"
  581. value: characterPreview.visible || languagePopupListActive
  582. restoreMode: Binding.RestoreBinding
  583. }
  584. Loader {
  585. id: styleLoader
  586. source: VirtualKeyboardSettings.style
  587. Binding {
  588. target: styleLoader.item
  589. property: "keyboardHeight"
  590. value: keyboardInnerContainer.height
  591. restoreMode: Binding.RestoreBinding
  592. }
  593. }
  594. Loader {
  595. id: navigationHighlight
  596. objectName: "navigationHighlight"
  597. property var highlightItem: {
  598. if (keyboard.navigationModeActive) {
  599. if (languagePopupListActive) {
  600. return languagePopupList.highlightItem
  601. } else if (keyboardInputArea.initialKey) {
  602. return keyboardInputArea.initialKey
  603. } else if (alternativeKeys.listView.count > 0) {
  604. return alternativeKeys.listView.highlightItem
  605. } else if (functionPopupList.listView.count > 0) {
  606. return functionPopupList.listView.highlightItem
  607. } else if (wordCandidateContextMenu.active) {
  608. return wordCandidateContextMenuList.highlightItem
  609. } else if (wordCandidateView.count > 0) {
  610. return wordCandidateView.highlightItem
  611. }
  612. }
  613. return keyboard
  614. }
  615. // Note: without "highlightItem.x - highlightItem.x" the binding does not work for alternativeKeys
  616. property var highlightItemOffset: highlightItem ? keyboard.mapFromItem(highlightItem, highlightItem.x - highlightItem.x, highlightItem.y - highlightItem.y) : ({x:0, y:0})
  617. property int moveDuration: !keyboard.noAnimations ? 200 : 0
  618. property int resizeDuration: !keyboard.noAnimations ? 200 : 0
  619. z: 2
  620. x: highlightItemOffset.x
  621. y: highlightItemOffset.y
  622. width: highlightItem ? highlightItem.width : 0
  623. height: highlightItem ? highlightItem.height : 0
  624. visible: keyboard.navigationModeActive && highlightItem !== null && highlightItem !== keyboard
  625. sourceComponent: keyboard.style.navigationHighlight
  626. Behavior on x {
  627. NumberAnimation { id: xAnimation; duration: navigationHighlight.moveDuration; easing.type: Easing.OutCubic }
  628. }
  629. Behavior on y {
  630. NumberAnimation { id: yAnimation; duration: navigationHighlight.moveDuration; easing.type: Easing.OutCubic }
  631. }
  632. Behavior on width {
  633. NumberAnimation { id: widthAnimation; duration: navigationHighlight.resizeDuration; easing.type: Easing.OutCubic }
  634. }
  635. Behavior on height {
  636. NumberAnimation { id: heightAnimation; duration: navigationHighlight.resizeDuration; easing.type: Easing.OutCubic }
  637. }
  638. }
  639. ShadowInputControl {
  640. id: shadowInputControl
  641. objectName: "shadowInputControl"
  642. z: -3
  643. anchors.left: parent.left
  644. anchors.right: parent.right
  645. anchors.bottom: wordCandidateView.top
  646. height: keyboard.screenHeight -
  647. keyboard.height +
  648. wordCandidateView.y
  649. visible: fullScreenMode && (shadowInputControlVisibleTimer.running || InputContext.animating)
  650. Connections {
  651. target: keyboard
  652. function onActiveChanged() {
  653. if (keyboard.active)
  654. shadowInputControlVisibleTimer.start()
  655. else
  656. shadowInputControlVisibleTimer.stop()
  657. }
  658. }
  659. Timer {
  660. id: shadowInputControlVisibleTimer
  661. interval: 2147483647
  662. repeat: true
  663. }
  664. MouseArea {
  665. onPressed: keyboard.hideLanguagePopup()
  666. anchors.fill: parent
  667. enabled: languagePopupList.enabled
  668. }
  669. }
  670. SelectionControl {
  671. id: fullScreenModeSelectionControl
  672. objectName: "fullScreenModeSelectionControl"
  673. inputContext: InputContext.priv.shadow
  674. anchors.top: shadowInputControl.top
  675. anchors.left: shadowInputControl.left
  676. enabled: keyboard.enabled && fullScreenMode
  677. }
  678. ListView {
  679. id: wordCandidateView
  680. objectName: "wordCandidateView"
  681. clip: true
  682. z: -2
  683. property bool empty: true
  684. readonly property bool visibleCondition: keyboard.active && InputContext.inputEngine.wordCandidateListVisibleHint &&
  685. (!wordCandidateView.empty || wordCandidateViewAutoHideTimer.running)
  686. readonly property bool alwaysVisibleCondition: InputContext.inputEngine.wordCandidateListVisibleHint &&
  687. (keyboard.fullScreenMode || VirtualKeyboardSettings.wordCandidateList.alwaysVisible)
  688. readonly property real visibleYOffset: -height
  689. height: style ? style.selectionListHeight : 0
  690. anchors.left: parent.left
  691. anchors.right: parent.right
  692. spacing: 0
  693. orientation: ListView.Horizontal
  694. snapMode: ListView.SnapToItem
  695. delegate: style.selectionListDelegate
  696. highlight: style.selectionListHighlight ? style.selectionListHighlight : defaultHighlight
  697. highlightMoveDuration: 0
  698. highlightResizeDuration: 0
  699. add: !keyboard.noAnimations ? style.selectionListAdd : null
  700. remove: !keyboard.noAnimations ? style.selectionListRemove : null
  701. keyNavigationWraps: true
  702. model: InputContext.inputEngine.wordCandidateListModel
  703. onCurrentItemChanged: if (currentItem) soundEffect.register(currentItem.soundEffect)
  704. Connections {
  705. target: wordCandidateView.model ? wordCandidateView.model : null
  706. function onActiveItemChanged(index) { wordCandidateView.currentIndex = index }
  707. function onItemSelected() { if (wordCandidateView.currentItem) soundEffect.play(wordCandidateView.currentItem.soundEffect) }
  708. function onCountChanged() {
  709. var empty = wordCandidateView.model.count === 0
  710. if (empty)
  711. wordCandidateViewAutoHideTimer.restart()
  712. else
  713. wordCandidateViewAutoHideTimer.stop()
  714. wordCandidateView.empty = empty
  715. keyboard.hideWordCandidateContextMenu()
  716. }
  717. }
  718. Connections {
  719. target: InputContext.priv
  720. function onInputItemChanged() { wordCandidateViewAutoHideTimer.stop() }
  721. }
  722. Connections {
  723. target: InputContext.inputEngine
  724. function onWordCandidateListVisibleHintChanged() { wordCandidateViewAutoHideTimer.stop() }
  725. }
  726. Timer {
  727. id: wordCandidateViewAutoHideTimer
  728. interval: VirtualKeyboardSettings.wordCandidateList.autoHideDelay
  729. }
  730. Loader {
  731. sourceComponent: style.selectionListBackground
  732. anchors.fill: parent
  733. z: -1
  734. }
  735. Component {
  736. id: defaultHighlight
  737. Item {}
  738. }
  739. states: [
  740. State {
  741. name: "visible"
  742. when: wordCandidateView.visibleCondition
  743. PropertyChanges {
  744. target: wordCandidateView
  745. y: wordCandidateView.visibleYOffset
  746. }
  747. },
  748. State {
  749. name: "alwaysVisible"
  750. when: wordCandidateView.alwaysVisibleCondition
  751. PropertyChanges {
  752. target: wordCandidateView
  753. y: wordCandidateView.visibleYOffset
  754. }
  755. }
  756. ]
  757. transitions: Transition {
  758. id: wordCandidateViewTransition
  759. from: ""
  760. to: "visible"
  761. enabled: !InputContext.animating && !keyboard.noAnimations
  762. reversible: true
  763. ParallelAnimation {
  764. NumberAnimation {
  765. properties: "y"
  766. duration: 250
  767. easing.type: Easing.InOutQuad
  768. }
  769. }
  770. }
  771. function longPressItem(index) {
  772. return keyboard.showWordCandidateContextMenu(index)
  773. }
  774. }
  775. Item {
  776. id: soundEffect
  777. property var __sounds: ({})
  778. property bool available: false
  779. signal playingChanged(url source, bool playing)
  780. Connections {
  781. target: VirtualKeyboardSettings
  782. function onStyleNameChanged() {
  783. soundEffect.__sounds = {}
  784. soundEffect.available = false
  785. }
  786. }
  787. function play(sound) {
  788. if (enabled && sound != Qt.resolvedUrl("")) {
  789. var soundId = Qt.md5(sound)
  790. var multiSoundEffect = __sounds[soundId]
  791. if (!multiSoundEffect)
  792. multiSoundEffect = register(sound)
  793. if (multiSoundEffect) {
  794. multiSoundEffect.soundVolume = VirtualKeyboardSettings.convertVolume(VirtualKeyboardSettings.keySoundVolume)
  795. multiSoundEffect.play()
  796. }
  797. }
  798. }
  799. function register(sound) {
  800. var multiSoundEffect = null
  801. if (enabled && sound != Qt.resolvedUrl("")) {
  802. var soundId = Qt.md5(sound)
  803. multiSoundEffect = __sounds[soundId]
  804. if (!multiSoundEffect) {
  805. multiSoundEffect = Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard; MultiSoundEffect {}', soundEffect)
  806. if (multiSoundEffect) {
  807. multiSoundEffect.playingChanged.connect(soundEffect.playingChanged)
  808. multiSoundEffect.source = sound
  809. __sounds[soundId] = multiSoundEffect
  810. available = true
  811. }
  812. }
  813. }
  814. return multiSoundEffect
  815. }
  816. }
  817. Loader {
  818. id: keyboardBackground
  819. z: -1
  820. anchors.left: parent.left
  821. anchors.right: parent.right
  822. anchors.bottom: parent.bottom
  823. height: keyboardInnerContainer.height
  824. sourceComponent: style.keyboardBackground
  825. Item {
  826. id: keyboardInnerContainer
  827. z: 1
  828. width: Math.round(keyboardBackground.width)
  829. height: style ? Math.round(style.keyboardDesignHeight * width / style.keyboardDesignWidth) : 0
  830. anchors.horizontalCenter: parent.horizontalCenter
  831. LayoutMirroring.enabled: false
  832. LayoutMirroring.childrenInherit: true
  833. KeyboardObserver {
  834. id: keyboardObserver
  835. function scanLayout() {
  836. if (keyboardLayoutLoader.item == null)
  837. return null
  838. return keyboardLayoutLoader.item.scanLayout()
  839. }
  840. }
  841. Component.onCompleted: InputContext.priv.setKeyboardObserver(keyboardObserver)
  842. onWidthChanged: notifyLayoutChanged()
  843. onHeightChanged: notifyLayoutChanged()
  844. Loader {
  845. id: keyboardLayoutLoader
  846. objectName: "keyboardLayoutLoader"
  847. anchors.fill: parent
  848. anchors.leftMargin: Math.round(style.keyboardRelativeLeftMargin * parent.width)
  849. anchors.rightMargin: Math.round(style.keyboardRelativeRightMargin * parent.width)
  850. anchors.topMargin: Math.round(style.keyboardRelativeTopMargin * parent.height)
  851. anchors.bottomMargin: Math.round(style.keyboardRelativeBottomMargin * parent.height)
  852. Binding {
  853. target: keyboardLayoutLoader
  854. property: "source"
  855. value: keyboard.layout
  856. when: keyboard.width > 0 && keyboard.layout.length > 0
  857. restoreMode: Binding.RestoreNone
  858. }
  859. onItemChanged: {
  860. if (!item)
  861. return
  862. // Reset input mode if the new layout wants to override it
  863. if (item.inputMode !== -1)
  864. inputModeNeedsReset = true
  865. if (!InputContext.inputEngine.inputMethod)
  866. updateInputMethod()
  867. notifyLayoutChanged()
  868. }
  869. MultiPointTouchArea {
  870. id: keyboardInputArea
  871. objectName: "keyboardInputArea"
  872. property Item initialKey: null
  873. property bool dragSymbolMode
  874. property real releaseMargin: initialKey !== null ? Math.min(initialKey.width / 3, initialKey.height / 3) : 0
  875. property point navigationCursor: Qt.point(-1, -1)
  876. anchors.fill: keyboardLayoutLoader
  877. Connections {
  878. target: keyboardLayoutLoader
  879. function onLoaded() {
  880. if (keyboard.navigationModeActive &&
  881. keyboardInputArea.navigationCursor !== Qt.point(-1, -1))
  882. keyboard.navigationModeActive = keyboardInputArea.navigateToNextKey(0, 0, false)
  883. }
  884. }
  885. Connections {
  886. target: keyboard
  887. function onNavigationModeActiveChanged() {
  888. if (!keyboard.navigationModeActive) {
  889. keyboardInputArea.navigationCursor = Qt.point(-1, -1)
  890. keyboardInputArea.reset()
  891. }
  892. }
  893. }
  894. function press(key, isRealPress) {
  895. if (key && key.enabled) {
  896. if (!key.noKeyEvent)
  897. InputContext.inputEngine.virtualKeyPress(key.key, key.uppercased ? key.text.toUpperCase() : key.text, key.uppercased ? Qt.ShiftModifier : 0, key.repeat && !dragSymbolMode)
  898. if (isRealPress)
  899. soundEffect.play(key.soundEffect)
  900. }
  901. }
  902. function release(key) {
  903. if (key && key.enabled) {
  904. if (!key.noKeyEvent)
  905. InputContext.inputEngine.virtualKeyRelease(key.key, key.uppercased ? key.text.toUpperCase() : key.text, key.uppercased ? Qt.ShiftModifier : 0)
  906. key.clicked()
  907. }
  908. }
  909. function click(key) {
  910. if (key && key.enabled) {
  911. if (!key.noKeyEvent)
  912. InputContext.inputEngine.virtualKeyClick(key.key, InputContext.uppercase ? key.text.toUpperCase() : key.text, InputContext.uppercase ? Qt.ShiftModifier : 0)
  913. key.clicked()
  914. }
  915. }
  916. function setActiveKey(activeKey) {
  917. if (keyboard.activeKey === activeKey)
  918. return
  919. if (keyboard.activeKey) {
  920. if (keyboard.activeKey.keyType === QtVirtualKeyboard.KeyType.FlickKey)
  921. keyboard.activeKey.onKeyChanged.disconnect(onFlickKeyKeyChanged)
  922. keyboard.activeKey.active = false
  923. }
  924. keyboard.activeKey = activeKey
  925. if (keyboard.activeKey) {
  926. keyboard.activeKey.active = true
  927. }
  928. }
  929. function keyOnPoint(px, py) {
  930. var parentItem = keyboardLayoutLoader
  931. var child = parentItem.childAt(px, py)
  932. while (child !== null) {
  933. var position = parentItem.mapToItem(child, px, py)
  934. px = position.x; py = position.y
  935. parentItem = child
  936. child = parentItem.childAt(px, py)
  937. if (child && child.key !== undefined)
  938. return child
  939. }
  940. return null
  941. }
  942. function hitInitialKey(x, y, margin) {
  943. if (!initialKey)
  944. return false
  945. var position = initialKey.mapFromItem(keyboardInputArea, x, y)
  946. return (position.x > -margin
  947. && position.y > -margin
  948. && position.x < initialKey.width + margin
  949. && position.y < initialKey.height + margin)
  950. }
  951. function containsPoint(touchPoints, point) {
  952. if (!point)
  953. return false
  954. for (var i in touchPoints)
  955. if (touchPoints[i].pointId == point.pointId)
  956. return true
  957. return false
  958. }
  959. function releaseActiveKey() {
  960. if (alternativeKeys.active) {
  961. alternativeKeys.clicked()
  962. } else if (functionPopupList.active) {
  963. functionPopupList.clicked()
  964. } else if (keyboard.activeKey) {
  965. release(keyboard.activeKey)
  966. }
  967. reset()
  968. }
  969. function reset() {
  970. releaseInaccuracyTimer.stop()
  971. pressAndHoldTimer.stop()
  972. setActiveKey(null)
  973. activeTouchPoint = null
  974. alternativeKeys.close()
  975. functionPopupList.close()
  976. if (dragSymbolMode) {
  977. keyboard.symbolMode = false
  978. dragSymbolMode = false
  979. }
  980. }
  981. function nextKeyInNavigation(dX, dY, wrapEnabled) {
  982. var nextKey = null, x, y, itemOffset
  983. if (dX !== 0 || dY !== 0) {
  984. var offsetX, offsetY
  985. for (offsetX = dX, offsetY = dY;
  986. Math.abs(offsetX) < width && Math.abs(offsetY) < height;
  987. offsetX += dX, offsetY += dY) {
  988. x = navigationCursor.x + offsetX
  989. if (x < 0) {
  990. if (!wrapEnabled)
  991. break
  992. x += width
  993. } else if (x >= width) {
  994. if (!wrapEnabled)
  995. break
  996. x -= width
  997. }
  998. y = navigationCursor.y + offsetY
  999. if (y < 0) {
  1000. if (!wrapEnabled)
  1001. break
  1002. y += height
  1003. } else if (y >= height) {
  1004. if (!wrapEnabled)
  1005. break
  1006. y -= height
  1007. }
  1008. nextKey = keyOnPoint(x, y)
  1009. if (nextKey) {
  1010. // Check if key is visible. Only the visible keys have keyPanelDelegate set.
  1011. if (nextKey != initialKey && nextKey.hasOwnProperty("keyPanelDelegate") && nextKey.keyPanelDelegate)
  1012. break
  1013. // Jump over the item to reduce the number of iterations in this loop
  1014. itemOffset = mapToItem(nextKey, x, y)
  1015. if (dX > 0)
  1016. offsetX += nextKey.width - itemOffset.x
  1017. else if (dX < 0)
  1018. offsetX -= itemOffset.x
  1019. else if (dY > 0)
  1020. offsetY += nextKey.height - itemOffset.y
  1021. else if (dY < 0)
  1022. offsetY -= itemOffset.y
  1023. }
  1024. nextKey = null
  1025. }
  1026. } else {
  1027. nextKey = keyOnPoint(navigationCursor.x, navigationCursor.y)
  1028. }
  1029. if (nextKey) {
  1030. itemOffset = mapFromItem(nextKey, nextKey.width / 2, nextKey.height / 2)
  1031. if (dX) {
  1032. x = itemOffset.x
  1033. } else if (dY) {
  1034. y = itemOffset.y
  1035. } else {
  1036. x = itemOffset.x
  1037. y = itemOffset.y
  1038. }
  1039. navigationCursor = Qt.point(x, y)
  1040. }
  1041. return nextKey
  1042. }
  1043. function navigateToNextKey(dX, dY, wrapEnabled) {
  1044. // Resolve initial landing point of the navigation cursor
  1045. if (!keyboard.navigationModeActive || keyboard.navigationCursor === Qt.point(-1, -1)) {
  1046. if (dX > 0)
  1047. navigationCursor = Qt.point(0, height / 2)
  1048. else if (dX < 0)
  1049. navigationCursor = Qt.point(width, height / 2)
  1050. else if (dY > 0)
  1051. navigationCursor = Qt.point(width / 2, 0)
  1052. else if (dY < 0)
  1053. navigationCursor = Qt.point(width / 2, height)
  1054. else
  1055. navigationCursor = Qt.point(width / 2, height / 2)
  1056. keyboard.navigationModeActive = true
  1057. }
  1058. if (dX && dY) {
  1059. initialKey = nextKeyInNavigation(dX, 0, wrapEnabled)
  1060. if (initialKey || wrapEnabled)
  1061. initialKey = nextKeyInNavigation(0, dY, wrapEnabled)
  1062. } else {
  1063. initialKey = nextKeyInNavigation(dX, dY, wrapEnabled)
  1064. }
  1065. return initialKey !== null
  1066. }
  1067. function onFlickKeyKeyChanged() {
  1068. InputContext.inputEngine.virtualKeyCancel()
  1069. press(activeKey, false)
  1070. }
  1071. onPressed: (touchPoints) => {
  1072. keyboard.navigationModeActive = false
  1073. // Immediately release any pending key that the user might be
  1074. // holding (and about to release) when a second key is pressed.
  1075. if (activeTouchPoint)
  1076. releaseActiveKey();
  1077. for (var i in touchPoints) {
  1078. // Release any key pressed by a previous iteration of the loop.
  1079. if (containsPoint(touchPoints, activeTouchPoint))
  1080. releaseActiveKey();
  1081. initialKey = keyOnPoint(touchPoints[i].x, touchPoints[i].y)
  1082. if (!initialKey)
  1083. continue
  1084. activeTouchPoint = touchPoints[i]
  1085. if (initialKey.keyType === QtVirtualKeyboard.KeyType.FlickKey) {
  1086. initialKey.press(activeTouchPoint.x, activeTouchPoint.y)
  1087. initialKey.onKeyChanged.connect(onFlickKeyKeyChanged)
  1088. } else {
  1089. releaseInaccuracyTimer.start()
  1090. pressAndHoldTimer.start()
  1091. }
  1092. setActiveKey(initialKey)
  1093. press(initialKey, true)
  1094. }
  1095. }
  1096. onUpdated: (touchPoints) => {
  1097. if (!containsPoint(touchPoints, activeTouchPoint))
  1098. return
  1099. if (alternativeKeys.active) {
  1100. alternativeKeys.move(mapToItem(alternativeKeys, activeTouchPoint.x, 0).x)
  1101. } else if (functionPopupList.active) {
  1102. functionPopupList.move(mapToItem(functionPopupList, activeTouchPoint.x, activeTouchPoint.y))
  1103. } else if (activeKey && activeKey.keyType === QtVirtualKeyboard.KeyType.FlickKey) {
  1104. activeKey.update(activeTouchPoint.x, activeTouchPoint.y)
  1105. } else {
  1106. var key = null
  1107. if (releaseInaccuracyTimer.running) {
  1108. if (hitInitialKey(activeTouchPoint.x, activeTouchPoint.y, releaseMargin)) {
  1109. key = initialKey
  1110. } else if (initialKey) {
  1111. releaseInaccuracyTimer.stop()
  1112. initialKey = null
  1113. }
  1114. }
  1115. if (key === null) {
  1116. key = keyOnPoint(activeTouchPoint.x, activeTouchPoint.y)
  1117. }
  1118. if (key !== keyboard.activeKey) {
  1119. InputContext.inputEngine.virtualKeyCancel()
  1120. setActiveKey(key)
  1121. press(key, false)
  1122. if (dragSymbolMode) {
  1123. if (key && key.functionKey && key.key !== Qt.Key_Context1)
  1124. pressAndHoldTimer.restart()
  1125. else
  1126. pressAndHoldTimer.stop()
  1127. }
  1128. }
  1129. }
  1130. }
  1131. onReleased: (touchPoints) => {
  1132. if (containsPoint(touchPoints, activeTouchPoint)) {
  1133. if (dragSymbolMode) {
  1134. var key = keyOnPoint(activeTouchPoint.x, activeTouchPoint.y)
  1135. if (key && key.key === Qt.Key_Context1) {
  1136. dragSymbolMode = false
  1137. InputContext.inputEngine.virtualKeyCancel()
  1138. reset()
  1139. return
  1140. }
  1141. }
  1142. releaseActiveKey();
  1143. }
  1144. }
  1145. onCanceled: (touchPoints) => {
  1146. if (containsPoint(touchPoints, activeTouchPoint))
  1147. reset()
  1148. }
  1149. }
  1150. }
  1151. }
  1152. }
  1153. Item {
  1154. id: languagePopup
  1155. z: 1
  1156. anchors.fill: parent
  1157. LayoutMirroring.enabled: false
  1158. LayoutMirroring.childrenInherit: true
  1159. MouseArea {
  1160. onPressed: keyboard.hideLanguagePopup()
  1161. anchors.fill: parent
  1162. enabled: languagePopupList.enabled
  1163. }
  1164. PopupList {
  1165. id: languagePopupList
  1166. objectName: "languagePopupList"
  1167. z: 2
  1168. anchors.left: parent.left
  1169. anchors.top: parent.top
  1170. enabled: false
  1171. model: languageListModel
  1172. delegate: keyboard.style ? keyboard.style.languageListDelegate : null
  1173. highlight: keyboard.style ? keyboard.style.languageListHighlight : defaultHighlight
  1174. add: keyboard.style && !keyboard.noAnimations ? keyboard.style.languageListAdd : null
  1175. remove: keyboard.style && !keyboard.noAnimations ? keyboard.style.languageListRemove : null
  1176. property rect previewRect: Qt.rect(keyboard.x + languagePopupList.x,
  1177. keyboard.y + languagePopupList.y,
  1178. languagePopupList.width,
  1179. languagePopupList.height)
  1180. }
  1181. Loader {
  1182. sourceComponent: keyboard.style.languageListBackground
  1183. anchors.fill: languagePopupList
  1184. z: -1
  1185. visible: languagePopupList.visible
  1186. }
  1187. ListModel {
  1188. id: languageListModel
  1189. function selectItem(index) {
  1190. languagePopupList.currentIndex = index
  1191. keyboard.soundEffect.play(languagePopupList.currentItem.soundEffect)
  1192. changeLanguageTimer.newLocaleIndex = languageListModel.get(index).localeIndex
  1193. changeLanguageTimer.start()
  1194. }
  1195. }
  1196. Timer {
  1197. id: changeLanguageTimer
  1198. interval: 1
  1199. property int newLocaleIndex
  1200. onTriggered: {
  1201. if (languagePopupListActive) {
  1202. hideLanguagePopup()
  1203. start()
  1204. } else {
  1205. localeIndex = newLocaleIndex
  1206. }
  1207. }
  1208. }
  1209. function show(locales, parentItem, customLayoutsOnly) {
  1210. let currentIndex = -1
  1211. if (!languagePopupList.enabled) {
  1212. languageListModel.clear()
  1213. for (var i = 0; i < locales.length; i++) {
  1214. languageListModel.append({localeName: locales[i].name, displayName: locales[i].locale.nativeLanguageName, localeIndex: locales[i].index})
  1215. if (locales[i].index === keyboard.localeIndex)
  1216. currentIndex = i
  1217. }
  1218. if (parentItem) {
  1219. languagePopupList.anchors.leftMargin = Qt.binding(function() {
  1220. const newLeftMargin = Math.round(keyboard.mapFromItem(parentItem, (parentItem.width - languagePopupList.width) / 2, 0).x)
  1221. return Math.min(Math.max(0, newLeftMargin), keyboard.width - languagePopupList.width)
  1222. })
  1223. languagePopupList.anchors.topMargin = Qt.binding(function() {return Math.round(keyboard.mapFromItem(parentItem, 0, -languagePopupList.height).y)})
  1224. } else {
  1225. languagePopupList.anchors.leftMargin = Qt.binding(function() {return Math.round((keyboard.width - languagePopupList.width) / 2)})
  1226. languagePopupList.anchors.topMargin = Qt.binding(function() {return Math.round((keyboard.height - languagePopupList.height) / 2)})
  1227. }
  1228. }
  1229. languagePopupList.enabled = true
  1230. if (currentIndex !== -1) {
  1231. languagePopupList.currentIndex = currentIndex
  1232. languagePopupList.forceLayout()
  1233. languagePopupList.positionViewAtIndex(currentIndex, ListView.Center)
  1234. }
  1235. }
  1236. function hide() {
  1237. if (languagePopupList.enabled) {
  1238. languagePopupList.enabled = false
  1239. languagePopupList.anchors.leftMargin = undefined
  1240. languagePopupList.anchors.topMargin = undefined
  1241. languageListModel.clear()
  1242. }
  1243. }
  1244. }
  1245. function showLanguagePopup(parentItem, customLayoutsOnly) {
  1246. var locales = keyboard.listLocales(customLayoutsOnly, parent.externalLanguageSwitchEnabled)
  1247. if (parent.externalLanguageSwitchEnabled) {
  1248. var currentIndex = 0
  1249. for (var i = 0; i < locales.length; i++) {
  1250. if (locales[i] === keyboard.locale) {
  1251. currentIndex = i
  1252. break
  1253. }
  1254. }
  1255. parent.externalLanguageSwitch(locales, currentIndex)
  1256. return
  1257. }
  1258. languagePopup.show(locales, parentItem, customLayoutsOnly)
  1259. }
  1260. function hideLanguagePopup() {
  1261. languagePopup.hide()
  1262. }
  1263. MouseArea {
  1264. anchors.left: parent.left
  1265. anchors.right: parent.right
  1266. anchors.bottom: parent.bottom
  1267. height: keyboard.screenHeight
  1268. onPressed: keyboard.hideWordCandidateContextMenu()
  1269. enabled: wordCandidateContextMenuList.enabled
  1270. }
  1271. Item {
  1272. id: wordCandidateContextMenu
  1273. objectName: "wordCandidateContextMenu"
  1274. z: 1
  1275. anchors.fill: parent
  1276. LayoutMirroring.enabled: false
  1277. LayoutMirroring.childrenInherit: true
  1278. property int previousWordCandidateIndex: -1
  1279. readonly property bool active: wordCandidateContextMenuList.visible
  1280. property bool openedByNavigationKeyLongPress
  1281. PopupList {
  1282. id: wordCandidateContextMenuList
  1283. objectName: "wordCandidateContextMenuList"
  1284. z: 2
  1285. anchors.left: parent.left
  1286. anchors.top: parent.top
  1287. enabled: false
  1288. model: wordCandidateContextMenuListModel
  1289. property rect previewRect: Qt.rect(keyboard.x + wordCandidateContextMenuList.x,
  1290. keyboard.y + wordCandidateContextMenuList.y,
  1291. wordCandidateContextMenuList.width,
  1292. wordCandidateContextMenuList.height)
  1293. }
  1294. Loader {
  1295. sourceComponent: keyboard.style.popupListBackground
  1296. anchors.fill: wordCandidateContextMenuList
  1297. z: -1
  1298. visible: wordCandidateContextMenuList.visible
  1299. }
  1300. ListModel {
  1301. id: wordCandidateContextMenuListModel
  1302. function selectItem(index) {
  1303. wordCandidateContextMenu.previousWordCandidateIndex = -1
  1304. wordCandidateContextMenuList.currentIndex = index
  1305. keyboard.soundEffect.play(wordCandidateContextMenuList.currentItem.soundEffect)
  1306. switch (get(index).action) {
  1307. case "remove":
  1308. wordCandidateView.model.removeItem(wordCandidateView.currentIndex)
  1309. break
  1310. }
  1311. keyboard.hideWordCandidateContextMenu()
  1312. }
  1313. }
  1314. function show(wordCandidateIndex) {
  1315. if (wordCandidateContextMenu.enabled)
  1316. wordCandidateContextMenu.hide()
  1317. wordCandidateContextMenuListModel.clear()
  1318. var canRemoveSuggestion = wordCandidateView.model.dataAt(wordCandidateIndex, SelectionListModel.Role.CanRemoveSuggestion)
  1319. if (canRemoveSuggestion) {
  1320. var dictionaryType = wordCandidateView.model.dataAt(wordCandidateIndex, SelectionListModel.Role.Dictionary)
  1321. var removeItemText;
  1322. switch (dictionaryType) {
  1323. case SelectionListModel.DictionaryType.User:
  1324. //~ VirtualKeyboard Context menu for word suggestion if it can be removed from the user dictionary.
  1325. removeItemText = qsTr("Remove from dictionary")
  1326. break
  1327. case SelectionListModel.DictionaryType.Default:
  1328. // Fallthrough
  1329. default:
  1330. //~ VirtualKeyboard Context menu for word suggestion if it can be removed from the default dictionary.
  1331. removeItemText = qsTr("Block word")
  1332. break
  1333. }
  1334. wordCandidateContextMenuListModel.append({action: "remove", display: removeItemText, wordCompletionLength: 0})
  1335. }
  1336. if (wordCandidateContextMenuListModel.count === 0)
  1337. return
  1338. previousWordCandidateIndex = wordCandidateView.currentIndex
  1339. wordCandidateView.currentIndex = wordCandidateIndex
  1340. wordCandidateContextMenuList.anchors.leftMargin = Qt.binding(function() {
  1341. if (!wordCandidateView.currentItem)
  1342. return 0
  1343. var leftBorder = Math.round(wordCandidateView.mapFromItem(wordCandidateView.currentItem, (wordCandidateView.currentItem.width - wordCandidateContextMenuList.width) / 2, 0).x)
  1344. var rightBorder = Math.round(wordCandidateContextMenuList.parent.width - wordCandidateContextMenuList.width)
  1345. return Math.max(0, Math.min(leftBorder, rightBorder))
  1346. })
  1347. wordCandidateContextMenuList.enabled = true
  1348. wordCandidateContextMenuList.currentIndex = 0
  1349. }
  1350. function hide() {
  1351. if (wordCandidateContextMenuList.enabled) {
  1352. if (previousWordCandidateIndex !== -1) {
  1353. wordCandidateView.currentIndex = previousWordCandidateIndex
  1354. previousWordCandidateIndex = -1
  1355. }
  1356. wordCandidateContextMenuList.enabled = false
  1357. wordCandidateContextMenuList.anchors.leftMargin = undefined
  1358. wordCandidateContextMenuListModel.clear()
  1359. }
  1360. openedByNavigationKeyLongPress = false
  1361. }
  1362. function selectCurrentItem() {
  1363. if (active && wordCandidateContextMenuList.currentIndex !== -1)
  1364. wordCandidateContextMenuListModel.selectItem(wordCandidateContextMenuList.currentIndex)
  1365. }
  1366. }
  1367. function showWordCandidateContextMenu(wordCandidateIndex) {
  1368. wordCandidateContextMenu.show(wordCandidateIndex)
  1369. }
  1370. function hideWordCandidateContextMenu() {
  1371. wordCandidateContextMenu.hide()
  1372. }
  1373. function updateInputMethod() {
  1374. if (!keyboardLayoutLoader.item)
  1375. return
  1376. if (!InputContext.priv.focus)
  1377. return
  1378. // Reset the custom input method if it is not included in the list of shared layouts
  1379. if (customInputMethod && !inputMethodNeedsReset && customInputMethodSharedLayouts.indexOf(layoutType) === -1)
  1380. inputMethodNeedsReset = true
  1381. var customInputMethodToDestroy = null
  1382. if (inputMethodNeedsReset) {
  1383. if (customInputMethod) {
  1384. // Postpones the destruction of the custom input method after creating a new one
  1385. // and after assigning it to the input engine. This allows the input method to clear
  1386. // its state before destroying.
  1387. customInputMethodToDestroy = customInputMethod
  1388. customInputMethod = null
  1389. }
  1390. customInputMethodSharedLayouts = []
  1391. inputMethodNeedsReset = false
  1392. }
  1393. var inputMethod = null
  1394. var inputMode = InputContext.inputEngine.inputMode
  1395. // Use input method from keyboard layout
  1396. if (keyboardLayoutLoader.item.inputMethod) {
  1397. inputMethod = keyboardLayoutLoader.item.inputMethod
  1398. } else if (!customInputMethod) {
  1399. try {
  1400. customInputMethod = keyboardLayoutLoader.item.createInputMethod()
  1401. if (customInputMethod) {
  1402. // Pull the list of shared layouts from the keyboard layout
  1403. if (keyboardLayoutLoader.item.sharedLayouts)
  1404. customInputMethodSharedLayouts = customInputMethodSharedLayouts.concat(keyboardLayoutLoader.item.sharedLayouts)
  1405. // Make sure the current layout is included in the list
  1406. if (customInputMethodSharedLayouts.indexOf(layoutType) === -1)
  1407. customInputMethodSharedLayouts.push(layoutType)
  1408. // Reset input mode, since inputEngine.inputModes is updated
  1409. inputModeNeedsReset = true
  1410. }
  1411. } catch (e) {
  1412. console.error(e.message)
  1413. }
  1414. }
  1415. if (!inputMethod) {
  1416. if (customInputMethod) {
  1417. inputMethod = customInputMethod
  1418. } else if (!VirtualKeyboardSettings.defaultInputMethodDisabled) {
  1419. inputMethod = defaultInputMethod
  1420. } else {
  1421. inputMethod = plainInputMethod
  1422. }
  1423. }
  1424. var inputMethodChanged = InputContext.inputEngine.inputMethod !== inputMethod
  1425. if (inputMethodChanged) {
  1426. InputContext.inputEngine.inputMethod = inputMethod
  1427. }
  1428. if (InputContext.inputEngine.inputMethod) {
  1429. var inputModes = InputContext.inputEngine.inputModes
  1430. if (inputModes.length > 0) {
  1431. // Reset to default input mode if the input locale has changed
  1432. if (inputModeNeedsReset) {
  1433. inputMode = inputModes[0]
  1434. // Check the current layout for input mode override
  1435. if (keyboardLayoutLoader.item.inputMode !== -1)
  1436. inputMode = keyboardLayoutLoader.item.inputMode
  1437. // Update input mode automatically in handwriting mode
  1438. if (keyboard.handwritingMode) {
  1439. if (keyboard.dialableCharactersOnly && inputModes.indexOf(InputEngine.InputMode.Dialable) !== -1)
  1440. inputMode = InputEngine.InputMode.Dialable
  1441. else if ((keyboard.formattedNumbersOnly || keyboard.digitsOnly) && inputModes.indexOf(InputEngine.InputMode.Numeric) !== -1)
  1442. inputMode = InputEngine.InputMode.Numeric
  1443. else if (keyboardLayoutLoader.item.inputMode === -1)
  1444. inputMode = inputModes[0]
  1445. }
  1446. // Check the input method hints for input mode overrides
  1447. if (latinOnly)
  1448. inputMode = InputEngine.InputMode.Latin
  1449. if (preferNumbers)
  1450. inputMode = InputEngine.InputMode.Numeric
  1451. }
  1452. // Make sure the input mode is supported by the current input method
  1453. if (inputModes.indexOf(inputMode) === -1)
  1454. inputMode = inputModes[0]
  1455. if (InputContext.inputEngine.inputMode !== inputMode || inputMethodChanged || inputModeNeedsReset) {
  1456. InputContext.priv.setKeyboardObserver(keyboardObserver)
  1457. InputContext.inputEngine.inputMode = inputMode
  1458. }
  1459. inputModeNeedsReset = false
  1460. }
  1461. }
  1462. if (customInputMethodToDestroy !== null)
  1463. customInputMethodToDestroy.destroy()
  1464. // Clear the toggle shift timer
  1465. InputContext.priv.shiftHandler.clearToggleShiftTimer()
  1466. }
  1467. function updateLayout() {
  1468. var newLayout
  1469. newLayout = findLayout(locale, layoutType)
  1470. if (!newLayout.length) {
  1471. newLayout = findLayout(locale, "main")
  1472. }
  1473. layout = newLayout
  1474. inputLocale = locale
  1475. updateInputMethod()
  1476. }
  1477. function updateDefaultLocale() {
  1478. updateAvailableLocaleIndices()
  1479. if (layoutsModel.count > 0) {
  1480. var defaultLocales = []
  1481. if (isValidLocale(VirtualKeyboardSettings.locale))
  1482. defaultLocales.push(VirtualKeyboardSettings.locale)
  1483. if (isValidLocale(InputContext.locale))
  1484. defaultLocales.push(InputContext.locale)
  1485. if (VirtualKeyboardSettings.activeLocales.length > 0 && isValidLocale(VirtualKeyboardSettings.activeLocales[0]))
  1486. defaultLocales.push(VirtualKeyboardSettings.activeLocales[0])
  1487. if (VirtualKeyboardSettings.availableLocales.indexOf("en_GB") !== -1)
  1488. defaultLocales.push("en_GB")
  1489. if (availableLocaleIndices.length > 0)
  1490. defaultLocales.push(layoutsModel.get(availableLocaleIndices[0], "fileName"))
  1491. var newDefaultLocaleIndex = -1
  1492. for (var i = 0; i < defaultLocales.length; i++) {
  1493. newDefaultLocaleIndex = findLocale(defaultLocales[i], -1)
  1494. if (availableLocaleIndices.indexOf(newDefaultLocaleIndex) !== -1)
  1495. break;
  1496. newDefaultLocaleIndex = -1
  1497. }
  1498. defaultLocaleIndex = newDefaultLocaleIndex
  1499. } else {
  1500. defaultLocaleIndex = -1
  1501. }
  1502. }
  1503. function filterLocaleIndices(filterCb) {
  1504. var localeIndices = []
  1505. for (var i = 0; i < layoutsModel.count; i++) {
  1506. if (localeIndices.indexOf(i) === -1) {
  1507. var localeName = layoutsModel.get(i, "fileName")
  1508. if (filterCb(localeName) && findLayout(localeName, "main"))
  1509. localeIndices.push(i)
  1510. }
  1511. }
  1512. return localeIndices
  1513. }
  1514. function updateAvailableLocaleIndices() {
  1515. // Update list of all available locales
  1516. var fallbackIndex = findFallbackIndex()
  1517. var newIndices = filterLocaleIndices(function(localeName) {
  1518. return isValidLocale(localeName)
  1519. })
  1520. // Handle case where the VirtualKeyboardSettings.activeLocales contains no valid entries
  1521. // Fetch all locales by ignoring active locales setting
  1522. var ignoreActiveLocales = newIndices.length === 0
  1523. if (ignoreActiveLocales) {
  1524. newIndices = filterLocaleIndices(function(localeName) {
  1525. return isValidLocale(localeName, ignoreActiveLocales)
  1526. })
  1527. }
  1528. // Fetch matching locale names
  1529. var newAvailableLocales = []
  1530. for (var i = 0; i < newIndices.length; i++) {
  1531. newAvailableLocales.push(layoutsModel.get(newIndices[i], "fileName"))
  1532. }
  1533. newAvailableLocales.sort()
  1534. var sortOrder = !ignoreActiveLocales && VirtualKeyboardSettings.activeLocales.length > 0 ?
  1535. VirtualKeyboardSettings.activeLocales :
  1536. newAvailableLocales
  1537. newIndices.sort(function(localeIndexA, localeIndexB) {
  1538. var localeNameA = layoutsModel.get(localeIndexA, "fileName")
  1539. var localeNameB = layoutsModel.get(localeIndexB, "fileName")
  1540. var sortIndexA = sortOrder.indexOf(localeNameA)
  1541. var sortIndexB = sortOrder.indexOf(localeNameB)
  1542. return sortIndexA - sortIndexB
  1543. })
  1544. availableLocaleIndices = newIndices
  1545. InputContext.priv.updateAvailableLocales(newAvailableLocales)
  1546. // Update list of custom locale indices
  1547. newIndices = []
  1548. for (i = 0; i < availableLocaleIndices.length; i++) {
  1549. if (availableLocaleIndices[i] === localeIndex ||
  1550. layoutExists(layoutsModel.get(availableLocaleIndices[i], "fileName"), layoutType))
  1551. newIndices.push(availableLocaleIndices[i])
  1552. }
  1553. availableCustomLocaleIndices = newIndices
  1554. }
  1555. function listLocales(customLayoutsOnly, localeNameOnly) {
  1556. var locales = []
  1557. var localeIndices = customLayoutsOnly ? availableCustomLocaleIndices : availableLocaleIndices
  1558. for (var i = 0; i < localeIndices.length; i++) {
  1559. var layoutFolder = layoutsModel.get(localeIndices[i], "fileName")
  1560. if (localeNameOnly)
  1561. locales.push(layoutFolder)
  1562. else
  1563. locales.push({locale:Qt.locale(layoutFolder), index:localeIndices[i], name:layoutFolder})
  1564. }
  1565. return locales
  1566. }
  1567. function nextLocaleIndex(customLayoutsOnly) {
  1568. var newLocaleIndex = localeIndex
  1569. var localeIndices = customLayoutsOnly ? availableCustomLocaleIndices : availableLocaleIndices
  1570. var i = localeIndices.indexOf(localeIndex)
  1571. if (i !== -1) {
  1572. i = (i + 1) % localeIndices.length
  1573. newLocaleIndex = localeIndices[i]
  1574. }
  1575. return newLocaleIndex
  1576. }
  1577. function changeInputLanguage(customLayoutsOnly) {
  1578. var newLocaleIndex = nextLocaleIndex(customLayoutsOnly)
  1579. if (newLocaleIndex !== -1 && newLocaleIndex !== localeIndex)
  1580. localeIndex = newLocaleIndex
  1581. }
  1582. function canChangeInputLanguage(customLayoutsOnly) {
  1583. if (customLayoutsOnly)
  1584. return availableCustomLocaleIndices.length > 1
  1585. return availableLocaleIndices.length > 1
  1586. }
  1587. function findLocale(localeName, defaultValue) {
  1588. var languageCode = localeName.substring(0, 3) // Including the '_' delimiter
  1589. var languageMatch = -1
  1590. for (var i = 0; i < layoutsModel.count; i++) {
  1591. if (!layoutsModel.isFolder(i))
  1592. continue
  1593. var layoutFolder = layoutsModel.get(i, "fileName")
  1594. if (layoutFolder === localeName)
  1595. return i
  1596. if (languageMatch == -1 && layoutFolder.substring(0, 3) === languageCode)
  1597. languageMatch = i
  1598. }
  1599. return (languageMatch != -1) ? languageMatch : defaultValue
  1600. }
  1601. function findFallbackIndex() {
  1602. for (var i = 0; i < layoutsModel.count; i++) {
  1603. var layoutFolder = layoutsModel.get(i, "fileName")
  1604. if (layoutFolder === "fallback")
  1605. return i
  1606. }
  1607. return -1
  1608. }
  1609. function isValidLocale(localeNameOrIndex, ignoreActiveLocales) {
  1610. var localeName
  1611. if (typeof localeNameOrIndex == "number") {
  1612. if (localeNameOrIndex < 0 || localeNameOrIndex >= layoutsModel.count)
  1613. return false
  1614. localeName = layoutsModel.get(localeNameOrIndex, "fileName")
  1615. } else {
  1616. localeName = localeNameOrIndex
  1617. }
  1618. if (!localeName)
  1619. return false
  1620. if (localeName === "fallback")
  1621. return false
  1622. if (Qt.locale(localeName).name === "C")
  1623. return false
  1624. if (ignoreActiveLocales !== true &&
  1625. VirtualKeyboardSettings.activeLocales.length > 0 &&
  1626. VirtualKeyboardSettings.activeLocales.indexOf(localeName) === -1)
  1627. return false
  1628. return true
  1629. }
  1630. function getLayoutFile(localeName, layoutType) {
  1631. if (localeName === "" || layoutType === "")
  1632. return ""
  1633. return layoutsModel.folder + "/" + localeName + "/" + layoutType + ".qml"
  1634. }
  1635. function getFallbackFile(localeName, layoutType) {
  1636. if (localeName === "" || layoutType === "")
  1637. return ""
  1638. return layoutsModel.folder + "/" + localeName + "/" + layoutType + ".fallback"
  1639. }
  1640. function layoutExists(localeName, layoutType) {
  1641. var result = InputContext.priv.fileExists(getLayoutFile(localeName, layoutType))
  1642. if (!result && layoutType === "handwriting")
  1643. result = InputContext.priv.fileExists(getFallbackFile(localeName, layoutType))
  1644. return result
  1645. }
  1646. function findLayout(localeName, layoutType) {
  1647. var layoutFile = getLayoutFile(localeName, layoutType)
  1648. if (InputContext.priv.fileExists(layoutFile))
  1649. return layoutFile
  1650. var fallbackFile = getFallbackFile(localeName, layoutType)
  1651. if (InputContext.priv.fileExists(fallbackFile)) {
  1652. layoutFile = getLayoutFile("fallback", layoutType)
  1653. if (InputContext.priv.fileExists(layoutFile))
  1654. return layoutFile
  1655. }
  1656. return ""
  1657. }
  1658. function isHandwritingAvailable() {
  1659. if (VirtualKeyboardSettings.handwritingModeDisabled)
  1660. return false
  1661. return VirtualKeyboardFeatures.Handwriting && layoutExists(locale, "handwriting")
  1662. }
  1663. function setHandwritingMode(enabled, resetInputMode) {
  1664. if (VirtualKeyboardSettings.handwritingModeDisabled)
  1665. return
  1666. if (enabled && resetInputMode)
  1667. inputModeNeedsReset = true
  1668. handwritingMode = enabled
  1669. }
  1670. function notifyLayoutChanged() {
  1671. Qt.callLater(function() {
  1672. if (keyboardLayoutLoader.item != null) keyboardObserver.layoutChanged()
  1673. })
  1674. }
  1675. function doKeyboardFunction(keyboardFunction) {
  1676. if (!isKeyboardFunctionAvailable(keyboardFunction))
  1677. return
  1678. switch (keyboardFunction) {
  1679. case QtVirtualKeyboard.KeyboardFunction.HideInputPanel:
  1680. InputContext.priv.hideInputPanel()
  1681. break
  1682. case QtVirtualKeyboard.KeyboardFunction.ChangeLanguage:
  1683. if (style.languagePopupListEnabled) {
  1684. if (!languagePopupListActive) {
  1685. showLanguagePopup(activeKey, false)
  1686. } else {
  1687. hideLanguagePopup()
  1688. }
  1689. } else {
  1690. const customLayoutsOnly = arguments.length == 2 && arguments[1]
  1691. changeInputLanguage(customLayoutsOnly)
  1692. }
  1693. break
  1694. case QtVirtualKeyboard.KeyboardFunction.ToggleHandwritingMode:
  1695. setHandwritingMode(!handwritingMode)
  1696. break
  1697. default:
  1698. console.warn("Unknown keyboard function '%1'".arg(keyboardFunction))
  1699. break
  1700. }
  1701. }
  1702. function isKeyboardFunctionAvailable(keyboardFunction) {
  1703. switch (keyboardFunction) {
  1704. case QtVirtualKeyboard.KeyboardFunction.HideInputPanel:
  1705. return true
  1706. case QtVirtualKeyboard.KeyboardFunction.ChangeLanguage:
  1707. const customLayoutsOnly = arguments.length == 2 && arguments[1]
  1708. return canChangeInputLanguage(customLayoutsOnly)
  1709. case QtVirtualKeyboard.KeyboardFunction.ToggleHandwritingMode:
  1710. return isHandwritingAvailable()
  1711. default:
  1712. return false
  1713. }
  1714. }
  1715. function isFunctionPopupListAvailable() {
  1716. const allFunctionKeys = QtVirtualKeyboard.KeyboardFunctionKeys.Hide |
  1717. QtVirtualKeyboard.KeyboardFunctionKeys.Language
  1718. return (VirtualKeyboardSettings.visibleFunctionKeys & allFunctionKeys) !== allFunctionKeys ||
  1719. isHandwritingAvailable()
  1720. }
  1721. }