added key navigation for palette panel

This commit is contained in:
Igor Korsukov 2021-04-09 18:45:39 +02:00
parent b732a3f81a
commit 1b73eed0ae
24 changed files with 493 additions and 665 deletions

View file

@ -158,7 +158,11 @@ DockPage {
Component { Component {
id: keynavComp id: keynavComp
KeyNavExample{} KeyNavExample {
onActiveFocusRequested: {
devtoolsCentral.forceActiveFocus()
}
}
} }
} }

View file

@ -10,6 +10,8 @@ Rectangle {
property string lastClickedInfo: "" property string lastClickedInfo: ""
signal activeFocusRequested()
Rectangle { Rectangle {
id: infoPanel id: infoPanel
@ -36,6 +38,13 @@ Rectangle {
sectionName: "mainMenu" sectionName: "mainMenu"
sectionOrder: 101 sectionOrder: 101
onActiveChanged: {
if (active) {
root.activeFocusRequested()
root.forceActiveFocus()
}
}
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
spacing: 8 spacing: 8

View file

@ -9,6 +9,7 @@ Rectangle {
property alias keynavSection: keynavsec property alias keynavSection: keynavsec
property alias sectionName: keynavsec.name property alias sectionName: keynavsec.name
property alias sectionOrder: keynavsec.order property alias sectionOrder: keynavsec.order
property alias active: keynavsec.active
default property alias content: contentItem.data default property alias content: contentItem.data
@ -20,14 +21,8 @@ Rectangle {
onActiveChanged: { onActiveChanged: {
console.debug("KeyNavSection.qml active: " + keynavsec.active) console.debug("KeyNavSection.qml active: " + keynavsec.active)
if (keynavsec.active) { if (keynavsec.active) {
root.forceActiveFocus()
var p = root.parent
while (p) {
p.focus = true
p = p.parent
}
root.focus = true
} }
} }
} }

View file

@ -133,12 +133,20 @@ DockPage {
} }
property KeyNavigationControl keynavTab: KeyNavigationControl { property KeyNavigationControl keynavTab: KeyNavigationControl {
name: "PanelTab" name: "PaletteTab"
order: 1 //! TODO Needs order from DockPanel order: 1 //! TODO Needs order from DockPanel
subsection: notationPage.keynavPanelTabSubSec(palettePanel.area) subsection: notationPage.keynavPanelTabSubSec(palettePanel.area)
onActiveChanged: {
if (active) {
palettePanel.forceActiveFocus()
}
}
} }
PalettesWidget {} PalettesWidget {
anchors.fill: parent
keynavSection: notationPage.keynavPanelSec(palettePanel.area)
}
}, },
DockPanel { DockPanel {
@ -167,11 +175,17 @@ DockPage {
name: "InstrumentsTab" name: "InstrumentsTab"
order: 2 //! TODO Needs order from DockPanel order: 2 //! TODO Needs order from DockPanel
subsection: notationPage.keynavPanelTabSubSec(instrumentsPanel.area) subsection: notationPage.keynavPanelTabSubSec(instrumentsPanel.area)
onActiveChanged: {
if (active) {
instrumentsPanel.forceActiveFocus()
}
}
} }
InstrumentsPanel { InstrumentsPanel {
anchors.fill: parent anchors.fill: parent
keynavSection: notationPage.keynavPanelSec(instrumentsPanel.area) keynavSection: notationPage.keynavPanelSec(instrumentsPanel.area)
visible: instrumentsPanel.isShown
} }
}, },
@ -201,6 +215,11 @@ DockPage {
name: "InspectorTab" name: "InspectorTab"
order: 3 //! TODO Needs order from DockPanel order: 3 //! TODO Needs order from DockPanel
subsection: notationPage.keynavPanelTabSubSec(inspectorPanel.area) subsection: notationPage.keynavPanelTabSubSec(inspectorPanel.area)
onActiveChanged: {
if (active) {
inspectorPanel.forceActiveFocus()
}
}
} }
InspectorForm { InspectorForm {

View file

@ -81,6 +81,7 @@ DockWindow {
toolbars: [ toolbars: [
DockToolBar { DockToolBar {
id: mainToolBar
objectName: "mainToolBar" objectName: "mainToolBar"
minimumWidth: 296 minimumWidth: 296
minimumHeight: dockWindow.toolbarHeight minimumHeight: dockWindow.toolbarHeight
@ -96,6 +97,12 @@ DockWindow {
keynav.order: 1 keynav.order: 1
currentUri: dockWindow.currentPageUri currentUri: dockWindow.currentPageUri
keynav.onActiveChanged: {
if (keynav.active) {
mainToolBar.forceActiveFocus()
}
}
onSelected: { onSelected: {
api.launcher.open(uri) api.launcher.open(uri)
} }
@ -120,6 +127,11 @@ DockWindow {
keynav.section: topToolKeyNavSec keynav.section: topToolKeyNavSec
keynav.order: 2 keynav.order: 2
keynav.enabled: notationToolBar.visible keynav.enabled: notationToolBar.visible
onActiveFocusRequested: {
if (keynav.active) {
notationToolBar.forceActiveFocus()
}
}
Connections { Connections {
target: notationToolBar target: notationToolBar

View file

@ -37,9 +37,17 @@ DockPanel::DockPanel(QQuickItem* parent)
connect(this, &DockPanel::visibleEdited, this, [this](bool visible) { connect(this, &DockPanel::visibleEdited, this, [this](bool visible) {
if (m_dock.panel->isVisible() != visible) { if (m_dock.panel->isVisible() != visible) {
m_dock.panel->setVisible(visible); m_dock.panel->setVisible(visible);
if (!visible) {
m_isShown = false;
}
} }
}); });
connect(m_dock.panel, &QDockWidget::visibilityChanged, this, [this](bool vsbl) {
m_isShown = vsbl;
emit isShownChanged(vsbl);
});
m_eventsWatcher = new EventsWatcher(this); m_eventsWatcher = new EventsWatcher(this);
m_dock.panel->installEventFilter(m_eventsWatcher); m_dock.panel->installEventFilter(m_eventsWatcher);
connect(m_eventsWatcher, &EventsWatcher::eventReceived, this, &DockPanel::onWidgetEvent); connect(m_eventsWatcher, &EventsWatcher::eventReceived, this, &DockPanel::onWidgetEvent);
@ -58,6 +66,7 @@ void DockPanel::onComponentCompleted()
updateStyle(); updateStyle();
m_preferedWidth = width(); m_preferedWidth = width();
m_isShown = panel()->isVisible();
if (minimumWidth() == 0) { if (minimumWidth() == 0) {
panel()->setMinimumWidth(width()); panel()->setMinimumWidth(width());
@ -161,6 +170,11 @@ bool DockPanel::closable() const
return featureEnabled(QDockWidget::DockWidgetClosable); return featureEnabled(QDockWidget::DockWidgetClosable);
} }
bool DockPanel::isShown() const
{
return m_isShown;
}
bool DockPanel::visible() const bool DockPanel::visible() const
{ {
return m_dock.panel ? m_dock.panel->isVisible() : false; return m_dock.panel ? m_dock.panel->isVisible() : false;

View file

@ -36,6 +36,8 @@ class DockPanel : public DockView
Q_PROPERTY(bool floatable READ floatable WRITE setFloatable NOTIFY floatableChanged) Q_PROPERTY(bool floatable READ floatable WRITE setFloatable NOTIFY floatableChanged)
Q_PROPERTY(bool closable READ closable WRITE setClosable NOTIFY closableChanged) Q_PROPERTY(bool closable READ closable WRITE setClosable NOTIFY closableChanged)
Q_PROPERTY(bool isShown READ isShown NOTIFY isShownChanged)
public: public:
explicit DockPanel(QQuickItem* parent = nullptr); explicit DockPanel(QQuickItem* parent = nullptr);
~DockPanel() override; ~DockPanel() override;
@ -49,6 +51,7 @@ public:
bool floatable() const; bool floatable() const;
bool closable() const; bool closable() const;
bool isShown() const;
bool visible() const override; bool visible() const override;
struct Widget { struct Widget {
@ -78,6 +81,7 @@ signals:
void floatableChanged(bool floatable); void floatableChanged(bool floatable);
void closableChanged(bool closable); void closableChanged(bool closable);
void closed(); void closed();
void isShownChanged(bool isShown);
protected: protected:
void onComponentCompleted() override; void onComponentCompleted() override;
@ -94,6 +98,7 @@ private:
EventsWatcher* m_eventsWatcher = nullptr; EventsWatcher* m_eventsWatcher = nullptr;
int m_preferedWidth = 0; int m_preferedWidth = 0;
bool m_isShown = false;
}; };
} }

View file

@ -42,13 +42,29 @@
<seq>Down</seq> <seq>Down</seq>
</SC> </SC>
<SC> <SC>
<key>nav-trigger</key> <key>nav-trigger-control</key>
<seq>Return</seq> <seq>Return</seq>
</SC> </SC>
<SC> <SC>
<key>nav-trigger</key> <key>nav-trigger-control</key>
<seq>Space</seq> <seq>Space</seq>
</SC> </SC>
<SC>
<key>nav-first-control</key>
<seq>Home</seq>
</SC>
<SC>
<key>nav-last-control</key>
<seq>End</seq>
</SC>
<SC>
<key>nav-nextrow-control</key>
<seq>PgDown</seq>
</SC>
<SC>
<key>nav-prevrow-control</key>
<seq>PgUp</seq>
</SC>
<!-- end Keyboard navigation --> <!-- end Keyboard navigation -->
<SC> <SC>

View file

@ -42,13 +42,30 @@
<seq>Down</seq> <seq>Down</seq>
</SC> </SC>
<SC> <SC>
<key>nav-trigger</key> <key>nav-trigger-control</key>
<seq>Return</seq> <seq>Return</seq>
</SC> </SC>
<SC> <SC>
<key>nav-trigger</key> <key>nav-trigger-control</key>
<seq>Space</seq> <seq>Space</seq>
</SC> </SC>
<SC>
<key>nav-first-control</key>
<seq>Home</seq>
</SC>
<SC>
<key>nav-last-control</key>
<seq>End</seq>
</SC>
<SC>
<key>nav-nextrow-control</key>
<seq>PgDown</seq>
</SC>
<SC>
<key>nav-prevrow-control</key>
<seq>PgUp</seq>
</SC>
<!-- end Keyboard navigation --> <!-- end Keyboard navigation -->
<SC> <SC>

View file

@ -28,60 +28,6 @@ using namespace mu::ui;
using MoveDirection = KeyNavigationController::MoveDirection; using MoveDirection = KeyNavigationController::MoveDirection;
// algorithms // algorithms
template<class T>
static IKeyNavigation::Index determinateExtremeIndex(const QSet<T*>& set, MoveDirection direction)
{
IKeyNavigation::Index index;
switch (direction) {
case MoveDirection::Right: {
index.column = -1;
// find min row
index.row = std::numeric_limits<int>::max();
for (T* v : set) {
if (v->index().row < index.row) {
index.row = v->index().row;
}
}
} break;
case MoveDirection::Left: {
index.column = std::numeric_limits<int>::max();
// find max row
index.row = -1;
for (T* v : set) {
if (v->index().row > index.row) {
index.row = v->index().row;
}
}
} break;
case MoveDirection::Down: {
index.row = -1;
// find min column
index.column = std::numeric_limits<int>::max();
for (T* v : set) {
if (v->index().column < index.column) {
index.column = v->index().column;
}
}
} break;
case MoveDirection::Up: {
index.row = std::numeric_limits<int>::max();
// find max row
index.column = -1;
for (T* v : set) {
if (v->index().column > index.column) {
index.column = v->index().row;
}
}
} break;
}
return index;
}
template<class T> template<class T>
static T* findNearestEnabled(const QSet<T*>& set, const IKeyNavigation::Index& currentIndex, MoveDirection direction) static T* findNearestEnabled(const QSet<T*>& set, const IKeyNavigation::Index& currentIndex, MoveDirection direction)
{ {
@ -92,6 +38,32 @@ static T* findNearestEnabled(const QSet<T*>& set, const IKeyNavigation::Index& c
} }
switch (direction) { switch (direction) {
case MoveDirection::First: {
if (!ret) {
ret = v;
continue;
}
if (v->index().row <= ret->index().row) {
if (v->index().column <= ret->index().column) {
ret = v;
continue;
}
}
} break;
case MoveDirection::Last: {
if (!ret) {
ret = v;
continue;
}
if (v->index().row >= ret->index().row) {
if (v->index().column >= ret->index().column) {
ret = v;
continue;
}
}
} break;
case MoveDirection::Right: { case MoveDirection::Right: {
if (v->index().row != currentIndex.row) { if (v->index().row != currentIndex.row) {
continue; continue;
@ -163,9 +135,7 @@ static T* findNearestEnabled(const QSet<T*>& set, const IKeyNavigation::Index& c
} }
template<class T> template<class T>
static T* firstEnabled(const QSet<T*>& set, static T* firstEnabled(const QSet<T*>& set, const IKeyNavigation::Index& currentIndex, MoveDirection direction)
const IKeyNavigation::Index& currentIndex = IKeyNavigation::Index(),
MoveDirection direction = MoveDirection::Right)
{ {
if (set.empty()) { if (set.empty()) {
return nullptr; return nullptr;
@ -175,31 +145,41 @@ static T* firstEnabled(const QSet<T*>& set,
return nullptr; return nullptr;
} }
if (currentIndex.column < 0 && currentIndex.row < 0) { return findNearestEnabled<T>(set, currentIndex, direction);
return findNearestEnabled<T>(set, determinateExtremeIndex(set, direction), direction); }
template<class T>
static T* firstEnabled(const QSet<T*>& set)
{
if (set.empty()) {
return nullptr;
}
return findNearestEnabled<T>(set, IKeyNavigation::Index(), MoveDirection::First);
}
template<class T>
static T* lastEnabled(const QSet<T*>& set, const IKeyNavigation::Index& currentIndex, MoveDirection direction)
{
if (set.empty()) {
return nullptr;
}
IF_ASSERT_FAILED(direction == MoveDirection::Left || (direction == MoveDirection::Up)) {
return nullptr;
} }
return findNearestEnabled<T>(set, currentIndex, direction); return findNearestEnabled<T>(set, currentIndex, direction);
} }
template<class T> template<class T>
static T* lastEnabled(const QSet<T*>& set, static T* lastEnabled(const QSet<T*>& set)
const IKeyNavigation::Index& currentIndex = IKeyNavigation::Index(),
MoveDirection direction = MoveDirection::Left)
{ {
if (set.empty()) { if (set.empty()) {
return nullptr; return nullptr;
} }
IF_ASSERT_FAILED(direction == MoveDirection::Left || direction == MoveDirection::Up) { return findNearestEnabled<T>(set, IKeyNavigation::Index(), MoveDirection::Last);
return nullptr;
}
if (currentIndex.column < 0 && currentIndex.row < 0) {
return findNearestEnabled<T>(set, determinateExtremeIndex(set, direction), direction);
}
return findNearestEnabled<T>(set, currentIndex, direction);
} }
template<class T> template<class T>
@ -250,11 +230,18 @@ void KeyNavigationController::init()
dispatcher()->reg(this, "nav-prev-section", this, &KeyNavigationController::goToPrevSection); dispatcher()->reg(this, "nav-prev-section", this, &KeyNavigationController::goToPrevSection);
dispatcher()->reg(this, "nav-next-subsection", this, &KeyNavigationController::goToNextSubSection); dispatcher()->reg(this, "nav-next-subsection", this, &KeyNavigationController::goToNextSubSection);
dispatcher()->reg(this, "nav-prev-subsection", this, &KeyNavigationController::goToPrevSubSection); dispatcher()->reg(this, "nav-prev-subsection", this, &KeyNavigationController::goToPrevSubSection);
dispatcher()->reg(this, "nav-trigger-control", this, &KeyNavigationController::doTriggerControl);
dispatcher()->reg(this, "nav-right", this, &KeyNavigationController::onRight); dispatcher()->reg(this, "nav-right", this, &KeyNavigationController::onRight);
dispatcher()->reg(this, "nav-left", this, &KeyNavigationController::onLeft); dispatcher()->reg(this, "nav-left", this, &KeyNavigationController::onLeft);
dispatcher()->reg(this, "nav-up", this, &KeyNavigationController::onUp); dispatcher()->reg(this, "nav-up", this, &KeyNavigationController::onUp);
dispatcher()->reg(this, "nav-down", this, &KeyNavigationController::onDown); dispatcher()->reg(this, "nav-down", this, &KeyNavigationController::onDown);
dispatcher()->reg(this, "nav-trigger", this, &KeyNavigationController::doTriggerControl);
dispatcher()->reg(this, "nav-first-control", this, &KeyNavigationController::goToFirstControl); // typically Home key
dispatcher()->reg(this, "nav-last-control", this, &KeyNavigationController::goToLastControl); // typically End key
dispatcher()->reg(this, "nav-nextrow-control", this, &KeyNavigationController::goToNextRowControl); // typically PageDown key
dispatcher()->reg(this, "nav-prevrow-control", this, &KeyNavigationController::goToPrevRowControl); // typically PageUp key
} }
void KeyNavigationController::reg(IKeyNavigationSection* s) void KeyNavigationController::reg(IKeyNavigationSection* s)
@ -594,6 +581,80 @@ void KeyNavigationController::onUp()
} }
} }
void KeyNavigationController::goToFirstControl()
{
LOGI() << "====";
goToControl(MoveDirection::First);
}
void KeyNavigationController::goToLastControl()
{
LOGI() << "====";
goToControl(MoveDirection::Last);
}
void KeyNavigationController::goToNextRowControl()
{
LOGI() << "====";
IKeyNavigationSubSection* activeSubSec = activeSubSection();
if (!activeSubSec) {
return;
}
IKeyNavigationControl* activeControl = findActive(activeSubSec->controls());
if (activeControl) {
doDeactivateControl(activeControl);
}
IKeyNavigationControl* toControl = nullptr;
if (activeControl) {
IKeyNavigation::Index index = activeControl->index();
index.column = 0;
toControl = nextEnabled(activeSubSec->controls(), index, MoveDirection::Down);
if (!toControl) { // last row
toControl = firstEnabled(activeSubSec->controls());
}
} else {
toControl = firstEnabled(activeSubSec->controls());
}
if (toControl) {
doActivateControl(toControl);
}
}
void KeyNavigationController::goToPrevRowControl()
{
LOGI() << "====";
IKeyNavigationSubSection* activeSubSec = activeSubSection();
if (!activeSubSec) {
return;
}
IKeyNavigationControl* activeControl = findActive(activeSubSec->controls());
if (activeControl) {
doDeactivateControl(activeControl);
}
IKeyNavigationControl* toControl = nullptr;
if (activeControl) {
IKeyNavigation::Index index = activeControl->index();
index.column = 0;
toControl = prevEnabled(activeSubSec->controls(), index, MoveDirection::Up);
if (!toControl) { // first row
toControl = lastEnabled(activeSubSec->controls());
}
} else {
toControl = lastEnabled(activeSubSec->controls());
}
if (toControl) {
doActivateControl(toControl);
}
}
void KeyNavigationController::goToControl(MoveDirection direction, IKeyNavigationSubSection* activeSubSec) void KeyNavigationController::goToControl(MoveDirection direction, IKeyNavigationSubSection* activeSubSec)
{ {
LOGI() << "direction: " << direction; LOGI() << "direction: " << direction;
@ -614,6 +675,12 @@ void KeyNavigationController::goToControl(MoveDirection direction, IKeyNavigatio
IKeyNavigationControl* toControl = nullptr; IKeyNavigationControl* toControl = nullptr;
switch (direction) { switch (direction) {
case MoveDirection::First: {
toControl = firstEnabled(activeSubSec->controls());
} break;
case MoveDirection::Last: {
toControl = lastEnabled(activeSubSec->controls());
} break;
case MoveDirection::Right: { case MoveDirection::Right: {
if (!activeControl) { // no any active if (!activeControl) { // no any active
toControl = firstEnabled(activeSubSec->controls(), IKeyNavigation::Index(), direction); toControl = firstEnabled(activeSubSec->controls(), IKeyNavigation::Index(), direction);

View file

@ -35,7 +35,9 @@ public:
KeyNavigationController() = default; KeyNavigationController() = default;
enum MoveDirection { enum MoveDirection {
Right = 0, First = 0,
Last,
Right,
Left, Left,
Up, Up,
Down Down
@ -52,7 +54,12 @@ private:
void goToNextSubSection(); void goToNextSubSection();
void goToPrevSubSection(); void goToPrevSubSection();
void goToControl(MoveDirection direction, IKeyNavigationSubSection* activeSubSec); void goToFirstControl();
void goToLastControl();
void goToNextRowControl();
void goToPrevRowControl();
void goToControl(MoveDirection direction, IKeyNavigationSubSection* activeSubSec = nullptr);
void onLeft(); void onLeft();
void onRight(); void onRight();

View file

@ -48,7 +48,19 @@ const UiActionList KeyNavigationUiActions::m_actions = {
UiAction("nav-down", UiAction("nav-down",
mu::context::UiCtxAny mu::context::UiCtxAny
), ),
UiAction("nav-trigger", UiAction("nav-trigger-control",
mu::context::UiCtxAny
),
UiAction("nav-first-control",
mu::context::UiCtxAny
),
UiAction("nav-last-control",
mu::context::UiCtxAny
),
UiAction("nav-nextrow-control",
mu::context::UiCtxAny
),
UiAction("nav-prevrow-control",
mu::context::UiCtxAny mu::context::UiCtxAny
) )
}; };

View file

@ -33,17 +33,8 @@ FocusableControl {
opacity: root.enabled ? 1.0 : ui.theme.itemOpacityDisabled opacity: root.enabled ? 1.0 : ui.theme.itemOpacityDisabled
onInternalClicked: { mouseArea.onClicked: root.clicked()
root.clicked() mouseArea.onPressAndHold: root.pressAndHold()
}
onInternalPressAndHold: {
root.pressAndHold()
}
onInternalTriggered: {
root.clicked()
}
mouseArea.hoverEnabled: true mouseArea.hoverEnabled: true
mouseArea.onContainsMouseChanged: { mouseArea.onContainsMouseChanged: {
@ -58,6 +49,8 @@ FocusableControl {
} }
} }
keynav.onTriggered: root.clicked()
background.color: normalStateColor background.color: normalStateColor
background.opacity: ui.theme.buttonOpacityNormal background.opacity: ui.theme.buttonOpacityNormal
background.radius: 3 background.radius: 3

View file

@ -12,13 +12,6 @@ FocusScope {
property alias keynav: keynavItem property alias keynav: keynavItem
signal internalPressed()
signal internalReleased()
signal internalClicked()
signal internalPressAndHold()
signal internalTriggered()
function insureActiveFocus() { function insureActiveFocus() {
if (!root.activeFocus) { if (!root.activeFocus) {
root.forceActiveFocus() root.forceActiveFocus()
@ -39,10 +32,6 @@ FocusScope {
root.insureActiveFocus() root.insureActiveFocus()
} }
} }
onTriggered: {
root.internalTriggered()
}
} }
Rectangle { Rectangle {
@ -64,19 +53,6 @@ FocusScope {
onClicked: { onClicked: {
root.insureActiveFocus() root.insureActiveFocus()
root.internalClicked()
}
onPressAndHold: {
root.internalPressAndHold()
}
onPressed: {
root.internalPressed()
}
onReleased: {
root.internalReleased()
} }
} }
} }

View file

@ -2,13 +2,13 @@ import QtQuick 2.15
import MuseScore.Ui 1.0 import MuseScore.Ui 1.0
Item { FocusableControl {
id: root id: root
property string hint property string hint
property bool isSelected: false property bool isSelected: false
property alias radius: background.radius property alias radius: root.background.radius
property color normalStateColor: "transparent" property color normalStateColor: "transparent"
property color hoveredStateColor: privateProperties.defaultColor property color hoveredStateColor: privateProperties.defaultColor
@ -24,20 +24,31 @@ Item {
Accessible.selectable: true Accessible.selectable: true
Accessible.selected: isSelected Accessible.selected: isSelected
mouseArea.hoverEnabled: root.visible
mouseArea.onHoveredChanged: root.hovered(mouseArea.containsMouse, mouseArea.mouseX, mouseArea.mouseY)
mouseArea.onClicked: root.clicked()
mouseArea.onDoubleClicked: root.doubleClicked()
mouseArea.onContainsMouseChanged: {
if (!Boolean(root.hint)) {
return
}
if (mouseArea.containsMouse) {
ui.tooltip.show(this, root.hint)
} else {
ui.tooltip.hide(this)
}
}
QtObject { QtObject {
id: privateProperties id: privateProperties
property color defaultColor: ui.theme.buttonColor property color defaultColor: ui.theme.buttonColor
} }
Rectangle { background.opacity: root.enabled ? 1 : ui.theme.itemOpacityDisabled
id: background
anchors.fill: parent
color: normalStateColor
opacity: root.enabled ? 1 : ui.theme.itemOpacityDisabled
}
states: [ states: [
State { State {
@ -45,7 +56,7 @@ Item {
when: mouseArea.containsMouse && !mouseArea.pressed && !root.isSelected when: mouseArea.containsMouse && !mouseArea.pressed && !root.isSelected
PropertyChanges { PropertyChanges {
target: background target: root.background
opacity: ui.theme.buttonOpacityHover opacity: ui.theme.buttonOpacityHover
color: hoveredStateColor color: hoveredStateColor
} }
@ -56,7 +67,7 @@ Item {
when: mouseArea.pressed && !root.isSelected when: mouseArea.pressed && !root.isSelected
PropertyChanges { PropertyChanges {
target: background target: root.background
opacity: ui.theme.buttonOpacityHit opacity: ui.theme.buttonOpacityHit
color: pressedStateColor color: pressedStateColor
} }
@ -67,42 +78,10 @@ Item {
when: root.isSelected when: root.isSelected
PropertyChanges { PropertyChanges {
target: background target: root.background
opacity: ui.theme.accentOpacityHit opacity: ui.theme.accentOpacityHit
color: ui.theme.accentColor color: ui.theme.accentColor
} }
} }
] ]
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: root.visible
onHoveredChanged: {
root.hovered(mouseArea.containsMouse, mouseX, mouseY)
}
onClicked: {
root.clicked()
}
onDoubleClicked: {
root.doubleClicked()
}
onContainsMouseChanged: {
if (!Boolean(root.hint)) {
return
}
if (containsMouse) {
ui.tooltip.show(this, root.hint)
} else {
ui.tooltip.hide(this)
}
}
}
} }

View file

@ -12,6 +12,7 @@ import MuseScore.Instruments 1.0
import "internal" import "internal"
Item { Item {
id: root id: root
property KeyNavigationSection keynavSection: null property KeyNavigationSection keynavSection: null
@ -37,6 +38,7 @@ Item {
name: "InstrumentsTree" name: "InstrumentsTree"
section: root.keynavSection section: root.keynavSection
direction: KeyNavigationSubSection.Both direction: KeyNavigationSubSection.Both
enabled: root.visible
order: 3 order: 3
} }
@ -57,6 +59,7 @@ Item {
Layout.rightMargin: contentColumn.sideMargin Layout.rightMargin: contentColumn.sideMargin
keynav.section: root.keynavSection keynav.section: root.keynavSection
keynav.enabled: root.visible
keynav.order: 2 keynav.order: 2
isMovingUpAvailable: instrumentTreeModel.isMovingUpAvailable isMovingUpAvailable: instrumentTreeModel.isMovingUpAvailable

View file

@ -31,7 +31,7 @@ RowLayout {
KeyNavigationSubSection { KeyNavigationSubSection {
id: keynavSub id: keynavSub
name: "InstrumentsControlPanel" name: "InstrumentsHeader"
} }
FlatButton { FlatButton {

View file

@ -262,7 +262,6 @@ bool InstrumentPanelTreeModel::moveRows(const QModelIndex& sourceParent, int sou
bool InstrumentPanelTreeModel::isSelected(const QModelIndex& rowIndex) const bool InstrumentPanelTreeModel::isSelected(const QModelIndex& rowIndex) const
{ {
TRACEFUNC;
if (m_selectionModel->selectedIndexes().isEmpty()) { if (m_selectionModel->selectedIndexes().isEmpty()) {
return false; return false;
} }
@ -346,7 +345,6 @@ int InstrumentPanelTreeModel::columnCount(const QModelIndex&) const
QVariant InstrumentPanelTreeModel::data(const QModelIndex& index, int role) const QVariant InstrumentPanelTreeModel::data(const QModelIndex& index, int role) const
{ {
TRACEFUNC;
if (!index.isValid() && role != ItemRole) { if (!index.isValid() && role != ItemRole) {
return QVariant(); return QVariant();
} }

View file

@ -8,6 +8,8 @@ Rectangle {
property alias keynav: keynavSub property alias keynav: keynavSub
signal activeFocusRequested()
Component.onCompleted: { Component.onCompleted: {
toolbarModel.load() toolbarModel.load()
} }
@ -15,6 +17,12 @@ Rectangle {
KeyNavigationSubSection { KeyNavigationSubSection {
id: keynavSub id: keynavSub
name: "NotationToolBar" name: "NotationToolBar"
onActiveChanged: {
if (active) {
root.activeFocusRequested()
root.forceActiveFocus()
}
}
} }
NotationToolBarModel { NotationToolBarModel {

View file

@ -17,23 +17,22 @@
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//============================================================================= //=============================================================================
import QtQuick 2.8 import QtQuick 2.15
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import QtQuick.Window 2.2 import QtQuick.Window 2.2
import MuseScore.Ui 1.0
import MuseScore.UiComponents 1.0 import MuseScore.UiComponents 1.0
import MuseScore.Palette 1.0 import MuseScore.Palette 1.0
// TODO: make some properties 'property alias`?
// and `readonly property`?
import "internal" import "internal"
Rectangle { Rectangle {
id: palettesWidget id: palettesWidget
readonly property PaletteWorkspace paletteWorkspace: paletteRootModel.paletteWorkspace property KeyNavigationSection keynavSection: null
readonly property bool hasFocus: Window.activeFocusItem readonly property PaletteWorkspace paletteWorkspace: paletteRootModel.paletteWorkspace
implicitHeight: 4 * palettesWidgetHeader.implicitHeight implicitHeight: 4 * palettesWidgetHeader.implicitHeight
implicitWidth: paletteTree.implicitWidth implicitWidth: paletteTree.implicitWidth
@ -46,14 +45,6 @@ Rectangle {
color: ui.theme.backgroundPrimaryColor color: ui.theme.backgroundPrimaryColor
FocusableItem {
id: focusBreaker
onActiveFocusChanged: {
parent.focus = false
}
}
PaletteRootModel { PaletteRootModel {
id: paletteRootModel id: paletteRootModel
@ -77,6 +68,9 @@ Rectangle {
rightMargin: 12 rightMargin: 12
} }
keynav.section: palettesWidget.keynavSection
keynav.order: 2
onAddCustomPaletteRequested: paletteTree.insertCustomPalette(0, paletteName); onAddCustomPaletteRequested: paletteTree.insertCustomPalette(0, paletteName);
} }
@ -96,6 +90,11 @@ Rectangle {
id: paletteTree id: paletteTree
clip: true clip: true
paletteWorkspace: palettesWidget.paletteWorkspace paletteWorkspace: palettesWidget.paletteWorkspace
backgroundColor: palettesWidget.color
keynav.section: palettesWidget.keynavSection
keynav.order: 3
keynav.enabled: paletteTree.visible
filter: palettesWidgetHeader.searchText filter: palettesWidgetHeader.searchText
enableAnimations: !palettesWidgetHeader.searching enableAnimations: !palettesWidgetHeader.searching

View file

@ -50,6 +50,10 @@ GridView {
property bool enableAnimations: true property bool enableAnimations: true
property KeyNavigationSubSection keynavSubSection: null
property int keynavRow: 0
property int keynavCol: 1
states: [ states: [
State { State {
name: "default" name: "default"
@ -97,7 +101,8 @@ GridView {
cellWidth: stretchWidth ? Math.floor(Utils.stretched(cellDefaultWidth, width)) : cellDefaultWidth cellWidth: stretchWidth ? Math.floor(Utils.stretched(cellDefaultWidth, width)) : cellDefaultWidth
cellHeight: cellSize.height cellHeight: cellSize.height
readonly property real ncolumns: Math.floor(width / cellWidth); readonly property int nrows: Math.max(0, Math.floor(height / cellHeight))
readonly property int ncolumns: Math.max(0, Math.floor(width / cellWidth))
readonly property real lastColumnCellWidth : cellWidth + (width % cellWidth) // width of last cell in a row: might be stretched to avoid a gap at row end readonly property real lastColumnCellWidth : cellWidth + (width % cellWidth) // width of last cell in a row: might be stretched to avoid a gap at row end
signal moreButtonClicked() signal moreButtonClicked()
@ -164,7 +169,12 @@ GridView {
id: moreButton id: moreButton
anchors.fill: parent anchors.fill: parent
activeFocusOnTab: this === paletteTree.currentTreeItem
keynav.subsection: paletteView.keynavSubSection
//! NOTE Just Up/Down navigation now
keynav.row: paletteView.ncells + paletteView.keynavRow
keynav.column: 1
keynav.enabled: paletteView.visible
onActiveFocusChanged: { onActiveFocusChanged: {
if (activeFocus) { if (activeFocus) {
@ -183,42 +193,6 @@ GridView {
pressedStateColor: ui.theme.accentColor pressedStateColor: ui.theme.accentColor
onClicked: paletteView.moreButtonClicked() 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();
break;
case Qt.Key_Down:
paletteTree.focusNextItem(false);
break;
default:
return; // don't accept event
}
event.accepted = true;
}
function focusPreviousItem() {
if (paletteView.count == 0) {
paletteTree.currentItem.forceActiveFocus();
} else {
paletteView.currentIndex = paletteView.count - 1
paletteView.currentItem.forceActiveFocus();
}
}
} }
} }
@ -408,34 +382,6 @@ GridView {
Utils.removeSelectedItems(paletteController, selectionModel, paletteRootIndex); Utils.removeSelectedItems(paletteController, selectionModel, paletteRootIndex);
} }
function focusNextItem(flags) {
if (flags === undefined) {
flags = ItemSelectionModel.ClearAndSelect;
}
if (currentIndex == count - 1) {
if (moreButton.visible) {
moreButton.forceActiveFocus();
} else {
paletteTree.focusNextItem(false);
}
} else {
currentIndex++; // next grid item
}
}
function focusPreviousItem(flags) {
if (flags === undefined) {
flags = ItemSelectionModel.ClearAndSelect;
}
if (currentIndex == 0) {
paletteTree.currentItem.forceActiveFocus();
} else {
currentIndex--; // previous grid item
}
}
function focusFirstItem() { function focusFirstItem() {
if (count == 0 && moreButton.visible) { if (count == 0 && moreButton.visible) {
moreButton.forceActiveFocus(); moreButton.forceActiveFocus();
@ -504,48 +450,6 @@ GridView {
} }
} }
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();
break;
case Qt.Key_Down:
focusNextItem();
break;
case Qt.Key_Left:
paletteTree.currentItem.forceActiveFocus();
break;
case Qt.Key_Right:
if (moreButton.visible)
moreButton.forceActiveFocus();
break;
case Qt.Key_Backspace:
case Qt.Key_Delete:
removeSelectedCells();
break;
default:
return; // don't accept event
}
event.accepted = true;
}
Rectangle { Rectangle {
id: draggedIcon id: draggedIcon
@ -576,6 +480,10 @@ GridView {
property var modelIndex: paletteView.model.modelIndex(index) property var modelIndex: paletteView.model.modelIndex(index)
property var parentModelIndex: paletteView.paletteRootIndex property var parentModelIndex: paletteView.paletteRootIndex
//! NOTE Please, don't remove (igor.korsukov@gmail.com)
//property int cellRow: paletteView.ncolumns == 0 ? 0 : Math.floor(model.index / paletteView.ncolumns)
//property int cellCol: model.index - (cellRow * paletteView.ncolumns)
onActiveFocusChanged: { onActiveFocusChanged: {
if (activeFocus) { if (activeFocus) {
paletteTree.currentTreeItem = this; paletteTree.currentTreeItem = this;
@ -593,7 +501,17 @@ GridView {
width: paletteView.cellWidth width: paletteView.cellWidth
height: paletteView.cellHeight height: paletteView.cellHeight
activeFocusOnTab: this === paletteTree.currentTreeItem keynav.subsection: paletteView.keynavSubSection
//! NOTE Please, don't remove (igor.korsukov@gmail.com)
//keynav.row: paletteCell.cellRow + paletteView.keynavRow
//keynav.column: paletteCell.cellCol + paletteView.keynavCol
//! NOTE Just Up/Down navigation now
keynav.row: model.index + paletteView.keynavRow
keynav.column: 1
keynav.enabled: paletteView.visible
keynav.onTriggered: paletteCell.doClicked()
IconView { IconView {
anchors.fill: parent anchors.fill: parent
@ -605,108 +523,37 @@ GridView {
Accessible.name: model.accessibleText; Accessible.name: model.accessibleText;
Keys.onShortcutOverride: { // leftClickArea
// Intercept all keys that we want to use with Keys.onPressed mouseArea.drag.target: this
// in case they are assigned as shortcuts in Preferences. mouseArea.onPressed: {
event.accepted = true; // intercept everything paletteView.currentIndex = paletteCell.rowIndex;
switch (event.key) { paletteCell.forceActiveFocus();
case Qt.Key_Space: paletteView.updateSelection(true);
case Qt.Key_Enter: paletteCell.beginDrag();
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: { function doClicked() {
// NOTE: All keys must be intercepted with Keys.onShortcutOverride. if (paletteView.paletteController.applyPaletteElement(paletteCell.modelIndex, ui.keyboardModifiers())) {
const shiftHeld = event.modifiers & Qt.ShiftModifier; paletteView.selectionModel.setCurrentIndex(paletteCell.modelIndex, ItemSelectionModel.Current);
const ctrlHeld = event.modifiers & Qt.ControlModifier;
switch (event.key) {
case Qt.Key_Space:
if (paletteTree.typeAheadStr.length) {
paletteView.typeAheadFind(' ');
} else {
paletteView.updateSelection(true);
}
break;
case Qt.Key_Enter:
case Qt.Key_Return:
paletteView.selectionModel.setCurrentIndex(modelIndex, ItemSelectionModel.ClearAndSelect);
paletteView.paletteController.applyPaletteElement(modelIndex, ui.keyboardModifiers());
break;
case Qt.Key_F10:
if (!shiftHeld) {
return;
}
// fallthrough
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.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;
} }
MouseArea { onClicked: paletteCell.doClicked()
id: leftClickArea
anchors.fill: parent
drag.target: this
onPressed: { onDoubleClicked: {
paletteView.currentIndex = paletteCell.rowIndex; const index = paletteCell.modelIndex;
paletteCell.forceActiveFocus(); paletteView.selectionModel.setCurrentIndex(index, ItemSelectionModel.Current);
paletteView.updateSelection(true); paletteView.paletteController.applyPaletteElement(index, mouseArea.mouse.modifiers);
paletteCell.beginDrag();
}
onClicked: {
if (paletteView.paletteController.applyPaletteElement(paletteCell.modelIndex, ui.keyboardModifiers())) {
paletteView.selectionModel.setCurrentIndex(paletteCell.modelIndex, ItemSelectionModel.Current);
}
}
onDoubleClicked: {
const index = paletteCell.modelIndex;
paletteView.selectionModel.setCurrentIndex(index, ItemSelectionModel.Current);
paletteView.paletteController.applyPaletteElement(index, mouse.modifiers);
}
} }
MouseArea { MouseArea {
id: rightClickArea id: rightClickArea
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.RightButton acceptedButtons: Qt.RightButton
onClicked: showCellMenu(true) onClicked: showCellMenu(true)
} }
Drag.active: leftClickArea.drag.active Drag.active: mouseArea.drag.active
Drag.dragType: Drag.Automatic Drag.dragType: Drag.Automatic
Drag.supportedActions: Qt.CopyAction | (model.editable ? Qt.MoveAction : 0) Drag.supportedActions: Qt.CopyAction | (model.editable ? Qt.MoveAction : 0)
Drag.mimeData: Drag.active ? mimeData : {} Drag.mimeData: Drag.active ? mimeData : {}

View file

@ -29,25 +29,44 @@ import "utils.js" as Utils
ListView { ListView {
id: paletteTree id: paletteTree
Accessible.name: qsTrc("palette", "Palettes Tree, contains %n palette(s)", "", count)
activeFocusOnTab: true // allow focus even when empty
property PaletteWorkspace paletteWorkspace property PaletteWorkspace paletteWorkspace
property var paletteModel: Boolean(paletteWorkspace) ? paletteWorkspace.mainPaletteModel : null property var paletteModel: Boolean(paletteWorkspace) ? paletteWorkspace.mainPaletteModel : null
property PaletteController paletteController: paletteWorkspace ? paletteWorkspace.mainPaletteController : null property PaletteController paletteController: paletteWorkspace ? paletteWorkspace.mainPaletteController : null
property alias keynav: keynavTree
// Scroll palettes list when dragging a palette close to the list's border // Scroll palettes list when dragging a palette close to the list's border
property bool itemDragged: false property bool itemDragged: false
preferredHighlightBegin: Math.min(48, Math.floor(0.1 * height))
preferredHighlightEnd: Math.ceil(height - preferredHighlightBegin)
highlightRangeMode: itemDragged ? ListView.ApplyRange : ListView.NoHighlightRange
property Item currentTreeItem: currentItem // most recently focused item at any level of the tree property Item currentTreeItem: currentItem // most recently focused item at any level of the tree
property string filter: "" property string filter: ""
property bool searchOpened: false property bool searchOpened: false
property bool enableAnimations: true
property int expandDuration: enableAnimations ? 150 : 0 // duration of expand / collapse animations
property string backgroundColor: "#ffffff"
preferredHighlightBegin: Math.min(48, Math.floor(0.1 * height))
preferredHighlightEnd: Math.ceil(height - preferredHighlightBegin)
highlightRangeMode: itemDragged ? ListView.ApplyRange : ListView.NoHighlightRange
Accessible.name: qsTrc("palette", "Palettes Tree, contains %n palette(s)", "", count)
KeyNavigationSubSection {
id: keynavTree
name: "PalettesTree"
direction: KeyNavigationSubSection.Both
onActiveChanged: {
if (active) {
paletteTree.forceActiveFocus()
}
}
}
onSearchOpenedChanged: { onSearchOpenedChanged: {
if (paletteWorkspace) { if (paletteWorkspace) {
paletteWorkspace.setSearching(searchOpened) paletteWorkspace.setSearching(searchOpened)
@ -65,9 +84,6 @@ ListView {
} }
} }
property bool enableAnimations: true
property int expandDuration: enableAnimations ? 150 : 0 // duration of expand / collapse animations
function insertCustomPalette(idx, paletteName) { function insertCustomPalette(idx, paletteName) {
if (paletteTree.paletteController.insertNewItem(paletteTreeDelegateModel.rootIndex, idx, paletteName)) { if (paletteTree.paletteController.insertNewItem(paletteTreeDelegateModel.rootIndex, idx, paletteName)) {
positionViewAtIndex(idx, ListView.Contain) positionViewAtIndex(idx, ListView.Contain)
@ -132,56 +148,6 @@ ListView {
Utils.removeSelectedItems(paletteController, paletteSelectionModel, parentIndex); 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();
break;
case Qt.Key_Up:
focusPreviousItem();
break;
case Qt.Key_Home:
focusFirstItem();
break;
case Qt.Key_End:
focusLastItem();
break;
case Qt.Key_PageUp:
focusPreviousPageItem();
break;
case Qt.Key_PageDown:
focusNextPageItem();
break;
case Qt.Key_Backspace:
case Qt.Key_Delete:
expandedPopupIndex = null;
removeSelectedItems();
break;
default:
return; // don't accept event
}
event.accepted = true;
}
displaced: Transition { displaced: Transition {
enabled: paletteTree.enableAnimations enabled: paletteTree.enableAnimations
NumberAnimation { property: "y"; duration: 150 } NumberAnimation { property: "y"; duration: 150 }
@ -200,103 +166,6 @@ ListView {
return { display: "", gridSize: Qt.size(1, 1), drawGrid: false, custom: false, editable: false, expanded: false }; return { display: "", gridSize: Qt.size(1, 1), drawGrid: false, custom: false, editable: false, expanded: false };
} }
function focusNextItem(includeChildren) {
if (includeChildren === undefined) { // https://stackoverflow.com/a/44128406
includeChildren = true;
}
if (includeChildren && currentItem.expanded) {
currentItem.focusFirstItem();
return;
}
if (currentIndex == count - 1) {
return; // no next item
}
incrementCurrentIndex();
currentItem.forceActiveFocus();
positionViewAtIndex(currentIndex, ListView.Contain);
}
function focusPreviousItem(includeChildren) {
if (includeChildren === undefined) { // https://stackoverflow.com/a/44128406
includeChildren = true;
}
if (currentIndex == 0) {
return; // no previous item
}
decrementCurrentIndex();
if (includeChildren && currentItem.expanded) {
currentItem.focusLastItem();
} else {
currentItem.forceActiveFocus();
}
positionViewAtIndex(currentIndex, ListView.Contain);
}
function focusNextPageItem() {
if (currentIndex < count - 1) {
currentIndex++; // move by at least one item
// try to keep going, but new item must stay entirely in view
var distance = currentItem.height;
while (currentIndex < count - 1) {
currentIndex++; // try another
distance += currentItem.height;
if (distance > height) {
currentIndex--; // too far, go back one
break;
}
}
}
currentItem.forceActiveFocus();
positionViewAtIndex(currentIndex, ListView.Contain);
}
function focusPreviousPageItem() {
if (currentIndex > 0) {
currentIndex--; // move by at least one item
// try to keep going, but new item must stay entirely in view
var distance = currentItem.height;
while (currentIndex > 0) {
currentIndex--; // try another
distance += currentItem.height;
if (distance > height) {
currentIndex++; // too far, go back one
break;
}
}
}
currentItem.forceActiveFocus();
positionViewAtIndex(currentIndex, ListView.Contain);
}
function focusFirstItem() {
currentIndex = 0;
currentItem.forceActiveFocus();
positionViewAtIndex(currentIndex, ListView.Contain);
}
function focusLastItem() {
currentIndex = count - 1;
if (currentItem.expanded) {
currentItem.focusLastItem();
} else {
currentItem.forceActiveFocus();
}
positionViewAtIndex(currentIndex, ListView.Contain);
}
function focusNextMatchingItem(str, startIndex) { function focusNextMatchingItem(str, startIndex) {
const modelIndex = paletteModel.index(startIndex, 0); const modelIndex = paletteModel.index(startIndex, 0);
const matchedIndexList = paletteModel.match(modelIndex, Qt.ToolTipRole, str); const matchedIndexList = paletteModel.match(modelIndex, Qt.ToolTipRole, str);
@ -373,6 +242,7 @@ ListView {
topPadding: 0 topPadding: 0
bottomPadding: expanded ? 4 : 0 bottomPadding: expanded ? 4 : 0
property int rowIndex: index property int rowIndex: index
property int keynavRow: (index + 1) * 10000 // to make unique
property var modelIndex: paletteTree.model.modelIndex(index, 0) property var modelIndex: paletteTree.model.modelIndex(index, 0)
onActiveFocusChanged: { onActiveFocusChanged: {
@ -386,14 +256,6 @@ ListView {
paletteTree.implicitWidth = Math.max(paletteTree.implicitWidth, w); paletteTree.implicitWidth = Math.max(paletteTree.implicitWidth, w);
} }
function focusFirstItem() {
mainPalette.focusFirstItem();
}
function focusLastItem() {
mainPalette.focusLastItem();
}
property bool expanded: filter.length || model.expanded property bool expanded: filter.length || model.expanded
function toggleExpand() { function toggleExpand() {
@ -418,13 +280,17 @@ ListView {
property bool selected: paletteSelectionModel.hasSelection ? paletteSelectionModel.isSelected(modelIndex) : false property bool selected: paletteSelectionModel.hasSelection ? paletteSelectionModel.isSelected(modelIndex) : false
onClicked: { function doItemClicked() {
forceActiveFocus(); forceActiveFocus();
const cmd = selected ? ItemSelectionModel.Toggle : ItemSelectionModel.ClearAndSelect; const cmd = selected ? ItemSelectionModel.Toggle : ItemSelectionModel.ClearAndSelect;
paletteSelectionModel.setCurrentIndex(modelIndex, cmd); paletteSelectionModel.setCurrentIndex(modelIndex, cmd);
paletteTree.currentIndex = index; paletteTree.currentIndex = index;
} }
onClicked: {
control.doItemClicked()
}
onDoubleClicked: { onDoubleClicked: {
forceActiveFocus(); forceActiveFocus();
paletteSelectionModel.setCurrentIndex(modelIndex, ItemSelectionModel.Deselect); paletteSelectionModel.setCurrentIndex(modelIndex, ItemSelectionModel.Deselect);
@ -432,9 +298,21 @@ ListView {
} }
background: ListItemBlank { background: ListItemBlank {
background.color: paletteTree.backgroundColor
visible: !control.Drag.active visible: !control.Drag.active
isSelected: control.selected isSelected: control.selected
keynav.name: "PaletteTreeItemDelegate"
keynav.subsection: keynavTree
keynav.row: control.keynavRow
keynav.column: 0
enabled: control.visible
keynav.onActiveChanged: {
if (keynav.active && !control.selected) {
control.doItemClicked()
}
paletteTree.positionViewAtIndex(control.rowIndex, ListView.Contain);
}
} }
highlighted: (activeFocus && !selected) || DelegateModel.isUnresolved highlighted: (activeFocus && !selected) || DelegateModel.isUnresolved
@ -449,94 +327,11 @@ ListView {
property size cellSize: model.gridSize property size cellSize: model.gridSize
property bool drawGrid: model.drawGrid property bool drawGrid: model.drawGrid
activeFocusOnTab: this === paletteTree.currentTreeItem
function hidePalette() { function hidePalette() {
paletteTree.expandedPopupIndex = null; paletteTree.expandedPopupIndex = null;
paletteTree.paletteController.remove(modelIndex); 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:
if (!expanded) {
toggleExpand();
} else if (event.key === Qt.Key_Right) {
focusFirstItem();
}
break;
case Qt.Key_Left:
case Qt.Key_Minus:
if (expanded)
toggleExpand();
break;
case Qt.Key_Space:
if (paletteTree.typeAheadStr.length) {
paletteTree.typeAheadFind(' ');
break;
}
// fallthrough
case Qt.Key_Enter:
case Qt.Key_Return:
toggleExpand();
break;
case Qt.Key_F10:
if (!(event.modifiers & Qt.ShiftModifier)) {
return;
}
// fallthrough
case Qt.Key_Menu:
paletteHeader.showPaletteMenu();
break;
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;
}
text: filter.length ? qsTrc("palette", "%1, contains %n matching element(s)", "palette", mainPalette.count).arg(model.accessibleText) text: filter.length ? qsTrc("palette", "%1, contains %n matching element(s)", "palette", mainPalette.count).arg(model.accessibleText)
: model.expanded ? qsTrc("palette", "%1 expanded", "tree item not collapsed").arg(model.accessibleText) : model.expanded ? qsTrc("palette", "%1 expanded", "tree item not collapsed").arg(model.accessibleText)
: model.accessibleText : model.accessibleText
@ -577,8 +372,8 @@ ListView {
if (dropAction == Qt.MoveAction) { if (dropAction == Qt.MoveAction) {
controller.move( controller.move(
root, rowIndex, root, rowIndex,
root, destIndex); root, destIndex);
} }
} }
@ -647,10 +442,13 @@ ListView {
hovered: control.hovered hovered: control.hovered
text: model.display text: model.display
keynavSubsection: keynavTree
keynavRow: control.keynavRow
hidePaletteElementVisible: { hidePaletteElementVisible: {
return !control.selected && control.expanded return !control.selected && control.expanded
&& paletteSelectionModel.hasSelection && paletteSelectionModel.columnIntersectsSelection(0, control.modelIndex) && paletteSelectionModel.hasSelection && paletteSelectionModel.columnIntersectsSelection(0, control.modelIndex)
&& paletteTree.paletteModel.parent(paletteSelectionModel.currentIndex) === control.modelIndex; // HACK to work around a (possible?) bug in columnIntersectsSelection && paletteTree.paletteModel.parent(paletteSelectionModel.currentIndex) === control.modelIndex; // HACK to work around a (possible?) bug in columnIntersectsSelection
} }
custom: model.custom custom: model.custom
@ -706,6 +504,9 @@ ListView {
id: mainPalette id: mainPalette
anchors { fill: parent; margins: parent.padding } anchors { fill: parent; margins: parent.padding }
keynavSubSection: keynavTree
keynavRow: control.keynavRow + 1
cellSize: control.cellSize cellSize: control.cellSize
drawGrid: control.drawGrid drawGrid: control.drawGrid
@ -811,14 +612,4 @@ ListView {
// placeholder footer item to reserve a space for "More" popup to expand // placeholder footer item to reserve a space for "More" popup to expand
footer: Item { height: 0 } footer: Item { height: 0 }
Connections {
target: palettesWidget
function onHasFocusChanged() {
if (!palettesWidget.hasFocus) {
paletteSelectionModel.clearSelection();
expandedPopupIndex = null;
}
}
}
} }

View file

@ -17,12 +17,12 @@
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//============================================================================= //=============================================================================
import QtQuick 2.8 import QtQuick 2.15
import QtQuick.Controls 2.1 import QtQuick.Controls 2.1
import MuseScore.Palette 1.0
import MuseScore.UiComponents 1.0
import MuseScore.Ui 1.0 import MuseScore.Ui 1.0
import MuseScore.UiComponents 1.0
import MuseScore.Palette 1.0
import "utils.js" as Utils import "utils.js" as Utils
@ -36,6 +36,8 @@ Item {
property alias popupMaxHeight: palettePopup.maxHeight property alias popupMaxHeight: palettePopup.maxHeight
property alias keynav: keynavSub
signal addCustomPaletteRequested(var paletteName) signal addCustomPaletteRequested(var paletteName)
implicitHeight: childrenRect.height implicitHeight: childrenRect.height
@ -55,20 +57,39 @@ Item {
} }
} }
KeyNavigationSubSection {
id: keynavSub
name: "PalettesHeader"
onActiveChanged: {
if (active) {
header.forceActiveFocus()
}
}
}
FlatButton { FlatButton {
id: morePalettesButton id: morePalettesButton
anchors.left: parent.left anchors.left: parent.left
anchors.right: searchTextButton.left anchors.right: searchTextButton.left
anchors.rightMargin: 8 anchors.rightMargin: 8
objectName: "AddPalettesBtn"
keynav.subsection: keynavSub
keynav.order: 1
enabled: !searchTextInput.visible
text: qsTrc("palette", "Add Palettes") text: qsTrc("palette", "Add Palettes")
onClicked: palettePopup.visible = !palettePopup.visible onClicked: {
palettePopup.visible = !palettePopup.visible
}
} }
FlatButton { FlatButton {
id: searchTextButton id: searchTextButton
anchors.right: parent.right anchors.right: parent.right
objectName: "SearchPalettesBtn"
keynav.subsection: keynavSub
keynav.order: 2
enabled: !searchTextInput.visible
icon: IconCode.SEARCH icon: IconCode.SEARCH
onClicked: { onClicked: {
toggleSearch() toggleSearch()
} }
@ -78,12 +99,47 @@ Item {
id: searchTextInput id: searchTextInput
width: parent.width width: parent.width
//! TODO Move to SearchField inside
KeyNavigationControl {
id: keynavSearchField
name: "SearchPalettesField"
subsection: keynavSub
order: 3
enabled: searchTextInput.visible
onActiveChanged: {
if (keynavSearchField.active) {
searchTextInput.forceActiveFocus()
}
}
}
KeyNavigationControl {
id: keynavSearchFieldClose
name: "SearchPalettesFieldClose"
subsection: keynavSub
order: 4
enabled: searchTextInput.visible && searchTextInput.clearTextButtonVisible
onTriggered: toggleSearch()
}
onVisibleChanged: {
if (!searchTextInput.visible) {
morePalettesButton.keynav.forceActive()
}
}
//! ----------
visible: false visible: false
onSearchTextChanged: resultsTimer.restart() onSearchTextChanged: resultsTimer.restart()
onActiveFocusChanged: { onActiveFocusChanged: {
resultsTimer.stop(); resultsTimer.stop();
Accessible.name = qsTrc("palette", "Palette Search") Accessible.name = qsTrc("palette", "Palette Search")
if (searchTextInput.activeFocus) {
keynavSearchField.forceActive()
}
} }
Timer { Timer {
@ -94,10 +150,6 @@ Item {
} }
} }
KeyNavigation.tab: paletteTree.currentTreeItem
Keys.onDownPressed: paletteTree.focusFirstItem();
Keys.onUpPressed: paletteTree.focusLastItem();
Keys.onEscapePressed: toggleSearch() Keys.onEscapePressed: toggleSearch()
clearTextButtonVisible: true clearTextButtonVisible: true
@ -137,12 +189,4 @@ Item {
createCustomPalettePopup.open() createCustomPalettePopup.open()
} }
} }
Connections {
target: palettesWidget
function onHasFocusChanged() {
if (!palettesWidget.hasFocus && !palettePopup.inMenuAction)
palettePopup.visible = false;
}
}
} }

View file

@ -37,6 +37,9 @@ Item {
property PaletteWorkspace paletteWorkspace property PaletteWorkspace paletteWorkspace
property var modelIndex: null property var modelIndex: null
property KeyNavigationSubSection keynavSubsection: null
property int keynavRow: 0
signal toggleExpandRequested() signal toggleExpandRequested()
signal enableEditingToggled(bool val) signal enableEditingToggled(bool val)
signal hideSelectedElementsRequested() signal hideSelectedElementsRequested()
@ -66,6 +69,11 @@ Item {
icon: paletteHeader.expanded ? IconCode.SMALL_ARROW_DOWN : IconCode.SMALL_ARROW_RIGHT icon: paletteHeader.expanded ? IconCode.SMALL_ARROW_DOWN : IconCode.SMALL_ARROW_RIGHT
normalStateColor: "transparent" normalStateColor: "transparent"
enabled: paletteExpandArrow.visible
keynav.subsection: paletteHeader.keynavSubsection
keynav.row: paletteHeader.keynavRow
keynav.column: 1
onClicked: paletteHeader.toggleExpandRequested() onClicked: paletteHeader.toggleExpandRequested()
} }
@ -99,8 +107,10 @@ Item {
activeFocusOnTab: mainPalette.currentItem === paletteTree.currentTreeItem activeFocusOnTab: mainPalette.currentItem === paletteTree.currentTreeItem
normalStateColor: "transparent" normalStateColor: "transparent"
KeyNavigation.backtab: mainPalette.currentItem enabled: deleteButton.visible
KeyNavigation.tab: focusBreaker keynav.subsection: paletteHeader.keynavSubsection
keynav.row: paletteHeader.keynavRow
keynav.column: 2
onClicked: { onClicked: {
hideSelectedElementsRequested() hideSelectedElementsRequested()
@ -115,7 +125,10 @@ Item {
visible: paletteHeader.expanded || paletteHeader.hovered || paletteHeaderMenu.visible visible: paletteHeader.expanded || paletteHeader.hovered || paletteHeaderMenu.visible
activeFocusOnTab: parent.parent.parent === paletteTree.currentTreeItem enabled: paletteHeaderMenuButton.visible
keynav.subsection: paletteHeader.keynavSubsection
keynav.row: paletteHeader.keynavRow
keynav.column: 3
icon: IconCode.MENU_THREE_DOTS icon: IconCode.MENU_THREE_DOTS
normalStateColor: "transparent" normalStateColor: "transparent"