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 }