/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include #include #include namespace { bool isDialogWindow(vcl::Window const* pWindow) { WindowType nType = pWindow->GetType(); if (nType == WindowType::DIALOG || nType == WindowType::MODELESSDIALOG) return true; // MESSBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX if (nType >= WindowType::MESSBOX && nType <= WindowType::QUERYBOX) return true; if (nType == WindowType::TABDIALOG) return true; return false; } bool isTopWindow(vcl::Window const* pWindow) { WindowType eType = pWindow->GetType(); if (eType == WindowType::FLOATINGWINDOW) { return pWindow->GetStyle() & WB_SYSTEMFLOATWIN; } return false; } vcl::Window* get_top_parent(vcl::Window* pWindow) { if (isDialogWindow(pWindow) || isTopWindow(pWindow)) return pWindow; vcl::Window* pParent = pWindow->GetParent(); if (!pParent) return pWindow; return get_top_parent(pParent); } } UITestLogger::UITestLogger() : mbValid(false) { static const char* pFile = std::getenv("LO_COLLECT_UIINFO"); if (pFile) { OUString aDirPath(u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/uitest/"_ustr); rtl::Bootstrap::expandMacros(aDirPath); osl::Directory::createPath(aDirPath); OUString aFilePath = aDirPath + OUString::fromUtf8(pFile); maStream.Open(aFilePath, StreamMode::READWRITE | StreamMode::TRUNC); mbValid = true; } } namespace { // most likely this should be recursive bool child_windows_have_focus(VclPtr const& xUIElement) { sal_Int32 nCount = xUIElement->GetChildCount(); for (sal_Int32 i = 0; i < nCount; ++i) { vcl::Window* pChild = xUIElement->GetChild(i); if (pChild->HasFocus()) { return true; } if (child_windows_have_focus(VclPtr(pChild))) return true; } return false; } } void UITestLogger::logAction(VclPtr const& xUIElement, VclEventId nEvent) { if (!mbValid) return; if (xUIElement->get_id().isEmpty()) return; std::unique_ptr pUIObject = xUIElement->GetUITestFactory()(xUIElement.get()); OUString aAction = pUIObject->get_action(nEvent); if (!xUIElement->HasFocus() && !child_windows_have_focus(xUIElement)) { return; } if (!aAction.isEmpty()) maStream.WriteLine(OUStringToOString(aAction, RTL_TEXTENCODING_UTF8)); } void UITestLogger::logAction(vcl::Window* const& xUIWin, VclEventId nEvent) { if (!mbValid) return; if (xUIWin->get_id().isEmpty()) return; std::unique_ptr pUIObject = xUIWin->GetUITestFactory()(xUIWin); OUString aAction = pUIObject->get_action(nEvent); if (!aAction.isEmpty()) maStream.WriteLine(OUStringToOString(aAction, RTL_TEXTENCODING_UTF8)); } void UITestLogger::log(std::u16string_view rString) { if (!mbValid) return; if (rString.empty()) return; maStream.WriteLine(OUStringToOString(rString, RTL_TEXTENCODING_UTF8)); } void UITestLogger::logKeyInput(VclPtr const& xUIElement, const KeyEvent& rEvent) { if (!mbValid) return; //We need to check for Parent's ID in case the UI Element is SubEdit of Combobox/SpinField const OUString& rID = xUIElement->get_id().isEmpty() ? xUIElement->GetParent()->get_id() : xUIElement->get_id(); if (rID.isEmpty()) return; sal_Unicode nChar = rEvent.GetCharCode(); sal_uInt16 nKeyCode = rEvent.GetKeyCode().GetCode(); bool bShift = rEvent.GetKeyCode().IsShift(); bool bMod1 = rEvent.GetKeyCode().IsMod1(); bool bMod2 = rEvent.GetKeyCode().IsMod2(); bool bMod3 = rEvent.GetKeyCode().IsMod3(); std::map aKeyMap = { { "ESC", KEY_ESCAPE }, { "TAB", KEY_TAB }, { "DOWN", KEY_DOWN }, { "UP", KEY_UP }, { "LEFT", KEY_LEFT }, { "RIGHT", KEY_RIGHT }, { "DELETE", KEY_DELETE }, { "INSERT", KEY_INSERT }, { "BACKSPACE", KEY_BACKSPACE }, { "RETURN", KEY_RETURN }, { "HOME", KEY_HOME }, { "END", KEY_END }, { "PAGEUP", KEY_PAGEUP }, { "PAGEDOWN", KEY_PAGEDOWN } }; OUString aFound; for (const auto& itr : aKeyMap) { if (itr.second == nKeyCode) { aFound = itr.first; break; } } OUString aKeyCode; if (!aFound.isEmpty() || bShift || bMod1 || bMod2 || bMod3) { aKeyCode = "{\"KEYCODE\": \""; if (bShift) aKeyCode += "SHIFT+"; if (bMod1) aKeyCode += "CTRL+"; if (bMod2) aKeyCode += "ALT+"; if (aFound.isEmpty()) aKeyCode += OUStringChar(nChar) + "\"}"; else aKeyCode += aFound + "\"}"; } else { aKeyCode = "{\"TEXT\": \"" + OUStringChar(nChar) + "\"}"; } std::unique_ptr pUIObject = xUIElement->GetUITestFactory()(xUIElement.get()); VclPtr pParent = xUIElement->GetParent(); while (pParent && !pParent->IsTopWindow()) { pParent = pParent->GetParent(); } OUString aParentID = pParent ? pParent->get_id() : OUString(); OUString aContent; if (pUIObject->get_type() == "EditUIObject") { if (aParentID.isEmpty()) { VclPtr pParent_top = get_top_parent(xUIElement); aParentID = pParent_top->get_id(); } if (aParentID.isEmpty()) { aContent += "Type on '" + rID + "' " + aKeyCode; } else { aContent += "Type on '" + rID + "' " + aKeyCode + " from " + aParentID; } } else if (pUIObject->get_type() == "SwEditWinUIObject" && rID == "writer_edit") { aContent = "Type on writer " + aKeyCode; } else if (pUIObject->get_type() == "ScGridWinUIObject" && rID == "grid_window") { aContent = "Type on current cell " + aKeyCode; } else if (pUIObject->get_type() == "ImpressWindowUIObject" && rID == "impress_win") { aContent = "Type on impress " + aKeyCode; } else if (pUIObject->get_type() == "WindowUIObject" && rID == "math_edit") { aContent = "Type on math " + aKeyCode; } else if (rID == "draw_win") { aContent = "Type on draw " + aKeyCode; } else { if (aParentID.isEmpty()) { VclPtr pParent_top = get_top_parent(xUIElement); aParentID = pParent_top->get_id(); } if (aParentID.isEmpty()) { aContent = "Type on '" + rID + "' " + aKeyCode; } else { aContent = "Type on '" + rID + "' " + aKeyCode + " from " + aParentID; } } maStream.WriteLine(OUStringToOString(aContent, RTL_TEXTENCODING_UTF8)); } namespace { OUString StringMapToOUString(const std::map& rParameters) { if (rParameters.empty()) return u""_ustr; OUStringBuffer aParameterString(static_cast(rParameters.size() * 32)); aParameterString.append(" {"); for (std::map::const_iterator itr = rParameters.begin(); itr != rParameters.end(); ++itr) { if (itr != rParameters.begin()) aParameterString.append(", "); aParameterString.append("\"" + itr->first + "\": \"" + itr->second + "\""); } aParameterString.append("}"); return aParameterString.makeStringAndClear(); } const OUString& GetValueInMapWithIndex(const std::map& rParameters, sal_Int32 index) { sal_Int32 j = 0; std::map::const_iterator itr = rParameters.begin(); for (; itr != rParameters.end() && j < index; ++itr, ++j) ; assert(itr != rParameters.end()); return itr->second; } const OUString& GetKeyInMapWithIndex(const std::map& rParameters, sal_Int32 index) { sal_Int32 j = 0; std::map::const_iterator itr = rParameters.begin(); for (; itr != rParameters.end() && j < index; ++itr, ++j) ; assert(itr != rParameters.end()); return itr->first; } } void UITestLogger::logEvent(const EventDescription& rDescription) { OUString aParameterString = StringMapToOUString(rDescription.aParameters); //here we will customize our statements depending on the caller of this function OUString aLogLine; //first check on general commands if (rDescription.aAction == "SET") { aLogLine = "Set Zoom to " + GetValueInMapWithIndex(rDescription.aParameters, 0); } else if (rDescription.aAction == "SIDEBAR") { aLogLine = "From SIDEBAR Choose " + aParameterString; } else if (rDescription.aKeyWord == "ValueSet") { aLogLine = "Choose element with position " + GetValueInMapWithIndex(rDescription.aParameters, 0) + " in '" + rDescription.aID + "' from '" + rDescription.aParent + "'"; } else if (rDescription.aAction == "SELECT" && rDescription.aID.isEmpty()) { aLogLine = "Select " + aParameterString; } else if (rDescription.aID == "writer_edit") { if (rDescription.aAction == "GOTO") { aLogLine = "GOTO page number " + GetValueInMapWithIndex(rDescription.aParameters, 0); } else if (rDescription.aAction == "SELECT") { OUString to = GetValueInMapWithIndex(rDescription.aParameters, 0); OUString from = GetValueInMapWithIndex(rDescription.aParameters, 1); aLogLine = "Select from Pos " + from + " to Pos " + to; } else if (rDescription.aAction == "CREATE_TABLE") { OUString size = GetValueInMapWithIndex(rDescription.aParameters, 0); aLogLine = "Create Table with " + size; ; } else if (rDescription.aAction == "COPY") { aLogLine = "Copy the Selected Text"; } else if (rDescription.aAction == "CUT") { aLogLine = "Cut the Selected Text"; } else if (rDescription.aAction == "PASTE") { aLogLine = "Paste in the Current Cursor Location"; } else if (rDescription.aAction == "BREAK_PAGE") { aLogLine = "Insert Break Page"; } } else if (rDescription.aID == "grid_window") { if (rDescription.aAction == "SELECT") { OUString type = GetKeyInMapWithIndex(rDescription.aParameters, 0); if (type == "CELL" || type == "RANGE") { aLogLine = "Select from calc" + aParameterString; } else if (type == "TABLE") { aLogLine = "Switch to sheet number " + GetValueInMapWithIndex(rDescription.aParameters, 0); } } else if (rDescription.aAction == "LAUNCH") { aLogLine = "Launch" + GetKeyInMapWithIndex(rDescription.aParameters, 2) + " from Col " + GetValueInMapWithIndex(rDescription.aParameters, 2) + " and Row " + GetValueInMapWithIndex(rDescription.aParameters, 1); } else if (rDescription.aAction == "DELETE_CONTENT") { aLogLine = "Remove Content from This " + aParameterString; } else if (rDescription.aAction == "DELETE_CELLS") { aLogLine = "Delete The Cells in" + aParameterString; } else if (rDescription.aAction == "INSERT_CELLS") { aLogLine = "Insert Cell around the " + aParameterString; } else if (rDescription.aAction == "CUT") { aLogLine = "CUT the selected " + aParameterString; } else if (rDescription.aAction == "COPY") { aLogLine = "COPY the selected " + aParameterString; } else if (rDescription.aAction == "PASTE") { aLogLine = "Paste in the " + aParameterString; } else if (rDescription.aAction == "MERGE_CELLS") { aLogLine = "Merge " + aParameterString; } else if (rDescription.aAction == "UNMERGE_CELL") { aLogLine = "Delete the merged " + aParameterString; } else if (rDescription.aAction == "Rename_Sheet") { aLogLine = "Rename The Selected Tab to \"" + GetValueInMapWithIndex(rDescription.aParameters, 0) + "\""; } else if (rDescription.aAction == "InsertTab") { aLogLine = "Insert New Tab "; } else if (rDescription.aAction == "COMMENT") { OUString type = GetKeyInMapWithIndex(rDescription.aParameters, 0); if (type == "OPEN") { aLogLine = "Open Comment"; } else if (type == "CLOSE") { aLogLine = "Close Comment"; } } } else if (rDescription.aID == "impress_win_or_draw_win") { if (rDescription.aAction == "Insert_New_Page_or_Slide") { if (UITestLogger::getInstance().getAppName() == "impress") { aLogLine = "Insert New Slide at Position " + GetValueInMapWithIndex(rDescription.aParameters, 0); } else if (UITestLogger::getInstance().getAppName() == "draw") { aLogLine = "Insert New Page at Position " + GetValueInMapWithIndex(rDescription.aParameters, 0); } } else if (rDescription.aAction == "Delete_Slide_or_Page") { if (UITestLogger::getInstance().getAppName() == "impress") { aLogLine = "Delete Slide number " + GetValueInMapWithIndex(rDescription.aParameters, 0); } else if (UITestLogger::getInstance().getAppName() == "draw") { aLogLine = "Delete Page number " + GetValueInMapWithIndex(rDescription.aParameters, 0); } } else if (rDescription.aAction == "Duplicate") { aLogLine = "Duplicate The Selected Slide "; } else if (rDescription.aAction == "RENAME") { if (UITestLogger::getInstance().getAppName() == "impress") { aLogLine = "Rename The Selected Slide from \"" + GetValueInMapWithIndex(rDescription.aParameters, 1) + "\" to \"" + GetValueInMapWithIndex(rDescription.aParameters, 0) + "\""; } else if (UITestLogger::getInstance().getAppName() == "draw") { aLogLine = "Rename The Selected Page from \"" + GetValueInMapWithIndex(rDescription.aParameters, 1) + "\" to \"" + GetValueInMapWithIndex(rDescription.aParameters, 0) + "\""; } } } else if (rDescription.aKeyWord == "SwEditWinUIObject") { if (rDescription.aAction == "LEAVE") { aLogLine = "Leave '" + rDescription.aID + "'"; } else if (rDescription.aAction == "SHOW") { aLogLine = "Show '" + rDescription.aID + "'"; } else if (rDescription.aAction == "HIDE") { aLogLine = "Hide '" + rDescription.aID + "'"; } else if (rDescription.aAction == "DELETE") { aLogLine = "Delete '" + rDescription.aID + "'"; } else if (rDescription.aAction == "SETRESOLVED") { aLogLine = "Resolve '" + rDescription.aID + "'"; } } else if (rDescription.aParent == "element_selector") { aLogLine = "Select element no " + rDescription.aID + " From " + rDescription.aParent; } else if (rDescription.aKeyWord == "MenuButton") { if (rDescription.aAction == "OPENLIST") { aLogLine = "Open List From " + rDescription.aID; } else if (rDescription.aAction == "CLOSELIST") { aLogLine = "Close List From " + rDescription.aID; } else if (rDescription.aAction == "OPENFROMLIST") { aLogLine = "Select item no " + GetValueInMapWithIndex(rDescription.aParameters, 0) + " From List of " + rDescription.aID; } } else if (rDescription.aKeyWord == "VerticalTab") { aLogLine = "Choose Tab number " + GetValueInMapWithIndex(rDescription.aParameters, 0) + " in '" + rDescription.aID + "'"; } else { aLogLine = rDescription.aKeyWord + " Action:" + rDescription.aAction + " Id:" + rDescription.aID + " Parent:" + rDescription.aParent + aParameterString; } log(aLogLine); } UITestLogger& UITestLogger::getInstance() { ImplSVData* const pSVData = ImplGetSVData(); assert(pSVData); if (!pSVData->maFrameData.m_pUITestLogger) { pSVData->maFrameData.m_pUITestLogger.reset(new UITestLogger); } return *pSVData->maFrameData.m_pUITestLogger; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */