xref: /core/sfx2/source/dialog/tabdlg.cxx (revision 503e0173)
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 
21 #include <stdlib.h>
22 #include <algorithm>
23 
24 #include <sfx2/tabdlg.hxx>
25 #include <sfx2/app.hxx>
26 #include <sfx2/sfxresid.hxx>
27 #include <sfx2/sfxdlg.hxx>
28 #include <unotools/viewoptions.hxx>
29 #include <vcl/virdev.hxx>
30 #include <sal/log.hxx>
31 #include <tools/debug.hxx>
32 #include <comphelper/lok.hxx>
33 
34 #include <sfx2/strings.hrc>
35 #include <helpids.h>
36 
37 using namespace ::com::sun::star::uno;
38 
39 #define USERITEM_NAME           "UserItem"
40 
41 
42 struct TabPageImpl
43 {
44     bool                        mbStandard;
45     SfxOkDialogController*      mpSfxDialogController;
46     css::uno::Reference< css::frame::XFrame > mxFrame;
47 
48     TabPageImpl() : mbStandard(false), mpSfxDialogController(nullptr) {}
49 };
50 
51 namespace {
52 
53 struct Data_Impl
54 {
55     OString sId;                  // The ID
56     CreateTabPage fnCreatePage;   // Pointer to Factory
57     GetTabPageRanges fnGetRanges; // Pointer to Ranges-Function
58     std::unique_ptr<SfxTabPage> xTabPage;         // The TabPage itself
59     bool bRefresh;                // Flag: Page must be re-initialized
60 
61     // Constructor
62     Data_Impl( const OString& rId, CreateTabPage fnPage,
63                GetTabPageRanges fnRanges ) :
64 
65         sId         ( rId ),
66         fnCreatePage( fnPage ),
67         fnGetRanges ( fnRanges ),
68         bRefresh    ( false )
69     {
70     }
71 };
72 
73 }
74 
75 SfxTabDialogItem::SfxTabDialogItem( const SfxTabDialogItem& rAttr, SfxItemPool* pItemPool )
76     : SfxSetItem( rAttr, pItemPool )
77 {
78 }
79 
80 SfxTabDialogItem::SfxTabDialogItem( sal_uInt16 nId, const SfxItemSet& rItemSet )
81     : SfxSetItem( nId, rItemSet )
82 {
83 }
84 
85 SfxTabDialogItem* SfxTabDialogItem::Clone(SfxItemPool* pToPool) const
86 {
87     return new SfxTabDialogItem( *this, pToPool );
88 }
89 
90 typedef std::vector<Data_Impl*> SfxTabDlgData_Impl;
91 
92 struct TabDlg_Impl
93 {
94     bool                bHideResetBtn : 1;
95     bool                bStarted : 1;
96     SfxTabDlgData_Impl  aData;
97 
98     explicit TabDlg_Impl(sal_uInt8 nCnt)
99         : bHideResetBtn(false)
100         , bStarted(false)
101     {
102         aData.reserve( nCnt );
103     }
104 };
105 
106 static Data_Impl* Find( const SfxTabDlgData_Impl& rArr, const OString& rId, sal_uInt16* pPos = nullptr)
107 {
108     const sal_uInt16 nCount = rArr.size();
109 
110     for ( sal_uInt16 i = 0; i < nCount; ++i )
111     {
112         Data_Impl* pObj = rArr[i];
113 
114         if ( pObj->sId == rId )
115         {
116             if ( pPos )
117                 *pPos = i;
118             return pObj;
119         }
120     }
121     return nullptr;
122 }
123 
124 void SfxTabPage::SetFrame(const css::uno::Reference< css::frame::XFrame >& xFrame)
125 {
126     if (pImpl)
127         pImpl->mxFrame = xFrame;
128 }
129 
130 css::uno::Reference< css::frame::XFrame > SfxTabPage::GetFrame() const
131 {
132     if (pImpl)
133         return pImpl->mxFrame;
134     return css::uno::Reference< css::frame::XFrame >();
135 }
136 
137 SfxTabPage::SfxTabPage(weld::Container* pPage, weld::DialogController* pController, const OUString& rUIXMLDescription, const OString& rID, const SfxItemSet *rAttrSet)
138     : BuilderPage(pPage, pController, rUIXMLDescription, rID)
139     , pSet                ( rAttrSet )
140     , bHasExchangeSupport ( false )
141     , pImpl               ( new TabPageImpl )
142 {
143     pImpl->mpSfxDialogController = dynamic_cast<SfxOkDialogController*>(m_pDialogController);
144 }
145 
146 SfxTabPage::~SfxTabPage()
147 {
148     if (m_xContainer)
149     {
150         std::unique_ptr<weld::Container> xParent(m_xContainer->weld_parent());
151         if (xParent)
152             xParent->move(m_xContainer.get(), nullptr);
153     }
154     m_xContainer.reset();
155     pImpl.reset();
156     m_xBuilder.reset();
157 }
158 
159 bool SfxTabPage::FillItemSet( SfxItemSet* )
160 {
161     return false;
162 }
163 
164 void SfxTabPage::Reset( const SfxItemSet* )
165 {
166 }
167 
168 void SfxTabPage::ActivatePage( const SfxItemSet& )
169 /*  [Description]
170 
171     Default implementation of the virtual ActivatePage method. This method is
172     called when a page of dialogue supports the exchange of data between pages.
173     <SfxTabPage::DeactivatePage(SfxItemSet *)>
174 */
175 {
176 }
177 
178 DeactivateRC SfxTabPage::DeactivatePage( SfxItemSet* )
179 
180 /*  [Description]
181 
182     Default implementation of the virtual DeactivatePage method. This method is
183     called by Sfx when leaving a page; the application can, through the return
184     value, control whether to leave the page. If the page is displayed through
185     bHasExchangeSupport which supports data exchange between pages, then a
186     pointer to the exchange set is passed as parameter. This takes on data for
187     the exchange, then the set is available as a parameter in
188     <SfxTabPage::ActivatePage(const SfxItemSet &)>.
189 
190     [Return value]
191 
192     DeactivateRC::LeavePage; Allow leaving the page
193 */
194 
195 {
196     return DeactivateRC::LeavePage;
197 }
198 
199 
200 void SfxTabPage::FillUserData()
201 
202 /*  [Description]
203 
204     Virtual method is called by the base class in the destructor to save
205     specific information of the TabPage in the ini-file. When overriding a
206     string must be compiled, which is then flushed with the <SetUserData()>.
207 */
208 
209 {
210 }
211 
212 
213 bool SfxTabPage::IsReadOnly() const
214 {
215     return false;
216 }
217 
218 
219 const SfxPoolItem* SfxTabPage::GetItem( const SfxItemSet& rSet, sal_uInt16 nSlot, bool bDeep )
220 
221 /*  [Description]
222 
223     static Method: hereby are the implementations of the TabPage code
224     being simplified.
225 */
226 
227 {
228     const SfxItemPool* pPool = rSet.GetPool();
229     sal_uInt16 nWh = pPool->GetWhich( nSlot, bDeep );
230     const SfxPoolItem* pItem = nullptr;
231     rSet.GetItemState( nWh, true, &pItem );
232 
233     if ( !pItem && nWh != nSlot )
234         pItem = &pPool->GetDefaultItem( nWh );
235     return pItem;
236 }
237 
238 
239 const SfxPoolItem* SfxTabPage::GetOldItem( const SfxItemSet& rSet,
240                                            sal_uInt16 nSlot, bool bDeep )
241 
242 /*  [Description]
243 
244     This method returns an attribute for comparison of the old value.
245 */
246 
247 {
248     const SfxItemSet& rOldSet = GetItemSet();
249     sal_uInt16 nWh = GetWhich( nSlot, bDeep );
250     const SfxPoolItem* pItem = nullptr;
251 
252     if ( pImpl->mbStandard && rOldSet.GetParent() )
253         pItem = GetItem( *rOldSet.GetParent(), nSlot );
254     else if ( rSet.GetParent() &&
255               SfxItemState::DONTCARE == rSet.GetItemState( nWh ) )
256         pItem = GetItem( *rSet.GetParent(), nSlot );
257     else
258         pItem = GetItem( rOldSet, nSlot );
259     return pItem;
260 }
261 
262 void SfxTabPage::PageCreated( const SfxAllItemSet& /*aSet*/ )
263 {
264     SAL_WARN( "sfx.dialog", "SfxTabPage::PageCreated should not be called");
265 }
266 
267 void SfxTabPage::ChangesApplied()
268 {
269 }
270 
271 void SfxTabPage::SetDialogController(SfxOkDialogController* pDialog)
272 {
273     pImpl->mpSfxDialogController = pDialog;
274     m_pDialogController = pImpl->mpSfxDialogController;
275 }
276 
277 SfxOkDialogController* SfxTabPage::GetDialogController() const
278 {
279     return pImpl->mpSfxDialogController;
280 }
281 
282 OString SfxTabPage::GetHelpId() const
283 {
284     if (m_xContainer)
285         return m_xContainer->get_help_id();
286     return OString();
287 }
288 
289 weld::Window* SfxTabPage::GetFrameWeld() const
290 {
291     if (m_pDialogController)
292         return m_pDialogController->getDialog();
293     return nullptr;
294 }
295 
296 const SfxItemSet* SfxTabPage::GetDialogExampleSet() const
297 {
298     if (pImpl->mpSfxDialogController)
299         return pImpl->mpSfxDialogController->GetExampleSet();
300     return nullptr;
301 }
302 
303 SfxTabDialogController::SfxTabDialogController
304 (
305     weld::Widget* pParent,              // Parent Window
306     const OUString& rUIXMLDescription, const OString& rID, // Dialog .ui path, Dialog Name
307     const SfxItemSet* pItemSet,   // Itemset with the data;
308                                   // can be NULL, when Pages are onDemand
309     bool bEditFmt                 // when yes -> additional Button for standard
310 )
311     : SfxOkDialogController(pParent, rUIXMLDescription, rID)
312     , m_xTabCtrl(m_xBuilder->weld_notebook("tabcontrol"))
313     , m_xOKBtn(m_xBuilder->weld_button("ok"))
314     , m_xApplyBtn(m_xBuilder->weld_button("apply"))
315     , m_xUserBtn(m_xBuilder->weld_button("user"))
316     , m_xCancelBtn(m_xBuilder->weld_button("cancel"))
317     , m_xResetBtn(m_xBuilder->weld_button("reset"))
318     , m_xBaseFmtBtn(m_xBuilder->weld_button("standard"))
319     , m_pSet(pItemSet ? new SfxItemSet(*pItemSet) : nullptr)
320     , m_bStandardPushed(false)
321 {
322     m_pImpl.reset(new TabDlg_Impl(m_xTabCtrl->get_n_pages()));
323     m_pImpl->bHideResetBtn = !m_xResetBtn->get_visible();
324     m_xOKBtn->connect_clicked(LINK(this, SfxTabDialogController, OkHdl));
325     m_xCancelBtn->connect_clicked(LINK(this, SfxTabDialogController, CancelHdl));
326     m_xResetBtn->connect_clicked(LINK(this, SfxTabDialogController, ResetHdl));
327     m_xResetBtn->set_label(SfxResId(STR_RESET));
328     m_xTabCtrl->connect_enter_page(LINK(this, SfxTabDialogController, ActivatePageHdl));
329     m_xTabCtrl->connect_leave_page(LINK(this, SfxTabDialogController, DeactivatePageHdl));
330     m_xResetBtn->set_help_id(HID_TABDLG_RESET_BTN);
331 
332     if (bEditFmt)
333     {
334         m_xBaseFmtBtn->set_label(SfxResId(STR_STANDARD_SHORTCUT));
335         m_xBaseFmtBtn->connect_clicked(LINK(this, SfxTabDialogController, BaseFmtHdl));
336         m_xBaseFmtBtn->set_help_id(HID_TABDLG_STANDARD_BTN);
337         m_xBaseFmtBtn->show();
338     }
339 
340     if (m_xUserBtn)
341         m_xUserBtn->connect_clicked(LINK(this, SfxTabDialogController, UserHdl));
342 
343     if (m_pSet)
344     {
345         m_xExampleSet.reset(new SfxItemSet(*m_pSet));
346         m_pOutSet.reset(new SfxItemSet(*m_pSet->GetPool(), m_pSet->GetRanges()));
347     }
348 
349     // The reset functionality seems to be confusing to many; disable in LOK.
350     if (comphelper::LibreOfficeKit::isActive())
351         RemoveResetButton();
352 }
353 
354 IMPL_LINK_NOARG(SfxTabDialogController, OkHdl, weld::Button&, void)
355 
356 /*  [Description]
357 
358     Handler of the Ok-Buttons
359     This calls the current page <SfxTabPage::DeactivatePage(SfxItemSet *)>.
360     Returns <DeactivateRC::LeavePage>, <SfxTabDialog::Ok()> is called
361     and the Dialog is ended.
362 */
363 
364 {
365     if (PrepareLeaveCurrentPage())
366         m_xDialog->response(Ok());
367 }
368 
369 IMPL_LINK_NOARG(SfxTabDialogController, UserHdl, weld::Button&, void)
370 
371 /*  [Description]
372 
373     Handler of the User-Buttons
374     This calls the current page <SfxTabPage::DeactivatePage(SfxItemSet *)>.
375     returns this <DeactivateRC::LeavePage> and  <SfxTabDialog::Ok()> is called.
376     Then the Dialog is ended with the Return value <SfxTabDialog::Ok()>
377 */
378 
379 {
380     if (PrepareLeaveCurrentPage())
381     {
382         short nRet = Ok();
383         if (RET_OK == nRet)
384             nRet = RET_USER;
385         else
386             nRet = RET_CANCEL;
387         m_xDialog->response(nRet);
388     }
389 }
390 
391 IMPL_LINK_NOARG(SfxTabDialogController, CancelHdl, weld::Button&, void)
392 {
393     m_xDialog->response(RET_CANCEL);
394 }
395 
396 IMPL_LINK_NOARG(SfxTabDialogController, ResetHdl, weld::Button&, void)
397 
398 /*  [Description]
399 
400     Handler behind the reset button.
401     The Current Page is new initialized with their initial data, all the
402     settings that the user has made on this page are repealed.
403 */
404 
405 {
406     Data_Impl* pDataObject = Find(m_pImpl->aData, m_xTabCtrl->get_current_page_ident());
407     assert(pDataObject && "Id not known");
408 
409     pDataObject->xTabPage->Reset(m_pSet.get());
410     // Also reset relevant items of ExampleSet and OutSet to initial state
411     if (!pDataObject->fnGetRanges)
412         return;
413 
414     if (!m_xExampleSet)
415         m_xExampleSet.reset(new SfxItemSet(*m_pSet));
416 
417     const SfxItemPool* pPool = m_pSet->GetPool();
418     const sal_uInt16* pTmpRanges = (pDataObject->fnGetRanges)();
419 
420     while (*pTmpRanges)
421     {
422         const sal_uInt16* pU = pTmpRanges + 1;
423 
424         // Correct Range with multiple values
425         sal_uInt16 nTmp = *pTmpRanges, nTmpEnd = *pU;
426         DBG_ASSERT(nTmp <= nTmpEnd, "Range is sorted the wrong way");
427 
428         if (nTmp > nTmpEnd)
429         {
430             // If really sorted wrongly, then set new
431             std::swap(nTmp, nTmpEnd);
432         }
433 
434         while (nTmp && nTmp <= nTmpEnd)
435         {
436             // Iterate over the Range and set the Items
437             sal_uInt16 nWh = pPool->GetWhich(nTmp);
438             const SfxPoolItem* pItem;
439             if (SfxItemState::SET == m_pSet->GetItemState(nWh, false, &pItem))
440             {
441                 m_xExampleSet->Put(*pItem);
442                 m_pOutSet->Put(*pItem);
443             }
444             else
445             {
446                 m_xExampleSet->ClearItem(nWh);
447                 m_pOutSet->ClearItem(nWh);
448             }
449             nTmp++;
450         }
451         // Go to the next pair
452         pTmpRanges += 2;
453     }
454 }
455 
456 /*  [Description]
457 
458     Handler behind the Standard-Button.
459     This button is available when editing style sheets. All the set attributes
460     in the edited stylesheet are deleted.
461 */
462 IMPL_LINK_NOARG(SfxTabDialogController, BaseFmtHdl, weld::Button&, void)
463 {
464     m_bStandardPushed = true;
465 
466     Data_Impl* pDataObject = Find(m_pImpl->aData, m_xTabCtrl->get_current_page_ident());
467     assert(pDataObject && "Id not known");
468 
469     if (!pDataObject->fnGetRanges)
470         return;
471 
472     if (!m_xExampleSet)
473         m_xExampleSet.reset(new SfxItemSet(*m_pSet));
474 
475     const SfxItemPool* pPool = m_pSet->GetPool();
476     const sal_uInt16* pTmpRanges = (pDataObject->fnGetRanges)();
477     SfxItemSet aTmpSet(*m_xExampleSet);
478 
479     while (*pTmpRanges)
480     {
481         const sal_uInt16* pU = pTmpRanges + 1;
482 
483         // Correct Range with multiple values
484         sal_uInt16 nTmp = *pTmpRanges, nTmpEnd = *pU;
485         DBG_ASSERT( nTmp <= nTmpEnd, "Range is sorted the wrong way" );
486 
487         if ( nTmp > nTmpEnd )
488         {
489             // If really sorted wrongly, then set new
490             std::swap(nTmp, nTmpEnd);
491         }
492 
493         while ( nTmp && nTmp <= nTmpEnd ) // guard against overflow
494         {
495             // Iterate over the Range and set the Items
496             sal_uInt16 nWh = pPool->GetWhich(nTmp);
497             m_xExampleSet->ClearItem(nWh);
498             aTmpSet.ClearItem(nWh);
499             // At the Outset of InvalidateItem,
500             // so that the change takes effect
501             m_pOutSet->InvalidateItem(nWh);
502             nTmp++;
503         }
504         // Go to the next pair
505         pTmpRanges += 2;
506     }
507     // Set all Items as new  -> the call the current Page Reset()
508     assert(pDataObject->xTabPage && "the Page is gone");
509     pDataObject->xTabPage->Reset( &aTmpSet );
510     pDataObject->xTabPage->pImpl->mbStandard = true;
511 }
512 
513 IMPL_LINK(SfxTabDialogController, ActivatePageHdl, const OString&, rPage, void)
514 
515 /*  [Description]
516 
517     Handler that is called by StarView for switching to a different page.
518     If possible the <SfxTabPage::Reset(const SfxItemSet &)> or
519     <SfxTabPage::ActivatePage(const SfxItemSet &)> is called on the new page
520 */
521 
522 {
523     assert(!m_pImpl->aData.empty() && "no Pages registered");
524     Data_Impl* pDataObject = Find(m_pImpl->aData, rPage);
525     if (!pDataObject)
526     {
527         SAL_WARN("sfx.dialog", "Tab Page ID not known, this is pretty serious and needs investigation");
528         return;
529     }
530 
531     SfxTabPage* pTabPage = pDataObject->xTabPage.get();
532     if (!pTabPage)
533         return;
534 
535     if (pDataObject->bRefresh)
536         pTabPage->Reset(m_pSet.get());
537     pDataObject->bRefresh = false;
538 
539     if (m_xExampleSet)
540         pTabPage->ActivatePage(*m_xExampleSet);
541 
542     if (pTabPage->IsReadOnly() || m_pImpl->bHideResetBtn)
543         m_xResetBtn->hide();
544     else
545         m_xResetBtn->show();
546 }
547 
548 IMPL_LINK(SfxTabDialogController, DeactivatePageHdl, const OString&, rPage, bool)
549 
550 /*  [Description]
551 
552     Handler that is called by StarView before leaving a page.
553 
554     [Cross-reference]
555 
556     <SfxTabPage::DeactivatePage(SfxItemSet *)>
557 */
558 
559 {
560     assert(!m_pImpl->aData.empty() && "no Pages registered");
561     Data_Impl* pDataObject = Find(m_pImpl->aData, rPage);
562     if (!pDataObject)
563     {
564         SAL_WARN("sfx.dialog", "Tab Page ID not known, this is pretty serious and needs investigation");
565         return false;
566     }
567 
568     SfxTabPage* pPage = pDataObject->xTabPage.get();
569     if (!pPage)
570         return true;
571 
572     DeactivateRC nRet = DeactivateRC::LeavePage;
573 
574     if (!m_xExampleSet && pPage->HasExchangeSupport() && m_pSet)
575         m_xExampleSet.reset(new SfxItemSet(*m_pSet->GetPool(), m_pSet->GetRanges()));
576 
577     if (m_pSet)
578     {
579         SfxItemSet aTmp( *m_pSet->GetPool(), m_pSet->GetRanges() );
580 
581         if (pPage->HasExchangeSupport())
582             nRet = pPage->DeactivatePage(&aTmp);
583         else
584             nRet = pPage->DeactivatePage(nullptr);
585         if ( ( DeactivateRC::LeavePage & nRet ) == DeactivateRC::LeavePage &&
586              aTmp.Count() && m_xExampleSet)
587         {
588             m_xExampleSet->Put( aTmp );
589             m_pOutSet->Put( aTmp );
590         }
591     }
592     else
593     {
594         if ( pPage->HasExchangeSupport() ) //!!!
595         {
596             if (!m_xExampleSet)
597             {
598                 SfxItemPool* pPool = pPage->GetItemSet().GetPool();
599                 m_xExampleSet.reset(new SfxItemSet(*pPool, GetInputRanges(*pPool)));
600             }
601             nRet = pPage->DeactivatePage(m_xExampleSet.get());
602         }
603         else
604             nRet = pPage->DeactivatePage( nullptr );
605     }
606 
607     if ( nRet & DeactivateRC::RefreshSet )
608     {
609         RefreshInputSet();
610         // Flag all Pages as to be initialized as new
611 
612         for (auto const& elem : m_pImpl->aData)
613         {
614             elem->bRefresh = ( elem->xTabPage.get() != pPage ); // Do not refresh own Page anymore
615         }
616     }
617     return static_cast<bool>(nRet & DeactivateRC::LeavePage);
618 }
619 
620 bool SfxTabDialogController::PrepareLeaveCurrentPage()
621 {
622     const OString sId = m_xTabCtrl->get_current_page_ident();
623     Data_Impl* pDataObject = Find(m_pImpl->aData, sId);
624     DBG_ASSERT( pDataObject, "Id not known" );
625     SfxTabPage* pPage = pDataObject ? pDataObject->xTabPage.get() : nullptr;
626 
627     bool bEnd = !pPage;
628 
629     if ( pPage )
630     {
631         DeactivateRC nRet = DeactivateRC::LeavePage;
632         if ( m_pSet )
633         {
634             SfxItemSet aTmp( *m_pSet->GetPool(), m_pSet->GetRanges() );
635 
636             if ( pPage->HasExchangeSupport() )
637                 nRet = pPage->DeactivatePage( &aTmp );
638             else
639                 nRet = pPage->DeactivatePage( nullptr );
640 
641             if ( ( DeactivateRC::LeavePage & nRet ) == DeactivateRC::LeavePage
642                  && aTmp.Count() )
643             {
644                 m_xExampleSet->Put( aTmp );
645                 m_pOutSet->Put( aTmp );
646             }
647         }
648         else
649             nRet = pPage->DeactivatePage( nullptr );
650         bEnd = nRet != DeactivateRC::KeepPage;
651     }
652 
653     return bEnd;
654 }
655 
656 const sal_uInt16* SfxTabDialogController::GetInputRanges(const SfxItemPool& rPool)
657 
658 /*  [Description]
659 
660     Makes the set over the range of all pages of the dialogue. Pages have the
661     static method for querying their range in AddTabPage, ie deliver their
662     sets onDemand.
663 
664     [Return value]
665 
666     Pointer to a null-terminated array of sal_uInt16. This array belongs to the
667     dialog and is deleted when the dialogue is destroy.
668 
669     [Cross-reference]
670 
671     <SfxTabDialog::AddTabPage(sal_uInt16, CreateTabPage, GetTabPageRanges, bool)>
672     <SfxTabDialog::AddTabPage(sal_uInt16, const String &, CreateTabPage, GetTabPageRanges, bool, sal_uInt16)>
673     <SfxTabDialog::AddTabPage(sal_uInt16, const Bitmap &, CreateTabPage, GetTabPageRanges, bool, sal_uInt16)>
674 */
675 
676 {
677     if ( m_pSet )
678     {
679         SAL_WARN( "sfx.dialog", "Set already exists!" );
680         return m_pSet->GetRanges();
681     }
682 
683     if ( m_pRanges )
684         return m_pRanges.get();
685     std::vector<sal_uInt16> aUS;
686 
687     for (auto const& elem : m_pImpl->aData)
688     {
689 
690         if ( elem->fnGetRanges )
691         {
692             const sal_uInt16* pTmpRanges = (elem->fnGetRanges)();
693             const sal_uInt16* pIter = pTmpRanges;
694 
695             sal_uInt16 nLen;
696             for( nLen = 0; *pIter; ++nLen, ++pIter )
697                 ;
698             aUS.insert( aUS.end(), pTmpRanges, pTmpRanges + nLen );
699         }
700     }
701 
702     //! Remove duplicated Ids?
703     {
704         for (auto & elem : aUS)
705             elem = rPool.GetWhich(elem);
706     }
707 
708     // sort
709     if ( aUS.size() > 1 )
710     {
711         std::sort( aUS.begin(), aUS.end() );
712     }
713 
714     m_pRanges.reset(new sal_uInt16[aUS.size() + 1]);
715     std::copy( aUS.begin(), aUS.end(), m_pRanges.get() );
716     m_pRanges[aUS.size()] = 0;
717     return m_pRanges.get();
718 }
719 
720 SfxTabDialogController::~SfxTabDialogController()
721 {
722     SavePosAndId();
723 
724     for (auto & elem : m_pImpl->aData)
725     {
726         if ( elem->xTabPage )
727         {
728             // save settings of all pages (user data)
729             elem->xTabPage->FillUserData();
730             OUString aPageData( elem->xTabPage->GetUserData() );
731             if ( !aPageData.isEmpty() )
732             {
733                 // save settings of all pages (user data)
734                 OUString sConfigId = OStringToOUString(elem->xTabPage->GetConfigId(),
735                     RTL_TEXTENCODING_UTF8);
736                 SvtViewOptions aPageOpt(EViewType::TabPage, sConfigId);
737                 aPageOpt.SetUserItem( USERITEM_NAME, makeAny( aPageData ) );
738             }
739 
740             elem->xTabPage.reset();
741         }
742         delete elem;
743         elem = nullptr;
744     }
745 }
746 
747 short SfxTabDialogController::Ok()
748 
749 /*  [Description]
750 
751     Ok handler for the Dialogue.
752 
753     Dialog's current location and current page are saved for the next time
754     the dialog is shown.
755 
756     The OutputSet is created and for each page this or the special OutputSet
757     is set by calling the method <SfxTabPage::FillItemSet(SfxItemSet &)>, to
758     insert the entered data by the user into the set.
759 
760     [Return value]
761 
762     RET_OK:       if at least one page has returned from FillItemSet,
763                   otherwise RET_CANCEL.
764 */
765 {
766     SavePosAndId(); //See fdo#38828 "Apply" resetting window position
767 
768     if ( !m_pOutSet )
769     {
770         if ( m_xExampleSet )
771             m_pOutSet.reset(new SfxItemSet( *m_xExampleSet ));
772         else if ( m_pSet )
773             m_pOutSet = m_pSet->Clone( false );  // without Items
774     }
775     bool bModified = false;
776 
777     for (auto const& elem : m_pImpl->aData)
778     {
779         SfxTabPage* pTabPage = elem->xTabPage.get();
780 
781         if ( pTabPage )
782         {
783             if ( m_pSet && !pTabPage->HasExchangeSupport() )
784             {
785                 SfxItemSet aTmp( *m_pSet->GetPool(), m_pSet->GetRanges() );
786 
787                 if ( pTabPage->FillItemSet( &aTmp ) )
788                 {
789                     bModified = true;
790                     if (m_xExampleSet)
791                         m_xExampleSet->Put( aTmp );
792                     m_pOutSet->Put( aTmp );
793                 }
794             }
795         }
796     }
797 
798     if (m_pOutSet && m_pOutSet->Count() > 0)
799         bModified = true;
800 
801     if (m_bStandardPushed)
802         bModified = true;
803 
804     return bModified ? RET_OK : RET_CANCEL;
805 }
806 
807 void SfxTabDialogController::RefreshInputSet()
808 
809 /*  [Description]
810 
811     Default implementation of the virtual Method.
812     This is called, when <SfxTabPage::DeactivatePage(SfxItemSet *)>
813     returns <DeactivateRC::RefreshSet>.
814 */
815 
816 {
817     SAL_INFO ( "sfx.dialog", "RefreshInputSet not implemented" );
818 }
819 
820 void SfxTabDialogController::PageCreated
821 
822 /*  [Description]
823 
824     Default implementation of the virtual method. This is called immediately
825     after creating a page. Here the dialogue can call the TabPage Method
826     directly.
827 */
828 
829 (
830     const OString&, // Id of the created page
831     SfxTabPage&     // Reference to the created page
832 )
833 {
834 }
835 
836 void SfxTabDialogController::SavePosAndId()
837 {
838     // save settings (screen position and current page)
839     SvtViewOptions aDlgOpt(EViewType::TabDialog, OStringToOUString(m_xDialog->get_help_id(), RTL_TEXTENCODING_UTF8));
840     aDlgOpt.SetPageID(m_xTabCtrl->get_current_page_ident());
841 }
842 
843 /*
844     Adds a page to the dialog. The Name must correspond to an entry in the
845     TabControl in the dialog .ui
846 */
847 void SfxTabDialogController::AddTabPage(const OString &rName /* Page ID */,
848                                         CreateTabPage pCreateFunc  /* Pointer to the Factory Method */,
849                                         GetTabPageRanges pRangesFunc /* Pointer to the Method for querying Ranges onDemand */)
850 {
851     m_pImpl->aData.push_back(new Data_Impl(rName, pCreateFunc, pRangesFunc));
852 }
853 
854 void SfxTabDialogController::AddTabPage(const OString &rName /* Page ID */,
855                                         sal_uInt16 nPageCreateId /* Identifier of the Factory Method to create the page */)
856 {
857     SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create();
858     CreateTabPage pCreateFunc = pFact->GetTabPageCreatorFunc(nPageCreateId);
859     GetTabPageRanges pRangesFunc = pFact->GetTabPageRangesFunc(nPageCreateId);
860     AddTabPage(rName, pCreateFunc, pRangesFunc);
861 }
862 
863 /*  [Description]
864 
865     Add a page to the dialog. The Rider text is passed on, the page has no
866     counterpart in the TabControl in the resource of the dialogue.
867 */
868 
869 void SfxTabDialogController::AddTabPage(const OString &rName, /* Page ID */
870                                         const OUString& rRiderText,
871                                         CreateTabPage pCreateFunc  /* Pointer to the Factory Method */)
872 {
873     assert(!m_xTabCtrl->get_page(rName) && "Double Page-Ids in the Tabpage");
874     m_xTabCtrl->append_page(rName, rRiderText);
875     AddTabPage(rName, pCreateFunc, nullptr);
876 }
877 
878 void SfxTabDialogController::AddTabPage(const OString &rName, const OUString& rRiderText,
879                                         sal_uInt16 nPageCreateId /* Identifier of the Factory Method to create the page */)
880 {
881     assert(!m_xTabCtrl->get_page(rName) && "Double Page-Ids in the Tabpage");
882     m_xTabCtrl->append_page(rName, rRiderText);
883     AddTabPage(rName, nPageCreateId);
884 }
885 
886 /*  [Description]
887 
888     Default implementation of the virtual Method.
889     This is called when pages create their sets onDemand.
890 */
891 SfxItemSet* SfxTabDialogController::CreateInputItemSet(const OString&)
892 {
893     SAL_WARN( "sfx.dialog", "CreateInputItemSet not implemented" );
894     return new SfxAllItemSet(SfxGetpApp()->GetPool());
895 }
896 
897 void SfxTabDialogController::CreatePages()
898 {
899     for (auto pDataObject : m_pImpl->aData)
900     {
901         if (pDataObject->xTabPage)
902            continue;
903         weld::Container* pPage = m_xTabCtrl->get_page(pDataObject->sId);
904         if (m_pSet)
905             pDataObject->xTabPage = (pDataObject->fnCreatePage)(pPage, this, m_pSet.get());
906         else
907             pDataObject->xTabPage = (pDataObject->fnCreatePage)(pPage, this, CreateInputItemSet(pDataObject->sId));
908         pDataObject->xTabPage->SetDialogController(this);
909         OUString sConfigId = OStringToOUString(pDataObject->xTabPage->GetConfigId(), RTL_TEXTENCODING_UTF8);
910         SvtViewOptions aPageOpt(EViewType::TabPage, sConfigId);
911         OUString sUserData;
912         Any aUserItem = aPageOpt.GetUserItem(USERITEM_NAME);
913         OUString aTemp;
914         if ( aUserItem >>= aTemp )
915             sUserData = aTemp;
916         pDataObject->xTabPage->SetUserData(sUserData);
917 
918         PageCreated(pDataObject->sId, *pDataObject->xTabPage);
919         pDataObject->xTabPage->Reset(m_pSet.get());
920     }
921 }
922 
923 void SfxTabDialogController::setPreviewsToSamePlace()
924 {
925     //where tab pages have the same basic layout with a preview on the right,
926     //get both of their non-preview areas to request the same size so that the
927     //preview appears in the same place in each one so flipping between tabs
928     //isn't distracting as it jumps around
929     std::vector<std::unique_ptr<weld::Widget>> aGrids;
930     for (auto pDataObject : m_pImpl->aData)
931     {
932         if (!pDataObject->xTabPage)
933             continue;
934         if (!pDataObject->xTabPage->m_xBuilder)
935             continue;
936         std::unique_ptr<weld::Widget> pGrid = pDataObject->xTabPage->m_xBuilder->weld_widget("maingrid");
937         if (!pGrid)
938             continue;
939         aGrids.emplace_back(std::move(pGrid));
940     }
941 
942     m_xSizeGroup.reset();
943 
944     if (aGrids.size() <= 1)
945         return;
946 
947     m_xSizeGroup = m_xBuilder->create_size_group();
948     m_xSizeGroup->set_mode(VclSizeGroupMode::Both);
949     for (auto& rGrid : aGrids)
950         m_xSizeGroup->add_widget(rGrid.get());
951 }
952 
953 void SfxTabDialogController::RemoveTabPage(const OString& rId)
954 
955 /*  [Description]
956 
957     Delete the TabPage with ID nId
958 */
959 
960 {
961     sal_uInt16 nPos = 0;
962     m_xTabCtrl->remove_page(rId);
963     Data_Impl* pDataObject = Find( m_pImpl->aData, rId, &nPos );
964 
965     if ( pDataObject )
966     {
967         if ( pDataObject->xTabPage )
968         {
969             pDataObject->xTabPage->FillUserData();
970             OUString aPageData( pDataObject->xTabPage->GetUserData() );
971             if ( !aPageData.isEmpty() )
972             {
973                 // save settings of this page (user data)
974                 OUString sConfigId = OStringToOUString(pDataObject->xTabPage->GetConfigId(),
975                     RTL_TEXTENCODING_UTF8);
976                 SvtViewOptions aPageOpt(EViewType::TabPage, sConfigId);
977                 aPageOpt.SetUserItem( USERITEM_NAME, makeAny( aPageData ) );
978             }
979 
980             pDataObject->xTabPage.reset();
981         }
982 
983         delete pDataObject;
984         m_pImpl->aData.erase( m_pImpl->aData.begin() + nPos );
985     }
986     else
987     {
988         SAL_INFO( "sfx.dialog", "TabPage-Id not known" );
989     }
990 }
991 
992 void SfxTabDialogController::Start_Impl()
993 {
994     CreatePages();
995 
996     setPreviewsToSamePlace();
997 
998     assert(m_pImpl->aData.size() == static_cast<size_t>(m_xTabCtrl->get_n_pages())
999             && "not all pages registered");
1000 
1001     // load old settings, when exists, setting SetCurPageId will override the settings,
1002     // something that the sort dialog in calc depends on
1003     if (m_sAppPageId.isEmpty())
1004     {
1005         SvtViewOptions aDlgOpt(EViewType::TabDialog, OStringToOUString(m_xDialog->get_help_id(), RTL_TEXTENCODING_UTF8));
1006         if (aDlgOpt.Exists())
1007             m_xTabCtrl->set_current_page(aDlgOpt.GetPageID());
1008     }
1009 
1010     ActivatePageHdl(m_xTabCtrl->get_current_page_ident());
1011 
1012     m_pImpl->bStarted = true;
1013 }
1014 
1015 void SfxTabDialogController::SetCurPageId(const OString& rIdent)
1016 {
1017     m_sAppPageId = rIdent;
1018     m_xTabCtrl->set_current_page(m_sAppPageId);
1019 }
1020 
1021 /*  [Description]
1022 
1023     The TabPage is activated with the specified Id.
1024 */
1025 void SfxTabDialogController::ShowPage(const OString& rIdent)
1026 {
1027     SetCurPageId(rIdent);
1028     ActivatePageHdl(rIdent);
1029 }
1030 
1031 OString SfxTabDialogController::GetCurPageId() const
1032 {
1033     return m_xTabCtrl->get_current_page_ident();
1034 }
1035 
1036 short SfxTabDialogController::run()
1037 {
1038     Start_Impl();
1039     return SfxDialogController::run();
1040 }
1041 
1042 bool SfxTabDialogController::runAsync(const std::shared_ptr<SfxTabDialogController>& rController,
1043                                       const std::function<void(sal_Int32)>& rFunc)
1044 {
1045     rController->Start_Impl();
1046     return weld::DialogController::runAsync(rController, rFunc);
1047 }
1048 
1049 void SfxTabDialogController::SetInputSet( const SfxItemSet* pInSet )
1050 
1051 /*  [Description]
1052 
1053     With this method the Input-Set can subsequently be set initially or re-set.
1054 */
1055 
1056 {
1057     bool bSet = ( m_pSet != nullptr );
1058     m_pSet.reset(pInSet ? new SfxItemSet(*pInSet) : nullptr);
1059 
1060     if (!bSet && !m_xExampleSet && !m_pOutSet && m_pSet)
1061     {
1062         m_xExampleSet.reset(new SfxItemSet(*m_pSet));
1063         m_pOutSet.reset(new SfxItemSet( *m_pSet->GetPool(), m_pSet->GetRanges() ));
1064     }
1065 }
1066 
1067 SfxItemSet* SfxTabDialogController::GetInputSetImpl()
1068 
1069 /*  [Description]
1070 
1071     Derived classes may create new storage for the InputSet. This has to be
1072     released in the Destructor. To do this, this method must be called.
1073 */
1074 
1075 {
1076     return m_pSet.get();
1077 }
1078 
1079 void SfxTabDialogController::RemoveResetButton()
1080 {
1081     m_xResetBtn->hide();
1082     m_pImpl->bHideResetBtn = true;
1083 }
1084 
1085 void SfxTabDialogController::RemoveStandardButton()
1086 {
1087     m_xBaseFmtBtn->hide();
1088 }
1089 
1090 SfxTabPage* SfxTabDialogController::GetTabPage(const OString& rPageId) const
1091 
1092 /*  [Description]
1093 
1094     Return TabPage with the specified Id.
1095 */
1096 
1097 {
1098     Data_Impl* pDataObject = Find(m_pImpl->aData, rPageId);
1099     if (pDataObject)
1100         return pDataObject->xTabPage.get();
1101     return nullptr;
1102 }
1103 
1104 void SfxTabDialogController::SetApplyHandler(const Link<weld::Button&, void>& _rHdl)
1105 {
1106     DBG_ASSERT( m_xApplyBtn, "SfxTabDialog::GetApplyHandler: no apply button enabled!" );
1107     if (m_xApplyBtn)
1108         m_xApplyBtn->connect_clicked(_rHdl);
1109 }
1110 
1111 bool SfxTabDialogController::Apply()
1112 {
1113     bool bApplied = false;
1114     if (PrepareLeaveCurrentPage())
1115     {
1116         bApplied = (Ok() == RET_OK);
1117         //let the pages update their saved values
1118         GetInputSetImpl()->Put(*GetOutputItemSet());
1119         for (auto pDataObject : m_pImpl->aData)
1120         {
1121             if (!pDataObject->xTabPage)
1122                 continue;
1123             pDataObject->xTabPage->ChangesApplied();
1124         }
1125     }
1126     return bApplied;
1127 }
1128 
1129 std::vector<OString> SfxTabDialogController::getAllPageUIXMLDescriptions() const
1130 {
1131     int nPages = m_xTabCtrl->get_n_pages();
1132     std::vector<OString> aRet;
1133     aRet.reserve(nPages);
1134     for (int i = 0; i < nPages; ++i)
1135         aRet.push_back(m_xTabCtrl->get_page_ident(i));
1136     return aRet;
1137 }
1138 
1139 bool SfxTabDialogController::selectPageByUIXMLDescription(const OString& rUIXMLDescription)
1140 {
1141     ShowPage(rUIXMLDescription);
1142     return m_xTabCtrl->get_current_page_ident() == rUIXMLDescription;
1143 }
1144 
1145 BitmapEx SfxTabDialogController::createScreenshot() const
1146 {
1147     // if we haven't run Start_Impl yet, do so now to create the initial pages
1148     if (!m_pImpl->bStarted)
1149     {
1150         const_cast<SfxTabDialogController*>(this)->Start_Impl();
1151     }
1152 
1153     VclPtr<VirtualDevice> xDialogSurface(VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT));
1154     m_xDialog->draw(*xDialogSurface);
1155     return xDialogSurface->GetBitmapEx(Point(), xDialogSurface->GetOutputSizePixel());
1156 }
1157 
1158 OString SfxTabDialogController::GetScreenshotId() const
1159 {
1160     const OString sId = m_xTabCtrl->get_current_page_ident();
1161     Data_Impl* pDataObject = Find(m_pImpl->aData, sId);
1162     SfxTabPage* pPage = pDataObject ? pDataObject->xTabPage.get() : nullptr;
1163     if (pPage)
1164     {
1165         OString sHelpId(pPage->GetHelpId());
1166         if (!sHelpId.isEmpty())
1167             return sHelpId;
1168     }
1169     return m_xDialog->get_help_id();
1170 }
1171 
1172 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1173