Skip to content

Commit

Permalink
Merge pull request #5829 from shoogle/qml-palette-mnemonics
Browse files Browse the repository at this point in the history
Palettes: Implement Type-Ahead Find with incremental search
  • Loading branch information
anatoly-os authored Apr 29, 2020
2 parents 81662a2 + 937e7f5 commit 84729e8
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 17 deletions.
93 changes: 86 additions & 7 deletions mscore/qml/palettes/Palette.qml
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,20 @@ GridView {

onClicked: paletteView.moreButtonClicked()

Keys.onShortcutOverride: {
// Intercept all keys that we want to use with Keys.onPressed
// in case they are assigned as shortcuts in Preferences.
event.accepted = true; // intercept everything
switch (event.key) {
case Qt.Key_Up:
case Qt.Key_Down:
return;
}
event.accepted = false; // allow key to function as shortcut (don't intercept)
}

Keys.onPressed: {
// NOTE: All keys must be intercepted with Keys.onShortcutOverride.
switch (event.key) {
case Qt.Key_Up:
focusPreviousItem();
Expand Down Expand Up @@ -409,14 +422,30 @@ GridView {
}
}

function focusNextMatchingItem(str) {
const nextIndex = (currentIndex === count - 1) ? 0 : currentIndex + 1;
const modelIndex = paletteModel.index(nextIndex, 0, paletteRootIndex);
function focusNextMatchingItem(str, startIndex) {
const modelIndex = paletteModel.index(startIndex, 0, paletteRootIndex);
const matchedIndexList = paletteModel.match(modelIndex, Qt.ToolTipRole, str);
if (matchedIndexList.length) {
currentIndex = matchedIndexList[0].row;
currentItem.forceActiveFocus();
return true;
}
return false;
}

function typeAheadFind(chr) {
if (paletteTree.typeAheadStr.length) {
// continue search on current item
const sameChr = chr === paletteTree.typeAheadStr;
paletteTree.typeAheadStr += chr;
const found = focusNextMatchingItem(paletteTree.typeAheadStr, currentIndex);
if (found || !sameChr)
return;
}
// start new search on next item
paletteTree.typeAheadStr = chr;
const nextIndex = (currentIndex === count - 1) ? 0 : currentIndex + 1;
focusNextMatchingItem(chr, nextIndex);
}

function updateSelection(itemPressed) {
Expand All @@ -439,7 +468,24 @@ GridView {
selectionModel.setCurrentIndex(currentItem.modelIndex, ItemSelectionModel.Select);
}

Keys.onShortcutOverride: {
// Intercept all keys that we want to use with Keys.onPressed
// in case they are assigned as shortcuts in Preferences.
event.accepted = true; // intercept everything
switch (event.key) {
case Qt.Key_Up:
case Qt.Key_Down:
case Qt.Key_Left:
case Qt.Key_Right:
case Qt.Key_Backspace:
case Qt.Key_Delete:
return;
}
event.accepted = false; // allow key to function as shortcut (don't intercept)
}

Keys.onPressed: {
// NOTE: All keys must be intercepted with Keys.onShortcutOverride.
switch (event.key) {
case Qt.Key_Up:
focusPreviousItem();
Expand Down Expand Up @@ -566,12 +612,35 @@ GridView {
Accessible.selectable: true;
Accessible.selected: selected;

Keys.onShortcutOverride: {
// Intercept all keys that we want to use with Keys.onPressed
// in case they are assigned as shortcuts in Preferences.
event.accepted = true; // intercept everything
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Menu:
case Qt.Key_Asterisk:
return;
}
if (event.key === Qt.Key_F10 && event.modifiers & Qt.ShiftModifier)
return;
if (event.text.match(/[^\x00-\x20\x7F]+$/) !== null)
return;
event.accepted = false; // allow key to function as shortcut (don't intercept)
}

Keys.onPressed: {
// NOTE: All keys must be intercepted with Keys.onShortcutOverride.
const shiftHeld = event.modifiers & Qt.ShiftModifier;
const ctrlHeld = event.modifiers & Qt.ControlModifier;
switch (event.key) {
case Qt.Key_Space:
paletteView.updateSelection(true);
if (paletteTree.typeAheadStr.length)
paletteView.typeAheadFind(' ');
else
paletteView.updateSelection(true);
break;
case Qt.Key_Enter:
case Qt.Key_Return:
Expand All @@ -585,11 +654,21 @@ GridView {
case Qt.Key_Menu:
showCellMenu();
break;
case Qt.Key_Asterisk:
if (paletteTree.typeAheadStr.length)
paletteView.typeAheadFind('*');
else if (!paletteTree.expandCollapseAll(null))
paletteTree.currentItem.forceActiveFocus();
break;
default:
if (event.modifiers === Qt.NoModifier && event.text.match(/\w/) !== null)
paletteView.focusNextMatchingItem(event.text);
else
if (event.text.match(/[^\x00-\x20\x7F]+$/) !== null) {
// Pressed non-control character(s) (e.g. "D") so go
// to matching item (e.g. "D Major" in keysig palette)
paletteView.typeAheadFind(event.text);
}
else {
return; // don't accept event
}
}
event.accepted = true;
}
Expand Down
107 changes: 97 additions & 10 deletions mscore/qml/palettes/PaletteTree.qml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,26 @@ ListView {
Utils.removeSelectedItems(paletteController, paletteSelectionModel, parentIndex);
}

Keys.onShortcutOverride: {
// Intercept all keys that we want to use with Keys.onPressed
// in case they are assigned as shortcuts in Preferences.
event.accepted = true; // intercept everything
switch (event.key) {
case Qt.Key_Down:
case Qt.Key_Up:
case Qt.Key_Home:
case Qt.Key_End:
case Qt.Key_PageUp:
case Qt.Key_PageDown:
case Qt.Key_Backspace:
case Qt.Key_Delete:
return;
}
event.accepted = false; // allow key to function as shortcut (don't intercept)
}

Keys.onPressed: {
// NOTE: All keys must be intercepted with Keys.onShortcutOverride.
switch (event.key) {
case Qt.Key_Down:
focusNextItem();
Expand All @@ -139,9 +158,6 @@ ListView {
expandedPopupIndex = null;
removeSelectedItems();
break;
case Qt.Key_Asterisk:
expandCollapseAll(null);
break;
default:
return; // don't accept event
}
Expand Down Expand Up @@ -258,15 +274,42 @@ ListView {
positionViewAtIndex(currentIndex, ListView.Contain);
}

function focusNextMatchingItem(str) {
const nextIndex = (currentIndex < count - 1) ? currentIndex + 1 : 0;
const modelIndex = paletteModel.index(nextIndex, 0);
function focusNextMatchingItem(str, startIndex) {
const modelIndex = paletteModel.index(startIndex, 0);
const matchedIndexList = paletteModel.match(modelIndex, Qt.ToolTipRole, str);
if (matchedIndexList.length) {
currentIndex = matchedIndexList[0].row;
currentItem.forceActiveFocus();
positionViewAtIndex(currentIndex, ListView.Contain);
return true;
}
return false;
}

function typeAheadFind(chr) {
if (typeAheadStr.length) {
// continue search on current item
const sameChr = chr === typeAheadStr;
typeAheadStr += chr;
const found = focusNextMatchingItem(typeAheadStr, currentIndex);
if (found || !sameChr)
return;
}
// start new search on next item
typeAheadStr = chr;
const nextIndex = (currentIndex === count - 1) ? 0 : currentIndex + 1;
focusNextMatchingItem(chr, nextIndex);
}

property string typeAheadStr: ''
Timer {
id: typeAheadTimer
interval: 1000
onTriggered: typeAheadStr = ''
}
onTypeAheadStrChanged: {
if (typeAheadStr.length)
typeAheadTimer.restart();
}

function expandCollapseAll(expand) {
Expand All @@ -284,6 +327,8 @@ ListView {
const paletteIndex = paletteModel.index(idx, 0);
paletteModel.setData(paletteIndex, expand, PaletteTreeModel.PaletteExpandedRole);
}
currentItem.bringIntoViewAfterExpanding();
return expand; // bool, did we expand?
}

function getTintedColor(baseColor, tintColor, opacity) {
Expand Down Expand Up @@ -331,14 +376,17 @@ ListView {
function toggleExpand() {
model.expanded = !expanded
}
function bringIntoViewAfterExpanding() {
expandTimer.restart();
}
Timer {
id: expandTimer
interval: expandDuration + 50 // allow extra grace period
onTriggered: paletteTree.positionViewAtIndex(index, ListView.Contain)
}
onExpandedChanged: {
if (ListView.isCurrentItem && !filter.length)
expandTimer.restart();
bringIntoViewAfterExpanding();
}

property bool selected: paletteSelectionModel.hasSelection ? paletteSelectionModel.isSelected(modelIndex) : false
Expand Down Expand Up @@ -378,7 +426,31 @@ ListView {
paletteTree.paletteController.remove(modelIndex);
}

Keys.onShortcutOverride: {
// Intercept all keys that we want to use with Keys.onPressed
// in case they are assigned as shortcuts in Preferences.
event.accepted = true; // intercept everything
switch (event.key) {
case Qt.Key_Right:
case Qt.Key_Plus:
case Qt.Key_Left:
case Qt.Key_Minus:
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Menu:
case Qt.Key_Asterisk:
return;
}
if (event.key === Qt.Key_F10 && event.modifiers & Qt.ShiftModifier)
return;
if (event.text.match(/[^\x00-\x20\x7F]+$/) !== null)
return;
event.accepted = false; // allow key to function as shortcut (don't intercept)
}

Keys.onPressed: {
// NOTE: All keys must be intercepted with Keys.onShortcutOverride.
switch (event.key) {
case Qt.Key_Right:
case Qt.Key_Plus:
Expand All @@ -393,6 +465,11 @@ ListView {
toggleExpand();
break;
case Qt.Key_Space:
if (paletteTree.typeAheadStr.length) {
paletteTree.typeAheadFind(' ');
break;
}
// fallthrough
case Qt.Key_Enter:
case Qt.Key_Return:
toggleExpand();
Expand All @@ -404,11 +481,21 @@ ListView {
case Qt.Key_Menu:
paletteHeader.showPaletteMenu();
break;
default:
if (event.modifiers === Qt.NoModifier && event.text.match(/\w/) !== null)
paletteTree.focusNextMatchingItem(event.text);
case Qt.Key_Asterisk:
if (paletteTree.typeAheadStr.length)
paletteTree.typeAheadFind('*');
else
paletteTree.expandCollapseAll(null);
break;
default:
if (event.text.match(/[^\x00-\x20\x7F]+$/) !== null) {
// Pressed non-control character(s) (e.g. "L")
// so go to matching palette (e.g. "Lines")
paletteTree.typeAheadFind(event.text);
}
else {
return; // don't accept event
}
}
event.accepted = true;
}
Expand Down

0 comments on commit 84729e8

Please sign in to comment.