diff options
Diffstat (limited to '')
23 files changed, 5543 insertions, 0 deletions
diff --git a/xbmc/windows/CMakeLists.txt b/xbmc/windows/CMakeLists.txt new file mode 100644 index 0000000..2fe465f --- /dev/null +++ b/xbmc/windows/CMakeLists.txt @@ -0,0 +1,25 @@ +set(SOURCES GUIMediaWindow.cpp + GUIWindowDebugInfo.cpp + GUIWindowFileManager.cpp + GUIWindowHome.cpp + GUIWindowLoginScreen.cpp + GUIWindowPointer.cpp + GUIWindowScreensaver.cpp + GUIWindowScreensaverDim.cpp + GUIWindowSplash.cpp + GUIWindowStartup.cpp + GUIWindowSystemInfo.cpp) + +set(HEADERS GUIMediaWindow.h + GUIWindowDebugInfo.h + GUIWindowFileManager.h + GUIWindowHome.h + GUIWindowLoginScreen.h + GUIWindowPointer.h + GUIWindowScreensaver.h + GUIWindowScreensaverDim.h + GUIWindowSplash.h + GUIWindowStartup.h + GUIWindowSystemInfo.h) + +core_add_library(windows) diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp new file mode 100644 index 0000000..b55cb17 --- /dev/null +++ b/xbmc/windows/GUIMediaWindow.cpp @@ -0,0 +1,2314 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIMediaWindow.h" + +#include "ContextMenuManager.h" +#include "FileItem.h" +#include "FileItemListModification.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "PartyModeManager.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "addons/AddonManager.h" +#include "addons/PluginSource.h" +#include "addons/addoninfo/AddonType.h" +#include "application/Application.h" +#include "messaging/ApplicationMessenger.h" +#if defined(TARGET_ANDROID) +#include "platform/android/activity/XBMCApp.h" +#endif +#include "dialogs/GUIDialogBusy.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "dialogs/GUIDialogMediaFilter.h" +#include "dialogs/GUIDialogProgress.h" +#include "dialogs/GUIDialogSmartPlaylistEditor.h" +#include "filesystem/FileDirectoryFactory.h" +#include "filesystem/MultiPathDirectory.h" +#include "filesystem/PluginDirectory.h" +#include "filesystem/SmartPlaylistDirectory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIEditControl.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "interfaces/generic/ScriptInvocationManager.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "network/Network.h" +#include "playlists/PlayList.h" +#include "profiles/ProfileManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "threads/IRunnable.h" +#include "utils/FileUtils.h" +#include "utils/LabelFormatter.h" +#include "utils/SortUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "view/GUIViewState.h" + +#include <inttypes.h> + +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_BTN_FILTER 19 + +#define CONTROL_LABELFILES 12 + +#define PROPERTY_PATH_DB "path.db" +#define PROPERTY_SORT_ORDER "sort.order" +#define PROPERTY_SORT_ASCENDING "sort.ascending" + +#define PLUGIN_REFRESH_DELAY 200 + +using namespace ADDON; +using namespace KODI::MESSAGING; +using namespace std::chrono_literals; + +namespace +{ +class CGetDirectoryItems : public IRunnable +{ +public: + CGetDirectoryItems(XFILE::CVirtualDirectory &dir, CURL &url, CFileItemList &items, bool useDir) + : m_dir(dir), m_url(url), m_items(items), m_useDir(useDir) + { + } + + void Run() override + { + m_result = m_dir.GetDirectory(m_url, m_items, m_useDir, true); + } + + void Cancel() override + { + m_dir.CancelDirectory(); + } + + bool m_result = false; + +protected: + XFILE::CVirtualDirectory &m_dir; + CURL m_url; + CFileItemList &m_items; + bool m_useDir; +}; +} + +CGUIMediaWindow::CGUIMediaWindow(int id, const char *xmlFile) + : CGUIWindow(id, xmlFile) +{ + m_loadType = KEEP_IN_MEMORY; + m_vecItems = new CFileItemList; + m_unfilteredItems = new CFileItemList; + m_vecItems->SetPath("?"); + m_iLastControl = -1; + m_canFilterAdvanced = false; + + m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems)); +} + +CGUIMediaWindow::~CGUIMediaWindow() +{ + delete m_vecItems; + delete m_unfilteredItems; +} + +bool CGUIMediaWindow::Load(TiXmlElement *pRootElement) +{ + bool retVal = CGUIWindow::Load(pRootElement); + + if (!retVal) + return false; + + // configure our view control + m_viewControl.Reset(); + m_viewControl.SetParentWindow(GetID()); + TiXmlElement *element = pRootElement->FirstChildElement("views"); + if (element && element->FirstChild()) + { // format is <views>50,29,51,95</views> + const std::string &allViews = element->FirstChild()->ValueStr(); + std::vector<std::string> views = StringUtils::Split(allViews, ","); + for (std::vector<std::string>::const_iterator i = views.begin(); i != views.end(); ++i) + { + int controlID = atol(i->c_str()); + CGUIControl *control = GetControl(controlID); + if (control && control->IsContainer()) + m_viewControl.AddView(control); + } + } + m_viewControl.SetViewControlID(CONTROL_BTNVIEWASICONS); + + return true; +} + +void CGUIMediaWindow::OnWindowLoaded() +{ + SendMessage(GUI_MSG_SET_TYPE, CONTROL_BTN_FILTER, CGUIEditControl::INPUT_TYPE_FILTER); + CGUIWindow::OnWindowLoaded(); + SetupShares(); +} + +void CGUIMediaWindow::OnWindowUnload() +{ + CGUIWindow::OnWindowUnload(); + m_viewControl.Reset(); +} + +CFileItemPtr CGUIMediaWindow::GetCurrentListItem(int offset) +{ + int item = m_viewControl.GetSelectedItem(); + if (!m_vecItems->Size() || item < 0) + return CFileItemPtr(); + item = (item + offset) % m_vecItems->Size(); + if (item < 0) item += m_vecItems->Size(); + return m_vecItems->Get(item); +} + +bool CGUIMediaWindow::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_PARENT_DIR) + { + GoParentFolder(); + return true; + } + + if (CGUIWindow::OnAction(action)) + return true; + + if (action.GetID() == ACTION_FILTER) + return Filter(); + + // live filtering + if (action.GetID() == ACTION_FILTER_CLEAR) + { + CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS); + message.SetStringParam(""); + OnMessage(message); + return true; + } + + if (action.GetID() == ACTION_BACKSPACE) + { + CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 2); // 2 for delete + OnMessage(message); + return true; + } + + if (action.GetID() >= ACTION_FILTER_SMS2 && action.GetID() <= ACTION_FILTER_SMS9) + { + std::string filter = std::to_string(action.GetID() - ACTION_FILTER_SMS2 + 2); + CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 1); // 1 for append + message.SetStringParam(filter); + OnMessage(message); + return true; + } + + return false; +} + +bool CGUIMediaWindow::OnBack(int actionID) +{ + CancelUpdateItems(); + + CURL filterUrl(m_strFilterPath); + if (actionID == ACTION_NAV_BACK && + !m_vecItems->IsVirtualDirectoryRoot() && + !URIUtils::PathEquals(m_vecItems->GetPath(), GetRootPath(), true) && + (!URIUtils::PathEquals(m_vecItems->GetPath(), m_startDirectory, true) || (m_canFilterAdvanced && filterUrl.HasOption("filter")))) + { + if (GoParentFolder()) + return true; + } + return CGUIWindow::OnBack(actionID); +} + +bool CGUIMediaWindow::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_DEINIT: + { + CancelUpdateItems(); + + m_iLastControl = GetFocusedControlID(); + CGUIWindow::OnMessage(message); + + // get rid of any active filtering + if (m_canFilterAdvanced) + { + m_canFilterAdvanced = false; + m_filter.Reset(); + } + m_strFilterPath.clear(); + + // Call ClearFileItems() after our window has finished doing any WindowClose + // animations + ClearFileItems(); + return true; + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_BTNVIEWASICONS) + { + // view as control could be a select button + int viewMode = 0; + const CGUIControl *control = GetControl(CONTROL_BTNVIEWASICONS); + if (control && control->GetControlType() != CGUIControl::GUICONTROL_BUTTON) + { + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTNVIEWASICONS); + OnMessage(msg); + viewMode = m_viewControl.GetViewModeNumber(msg.GetParam1()); + } + else + viewMode = m_viewControl.GetNextViewMode(); + + if (m_guiState) + m_guiState->SaveViewAsControl(viewMode); + + UpdateButtons(); + return true; + } + else if (iControl == CONTROL_BTNSORTASC) // sort asc + { + if (m_guiState) + m_guiState->SetNextSortOrder(); + UpdateFileList(); + return true; + } + else if (iControl == CONTROL_BTNSORTBY) // sort by + { + if (m_guiState.get() && m_guiState->ChooseSortMethod()) + UpdateFileList(); + return true; + } + else if (iControl == CONTROL_BTN_FILTER) + return Filter(false); + else if (m_viewControl.HasControl(iControl)) // list/thumb control + { + int iItem = m_viewControl.GetSelectedItem(); + int iAction = message.GetParam1(); + if (iItem < 0) break; + if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) + { + OnSelect(iItem); + } + else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) + { + OnPopupMenu(iItem); + return true; + } + } + } + break; + + case GUI_MSG_SETFOCUS: + { + if (m_viewControl.HasControl(message.GetControlId()) && m_viewControl.GetCurrentControl() != message.GetControlId()) + { + m_viewControl.SetFocused(); + return true; + } + } + break; + + case GUI_MSG_NOTIFY_ALL: + { // Message is received even if this window is inactive + if (message.GetParam1() == GUI_MSG_WINDOW_RESET) + { + m_vecItems->SetPath("?"); + return true; + } + else if ( message.GetParam1() == GUI_MSG_REFRESH_THUMBS ) + { + for (int i = 0; i < m_vecItems->Size(); i++) + m_vecItems->Get(i)->FreeMemory(true); + break; // the window will take care of any info images + } + else if (message.GetParam1() == GUI_MSG_REMOVED_MEDIA) + { + if ((m_vecItems->IsVirtualDirectoryRoot() || + m_vecItems->IsSourcesPath()) && IsActive()) + { + int iItem = m_viewControl.GetSelectedItem(); + Refresh(); + m_viewControl.SetSelectedItem(iItem); + } + else if (m_vecItems->IsRemovable()) + { // check that we have this removable share still + if (!m_rootDir.IsInSource(m_vecItems->GetPath())) + { // don't have this share any more + if (IsActive()) Update(""); + else + { + m_history.ClearPathHistory(); + m_vecItems->SetPath(""); + } + } + } + + return true; + } + else if (message.GetParam1()==GUI_MSG_UPDATE_SOURCES) + { // State of the sources changed, so update our view + if ((m_vecItems->IsVirtualDirectoryRoot() || + m_vecItems->IsSourcesPath()) && IsActive()) + { + if (m_vecItemsUpdating) + { + CLog::Log(LOGWARNING, "CGUIMediaWindow::OnMessage - updating in progress"); + return true; + } + CUpdateGuard ug(m_vecItemsUpdating); + int iItem = m_viewControl.GetSelectedItem(); + Refresh(true); + m_viewControl.SetSelectedItem(iItem); + } + return true; + } + else if (message.GetParam1()==GUI_MSG_UPDATE && IsActive()) + { + if (m_vecItemsUpdating) + { + CLog::Log(LOGWARNING, "CGUIMediaWindow::OnMessage - updating in progress"); + return true; + } + CUpdateGuard ug(m_vecItemsUpdating); + if (message.GetNumStringParams()) + { + if (message.GetParam2()) // param2 is used for resetting the history + SetHistoryForPath(message.GetStringParam()); + + CFileItemList list(message.GetStringParam()); + list.RemoveDiscCache(GetID()); + Update(message.GetStringParam()); + } + else + Refresh(true); // refresh the listing + } + else if (message.GetParam1()==GUI_MSG_UPDATE_ITEM && message.GetItem()) + { + int flag = message.GetParam2(); + CFileItemPtr newItem = std::static_pointer_cast<CFileItem>(message.GetItem()); + + if (IsActive() || (flag & GUI_MSG_FLAG_FORCE_UPDATE)) + { + m_vecItems->UpdateItem(newItem.get()); + + if (flag & GUI_MSG_FLAG_UPDATE_LIST) + { // need the list updated as well + UpdateFileList(); + } + } + else if (newItem) + { // need to remove the disc cache + CFileItemList items; + items.SetPath(URIUtils::GetDirectory(newItem->GetPath())); + if (newItem->HasProperty("cachefilename")) + { + // Use stored cache file name + std::string crcfile = newItem->GetProperty("cachefilename").asString(); + items.RemoveDiscCacheCRC(crcfile); + } + else + // No stored cache file name, attempt using truncated item path as list path + items.RemoveDiscCache(GetID()); + } + } + else if (message.GetParam1()==GUI_MSG_UPDATE_PATH) + { + if (IsActive()) + { + if((message.GetStringParam() == m_vecItems->GetPath()) || + (m_vecItems->IsMultiPath() && XFILE::CMultiPathDirectory::HasPath(m_vecItems->GetPath(), message.GetStringParam()))) + Refresh(); + } + } + else if (message.GetParam1() == GUI_MSG_FILTER_ITEMS && IsActive()) + { + std::string filter = GetProperty("filter").asString(); + // check if this is meant for advanced filtering + if (message.GetParam2() != 10) + { + if (message.GetParam2() == 1) // append + filter += message.GetStringParam(); + else if (message.GetParam2() == 2) + { // delete + if (filter.size()) + filter.erase(filter.size() - 1); + } + else + filter = message.GetStringParam(); + } + OnFilterItems(filter); + UpdateButtons(); + return true; + } + else + return CGUIWindow::OnMessage(message); + + return true; + } + break; + case GUI_MSG_PLAYBACK_STARTED: + case GUI_MSG_PLAYBACK_ENDED: + case GUI_MSG_PLAYBACK_STOPPED: + case GUI_MSG_PLAYLIST_CHANGED: + case GUI_MSG_PLAYLISTPLAYER_STOPPED: + case GUI_MSG_PLAYLISTPLAYER_STARTED: + case GUI_MSG_PLAYLISTPLAYER_CHANGED: + { // send a notify all to all controls on this window + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_REFRESH_LIST); + OnMessage(msg); + break; + } + case GUI_MSG_CHANGE_VIEW_MODE: + { + int viewMode = 0; + if (message.GetParam1()) // we have an id + viewMode = m_viewControl.GetViewModeByID(message.GetParam1()); + else if (message.GetParam2()) + viewMode = m_viewControl.GetNextViewMode(message.GetParam2()); + + if (m_guiState) + m_guiState->SaveViewAsControl(viewMode); + UpdateButtons(); + return true; + } + break; + case GUI_MSG_CHANGE_SORT_METHOD: + { + if (m_guiState) + { + if (message.GetParam1()) + m_guiState->SetCurrentSortMethod(message.GetParam1()); + else if (message.GetParam2()) + m_guiState->SetNextSortMethod(message.GetParam2()); + } + UpdateFileList(); + return true; + } + break; + case GUI_MSG_CHANGE_SORT_DIRECTION: + { + if (m_guiState) + m_guiState->SetNextSortOrder(); + UpdateFileList(); + return true; + } + break; + case GUI_MSG_WINDOW_INIT: + { + if (m_vecItems->GetPath() == "?") + m_vecItems->SetPath(""); + + std::string dir = message.GetStringParam(0); + const std::string& ret = message.GetStringParam(1); + const std::string& swap = message.GetStringParam(message.GetNumStringParams() - 1); + const bool returning = StringUtils::EqualsNoCase(ret, "return"); + const bool replacing = StringUtils::EqualsNoCase(swap, "replace"); + + if (!dir.empty()) + { + // ensure our directory is valid + dir = GetStartFolder(dir); + bool resetHistory = false; + if (!returning || !URIUtils::PathEquals(dir, m_startDirectory, true)) + { // we're not returning to the same path, so set our directory to the requested path + m_vecItems->SetPath(dir); + resetHistory = true; + } + else if (m_vecItems->GetPath().empty() && URIUtils::PathEquals(dir, m_startDirectory, true)) + m_vecItems->SetPath(dir); + + // check for network up + if (URIUtils::IsRemote(m_vecItems->GetPath()) && !WaitForNetwork()) + { + m_vecItems->SetPath(""); + resetHistory = true; + } + if (resetHistory) + { + m_vecItems->RemoveDiscCache(GetID()); + // only compute the history for the provided path if "return" is not defined + // (otherwise the root level for the path will be added by default to the path history + // and we won't be able to move back to the path we came from) + if (!returning) + SetHistoryForPath(m_vecItems->GetPath()); + } + } + if (message.GetParam1() != WINDOW_INVALID) + { + // if this is the first time to this window - make sure we set the root path + // if "return" is defined make sure we set the startDirectory to the directory we are + // moving to (so that we can move back to where we were onBack). If we are activating + // the same window but with a different path, do nothing - we are simply adding to the + // window history. Note that if the window is just being replaced, the start directory + // also needs to be set as the manager has just popped the previous window. + if (message.GetParam1() != message.GetParam2() || replacing) + m_startDirectory = returning ? dir : GetRootPath(); + } + if (message.GetParam2() == PLUGIN_REFRESH_DELAY) + { + Refresh(); + SetInitialVisibility(); + RestoreControlStates(); + SetInitialVisibility(); + return true; + } + } + break; + } + + return CGUIWindow::OnMessage(message); +} + +/*! + * \brief Updates the states + * + * This updates the states (enable, disable, visible...) of the controls defined + * by this window. + * + * \note Override this function in a derived class to add new controls + */ +void CGUIMediaWindow::UpdateButtons() +{ + if (m_guiState) + { + // Update sorting controls + if (m_guiState->GetSortOrder() == SortOrderNone) + { + CONTROL_DISABLE(CONTROL_BTNSORTASC); + } + else + { + CONTROL_ENABLE(CONTROL_BTNSORTASC); + SET_CONTROL_SELECTED(GetID(), CONTROL_BTNSORTASC, m_guiState->GetSortOrder() != SortOrderAscending); + } + + // Update list/thumb control + m_viewControl.SetCurrentView(m_guiState->GetViewAsControl()); + + // Update sort by button + if (!m_guiState->HasMultipleSortMethods()) + CONTROL_DISABLE(CONTROL_BTNSORTBY); + else + CONTROL_ENABLE(CONTROL_BTNSORTBY); + + std::string sortLabel = StringUtils::Format( + g_localizeStrings.Get(550), g_localizeStrings.Get(m_guiState->GetSortMethodLabel())); + SET_CONTROL_LABEL(CONTROL_BTNSORTBY, sortLabel); + } + + std::string items = + StringUtils::Format("{} {}", m_vecItems->GetObjectCount(), g_localizeStrings.Get(127)); + SET_CONTROL_LABEL(CONTROL_LABELFILES, items); + + SET_CONTROL_LABEL2(CONTROL_BTN_FILTER, GetProperty("filter").asString()); +} + +void CGUIMediaWindow::ClearFileItems() +{ + m_viewControl.Clear(); + m_vecItems->Clear(); + m_unfilteredItems->Clear(); +} + +/*! + * \brief Sort file items + * + * This sorts file items based on the sort method and sort order provided by + * guiViewState. + */ +void CGUIMediaWindow::SortItems(CFileItemList &items) +{ + std::unique_ptr<CGUIViewState> guiState(CGUIViewState::GetViewState(GetID(), items)); + + if (guiState) + { + SortDescription sorting = guiState->GetSortMethod(); + sorting.sortOrder = guiState->GetSortOrder(); + // If the sort method is "sort by playlist" and we have a specific + // sort order available we can use the specified sort order to do the sorting + // We do this as the new SortBy methods are a superset of the SORT_METHOD methods, thus + // not all are available. This may be removed once SORT_METHOD_* have been replaced by + // SortBy. + if ((sorting.sortBy == SortByPlaylistOrder) && items.HasProperty(PROPERTY_SORT_ORDER)) + { + SortBy sortBy = (SortBy)items.GetProperty(PROPERTY_SORT_ORDER).asInteger(); + if (sortBy != SortByNone && sortBy != SortByPlaylistOrder && sortBy != SortByProgramCount) + { + sorting.sortBy = sortBy; + sorting.sortOrder = items.GetProperty(PROPERTY_SORT_ASCENDING).asBoolean() ? SortOrderAscending : SortOrderDescending; + sorting.sortAttributes = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone; + + // if the sort order is descending, we need to switch the original sort order, as we assume + // in CGUIViewState::AddPlaylistOrder that SortByPlaylistOrder is ascending. + if (guiState->GetSortOrder() == SortOrderDescending) + sorting.sortOrder = sorting.sortOrder == SortOrderDescending ? SortOrderAscending : SortOrderDescending; + } + } + + items.Sort(sorting); + } +} + +/*! + * \brief Formats item labels + * + * This is based on the formatting provided by guiViewState. + */ +void CGUIMediaWindow::FormatItemLabels(CFileItemList &items, const LABEL_MASKS &labelMasks) +{ + CLabelFormatter fileFormatter(labelMasks.m_strLabelFile, labelMasks.m_strLabel2File); + CLabelFormatter folderFormatter(labelMasks.m_strLabelFolder, labelMasks.m_strLabel2Folder); + for (int i=0; i<items.Size(); ++i) + { + CFileItemPtr pItem=items[i]; + + if (pItem->IsLabelPreformatted()) + continue; + + if (pItem->m_bIsFolder) + folderFormatter.FormatLabels(pItem.get()); + else + fileFormatter.FormatLabels(pItem.get()); + } + + if (items.GetSortMethod() == SortByLabel) + items.ClearSortState(); +} + +/*! + * \brief Format and sort file items + * + * Prepares and adds the fileitems to list/thumb panel + */ +void CGUIMediaWindow::FormatAndSort(CFileItemList &items) +{ + std::unique_ptr<CGUIViewState> viewState(CGUIViewState::GetViewState(GetID(), items)); + + if (viewState) + { + LABEL_MASKS labelMasks; + viewState->GetSortMethodLabelMasks(labelMasks); + FormatItemLabels(items, labelMasks); + + items.Sort(viewState->GetSortMethod().sortBy, viewState->GetSortOrder(), viewState->GetSortMethod().sortAttributes); + } +} + +/*! + * \brief Overwrite to fill fileitems from a source + * + * \param[in] strDirectory Path to read + * \param[out] items Fill with items specified in \e strDirectory + * \return false if given directory not present + */ +bool CGUIMediaWindow::GetDirectory(const std::string &strDirectory, CFileItemList &items) +{ + CURL pathToUrl(strDirectory); + + std::string strParentPath = m_history.GetParentPath(); + + CLog::Log(LOGDEBUG, "CGUIMediaWindow::GetDirectory ({})", CURL::GetRedacted(strDirectory)); + CLog::Log(LOGDEBUG, " ParentPath = [{}]", CURL::GetRedacted(strParentPath)); + + if (pathToUrl.IsProtocol("plugin") && !pathToUrl.GetHostName().empty()) + CServiceBroker::GetAddonMgr().UpdateLastUsed(pathToUrl.GetHostName()); + + // see if we can load a previously cached folder + CFileItemList cachedItems(strDirectory); + if (!strDirectory.empty() && cachedItems.Load(GetID())) + { + items.Assign(cachedItems); + } + else + { + auto start = std::chrono::steady_clock::now(); + + if (strDirectory.empty()) + SetupShares(); + + CFileItemList dirItems; + if (!GetDirectoryItems(pathToUrl, dirItems, UseFileDirectories())) + return false; + + // assign fetched directory items + items.Assign(dirItems); + + // took over a second, and not normally cached, so cache it + auto end = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); + + if (duration.count() > 1000 && items.CacheToDiscIfSlow()) + items.Save(GetID()); + + // if these items should replace the current listing, then pop it off the top + if (items.GetReplaceListing()) + m_history.RemoveParentPath(); + } + + // update the view state's reference to the current items + m_guiState.reset(CGUIViewState::GetViewState(GetID(), items)); + + bool bHideParent = false; + + if (m_guiState && m_guiState->HideParentDirItems()) + bHideParent = true; + if (items.GetPath() == GetRootPath()) + bHideParent = true; + + if (!bHideParent) + { + CFileItemPtr pItem(new CFileItem("..")); + pItem->SetPath(strParentPath); + pItem->m_bIsFolder = true; + pItem->m_bIsShareOrDrive = false; + items.AddFront(pItem, 0); + } + + int iWindow = GetID(); + std::vector<std::string> regexps; + + //! @todo Do we want to limit the directories we apply the video ones to? + if (iWindow == WINDOW_VIDEO_NAV) + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoExcludeFromListingRegExps; + if (iWindow == WINDOW_MUSIC_NAV) + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioExcludeFromListingRegExps; + if (iWindow == WINDOW_PICTURES) + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pictureExcludeFromListingRegExps; + + if (regexps.size()) + { + for (int i=0; i < items.Size();) + { + if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps)) + items.Remove(i); + else + i++; + } + } + + // clear the filter + SetProperty("filter", ""); + m_canFilterAdvanced = false; + m_filter.Reset(); + return true; +} + +bool CGUIMediaWindow::Update(const std::string &strDirectory, bool updateFilterPath /* = true */) +{ + //! @todo OnInitWindow calls Update() before window path has been set properly. + if (strDirectory == "?") + return false; + + // The path to load. Empty string is used in various places to denote root, so translate to the + // real root path first + const std::string path = strDirectory.empty() ? GetRootPath() : strDirectory; + + // stores the selected item in history + SaveSelectedItemInHistory(); + + const std::string previousPath = m_vecItems->GetPath(); + + // check if the path contains a filter and temporarily remove it + // so that the retrieved list of items is unfiltered + std::string pathNoFilter = path; + if (CanContainFilter(pathNoFilter) && CURL(pathNoFilter).HasOption("filter")) + pathNoFilter = RemoveParameterFromPath(pathNoFilter, "filter"); + + if (!GetDirectory(pathNoFilter, *m_vecItems)) + { + CLog::Log(LOGERROR, "CGUIMediaWindow::GetDirectory({}) failed", CURL(path).GetRedacted()); + + if (URIUtils::PathEquals(path, GetRootPath())) + return false; // Nothing to fallback to + + // Try to return to the previous directory, if not the same + // else fallback to root + if (URIUtils::PathEquals(path, previousPath) || !Update(m_history.RemoveParentPath())) + Update(""); // Fallback to root + + // Return false to be able to eg. show + // an error message. + return false; + } + + if (m_vecItems->GetLabel().empty()) + { + // Removable sources + VECSOURCES removables; + CServiceBroker::GetMediaManager().GetRemovableDrives(removables); + for (const auto& s : removables) + { + if (URIUtils::CompareWithoutSlashAtEnd(s.strPath, m_vecItems->GetPath())) + { + m_vecItems->SetLabel(s.strName); + break; + } + } + } + + if (m_vecItems->GetLabel().empty()) + m_vecItems->SetLabel(CUtil::GetTitleFromPath(m_vecItems->GetPath(), true)); + + // check the given path for filter data + UpdateFilterPath(path, *m_vecItems, updateFilterPath); + + // if we're getting the root source listing + // make sure the path history is clean + if (URIUtils::PathEquals(path, GetRootPath())) + m_history.ClearPathHistory(); + + int iWindow = GetID(); + int showLabel = 0; + if (URIUtils::PathEquals(path, GetRootPath())) + { + if (iWindow == WINDOW_PICTURES) + showLabel = 997; + else if (iWindow == WINDOW_FILES) + showLabel = 1026; + else if (iWindow == WINDOW_GAMES) + showLabel = 35250; // "Add games..." + } + if (m_vecItems->IsPath("sources://video/")) + showLabel = 999; + else if (m_vecItems->IsPath("sources://music/")) + showLabel = 998; + else if (m_vecItems->IsPath("sources://pictures/")) + showLabel = 997; + else if (m_vecItems->IsPath("sources://files/")) + showLabel = 1026; + else if (m_vecItems->IsPath("sources://games/")) + showLabel = 35250; // "Add games..." + // Add 'Add source ' item + if (showLabel && (m_vecItems->Size() == 0 || !m_guiState->DisableAddSourceButtons()) && + iWindow != WINDOW_MUSIC_PLAYLIST_EDITOR) + { + const std::string& strLabel = g_localizeStrings.Get(showLabel); + CFileItemPtr pItem(new CFileItem(strLabel)); + pItem->SetPath("add"); + pItem->SetArt("icon", "DefaultAddSource.png"); + pItem->SetLabel(strLabel); + pItem->SetLabelPreformatted(true); + pItem->m_bIsFolder = true; + pItem->SetSpecialSort(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_addSourceOnTop ? + SortSpecialOnTop : SortSpecialOnBottom); + m_vecItems->Add(pItem); + } + m_iLastControl = GetFocusedControlID(); + + // Check whether to enabled advanced filtering based on the content type + m_canFilterAdvanced = CheckFilterAdvanced(*m_vecItems); + if (m_canFilterAdvanced) + m_filter.SetType(m_vecItems->GetContent()); + + // Ask the derived class if it wants to load additional info + // for the fileitems like media info or additional + // filtering on the items, setting thumbs. + OnPrepareFileItems(*m_vecItems); + + m_vecItems->FillInDefaultIcons(); + + // remember the original (untouched) list of items (for filtering etc) + m_unfilteredItems->Assign(*m_vecItems); + + // Cache the list of items if possible + OnCacheFileItems(*m_vecItems); + + // Filter and group the items if necessary + OnFilterItems(GetProperty("filter").asString()); + UpdateButtons(); + + // Restore selected item from history + RestoreSelectedItemFromHistory(); + + m_history.AddPath(m_vecItems->GetPath(), m_strFilterPath); + + //m_history.DumpPathHistory(); + + return true; +} + +bool CGUIMediaWindow::Refresh(bool clearCache /* = false */) +{ + std::string strCurrentDirectory = m_vecItems->GetPath(); + if (strCurrentDirectory == "?") + return false; + + if (clearCache) + m_vecItems->RemoveDiscCache(GetID()); + + bool ret = true; + + // get the original number of items + if (!Update(strCurrentDirectory, false)) + { + ret = false; + } + + return ret; +} + +/*! + * \brief On prepare file items + * + * This function will be called by Update() before the labels of the fileitems + * are formatted. + * + * \note Override this function to set custom thumbs or load additional media + * info. + * + * It's used to load tag info for music. + */ +void CGUIMediaWindow::OnPrepareFileItems(CFileItemList &items) +{ + CFileItemListModification::GetInstance().Modify(items); +} + +/*! + * \brief On cache file items + * + * This function will be called by Update() before + * any additional formatting, filtering or sorting is applied. + * + * \note Override this function to define a custom caching behaviour. + */ +void CGUIMediaWindow::OnCacheFileItems(CFileItemList &items) +{ + // Should these items be saved to the hdd + if (items.CacheToDiscAlways() && !IsFiltered()) + items.Save(GetID()); +} + +/*! + * \brief On click + * + * With this function you can react on a users click in the list/thumb panel. + * It returns true, if the click is handled. + * This function calls OnPlayMedia() + */ +bool CGUIMediaWindow::OnClick(int iItem, const std::string &player) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) + return true; + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + CFileItemPtr pItem = m_vecItems->Get(iItem); + + if (pItem->IsParentFolder()) + { + GoParentFolder(); + return true; + } + + if (pItem->GetPath() == "add" || pItem->GetPath() == "sources://add/") // 'add source button' in empty root + { + if (profileManager->IsMasterProfile()) + { + if (!g_passwordManager.IsMasterLockUnlocked(true)) + return false; + } + else if (!profileManager->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) + return false; + + if (OnAddMediaSource()) + Refresh(true); + + return true; + } + + if (!pItem->m_bIsFolder && pItem->IsFileFolder(EFILEFOLDER_MASK_ONCLICK)) + { + XFILE::IFileDirectory *pFileDirectory = nullptr; + pFileDirectory = XFILE::CFileDirectoryFactory::Create(pItem->GetURL(), pItem.get(), ""); + if(pFileDirectory) + pItem->m_bIsFolder = true; + else if(pItem->m_bIsFolder) + pItem->m_bIsFolder = false; + delete pFileDirectory; + } + + if (pItem->IsScript()) + { + // execute the script + CURL url(pItem->GetPath()); + AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, AddonType::SCRIPT, + OnlyEnabled::CHOICE_YES)) + { + if (!CScriptInvocationManager::GetInstance().Stop(addon->LibPath())) + { + CServiceBroker::GetAddonMgr().UpdateLastUsed(addon->ID()); + CScriptInvocationManager::GetInstance().ExecuteAsync(addon->LibPath(), addon); + } + return true; + } + } + + if (pItem->m_bIsFolder) + { + if ( pItem->m_bIsShareOrDrive ) + { + const std::string& strLockType=m_guiState->GetLockType(); + if (profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE) + if (!strLockType.empty() && !g_passwordManager.IsItemUnlocked(pItem.get(), strLockType)) + return true; + + if (!HaveDiscOrConnection(pItem->GetPath(), pItem->m_iDriveType)) + return true; + } + + // check for the partymode playlist items - they may not exist yet + if ((pItem->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp")) || + (pItem->GetPath() == profileManager->GetUserDataItem("PartyMode-Video.xsp"))) + { + // party mode playlist item - if it doesn't exist, prompt for user to define it + if (!CFileUtils::Exists(pItem->GetPath())) + { + m_vecItems->RemoveDiscCache(GetID()); + if (CGUIDialogSmartPlaylistEditor::EditPlaylist(pItem->GetPath())) + Refresh(); + return true; + } + } + + // remove the directory cache if the folder is not normally cached + CFileItemList items(pItem->GetPath()); + if (!items.AlwaysCache()) + items.RemoveDiscCache(GetID()); + + // if we have a filtered list, we need to add the filtered + // path to be able to come back to the filtered view + std::string strCurrentDirectory = m_vecItems->GetPath(); + if (m_canFilterAdvanced && !m_filter.IsEmpty() && + !URIUtils::PathEquals(m_strFilterPath, strCurrentDirectory)) + { + m_history.RemoveParentPath(); + m_history.AddPath(strCurrentDirectory, m_strFilterPath); + } + + if (m_vecItemsUpdating) + { + CLog::Log(LOGWARNING, "CGUIMediaWindow::OnClick - updating in progress"); + return true; + } + CUpdateGuard ug(m_vecItemsUpdating); + + CFileItem directory(*pItem); + if (!Update(directory.GetPath())) + ShowShareErrorMessage(&directory); + + return true; + } + else if (pItem->IsPlugin() && !pItem->GetProperty("isplayable").asBoolean()) + { + bool resume = pItem->GetStartOffset() == STARTOFFSET_RESUME; + return XFILE::CPluginDirectory::RunScriptWithParams(pItem->GetPath(), resume); + } +#if defined(TARGET_ANDROID) + else if (pItem->IsAndroidApp()) + { + std::string appName = URIUtils::GetFileName(pItem->GetPath()); + CLog::Log(LOGDEBUG, "CGUIMediaWindow::OnClick Trying to run: {}", appName); + return CXBMCApp::StartActivity(appName); + } +#endif + else + { + SaveSelectedItemInHistory(); + + if (pItem->GetPath() == "newplaylist://") + { + m_vecItems->RemoveDiscCache(GetID()); + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR,"newplaylist://"); + return true; + } + else if (StringUtils::StartsWithNoCase(pItem->GetPath(), "newsmartplaylist://")) + { + m_vecItems->RemoveDiscCache(GetID()); + if (CGUIDialogSmartPlaylistEditor::NewPlaylist(pItem->GetPath().substr(19))) + Refresh(); + return true; + } + + bool autoplay = m_guiState.get() && m_guiState->AutoPlayNextItem(); + + if (m_vecItems->IsPlugin()) + { + CURL url(m_vecItems->GetPath()); + AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, OnlyEnabled::CHOICE_YES)) + { + const auto plugin = std::dynamic_pointer_cast<CPluginSource>(addon); + if (plugin && plugin->Provides(CPluginSource::AUDIO)) + { + CFileItemList items; + std::unique_ptr<CGUIViewState> state(CGUIViewState::GetViewState(GetID(), items)); + autoplay = state.get() && state->AutoPlayNextItem(); + } + } + } + + if (autoplay && !g_partyModeManager.IsEnabled()) + { + return OnPlayAndQueueMedia(pItem, player); + } + else + { + return OnPlayMedia(iItem, player); + } + } + + return false; +} + +bool CGUIMediaWindow::OnSelect(int item) +{ + return OnClick(item); +} + +/*! + * \brief Check disc or connection present + * + * Checks if there is a disc in the dvd drive and whether the + * network is connected or not. + */ +bool CGUIMediaWindow::HaveDiscOrConnection(const std::string& strPath, int iDriveType) +{ + if (iDriveType==CMediaSource::SOURCE_TYPE_DVD) + { + if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath)) + { + HELPERS::ShowOKDialogText(CVariant{218}, CVariant{219}); + return false; + } + } + else if (iDriveType==CMediaSource::SOURCE_TYPE_REMOTE) + { + //! @todo Handle not connected to a remote share + if (!CServiceBroker::GetNetwork().IsConnected()) + { + HELPERS::ShowOKDialogText(CVariant{220}, CVariant{221}); + return false; + } + } + + return true; +} + +/*! + * \brief Shows a standard error message for a given pItem. + */ +void CGUIMediaWindow::ShowShareErrorMessage(CFileItem* pItem) const +{ + if (!pItem->m_bIsShareOrDrive) + return; + + int idMessageText = 0; + CURL url(pItem->GetPath()); + + if (url.IsProtocol("smb") && url.GetHostName().empty()) // smb workgroup + idMessageText = 15303; // Workgroup not found + else if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_REMOTE || URIUtils::IsRemote(pItem->GetPath())) + idMessageText = 15301; // Could not connect to network server + else + idMessageText = 15300; // Path not found or invalid + + HELPERS::ShowOKDialogText(CVariant{220}, CVariant{idMessageText}); +} + +/*! + * \brief Go one directory up on list items + * + * The function goes up one level in the directory tree + */ +bool CGUIMediaWindow::GoParentFolder() +{ + if (m_vecItems->IsVirtualDirectoryRoot()) + return false; + + if (URIUtils::PathEquals(m_vecItems->GetPath(), GetRootPath())) + return false; + + //m_history.DumpPathHistory(); + + const std::string currentPath = m_vecItems->GetPath(); + std::string parentPath = m_history.GetParentPath(); + // Check if a) the current folder is on the stack more than once, (parent is + // often same as current), OR + // b) the parent is an xml file (happens when ActivateWindow() called with + // a node file) and so current path is the result of expanding the xml. + // Keep going until there's nothing left or they dont match anymore. + while (!parentPath.empty() && + (URIUtils::PathEquals(parentPath, currentPath, true) || + StringUtils::EndsWith(parentPath, ".xml/") || StringUtils::EndsWith(parentPath, ".xml"))) + { + m_history.RemoveParentPath(); + parentPath = m_history.GetParentPath(); + } + + // remove the current filter but only if the parent + // item doesn't have a filter as well + CURL filterUrl(m_strFilterPath); + if (filterUrl.HasOption("filter")) + { + CURL parentUrl(m_history.GetParentPath(true)); + if (!parentUrl.HasOption("filter")) + { + // we need to overwrite m_strFilterPath because + // Refresh() will set updateFilterPath to false + m_strFilterPath.clear(); + Refresh(); + return true; + } + } + + // pop directory path from the stack + m_strFilterPath = m_history.GetParentPath(true); + m_history.RemoveParentPath(); + + if (!Update(parentPath, false)) + return false; + + // No items to show so go another level up + if (!m_vecItems->GetPath().empty() && (m_filter.IsEmpty() ? m_vecItems->Size() : m_unfilteredItems->Size()) <= 0) + { + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(2080), g_localizeStrings.Get(2081)); + return GoParentFolder(); + } + return true; +} + +void CGUIMediaWindow::SaveSelectedItemInHistory() +{ + int iItem = m_viewControl.GetSelectedItem(); + std::string strSelectedItem; + if (iItem >= 0 && iItem < m_vecItems->Size()) + { + CFileItemPtr pItem = m_vecItems->Get(iItem); + GetDirectoryHistoryString(pItem.get(), strSelectedItem); + } + + m_history.SetSelectedItem(strSelectedItem, m_vecItems->GetPath()); +} + +void CGUIMediaWindow::RestoreSelectedItemFromHistory() +{ + std::string strSelectedItem = m_history.GetSelectedItem(m_vecItems->GetPath()); + + if (!strSelectedItem.empty()) + { + for (int i = 0; i < m_vecItems->Size(); ++i) + { + CFileItemPtr pItem = m_vecItems->Get(i); + std::string strHistory; + GetDirectoryHistoryString(pItem.get(), strHistory); + // set selected item if equals with history + if (strHistory == strSelectedItem) + { + m_viewControl.SetSelectedItem(i); + return; + } + } + } + + // if we haven't found the selected item, select the first item + m_viewControl.SetSelectedItem(0); +} + +/*! + * \brief Get history string for given file item + * + * \note Override the function to change the default behavior on how + * a selected item history should look like + */ +void CGUIMediaWindow::GetDirectoryHistoryString(const CFileItem* pItem, std::string& strHistoryString) const +{ + if (pItem->m_bIsShareOrDrive) + { + // We are in the virtual directory + + // History string of the DVD drive + // must be handled separately + if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_DVD) + { + // Remove disc label from item label + // and use as history string, m_strPath + // can change for new discs + std::string strLabel = pItem->GetLabel(); + size_t nPosOpen = strLabel.find('('); + size_t nPosClose = strLabel.rfind(')'); + if (nPosOpen != std::string::npos && + nPosClose != std::string::npos && + nPosClose > nPosOpen) + { + strLabel.erase(nPosOpen + 1, (nPosClose) - (nPosOpen + 1)); + strHistoryString = strLabel; + } + else + strHistoryString = strLabel; + } + else + { + // Other items in virtual directory + std::string strPath = pItem->GetPath(); + URIUtils::RemoveSlashAtEnd(strPath); + + strHistoryString = pItem->GetLabel() + strPath; + } + } + else if (pItem->GetEndOffset() > pItem->GetStartOffset() && + pItem->GetStartOffset() != STARTOFFSET_RESUME) + { + // Could be a cue item, all items of a cue share the same filename + // so add the offsets to build the history string + strHistoryString = StringUtils::Format("{}{}", pItem->GetStartOffset(), pItem->GetEndOffset()); + strHistoryString += pItem->GetPath(); + } + else + { + // Normal directory items + strHistoryString = pItem->GetPath(); + } + + // remove any filter + if (CanContainFilter(strHistoryString)) + strHistoryString = RemoveParameterFromPath(strHistoryString, "filter"); + + URIUtils::RemoveSlashAtEnd(strHistoryString); + StringUtils::ToLower(strHistoryString); +} + +/*! + * \brief Set history for path + * + * Call this function to create a directory history for the + * path given by strDirectory. + */ +void CGUIMediaWindow::SetHistoryForPath(const std::string& strDirectory) +{ + // Make sure our shares are configured + SetupShares(); + if (!strDirectory.empty()) + { + // Build the directory history for default path + std::string strPath, strParentPath; + strPath = strDirectory; + URIUtils::RemoveSlashAtEnd(strPath); + + CFileItemList items; + CURL url; + GetDirectoryItems(url, items, UseFileDirectories()); + + m_history.ClearPathHistory(); + + bool originalPath = true; + while (URIUtils::GetParentPath(strPath, strParentPath)) + { + for (int i = 0; i < items.Size(); ++i) + { + CFileItemPtr pItem = items[i]; + std::string path(pItem->GetPath()); + URIUtils::RemoveSlashAtEnd(path); + if (URIUtils::PathEquals(path, strPath)) + { + std::string strHistory; + GetDirectoryHistoryString(pItem.get(), strHistory); + m_history.SetSelectedItem(strHistory, ""); + URIUtils::AddSlashAtEnd(strPath); + m_history.AddPathFront(strPath); + m_history.AddPathFront(""); + + //m_history.DumpPathHistory(); + return ; + } + } + + if (URIUtils::IsVideoDb(strPath)) + { + CURL url(strParentPath); + url.SetOptions(""); // clear any URL options from recreated parent path + strParentPath = url.Get(); + } + + // set the original path exactly as it was passed in + if (URIUtils::PathEquals(strPath, strDirectory, true)) + strPath = strDirectory; + else + URIUtils::AddSlashAtEnd(strPath); + + m_history.AddPathFront(strPath, originalPath ? m_strFilterPath : ""); + m_history.SetSelectedItem(strPath, strParentPath); + originalPath = false; + strPath = strParentPath; + URIUtils::RemoveSlashAtEnd(strPath); + } + } + else + m_history.ClearPathHistory(); + + //m_history.DumpPathHistory(); +} + +/*! + * \brief On media play + * + * \note Override if you want to change the default behavior, what is done + * when the user clicks on a file. + * + * This function is called by OnClick() + */ +bool CGUIMediaWindow::OnPlayMedia(int iItem, const std::string &player) +{ + // Reset Playlistplayer, playback started now does + // not use the playlistplayer. + CServiceBroker::GetPlaylistPlayer().Reset(); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_NONE); + CFileItemPtr pItem=m_vecItems->Get(iItem); + + CLog::Log(LOGDEBUG, "{} {}", __FUNCTION__, CURL::GetRedacted(pItem->GetPath())); + + bool bResult = false; + if (pItem->IsInternetStream() || pItem->IsPlayList()) + bResult = g_application.PlayMedia(*pItem, player, m_guiState->GetPlaylist()); + else + bResult = g_application.PlayFile(*pItem, player); + + if (pItem->GetStartOffset() == STARTOFFSET_RESUME) + pItem->SetStartOffset(0); + + return bResult; +} + +/*! + * \brief On play and media queue + * + * \note Override if you want to change the default behavior of what is done + * when the user clicks on a file in a "folder" with similar files. + * + * This function is called by OnClick() + */ +bool CGUIMediaWindow::OnPlayAndQueueMedia(const CFileItemPtr& item, const std::string& player) +{ + //play and add current directory to temporary playlist + PLAYLIST::Id playlistId = m_guiState->GetPlaylist(); + if (playlistId != PLAYLIST::TYPE_NONE) + { + CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId); + CServiceBroker::GetPlaylistPlayer().Reset(); + int mediaToPlay = 0; + + // first try to find mainDVD file (VIDEO_TS.IFO). + // If we find this we should not allow to queue VOB files + std::string mainDVD; + for (int i = 0; i < m_vecItems->Size(); i++) + { + std::string path = URIUtils::GetFileName(m_vecItems->Get(i)->GetDynPath()); + if (StringUtils::EqualsNoCase(path, "VIDEO_TS.IFO")) + { + mainDVD = path; + break; + } + } + + // now queue... + for ( int i = 0; i < m_vecItems->Size(); i++ ) + { + CFileItemPtr nItem = m_vecItems->Get(i); + + if (nItem->m_bIsFolder) + continue; + + if (!nItem->IsZIP() && !nItem->IsRAR() && (!nItem->IsDVDFile() || (URIUtils::GetFileName(nItem->GetDynPath()) == mainDVD))) + CServiceBroker::GetPlaylistPlayer().Add(playlistId, nItem); + + if (item->IsSamePath(nItem.get())) + { // item that was clicked + mediaToPlay = CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size() - 1; + } + } + + // Save current window and directory to know where the selected item was + if (m_guiState) + m_guiState->SetPlaylistDirectory(m_vecItems->GetPath()); + + // figure out where we start playback + if (CServiceBroker::GetPlaylistPlayer().IsShuffled(playlistId)) + { + int iIndex = + CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).FindOrder(mediaToPlay); + CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).Swap(0, iIndex); + mediaToPlay = 0; + } + + // play + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId); + CServiceBroker::GetPlaylistPlayer().Play(mediaToPlay, player); + } + return true; +} + +/*! + * \brief Update file list + * + * Synchronize the fileitems with the playlistplayer + * also recreates the playlist of the playlistplayer based + * on the fileitems of the window + */ +void CGUIMediaWindow::UpdateFileList() +{ + int nItem = m_viewControl.GetSelectedItem(); + std::string strSelected; + if (nItem >= 0) + strSelected = m_vecItems->Get(nItem)->GetPath(); + + FormatAndSort(*m_vecItems); + UpdateButtons(); + + m_viewControl.SetItems(*m_vecItems); + m_viewControl.SetSelectedItem(strSelected); + + // set the currently playing item as selected, if its in this directory + if (m_guiState.get() && m_guiState->IsCurrentPlaylistDirectory(m_vecItems->GetPath())) + { + PLAYLIST::Id playlistId = m_guiState->GetPlaylist(); + int nSong = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + CFileItem playlistItem; + if (nSong > -1 && playlistId != PLAYLIST::TYPE_NONE) + playlistItem = *CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId)[nSong]; + + CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId); + CServiceBroker::GetPlaylistPlayer().Reset(); + + for (int i = 0; i < m_vecItems->Size(); i++) + { + CFileItemPtr pItem = m_vecItems->Get(i); + if (pItem->m_bIsFolder) + continue; + + if (!pItem->IsPlayList() && !pItem->IsZIP() && !pItem->IsRAR()) + CServiceBroker::GetPlaylistPlayer().Add(playlistId, pItem); + + if (pItem->GetPath() == playlistItem.GetPath() && + pItem->GetStartOffset() == playlistItem.GetStartOffset()) + CServiceBroker::GetPlaylistPlayer().SetCurrentSong( + CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size() - 1); + } + } +} + +void CGUIMediaWindow::OnDeleteItem(int iItem) +{ + if ( iItem < 0 || iItem >= m_vecItems->Size()) return; + CFileItemPtr item = m_vecItems->Get(iItem); + + if (item->IsPlayList()) + item->m_bIsFolder = false; + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + if (profileManager->GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && profileManager->GetCurrentProfile().filesLocked()) + { + if (!g_passwordManager.IsMasterLockUnlocked(true)) + return; + } + + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui && gui->ConfirmDelete(item->GetPath())) + { + if (!CFileUtils::DeleteItem(item)) + return; + } + else + return; + + Refresh(true); + m_viewControl.SetSelectedItem(iItem); +} + +void CGUIMediaWindow::OnRenameItem(int iItem) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) + return; + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + if (profileManager->GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && profileManager->GetCurrentProfile().filesLocked()) + { + if (!g_passwordManager.IsMasterLockUnlocked(true)) + return; + } + + if (!CFileUtils::RenameFile(m_vecItems->Get(iItem)->GetPath())) + return; + + Refresh(true); + m_viewControl.SetSelectedItem(iItem); +} + +void CGUIMediaWindow::OnInitWindow() +{ + // initial fetch is done unthreaded to ensure the items are setup prior to skin animations kicking off + m_backgroundLoad = false; + + // the start directory may change during Refresh + bool updateStartDirectory = URIUtils::PathEquals(m_vecItems->GetPath(), m_startDirectory, true); + + // we have python scripts hooked in everywhere :( + // those scripts may open windows and we can't open a window + // while opening this one. + // for plugin sources delay call to Refresh + if (!URIUtils::IsPlugin(m_vecItems->GetPath())) + { + Refresh(); + } + else + { + CGUIMessage msg(GUI_MSG_WINDOW_INIT, 0, 0, WINDOW_INVALID, PLUGIN_REFRESH_DELAY); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, GetID()); + } + + if (updateStartDirectory) + { + // reset the start directory to the path of the items + m_startDirectory = m_vecItems->GetPath(); + + // reset the history based on the path of the items + SetHistoryForPath(m_startDirectory); + } + + m_backgroundLoad = true; + + CGUIWindow::OnInitWindow(); +} + +void CGUIMediaWindow::SaveControlStates() +{ + CGUIWindow::SaveControlStates(); + SaveSelectedItemInHistory(); +} + +void CGUIMediaWindow::RestoreControlStates() +{ + CGUIWindow::RestoreControlStates(); + RestoreSelectedItemFromHistory(); +} + +CGUIControl *CGUIMediaWindow::GetFirstFocusableControl(int id) +{ + if (m_viewControl.HasControl(id)) + id = m_viewControl.GetCurrentControl(); + return CGUIWindow::GetFirstFocusableControl(id); +} + +void CGUIMediaWindow::SetupShares() +{ + // Setup shares and filemasks for this window + CFileItemList items; + CGUIViewState* viewState=CGUIViewState::GetViewState(GetID(), items); + if (viewState) + { + m_rootDir.SetMask(viewState->GetExtensions()); + m_rootDir.SetSources(viewState->GetSources()); + delete viewState; + } +} + +bool CGUIMediaWindow::OnPopupMenu(int itemIdx) +{ + auto InRange = [](size_t i, std::pair<size_t, size_t> range){ return i >= range.first && i < range.second; }; + + if (itemIdx < 0 || itemIdx >= m_vecItems->Size()) + return false; + + auto item = m_vecItems->Get(itemIdx); + if (!item) + return false; + + CContextButtons buttons; + + //Add items from plugin + { + int i = 0; + while (item->HasProperty(StringUtils::Format("contextmenulabel({})", i))) + { + buttons.emplace_back( + ~buttons.size(), + item->GetProperty(StringUtils::Format("contextmenulabel({})", i)).asString()); + ++i; + } + } + auto pluginMenuRange = std::make_pair(static_cast<size_t>(0), buttons.size()); + + //Add the global menu + auto globalMenu = CServiceBroker::GetContextMenuManager().GetItems(*item, CContextMenuManager::MAIN); + auto globalMenuRange = std::make_pair(buttons.size(), buttons.size() + globalMenu.size()); + for (const auto& menu : globalMenu) + buttons.emplace_back(~buttons.size(), menu->GetLabel(*item)); + + //Add legacy items from windows + auto buttonsSize = buttons.size(); + GetContextButtons(itemIdx, buttons); + auto windowMenuRange = std::make_pair(buttonsSize, buttons.size()); + + //Add addon menus + auto addonMenu = CServiceBroker::GetContextMenuManager().GetAddonItems(*item, CContextMenuManager::MAIN); + auto addonMenuRange = std::make_pair(buttons.size(), buttons.size() + addonMenu.size()); + for (const auto& menu : addonMenu) + buttons.emplace_back(~buttons.size(), menu->GetLabel(*item)); + + if (buttons.empty()) + return true; + + int idx = CGUIDialogContextMenu::Show(buttons); + if (idx < 0 || idx >= static_cast<int>(buttons.size())) + return false; + + if (InRange(static_cast<size_t>(idx), pluginMenuRange)) + { + bool saveVal = m_backgroundLoad; + m_backgroundLoad = false; + CServiceBroker::GetAppMessenger()->SendMsg( + TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, + item->GetProperty(StringUtils::Format("contextmenuaction({})", idx - pluginMenuRange.first)) + .asString()); + m_backgroundLoad = saveVal; + return true; + } + + if (InRange(idx, windowMenuRange)) + return OnContextButton(itemIdx, static_cast<CONTEXT_BUTTON>(buttons[idx].first)); + + if (InRange(idx, globalMenuRange)) + return CONTEXTMENU::LoopFrom(*globalMenu[idx - globalMenuRange.first], item); + + return CONTEXTMENU::LoopFrom(*addonMenu[idx - addonMenuRange.first], item); +} + +void CGUIMediaWindow::GetContextButtons(int itemNumber, CContextButtons &buttons) +{ + CFileItemPtr item = (itemNumber >= 0 && itemNumber < m_vecItems->Size()) ? m_vecItems->Get(itemNumber) : CFileItemPtr(); + + if (!item || item->IsParentFolder()) + return; + + if (item->IsFileFolder(EFILEFOLDER_MASK_ONBROWSE)) + buttons.Add(CONTEXT_BUTTON_BROWSE_INTO, 37015); + +} + +bool CGUIMediaWindow::OnContextButton(int itemNumber, CONTEXT_BUTTON button) +{ + switch (button) + { + case CONTEXT_BUTTON_BROWSE_INTO: + { + CFileItemPtr item = m_vecItems->Get(itemNumber); + Update(item->GetPath()); + return true; + } + default: + break; + } + return false; +} + +const CGUIViewState *CGUIMediaWindow::GetViewState() const +{ + return m_guiState.get(); +} + +const CFileItemList& CGUIMediaWindow::CurrentDirectory() const +{ + return *m_vecItems; +} + +bool CGUIMediaWindow::WaitForNetwork() const +{ + if (CServiceBroker::GetNetwork().IsAvailable()) + return true; + + CGUIDialogProgress *progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + if (!progress) + return true; + + CURL url(m_vecItems->GetPath()); + progress->SetHeading(CVariant{1040}); // Loading Directory + progress->SetLine(1, CVariant{url.GetWithoutUserDetails()}); + progress->ShowProgressBar(false); + progress->Open(); + while (!CServiceBroker::GetNetwork().IsAvailable()) + { + progress->Progress(); + if (progress->IsCanceled()) + { + progress->Close(); + return false; + } + } + progress->Close(); + return true; +} + +void CGUIMediaWindow::UpdateFilterPath(const std::string &strDirectory, const CFileItemList &items, bool updateFilterPath) +{ + bool canfilter = CanContainFilter(strDirectory); + + std::string filter; + CURL url(strDirectory); + if (canfilter && url.HasOption("filter")) + filter = url.GetOption("filter"); + + // only set the filter path if it hasn't been marked + // as preset or if it's empty + if (updateFilterPath || m_strFilterPath.empty()) + { + if (items.HasProperty(PROPERTY_PATH_DB)) + m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString(); + else + m_strFilterPath = items.GetPath(); + } + + // maybe the filter path can contain a filter + if (!canfilter && CanContainFilter(m_strFilterPath)) + canfilter = true; + + // check if the filter path contains a filter + CURL filterPathUrl(m_strFilterPath); + if (canfilter && filter.empty()) + { + if (filterPathUrl.HasOption("filter")) + filter = filterPathUrl.GetOption("filter"); + } + + // check if there is a filter and re-apply it + if (canfilter && !filter.empty()) + { + if (!m_filter.LoadFromJson(filter)) + { + CLog::Log(LOGWARNING, + "CGUIMediaWindow::UpdateFilterPath(): unable to load existing filter ({})", filter); + m_filter.Reset(); + m_strFilterPath = m_vecItems->GetPath(); + } + else + { + // add the filter to the filter path + filterPathUrl.SetOption("filter", filter); + m_strFilterPath = filterPathUrl.Get(); + } + } +} + +void CGUIMediaWindow::OnFilterItems(const std::string &filter) +{ + m_viewControl.Clear(); + + CFileItemList items; + items.Copy(*m_vecItems, false); // use the original path - it'll likely be relied on for other things later. + items.Append(*m_unfilteredItems); + bool filtered = GetFilteredItems(filter, items); + + m_vecItems->ClearItems(); + // we need to clear the sort state and re-sort the items + m_vecItems->ClearSortState(); + m_vecItems->Append(items); + + // if the filter has changed, get the new filter path + if (filtered && m_canFilterAdvanced) + { + if (items.HasProperty(PROPERTY_PATH_DB)) + m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString(); + // only set m_strFilterPath if it hasn't been set before + // otherwise we might overwrite it with a non-filter path + // in case GetFilteredItems() returns true even though no + // db-based filter (e.g. watched filter) has been applied + else if (m_strFilterPath.empty()) + m_strFilterPath = items.GetPath(); + } + + GetGroupedItems(*m_vecItems); + FormatAndSort(*m_vecItems); + + CFileItemPtr currentItem; + std::string currentItemPath; + int item = m_viewControl.GetSelectedItem(); + if (item >= 0 && item < m_vecItems->Size()) + { + currentItem = m_vecItems->Get(item); + currentItemPath = currentItem->GetPath(); + } + + // get the "filter" option + std::string filterOption; + CURL filterUrl(m_strFilterPath); + if (filterUrl.HasOption("filter")) + filterOption = filterUrl.GetOption("filter"); + + // apply the "filter" option to any folder item so that + // the filter can be passed down to the sub-directory + for (int index = 0; index < m_vecItems->Size(); index++) + { + CFileItemPtr pItem = m_vecItems->Get(index); + // if the item is a folder we need to copy the path of + // the filtered item to be able to keep the applied filters + if (pItem->m_bIsFolder) + { + CURL itemUrl(pItem->GetPath()); + if (!filterOption.empty()) + itemUrl.SetOption("filter", filterOption); + else + itemUrl.RemoveOption("filter"); + pItem->SetPath(itemUrl.Get()); + } + } + + SetProperty("filter", filter); + if (filtered && m_canFilterAdvanced) + { + // to be able to select the same item as before we need to adjust + // the path of the item i.e. add or remove the "filter=" URL option + // but that's only necessary for folder items + if (currentItem.get() && currentItem->m_bIsFolder) + { + CURL curUrl(currentItemPath), newUrl(m_strFilterPath); + if (newUrl.HasOption("filter")) + curUrl.SetOption("filter", newUrl.GetOption("filter")); + else if (curUrl.HasOption("filter")) + curUrl.RemoveOption("filter"); + + currentItemPath = curUrl.Get(); + } + } + + // The idea here is to ensure we have something to focus if our file list + // is empty. As such, this check MUST be last and ignore the hide parent + // fileitems settings. + if (m_vecItems->IsEmpty()) + { + CFileItemPtr pItem(new CFileItem("..")); + pItem->SetPath(m_history.GetParentPath()); + pItem->m_bIsFolder = true; + pItem->m_bIsShareOrDrive = false; + m_vecItems->AddFront(pItem, 0); + } + + // and update our view control + buttons + m_viewControl.SetItems(*m_vecItems); + m_viewControl.SetSelectedItem(currentItemPath); +} + +bool CGUIMediaWindow::GetFilteredItems(const std::string &filter, CFileItemList &items) +{ + bool result = false; + if (m_canFilterAdvanced) + result = GetAdvanceFilteredItems(items); + + std::string trimmedFilter(filter); + StringUtils::TrimLeft(trimmedFilter); + StringUtils::ToLower(trimmedFilter); + + if (trimmedFilter.empty()) + return result; + + CFileItemList filteredItems(items.GetPath()); // use the original path - it'll likely be relied on for other things later. + bool numericMatch = StringUtils::IsNaturalNumber(trimmedFilter); + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items.Get(i); + if (item->IsParentFolder()) + { + filteredItems.Add(item); + continue; + } + //! @todo Need to update this to get all labels, ideally out of the displayed info (ie from m_layout and m_focusedLayout) + //! though that isn't practical. Perhaps a better idea would be to just grab the info that we should filter on based on + //! where we are in the library tree. + //! Another idea is tying the filter string to the current level of the tree, so that going deeper disables the filter, + //! but it's re-enabled on the way back out. + std::string match; + /* if (item->GetFocusedLayout()) + match = item->GetFocusedLayout()->GetAllText(); + else if (item->GetLayout()) + match = item->GetLayout()->GetAllText(); + else*/ + match = item->GetLabel(); // Filter label only for now + + if (numericMatch) + StringUtils::WordToDigits(match); + + size_t pos = StringUtils::FindWords(match.c_str(), trimmedFilter.c_str()); + if (pos != std::string::npos) + filteredItems.Add(item); + } + + items.ClearItems(); + items.Append(filteredItems); + + return items.GetObjectCount() > 0; +} + +bool CGUIMediaWindow::GetAdvanceFilteredItems(CFileItemList &items) +{ + // don't run the advanced filter if the filter is empty + // and there hasn't been a filter applied before which + // would have to be removed + CURL url(m_strFilterPath); + if (m_filter.IsEmpty() && !url.HasOption("filter")) + return false; + + CFileItemList resultItems; + XFILE::CSmartPlaylistDirectory::GetDirectory(m_filter, resultItems, m_strFilterPath, true); + + // put together a lookup map for faster path comparison + std::map<std::string, CFileItemPtr> lookup; + for (int j = 0; j < resultItems.Size(); j++) + { + std::string itemPath = CURL(resultItems[j]->GetPath()).GetWithoutOptions(); + StringUtils::ToLower(itemPath); + + lookup[itemPath] = resultItems[j]; + } + + // loop through all the original items and find + // those which are still part of the filter + CFileItemList filteredItems; + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items.Get(i); + if (item->IsParentFolder()) + { + filteredItems.Add(item); + continue; + } + + // check if the item is part of the resultItems list + // by comparing their paths (but ignoring any special + // options because they differ from filter to filter) + std::string path = CURL(item->GetPath()).GetWithoutOptions(); + StringUtils::ToLower(path); + + std::map<std::string, CFileItemPtr>::iterator itItem = lookup.find(path); + if (itItem != lookup.end()) + { + // add the item to the list of filtered items + filteredItems.Add(item); + + // remove the item from the lists + resultItems.Remove(itItem->second.get()); + lookup.erase(itItem); + } + } + + if (resultItems.Size() > 0) + CLog::Log(LOGWARNING, "CGUIMediaWindow::GetAdvanceFilteredItems(): {} unknown items", + resultItems.Size()); + + items.ClearItems(); + items.Append(filteredItems); + items.SetPath(resultItems.GetPath()); + if (resultItems.HasProperty(PROPERTY_PATH_DB)) + items.SetProperty(PROPERTY_PATH_DB, resultItems.GetProperty(PROPERTY_PATH_DB)); + return true; +} + +bool CGUIMediaWindow::IsFiltered() +{ + return (!m_canFilterAdvanced && !GetProperty("filter").empty()) || + (m_canFilterAdvanced && !m_filter.IsEmpty()); +} + +bool CGUIMediaWindow::IsSameStartFolder(const std::string &dir) +{ + const std::string startFolder = GetStartFolder(dir); + return URIUtils::PathHasParent(m_vecItems->GetPath(), startFolder); +} + +bool CGUIMediaWindow::Filter(bool advanced /* = true */) +{ + // basic filtering + if (!m_canFilterAdvanced || !advanced) + { + const CGUIControl *btnFilter = GetControl(CONTROL_BTN_FILTER); + if (btnFilter && btnFilter->GetControlType() == CGUIControl::GUICONTROL_EDIT) + { // filter updated + CGUIMessage selected(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTN_FILTER); + OnMessage(selected); + OnFilterItems(selected.GetLabel()); + UpdateButtons(); + return true; + } + if (GetProperty("filter").empty()) + { + std::string filter = GetProperty("filter").asString(); + CGUIKeyboardFactory::ShowAndGetFilter(filter, false); + SetProperty("filter", filter); + } + else + { + OnFilterItems(""); + UpdateButtons(); + } + } + // advanced filtering + else + CGUIDialogMediaFilter::ShowAndEditMediaFilter(m_strFilterPath, m_filter); + + return true; +} + +std::string CGUIMediaWindow::GetStartFolder(const std::string &dir) +{ + if (StringUtils::EqualsNoCase(dir, "$root") || + StringUtils::EqualsNoCase(dir, "root")) + return ""; + + // Let plugins handle their own urls themselves + if (StringUtils::StartsWith(dir, "plugin://")) + return dir; + +//! @todo This ifdef block probably belongs somewhere else. Move it to a better place! +#if defined(TARGET_ANDROID) + // Hack for Android items (numbered id's) on the leanback screen + std::string path; + std::string fileName; + URIUtils::Split(dir, path, fileName); + URIUtils::RemoveExtension(fileName); + if (StringUtils::IsInteger(fileName)) + return path; +#endif + + return dir; +} + +std::string CGUIMediaWindow::RemoveParameterFromPath(const std::string &strDirectory, const std::string &strParameter) +{ + CURL url(strDirectory); + if (url.HasOption(strParameter)) + { + url.RemoveOption(strParameter); + return url.Get(); + } + + return strDirectory; +} + +bool CGUIMediaWindow::ProcessRenderLoop(bool renderOnly) +{ + return CServiceBroker::GetGUI()->GetWindowManager().ProcessRenderLoop(renderOnly); +} + +bool CGUIMediaWindow::GetDirectoryItems(CURL &url, CFileItemList &items, bool useDir) +{ + if (m_backgroundLoad) + { + bool ret = true; + CGetDirectoryItems getItems(m_rootDir, url, items, useDir); + + if (!WaitGetDirectoryItems(getItems)) + { + // cancelled + ret = false; + } + else if (!getItems.m_result) + { + if (CServiceBroker::GetAppMessenger()->IsProcessThread() && m_rootDir.GetDirImpl() && + !m_rootDir.GetDirImpl()->ProcessRequirements()) + { + ret = false; + } + else if (!WaitGetDirectoryItems(getItems) || !getItems.m_result) + { + ret = false; + } + } + + m_updateJobActive = false; + m_rootDir.ReleaseDirImpl(); + return ret; + } + else + { + return m_rootDir.GetDirectory(url, items, useDir, false); + } +} + +bool CGUIMediaWindow::WaitGetDirectoryItems(CGetDirectoryItems &items) +{ + bool ret = true; + CGUIDialogBusy* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogBusy>(WINDOW_DIALOG_BUSY); + if (dialog && !dialog->IsDialogRunning()) + { + if (!CGUIDialogBusy::Wait(&items, 100, true)) + { + // cancelled + ret = false; + } + } + else + { + m_updateJobActive = true; + m_updateAborted = false; + m_updateEvent.Reset(); + CServiceBroker::GetJobManager()->Submit( + [&]() { + items.Run(); + m_updateEvent.Set(); + }, + nullptr, CJob::PRIORITY_NORMAL); + + // Loop until either the job ended or update canceled via CGUIMediaWindow::CancelUpdateItems. + while (!m_updateAborted && !m_updateEvent.Wait(1ms)) + { + if (!ProcessRenderLoop(false)) + break; + } + + if (m_updateAborted) + { + CLog::LogF(LOGDEBUG, "Get directory items job was canceled."); + ret = false; + } + else if (!items.m_result) + { + CLog::LogF(LOGDEBUG, "Get directory items job was unsuccessful."); + ret = false; + } + } + return ret; +} + +void CGUIMediaWindow::CancelUpdateItems() +{ + if (m_updateJobActive) + { + m_rootDir.CancelDirectory(); + m_updateAborted = true; + if (!m_updateEvent.Wait(5000ms)) + { + CLog::Log(LOGERROR, "CGUIMediaWindow::CancelUpdateItems - error cancel update"); + } + m_updateJobActive = false; + } +} diff --git a/xbmc/windows/GUIMediaWindow.h b/xbmc/windows/GUIMediaWindow.h new file mode 100644 index 0000000..1fc1dfc --- /dev/null +++ b/xbmc/windows/GUIMediaWindow.h @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "dialogs/GUIDialogContextMenu.h" +#include "filesystem/DirectoryHistory.h" +#include "filesystem/VirtualDirectory.h" +#include "guilib/GUIWindow.h" +#include "playlists/SmartPlayList.h" +#include "view/GUIViewControl.h" + +#include <atomic> + +class CFileItemList; +class CGUIViewState; +namespace +{ +class CGetDirectoryItems; +} + +// base class for all media windows +class CGUIMediaWindow : public CGUIWindow +{ +public: + CGUIMediaWindow(int id, const char *xmlFile); + ~CGUIMediaWindow(void) override; + + // specializations of CGUIControl + bool OnAction(const CAction &action) override; + bool OnBack(int actionID) override; + bool OnMessage(CGUIMessage& message) override; + + // specializations of CGUIWindow + void OnWindowLoaded() override; + void OnWindowUnload() override; + void OnInitWindow() override; + bool IsMediaWindow() const override { return true; } + int GetViewContainerID() const override { return m_viewControl.GetCurrentControl(); } + int GetViewCount() const override { return m_viewControl.GetViewModeCount(); } + bool HasListItems() const override { return true; } + CFileItemPtr GetCurrentListItem(int offset = 0) override; + + // custom methods + virtual bool CanFilterAdvanced() { return m_canFilterAdvanced; } + virtual bool IsFiltered(); + virtual bool IsSameStartFolder(const std::string &dir); + + virtual std::string GetRootPath() const { return ""; } + + const CFileItemList &CurrentDirectory() const; + const CGUIViewState *GetViewState() const; + virtual bool UseFileDirectories() { return true; } + +protected: + // specializations of CGUIControlGroup + CGUIControl *GetFirstFocusableControl(int id) override; + + bool Load(TiXmlElement *pRootElement) override; + + // custom methods + virtual void SetupShares(); + virtual bool GoParentFolder(); + virtual bool OnClick(int iItem, const std::string &player = ""); + + /* \brief React to a "Select" action on an item in a view. + \param item selected item. + \return true if the action is handled, false otherwise. + */ + virtual bool OnSelect(int item); + virtual bool OnPopupMenu(int iItem); + + virtual void GetContextButtons(int itemNumber, CContextButtons &buttons); + virtual bool OnContextButton(int itemNumber, CONTEXT_BUTTON button); + virtual bool OnAddMediaSource() { return false; } + + virtual void FormatItemLabels(CFileItemList &items, const LABEL_MASKS &labelMasks); + virtual void UpdateButtons(); + void SaveControlStates() override; + void RestoreControlStates() override; + + virtual bool GetDirectory(const std::string &strDirectory, CFileItemList &items); + /*! \brief Retrieves the items from the given path and updates the list + \param strDirectory The path to the directory to get the items from + \param updateFilterPath Whether to update the filter path in m_strFilterPath or not + \return true if the list was successfully updated otherwise false + \sa GetDirectory + \sa m_vecItems + \sa m_strFilterPath + */ + virtual bool Update(const std::string &strDirectory, bool updateFilterPath = true); + /*! \brief Refreshes the current list by retrieving the lists's path + \return true if the list was successfully refreshed otherwise false + \sa Update + \sa GetDirectory + */ + virtual bool Refresh(bool clearCache = false); + + virtual void FormatAndSort(CFileItemList &items); + virtual void OnPrepareFileItems(CFileItemList &items); + virtual void OnCacheFileItems(CFileItemList &items); + virtual void GetGroupedItems(CFileItemList &items) { } + + void ClearFileItems(); + virtual void SortItems(CFileItemList &items); + + /*! \brief Check if the given list can be advance filtered or not + \param items List of items to check + \return true if the list can be advance filtered otherwise false + */ + virtual bool CheckFilterAdvanced(CFileItemList &items) const { return false; } + /*! \brief Check if the given path can contain a "filter" parameter + \param strDirectory Path to check + \return true if the given path can contain a "filter" parameter otherwise false + */ + virtual bool CanContainFilter(const std::string &strDirectory) const { return false; } + virtual void UpdateFilterPath(const std::string &strDirector, const CFileItemList &items, bool updateFilterPath); + virtual bool Filter(bool advanced = true); + + /* \brief Called on response to a GUI_MSG_FILTER_ITEMS message + Filters the current list with the given filter using FilterItems() + \param filter the filter to use. + \sa FilterItems + */ + void OnFilterItems(const std::string &filter); + + /* \brief Retrieve the filtered item list + \param filter filter to apply + \param items CFileItemList to filter + \sa OnFilterItems + */ + virtual bool GetFilteredItems(const std::string &filter, CFileItemList &items); + + /* \brief Retrieve the advance filtered item list + \param items CFileItemList to filter + \param hasNewItems Whether the filtered item list contains new items + which were not present in the original list + \sa GetFilteredItems + */ + virtual bool GetAdvanceFilteredItems(CFileItemList &items); + + // check for a disc or connection + virtual bool HaveDiscOrConnection(const std::string& strPath, int iDriveType); + void ShowShareErrorMessage(CFileItem* pItem) const; + + void SaveSelectedItemInHistory(); + void RestoreSelectedItemFromHistory(); + void GetDirectoryHistoryString(const CFileItem* pItem, std::string& strHistoryString) const; + void SetHistoryForPath(const std::string& strDirectory); + virtual void LoadPlayList(const std::string& strFileName) {} + virtual bool OnPlayMedia(int iItem, const std::string &player = ""); + virtual bool OnPlayAndQueueMedia(const CFileItemPtr& item, const std::string& player = ""); + void UpdateFileList(); + virtual void OnDeleteItem(int iItem); + void OnRenameItem(int iItem); + bool WaitForNetwork() const; + bool GetDirectoryItems(CURL &url, CFileItemList &items, bool useDir); + bool WaitGetDirectoryItems(CGetDirectoryItems &items); + void CancelUpdateItems(); + + /*! \brief Translate the folder to start in from the given quick path + \param url the folder the user wants + \return the resulting path */ + virtual std::string GetStartFolder(const std::string &url); + + /*! \brief Utility method to remove the given parameter from a path/URL + \param strDirectory Path/URL from which to remove the given parameter + \param strParameter Parameter to remove from the given path/URL + \return Path/URL without the given parameter + */ + static std::string RemoveParameterFromPath(const std::string &strDirectory, const std::string &strParameter); + + bool ProcessRenderLoop(bool renderOnly); + + XFILE::CVirtualDirectory m_rootDir; + CGUIViewControl m_viewControl; + + // current path and history + CFileItemList* m_vecItems; + CFileItemList* m_unfilteredItems; ///< \brief items prior to filtering using FilterItems() + CDirectoryHistory m_history; + std::unique_ptr<CGUIViewState> m_guiState; + std::atomic_bool m_vecItemsUpdating = {false}; + class CUpdateGuard + { + public: + CUpdateGuard(std::atomic_bool &update) : m_update(update) + { + m_update = true; + } + ~CUpdateGuard() + { + m_update = false; + } + protected: + std::atomic_bool &m_update; + }; + CEvent m_updateEvent; + std::atomic_bool m_updateAborted = {false}; + std::atomic_bool m_updateJobActive = {false}; + + // save control state on window exit + int m_iLastControl; + std::string m_startDirectory; + + CSmartPlaylist m_filter; + bool m_canFilterAdvanced; + /*! \brief Contains the path used for filtering (including any active filter) + + When Update() is called with a path to e.g. a smartplaylist or + a library node filter, that "original" path will be stored in + m_vecItems->m_strPath. But the path used by XBMC to retrieve + those items from the database (Videodb:// or musicdb://) + is stored in this member variable to still have access to it + because it is used for filtering by appending the currently active + filter as a "filter" parameter to the filter path/URL. + + \sa Update + */ + std::string m_strFilterPath; + bool m_backgroundLoad = false; +}; diff --git a/xbmc/windows/GUIWindowDebugInfo.cpp b/xbmc/windows/GUIWindowDebugInfo.cpp new file mode 100644 index 0000000..764e6b0 --- /dev/null +++ b/xbmc/windows/GUIWindowDebugInfo.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowDebugInfo.h" + +#include "CompileInfo.h" +#include "GUIInfoManager.h" +#include "ServiceBroker.h" +#include "addons/Skin.h" +#include "filesystem/SpecialProtocol.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIControlFactory.h" +#include "guilib/GUIControlProfiler.h" +#include "guilib/GUIFontManager.h" +#include "guilib/GUITextLayout.h" +#include "guilib/GUIWindowManager.h" +#include "input/WindowTranslator.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/CPUInfo.h" +#include "utils/MemUtils.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <inttypes.h> + +CGUIWindowDebugInfo::CGUIWindowDebugInfo(void) + : CGUIDialog(WINDOW_DEBUG_INFO, "", DialogModalityType::MODELESS) +{ + m_needsScaling = false; + m_layout = nullptr; + m_renderOrder = RENDER_ORDER_WINDOW_DEBUG; +} + +CGUIWindowDebugInfo::~CGUIWindowDebugInfo(void) = default; + +void CGUIWindowDebugInfo::UpdateVisibility() +{ + if (LOG_LEVEL_DEBUG_FREEMEM <= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevel || g_SkinInfo->IsDebugging()) + Open(); + else + Close(); +} + +bool CGUIWindowDebugInfo::OnMessage(CGUIMessage &message) +{ + if (message.GetMessage() == GUI_MSG_WINDOW_DEINIT) + { + delete m_layout; + m_layout = nullptr; + } + else if (message.GetMessage() == GUI_MSG_REFRESH_TIMER) + MarkDirtyRegion(); + + return CGUIDialog::OnMessage(message); +} + +void CGUIWindowDebugInfo::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) +{ + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(), false); + + CServiceBroker::GetCPUInfo()->GetUsedPercentage(); // must call it to recalculate pct values + + static int yShift = 20; + static int xShift = 40; + static unsigned int lastShift = time(nullptr); + time_t now = time(nullptr); + if (now - lastShift > 10) + { + yShift *= -1; + if (now % 5 == 0) + xShift *= -1; + lastShift = now; + MarkDirtyRegion(); + } + + if (!m_layout) + { + CGUIFont *font13 = g_fontManager.GetDefaultFont(); + CGUIFont *font13border = g_fontManager.GetDefaultFont(true); + if (font13) + m_layout = new CGUITextLayout(font13, true, 0, font13border); + } + if (!m_layout) + return; + + std::string info; + if (LOG_LEVEL_DEBUG_FREEMEM <= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevel) + { + KODI::MEMORY::MemoryStatus stat; + KODI::MEMORY::GetMemoryStatus(&stat); + std::string profiling = CGUIControlProfiler::IsRunning() ? " (profiling)" : ""; + std::string strCores; + if (CServiceBroker::GetCPUInfo()->SupportsCPUUsage()) + strCores = CServiceBroker::GetCPUInfo()->GetCoresUsageString(); + else + strCores = "N/A"; + std::string lcAppName = CCompileInfo::GetAppName(); + StringUtils::ToLower(lcAppName); +#if !defined(TARGET_POSIX) + info = StringUtils::Format("LOG: {}{}.log\nMEM: {}/{} KB - FPS: {:2.1f} fps\nCPU: {}{}", + CSpecialProtocol::TranslatePath("special://logpath"), lcAppName, + stat.availPhys / 1024, stat.totalPhys / 1024, + CServiceBroker::GetGUI() + ->GetInfoManager() + .GetInfoProviders() + .GetSystemInfoProvider() + .GetFPS(), + strCores, profiling); +#else + double dCPU = m_resourceCounter.GetCPUUsage(); + std::string ucAppName = lcAppName; + StringUtils::ToUpper(ucAppName); + info = StringUtils::Format("LOG: {}{}.log\n" + "MEM: {}/{} KB - FPS: {:2.1f} fps\n" + "CPU: {} (CPU-{} {:4.2f}%{})", + CSpecialProtocol::TranslatePath("special://logpath"), lcAppName, + stat.availPhys / 1024, stat.totalPhys / 1024, + CServiceBroker::GetGUI() + ->GetInfoManager() + .GetInfoProviders() + .GetSystemInfoProvider() + .GetFPS(), + strCores, ucAppName, dCPU, profiling); +#endif + } + + // render the skin debug info + if (g_SkinInfo->IsDebugging()) + { + if (!info.empty()) + info += "\n"; + CGUIWindow *window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()); + CGUIWindow *pointer = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_POINTER); + CPoint point; + if (pointer) + point = CPoint(pointer->GetXPosition(), pointer->GetYPosition()); + if (window) + { + std::string windowName = CWindowTranslator::TranslateWindow(window->GetID()); + if (!windowName.empty()) + windowName += " (" + window->GetProperty("xmlfile").asString() + ")"; + else + windowName = window->GetProperty("xmlfile").asString(); + info += "Window: " + windowName + "\n"; + // transform the mouse coordinates to this window's coordinates + CServiceBroker::GetWinSystem()->GetGfxContext().SetScalingResolution(window->GetCoordsRes(), true); + point.x *= CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleX(); + point.y *= CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIScaleY(); + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(), false); + } + info += StringUtils::Format("Mouse: ({},{}) ", static_cast<int>(point.x), + static_cast<int>(point.y)); + if (window) + { + CGUIControl *control = window->GetFocusedControl(); + if (control) + info += StringUtils::Format( + "Focused: {} ({})", control->GetID(), + CGUIControlFactory::TranslateControlType(control->GetControlType())); + } + } + + float w, h; + if (m_layout->Update(info)) + MarkDirtyRegion(); + m_layout->GetTextExtent(w, h); + + float x = xShift + 0.04f * CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(); + float y = yShift + 0.04f * CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(); + m_renderRegion.SetRect(x, y, x+w, y+h); +} + +void CGUIWindowDebugInfo::Render() +{ + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(), false); + if (m_layout) + m_layout->RenderOutline(m_renderRegion.x1, m_renderRegion.y1, 0xffffffff, 0xff000000, 0, 0); +} diff --git a/xbmc/windows/GUIWindowDebugInfo.h b/xbmc/windows/GUIWindowDebugInfo.h new file mode 100644 index 0000000..b29405c --- /dev/null +++ b/xbmc/windows/GUIWindowDebugInfo.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#ifdef TARGET_POSIX +#include "platform/posix/PosixResourceCounter.h" +#endif + +class CGUITextLayout; + +class CGUIWindowDebugInfo : + public CGUIDialog +{ +public: + CGUIWindowDebugInfo(); + ~CGUIWindowDebugInfo() override; + void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override; + void Render() override; + bool OnMessage(CGUIMessage &message) override; +protected: + void UpdateVisibility() override; +private: + CGUITextLayout *m_layout; +#ifdef TARGET_POSIX + CPosixResourceCounter m_resourceCounter; +#endif +}; diff --git a/xbmc/windows/GUIWindowFileManager.cpp b/xbmc/windows/GUIWindowFileManager.cpp new file mode 100644 index 0000000..4d3f399 --- /dev/null +++ b/xbmc/windows/GUIWindowFileManager.cpp @@ -0,0 +1,1331 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowFileManager.h" + +#include "Autorun.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "cores/playercorefactory/PlayerCoreFactory.h" +#include "dialogs/GUIDialogBusy.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "dialogs/GUIDialogMediaSource.h" +#include "dialogs/GUIDialogProgress.h" +#include "dialogs/GUIDialogTextViewer.h" +#include "dialogs/GUIDialogYesNo.h" +#include "favourites/FavouritesService.h" +#include "filesystem/Directory.h" +#include "filesystem/FileDirectoryFactory.h" +#include "filesystem/ZipManager.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/InputManager.h" +#include "interfaces/generic/ScriptInvocationManager.h" +#include "messaging/ApplicationMessenger.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "network/Network.h" +#include "pictures/GUIWindowSlideShow.h" +#include "platform/Filesystem.h" +#include "playlists/PlayList.h" +#include "playlists/PlayListFactory.h" +#include "settings/MediaSourceSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "threads/IRunnable.h" +#include "utils/FileOperationJob.h" +#include "utils/FileUtils.h" +#include "utils/JobManager.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +using namespace XFILE; +using namespace KODI::MESSAGING; + +#define CONTROL_BTNSELECTALL 1 +#define CONTROL_BTNFAVOURITES 2 +#define CONTROL_BTNPLAYWITH 3 +#define CONTROL_BTNRENAME 4 +#define CONTROL_BTNDELETE 5 +#define CONTROL_BTNCOPY 6 +#define CONTROL_BTNMOVE 7 +#define CONTROL_BTNNEWFOLDER 8 +#define CONTROL_BTNCALCSIZE 9 +#define CONTROL_BTNSWITCHMEDIA 11 +#define CONTROL_BTNCANCELJOB 12 +#define CONTROL_BTNVIEW 13 + + +#define CONTROL_NUMFILES_LEFT 12 +#define CONTROL_NUMFILES_RIGHT 13 + +#define CONTROL_LEFT_LIST 20 +#define CONTROL_RIGHT_LIST 21 + +#define CONTROL_CURRENTDIRLABEL_LEFT 101 +#define CONTROL_CURRENTDIRLABEL_RIGHT 102 + +namespace +{ +class CGetDirectoryItems : public IRunnable +{ +public: + CGetDirectoryItems(XFILE::CVirtualDirectory &dir, CURL &url, CFileItemList &items) + : m_result(false), m_dir(dir), m_url(url), m_items(items) + { + } + void Run() override + { + m_result = m_dir.GetDirectory(m_url, m_items, false, false); + } + void Cancel() override + { + m_dir.CancelDirectory(); + } + bool m_result; +protected: + XFILE::CVirtualDirectory &m_dir; + CURL m_url; + CFileItemList &m_items; +}; +} + +CGUIWindowFileManager::CGUIWindowFileManager(void) + : CGUIWindow(WINDOW_FILES, "FileManager.xml"), + CJobQueue(false,2) +{ + m_Directory[0] = new CFileItem; + m_Directory[1] = new CFileItem; + m_vecItems[0] = new CFileItemList; + m_vecItems[1] = new CFileItemList; + m_Directory[0]->SetPath("?"); + m_Directory[1]->SetPath("?"); + m_Directory[0]->m_bIsFolder = true; + m_Directory[1]->m_bIsFolder = true; + bCheckShareConnectivity = true; + m_loadType = KEEP_IN_MEMORY; +} + +CGUIWindowFileManager::~CGUIWindowFileManager(void) +{ + delete m_Directory[0]; + delete m_Directory[1]; + delete m_vecItems[0]; + delete m_vecItems[1]; +} + +bool CGUIWindowFileManager::OnAction(const CAction &action) +{ + int list = GetFocusedList(); + if (list >= 0 && list <= 1) + { + int item; + + // the non-contextual menu can be called at any time + if (action.GetID() == ACTION_CONTEXT_MENU && m_vecItems[list]->Size() == 0) + { + OnPopupMenu(list,-1, false); + return true; + } + if (action.GetID() == ACTION_DELETE_ITEM) + { + if (CanDelete(list)) + { + bool bDeselect = SelectItem(list, item); + OnDelete(list); + if (bDeselect) m_vecItems[list]->Get(item)->Select(false); + } + return true; + } + if (action.GetID() == ACTION_COPY_ITEM) + { + if (CanCopy(list)) + { + bool bDeselect = SelectItem(list, item); + OnCopy(list); + if (bDeselect) m_vecItems[list]->Get(item)->Select(false); + } + return true; + } + if (action.GetID() == ACTION_MOVE_ITEM) + { + if (CanMove(list)) + { + bool bDeselect = SelectItem(list, item); + OnMove(list); + if (bDeselect) m_vecItems[list]->Get(item)->Select(false); + } + return true; + } + if (action.GetID() == ACTION_RENAME_ITEM) + { + if (CanRename(list)) + { + bool bDeselect = SelectItem(list, item); + OnRename(list); + if (bDeselect) m_vecItems[list]->Get(item)->Select(false); + } + return true; + } + if (action.GetID() == ACTION_PARENT_DIR) + { + GoParentFolder(list); + return true; + } + if (action.GetID() == ACTION_PLAYER_PLAY) + { +#ifdef HAS_DVD_DRIVE + if (m_vecItems[list]->Get(GetSelectedItem(list))->IsDVD()) + return MEDIA_DETECT::CAutorun::PlayDiscAskResume(m_vecItems[list]->Get(GetSelectedItem(list))->GetPath()); +#endif + } + } + return CGUIWindow::OnAction(action); +} + +bool CGUIWindowFileManager::OnBack(int actionID) +{ + int list = GetFocusedList(); + if (list >= 0 && list <= 1 && actionID == ACTION_NAV_BACK && !m_vecItems[list]->IsVirtualDirectoryRoot()) + { + GoParentFolder(list); + return true; + } + return CGUIWindow::OnBack(actionID); +} + +bool CGUIWindowFileManager::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_NOTIFY_ALL: + { // Message is received even if window is inactive + if (message.GetParam1() == GUI_MSG_WINDOW_RESET) + { + m_Directory[0]->SetPath("?"); + m_Directory[1]->SetPath("?"); + m_Directory[0]->m_bIsFolder = true; + m_Directory[1]->m_bIsFolder = true; + return true; + } + + // handle removable media + if (message.GetParam1() == GUI_MSG_REMOVED_MEDIA) + { + for (int i = 0; i < 2; i++) + { + if (m_Directory[i]->IsVirtualDirectoryRoot() && IsActive()) + { + int iItem = GetSelectedItem(i); + Update(i, m_Directory[i]->GetPath()); + CONTROL_SELECT_ITEM(CONTROL_LEFT_LIST + i, iItem); + } + else if (m_Directory[i]->IsRemovable() && !m_rootDir.IsInSource(m_Directory[i]->GetPath())) + { // + if (IsActive()) + Update(i, ""); + else + m_Directory[i]->SetPath(""); + } + } + return true; + } + else if (message.GetParam1()==GUI_MSG_UPDATE_SOURCES) + { // State of the sources changed, so update our view + for (int i = 0; i < 2; i++) + { + if (m_Directory[i]->IsVirtualDirectoryRoot() && IsActive()) + { + int iItem = GetSelectedItem(i); + Update(i, m_Directory[i]->GetPath()); + CONTROL_SELECT_ITEM(CONTROL_LEFT_LIST + i, iItem); + } + } + return true; + } + else if (message.GetParam1()==GUI_MSG_UPDATE && IsActive()) + { + Refresh(); + return true; + } + } + break; + case GUI_MSG_PLAYBACK_STARTED: + case GUI_MSG_PLAYBACK_ENDED: + case GUI_MSG_PLAYBACK_STOPPED: + case GUI_MSG_PLAYLIST_CHANGED: + case GUI_MSG_PLAYLISTPLAYER_STOPPED: + case GUI_MSG_PLAYLISTPLAYER_STARTED: + case GUI_MSG_PLAYLISTPLAYER_CHANGED: + { // send a notify all to all controls on this window + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_REFRESH_LIST); + OnMessage(msg); + break; + } + case GUI_MSG_WINDOW_DEINIT: + { + CGUIWindow::OnMessage(message); + ClearFileItems(0); + ClearFileItems(1); + return true; + } + break; + + case GUI_MSG_WINDOW_INIT: + { + SetInitialPath(message.GetStringParam()); + message.SetStringParam(""); + + return CGUIWindow::OnMessage(message); + } + break; + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + + if (iControl == CONTROL_LEFT_LIST || iControl == CONTROL_RIGHT_LIST) // list/thumb control + { + // get selected item + int list = iControl - CONTROL_LEFT_LIST; + int iItem = GetSelectedItem(list); + int iAction = message.GetParam1(); + + // iItem is checked for validity inside these routines + if (iAction == ACTION_HIGHLIGHT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) + { + OnMark(list, iItem); + if (!CServiceBroker::GetInputManager().IsMouseActive()) + { + //move to next item + CGUIMessage msg(GUI_MSG_ITEM_SELECT, GetID(), iControl, iItem + 1); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } + } + else if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_DOUBLE_CLICK) + { + OnClick(list, iItem); + } + else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) + { + OnPopupMenu(list, iItem); + } + } + } + break; + // prevent touch/gesture unfocussing .. + case GUI_MSG_GESTURE_NOTIFY: + case GUI_MSG_UNFOCUS_ALL: + return true; + } + return CGUIWindow::OnMessage(message); +} + +void CGUIWindowFileManager::OnSort(int iList) +{ + using namespace KODI::PLATFORM::FILESYSTEM; + // always sort the list by label in ascending order + for (int i = 0; i < m_vecItems[iList]->Size(); i++) + { + CFileItemPtr pItem = m_vecItems[iList]->Get(i); + if (pItem->m_bIsFolder && (!pItem->m_dwSize || pItem->IsPath("add"))) + pItem->SetLabel2(""); + else + pItem->SetFileSizeLabel(); + + // Set free space on disc + if (pItem->m_bIsShareOrDrive) + { + if (pItem->IsHD()) + { + std::error_code ec; + auto freeSpace = space(pItem->GetPath(), ec); + if (ec.value() == 0) + { + pItem->m_dwSize = freeSpace.free; + pItem->SetFileSizeLabel(); + } + } + else if (pItem->IsDVD() && CServiceBroker::GetMediaManager().IsDiscInDrive()) + { + std::error_code ec; + auto freeSpace = space(pItem->GetPath(), ec); + if (ec.value() == 0) + { + pItem->m_dwSize = freeSpace.capacity; + pItem->SetFileSizeLabel(); + } + } + } // if (pItem->m_bIsShareOrDrive) + + } + + m_vecItems[iList]->Sort(SortByLabel, SortOrderAscending); +} + +void CGUIWindowFileManager::ClearFileItems(int iList) +{ + CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), iList + CONTROL_LEFT_LIST); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + + m_vecItems[iList]->Clear(); // will clean up everything +} + +void CGUIWindowFileManager::UpdateButtons() +{ + // update our current directory labels + std::string strDir = CURL(m_Directory[0]->GetPath()).GetWithoutUserDetails(); + if (strDir.empty()) + { + SET_CONTROL_LABEL(CONTROL_CURRENTDIRLABEL_LEFT,g_localizeStrings.Get(20108)); + } + else + { + SET_CONTROL_LABEL(CONTROL_CURRENTDIRLABEL_LEFT, strDir); + } + strDir = CURL(m_Directory[1]->GetPath()).GetWithoutUserDetails(); + if (strDir.empty()) + { + SET_CONTROL_LABEL(CONTROL_CURRENTDIRLABEL_RIGHT,g_localizeStrings.Get(20108)); + } + else + { + SET_CONTROL_LABEL(CONTROL_CURRENTDIRLABEL_RIGHT, strDir); + } + + // update the number of items in each list + UpdateItemCounts(); +} + +void CGUIWindowFileManager::UpdateItemCounts() +{ + for (int i = 0; i < 2; i++) + { + unsigned int selectedCount = 0; + unsigned int totalCount = 0; + int64_t selectedSize = 0; + for (int j = 0; j < m_vecItems[i]->Size(); j++) + { + CFileItemPtr item = m_vecItems[i]->Get(j); + if (item->IsParentFolder()) continue; + if (item->IsSelected()) + { + selectedCount++; + selectedSize += item->m_dwSize; + } + totalCount++; + } + std::string items; + if (selectedCount > 0) + items = + StringUtils::Format("{}/{} {} ({})", selectedCount, totalCount, + g_localizeStrings.Get(127), StringUtils::SizeToString(selectedSize)); + else + items = StringUtils::Format("{} {}", totalCount, g_localizeStrings.Get(127)); + SET_CONTROL_LABEL(CONTROL_NUMFILES_LEFT + i, items); + } +} + +bool CGUIWindowFileManager::Update(int iList, const std::string &strDirectory) +{ + if (m_updating) + { + CLog::Log(LOGWARNING, "CGUIWindowFileManager::Update - updating in progress"); + return true; + } + CUpdateGuard ug(m_updating); + + // get selected item + int iItem = GetSelectedItem(iList); + std::string strSelectedItem = ""; + + if (iItem >= 0 && iItem < m_vecItems[iList]->Size()) + { + CFileItemPtr pItem = m_vecItems[iList]->Get(iItem); + if (!pItem->IsParentFolder()) + { + GetDirectoryHistoryString(pItem.get(), strSelectedItem); + m_history[iList].SetSelectedItem(strSelectedItem, m_Directory[iList]->GetPath()); + } + } + + std::string strOldDirectory=m_Directory[iList]->GetPath(); + m_Directory[iList]->SetPath(strDirectory); + + CFileItemList items; + if (!GetDirectory(iList, m_Directory[iList]->GetPath(), items)) + { + if (strDirectory != strOldDirectory && GetDirectory(iList, strOldDirectory, items)) + m_Directory[iList]->SetPath(strOldDirectory); // Fallback to old (previous) path) + else + Update(iList, ""); // Fallback to root + + return false; + } + + m_history[iList].SetSelectedItem(strSelectedItem, strOldDirectory); + + ClearFileItems(iList); + + m_vecItems[iList]->Append(items); + m_vecItems[iList]->SetPath(items.GetPath()); + + std::string strParentPath; + URIUtils::GetParentPath(strDirectory, strParentPath); + if (strDirectory.empty() && (m_vecItems[iList]->Size() == 0 || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWADDSOURCEBUTTONS))) + { // add 'add source button' + const std::string& strLabel = g_localizeStrings.Get(1026); + CFileItemPtr pItem(new CFileItem(strLabel)); + pItem->SetPath("add"); + pItem->SetArt("icon", "DefaultAddSource.png"); + pItem->SetLabel(strLabel); + pItem->SetLabelPreformatted(true); + pItem->m_bIsFolder = true; + pItem->SetSpecialSort(SortSpecialOnBottom); + m_vecItems[iList]->Add(pItem); + } + else if (items.IsEmpty() || CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWPARENTDIRITEMS)) + { + CFileItemPtr pItem(new CFileItem("..")); + pItem->SetPath(m_rootDir.IsSource(strDirectory) ? "" : strParentPath); + pItem->m_bIsFolder = true; + pItem->m_bIsShareOrDrive = false; + m_vecItems[iList]->AddFront(pItem, 0); + } + + m_strParentPath[iList] = (m_rootDir.IsSource(strDirectory) ? "" : strParentPath); + + if (strDirectory.empty()) + { + CFileItemPtr pItem(new CFileItem("special://profile/", true)); + pItem->SetLabel(g_localizeStrings.Get(20070)); + pItem->SetArt("thumb", "DefaultFolder.png"); + pItem->SetLabelPreformatted(true); + m_vecItems[iList]->Add(pItem); + + #ifdef TARGET_DARWIN_EMBEDDED + CFileItemPtr iItem(new CFileItem("special://envhome/Documents/Inbox", true)); + iItem->SetLabel("Inbox"); + iItem->SetArt("thumb", "DefaultFolder.png"); + iItem->SetLabelPreformatted(true); + m_vecItems[iList]->Add(iItem); + #endif + } + + // if we have a .tbn file, use itself as the thumb + for (int i = 0; i < m_vecItems[iList]->Size(); i++) + { + CFileItemPtr pItem = m_vecItems[iList]->Get(i); + if (pItem->IsHD() && + URIUtils::HasExtension(pItem->GetPath(), ".tbn")) + { + pItem->SetArt("thumb", pItem->GetPath()); + } + } + m_vecItems[iList]->FillInDefaultIcons(); + + OnSort(iList); + UpdateButtons(); + + int item = 0; + strSelectedItem = m_history[iList].GetSelectedItem(m_Directory[iList]->GetPath()); + for (int i = 0; i < m_vecItems[iList]->Size(); ++i) + { + CFileItemPtr pItem = m_vecItems[iList]->Get(i); + std::string strHistory; + GetDirectoryHistoryString(pItem.get(), strHistory); + if (strHistory == strSelectedItem) + { + item = i; + break; + } + } + UpdateControl(iList, item); + return true; +} + + +void CGUIWindowFileManager::OnClick(int iList, int iItem) +{ + if ( iList < 0 || iList >= 2) return ; + if ( iItem < 0 || iItem >= m_vecItems[iList]->Size() ) return ; + + CFileItemPtr pItem = m_vecItems[iList]->Get(iItem); + if (pItem->GetPath() == "add" && pItem->GetLabel() == g_localizeStrings.Get(1026)) // 'add source button' in empty root + { + if (CGUIDialogMediaSource::ShowAndAddMediaSource("files")) + { + m_rootDir.SetSources(*CMediaSourceSettings::GetInstance().GetSources("files")); + Update(0,m_Directory[0]->GetPath()); + Update(1,m_Directory[1]->GetPath()); + } + return; + } + + if (!pItem->m_bIsFolder && pItem->IsFileFolder(EFILEFOLDER_MASK_ALL)) + { + XFILE::IFileDirectory *pFileDirectory = NULL; + pFileDirectory = XFILE::CFileDirectoryFactory::Create(pItem->GetURL(), pItem.get(), ""); + if(pFileDirectory) + pItem->m_bIsFolder = true; + else if(pItem->m_bIsFolder) + pItem->m_bIsFolder = false; + delete pFileDirectory; + } + + if (pItem->m_bIsFolder) + { + // save path + drive type because of the possible refresh + std::string strPath = pItem->GetPath(); + int iDriveType = pItem->m_iDriveType; + if ( pItem->m_bIsShareOrDrive ) + { + if ( !g_passwordManager.IsItemUnlocked( pItem.get(), "files" ) ) + { + Refresh(); + return ; + } + + if ( !HaveDiscOrConnection( strPath, iDriveType ) ) + return ; + } + if (!Update(iList, strPath)) + ShowShareErrorMessage(pItem.get()); + } + else if (pItem->IsZIP() || pItem->IsCBZ()) // mount zip archive + { + CURL pathToUrl = URIUtils::CreateArchivePath("zip", pItem->GetURL(), ""); + Update(iList, pathToUrl.Get()); + } + else if (pItem->IsRAR() || pItem->IsCBR()) + { + CURL pathToUrl = URIUtils::CreateArchivePath("rar", pItem->GetURL(), ""); + Update(iList, pathToUrl.Get()); + } + else + { + OnStart(pItem.get(), ""); + return ; + } + // UpdateButtons(); +} + +//! @todo 2.0: Can this be removed, or should we run without the "special" file directories while +// in filemanager view. +void CGUIWindowFileManager::OnStart(CFileItem *pItem, const std::string &player) +{ + // start playlists from file manager + if (pItem->IsPlayList()) + { + const std::string& strPlayList = pItem->GetPath(); + std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(strPlayList)); + if (nullptr != pPlayList) + { + if (!pPlayList->Load(strPlayList)) + { + HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477}); + return; + } + } + g_application.ProcessAndStartPlaylist(strPlayList, *pPlayList, PLAYLIST::TYPE_MUSIC); + return; + } + if (pItem->IsAudio() || pItem->IsVideo()) + { + CServiceBroker::GetPlaylistPlayer().Play(std::make_shared<CFileItem>(*pItem), player); + return; + } + if (pItem->IsGame()) + { + g_application.PlayFile(*pItem, player); + return ; + } +#ifdef HAS_PYTHON + if (pItem->IsPythonScript()) + { + CScriptInvocationManager::GetInstance().ExecuteAsync(pItem->GetPath()); + return ; + } +#endif + if (pItem->IsPicture()) + { + CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (!pSlideShow) + return ; + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->IsPlayingVideo()) + g_application.StopPlaying(); + + pSlideShow->Reset(); + pSlideShow->Add(pItem); + pSlideShow->Select(pItem->GetPath()); + + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW); + return; + } + if (pItem->IsType(".txt") || pItem->IsType(".xml")) + CGUIDialogTextViewer::ShowForFile(pItem->GetPath(), true); +} + +bool CGUIWindowFileManager::HaveDiscOrConnection( std::string& strPath, int iDriveType ) +{ + if ( iDriveType == CMediaSource::SOURCE_TYPE_DVD ) + { + if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath)) + { + HELPERS::ShowOKDialogText(CVariant{218}, CVariant{219}); + int iList = GetFocusedList(); + int iItem = GetSelectedItem(iList); + Update(iList, ""); + CONTROL_SELECT_ITEM(iList + CONTROL_LEFT_LIST, iItem); + return false; + } + } + else if ( iDriveType == CMediaSource::SOURCE_TYPE_REMOTE ) + { + //! @todo Handle not connected to a remote share + if (!CServiceBroker::GetNetwork().IsConnected()) + { + HELPERS::ShowOKDialogText(CVariant{220}, CVariant{221}); + return false; + } + } + else + return true; + return true; +} + +void CGUIWindowFileManager::UpdateControl(int iList, int item) +{ + CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), iList + CONTROL_LEFT_LIST, item, 0, m_vecItems[iList]); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); +} + +void CGUIWindowFileManager::OnMark(int iList, int iItem) +{ + CFileItemPtr pItem = m_vecItems[iList]->Get(iItem); + + if (!pItem->m_bIsShareOrDrive) + { + if (!pItem->IsParentFolder()) + { + // MARK file + pItem->Select(!pItem->IsSelected()); + } + } + + UpdateItemCounts(); + // UpdateButtons(); +} + +void CGUIWindowFileManager::OnCopy(int iList) +{ + if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{120}, CVariant{123})) + return; + + AddJob(new CFileOperationJob(CFileOperationJob::ActionCopy, + *m_vecItems[iList], + m_Directory[1 - iList]->GetPath(), + true, 16201, 16202)); +} + +void CGUIWindowFileManager::OnMove(int iList) +{ + if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{121}, CVariant{124})) + return; + + AddJob(new CFileOperationJob(CFileOperationJob::ActionMove, + *m_vecItems[iList], + m_Directory[1 - iList]->GetPath(), + true, 16203, 16204)); +} + +void CGUIWindowFileManager::OnDelete(int iList) +{ + if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{122}, CVariant{125})) + return; + + AddJob(new CFileOperationJob(CFileOperationJob::ActionDelete, + *m_vecItems[iList], + m_Directory[iList]->GetPath(), + true, 16205, 16206)); +} + +void CGUIWindowFileManager::OnRename(int iList) +{ + std::string strFile; + for (int i = 0; i < m_vecItems[iList]->Size();++i) + { + CFileItemPtr pItem = m_vecItems[iList]->Get(i); + if (pItem->IsSelected()) + { + strFile = pItem->GetPath(); + break; + } + } + + CFileUtils::RenameFile(strFile); + + Refresh(iList); +} + +void CGUIWindowFileManager::OnSelectAll(int iList) +{ + for (int i = 0; i < m_vecItems[iList]->Size();++i) + { + CFileItemPtr pItem = m_vecItems[iList]->Get(i); + if (!pItem->IsParentFolder()) + { + pItem->Select(true); + } + } +} + +void CGUIWindowFileManager::OnNewFolder(int iList) +{ + std::string strNewFolder = ""; + if (CGUIKeyboardFactory::ShowAndGetInput(strNewFolder, CVariant{g_localizeStrings.Get(16014)}, false)) + { + std::string strNewPath = m_Directory[iList]->GetPath(); + URIUtils::AddSlashAtEnd(strNewPath); + strNewPath += strNewFolder; + CDirectory::Create(strNewPath); + Refresh(iList); + + // select the new folder + for (int i=0; i<m_vecItems[iList]->Size(); ++i) + { + CFileItemPtr pItem=m_vecItems[iList]->Get(i); + std::string strPath=pItem->GetPath(); + URIUtils::RemoveSlashAtEnd(strPath); + if (strPath==strNewPath) + { + CONTROL_SELECT_ITEM(iList + CONTROL_LEFT_LIST, i); + break; + } + } + } +} + +void CGUIWindowFileManager::Refresh(int iList) +{ + int nSel = GetSelectedItem(iList); + // update the list views + Update(iList, m_Directory[iList]->GetPath()); + + while (nSel > m_vecItems[iList]->Size()) + nSel--; + + CONTROL_SELECT_ITEM(iList + CONTROL_LEFT_LIST, nSel); +} + + +void CGUIWindowFileManager::Refresh() +{ + int iList = GetFocusedList(); + int nSel = GetSelectedItem(iList); + // update the list views + Update(0, m_Directory[0]->GetPath()); + Update(1, m_Directory[1]->GetPath()); + + while (nSel > m_vecItems[iList]->Size()) + nSel--; + + CONTROL_SELECT_ITEM(iList + CONTROL_LEFT_LIST, nSel); +} + +int CGUIWindowFileManager::GetSelectedItem(int iControl) +{ + if (iControl < 0 || iControl > 1 || m_vecItems[iControl]->IsEmpty()) + return -1; + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), iControl + CONTROL_LEFT_LIST); + if (OnMessage(msg)) + return msg.GetParam1(); + return -1; +} + +void CGUIWindowFileManager::GoParentFolder(int iList) +{ + CURL url(m_Directory[iList]->GetPath()); + if (url.IsProtocol("rar") || url.IsProtocol("zip")) + { + // check for step-below, if, unmount rar + if (url.GetFileName().empty()) + if (url.IsProtocol("zip")) + g_ZipManager.release(m_Directory[iList]->GetPath()); // release resources + } + + std::string strPath(m_strParentPath[iList]), strOldPath(m_Directory[iList]->GetPath()); + Update(iList, strPath); +} + +/// \brief Build a directory history string +/// \param pItem Item to build the history string from +/// \param strHistoryString History string build as return value +void CGUIWindowFileManager::GetDirectoryHistoryString(const CFileItem* pItem, std::string& strHistoryString) +{ + if (pItem->m_bIsShareOrDrive) + { + // We are in the virtual directory + + // History string of the DVD drive + // must be handled separately + if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_DVD) + { + // Remove disc label from item label + // and use as history string, m_strPath + // can change for new discs + std::string strLabel = pItem->GetLabel(); + size_t nPosOpen = strLabel.find('('); + size_t nPosClose = strLabel.rfind(')'); + if (nPosOpen != std::string::npos && + nPosClose != std::string::npos && + nPosClose > nPosOpen) + { + strLabel.erase(nPosOpen + 1, (nPosClose) - (nPosOpen + 1)); + strHistoryString = strLabel; + } + else + strHistoryString = strLabel; + } + else + { + // Other items in virtual directory + strHistoryString = pItem->GetLabel() + pItem->GetPath(); + URIUtils::RemoveSlashAtEnd(strHistoryString); + } + } + else + { + // Normal directory items + strHistoryString = pItem->GetPath(); + URIUtils::RemoveSlashAtEnd(strHistoryString); + } +} + +bool CGUIWindowFileManager::GetDirectory(int iList, const std::string &strDirectory, CFileItemList &items) +{ + CURL pathToUrl(strDirectory); + + CGetDirectoryItems getItems(m_rootDir, pathToUrl, items); + if (!CGUIDialogBusy::Wait(&getItems, 100, true)) + { + return false; + } + return getItems.m_result; +} + +bool CGUIWindowFileManager::CanRename(int iList) +{ + //! @todo Renaming of shares (requires writing to sources.xml) + //! this might be able to be done via the webserver code stuff... + if (m_Directory[iList]->IsVirtualDirectoryRoot()) return false; + if (m_Directory[iList]->IsReadOnly()) return false; + + return true; +} + +bool CGUIWindowFileManager::CanCopy(int iList) +{ + // can't copy if the destination is not writeable, or if the source is a share! + //! @todo Perhaps if the source is removeable media (DVD/CD etc.) we could + //! put ripping/backup in here. + if (!CUtil::SupportsReadFileOperations(m_Directory[iList]->GetPath())) return false; + if (m_Directory[iList]->IsVirtualDirectoryRoot()) return false; + if (m_Directory[1 - iList]->IsVirtualDirectoryRoot()) return false; + if (m_Directory[iList]->IsVirtualDirectoryRoot()) return false; + if (m_Directory[1 -iList]->IsReadOnly()) return false; + return true; +} + +bool CGUIWindowFileManager::CanMove(int iList) +{ + // can't move if the destination is not writeable, or if the source is a share or not writeable! + if (m_Directory[0]->IsVirtualDirectoryRoot() || m_Directory[0]->IsReadOnly()) return false; + if (m_Directory[1]->IsVirtualDirectoryRoot() || m_Directory[1]->IsReadOnly()) return false; + return true; +} + +bool CGUIWindowFileManager::CanDelete(int iList) +{ + if (m_Directory[iList]->IsVirtualDirectoryRoot()) return false; + if (m_Directory[iList]->IsReadOnly()) return false; + return true; +} + +bool CGUIWindowFileManager::CanNewFolder(int iList) +{ + if (m_Directory[iList]->IsVirtualDirectoryRoot()) return false; + if (m_Directory[iList]->IsReadOnly()) return false; + return true; +} + +int CGUIWindowFileManager::NumSelected(int iList) +{ + int iSelectedItems = 0; + for (int iItem = 0; iItem < m_vecItems[iList]->Size(); ++iItem) + { + if (m_vecItems[iList]->Get(iItem)->IsSelected()) iSelectedItems++; + } + return iSelectedItems; +} + +int CGUIWindowFileManager::GetFocusedList() const +{ + return GetFocusedControlID() - CONTROL_LEFT_LIST; +} + +void CGUIWindowFileManager::OnPopupMenu(int list, int item, bool bContextDriven /* = true */) +{ + if (list < 0 || list >= 2) return ; + bool bDeselect = SelectItem(list, item); + // calculate the position for our menu + float posX = 200; + float posY = 100; + const CGUIControl *pList = GetControl(CONTROL_LEFT_LIST + list); + if (pList) + { + posX = pList->GetXPosition() + pList->GetWidth() / 2; + posY = pList->GetYPosition() + pList->GetHeight() / 2; + } + + CFileItemPtr pItem = m_vecItems[list]->Get(item); + if (!pItem.get()) + return; + + if (m_Directory[list]->IsVirtualDirectoryRoot()) + { + if (item < 0) + { //! @todo We should add the option here for shares to be added if there aren't any + return ; + } + + // and do the popup menu + if (CGUIDialogContextMenu::SourcesMenu("files", pItem, posX, posY)) + { + m_rootDir.SetSources(*CMediaSourceSettings::GetInstance().GetSources("files")); + if (m_Directory[1 - list]->IsVirtualDirectoryRoot()) + Refresh(); + else + Refresh(list); + return ; + } + pItem->Select(false); + return ; + } + + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + // popup the context menu + + bool showEntry = false; + if (item >= m_vecItems[list]->Size()) item = -1; + if (item >= 0) + showEntry=(!pItem->IsParentFolder() || (pItem->IsParentFolder() && m_vecItems[list]->GetSelectedCount()>0)); + + // determine available players + std::vector<std::string>players; + playerCoreFactory.GetPlayers(*pItem, players); + + // add the needed buttons + CContextButtons choices; + if (item >= 0) + { + //The ".." item is not selectable. Take that into account when figuring out if all items are selected + int notSelectable = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWPARENTDIRITEMS) ? 1 : 0; + if (NumSelected(list) < m_vecItems[list]->Size() - notSelectable) + choices.Add(CONTROL_BTNSELECTALL, 188); // SelectAll + if (!pItem->IsParentFolder()) + choices.Add(CONTROL_BTNFAVOURITES, CServiceBroker::GetFavouritesService().IsFavourited(*pItem.get(), GetID()) ? 14077 : 14076); // Add/Remove Favourite + if (players.size() > 1) + choices.Add(CONTROL_BTNPLAYWITH, 15213); + if (CanRename(list) && !pItem->IsParentFolder()) + choices.Add(CONTROL_BTNRENAME, 118); + if (CanDelete(list) && showEntry) + choices.Add(CONTROL_BTNDELETE, 117); + if (CanCopy(list) && showEntry) + choices.Add(CONTROL_BTNCOPY, 115); + if (CanMove(list) && showEntry) + choices.Add(CONTROL_BTNMOVE, 116); + } + if (CanNewFolder(list)) + choices.Add(CONTROL_BTNNEWFOLDER, 20309); + if (item >= 0 && pItem->m_bIsFolder && !pItem->IsParentFolder()) + choices.Add(CONTROL_BTNCALCSIZE, 13393); + choices.Add(CONTROL_BTNSWITCHMEDIA, 523); + if (CServiceBroker::GetJobManager()->IsProcessing("filemanager")) + choices.Add(CONTROL_BTNCANCELJOB, 167); + + if (!pItem->m_bIsFolder) + choices.Add(CONTROL_BTNVIEW, 39104); + + int btnid = CGUIDialogContextMenu::ShowAndGetChoice(choices); + if (btnid == CONTROL_BTNSELECTALL) + { + OnSelectAll(list); + bDeselect=false; + } + if (btnid == CONTROL_BTNFAVOURITES) + { + CServiceBroker::GetFavouritesService().AddOrRemove(*pItem.get(), GetID()); + return; + } + if (btnid == CONTROL_BTNPLAYWITH) + { + std::vector<std::string>players; + playerCoreFactory.GetPlayers(*pItem, players); + std::string player = playerCoreFactory.SelectPlayerDialog(players); + if (!player.empty()) + OnStart(pItem.get(), player); + } + if (btnid == CONTROL_BTNRENAME) + OnRename(list); + if (btnid == CONTROL_BTNDELETE) + OnDelete(list); + if (btnid == CONTROL_BTNCOPY) + OnCopy(list); + if (btnid == CONTROL_BTNMOVE) + OnMove(list); + if (btnid == CONTROL_BTNNEWFOLDER) + OnNewFolder(list); + if (btnid == CONTROL_BTNCALCSIZE) + { + // setup the progress dialog, and show it + CGUIDialogProgress *progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + if (progress) + { + progress->SetHeading(CVariant{13394}); + for (int i=0; i < 3; i++) + progress->SetLine(i, CVariant{""}); + progress->Open(); + } + + // Calculate folder size for each selected item + for (int i=0; i<m_vecItems[list]->Size(); ++i) + { + CFileItemPtr pItem2=m_vecItems[list]->Get(i); + if (pItem2->m_bIsFolder && pItem2->IsSelected()) + { + int64_t folderSize = CalculateFolderSize(pItem2->GetPath(), progress); + if (folderSize >= 0) + { + pItem2->m_dwSize = folderSize; + if (folderSize == 0) + pItem2->SetLabel2(StringUtils::SizeToString(folderSize)); + else + pItem2->SetFileSizeLabel(); + } + } + } + if (progress) + progress->Close(); + } + if (btnid == CONTROL_BTNSWITCHMEDIA) + { + CGUIDialogContextMenu::SwitchMedia("files", m_vecItems[list]->GetPath()); + return; + } + if (btnid == CONTROL_BTNCANCELJOB) + CancelJobs(); + if (btnid == CONTROL_BTNVIEW) + CGUIDialogTextViewer::ShowForFile(pItem->GetPath(), true); + + if (bDeselect && item >= 0 && item < m_vecItems[list]->Size()) + { // deselect item as we didn't do anything + pItem->Select(false); + } +} + +// Highlights the item in the list under the cursor +// returns true if we should deselect the item, false otherwise +bool CGUIWindowFileManager::SelectItem(int list, int &item) +{ + // get the currently selected item in the list + item = GetSelectedItem(list); + + // select the item if we need to + if (item > -1 && !NumSelected(list) && !m_vecItems[list]->Get(item)->IsParentFolder()) + { + m_vecItems[list]->Get(item)->Select(true); + return true; + } + return false; +} + +// recursively calculates the selected folder size +int64_t CGUIWindowFileManager::CalculateFolderSize(const std::string &strDirectory, CGUIDialogProgress *pProgress) +{ + const CURL pathToUrl(strDirectory); + if (pProgress) + { // update our progress control + pProgress->Progress(); + pProgress->SetLine(1, strDirectory); + if (pProgress->IsCanceled()) + return -1; + } + // start by calculating the size of the files in this folder... + int64_t totalSize = 0; + CFileItemList items; + CVirtualDirectory rootDir; + rootDir.SetSources(*CMediaSourceSettings::GetInstance().GetSources("files")); + rootDir.GetDirectory(pathToUrl, items, false, false); + for (int i=0; i < items.Size(); i++) + { + if (items[i]->m_bIsFolder && !items[i]->IsParentFolder()) // folder + { + int64_t folderSize = CalculateFolderSize(items[i]->GetPath(), pProgress); + if (folderSize < 0) return -1; + totalSize += folderSize; + } + else // file + totalSize += items[i]->m_dwSize; + } + return totalSize; +} + +void CGUIWindowFileManager::OnJobComplete(unsigned int jobID, bool success, CJob *job) +{ + if(!success) + { + CFileOperationJob* fileJob = static_cast<CFileOperationJob*>(job); + HELPERS::ShowOKDialogLines(CVariant{fileJob->GetHeading()}, + CVariant{fileJob->GetLine()}, CVariant{16200}, CVariant{0}); + } + + if (IsActive()) + { + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_UPDATE); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, GetID(), false); + } + + CJobQueue::OnJobComplete(jobID, success, job); +} + +void CGUIWindowFileManager::ShowShareErrorMessage(CFileItem* pItem) +{ + int idMessageText = 0; + CURL url(pItem->GetPath()); + + if (url.IsProtocol("smb") && url.GetHostName().empty()) // smb workgroup + idMessageText = 15303; // Workgroup not found + else if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_REMOTE || URIUtils::IsRemote(pItem->GetPath())) + idMessageText = 15301; // Could not connect to network server + else + idMessageText = 15300; // Path not found or invalid + + HELPERS::ShowOKDialogText(CVariant{220}, CVariant{idMessageText}); +} + +void CGUIWindowFileManager::OnInitWindow() +{ + bool bResult0 = Update(0, m_Directory[0]->GetPath()); + bool bResult1 = Update(1, m_Directory[1]->GetPath()); + + CGUIWindow::OnInitWindow(); + + if (!bCheckShareConnectivity) + { + bCheckShareConnectivity = true; //reset + CFileItem pItem(strCheckSharePath, true); + ShowShareErrorMessage(&pItem); //show the error message after window is loaded! + Update(0,""); // reset view to root + } + else if (!bResult0) + { + ShowShareErrorMessage(m_Directory[0]); //show the error message after window is loaded! + Update(0, ""); // reset view to root + } + + if (!bResult1) + { + ShowShareErrorMessage(m_Directory[1]); //show the error message after window is loaded! + Update(1, ""); // reset view to root + } +} + +void CGUIWindowFileManager::SetInitialPath(const std::string &path) +{ + // check for a passed destination path + std::string strDestination = path; + m_rootDir.SetSources(*CMediaSourceSettings::GetInstance().GetSources("files")); + if (!strDestination.empty()) + { + CLog::Log(LOGINFO, "Attempting to quickpath to: {}", strDestination); + } + // otherwise, is this the first time accessing this window? + else if (m_Directory[0]->GetPath() == "?") + { + m_Directory[0]->SetPath(strDestination = CMediaSourceSettings::GetInstance().GetDefaultSource("files")); + CLog::Log(LOGINFO, "Attempting to default to: {}", strDestination); + } + // try to open the destination path + if (!strDestination.empty()) + { + // open root + if (StringUtils::EqualsNoCase(strDestination, "$ROOT")) + { + m_Directory[0]->SetPath(""); + CLog::Log(LOGINFO, " Success! Opening root listing."); + } + else + { + // default parameters if the jump fails + m_Directory[0]->SetPath(""); + + bool bIsSourceName = false; + VECSOURCES shares; + m_rootDir.GetSources(shares); + int iIndex = CUtil::GetMatchingSource(strDestination, shares, bIsSourceName); + if (iIndex > -1 +#if defined(TARGET_DARWIN_EMBEDDED) + || URIUtils::PathHasParent(strDestination, "special://envhome/Documents/Inbox/") +#endif + || URIUtils::PathHasParent(strDestination, "special://profile/")) + { + // set current directory to matching share + std::string path; + if (bIsSourceName && iIndex < (int)shares.size()) + path = shares[iIndex].strPath; + else + path = strDestination; + URIUtils::RemoveSlashAtEnd(path); + m_Directory[0]->SetPath(path); + CLog::Log(LOGINFO, " Success! Opened destination path: {}", strDestination); + + // outside call: check the share for connectivity + bCheckShareConnectivity = Update(0, m_Directory[0]->GetPath()); + if(!bCheckShareConnectivity) + strCheckSharePath = m_Directory[0]->GetPath(); + } + else + { + CLog::Log(LOGERROR, " Failed! Destination parameter ({}) does not match a valid share!", + strDestination); + } + } + } + + if (m_Directory[1]->GetPath() == "?") m_Directory[1]->SetPath(""); +} + +const CFileItem& CGUIWindowFileManager::CurrentDirectory(int indx) const +{ + return *m_Directory[indx]; +} diff --git a/xbmc/windows/GUIWindowFileManager.h b/xbmc/windows/GUIWindowFileManager.h new file mode 100644 index 0000000..425e96e --- /dev/null +++ b/xbmc/windows/GUIWindowFileManager.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "filesystem/DirectoryHistory.h" +#include "filesystem/VirtualDirectory.h" +#include "guilib/GUIWindow.h" +#include "utils/JobManager.h" + +#include <atomic> +#include <string> +#include <vector> + +class CFileItem; +class CFileItemList; +class CGUIDialogProgress; + +class CGUIWindowFileManager : + public CGUIWindow, + public CJobQueue +{ +public: + + CGUIWindowFileManager(void); + ~CGUIWindowFileManager(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + bool OnBack(int actionID) override; + const CFileItem &CurrentDirectory(int indx) const; + + static int64_t CalculateFolderSize(const std::string &strDirectory, CGUIDialogProgress *pProgress = NULL); + + void OnJobComplete(unsigned int jobID, bool success, CJob *job) override; +protected: + void OnInitWindow() override; + void SetInitialPath(const std::string &path); + void GoParentFolder(int iList); + void UpdateControl(int iList, int item); + bool Update(int iList, const std::string &strDirectory); //??? + void OnStart(CFileItem *pItem, const std::string &player); + bool SelectItem(int iList, int &item); + void ClearFileItems(int iList); + void OnClick(int iList, int iItem); + void OnMark(int iList, int iItem); + void OnSort(int iList); + void UpdateButtons(); + void OnCopy(int iList); + void OnMove(int iList); + void OnDelete(int iList); + void OnRename(int iList); + void OnSelectAll(int iList); + void OnNewFolder(int iList); + void Refresh(); + void Refresh(int iList); + int GetSelectedItem(int iList); + bool HaveDiscOrConnection( std::string& strPath, int iDriveType ); + void GetDirectoryHistoryString(const CFileItem* pItem, std::string& strHistoryString); + bool GetDirectory(int iList, const std::string &strDirectory, CFileItemList &items); + int NumSelected(int iList); + int GetFocusedList() const; + // functions to check for actions that we can perform + bool CanRename(int iList); + bool CanCopy(int iList); + bool CanMove(int iList); + bool CanDelete(int iList); + bool CanNewFolder(int iList); + void OnPopupMenu(int iList, int iItem, bool bContextDriven = true); + void ShowShareErrorMessage(CFileItem* pItem); + void UpdateItemCounts(); + + // + bool bCheckShareConnectivity; + std::string strCheckSharePath; + + XFILE::CVirtualDirectory m_rootDir; + CFileItemList* m_vecItems[2]; + typedef std::vector <CFileItem*> ::iterator ivecItems; + CFileItem* m_Directory[2]; + std::string m_strParentPath[2]; + CDirectoryHistory m_history[2]; + + int m_errorHeading, m_errorLine; +private: + std::atomic_bool m_updating = {false}; + class CUpdateGuard + { + public: + CUpdateGuard(std::atomic_bool &update) : m_update(update) + { + m_update = true; + } + ~CUpdateGuard() + { + m_update = false; + } + private: + std::atomic_bool &m_update; + }; +}; diff --git a/xbmc/windows/GUIWindowHome.cpp b/xbmc/windows/GUIWindowHome.cpp new file mode 100644 index 0000000..abc6cfc --- /dev/null +++ b/xbmc/windows/GUIWindowHome.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowHome.h" + +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/WindowIDs.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "interfaces/AnnouncementManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/JobManager.h" +#include "utils/RecentlyAddedJob.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <mutex> + +CGUIWindowHome::CGUIWindowHome(void) : CGUIWindow(WINDOW_HOME, "Home.xml") +{ + m_updateRA = (Audio | Video | Totals); + m_loadType = KEEP_IN_MEMORY; + + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); +} + +CGUIWindowHome::~CGUIWindowHome(void) +{ + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); +} + +bool CGUIWindowHome::OnAction(const CAction &action) +{ + static unsigned int min_hold_time = 1000; + if (action.GetID() == ACTION_NAV_BACK && action.GetHoldTime() < min_hold_time) + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->IsPlaying()) + { + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetWindowManager().SwitchToFullScreen(); + + return true; + } + } + return CGUIWindow::OnAction(action); +} + +void CGUIWindowHome::OnInitWindow() +{ + // for shared databases (ie mysql) always force an update on return to home + // this is a temporary solution until remote announcements can be delivered + if (StringUtils::EqualsNoCase(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_databaseVideo.type, "mysql") || + StringUtils::EqualsNoCase(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_databaseMusic.type, "mysql") ) + m_updateRA = (Audio | Video | Totals); + AddRecentlyAddedJobs( m_updateRA ); + + CGUIWindow::OnInitWindow(); +} + +void CGUIWindowHome::Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + int ra_flag = 0; + + CLog::Log(LOGDEBUG, LOGANNOUNCE, "GOT ANNOUNCEMENT, type: {}, from {}, message {}", + AnnouncementFlagToString(flag), sender, message); + + // we are only interested in library changes + if ((flag & (ANNOUNCEMENT::VideoLibrary | ANNOUNCEMENT::AudioLibrary)) == 0) + return; + + if (data.isMember("transaction") && data["transaction"].asBoolean()) + return; + + if (message == "OnScanStarted" || message == "OnCleanStarted") + return; + + bool onUpdate = message == "OnUpdate"; + // always update Totals except on an OnUpdate with no playcount update + if (!onUpdate || data.isMember("playcount")) + ra_flag |= Totals; + + // always update the full list except on an OnUpdate + if (!onUpdate) + { + if (flag & ANNOUNCEMENT::VideoLibrary) + ra_flag |= Video; + else if (flag & ANNOUNCEMENT::AudioLibrary) + ra_flag |= Audio; + } + + CGUIMessage reload(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_REFRESH_THUMBS, ra_flag); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(reload, GetID()); +} + +void CGUIWindowHome::AddRecentlyAddedJobs(int flag) +{ + bool getAJob = false; + + // this block checks to see if another one is running + // and keeps track of the flag + { + std::unique_lock<CCriticalSection> lockMe(*this); + if (!m_recentlyAddedRunning) + { + getAJob = true; + + flag |= m_cumulativeUpdateFlag; // add the flags from previous calls to AddRecentlyAddedJobs + + m_cumulativeUpdateFlag = 0; // now taken care of in flag. + // reset this since we're going to execute a job + + // we're about to add one so set the indicator + if (flag) + m_recentlyAddedRunning = true; // this will happen in the if clause below + } + else + // since we're going to skip a job, mark that one came in and ... + m_cumulativeUpdateFlag |= flag; // this will be used later + } + + if (flag && getAJob) + CServiceBroker::GetJobManager()->AddJob(new CRecentlyAddedJob(flag), this); + + m_updateRA = 0; +} + +void CGUIWindowHome::OnJobComplete(unsigned int jobID, bool success, CJob *job) +{ + int flag = 0; + + { + std::unique_lock<CCriticalSection> lockMe(*this); + + // the job is finished. + // did one come in the meantime? + flag = m_cumulativeUpdateFlag; + m_recentlyAddedRunning = false; /// we're done. + } + + if (flag) + AddRecentlyAddedJobs(0 /* the flag will be set inside AddRecentlyAddedJobs via m_cumulativeUpdateFlag */ ); +} + + +bool CGUIWindowHome::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_NOTIFY_ALL: + if (message.GetParam1() == GUI_MSG_WINDOW_RESET || message.GetParam1() == GUI_MSG_REFRESH_THUMBS) + { + int updateRA = (message.GetSenderId() == GetID()) ? message.GetParam2() : (Video | Audio | Totals); + + if (IsActive()) + AddRecentlyAddedJobs(updateRA); + else + m_updateRA |= updateRA; + } + break; + + default: + break; + } + + return CGUIWindow::OnMessage(message); +} diff --git a/xbmc/windows/GUIWindowHome.h b/xbmc/windows/GUIWindowHome.h new file mode 100644 index 0000000..08e0066 --- /dev/null +++ b/xbmc/windows/GUIWindowHome.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIWindow.h" +#include "interfaces/IAnnouncer.h" +#include "utils/Job.h" + +class CVariant; + +class CGUIWindowHome : + public CGUIWindow, + public ANNOUNCEMENT::IAnnouncer, + public IJobCallback +{ +public: + CGUIWindowHome(void); + ~CGUIWindowHome(void) override; + void OnInitWindow() override; + void Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) override; + + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + + void OnJobComplete(unsigned int jobID, bool success, CJob *job) override; +private: + int m_updateRA; // flag for which recently added items needs to be queried + void AddRecentlyAddedJobs(int flag); + + bool m_recentlyAddedRunning = false; + int m_cumulativeUpdateFlag = 0; +}; diff --git a/xbmc/windows/GUIWindowLoginScreen.cpp b/xbmc/windows/GUIWindowLoginScreen.cpp new file mode 100644 index 0000000..b00c2ae --- /dev/null +++ b/xbmc/windows/GUIWindowLoginScreen.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowLoginScreen.h" + +#include "FileItem.h" +#include "GUIPassword.h" +#include "ServiceBroker.h" +#include "addons/Skin.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "interfaces/builtins/Builtins.h" +#include "messaging/ApplicationMessenger.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "profiles/Profile.h" +#include "profiles/ProfileManager.h" +#include "profiles/dialogs/GUIDialogProfileSettings.h" +#include "pvr/PVRManager.h" +#include "pvr/guilib/PVRGUIActionsPowerManagement.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "view/ViewState.h" + +using namespace KODI::MESSAGING; + +#define CONTROL_BIG_LIST 52 +#define CONTROL_LABEL_HEADER 2 +#define CONTROL_LABEL_SELECTED_PROFILE 3 + +CGUIWindowLoginScreen::CGUIWindowLoginScreen(void) + : CGUIWindow(WINDOW_LOGIN_SCREEN, "LoginScreen.xml") +{ + watch.StartZero(); + m_vecItems = new CFileItemList; + m_iSelectedItem = -1; + m_loadType = KEEP_IN_MEMORY; +} + +CGUIWindowLoginScreen::~CGUIWindowLoginScreen(void) +{ + delete m_vecItems; +} + +bool CGUIWindowLoginScreen::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_DEINIT: + { + m_vecItems->Clear(); + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_BIG_LIST) + { + int iAction = message.GetParam1(); + + // iItem is checked for validity inside these routines + if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) + { + int iItem = m_viewControl.GetSelectedItem(); + bool bResult = OnPopupMenu(m_viewControl.GetSelectedItem()); + if (bResult) + { + Update(); + CGUIMessage msg(GUI_MSG_ITEM_SELECT,GetID(),CONTROL_BIG_LIST,iItem); + OnMessage(msg); + } + + return bResult; + } + else if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) + { + int iItem = m_viewControl.GetSelectedItem(); + bool bCanceled; + bool bOkay = g_passwordManager.IsProfileLockUnlocked(iItem, bCanceled); + + if (bOkay) + { + if (iItem >= 0) + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_LOADPROFILE, iItem); + } + else + { + if (!bCanceled && iItem != 0) + HELPERS::ShowOKDialogText(CVariant{20068}, CVariant{20117}); + } + } + } + } + break; + case GUI_MSG_SETFOCUS: + { + if (m_viewControl.HasControl(message.GetControlId()) && m_viewControl.GetCurrentControl() != message.GetControlId()) + { + m_viewControl.SetFocused(); + return true; + } + } + default: + break; + + } + + return CGUIWindow::OnMessage(message); +} + +bool CGUIWindowLoginScreen::OnAction(const CAction &action) +{ + // don't allow built in actions to act here except shutdown related ones. + // this forces only navigation type actions to be performed. + if (action.GetID() == ACTION_BUILT_IN_FUNCTION) + { + std::string actionName = action.GetName(); + StringUtils::ToLower(actionName); + if ((actionName.find("shutdown") != std::string::npos) && + CServiceBroker::GetPVRManager().Get<PVR::GUI::PowerManagement>().CanSystemPowerdown()) + CBuiltins::GetInstance().Execute(action.GetName()); + return true; + } + return CGUIWindow::OnAction(action); +} + +bool CGUIWindowLoginScreen::OnBack(int actionID) +{ + // no escape from the login window + return false; +} + +void CGUIWindowLoginScreen::FrameMove() +{ + if (GetFocusedControlID() == CONTROL_BIG_LIST && !CServiceBroker::GetGUI()->GetWindowManager().HasModalDialog(true)) + { + if (m_viewControl.HasControl(CONTROL_BIG_LIST)) + m_iSelectedItem = m_viewControl.GetSelectedItem(); + } + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + std::string strLabel = StringUtils::Format(g_localizeStrings.Get(20114), m_iSelectedItem + 1, + profileManager->GetNumberOfProfiles()); + SET_CONTROL_LABEL(CONTROL_LABEL_SELECTED_PROFILE,strLabel); + CGUIWindow::FrameMove(); +} + +void CGUIWindowLoginScreen::OnInitWindow() +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + m_iSelectedItem = static_cast<int>(profileManager->GetLastUsedProfileIndex()); + + // Update list/thumb control + m_viewControl.SetCurrentView(DEFAULT_VIEW_LIST); + Update(); + m_viewControl.SetFocused(); + SET_CONTROL_LABEL(CONTROL_LABEL_HEADER,g_localizeStrings.Get(20115)); + SET_CONTROL_VISIBLE(CONTROL_BIG_LIST); + + CGUIWindow::OnInitWindow(); +} + +void CGUIWindowLoginScreen::OnWindowLoaded() +{ + CGUIWindow::OnWindowLoaded(); + m_viewControl.Reset(); + m_viewControl.SetParentWindow(GetID()); + m_viewControl.AddView(GetControl(CONTROL_BIG_LIST)); +} + +void CGUIWindowLoginScreen::OnWindowUnload() +{ + CGUIWindow::OnWindowUnload(); + m_viewControl.Reset(); +} + +void CGUIWindowLoginScreen::Update() +{ + m_vecItems->Clear(); + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + for (unsigned int i = 0; i < profileManager->GetNumberOfProfiles(); ++i) + { + const CProfile *profile = profileManager->GetProfile(i); + + CFileItemPtr item(new CFileItem(profile->getName())); + + std::string strLabel; + if (profile->getDate().empty()) + strLabel = g_localizeStrings.Get(20113); + else + strLabel = StringUtils::Format(g_localizeStrings.Get(20112), profile->getDate()); + + item->SetLabel2(strLabel); + item->SetArt("thumb", profile->getThumb()); + if (profile->getThumb().empty()) + item->SetArt("thumb", "DefaultUser.png"); + item->SetLabelPreformatted(true); + + m_vecItems->Add(item); + } + m_viewControl.SetItems(*m_vecItems); + m_viewControl.SetSelectedItem(m_iSelectedItem); +} + +bool CGUIWindowLoginScreen::OnPopupMenu(int iItem) +{ + if (iItem < 0 || iItem >= m_vecItems->Size()) + return false; + + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + CFileItemPtr pItem = m_vecItems->Get(iItem); + bool bSelect = pItem->IsSelected(); + + // mark the item + pItem->Select(true); + + CContextButtons choices; + choices.Add(1, 20067); + + if (iItem == 0 && g_passwordManager.iMasterLockRetriesLeft == 0) + choices.Add(2, 12334); + + int choice = CGUIDialogContextMenu::ShowAndGetChoice(choices); + if (choice == 2) + { + if (g_passwordManager.CheckLock(profileManager->GetMasterProfile().getLockMode(), profileManager->GetMasterProfile().getLockCode(), 20075)) + g_passwordManager.iMasterLockRetriesLeft = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MASTERLOCK_MAXRETRIES); + else // be inconvenient + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SHUTDOWN); + + return true; + } + + // Edit the profile after checking if the correct master lock password was given. + if (choice == 1 && g_passwordManager.IsMasterLockUnlocked(true)) + CGUIDialogProfileSettings::ShowForProfile(m_viewControl.GetSelectedItem()); + + //NOTE: this can potentially (de)select the wrong item if the filelisting has changed because of an action above. + if (iItem < static_cast<int>(profileManager->GetNumberOfProfiles())) + m_vecItems->Get(iItem)->Select(bSelect); + + return false; +} + +CFileItemPtr CGUIWindowLoginScreen::GetCurrentListItem(int offset) +{ + int item = m_viewControl.GetSelectedItem(); + if (item < 0 || !m_vecItems->Size()) return CFileItemPtr(); + + item = (item + offset) % m_vecItems->Size(); + if (item < 0) item += m_vecItems->Size(); + return m_vecItems->Get(item); +} diff --git a/xbmc/windows/GUIWindowLoginScreen.h b/xbmc/windows/GUIWindowLoginScreen.h new file mode 100644 index 0000000..500bee7 --- /dev/null +++ b/xbmc/windows/GUIWindowLoginScreen.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "utils/Stopwatch.h" +#include "view/GUIViewControl.h" + +class CFileItemList; + +class CGUIWindowLoginScreen : public CGUIWindow +{ +public: + CGUIWindowLoginScreen(void); + ~CGUIWindowLoginScreen(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + bool OnBack(int actionID) override; + void FrameMove() override; + bool HasListItems() const override { return true; } + CFileItemPtr GetCurrentListItem(int offset = 0) override; + int GetViewContainerID() const override { return m_viewControl.GetCurrentControl(); } + +protected: + void OnInitWindow() override; + void OnWindowLoaded() override; + void OnWindowUnload() override; + void Update(); + void SetLabel(int iControl, const std::string& strLabel); + + bool OnPopupMenu(int iItem); + CGUIViewControl m_viewControl; + CFileItemList* m_vecItems; + + int m_iSelectedItem; + CStopWatch watch; +}; diff --git a/xbmc/windows/GUIWindowPointer.cpp b/xbmc/windows/GUIWindowPointer.cpp new file mode 100644 index 0000000..d9e1bda --- /dev/null +++ b/xbmc/windows/GUIWindowPointer.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowPointer.h" + +#include "ServiceBroker.h" +#include "input/InputManager.h" +#include "input/mouse/MouseStat.h" +#include "windowing/WinSystem.h" + +#define ID_POINTER 10 + +CGUIWindowPointer::CGUIWindowPointer(void) + : CGUIDialog(WINDOW_DIALOG_POINTER, "Pointer.xml", DialogModalityType::MODELESS) +{ + m_pointer = 0; + m_loadType = LOAD_ON_GUI_INIT; + m_needsScaling = false; + m_active = false; + m_renderOrder = RENDER_ORDER_WINDOW_POINTER; +} + +CGUIWindowPointer::~CGUIWindowPointer(void) = default; + +void CGUIWindowPointer::SetPointer(int pointer) +{ + if (m_pointer == pointer) return; + // set the new pointer visible + CGUIControl *pControl = GetControl(pointer); + if (pControl) + { + pControl->SetVisible(true); + // disable the old pointer + pControl = GetControl(m_pointer); + if (pControl) pControl->SetVisible(false); + // set pointer to the new one + m_pointer = pointer; + } +} + +void CGUIWindowPointer::UpdateVisibility() +{ + if(CServiceBroker::GetWinSystem()->HasCursor()) + { + if (CServiceBroker::GetInputManager().IsMouseActive()) + Open(); + else + Close(); + } +} + +void CGUIWindowPointer::OnWindowLoaded() +{ // set all our pointer images invisible + for (iControls i = m_children.begin();i != m_children.end(); ++i) + { + CGUIControl* pControl = *i; + pControl->SetVisible(false); + } + CGUIWindow::OnWindowLoaded(); + DynamicResourceAlloc(false); + m_pointer = 0; + m_renderOrder = RENDER_ORDER_WINDOW_POINTER; +} + +void CGUIWindowPointer::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) +{ + bool active = CServiceBroker::GetInputManager().IsMouseActive(); + if (active != m_active) + { + MarkDirtyRegion(); + m_active = active; + } + MousePosition pos = CServiceBroker::GetInputManager().GetMousePosition(); + SetPosition((float)pos.x, (float)pos.y); + SetPointer(CServiceBroker::GetInputManager().GetMouseState()); + return CGUIWindow::Process(currentTime, dirtyregions); +} diff --git a/xbmc/windows/GUIWindowPointer.h b/xbmc/windows/GUIWindowPointer.h new file mode 100644 index 0000000..476f0a2 --- /dev/null +++ b/xbmc/windows/GUIWindowPointer.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class CGUIWindowPointer : + public CGUIDialog +{ +public: + CGUIWindowPointer(void); + ~CGUIWindowPointer(void) override; + void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override; +protected: + void SetPointer(int pointer); + void OnWindowLoaded() override; + void UpdateVisibility() override; +private: + int m_pointer; + bool m_active; +}; diff --git a/xbmc/windows/GUIWindowScreensaver.cpp b/xbmc/windows/GUIWindowScreensaver.cpp new file mode 100644 index 0000000..76eaa96 --- /dev/null +++ b/xbmc/windows/GUIWindowScreensaver.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowScreensaver.h" + +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/ScreenSaver.h" +#include "addons/addoninfo/AddonType.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" + +CGUIWindowScreensaver::CGUIWindowScreensaver() : CGUIWindow(WINDOW_SCREENSAVER, "") +{ +} + +void CGUIWindowScreensaver::Process(unsigned int currentTime, CDirtyRegionList& regions) +{ + MarkDirtyRegion(); + CGUIWindow::Process(currentTime, regions); + const auto& context = CServiceBroker::GetWinSystem()->GetGfxContext(); + m_renderRegion.SetRect(0, 0, static_cast<float>(context.GetWidth()), + static_cast<float>(context.GetHeight())); +} + +void CGUIWindowScreensaver::Render() +{ + if (m_addon) + { + auto& context = CServiceBroker::GetWinSystem()->GetGfxContext(); + + context.CaptureStateBlock(); + m_addon->Render(); + context.ApplyStateBlock(); + return; + } + + CGUIWindow::Render(); +} + +// called when the mouse is moved/clicked etc. etc. +EVENT_RESULT CGUIWindowScreensaver::OnMouseEvent(const CPoint& point, const CMouseEvent& event) +{ + CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow(); + return EVENT_RESULT_HANDLED; +} + +bool CGUIWindowScreensaver::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_DEINIT: + { + if (m_addon) + { + m_addon->Stop(); + m_addon.reset(); + } + + CServiceBroker::GetWinSystem()->GetGfxContext().ApplyStateBlock(); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + CGUIWindow::OnMessage(message); + + CServiceBroker::GetWinSystem()->GetGfxContext().CaptureStateBlock(); + + const std::string addon = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( + CSettings::SETTING_SCREENSAVER_MODE); + const ADDON::AddonInfoPtr addonBase = + CServiceBroker::GetAddonMgr().GetAddonInfo(addon, ADDON::AddonType::SCREENSAVER); + if (!addonBase) + return false; + m_addon = std::make_unique<KODI::ADDONS::CScreenSaver>(addonBase); + return m_addon->Start(); + } + + case GUI_MSG_CHECK_LOCK: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + if (!g_passwordManager.IsProfileLockUnlocked()) + { + appPower->SetScreenSaverLockFailed(); + return false; + } + appPower->SetScreenSaverUnlocked(); + return true; + } + } + + return CGUIWindow::OnMessage(message); +} diff --git a/xbmc/windows/GUIWindowScreensaver.h b/xbmc/windows/GUIWindowScreensaver.h new file mode 100644 index 0000000..903efde --- /dev/null +++ b/xbmc/windows/GUIWindowScreensaver.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIWindow.h" + +#include <memory> + +namespace KODI +{ +namespace ADDONS +{ +class CScreenSaver; +} // namespace ADDONS +} // namespace KODI + +class CGUIWindowScreensaver : public CGUIWindow +{ +public: + CGUIWindowScreensaver(); + + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction& action) override + { + // We're just a screen saver, nothing to do here + return false; + } + void Render() override; + void Process(unsigned int currentTime, CDirtyRegionList& regions) override; + +protected: + EVENT_RESULT OnMouseEvent(const CPoint& point, const CMouseEvent& event) override; + +private: + std::unique_ptr<KODI::ADDONS::CScreenSaver> m_addon; +}; diff --git a/xbmc/windows/GUIWindowScreensaverDim.cpp b/xbmc/windows/GUIWindowScreensaverDim.cpp new file mode 100644 index 0000000..f617fba --- /dev/null +++ b/xbmc/windows/GUIWindowScreensaverDim.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowScreensaverDim.h" + +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/IAddon.h" +#include "addons/addoninfo/AddonType.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" +#include "guilib/GUITexture.h" +#include "utils/ColorUtils.h" +#include "windowing/GraphicContext.h" + +CGUIWindowScreensaverDim::CGUIWindowScreensaverDim(void) + : CGUIDialog(WINDOW_SCREENSAVER_DIM, "", DialogModalityType::MODELESS) +{ + m_needsScaling = false; + m_animations.push_back(CAnimation::CreateFader(0, 100, 0, 1000, ANIM_TYPE_WINDOW_OPEN)); + m_animations.push_back(CAnimation::CreateFader(100, 0, 0, 1000, ANIM_TYPE_WINDOW_CLOSE)); + m_renderOrder = RENDER_ORDER_WINDOW_SCREENSAVER; +} + +void CGUIWindowScreensaverDim::UpdateVisibility() +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + if (appPower->IsInScreenSaver()) + { + if (m_visible) + return; + + std::string usedId = appPower->ScreensaverIdInUse(); + if (usedId == "screensaver.xbmc.builtin.dim" || + usedId == "screensaver.xbmc.builtin.black") + { + m_visible = true; + ADDON::AddonPtr info; + bool success = CServiceBroker::GetAddonMgr().GetAddon( + usedId, info, ADDON::AddonType::SCREENSAVER, ADDON::OnlyEnabled::CHOICE_YES); + if (success && info && !info->GetSetting("level").empty()) + m_newDimLevel = 100.0f - (float)atof(info->GetSetting("level").c_str()); + else + m_newDimLevel = 100.0f; + Open(); + } + } + else if (m_visible) + { + m_visible = false; + Close(); + } +} + +void CGUIWindowScreensaverDim::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) +{ + if (m_newDimLevel != m_dimLevel && !IsAnimating(ANIM_TYPE_WINDOW_CLOSE)) + m_dimLevel = m_newDimLevel; + CGUIDialog::Process(currentTime, dirtyregions); + m_renderRegion.SetRect(0, 0, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()); +} + +void CGUIWindowScreensaverDim::Render() +{ + // draw a translucent black quad - fading is handled by the window animation + UTILS::COLOR::Color color = (static_cast<UTILS::COLOR::Color>(m_dimLevel * 2.55f) & 0xff) << 24; + color = CServiceBroker::GetWinSystem()->GetGfxContext().MergeAlpha(color); + CRect rect(0, 0, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()); + CGUITexture::DrawQuad(rect, color); + CGUIDialog::Render(); +} diff --git a/xbmc/windows/GUIWindowScreensaverDim.h b/xbmc/windows/GUIWindowScreensaverDim.h new file mode 100644 index 0000000..c4d5a78 --- /dev/null +++ b/xbmc/windows/GUIWindowScreensaverDim.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class CGUIWindowScreensaverDim : public CGUIDialog +{ +public: + CGUIWindowScreensaverDim(); + + void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override; + void Render() override; + +protected: + void UpdateVisibility() override; + +private: + float m_dimLevel = 100.0f; + float m_newDimLevel = 100.0f; + bool m_visible = false; +}; diff --git a/xbmc/windows/GUIWindowSplash.cpp b/xbmc/windows/GUIWindowSplash.cpp new file mode 100644 index 0000000..dadd61e --- /dev/null +++ b/xbmc/windows/GUIWindowSplash.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowSplash.h" + +#include "Util.h" +#include "guilib/GUIImage.h" +#include "guilib/GUIWindowManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" + +CGUIWindowSplash::CGUIWindowSplash(void) : CGUIWindow(WINDOW_SPLASH, ""), m_image(nullptr) +{ + m_loadType = LOAD_ON_GUI_INIT; +} + +CGUIWindowSplash::~CGUIWindowSplash(void) = default; + +void CGUIWindowSplash::OnInitWindow() +{ + if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_splashImage) + return; + + m_image = std::unique_ptr<CGUIImage>(new CGUIImage(0, 0, 0, 0, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(), CTextureInfo(CUtil::GetSplashPath()))); + m_image->SetAspectRatio(CAspectRatio::AR_SCALE); +} + +void CGUIWindowSplash::Render() +{ + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(), true); + + if (!m_image) + return; + + m_image->SetWidth(CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth()); + m_image->SetHeight(CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight()); + m_image->AllocResources(); + m_image->Render(); + m_image->FreeResources(); +} diff --git a/xbmc/windows/GUIWindowSplash.h b/xbmc/windows/GUIWindowSplash.h new file mode 100644 index 0000000..f5f54ee --- /dev/null +++ b/xbmc/windows/GUIWindowSplash.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIWindow.h" + +#include <memory> + +class CGUITextLayout; +class CGUIImage; + +class CGUIWindowSplash : public CGUIWindow +{ +public: + CGUIWindowSplash(void); + ~CGUIWindowSplash(void) override; + bool OnAction(const CAction& action) override { return false; } + void Render() override; +protected: + void OnInitWindow() override; +private: + std::unique_ptr<CGUIImage> m_image; +}; diff --git a/xbmc/windows/GUIWindowStartup.cpp b/xbmc/windows/GUIWindowStartup.cpp new file mode 100644 index 0000000..ffa3d79 --- /dev/null +++ b/xbmc/windows/GUIWindowStartup.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowStartup.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/WindowIDs.h" +#include "input/Key.h" + +CGUIWindowStartup::CGUIWindowStartup(void) + : CGUIWindow(WINDOW_STARTUP_ANIM, "Startup.xml") +{ +} + +CGUIWindowStartup::~CGUIWindowStartup(void) = default; + +bool CGUIWindowStartup::OnAction(const CAction &action) +{ + if (action.IsMouse()) + return true; + return CGUIWindow::OnAction(action); +} + +void CGUIWindowStartup::OnDeinitWindow(int nextWindowID) +{ + CGUIWindow::OnDeinitWindow(nextWindowID); + + // let everyone know that the user interface is now ready for usage + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UI_READY); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); +} diff --git a/xbmc/windows/GUIWindowStartup.h b/xbmc/windows/GUIWindowStartup.h new file mode 100644 index 0000000..c2e35ad --- /dev/null +++ b/xbmc/windows/GUIWindowStartup.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIWindow.h" + +class CGUIWindowStartup : + public CGUIWindow +{ +public: + CGUIWindowStartup(void); + ~CGUIWindowStartup(void) override; + bool OnAction(const CAction &action) override; + + // specialization of CGUIWindow + void OnDeinitWindow(int nextWindowID) override; +}; diff --git a/xbmc/windows/GUIWindowSystemInfo.cpp b/xbmc/windows/GUIWindowSystemInfo.cpp new file mode 100644 index 0000000..157b7b8 --- /dev/null +++ b/xbmc/windows/GUIWindowSystemInfo.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIWindowSystemInfo.h" + +#include "GUIInfoManager.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "guilib/guiinfo/GUIInfoLabels.h" +#include "pvr/PVRManager.h" +#include "storage/MediaManager.h" +#include "utils/CPUInfo.h" +#include "utils/StringUtils.h" +#include "utils/SystemInfo.h" + +#define CONTROL_TB_POLICY 30 +#define CONTROL_BT_STORAGE 94 +#define CONTROL_BT_DEFAULT 95 +#define CONTROL_BT_NETWORK 96 +#define CONTROL_BT_VIDEO 97 +#define CONTROL_BT_HARDWARE 98 +#define CONTROL_BT_PVR 99 +#define CONTROL_BT_POLICY 100 + +#define CONTROL_START CONTROL_BT_STORAGE +#define CONTROL_END CONTROL_BT_POLICY + +CGUIWindowSystemInfo::CGUIWindowSystemInfo(void) : + CGUIWindow(WINDOW_SYSTEM_INFORMATION, "SettingsSystemInfo.xml") +{ + m_section = CONTROL_BT_DEFAULT; + m_loadType = KEEP_IN_MEMORY; +} + +CGUIWindowSystemInfo::~CGUIWindowSystemInfo(void) = default; + +bool CGUIWindowSystemInfo::OnMessage(CGUIMessage& message) +{ + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + { + CGUIWindow::OnMessage(message); + SET_CONTROL_LABEL(52, CSysInfo::GetAppName() + " " + CSysInfo::GetVersion()); + SET_CONTROL_LABEL(53, CSysInfo::GetBuildDate()); + CONTROL_ENABLE_ON_CONDITION(CONTROL_BT_PVR, CServiceBroker::GetPVRManager().IsStarted()); + return true; + } + break; + + case GUI_MSG_WINDOW_DEINIT: + { + CGUIWindow::OnMessage(message); + m_diskUsage.clear(); + return true; + } + break; + + case GUI_MSG_FOCUSED: + { + CGUIWindow::OnMessage(message); + int focusedControl = GetFocusedControlID(); + if (m_section != focusedControl && focusedControl >= CONTROL_START && focusedControl <= CONTROL_END) + { + ResetLabels(); + m_section = focusedControl; + } + if (m_section >= CONTROL_BT_STORAGE && m_section <= CONTROL_BT_PVR) + SET_CONTROL_HIDDEN(CONTROL_TB_POLICY); + else if (m_section == CONTROL_BT_POLICY) + { + SET_CONTROL_LABEL(CONTROL_TB_POLICY, CServiceBroker::GetGUI()->GetInfoManager().GetLabel( + SYSTEM_PRIVACY_POLICY, INFO::DEFAULT_CONTEXT)); + SET_CONTROL_VISIBLE(CONTROL_TB_POLICY); + } + return true; + } + break; + } + return CGUIWindow::OnMessage(message); +} + +void CGUIWindowSystemInfo::FrameMove() +{ + int i = 2; + if (m_section == CONTROL_BT_DEFAULT) + { + SET_CONTROL_LABEL(40, g_localizeStrings.Get(20154)); + SetControlLabel(i++, "{}: {}", 158, SYSTEM_FREE_MEMORY); + SetControlLabel(i++, "{}: {}", 150, NETWORK_IP_ADDRESS); + SetControlLabel(i++, "{} {}", 13287, SYSTEM_SCREEN_RESOLUTION); + SetControlLabel(i++, "{} {}", 13283, SYSTEM_OS_VERSION_INFO); + SetControlLabel(i++, "{}: {}", 12390, SYSTEM_UPTIME); + SetControlLabel(i++, "{}: {}", 12394, SYSTEM_TOTALUPTIME); + SetControlLabel(i++, "{}: {}", 12395, SYSTEM_BATTERY_LEVEL); + } + + else if (m_section == CONTROL_BT_STORAGE) + { + SET_CONTROL_LABEL(40, g_localizeStrings.Get(20155)); + if (m_diskUsage.empty()) + m_diskUsage = CServiceBroker::GetMediaManager().GetDiskUsage(); + + for (size_t d = 0; d < m_diskUsage.size(); d++) + { + SET_CONTROL_LABEL(i++, m_diskUsage[d]); + } + } + + else if (m_section == CONTROL_BT_NETWORK) + { + SET_CONTROL_LABEL(40,g_localizeStrings.Get(20158)); + SET_CONTROL_LABEL(i++, CServiceBroker::GetGUI()->GetInfoManager().GetLabel( + NETWORK_LINK_STATE, INFO::DEFAULT_CONTEXT)); + SetControlLabel(i++, "{}: {}", 149, NETWORK_MAC_ADDRESS); + SetControlLabel(i++, "{}: {}", 150, NETWORK_IP_ADDRESS); + SetControlLabel(i++, "{}: {}", 13159, NETWORK_SUBNET_MASK); + SetControlLabel(i++, "{}: {}", 13160, NETWORK_GATEWAY_ADDRESS); + SetControlLabel(i++, "{}: {}", 13161, NETWORK_DNS1_ADDRESS); + SetControlLabel(i++, "{}: {}", 20307, NETWORK_DNS2_ADDRESS); + SetControlLabel(i++, "{} {}", 13295, SYSTEM_INTERNET_STATE); + } + + else if (m_section == CONTROL_BT_VIDEO) + { + SET_CONTROL_LABEL(40,g_localizeStrings.Get(20159)); + SET_CONTROL_LABEL(i++, CServiceBroker::GetGUI()->GetInfoManager().GetLabel( + SYSTEM_VIDEO_ENCODER_INFO, INFO::DEFAULT_CONTEXT)); + SetControlLabel(i++, "{} {}", 13287, SYSTEM_SCREEN_RESOLUTION); + + auto renderingSystem = CServiceBroker::GetRenderSystem(); + if (renderingSystem) + { + static std::string vendor = renderingSystem->GetRenderVendor(); + if (!vendor.empty()) + SET_CONTROL_LABEL(i++, StringUtils::Format("{} {}", g_localizeStrings.Get(22007), vendor)); + +#if defined(HAS_DX) + int renderVersionLabel = 22024; +#else + int renderVersionLabel = 22009; +#endif + static std::string version = renderingSystem->GetRenderVersionString(); + if (!version.empty()) + SET_CONTROL_LABEL( + i++, StringUtils::Format("{} {}", g_localizeStrings.Get(renderVersionLabel), version)); + } + + auto windowSystem = CServiceBroker::GetWinSystem(); + if (windowSystem) + { + static std::string platform = windowSystem->GetName(); + if (platform != "platform default") + SET_CONTROL_LABEL(i++, + StringUtils::Format("{} {}", g_localizeStrings.Get(39153), platform)); + } + + SetControlLabel(i++, "{} {}", 22010, SYSTEM_GPU_TEMPERATURE); + + const std::string hdrTypes = CServiceBroker::GetGUI()->GetInfoManager().GetLabel( + SYSTEM_SUPPORTED_HDR_TYPES, INFO::DEFAULT_CONTEXT); + SET_CONTROL_LABEL( + i++, StringUtils::Format("{}: {}", g_localizeStrings.Get(39174), + hdrTypes.empty() ? g_localizeStrings.Get(231) : hdrTypes)); + } + + else if (m_section == CONTROL_BT_HARDWARE) + { + SET_CONTROL_LABEL(40,g_localizeStrings.Get(20160)); + + auto cpuInfo = CServiceBroker::GetCPUInfo(); + if (cpuInfo) + { + static std::string model = cpuInfo->GetCPUModel(); + if (!model.empty()) + SET_CONTROL_LABEL(i++, "CPU: " + model); + + static std::string mips = cpuInfo->GetCPUBogoMips(); + if (!mips.empty()) + SET_CONTROL_LABEL(i++, "BogoMips: " + mips); + + static std::string soc = cpuInfo->GetCPUSoC(); + if (!soc.empty()) + SET_CONTROL_LABEL(i++, "SoC: " + soc); + + static std::string hardware = cpuInfo->GetCPUHardware(); + if (!hardware.empty()) + SET_CONTROL_LABEL(i++, "Hardware: " + hardware); + + static std::string revision = cpuInfo->GetCPURevision(); + if (!revision.empty()) + SET_CONTROL_LABEL(i++, "Revision: " + revision); + + static std::string serial = cpuInfo->GetCPUSerial(); + if (!serial.empty()) + SET_CONTROL_LABEL(i++, "Serial: " + serial); + + // temperature can't really be conditional because of localization units + SetControlLabel(i++, "{} {}", 22011, SYSTEM_CPU_TEMPERATURE); + + // we can check if the cpufrequency is not 0 (default if not implemented) + // but we have to call through CGUIInfoManager -> CSystemGUIInfo -> CSysInfo + // to limit the frequency of updates + static float cpuFreq = cpuInfo->GetCPUFrequency(); + if (cpuFreq > 0) + SetControlLabel(i++, "{} {}", 13284, SYSTEM_CPUFREQUENCY); + } + } + + else if (m_section == CONTROL_BT_PVR) + { + SET_CONTROL_LABEL(40, g_localizeStrings.Get(19166)); + int i = 2; + + SetControlLabel(i++, "{}: {}", 19120, PVR_BACKEND_NUMBER); + i++; // empty line + SetControlLabel(i++, "{}: {}", 19012, PVR_BACKEND_NAME); + SetControlLabel(i++, "{}: {}", 19114, PVR_BACKEND_VERSION); + SetControlLabel(i++, "{}: {}", 19115, PVR_BACKEND_HOST); + SetControlLabel(i++, "{}: {}", 19116, PVR_BACKEND_DISKSPACE); + SetControlLabel(i++, "{}: {}", 19334, PVR_BACKEND_PROVIDERS); + SetControlLabel(i++, "{}: {}", 19042, PVR_BACKEND_CHANNEL_GROUPS); + SetControlLabel(i++, "{}: {}", 19019, PVR_BACKEND_CHANNELS); + SetControlLabel(i++, "{}: {}", 19163, PVR_BACKEND_RECORDINGS); + SetControlLabel(i++, "{}: {}", 19168, + PVR_BACKEND_DELETED_RECORDINGS); // Deleted and recoverable recordings + SetControlLabel(i++, "{}: {}", 19025, PVR_BACKEND_TIMERS); + } + + else if (m_section == CONTROL_BT_POLICY) + { + SET_CONTROL_LABEL(40, g_localizeStrings.Get(12389)); + } + CGUIWindow::FrameMove(); +} + +void CGUIWindowSystemInfo::ResetLabels() +{ + for (int i = 2; i < 13; i++) + { + SET_CONTROL_LABEL(i, ""); + } + SET_CONTROL_LABEL(CONTROL_TB_POLICY, ""); +} + +void CGUIWindowSystemInfo::SetControlLabel(int id, const char *format, int label, int info) +{ + std::string tmpStr = StringUtils::Format( + format, g_localizeStrings.Get(label), + CServiceBroker::GetGUI()->GetInfoManager().GetLabel(info, INFO::DEFAULT_CONTEXT)); + SET_CONTROL_LABEL(id, tmpStr); +} diff --git a/xbmc/windows/GUIWindowSystemInfo.h b/xbmc/windows/GUIWindowSystemInfo.h new file mode 100644 index 0000000..e2fc50a --- /dev/null +++ b/xbmc/windows/GUIWindowSystemInfo.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIWindow.h" + +#include <string> +#include <vector> + +class CGUIWindowSystemInfo : public CGUIWindow +{ +public: + CGUIWindowSystemInfo(void); + ~CGUIWindowSystemInfo(void) override; + bool OnMessage(CGUIMessage& message) override; + void FrameMove() override; +private: + int m_section; + void ResetLabels(); + void SetControlLabel(int id, const char *format, int label, int info); + std::vector<std::string> m_diskUsage; +}; + |