LibreOffice Module vcl (master)  1
logger.cxx
Go to the documentation of this file.
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 <config_folders.h>
11 
12 #include <vcl/uitest/logger.hxx>
13 
14 #include <rtl/bootstrap.hxx>
15 #include <osl/file.hxx>
16 #include <vcl/event.hxx>
17 #include <vcl/uitest/uiobject.hxx>
19 #include <svdata.hxx>
20 #include <com/sun/star/beans/PropertyValue.hpp>
21 #include <memory>
22 
23 namespace
24 {
25 bool isDialogWindow(vcl::Window const* pWindow)
26 {
27  WindowType nType = pWindow->GetType();
28  // DIALOG to MODALDIALOG
29  if (nType >= WindowType::DIALOG && nType <= WindowType::MODALDIALOG)
30  return true;
31 
32  // MESSBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX
33  if (nType >= WindowType::MESSBOX && nType <= WindowType::QUERYBOX)
34  return true;
35 
36  if (nType == WindowType::TABDIALOG)
37  return true;
38 
39  return false;
40 }
41 
42 bool isTopWindow(vcl::Window const* pWindow)
43 {
44  WindowType eType = pWindow->GetType();
45  if (eType == WindowType::FLOATINGWINDOW)
46  {
47  return pWindow->GetStyle() & WB_SYSTEMFLOATWIN;
48  }
49  return false;
50 }
51 
52 vcl::Window* get_top_parent(vcl::Window* pWindow)
53 {
54  if (isDialogWindow(pWindow) || isTopWindow(pWindow))
55  return pWindow;
56 
57  vcl::Window* pParent = pWindow->GetParent();
58  if (!pParent)
59  return pWindow;
60 
61  return get_top_parent(pParent);
62 }
63 }
65  : maStream()
66  , mbValid(false)
67 {
68  static const char* pFile = std::getenv("LO_COLLECT_UIINFO");
69  if (pFile)
70  {
71  OUString aDirPath("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
72  "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/uitest/");
73  rtl::Bootstrap::expandMacros(aDirPath);
74  osl::Directory::createPath(aDirPath);
75  OUString aFilePath = aDirPath + OUString::fromUtf8(pFile);
76 
77  maStream.Open(aFilePath, StreamMode::READWRITE | StreamMode::TRUNC);
78  mbValid = true;
79  }
80 }
81 
82 void UITestLogger::logCommand(const OUString& rAction,
83  const css::uno::Sequence<css::beans::PropertyValue>& rArgs)
84 {
85  if (!mbValid)
86  return;
87 
88  OUStringBuffer aBuffer(rAction);
89 
90  if (rArgs.hasElements())
91  {
92  aBuffer.append(" {");
93  for (const css::beans::PropertyValue& rProp : rArgs)
94  {
95  OUString aTypeName = rProp.Value.getValueTypeName();
96 
97  if (aTypeName == "long" || aTypeName == "short")
98  {
99  sal_Int32 nValue = 0;
100  rProp.Value >>= nValue;
101  aBuffer.append("\"").append(rProp.Name).append("\": ");
102  aBuffer.append(OUString::number(nValue)).append(", ");
103  }
104  else if (aTypeName == "unsigned long")
105  {
106  sal_uInt32 nValue = 0;
107  rProp.Value >>= nValue;
108  aBuffer.append("\"").append(rProp.Name).append("\": ");
109  aBuffer.append(OUString::number(nValue)).append(", ");
110  }
111  else if (aTypeName == "boolean")
112  {
113  bool bValue = false;
114  rProp.Value >>= bValue;
115  aBuffer.append("\"").append(rProp.Name).append("\": ");
116  if (bValue)
117  aBuffer.append("True, ");
118  else
119  aBuffer.append("False, ");
120  }
121  }
122  aBuffer.append("}");
123  }
124 
125  OUString aCommand(aBuffer.makeStringAndClear());
126  maStream.WriteLine(OUStringToOString(aCommand, RTL_TEXTENCODING_UTF8));
127 }
128 
129 namespace
130 {
131 // most likely this should be recursive
132 bool child_windows_have_focus(VclPtr<vcl::Window> const& xUIElement)
133 {
134  sal_Int32 nCount = xUIElement->GetChildCount();
135  for (sal_Int32 i = 0; i < nCount; ++i)
136  {
137  vcl::Window* pChild = xUIElement->GetChild(i);
138  if (pChild->HasFocus())
139  {
140  return true;
141  }
142  if (child_windows_have_focus(VclPtr<vcl::Window>(pChild)))
143  return true;
144  }
145  return false;
146 }
147 }
148 
149 void UITestLogger::logAction(VclPtr<Control> const& xUIElement, VclEventId nEvent)
150 {
151  if (!mbValid)
152  return;
153 
154  if (xUIElement->get_id().isEmpty())
155  return;
156 
157  std::unique_ptr<UIObject> pUIObject = xUIElement->GetUITestFactory()(xUIElement.get());
158  OUString aAction = pUIObject->get_action(nEvent);
159  if (!xUIElement->HasFocus() && !child_windows_have_focus(xUIElement))
160  {
161  return;
162  }
163 
164  if (!aAction.isEmpty())
165  maStream.WriteLine(OUStringToOString(aAction, RTL_TEXTENCODING_UTF8));
166 }
167 
168 void UITestLogger::log(const OUString& rString)
169 {
170  if (!mbValid)
171  return;
172 
173  if (rString.isEmpty())
174  return;
175 
176  maStream.WriteLine(OUStringToOString(rString, RTL_TEXTENCODING_UTF8));
177 }
178 
179 void UITestLogger::logKeyInput(VclPtr<vcl::Window> const& xUIElement, const KeyEvent& rEvent)
180 {
181  if (!mbValid)
182  return;
183 
184  //We need to check for Parent's ID in case the UI Element is SubEdit of Combobox/SpinField
185  const OUString& rID
186  = xUIElement->get_id().isEmpty() ? xUIElement->GetParent()->get_id() : xUIElement->get_id();
187  if (rID.isEmpty())
188  return;
189 
190  sal_Unicode nChar = rEvent.GetCharCode();
191  sal_uInt16 nKeyCode = rEvent.GetKeyCode().GetCode();
192  bool bShift = rEvent.GetKeyCode().IsShift();
193  bool bMod1 = rEvent.GetKeyCode().IsMod1();
194  bool bMod2 = rEvent.GetKeyCode().IsMod2();
195  bool bMod3 = rEvent.GetKeyCode().IsMod3();
196 
197  std::map<OUString, sal_uInt16> aKeyMap
198  = { { "ESC", KEY_ESCAPE }, { "TAB", KEY_TAB }, { "DOWN", KEY_DOWN },
199  { "UP", KEY_UP }, { "LEFT", KEY_LEFT }, { "RIGHT", KEY_RIGHT },
200  { "DELETE", KEY_DELETE }, { "INSERT", KEY_INSERT }, { "BACKSPACE", KEY_BACKSPACE },
201  { "RETURN", KEY_RETURN }, { "HOME", KEY_HOME }, { "END", KEY_END },
202  { "PAGEUP", KEY_PAGEUP }, { "PAGEDOWN", KEY_PAGEDOWN } };
203 
204  OUString aFound;
205  for (const auto& itr : aKeyMap)
206  {
207  if (itr.second == nKeyCode)
208  {
209  aFound = itr.first;
210  break;
211  }
212  }
213 
214  OUString aKeyCode;
215  if (!aFound.isEmpty() || bShift || bMod1 || bMod2 || bMod3)
216  {
217  aKeyCode = "{\"KEYCODE\": \"";
218  if (bShift)
219  aKeyCode += "SHIFT+";
220 
221  if (bMod1)
222  aKeyCode += "CTRL+";
223 
224  if (bMod2)
225  aKeyCode += "ALT+";
226 
227  if (aFound.isEmpty())
228  aKeyCode += OUStringChar(nChar) + "\"}";
229  else
230  aKeyCode += aFound + "\"}";
231  }
232  else
233  {
234  aKeyCode = "{\"TEXT\": \"" + OUStringChar(nChar) + "\"}";
235  }
236 
237  std::unique_ptr<UIObject> pUIObject = xUIElement->GetUITestFactory()(xUIElement.get());
238 
239  VclPtr<vcl::Window> pParent = xUIElement->GetParent();
240 
241  while (!pParent->IsTopWindow())
242  {
243  pParent = pParent->GetParent();
244  }
245 
246  OUString aParentID = pParent->get_id();
247 
248  OUString aContent;
249 
250  if (pUIObject->get_type() == "EditUIObject")
251  {
252  if (aParentID.isEmpty())
253  {
254  VclPtr<vcl::Window> pParent_top = get_top_parent(xUIElement);
255  aParentID = pParent_top->get_id();
256  }
257  if (aParentID.isEmpty())
258  {
259  aContent += "Type on '" + rID + "' " + aKeyCode;
260  }
261  else
262  {
263  aContent += "Type on '" + rID + "' " + aKeyCode + " from " + aParentID;
264  }
265  }
266  else if (pUIObject->get_type() == "SwEditWinUIObject" && rID == "writer_edit")
267  {
268  aContent = "Type on writer " + aKeyCode;
269  }
270  else if (pUIObject->get_type() == "ScGridWinUIObject" && rID == "grid_window")
271  {
272  aContent = "Type on current cell " + aKeyCode;
273  }
274  else if (pUIObject->get_type() == "ImpressWindowUIObject" && rID == "impress_win")
275  {
276  aContent = "Type on impress " + aKeyCode;
277  }
278  else if (pUIObject->get_type() == "WindowUIObject" && rID == "math_edit")
279  {
280  aContent = "Type on math " + aKeyCode;
281  }
282  else if (rID == "draw_win")
283  {
284  aContent = "Type on draw " + aKeyCode;
285  }
286  else
287  {
288  if (aParentID.isEmpty())
289  {
290  VclPtr<vcl::Window> pParent_top = get_top_parent(xUIElement);
291  aParentID = pParent_top->get_id();
292  }
293  if (aParentID.isEmpty())
294  {
295  aContent = "Type on '" + rID + "' " + aKeyCode;
296  }
297  else
298  {
299  aContent = "Type on '" + rID + "' " + aKeyCode + " from " + aParentID;
300  }
301  }
302  maStream.WriteLine(OUStringToOString(aContent, RTL_TEXTENCODING_UTF8));
303 }
304 
305 namespace
306 {
307 OUString StringMapToOUString(const std::map<OUString, OUString>& rParameters)
308 {
309  if (rParameters.empty())
310  return "";
311 
312  OUStringBuffer aParameterString(static_cast<int>(rParameters.size()*32));
313  aParameterString.append(" {");
314 
315  for (std::map<OUString, OUString>::const_iterator itr = rParameters.begin();
316  itr != rParameters.end(); ++itr)
317  {
318  if (itr != rParameters.begin())
319  aParameterString.append(", ");
320  aParameterString.append("\"")
321  .append(itr->first)
322  .append("\": \"")
323  .append(itr->second)
324  .append("\"");
325  }
326 
327  aParameterString.append("}");
328 
329  return aParameterString.makeStringAndClear();
330 }
331 
332 OUString GetValueInMapWithIndex(const std::map<OUString, OUString>& rParameters, sal_Int32 index)
333 {
334  sal_Int32 j = 0;
335 
336  std::map<OUString, OUString>::const_iterator itr = rParameters.begin();
337 
338  for (; itr != rParameters.end() && j < index; ++itr, ++j)
339  ;
340 
341  assert(itr != rParameters.end());
342 
343  return itr->second;
344 }
345 
346 OUString GetKeyInMapWithIndex(const std::map<OUString, OUString>& rParameters, sal_Int32 index)
347 {
348  sal_Int32 j = 0;
349 
350  std::map<OUString, OUString>::const_iterator itr = rParameters.begin();
351 
352  for (; itr != rParameters.end() && j < index; ++itr, ++j)
353  ;
354 
355  assert(itr != rParameters.end());
356 
357  return itr->first;
358 }
359 }
360 
361 void UITestLogger::logEvent(const EventDescription& rDescription)
362 {
363  OUString aParameterString = StringMapToOUString(rDescription.aParameters);
364 
365  //here we will customize our statements depending on the caller of this function
366  OUString aLogLine;
367  //first check on general commands
368  if (rDescription.aAction == "SET")
369  {
370  aLogLine = "Set Zoom to " + GetValueInMapWithIndex(rDescription.aParameters, 0);
371  }
372  else if (rDescription.aAction == "SIDEBAR")
373  {
374  aLogLine = "From SIDEBAR Choose " + aParameterString;
375  }
376  else if (rDescription.aAction == "SELECT" && rDescription.aID.isEmpty())
377  {
378  aLogLine = "Select " + aParameterString;
379  }
380  else if (rDescription.aID == "writer_edit")
381  {
382  if (rDescription.aAction == "GOTO")
383  {
384  aLogLine = "GOTO page number " + GetValueInMapWithIndex(rDescription.aParameters, 0);
385  }
386  else if (rDescription.aAction == "SELECT")
387  {
388  OUString to = GetValueInMapWithIndex(rDescription.aParameters, 0);
389  OUString from = GetValueInMapWithIndex(rDescription.aParameters, 1);
390  aLogLine = "Select from Pos " + from + " to Pos " + to;
391  }
392  else if (rDescription.aAction == "CREATE_TABLE")
393  {
394  OUString size = GetValueInMapWithIndex(rDescription.aParameters, 0);
395  aLogLine = "Create Table with " + size;
396  ;
397  }
398  else if (rDescription.aAction == "COPY")
399  {
400  aLogLine = "Copy the Selected Text";
401  }
402  else if (rDescription.aAction == "CUT")
403  {
404  aLogLine = "Cut the Selected Text";
405  }
406  else if (rDescription.aAction == "PASTE")
407  {
408  aLogLine = "Paste in the Current Cursor Location";
409  }
410  else if (rDescription.aAction == "BREAK_PAGE")
411  {
412  aLogLine = "Insert Break Page";
413  }
414  }
415  else if (rDescription.aID == "grid_window")
416  {
417  if (rDescription.aAction == "SELECT")
418  {
419  OUString type = GetKeyInMapWithIndex(rDescription.aParameters, 0);
420  if (type == "CELL" || type == "RANGE")
421  {
422  aLogLine = "Select from calc" + aParameterString;
423  }
424  else if (type == "TABLE")
425  {
426  aLogLine = "Switch to sheet number "
427  + GetValueInMapWithIndex(rDescription.aParameters, 0);
428  }
429  }
430  else if (rDescription.aAction == "LAUNCH")
431  {
432  aLogLine = "Launch AutoFilter from Col "
433  + GetValueInMapWithIndex(rDescription.aParameters, 2) + " and Row "
434  + GetValueInMapWithIndex(rDescription.aParameters, 1);
435  }
436  else if (rDescription.aAction == "DELETE_CONTENT")
437  {
438  aLogLine = "Remove Content from This " + aParameterString;
439  }
440  else if (rDescription.aAction == "DELETE_CELLS")
441  {
442  aLogLine = "Delete The Cells in" + aParameterString;
443  }
444  else if (rDescription.aAction == "INSERT_CELLS")
445  {
446  aLogLine = "Insert Cell around the " + aParameterString;
447  }
448  else if (rDescription.aAction == "CUT")
449  {
450  aLogLine = "CUT the selected " + aParameterString;
451  }
452  else if (rDescription.aAction == "COPY")
453  {
454  aLogLine = "COPY the selected " + aParameterString;
455  }
456  else if (rDescription.aAction == "PASTE")
457  {
458  aLogLine = "Paste in the " + aParameterString;
459  }
460  else if (rDescription.aAction == "MERGE_CELLS")
461  {
462  aLogLine = "Merge " + aParameterString;
463  }
464  else if (rDescription.aAction == "UNMERGE_CELL")
465  {
466  aLogLine = "Delete the merged " + aParameterString;
467  }
468  else if (rDescription.aAction == "Rename_Sheet")
469  {
470  aLogLine = "Rename The Selected Tab to \""
471  + GetValueInMapWithIndex(rDescription.aParameters, 0) + "\"";
472  }
473  else if (rDescription.aAction == "InsertTab")
474  {
475  aLogLine = "Insert New Tab ";
476  }
477  }
478  else if (rDescription.aID == "impress_win_or_draw_win")
479  {
480  if (rDescription.aAction == "Insert_New_Page_or_Slide")
481  {
482  if (UITestLogger::getInstance().getAppName() == "impress")
483  {
484  aLogLine = "Insert New Slide at Position "
485  + GetValueInMapWithIndex(rDescription.aParameters, 0);
486  }
487  else if (UITestLogger::getInstance().getAppName() == "draw")
488  {
489  aLogLine = "Insert New Page at Position "
490  + GetValueInMapWithIndex(rDescription.aParameters, 0);
491  }
492  }
493  else if (rDescription.aAction == "Delete_Slide_or_Page")
494  {
495  if (UITestLogger::getInstance().getAppName() == "impress")
496  {
497  aLogLine
498  = "Delete Slide number " + GetValueInMapWithIndex(rDescription.aParameters, 0);
499  }
500  else if (UITestLogger::getInstance().getAppName() == "draw")
501  {
502  aLogLine
503  = "Delete Page number " + GetValueInMapWithIndex(rDescription.aParameters, 0);
504  }
505  }
506  else if (rDescription.aAction == "Duplicate")
507  {
508  aLogLine = "Duplicate The Selected Slide ";
509  }
510  else if (rDescription.aAction == "RENAME")
511  {
512  if (UITestLogger::getInstance().getAppName() == "impress")
513  {
514  aLogLine = "Rename The Selected Slide from \""
515  + GetValueInMapWithIndex(rDescription.aParameters, 1) + "\" to \""
516  + GetValueInMapWithIndex(rDescription.aParameters, 0) + "\"";
517  }
518  else if (UITestLogger::getInstance().getAppName() == "draw")
519  {
520  aLogLine = "Rename The Selected Page from \""
521  + GetValueInMapWithIndex(rDescription.aParameters, 1) + "\" to \""
522  + GetValueInMapWithIndex(rDescription.aParameters, 0) + "\"";
523  }
524  }
525  }
526  else if (rDescription.aParent == "element_selector")
527  {
528  aLogLine = "Select element no " + rDescription.aID + " From " + rDescription.aParent;
529  }
530  else
531  {
532  aLogLine = rDescription.aKeyWord + " Action:" + rDescription.aAction + " Id:"
533  + rDescription.aID + " Parent:" + rDescription.aParent + aParameterString;
534  }
535  log(aLogLine);
536 }
537 
539 {
540  ImplSVData* const pSVData = ImplGetSVData();
541  assert(pSVData);
542 
543  if (!pSVData->maWinData.m_pUITestLogger)
544  {
545  pSVData->maWinData.m_pUITestLogger.reset(new UITestLogger);
546  }
547 
548  return *pSVData->maWinData.m_pUITestLogger;
549 }
550 
551 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
#define KEY_BACKSPACE
Definition: keycodes.hxx:122
#define KEY_DELETE
Definition: keycodes.hxx:125
virtual FactoryFunction GetUITestFactory() const
Definition: window.cxx:3888
#define KEY_TAB
Definition: keycodes.hxx:121
#define KEY_PAGEDOWN
Definition: keycodes.hxx:117
#define KEY_PAGEUP
Definition: keycodes.hxx:116
sal_uInt16 GetCode() const
Definition: keycod.hxx:53
#define KEY_LEFT
Definition: keycodes.hxx:112
void log(const OUString &rString)
Definition: logger.cxx:168
void logAction(VclPtr< Control > const &xUIElement, VclEventId nEvent)
Definition: logger.cxx:149
static UITestLogger & getInstance()
Definition: logger.cxx:538
void logEvent(const EventDescription &rDescription)
Definition: logger.cxx:361
sal_uInt16 sal_Unicode
const OUString & get_id() const
Get the ID of the window.
Definition: window.cxx:3883
UBlockCode from
VclEventId
Definition: vclevent.hxx:41
std::unique_ptr< UITestLogger > m_pUITestLogger
Definition: svdata.hxx:219
UBlockCode to
bool IsTopWindow() const
Definition: stacking.cxx:606
WinBits const WB_SYSTEMFLOATWIN
void Open(const OUString &rFileName, StreamMode eOpenMode)
UITestLogger()
Definition: logger.cxx:64
ImplSVData * ImplGetSVData()
Definition: svdata.cxx:67
#define SAL_CONFIGFILE(name)
void logCommand(const OUString &rAction, const css::uno::Sequence< css::beans::PropertyValue > &rArgs)
Definition: logger.cxx:82
bool WriteLine(const OString &rStr)
SvFileStream maStream
Definition: logger.hxx:52
int i
QPRO_FUNC_TYPE const nType
#define KEY_ESCAPE
Definition: keycodes.hxx:120
void logKeyInput(VclPtr< vcl::Window > const &xUIElement, const KeyEvent &rEvent)
Definition: logger.cxx:179
size
DocumentType const eType
vcl::Window * GetParent() const
Definition: window2.cxx:1086
#define KEY_END
Definition: keycodes.hxx:115
OString OUStringToOString(const OUString &str, ConnectionSettings const *settings)
ImplSVWinData maWinData
Definition: svdata.hxx:349
#define KEY_RETURN
Definition: keycodes.hxx:119
sal_uInt16 GetChildCount() const
Definition: stacking.cxx:1001
const vcl::KeyCode & GetKeyCode() const
Definition: event.hxx:53
bool IsShift() const
Definition: keycod.hxx:58
#define KEY_DOWN
Definition: keycodes.hxx:110
WindowType
sal_Unicode GetCharCode() const
Definition: event.hxx:52
bool IsMod1() const
Definition: keycod.hxx:60
OUString getAppName() const
Definition: logger.hxx:76
bool mbValid
Definition: logger.hxx:54
WindowType GetType() const
Definition: window2.cxx:963
#define KEY_HOME
Definition: keycodes.hxx:114
reference_type * get() const
Get the body.
Definition: vclptr.hxx:143
#define KEY_INSERT
Definition: keycodes.hxx:124
WinBits GetStyle() const
Definition: window2.cxx:942
std::map< OUString, OUString > aParameters
bool HasFocus() const
Definition: window.cxx:2985
#define KEY_RIGHT
Definition: keycodes.hxx:113
bool IsMod3() const
Definition: keycod.hxx:64
bool mbValid
vcl::Window * GetChild(sal_uInt16 nChild) const
Definition: stacking.cxx:1017
#define KEY_UP
Definition: keycodes.hxx:111
bool IsMod2() const
Definition: keycod.hxx:62