Changeset View
Changeset View
Standalone View
Standalone View
applet/contents/ui/ListItemBase.qml
Show All 30 Lines | |||||
31 | import org.kde.draganddrop 2.0 as DragAndDrop | 31 | import org.kde.draganddrop 2.0 as DragAndDrop | ||
32 | import org.kde.plasma.private.volume 0.1 | 32 | import org.kde.plasma.private.volume 0.1 | ||
33 | 33 | | |||
34 | import "../code/icon.js" as Icon | 34 | import "../code/icon.js" as Icon | ||
35 | 35 | | |||
36 | PlasmaComponents.ListItem { | 36 | PlasmaComponents.ListItem { | ||
37 | id: item | 37 | id: item | ||
38 | 38 | | |||
39 | property alias label: textLabel.text | 39 | property alias label: defaultButton.text | ||
40 | property alias labelOpacity: textLabel.opacity | | |||
41 | property alias draggable: dragArea.enabled | 40 | property alias draggable: dragArea.enabled | ||
42 | property alias icon: clientIcon.source | 41 | property alias icon: clientIcon.source | ||
43 | property alias iconUsesPlasmaTheme: clientIcon.usesPlasmaTheme | 42 | property alias iconUsesPlasmaTheme: clientIcon.usesPlasmaTheme | ||
44 | property string type | 43 | property string type | ||
45 | 44 | | |||
46 | checked: dropArea.containsDrag | 45 | checked: dropArea.containsDrag | ||
47 | opacity: (draggedStream && draggedStream.deviceIndex == Index) ? 0.3 : 1.0 | 46 | opacity: (draggedStream && draggedStream.deviceIndex == Index) ? 0.3 : 1.0 | ||
47 | separatorVisible: false | ||||
48 | 48 | | |||
49 | ListView.delayRemove: dragArea.dragActive | 49 | ListView.delayRemove: dragArea.dragActive | ||
50 | 50 | | |||
51 | Item { | 51 | Item { | ||
52 | width: parent.width | 52 | width: parent.width | ||
53 | height: rowLayout.height | 53 | height: column.height | ||
54 | 54 | | |||
55 | RowLayout { | 55 | RowLayout { | ||
56 | id: rowLayout | 56 | id: rowLayout | ||
57 | anchors.left: parent.left | 57 | anchors.left: parent.left | ||
58 | anchors.right: parent.right | 58 | anchors.right: parent.right | ||
59 | anchors.rightMargin: LayoutMirroring.enabled ? 0 : units.smallSpacing | | |||
60 | anchors.leftMargin: LayoutMirroring.enabled ? units.smallSpacing : 0 | | |||
61 | spacing: units.smallSpacing | | |||
62 | 59 | | |||
63 | PlasmaCore.IconItem { | 60 | PlasmaCore.IconItem { | ||
64 | id: clientIcon | 61 | id: clientIcon | ||
65 | Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter | 62 | Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter | ||
66 | Layout.preferredHeight: column.height * 0.75 | 63 | Layout.preferredHeight: column.height * 0.75 | ||
67 | Layout.preferredWidth: Layout.preferredHeight | 64 | Layout.preferredWidth: Layout.preferredHeight | ||
68 | source: "unknown" | 65 | source: "unknown" | ||
66 | visible: type === "sink-input" || type === "source-input" | ||||
69 | 67 | | |||
70 | onSourceChanged: { | 68 | onSourceChanged: { | ||
71 | if (!valid && source != "unknown") { | 69 | if (!valid && source != "unknown") { | ||
72 | source = "unknown"; | 70 | source = "unknown"; | ||
73 | } | 71 | } | ||
74 | } | 72 | } | ||
75 | 73 | | |||
76 | DragAndDrop.DragArea { | 74 | DragAndDrop.DragArea { | ||
Show All 25 Lines | 97 | onClicked: { | |||
102 | } | 100 | } | ||
103 | } | 101 | } | ||
104 | } | 102 | } | ||
105 | } | 103 | } | ||
106 | } | 104 | } | ||
107 | 105 | | |||
108 | ColumnLayout { | 106 | ColumnLayout { | ||
109 | id: column | 107 | id: column | ||
110 | spacing: 1 | 108 | spacing: 0 | ||
111 | 109 | | |||
112 | RowLayout { | 110 | RowLayout { | ||
113 | Layout.fillWidth: true | 111 | Layout.minimumHeight: contextMenuButton.height | ||
davidedmundson: That's asking for a binding loop.
An implicit size should never be based on a current size. | |||||
114 | 112 | | |||
115 | PlasmaExtras.Heading { | 113 | PlasmaComponents3.RadioButton { | ||
Give the radio button a text: property and remove the heading item; that way the text will be clickable ngraham: Give the radio button a `text:` property and remove the heading item; that way the text will be… | |||||
116 | id: textLabel | 114 | id: defaultButton | ||
117 | Layout.fillWidth: true | 115 | Layout.leftMargin: LayoutMirroring.enabled ? 0 : Math.round((muteButton.width - defaultButton.indicator.width) / 2) | ||
Margins should but don't get mirrored when using a RTL layout. I haven't tested anything, but I think you need to add a rightMargin when layout mirroring is enabled. filipf: Margins should but don't get mirrored when using a RTL layout. I haven't tested anything, but I… | |||||
Don't multiply by 0.75; this could result in a fractional value and the spacing doesn't doesn't need to be literally pixel-identical with the mockup ngraham: Don't multiply by 0.75; this could result in a fractional value and the spacing doesn't doesn't… | |||||
118 | height: undefined | 116 | Layout.rightMargin: LayoutMirroring.enabled ? Math.round((muteButton.width - defaultButton.indicator.width) / 2) : 0 | ||
Don't override this here. If it looks bad, let's fix it in the RadioButton control itself ngraham: Don't override this here. If it looks bad, let's fix it in the RadioButton control itself | |||||
119 | level: 5 | 117 | spacing: units.smallSpacing + Math.round((muteButton.width - defaultButton.indicator.width) / 2) | ||
120 | opacity: 0.6 | 118 | checked: PulseObject.default ? PulseObject.default : false | ||
121 | wrapMode: Text.NoWrap | 119 | visible: (type == "sink" && sinkView.model.count > 1) || (type == "source" && sourceView.model.count > 1) | ||
122 | elide: Text.ElideRight | 120 | onClicked: PulseObject.default = true; | ||
123 | visible: !portbox.visible | | |||
124 | } | 121 | } | ||
125 | 122 | | |||
126 | PlasmaComponents3.ComboBox { | 123 | Label { | ||
127 | id: portbox | 124 | id: soloLabel | ||
128 | visible: portbox.count > 1 | 125 | text: defaultButton.text | ||
129 | Layout.minimumWidth: units.gridUnit * 10 | 126 | visible: !defaultButton.visible | ||
130 | model: { | 127 | elide: Text.ElideRight | ||
ngraham: This is the default wrap value; no need to explicitly set it | |||||
131 | var items = []; | | |||
132 | for (var i = 0; i < PulseObject.ports.length; ++i) { | | |||
133 | var port = PulseObject.ports[i]; | | |||
134 | if (port.availability != Port.Unavailable) { | | |||
135 | items.push(port.description); | | |||
136 | } | | |||
137 | } | | |||
138 | return items | | |||
139 | } | | |||
140 | currentIndex: ActivePortIndex | | |||
141 | onActivated: ActivePortIndex = index | | |||
142 | } | 128 | } | ||
143 | 129 | | |||
144 | Item { | 130 | Item { | ||
145 | visible: portbox.visible | | |||
146 | Layout.fillWidth: true | 131 | Layout.fillWidth: true | ||
147 | } | 132 | } | ||
148 | 133 | | |||
149 | PlasmaComponents3.ToolButton { | | |||
150 | id: defaultButton | | |||
151 | text: i18n("Default Device") | | |||
152 | icon.name: PulseObject.default ? "starred-symbolic" : "non-starred-symbolic" | | |||
153 | checkable: true | | |||
154 | checked: PulseObject.default | | |||
155 | visible: (type == "sink" && sinkView.model.count > 1) || (type == "source" && sourceView.model.count > 1) | | |||
156 | onClicked: PulseObject.default = true; | | |||
157 | } | | |||
158 | | ||||
159 | SmallToolButton { | 134 | SmallToolButton { | ||
160 | id: contextMenuButton | 135 | id: contextMenuButton | ||
161 | icon: "application-menu" | 136 | icon: "application-menu" | ||
162 | checkable: true | 137 | checkable: true | ||
163 | onClicked: contextMenu.show() | 138 | onClicked: contextMenu.show() | ||
164 | tooltip: i18n("Show additional options for %1", textLabel.text) | 139 | tooltip: i18n("Show additional options for %1", defaultButton.text) | ||
165 | } | 140 | } | ||
166 | } | 141 | } | ||
167 | 142 | | |||
168 | RowLayout { | 143 | RowLayout { | ||
169 | SmallToolButton { | 144 | SmallToolButton { | ||
145 | id: muteButton | ||||
170 | readonly property bool isPlayback: type.substring(0, 4) == "sink" | 146 | readonly property bool isPlayback: type.substring(0, 4) == "sink" | ||
171 | icon: Icon.name(Volume, Muted, isPlayback ? "audio-volume" : "microphone-sensitivity") | 147 | icon: Icon.name(Volume, Muted, isPlayback ? "audio-volume" : "microphone-sensitivity") | ||
172 | onClicked: Muted = !Muted | 148 | onClicked: Muted = !Muted | ||
173 | checked: Muted | 149 | checked: Muted | ||
174 | tooltip: i18n("Mute %1", textLabel.text) | 150 | tooltip: i18n("Mute %1", defaultButton.text) | ||
175 | | ||||
176 | } | 151 | } | ||
177 | 152 | | |||
178 | PlasmaComponents.Slider { | 153 | PlasmaComponents.Slider { | ||
179 | id: slider | 154 | id: slider | ||
180 | 155 | | |||
181 | // Helper properties to allow async slider updates. | 156 | // Helper properties to allow async slider updates. | ||
182 | // While we are sliding we must not react to value updates | 157 | // While we are sliding we must not react to value updates | ||
183 | // as otherwise we can easily end up in a loop where value | 158 | // as otherwise we can easily end up in a loop where value | ||
184 | // changes trigger volume changes trigger value changes. | 159 | // changes trigger volume changes trigger value changes. | ||
185 | property int volume: Volume | 160 | property int volume: Volume | ||
186 | property bool ignoreValueChange: true | 161 | property bool ignoreValueChange: true | ||
187 | property bool forceRaiseMaxVolume: false | 162 | property bool forceRaiseMaxVolume: false | ||
188 | readonly property bool raiseMaxVolume: forceRaiseMaxVolume || volume >= PulseAudio.NormalVolume * 1.01 | 163 | readonly property bool raiseMaxVolume: forceRaiseMaxVolume || volume >= PulseAudio.NormalVolume * 1.01 | ||
189 | 164 | | |||
190 | Layout.fillWidth: true | 165 | Layout.fillWidth: true | ||
191 | minimumValue: PulseAudio.MinimalVolume | 166 | minimumValue: PulseAudio.MinimalVolume | ||
192 | maximumValue: raiseMaxVolume ? PulseAudio.MaximalVolume : PulseAudio.NormalVolume | 167 | maximumValue: raiseMaxVolume ? PulseAudio.MaximalVolume : PulseAudio.NormalVolume | ||
193 | stepSize: maximumValue / (maximumValue / PulseAudio.NormalVolume * 100.0) | 168 | stepSize: maximumValue / (maximumValue / PulseAudio.NormalVolume * 100.0) | ||
194 | visible: HasVolume | 169 | visible: HasVolume | ||
195 | enabled: VolumeWritable | 170 | enabled: VolumeWritable | ||
196 | opacity: Muted ? 0.5 : 1 | 171 | opacity: Muted ? 0.5 : 1 | ||
197 | 172 | | |||
198 | Accessible.name: i18nc("Accessibility data on volume slider", "Adjust volume for %1", textLabel.text) | 173 | Accessible.name: i18nc("Accessibility data on volume slider", "Adjust volume for %1", defaultButton.text) | ||
199 | 174 | | |||
200 | Component.onCompleted: { | 175 | Component.onCompleted: { | ||
201 | ignoreValueChange = false; | 176 | ignoreValueChange = false; | ||
202 | } | 177 | } | ||
203 | 178 | | |||
204 | onVolumeChanged: { | 179 | onVolumeChanged: { | ||
205 | var oldIgnoreValueChange = ignoreValueChange; | 180 | var oldIgnoreValueChange = ignoreValueChange; | ||
206 | ignoreValueChange = true; | 181 | ignoreValueChange = true; | ||
▲ Show 20 Lines • Show All 108 Lines • ▼ Show 20 Line(s) | 289 | menuItem.clicked.connect(function() { | |||
315 | slider.forceRaiseMaxVolume = !slider.forceRaiseMaxVolume; | 290 | slider.forceRaiseMaxVolume = !slider.forceRaiseMaxVolume; | ||
316 | if (!slider.forceRaiseMaxVolume && Volume > PulseAudio.NormalVolume) { | 291 | if (!slider.forceRaiseMaxVolume && Volume > PulseAudio.NormalVolume) { | ||
317 | Volume = PulseAudio.NormalVolume; | 292 | Volume = PulseAudio.NormalVolume; | ||
318 | } | 293 | } | ||
319 | }); | 294 | }); | ||
320 | contextMenu.addMenuItem(menuItem); | 295 | contextMenu.addMenuItem(menuItem); | ||
321 | 296 | | |||
322 | // Switch all streams of the relevant kind to this device | 297 | // Switch all streams of the relevant kind to this device | ||
323 | if (type == "source") { | 298 | if (type == "source" && sourceView.model.count > 1) { | ||
324 | menuItem = newMenuItem(); | 299 | menuItem = newMenuItem(); | ||
325 | menuItem.text = i18n("Record all audio via this device"); | 300 | menuItem.text = i18n("Record all audio via this device"); | ||
326 | menuItem.icon = "mic-on" // or "mic-ready" // or "audio-input-microphone-symbolic" | 301 | menuItem.icon = "mic-on" // or "mic-ready" // or "audio-input-microphone-symbolic" | ||
327 | menuItem.clicked.connect(function() { | 302 | menuItem.clicked.connect(function() { | ||
328 | PulseObject.switchStreams(); | 303 | PulseObject.switchStreams(); | ||
329 | }); | 304 | }); | ||
330 | contextMenu.addMenuItem(menuItem); | 305 | contextMenu.addMenuItem(menuItem); | ||
331 | } else if (type == "sink") { | 306 | } else if (type == "sink" && sinkView.model.count > 1) { | ||
332 | menuItem = newMenuItem(); | 307 | menuItem = newMenuItem(); | ||
333 | menuItem.text = i18n("Play all audio via this device"); | 308 | menuItem.text = i18n("Play all audio via this device"); | ||
334 | menuItem.icon = "audio-on" // or "audio-ready" // or "audio-speakers-symbolic" | 309 | menuItem.icon = "audio-on" // or "audio-ready" // or "audio-speakers-symbolic" | ||
335 | menuItem.clicked.connect(function() { | 310 | menuItem.clicked.connect(function() { | ||
336 | PulseObject.switchStreams(); | 311 | PulseObject.switchStreams(); | ||
337 | }); | 312 | }); | ||
338 | contextMenu.addMenuItem(menuItem); | 313 | contextMenu.addMenuItem(menuItem); | ||
339 | } | 314 | } | ||
340 | 315 | | |||
316 | // Ports | ||||
317 | // Intentionally only shown when there are at least two available ports. | ||||
ngraham: By choice -> Intentionally | |||||
318 | if (PulseObject.ports && PulseObject.ports.length > 1) { | ||||
319 | contextMenu.addMenuItem(newSeperator()); | ||||
320 | | ||||
321 | var menuItem = newMenuItem(); | ||||
322 | menuItem.text = i18nc("Heading for a list of ports of a device (for example built-in laptop speakers or a plug for headphones)", "Ports"); | ||||
323 | menuItem.section = true; | ||||
324 | contextMenu.addMenuItem(menuItem); | ||||
325 | menuItem.visible = false; | ||||
326 | | ||||
327 | var menuItemsPorts = []; | ||||
328 | var availablePorts = 0; | ||||
329 | for (var i = 0; i < PulseObject.ports.length; i++) { | ||||
330 | var port = PulseObject.ports[i]; | ||||
331 | if (port.availability != Port.Unavailable) { | ||||
332 | menuItemsPorts[availablePorts] = newMenuItem(); | ||||
333 | menuItemsPorts[availablePorts].text = port.description; | ||||
334 | menuItemsPorts[availablePorts].checkable = true; | ||||
335 | menuItemsPorts[availablePorts].checked = i === PulseObject.activePortIndex; | ||||
336 | var setActivePort = function(portIndex) { | ||||
337 | return function() { | ||||
338 | PulseObject.activePortIndex = portIndex; | ||||
339 | }; | ||||
340 | }; | ||||
341 | menuItemsPorts[availablePorts].clicked.connect(setActivePort(i)); | ||||
342 | contextMenu.addMenuItem(menuItemsPorts[availablePorts]); | ||||
343 | menuItemsPorts[availablePorts].visible = false; | ||||
344 | availablePorts++; | ||||
345 | } | ||||
346 | } | ||||
347 | | ||||
348 | if (1 < availablePorts){ | ||||
349 | menuItem.visible = true; | ||||
350 | for (var i = 0; i < availablePorts; i++) { | ||||
351 | menuItemsPorts[i].visible = true; | ||||
352 | } | ||||
353 | } | ||||
354 | } | ||||
355 | | ||||
341 | // Choose output / input device | 356 | // Choose output / input device | ||
342 | // By choice only shown when there are at least two options | 357 | // Intentionally only shown when there are at least two options | ||
343 | if ((type == "sink-input" && sinkView.model.count > 1) || (type == "source-input" && sourceView.model.count > 1)) { | 358 | if ((type == "sink-input" && sinkView.model.count > 1) || (type == "source-input" && sourceView.model.count > 1)) { | ||
344 | contextMenu.addMenuItem(newSeperator()); | 359 | contextMenu.addMenuItem(newSeperator()); | ||
345 | var menuItem = newMenuItem(); | 360 | var menuItem = newMenuItem(); | ||
346 | if (type == "sink-input") { | 361 | if (type == "sink-input") { | ||
347 | menuItem.text = i18nc("Heading for a list of possible output devices (speakers, headphones, ...) to choose", "Play audio using"); | 362 | menuItem.text = i18nc("Heading for a list of possible output devices (speakers, headphones, ...) to choose", "Play audio using"); | ||
348 | } else { | 363 | } else { | ||
349 | menuItem.text = i18nc("Heading for a list of possible input devices (built-in microphone, headset, ...) to choose", "Record audio using"); | 364 | menuItem.text = i18nc("Heading for a list of possible input devices (built-in microphone, headset, ...) to choose", "Record audio using"); | ||
350 | } | 365 | } | ||
Show All 28 Lines |
That's asking for a binding loop.
An implicit size should never be based on a current size.
This could be:
Layout.minimumHeight: contextMenuButton.implicitHeight
Though I'm surprised you need this line at all.