office-gobmx/sfx2/source/sidebar/FocusManager.cxx
Rafael Lima 6113d5dc45 tdf#161782 Focus document when Esc is pressed in the Gallery/Navigator
When Esc is pressed on a Panel that has no visible title, move focus to the document.

Change-Id: Ib5571dbd9f8a37017e21d2e01e7b546eb2330751
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/169668
Reviewed-by: Jim Raykowski <raykowj@gmail.com>
Reviewed-by: Rafael Lima <rafael.palma.lima@gmail.com>
Tested-by: Jenkins
2024-07-15 20:57:43 +02:00

480 lines
15 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <o3tl/safeint.hxx>
#include <sfx2/sidebar/FocusManager.hxx>
#include <sfx2/sidebar/Deck.hxx>
#include <sfx2/sidebar/Panel.hxx>
#include <sidebar/DeckTitleBar.hxx>
#include <sidebar/PanelTitleBar.hxx>
#include <sidebar/TitleBar.hxx>
#include <utility>
#include <vcl/event.hxx>
#include <vcl/weld.hxx>
namespace sfx2::sidebar {
FocusManager::FocusLocation::FocusLocation (const PanelComponent eComponent, const sal_Int32 nIndex)
: meComponent(eComponent),
mnIndex(nIndex)
{
}
FocusManager::FocusManager(std::function<void(const Panel&)> aShowPanelFunctor)
: mpDeckTitleBar(nullptr),
maShowPanelFunctor(std::move(aShowPanelFunctor))
{
}
FocusManager::~FocusManager()
{
Clear();
}
void FocusManager::GrabFocus()
{
FocusDeckTitle();
}
void FocusManager::GrabFocusPanel()
{
FocusPanel(0, false);
}
void FocusManager::Clear()
{
SetDeck(nullptr);
ClearPanels();
ClearButtons();
}
void FocusManager::ClearPanels()
{
SharedPanelContainer aPanels;
aPanels.swap(maPanels);
for (auto const& panel : aPanels)
{
if (panel->GetTitleBar())
{
UnregisterWindow(panel->GetTitleBar()->GetToolBox());
UnregisterWindow(panel->GetTitleBar()->GetExpander());
}
weld::Container* pContents = panel->GetContents();
UnregisterWindow(*pContents);
}
}
void FocusManager::ClearButtons()
{
std::vector<weld::Widget*> aButtons;
aButtons.swap(maButtons);
for (auto const& button : aButtons)
{
UnregisterWindow(*button);
}
}
void FocusManager::SetDeck(Deck* pDeck)
{
DeckTitleBar* pDeckTitleBar = pDeck ? pDeck->GetTitleBar() : nullptr;
if (mpDeckTitleBar != nullptr)
UnregisterWindow(mpDeckTitleBar->GetToolBox());
mxDeck = pDeck;
mpDeckTitleBar = pDeckTitleBar;
if (mpDeckTitleBar != nullptr)
RegisterWindow(mpDeckTitleBar->GetToolBox());
}
void FocusManager::SetPanels (const SharedPanelContainer& rPanels)
{
ClearPanels();
for (auto const& panel : rPanels)
{
if (panel->GetTitleBar())
{
RegisterWindow(panel->GetTitleBar()->GetToolBox());
RegisterWindow(panel->GetTitleBar()->GetExpander());
}
// Register also as key event listener at the panel.
weld::Container* pContents = panel->GetContents();
RegisterWindow(*pContents);
maPanels.emplace_back(panel);
}
}
void FocusManager::SetButtons(const std::vector<weld::Widget*>& rButtons)
{
ClearButtons();
for (auto const& button : rButtons)
{
RegisterWindow(*button);
maButtons.emplace_back(button);
}
}
void FocusManager::RegisterWindow(weld::Widget& rWidget)
{
UnregisterWindow(rWidget); // explicitly unset key press handler so we can reconnect without warnings
rWidget.connect_key_press(LINK(this, FocusManager, KeyInputHdl));
}
void FocusManager::UnregisterWindow(weld::Widget& rWidget)
{
rWidget.connect_key_press(Link<const KeyEvent&, bool>());
}
FocusManager::FocusLocation FocusManager::GetFocusLocation() const
{
// Check the deck title.
if (mpDeckTitleBar && mpDeckTitleBar->GetToolBox().has_focus())
return FocusLocation(PC_DeckToolBox, -1);
// Search the panels.
for (size_t nIndex = 0; nIndex < maPanels.size(); ++nIndex)
{
PanelTitleBar* pTitleBar = maPanels[nIndex]->GetTitleBar();
if (!pTitleBar)
continue;
if (pTitleBar->GetExpander().has_focus())
return FocusLocation(PC_PanelTitle, nIndex);
if (pTitleBar->GetToolBox().has_focus())
return FocusLocation(PC_PanelToolBox, nIndex);
weld::Container* pContents = maPanels[nIndex]->GetContents();
if (pContents->has_child_focus())
return FocusLocation(PC_PanelContent, nIndex);
}
// Search the buttons.
for (size_t nIndex=0; nIndex < maButtons.size(); ++nIndex)
{
if (maButtons[nIndex]->has_focus())
return FocusLocation(PC_TabBar, nIndex);
}
return FocusLocation(PC_None, -1);
}
void FocusManager::FocusDeckTitle()
{
if (mpDeckTitleBar != nullptr)
{
if (mpDeckTitleBar->GetToolBox().get_n_items() > 0)
{
weld::Toolbar& rToolBox = mpDeckTitleBar->GetToolBox();
rToolBox.grab_focus();
}
else
FocusPanel(0, false);
}
else
FocusPanel(0, false);
}
bool FocusManager::IsDeckTitleVisible() const
{
return mpDeckTitleBar != nullptr && mpDeckTitleBar->GetVisible();
}
void FocusManager::FocusPanel (
const sal_Int32 nPanelIndex,
const bool bFallbackToDeckTitle)
{
if (nPanelIndex<0 || o3tl::make_unsigned(nPanelIndex)>=maPanels.size())
{
if (bFallbackToDeckTitle)
FocusDeckTitle();
return;
}
Panel& rPanel (*maPanels[nPanelIndex]);
PanelTitleBar* pTitleBar = rPanel.GetTitleBar();
if (pTitleBar && pTitleBar->GetVisible())
{
rPanel.SetExpanded(true);
pTitleBar->GetExpander().grab_focus();
}
// Fallback to deck title should only be applicable when there is more than one panel,
// or else it will never be possible to enter the panel contents when there's a single panel
// without a titlebar and expander
else if (bFallbackToDeckTitle && maPanels.size() > 1)
{
// The panel title is not visible, fall back to the deck
// title.
// Make sure that the desk title is visible here to prevent a
// loop when both the title of panel 0 and the deck title are
// not present.
if (IsDeckTitleVisible())
FocusDeckTitle();
else
FocusPanelContent(nPanelIndex);
}
else
FocusPanelContent(nPanelIndex);
if (maShowPanelFunctor)
maShowPanelFunctor(rPanel);
}
void FocusManager::FocusPanelContent(const sal_Int32 nPanelIndex)
{
if (!maPanels[nPanelIndex]->IsExpanded())
maPanels[nPanelIndex]->SetExpanded(true);
weld::Container* pContents = maPanels[nPanelIndex]->GetContents();
pContents->child_grab_focus();
}
void FocusManager::FocusButton (const sal_Int32 nButtonIndex)
{
maButtons[nButtonIndex]->grab_focus();
}
void FocusManager::MoveFocusInsidePanel (
const FocusLocation& rFocusLocation,
const sal_Int32 nDirection)
{
const bool bHasToolBoxItem (
maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GetToolBox().get_n_items() > 0);
switch (rFocusLocation.meComponent)
{
case PC_PanelTitle:
if (nDirection > 0 && bHasToolBoxItem)
maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GetToolBox().grab_focus();
else
FocusPanelContent(rFocusLocation.mnIndex);
break;
case PC_PanelToolBox:
if (nDirection < 0 && bHasToolBoxItem)
maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GetExpander().grab_focus();
else
FocusPanelContent(rFocusLocation.mnIndex);
break;
default: break;
}
}
bool FocusManager::HandleKeyEvent(
const vcl::KeyCode& rKeyCode,
const FocusLocation& aLocation)
{
bool bConsumed = false;
switch (rKeyCode.GetCode())
{
case KEY_ESCAPE:
switch (aLocation.meComponent)
{
case PC_TabBar:
case PC_DeckToolBox:
case PC_PanelTitle:
case PC_PanelToolBox:
{
if (mxDeck)
{
mxDeck->GrabFocusToDocument();
bConsumed = true;
}
break;
}
case PC_PanelContent:
// Return focus to tab bar sidebar settings button or panel title.
if ((!IsDeckTitleVisible() && maPanels.size() == 1) ||
(!maPanels[aLocation.mnIndex]->GetTitleBar()->GetVisible()))
FocusButton(0);
else
FocusPanel(aLocation.mnIndex, true);
bConsumed = true;
break;
default:
break;
}
return bConsumed;
case KEY_RETURN:
switch (aLocation.meComponent)
{
case PC_PanelTitle:
// Enter the panel.
FocusPanelContent(aLocation.mnIndex);
bConsumed = true;
break;
default:
break;
}
return bConsumed;
case KEY_TAB:
{
const sal_Int32 nDirection (
rKeyCode.IsShift()
? -1
: +1);
switch (aLocation.meComponent)
{
case PC_PanelTitle:
case PC_PanelToolBox:
if (rKeyCode.IsShift())
break;
MoveFocusInsidePanel(aLocation, nDirection);
bConsumed = true;
break;
case PC_DeckToolBox:
{
// Moves to the first deck activation button that is visible and sensitive
sal_Int32 nIndex(0);
sal_Int32 nButtons(maButtons.size());
if (nButtons > 1)
{
nIndex = 1;
// Finds the next visible button that is sensitive
while((!maButtons[nIndex]->get_visible() ||
!maButtons[nIndex]->get_sensitive()) && ++nIndex < nButtons);
// Wrap to the menu button when going past the last button
if (nIndex >= nButtons)
nIndex = 0;
}
FocusButton(nIndex);
bConsumed = true;
}
break;
case PC_TabBar:
if (rKeyCode.IsShift())
{
if (IsDeckTitleVisible())
FocusDeckTitle();
else
FocusPanel(0, true);
}
else
FocusPanel(0, true);
bConsumed = true;
break;
default:
break;
}
break;
}
case KEY_LEFT:
case KEY_UP:
switch (aLocation.meComponent)
{
case PC_PanelTitle:
case PC_PanelToolBox:
// Go to previous panel or the deck title.
if (aLocation.mnIndex > 0)
FocusPanel(aLocation.mnIndex-1, true);
else if (IsDeckTitleVisible())
FocusDeckTitle();
else
{
// Set focus to the last visible sensitive button.
sal_Int32 nIndex(maButtons.size()-1);
while((!maButtons[nIndex]->get_visible() ||
!maButtons[nIndex]->get_sensitive()) && --nIndex > 0);
FocusButton(nIndex);
}
bConsumed = true;
break;
case PC_TabBar:
{
if (rKeyCode.GetCode() == KEY_LEFT)
break;
sal_Int32 nIndex;
if (aLocation.mnIndex <= 0)
nIndex = maButtons.size() - 1;
else
nIndex = aLocation.mnIndex - 1;
// Finds the previous visible sensitive button
while((!maButtons[nIndex]->get_visible() ||
!maButtons[nIndex]->get_sensitive()) && --nIndex > 0);
FocusButton(nIndex);
bConsumed = true;
}
break;
default:
break;
}
break;
case KEY_RIGHT:
case KEY_DOWN:
switch(aLocation.meComponent)
{
case PC_PanelTitle:
case PC_PanelToolBox:
// Go to next panel.
if (aLocation.mnIndex < static_cast<sal_Int32>(maPanels.size())-1)
FocusPanel(aLocation.mnIndex+1, false);
else
FocusButton(0);
bConsumed = true;
break;
case PC_TabBar:
{
if (rKeyCode.GetCode() == KEY_RIGHT)
break;
sal_Int32 nButtons(maButtons.size());
sal_Int32 nIndex = aLocation.mnIndex + 1;
if (nIndex >= nButtons)
nIndex = 0;
// Finds the next visible sensitive button
while((!maButtons[nIndex]->get_visible() ||
!maButtons[nIndex]->get_sensitive()) && ++nIndex < nButtons);
// Wrap to the menu button when going past the last button
if (nIndex >= nButtons)
nIndex = 0;
FocusButton(nIndex);
bConsumed = true;
}
break;
default:
break;
}
break;
}
return bConsumed;
}
IMPL_LINK(FocusManager, KeyInputHdl, const KeyEvent&, rKeyEvent, bool)
{
return HandleKeyEvent(rKeyEvent.GetKeyCode(), GetFocusLocation());
}
} // end of namespace sfx2::sidebar
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */