mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Keybinds: adjust layout, reject duplicate keybinds
This commit is contained in:
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Monospace-Schriftarten suchen...",
|
||||
"fonts-reset-scaling": "Skalierung zurücksetzen",
|
||||
"fonts-title": "Schriftarten",
|
||||
"keybinds-conflict-description": "Die Tastenkombination ist bereits {action} zugewiesen.",
|
||||
"keybinds-conflict-title": "Tastenbelegungskonflikt",
|
||||
"keybinds-description": "Globale Navigationstasten für Panels und Launcher konfigurieren.",
|
||||
"keybinds-down": "Nach unten verschieben",
|
||||
"keybinds-enter": "Bestätigen / Aktion",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Search monospace font...",
|
||||
"fonts-reset-scaling": "Reset scaling",
|
||||
"fonts-title": "Fonts",
|
||||
"keybinds-conflict-description": "The key combination is already assigned to {action}.",
|
||||
"keybinds-conflict-title": "Keybind Conflict",
|
||||
"keybinds-description": "Configure global navigation keys for panels and launcher.",
|
||||
"keybinds-down": "Move Down",
|
||||
"keybinds-enter": "Confirm / Action",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Buscar fuentes monoespaciadas...",
|
||||
"fonts-reset-scaling": "Restablecer la escala",
|
||||
"fonts-title": "Fuentes",
|
||||
"keybinds-conflict-description": "La combinación de teclas ya está asignada a {action}.",
|
||||
"keybinds-conflict-title": "Conflicto de Atajos de Teclado",
|
||||
"keybinds-description": "Configura las teclas de navegación globales para paneles y el lanzador.",
|
||||
"keybinds-down": "Mover hacia abajo",
|
||||
"keybinds-enter": "Confirmar / Acción",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Rechercher des polices à chasse fixe...",
|
||||
"fonts-reset-scaling": "Réinitialiser l'échelle",
|
||||
"fonts-title": "Polices",
|
||||
"keybinds-conflict-description": "La combinaison de touches est déjà attribuée à {action}.",
|
||||
"keybinds-conflict-title": "Conflit de Raccourcis Clavier",
|
||||
"keybinds-description": "Configurez les touches de navigation globales pour les panneaux et le lanceur.",
|
||||
"keybinds-down": "Déplacer vers le bas",
|
||||
"keybinds-enter": "Confirmer / Action",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Monospace betűtípus keresése...",
|
||||
"fonts-reset-scaling": "Méretezés visszaállítása",
|
||||
"fonts-title": "Betűtípusok",
|
||||
"keybinds-conflict-description": "A billentyűkombináció már hozzá van rendelve ehhez: {action}.",
|
||||
"keybinds-conflict-title": "Billentyűkombináció Ütközés",
|
||||
"keybinds-description": "Globális navigációs billentyűk beállítása a panelekhez és az indítóhoz.",
|
||||
"keybinds-down": "Lejjebb mozgatás",
|
||||
"keybinds-enter": "Megerősítés / Művelet",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "等幅フォントを検索...",
|
||||
"fonts-reset-scaling": "サイズ設定をリセット",
|
||||
"fonts-title": "フォント",
|
||||
"keybinds-conflict-description": "このキーの組み合わせはすでに{action}に割り当てられています。",
|
||||
"keybinds-conflict-title": "キーバインドの競合",
|
||||
"keybinds-description": "パネルとランチャーのグローバルナビゲーションキーを設定します。",
|
||||
"keybinds-down": "下に移動",
|
||||
"keybinds-enter": "確認 / アクション",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "고정폭 글꼴 검색...",
|
||||
"fonts-reset-scaling": "크기 초기화",
|
||||
"fonts-title": "글꼴",
|
||||
"keybinds-conflict-description": "이 키 조합은 이미 {action}에 할당되어 있습니다.",
|
||||
"keybinds-conflict-title": "키 바인딩 충돌",
|
||||
"keybinds-description": "패널 및 런처에 대한 전역 탐색 키를 구성합니다.",
|
||||
"keybinds-down": "아래로 이동",
|
||||
"keybinds-enter": "확인 / 동작",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Monospace-lettertype zoeken...",
|
||||
"fonts-reset-scaling": "Schaling resetten",
|
||||
"fonts-title": "Lettertypes",
|
||||
"keybinds-conflict-description": "De toetscombinatie is al toegewezen aan {action}.",
|
||||
"keybinds-conflict-title": "Toetsencombinatieconflict",
|
||||
"keybinds-description": "Globale navigatietoetsen voor panelen en de starter configureren.",
|
||||
"keybinds-down": "Naar beneden verplaatsen",
|
||||
"keybinds-enter": "Bevestigen / Actie",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Szukaj czcionki monospace...",
|
||||
"fonts-reset-scaling": "Resetuj skalowanie",
|
||||
"fonts-title": "Czcionki",
|
||||
"keybinds-conflict-description": "Kombinacja klawiszy jest już przypisana do {action}.",
|
||||
"keybinds-conflict-title": "Konflikt Skrótów Klawiszowych",
|
||||
"keybinds-description": "Skonfiguruj globalne klawisze nawigacyjne dla paneli i launchera.",
|
||||
"keybinds-down": "Przenieś w dół",
|
||||
"keybinds-enter": "Potwierdź / Akcja",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Pesquisar fontes monoespaçadas...",
|
||||
"fonts-reset-scaling": "Redefinir escala",
|
||||
"fonts-title": "Fontes",
|
||||
"keybinds-conflict-description": "A combinação de teclas já está atribuída a {action}.",
|
||||
"keybinds-conflict-title": "Conflito de Atalhos de Teclado",
|
||||
"keybinds-description": "Configure as teclas de navegação globais para painéis e o lançador.",
|
||||
"keybinds-down": "Mover para baixo",
|
||||
"keybinds-enter": "Confirmar / Ação",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Поиск моноширинного шрифта...",
|
||||
"fonts-reset-scaling": "Сбросить масштабирование",
|
||||
"fonts-title": "Шрифты",
|
||||
"keybinds-conflict-description": "Комбинация клавиш уже назначена для {action}.",
|
||||
"keybinds-conflict-title": "Конфликт Горячих Клавиш",
|
||||
"keybinds-description": "Настройте глобальные клавиши навигации для панелей и лаунчера.",
|
||||
"keybinds-down": "Переместить вниз",
|
||||
"keybinds-enter": "Подтвердить / Действие",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Sök teckensnitt med fast bredd...",
|
||||
"fonts-reset-scaling": "Återställ skalning",
|
||||
"fonts-title": "Teckensnitt",
|
||||
"keybinds-conflict-description": "Tangentkombinationen är redan tilldelad {action}.",
|
||||
"keybinds-conflict-title": "Tangentbindningskonflikt",
|
||||
"keybinds-description": "Konfigurera globala navigeringsknappar för paneler och startprogram.",
|
||||
"keybinds-down": "Flytta nedåt",
|
||||
"keybinds-enter": "Bekräfta / Åtgärd",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Eş aralıklı yazı tipi ara...",
|
||||
"fonts-reset-scaling": "Ölçeklemeyi sıfırla",
|
||||
"fonts-title": "Yazı tipleri",
|
||||
"keybinds-conflict-description": "Tuş kombinasyonu zaten {action} için atanmış.",
|
||||
"keybinds-conflict-title": "Tuş Ataması Çakışması",
|
||||
"keybinds-description": "Paneller ve başlatıcı için genel navigasyon tuşlarını yapılandırın.",
|
||||
"keybinds-down": "Aşağı Taşı",
|
||||
"keybinds-enter": "Onayla / Eylem",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "Пошук моноширинного шрифту...",
|
||||
"fonts-reset-scaling": "Скинути масштаб",
|
||||
"fonts-title": "Шрифти",
|
||||
"keybinds-conflict-description": "Комбінація клавіш вже призначена для {action}.",
|
||||
"keybinds-conflict-title": "Конфлікт Гарячих Клавіш",
|
||||
"keybinds-description": "Налаштуйте глобальні клавіші навігації для панелей та запускача.",
|
||||
"keybinds-down": "Перемістити вниз",
|
||||
"keybinds-enter": "Підтвердити / Дія",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "搜索等宽字体...",
|
||||
"fonts-reset-scaling": "恢复默认缩放",
|
||||
"fonts-title": "字体",
|
||||
"keybinds-conflict-description": "此按键组合已分配给 {action}。",
|
||||
"keybinds-conflict-title": "按键绑定冲突",
|
||||
"keybinds-description": "配置面板和启动器的全局导航键。",
|
||||
"keybinds-down": "下移",
|
||||
"keybinds-enter": "确认 / 操作",
|
||||
|
||||
@@ -1030,6 +1030,8 @@
|
||||
"fonts-monospace-search-placeholder": "搜尋等寬字型...",
|
||||
"fonts-reset-scaling": "重設文字大小",
|
||||
"fonts-title": "字型",
|
||||
"keybinds-conflict-description": "此按鍵組合已分配給 {action}。",
|
||||
"keybinds-conflict-title": "按鍵綁定衝突",
|
||||
"keybinds-description": "配置面板和啟動器的全域導航鍵。",
|
||||
"keybinds-down": "下移",
|
||||
"keybinds-enter": "確認 / 動作",
|
||||
|
||||
@@ -109,3 +109,62 @@ function checkKey(event, settingName, settings) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a keybind string conflicts with any other existing keybinds.
|
||||
* @param {string} keyStr - The keybind string to check (e.g., "Ctrl+A").
|
||||
* @param {string} currentPath - The settings path of the keybind being edited (to skip checking itself).
|
||||
* @param {object} data - The settings data object (from Settings.data).
|
||||
* @returns {string|null} - The name of the conflicting action, or null if no conflict.
|
||||
*/
|
||||
function getKeybindConflict(keyStr, currentPath, data) {
|
||||
if (!keyStr || !data) return null;
|
||||
|
||||
const searchKey = String(keyStr).trim().toLowerCase();
|
||||
|
||||
// 1. Check navigation keybinds
|
||||
const navKeybinds = data.general ? data.general.keybinds : null;
|
||||
const navMap = {
|
||||
"keyUp": "Navigation: Up",
|
||||
"keyDown": "Navigation: Down",
|
||||
"keyLeft": "Navigation: Left",
|
||||
"keyRight": "Navigation: Right",
|
||||
"keyEnter": "Navigation: Enter",
|
||||
"keyEscape": "Navigation: Escape"
|
||||
};
|
||||
|
||||
if (navKeybinds) {
|
||||
for (const prop in navMap) {
|
||||
const fullPath = "general.keybinds." + prop;
|
||||
if (fullPath === currentPath) continue;
|
||||
|
||||
const boundKeys = navKeybinds[prop];
|
||||
if (boundKeys && boundKeys.length !== undefined) {
|
||||
for (let i = 0; i < boundKeys.length; i++) {
|
||||
if (String(boundKeys[i]).trim().toLowerCase() === searchKey) {
|
||||
return navMap[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check session menu power options
|
||||
const sessionMenu = data.sessionMenu;
|
||||
if (sessionMenu && sessionMenu.powerOptions) {
|
||||
const powerOptions = sessionMenu.powerOptions;
|
||||
for (let i = 0; i < powerOptions.length; i++) {
|
||||
const entry = powerOptions[i];
|
||||
const fullPath = "sessionMenu.powerOptions[" + i + "].keybind";
|
||||
if (fullPath === currentPath) continue;
|
||||
|
||||
if (entry.keybind && String(entry.keybind).trim().toLowerCase() === searchKey) {
|
||||
// Capitalize action name
|
||||
const actionName = entry.action ? entry.action.charAt(0).toUpperCase() + entry.action.slice(1) : "Unknown";
|
||||
return "Session Menu: " + actionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ ColumnLayout {
|
||||
label: I18n.tr("panels.general.keybinds-up")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyUp
|
||||
defaultKeybind: "Up"
|
||||
settingsPath: "general.keybinds.keyUp"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyUp = newKeybinds
|
||||
}
|
||||
|
||||
@@ -29,6 +30,7 @@ ColumnLayout {
|
||||
label: I18n.tr("panels.general.keybinds-down")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyDown
|
||||
defaultKeybind: "Down"
|
||||
settingsPath: "general.keybinds.keyDown"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyDown = newKeybinds
|
||||
}
|
||||
|
||||
@@ -37,6 +39,7 @@ ColumnLayout {
|
||||
label: I18n.tr("panels.general.keybinds-left")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyLeft
|
||||
defaultKeybind: "Left"
|
||||
settingsPath: "general.keybinds.keyLeft"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyLeft = newKeybinds
|
||||
}
|
||||
|
||||
@@ -45,6 +48,7 @@ ColumnLayout {
|
||||
label: I18n.tr("panels.general.keybinds-right")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyRight
|
||||
defaultKeybind: "Right"
|
||||
settingsPath: "general.keybinds.keyRight"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyRight = newKeybinds
|
||||
}
|
||||
|
||||
@@ -53,6 +57,7 @@ ColumnLayout {
|
||||
label: I18n.tr("panels.general.keybinds-enter")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyEnter
|
||||
defaultKeybind: "Return"
|
||||
settingsPath: "general.keybinds.keyEnter"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyEnter = newKeybinds
|
||||
}
|
||||
|
||||
@@ -61,6 +66,7 @@ ColumnLayout {
|
||||
label: I18n.tr("panels.general.keybinds-escape")
|
||||
currentKeybinds: Settings.data.general.keybinds.keyEscape
|
||||
defaultKeybind: "Esc"
|
||||
settingsPath: "general.keybinds.keyEscape"
|
||||
onKeybindsChanged: newKeybinds => Settings.data.general.keybinds.keyEscape = newKeybinds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +163,7 @@ Popup {
|
||||
allowEmpty: true
|
||||
maxKeybinds: 1
|
||||
currentKeybinds: keybindInputText ? [keybindInputText] : []
|
||||
settingsPath: "sessionMenu.powerOptions[" + root.entryIndex + "].keybind"
|
||||
onKeybindsChanged: newKeybinds => {
|
||||
keybindInputText = newKeybinds.length > 0 ? newKeybinds[0] : "";
|
||||
root.save();
|
||||
|
||||
@@ -4,6 +4,7 @@ import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
import qs.Widgets
|
||||
import "../Helpers/Keybinds.js" as Keybinds
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -15,6 +16,7 @@ Item {
|
||||
property bool allowEmpty: false
|
||||
property color labelColor: Color.mOnSurface
|
||||
property color descriptionColor: Color.mOnSurfaceVariant
|
||||
property string settingsPath: ""
|
||||
|
||||
property int maxKeybinds: 2
|
||||
signal keybindsChanged(var newKeybinds)
|
||||
@@ -23,12 +25,39 @@ Item {
|
||||
|
||||
// -1 = not recording, >= 0 = re-recording at index, -2 = adding new
|
||||
property int recordingIndex: -1
|
||||
property bool hasConflict: false
|
||||
|
||||
onRecordingIndexChanged: PanelService.isKeybindRecording = recordingIndex !== -1
|
||||
onRecordingIndexChanged: {
|
||||
PanelService.isKeybindRecording = recordingIndex !== -1;
|
||||
if (recordingIndex !== -1) {
|
||||
hasConflict = false;
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real _pillHeight: Style.baseWidgetSize * 1.1 * Style.uiScaleRatio
|
||||
|
||||
function _applyKeybind(keyStr) {
|
||||
if (!keyStr) return;
|
||||
|
||||
// 1. Internal duplicate check (same action)
|
||||
for (let i = 0; i < root.currentKeybinds.length; i++) {
|
||||
if (i !== root.recordingIndex && String(root.currentKeybinds[i]).toLowerCase() === keyStr.toLowerCase()) {
|
||||
hasConflict = true;
|
||||
ToastService.showWarning(I18n.tr("panels.general.keybinds-conflict-title"), I18n.tr("panels.general.keybinds-conflict-description", { "action": root.label || "This action" }));
|
||||
conflictTimer.restart();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. External conflict check (other actions)
|
||||
const conflict = Keybinds.getKeybindConflict(keyStr, root.settingsPath, Settings.data);
|
||||
if (conflict) {
|
||||
hasConflict = true;
|
||||
ToastService.showWarning(I18n.tr("panels.general.keybinds-conflict-title"), I18n.tr("panels.general.keybinds-conflict-description", { "action": conflict }));
|
||||
conflictTimer.restart();
|
||||
return;
|
||||
}
|
||||
|
||||
var newKeybinds = Array.from(root.currentKeybinds);
|
||||
if (recordingIndex >= 0) {
|
||||
newKeybinds[recordingIndex] = keyStr;
|
||||
@@ -39,6 +68,15 @@ Item {
|
||||
root.keybindsChanged(newKeybinds);
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: conflictTimer
|
||||
interval: 2000
|
||||
onTriggered: {
|
||||
hasConflict = false;
|
||||
recordingIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: contentLayout
|
||||
width: parent.width
|
||||
@@ -87,8 +125,8 @@ Item {
|
||||
id: slotBg
|
||||
anchors.fill: parent
|
||||
radius: Style.iRadiusS
|
||||
color: slotArea.isRecordingThis ? Color.mSecondary : (slotArea.containsMouse ? Qt.alpha(Color.mSecondary, 0.15) : Color.mSurface)
|
||||
border.color: slotArea.isRecordingThis ? Color.mPrimary : (slotArea.containsMouse ? Color.mSecondary : Color.mOutline)
|
||||
color: root.hasConflict && slotArea.isRecordingThis ? Color.mError : (slotArea.isRecordingThis ? Color.mSecondary : (slotArea.containsMouse ? Qt.alpha(Color.mSecondary, 0.15) : Color.mSurface))
|
||||
border.color: root.hasConflict && slotArea.isRecordingThis ? Color.mError : (slotArea.isRecordingThis ? Color.mPrimary : (slotArea.containsMouse ? Color.mSecondary : Color.mOutline))
|
||||
border.width: Style.borderS
|
||||
|
||||
Behavior on color {
|
||||
@@ -109,10 +147,10 @@ Item {
|
||||
spacing: Style.marginXS
|
||||
|
||||
NIcon {
|
||||
icon: slotArea.isRecordingThis ? "circle-dot" : "keyboard"
|
||||
icon: root.hasConflict && slotArea.isRecordingThis ? "alert-circle" : (slotArea.isRecordingThis ? "circle-dot" : "keyboard")
|
||||
color: slotArea.isRecordingThis ? Color.mOnSecondary : (slotArea.isOccupied ? Color.mOnSurfaceVariant : Qt.alpha(Color.mOnSurfaceVariant, 0.4))
|
||||
opacity: 0.8
|
||||
visible: !slotArea.isRecordingThis
|
||||
visible: !slotArea.isRecordingThis || root.hasConflict
|
||||
}
|
||||
|
||||
NText {
|
||||
@@ -129,7 +167,7 @@ Item {
|
||||
Item {
|
||||
Layout.preferredWidth: Math.round(root._pillHeight * 0.7)
|
||||
Layout.fillHeight: true
|
||||
visible: slotArea.isOccupied
|
||||
visible: slotArea.isOccupied && root.recordingIndex === -1
|
||||
|
||||
NIconButton {
|
||||
anchors.centerIn: parent
|
||||
@@ -162,7 +200,7 @@ Item {
|
||||
focus: true
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (root.recordingIndex === -1)
|
||||
if (root.recordingIndex === -1 || root.hasConflict)
|
||||
return;
|
||||
|
||||
// Handle Escape specifically to ensure it doesn't close the panel
|
||||
|
||||
Reference in New Issue
Block a user