=== modified file 'app/engine/CalculationHistory.qml'
--- app/engine/CalculationHistory.qml 2015-01-15 13:45:19 +0000
+++ app/engine/CalculationHistory.qml 2015-01-30 10:41:41 +0000
@@ -25,6 +25,14 @@
Component.onCompleted: {
getCalculations(function(calc) {
+ // Also if isFavourite is set as BOOL, LocalStorage saves it as
+ // int, so we need to convert it before adding the calc to
+ // the history
+ if (calc.isFavourite === 1) {
+ calc.isFavourite = true;
+ } else {
+ calc.isFavourite = false;
+ }
history.append(calc);
});
}
@@ -34,7 +42,7 @@
formula: ''
result: ''
date: 0
- isFavourite: 0
+ isFavourite: false
favouriteText: ''
}
}
@@ -104,7 +112,7 @@
);
}
- function addCalculationToScreen(formula, result) {
+ function addCalculationToScreen(formula, result, isFavourite, favouriteText) {
// The function add the last formula to the model, and leave to
// addCalculationToDatabase the job to add it to the database
// that is called only after the element has been added to the
@@ -113,27 +121,25 @@
history.append({"formula": formula,
"result": result,
"date": date,
- "isFavourite": 0,
- "favouriteText": ''});
-
+ "isFavourite": isFavourite,
+ "favouriteText": favouriteText});
var index = history.count - 1;
// TODO: move this function to a plave that retards the execution to
// improve performances
timer.execute.push(function() {
- calculationHistory.addCalculationToDatabase(formula, result, date, index);
+ calculationHistory.addCalculationToDatabase(formula, result, date, index, isFavourite, favouriteText);
});
timer.start();
}
- function addCalculationToDatabase(formula, result, date, index) {
+ function addCalculationToDatabase(formula, result, date, index, isFavourite, favouriteText) {
openDatabase();
-
calculationHistoryDatabase.transaction(
function (tx) {
var results = tx.executeSql('INSERT INTO Calculations (
formula, result, date, isFavourite, favouriteText) VALUES(
?, ?, ?, ?, ?)',
- [formula, result, date, false, '']
+ [formula, result, date, isFavourite, favouriteText]
);
// we need to update the listmodel unless we would have dbId = 0 on the
// last inserted item
@@ -160,4 +166,25 @@
});
timer.start();
}
+
+ function removeFavourites(removedFavourites) {
+ openDatabase();
+ var sql = "UPDATE Calculations SET isFavourite = 'false' WHERE dbId IN (";
+ var removed = removedFavourites[0];
+ history.setProperty(removedFavourites[0], "isFavourite", false);
+ removedFavourites.splice(0, 1);
+
+ for (var index in removedFavourites) {
+ history.setProperty(removedFavourites[index], "isFavourite", false);
+ removed += "," + removedFavourites[index];
+ }
+
+ sql += removed + ")";
+
+ calculationHistoryDatabase.transaction(
+ function (tx) {
+ var result = tx.executeSql(sql);
+ }
+ );
+ }
}
=== modified file 'app/ubuntu-calculator-app.qml'
--- app/ubuntu-calculator-app.qml 2015-01-30 09:42:00 +0000
+++ app/ubuntu-calculator-app.qml 2015-01-30 10:41:41 +0000
@@ -62,33 +62,12 @@
property var decimalPoint: Qt.locale().decimalPoint
+ // Var used to save favourite calcs
+ property bool isFavourite: false
+
// By default we delete selected calculation from history
property bool deleteSelectedCalculation: true;
- state: visualModel.isInSelectionMode ? "selection" : "default"
- states: [
- State {
- name: "default"
- StateChangeScript {
- script: header.hide()
- }
- PropertyChanges {
- target: scrollableView
- clip: false
- }
- },
- State {
- name: "selection"
- StateChangeScript {
- script: header.show()
- }
- PropertyChanges {
- target: scrollableView
- clip: true
- }
- }
- ]
-
/**
* The function calls the Formula.deleteLastFormulaElement function and
* place the result in right vars
@@ -208,281 +187,420 @@
}
- calculationHistory.addCalculationToScreen(longFormula, result);
+ if (!isFavourite) {
+ favouriteTextField.text = "";
+ }
+
+ calculationHistory.addCalculationToScreen(longFormula, result, isFavourite, favouriteTextField.text);
longFormula = result;
shortFormula = result;
+ favouriteTextField.text = "";
+ isFavourite = false;
displayedInputText = result;
}
- CalculationHistory {
- id: calculationHistory
- }
-
- Keys.onPressed: {
- keyboardLoader.item.pressedKey = event.key;
- keyboardLoader.item.pressedKeyText = event.text;
- }
-
- Keys.onReleased: {
- keyboardLoader.item.pressedKey = -1;
- keyboardLoader.item.pressedKeyText = "";
- }
-
- Header {
- id: header
- visible: true
- useDeprecatedToolbar: false
- property color dividerColor: "#babbbc"
- property color panelColor: "white"
- config: PageHeadConfiguration {
- backAction: Action {
- objectName: "cancelSelectionAction"
- iconName: "close"
- text: i18n.tr("Cancel")
- onTriggered: visualModel.cancelSelection()
+ PageStack {
+ id: mainStack
+
+ Component.onCompleted: push(calculatorPage)
+
+ PageWithBottomEdge {
+ id: calculatorPage
+
+ bottomEdgeTitle: i18n.tr("Favorite")
+
+ bottomEdgePageComponent: FavouritePage {
+ anchors.fill: parent
+
+ title: i18n.tr("Favorite")
}
- actions: [
- Action {
- id: selectAllAction
- objectName: "selectAllAction"
- iconName: "select"
- text: i18n.tr("Select All")
- onTriggered: visualModel.selectAll()
- },
- Action {
- id: copySelectedAction
- objectName: "copySelectedAction"
- iconName: "edit-copy"
- text: i18n.tr("Copy")
- onTriggered: copySelectedCalculations()
- },
- Action {
- id: multiDeleteAction
- objectName: "multiDeleteAction"
- iconName: "delete"
- text: i18n.tr("Delete")
- onTriggered: deleteSelectedCalculations()
+
+ state: visualModel.isInSelectionMode ? "selection" : "default"
+ states: [
+ State {
+ name: "default"
+ StateChangeScript {
+ script: header.hide()
+ }
+ PropertyChanges {
+ target: scrollableView
+ clip: false
+ }
+ },
+ State {
+ name: "selection"
+ StateChangeScript {
+ script: header.show()
+ }
+ PropertyChanges {
+ target: scrollableView
+ clip: true
+ }
}
]
- }
- }
-
- Component {
- id: emptyDelegate
- Item { }
- }
-
- Component {
- id: screenDelegateComponent
- Screen {
- id: screenDelegate
- width: parent ? parent.width : 0
-
- property var model: itemModel
- visible: model.dbId !== -1
-
- selectionMode: visualModel.isInSelectionMode
- selected: visualModel.isSelected(visualDelegate)
-
- property var removalAnimation
- function remove() {
- removalAnimation.start();
- }
-
- // parent is the loader component
- property var visualDelegate: parent ? parent : null
-
- onSwippingChanged: {
- visualModel.updateSwipeState(screenDelegate);
- }
-
- onSwipeStateChanged: {
- visualModel.updateSwipeState(screenDelegate);
- }
-
- onItemClicked: {
- if (visualModel.isInSelectionMode) {
- if (!visualModel.selectItem(visualDelegate)) {
- visualModel.deselectItem(visualDelegate);
- }
- }
- }
-
- onItemPressAndHold: {
- visualModel.startSelection();
- visualModel.selectItem(visualDelegate);
- }
-
- rightSideActions: [ screenDelegateCopyAction.item ]
- leftSideAction: screenDelegateDeleteAction.item
-
- Loader {
- id: screenDelegateCopyAction
- sourceComponent: Action {
- iconName: "edit-copy"
- text: i18n.tr("Copy")
- onTriggered: {
+
+ CalculationHistory {
+ id: calculationHistory
+ }
+
+ Keys.onPressed: {
+ keyboardLoader.item.pressedKey = event.key;
+ keyboardLoader.item.pressedKeyText = event.text;
+ }
+
+ Keys.onReleased: {
+ keyboardLoader.item.pressedKey = -1;
+ keyboardLoader.item.pressedKeyText = "";
+ }
+
+ Header {
+ id: header
+ visible: true
+ useDeprecatedToolbar: false
+ property color dividerColor: "#babbbc"
+ property color panelColor: "white"
+ config: PageHeadConfiguration {
+ backAction: Action {
+ objectName: "cancelSelectionAction"
+ iconName: "close"
+ text: i18n.tr("Cancel")
+ onTriggered: visualModel.cancelSelection()
+ }
+ actions: [
+ Action {
+ id: selectAllAction
+ objectName: "selectAllAction"
+ iconName: "select"
+ text: i18n.tr("Select All")
+ onTriggered: visualModel.selectAll()
+ },
+ Action {
+ id: copySelectedAction
+ objectName: "copySelectedAction"
+ iconName: "edit-copy"
+ text: i18n.tr("Copy")
+ onTriggered: copySelectedCalculations()
+ },
+ Action {
+ id: multiDeleteAction
+ objectName: "multiDeleteAction"
+ iconName: "delete"
+ text: i18n.tr("Delete")
+ onTriggered: deleteSelectedCalculations()
+ }
+ ]
+ }
+ }
+
+ Component {
+ id: emptyDelegate
+ Item { }
+ }
+
+ Component {
+ id: screenDelegateComponent
+ Screen {
+ id: screenDelegate
+ width: parent ? parent.width : 0
+
+ property var model: itemModel
+ visible: model.dbId !== -1
+
+ selectionMode: visualModel.isInSelectionMode
+ selected: visualModel.isSelected(visualDelegate)
+
+ property var removalAnimation
+ function remove() {
+ removalAnimation.start();
+ }
+
+ // parent is the loader component
+ property var visualDelegate: parent ? parent : null
+
+ onSwippingChanged: {
+ visualModel.updateSwipeState(screenDelegate);
+ }
+
+ onSwipeStateChanged: {
+ visualModel.updateSwipeState(screenDelegate);
+ }
+
+ onItemClicked: {
+ if (visualModel.isInSelectionMode) {
+ if (!visualModel.selectItem(visualDelegate)) {
+ visualModel.deselectItem(visualDelegate);
+ }
+ }
+ }
+
+ onItemPressAndHold: {
+ visualModel.startSelection();
+ visualModel.selectItem(visualDelegate);
+ }
+
+ rightSideActions: [ screenDelegateCopyAction.item ]
+ leftSideAction: screenDelegateDeleteAction.item
+
+ Loader {
+ id: screenDelegateCopyAction
+ sourceComponent: Action {
+ iconName: "edit-copy"
+ text: i18n.tr("Copy")
+ onTriggered: {
+ var mimeData = Clipboard.newData();
+ mimeData.text = model.formula + "=" + model.result;
+ Clipboard.push(mimeData);
+ }
+ }
+ }
+
+ Loader {
+ id: screenDelegateDeleteAction
+ sourceComponent: Action {
+ iconName: "delete"
+ text: i18n.tr("Delete")
+ onTriggered: {
+ screenDelegate.remove();
+ }
+ }
+ }
+
+ removalAnimation: SequentialAnimation {
+ alwaysRunToEnd: true
+
+ ScriptAction {
+ script: {
+ if (visualModel.currentSwipedItem === screenDelegate) {
+ visualModel.currentSwipedItem = null;
+ }
+ }
+ }
+
+ UbuntuNumberAnimation {
+ target: screenDelegate
+ property: "height"
+ to: 0
+ }
+
+ ScriptAction {
+ script: {
+ calculationHistory.deleteCalc(model.dbId, model.index);
+ }
+ }
+ }
+ }
+ }
+
+ function deleteSelectedCalculations() {
+ deleteSelectedCalculation = true;
+ visualModel.endSelection();
+ }
+
+ function copySelectedCalculations() {
+ deleteSelectedCalculation = false;
+ visualModel.endSelection();
+ }
+
+ MultipleSelectionVisualModel {
+ id: visualModel
+ model: calculationHistory.getContents()
+
+ onSelectionDone: {
+ if(deleteSelectedCalculation === true) {
+ for(var i = 0; i < items.count; i++) {
+ calculationHistory.deleteCalc(items.get(i).model.dbId, items.get(i).model.index);
+ }
+ } else {
var mimeData = Clipboard.newData();
- mimeData.text = model.formula + "=" + model.result;
+ mimeData.text = "";
+ for(var j = 0; j < items.count; j++) {
+ if (items.get(j).model.dbId !== -1) {
+ mimeData.text = mimeData.text + items.get(j).model.formula + "=" + items.get(j).model.result + "\n";
+ }
+ }
Clipboard.push(mimeData);
}
}
- }
-
- Loader {
- id: screenDelegateDeleteAction
- sourceComponent: Action {
- iconName: "delete"
- text: i18n.tr("Delete")
- onTriggered: {
- screenDelegate.remove();
- }
- }
- }
-
- removalAnimation: SequentialAnimation {
- alwaysRunToEnd: true
-
- ScriptAction {
- script: {
- if (visualModel.currentSwipedItem === screenDelegate) {
- visualModel.currentSwipedItem = null;
- }
- }
- }
-
- UbuntuNumberAnimation {
- target: screenDelegate
- property: "height"
- to: 0
- }
-
- ScriptAction {
- script: {
- calculationHistory.deleteCalc(model.dbId, model.index);
- }
- }
- }
- }
- }
-
- function deleteSelectedCalculations() {
- deleteSelectedCalculation = true;
- visualModel.endSelection();
- }
-
- function copySelectedCalculations() {
- deleteSelectedCalculation = false;
- visualModel.endSelection();
- }
-
- MultipleSelectionVisualModel {
- id: visualModel
- model: calculationHistory.getContents()
-
- onSelectionDone: {
- if(deleteSelectedCalculation === true) {
- for(var i = 0; i < items.count; i++) {
- calculationHistory.deleteCalc(items.get(i).model.dbId, items.get(i).model.index);
- }
- } else {
- var mimeData = Clipboard.newData();
- mimeData.text = "";
- for(var j = 0; j < items.count; j++) {
- if (items.get(j).model.dbId !== -1) {
- mimeData.text = mimeData.text + items.get(j).model.formula + "=" + items.get(j).model.result + "\n";
- }
- }
- Clipboard.push(mimeData);
- }
- }
-
- delegate: Component {
- Loader {
- property var itemModel: model
- width: parent.width
- height: model.dbId !== -1 ? item.height : 0;
- sourceComponent: screenDelegateComponent
- opacity: ((y + height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0
- onOpacityChanged: {
- if (this.hasOwnProperty('item') && this.item !== null) {
- if (opacity > 0) {
- sourceComponent = screenDelegateComponent;
- } else {
- this.item.visible = false;
- sourceComponent = emptyDelegate;
- }
- }
- }
- }
- }
- }
-
- ScrollableView {
- anchors {
- top: header.bottom
- bottom: parent.bottom
- left: parent.left
- right: parent.right
- }
- id: scrollableView
- objectName: "scrollableView"
-
- Component.onCompleted: {
- // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
- // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
- var scaleFactor = units.gridUnit / 8;
- maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
- flickDeceleration = flickDeceleration * scaleFactor;
- }
-
- Repeater {
- id: formulaView
- model: visualModel
- }
-
- TextField {
- id: textInputField
- objectName: "textInputField"
- width: contentWidth + units.gu(3)
- // TODO: Make sure this bug gets fixed in SDK:
- // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1320885
- //width: parent.width
- height: units.gu(6)
-
- // remove ubuntu shape
- style: TextFieldStyle {
- background: Item {
- }
- }
-
- text: Formula.returnFormulaToDisplay(displayedInputText)
- font.pixelSize: height * 0.7
- //horizontalAlignment: TextInput.AlignRight
- anchors {
- right: parent.right
- rightMargin: units.gu(1)
- }
-
- readOnly: true
- selectByMouse: true
- cursorVisible: true
- onCursorPositionChanged:
- if (cursorPosition !== length ) {
- // Count cursor position from the end of line
- var preservedCursorPosition = length - cursorPosition;
- displayedInputText = longFormula;
- cursorPosition = length - preservedCursorPosition;
- } else {
- displayedInputText = shortFormula;
- }
- }
-
- Loader {
- id: keyboardLoader
- width: parent.width
- source: scrollableView.width > scrollableView.height ? "ui/LandscapeKeyboard.qml" : "ui/PortraitKeyboard.qml"
- opacity: ((y+height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0
+
+ delegate: Component {
+ Loader {
+ property var itemModel: model
+ width: parent.width
+ height: model.dbId !== -1 ? item.height : 0;
+ sourceComponent: screenDelegateComponent
+ opacity: ((y + height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0
+ onOpacityChanged: {
+ if (this.hasOwnProperty('item') && this.item !== null) {
+ if (opacity > 0) {
+ sourceComponent = screenDelegateComponent;
+ } else {
+ this.item.visible = false;
+ sourceComponent = emptyDelegate;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ScrollableView {
+ anchors {
+ top: header.bottom
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+ id: scrollableView
+ objectName: "scrollableView"
+
+ Component.onCompleted: {
+ // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
+ // for Flickable.maximumFlickVelocity and Flickable.flickDeceleration
+ var scaleFactor = units.gridUnit / 8;
+ maximumFlickVelocity = maximumFlickVelocity * scaleFactor;
+ flickDeceleration = flickDeceleration * scaleFactor;
+ }
+
+ Repeater {
+ id: formulaView
+ model: visualModel
+ }
+
+ Rectangle {
+ width: parent.width
+ height: units.gu(6)
+
+ Icon {
+ id: favouriteIcon
+ height: parent.height - units.gu(2)
+ width: height
+
+ anchors {
+ left: parent.left
+ leftMargin: units.gu(1)
+ top: parent.top
+ topMargin: units.gu(1)
+ }
+
+ name: isFavourite ? "starred" : "non-starred"
+ color: isFavourite ? "#dd4814" : "#808080"
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ if (isFavourite) {
+ textInputField.visible = true;
+ textInputField.forceActiveFocus();
+ } else {
+ textInputField.visible = false;
+ favouriteTextField.forceActiveFocus();
+ }
+ isFavourite = !isFavourite;
+ }
+ }
+ }
+
+ TextField {
+ id: favouriteTextField
+
+ anchors {
+ right: confirmFavourite.left
+ rightMargin: units.gu(1)
+ }
+ width: parent.width - favouriteIcon.width - confirmFavourite.width - units.gu(3)
+ height: parent.height
+ visible: !textInputField.visible
+
+ font.italic: true
+ font.pixelSize: height * 0.5
+ verticalAlignment: TextInput.AlignVCenter
+
+ // TRANSLATORS: this is a time formatting string, see
+ // http://qt-project.org/doc/qt-5/qml-qtqml-date.html#details for
+ // valid expressions
+ placeholderText: Qt.formatDateTime(new Date(), i18n.tr("dd MMM yyyy"))
+
+ // remove ubuntu shape
+ style: TextFieldStyle {
+ background: Item {
+ }
+ }
+
+ InverseMouseArea {
+ anchors.fill: parent
+
+ onClicked: {
+ textInputField.visible = true;
+ textInputField.forceActiveFocus();
+ }
+ }
+ }
+
+ Icon {
+ id: confirmFavourite
+ visible: favouriteTextField.visible
+
+ name: "keyboard-enter"
+
+ anchors {
+ right: parent.right
+ rightMargin: units.gu(1)
+ top: parent.top
+ topMargin: units.gu(1)
+ }
+
+ height: parent.height - units.gu(2)
+ width: height
+ }
+
+ TextField {
+ id: textInputField
+ objectName: "textInputField"
+ // TODO: Make sure this bug gets fixed in SDK:
+ // https://bugs.launchpad.net/ubuntu/+source/ubuntu-ui-toolkit/+bug/1320885
+ // It has been fixed in vivid - wait until it becomes the stable
+ // version before removing this
+ width: parent.width - favouriteIcon.width - units.gu(2)
+ //width: Math.min(contentWidth + units.gu(3), parent.width - favouriteIcon.width - units.gu(2))
+ height: parent.height
+
+ // remove ubuntu shape
+ style: TextFieldStyle {
+ background: Item {
+ }
+ }
+
+ text: Formula.returnFormulaToDisplay(displayedInputText)
+ font.pixelSize: height * 0.7
+ horizontalAlignment: TextInput.AlignRight
+ anchors {
+ right: parent.right
+ rightMargin: units.gu(1)
+ }
+
+ readOnly: true
+ selectByMouse: true
+ cursorVisible: true
+ onCursorPositionChanged:
+ if (cursorPosition !== length ) {
+ // Count cursor position from the end of line
+ var preservedCursorPosition = length - cursorPosition;
+ displayedInputText = longFormula;
+ cursorPosition = length - preservedCursorPosition;
+ } else {
+ displayedInputText = shortFormula;
+ }
+ }
+ }
+
+ Loader {
+ id: keyboardLoader
+ width: parent.width
+ visible: textInputField.visible
+ source: scrollableView.width > scrollableView.height ? "ui/LandscapeKeyboard.qml" : "ui/PortraitKeyboard.qml"
+ opacity: ((y+height) >= scrollableView.contentY) && (y <= (scrollableView.contentY + scrollableView.height)) ? 1 : 0
+ }
+ }
}
}
}
=== added file 'app/ui/FavouritePage.qml'
--- app/ui/FavouritePage.qml 1970-01-01 00:00:00 +0000
+++ app/ui/FavouritePage.qml 2015-01-30 10:41:41 +0000
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 Canonical Ltd
+ *
+ * This file is part of Ubuntu Calculator App
+ *
+ * Ubuntu Calculator App is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * Ubuntu Calculator App is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+import QtQuick 2.3
+import QtQuick.Layouts 1.1
+import Ubuntu.Components 1.1
+import Ubuntu.Components.ListItems 1.0 as ListItem
+
+import "../engine"
+
+Page {
+ anchors.fill: parent
+
+ property var removedFavourites: []
+
+ head.backAction: Action {
+ iconName: "back"
+ onTriggered: {
+ if (removedFavourites.length > 0) {
+ calculationHistory.removeFavourites(removedFavourites);
+ }
+ mainStack.pop();
+ }
+ }
+
+ ListView {
+ id: favouriteListview
+ anchors.fill: parent
+ model: calculationHistory.getContents();
+
+ delegate: ListItem.Empty {
+ visible: model.isFavourite
+ height: visible ? units.gu(6) : 0
+
+ MouseArea {
+ anchors.fill: parent
+
+ onClicked: {
+ if (favouriteIcon.name == "starred") {
+ favouriteIcon.name = "non-starred";
+ removedFavourites.push(model.dbId);
+ }
+ else {
+ favouriteIcon.name = "starred";
+ removedFavourites.splice(removedFavourites.indexOf(model.dbId), 1);
+ }
+ }
+ }
+
+ RowLayout {
+ height: units.gu(5)
+ spacing: units.gu(2)
+ width: parent.width - units.gu(4)
+ anchors.horizontalCenter: parent.horizontalCenter
+ Icon {
+ id: favouriteIcon
+ height: parent.height - units.gu(2)
+ width: height
+
+ Layout.alignment: Qt.AlignVCenter
+
+ name: "starred"
+ }
+
+ Text {
+ text: model.favouriteText
+ Layout.fillWidth: true
+ }
+
+ Text {
+ text: model.result
+ font.bold: true
+ }
+ }
+ }
+ }
+}
=== modified file 'app/ui/PortraitKeyboard.qml'
--- app/ui/PortraitKeyboard.qml 2015-01-29 22:17:33 +0000
+++ app/ui/PortraitKeyboard.qml 2015-01-30 10:41:41 +0000
@@ -31,7 +31,7 @@
}
KeyboardPage {
- buttonRatio: 0.7
+ buttonRatio: 0.6
buttonMaxHeight: scrollableView.height / 10.0
keyboardModel: new Array(
=== added file 'app/upstreamcomponents/PageWithBottomEdge.qml'
--- app/upstreamcomponents/PageWithBottomEdge.qml 1970-01-01 00:00:00 +0000
+++ app/upstreamcomponents/PageWithBottomEdge.qml 2015-01-30 10:41:41 +0000
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2014 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/*
+ Example:
+
+ MainView {
+ objectName: "mainView"
+
+ applicationName: "com.ubuntu.developer.boiko.bottomedge"
+
+ width: units.gu(100)
+ height: units.gu(75)
+
+ Component {
+ id: pageComponent
+
+ PageWithBottomEdge {
+ id: mainPage
+ title: i18n.tr("Main Page")
+
+ Rectangle {
+ anchors.fill: parent
+ color: "white"
+ }
+
+ bottomEdgePageComponent: Page {
+ title: "Contents"
+ anchors.fill: parent
+ //anchors.topMargin: contentsPage.flickable.contentY
+
+ ListView {
+ anchors.fill: parent
+ model: 50
+ delegate: ListItems.Standard {
+ text: "One Content Item: " + index
+ }
+ }
+ }
+ bottomEdgeTitle: i18n.tr("Bottom edge action")
+ }
+ }
+
+ PageStack {
+ id: stack
+ Component.onCompleted: stack.push(pageComponent)
+ }
+ }
+
+*/
+
+import QtQuick 2.2
+import Ubuntu.Components 1.1
+
+Page {
+ id: page
+
+ property alias bottomEdgePageComponent: edgeLoader.sourceComponent
+ property alias bottomEdgePageSource: edgeLoader.source
+ property alias bottomEdgeTitle: tipLabel.text
+ property bool bottomEdgeEnabled: true
+ property int bottomEdgeExpandThreshold: page.height * 0.2
+ property int bottomEdgeExposedArea: bottomEdge.state !== "expanded" ? (page.height - bottomEdge.y - bottomEdge.tipHeight) : _areaWhenExpanded
+ property bool reloadBottomEdgePage: true
+
+ readonly property alias bottomEdgePage: edgeLoader.item
+ readonly property bool isReady: ((bottomEdge.y === 0) && bottomEdgePageLoaded && edgeLoader.item.active)
+ readonly property bool isCollapsed: (bottomEdge.y === page.height)
+ readonly property bool bottomEdgePageLoaded: (edgeLoader.status == Loader.Ready)
+
+ property bool _showEdgePageWhenReady: false
+ property int _areaWhenExpanded: 0
+
+ signal bottomEdgeReleased()
+ signal bottomEdgeDismissed()
+
+
+ function showBottomEdgePage(source, properties)
+ {
+ edgeLoader.setSource(source, properties)
+ _showEdgePageWhenReady = true
+ }
+
+ function setBottomEdgePage(source, properties)
+ {
+ edgeLoader.setSource(source, properties)
+ }
+
+ function _pushPage()
+ {
+ if (edgeLoader.status === Loader.Ready) {
+ edgeLoader.item.active = true
+ page.pageStack.push(edgeLoader.item)
+ if (edgeLoader.item.flickable) {
+ edgeLoader.item.flickable.contentY = -page.header.height
+ edgeLoader.item.flickable.returnToBounds()
+ }
+ if (edgeLoader.item.ready)
+ edgeLoader.item.ready()
+ }
+ }
+
+
+ Component.onCompleted: {
+ // avoid a binding on the expanded height value
+ var expandedHeight = height;
+ _areaWhenExpanded = expandedHeight;
+ }
+
+ onActiveChanged: {
+ if (active) {
+ bottomEdge.state = "collapsed"
+ }
+ }
+
+ onBottomEdgePageLoadedChanged: {
+ if (_showEdgePageWhenReady && bottomEdgePageLoaded) {
+ bottomEdge.state = "expanded"
+ _showEdgePageWhenReady = false
+ }
+ }
+
+ Rectangle {
+ id: bgVisual
+
+ color: "black"
+ anchors.fill: page
+ opacity: 0.7 * ((page.height - bottomEdge.y) / page.height)
+ z: 1
+ }
+
+ UbuntuShape {
+ id: tip
+ objectName: "bottomEdgeTip"
+
+ property bool hidden: (activeFocus === false) || ((bottomEdge.y - units.gu(1)) < tip.y)
+
+ enabled: mouseArea.enabled
+ visible: page.bottomEdgeEnabled
+ anchors {
+ bottom: parent.bottom
+ horizontalCenter: bottomEdge.horizontalCenter
+ bottomMargin: hidden ? - height + units.gu(1) : -units.gu(1)
+ Behavior on bottomMargin {
+ SequentialAnimation {
+ // wait some msecs in case of the focus change again, to avoid flickering
+ PauseAnimation {
+ duration: 300
+ }
+ UbuntuNumberAnimation {
+ duration: UbuntuAnimation.SnapDuration
+ }
+ }
+ }
+ }
+
+ z: 1
+ width: tipLabel.paintedWidth + units.gu(6)
+ height: bottomEdge.tipHeight + units.gu(1)
+ color: Theme.palette.normal.overlay
+ Label {
+ id: tipLabel
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+ height: bottomEdge.tipHeight
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ opacity: tip.hidden ? 0.0 : 1.0
+ Behavior on opacity {
+ UbuntuNumberAnimation {
+ duration: UbuntuAnimation.SnapDuration
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ id: shadow
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+ height: units.gu(1)
+ z: 1
+ opacity: 0.0
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: "transparent" }
+ GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.2) }
+ }
+ }
+
+ MouseArea {
+ id: mouseArea
+
+ property real previousY: -1
+ property string dragDirection: "None"
+
+ preventStealing: true
+ drag {
+ axis: Drag.YAxis
+ target: bottomEdge
+ minimumY: bottomEdge.pageStartY
+ maximumY: page.height
+ }
+ enabled: edgeLoader.status == Loader.Ready
+ visible: page.bottomEdgeEnabled
+
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+
+ }
+ height: bottomEdge.tipHeight
+ z: 1
+
+ onReleased: {
+ page.bottomEdgeReleased()
+ if ((dragDirection === "BottomToTop") &&
+ bottomEdge.y < (page.height - bottomEdgeExpandThreshold - bottomEdge.tipHeight)) {
+ bottomEdge.state = "expanded"
+ } else {
+ bottomEdge.state = "collapsed"
+ }
+ previousY = -1
+ dragDirection = "None"
+ }
+
+ onPressed: {
+ previousY = mouse.y
+ tip.forceActiveFocus()
+ }
+
+ onMouseYChanged: {
+ var yOffset = previousY - mouseY
+ // skip if was a small move
+ if (Math.abs(yOffset) <= units.gu(2)) {
+ return
+ }
+ previousY = mouseY
+ dragDirection = yOffset > 0 ? "BottomToTop" : "TopToBottom"
+ }
+ }
+
+ Rectangle {
+ id: bottomEdge
+ objectName: "bottomEdge"
+
+ readonly property int tipHeight: units.gu(3)
+ readonly property int pageStartY: 0
+
+ z: 1
+ color: Theme.palette.normal.background
+ clip: true
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ height: page.height
+ y: height
+ visible: !page.isCollapsed
+ state: "collapsed"
+ states: [
+ State {
+ name: "collapsed"
+ PropertyChanges {
+ target: bottomEdge
+ y: bottomEdge.height
+ }
+ },
+ State {
+ name: "expanded"
+ PropertyChanges {
+ target: bottomEdge
+ y: bottomEdge.pageStartY
+ }
+ },
+ State {
+ name: "floating"
+ when: mouseArea.drag.active
+ PropertyChanges {
+ target: shadow
+ opacity: 1.0
+ }
+ }
+ ]
+
+ transitions: [
+ Transition {
+ to: "expanded"
+ SequentialAnimation {
+ alwaysRunToEnd: true
+
+ SmoothedAnimation {
+ target: bottomEdge
+ property: "y"
+ duration: UbuntuAnimation.FastDuration
+ easing.type: Easing.Linear
+ }
+ SmoothedAnimation {
+ target: edgeLoader
+ property: "anchors.topMargin"
+ to: - units.gu(4)
+ duration: UbuntuAnimation.FastDuration
+ easing.type: Easing.Linear
+ }
+ SmoothedAnimation {
+ target: edgeLoader
+ property: "anchors.topMargin"
+ to: 0
+ duration: UbuntuAnimation.FastDuration
+ easing: UbuntuAnimation.StandardEasing
+ }
+ ScriptAction {
+ script: page._pushPage()
+ }
+ }
+ },
+ Transition {
+ from: "expanded"
+ to: "collapsed"
+ SequentialAnimation {
+ alwaysRunToEnd: true
+
+ ScriptAction {
+ script: {
+ Qt.inputMethod.hide()
+ edgeLoader.item.parent = edgeLoader
+ edgeLoader.item.anchors.fill = edgeLoader
+ edgeLoader.item.active = false
+ }
+ }
+ SmoothedAnimation {
+ target: bottomEdge
+ property: "y"
+ duration: UbuntuAnimation.SlowDuration
+ }
+ ScriptAction {
+ script: {
+ // destroy current bottom page
+ if (page.reloadBottomEdgePage) {
+ edgeLoader.active = false
+ // tip will receive focus on page active true
+ } else {
+ tip.forceActiveFocus()
+ }
+
+ // notify
+ page.bottomEdgeDismissed()
+
+ edgeLoader.active = true
+ }
+ }
+ }
+ },
+ Transition {
+ from: "floating"
+ to: "collapsed"
+ SmoothedAnimation {
+ target: bottomEdge
+ property: "y"
+ duration: UbuntuAnimation.FastDuration
+ }
+ }
+ ]
+
+ Loader {
+ id: edgeLoader
+
+ asynchronous: true
+ anchors.fill: parent
+ //WORKAROUND: The SDK move the page contents down to allocate space for the header we need to avoid that during the page dragging
+ Binding {
+ target: edgeLoader.status === Loader.Ready ? edgeLoader : null
+ property: "anchors.topMargin"
+ value: edgeLoader.item && edgeLoader.item.flickable ? edgeLoader.item.flickable.contentY : 0
+ when: !page.isReady
+ }
+
+ onLoaded: {
+ tip.forceActiveFocus()
+ if (page.isReady && edgeLoader.item.active !== true) {
+ page._pushPage()
+ }
+ }
+ }
+ }
+}