Warning, /utilities/kalk/src/qml/CalculationPage.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 * SPDX-FileCopyrightText: 2020-2021 Han Young <hanyoung@protonmail.com> 0003 * SPDX-FileCopyrightText: 2020-2022 Devin Lin <espidev@gmail.com> 0004 * 0005 * SPDX-License-Identifier: GPL-3.0-or-later 0006 */ 0007 import QtQuick 0008 import QtQuick.Layouts 0009 import QtQuick.Controls as Controls 0010 import Qt5Compat.GraphicalEffects 0011 0012 import org.kde.kirigami as Kirigami 0013 0014 Kirigami.Page { 0015 id: initialPage 0016 0017 title: i18n("Calculator") 0018 0019 topPadding: 0 0020 leftPadding: 0 0021 rightPadding: 0 0022 bottomPadding: 0 0023 0024 actions: [ 0025 Kirigami.Action { 0026 text: i18n("History") 0027 icon.name: "shallow-history" 0028 shortcut: "Ctrl+H" 0029 onTriggered: { 0030 if (applicationWindow().pageStack.depth > 1) { 0031 applicationWindow().pageStack.pop(); 0032 } 0033 else { 0034 applicationWindow().pageStack.push("qrc:/qml/HistoryView.qml", { historyIndex: inputManager.historyIndex }); 0035 }; 0036 outputScreen.forceActiveFocus(); 0037 } 0038 }, 0039 Kirigami.Action { 0040 icon.name: "edit-undo-symbolic" 0041 text: i18n("Undo") 0042 shortcut: "Ctrl+Z" 0043 enabled: inputManager.canUndo 0044 onTriggered: { 0045 inputManager.undo(); 0046 outputScreen.forceActiveFocus(); 0047 } 0048 }, 0049 Kirigami.Action { 0050 icon.name: "edit-redo-symbolic" 0051 text: i18n("Redo") 0052 shortcut: "Ctrl+Shift+Z" 0053 enabled: inputManager.canRedo 0054 onTriggered: { 0055 inputManager.redo(); 0056 outputScreen.forceActiveFocus(); 0057 } 0058 }, 0059 Kirigami.Action { 0060 icon.name: "edit-cut" 0061 text: i18n("Cut") 0062 shortcut: "ctrl+X" 0063 enabled: expressionRow.selectedText 0064 onTriggered: { 0065 cut(); 0066 outputScreen.forceActiveFocus(); 0067 } 0068 }, 0069 Kirigami.Action { 0070 icon.name: "edit-copy" 0071 text: i18n("Copy") 0072 shortcut: "ctrl+C" 0073 enabled: expressionRow.text || result.text 0074 onTriggered: { 0075 copy(); 0076 outputScreen.forceActiveFocus(); 0077 } 0078 }, 0079 Kirigami.Action { 0080 icon.name: "edit-paste" 0081 text: i18n("Paste") 0082 shortcut: "ctrl+V" 0083 onTriggered: { 0084 paste(); 0085 outputScreen.forceActiveFocus(); 0086 } 0087 } 0088 ] 0089 0090 property int yTranslate: 0 0091 property real mainOpacity: 1 0092 0093 property color dropShadowColor: Qt.darker(Kirigami.Theme.backgroundColor, 1.15) 0094 readonly property bool inPortrait: initialPage.width < initialPage.height 0095 property int keypadHeight: initialPage.height * 0.8 0096 property int screenHeight: initialPage.height - initialPage.keypadHeight 0097 0098 function cut() { 0099 if (expressionRow.selectedText) { 0100 const pos = expressionRow.cursorPosition - expressionRow.selectedText.length; 0101 expressionRow.cut(); 0102 expressionRow.cursorPosition = pos; 0103 } 0104 } 0105 function copy() { 0106 if (expressionRow.selectedText) { 0107 expressionRow.copy(); 0108 } else if (result.selectedText) { 0109 result.copy(); 0110 } else { 0111 result.selectAll(); 0112 result.copy(); 0113 result.deselect(); 0114 } 0115 } 0116 function paste() { 0117 expressionRow.paste(); 0118 } 0119 0120 Keys.onPressed: event => { 0121 if (event.matches(StandardKey.Undo)) { 0122 inputManager.undo(); 0123 event.accepted = true; 0124 return; 0125 } else if (event.matches(StandardKey.Redo)) { 0126 inputManager.redo(); 0127 event.accepted = true; 0128 return; 0129 } else if (event.matches(StandardKey.Cut)) { 0130 cut(); 0131 event.accepted = true; 0132 return; 0133 } else if (event.matches(StandardKey.Copy)) { 0134 copy(); 0135 event.accepted = true; 0136 return; 0137 } else if (event.matches(StandardKey.Paste)) { 0138 paste(); 0139 event.accepted = true; 0140 return; 0141 } else if (event.matches(StandardKey.SelectAll)) { 0142 if (expressionRow.focus) { 0143 expressionRow.focus = false; 0144 expressionRow.selectAll(); 0145 } else { 0146 result.selectAll(); 0147 } 0148 event.accepted = true; 0149 return; 0150 } else if (event.modifiers && !event.text) { 0151 event.accepted = true; 0152 return; 0153 } 0154 0155 switch(event.key) { 0156 case Qt.Key_Delete: 0157 if (expressionRow.cursorPosition < expressionRow.length) { 0158 expressionRow.cursorPosition = inputManager.idealCursorPosition(expressionRow.cursorPosition + 1, 1); 0159 inputManager.backspace(); 0160 } 0161 break; 0162 case Qt.Key_Backspace: 0163 inputManager.backspace(); break; 0164 case Qt.Key_0: 0165 inputManager.append("0"); break; 0166 case Qt.Key_1: 0167 inputManager.append("1"); break; 0168 case Qt.Key_2: 0169 inputManager.append("2"); break; 0170 case Qt.Key_3: 0171 inputManager.append("3"); break; 0172 case Qt.Key_4: 0173 inputManager.append("4"); break; 0174 case Qt.Key_5: 0175 inputManager.append("5"); break; 0176 case Qt.Key_6: 0177 inputManager.append("6"); break; 0178 case Qt.Key_7: 0179 inputManager.append("7"); break; 0180 case Qt.Key_8: 0181 inputManager.append("8"); break; 0182 case Qt.Key_9: 0183 inputManager.append("9"); break; 0184 case Qt.Key_Plus: 0185 inputManager.append("+"); break; 0186 case Qt.Key_Minus: 0187 inputManager.append("-"); break; 0188 case Qt.Key_Asterisk: 0189 inputManager.append("×"); break; 0190 case Qt.Key_Slash: 0191 inputManager.append("÷"); break; 0192 case Qt.Key_AsciiCircum: 0193 inputManager.append("^"); break; 0194 case Qt.Key_Period: 0195 inputManager.append("."); break; 0196 case Qt.Key_ParenLeft: 0197 inputManager.append("("); break; 0198 case Qt.Key_ParenRight: 0199 inputManager.append(")"); break; 0200 case Qt.Key_Comma: 0201 inputManager.append(","); break; 0202 case Qt.Key_Equal: 0203 case Qt.Key_Return: 0204 case Qt.Key_Enter: 0205 inputManager.equal(); 0206 expressionRow.focus = false; 0207 break; 0208 case Qt.Key_Left: 0209 expressionRow.focus = true; 0210 expressionRow.cursorPosition = inputManager.idealCursorPosition(expressionRow.cursorPosition - 1, -1); 0211 break; 0212 case Qt.Key_Right: 0213 expressionRow.focus = true; 0214 expressionRow.cursorPosition = inputManager.idealCursorPosition(expressionRow.cursorPosition + 1, 1); 0215 break; 0216 case Qt.Key_Underscore: break; 0217 default: 0218 inputManager.append(event.text); 0219 } 0220 event.accepted = true; 0221 } 0222 0223 // Changes the current mode of the backend to non-binary 0224 onIsCurrentPageChanged: { 0225 if (inputManager.binaryMode) 0226 inputManager.binaryMode = false 0227 } 0228 0229 background: Rectangle { 0230 opacity: mainOpacity 0231 0232 Kirigami.Theme.colorSet: Kirigami.Theme.Window 0233 Kirigami.Theme.inherit: false 0234 color: Kirigami.Theme.backgroundColor 0235 anchors.fill: parent 0236 } 0237 0238 Item { 0239 anchors.fill: parent 0240 opacity: mainOpacity 0241 transform: Translate { y: yTranslate } 0242 0243 // top panel drop shadow 0244 RectangularGlow { 0245 opacity: mainOpacity 0246 0247 anchors.fill: topPanelBackground 0248 anchors.topMargin: 1 0249 glowRadius: 4 0250 spread: 0.2 0251 color: dropShadowColor 0252 } 0253 0254 Rectangle { 0255 id: topPanelBackground 0256 opacity: mainOpacity 0257 0258 anchors.top: parent.top 0259 anchors.left: parent.left 0260 anchors.right: parent.right 0261 color: Kirigami.Theme.backgroundColor 0262 implicitHeight: outputScreen.height 0263 } 0264 0265 ColumnLayout { 0266 opacity: mainOpacity 0267 transform: Translate { y: yTranslate } 0268 anchors.fill: parent 0269 spacing: 0 0270 0271 Item { 0272 id: outputScreen 0273 z: 1 0274 Layout.fillWidth: true 0275 Layout.alignment: Qt.AlignTop 0276 Layout.preferredHeight: initialPage.screenHeight 0277 0278 property int flexPointSize: Math.max(Math.min(height / 4, width / 16), Kirigami.Theme.defaultFont.pointSize) 0279 0280 TextEdit { 0281 id: textEdit 0282 visible: false 0283 } 0284 0285 // input row 0286 Flickable { 0287 anchors.top: parent.top 0288 anchors.right: parent.right 0289 anchors.leftMargin: Kirigami.Units.largeSpacing * 2 0290 anchors.rightMargin: Kirigami.Units.largeSpacing * 2 0291 0292 height: contentHeight 0293 width: Math.min(parent.width - Kirigami.Units.largeSpacing * 2, contentWidth) 0294 0295 contentHeight: expressionRow.height 0296 contentWidth: expressionRow.width 0297 flickableDirection: Flickable.HorizontalFlick 0298 0299 Controls.TextArea { 0300 id: expressionRow 0301 activeFocusOnPress: false 0302 font.pointSize: outputScreen.flexPointSize * (text.length * outputScreen.flexPointSize * 0.7 > outputScreen.width ? 0.7 : 1) 0303 font.weight: Font.Light 0304 text: inputManager.expression 0305 color: Kirigami.Theme.disabledTextColor 0306 background: Rectangle { color: "transparent" } 0307 padding: 0 0308 selectByMouse: false 0309 textFormat: Text.PlainText 0310 0311 property string lastText 0312 onTextChanged: { 0313 if (text !== inputManager.expression) { 0314 const value = text; 0315 inputManager.clear(false); 0316 inputManager.append(value); 0317 } else { 0318 cursorPosition = inputManager.cursorPosition; 0319 } 0320 } 0321 onCursorPositionChanged: { 0322 if (lastText === text && selectedText === "") { 0323 const pos = inputManager.idealCursorPosition(cursorPosition); // this only calculate the postion, doesn't modify inputManager in anyway 0324 cursorPosition = pos; 0325 inputManager.cursorPosition = pos; 0326 } else { 0327 lastText = text; 0328 } 0329 } 0330 onPressed: { 0331 focus = true; 0332 result.focus = false; 0333 result.deselect(); 0334 textEdit.deselect(); 0335 } 0336 onEditingFinished: { 0337 if (textEdit.selectedText) { 0338 select(textEdit.selectionStart, textEdit.selectionEnd); 0339 } 0340 } 0341 onPressAndHold: { 0342 // use textEdit as a proxy to select 0343 // replace separators with letters so are treated a single words 0344 // replace symbols with spaces 0345 textEdit.text = inputManager.expression.replace(/[,\.\(\)]/g, "n").replace(/[\+\-×÷%\!\^]/g, " "); 0346 textEdit.cursorPosition = cursorPosition; 0347 textEdit.selectWord(); 0348 select(textEdit.selectionStart, textEdit.selectionEnd); 0349 } 0350 0351 Keys.onPressed: event => { 0352 event.accepted = false; 0353 initialPage.Keys.pressed(event); 0354 } 0355 } 0356 onContentWidthChanged: { 0357 if (contentWidth > width) { 0358 // keep flickable at start if coming from result 0359 if (inputManager.moveFromResult) { 0360 contentX = 0; 0361 } else if (inputManager.cursorPosition < expressionRow.positionAt(contentX + width, height / 2)) { 0362 // do nothing 0363 } else { 0364 contentX = contentWidth - width; 0365 } 0366 } 0367 } 0368 } 0369 0370 // answer row 0371 Flickable { 0372 anchors.bottom: parent.bottom 0373 anchors.right: parent.right 0374 anchors.leftMargin: Kirigami.Units.largeSpacing * 2 0375 anchors.rightMargin: Kirigami.Units.largeSpacing * 2 0376 0377 height: contentHeight 0378 width: Math.min(parent.width - Kirigami.Units.largeSpacing * 2, contentWidth) 0379 0380 contentHeight: result.height 0381 contentWidth: result.width 0382 flickableDirection: Flickable.HorizontalFlick 0383 0384 Controls.TextArea { 0385 id: result 0386 activeFocusOnPress: false 0387 font.pointSize: Math.round(outputScreen.flexPointSize) * (text.length * outputScreen.flexPointSize > outputScreen.width ? 0.9 : 1.4) 0388 font.weight: Font.Light 0389 text: inputManager.result 0390 background: Rectangle { color: "transparent" } 0391 padding: 0 0392 selectByMouse: false 0393 textFormat: Text.PlainText 0394 readOnly: true 0395 0396 onTextChanged: resultFadeInAnimation.start() 0397 onPressed: { 0398 focus = false; 0399 expressionRow.focus = false; 0400 expressionRow.deselect(); 0401 } 0402 onPressAndHold: selectAll() 0403 0404 NumberAnimation on opacity { 0405 id: resultFadeInAnimation 0406 from: 0.5 0407 to: 1 0408 duration: Kirigami.Units.shortDuration 0409 } 0410 NumberAnimation on opacity { 0411 id: resultFadeOutAnimation 0412 from: 1 0413 to: 0 0414 duration: Kirigami.Units.shortDuration 0415 } 0416 } 0417 } 0418 0419 TapHandler { 0420 onTapped: { 0421 expressionRow.focus = false; 0422 result.focus = false; 0423 result.deselect(); 0424 expressionRow.deselect(); 0425 } 0426 } 0427 } 0428 0429 // keypad area 0430 RowLayout { 0431 Layout.fillHeight: true 0432 Layout.fillWidth: true 0433 spacing: 0 0434 onWidthChanged: view.currentIndex = 0 0435 0436 Item { 0437 id: inputPad 0438 Layout.fillHeight: true 0439 Layout.preferredWidth: inPortrait ? initialPage.width : initialPage.width >= initialPage.height * 1.6 ? initialPage.width * 0.5 : initialPage.width * 0.6 0440 Layout.alignment: Qt.AlignLeft 0441 0442 Controls.PageIndicator { 0443 id: indicator 0444 visible: view.interactive 0445 anchors.bottom: view.top 0446 anchors.horizontalCenter: parent.horizontalCenter 0447 count: view.count 0448 currentIndex: view.currentIndex 0449 0450 TapHandler { 0451 onTapped: view.currentIndex = !view.currentIndex 0452 } 0453 } 0454 0455 Controls.SwipeView { 0456 id: view 0457 anchors.fill: parent 0458 anchors.margins: Kirigami.Units.smallSpacing 0459 anchors.topMargin: inPortrait ? indicator.height + Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing 0460 currentIndex: 1 0461 interactive: inPortrait 0462 spacing: Kirigami.Units.smallSpacing 0463 0464 NumberPad { 0465 id: numberPad 0466 inPortrait: initialPage.inPortrait 0467 onPressed: text => { 0468 if (text == "DEL") { 0469 inputManager.backspace(); 0470 } else if (text == "=") { 0471 inputManager.equal(); 0472 expressionRow.focus = false; 0473 } else { 0474 inputManager.append(text); 0475 } 0476 } 0477 onClear: { 0478 inputManager.clear(); 0479 resultFadeOutAnimation.start(); 0480 } 0481 } 0482 0483 FunctionPad { 0484 onPressed: text => inputManager.append(text) 0485 } 0486 } 0487 } 0488 0489 Item { 0490 Layout.alignment: Qt.AlignRight 0491 Layout.fillHeight: true 0492 Layout.fillWidth: true 0493 visible: !inPortrait 0494 FunctionPad { 0495 anchors.fill: parent 0496 anchors.bottom: parent.Bottom 0497 anchors.leftMargin: Kirigami.Units.smallSpacing 0498 anchors.rightMargin: Kirigami.Units.smallSpacing 0499 anchors.topMargin: Kirigami.Units.largeSpacing 0500 anchors.bottomMargin: Kirigami.Units.smallSpacing 0501 onPressed: text => inputManager.append(text) 0502 } 0503 } 0504 } 0505 } 0506 } 0507 }