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.erase(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 = StringUtils::EqualsNoCase(ret, "return");
493 m_history.ClearPathHistory();
494 // ensure our directory is valid
495 dir = GetStartFolder(dir);
496 if (!returning || !StringUtils::StartsWith(m_vecItems->GetPath(), 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.empty() && cachedItems.Load(GetID()))
662 items.Assign(cachedItems);
666 unsigned int time = XbmcThreads::SystemClockMillis();
668 if (strDirectory.empty())
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().empty())
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().empty())
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.empty())
780 m_history.ClearPathHistory();
782 int iWindow = GetID();
784 if (strDirectory.empty())
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 ||
869 (iWindow == WINDOW_PVR && StringUtils::StartsWith(m_vecItems->GetPath(), "pvr://recordings/")))
870 m_history.AddPath(m_vecItems->GetPath(), m_strFilterPath);
872 //m_history.DumpPathHistory();
877 bool CGUIMediaWindow::Refresh(bool clearCache /* = false */)
879 CStdString strCurrentDirectory = m_vecItems->GetPath();
880 if (strCurrentDirectory.Equals("?"))
884 m_vecItems->RemoveDiscCache(GetID());
886 // get the original number of items
887 if (!Update(strCurrentDirectory, false))
893 // \brief This function will be called by Update() before the
894 // labels of the fileitems are formatted. Override this function
895 // to set custom thumbs or load additional media info.
896 // It's used to load tag info for music.
897 void CGUIMediaWindow::OnPrepareFileItems(CFileItemList &items)
899 CFileItemListModification::Get().Modify(items);
902 // \brief This function will be called by Update() before
903 // any additional formatting, filtering or sorting is applied.
904 // Override this function to define a custom caching behaviour.
905 void CGUIMediaWindow::OnCacheFileItems(CFileItemList &items)
907 // Should these items be saved to the hdd
908 if (items.CacheToDiscAlways() && !IsFiltered())
912 // \brief This function will be called by Update() after the
913 // labels of the fileitems are formatted. Override this function
914 // to modify the fileitems. Eg. to modify the item label
915 void CGUIMediaWindow::OnFinalizeFileItems(CFileItemList &items)
920 // \brief With this function you can react on a users click in the list/thumb panel.
921 // It returns true, if the click is handled.
922 // This function calls OnPlayMedia()
923 bool CGUIMediaWindow::OnClick(int iItem)
925 if ( iItem < 0 || iItem >= (int)m_vecItems->Size() ) return true;
926 CFileItemPtr pItem = m_vecItems->Get(iItem);
928 if (pItem->IsParentFolder())
933 if (pItem->GetPath() == "add" || pItem->GetPath() == "sources://add/") // 'add source button' in empty root
935 OnContextButton(iItem, CONTEXT_BUTTON_ADD_SOURCE);
939 if (!pItem->m_bIsFolder && pItem->IsFileFolder(EFILEFOLDER_MASK_ONCLICK))
941 XFILE::IFileDirectory *pFileDirectory = NULL;
942 pFileDirectory = XFILE::CFileDirectoryFactory::Create(pItem->GetPath(), pItem.get(), "");
944 pItem->m_bIsFolder = true;
945 else if(pItem->m_bIsFolder)
946 pItem->m_bIsFolder = false;
947 delete pFileDirectory;
950 if (pItem->IsScript())
952 // execute the script
953 CURL url(pItem->GetPath());
955 if (CAddonMgr::Get().GetAddon(url.GetHostName(), addon, ADDON_SCRIPT))
957 if (!CScriptInvocationManager::Get().Stop(addon->LibPath()))
958 CScriptInvocationManager::Get().Execute(addon->LibPath(), addon);
963 if (pItem->m_bIsFolder)
965 if ( pItem->m_bIsShareOrDrive )
967 const CStdString& strLockType=m_guiState->GetLockType();
968 if (CProfilesManager::Get().GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE)
969 if (!strLockType.empty() && !g_passwordManager.IsItemUnlocked(pItem.get(), strLockType))
972 if (!HaveDiscOrConnection(pItem->GetPath(), pItem->m_iDriveType))
976 // check for the partymode playlist items - they may not exist yet
977 if ((pItem->GetPath() == CProfilesManager::Get().GetUserDataItem("PartyMode.xsp")) ||
978 (pItem->GetPath() == CProfilesManager::Get().GetUserDataItem("PartyMode-Video.xsp")))
980 // party mode playlist item - if it doesn't exist, prompt for user to define it
981 if (!XFILE::CFile::Exists(pItem->GetPath()))
983 m_vecItems->RemoveDiscCache(GetID());
984 if (CGUIDialogSmartPlaylistEditor::EditPlaylist(pItem->GetPath()))
990 // remove the directory cache if the folder is not normally cached
991 CFileItemList items(pItem->GetPath());
992 if (!items.AlwaysCache())
993 items.RemoveDiscCache(GetID());
995 // if we have a filtered list, we need to add the filtered
996 // path to be able to come back to the filtered view
997 CStdString strCurrentDirectory = m_vecItems->GetPath();
998 if (m_canFilterAdvanced && !m_filter.IsEmpty() &&
999 !m_strFilterPath.Equals(strCurrentDirectory))
1001 m_history.RemoveParentPath();
1002 m_history.AddPath(strCurrentDirectory, m_strFilterPath);
1005 CFileItem directory(*pItem);
1006 if (!Update(directory.GetPath()))
1007 ShowShareErrorMessage(&directory);
1011 else if (pItem->IsPlugin() && !pItem->GetProperty("isplayable").asBoolean())
1013 return XFILE::CPluginDirectory::RunScriptWithParams(pItem->GetPath());
1015 #if defined(TARGET_ANDROID)
1016 else if (pItem->IsAndroidApp())
1018 CStdString appName = URIUtils::GetFileName(pItem->GetPath());
1019 CLog::Log(LOGDEBUG, "CGUIMediaWindow::OnClick Trying to run: %s",appName.c_str());
1020 return CXBMCApp::StartActivity(appName);
1025 m_iSelectedItem = m_viewControl.GetSelectedItem();
1027 if (pItem->GetPath() == "newplaylist://")
1029 m_vecItems->RemoveDiscCache(GetID());
1030 g_windowManager.ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR,"newplaylist://");
1033 else if (StringUtils::StartsWithNoCase(pItem->GetPath(), "newsmartplaylist://"))
1035 m_vecItems->RemoveDiscCache(GetID());
1036 if (CGUIDialogSmartPlaylistEditor::NewPlaylist(pItem->GetPath().substr(19)))
1040 else if (StringUtils::StartsWithNoCase(pItem->GetPath(), "addons://more/"))
1042 CBuiltins::Execute("ActivateWindow(AddonBrowser,addons://all/xbmc.addon." + pItem->GetPath().substr(14) + ",return)");
1046 // If karaoke song is being played AND popup autoselector is enabled, the playlist should not be added
1047 bool do_not_add_karaoke = CSettings::Get().GetBool("karaoke.enabled") &&
1048 CSettings::Get().GetBool("karaoke.autopopupselector") && pItem->IsKaraoke();
1049 bool autoplay = m_guiState.get() && m_guiState->AutoPlayNextItem();
1051 if (m_vecItems->IsPlugin())
1053 CURL url(m_vecItems->GetPath());
1055 if (CAddonMgr::Get().GetAddon(url.GetHostName(),addon))
1057 PluginPtr plugin = boost::dynamic_pointer_cast<CPluginSource>(addon);
1058 if (plugin && plugin->Provides(CPluginSource::AUDIO))
1060 CFileItemList items;
1061 auto_ptr<CGUIViewState> state(CGUIViewState::GetViewState(GetID(), items));
1062 autoplay = state.get() && state->AutoPlayNextItem();
1067 if (autoplay && !g_partyModeManager.IsEnabled() &&
1068 !pItem->IsPlayList() && !do_not_add_karaoke)
1070 return OnPlayAndQueueMedia(pItem);
1074 return OnPlayMedia(iItem);
1081 bool CGUIMediaWindow::OnSelect(int item)
1083 return OnClick(item);
1086 // \brief Checks if there is a disc in the dvd drive and whether the
1087 // network is connected or not.
1088 bool CGUIMediaWindow::HaveDiscOrConnection(const CStdString& strPath, int iDriveType)
1090 if (iDriveType==CMediaSource::SOURCE_TYPE_DVD)
1092 if (!g_mediaManager.IsDiscInDrive(strPath))
1094 CGUIDialogOK::ShowAndGetInput(218, 219, 0, 0);
1098 else if (iDriveType==CMediaSource::SOURCE_TYPE_REMOTE)
1100 // TODO: Handle not connected to a remote share
1101 if ( !g_application.getNetwork().IsConnected() )
1103 CGUIDialogOK::ShowAndGetInput(220, 221, 0, 0);
1111 // \brief Shows a standard errormessage for a given pItem.
1112 void CGUIMediaWindow::ShowShareErrorMessage(CFileItem* pItem)
1114 if (!pItem->m_bIsShareOrDrive)
1117 int idMessageText = 0;
1118 CURL url(pItem->GetPath());
1119 const CStdString& strHostName = url.GetHostName();
1121 if (url.GetProtocol() == "smb" && strHostName.empty()) // smb workgroup
1122 idMessageText = 15303; // Workgroup not found
1123 else if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_REMOTE || URIUtils::IsRemote(pItem->GetPath()))
1124 idMessageText = 15301; // Could not connect to network server
1126 idMessageText = 15300; // Path not found or invalid
1128 CGUIDialogOK::ShowAndGetInput(220, idMessageText, 0, 0);
1131 // \brief The functon goes up one level in the directory tree
1132 void CGUIMediaWindow::GoParentFolder()
1134 //m_history.DumpPathHistory();
1136 // remove current directory if its on the stack
1137 // there were some issues due some folders having a trailing slash and some not
1138 // so just add a trailing slash to all of them for comparison.
1139 CStdString strPath = m_vecItems->GetPath();
1140 URIUtils::AddSlashAtEnd(strPath);
1141 CStdString strParent = m_history.GetParentPath();
1142 // in case the path history is messed up and the current folder is on
1143 // the stack more than once, keep going until there's nothing left or they
1144 // dont match anymore.
1145 while (!strParent.empty())
1147 URIUtils::AddSlashAtEnd(strParent);
1148 if (strParent.Equals(strPath))
1149 m_history.RemoveParentPath();
1152 strParent = m_history.GetParentPath();
1155 // remove the current filter but only if the parent
1156 // item doesn't have a filter as well
1157 CURL filterUrl(m_strFilterPath);
1158 if (filterUrl.HasOption("filter"))
1160 CURL parentUrl(m_history.GetParentPath(true));
1161 if (!parentUrl.HasOption("filter"))
1163 // we need to overwrite m_strFilterPath because
1164 // Refresh() will set updateFilterPath to false
1165 m_strFilterPath.clear();
1171 // if vector is not empty, pop parent
1172 // if vector is empty, parent is root source listing
1173 m_strFilterPath = m_history.GetParentPath(true);
1174 strParent = m_history.RemoveParentPath();
1175 if (!Update(strParent, false))
1178 // No items to show so go another level up
1179 if (!m_vecItems->GetPath().empty() && (m_filter.IsEmpty() ? m_vecItems->Size() : m_unfilteredItems->Size()) <= 0)
1181 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(2080), g_localizeStrings.Get(2081));
1186 // \brief Override the function to change the default behavior on how
1187 // a selected item history should look like
1188 void CGUIMediaWindow::GetDirectoryHistoryString(const CFileItem* pItem, CStdString& strHistoryString)
1190 if (pItem->m_bIsShareOrDrive)
1192 // We are in the virual directory
1194 // History string of the DVD drive
1195 // must be handel separately
1196 if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_DVD)
1198 // Remove disc label from item label
1199 // and use as history string, m_strPath
1200 // can change for new discs
1201 CStdString strLabel = pItem->GetLabel();
1202 size_t nPosOpen = strLabel.find('(');
1203 size_t nPosClose = strLabel.rfind(')');
1204 if (nPosOpen != std::string::npos &&
1205 nPosClose != std::string::npos &&
1206 nPosClose > nPosOpen)
1208 strLabel.erase(nPosOpen + 1, (nPosClose) - (nPosOpen + 1));
1209 strHistoryString = strLabel;
1212 strHistoryString = strLabel;
1216 // Other items in virual directory
1217 CStdString strPath = pItem->GetPath();
1218 URIUtils::RemoveSlashAtEnd(strPath);
1220 strHistoryString = pItem->GetLabel() + strPath;
1223 else if (pItem->m_lEndOffset>pItem->m_lStartOffset && pItem->m_lStartOffset != -1)
1225 // Could be a cue item, all items of a cue share the same filename
1226 // so add the offsets to build the history string
1227 strHistoryString = StringUtils::Format("%ld%ld",
1228 pItem->m_lStartOffset,
1229 pItem->m_lEndOffset);
1230 strHistoryString += pItem->GetPath();
1234 // Normal directory items
1235 strHistoryString = pItem->GetPath();
1238 // remove any filter
1239 if (CanContainFilter(strHistoryString))
1240 strHistoryString = RemoveParameterFromPath(strHistoryString, "filter");
1242 URIUtils::RemoveSlashAtEnd(strHistoryString);
1243 StringUtils::ToLower(strHistoryString);
1246 // \brief Call this function to create a directory history for the
1247 // path given by strDirectory.
1248 void CGUIMediaWindow::SetHistoryForPath(const CStdString& strDirectory)
1250 // Make sure our shares are configured
1252 if (!strDirectory.empty())
1254 // Build the directory history for default path
1255 CStdString strPath, strParentPath;
1256 strPath = strDirectory;
1257 URIUtils::RemoveSlashAtEnd(strPath);
1259 CFileItemList items;
1260 m_rootDir.GetDirectory("", items);
1262 m_history.ClearPathHistory();
1264 while (URIUtils::GetParentPath(strPath, strParentPath))
1266 for (int i = 0; i < (int)items.Size(); ++i)
1268 CFileItemPtr pItem = items[i];
1269 CStdString path(pItem->GetPath());
1270 URIUtils::RemoveSlashAtEnd(path);
1271 if (path == strPath)
1273 CStdString strHistory;
1274 GetDirectoryHistoryString(pItem.get(), strHistory);
1275 m_history.SetSelectedItem(strHistory, "");
1276 URIUtils::AddSlashAtEnd(strPath);
1277 m_history.AddPathFront(strPath);
1278 m_history.AddPathFront("");
1280 //m_history.DumpPathHistory();
1285 if (URIUtils::IsVideoDb(strPath))
1287 CURL url(strParentPath);
1288 url.SetOptions(""); // clear any URL options from recreated parent path
1289 strParentPath = url.Get();
1292 URIUtils::AddSlashAtEnd(strPath);
1293 m_history.AddPathFront(strPath);
1294 m_history.SetSelectedItem(strPath, strParentPath);
1295 strPath = strParentPath;
1296 URIUtils::RemoveSlashAtEnd(strPath);
1300 m_history.ClearPathHistory();
1302 //m_history.DumpPathHistory();
1305 // \brief Override if you want to change the default behavior, what is done
1306 // when the user clicks on a file.
1307 // This function is called by OnClick()
1308 bool CGUIMediaWindow::OnPlayMedia(int iItem)
1310 // Reset Playlistplayer, playback started now does
1311 // not use the playlistplayer.
1312 g_playlistPlayer.Reset();
1313 g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_NONE);
1314 CFileItemPtr pItem=m_vecItems->Get(iItem);
1316 CLog::Log(LOGDEBUG, "%s %s", __FUNCTION__, CURL::GetRedacted(pItem->GetPath()).c_str());
1318 bool bResult = false;
1319 if (pItem->IsInternetStream() || pItem->IsPlayList())
1320 bResult = g_application.PlayMedia(*pItem, m_guiState->GetPlaylist());
1322 bResult = g_application.PlayFile(*pItem) == PLAYBACK_OK;
1324 if (pItem->m_lStartOffset == STARTOFFSET_RESUME)
1325 pItem->m_lStartOffset = 0;
1330 // \brief Override if you want to change the default behavior of what is done
1331 // when the user clicks on a file in a "folder" with similar files.
1332 // This function is called by OnClick()
1333 bool CGUIMediaWindow::OnPlayAndQueueMedia(const CFileItemPtr &item)
1335 //play and add current directory to temporary playlist
1336 int iPlaylist = m_guiState->GetPlaylist();
1337 if (iPlaylist != PLAYLIST_NONE)
1339 g_playlistPlayer.ClearPlaylist(iPlaylist);
1340 g_playlistPlayer.Reset();
1341 int mediaToPlay = 0;
1343 // first try to find mainDVD file (VIDEO_TS.IFO).
1344 // If we find this we should not allow to queue VOB files
1345 std::string mainDVD;
1346 for (int i = 0; i < m_vecItems->Size(); i++)
1348 std::string path = URIUtils::GetFileName(m_vecItems->Get(i)->GetPath());
1349 if (StringUtils::EqualsNoCase(path, "VIDEO_TS.IFO"))
1357 for ( int i = 0; i < m_vecItems->Size(); i++ )
1359 CFileItemPtr nItem = m_vecItems->Get(i);
1361 if (nItem->m_bIsFolder)
1364 if (!nItem->IsPlayList() && !nItem->IsZIP() && !nItem->IsRAR() && (!nItem->IsDVDFile() || (URIUtils::GetFileName(nItem->GetPath()) == mainDVD)))
1365 g_playlistPlayer.Add(iPlaylist, nItem);
1367 if (item->IsSamePath(nItem.get()))
1368 { // item that was clicked
1369 mediaToPlay = g_playlistPlayer.GetPlaylist(iPlaylist).size() - 1;
1373 // Save current window and directory to know where the selected item was
1374 if (m_guiState.get())
1375 m_guiState->SetPlaylistDirectory(m_vecItems->GetPath());
1377 // figure out where we start playback
1378 if (g_playlistPlayer.IsShuffled(iPlaylist))
1380 int iIndex = g_playlistPlayer.GetPlaylist(iPlaylist).FindOrder(mediaToPlay);
1381 g_playlistPlayer.GetPlaylist(iPlaylist).Swap(0, iIndex);
1386 g_playlistPlayer.SetCurrentPlaylist(iPlaylist);
1387 g_playlistPlayer.Play(mediaToPlay);
1392 // \brief Synchonize the fileitems with the playlistplayer
1393 // It recreated the playlist of the playlistplayer based
1394 // on the fileitems of the window
1395 void CGUIMediaWindow::UpdateFileList()
1397 int nItem = m_viewControl.GetSelectedItem();
1398 CStdString strSelected;
1400 strSelected = m_vecItems->Get(nItem)->GetPath();
1402 FormatAndSort(*m_vecItems);
1405 m_viewControl.SetItems(*m_vecItems);
1406 m_viewControl.SetSelectedItem(strSelected);
1408 // set the currently playing item as selected, if its in this directory
1409 if (m_guiState.get() && m_guiState->IsCurrentPlaylistDirectory(m_vecItems->GetPath()))
1411 int iPlaylist=m_guiState->GetPlaylist();
1412 int nSong = g_playlistPlayer.GetCurrentSong();
1413 CFileItem playlistItem;
1414 if (nSong > -1 && iPlaylist > -1)
1415 playlistItem=*g_playlistPlayer.GetPlaylist(iPlaylist)[nSong];
1417 g_playlistPlayer.ClearPlaylist(iPlaylist);
1418 g_playlistPlayer.Reset();
1420 for (int i = 0; i < m_vecItems->Size(); i++)
1422 CFileItemPtr pItem = m_vecItems->Get(i);
1423 if (pItem->m_bIsFolder)
1426 if (!pItem->IsPlayList() && !pItem->IsZIP() && !pItem->IsRAR())
1427 g_playlistPlayer.Add(iPlaylist, pItem);
1429 if (pItem->GetPath() == playlistItem.GetPath() &&
1430 pItem->m_lStartOffset == playlistItem.m_lStartOffset)
1431 g_playlistPlayer.SetCurrentSong(g_playlistPlayer.GetPlaylist(iPlaylist).size() - 1);
1436 void CGUIMediaWindow::OnDeleteItem(int iItem)
1438 if ( iItem < 0 || iItem >= m_vecItems->Size()) return;
1439 CFileItemPtr item = m_vecItems->Get(iItem);
1441 if (item->IsPlayList())
1442 item->m_bIsFolder = false;
1444 if (CProfilesManager::Get().GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && CProfilesManager::Get().GetCurrentProfile().filesLocked())
1445 if (!g_passwordManager.IsMasterLockUnlocked(true))
1448 if (!CFileUtils::DeleteItem(item))
1451 m_viewControl.SetSelectedItem(iItem);
1454 void CGUIMediaWindow::OnRenameItem(int iItem)
1456 if ( iItem < 0 || iItem >= m_vecItems->Size()) return;
1458 if (CProfilesManager::Get().GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && CProfilesManager::Get().GetCurrentProfile().filesLocked())
1459 if (!g_passwordManager.IsMasterLockUnlocked(true))
1462 if (!CFileUtils::RenameFile(m_vecItems->Get(iItem)->GetPath()))
1465 m_viewControl.SetSelectedItem(iItem);
1468 void CGUIMediaWindow::OnInitWindow()
1470 // initial fetch is done unthreaded to ensure the items are setup prior to skin animations kicking off
1471 m_rootDir.SetAllowThreads(false);
1473 // the start directory may change during Refresh
1474 bool updateStartDirectory = (m_startDirectory == m_vecItems->GetPath());
1476 if (updateStartDirectory)
1477 m_startDirectory = m_vecItems->GetPath();
1479 m_rootDir.SetAllowThreads(true);
1481 if (m_iSelectedItem > -1)
1482 m_viewControl.SetSelectedItem(m_iSelectedItem);
1484 CGUIWindow::OnInitWindow();
1487 CGUIControl *CGUIMediaWindow::GetFirstFocusableControl(int id)
1489 if (m_viewControl.HasControl(id))
1490 id = m_viewControl.GetCurrentControl();
1491 return CGUIWindow::GetFirstFocusableControl(id);
1494 void CGUIMediaWindow::SetupShares()
1496 // Setup shares and filemasks for this window
1497 CFileItemList items;
1498 CGUIViewState* viewState=CGUIViewState::GetViewState(GetID(), items);
1501 m_rootDir.SetMask(viewState->GetExtensions());
1502 m_rootDir.SetSources(viewState->GetSources());
1507 bool CGUIMediaWindow::OnPopupMenu(int iItem)
1509 // popup the context menu
1510 // grab our context menu
1511 CContextButtons buttons;
1512 GetContextButtons(iItem, buttons);
1517 if (iItem >= 0 && iItem < m_vecItems->Size())
1518 m_vecItems->Get(iItem)->Select(true);
1520 int choice = CGUIDialogContextMenu::ShowAndGetChoice(buttons);
1522 // deselect our item
1523 if (iItem >= 0 && iItem < m_vecItems->Size())
1524 m_vecItems->Get(iItem)->Select(false);
1527 return OnContextButton(iItem, (CONTEXT_BUTTON)choice);
1532 void CGUIMediaWindow::GetContextButtons(int itemNumber, CContextButtons &buttons)
1534 CFileItemPtr item = (itemNumber >= 0 && itemNumber < m_vecItems->Size()) ? m_vecItems->Get(itemNumber) : CFileItemPtr();
1539 // user added buttons
1542 for (int i = CONTEXT_BUTTON_USER1; i <= CONTEXT_BUTTON_USER10; i++)
1544 label = StringUtils::Format("contextmenulabel(%i)", i - CONTEXT_BUTTON_USER1);
1545 if (item->GetProperty(label).empty())
1548 action = StringUtils::Format("contextmenuaction(%i)", i - CONTEXT_BUTTON_USER1);
1549 if (item->GetProperty(action).empty())
1552 buttons.Add((CONTEXT_BUTTON)i, item->GetProperty(label).asString());
1555 if (item->GetProperty("pluginreplacecontextitems").asBoolean())
1558 // TODO: FAVOURITES Conditions on masterlock and localisation
1559 if (!item->IsParentFolder() && !item->GetPath().Equals("add") && !item->GetPath().Equals("newplaylist://") &&
1560 !StringUtils::StartsWithNoCase(item->GetPath(), "newsmartplaylist://") && !StringUtils::StartsWithNoCase(item->GetPath(), "newtag://") &&
1561 !StringUtils::StartsWithNoCase(item->GetPath(), "addons://more/") && !StringUtils::StartsWithNoCase(item->GetPath(), "musicsearch://"))
1563 if (XFILE::CFavouritesDirectory::IsFavourite(item.get(), GetID()))
1564 buttons.Add(CONTEXT_BUTTON_ADD_FAVOURITE, 14077); // Remove Favourite
1566 buttons.Add(CONTEXT_BUTTON_ADD_FAVOURITE, 14076); // Add To Favourites;
1569 if (item->IsFileFolder(EFILEFOLDER_MASK_ONBROWSE))
1570 buttons.Add(CONTEXT_BUTTON_BROWSE_INTO, 37015);
1574 bool CGUIMediaWindow::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
1578 case CONTEXT_BUTTON_ADD_FAVOURITE:
1580 CFileItemPtr item = m_vecItems->Get(itemNumber);
1581 XFILE::CFavouritesDirectory::AddOrRemove(item.get(), GetID());
1584 case CONTEXT_BUTTON_PLUGIN_SETTINGS:
1586 CFileItemPtr item = m_vecItems->Get(itemNumber);
1587 // CONTEXT_BUTTON_PLUGIN_SETTINGS can be called for plugin item
1588 // or script item; or for the plugin directory current listing.
1589 bool isPluginOrScriptItem = (item && (item->IsPlugin() || item->IsScript()));
1590 CURL plugin(isPluginOrScriptItem ? item->GetPath() : m_vecItems->GetPath());
1591 ADDON::AddonPtr addon;
1592 if (CAddonMgr::Get().GetAddon(plugin.GetHostName(), addon))
1593 if (CGUIDialogAddonSettings::ShowAndGetInput(addon))
1597 case CONTEXT_BUTTON_BROWSE_INTO:
1599 CFileItemPtr item = m_vecItems->Get(itemNumber);
1600 if(Update(item->GetPath()))
1604 case CONTEXT_BUTTON_USER1:
1605 case CONTEXT_BUTTON_USER2:
1606 case CONTEXT_BUTTON_USER3:
1607 case CONTEXT_BUTTON_USER4:
1608 case CONTEXT_BUTTON_USER5:
1609 case CONTEXT_BUTTON_USER6:
1610 case CONTEXT_BUTTON_USER7:
1611 case CONTEXT_BUTTON_USER8:
1612 case CONTEXT_BUTTON_USER9:
1613 case CONTEXT_BUTTON_USER10:
1615 CStdString action = StringUtils::Format("contextmenuaction(%i)", button - CONTEXT_BUTTON_USER1);
1616 CApplicationMessenger::Get().ExecBuiltIn(m_vecItems->Get(itemNumber)->GetProperty(action).asString());
1625 const CGUIViewState *CGUIMediaWindow::GetViewState() const
1627 return m_guiState.get();
1630 const CFileItemList& CGUIMediaWindow::CurrentDirectory() const
1635 bool CGUIMediaWindow::WaitForNetwork() const
1637 if (g_application.getNetwork().IsAvailable())
1640 CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
1644 CURL url(m_vecItems->GetPath());
1645 progress->SetHeading(1040); // Loading Directory
1646 progress->SetLine(1, url.GetWithoutUserDetails());
1647 progress->ShowProgressBar(false);
1648 progress->StartModal();
1649 while (!g_application.getNetwork().IsAvailable())
1651 progress->Progress();
1652 if (progress->IsCanceled())
1662 void CGUIMediaWindow::UpdateFilterPath(const CStdString &strDirectory, const CFileItemList &items, bool updateFilterPath)
1664 bool canfilter = CanContainFilter(strDirectory);
1667 CURL url(strDirectory);
1668 if (canfilter && url.HasOption("filter"))
1669 filter = url.GetOption("filter");
1671 // only set the filter path if it hasn't been marked
1672 // as preset or if it's empty
1673 if (updateFilterPath || m_strFilterPath.empty())
1675 if (items.HasProperty(PROPERTY_PATH_DB))
1676 m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString();
1678 m_strFilterPath = items.GetPath();
1681 // maybe the filter path can contain a filter
1682 if (!canfilter && CanContainFilter(m_strFilterPath))
1685 // check if the filter path contains a filter
1686 CURL filterPathUrl(m_strFilterPath);
1687 if (canfilter && filter.empty())
1689 if (filterPathUrl.HasOption("filter"))
1690 filter = filterPathUrl.GetOption("filter");
1693 // check if there is a filter and re-apply it
1694 if (canfilter && !filter.empty())
1696 if (!m_filter.LoadFromJson(filter))
1698 CLog::Log(LOGWARNING, "CGUIMediaWindow::UpdateFilterPath(): unable to load existing filter (%s)", filter.c_str());
1700 m_strFilterPath = m_vecItems->GetPath();
1704 // add the filter to the filter path
1705 filterPathUrl.SetOption("filter", filter);
1706 m_strFilterPath = filterPathUrl.Get();
1711 void CGUIMediaWindow::OnFilterItems(const CStdString &filter)
1713 CFileItemPtr currentItem;
1714 CStdString currentItemPath;
1715 int item = m_viewControl.GetSelectedItem();
1716 if (item >= 0 && item < m_vecItems->Size())
1718 currentItem = m_vecItems->Get(item);
1719 currentItemPath = currentItem->GetPath();
1722 m_viewControl.Clear();
1724 CFileItemList items;
1725 items.Copy(*m_vecItems, false); // use the original path - it'll likely be relied on for other things later.
1726 items.Append(*m_unfilteredItems);
1727 bool filtered = GetFilteredItems(filter, items);
1729 m_vecItems->ClearItems();
1730 // we need to clear the sort state and re-sort the items
1731 m_vecItems->ClearSortState();
1732 m_vecItems->Append(items);
1734 // if the filter has changed, get the new filter path
1735 if (filtered && m_canFilterAdvanced)
1737 if (items.HasProperty(PROPERTY_PATH_DB))
1738 m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString();
1739 // only set m_strFilterPath if it hasn't been set before
1740 // otherwise we might overwrite it with a non-filter path
1741 // in case GetFilteredItems() returns true even though no
1742 // db-based filter (e.g. watched filter) has been applied
1743 else if (m_strFilterPath.empty())
1744 m_strFilterPath = items.GetPath();
1747 GetGroupedItems(*m_vecItems);
1748 FormatAndSort(*m_vecItems);
1750 // get the "filter" option
1751 CStdString filterOption;
1752 CURL filterUrl(m_strFilterPath);
1753 if (filterUrl.HasOption("filter"))
1754 filterOption = filterUrl.GetOption("filter");
1756 // apply the "filter" option to any folder item so that
1757 // the filter can be passed down to the sub-directory
1758 for (int index = 0; index < m_vecItems->Size(); index++)
1760 CFileItemPtr pItem = m_vecItems->Get(index);
1761 // if the item is a folder we need to copy the path of
1762 // the filtered item to be able to keep the applied filters
1763 if (pItem->m_bIsFolder)
1765 CURL itemUrl(pItem->GetPath());
1766 if (!filterOption.empty())
1767 itemUrl.SetOption("filter", filterOption);
1769 itemUrl.RemoveOption("filter");
1770 pItem->SetPath(itemUrl.Get());
1774 SetProperty("filter", filter);
1775 if (filtered && m_canFilterAdvanced)
1777 // to be able to select the same item as before we need to adjust
1778 // the path of the item i.e. add or remove the "filter=" URL option
1779 // but that's only necessary for folder items
1780 if (currentItem.get() != NULL && currentItem->m_bIsFolder)
1782 CURL curUrl(currentItemPath), newUrl(m_strFilterPath);
1783 if (newUrl.HasOption("filter"))
1784 curUrl.SetOption("filter", newUrl.GetOption("filter"));
1785 else if (curUrl.HasOption("filter"))
1786 curUrl.RemoveOption("filter");
1788 currentItemPath = curUrl.Get();
1792 // The idea here is to ensure we have something to focus if our file list
1793 // is empty. As such, this check MUST be last and ignore the hide parent
1794 // fileitems settings.
1795 if (m_vecItems->IsEmpty())
1797 CFileItemPtr pItem(new CFileItem(".."));
1798 pItem->SetPath(m_history.GetParentPath());
1799 pItem->m_bIsFolder = true;
1800 pItem->m_bIsShareOrDrive = false;
1801 m_vecItems->AddFront(pItem, 0);
1804 // and update our view control + buttons
1805 m_viewControl.SetItems(*m_vecItems);
1806 m_viewControl.SetSelectedItem(currentItemPath);
1810 bool CGUIMediaWindow::GetFilteredItems(const CStdString &filter, CFileItemList &items)
1812 bool result = false;
1813 if (m_canFilterAdvanced)
1814 result = GetAdvanceFilteredItems(items);
1816 CStdString trimmedFilter(filter);
1817 StringUtils::TrimLeft(trimmedFilter);
1818 StringUtils::ToLower(trimmedFilter);
1820 if (trimmedFilter.empty())
1823 CFileItemList filteredItems(items.GetPath()); // use the original path - it'll likely be relied on for other things later.
1824 bool numericMatch = StringUtils::IsNaturalNumber(trimmedFilter);
1825 for (int i = 0; i < items.Size(); i++)
1827 CFileItemPtr item = items.Get(i);
1828 if (item->IsParentFolder())
1830 filteredItems.Add(item);
1833 // TODO: Need to update this to get all labels, ideally out of the displayed info (ie from m_layout and m_focusedLayout)
1834 // though that isn't practical. Perhaps a better idea would be to just grab the info that we should filter on based on
1835 // where we are in the library tree.
1836 // Another idea is tying the filter string to the current level of the tree, so that going deeper disables the filter,
1837 // but it's re-enabled on the way back out.
1839 /* if (item->GetFocusedLayout())
1840 match = item->GetFocusedLayout()->GetAllText();
1841 else if (item->GetLayout())
1842 match = item->GetLayout()->GetAllText();
1844 match = item->GetLabel(); // Filter label only for now
1847 StringUtils::WordToDigits(match);
1849 size_t pos = StringUtils::FindWords(match.c_str(), trimmedFilter.c_str());
1850 if (pos != CStdString::npos)
1851 filteredItems.Add(item);
1855 items.Append(filteredItems);
1857 return items.GetObjectCount() > 0;
1860 bool CGUIMediaWindow::GetAdvanceFilteredItems(CFileItemList &items)
1862 // don't run the advanced filter if the filter is empty
1863 // and there hasn't been a filter applied before which
1864 // would have to be removed
1865 CURL url(m_strFilterPath);
1866 if (m_filter.IsEmpty() && !url.HasOption("filter"))
1869 CFileItemList resultItems;
1870 XFILE::CSmartPlaylistDirectory::GetDirectory(m_filter, resultItems, m_strFilterPath, true);
1872 // put together a lookup map for faster path comparison
1873 map<CStdString, CFileItemPtr> lookup;
1874 for (int j = 0; j < resultItems.Size(); j++)
1876 CStdString itemPath = RemoveParameterFromPath(resultItems[j]->GetPath(), "filter");
1877 StringUtils::ToLower(itemPath);
1879 lookup[itemPath] = resultItems[j];
1882 // loop through all the original items and find
1883 // those which are still part of the filter
1884 CFileItemList filteredItems;
1885 for (int i = 0; i < items.Size(); i++)
1887 CFileItemPtr item = items.Get(i);
1888 if (item->IsParentFolder())
1890 filteredItems.Add(item);
1894 // check if the item is part of the resultItems list
1895 // by comparing their paths (but ignoring any special
1896 // options because they differ from filter to filter)
1897 CStdString path = RemoveParameterFromPath(item->GetPath(), "filter");
1898 StringUtils::ToLower(path);
1900 map<CStdString, CFileItemPtr>::iterator itItem = lookup.find(path);
1901 if (itItem != lookup.end())
1903 // add the item to the list of filtered items
1904 filteredItems.Add(item);
1906 // remove the item from the lists
1907 resultItems.Remove(itItem->second.get());
1908 lookup.erase(itItem);
1912 if (resultItems.Size() > 0)
1913 CLog::Log(LOGWARNING, "CGUIMediaWindow::GetAdvanceFilteredItems(): %d unknown items", resultItems.Size());
1916 items.Append(filteredItems);
1917 items.SetPath(resultItems.GetPath());
1918 if (resultItems.HasProperty(PROPERTY_PATH_DB))
1919 items.SetProperty(PROPERTY_PATH_DB, resultItems.GetProperty(PROPERTY_PATH_DB));
1923 bool CGUIMediaWindow::IsFiltered()
1925 return (!m_canFilterAdvanced && !GetProperty("filter").empty()) ||
1926 (m_canFilterAdvanced && !m_filter.IsEmpty());
1929 bool CGUIMediaWindow::Filter(bool advanced /* = true */)
1932 if (!m_canFilterAdvanced || !advanced)
1934 const CGUIControl *btnFilter = GetControl(CONTROL_BTN_FILTER);
1935 if (btnFilter != NULL && btnFilter->GetControlType() == CGUIControl::GUICONTROL_EDIT)
1937 CGUIMessage selected(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTN_FILTER);
1938 OnMessage(selected);
1939 OnFilterItems(selected.GetLabel());
1942 if (GetProperty("filter").empty())
1944 CStdString filter = GetProperty("filter").asString();
1945 CGUIKeyboardFactory::ShowAndGetFilter(filter, false);
1946 SetProperty("filter", filter);
1951 // advanced filtering
1953 CGUIDialogMediaFilter::ShowAndEditMediaFilter(m_strFilterPath, m_filter);
1958 CStdString CGUIMediaWindow::GetStartFolder(const CStdString &dir)
1960 if (dir.Equals("$ROOT") || dir.Equals("Root"))
1965 CStdString CGUIMediaWindow::RemoveParameterFromPath(const CStdString &strDirectory, const CStdString &strParameter)
1967 CURL url(strDirectory);
1968 if (url.HasOption(strParameter))
1970 url.RemoveOption(strParameter);
1974 return strDirectory;