1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sfx2/sidebar/FocusManager.hxx>
21 #include <sfx2/sidebar/Panel.hxx>
22 #include <sfx2/sidebar/DeckTitleBar.hxx>
23 #include <sfx2/sidebar/PanelTitleBar.hxx>
24 #include <sfx2/sidebar/Tools.hxx>
25 #include <sfx2/sidebar/TitleBar.hxx>
26 #include <vcl/button.hxx>
27 #include <vcl/event.hxx>
28 #include <vcl/toolbox.hxx>
29 #include <vcl/svapp.hxx>
30 #include <toolkit/helper/vclunohelper.hxx>
31 
32 #include <sfx2/app.hxx>
33 
34 namespace sfx2 { namespace sidebar {
35 
FocusLocation(const PanelComponent eComponent,const sal_Int32 nIndex)36 FocusManager::FocusLocation::FocusLocation (const PanelComponent eComponent, const sal_Int32 nIndex)
37     : meComponent(eComponent),
38       mnIndex(nIndex)
39 {
40 }
41 
FocusManager(const std::function<void (const Panel &)> & rShowPanelFunctor,const std::function<bool (const sal_Int32)> & rIsDeckOpenFunctor)42 FocusManager::FocusManager(const std::function<void(const Panel&)>& rShowPanelFunctor,
43                            const std::function<bool(const sal_Int32)>& rIsDeckOpenFunctor)
44     : mpDeckTitleBar(),
45       maPanels(),
46       maButtons(),
47       maShowPanelFunctor(rShowPanelFunctor),
48       mbIsDeckOpenFunctor(rIsDeckOpenFunctor)
49 {
50 }
51 
~FocusManager()52 FocusManager::~FocusManager()
53 {
54     Clear();
55 }
56 
GrabFocus()57 void FocusManager::GrabFocus()
58 {
59     FocusDeckTitle();
60 }
61 
GrabFocusPanel()62 void FocusManager::GrabFocusPanel()
63 {
64     FocusPanel(0, false);
65 }
66 
Clear()67 void FocusManager::Clear()
68 {
69     SetDeckTitle(nullptr);
70     ClearPanels();
71     ClearButtons();
72 }
73 
ClearPanels()74 void FocusManager::ClearPanels()
75 {
76     std::vector<VclPtr<Panel> > aPanels;
77     aPanels.swap(maPanels);
78     for (auto const& panel : aPanels)
79     {
80         UnregisterWindow(*panel);
81         if (panel->GetTitleBar())
82         {
83             UnregisterWindow(*panel->GetTitleBar());
84             UnregisterWindow(panel->GetTitleBar()->GetToolBox());
85         }
86 
87         panel->RemoveChildEventListener(LINK(this, FocusManager, ChildEventListener));
88     }
89 }
90 
ClearButtons()91 void FocusManager::ClearButtons()
92 {
93     std::vector<VclPtr<Button> > aButtons;
94     aButtons.swap(maButtons);
95     for (auto const& button : aButtons)
96     {
97         UnregisterWindow(*button);
98     }
99 }
100 
SetDeckTitle(DeckTitleBar * pDeckTitleBar)101 void FocusManager::SetDeckTitle (DeckTitleBar* pDeckTitleBar)
102 {
103     if (mpDeckTitleBar != nullptr)
104     {
105         UnregisterWindow(*mpDeckTitleBar);
106         UnregisterWindow(mpDeckTitleBar->GetToolBox());
107     }
108     mpDeckTitleBar = pDeckTitleBar;
109 
110     if (mpDeckTitleBar != nullptr)
111     {
112         RegisterWindow(*mpDeckTitleBar);
113         RegisterWindow(mpDeckTitleBar->GetToolBox());
114     }
115 }
116 
SetPanels(const SharedPanelContainer & rPanels)117 void FocusManager::SetPanels (const SharedPanelContainer& rPanels)
118 {
119     ClearPanels();
120     for (auto const& panel : rPanels)
121     {
122         RegisterWindow(*panel);
123         if (panel->GetTitleBar())
124         {
125             RegisterWindow(*panel->GetTitleBar());
126             RegisterWindow(panel->GetTitleBar()->GetToolBox());
127         }
128 
129         // Register also as child event listener at the panel.
130         panel->AddChildEventListener(LINK(this, FocusManager, ChildEventListener));
131 
132         maPanels.emplace_back(panel.get());
133     }
134 }
135 
SetButtons(const::std::vector<Button * > & rButtons)136 void FocusManager::SetButtons (const ::std::vector<Button*>& rButtons)
137 {
138     ClearButtons();
139     for (auto const& button : rButtons)
140     {
141         RegisterWindow(*button);
142         maButtons.emplace_back(button);
143     }
144 }
145 
RegisterWindow(vcl::Window & rWindow)146 void FocusManager::RegisterWindow (vcl::Window& rWindow)
147 {
148     rWindow.AddEventListener(LINK(this, FocusManager, WindowEventListener));
149 }
150 
UnregisterWindow(vcl::Window & rWindow)151 void FocusManager::UnregisterWindow (vcl::Window& rWindow)
152 {
153     rWindow.RemoveEventListener(LINK(this, FocusManager, WindowEventListener));
154 }
155 
GetFocusLocation(const vcl::Window & rWindow) const156 FocusManager::FocusLocation FocusManager::GetFocusLocation (const vcl::Window& rWindow) const
157 {
158     // Check the deck title.
159     if (mpDeckTitleBar != nullptr)
160     {
161         if (mpDeckTitleBar == &rWindow)
162             return FocusLocation(PC_DeckTitle, -1);
163         else if (&mpDeckTitleBar->GetToolBox() == &rWindow)
164             return FocusLocation(PC_DeckToolBox, -1);
165     }
166 
167     // Search the panels.
168     for (size_t nIndex = 0; nIndex < maPanels.size(); ++nIndex)
169     {
170         if (maPanels[nIndex] == &rWindow)
171             return FocusLocation(PC_PanelContent, nIndex);
172         VclPtr<TitleBar> pTitleBar = maPanels[nIndex]->GetTitleBar();
173         if (pTitleBar == &rWindow)
174             return FocusLocation(PC_PanelTitle, nIndex);
175         if (pTitleBar!=nullptr && &pTitleBar->GetToolBox()==&rWindow)
176             return FocusLocation(PC_PanelToolBox, nIndex);
177     }
178 
179     // Search the buttons.
180     for (size_t nIndex=0; nIndex < maButtons.size(); ++nIndex)
181     {
182         if (maButtons[nIndex] == &rWindow)
183             return FocusLocation(PC_TabBar, nIndex);
184     }
185     return FocusLocation(PC_None, -1);
186 }
187 
FocusDeckTitle()188 void FocusManager::FocusDeckTitle()
189 {
190     if (mpDeckTitleBar != nullptr)
191     {
192         if (IsDeckTitleVisible())
193         {
194             mpDeckTitleBar->GrabFocus();
195         }
196         else if (mpDeckTitleBar->GetToolBox().GetItemCount() > 0)
197         {
198             ToolBox& rToolBox = mpDeckTitleBar->GetToolBox();
199             rToolBox.GrabFocus();
200             rToolBox.Invalidate();
201         }
202         else
203             FocusPanel(0, false);
204     }
205     else
206         FocusPanel(0, false);
207 }
208 
IsDeckTitleVisible() const209 bool FocusManager::IsDeckTitleVisible() const
210 {
211     return mpDeckTitleBar != nullptr && mpDeckTitleBar->IsVisible();
212 }
213 
IsPanelTitleVisible(const sal_Int32 nPanelIndex) const214 bool FocusManager::IsPanelTitleVisible (const sal_Int32 nPanelIndex) const
215 {
216     if (nPanelIndex<0 || nPanelIndex>=static_cast<sal_Int32>(maPanels.size()))
217         return false;
218 
219     VclPtr<TitleBar> pTitleBar = maPanels[nPanelIndex]->GetTitleBar();
220     if (!pTitleBar)
221         return false;
222     return pTitleBar->IsVisible();
223 }
224 
FocusPanel(const sal_Int32 nPanelIndex,const bool bFallbackToDeckTitle)225 void FocusManager::FocusPanel (
226     const sal_Int32 nPanelIndex,
227     const bool bFallbackToDeckTitle)
228 {
229     if (nPanelIndex<0 || nPanelIndex>=static_cast<sal_Int32>(maPanels.size()))
230     {
231         if (bFallbackToDeckTitle)
232             FocusDeckTitle();
233         return;
234     }
235 
236     Panel& rPanel (*maPanels[nPanelIndex]);
237     VclPtr<TitleBar> pTitleBar = rPanel.GetTitleBar();
238     if (pTitleBar && pTitleBar->IsVisible())
239     {
240         rPanel.SetExpanded(true);
241         pTitleBar->GrabFocus();
242     }
243     else if (bFallbackToDeckTitle)
244     {
245         // The panel title is not visible, fall back to the deck
246         // title.
247         // Make sure that the desk title is visible here to prevent a
248         // loop when both the title of panel 0 and the deck title are
249         // not present.
250         if (IsDeckTitleVisible())
251             FocusDeckTitle();
252         else
253             FocusPanelContent(nPanelIndex);
254     }
255     else
256         FocusPanelContent(nPanelIndex);
257 
258     if (maShowPanelFunctor)
259         maShowPanelFunctor(rPanel);
260 }
261 
FocusPanelContent(const sal_Int32 nPanelIndex)262 void FocusManager::FocusPanelContent (const sal_Int32 nPanelIndex)
263 {
264     if (!maPanels[nPanelIndex]->IsExpanded())
265         maPanels[nPanelIndex]->SetExpanded(true);
266 
267     VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(maPanels[nPanelIndex]->GetElementWindow());
268     if (pWindow)
269         pWindow->GrabFocus();
270 }
271 
FocusButton(const sal_Int32 nButtonIndex)272 void FocusManager::FocusButton (const sal_Int32 nButtonIndex)
273 {
274     maButtons[nButtonIndex]->GrabFocus();
275     maButtons[nButtonIndex]->Invalidate();
276 }
277 
ClickButton(const sal_Int32 nButtonIndex)278 void FocusManager::ClickButton (const sal_Int32 nButtonIndex)
279 {
280     if (mbIsDeckOpenFunctor)
281     {
282         if (!mbIsDeckOpenFunctor(-1) || !mbIsDeckOpenFunctor(nButtonIndex-1))
283             maButtons[nButtonIndex]->Click();
284     }
285     if (nButtonIndex > 0)
286         FocusPanel(0, true);
287     maButtons[nButtonIndex]->GetParent()->Invalidate();
288 }
289 
RemoveWindow(vcl::Window & rWindow)290 void FocusManager::RemoveWindow (vcl::Window& rWindow)
291 {
292     auto iPanel (::std::find(maPanels.begin(), maPanels.end(), &rWindow));
293     if (iPanel != maPanels.end())
294     {
295         UnregisterWindow(rWindow);
296         if ((*iPanel)->GetTitleBar() != nullptr)
297         {
298             UnregisterWindow(*(*iPanel)->GetTitleBar());
299             UnregisterWindow((*iPanel)->GetTitleBar()->GetToolBox());
300         }
301         maPanels.erase(iPanel);
302         return;
303     }
304 
305     auto iButton (::std::find(maButtons.begin(), maButtons.end(), &rWindow));
306     if (iButton != maButtons.end())
307     {
308         UnregisterWindow(rWindow);
309         maButtons.erase(iButton);
310         return;
311     }
312 }
313 
MoveFocusInsidePanel(const FocusLocation & rFocusLocation,const sal_Int32 nDirection)314 void FocusManager::MoveFocusInsidePanel (
315     const FocusLocation& rFocusLocation,
316     const sal_Int32 nDirection)
317 {
318     const bool bHasToolBoxItem (
319         maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GetToolBox().GetItemCount() > 0);
320     switch (rFocusLocation.meComponent)
321     {
322         case  PC_PanelTitle:
323             if (nDirection > 0 && bHasToolBoxItem)
324                 maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GetToolBox().GrabFocus();
325             else
326                 FocusPanelContent(rFocusLocation.mnIndex);
327             break;
328 
329         case PC_PanelToolBox:
330             if (nDirection < 0 && bHasToolBoxItem)
331                 maPanels[rFocusLocation.mnIndex]->GetTitleBar()->GrabFocus();
332             else
333                 FocusPanelContent(rFocusLocation.mnIndex);
334             break;
335 
336         default: break;
337     }
338 }
339 
MoveFocusInsideDeckTitle(const FocusLocation & rFocusLocation,const sal_Int32 nDirection)340 void FocusManager::MoveFocusInsideDeckTitle (
341     const FocusLocation& rFocusLocation,
342     const sal_Int32 nDirection)
343 {
344     // Note that when the title bar of the first (and only) panel is
345     // not visible then the deck title takes its place and the focus
346     // is moved between a) deck title, b) deck closer and c) content
347     // of panel 0.
348     const bool bHasToolBoxItem (
349         mpDeckTitleBar->GetToolBox().GetItemCount() > 0);
350     switch (rFocusLocation.meComponent)
351     {
352         case  PC_DeckTitle:
353             if (nDirection<0 && ! IsPanelTitleVisible(0))
354                 FocusPanelContent(0);
355             else if (bHasToolBoxItem)
356                 mpDeckTitleBar->GetToolBox().GrabFocus();
357             break;
358 
359         case PC_DeckToolBox:
360             if (nDirection>0 && ! IsPanelTitleVisible(0))
361                 FocusPanelContent(0);
362             else
363                 mpDeckTitleBar->GrabFocus();
364             break;
365 
366         default: break;
367     }
368 }
369 
HandleKeyEvent(const vcl::KeyCode & rKeyCode,const vcl::Window & rWindow)370 void FocusManager::HandleKeyEvent (
371     const vcl::KeyCode& rKeyCode,
372     const vcl::Window& rWindow)
373 {
374     const FocusLocation aLocation (GetFocusLocation(rWindow));
375 
376     switch (rKeyCode.GetCode())
377     {
378         case KEY_ESCAPE:
379             switch (aLocation.meComponent)
380             {
381                 case PC_TabBar:
382                 case PC_DeckTitle:
383                 case PC_DeckToolBox:
384                 case PC_PanelTitle:
385                 case PC_PanelToolBox:
386                 {
387                     vcl::Window* pFocusWin = Application::GetFocusWindow();
388                     if (pFocusWin)
389                         pFocusWin->GrabFocusToDocument();
390                     break;
391                 }
392 
393                 default:
394                     break;
395             }
396             return;
397 
398         case KEY_SPACE:
399             switch (aLocation.meComponent)
400             {
401                 case PC_PanelTitle:
402                     // Toggle panel between expanded and collapsed.
403                     maPanels[aLocation.mnIndex]->SetExpanded( ! maPanels[aLocation.mnIndex]->IsExpanded());
404                     maPanels[aLocation.mnIndex]->GetTitleBar()->Invalidate();
405                     break;
406 
407                 default:
408                     break;
409             }
410             return;
411 
412         case KEY_RETURN:
413             switch (aLocation.meComponent)
414             {
415                 case PC_DeckToolBox:
416                     FocusButton(0);
417                     break;
418 
419                 case PC_PanelTitle:
420                     // Enter the panel.
421                     FocusPanelContent(aLocation.mnIndex);
422                     break;
423 
424                 case PC_TabBar:
425                     // Activate the button.
426                     ClickButton(aLocation.mnIndex);
427                     break;
428 
429                 default:
430                     break;
431             }
432             return;
433 
434         case KEY_TAB:
435         {
436             const sal_Int32 nDirection (
437                 rKeyCode.IsShift()
438                     ? -1
439                     : +1);
440             switch (aLocation.meComponent)
441             {
442                 case PC_PanelTitle:
443                 case PC_PanelToolBox:
444                 case PC_PanelContent:
445                     MoveFocusInsidePanel(aLocation, nDirection);
446                     break;
447 
448                 case PC_DeckTitle:
449                 case PC_DeckToolBox:
450                     MoveFocusInsideDeckTitle(aLocation, nDirection);
451                     break;
452 
453                 default:
454                     break;
455             }
456             break;
457         }
458 
459         case KEY_LEFT:
460         case KEY_UP:
461             switch (aLocation.meComponent)
462             {
463                 case PC_PanelTitle:
464                 case PC_PanelToolBox:
465                 case PC_PanelContent:
466                     // Go to previous panel or the deck title.
467                     if (aLocation.mnIndex > 0)
468                         FocusPanel(aLocation.mnIndex-1, true);
469                     else if (IsDeckTitleVisible())
470                         FocusDeckTitle();
471                     else
472                     {
473                         // Focus the last button.
474                         sal_Int32 nIndex(maButtons.size()-1);
475                         while(!maButtons[nIndex]->IsVisible() && --nIndex > 0);
476                         FocusButton(nIndex);
477                     }
478                     break;
479 
480                 case PC_DeckTitle:
481                 case PC_DeckToolBox:
482                 {
483                     // Focus the last button.
484                     sal_Int32 nIndex(maButtons.size()-1);
485                     while(!maButtons[nIndex]->IsVisible() && --nIndex > 0);
486                     FocusButton(nIndex);
487                     break;
488                 }
489 
490                 case PC_TabBar:
491                     // Go to previous tab bar item.
492                     if (aLocation.mnIndex == 0)
493                         FocusPanel(maPanels.size()-1, true);
494                     else
495                     {
496                         sal_Int32 nIndex((aLocation.mnIndex + maButtons.size() - 1) % maButtons.size());
497                         while(!maButtons[nIndex]->IsVisible() && --nIndex > 0);
498                         FocusButton(nIndex);
499                     }
500                     break;
501 
502                 default:
503                     break;
504             }
505             break;
506 
507         case KEY_RIGHT:
508         case KEY_DOWN:
509             switch(aLocation.meComponent)
510             {
511                 case PC_PanelTitle:
512                 case PC_PanelToolBox:
513                 case PC_PanelContent:
514                     // Go to next panel.
515                     if (aLocation.mnIndex < static_cast<sal_Int32>(maPanels.size())-1)
516                         FocusPanel(aLocation.mnIndex+1, false);
517                     else
518                         FocusButton(0);
519                     break;
520 
521                 case PC_DeckTitle:
522                 case PC_DeckToolBox:
523                     // Focus the first panel.
524                     if (IsPanelTitleVisible(0))
525                         FocusPanel(0, false);
526                     else
527                         FocusButton(0);
528                     break;
529 
530                 case PC_TabBar:
531                     // Go to next tab bar item.
532                     if (aLocation.mnIndex < static_cast<sal_Int32>(maButtons.size())-1)
533                     {
534                         sal_Int32 nIndex(aLocation.mnIndex + 1);
535                         while(!maButtons[nIndex]->IsVisible() && ++nIndex < static_cast<sal_Int32>(maButtons.size()));
536                         if (nIndex < static_cast<sal_Int32>(maButtons.size()))
537                         {
538                             FocusButton(nIndex);
539                             break;
540                         }
541                     }
542                     if (IsDeckTitleVisible())
543                         FocusDeckTitle();
544                     else
545                         FocusPanel(0, true);
546                     break;
547 
548                 default:
549                     break;
550             }
551             break;
552     }
553 }
554 
IMPL_LINK(FocusManager,WindowEventListener,VclWindowEvent &,rWindowEvent,void)555 IMPL_LINK(FocusManager, WindowEventListener, VclWindowEvent&, rWindowEvent, void)
556 {
557     vcl::Window* pSource = rWindowEvent.GetWindow();
558     if (pSource == nullptr)
559         return;
560 
561     switch (rWindowEvent.GetId())
562     {
563         case VclEventId::WindowKeyInput:
564         {
565             KeyEvent* pKeyEvent = static_cast<KeyEvent*>(rWindowEvent.GetData());
566             HandleKeyEvent(pKeyEvent->GetKeyCode(), *pSource);
567             break;
568         }
569 
570         case VclEventId::ObjectDying:
571             RemoveWindow(*pSource);
572             break;
573 
574         case VclEventId::WindowGetFocus:
575         case VclEventId::WindowLoseFocus:
576             pSource->Invalidate();
577             break;
578 
579         default:
580             break;
581     }
582 }
583 
IMPL_LINK(FocusManager,ChildEventListener,VclWindowEvent &,rEvent,void)584 IMPL_LINK(FocusManager, ChildEventListener, VclWindowEvent&, rEvent, void)
585 {
586     vcl::Window* pSource = rEvent.GetWindow();
587     if (pSource == nullptr)
588         return;
589 
590     switch (rEvent.GetId())
591     {
592         case VclEventId::WindowKeyInput:
593         {
594             KeyEvent* pKeyEvent = static_cast<KeyEvent*>(rEvent.GetData());
595 
596             // Go up the window hierarchy to find out whether the
597             // parent of the event source is known to us.
598             vcl::Window* pWindow = pSource;
599             FocusLocation aLocation (PC_None, -1);
600             while (true)
601             {
602                 if (pWindow == nullptr)
603                     break;
604                 aLocation = GetFocusLocation(*pWindow);
605                 if (aLocation.meComponent != PC_None)
606                     break;
607                 pWindow = pWindow->GetParent();
608             }
609 
610             if (aLocation.meComponent != PC_None)
611             {
612                 switch (pKeyEvent->GetKeyCode().GetCode())
613                 {
614                     case KEY_ESCAPE:
615                         // Return focus to tab bar sidebar settings button or panel title.
616                         if (!IsDeckTitleVisible() && maPanels.size() == 1)
617                             FocusButton(0);
618                         else
619                             FocusPanel(aLocation.mnIndex, true);
620                         break;
621 
622                     default:
623                         break;
624                 }
625             }
626             return;
627         }
628 
629         default:
630             break;
631     }
632 }
633 
634 } } // end of namespace sfx2::sidebar
635 
636 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
637