xref: /core/cui/source/dialogs/tipofthedaydlg.cxx (revision ecf6f6f2)
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 <sal/config.h>
21 #include <tipofthedaydlg.hxx>
22 #include <tipoftheday.hrc>
23 
24 #include <sfx2/viewfrm.hxx>
25 #include <vcl/commandinfoprovider.hxx>
26 #include <vcl/graphicfilter.hxx>
27 #include <vcl/help.hxx>
28 #include <vcl/window.hxx>
29 #include <vcl/ImageTree.hxx>
30 
31 #include <com/sun/star/frame/XDispatch.hpp>
32 #include <com/sun/star/frame/XDispatchProvider.hpp>
33 #include <com/sun/star/util/URL.hpp>
34 #include <com/sun/star/util/URLTransformer.hpp>
35 
36 #include <comphelper/dispatchcommand.hxx>
37 #include <dialmgr.hxx>
38 #include <i18nlangtag/languagetag.hxx>
39 #include <officecfg/Office/Common.hxx>
40 #include <osl/file.hxx>
41 #include <rtl/bootstrap.hxx>
42 #include <toolkit/helper/vclunohelper.hxx>
43 #include <unotools/resmgr.hxx>
44 #include <unotools/configmgr.hxx>
45 #include <com/sun/star/beans/PropertyValue.hpp>
46 #include <bitmaps.hlst>
47 
48 //size of preview
49 const Size ThumbSize(150, 150);
50 
TipOfTheDayDialog(weld::Window * pParent)51 TipOfTheDayDialog::TipOfTheDayDialog(weld::Window* pParent)
52     : GenericDialogController(pParent, u"cui/ui/tipofthedaydialog.ui"_ustr,
53                               u"TipOfTheDayDialog"_ustr)
54     , m_xParent(pParent ? pParent->GetXWindow() : nullptr)
55     , m_pText(m_xBuilder->weld_label(u"lbText"_ustr))
56     , m_pShowTip(m_xBuilder->weld_check_button(u"cbShowTip"_ustr))
57     , m_pNext(m_xBuilder->weld_button(u"btnNext"_ustr))
58     , m_pLink(m_xBuilder->weld_link_button(u"btnLink"_ustr))
59     , m_pPreview(new weld::CustomWeld(*m_xBuilder, u"imPreview"_ustr, m_aPreview))
60 {
61     m_pShowTip->set_active(officecfg::Office::Common::Misc::ShowTipOfTheDay::get());
62     m_pNext->connect_clicked(LINK(this, TipOfTheDayDialog, OnNextClick));
63     m_nCurrentTip = officecfg::Office::Common::Misc::LastTipOfTheDayID::get();
64     m_pPreview->set_size_request(ThumbSize.Width(), ThumbSize.Height());
65 
66     if (m_xParent.is())
67     {
68         VclPtr<vcl::Window> xVclWin(VCLUnoHelper::GetWindow(m_xParent));
69         if (xVclWin != nullptr)
70             xVclWin->AddEventListener(LINK(this, TipOfTheDayDialog, Terminated));
71     }
72 
73     const auto t0 = std::chrono::system_clock::now().time_since_epoch();
74     sal_Int32 nDay = std::chrono::duration_cast<std::chrono::hours>(t0).count() / 24;
75     //show next tip after one day
76     if (nDay > officecfg::Office::Common::Misc::LastTipOfTheDayShown::get())
77         m_nCurrentTip++;
78 
79     // save this time to the config now instead of in the dtor otherwise we
80     // end up with multiple copies of this dialog every time we open a new
81     // document if the first one isn't closed
82     std::shared_ptr<comphelper::ConfigurationChanges> xChanges(
83         comphelper::ConfigurationChanges::create());
84     officecfg::Office::Common::Misc::LastTipOfTheDayShown::set(nDay, xChanges);
85     xChanges->commit();
86 
87     UpdateTip();
88 }
89 
IMPL_LINK(TipOfTheDayDialog,Terminated,VclWindowEvent &,rEvent,void)90 IMPL_LINK(TipOfTheDayDialog, Terminated, VclWindowEvent&, rEvent, void)
91 {
92     if (rEvent.GetId() == VclEventId::ObjectDying)
93     {
94         m_xParent.clear();
95         TipOfTheDayDialog::response(RET_OK);
96     }
97 }
98 
~TipOfTheDayDialog()99 TipOfTheDayDialog::~TipOfTheDayDialog()
100 {
101     std::shared_ptr<comphelper::ConfigurationChanges> xChanges(
102         comphelper::ConfigurationChanges::create());
103     officecfg::Office::Common::Misc::LastTipOfTheDayID::set(m_nCurrentTip, xChanges);
104     officecfg::Office::Common::Misc::ShowTipOfTheDay::set(m_pShowTip->get_active(), xChanges);
105     xChanges->commit();
106 
107     if (m_xParent.is())
108     {
109         VclPtr<vcl::Window> xVclWin(VCLUnoHelper::GetWindow(m_xParent));
110         if (xVclWin != nullptr)
111             xVclWin->RemoveEventListener(LINK(this, TipOfTheDayDialog, Terminated));
112     }
113 }
114 
file_exists(const OUString & fileName)115 static bool file_exists(const OUString& fileName)
116 {
117     ::osl::File aFile(fileName);
118     return aFile.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None;
119 }
120 
UpdateTip()121 void TipOfTheDayDialog::UpdateTip()
122 {
123     constexpr sal_Int32 nNumberOfTips = std::size(TIPOFTHEDAY_STRINGARRAY);
124 
125     for (;;)
126     {
127         if ((m_nCurrentTip >= nNumberOfTips) || (m_nCurrentTip < 0))
128             m_nCurrentTip = 0;
129         if (std::get<1>(TIPOFTHEDAY_STRINGARRAY[m_nCurrentTip])
130                 == "svx/ui/safemodedialog/SafeModeDialog"
131             && !officecfg::Office::Common::Misc::OfferSafeMode::get())
132         {
133             ++m_nCurrentTip;
134         }
135         else
136         {
137             break;
138         }
139     }
140 
141     //title
142     m_xDialog->set_title(CuiResId(STR_TITLE)
143                              .replaceFirst("%CURRENT", OUString::number(m_nCurrentTip + 1))
144                              .replaceFirst("%TOTAL", OUString::number(nNumberOfTips)));
145 
146     auto[sTip, sLink, sImage, nType] = TIPOFTHEDAY_STRINGARRAY[m_nCurrentTip];
147 
148     // text
149 //replace MOD1 & MOD2 shortcuts depending on platform
150 #ifdef MACOSX
151     const OUString aMOD1 = CuiResId(STR_CMD);
152     const OUString aMOD2 = CuiResId(STR_Option);
153 #else
154     const OUString aMOD1 = CuiResId(STR_CTRL);
155     const OUString aMOD2 = CuiResId(STR_Alt);
156 #endif
157     m_pText->set_label(CuiResId(sTip).replaceAll("%MOD1", aMOD1).replaceAll("%MOD2", aMOD2));
158 
159     // hyperlink
160     if (sLink.isEmpty())
161     {
162         m_pLink->set_visible(false);
163     }
164     else if (sLink.startsWith(".uno:"))
165     {
166         m_pLink->set_visible(false);
167         //show the link only if the UNO command is available in the current module
168         if (SfxViewFrame* pViewFrame = SfxViewFrame::Current())
169         {
170             const auto xFrame = pViewFrame->GetFrame().GetFrameInterface();
171             const css::uno::Reference<css::frame::XDispatchProvider> xDispatchProvider(
172                 xFrame, css::uno::UNO_QUERY);
173             if (xDispatchProvider.is())
174             {
175                 css::util::URL aCommandURL;
176                 aCommandURL.Complete = sLink;
177                 const css::uno::Reference<css::uno::XComponentContext> xContext
178                     = comphelper::getProcessComponentContext();
179                 const css::uno::Reference<css::util::XURLTransformer> xParser
180                     = css::util::URLTransformer::create(xContext);
181                 xParser->parseStrict(aCommandURL);
182                 const css::uno::Reference<css::frame::XDispatch> xDisp
183                     = xDispatchProvider->queryDispatch(aCommandURL, OUString(), 0);
184                 if (xDisp.is())
185                 {
186                     m_pLink->set_label(CuiResId(STR_UNO_LINK));
187                     m_pLink->set_uri(sLink);
188 
189                     const OUString aModuleName(
190                         vcl::CommandInfoProvider::GetModuleIdentifier(xFrame));
191                     const auto aProperties
192                         = vcl::CommandInfoProvider::GetCommandProperties(sLink, aModuleName);
193                     m_pLink->set_tooltip_text(
194                         vcl::CommandInfoProvider::GetTooltipForCommand(sLink, aProperties, xFrame));
195 
196                     m_pLink->set_visible(true);
197                     m_pLink->connect_activate_link(LINK(this, TipOfTheDayDialog, OnLinkClick));
198                 }
199             }
200         }
201     }
202     else if (sLink.startsWith("http"))
203     {
204         // Links may have some %PRODUCTVERSION which need to be expanded
205         OUString aText = Translate::ExpandVariables(sLink);
206         OUString aLang = LanguageTag(utl::ConfigManager::getUILocale()).getLanguage();
207         if (aLang == "en" || aLang == "pt" || aLang == "zh") //en-US/GB, pt-BR, zh-CH/TW
208             aLang = LanguageTag(utl::ConfigManager::getUILocale()).getBcp47();
209         m_pLink->set_uri(aText.replaceFirst("%LANGUAGENAME", aLang));
210         m_pLink->set_label(CuiResId(STR_MORE_LINK));
211         m_pLink->set_visible(true);
212         m_pLink->connect_activate_link(Link<weld::LinkButton&, bool>());
213     }
214     else
215     {
216         m_pLink->set_uri(sLink);
217         m_pLink->set_label(CuiResId(STR_HELP_LINK));
218         m_pLink->set_visible(true);
219         m_pLink->connect_activate_link(LINK(this, TipOfTheDayDialog, OnLinkClick));
220     }
221     // image
222     OUString aURL(u"$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/tipoftheday/"_ustr);
223     rtl::Bootstrap::expandMacros(aURL);
224     OUString aImageName = sImage;
225     Graphic aGraphic;
226 
227     if (!aImageName.isEmpty() && file_exists(aURL + aImageName))
228         GraphicFilter::LoadGraphic(aURL + aImageName, OUString(), aGraphic);
229     else
230     {
231         const OUString sModuleImage[5]
232             = { RID_SVXBMP_TOTD_WRITER, RID_SVXBMP_TOTD_CALC, RID_SVXBMP_TOTD_DRAW,
233                 RID_SVXBMP_TOTD_IMPRESS, RID_SVXBMP_TOTD_SOFFICE };
234         const OUString aIconTheme
235             = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
236         BitmapEx aBmpEx;
237         ImageTree::get().loadImage(sModuleImage[nType], aIconTheme, aBmpEx, true,
238                                    ImageLoadFlags::IgnoreDarkTheme);
239         aGraphic = aBmpEx;
240     }
241 
242     if (!aGraphic.IsAnimated())
243     {
244         BitmapEx aBmpEx(aGraphic.GetBitmapEx());
245         if (aBmpEx.Scale(ThumbSize))
246             aGraphic = aBmpEx;
247     }
248     m_aPreview.SetPreview(aGraphic);
249 }
250 
IMPL_LINK(TipOfTheDayDialog,OnLinkClick,weld::LinkButton &,rButton,bool)251 IMPL_LINK(TipOfTheDayDialog, OnLinkClick, weld::LinkButton&, rButton, bool)
252 {
253     const OUString sLink = rButton.get_uri();
254     if (sLink.startsWith(".uno:"))
255     {
256         comphelper::dispatchCommand(sLink, {});
257         TipOfTheDayDialog::response(RET_OK);
258     }
259     else
260     {
261         Application::GetHelp()->Start(sLink, static_cast<weld::Widget*>(nullptr));
262     }
263     return true;
264 }
265 
IMPL_LINK_NOARG(TipOfTheDayDialog,OnNextClick,weld::Button &,void)266 IMPL_LINK_NOARG(TipOfTheDayDialog, OnNextClick, weld::Button&, void)
267 {
268     m_nCurrentTip++; //zeroed at updatetip when out of range
269     UpdateTip();
270 }
271 
272 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
273