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 
10 #include <sal/config.h>
11 
12 #include <iostream>
13 
14 #include <test/screenshot_test.hxx>
15 
16 #include <com/sun/star/frame/Desktop.hpp>
17 #include <comphelper/processfactory.hxx>
18 #include <vcl/abstdlg.hxx>
19 #include <vcl/pngwrite.hxx>
20 #include <vcl/svapp.hxx>
21 #include <vcl/virdev.hxx>
22 #include <vcl/weld.hxx>
23 #include <tools/stream.hxx>
24 
25 
26 namespace {
splitHelpId(const OString & rHelpId,OUString & rDirname,OUString & rBasename)27     void splitHelpId( const OString& rHelpId, OUString& rDirname, OUString &rBasename )
28     {
29         sal_Int32 nIndex = rHelpId.lastIndexOf( '/' );
30 
31         if( nIndex > 0 )
32             rDirname = OStringToOUString( rHelpId.subView( 0, nIndex ), RTL_TEXTENCODING_UTF8 );
33 
34         if( rHelpId.getLength() > nIndex+1 )
35             rBasename= OStringToOUString( rHelpId.subView( nIndex+1 ), RTL_TEXTENCODING_UTF8 );
36     }
37 }
38 
39 using namespace css;
40 using namespace css::uno;
41 
42     /// the target directory for screenshots
43 constexpr OUStringLiteral g_aScreenshotDirectory(u"screenshots");
44 
ScreenshotTest()45 ScreenshotTest::ScreenshotTest()
46     : maKnownDialogs()
47     , maParent(nullptr, "vcl/ui/screenshotparent.ui", "ScreenShot")
48     , mxParentWidget(maParent.getDialog()->weld_content_area())
49 {
50     if (auto const env = getenv("LO_TEST_LOCALE")) {
51         maCurrentLanguage = OUString::fromUtf8(env);
52     }
53 }
54 
~ScreenshotTest()55 ScreenshotTest::~ScreenshotTest()
56 {
57 }
58 
setUp()59 void ScreenshotTest::setUp()
60 {
61     test::BootstrapFixture::setUp();
62 
63     mxDesktop = css::frame::Desktop::create( comphelper::getComponentContext(getMultiServiceFactory()) );
64     CPPUNIT_ASSERT_MESSAGE("no desktop!", mxDesktop.is());
65 
66     osl::Directory::create( m_directories.getURLFromWorkdir( g_aScreenshotDirectory)) ;
67 
68     // initialize maKnownDialogs
69     if (maKnownDialogs.empty())
70     {
71         registerKnownDialogsByID(maKnownDialogs);
72     }
73 }
74 
implSaveScreenshot(const BitmapEx & rScreenshot,const OString & rScreenshotId)75 void ScreenshotTest::implSaveScreenshot(const BitmapEx& rScreenshot, const OString& rScreenshotId)
76 {
77     OUString aDirname, aBasename;
78     splitHelpId(rScreenshotId, aDirname, aBasename);
79     aDirname = g_aScreenshotDirectory + "/" + aDirname +
80                ( (maCurrentLanguage == "en-US") ? OUString() : "/" + maCurrentLanguage );
81 
82     auto const dirUrl = m_directories.getURLFromWorkdir(aDirname);
83     auto const e = osl::Directory::createPath(dirUrl);
84     if (e != osl::FileBase::E_EXIST) {
85         CPPUNIT_ASSERT_EQUAL_MESSAGE(
86             OString("Failed to create " + OUStringToOString(dirUrl, RTL_TEXTENCODING_UTF8))
87                 .getStr(),
88             osl::FileBase::E_None, e);
89     }
90 
91     auto const pngUrl = OUString(dirUrl + "/" + aBasename + ".png");
92     SvFileStream aNew(pngUrl, StreamMode::WRITE | StreamMode::TRUNC);
93     CPPUNIT_ASSERT_MESSAGE(OString("Failed to open <" + OUStringToOString(pngUrl, RTL_TEXTENCODING_UTF8) + ">: " + OString::number(sal_uInt32(aNew.GetErrorCode()))).getStr(), aNew.IsOpen());
94 
95     std::cout << "saving " << pngUrl << ":\n";
96     vcl::PNGWriter aPNGWriter(rScreenshot);
97     aPNGWriter.Write(aNew);
98 }
99 
saveScreenshot(VclAbstractDialog const & rDialog)100 void ScreenshotTest::saveScreenshot(VclAbstractDialog const & rDialog)
101 {
102     const BitmapEx aScreenshot(rDialog.createScreenshot());
103 
104     if (!aScreenshot.IsEmpty())
105     {
106         const OString aScreenshotId = rDialog.GetScreenshotId();
107 
108         if (!aScreenshotId.isEmpty())
109         {
110             implSaveScreenshot(aScreenshot, aScreenshotId);
111         }
112     }
113 }
114 
saveScreenshot(weld::Window & rDialog)115 void ScreenshotTest::saveScreenshot(weld::Window& rDialog)
116 {
117     VclPtr<VirtualDevice> xDialogSurface(rDialog.screenshot());
118     const BitmapEx aScreenshot(xDialogSurface->GetBitmapEx(Point(), xDialogSurface->GetOutputSizePixel()));
119 
120     if (!aScreenshot.IsEmpty())
121     {
122         const OString aScreenshotId = rDialog.get_help_id();
123         assert(!aScreenshotId.isEmpty());
124         implSaveScreenshot(aScreenshot, aScreenshotId);
125     }
126 }
127 
createDialogByName(const OString & rName)128 VclPtr<VclAbstractDialog> ScreenshotTest::createDialogByName(const OString& rName)
129 {
130     const mapType::const_iterator aHit = maKnownDialogs.find(rName);
131 
132     if (aHit != maKnownDialogs.end())
133     {
134         return createDialogByID((*aHit).second);
135     }
136 
137     return VclPtr<VclAbstractDialog>();
138 }
139 
dumpDialogToPath(VclAbstractDialog & rDialog)140 void ScreenshotTest::dumpDialogToPath(VclAbstractDialog& rDialog)
141 {
142     const std::vector<OString> aPageDescriptions(rDialog.getAllPageUIXMLDescriptions());
143 
144     if (!aPageDescriptions.empty())
145     {
146         for (size_t a(0); a < aPageDescriptions.size(); a++)
147         {
148             if (rDialog.selectPageByUIXMLDescription(aPageDescriptions[a]))
149             {
150                 saveScreenshot(rDialog);
151             }
152             else
153             {
154                 CPPUNIT_ASSERT(false);
155             }
156         }
157     }
158     else
159     {
160         saveScreenshot(rDialog);
161     }
162 }
163 
dumpDialogToPath(weld::Builder & rBuilder)164 void ScreenshotTest::dumpDialogToPath(weld::Builder& rBuilder)
165 {
166     std::unique_ptr<weld::Window> xDialog(rBuilder.create_screenshot_window());
167 
168     auto xTabCtrl = rBuilder.weld_notebook("tabcontrol");
169 
170     int nPages = xTabCtrl ? xTabCtrl->get_n_pages() : 0;
171     if (nPages)
172     {
173         for (int i = 0; i < nPages; ++i)
174         {
175             OString sIdent(xTabCtrl->get_page_ident(i));
176             xTabCtrl->set_current_page(sIdent);
177             if (xTabCtrl->get_current_page_ident() == sIdent)
178             {
179                 OString sOrigHelpId(xDialog->get_help_id());
180                 // skip empty pages
181                 weld::Container* pPage = xTabCtrl->get_page(sIdent);
182                 OString sBuildableName(pPage->get_buildable_name());
183                 if (!sBuildableName.isEmpty() && !sBuildableName.startsWith("__"))
184                     xDialog->set_help_id(pPage->get_help_id());
185                 saveScreenshot(*xDialog);
186                 xDialog->set_help_id(sOrigHelpId);
187             }
188             else
189             {
190                 CPPUNIT_ASSERT(false);
191             }
192         }
193     }
194     else
195     {
196         saveScreenshot(*xDialog);
197     }
198 }
199 
dumpDialogToPath(std::string_view rUIXMLDescription)200 void ScreenshotTest::dumpDialogToPath(std::string_view rUIXMLDescription)
201 {
202     if (rUIXMLDescription.empty())
203         return;
204 
205     bool bNonConforming = rUIXMLDescription == "modules/swriter/ui/sidebarstylepresets.ui" ||
206                           rUIXMLDescription == "modules/swriter/ui/sidebartheme.ui" ||
207                           rUIXMLDescription == "modules/swriter/ui/notebookbar.ui" ||
208                           rUIXMLDescription == "modules/scalc/ui/sidebaralignment.ui" ||
209                           rUIXMLDescription == "modules/scalc/ui/sidebarcellappearance.ui" ||
210                           rUIXMLDescription == "modules/scalc/ui/sidebarnumberformat.ui" ||
211                           rUIXMLDescription == "sfx/ui/helpbookmarkpage.ui" ||
212                           rUIXMLDescription == "sfx/ui/helpcontentpage.ui" ||
213                           rUIXMLDescription == "sfx/ui/helpindexpage.ui" ||
214                           rUIXMLDescription == "sfx/ui/helpsearchpage.ui" ||
215                           rUIXMLDescription == "sfx/ui/startcenter.ui" ||
216                           rUIXMLDescription == "svx/ui/datanavigator.ui" ||
217                           rUIXMLDescription == "svx/ui/xformspage.ui" ||
218                           rUIXMLDescription == "modules/dbreport/ui/conditionwin.ui";
219     if (bNonConforming) // skip these broken ones
220         return;
221     std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(mxParentWidget.get(), OStringToOUString(rUIXMLDescription, RTL_TEXTENCODING_UTF8)));
222     dumpDialogToPath(*xBuilder);
223 }
224 
processAllKnownDialogs()225 void ScreenshotTest::processAllKnownDialogs()
226 {
227     for (const auto& rDialog : getKnownDialogs())
228     {
229         ScopedVclPtr<VclAbstractDialog> pDlg(createDialogByID(rDialog.second));
230 
231         if (pDlg)
232         {
233             // known dialog, dump screenshot to path
234             dumpDialogToPath(*pDlg);
235         }
236         else
237         {
238             // unknown dialog, should not happen in this basic loop.
239             // You have probably forgotten to add a case and
240             // implementation to createDialogByID, please do this
241         }
242     }
243 }
244 
processDialogBatchFile(std::u16string_view rFile)245 void ScreenshotTest::processDialogBatchFile(std::u16string_view rFile)
246 {
247     test::Directories aDirectories;
248     const OUString aURL(aDirectories.getURLFromSrc(rFile));
249     SvFileStream aStream(aURL, StreamMode::READ);
250     OString aNextUIFile;
251     const OString aComment("#");
252 
253     while (aStream.ReadLine(aNextUIFile))
254     {
255         if (!aNextUIFile.isEmpty() && !aNextUIFile.startsWith(aComment))
256         {
257             std::cout << "processing " << aNextUIFile << ":\n";
258 
259             // first check if it's a known dialog
260             ScopedVclPtr<VclAbstractDialog> pDlg(createDialogByName(aNextUIFile));
261 
262             if (pDlg)
263             {
264                 // known dialog, dump screenshot to path
265                 dumpDialogToPath(*pDlg);
266             }
267             else
268             {
269                 // unknown dialog, try fallback to generic created
270                 // Builder-generated instance. Keep in mind that Dialogs
271                 // using this mechanism will probably not be layouted well
272                 // since the setup/initialization part is missing. Thus,
273                 // only use for fallback when only the UI file is available.
274                 dumpDialogToPath(aNextUIFile);
275             }
276         }
277     }
278 }
279 
280 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
281