summaryrefslogtreecommitdiffstats
path: root/sfx2/source/sidebar/FocusManager.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sfx2/source/sidebar/FocusManager.cxx')
-rw-r--r--sfx2/source/sidebar/FocusManager.cxx478
1 files changed, 478 insertions, 0 deletions
diff --git a/sfx2/source/sidebar/FocusManager.cxx b/sfx2/source/sidebar/FocusManager.cxx
new file mode 100644
index 0000000000..e86de2b7de
--- /dev/null
+++ b/sfx2/source/sidebar/FocusManager.cxx
@@ -0,0 +1,478 @@
+/* -*- 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)
+ 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: */