93 files changed
+6794
-261
No files matched your search
@@ -140,11 +140,66 @@ ApplicationWindow {
|
||||
// the frame, and the reference never draws its own there
|
||||
// ("Linux only — macOS keeps the native traffic lights",
|
||||
// WindowControls.slint:1-2).
|
||||
// WINDOWS takes the macOS shape, not the Linux one, and the difference is
|
||||
// the whole feature (W27).
|
||||
//
|
||||
// `Qt.FramelessWindowHint` removes WS_THICKFRAME, and THAT is what the
|
||||
// shell keys tiling on: Win+Arrow and Aero Snap by dragging to an edge both
|
||||
// stop, along with the DWM drop shadow and the Win11 rounded corners. So
|
||||
// "hide title bar" was not making the app refuse the shortcuts -- it had
|
||||
// stopped being a window the shell could tile.
|
||||
//
|
||||
// NOT restored, and it cannot be from flags alone: the Windows 11 Snap
|
||||
// Layouts flyout that appears when hovering a maximise button. Windows asks
|
||||
// for it by hit-testing that pixel as HTMAXBUTTON, and QBZ's maximise
|
||||
// button is a QML item inside the client area, so it answers HTCLIENT.
|
||||
// Wiring it would mean reporting HTMAXBUTTON over that one rect from the
|
||||
// native filter -- deliberate future work, not something this arm does.
|
||||
//
|
||||
// Qt 6.9's Windows plugin already implements WM_NCCALCSIZE -> 0,
|
||||
// DwmExtendFrameIntoClientArea, the eight resize edges and
|
||||
// safeAreaMargins() for `ExpandedClientAreaHint` -- the same flags the
|
||||
// macOS arm uses. `CustomizeWindowHint` with no button hints then
|
||||
// suppresses Qt's own painted title cluster
|
||||
// (qwindowswindow.cpp :3561/:3600/:3624/:3645) while the window KEEPS
|
||||
// WS_CAPTION, so the shell still treats it as tileable.
|
||||
//
|
||||
// It also provides no HTCAPTION drag band of its own (:3332-3338: with
|
||||
// both isDefaultTitleBar and isCustomized false the whole title block is
|
||||
// skipped), which is why HeaderBar's QML `startSystemMove()` stays: it
|
||||
// issues SC_MOVE, and SC_MOVE drags DO snap. The eight QML resize grips
|
||||
// stay enabled too and do not fight the native edges -- Qt answers
|
||||
// WM_NCHITTEST first, so a press on the border never reaches QML.
|
||||
flags: QbzShell.systemTitleBar
|
||||
? Qt.Window
|
||||
: (QbzShell.isMacos
|
||||
? (Qt.Window | Qt.ExpandedClientAreaHint | Qt.NoTitleBarBackgroundHint)
|
||||
: (Qt.Window | Qt.FramelessWindowHint))
|
||||
: (QbzShell.isWindows
|
||||
// Both halves were MEASURED on the box, and neither is
|
||||
// optional:
|
||||
//
|
||||
// WITHOUT CustomizeWindowHint, Qt populates the default title
|
||||
// hints and its customized-title path DRAWS minimise/maximise/
|
||||
// close over the QBZ header.
|
||||
//
|
||||
// WITH it, Qt answers WM_NCHITTEST with HTNOWHERE for every
|
||||
// pixel -- centre, header, player bar and sidebar all returned
|
||||
// 0 -- and Windows sends no mouse input to HTNOWHERE, so the
|
||||
// whole app becomes unclickable while still running.
|
||||
//
|
||||
// No public flag combination gives no-buttons + clicks +
|
||||
// the WS_THICKFRAME that tiling needs, so the hint stays and
|
||||
// `win_shell::install_hittest_filter()` answers the hit test
|
||||
// instead. What this arm must NOT do is drop the hint: that
|
||||
// trades the buttons back in.
|
||||
//
|
||||
// Frameless is not the answer either: it removes
|
||||
// WS_THICKFRAME, and THAT is what Win+Arrow and Aero Snap
|
||||
// actually key on -- not WS_CAPTION, which this window does
|
||||
// not have in either arrangement.
|
||||
? (Qt.Window | Qt.ExpandedClientAreaHint | Qt.NoTitleBarBackgroundHint
|
||||
| Qt.CustomizeWindowHint)
|
||||
: (Qt.Window | Qt.FramelessWindowHint)))
|
||||
|
||||
// THE OTHER HALF OF ExpandedClientAreaHint, and the piece that made the
|
||||
// flag look broken when it was not.
|
||||
@@ -369,7 +424,10 @@ ApplicationWindow {
|
||||
// all stay at their defaults, and nothing would be logged on either
|
||||
// side. The wizard is the loudest case after QbzBlacklist, because its
|
||||
// first step ends on "Checking your audio stack…" forever.
|
||||
QbzDacWizard.boot()
|
||||
// Linux only: the wizard's row is hidden elsewhere (AudioSettings.qml),
|
||||
// so booting it would spin up an audio-stack probe nothing can reach.
|
||||
if (QbzShell.isLinux)
|
||||
QbzDacWizard.boot()
|
||||
// Offline Cache Manager. Nothing to seed — the view is unmounted until
|
||||
// the Settings row opens it — but without this line the manager's very
|
||||
// first publish is dropped and the view sits on its spinner forever:
|
||||
|
||||
@@ -43,7 +43,20 @@ Window {
|
||||
// clients — there is no such request in the core protocol; it takes a
|
||||
// compositor policy or a KWin rule, and Qt has nothing to send. The owner
|
||||
// chose removal over a button that lies.
|
||||
// WINDOWS ONLY, owner decision D8 (2026-08-27), reversing nothing about
|
||||
// K1: that ruling removed always-on-top because the owner smoked it on
|
||||
// real Plasma Wayland and the hint did nothing -- a Wayland client cannot
|
||||
// raise itself above other clients, there is no such request in the core
|
||||
// protocol, and Qt had nothing to send. On Windows the same hint IS
|
||||
// HWND_TOPMOST and does exactly what it says.
|
||||
//
|
||||
// So this is a DELIBERATE PER-PLATFORM DIVERGENCE, not a revert: the pin
|
||||
// exists where the window manager can honour it and stays absent where it
|
||||
// cannot. Linux and macOS keep the exact flags they had.
|
||||
property bool pinnedOnTop: false
|
||||
|
||||
flags: Qt.Window | Qt.FramelessWindowHint
|
||||
| ((QbzShell.isWindows && root.pinnedOnTop) ? Qt.WindowStaysOnTopHint : 0)
|
||||
|
||||
// Literal, never translated (app.slint:283 uses a plain string, not @tr).
|
||||
title: "QBZ Mini Player"
|
||||
|
||||
@@ -187,7 +187,21 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: QbzMini.exit()
|
||||
}
|
||||
// NO PIN. Owner ruling K1 shipped always-on-top as NEW work
|
||||
// THE PIN, on Windows only (owner decision D8, 2026-08-27).
|
||||
// `Qt.WindowStaysOnTopHint` is HWND_TOPMOST there and works;
|
||||
// everything below is why it is absent everywhere else, and
|
||||
// that reasoning still stands unchanged.
|
||||
CapBtn {
|
||||
name: "pin"
|
||||
visible: QbzShell.isWindows
|
||||
active: root.hostWindow ? root.hostWindow.pinnedOnTop : false
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
if (root.hostWindow)
|
||||
root.hostWindow.pinnedOnTop = !root.hostWindow.pinnedOnTop
|
||||
}
|
||||
}
|
||||
// NO PIN ELSEWHERE. Owner ruling K1 shipped always-on-top as NEW work
|
||||
// (the reference promises it in three comments and implements
|
||||
// it nowhere), and the owner smoked it on real Plasma Wayland
|
||||
// on 2026-08-04: `Qt.WindowStaysOnTopHint` did not keep the
|
||||
|
||||
@@ -613,7 +613,12 @@ Column {
|
||||
SettingRow {
|
||||
// Absent while the tray is off — there is no icon to give a
|
||||
// variant to (owner 2026-08-21).
|
||||
visible: root.doc.trayEnable === true
|
||||
//
|
||||
// And absent on Windows, where the notification area draws the
|
||||
// executable's own icon: Windows owns light/dark there and the tray
|
||||
// has no theme arm, so every choice here would render, persist and
|
||||
// change nothing.
|
||||
visible: root.doc.trayEnable === true && QbzShell.isWindows !== true
|
||||
label: QbzSession.tr("Tray icon variant", QbzSession.trRev)
|
||||
description: QbzSession.tr("Pick a mono glyph to match your panel (Plasma, GNOME's permanently dark top bar) or the full colour vinyl logo", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
|
||||
@@ -150,6 +150,13 @@ Column {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
// ALSA only. fc0d35e29 opened this to WASAPI expecting plan B Task 6
|
||||
// (DoP over exclusive mode); the spike then measured this DAC refusing
|
||||
// 176400, which is the DSD64 DoP carrier, and Task 6 lost its target.
|
||||
// Both non-convert modes are `cfg(target_os = "linux")` in the audio
|
||||
// thread and answer "DoP playback is Linux-only" with a stream error,
|
||||
// so on Windows the row could only ever offer two ways to fail. DSD
|
||||
// still plays there -- converted to PCM, which is the default.
|
||||
visible: root.doc.backendIsAlsa === true
|
||||
label: QbzSession.tr("DSD playback", QbzSession.trRev)
|
||||
description: QbzSession.tr("How DSD tracks reach the DAC. WARNING: choose DoP or Native only if your DAC supports it — on any other DAC they play as loud noise. Volume is fixed and seeking is disabled in DoP/Native mode. Native additionally needs kernel support for the DAC.", QbzSession.trRev)
|
||||
@@ -174,6 +181,12 @@ Column {
|
||||
// Opening it resets the wizard and kicks the audio-stack probe off the UI
|
||||
// thread, so there is nothing to arm here.
|
||||
SettingRow {
|
||||
// The generated config is PipeWire/WirePlumber and qbz-dac-wizard-core
|
||||
// knows no other stack, so off Linux the wizard cannot finish. Hidden,
|
||||
// not disabled: a disabled row invites a bug report. This is NOT the
|
||||
// reference's `backend-is-pipewire` gate coming back - within Linux
|
||||
// every backend still sees it, which is the owner ruling above.
|
||||
visible: QbzShell.isLinux
|
||||
label: QbzSession.tr("HiFi Wizard", QbzSession.trRev)
|
||||
description: QbzSession.tr("Auto-detect your DACs and set up bit-perfect playback, step by step.", QbzSession.trRev)
|
||||
SettingsButton {
|
||||
@@ -190,16 +203,35 @@ Column {
|
||||
// =========================== BIT-PERFECT =============================
|
||||
GroupHeader { text: QbzSession.tr("BIT-PERFECT", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
// Hidden where no backend can honour it: on Windows until WASAPI
|
||||
// exclusive lands (Phase B publishes backendIsWasapi), and on macOS,
|
||||
// whose only backend is System default. Linux is unchanged - visible
|
||||
// always, enabled only on ALSA.
|
||||
//
|
||||
// WINDOWS: hidden on purpose, even now that Phase B publishes
|
||||
// `backendIsWasapi`. The exclusive path is chosen by the BACKEND
|
||||
// dropdown ("WASAPI Exclusive"), not by this toggle -- the selection
|
||||
// arm in qbz-player keys on `backend_type` and never reads
|
||||
// `exclusive_mode`. Showing the toggle would put a control under the
|
||||
// BIT-PERFECT header that changes nothing, which is worse than no
|
||||
// control at all when the thing being judged IS bit-perfect. Whether
|
||||
// Windows should instead mirror the Linux split (backend = transport,
|
||||
// toggle = hw/plughw) is an open design question for the owner.
|
||||
visible: QbzShell.isLinux
|
||||
label: QbzSession.tr("Exclusive mode", QbzSession.trRev)
|
||||
description: QbzSession.tr("Lock the device so no other app can resample it.", QbzSession.trRev)
|
||||
rowEnabled: root.doc.backendIsAlsa === true
|
||||
rowEnabled: root.doc.backendIsAlsa === true || root.doc.backendIsWasapi === true
|
||||
QbzToggle {
|
||||
checked: root.doc.exclusiveMode === true
|
||||
enabled: root.doc.backendIsAlsa === true
|
||||
enabled: root.doc.backendIsAlsa === true || root.doc.backendIsWasapi === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("exclusive-mode", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
// D-Bus org.freedesktop.ReserveDevice1, which is Linux only. On
|
||||
// Windows exclusive mode IS the reservation (Phase B); on macOS it is
|
||||
// Hog Mode.
|
||||
visible: QbzShell.isLinux
|
||||
label: QbzSession.tr("Reserve DAC while running", QbzSession.trRev)
|
||||
description: QbzSession.tr("Hold the device reserved so other apps can't grab it.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
|
||||
@@ -929,4 +929,16 @@ Rectangle {
|
||||
WhatsNewModal {
|
||||
anchors.fill: parent
|
||||
}
|
||||
// A LOADER, not a static mount. `visible: false` still CONSTRUCTS the
|
||||
// whole subtree -- panel, Flickable, fourteen Repeater delegates, their
|
||||
// texts and bindings -- on Linux and macOS, where this modal can never be
|
||||
// shown. The Loader makes that cost real only where the modal exists.
|
||||
//
|
||||
// Last of the three so it lands on top at equal z: it is shown once per
|
||||
// version at startup and has to be answered.
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
active: QbzShell.isWindows && QbzShell.windowsDisclaimerOpen === true
|
||||
sourceComponent: WindowsDisclaimerModal { }
|
||||
}
|
||||
}
|
||||
@@ -72,11 +72,23 @@ Item {
|
||||
// otherwise. Empty when no detail exists (never a bare "CD: ").
|
||||
readonly property string qualityTooltip: {
|
||||
if (QbzPlayer.npQualityDetail === "") return ""
|
||||
if (!showDelivered) return tierLabel + ": " + QbzPlayer.npQualityDetail
|
||||
if (!showDelivered)
|
||||
return tierLabel + ": " + QbzPlayer.npQualityDetail + root.streamModeLine
|
||||
var t = QbzSession.tr("Source: {}", QbzSession.trRev).replace("{}", QbzPlayer.npQualityDetail)
|
||||
+ "\n"
|
||||
+ QbzSession.tr("Output: {}", QbzSession.trRev).replace("{}", QbzPlayer.npQualityTrueDetail)
|
||||
return causeLine === "" ? t : t + "\n" + causeLine
|
||||
t = causeLine === "" ? t : t + "\n" + causeLine
|
||||
return t + root.streamModeLine
|
||||
}
|
||||
|
||||
// What the OPENED stream negotiated, appended to the tooltip. The mode LED
|
||||
// beside it is derived from SETTINGS, so it says what was ASKED FOR; this
|
||||
// says what the engine actually got. When they disagree, that is the whole
|
||||
// point. Four technical tokens, so nothing new to translate.
|
||||
readonly property string streamModeLine: {
|
||||
var m = QbzPlayer.npBitPerfectMode
|
||||
if (m === "" || m === "unknown") return ""
|
||||
return "\nstream: " + m
|
||||
}
|
||||
|
||||
readonly property bool localOutput: !QbzPlayer.npIsRemote && !QbzPlayer.npCastActive
|
||||
|
||||
@@ -250,9 +250,17 @@ Item {
|
||||
}
|
||||
entries.push(
|
||||
{ "label": nav.trs("Open folder…"), "icon": "folder-open",
|
||||
"view": "", "tab": "", "action": "openFolder", "enabled": true },
|
||||
{ "label": nav.trs("Open audio CD"), "icon": "disc",
|
||||
"view": "", "tab": "", "action": "openCd", "enabled": true },
|
||||
"view": "", "tab": "", "action": "openFolder", "enabled": true })
|
||||
// W16: qbz-disc has no Windows CDDA backend -- `mod unsupported`
|
||||
// compiles and every call returns NoDrive. Offering the entry would
|
||||
// only ever produce "no drive found" on a machine that has one. SACD
|
||||
// stays: that opens an image file, which needs no drive.
|
||||
if (!QbzShell.isWindows) {
|
||||
entries.push(
|
||||
{ "label": nav.trs("Open audio CD"), "icon": "disc",
|
||||
"view": "", "tab": "", "action": "openCd", "enabled": true })
|
||||
}
|
||||
entries.push(
|
||||
{ "label": nav.trs("Open SACD image…"), "icon": "disc-3",
|
||||
"view": "", "tab": "", "action": "openSacd", "enabled": true })
|
||||
if (QbzLocal.localEphemeralActive) {
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
// MSL 1.2 for Metal, SPIR-V always embedded for Vulkan). Regenerate after
|
||||
// ANY shader-source edit, from crates/qbz-qt:
|
||||
// for m in bars wave energy; do \
|
||||
// /usr/lib64/qt6/bin/qsb --glsl 440 --msl 12 \
|
||||
// /usr/lib64/qt6/bin/qsb --glsl "100 es,300 es,310 es,320 es,120,150,440" --hlsl 50 --msl 12 \
|
||||
// -o qml/assets/shaders/spectrum_$m.frag.qsb \
|
||||
// qml/assets/shaders/spectrum_$m.frag; \
|
||||
// done
|
||||
// /usr/lib64/qt6/bin/qsb --glsl 440 --msl 12 \
|
||||
// /usr/lib64/qt6/bin/qsb --glsl "100 es,300 es,310 es,320 es,120,150,440" --hlsl 50 --msl 12 -b \
|
||||
// -o qml/assets/shaders/spectrum.vert.qsb qml/assets/shaders/spectrum.vert
|
||||
// (macOS: qsb is at $(brew --prefix qt)/bin/qsb.) A .qsb is keyed to the Qt
|
||||
// Shader Tools version that baked it — rebake when the system Qt moves.
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
// The Windows "as-is" disclaimer, shown once per version at startup.
|
||||
//
|
||||
// Structure, scrim, shadow, z and the self-gate follow WhatsNewModal.qml,
|
||||
// which follows LogViewerModal.qml. Colour literals are `Qt.rgba(...)`
|
||||
// because Slint is #RRGGBBAA and Qt is #AARRGGBB -- the copy that shipped an
|
||||
// invisible scrim in five modals, fixed 2026-08-14.
|
||||
//
|
||||
// ── WHY THE BODY IS NOT TRANSLATED ─────────────────────────────────────────
|
||||
//
|
||||
// This is the author's own statement of policy, quoted from the release notes
|
||||
// ("Windows -- yes, but read this first"). The wording is the point: it says
|
||||
// what is and is not promised, and in whose voice. The CHROME around it (the
|
||||
// title, the checkbox, the button) goes through `tr` like everything else, so
|
||||
// the frame localises and the statement does not get paraphrased by a
|
||||
// translator into something that promises more or less than it does.
|
||||
//
|
||||
// ── WHY THIS IS NOT `Text.MarkdownText` ────────────────────────────────────
|
||||
//
|
||||
// Same reason WhatsNewModal gives, minus the parser: the source has exactly
|
||||
// two constructs, `**bold**` and one link, so the paragraphs below carry
|
||||
// `<b>` and `<a>` and render as StyledText. No dependency, no subset to
|
||||
// disagree about.
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
anchors.fill: parent
|
||||
z: 3000
|
||||
|
||||
QbzTheme { id: theme }
|
||||
|
||||
// The body, verbatim from the release notes. One entry per paragraph.
|
||||
// `lead: true` renders at the emphasis size -- the two sentences that are
|
||||
// the actual disclaimer rather than the explanation around it.
|
||||
readonly property var paragraphs: [
|
||||
{ lead: false, text: "There is now a Win32 build of QBZ." },
|
||||
{ lead: false, text: "There is, however, one important distinction:" },
|
||||
{ lead: true, text: "<b>Windows is not a supported QBZ platform.</b>" },
|
||||
{ lead: false, text: "The Windows build is provided <b>as-is</b>. Think of it like buying something second-hand: what you see is what you get." },
|
||||
{ lead: false, text: "I won't promise feature parity, chase Windows-specific regressions, maintain a separate Windows roadmap, or guarantee that something Microsoft changes six months from now won't break it." },
|
||||
{ lead: false, text: "If it works for you, great. If it doesn't, there is no promise that I'll fix it." },
|
||||
{ lead: false, text: "And to be completely transparent about why: <b>I hated using Windows while working on this port.</b>" },
|
||||
{ lead: false, text: "Installing it, configuring the build box, dealing with the environment and especially figuring out its audio setup reminded me why I don't use it in the first place. I have no desire to spend more time than absolutely necessary inside Redmond's OS every time QBZ gets a release." },
|
||||
{ lead: false, text: "Sorry. Long-time Gentoo Linux user. Some biases are too old to fix." },
|
||||
{ lead: false, text: "So why ship it?" },
|
||||
{ lead: false, text: "Because the hard part — getting QBZ running properly on Win32 — is done, and useful code is better in people's hands than sitting in a branch because I personally don't want to maintain another operating system." },
|
||||
{ lead: false, text: "More importantly, <b>the Windows port is open for adoption</b>." },
|
||||
{ lead: false, text: "That's essentially what happened with macOS. <a href=\"https://github.com/afonsojramos\">@afonsojramos</a> took ownership of the macOS port and is the reason it grew from an experiment into a properly supported QBZ platform." },
|
||||
{ lead: false, text: "If someone wants to do the same for Windows, maintain it and eventually remove this rather large disclaimer, get in touch." }
|
||||
]
|
||||
|
||||
property bool remember: false
|
||||
|
||||
// Component.onCompleted, NOT onVisibleChanged. `Item.visible` defaults to
|
||||
// true and the Loader only creates this when it should already be shown,
|
||||
// so there is no false->true transition to observe: the old handler never
|
||||
// ran, focus was never taken, Escape never reached the handler below, and
|
||||
// ordinary hotkeys kept working BEHIND the modal.
|
||||
Component.onCompleted: keyScope.forceActiveFocus()
|
||||
|
||||
// Qt strands activeFocus on an item that is being destroyed, which kills
|
||||
// AppShell's key dispatcher until the next click. Hand focus back to the
|
||||
// shell root on the way out -- the same duck-walk QbzConfirmModal uses,
|
||||
// and for the same measured reason.
|
||||
Component.onDestruction: root._restoreShellFocus()
|
||||
|
||||
function _restoreShellFocus() {
|
||||
var p = root
|
||||
while (p.parent) {
|
||||
if (p.parent.isQbzShellRoot === true) {
|
||||
p.parent.forceActiveFocus()
|
||||
return
|
||||
}
|
||||
p = p.parent
|
||||
}
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: keyScope
|
||||
anchors.fill: parent
|
||||
// Escape closes WITHOUT remembering: dismissing a disclaimer by
|
||||
// reflex must never be read as having accepted it for good.
|
||||
Keys.onEscapePressed: QbzShell.dismissWindowsDisclaimer(false)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.75)
|
||||
// No click-through close. Unlike the other modals this one is the
|
||||
// first thing a new user sees and says what is not promised; a stray
|
||||
// click on the scrim should not dismiss it.
|
||||
MouseArea { anchors.fill: parent }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: panel
|
||||
width: panel.width + 8
|
||||
height: panel.height + 8
|
||||
radius: theme.radiusMd
|
||||
color: Qt.rgba(0, 0, 0, 0.5)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: panel
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(root.width - 80, 720)
|
||||
height: Math.min(root.height - 80, 640)
|
||||
radius: theme.radiusMd
|
||||
color: theme.surfaceCard
|
||||
border.width: 1
|
||||
border.color: theme.borderSubtle
|
||||
|
||||
MouseArea { anchors.fill: parent }
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 24
|
||||
|
||||
Text {
|
||||
id: headerText
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
text: QbzSession.tr("Windows — yes, but read this first", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: 18
|
||||
font.weight: Font.DemiBold
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: scroller
|
||||
anchors.top: headerText.bottom
|
||||
anchors.topMargin: 16
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: footerRow.top
|
||||
anchors.bottomMargin: 16
|
||||
clip: true
|
||||
contentWidth: width
|
||||
contentHeight: body.height
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Column {
|
||||
id: body
|
||||
width: scroller.width
|
||||
spacing: 12
|
||||
|
||||
Repeater {
|
||||
model: root.paragraphs
|
||||
Text {
|
||||
width: parent.width
|
||||
text: modelData.text
|
||||
// StyledText, not RichText: it is the cheap
|
||||
// subset and it is all these paragraphs use.
|
||||
textFormat: Text.StyledText
|
||||
wrapMode: Text.WordWrap
|
||||
color: modelData.lead ? theme.textPrimary : theme.textSecondary
|
||||
font.pixelSize: modelData.lead ? 15 : 13
|
||||
linkColor: theme.accent
|
||||
onLinkActivated: function (url) { Qt.openUrlExternally(url) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: footerRow
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 32
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 8
|
||||
|
||||
QbzCheckbox {
|
||||
id: rememberBox
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: root.remember
|
||||
// `toggled()` carries NO argument here (see
|
||||
// QbzCheckbox.qml): the control reports that it was
|
||||
// hit, and the owner of the state flips it.
|
||||
onToggled: root.remember = !root.remember
|
||||
}
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzSession.tr("Don't show this again", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.remember = !root.remember
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QbzPrimaryButton {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
// `label`, not `text` -- this control is not a Qt Button.
|
||||
label: QbzSession.tr("I understand", QbzSession.trRev)
|
||||
btnHeight: 32
|
||||
onClicked: QbzShell.dismissWindowsDisclaimer(root.remember)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,14 +242,19 @@ Item {
|
||||
CardMenu {
|
||||
id: openMenu
|
||||
menuWidth: 220
|
||||
// W16: the audio-CD row is dropped on Windows -- qbz-disc's CDDA
|
||||
// backend is `mod unsupported` there and answers NoDrive to
|
||||
// everything. SACD is an image file, so it stays on every platform.
|
||||
entries: [
|
||||
{ "label": QbzSession.tr("Open folder…", QbzSession.trRev),
|
||||
"icon": "folder-open", "action": "folder" },
|
||||
"icon": "folder-open", "action": "folder" }
|
||||
].concat(QbzShell.isWindows ? [] : [
|
||||
{ "label": QbzSession.tr("Open audio CD", QbzSession.trRev),
|
||||
"icon": "disc", "action": "cd" },
|
||||
"icon": "disc", "action": "cd" }
|
||||
]).concat([
|
||||
{ "label": QbzSession.tr("Open SACD image…", QbzSession.trRev),
|
||||
"icon": "disc-3", "action": "sacd" }
|
||||
]
|
||||
])
|
||||
onPicked: function (action) {
|
||||
if (action === "folder") QbzLocal.ephemeralOpen()
|
||||
else if (action === "cd") QbzLocal.ephemeralOpenCd()
|
||||
|
||||
Reference in new issue
Block a user