Merge pull request #26362 from ksooo/estuary-rework-pvr-info-dialog
[xbmc.git] / xbmc / windows / GUIMediaWindow.cpp
blob969fade39ed0bf6c1b6a3be60bc35c2a10c9829b
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "GUIMediaWindow.h"
11 #include "ContextMenuManager.h"
12 #include "FileItem.h"
13 #include "FileItemList.h"
14 #include "FileItemListModification.h"
15 #include "GUIPassword.h"
16 #include "GUIUserMessages.h"
17 #include "PartyModeManager.h"
18 #include "PlayListPlayer.h"
19 #include "ServiceBroker.h"
20 #include "URL.h"
21 #include "Util.h"
22 #include "addons/AddonManager.h"
23 #include "addons/PluginSource.h"
24 #include "addons/addoninfo/AddonType.h"
25 #include "application/Application.h"
26 #include "messaging/ApplicationMessenger.h"
27 #include "network/NetworkFileItemClassify.h"
28 #include "playlists/PlayListFileItemClassify.h"
29 #if defined(TARGET_ANDROID)
30 #include "platform/android/activity/XBMCApp.h"
31 #endif
32 #include "dialogs/GUIDialogBusy.h"
33 #include "dialogs/GUIDialogKaiToast.h"
34 #include "dialogs/GUIDialogMediaFilter.h"
35 #include "dialogs/GUIDialogProgress.h"
36 #include "dialogs/GUIDialogSmartPlaylistEditor.h"
37 #include "filesystem/FileDirectoryFactory.h"
38 #include "filesystem/MultiPathDirectory.h"
39 #include "filesystem/PluginDirectory.h"
40 #include "filesystem/SmartPlaylistDirectory.h"
41 #include "guilib/GUIComponent.h"
42 #include "guilib/GUIEditControl.h"
43 #include "guilib/GUIKeyboardFactory.h"
44 #include "guilib/GUIWindowManager.h"
45 #include "guilib/LocalizeStrings.h"
46 #include "input/actions/Action.h"
47 #include "input/actions/ActionIDs.h"
48 #include "interfaces/generic/ScriptInvocationManager.h"
49 #include "messaging/helpers/DialogOKHelper.h"
50 #include "music/MusicFileItemClassify.h"
51 #include "music/tags/MusicInfoTag.h"
52 #include "network/Network.h"
53 #include "playlists/PlayList.h"
54 #include "profiles/ProfileManager.h"
55 #include "settings/AdvancedSettings.h"
56 #include "settings/Settings.h"
57 #include "settings/SettingsComponent.h"
58 #include "storage/MediaManager.h"
59 #include "threads/IRunnable.h"
60 #include "utils/FileUtils.h"
61 #include "utils/LabelFormatter.h"
62 #include "utils/SortUtils.h"
63 #include "utils/StringUtils.h"
64 #include "utils/URIUtils.h"
65 #include "utils/Variant.h"
66 #include "utils/log.h"
67 #include "view/GUIViewState.h"
69 #define CONTROL_BTNVIEWASICONS 2
70 #define CONTROL_BTNSORTBY 3
71 #define CONTROL_BTNSORTASC 4
72 #define CONTROL_BTN_FILTER 19
74 #define CONTROL_LABELFILES 12
76 #define PROPERTY_PATH_DB "path.db"
77 #define PROPERTY_SORT_ORDER "sort.order"
78 #define PROPERTY_SORT_ASCENDING "sort.ascending"
80 #define PLUGIN_REFRESH_DELAY 200
82 using namespace ADDON;
83 using namespace KODI;
84 using namespace KODI::MESSAGING;
85 using namespace std::chrono_literals;
87 namespace
89 class CGetDirectoryItems : public IRunnable
91 public:
92 CGetDirectoryItems(XFILE::CVirtualDirectory &dir, CURL &url, CFileItemList &items, bool useDir)
93 : m_dir(dir), m_url(url), m_items(items), m_useDir(useDir)
97 void Run() override
99 m_result = m_dir.GetDirectory(m_url, m_items, m_useDir, true);
102 void Cancel() override
104 m_dir.CancelDirectory();
107 bool m_result = false;
109 protected:
110 XFILE::CVirtualDirectory &m_dir;
111 CURL m_url;
112 CFileItemList &m_items;
113 bool m_useDir;
117 CGUIMediaWindow::CGUIMediaWindow(int id, const char *xmlFile)
118 : CGUIWindow(id, xmlFile)
120 m_loadType = KEEP_IN_MEMORY;
121 m_vecItems = new CFileItemList;
122 m_unfilteredItems = new CFileItemList;
123 m_vecItems->SetPath("?");
124 m_iLastControl = -1;
125 m_canFilterAdvanced = false;
127 m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems));
130 CGUIMediaWindow::~CGUIMediaWindow()
132 delete m_vecItems;
133 delete m_unfilteredItems;
136 bool CGUIMediaWindow::Load(TiXmlElement *pRootElement)
138 bool retVal = CGUIWindow::Load(pRootElement);
140 if (!retVal)
141 return false;
143 // configure our view control
144 m_viewControl.Reset();
145 m_viewControl.SetParentWindow(GetID());
146 TiXmlElement *element = pRootElement->FirstChildElement("views");
147 if (element && element->FirstChild())
148 { // format is <views>50,29,51,95</views>
149 const std::string &allViews = element->FirstChild()->ValueStr();
150 std::vector<std::string> views = StringUtils::Split(allViews, ",");
151 for (std::vector<std::string>::const_iterator i = views.begin(); i != views.end(); ++i)
153 int controlID = atol(i->c_str());
154 CGUIControl *control = GetControl(controlID);
155 if (control && control->IsContainer())
156 m_viewControl.AddView(control);
159 m_viewControl.SetViewControlID(CONTROL_BTNVIEWASICONS);
161 return true;
164 void CGUIMediaWindow::OnWindowLoaded()
166 SendMessage(GUI_MSG_SET_TYPE, CONTROL_BTN_FILTER, CGUIEditControl::INPUT_TYPE_FILTER);
167 CGUIWindow::OnWindowLoaded();
168 SetupShares();
171 void CGUIMediaWindow::OnWindowUnload()
173 CGUIWindow::OnWindowUnload();
174 m_viewControl.Reset();
177 CFileItemPtr CGUIMediaWindow::GetCurrentListItem(int offset)
179 int item = m_viewControl.GetSelectedItem();
180 if (!m_vecItems->Size() || item < 0)
181 return CFileItemPtr();
182 item = (item + offset) % m_vecItems->Size();
183 if (item < 0) item += m_vecItems->Size();
184 return m_vecItems->Get(item);
187 bool CGUIMediaWindow::OnAction(const CAction &action)
189 if (action.GetID() == ACTION_PARENT_DIR)
191 GoParentFolder();
192 return true;
195 if (CGUIWindow::OnAction(action))
196 return true;
198 if (action.GetID() == ACTION_FILTER)
199 return Filter();
201 // live filtering
202 if (action.GetID() == ACTION_FILTER_CLEAR)
204 CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS);
205 message.SetStringParam("");
206 OnMessage(message);
207 return true;
210 if (action.GetID() == ACTION_BACKSPACE)
212 CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 2); // 2 for delete
213 OnMessage(message);
214 return true;
217 if (action.GetID() >= ACTION_FILTER_SMS2 && action.GetID() <= ACTION_FILTER_SMS9)
219 std::string filter = std::to_string(action.GetID() - ACTION_FILTER_SMS2 + 2);
220 CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 1); // 1 for append
221 message.SetStringParam(filter);
222 OnMessage(message);
223 return true;
226 return false;
229 bool CGUIMediaWindow::OnBack(int actionID)
231 CancelUpdateItems();
233 CURL filterUrl(m_strFilterPath);
234 if (actionID == ACTION_NAV_BACK &&
235 !m_vecItems->IsVirtualDirectoryRoot() &&
236 !URIUtils::PathEquals(m_vecItems->GetPath(), GetRootPath(), true) &&
237 (!URIUtils::PathEquals(m_vecItems->GetPath(), m_startDirectory, true) || (m_canFilterAdvanced && filterUrl.HasOption("filter"))))
239 if (GoParentFolder())
240 return true;
242 return CGUIWindow::OnBack(actionID);
245 bool CGUIMediaWindow::OnMessage(CGUIMessage& message)
247 switch ( message.GetMessage() )
249 case GUI_MSG_WINDOW_DEINIT:
251 CancelUpdateItems();
253 m_iLastControl = GetFocusedControlID();
254 CGUIWindow::OnMessage(message);
256 // get rid of any active filtering
257 if (m_canFilterAdvanced)
259 m_canFilterAdvanced = false;
260 m_filter.Reset();
262 m_strFilterPath.clear();
264 // Call ClearFileItems() after our window has finished doing any WindowClose
265 // animations
266 ClearFileItems();
267 return true;
269 break;
271 case GUI_MSG_CLICKED:
273 int iControl = message.GetSenderId();
274 if (iControl == CONTROL_BTNVIEWASICONS)
276 // view as control could be a select button
277 int viewMode = 0;
278 const CGUIControl *control = GetControl(CONTROL_BTNVIEWASICONS);
279 if (control && control->GetControlType() != CGUIControl::GUICONTROL_BUTTON)
281 CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTNVIEWASICONS);
282 OnMessage(msg);
283 viewMode = m_viewControl.GetViewModeNumber(msg.GetParam1());
285 else
286 viewMode = m_viewControl.GetNextViewMode();
288 if (m_guiState)
289 m_guiState->SaveViewAsControl(viewMode);
291 UpdateButtons();
292 return true;
294 else if (iControl == CONTROL_BTNSORTASC) // sort asc
296 if (m_guiState)
297 m_guiState->SetNextSortOrder();
298 UpdateFileList();
299 return true;
301 else if (iControl == CONTROL_BTNSORTBY) // sort by
303 if (m_guiState.get() && m_guiState->ChooseSortMethod())
304 UpdateFileList();
305 return true;
307 else if (iControl == CONTROL_BTN_FILTER)
308 return Filter(false);
309 else if (m_viewControl.HasControl(iControl)) // list/thumb control
311 int iItem = m_viewControl.GetSelectedItem();
312 int iAction = message.GetParam1();
313 if (iItem < 0) break;
314 if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK)
316 OnSelect(iItem);
318 else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK)
320 OnPopupMenu(iItem);
321 return true;
325 break;
327 case GUI_MSG_SETFOCUS:
329 if (m_viewControl.HasControl(message.GetControlId()) && m_viewControl.GetCurrentControl() != message.GetControlId())
331 m_viewControl.SetFocused();
332 return true;
335 break;
337 case GUI_MSG_NOTIFY_ALL:
338 { // Message is received even if this window is inactive
339 if (message.GetParam1() == GUI_MSG_WINDOW_RESET)
341 m_vecItems->SetPath("?");
342 return true;
344 else if ( message.GetParam1() == GUI_MSG_REFRESH_THUMBS )
346 for (int i = 0; i < m_vecItems->Size(); i++)
347 m_vecItems->Get(i)->FreeMemory(true);
348 break; // the window will take care of any info images
350 else if (message.GetParam1() == GUI_MSG_REMOVED_MEDIA)
352 if ((m_vecItems->IsVirtualDirectoryRoot() ||
353 m_vecItems->IsSourcesPath()) && IsActive())
355 int iItem = m_viewControl.GetSelectedItem();
356 Refresh();
357 m_viewControl.SetSelectedItem(iItem);
359 else if (m_vecItems->IsRemovable())
360 { // check that we have this removable share still
361 if (!m_rootDir.IsInSource(m_vecItems->GetPath()))
362 { // don't have this share any more
363 if (IsActive()) Update("");
364 else
366 m_history.ClearPathHistory();
367 m_vecItems->SetPath("");
372 return true;
374 else if (message.GetParam1()==GUI_MSG_UPDATE_SOURCES)
375 { // State of the sources changed, so update our view
376 if ((m_vecItems->IsVirtualDirectoryRoot() ||
377 m_vecItems->IsSourcesPath()) && IsActive())
379 if (m_vecItemsUpdating)
381 CLog::Log(LOGWARNING, "CGUIMediaWindow::OnMessage - updating in progress");
382 return true;
384 CUpdateGuard ug(m_vecItemsUpdating);
385 int iItem = m_viewControl.GetSelectedItem();
386 Refresh(true);
387 m_viewControl.SetSelectedItem(iItem);
389 return true;
391 else if (message.GetParam1()==GUI_MSG_UPDATE && IsActive())
393 if (m_vecItemsUpdating)
395 CLog::Log(LOGWARNING, "CGUIMediaWindow::OnMessage - updating in progress");
396 return true;
398 CUpdateGuard ug(m_vecItemsUpdating);
399 if (message.GetNumStringParams())
401 if (message.GetParam2()) // param2 is used for resetting the history
402 SetHistoryForPath(message.GetStringParam());
404 CFileItemList list(message.GetStringParam());
405 list.RemoveDiscCache(GetID());
406 Update(message.GetStringParam());
408 else
409 Refresh(true); // refresh the listing
411 else if (message.GetParam1()==GUI_MSG_UPDATE_ITEM && message.GetItem())
413 int flag = message.GetParam2();
414 CFileItemPtr newItem = std::static_pointer_cast<CFileItem>(message.GetItem());
416 if (IsActive() || (flag & GUI_MSG_FLAG_FORCE_UPDATE))
418 m_vecItems->UpdateItem(newItem.get());
420 if (flag & GUI_MSG_FLAG_UPDATE_LIST)
421 { // need the list updated as well
422 UpdateFileList();
425 else if (newItem)
426 { // need to remove the disc cache
427 CFileItemList items;
428 items.SetPath(URIUtils::GetDirectory(newItem->GetPath()));
429 if (newItem->HasProperty("cachefilename"))
431 // Use stored cache file name
432 std::string crcfile = newItem->GetProperty("cachefilename").asString();
433 items.RemoveDiscCacheCRC(crcfile);
435 else
436 // No stored cache file name, attempt using truncated item path as list path
437 items.RemoveDiscCache(GetID());
440 else if (message.GetParam1()==GUI_MSG_UPDATE_PATH)
442 if (IsActive())
444 if((message.GetStringParam() == m_vecItems->GetPath()) ||
445 (m_vecItems->IsMultiPath() && XFILE::CMultiPathDirectory::HasPath(m_vecItems->GetPath(), message.GetStringParam())))
446 Refresh();
449 else if (message.GetParam1() == GUI_MSG_FILTER_ITEMS && IsActive())
451 std::string filter = GetProperty("filter").asString();
452 // check if this is meant for advanced filtering
453 if (message.GetParam2() != 10)
455 if (message.GetParam2() == 1) // append
456 filter += message.GetStringParam();
457 else if (message.GetParam2() == 2)
458 { // delete
459 if (filter.size())
460 filter.erase(filter.size() - 1);
462 else
463 filter = message.GetStringParam();
465 OnFilterItems(filter);
466 UpdateButtons();
467 return true;
469 else
470 return CGUIWindow::OnMessage(message);
472 return true;
474 break;
475 case GUI_MSG_PLAYBACK_STARTED:
476 case GUI_MSG_PLAYBACK_ENDED:
477 case GUI_MSG_PLAYBACK_STOPPED:
478 case GUI_MSG_PLAYLIST_CHANGED:
479 case GUI_MSG_PLAYLISTPLAYER_STOPPED:
480 case GUI_MSG_PLAYLISTPLAYER_STARTED:
481 case GUI_MSG_PLAYLISTPLAYER_CHANGED:
482 { // send a notify all to all controls on this window
483 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_REFRESH_LIST);
484 OnMessage(msg);
485 break;
487 case GUI_MSG_CHANGE_VIEW_MODE:
489 int viewMode = 0;
490 if (message.GetParam1()) // we have an id
491 viewMode = m_viewControl.GetViewModeByID(message.GetParam1());
492 else if (message.GetParam2())
493 viewMode = m_viewControl.GetNextViewMode(message.GetParam2());
495 if (m_guiState)
496 m_guiState->SaveViewAsControl(viewMode);
497 UpdateButtons();
498 return true;
500 break;
501 case GUI_MSG_CHANGE_SORT_METHOD:
503 if (m_guiState)
505 if (message.GetParam1())
506 m_guiState->SetCurrentSortMethod(message.GetParam1());
507 else if (message.GetParam2())
508 m_guiState->SetNextSortMethod(message.GetParam2());
510 UpdateFileList();
511 return true;
513 break;
514 case GUI_MSG_CHANGE_SORT_DIRECTION:
516 if (m_guiState)
517 m_guiState->SetNextSortOrder();
518 UpdateFileList();
519 return true;
521 break;
522 case GUI_MSG_WINDOW_INIT:
524 if (m_vecItems->GetPath() == "?")
525 m_vecItems->SetPath("");
527 std::string dir = message.GetStringParam(0);
528 const std::string& ret = message.GetStringParam(1);
529 const std::string& swap = message.GetStringParam(message.GetNumStringParams() - 1);
530 const bool returning = StringUtils::EqualsNoCase(ret, "return");
531 const bool replacing = StringUtils::EqualsNoCase(swap, "replace");
533 if (!dir.empty())
535 // ensure our directory is valid
536 dir = GetStartFolder(dir);
537 bool resetHistory = false;
538 if (!returning || !URIUtils::PathEquals(dir, m_startDirectory, true))
539 { // we're not returning to the same path, so set our directory to the requested path
540 m_vecItems->SetPath(dir);
541 resetHistory = true;
543 else if (m_vecItems->GetPath().empty() && URIUtils::PathEquals(dir, m_startDirectory, true))
544 m_vecItems->SetPath(dir);
546 // check for network up
547 if (URIUtils::IsRemote(m_vecItems->GetPath()) && !WaitForNetwork())
549 m_vecItems->SetPath("");
550 resetHistory = true;
552 if (resetHistory)
554 m_vecItems->RemoveDiscCache(GetID());
555 // only compute the history for the provided path if "return" is not defined
556 // (otherwise the root level for the path will be added by default to the path history
557 // and we won't be able to move back to the path we came from)
558 if (!returning)
559 SetHistoryForPath(m_vecItems->GetPath());
562 if (message.GetParam1() != WINDOW_INVALID)
564 // if this is the first time to this window - make sure we set the root path
565 // if "return" is defined make sure we set the startDirectory to the directory we are
566 // moving to (so that we can move back to where we were onBack). If we are activating
567 // the same window but with a different path, do nothing - we are simply adding to the
568 // window history. Note that if the window is just being replaced, the start directory
569 // also needs to be set as the manager has just popped the previous window.
570 if (message.GetParam1() != message.GetParam2() || replacing)
571 m_startDirectory = returning ? dir : GetRootPath();
573 if (message.GetParam2() == PLUGIN_REFRESH_DELAY)
575 Refresh();
576 SetInitialVisibility();
577 RestoreControlStates();
578 SetInitialVisibility();
579 return true;
582 break;
585 return CGUIWindow::OnMessage(message);
589 * \brief Updates the states
591 * This updates the states (enable, disable, visible...) of the controls defined
592 * by this window.
594 * \note Override this function in a derived class to add new controls
596 void CGUIMediaWindow::UpdateButtons()
598 if (m_guiState)
600 // Update sorting controls
601 if (m_guiState->GetSortOrder() == SortOrderNone)
603 CONTROL_DISABLE(CONTROL_BTNSORTASC);
605 else
607 CONTROL_ENABLE(CONTROL_BTNSORTASC);
608 SET_CONTROL_SELECTED(GetID(), CONTROL_BTNSORTASC, m_guiState->GetSortOrder() != SortOrderAscending);
611 // Update list/thumb control
612 m_viewControl.SetCurrentView(m_guiState->GetViewAsControl());
614 // Update sort by button
615 if (!m_guiState->HasMultipleSortMethods())
616 CONTROL_DISABLE(CONTROL_BTNSORTBY);
617 else
618 CONTROL_ENABLE(CONTROL_BTNSORTBY);
620 std::string sortLabel = StringUtils::Format(
621 g_localizeStrings.Get(550), g_localizeStrings.Get(m_guiState->GetSortMethodLabel()));
622 SET_CONTROL_LABEL(CONTROL_BTNSORTBY, sortLabel);
625 std::string items =
626 StringUtils::Format("{} {}", m_vecItems->GetObjectCount(), g_localizeStrings.Get(127));
627 SET_CONTROL_LABEL(CONTROL_LABELFILES, items);
629 SET_CONTROL_LABEL2(CONTROL_BTN_FILTER, GetProperty("filter").asString());
632 void CGUIMediaWindow::ClearFileItems()
634 m_viewControl.Clear();
635 m_vecItems->Clear();
636 m_unfilteredItems->Clear();
640 * \brief Sort file items
642 * This sorts file items based on the sort method and sort order provided by
643 * guiViewState.
645 void CGUIMediaWindow::SortItems(CFileItemList &items)
647 std::unique_ptr<CGUIViewState> guiState(CGUIViewState::GetViewState(GetID(), items));
649 if (guiState)
651 SortDescription sorting = guiState->GetSortMethod();
652 sorting.sortOrder = guiState->GetSortOrder();
653 // If the sort method is "sort by playlist" and we have a specific
654 // sort order available we can use the specified sort order to do the sorting
655 // We do this as the new SortBy methods are a superset of the SORT_METHOD methods, thus
656 // not all are available. This may be removed once SORT_METHOD_* have been replaced by
657 // SortBy.
658 if ((sorting.sortBy == SortByPlaylistOrder) && items.HasProperty(PROPERTY_SORT_ORDER))
660 SortBy sortBy = (SortBy)items.GetProperty(PROPERTY_SORT_ORDER).asInteger();
661 if (sortBy != SortByNone && sortBy != SortByPlaylistOrder && sortBy != SortByProgramCount)
663 sorting.sortBy = sortBy;
664 sorting.sortOrder = items.GetProperty(PROPERTY_SORT_ASCENDING).asBoolean() ? SortOrderAscending : SortOrderDescending;
665 sorting.sortAttributes = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone;
667 // if the sort order is descending, we need to switch the original sort order, as we assume
668 // in CGUIViewState::AddPlaylistOrder that SortByPlaylistOrder is ascending.
669 if (guiState->GetSortOrder() == SortOrderDescending)
670 sorting.sortOrder = sorting.sortOrder == SortOrderDescending ? SortOrderAscending : SortOrderDescending;
674 items.Sort(sorting);
679 * \brief Formats item labels
681 * This is based on the formatting provided by guiViewState.
683 void CGUIMediaWindow::FormatItemLabels(CFileItemList &items, const LABEL_MASKS &labelMasks)
685 CLabelFormatter fileFormatter(labelMasks.m_strLabelFile, labelMasks.m_strLabel2File);
686 CLabelFormatter folderFormatter(labelMasks.m_strLabelFolder, labelMasks.m_strLabel2Folder);
687 for (int i=0; i<items.Size(); ++i)
689 CFileItemPtr pItem=items[i];
691 if (pItem->IsLabelPreformatted())
692 continue;
694 if (pItem->m_bIsFolder)
695 folderFormatter.FormatLabels(pItem.get());
696 else
697 fileFormatter.FormatLabels(pItem.get());
700 if (items.GetSortMethod() == SortByLabel)
701 items.ClearSortState();
705 * \brief Format and sort file items
707 * Prepares and adds the fileitems to list/thumb panel
709 void CGUIMediaWindow::FormatAndSort(CFileItemList &items)
711 std::unique_ptr<CGUIViewState> viewState(CGUIViewState::GetViewState(GetID(), items));
713 if (viewState)
715 LABEL_MASKS labelMasks;
716 viewState->GetSortMethodLabelMasks(labelMasks);
717 FormatItemLabels(items, labelMasks);
719 items.Sort(viewState->GetSortMethod().sortBy, viewState->GetSortOrder(), viewState->GetSortMethod().sortAttributes);
724 * \brief Overwrite to fill fileitems from a source
726 * \param[in] strDirectory Path to read
727 * \param[out] items Fill with items specified in \e strDirectory
728 * \return false if given directory not present
730 bool CGUIMediaWindow::GetDirectory(const std::string &strDirectory, CFileItemList &items)
732 CURL pathToUrl(strDirectory);
734 std::string strParentPath = m_history.GetParentPath();
736 CLog::Log(LOGDEBUG, "CGUIMediaWindow::GetDirectory ({})", CURL::GetRedacted(strDirectory));
737 CLog::Log(LOGDEBUG, " ParentPath = [{}]", CURL::GetRedacted(strParentPath));
739 if (pathToUrl.IsProtocol("plugin") && !pathToUrl.GetHostName().empty())
740 CServiceBroker::GetAddonMgr().UpdateLastUsed(pathToUrl.GetHostName());
742 // see if we can load a previously cached folder
743 CFileItemList cachedItems(strDirectory);
744 if (!strDirectory.empty() && cachedItems.Load(GetID()))
746 items.Assign(cachedItems);
748 else
750 auto start = std::chrono::steady_clock::now();
752 if (strDirectory.empty())
753 SetupShares();
755 CFileItemList dirItems;
756 if (!GetDirectoryItems(pathToUrl, dirItems, UseFileDirectories()))
757 return false;
759 // assign fetched directory items
760 items.Assign(dirItems);
762 // took over a second, and not normally cached, so cache it
763 auto end = std::chrono::steady_clock::now();
764 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
766 if (duration.count() > 1000 && items.CacheToDiscIfSlow())
767 items.Save(GetID());
769 // if these items should replace the current listing, then pop it off the top
770 if (items.GetReplaceListing())
771 m_history.RemoveParentPath();
774 // Store parent path along with item as parent path cannot safely be calculated from item's path.
775 for (const auto& item : items)
777 item->SetProperty("ParentPath", m_vecItems->GetPath());
780 // update the view state's reference to the current items
781 m_guiState.reset(CGUIViewState::GetViewState(GetID(), items));
783 bool bHideParent = false;
785 if (m_guiState && m_guiState->HideParentDirItems())
786 bHideParent = true;
787 if (items.GetPath() == GetRootPath())
788 bHideParent = true;
790 if (!bHideParent)
792 CFileItemPtr pItem(new CFileItem(".."));
793 pItem->SetPath(strParentPath);
794 pItem->m_bIsFolder = true;
795 pItem->m_bIsShareOrDrive = false;
796 items.AddFront(pItem, 0);
799 int iWindow = GetID();
800 std::vector<std::string> regexps;
802 //! @todo Do we want to limit the directories we apply the video ones to?
803 if (iWindow == WINDOW_VIDEO_NAV)
804 regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoExcludeFromListingRegExps;
805 if (iWindow == WINDOW_MUSIC_NAV)
806 regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioExcludeFromListingRegExps;
807 if (iWindow == WINDOW_PICTURES)
808 regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pictureExcludeFromListingRegExps;
810 if (regexps.size())
812 for (int i=0; i < items.Size();)
814 if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps))
815 items.Remove(i);
816 else
817 i++;
821 // clear the filter
822 SetProperty("filter", "");
823 m_canFilterAdvanced = false;
824 m_filter.Reset();
825 return true;
828 bool CGUIMediaWindow::Update(const std::string &strDirectory, bool updateFilterPath /* = true */)
830 //! @todo OnInitWindow calls Update() before window path has been set properly.
831 if (strDirectory == "?")
832 return false;
834 // The path to load. Empty string is used in various places to denote root, so translate to the
835 // real root path first
836 const std::string path = strDirectory.empty() ? GetRootPath() : strDirectory;
838 // stores the selected item in history
839 SaveSelectedItemInHistory();
841 const std::string previousPath = m_vecItems->GetPath();
843 // check if the path contains a filter and temporarily remove it
844 // so that the retrieved list of items is unfiltered
845 std::string pathNoFilter = path;
846 if (CanContainFilter(pathNoFilter) && CURL(pathNoFilter).HasOption("filter"))
847 pathNoFilter = RemoveParameterFromPath(pathNoFilter, "filter");
849 if (!GetDirectory(pathNoFilter, *m_vecItems))
851 CLog::Log(LOGERROR, "CGUIMediaWindow::GetDirectory({}) failed", CURL(path).GetRedacted());
853 if (URIUtils::PathEquals(path, GetRootPath()))
854 return false; // Nothing to fallback to
856 // Try to return to the previous directory, if not the same
857 // else fallback to root
858 if (URIUtils::PathEquals(path, previousPath) || !Update(m_history.RemoveParentPath()))
859 Update(""); // Fallback to root
861 // Return false to be able to eg. show
862 // an error message.
863 return false;
866 if (m_vecItems->GetLabel().empty())
868 // Removable sources
869 std::vector<CMediaSource> removables;
870 CServiceBroker::GetMediaManager().GetRemovableDrives(removables);
871 for (const auto& s : removables)
873 if (URIUtils::CompareWithoutSlashAtEnd(s.strPath, m_vecItems->GetPath()))
875 m_vecItems->SetLabel(s.strName);
876 break;
881 if (m_vecItems->GetLabel().empty())
882 m_vecItems->SetLabel(CUtil::GetTitleFromPath(m_vecItems->GetPath(), true));
884 // check the given path for filter data
885 UpdateFilterPath(path, *m_vecItems, updateFilterPath);
887 // if we're getting the root source listing
888 // make sure the path history is clean
889 if (URIUtils::PathEquals(path, GetRootPath()))
890 m_history.ClearPathHistory();
892 int iWindow = GetID();
893 int showLabel = 0;
894 if (URIUtils::PathEquals(path, GetRootPath()))
896 if (iWindow == WINDOW_PICTURES)
897 showLabel = 997;
898 else if (iWindow == WINDOW_FILES)
899 showLabel = 1026;
900 else if (iWindow == WINDOW_GAMES)
901 showLabel = 35250; // "Add games..."
903 if (m_vecItems->IsPath("sources://video/"))
904 showLabel = 999;
905 else if (m_vecItems->IsPath("sources://music/"))
906 showLabel = 998;
907 else if (m_vecItems->IsPath("sources://pictures/"))
908 showLabel = 997;
909 else if (m_vecItems->IsPath("sources://files/"))
910 showLabel = 1026;
911 else if (m_vecItems->IsPath("sources://games/"))
912 showLabel = 35250; // "Add games..."
913 // Add 'Add source ' item
914 if (showLabel && (m_vecItems->Size() == 0 || !m_guiState->DisableAddSourceButtons()) &&
915 iWindow != WINDOW_MUSIC_PLAYLIST_EDITOR)
917 const std::string& strLabel = g_localizeStrings.Get(showLabel);
918 CFileItemPtr pItem(new CFileItem(strLabel));
919 pItem->SetPath("add");
920 pItem->SetArt("icon", "DefaultAddSource.png");
921 pItem->SetLabel(strLabel);
922 pItem->SetLabelPreformatted(true);
923 pItem->m_bIsFolder = true;
924 pItem->SetSpecialSort(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_addSourceOnTop ?
925 SortSpecialOnTop : SortSpecialOnBottom);
926 m_vecItems->Add(pItem);
928 m_iLastControl = GetFocusedControlID();
930 // Check whether to enabled advanced filtering based on the content type
931 m_canFilterAdvanced = CheckFilterAdvanced(*m_vecItems);
932 if (m_canFilterAdvanced)
933 m_filter.SetType(m_vecItems->GetContent());
935 // Ask the derived class if it wants to load additional info
936 // for the fileitems like media info or additional
937 // filtering on the items, setting thumbs.
938 OnPrepareFileItems(*m_vecItems);
940 m_vecItems->FillInDefaultIcons();
942 // remember the original (untouched) list of items (for filtering etc)
943 m_unfilteredItems->Assign(*m_vecItems);
945 // Cache the list of items if possible
946 OnCacheFileItems(*m_vecItems);
948 // Filter and group the items if necessary
949 OnFilterItems(GetProperty("filter").asString());
950 UpdateButtons();
952 // Restore selected item from history
953 RestoreSelectedItemFromHistory();
955 m_history.AddPath(m_vecItems->GetPath(), m_strFilterPath);
957 //m_history.DumpPathHistory();
959 return true;
962 bool CGUIMediaWindow::Refresh(bool clearCache /* = false */)
964 std::string strCurrentDirectory = m_vecItems->GetPath();
965 if (strCurrentDirectory == "?")
966 return false;
968 if (clearCache)
969 m_vecItems->RemoveDiscCache(GetID());
971 bool ret = true;
973 // get the original number of items
974 if (!Update(strCurrentDirectory, false))
976 ret = false;
979 return ret;
983 * \brief On prepare file items
985 * This function will be called by Update() before the labels of the fileitems
986 * are formatted.
988 * \note Override this function to set custom thumbs or load additional media
989 * info.
991 * It's used to load tag info for music.
993 void CGUIMediaWindow::OnPrepareFileItems(CFileItemList &items)
995 CFileItemListModification::GetInstance().Modify(items);
999 * \brief On cache file items
1001 * This function will be called by Update() before
1002 * any additional formatting, filtering or sorting is applied.
1004 * \note Override this function to define a custom caching behaviour.
1006 void CGUIMediaWindow::OnCacheFileItems(CFileItemList &items)
1008 // Should these items be saved to the hdd
1009 if (items.CacheToDiscAlways() && !IsFiltered())
1010 items.Save(GetID());
1014 * \brief On click
1016 * With this function you can react on a users click in the list/thumb panel.
1017 * It returns true, if the click is handled.
1018 * This function calls OnPlayMedia()
1020 bool CGUIMediaWindow::OnClick(int iItem, const std::string &player)
1022 if (iItem < 0 || iItem >= m_vecItems->Size())
1023 return true;
1025 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
1027 CFileItemPtr pItem = m_vecItems->Get(iItem);
1029 if (pItem->IsParentFolder())
1031 GoParentFolder();
1032 return true;
1035 if (pItem->GetPath() == "add" || pItem->GetPath() == "sources://add/") // 'add source button' in empty root
1037 if (profileManager->IsMasterProfile())
1039 if (!g_passwordManager.IsMasterLockUnlocked(true))
1040 return false;
1042 else if (!profileManager->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked())
1043 return false;
1045 if (OnAddMediaSource())
1046 Refresh(true);
1048 return true;
1051 if (!pItem->m_bIsFolder && pItem->IsFileFolder(FileFolderType::MASK_ONCLICK))
1053 XFILE::IFileDirectory *pFileDirectory = nullptr;
1054 pFileDirectory = XFILE::CFileDirectoryFactory::Create(pItem->GetURL(), pItem.get(), "");
1055 if(pFileDirectory)
1056 pItem->m_bIsFolder = true;
1057 else if(pItem->m_bIsFolder)
1058 pItem->m_bIsFolder = false;
1059 delete pFileDirectory;
1062 if (pItem->IsScript())
1064 // execute the script
1065 CURL url(pItem->GetPath());
1066 AddonPtr addon;
1067 if (CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, AddonType::SCRIPT,
1068 OnlyEnabled::CHOICE_YES))
1070 if (!CScriptInvocationManager::GetInstance().Stop(addon->LibPath()))
1072 CServiceBroker::GetAddonMgr().UpdateLastUsed(addon->ID());
1073 CScriptInvocationManager::GetInstance().ExecuteAsync(addon->LibPath(), addon);
1075 return true;
1079 if (pItem->m_bIsFolder)
1081 if ( pItem->m_bIsShareOrDrive )
1083 const std::string& strLockType=m_guiState->GetLockType();
1084 if (profileManager->GetMasterProfile().getLockMode() != LockMode::EVERYONE)
1085 if (!strLockType.empty() && !g_passwordManager.IsItemUnlocked(pItem.get(), strLockType))
1086 return true;
1088 if (!HaveDiscOrConnection(pItem->GetPath(), pItem->m_iDriveType))
1089 return true;
1092 // check for the partymode playlist items - they may not exist yet
1093 if ((pItem->GetPath() == profileManager->GetUserDataItem("PartyMode.xsp")) ||
1094 (pItem->GetPath() == profileManager->GetUserDataItem("PartyMode-Video.xsp")))
1096 // party mode playlist item - if it doesn't exist, prompt for user to define it
1097 if (!CFileUtils::Exists(pItem->GetPath()))
1099 m_vecItems->RemoveDiscCache(GetID());
1100 if (CGUIDialogSmartPlaylistEditor::EditPlaylist(pItem->GetPath()))
1101 Refresh();
1102 return true;
1106 // remove the directory cache if the folder is not normally cached
1107 CFileItemList items(pItem->GetPath());
1108 if (!items.AlwaysCache())
1109 items.RemoveDiscCache(GetID());
1111 // if we have a filtered list, we need to add the filtered
1112 // path to be able to come back to the filtered view
1113 std::string strCurrentDirectory = m_vecItems->GetPath();
1114 if (m_canFilterAdvanced && !m_filter.IsEmpty() &&
1115 !URIUtils::PathEquals(m_strFilterPath, strCurrentDirectory))
1117 m_history.RemoveParentPath();
1118 m_history.AddPath(strCurrentDirectory, m_strFilterPath);
1121 if (m_vecItemsUpdating)
1123 CLog::Log(LOGWARNING, "CGUIMediaWindow::OnClick - updating in progress");
1124 return true;
1126 CUpdateGuard ug(m_vecItemsUpdating);
1128 CFileItem directory(*pItem);
1129 if (!Update(directory.GetPath()))
1130 ShowShareErrorMessage(&directory);
1132 return true;
1134 else if (pItem->IsPlugin() && !pItem->GetProperty("isplayable").asBoolean())
1136 bool resume = pItem->GetStartOffset() == STARTOFFSET_RESUME;
1137 return XFILE::CPluginDirectory::RunScriptWithParams(pItem->GetPath(), resume);
1139 #if defined(TARGET_ANDROID)
1140 else if (pItem->IsAndroidApp())
1142 std::string appName = URIUtils::GetFileName(pItem->GetPath());
1143 CLog::Log(LOGDEBUG, "CGUIMediaWindow::OnClick Trying to run: {}", appName);
1144 return CXBMCApp::StartActivity(appName);
1146 #endif
1147 else
1149 SaveSelectedItemInHistory();
1151 if (pItem->GetPath() == "newplaylist://")
1153 m_vecItems->RemoveDiscCache(GetID());
1154 CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR,"newplaylist://");
1155 return true;
1157 else if (StringUtils::StartsWithNoCase(pItem->GetPath(), "newsmartplaylist://"))
1159 m_vecItems->RemoveDiscCache(GetID());
1160 if (CGUIDialogSmartPlaylistEditor::NewPlaylist(pItem->GetPath().substr(19)))
1161 Refresh();
1162 return true;
1165 bool autoplay = m_guiState.get() && m_guiState->AutoPlayNextItem();
1167 if (m_vecItems->IsPlugin())
1169 CURL url(m_vecItems->GetPath());
1170 AddonPtr addon;
1171 if (CServiceBroker::GetAddonMgr().GetAddon(url.GetHostName(), addon, OnlyEnabled::CHOICE_YES))
1173 const auto plugin = std::dynamic_pointer_cast<CPluginSource>(addon);
1174 if (plugin && plugin->Provides(CPluginSource::AUDIO))
1176 CFileItemList items;
1177 std::unique_ptr<CGUIViewState> state(CGUIViewState::GetViewState(GetID(), items));
1178 autoplay = state.get() && state->AutoPlayNextItem();
1183 if (autoplay && !g_partyModeManager.IsEnabled())
1185 return OnPlayAndQueueMedia(pItem, player);
1187 else
1189 return OnPlayMedia(iItem, player);
1193 return false;
1196 bool CGUIMediaWindow::OnSelect(int item)
1198 return OnClick(item);
1202 * \brief Check disc or connection present
1204 * Checks if there is a disc in the dvd drive and whether the
1205 * network is connected or not.
1207 bool CGUIMediaWindow::HaveDiscOrConnection(const std::string& strPath, SourceType iDriveType)
1209 if (iDriveType == SourceType::OPTICAL_DISC)
1211 if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath))
1213 HELPERS::ShowOKDialogText(CVariant{218}, CVariant{219});
1214 return false;
1217 else if (iDriveType == SourceType::REMOTE)
1219 //! @todo Handle not connected to a remote share
1220 if (!CServiceBroker::GetNetwork().IsConnected())
1222 HELPERS::ShowOKDialogText(CVariant{220}, CVariant{221});
1223 return false;
1227 return true;
1231 * \brief Shows a standard error message for a given pItem.
1233 void CGUIMediaWindow::ShowShareErrorMessage(CFileItem* pItem) const
1235 if (!pItem->m_bIsShareOrDrive)
1236 return;
1238 int idMessageText = 0;
1239 CURL url(pItem->GetPath());
1241 if (url.IsProtocol("smb") && url.GetHostName().empty()) // smb workgroup
1242 idMessageText = 15303; // Workgroup not found
1243 else if (pItem->m_iDriveType == SourceType::REMOTE || URIUtils::IsRemote(pItem->GetPath()))
1244 idMessageText = 15301; // Could not connect to network server
1245 else
1246 idMessageText = 15300; // Path not found or invalid
1248 HELPERS::ShowOKDialogText(CVariant{220}, CVariant{idMessageText});
1252 * \brief Go one directory up on list items
1254 * The function goes up one level in the directory tree
1256 bool CGUIMediaWindow::GoParentFolder()
1258 if (m_vecItems->IsVirtualDirectoryRoot())
1259 return false;
1261 if (URIUtils::PathEquals(m_vecItems->GetPath(), GetRootPath()))
1262 return false;
1264 //m_history.DumpPathHistory();
1266 const std::string currentPath = m_vecItems->GetPath();
1267 std::string parentPath = m_history.GetParentPath();
1268 // Check if a) the current folder is on the stack more than once, (parent is
1269 // often same as current), OR
1270 // b) the parent is an xml file (happens when ActivateWindow() called with
1271 // a node file) and so current path is the result of expanding the xml.
1272 // Keep going until there's nothing left or they dont match anymore.
1273 while (!parentPath.empty() &&
1274 (URIUtils::PathEquals(parentPath, currentPath, true) ||
1275 StringUtils::EndsWith(parentPath, ".xml/") || StringUtils::EndsWith(parentPath, ".xml")))
1277 m_history.RemoveParentPath();
1278 parentPath = m_history.GetParentPath();
1281 // remove the current filter but only if the parent
1282 // item doesn't have a filter as well
1283 CURL filterUrl(m_strFilterPath);
1284 if (filterUrl.HasOption("filter"))
1286 CURL parentUrl(m_history.GetParentPath(true));
1287 if (!parentUrl.HasOption("filter"))
1289 // we need to overwrite m_strFilterPath because
1290 // Refresh() will set updateFilterPath to false
1291 m_strFilterPath.clear();
1292 Refresh();
1293 return true;
1297 // pop directory path from the stack
1298 m_strFilterPath = m_history.GetParentPath(true);
1299 m_history.RemoveParentPath();
1301 if (!Update(parentPath, false))
1302 return false;
1304 // No items to show so go another level up
1305 if (!m_vecItems->GetPath().empty() && (m_filter.IsEmpty() ? m_vecItems->Size() : m_unfilteredItems->Size()) <= 0)
1307 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(2080), g_localizeStrings.Get(2081));
1308 return GoParentFolder();
1310 return true;
1313 void CGUIMediaWindow::SaveSelectedItemInHistory()
1315 int iItem = m_viewControl.GetSelectedItem();
1316 std::string strSelectedItem;
1317 if (iItem >= 0 && iItem < m_vecItems->Size())
1319 CFileItemPtr pItem = m_vecItems->Get(iItem);
1320 GetDirectoryHistoryString(pItem.get(), strSelectedItem);
1323 m_history.SetSelectedItem(strSelectedItem, m_vecItems->GetPath(), iItem);
1326 void CGUIMediaWindow::RestoreSelectedItemFromHistory()
1328 std::string strSelectedItem = m_history.GetSelectedItem(m_vecItems->GetPath());
1330 if (!strSelectedItem.empty())
1332 for (int i = 0; i < m_vecItems->Size(); ++i)
1334 CFileItemPtr pItem = m_vecItems->Get(i);
1335 std::string strHistory;
1336 GetDirectoryHistoryString(pItem.get(), strHistory);
1337 // set selected item if equals with history
1338 if (strHistory == strSelectedItem)
1340 m_viewControl.SetSelectedItem(i);
1341 return;
1346 // Exact item not found - maybe deleted, watched status change, filtered out, ...
1347 // Attempt to restore the position of the selection
1348 int selectedItemIndex = m_history.GetSelectedItemIndex(m_vecItems->GetPath());
1349 if (selectedItemIndex >= 0 && m_vecItems->Size() > 0)
1351 int newIndex = std::min(selectedItemIndex, m_vecItems->Size() - 1);
1352 m_viewControl.SetSelectedItem(newIndex);
1353 return;
1356 // Fallback: select the first item
1357 m_viewControl.SetSelectedItem(0);
1361 * \brief Get history string for given file item
1363 * \note Override the function to change the default behavior on how
1364 * a selected item history should look like
1366 void CGUIMediaWindow::GetDirectoryHistoryString(const CFileItem* pItem, std::string& strHistoryString) const
1368 if (pItem->m_bIsShareOrDrive)
1370 // We are in the virtual directory
1372 // History string of the DVD drive
1373 // must be handled separately
1374 if (pItem->m_iDriveType == SourceType::OPTICAL_DISC)
1376 // Remove disc label from item label
1377 // and use as history string, m_strPath
1378 // can change for new discs
1379 std::string strLabel = pItem->GetLabel();
1380 size_t nPosOpen = strLabel.find('(');
1381 size_t nPosClose = strLabel.rfind(')');
1382 if (nPosOpen != std::string::npos &&
1383 nPosClose != std::string::npos &&
1384 nPosClose > nPosOpen)
1386 strLabel.erase(nPosOpen + 1, (nPosClose) - (nPosOpen + 1));
1387 strHistoryString = strLabel;
1389 else
1390 strHistoryString = strLabel;
1392 else
1394 // Other items in virtual directory
1395 std::string strPath = pItem->GetPath();
1396 URIUtils::RemoveSlashAtEnd(strPath);
1398 strHistoryString = pItem->GetLabel() + strPath;
1401 else if (pItem->GetEndOffset() > pItem->GetStartOffset() &&
1402 pItem->GetStartOffset() != STARTOFFSET_RESUME)
1404 // Could be a cue item, all items of a cue share the same filename
1405 // so add the offsets to build the history string
1406 strHistoryString = StringUtils::Format("{}{}", pItem->GetStartOffset(), pItem->GetEndOffset());
1407 strHistoryString += pItem->GetPath();
1409 else
1411 // Normal directory items
1412 strHistoryString = pItem->GetPath();
1415 // remove any filter
1416 if (CanContainFilter(strHistoryString))
1417 strHistoryString = RemoveParameterFromPath(strHistoryString, "filter");
1419 URIUtils::RemoveSlashAtEnd(strHistoryString);
1420 StringUtils::ToLower(strHistoryString);
1424 * \brief Set history for path
1426 * Call this function to create a directory history for the
1427 * path given by strDirectory.
1429 void CGUIMediaWindow::SetHistoryForPath(const std::string& strDirectory)
1431 // Make sure our shares are configured
1432 SetupShares();
1433 if (!strDirectory.empty())
1435 // Build the directory history for default path
1436 std::string strPath, strParentPath;
1437 strPath = strDirectory;
1438 URIUtils::RemoveSlashAtEnd(strPath);
1440 CFileItemList items;
1441 CURL url;
1442 GetDirectoryItems(url, items, UseFileDirectories());
1444 m_history.ClearPathHistory();
1446 bool originalPath = true;
1447 while (URIUtils::GetParentPath(strPath, strParentPath))
1449 for (int i = 0; i < items.Size(); ++i)
1451 CFileItemPtr pItem = items[i];
1452 std::string path(pItem->GetPath());
1453 URIUtils::RemoveSlashAtEnd(path);
1454 if (URIUtils::PathEquals(path, strPath))
1456 std::string strHistory;
1457 GetDirectoryHistoryString(pItem.get(), strHistory);
1458 m_history.SetSelectedItem(strHistory, "");
1459 URIUtils::AddSlashAtEnd(strPath);
1460 m_history.AddPathFront(strPath);
1461 m_history.AddPathFront("");
1463 //m_history.DumpPathHistory();
1464 return ;
1468 if (URIUtils::IsVideoDb(strPath))
1470 CURL url(strParentPath);
1471 url.SetOptions(""); // clear any URL options from recreated parent path
1472 strParentPath = url.Get();
1475 // set the original path exactly as it was passed in
1476 if (URIUtils::PathEquals(strPath, strDirectory, true))
1477 strPath = strDirectory;
1478 else
1479 URIUtils::AddSlashAtEnd(strPath);
1481 m_history.AddPathFront(strPath, originalPath ? m_strFilterPath : "");
1482 m_history.SetSelectedItem(strPath, strParentPath);
1483 originalPath = false;
1484 strPath = strParentPath;
1485 URIUtils::RemoveSlashAtEnd(strPath);
1488 else
1489 m_history.ClearPathHistory();
1491 //m_history.DumpPathHistory();
1495 * \brief On media play
1497 * \note Override if you want to change the default behavior, what is done
1498 * when the user clicks on a file.
1500 * This function is called by OnClick()
1502 bool CGUIMediaWindow::OnPlayMedia(int iItem, const std::string &player)
1504 // Reset Playlistplayer, playback started now does
1505 // not use the playlistplayer.
1506 CServiceBroker::GetPlaylistPlayer().Reset();
1507 CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::Id::TYPE_NONE);
1508 CFileItemPtr pItem=m_vecItems->Get(iItem);
1510 CLog::Log(LOGDEBUG, "{} {}", __FUNCTION__, CURL::GetRedacted(pItem->GetPath()));
1512 bool bResult = false;
1513 if (NETWORK::IsInternetStream(*pItem) || PLAYLIST::IsPlayList(*pItem))
1514 bResult = g_application.PlayMedia(*pItem, player, m_guiState->GetPlaylist());
1515 else
1516 bResult = g_application.PlayFile(*pItem, player);
1518 if (pItem->GetStartOffset() == STARTOFFSET_RESUME)
1519 pItem->SetStartOffset(0);
1521 return bResult;
1525 * \brief On play and media queue
1527 * \note Override if you want to change the default behavior of what is done
1528 * when the user clicks on a file in a "folder" with similar files.
1530 * This function is called by OnClick()
1532 bool CGUIMediaWindow::OnPlayAndQueueMedia(const CFileItemPtr& item, const std::string& player)
1534 //play and add current directory to temporary playlist
1535 PLAYLIST::Id playlistId = m_guiState->GetPlaylist();
1536 if (playlistId != PLAYLIST::Id::TYPE_NONE)
1538 // Remove ZIP, RAR files and folders
1539 CFileItemList playlist;
1540 playlist.Copy(*m_vecItems, true);
1541 playlist.erase(std::remove_if(playlist.begin(), playlist.end(),
1542 [](const std::shared_ptr<CFileItem>& i)
1543 { return i->IsZIP() || i->IsRAR() || i->m_bIsFolder; }),
1544 playlist.end());
1546 // Chosen item
1547 int mediaToPlay =
1548 std::distance(playlist.begin(), std::find_if(playlist.begin(), playlist.end(),
1549 [&item](const std::shared_ptr<CFileItem>& i)
1550 { return i->GetPath() == item->GetPath(); }));
1551 /* For .mka albums, all tracks are in the same file so using path as above will always play the
1552 * first track. Use the track and disk number to ensure we start playback on the correct track.
1553 * This only applies to mka or m4b items played back via files view. Music library takes
1554 * a different play path.
1556 if (MUSIC::IsAudioBook(*item))
1557 mediaToPlay = std::distance(
1558 playlist.begin(), std::find_if(playlist.begin(), playlist.end(),
1559 [&item](const std::shared_ptr<CFileItem>& i)
1561 return i->GetMusicInfoTag()->GetTrackAndDiscNumber() ==
1562 item->GetMusicInfoTag()->GetTrackAndDiscNumber();
1563 }));
1565 // Add to playlist
1566 CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId);
1567 CServiceBroker::GetPlaylistPlayer().Reset();
1568 CServiceBroker::GetPlaylistPlayer().Add(playlistId, playlist);
1570 // Save current window and directory to know where the selected item was
1571 if (m_guiState)
1572 m_guiState->SetPlaylistDirectory(m_vecItems->GetPath());
1574 // figure out where we start playback
1575 if (CServiceBroker::GetPlaylistPlayer().IsShuffled(playlistId))
1577 int iIndex =
1578 CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).FindOrder(mediaToPlay);
1579 CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).Swap(0, iIndex);
1580 mediaToPlay = 0;
1583 // play
1584 CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(playlistId);
1585 CServiceBroker::GetPlaylistPlayer().Play(mediaToPlay, player);
1587 return true;
1591 * \brief Update file list
1593 * Synchronize the fileitems with the playlistplayer
1594 * also recreates the playlist of the playlistplayer based
1595 * on the fileitems of the window
1597 void CGUIMediaWindow::UpdateFileList()
1599 int nItem = m_viewControl.GetSelectedItem();
1600 std::string strSelected;
1601 if (nItem >= 0)
1602 strSelected = m_vecItems->Get(nItem)->GetPath();
1604 FormatAndSort(*m_vecItems);
1605 UpdateButtons();
1607 m_viewControl.SetItems(*m_vecItems);
1608 m_viewControl.SetSelectedItem(strSelected);
1610 // set the currently playing item as selected, if its in this directory
1611 if (m_guiState.get() && m_guiState->IsCurrentPlaylistDirectory(m_vecItems->GetPath()))
1613 PLAYLIST::Id playlistId = m_guiState->GetPlaylist();
1614 int nSong = CServiceBroker::GetPlaylistPlayer().GetCurrentItemIdx();
1615 CFileItem playlistItem;
1616 if (nSong > -1 && playlistId != PLAYLIST::Id::TYPE_NONE)
1617 playlistItem = *CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId)[nSong];
1619 CServiceBroker::GetPlaylistPlayer().ClearPlaylist(playlistId);
1620 CServiceBroker::GetPlaylistPlayer().Reset();
1622 for (int i = 0; i < m_vecItems->Size(); i++)
1624 CFileItemPtr pItem = m_vecItems->Get(i);
1625 if (pItem->m_bIsFolder)
1626 continue;
1628 if (!PLAYLIST::IsPlayList(*pItem) && !pItem->IsZIP() && !pItem->IsRAR())
1629 CServiceBroker::GetPlaylistPlayer().Add(playlistId, pItem);
1631 if (pItem->GetPath() == playlistItem.GetPath() &&
1632 pItem->GetStartOffset() == playlistItem.GetStartOffset())
1633 CServiceBroker::GetPlaylistPlayer().SetCurrentItemIdx(
1634 CServiceBroker::GetPlaylistPlayer().GetPlaylist(playlistId).size() - 1);
1639 void CGUIMediaWindow::OnDeleteItem(int iItem)
1641 if ( iItem < 0 || iItem >= m_vecItems->Size()) return;
1642 CFileItemPtr item = m_vecItems->Get(iItem);
1644 if (PLAYLIST::IsPlayList(*item))
1645 item->m_bIsFolder = false;
1647 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
1649 if (profileManager->GetCurrentProfile().getLockMode() != LockMode::EVERYONE &&
1650 profileManager->GetCurrentProfile().filesLocked())
1652 if (!g_passwordManager.IsMasterLockUnlocked(true))
1653 return;
1656 if (!CFileUtils::DeleteItemWithConfirm(item))
1657 return;
1659 Refresh(true);
1660 m_viewControl.SetSelectedItem(iItem);
1663 void CGUIMediaWindow::OnRenameItem(int iItem)
1665 if (iItem < 0 || iItem >= m_vecItems->Size())
1666 return;
1668 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
1670 if (profileManager->GetCurrentProfile().getLockMode() != LockMode::EVERYONE &&
1671 profileManager->GetCurrentProfile().filesLocked())
1673 if (!g_passwordManager.IsMasterLockUnlocked(true))
1674 return;
1677 if (!CFileUtils::RenameFile(m_vecItems->Get(iItem)->GetPath()))
1678 return;
1680 Refresh(true);
1681 m_viewControl.SetSelectedItem(iItem);
1684 void CGUIMediaWindow::OnInitWindow()
1686 // initial fetch is done unthreaded to ensure the items are setup prior to skin animations kicking off
1687 m_backgroundLoad = false;
1689 // the start directory may change during Refresh
1690 bool updateStartDirectory = URIUtils::PathEquals(m_vecItems->GetPath(), m_startDirectory, true);
1692 // we have python scripts hooked in everywhere :(
1693 // those scripts may open windows and we can't open a window
1694 // while opening this one.
1695 // for plugin sources delay call to Refresh
1696 if (!URIUtils::IsPlugin(m_vecItems->GetPath()))
1698 Refresh();
1700 else
1702 CGUIMessage msg(GUI_MSG_WINDOW_INIT, 0, 0, WINDOW_INVALID, PLUGIN_REFRESH_DELAY);
1703 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, GetID());
1706 if (updateStartDirectory)
1708 // reset the start directory to the path of the items
1709 m_startDirectory = m_vecItems->GetPath();
1711 // reset the history based on the path of the items
1712 SetHistoryForPath(m_startDirectory);
1715 m_backgroundLoad = true;
1717 CGUIWindow::OnInitWindow();
1720 void CGUIMediaWindow::SaveControlStates()
1722 CGUIWindow::SaveControlStates();
1723 SaveSelectedItemInHistory();
1726 void CGUIMediaWindow::RestoreControlStates()
1728 CGUIWindow::RestoreControlStates();
1729 RestoreSelectedItemFromHistory();
1732 CGUIControl *CGUIMediaWindow::GetFirstFocusableControl(int id)
1734 if (m_viewControl.HasControl(id))
1735 id = m_viewControl.GetCurrentControl();
1736 return CGUIWindow::GetFirstFocusableControl(id);
1739 void CGUIMediaWindow::SetupShares()
1741 // Setup shares and filemasks for this window
1742 CFileItemList items;
1743 CGUIViewState* viewState=CGUIViewState::GetViewState(GetID(), items);
1744 if (viewState)
1746 m_rootDir.SetMask(viewState->GetExtensions());
1747 m_rootDir.SetSources(viewState->GetSources());
1748 delete viewState;
1752 bool CGUIMediaWindow::OnPopupMenu(int itemIdx)
1754 auto InRange = [](size_t i, std::pair<size_t, size_t> range){ return i >= range.first && i < range.second; };
1756 if (itemIdx < 0 || itemIdx >= m_vecItems->Size())
1757 return false;
1759 auto item = m_vecItems->Get(itemIdx);
1760 if (!item)
1761 return false;
1763 CContextButtons buttons;
1765 //Add items from plugin
1767 int i = 0;
1768 while (item->HasProperty(StringUtils::Format("contextmenulabel({})", i)))
1770 buttons.emplace_back(
1771 ~buttons.size(),
1772 item->GetProperty(StringUtils::Format("contextmenulabel({})", i)).asString());
1773 ++i;
1776 auto pluginMenuRange = std::make_pair(static_cast<size_t>(0), buttons.size());
1778 //Add the global menu
1779 auto globalMenu = CServiceBroker::GetContextMenuManager().GetItems(*item, CContextMenuManager::MAIN);
1780 auto globalMenuRange = std::make_pair(buttons.size(), buttons.size() + globalMenu.size());
1781 for (const auto& menu : globalMenu)
1782 buttons.emplace_back(~buttons.size(), menu->GetLabel(*item));
1784 //Add legacy items from windows
1785 auto buttonsSize = buttons.size();
1786 GetContextButtons(itemIdx, buttons);
1787 auto windowMenuRange = std::make_pair(buttonsSize, buttons.size());
1789 //Add addon menus
1790 auto addonMenu = CServiceBroker::GetContextMenuManager().GetAddonItems(*item, CContextMenuManager::MAIN);
1791 auto addonMenuRange = std::make_pair(buttons.size(), buttons.size() + addonMenu.size());
1792 for (const auto& menu : addonMenu)
1793 buttons.emplace_back(~buttons.size(), menu->GetLabel(*item));
1795 if (buttons.empty())
1796 return true;
1798 int idx = CGUIDialogContextMenu::Show(buttons);
1799 if (idx < 0 || idx >= static_cast<int>(buttons.size()))
1800 return false;
1802 if (InRange(static_cast<size_t>(idx), pluginMenuRange))
1804 bool saveVal = m_backgroundLoad;
1805 m_backgroundLoad = false;
1806 CServiceBroker::GetAppMessenger()->SendMsg(
1807 TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr,
1808 item->GetProperty(StringUtils::Format("contextmenuaction({})", idx - pluginMenuRange.first))
1809 .asString());
1810 m_backgroundLoad = saveVal;
1811 return true;
1814 if (InRange(idx, windowMenuRange))
1815 return OnContextButton(itemIdx, static_cast<CONTEXT_BUTTON>(buttons[idx].first));
1817 if (InRange(idx, globalMenuRange))
1818 return CONTEXTMENU::LoopFrom(*globalMenu[idx - globalMenuRange.first], item);
1820 return CONTEXTMENU::LoopFrom(*addonMenu[idx - addonMenuRange.first], item);
1823 const CGUIViewState *CGUIMediaWindow::GetViewState() const
1825 return m_guiState.get();
1828 const CFileItemList& CGUIMediaWindow::CurrentDirectory() const
1830 return *m_vecItems;
1833 bool CGUIMediaWindow::WaitForNetwork() const
1835 if (CServiceBroker::GetNetwork().IsAvailable())
1836 return true;
1838 CGUIDialogProgress *progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
1839 if (!progress)
1840 return true;
1842 CURL url(m_vecItems->GetPath());
1843 progress->SetHeading(CVariant{1040}); // Loading Directory
1844 progress->SetLine(1, CVariant{url.GetWithoutUserDetails()});
1845 progress->ShowProgressBar(false);
1846 progress->Open();
1847 while (!CServiceBroker::GetNetwork().IsAvailable())
1849 progress->Progress();
1850 if (progress->IsCanceled())
1852 progress->Close();
1853 return false;
1856 progress->Close();
1857 return true;
1860 void CGUIMediaWindow::UpdateFilterPath(const std::string &strDirectory, const CFileItemList &items, bool updateFilterPath)
1862 bool canfilter = CanContainFilter(strDirectory);
1864 std::string filter;
1865 CURL url(strDirectory);
1866 if (canfilter && url.HasOption("filter"))
1867 filter = url.GetOption("filter");
1869 // only set the filter path if it hasn't been marked
1870 // as preset or if it's empty
1871 if (updateFilterPath || m_strFilterPath.empty())
1873 if (items.HasProperty(PROPERTY_PATH_DB))
1874 m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString();
1875 else
1876 m_strFilterPath = items.GetPath();
1879 // maybe the filter path can contain a filter
1880 if (!canfilter && CanContainFilter(m_strFilterPath))
1881 canfilter = true;
1883 // check if the filter path contains a filter
1884 CURL filterPathUrl(m_strFilterPath);
1885 if (canfilter && filter.empty())
1887 if (filterPathUrl.HasOption("filter"))
1888 filter = filterPathUrl.GetOption("filter");
1891 // check if there is a filter and re-apply it
1892 if (canfilter && !filter.empty())
1894 if (!m_filter.LoadFromJson(filter))
1896 CLog::Log(LOGWARNING,
1897 "CGUIMediaWindow::UpdateFilterPath(): unable to load existing filter ({})", filter);
1898 m_filter.Reset();
1899 m_strFilterPath = m_vecItems->GetPath();
1901 else
1903 // add the filter to the filter path
1904 filterPathUrl.SetOption("filter", filter);
1905 m_strFilterPath = filterPathUrl.Get();
1910 void CGUIMediaWindow::OnFilterItems(const std::string &filter)
1912 m_viewControl.Clear();
1914 CFileItemList items;
1915 items.Copy(*m_vecItems, false); // use the original path - it'll likely be relied on for other things later.
1916 items.Append(*m_unfilteredItems);
1917 bool filtered = GetFilteredItems(filter, items);
1919 m_vecItems->ClearItems();
1920 // we need to clear the sort state and re-sort the items
1921 m_vecItems->ClearSortState();
1922 m_vecItems->Append(items);
1924 // if the filter has changed, get the new filter path
1925 if (filtered && m_canFilterAdvanced)
1927 if (items.HasProperty(PROPERTY_PATH_DB))
1928 m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString();
1929 // only set m_strFilterPath if it hasn't been set before
1930 // otherwise we might overwrite it with a non-filter path
1931 // in case GetFilteredItems() returns true even though no
1932 // db-based filter (e.g. watched filter) has been applied
1933 else if (m_strFilterPath.empty())
1934 m_strFilterPath = items.GetPath();
1937 GetGroupedItems(*m_vecItems);
1938 FormatAndSort(*m_vecItems);
1940 CFileItemPtr currentItem;
1941 std::string currentItemPath;
1942 int item = m_viewControl.GetSelectedItem();
1943 if (item >= 0 && item < m_vecItems->Size())
1945 currentItem = m_vecItems->Get(item);
1946 currentItemPath = currentItem->GetPath();
1949 // get the "filter" option
1950 std::string filterOption;
1951 CURL filterUrl(m_strFilterPath);
1952 if (filterUrl.HasOption("filter"))
1953 filterOption = filterUrl.GetOption("filter");
1955 // apply the "filter" option to any folder item so that
1956 // the filter can be passed down to the sub-directory
1957 for (int index = 0; index < m_vecItems->Size(); index++)
1959 CFileItemPtr pItem = m_vecItems->Get(index);
1960 // if the item is a folder we need to copy the path of
1961 // the filtered item to be able to keep the applied filters
1962 if (pItem->m_bIsFolder)
1964 CURL itemUrl(pItem->GetPath());
1965 if (!filterOption.empty())
1966 itemUrl.SetOption("filter", filterOption);
1967 else
1968 itemUrl.RemoveOption("filter");
1969 pItem->SetPath(itemUrl.Get());
1973 SetProperty("filter", filter);
1974 if (filtered && m_canFilterAdvanced)
1976 // to be able to select the same item as before we need to adjust
1977 // the path of the item i.e. add or remove the "filter=" URL option
1978 // but that's only necessary for folder items
1979 if (currentItem.get() && currentItem->m_bIsFolder)
1981 CURL curUrl(currentItemPath), newUrl(m_strFilterPath);
1982 if (newUrl.HasOption("filter"))
1983 curUrl.SetOption("filter", newUrl.GetOption("filter"));
1984 else if (curUrl.HasOption("filter"))
1985 curUrl.RemoveOption("filter");
1987 currentItemPath = curUrl.Get();
1991 // The idea here is to ensure we have something to focus if our file list
1992 // is empty. As such, this check MUST be last and ignore the hide parent
1993 // fileitems settings.
1994 if (m_vecItems->IsEmpty())
1996 CFileItemPtr pItem(new CFileItem(".."));
1997 pItem->SetPath(m_history.GetParentPath());
1998 pItem->m_bIsFolder = true;
1999 pItem->m_bIsShareOrDrive = false;
2000 m_vecItems->AddFront(pItem, 0);
2003 // and update our view control + buttons
2004 m_viewControl.SetItems(*m_vecItems);
2005 m_viewControl.SetSelectedItem(currentItemPath);
2008 bool CGUIMediaWindow::GetFilteredItems(const std::string &filter, CFileItemList &items)
2010 bool result = false;
2011 if (m_canFilterAdvanced)
2012 result = GetAdvanceFilteredItems(items);
2014 std::string trimmedFilter(filter);
2015 StringUtils::TrimLeft(trimmedFilter);
2016 StringUtils::ToLower(trimmedFilter);
2018 if (trimmedFilter.empty())
2019 return result;
2021 CFileItemList filteredItems(items.GetPath()); // use the original path - it'll likely be relied on for other things later.
2022 bool numericMatch = StringUtils::IsNaturalNumber(trimmedFilter);
2023 for (int i = 0; i < items.Size(); i++)
2025 CFileItemPtr item = items.Get(i);
2026 if (item->IsParentFolder())
2028 filteredItems.Add(item);
2029 continue;
2031 //! @todo Need to update this to get all labels, ideally out of the displayed info (ie from m_layout and m_focusedLayout)
2032 //! though that isn't practical. Perhaps a better idea would be to just grab the info that we should filter on based on
2033 //! where we are in the library tree.
2034 //! Another idea is tying the filter string to the current level of the tree, so that going deeper disables the filter,
2035 //! but it's re-enabled on the way back out.
2036 std::string match;
2037 /* if (item->GetFocusedLayout())
2038 match = item->GetFocusedLayout()->GetAllText();
2039 else if (item->GetLayout())
2040 match = item->GetLayout()->GetAllText();
2041 else*/
2042 match = item->GetLabel(); // Filter label only for now
2044 if (numericMatch)
2045 StringUtils::WordToDigits(match);
2047 size_t pos = StringUtils::FindWords(match.c_str(), trimmedFilter.c_str());
2048 if (pos != std::string::npos)
2049 filteredItems.Add(item);
2052 items.ClearItems();
2053 items.Append(filteredItems);
2055 return items.GetObjectCount() > 0;
2058 bool CGUIMediaWindow::GetAdvanceFilteredItems(CFileItemList &items)
2060 // don't run the advanced filter if the filter is empty
2061 // and there hasn't been a filter applied before which
2062 // would have to be removed
2063 CURL url(m_strFilterPath);
2064 if (m_filter.IsEmpty() && !url.HasOption("filter"))
2065 return false;
2067 CFileItemList resultItems;
2068 XFILE::CSmartPlaylistDirectory::GetDirectory(m_filter, resultItems, m_strFilterPath, true);
2070 // put together a lookup map for faster path comparison
2071 std::map<std::string, CFileItemPtr> lookup;
2072 for (int j = 0; j < resultItems.Size(); j++)
2074 std::string itemPath = CURL(resultItems[j]->GetPath()).GetWithoutOptions();
2075 StringUtils::ToLower(itemPath);
2077 lookup[itemPath] = resultItems[j];
2080 // loop through all the original items and find
2081 // those which are still part of the filter
2082 CFileItemList filteredItems;
2083 for (int i = 0; i < items.Size(); i++)
2085 CFileItemPtr item = items.Get(i);
2086 if (item->IsParentFolder())
2088 filteredItems.Add(item);
2089 continue;
2092 // check if the item is part of the resultItems list
2093 // by comparing their paths (but ignoring any special
2094 // options because they differ from filter to filter)
2095 std::string path = CURL(item->GetPath()).GetWithoutOptions();
2096 StringUtils::ToLower(path);
2098 std::map<std::string, CFileItemPtr>::iterator itItem = lookup.find(path);
2099 if (itItem != lookup.end())
2101 // add the item to the list of filtered items
2102 filteredItems.Add(item);
2104 // remove the item from the lists
2105 resultItems.Remove(itItem->second.get());
2106 lookup.erase(itItem);
2110 if (resultItems.Size() > 0)
2111 CLog::Log(LOGWARNING, "CGUIMediaWindow::GetAdvanceFilteredItems(): {} unknown items",
2112 resultItems.Size());
2114 items.ClearItems();
2115 items.Append(filteredItems);
2116 items.SetPath(resultItems.GetPath());
2117 if (resultItems.HasProperty(PROPERTY_PATH_DB))
2118 items.SetProperty(PROPERTY_PATH_DB, resultItems.GetProperty(PROPERTY_PATH_DB));
2119 return true;
2122 bool CGUIMediaWindow::IsFiltered()
2124 return (!m_canFilterAdvanced && !GetProperty("filter").empty()) ||
2125 (m_canFilterAdvanced && !m_filter.IsEmpty());
2128 bool CGUIMediaWindow::IsSameStartFolder(const std::string &dir)
2130 const std::string startFolder = GetStartFolder(dir);
2131 return URIUtils::PathHasParent(m_vecItems->GetPath(), startFolder);
2134 bool CGUIMediaWindow::Filter(bool advanced /* = true */)
2136 // basic filtering
2137 if (!m_canFilterAdvanced || !advanced)
2139 const CGUIControl *btnFilter = GetControl(CONTROL_BTN_FILTER);
2140 if (btnFilter && btnFilter->GetControlType() == CGUIControl::GUICONTROL_EDIT)
2141 { // filter updated
2142 CGUIMessage selected(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTN_FILTER);
2143 OnMessage(selected);
2144 OnFilterItems(selected.GetLabel());
2145 UpdateButtons();
2146 return true;
2148 if (GetProperty("filter").empty())
2150 std::string filter = GetProperty("filter").asString();
2151 CGUIKeyboardFactory::ShowAndGetFilter(filter, false);
2152 SetProperty("filter", filter);
2154 else
2156 OnFilterItems("");
2157 UpdateButtons();
2160 // advanced filtering
2161 else
2162 CGUIDialogMediaFilter::ShowAndEditMediaFilter(m_strFilterPath, m_filter);
2164 return true;
2167 std::string CGUIMediaWindow::GetStartFolder(const std::string &dir)
2169 if (StringUtils::EqualsNoCase(dir, "$root") ||
2170 StringUtils::EqualsNoCase(dir, "root"))
2171 return "";
2173 // Let plugins handle their own urls themselves
2174 if (StringUtils::StartsWith(dir, "plugin://"))
2175 return dir;
2177 //! @todo This ifdef block probably belongs somewhere else. Move it to a better place!
2178 #if defined(TARGET_ANDROID)
2179 // Hack for Android items (numbered id's) on the leanback screen
2180 std::string path;
2181 std::string fileName;
2182 URIUtils::Split(dir, path, fileName);
2183 URIUtils::RemoveExtension(fileName);
2184 if (StringUtils::IsInteger(fileName))
2185 return path;
2186 #endif
2188 return dir;
2191 std::string CGUIMediaWindow::RemoveParameterFromPath(const std::string &strDirectory, const std::string &strParameter)
2193 CURL url(strDirectory);
2194 if (url.HasOption(strParameter))
2196 url.RemoveOption(strParameter);
2197 return url.Get();
2200 return strDirectory;
2203 bool CGUIMediaWindow::ProcessRenderLoop(bool renderOnly)
2205 return CServiceBroker::GetGUI()->GetWindowManager().ProcessRenderLoop(renderOnly);
2208 bool CGUIMediaWindow::GetDirectoryItems(CURL &url, CFileItemList &items, bool useDir)
2210 if (m_backgroundLoad)
2212 bool ret = true;
2213 CGetDirectoryItems getItems(m_rootDir, url, items, useDir);
2215 if (!WaitGetDirectoryItems(getItems))
2217 // cancelled
2218 ret = false;
2220 else if (!getItems.m_result)
2222 if (CServiceBroker::GetAppMessenger()->IsProcessThread() && m_rootDir.GetDirImpl() &&
2223 !m_rootDir.GetDirImpl()->ProcessRequirements())
2225 ret = false;
2227 else if (!WaitGetDirectoryItems(getItems) || !getItems.m_result)
2229 ret = false;
2233 m_updateJobActive = false;
2234 m_rootDir.ReleaseDirImpl();
2235 return ret;
2237 else
2239 return m_rootDir.GetDirectory(url, items, useDir, false);
2243 bool CGUIMediaWindow::WaitGetDirectoryItems(CGetDirectoryItems &items)
2245 bool ret = true;
2246 CGUIDialogBusy* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogBusy>(WINDOW_DIALOG_BUSY);
2247 if (dialog && !dialog->IsDialogRunning())
2249 if (!CGUIDialogBusy::Wait(&items, 100, true))
2251 // cancelled
2252 ret = false;
2255 else
2257 m_updateJobActive = true;
2258 m_updateAborted = false;
2259 m_updateEvent.Reset();
2260 CServiceBroker::GetJobManager()->Submit(
2261 [&]() {
2262 items.Run();
2263 m_updateEvent.Set();
2265 nullptr, CJob::PRIORITY_NORMAL);
2267 // Loop until either the job ended or update canceled via CGUIMediaWindow::CancelUpdateItems.
2268 while (!m_updateAborted && !m_updateEvent.Wait(1ms))
2270 if (!ProcessRenderLoop(false))
2271 break;
2274 if (m_updateAborted)
2276 CLog::LogF(LOGDEBUG, "Get directory items job was canceled.");
2277 ret = false;
2279 else if (!items.m_result)
2281 CLog::LogF(LOGDEBUG, "Get directory items job was unsuccessful.");
2282 ret = false;
2285 return ret;
2288 void CGUIMediaWindow::CancelUpdateItems()
2290 if (m_updateJobActive)
2292 m_rootDir.CancelDirectory();
2293 m_updateAborted = true;
2294 if (!m_updateEvent.Wait(5000ms))
2296 CLog::Log(LOGERROR, "CGUIMediaWindow::CancelUpdateItems - error cancel update");
2298 m_updateJobActive = false;