2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "threads/SystemClock.h"
22 #include "GUIMediaWindow.h"
23 #include "GUIUserMessages.h"
25 #include "PlayListPlayer.h"
26 #include "addons/AddonManager.h"
27 #include "addons/PluginSource.h"
28 #include "filesystem/PluginDirectory.h"
29 #include "filesystem/MultiPathDirectory.h"
30 #include "GUIPassword.h"
31 #include "Application.h"
32 #include "ApplicationMessenger.h"
33 #include "network/Network.h"
34 #include "utils/RegExp.h"
35 #include "PartyModeManager.h"
36 #include "dialogs/GUIDialogMediaSource.h"
37 #include "GUIWindowFileManager.h"
38 #include "filesystem/FavouritesDirectory.h"
39 #include "utils/LabelFormatter.h"
40 #include "dialogs/GUIDialogProgress.h"
41 #include "profiles/ProfilesManager.h"
42 #include "settings/AdvancedSettings.h"
43 #include "settings/Settings.h"
46 #include "dialogs/GUIDialogSmartPlaylistEditor.h"
47 #include "addons/GUIDialogAddonSettings.h"
48 #include "dialogs/GUIDialogYesNo.h"
49 #include "guilib/GUIWindowManager.h"
50 #include "dialogs/GUIDialogOK.h"
51 #include "playlists/PlayList.h"
52 #include "storage/MediaManager.h"
53 #include "utils/StringUtils.h"
54 #include "utils/URIUtils.h"
55 #include "guilib/Key.h"
56 #include "guilib/LocalizeStrings.h"
57 #include "utils/TimeUtils.h"
58 #include "filesystem/File.h"
59 #include "filesystem/FileDirectoryFactory.h"
60 #include "utils/log.h"
61 #include "utils/FileUtils.h"
62 #include "guilib/GUIEditControl.h"
63 #include "guilib/GUIKeyboardFactory.h"
64 #include "interfaces/Builtins.h"
65 #include "interfaces/generic/ScriptInvocationManager.h"
66 #include "dialogs/GUIDialogKaiToast.h"
67 #include "dialogs/GUIDialogMediaFilter.h"
68 #include "filesystem/SmartPlaylistDirectory.h"
69 #if defined(TARGET_ANDROID)
70 #include "xbmc/android/activity/XBMCApp.h"
72 #include "FileItemListModification.h"
74 #define CONTROL_BTNVIEWASICONS 2
75 #define CONTROL_BTNSORTBY 3
76 #define CONTROL_BTNSORTASC 4
77 #define CONTROL_BTN_FILTER 19
79 #define CONTROL_LABELFILES 12
81 #define PROPERTY_PATH_DB "path.db"
82 #define PROPERTY_SORT_ORDER "sort.order"
83 #define PROPERTY_SORT_ASCENDING "sort.ascending"
86 using namespace ADDON;
88 CGUIMediaWindow::CGUIMediaWindow(int id, const char *xmlFile)
89 : CGUIWindow(id, xmlFile)
91 m_loadType = KEEP_IN_MEMORY;
92 m_vecItems = new CFileItemList;
93 m_unfilteredItems = new CFileItemList;
94 m_vecItems->SetPath("?");
97 m_canFilterAdvanced = false;
99 m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems));
102 CGUIMediaWindow::~CGUIMediaWindow()
105 delete m_unfilteredItems;
108 #define CONTROL_VIEW_START 50
109 #define CONTROL_VIEW_END 59
111 void CGUIMediaWindow::LoadAdditionalTags(TiXmlElement *root)
113 CGUIWindow::LoadAdditionalTags(root);
114 // configure our view control
115 m_viewControl.Reset();
116 m_viewControl.SetParentWindow(GetID());
117 TiXmlElement *element = root->FirstChildElement("views");
118 if (element && element->FirstChild())
119 { // format is <views>50,29,51,95</views>
120 CStdString allViews = element->FirstChild()->Value();
121 CStdStringArray views;
122 StringUtils::SplitString(allViews, ",", views);
123 for (unsigned int i = 0; i < views.size(); i++)
125 int controlID = atol(views[i].c_str());
126 CGUIControl *control = (CGUIControl *)GetControl(controlID);
127 if (control && control->IsContainer())
128 m_viewControl.AddView(control);
132 { // backward compatibility
133 vector<CGUIControl *> controls;
134 GetContainers(controls);
135 for (ciControls it = controls.begin(); it != controls.end(); it++)
137 CGUIControl *control = *it;
138 if (control->GetID() >= CONTROL_VIEW_START && control->GetID() <= CONTROL_VIEW_END)
139 m_viewControl.AddView(control);
142 m_viewControl.SetViewControlID(CONTROL_BTNVIEWASICONS);
145 void CGUIMediaWindow::OnWindowLoaded()
147 SendMessage(GUI_MSG_SET_TYPE, CONTROL_BTN_FILTER, CGUIEditControl::INPUT_TYPE_FILTER);
148 CGUIWindow::OnWindowLoaded();
152 void CGUIMediaWindow::OnWindowUnload()
154 CGUIWindow::OnWindowUnload();
155 m_viewControl.Reset();
158 CFileItemPtr CGUIMediaWindow::GetCurrentListItem(int offset)
160 int item = m_viewControl.GetSelectedItem();
161 if (!m_vecItems->Size() || item < 0)
162 return CFileItemPtr();
163 item = (item + offset) % m_vecItems->Size();
164 if (item < 0) item += m_vecItems->Size();
165 return m_vecItems->Get(item);
168 bool CGUIMediaWindow::OnAction(const CAction &action)
170 if (action.GetID() == ACTION_PARENT_DIR)
176 // the non-contextual menu can be called at any time
177 if (action.GetID() == ACTION_CONTEXT_MENU && !m_viewControl.HasControl(GetFocusedControlID()))
183 if (CGUIWindow::OnAction(action))
186 if (action.GetID() == ACTION_FILTER)
190 if (action.GetID() == ACTION_FILTER_CLEAR)
192 CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS);
193 message.SetStringParam("");
198 if (action.GetID() == ACTION_BACKSPACE)
200 CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 2); // 2 for delete
205 if (action.GetID() >= ACTION_FILTER_SMS2 && action.GetID() <= ACTION_FILTER_SMS9)
207 CStdString filter = StringUtils::Format("%i", (int)(action.GetID() - ACTION_FILTER_SMS2 + 2));
208 CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 1); // 1 for append
209 message.SetStringParam(filter);
217 bool CGUIMediaWindow::OnBack(int actionID)
219 CURL filterUrl(m_strFilterPath);
220 if (actionID == ACTION_NAV_BACK && !m_vecItems->IsVirtualDirectoryRoot() &&
221 (m_vecItems->GetPath() != m_startDirectory || (m_canFilterAdvanced && filterUrl.HasOption("filter"))))
226 return CGUIWindow::OnBack(actionID);
229 bool CGUIMediaWindow::OnMessage(CGUIMessage& message)
231 switch ( message.GetMessage() )
233 case GUI_MSG_WINDOW_DEINIT:
235 m_iSelectedItem = m_viewControl.GetSelectedItem();
236 m_iLastControl = GetFocusedControlID();
237 CGUIWindow::OnMessage(message);
238 CGUIDialogContextMenu* pDlg = (CGUIDialogContextMenu*)g_windowManager.GetWindow(WINDOW_DIALOG_CONTEXT_MENU);
239 if (pDlg && pDlg->IsActive())
242 // get rid of any active filtering
243 if (m_canFilterAdvanced)
245 m_canFilterAdvanced = false;
248 m_strFilterPath.clear();
250 // Call ClearFileItems() after our window has finished doing any WindowClose
257 case GUI_MSG_CLICKED:
259 int iControl = message.GetSenderId();
260 if (iControl == CONTROL_BTNVIEWASICONS)
262 // view as control could be a select button
264 const CGUIControl *control = GetControl(CONTROL_BTNVIEWASICONS);
265 if (control && control->GetControlType() != CGUIControl::GUICONTROL_BUTTON)
267 CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTNVIEWASICONS);
269 viewMode = m_viewControl.GetViewModeNumber(msg.GetParam1());
272 viewMode = m_viewControl.GetNextViewMode();
274 if (m_guiState.get())
275 m_guiState->SaveViewAsControl(viewMode);
280 else if (iControl == CONTROL_BTNSORTASC) // sort asc
282 if (m_guiState.get())
283 m_guiState->SetNextSortOrder();
287 else if (iControl == CONTROL_BTNSORTBY) // sort by
289 if (m_guiState.get())
290 m_guiState->SetNextSortMethod();
294 else if (iControl == CONTROL_BTN_FILTER)
295 return Filter(false);
296 else if (m_viewControl.HasControl(iControl)) // list/thumb control
298 int iItem = m_viewControl.GetSelectedItem();
299 int iAction = message.GetParam1();
300 if (iItem < 0) break;
301 if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK)
305 else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK)
314 case GUI_MSG_SETFOCUS:
316 if (m_viewControl.HasControl(message.GetControlId()) && m_viewControl.GetCurrentControl() != message.GetControlId())
318 m_viewControl.SetFocused();
324 case GUI_MSG_NOTIFY_ALL:
325 { // Message is received even if this window is inactive
326 if (message.GetParam1() == GUI_MSG_WINDOW_RESET)
328 m_vecItems->SetPath("?");
331 else if ( message.GetParam1() == GUI_MSG_REFRESH_THUMBS )
333 for (int i = 0; i < m_vecItems->Size(); i++)
334 m_vecItems->Get(i)->FreeMemory(true);
335 break; // the window will take care of any info images
337 else if (message.GetParam1() == GUI_MSG_REMOVED_MEDIA)
339 if ((m_vecItems->IsVirtualDirectoryRoot() ||
340 m_vecItems->IsSourcesPath()) && IsActive())
342 int iItem = m_viewControl.GetSelectedItem();
344 m_viewControl.SetSelectedItem(iItem);
346 else if (m_vecItems->IsRemovable())
347 { // check that we have this removable share still
348 if (!m_rootDir.IsInSource(m_vecItems->GetPath()))
349 { // don't have this share any more
350 if (IsActive()) Update("");
353 m_history.ClearPathHistory();
354 m_vecItems->SetPath("");
361 else if (message.GetParam1()==GUI_MSG_UPDATE_SOURCES)
362 { // State of the sources changed, so update our view
363 if ((m_vecItems->IsVirtualDirectoryRoot() ||
364 m_vecItems->IsSourcesPath()) && IsActive())
366 int iItem = m_viewControl.GetSelectedItem();
368 m_viewControl.SetSelectedItem(iItem);
372 else if (message.GetParam1()==GUI_MSG_UPDATE && IsActive())
374 if (message.GetNumStringParams())
376 if (message.GetParam2()) // param2 is used for resetting the history
377 SetHistoryForPath(message.GetStringParam());
379 CFileItemList list(message.GetStringParam());
380 list.RemoveDiscCache(GetID());
381 Update(message.GetStringParam());
384 Refresh(true); // refresh the listing
386 else if (message.GetParam1()==GUI_MSG_UPDATE_ITEM && message.GetItem())
388 CFileItemPtr newItem = boost::static_pointer_cast<CFileItem>(message.GetItem());
391 if (m_vecItems->UpdateItem(newItem.get()) && message.GetParam2() == 1)
392 { // need the list updated as well
397 { // need to remove the disc cache
399 items.SetPath(URIUtils::GetDirectory(newItem->GetPath()));
400 items.RemoveDiscCache(GetID());
403 else if (message.GetParam1()==GUI_MSG_UPDATE_PATH)
407 if((message.GetStringParam() == m_vecItems->GetPath()) ||
408 (m_vecItems->IsMultiPath() && XFILE::CMultiPathDirectory::HasPath(m_vecItems->GetPath(), message.GetStringParam())))
412 else if (message.GetParam1() == GUI_MSG_FILTER_ITEMS && IsActive())
414 CStdString filter = GetProperty("filter").asString();
415 // check if this is meant for advanced filtering
416 if (message.GetParam2() != 10)
418 if (message.GetParam2() == 1) // append
419 filter += message.GetStringParam();
420 else if (message.GetParam2() == 2)
423 filter = filter.Left(filter.size() - 1);
426 filter = message.GetStringParam();
428 OnFilterItems(filter);
432 return CGUIWindow::OnMessage(message);
437 case GUI_MSG_PLAYBACK_STARTED:
438 case GUI_MSG_PLAYBACK_ENDED:
439 case GUI_MSG_PLAYBACK_STOPPED:
440 case GUI_MSG_PLAYLIST_CHANGED:
441 case GUI_MSG_PLAYLISTPLAYER_STOPPED:
442 case GUI_MSG_PLAYLISTPLAYER_STARTED:
443 case GUI_MSG_PLAYLISTPLAYER_CHANGED:
444 { // send a notify all to all controls on this window
445 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_REFRESH_LIST);
449 case GUI_MSG_CHANGE_VIEW_MODE:
452 if (message.GetParam1()) // we have an id
453 viewMode = m_viewControl.GetViewModeByID(message.GetParam1());
454 else if (message.GetParam2())
455 viewMode = m_viewControl.GetNextViewMode((int)message.GetParam2());
457 if (m_guiState.get())
458 m_guiState->SaveViewAsControl(viewMode);
463 case GUI_MSG_CHANGE_SORT_METHOD:
465 if (m_guiState.get())
467 if (message.GetParam1())
468 m_guiState->SetCurrentSortMethod((int)message.GetParam1());
469 else if (message.GetParam2())
470 m_guiState->SetNextSortMethod((int)message.GetParam2());
476 case GUI_MSG_CHANGE_SORT_DIRECTION:
478 if (m_guiState.get())
479 m_guiState->SetNextSortOrder();
484 case GUI_MSG_WINDOW_INIT:
486 if (m_vecItems->GetPath() == "?")
487 m_vecItems->SetPath("");
488 CStdString dir = message.GetStringParam(0);
489 const CStdString &ret = message.GetStringParam(1);
490 bool returning = ret.CompareNoCase("return") == 0;
493 m_history.ClearPathHistory();
494 // ensure our directory is valid
495 dir = GetStartFolder(dir);
496 if (!returning || m_vecItems->GetPath().Left(dir.GetLength()) != dir)
497 { // we're not returning to the same path, so set our directory to the requested path
498 m_vecItems->SetPath(dir);
500 // check for network up
501 if (URIUtils::IsRemote(m_vecItems->GetPath()) && !WaitForNetwork())
502 m_vecItems->SetPath("");
503 SetHistoryForPath(m_vecItems->GetPath());
505 if (message.GetParam1() != WINDOW_INVALID)
506 { // first time to this window - make sure we set the root path
507 m_startDirectory = returning ? dir : "";
513 return CGUIWindow::OnMessage(message);
516 // \brief Updates the states (enable, disable, visible...)
517 // of the controls defined by this window
518 // Override this function in a derived class to add new controls
519 void CGUIMediaWindow::UpdateButtons()
521 if (m_guiState.get())
523 // Update sorting controls
524 if (m_guiState->GetDisplaySortOrder() == SortOrderNone)
526 CONTROL_DISABLE(CONTROL_BTNSORTASC);
530 CONTROL_ENABLE(CONTROL_BTNSORTASC);
531 if (m_guiState->GetDisplaySortOrder() == SortOrderAscending)
533 CGUIMessage msg(GUI_MSG_DESELECTED, GetID(), CONTROL_BTNSORTASC);
534 g_windowManager.SendMessage(msg);
538 CGUIMessage msg(GUI_MSG_SELECTED, GetID(), CONTROL_BTNSORTASC);
539 g_windowManager.SendMessage(msg);
543 // Update list/thumb control
544 m_viewControl.SetCurrentView(m_guiState->GetViewAsControl());
546 // Update sort by button
547 if (!m_guiState->HasMultipleSortMethods())
548 CONTROL_DISABLE(CONTROL_BTNSORTBY);
550 CONTROL_ENABLE(CONTROL_BTNSORTBY);
552 CStdString sortLabel = StringUtils::Format(g_localizeStrings.Get(550).c_str(),
553 g_localizeStrings.Get(m_guiState->GetSortMethodLabel()).c_str());
554 SET_CONTROL_LABEL(CONTROL_BTNSORTBY, sortLabel);
557 CStdString items = StringUtils::Format("%i %s", m_vecItems->GetObjectCount(), g_localizeStrings.Get(127).c_str());
558 SET_CONTROL_LABEL(CONTROL_LABELFILES, items);
560 SET_CONTROL_LABEL2(CONTROL_BTN_FILTER, GetProperty("filter").asString());
563 void CGUIMediaWindow::ClearFileItems()
565 m_viewControl.Clear();
567 m_unfilteredItems->Clear();
570 // \brief Sorts Fileitems based on the sort method and sort oder provided by guiViewState
571 void CGUIMediaWindow::SortItems(CFileItemList &items)
573 auto_ptr<CGUIViewState> guiState(CGUIViewState::GetViewState(GetID(), items));
577 SortDescription sorting = guiState->GetSortMethod();
578 sorting.sortOrder = guiState->GetDisplaySortOrder();
579 // If the sort method is "sort by playlist" and we have a specific
580 // sort order available we can use the specified sort order to do the sorting
581 // We do this as the new SortBy methods are a superset of the SORT_METHOD methods, thus
582 // not all are available. This may be removed once SORT_METHOD_* have been replaced by
584 if ((sorting.sortBy == SortByPlaylistOrder) && items.HasProperty(PROPERTY_SORT_ORDER))
586 SortBy sortBy = (SortBy)items.GetProperty(PROPERTY_SORT_ORDER).asInteger();
587 if (sortBy != SortByNone && sortBy != SortByPlaylistOrder && sortBy != SortByProgramCount)
589 sorting.sortBy = sortBy;
590 sorting.sortOrder = items.GetProperty(PROPERTY_SORT_ASCENDING).asBoolean() ? SortOrderAscending : SortOrderDescending;
591 sorting.sortAttributes = CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone;
593 // if the sort order is descending, we need to switch the original sort order, as we assume
594 // in CGUIViewState::AddPlaylistOrder that SortByPlaylistOrder is ascending.
595 if (guiState->GetDisplaySortOrder() == SortOrderDescending)
596 sorting.sortOrder = sorting.sortOrder == SortOrderDescending ? SortOrderAscending : SortOrderDescending;
604 // \brief Formats item labels based on the formatting provided by guiViewState
605 void CGUIMediaWindow::FormatItemLabels(CFileItemList &items, const LABEL_MASKS &labelMasks)
607 CLabelFormatter fileFormatter(labelMasks.m_strLabelFile, labelMasks.m_strLabel2File);
608 CLabelFormatter folderFormatter(labelMasks.m_strLabelFolder, labelMasks.m_strLabel2Folder);
609 for (int i=0; i<items.Size(); ++i)
611 CFileItemPtr pItem=items[i];
613 if (pItem->IsLabelPreformated())
616 if (pItem->m_bIsFolder)
617 folderFormatter.FormatLabels(pItem.get());
619 fileFormatter.FormatLabels(pItem.get());
622 if (items.GetSortMethod() == SortByLabel)
623 items.ClearSortState();
626 // \brief Prepares and adds the fileitems list/thumb panel
627 void CGUIMediaWindow::FormatAndSort(CFileItemList &items)
629 auto_ptr<CGUIViewState> viewState(CGUIViewState::GetViewState(GetID(), items));
633 LABEL_MASKS labelMasks;
634 viewState->GetSortMethodLabelMasks(labelMasks);
635 FormatItemLabels(items, labelMasks);
637 items.Sort(viewState->GetSortMethod().sortBy, viewState->GetDisplaySortOrder(), viewState->GetSortMethod().sortAttributes);
642 \brief Overwrite to fill fileitems from a source
643 \param strDirectory Path to read
644 \param items Fill with items specified in \e strDirectory
646 bool CGUIMediaWindow::GetDirectory(const CStdString &strDirectory, CFileItemList &items)
652 CStdString strParentPath = m_history.GetParentPath();
654 CLog::Log(LOGDEBUG,"CGUIMediaWindow::GetDirectory (%s)",
655 CURL::GetRedacted(strDirectory).c_str());
656 CLog::Log(LOGDEBUG," ParentPath = [%s]", CURL::GetRedacted(strParentPath).c_str());
658 // see if we can load a previously cached folder
659 CFileItemList cachedItems(strDirectory);
660 if (!strDirectory.IsEmpty() && cachedItems.Load(GetID()))
662 items.Assign(cachedItems);
666 unsigned int time = XbmcThreads::SystemClockMillis();
668 if (strDirectory.IsEmpty())
671 if (!m_rootDir.GetDirectory(strDirectory, items))
674 // took over a second, and not normally cached, so cache it
675 if ((XbmcThreads::SystemClockMillis() - time) > 1000 && items.CacheToDiscIfSlow())
678 // if these items should replace the current listing, then pop it off the top
679 if (items.GetReplaceListing())
680 m_history.RemoveParentPath();
683 if (m_guiState.get() && !m_guiState->HideParentDirItems() && !items.GetPath().IsEmpty())
685 CFileItemPtr pItem(new CFileItem(".."));
686 pItem->SetPath(strParentPath);
687 pItem->m_bIsFolder = true;
688 pItem->m_bIsShareOrDrive = false;
689 items.AddFront(pItem, 0);
692 int iWindow = GetID();
693 CStdStringArray regexps;
695 // TODO: Do we want to limit the directories we apply the video ones to?
696 if (iWindow == WINDOW_VIDEO_NAV)
697 regexps = g_advancedSettings.m_videoExcludeFromListingRegExps;
698 if (iWindow == WINDOW_MUSIC_FILES)
699 regexps = g_advancedSettings.m_audioExcludeFromListingRegExps;
700 if (iWindow == WINDOW_PICTURES)
701 regexps = g_advancedSettings.m_pictureExcludeFromListingRegExps;
705 for (int i=0; i < items.Size();)
707 if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps))
715 SetProperty("filter", "");
716 m_canFilterAdvanced = false;
721 // \brief Set window to a specific directory
722 // \param strDirectory The directory to be displayed in list/thumb control
723 // This function calls OnPrepareFileItems() and OnFinalizeFileItems()
724 bool CGUIMediaWindow::Update(const CStdString &strDirectory, bool updateFilterPath /* = true */)
726 // TODO: OnInitWindow calls Update() before window path has been set properly.
727 if (strDirectory == "?")
731 int iItem = m_viewControl.GetSelectedItem();
732 CStdString strSelectedItem = "";
733 if (iItem >= 0 && iItem < m_vecItems->Size())
735 CFileItemPtr pItem = m_vecItems->Get(iItem);
736 if (!pItem->IsParentFolder())
738 GetDirectoryHistoryString(pItem.get(), strSelectedItem);
742 CStdString strCurrentDirectory = m_vecItems->GetPath();
743 m_history.SetSelectedItem(strSelectedItem, strCurrentDirectory);
745 CStdString directory = strDirectory;
746 // check if the path contains a filter and temporarily remove it
747 // so that the retrieved list of items is unfiltered
748 bool canfilter = CanContainFilter(directory);
751 if (canfilter && url.HasOption("filter"))
752 directory = RemoveParameterFromPath(directory, "filter");
755 if (!GetDirectory(directory, items))
757 CLog::Log(LOGERROR,"CGUIMediaWindow::GetDirectory(%s) failed", url.GetRedacted().c_str());
758 // Try to return to the previous directory, if not the same
759 // else fallback to root
760 if (strDirectory.Equals(strCurrentDirectory) || !Update(m_history.RemoveParentPath()))
761 Update(""); // Fallback to root
763 // Return false to be able to eg. show
768 if (items.GetLabel().IsEmpty())
769 items.SetLabel(CUtil::GetTitleFromPath(items.GetPath(), true));
772 m_vecItems->Copy(items);
774 // check the given path for filter data
775 UpdateFilterPath(strDirectory, items, updateFilterPath);
777 // if we're getting the root source listing
778 // make sure the path history is clean
779 if (strDirectory.IsEmpty())
780 m_history.ClearPathHistory();
782 int iWindow = GetID();
784 if (strDirectory.IsEmpty())
786 if (iWindow == WINDOW_PICTURES)
788 else if (iWindow == WINDOW_MUSIC_FILES)
790 else if (iWindow == WINDOW_FILES || iWindow == WINDOW_PROGRAMS)
793 if (m_vecItems->GetPath().Equals("sources://video/"))
795 else if (m_vecItems->GetPath().Equals("sources://music/"))
797 else if (m_vecItems->GetPath().Equals("sources://pictures/"))
799 else if (m_vecItems->GetPath().Equals("sources://programs/") ||
800 m_vecItems->GetPath().Equals("sources://files/"))
802 if (showLabel && (m_vecItems->Size() == 0 || !m_guiState->DisableAddSourceButtons())) // add 'add source button'
804 CStdString strLabel = g_localizeStrings.Get(showLabel);
805 CFileItemPtr pItem(new CFileItem(strLabel));
806 pItem->SetPath("add");
807 pItem->SetIconImage("DefaultAddSource.png");
808 pItem->SetLabel(strLabel);
809 pItem->SetLabelPreformated(true);
810 pItem->m_bIsFolder = true;
811 pItem->SetSpecialSort(SortSpecialOnBottom);
812 m_vecItems->Add(pItem);
814 m_iLastControl = GetFocusedControlID();
816 // Check whether to enabled advanced filtering based on the content type
817 m_canFilterAdvanced = CheckFilterAdvanced(*m_vecItems);
818 if (m_canFilterAdvanced)
819 m_filter.SetType(m_vecItems->GetContent());
821 // Ask the derived class if it wants to load additional info
822 // for the fileitems like media info or additional
823 // filtering on the items, setting thumbs.
824 OnPrepareFileItems(*m_vecItems);
826 m_vecItems->FillInDefaultIcons();
828 m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems));
830 // remember the original (untouched) list of items (for filtering etc)
831 m_unfilteredItems->SetPath(m_vecItems->GetPath()); // use the original path - it'll likely be relied on for other things later.
832 m_unfilteredItems->Append(*m_vecItems);
834 // Cache the list of items if possible
835 OnCacheFileItems(*m_vecItems);
837 // Filter and group the items if necessary
838 OnFilterItems(GetProperty("filter").asString());
840 // Ask the devived class if it wants to do custom list operations,
841 // eg. changing the label
842 OnFinalizeFileItems(*m_vecItems);
845 strSelectedItem = m_history.GetSelectedItem(m_vecItems->GetPath());
847 bool bSelectedFound = false;
848 //int iSongInDirectory = -1;
849 for (int i = 0; i < m_vecItems->Size(); ++i)
851 CFileItemPtr pItem = m_vecItems->Get(i);
853 // Update selected item
854 CStdString strHistory;
855 GetDirectoryHistoryString(pItem.get(), strHistory);
856 if (strHistory == strSelectedItem)
858 m_viewControl.SetSelectedItem(i);
859 bSelectedFound = true;
864 // if we haven't found the selected item, select the first item
866 m_viewControl.SetSelectedItem(0);
868 if (iWindow != WINDOW_PVR || (iWindow == WINDOW_PVR && m_vecItems->GetPath().Left(17) == "pvr://recordings/"))
869 m_history.AddPath(m_vecItems->GetPath(), m_strFilterPath);
871 //m_history.DumpPathHistory();
876 bool CGUIMediaWindow::Refresh(bool clearCache /* = false */)
878 CStdString strCurrentDirectory = m_vecItems->GetPath();
879 if (strCurrentDirectory.Equals("?"))
883 m_vecItems->RemoveDiscCache(GetID());
885 // get the original number of items
886 if (!Update(strCurrentDirectory, false))
892 // \brief This function will be called by Update() before the
893 // labels of the fileitems are formatted. Override this function
894 // to set custom thumbs or load additional media info.
895 // It's used to load tag info for music.
896 void CGUIMediaWindow::OnPrepareFileItems(CFileItemList &items)
898 CFileItemListModification::Get().Modify(items);
901 // \brief This function will be called by Update() before
902 // any additional formatting, filtering or sorting is applied.
903 // Override this function to define a custom caching behaviour.
904 void CGUIMediaWindow::OnCacheFileItems(CFileItemList &items)
906 // Should these items be saved to the hdd
907 if (items.CacheToDiscAlways() && !IsFiltered())
911 // \brief This function will be called by Update() after the
912 // labels of the fileitems are formatted. Override this function
913 // to modify the fileitems. Eg. to modify the item label
914 void CGUIMediaWindow::OnFinalizeFileItems(CFileItemList &items)
919 // \brief With this function you can react on a users click in the list/thumb panel.
920 // It returns true, if the click is handled.
921 // This function calls OnPlayMedia()
922 bool CGUIMediaWindow::OnClick(int iItem)
924 if ( iItem < 0 || iItem >= (int)m_vecItems->Size() ) return true;
925 CFileItemPtr pItem = m_vecItems->Get(iItem);
927 if (pItem->IsParentFolder())
932 if (pItem->GetPath() == "add" || pItem->GetPath() == "sources://add/") // 'add source button' in empty root
934 OnContextButton(iItem, CONTEXT_BUTTON_ADD_SOURCE);
938 if (!pItem->m_bIsFolder && pItem->IsFileFolder(EFILEFOLDER_MASK_ONCLICK))
940 XFILE::IFileDirectory *pFileDirectory = NULL;
941 pFileDirectory = XFILE::CFileDirectoryFactory::Create(pItem->GetPath(), pItem.get(), "");
943 pItem->m_bIsFolder = true;
944 else if(pItem->m_bIsFolder)
945 pItem->m_bIsFolder = false;
946 delete pFileDirectory;
949 if (pItem->IsScript())
951 // execute the script
952 CURL url(pItem->GetPath());
954 if (CAddonMgr::Get().GetAddon(url.GetHostName(), addon, ADDON_SCRIPT))
956 if (!CScriptInvocationManager::Get().Stop(addon->LibPath()))
957 CScriptInvocationManager::Get().Execute(addon->LibPath(), addon);
962 if (pItem->m_bIsFolder)
964 if ( pItem->m_bIsShareOrDrive )
966 const CStdString& strLockType=m_guiState->GetLockType();
967 if (CProfilesManager::Get().GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE)
968 if (!strLockType.IsEmpty() && !g_passwordManager.IsItemUnlocked(pItem.get(), strLockType))
971 if (!HaveDiscOrConnection(pItem->GetPath(), pItem->m_iDriveType))
975 // check for the partymode playlist items - they may not exist yet
976 if ((pItem->GetPath() == CProfilesManager::Get().GetUserDataItem("PartyMode.xsp")) ||
977 (pItem->GetPath() == CProfilesManager::Get().GetUserDataItem("PartyMode-Video.xsp")))
979 // party mode playlist item - if it doesn't exist, prompt for user to define it
980 if (!XFILE::CFile::Exists(pItem->GetPath()))
982 m_vecItems->RemoveDiscCache(GetID());
983 if (CGUIDialogSmartPlaylistEditor::EditPlaylist(pItem->GetPath()))
989 // remove the directory cache if the folder is not normally cached
990 CFileItemList items(pItem->GetPath());
991 if (!items.AlwaysCache())
992 items.RemoveDiscCache(GetID());
994 // if we have a filtered list, we need to add the filtered
995 // path to be able to come back to the filtered view
996 CStdString strCurrentDirectory = m_vecItems->GetPath();
997 if (m_canFilterAdvanced && !m_filter.IsEmpty() &&
998 !m_strFilterPath.Equals(strCurrentDirectory))
1000 m_history.RemoveParentPath();
1001 m_history.AddPath(strCurrentDirectory, m_strFilterPath);
1004 CFileItem directory(*pItem);
1005 if (!Update(directory.GetPath()))
1006 ShowShareErrorMessage(&directory);
1010 else if (pItem->IsPlugin() && !pItem->GetProperty("isplayable").asBoolean())
1012 return XFILE::CPluginDirectory::RunScriptWithParams(pItem->GetPath());
1014 #if defined(TARGET_ANDROID)
1015 else if (pItem->IsAndroidApp())
1017 CStdString appName = URIUtils::GetFileName(pItem->GetPath());
1018 CLog::Log(LOGDEBUG, "CGUIMediaWindow::OnClick Trying to run: %s",appName.c_str());
1019 return CXBMCApp::StartActivity(appName);
1024 m_iSelectedItem = m_viewControl.GetSelectedItem();
1026 if (pItem->GetPath() == "newplaylist://")
1028 m_vecItems->RemoveDiscCache(GetID());
1029 g_windowManager.ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR,"newplaylist://");
1032 else if (StringUtils::StartsWithNoCase(pItem->GetPath(), "newsmartplaylist://"))
1034 m_vecItems->RemoveDiscCache(GetID());
1035 if (CGUIDialogSmartPlaylistEditor::NewPlaylist(pItem->GetPath().Mid(19)))
1039 else if (StringUtils::StartsWithNoCase(pItem->GetPath(), "addons://more/"))
1041 CBuiltins::Execute("ActivateWindow(AddonBrowser,addons://all/xbmc.addon." + pItem->GetPath().Mid(14) + ",return)");
1045 // If karaoke song is being played AND popup autoselector is enabled, the playlist should not be added
1046 bool do_not_add_karaoke = CSettings::Get().GetBool("karaoke.enabled") &&
1047 CSettings::Get().GetBool("karaoke.autopopupselector") && pItem->IsKaraoke();
1048 bool autoplay = m_guiState.get() && m_guiState->AutoPlayNextItem();
1050 if (m_vecItems->IsPlugin())
1052 CURL url(m_vecItems->GetPath());
1054 if (CAddonMgr::Get().GetAddon(url.GetHostName(),addon))
1056 PluginPtr plugin = boost::dynamic_pointer_cast<CPluginSource>(addon);
1057 if (plugin && plugin->Provides(CPluginSource::AUDIO))
1059 CFileItemList items;
1060 auto_ptr<CGUIViewState> state(CGUIViewState::GetViewState(GetID(), items));
1061 autoplay = state.get() && state->AutoPlayNextItem();
1066 if (autoplay && !g_partyModeManager.IsEnabled() &&
1067 !pItem->IsPlayList() && !do_not_add_karaoke)
1069 return OnPlayAndQueueMedia(pItem);
1073 return OnPlayMedia(iItem);
1080 bool CGUIMediaWindow::OnSelect(int item)
1082 return OnClick(item);
1085 // \brief Checks if there is a disc in the dvd drive and whether the
1086 // network is connected or not.
1087 bool CGUIMediaWindow::HaveDiscOrConnection(const CStdString& strPath, int iDriveType)
1089 if (iDriveType==CMediaSource::SOURCE_TYPE_DVD)
1091 if (!g_mediaManager.IsDiscInDrive(strPath))
1093 CGUIDialogOK::ShowAndGetInput(218, 219, 0, 0);
1097 else if (iDriveType==CMediaSource::SOURCE_TYPE_REMOTE)
1099 // TODO: Handle not connected to a remote share
1100 if ( !g_application.getNetwork().IsConnected() )
1102 CGUIDialogOK::ShowAndGetInput(220, 221, 0, 0);
1110 // \brief Shows a standard errormessage for a given pItem.
1111 void CGUIMediaWindow::ShowShareErrorMessage(CFileItem* pItem)
1113 int idMessageText = 0;
1114 CURL url(pItem->GetPath());
1115 const CStdString& strHostName = url.GetHostName();
1117 if (url.GetProtocol() == "smb" && strHostName.IsEmpty()) // smb workgroup
1118 idMessageText = 15303; // Workgroup not found
1119 else if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_REMOTE || URIUtils::IsRemote(pItem->GetPath()))
1120 idMessageText = 15301; // Could not connect to network server
1122 idMessageText = 15300; // Path not found or invalid
1124 CGUIDialogOK::ShowAndGetInput(220, idMessageText, 0, 0);
1127 // \brief The functon goes up one level in the directory tree
1128 void CGUIMediaWindow::GoParentFolder()
1130 //m_history.DumpPathHistory();
1132 // remove current directory if its on the stack
1133 // there were some issues due some folders having a trailing slash and some not
1134 // so just add a trailing slash to all of them for comparison.
1135 CStdString strPath = m_vecItems->GetPath();
1136 URIUtils::AddSlashAtEnd(strPath);
1137 CStdString strParent = m_history.GetParentPath();
1138 // in case the path history is messed up and the current folder is on
1139 // the stack more than once, keep going until there's nothing left or they
1140 // dont match anymore.
1141 while (!strParent.IsEmpty())
1143 URIUtils::AddSlashAtEnd(strParent);
1144 if (strParent.Equals(strPath))
1145 m_history.RemoveParentPath();
1148 strParent = m_history.GetParentPath();
1151 // remove the current filter but only if the parent
1152 // item doesn't have a filter as well
1153 CURL filterUrl(m_strFilterPath);
1154 if (filterUrl.HasOption("filter"))
1156 CURL parentUrl(m_history.GetParentPath(true));
1157 if (!parentUrl.HasOption("filter"))
1159 // we need to overwrite m_strFilterPath because
1160 // Refresh() will set updateFilterPath to false
1161 m_strFilterPath.clear();
1167 // if vector is not empty, pop parent
1168 // if vector is empty, parent is root source listing
1169 m_strFilterPath = m_history.GetParentPath(true);
1170 strParent = m_history.RemoveParentPath();
1171 if (!Update(strParent, false))
1174 // No items to show so go another level up
1175 if (!m_vecItems->GetPath().empty() && (m_filter.IsEmpty() ? m_vecItems->Size() : m_unfilteredItems->Size()) <= 0)
1177 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(2080), g_localizeStrings.Get(2081));
1182 // \brief Override the function to change the default behavior on how
1183 // a selected item history should look like
1184 void CGUIMediaWindow::GetDirectoryHistoryString(const CFileItem* pItem, CStdString& strHistoryString)
1186 if (pItem->m_bIsShareOrDrive)
1188 // We are in the virual directory
1190 // History string of the DVD drive
1191 // must be handel separately
1192 if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_DVD)
1194 // Remove disc label from item label
1195 // and use as history string, m_strPath
1196 // can change for new discs
1197 CStdString strLabel = pItem->GetLabel();
1198 int nPosOpen = strLabel.Find('(');
1199 int nPosClose = strLabel.ReverseFind(')');
1200 if (nPosOpen > -1 && nPosClose > -1 && nPosClose > nPosOpen)
1202 strLabel.Delete(nPosOpen + 1, (nPosClose) - (nPosOpen + 1));
1203 strHistoryString = strLabel;
1206 strHistoryString = strLabel;
1210 // Other items in virual directory
1211 CStdString strPath = pItem->GetPath();
1212 URIUtils::RemoveSlashAtEnd(strPath);
1214 strHistoryString = pItem->GetLabel() + strPath;
1217 else if (pItem->m_lEndOffset>pItem->m_lStartOffset && pItem->m_lStartOffset != -1)
1219 // Could be a cue item, all items of a cue share the same filename
1220 // so add the offsets to build the history string
1221 strHistoryString = StringUtils::Format("%ld%ld",
1222 pItem->m_lStartOffset,
1223 pItem->m_lEndOffset);
1224 strHistoryString += pItem->GetPath();
1228 // Normal directory items
1229 strHistoryString = pItem->GetPath();
1232 // remove any filter
1233 if (CanContainFilter(strHistoryString))
1234 strHistoryString = RemoveParameterFromPath(strHistoryString, "filter");
1236 URIUtils::RemoveSlashAtEnd(strHistoryString);
1237 strHistoryString.ToLower();
1240 // \brief Call this function to create a directory history for the
1241 // path given by strDirectory.
1242 void CGUIMediaWindow::SetHistoryForPath(const CStdString& strDirectory)
1244 // Make sure our shares are configured
1246 if (!strDirectory.IsEmpty())
1248 // Build the directory history for default path
1249 CStdString strPath, strParentPath;
1250 strPath = strDirectory;
1251 URIUtils::RemoveSlashAtEnd(strPath);
1253 CFileItemList items;
1254 m_rootDir.GetDirectory("", items);
1256 m_history.ClearPathHistory();
1258 while (URIUtils::GetParentPath(strPath, strParentPath))
1260 for (int i = 0; i < (int)items.Size(); ++i)
1262 CFileItemPtr pItem = items[i];
1263 CStdString path(pItem->GetPath());
1264 URIUtils::RemoveSlashAtEnd(path);
1265 if (path == strPath)
1267 CStdString strHistory;
1268 GetDirectoryHistoryString(pItem.get(), strHistory);
1269 m_history.SetSelectedItem(strHistory, "");
1270 URIUtils::AddSlashAtEnd(strPath);
1271 m_history.AddPathFront(strPath);
1272 m_history.AddPathFront("");
1274 //m_history.DumpPathHistory();
1279 if (URIUtils::IsVideoDb(strPath))
1281 CURL url(strParentPath);
1282 url.SetOptions(""); // clear any URL options from recreated parent path
1283 strParentPath = url.Get();
1286 URIUtils::AddSlashAtEnd(strPath);
1287 m_history.AddPathFront(strPath);
1288 m_history.SetSelectedItem(strPath, strParentPath);
1289 strPath = strParentPath;
1290 URIUtils::RemoveSlashAtEnd(strPath);
1294 m_history.ClearPathHistory();
1296 //m_history.DumpPathHistory();
1299 // \brief Override if you want to change the default behavior, what is done
1300 // when the user clicks on a file.
1301 // This function is called by OnClick()
1302 bool CGUIMediaWindow::OnPlayMedia(int iItem)
1304 // Reset Playlistplayer, playback started now does
1305 // not use the playlistplayer.
1306 g_playlistPlayer.Reset();
1307 g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_NONE);
1308 CFileItemPtr pItem=m_vecItems->Get(iItem);
1310 CLog::Log(LOGDEBUG, "%s %s", __FUNCTION__, CURL::GetRedacted(pItem->GetPath()).c_str());
1312 bool bResult = false;
1313 if (pItem->IsInternetStream() || pItem->IsPlayList())
1314 bResult = g_application.PlayMedia(*pItem, m_guiState->GetPlaylist());
1316 bResult = g_application.PlayFile(*pItem) == PLAYBACK_OK;
1318 if (pItem->m_lStartOffset == STARTOFFSET_RESUME)
1319 pItem->m_lStartOffset = 0;
1324 // \brief Override if you want to change the default behavior of what is done
1325 // when the user clicks on a file in a "folder" with similar files.
1326 // This function is called by OnClick()
1327 bool CGUIMediaWindow::OnPlayAndQueueMedia(const CFileItemPtr &item)
1329 //play and add current directory to temporary playlist
1330 int iPlaylist = m_guiState->GetPlaylist();
1331 if (iPlaylist != PLAYLIST_NONE)
1333 g_playlistPlayer.ClearPlaylist(iPlaylist);
1334 g_playlistPlayer.Reset();
1335 int mediaToPlay = 0;
1336 for ( int i = 0; i < m_vecItems->Size(); i++ )
1338 CFileItemPtr nItem = m_vecItems->Get(i);
1340 if (nItem->m_bIsFolder)
1343 if (!nItem->IsPlayList() && !nItem->IsZIP() && !nItem->IsRAR())
1344 g_playlistPlayer.Add(iPlaylist, nItem);
1346 if (item->IsSamePath(nItem.get()))
1347 { // item that was clicked
1348 mediaToPlay = g_playlistPlayer.GetPlaylist(iPlaylist).size() - 1;
1352 // Save current window and directory to know where the selected item was
1353 if (m_guiState.get())
1354 m_guiState->SetPlaylistDirectory(m_vecItems->GetPath());
1356 // figure out where we start playback
1357 if (g_playlistPlayer.IsShuffled(iPlaylist))
1359 int iIndex = g_playlistPlayer.GetPlaylist(iPlaylist).FindOrder(mediaToPlay);
1360 g_playlistPlayer.GetPlaylist(iPlaylist).Swap(0, iIndex);
1365 g_playlistPlayer.SetCurrentPlaylist(iPlaylist);
1366 g_playlistPlayer.Play(mediaToPlay);
1371 // \brief Synchonize the fileitems with the playlistplayer
1372 // It recreated the playlist of the playlistplayer based
1373 // on the fileitems of the window
1374 void CGUIMediaWindow::UpdateFileList()
1376 int nItem = m_viewControl.GetSelectedItem();
1377 CStdString strSelected;
1379 strSelected = m_vecItems->Get(nItem)->GetPath();
1381 FormatAndSort(*m_vecItems);
1384 m_viewControl.SetItems(*m_vecItems);
1385 m_viewControl.SetSelectedItem(strSelected);
1387 // set the currently playing item as selected, if its in this directory
1388 if (m_guiState.get() && m_guiState->IsCurrentPlaylistDirectory(m_vecItems->GetPath()))
1390 int iPlaylist=m_guiState->GetPlaylist();
1391 int nSong = g_playlistPlayer.GetCurrentSong();
1392 CFileItem playlistItem;
1393 if (nSong > -1 && iPlaylist > -1)
1394 playlistItem=*g_playlistPlayer.GetPlaylist(iPlaylist)[nSong];
1396 g_playlistPlayer.ClearPlaylist(iPlaylist);
1397 g_playlistPlayer.Reset();
1399 for (int i = 0; i < m_vecItems->Size(); i++)
1401 CFileItemPtr pItem = m_vecItems->Get(i);
1402 if (pItem->m_bIsFolder)
1405 if (!pItem->IsPlayList() && !pItem->IsZIP() && !pItem->IsRAR())
1406 g_playlistPlayer.Add(iPlaylist, pItem);
1408 if (pItem->GetPath() == playlistItem.GetPath() &&
1409 pItem->m_lStartOffset == playlistItem.m_lStartOffset)
1410 g_playlistPlayer.SetCurrentSong(g_playlistPlayer.GetPlaylist(iPlaylist).size() - 1);
1415 void CGUIMediaWindow::OnDeleteItem(int iItem)
1417 if ( iItem < 0 || iItem >= m_vecItems->Size()) return;
1418 CFileItemPtr item = m_vecItems->Get(iItem);
1420 if (item->IsPlayList())
1421 item->m_bIsFolder = false;
1423 if (CProfilesManager::Get().GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && CProfilesManager::Get().GetCurrentProfile().filesLocked())
1424 if (!g_passwordManager.IsMasterLockUnlocked(true))
1427 if (!CFileUtils::DeleteItem(item))
1430 m_viewControl.SetSelectedItem(iItem);
1433 void CGUIMediaWindow::OnRenameItem(int iItem)
1435 if ( iItem < 0 || iItem >= m_vecItems->Size()) return;
1437 if (CProfilesManager::Get().GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && CProfilesManager::Get().GetCurrentProfile().filesLocked())
1438 if (!g_passwordManager.IsMasterLockUnlocked(true))
1441 if (!CFileUtils::RenameFile(m_vecItems->Get(iItem)->GetPath()))
1444 m_viewControl.SetSelectedItem(iItem);
1447 void CGUIMediaWindow::OnInitWindow()
1449 // initial fetch is done unthreaded to ensure the items are setup prior to skin animations kicking off
1450 m_rootDir.SetAllowThreads(false);
1452 // the start directory may change during Refresh
1453 bool updateStartDirectory = (m_startDirectory == m_vecItems->GetPath());
1455 if (updateStartDirectory)
1456 m_startDirectory = m_vecItems->GetPath();
1458 m_rootDir.SetAllowThreads(true);
1460 if (m_iSelectedItem > -1)
1461 m_viewControl.SetSelectedItem(m_iSelectedItem);
1463 CGUIWindow::OnInitWindow();
1466 CGUIControl *CGUIMediaWindow::GetFirstFocusableControl(int id)
1468 if (m_viewControl.HasControl(id))
1469 id = m_viewControl.GetCurrentControl();
1470 return CGUIWindow::GetFirstFocusableControl(id);
1473 void CGUIMediaWindow::SetupShares()
1475 // Setup shares and filemasks for this window
1476 CFileItemList items;
1477 CGUIViewState* viewState=CGUIViewState::GetViewState(GetID(), items);
1480 m_rootDir.SetMask(viewState->GetExtensions());
1481 m_rootDir.SetSources(viewState->GetSources());
1486 bool CGUIMediaWindow::OnPopupMenu(int iItem)
1488 // popup the context menu
1489 // grab our context menu
1490 CContextButtons buttons;
1491 GetContextButtons(iItem, buttons);
1496 if (iItem >= 0 && iItem < m_vecItems->Size())
1497 m_vecItems->Get(iItem)->Select(true);
1499 int choice = CGUIDialogContextMenu::ShowAndGetChoice(buttons);
1501 // deselect our item
1502 if (iItem >= 0 && iItem < m_vecItems->Size())
1503 m_vecItems->Get(iItem)->Select(false);
1506 return OnContextButton(iItem, (CONTEXT_BUTTON)choice);
1511 void CGUIMediaWindow::GetContextButtons(int itemNumber, CContextButtons &buttons)
1513 CFileItemPtr item = (itemNumber >= 0 && itemNumber < m_vecItems->Size()) ? m_vecItems->Get(itemNumber) : CFileItemPtr();
1518 // user added buttons
1521 for (int i = CONTEXT_BUTTON_USER1; i <= CONTEXT_BUTTON_USER10; i++)
1523 label = StringUtils::Format("contextmenulabel(%i)", i - CONTEXT_BUTTON_USER1);
1524 if (item->GetProperty(label).empty())
1527 action = StringUtils::Format("contextmenuaction(%i)", i - CONTEXT_BUTTON_USER1);
1528 if (item->GetProperty(action).empty())
1531 buttons.Add((CONTEXT_BUTTON)i, item->GetProperty(label).asString());
1534 if (item->GetProperty("pluginreplacecontextitems").asBoolean())
1537 // TODO: FAVOURITES Conditions on masterlock and localisation
1538 if (!item->IsParentFolder() && !item->GetPath().Equals("add") && !item->GetPath().Equals("newplaylist://") &&
1539 !StringUtils::StartsWithNoCase(item->GetPath(), "newsmartplaylist://") && !StringUtils::StartsWithNoCase(item->GetPath(), "newtag://") &&
1540 !StringUtils::StartsWithNoCase(item->GetPath(), "addons://more/") && !StringUtils::StartsWithNoCase(item->GetPath(), "musicsearch://"))
1542 if (XFILE::CFavouritesDirectory::IsFavourite(item.get(), GetID()))
1543 buttons.Add(CONTEXT_BUTTON_ADD_FAVOURITE, 14077); // Remove Favourite
1545 buttons.Add(CONTEXT_BUTTON_ADD_FAVOURITE, 14076); // Add To Favourites;
1548 if (item->IsFileFolder(EFILEFOLDER_MASK_ONBROWSE))
1549 buttons.Add(CONTEXT_BUTTON_BROWSE_INTO, 37015);
1553 bool CGUIMediaWindow::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
1557 case CONTEXT_BUTTON_ADD_FAVOURITE:
1559 CFileItemPtr item = m_vecItems->Get(itemNumber);
1560 XFILE::CFavouritesDirectory::AddOrRemove(item.get(), GetID());
1563 case CONTEXT_BUTTON_PLUGIN_SETTINGS:
1565 CFileItemPtr item = m_vecItems->Get(itemNumber);
1566 // CONTEXT_BUTTON_PLUGIN_SETTINGS can be called for plugin item
1567 // or script item; or for the plugin directory current listing.
1568 bool isPluginOrScriptItem = (item && (item->IsPlugin() || item->IsScript()));
1569 CURL plugin(isPluginOrScriptItem ? item->GetPath() : m_vecItems->GetPath());
1570 ADDON::AddonPtr addon;
1571 if (CAddonMgr::Get().GetAddon(plugin.GetHostName(), addon))
1572 if (CGUIDialogAddonSettings::ShowAndGetInput(addon))
1576 case CONTEXT_BUTTON_BROWSE_INTO:
1578 CFileItemPtr item = m_vecItems->Get(itemNumber);
1579 if(Update(item->GetPath()))
1583 case CONTEXT_BUTTON_USER1:
1584 case CONTEXT_BUTTON_USER2:
1585 case CONTEXT_BUTTON_USER3:
1586 case CONTEXT_BUTTON_USER4:
1587 case CONTEXT_BUTTON_USER5:
1588 case CONTEXT_BUTTON_USER6:
1589 case CONTEXT_BUTTON_USER7:
1590 case CONTEXT_BUTTON_USER8:
1591 case CONTEXT_BUTTON_USER9:
1592 case CONTEXT_BUTTON_USER10:
1594 CStdString action = StringUtils::Format("contextmenuaction(%i)", button - CONTEXT_BUTTON_USER1);
1595 CApplicationMessenger::Get().ExecBuiltIn(m_vecItems->Get(itemNumber)->GetProperty(action).asString());
1604 const CGUIViewState *CGUIMediaWindow::GetViewState() const
1606 return m_guiState.get();
1609 const CFileItemList& CGUIMediaWindow::CurrentDirectory() const
1614 bool CGUIMediaWindow::WaitForNetwork() const
1616 if (g_application.getNetwork().IsAvailable())
1619 CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
1623 CURL url(m_vecItems->GetPath());
1624 progress->SetHeading(1040); // Loading Directory
1625 progress->SetLine(1, url.GetWithoutUserDetails());
1626 progress->ShowProgressBar(false);
1627 progress->StartModal();
1628 while (!g_application.getNetwork().IsAvailable())
1630 progress->Progress();
1631 if (progress->IsCanceled())
1641 void CGUIMediaWindow::UpdateFilterPath(const CStdString &strDirectory, const CFileItemList &items, bool updateFilterPath)
1643 bool canfilter = CanContainFilter(strDirectory);
1646 CURL url(strDirectory);
1647 if (canfilter && url.HasOption("filter"))
1648 filter = url.GetOption("filter");
1650 // only set the filter path if it hasn't been marked
1651 // as preset or if it's empty
1652 if (updateFilterPath || m_strFilterPath.empty())
1654 if (items.HasProperty(PROPERTY_PATH_DB))
1655 m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString();
1657 m_strFilterPath = items.GetPath();
1660 // maybe the filter path can contain a filter
1661 if (!canfilter && CanContainFilter(m_strFilterPath))
1664 // check if the filter path contains a filter
1665 CURL filterPathUrl(m_strFilterPath);
1666 if (canfilter && filter.empty())
1668 if (filterPathUrl.HasOption("filter"))
1669 filter = filterPathUrl.GetOption("filter");
1672 // check if there is a filter and re-apply it
1673 if (canfilter && !filter.empty())
1675 if (!m_filter.LoadFromJson(filter))
1677 CLog::Log(LOGWARNING, "CGUIMediaWindow::UpdateFilterPath(): unable to load existing filter (%s)", filter.c_str());
1679 m_strFilterPath = m_vecItems->GetPath();
1683 // add the filter to the filter path
1684 filterPathUrl.SetOption("filter", filter);
1685 m_strFilterPath = filterPathUrl.Get();
1690 void CGUIMediaWindow::OnFilterItems(const CStdString &filter)
1692 CFileItemPtr currentItem;
1693 CStdString currentItemPath;
1694 int item = m_viewControl.GetSelectedItem();
1695 if (item >= 0 && item < m_vecItems->Size())
1697 currentItem = m_vecItems->Get(item);
1698 currentItemPath = currentItem->GetPath();
1701 m_viewControl.Clear();
1703 CFileItemList items;
1704 items.Copy(*m_vecItems, false); // use the original path - it'll likely be relied on for other things later.
1705 items.Append(*m_unfilteredItems);
1706 bool filtered = GetFilteredItems(filter, items);
1708 m_vecItems->ClearItems();
1709 // we need to clear the sort state and re-sort the items
1710 m_vecItems->ClearSortState();
1711 m_vecItems->Append(items);
1713 // if the filter has changed, get the new filter path
1714 if (filtered && m_canFilterAdvanced)
1716 if (items.HasProperty(PROPERTY_PATH_DB))
1717 m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString();
1718 // only set m_strFilterPath if it hasn't been set before
1719 // otherwise we might overwrite it with a non-filter path
1720 // in case GetFilteredItems() returns true even though no
1721 // db-based filter (e.g. watched filter) has been applied
1722 else if (m_strFilterPath.empty())
1723 m_strFilterPath = items.GetPath();
1726 GetGroupedItems(*m_vecItems);
1727 FormatAndSort(*m_vecItems);
1729 // get the "filter" option
1730 CStdString filterOption;
1731 CURL filterUrl(m_strFilterPath);
1732 if (filterUrl.HasOption("filter"))
1733 filterOption = filterUrl.GetOption("filter");
1735 // apply the "filter" option to any folder item so that
1736 // the filter can be passed down to the sub-directory
1737 for (int index = 0; index < m_vecItems->Size(); index++)
1739 CFileItemPtr pItem = m_vecItems->Get(index);
1740 // if the item is a folder we need to copy the path of
1741 // the filtered item to be able to keep the applied filters
1742 if (pItem->m_bIsFolder)
1744 CURL itemUrl(pItem->GetPath());
1745 if (!filterOption.empty())
1746 itemUrl.SetOption("filter", filterOption);
1748 itemUrl.RemoveOption("filter");
1749 pItem->SetPath(itemUrl.Get());
1753 SetProperty("filter", filter);
1754 if (filtered && m_canFilterAdvanced)
1756 // to be able to select the same item as before we need to adjust
1757 // the path of the item i.e. add or remove the "filter=" URL option
1758 // but that's only necessary for folder items
1759 if (currentItem.get() != NULL && currentItem->m_bIsFolder)
1761 CURL curUrl(currentItemPath), newUrl(m_strFilterPath);
1762 if (newUrl.HasOption("filter"))
1763 curUrl.SetOption("filter", newUrl.GetOption("filter"));
1764 else if (curUrl.HasOption("filter"))
1765 curUrl.RemoveOption("filter");
1767 currentItemPath = curUrl.Get();
1771 // The idea here is to ensure we have something to focus if our file list
1772 // is empty. As such, this check MUST be last and ignore the hide parent
1773 // fileitems settings.
1774 if (m_vecItems->IsEmpty())
1776 CFileItemPtr pItem(new CFileItem(".."));
1777 pItem->SetPath(m_history.GetParentPath());
1778 pItem->m_bIsFolder = true;
1779 pItem->m_bIsShareOrDrive = false;
1780 m_vecItems->AddFront(pItem, 0);
1783 // and update our view control + buttons
1784 m_viewControl.SetItems(*m_vecItems);
1785 m_viewControl.SetSelectedItem(currentItemPath);
1789 bool CGUIMediaWindow::GetFilteredItems(const CStdString &filter, CFileItemList &items)
1791 bool result = false;
1792 if (m_canFilterAdvanced)
1793 result = GetAdvanceFilteredItems(items);
1795 CStdString trimmedFilter(filter);
1796 trimmedFilter.TrimLeft().ToLower();
1798 if (trimmedFilter.IsEmpty())
1801 CFileItemList filteredItems(items.GetPath()); // use the original path - it'll likely be relied on for other things later.
1802 bool numericMatch = StringUtils::IsNaturalNumber(trimmedFilter);
1803 for (int i = 0; i < items.Size(); i++)
1805 CFileItemPtr item = items.Get(i);
1806 if (item->IsParentFolder())
1808 filteredItems.Add(item);
1811 // TODO: Need to update this to get all labels, ideally out of the displayed info (ie from m_layout and m_focusedLayout)
1812 // though that isn't practical. Perhaps a better idea would be to just grab the info that we should filter on based on
1813 // where we are in the library tree.
1814 // Another idea is tying the filter string to the current level of the tree, so that going deeper disables the filter,
1815 // but it's re-enabled on the way back out.
1817 /* if (item->GetFocusedLayout())
1818 match = item->GetFocusedLayout()->GetAllText();
1819 else if (item->GetLayout())
1820 match = item->GetLayout()->GetAllText();
1822 match = item->GetLabel(); // Filter label only for now
1825 StringUtils::WordToDigits(match);
1827 size_t pos = StringUtils::FindWords(match.c_str(), trimmedFilter.c_str());
1828 if (pos != CStdString::npos)
1829 filteredItems.Add(item);
1833 items.Append(filteredItems);
1835 return items.GetObjectCount() > 0;
1838 bool CGUIMediaWindow::GetAdvanceFilteredItems(CFileItemList &items)
1840 // don't run the advanced filter if the filter is empty
1841 // and there hasn't been a filter applied before which
1842 // would have to be removed
1843 CURL url(m_strFilterPath);
1844 if (m_filter.IsEmpty() && !url.HasOption("filter"))
1847 CFileItemList resultItems;
1848 XFILE::CSmartPlaylistDirectory::GetDirectory(m_filter, resultItems, m_strFilterPath, true);
1850 // put together a lookup map for faster path comparison
1851 map<CStdString, CFileItemPtr> lookup;
1852 for (int j = 0; j < resultItems.Size(); j++)
1854 CStdString itemPath = RemoveParameterFromPath(resultItems[j]->GetPath(), "filter");
1857 lookup[itemPath] = resultItems[j];
1860 // loop through all the original items and find
1861 // those which are still part of the filter
1862 CFileItemList filteredItems;
1863 for (int i = 0; i < items.Size(); i++)
1865 CFileItemPtr item = items.Get(i);
1866 if (item->IsParentFolder())
1868 filteredItems.Add(item);
1872 // check if the item is part of the resultItems list
1873 // by comparing their paths (but ignoring any special
1874 // options because they differ from filter to filter)
1875 CStdString path = RemoveParameterFromPath(item->GetPath(), "filter");
1878 map<CStdString, CFileItemPtr>::iterator itItem = lookup.find(path);
1879 if (itItem != lookup.end())
1881 // add the item to the list of filtered items
1882 filteredItems.Add(item);
1884 // remove the item from the lists
1885 resultItems.Remove(itItem->second.get());
1886 lookup.erase(itItem);
1890 if (resultItems.Size() > 0)
1891 CLog::Log(LOGWARNING, "CGUIMediaWindow::GetAdvanceFilteredItems(): %d unknown items", resultItems.Size());
1894 items.Append(filteredItems);
1895 items.SetPath(resultItems.GetPath());
1896 if (resultItems.HasProperty(PROPERTY_PATH_DB))
1897 items.SetProperty(PROPERTY_PATH_DB, resultItems.GetProperty(PROPERTY_PATH_DB));
1901 bool CGUIMediaWindow::IsFiltered()
1903 return (!m_canFilterAdvanced && !GetProperty("filter").empty()) ||
1904 (m_canFilterAdvanced && !m_filter.IsEmpty());
1907 bool CGUIMediaWindow::Filter(bool advanced /* = true */)
1910 if (!m_canFilterAdvanced || !advanced)
1912 const CGUIControl *btnFilter = GetControl(CONTROL_BTN_FILTER);
1913 if (btnFilter != NULL && btnFilter->GetControlType() == CGUIControl::GUICONTROL_EDIT)
1915 CGUIMessage selected(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTN_FILTER);
1916 OnMessage(selected);
1917 OnFilterItems(selected.GetLabel());
1920 if (GetProperty("filter").empty())
1922 CStdString filter = GetProperty("filter").asString();
1923 CGUIKeyboardFactory::ShowAndGetFilter(filter, false);
1924 SetProperty("filter", filter);
1929 // advanced filtering
1931 CGUIDialogMediaFilter::ShowAndEditMediaFilter(m_strFilterPath, m_filter);
1936 CStdString CGUIMediaWindow::GetStartFolder(const CStdString &dir)
1938 if (dir.Equals("$ROOT") || dir.Equals("Root"))
1943 CStdString CGUIMediaWindow::RemoveParameterFromPath(const CStdString &strDirectory, const CStdString &strParameter)
1945 CURL url(strDirectory);
1946 if (url.HasOption(strParameter))
1948 url.RemoveOption(strParameter);
1952 return strDirectory;