plasma-framework/declarativeimports/plasmacomponents/qml/ToolButton.qml
Sebastian Gottfried db1d3d37e9 Plasma QML components: fix internal anchoring in Button and ToolButton
There is a bug in the tool button and button components resulting in
layout breakage if one clears and sets the text property of the
component when not visible, see the attached screenshot.

I have tried to solve the issue without changing the existing
anchoring system, but without success. The only working solution
was to put the icon and the label item into Row item. That way I was
able to fix the bug and even get rid of the ugly explicit
non-declarative anchor assignments.

I have also removed the preferredWidth property of the label item,
that one always evaluated to paintedWidth, anyway.

REVIEW: 107813
2012-12-19 16:07:50 +01:00

423 lines
14 KiB
QML

/*
* Copyright (C) 2011 by Daker Fernandes Pinheiro <dakerfp@gmail.com>
* Copyright (C) 2011 by Marco Martin <mart@kde.org>
*
* 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 02110-1301, USA.
*/
/**Documented API
Inherits:
Item
Imports:
QtQuick 1.1
org.kde.plasma.core
Description:
A plasma theme based toolbutton.
Properties:
bool flat:
Returns true if the button is flat.
bool checked: false
Returns true if the button is checked.
bool checkable:
Returns true if the button is checkable.
bool pressed:
Returns true if the button is pressed.
alias text:
Sets the text for the button.
variant iconSource:
Sets the icon for the button.
It can be any image from any protocol supported by the Image element, or a freedesktop-compatible icon name
string font:
Sets the font for the button.
bool enabled:
Returns whether the button is currently enabled and receives user input.
Signals:
onClicked:
The signal is being emmited when the button is being clicked.
Plasma properties:
* real minimumWidth:
This property holds the smallest width this button can be to show all the contents
* real minimumHeight:
This property holds the smallest height this button can be to show all the contents
**/
import QtQuick 1.1
import org.kde.plasma.core 0.1 as PlasmaCore
import "private" as Private
Item {
id: button
// Commmon API
property bool flat: true
property bool checked: defaultAction ? defaultAction.checked : false
property bool checkable: defaultAction ? defaultAction.checkable : false
property alias pressed: mouse.pressed
property alias text: label.text
property alias iconSource: icon.source
property alias font: label.font
signal clicked()
// Plasma extensiuons
property QtObject defaultAction
enabled: defaultAction == undefined || defaultAction.enabled
//icon + label + left margin + right margin + spacing between icon and text
//here it assumesleft margin = right top = bottom, why?
// because the right and bottom margins can be disabled, so they would return 0, but their actual size is still needed for size hints
property real minimumWidth: theme.smallIconSize + label.paintedWidth + delegate.margins.left + delegate.margins.left + ((icon.valid) ? delegate.margins.left : 0)
property real minimumHeight: Math.max(theme.smallIconSize, label.paintedHeight) + delegate.margins.top + delegate.margins.top
implicitWidth: {
if (label.text.length == 0) {
height;
} else {
Math.max(theme.defaultFont.mSize.width*12, minimumWidth);
}
}
implicitHeight: Math.max(theme.defaultFont.mSize.height*1.6, minimumHeight)
// TODO: needs to define if there will be specific graphics for
// disabled buttons
opacity: enabled ? 1.0 : 0.5
Keys.onSpacePressed: internal.userPressed = true
Keys.onReturnPressed: internal.userPressed = true
Keys.onReleased: {
internal.userPressed = false
if (event.key == Qt.Key_Space ||
event.key == Qt.Key_Return)
internal.clickButton()
}
onActiveFocusChanged: {
if (activeFocus) {
shadow.state = "focus"
} else if (checked) {
shadow.state = "hidden"
} else {
shadow.state = "shadow"
}
}
QtObject {
id: internal
property bool userPressed: false
function clickButton()
{
if (!button.enabled) {
return
}
if (defaultAction && defaultAction.checkable) {
defaultAction.checked = !defaultAction.checked
} else if (button.checkable) {
button.checked = !button.checked
}
if (button.KeyNavigation.tab || button.KeyNavigation.backtab) {
// Only focus the button if it is set up for keyboard
// navigation. This avoid getting a strange focus frame around
// buttons which are usually not focusable, such as buttons in
// a toolbar.
button.forceActiveFocus();
}
button.clicked()
if (defaultAction) {
defaultAction.trigger()
}
}
}
Loader {
id: delegate
anchors.fill: parent
property QtObject margins: item.margins
property string shadowState: "shadow"
sourceComponent: {
if (label.text.length == 0 && button.width == button.height && button.parent.checkedButton === undefined && !flat) {
return roundButtonComponent
} else {
return buttonComponent
}
}
}
Component {
id: buttonComponent
Item {
parent: delegate
anchors.fill: parent
property alias margins: surface.margins
property alias hasOverState: shadow.hasOverState
Private.ButtonShadow {
id: shadow
anchors.fill: parent
visible: !flat && (surface.enabledBorders == "AllBorders" || state == "hover" || state == "focus")
state: delegate.shadowState
}
PlasmaCore.FrameSvgItem {
id: surface
enabledBorders: {
if (flat ||
button.parent.width < button.parent.implicitWidth ||
button.parent.checkedButton === undefined ||
!bordersSvg.hasElement("pressed-hint-compose-over-border")) {
if (shadows !== null) {
shadows.destroy()
}
return "AllBorders"
}
var borders = new Array()
if (button.x == 0) {
borders.push("LeftBorder")
}
if (button.y == 0) {
borders.push("TopBorder")
}
if (button.x + button.width >= button.parent.width) {
borders.push("RightBorder")
}
if (button.y + button.height >= button.parent.height) {
borders.push("BottomBorder")
}
if (shadows === null) {
shadows = shadowsComponent.createObject(surface)
}
return borders.join("|")
}
anchors.fill: parent
imagePath: "widgets/button"
prefix: (internal.userPressed || checked) ? "pressed" : "normal"
//internal: if there is no hover status, don't paint on mouse over in touchscreens
opacity: (internal.userPressed || checked || !flat || (shadow.hasOverState && mouse.containsMouse && button.enabled)) ? 1 : 0
Behavior on opacity {
PropertyAnimation { duration: 250 }
}
PlasmaCore.Svg {
id: bordersSvg
imagePath: "widgets/button"
}
property Item shadows
Component {
id: shadowsComponent
Item {
anchors.fill: parent
PlasmaCore.SvgItem {
svg: bordersSvg
width: naturalSize.width
elementId: surface.prefix+"-left"
visible: button.x > 0
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
margins: 1
leftMargin: -1
}
}
PlasmaCore.SvgItem {
svg: bordersSvg
width: naturalSize.width
elementId: surface.prefix+"-right"
visible: button.x + button.width < button.parent.width
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
margins: 1
rightMargin: -1
}
}
PlasmaCore.SvgItem {
svg: bordersSvg
height: naturalSize.height
elementId: surface.prefix+"-top"
visible: button.y > 0
anchors {
left: parent.left
top: parent.top
right: parent.right
margins: 1
topMargin: -1
}
}
PlasmaCore.SvgItem {
svg: bordersSvg
width: naturalSize.width
elementId: surface.prefix+"-bottom"
visible: button.y + button.height < button.parent.height
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
margins: 1
bottomMargin: -1
}
}
}
}
}
}
}
Component {
id: roundButtonComponent
Item {
id: roundButtonDelegate
parent: delegate
anchors.fill: parent
property QtObject margins: QtObject {
property int left: delegate.width/8
property int top: delegate.width/8
property int right: delegate.width/8
property int bottom: delegate.width/8
}
property alias hasOverState: roundShadow.hasOverState
Private.RoundShadow {
id: roundShadow
visible: !flat
anchors.fill: parent
state: delegate.shadowState
}
PlasmaCore.Svg {
id: buttonSvg
imagePath: "widgets/actionbutton"
}
PlasmaCore.SvgItem {
id: buttonItem
svg: buttonSvg
elementId: (internal.userPressed || checked) ? "pressed" : "normal"
width: parent.height
height: width
//internal: if there is no hover status, don't paint on mouse over in touchscreens
opacity: (internal.userPressed || checked || !flat || (roundShadow.hasOverState && mouse.containsMouse)) ? 1 : 0
Behavior on opacity {
PropertyAnimation { duration: 250 }
}
}
}
}
Row {
anchors {
fill: parent
leftMargin: delegate.margins.left
topMargin: delegate.margins.top
rightMargin: delegate.margins.right
bottomMargin: delegate.margins.bottom
}
spacing: icon.valid ? delegate.margins.left : 0
PlasmaCore.IconItem {
id: icon
anchors.verticalCenter: parent.verticalCenter
width: valid ? parent.height: 0
height: width
active: delegate.item.hasOverState && mouse.containsMouse
}
Text {
id: label
width: parent.width - icon.width - parent.spacing
height: parent.height
font.capitalization: theme.defaultFont.capitalization
font.family: theme.defaultFont.family
font.italic: theme.defaultFont.italic
font.letterSpacing: theme.defaultFont.letterSpacing
font.pointSize: theme.defaultFont.pointSize
font.strikeout: theme.defaultFont.strikeout
font.underline: theme.defaultFont.underline
font.weight: theme.defaultFont.weight
font.wordSpacing: theme.defaultFont.wordSpacing
color: mouse.containsMouse ? theme.buttonTextColor : theme.textColor
Behavior on color { ColorAnimation { duration: 100 } }
horizontalAlignment: icon.valid ? Text.AlignLeft : Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: delegate.item.hasOverState
onPressed: internal.userPressed = true
onReleased: internal.userPressed = false
onCanceled: {
internal.userPressed = false
delegate.shadowState = "shadow"
}
onClicked: internal.clickButton()
onEntered: {
if (delegate.item.hasOverState && !flat && !internal.userPressed && !checked) {
delegate.shadowState = "hover"
}
button.z += 2
}
onExited: {
if (!flat) {
if (button.activeFocus) {
delegate.shadowState = "focus"
} else if (checked) {
delegate.shadowState = "hidden"
} else {
delegate.shadowState = "shadow"
}
}
button.z -= 2
}
}
}