Merge branch 'noctalia-dev:main' into Centered-Circles

This commit is contained in:
MrDowntempo
2025-09-27 12:37:38 -04:00
committed by GitHub
25 changed files with 350 additions and 106 deletions
+110
View File
@@ -0,0 +1,110 @@
name: Update AUR Package
on:
push:
tags:
- 'v*'
workflow_dispatch:
jobs:
aur-sync:
name: Sync PKGBUILD with release
runs-on: ubuntu-latest
container:
image: archlinux:latest
defaults:
run:
shell: bash
env:
AUR_REPO: ssh://aur@aur.archlinux.org/noctalia-shell.git
GIT_SSH_COMMAND: ssh -i /root/.ssh/id_aur -o StrictHostKeyChecking=yes -o IdentitiesOnly=yes
PKGNAME: noctalia-shell
AUR_LINK: https://aur.archlinux.org/packages/noctalia-shell
steps:
- name: Install dependencies
run: |
set -euo pipefail
pacman -Syu --noconfirm git base-devel pacman-contrib openssh
- name: Create build user
run: |
set -euo pipefail
useradd -m builduser
echo 'builduser ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers
- name: Configure SSH
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
run: |
set -euo pipefail
mkdir -p /root/.ssh
chmod 700 /root/.ssh
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > /root/.ssh/id_aur
chmod 600 /root/.ssh/id_aur
ssh-keyscan aur.archlinux.org >> /root/.ssh/known_hosts
chmod 600 /root/.ssh/known_hosts
- name: Determine version
id: vars
env:
TAG_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
PKGVER="${TAG_NAME#v}"
echo "pkgver=$PKGVER" >> "$GITHUB_OUTPUT"
- name: Clone AUR repository
run: |
set -euo pipefail
git clone "$AUR_REPO" "$GITHUB_WORKSPACE/aur"
- name: Update PKGBUILD
env:
PKGVER: ${{ steps.vars.outputs.pkgver }}
working-directory: ${{ github.workspace }}/aur
run: |
set -euo pipefail
sed -i "s/^pkgver=.*/pkgver=${PKGVER}/" PKGBUILD
sed -i "s/^pkgrel=.*/pkgrel=1/" PKGBUILD
- name: Refresh checksums and metadata
env:
AUR_DIR: ${{ github.workspace }}/aur
run: |
set -euo pipefail
chown -R builduser:builduser "$AUR_DIR"
su - builduser -c "cd $AUR_DIR && updpkgsums"
su - builduser -c "cd $AUR_DIR && makepkg --printsrcinfo > .SRCINFO"
- name: Commit and push changes
env:
PKGVER: ${{ steps.vars.outputs.pkgver }}
working-directory: ${{ github.workspace }}/aur
run: |
set -euo pipefail
git config --global --add safe.directory "$PWD"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if [[ -n "$(git status --porcelain)" ]]; then
git add PKGBUILD .SRCINFO
git commit -m "chore(package): release ${PKGVER}"
git push origin HEAD
else
echo "No updates necessary."
fi
- name: Summarize update
env:
PKGNAME: noctalia-shell
PKGVER: ${{ steps.vars.outputs.pkgver }}
AUR_LINK: https://aur.archlinux.org/packages/noctalia-shell
run: |
set -euo pipefail
{
echo "## AUR Update"
echo ""
echo "- Package: ${PKGNAME}"
echo "- Updated version: ${PKGVER}"
echo "- AUR page: ${AUR_LINK}"
} >> "$GITHUB_STEP_SUMMARY"
+4
View File
@@ -478,6 +478,10 @@
"enable-matugen": {
"label": "Matugen aktivieren",
"description": "Automatisch Farben basierend auf Ihrem aktiven Hintergrundbild generieren."
},
"matugen-scheme-type": {
"label": "Matugen-Schema-Typ",
"description": "Wähle einen Farbstil für Matugen aus."
}
},
"predefined": {
+4
View File
@@ -478,6 +478,10 @@
"enable-matugen": {
"label": "Enable Matugen",
"description": "Automatically generate colors based on your active wallpaper."
},
"matugen-scheme-type": {
"label": "Matugen scheme type",
"description": "Choose the color scheme generation algorithm for Matugen."
}
},
"predefined": {
+4
View File
@@ -476,6 +476,10 @@
"enable-matugen": {
"label": "Activar Matugen",
"description": "Genera colores automáticamente basados en tu fondo de pantalla activo."
},
"matugen-scheme-type": {
"label": "Tipo de esquema Matugen",
"description": "Elige el algoritmo de generación de esquema de colores para Matugen."
}
},
"predefined": {
+4
View File
@@ -476,6 +476,10 @@
"enable-matugen": {
"label": "Activer Matugen",
"description": "Générez automatiquement des couleurs en fonction de votre fond d'écran actif."
},
"matugen-scheme-type": {
"label": "Type de schéma Matugen",
"description": "Choisissez l'algorithme de génération de schéma de couleurs pour Matugen."
}
},
"predefined": {
+4
View File
@@ -442,6 +442,10 @@
"enable-matugen": {
"label": "Ativar Matugen",
"description": "Gera cores automaticamente com base no seu papel de parede ativo."
},
"matugen-scheme-type": {
"label": "Tipo de esquema Matugen",
"description": "Escolha o algoritmo de geração de esquema de cores para Matugen."
}
},
"predefined": {
+4
View File
@@ -476,6 +476,10 @@
"enable-matugen": {
"label": "启用 Matugen",
"description": "根据您的活动壁纸自动生成颜色。"
},
"matugen-scheme-type": {
"label": "Matugen 配色方案类型",
"description": "为 Matugen 选择配色方案生成算法。"
}
},
"predefined": {
+3 -5
View File
@@ -1,5 +1,5 @@
{
"settingsVersion": 8,
"settingsVersion": 10,
"bar": {
"position": "top",
"backgroundOpacity": 1,
@@ -51,9 +51,6 @@
{
"id": "Brightness"
},
{
"id": "NightLight"
},
{
"id": "Clock"
},
@@ -161,7 +158,8 @@
"colorSchemes": {
"useWallpaperColors": false,
"predefinedScheme": "Noctalia (default)",
"darkMode": true
"darkMode": true,
"matugenSchemeType": "scheme-fruit-salad"
},
"matugen": {
"gtk4": false,
+2 -2
View File
@@ -41,6 +41,6 @@ gdbus call --session \
"dialog-question" \
"Confirmation Required" \
"Do you want to proceed with the action?" \
"['default', 'OK', 'cancel', 'Cancel']" \
"['default', 'OK', 'cancel', 'Cancel', 'maybe', 'Maybe', 'undecided', 'Undecided']" \
"{}" \
5000
5000
+2 -3
View File
@@ -114,7 +114,7 @@ Singleton {
JsonAdapter {
id: adapter
property int settingsVersion: 8
property int settingsVersion: 10
// bar
property JsonObject bar: JsonObject {
@@ -158,8 +158,6 @@ Singleton {
"id": "Volume"
}, {
"id": "Brightness"
}, {
"id": "NightLight"
}, {
"id": "Clock"
}, {
@@ -293,6 +291,7 @@ Singleton {
property bool useWallpaperColors: false
property string predefinedScheme: "Noctalia (default)"
property bool darkMode: true
property string matugenSchemeType: "scheme-fruit-salad"
}
// matugen templates toggles
+2 -1
View File
@@ -148,7 +148,8 @@ Singleton {
"filepicker-file": "file",
"filepicker-text": "file-text",
"filepicker-eye": "eye",
"filepicker-eye-off": "eye-off"
"filepicker-eye-off": "eye-off",
"filepicker-folder-current": "checks"
}
// Fonts Codepoints - do not change!
+6
View File
@@ -24,6 +24,12 @@ NIconButton {
icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off"
tooltipText: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? I18n.tr("tooltips.night-light-forced") : I18n.tr("tooltips.night-light-enabled")) : I18n.tr("tooltips.night-light-disabled")
onClicked: {
// Check if wlsunset is available before enabling night light
if (!ProgramCheckerService.wlsunsetAvailable) {
ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed"))
return
}
if (!Settings.data.nightLight.enabled) {
Settings.data.nightLight.enabled = true
Settings.data.nightLight.forced = false
+5 -7
View File
@@ -341,11 +341,14 @@ Variants {
}
// Notification actions
RowLayout {
Flow {
Layout.fillWidth: true
spacing: Style.marginS * scaling
Layout.topMargin: Style.marginM * scaling
flow: Flow.LeftToRight
layoutDirection: Qt.LeftToRight
// Store the notification ID for access in button delegates
property string parentNotificationId: notificationId
@@ -378,17 +381,12 @@ Variants {
textColor: hovered ? Color.mOnTertiary : Color.mOnPrimary
hoverColor: Color.mTertiary
outlined: false
Layout.preferredHeight: 24 * scaling
implicitHeight: 24 * scaling
onClicked: {
NotificationService.invokeAction(parent.parentNotificationId, actionData.identifier)
}
}
}
// Spacer to push buttons to the left
Item {
Layout.fillWidth: true
}
}
}
}
@@ -90,8 +90,7 @@ ColumnLayout {
NFilePicker {
id: imagePicker
title: I18n.tr("bar.widget-settings.control-center.select-custom-icon")
selectFiles: true
selectFolders: false
selectionMode: "files"
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"]
initialPath: Quickshell.env("HOME")
onAccepted: paths => {
@@ -62,6 +62,7 @@ ColumnLayout {
label: I18n.tr("bar.widget-settings.system-monitor.memory-percentage")
checked: valueShowMemoryAsPercent
onToggled: checked => valueShowMemoryAsPercent = checked
visible: valueShowMemoryUsage
}
NToggle {
+44
View File
@@ -140,6 +140,50 @@ ColumnLayout {
}
}
// Matugen Scheme Type Selection
NComboBox {
label: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.label")
description: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.description")
enabled: Settings.data.colorSchemes.useWallpaperColors
opacity: Settings.data.colorSchemes.useWallpaperColors ? 1.0 : 0.6
visible: Settings.data.colorSchemes.useWallpaperColors
model: [{
"key": "scheme-content",
"name": "Content"
}, {
"key": "scheme-expressive",
"name": "Expressive"
}, {
"key": "scheme-fidelity",
"name": "Fidelity"
}, {
"key": "scheme-fruit-salad",
"name": "Fruit Salad"
}, {
"key": "scheme-monochrome",
"name": "Monochrome"
}, {
"key": "scheme-neutral",
"name": "Neutral"
}, {
"key": "scheme-rainbow",
"name": "Rainbow"
}, {
"key": "scheme-tonal-spot",
"name": "Tonal Spot"
}]
currentKey: Settings.data.colorSchemes.matugenSchemeType
onSelected: key => {
Settings.data.colorSchemes.matugenSchemeType = key
if (Settings.data.colorSchemes.useWallpaperColors) {
MatugenService.generateFromWallpaper()
}
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
+1 -2
View File
@@ -49,8 +49,7 @@ ColumnLayout {
NFilePicker {
id: avatarPicker
title: I18n.tr("settings.general.profile.select-avatar")
selectFiles: true
selectFolders: true
selectionMode: "files"
initialPath: Settings.data.general.avatarImage.substr(0, Settings.data.general.avatarImage.lastIndexOf("/")) || Quickshell.env("HOME")
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"]
onAccepted: paths => {
+1 -2
View File
@@ -229,8 +229,7 @@ ColumnLayout {
NFilePicker {
id: folderPicker
selectFiles: false
selectFolders: true
selectionMode: "folders"
title: I18n.tr("settings.screen-recorder.general.select-output-folder")
initialPath: Settings.data.screenRecorder.directory || Quickshell.env("HOME") + "/Videos"
onAccepted: paths => {
+2 -4
View File
@@ -340,8 +340,7 @@ ColumnLayout {
NFilePicker {
id: mainFolderPicker
selectFiles: false
selectFolders: true
selectionMode: "folders"
title: I18n.tr("settings.wallpaper.settings.select-folder")
initialPath: Settings.data.wallpaper.directory || Quickshell.env("HOME") + "/Pictures"
onAccepted: paths => {
@@ -353,8 +352,7 @@ ColumnLayout {
NFilePicker {
id: monitorFolderPicker
selectFiles: false
selectFolders: true
selectionMode: "folders"
title: I18n.tr("settings.wallpaper.settings.select-monitor-folder")
initialPath: WallpaperService.getMonitorDirectory(specificFolderMonitorName) || Quickshell.env("HOME") + "/Pictures"
onAccepted: paths => {
+29 -29
View File
@@ -10,6 +10,7 @@ Singleton {
readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter
readonly property bool available: (adapter !== null)
readonly property bool enabled: adapter?.enabled ?? false
readonly property bool discovering: (adapter && adapter.discovering) ?? false
readonly property var devices: adapter ? adapter.devices : null
readonly property var pairedDevices: {
@@ -30,18 +31,28 @@ Singleton {
}
property bool lastAdapterState: false
property bool restoringState: false
function init() {
Logger.log("Bluetooth", "Service initialized")
syncStateTimer.running = true
// Try to restore saved state if needed
if (Settings.data.network.bluetoothEnabled !== undefined && adapter) {
restoringState = true
adapter.enabled = Settings.data.network.bluetoothEnabled
restoringState = false
}
// Need to delay a bit for the adapter to be ready
initTimer.running = true
}
Timer {
id: syncStateTimer
id: initTimer
interval: 1000
repeat: false
onTriggered: {
lastAdapterState = Settings.data.network.bluetoothEnabled = adapter.enabled
lastAdapterState = adapter?.enabled ?? false
Logger.log("Bluetooth", "LastAdapterState:", lastAdapterState)
}
}
@@ -52,27 +63,6 @@ Singleton {
onTriggered: adapter.discovering = true
}
Timer {
id: stateDebounceTimer
interval: 200
repeat: false
onTriggered: {
if (!adapter) {
Logger.warn("Bluetooth", "State debouncer", "No adapter available")
return
}
if (lastAdapterState === adapter.enabled) {
return
}
lastAdapterState = adapter.enabled
if (adapter.enabled) {
ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.enabled"))
} else {
ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.disabled"))
}
}
}
Connections {
target: adapter
function onEnabledChanged() {
@@ -82,11 +72,21 @@ Singleton {
}
Logger.log("Bluetooth", "onEnableChanged", adapter.enabled)
Settings.data.network.bluetoothEnabled = adapter.enabled
stateDebounceTimer.restart()
if (adapter.enabled) {
// Using a timer to give a little time so the adapter is really enabled
discoveryTimer.running = true
// Only save to settings if this is a user-initiated change
if (!restoringState) {
Settings.data.network.bluetoothEnabled = adapter.enabled
}
// Show toast only for actual state changes
if (lastAdapterState !== adapter.enabled) {
lastAdapterState = adapter.enabled
if (adapter.enabled) {
ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.enabled"))
discoveryTimer.running = true
} else {
ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.disabled"))
}
}
}
}
+3 -3
View File
@@ -66,14 +66,14 @@ Singleton {
var extraUser = (Settings.configDir + "matugen.d").replace(/'/g, "'\\''")
// Build the main script
var script = "cat > '" + pathEsc + "' << 'EOF'\n" + content + "EOF\n" + "for d in '" + extraRepo + "' '" + extraUser + "'; do\n" + " if [ -d \"$d\" ]; then\n" + " for f in \"$d\"/*.toml; do\n" + " [ -f \"$f\" ] && { echo; echo \"# extra: $f\"; cat \"$f\"; } >> '" + pathEsc + "'\n" + " done\n" + " fi\n" + "done\n" + "matugen image '"
+ wp + "' --config '" + pathEsc + "' --mode " + mode
var script = "cat > '" + pathEsc + "' << 'EOF'\n" + content + "EOF\n" + "for d in '" + extraRepo + "' '" + extraUser + "'; do\n" + " if [ -d \"$d\" ]; then\n" + " for f in \"$d\"/*.toml; do\n" + " [ -f \"$f\" ] && { echo; echo \"# extra: $f\"; cat \"$f\"; } >> '" + pathEsc + "'\n" + " done\n" + " fi\n"
+ "done\n" + "matugen image '" + wp + "' --config '" + pathEsc + "' --mode " + mode + " --type " + Settings.data.colorSchemes.matugenSchemeType
// Add user config execution if enabled
if (Settings.data.matugen.enableUserTemplates) {
var userConfigDir = (Quickshell.env("HOME") + "/.config/matugen/").replace(/'/g, "'\\''")
script += "\n# Execute user config if it exists\nif [ -f '" + userConfigDir + "config.toml' ]; then\n"
script += " matugen image '" + wp + "' --config '" + userConfigDir + "config.toml' --mode " + mode + "\n"
script += " matugen image '" + wp + "' --config '" + userConfigDir + "config.toml' --mode " + mode + " --type " + Settings.data.colorSchemes.matugenSchemeType + "\n"
script += "fi"
}
+3 -1
View File
@@ -18,6 +18,7 @@ Singleton {
property bool fuzzelAvailable: false
property bool vesktopAvailable: false
property bool gpuScreenRecorderAvailable: false
property bool wlsunsetAvailable: false
// Signal emitted when all checks are complete
signal checksCompleted
@@ -31,7 +32,8 @@ Singleton {
"footAvailable": ["which", "foot"],
"fuzzelAvailable": ["which", "fuzzel"],
"vesktopAvailable": ["which", "vesktop"],
"gpuScreenRecorderAvailable": ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"]
"gpuScreenRecorderAvailable": ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"],
"wlsunsetAvailable": ["which", "wlsunset"]
})
// Internal tracking
+1 -1
View File
@@ -8,7 +8,7 @@ Singleton {
id: root
// Public properties
property string baseVersion: "2.14.1"
property string baseVersion: "2.14.3"
property bool isDevelopment: true
property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}`
+76 -44
View File
@@ -15,8 +15,7 @@ Popup {
// Properties
property string title: "File Picker"
property string initialPath: Quickshell.env("HOME") || "/home"
property bool selectFiles: true
property bool selectFolders: true
property string selectionMode: "files" // "files" or "folders"
property var nameFilters: ["*"]
property bool showDirs: true
property bool showHiddenFiles: false
@@ -117,8 +116,16 @@ Popup {
const fileIsDir = folderModel.get(i, "fileIsDir")
const fileSize = folderModel.get(i, "fileSize")
if (root.selectFolders && !root.selectFiles && !fileIsDir)
// Skip hidden items if showHiddenFiles is false
// This additional check ensures hidden files are properly filtered
if (!root.showHiddenFiles && fileName.startsWith(".")) {
continue
}
// In folder mode, hide files
if (root.selectionMode === "folders" && !fileIsDir)
continue
if (searchText === "" || fileName.toLowerCase().includes(searchText)) {
filteredModel.append({
"fileName": fileName,
@@ -193,10 +200,28 @@ Popup {
color: Color.mPrimary
Layout.fillWidth: true
}
// "Select Current" button only visible in folder selection mode
NButton {
text: "Select Current"
icon: "filepicker-folder-current"
visible: root.selectionMode === "folders"
onClicked: {
filePickerPanel.currentSelection = [root.currentPath]
root.confirmSelection()
}
}
NIconButton {
icon: "filepicker-refresh"
tooltipText: "Refresh"
onClicked: folderModel.refresh()
onClicked: {
// Force a proper refresh by resetting the folder
const currentFolder = folderModel.folder
folderModel.folder = ""
folderModel.folder = currentFolder
Qt.callLater(root.updateFilteredModel)
}
}
NIconButton {
icon: "filepicker-close"
@@ -301,7 +326,11 @@ Popup {
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
root.showHiddenFiles = !root.showHiddenFiles
root.updateFilteredModel()
// Force model refresh by resetting the folder
const currentFolder = folderModel.folder
folderModel.folder = ""
folderModel.folder = currentFolder
Qt.callLater(root.updateFilteredModel)
}
}
}
@@ -375,15 +404,20 @@ Popup {
FolderListModel {
id: folderModel
folder: "file://" + root.currentPath
nameFilters: root.nameFilters
// Use wildcard filters including hidden files when showHiddenFiles is true
nameFilters: root.showHiddenFiles ? ["*", ".*"] : root.nameFilters
showDirs: root.showDirs
showHidden: root.showHiddenFiles
showHidden: true // Always true, we'll filter in updateFilteredModel
showDotAndDotDot: false
sortField: FolderListModel.Name
sortReversed: false
onFolderChanged: {
root.currentPath = folder.toString().replace("file://", "")
filePickerPanel.currentSelection = []
Qt.callLater(root.updateFilteredModel)
}
onStatusChanged: {
if (status === FolderListModel.Error) {
if (root.currentPath !== Quickshell.env("HOME")) {
@@ -396,6 +430,14 @@ Popup {
}
}
// Update nameFilters when showHiddenFiles changes
Connections {
target: root
function onShowHiddenFilesChanged() {
folderModel.nameFilters = root.showHiddenFiles ? ["*", ".*"] : root.nameFilters
}
}
ListModel {
id: filteredModel
}
@@ -618,15 +660,16 @@ Popup {
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
if (model.fileIsDir) {
if (root.selectFolders && !root.selectFiles) {
// In folder mode, single click selects the folder
if (root.selectionMode === "folders") {
filePickerPanel.currentSelection = [model.filePath]
} else {
folderModel.folder = "file://" + model.filePath
root.currentPath = model.filePath
}
// In file mode, single click on folder does nothing (must double-click to enter)
} else {
if (root.selectFiles)
filePickerPanel.currentSelection = [model.filePath]
// Single click on file selects it (only in file mode)
if (root.selectionMode === "files") {
filePickerPanel.currentSelection = [model.filePath]
}
}
}
}
@@ -634,15 +677,12 @@ Popup {
onDoubleClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
if (model.fileIsDir) {
if (root.selectFolders && !root.selectFiles) {
filePickerPanel.currentSelection = [model.filePath]
root.confirmSelection()
} else {
folderModel.folder = "file://" + model.filePath
root.currentPath = model.filePath
}
// Double-click on folder always navigates into it
folderModel.folder = "file://" + model.filePath
root.currentPath = model.filePath
} else {
if (root.selectFiles) {
// Double-click on file selects and confirms (only in file mode)
if (root.selectionMode === "files") {
filePickerPanel.currentSelection = [model.filePath]
root.confirmSelection()
}
@@ -719,15 +759,16 @@ Popup {
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
if (model.fileIsDir) {
if (root.selectFolders && !root.selectFiles) {
// In folder mode, single click selects the folder
if (root.selectionMode === "folders") {
filePickerPanel.currentSelection = [model.filePath]
} else {
folderModel.folder = "file://" + model.filePath
root.currentPath = model.filePath
}
// In file mode, single click on folder does nothing (must double-click to enter)
} else {
if (root.selectFiles)
filePickerPanel.currentSelection = [model.filePath]
// Single click on file selects it (only in file mode)
if (root.selectionMode === "files") {
filePickerPanel.currentSelection = [model.filePath]
}
}
}
}
@@ -735,15 +776,12 @@ Popup {
onDoubleClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
if (model.fileIsDir) {
if (root.selectFolders && !root.selectFiles) {
filePickerPanel.currentSelection = [model.filePath]
root.confirmSelection()
} else {
folderModel.folder = "file://" + model.filePath
root.currentPath = model.filePath
}
// Double-click on folder always navigates into it
folderModel.folder = "file://" + model.filePath
root.currentPath = model.filePath
} else {
if (root.selectFiles) {
// Double-click on file selects and confirms (only in file mode)
if (root.selectionMode === "files") {
filePickerPanel.currentSelection = [model.filePath]
root.confirmSelection()
}
@@ -765,7 +803,8 @@ Popup {
if (filePickerPanel.searchText.length > 0) {
return "Searching for: \"" + filePickerPanel.searchText + "\" (" + filteredModel.count + " matches)"
} else if (filePickerPanel.currentSelection.length > 0) {
return filePickerPanel.currentSelection.length + " item(s) selected"
const selectedName = filePickerPanel.currentSelection[0].split('/').pop()
return "Selected: " + selectedName
} else {
return filteredModel.count + " items"
}
@@ -785,14 +824,7 @@ Popup {
}
NButton {
text: {
if (root.selectFolders && !root.selectFiles)
return "Select Folder"
else if (root.selectFiles && !root.selectFolders)
return "Select File"
else
return "Select"
}
text: root.selectionMode === "folders" ? "Select Folder" : "Select File"
icon: "filepicker-check"
enabled: filePickerPanel.currentSelection.length > 0
onClicked: root.confirmSelection()
+34
View File
@@ -14,10 +14,44 @@ T.ScrollView {
property real handleRadius: Style.radiusM * scaling
property int verticalPolicy: ScrollBar.AsNeeded
property int horizontalPolicy: ScrollBar.AsNeeded
property bool preventHorizontalScroll: horizontalPolicy === ScrollBar.AlwaysOff
property int boundsBehavior: Flickable.StopAtBounds
property int flickableDirection: Flickable.VerticalFlick
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding)
// Configure the internal flickable when it becomes available
Component.onCompleted: {
configureFlickable()
}
// Function to configure the underlying Flickable
function configureFlickable() {
// Find the internal Flickable (it's usually the first child)
for (var i = 0; i < children.length; i++) {
var child = children[i]
if (child.toString().indexOf("Flickable") !== -1) {
// Configure the flickable to prevent horizontal scrolling
child.boundsBehavior = root.boundsBehavior
if (root.preventHorizontalScroll) {
child.flickableDirection = Flickable.VerticalFlick
child.contentWidth = Qt.binding(() => child.width)
} else {
child.flickableDirection = root.flickableDirection
}
break
}
}
}
// Watch for changes in horizontalPolicy
onHorizontalPolicyChanged: {
preventHorizontalScroll = (horizontalPolicy === ScrollBar.AlwaysOff)
configureFlickable()
}
ScrollBar.vertical: ScrollBar {
parent: root
x: root.mirrored ? 0 : root.width - width