summaryrefslogtreecommitdiffstats
path: root/xbmc/windows/GUIMediaWindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/windows/GUIMediaWindow.cpp')
-rw-r--r--xbmc/windows/GUIMediaWindow.cpp2314
1 files changed, 2314 insertions, 0 deletions
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;
+ }
+}