Skip to content

Commit

Permalink
Make it possible to do some TextField-related actions
Browse files Browse the repository at this point in the history
Introduces copy, paste and select all when a text field is selected.
Uses the __editMenu property introduced in Qt 5.6.
Integrates the text field custom menu, as shown in the demo.
  • Loading branch information
aleixpol committed Nov 3, 2015
1 parent 42da3ad commit 5efca48
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 1 deletion.
12 changes: 12 additions & 0 deletions demo/TextFieldDemo.qml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import QtQuick 2.0
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.1 as Controls
import Material 0.1

Item {
Expand Down Expand Up @@ -35,6 +36,17 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
}

TextField {
placeholderText: "Text Field with Menu"
anchors.horizontalCenter: parent.horizontalCenter
menu: Controls.Menu {
Controls.MenuItem {
text: "Print \"awesome\""
onTriggered: console.log("awesome");
}
}
}

TextField {
id: passwordField
placeholderText: "Password"
Expand Down
2 changes: 1 addition & 1 deletion modules/Material/Button.qml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Controls.Button {
The context of the button, which is used to control special styling of
buttons in dialogs or snackbars.
*/
property string context: "default" // or "dialog" or "snackbar"
property string context: "default" // "dialog", "snackbar" or "editmenu"

/*!
Set to \c true if the button is on a dark background
Expand Down
1 change: 1 addition & 0 deletions modules/QtQuick/Controls/Styles/Material/ButtonStyle.qml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ ButtonStyle {
implicitWidth: context == "dialog"
? Math.max(Units.dp(64), label.width + Units.dp(16))
: context == "snackbar" ? label.width + Units.dp(16)
: context == "editmenu" ? label.width
: Math.max(Units.dp(88), label.width + Units.dp(32))

Label {
Expand Down
188 changes: 188 additions & 0 deletions modules/QtQuick/Controls/Styles/Material/MaterialEditMenu.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright (C) 2015 by Aleix Pol Gonzalez <[email protected]>
* Copyright (C) 2015 by Marco Martin <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/

import QtQuick 2.1
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.0
import Material 0.1

Item {
id: root
anchors.fill: parent

property bool editing: true
onEditingChanged: {
updateCursorOpacity()
}
function updateCursorOpacity() {
var opacity = root.editing ? 0 : 1;
cursorHandle.opacity = opacity;
selectionHandle.opacity = opacity;
}
Component.onCompleted: updateCursorOpacity()

// https://www.google.com/design/spec/patterns/selection.html#selection-text-selection
Component {
id: editControls
Item {
id: popup
visible: input.activeFocus && !root.editing
z: 9999

Behavior on x { NumberAnimation { duration: 500; easing.type: Easing.InOutQuad } }

Component.onCompleted: {
var par = control
//heuristic: if a flickable is found in the parents,
//reparent to it, so it scrolls together
while (par.parent && par.parent.contentY === undefined) {
par = par.parent
}

popup.parent = par
}

function popup(startRect) {
// var selectedTextTopPadding = Units.dp(8);
var mapped = parent.mapFromItem(input, startRect.x, startRect.y);
var mapWidget = parent.mapFromItem(input.parent, input.x, input.y, input.width, input.height);

popup.x = Math.min(Math.max(mapWidget.x, mapped.x), mapWidget.x+mapWidget.width-bg.width);
popup.y = Math.max(0, mapped.y - bg.height);
}
function dismiss() {
popup.visible = false;
}

View {
id: bg
elevation: 1
anchors {
fill: buttons
topMargin: -Units.dp(12)
bottomMargin: -Units.dp(14)
leftMargin: -Units.dp(24)
rightMargin: -Units.dp(16)
}
}

RowLayout {
id: buttons
spacing: Units.dp(32)
Button {
text: qsTr("Cut")
visible: input.selectedText != ""
context: "editmenu"
onClicked: {
control.cut();
select(input.cursorPosition, input.cursorPosition);
}
}
Button {
text: qsTr("Copy")
visible: input.selectedText != ""
context: "editmenu"
onClicked: {
control.copy();
select(input.cursorPosition, input.cursorPosition);
}
}
Button {
text: qsTr("Paste")
visible: input.canPaste
context: "editmenu"
onClicked: {
control.paste();
}
}
Button {
text: qsTr("Select All")
visible: input.text != ""
context: "editmenu"
onClicked: {
control.selectAll();
}
}

IconButton {
iconName: "navigation/more_vert"
visible: control.menu !== null
onClicked: {
getMenuInstance().popup()
}
}
}
}
}

Connections {
target: mouseArea

onClicked: {
var pos = input.positionAt(mouse.x, mouse.y)
input.moveHandles(pos, pos)
input.activate()
}
onPressAndHold: {
root.editing = false;
var pos = input.positionAt(mouse.x, mouse.y)
input.moveHandles(pos, control.selectByMouse ? -1 : pos)
input.activate()
}
}

Connections {
target: input
onSelectionStartChanged: popupTimer.restart()
onSelectionEndChanged: popupTimer.restart()
onActiveFocusChanged: {
popupTimer.restart()
root.editing = true;
}
onTextChanged: root.editing = true;
}

Connections {
target: flickable
onMovingChanged: popupTimer.restart()
}

property Item editControlsInstance: null
function getEditControlsInstance() {
// Lazy load the view when first requested
if (!editControlsInstance) {
editControlsInstance = editControls.createObject(control);
}
return editControlsInstance;
}

Timer {
id: popupTimer
interval: 200
onTriggered: {
if (input.canPaste || selectionStart !== selectionEnd) {
var startRect = input.positionToRectangle(input.selectionStart);

getEditControlsInstance().popup(startRect);
}
}
}
}
16 changes: 16 additions & 0 deletions modules/QtQuick/Controls/Styles/Material/TextFieldStyle.qml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ TextFieldStyle {
selectionColor: control.hasOwnProperty("color") ? control.color : Theme.accentColor
textColor: Theme.light.textColor

//make sure to QT_QUICK_CONTROLS_MOBILE=ON to properly test this
property Component __editMenu: MaterialEditMenu {
id: menu
}

property Component __cursorHandle: TextHandle {
side: control.selectionEnd - control.selectionStart
color: Palette.colors["blue"]["400"]
}

property Component __selectionHandle: TextHandle {
side: control.selectionStart - control.selectionEnd
color: Palette.colors["blue"]["400"]
}


background : Item {
id: background

Expand Down
64 changes: 64 additions & 0 deletions modules/QtQuick/Controls/Styles/Material/TextHandle.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2015 by Aleix Pol Gonzalez <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Library General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
*/

import QtQuick 2.1
import Material 0.1

Rectangle {
id: root
property int side: 0 //-1 left, 0 center, 1 right

width: Units.dp(22)
height: Units.dp(22)

radius: width
y: styleData.lineHeight

Rectangle {
id: rect
width: parent.width/2
height: parent.height/2
color: parent.color
}

states: [
State {
when: side<0
name: "right"
PropertyChanges { target: root; x: -width}
AnchorChanges { target: rect; anchors.right: parent.right }
},
State {
when: side>0
name: "left"
AnchorChanges { target: rect; anchors.left: parent.left }
},
State {
when: side==0
name: "center"
PropertyChanges { target: rect; rotation: 45 }
PropertyChanges { target: rect; width: root.width/Math.SQRT2 }
PropertyChanges { target: rect; height: rect.width }
PropertyChanges { target: rect; x: (root.width-rect.width)/2 }

PropertyChanges { target: root; y: styleData.lineHeight/*+root.height/2*/ }
PropertyChanges { target: root; x: -root.width/2 }
}
]
}

0 comments on commit 5efca48

Please sign in to comment.