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