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 "Favourites.h"
39 #include "utils/LabelFormatter.h"
40 #include "dialogs/GUIDialogProgress.h"
41 #include "profiles/ProfilesManager.h"
42 #include "settings/AdvancedSettings.h"
43 #include "settings/GUISettings.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"
65 #include "interfaces/python/XBPython.h"
67 #include "interfaces/Builtins.h"
68 #include "dialogs/GUIDialogKaiToast.h"
69 #include "dialogs/GUIDialogMediaFilter.h"
70 #include "filesystem/SmartPlaylistDirectory.h"
71 #if defined(TARGET_ANDROID)
72 #include "xbmc/android/activity/XBMCApp.h"
75 #define CONTROL_BTNVIEWASICONS 2
76 #define CONTROL_BTNSORTBY 3
77 #define CONTROL_BTNSORTASC 4
78 #define CONTROL_BTN_FILTER 19
80 #define CONTROL_LABELFILES 12
82 #define PROPERTY_PATH_DB "path.db"
83 #define PROPERTY_SORT_ORDER "sort.order"
84 #define PROPERTY_SORT_ASCENDING "sort.ascending"
87 using namespace ADDON;
89 CGUIMediaWindow::CGUIMediaWindow(int id, const char *xmlFile)
90 : CGUIWindow(id, xmlFile)
92 m_loadType = KEEP_IN_MEMORY;
93 m_vecItems = new CFileItemList;
94 m_unfilteredItems = new CFileItemList;
95 m_vecItems->SetPath("?");
98 m_canFilterAdvanced = false;
100 m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems));
103 CGUIMediaWindow::~CGUIMediaWindow()
106 delete m_unfilteredItems;
109 #define CONTROL_VIEW_START 50
110 #define CONTROL_VIEW_END 59
112 void CGUIMediaWindow::LoadAdditionalTags(TiXmlElement *root)
114 CGUIWindow::LoadAdditionalTags(root);
115 // configure our view control
116 m_viewControl.Reset();
117 m_viewControl.SetParentWindow(GetID());
118 TiXmlElement *element = root->FirstChildElement("views");
119 if (element && element->FirstChild())
120 { // format is <views>50,29,51,95</views>
121 CStdString allViews = element->FirstChild()->Value();
122 CStdStringArray views;
123 StringUtils::SplitString(allViews, ",", views);
124 for (unsigned int i = 0; i < views.size(); i++)
126 int controlID = atol(views[i].c_str());
127 CGUIControl *control = (CGUIControl *)GetControl(controlID);
128 if (control && control->IsContainer())
129 m_viewControl.AddView(control);
133 { // backward compatibility
134 vector<CGUIControl *> controls;
135 GetContainers(controls);
136 for (ciControls it = controls.begin(); it != controls.end(); it++)
138 CGUIControl *control = *it;
139 if (control->GetID() >= CONTROL_VIEW_START && control->GetID() <= CONTROL_VIEW_END)
140 m_viewControl.AddView(control);
143 m_viewControl.SetViewControlID(CONTROL_BTNVIEWASICONS);
146 void CGUIMediaWindow::OnWindowLoaded()
148 SendMessage(GUI_MSG_SET_TYPE, CONTROL_BTN_FILTER, CGUIEditControl::INPUT_TYPE_FILTER);
149 CGUIWindow::OnWindowLoaded();
153 void CGUIMediaWindow::OnWindowUnload()
155 CGUIWindow::OnWindowUnload();
156 m_viewControl.Reset();
159 CFileItemPtr CGUIMediaWindow::GetCurrentListItem(int offset)
161 int item = m_viewControl.GetSelectedItem();
162 if (!m_vecItems->Size() || item < 0)
163 return CFileItemPtr();
164 item = (item + offset) % m_vecItems->Size();
165 if (item < 0) item += m_vecItems->Size();
166 return m_vecItems->Get(item);
169 bool CGUIMediaWindow::OnAction(const CAction &action)
171 if (action.GetID() == ACTION_PARENT_DIR)
177 // the non-contextual menu can be called at any time
178 if (action.GetID() == ACTION_CONTEXT_MENU && !m_viewControl.HasControl(GetFocusedControlID()))
184 if (CGUIWindow::OnAction(action))
187 if (action.GetID() == ACTION_FILTER)
191 if (action.GetID() == ACTION_FILTER_CLEAR)
193 CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS);
194 message.SetStringParam("");
199 if (action.GetID() == ACTION_BACKSPACE)
201 CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 2); // 2 for delete
206 if (action.GetID() >= ACTION_FILTER_SMS2 && action.GetID() <= ACTION_FILTER_SMS9)
209 filter.Format("%i", (int)(action.GetID() - ACTION_FILTER_SMS2 + 2));
210 CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 1); // 1 for append
211 message.SetStringParam(filter);
219 bool CGUIMediaWindow::OnBack(int actionID)
221 CURL filterUrl(m_strFilterPath);
222 if (actionID == ACTION_NAV_BACK && !m_vecItems->IsVirtualDirectoryRoot() &&
223 (m_vecItems->GetPath() != m_startDirectory || (m_canFilterAdvanced && filterUrl.HasOption("filter"))))
228 return CGUIWindow::OnBack(actionID);
231 bool CGUIMediaWindow::OnMessage(CGUIMessage& message)
233 switch ( message.GetMessage() )
235 case GUI_MSG_WINDOW_DEINIT:
237 m_iSelectedItem = m_viewControl.GetSelectedItem();
238 m_iLastControl = GetFocusedControlID();
239 CGUIWindow::OnMessage(message);
240 CGUIDialogContextMenu* pDlg = (CGUIDialogContextMenu*)g_windowManager.GetWindow(WINDOW_DIALOG_CONTEXT_MENU);
241 if (pDlg && pDlg->IsActive())
244 // get rid of any active filtering
245 if (m_canFilterAdvanced)
247 m_canFilterAdvanced = false;
250 m_strFilterPath.clear();
252 // Call ClearFileItems() after our window has finished doing any WindowClose
259 case GUI_MSG_CLICKED:
261 int iControl = message.GetSenderId();
262 if (iControl == CONTROL_BTNVIEWASICONS)
264 // view as control could be a select button
266 const CGUIControl *control = GetControl(CONTROL_BTNVIEWASICONS);
267 if (control && control->GetControlType() != CGUIControl::GUICONTROL_BUTTON)
269 CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTNVIEWASICONS);
271 viewMode = m_viewControl.GetViewModeNumber(msg.GetParam1());
274 viewMode = m_viewControl.GetNextViewMode();
276 if (m_guiState.get())
277 m_guiState->SaveViewAsControl(viewMode);
282 else if (iControl == CONTROL_BTNSORTASC) // sort asc
284 if (m_guiState.get())
285 m_guiState->SetNextSortOrder();
289 else if (iControl == CONTROL_BTNSORTBY) // sort by
291 if (m_guiState.get())
292 m_guiState->SetNextSortMethod();
296 else if (iControl == CONTROL_BTN_FILTER)
297 return Filter(false);
298 else if (m_viewControl.HasControl(iControl)) // list/thumb control
300 int iItem = m_viewControl.GetSelectedItem();
301 int iAction = message.GetParam1();
302 if (iItem < 0) break;
303 if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK)
307 else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK)
316 case GUI_MSG_SETFOCUS:
318 if (m_viewControl.HasControl(message.GetControlId()) && m_viewControl.GetCurrentControl() != message.GetControlId())
320 m_viewControl.SetFocused();
326 case GUI_MSG_NOTIFY_ALL:
327 { // Message is received even if this window is inactive
328 if (message.GetParam1() == GUI_MSG_WINDOW_RESET)
330 m_vecItems->SetPath("?");
333 else if ( message.GetParam1() == GUI_MSG_REFRESH_THUMBS )
335 for (int i = 0; i < m_vecItems->Size(); i++)
336 m_vecItems->Get(i)->FreeMemory(true);
337 break; // the window will take care of any info images
339 else if (message.GetParam1() == GUI_MSG_REMOVED_MEDIA)
341 if ((m_vecItems->IsVirtualDirectoryRoot() ||
342 m_vecItems->IsSourcesPath()) && IsActive())
344 int iItem = m_viewControl.GetSelectedItem();
346 m_viewControl.SetSelectedItem(iItem);
348 else if (m_vecItems->IsRemovable())
349 { // check that we have this removable share still
350 if (!m_rootDir.IsInSource(m_vecItems->GetPath()))
351 { // don't have this share any more
352 if (IsActive()) Update("");
355 m_history.ClearPathHistory();
356 m_vecItems->SetPath("");
363 else if (message.GetParam1()==GUI_MSG_UPDATE_SOURCES)
364 { // State of the sources changed, so update our view
365 if ((m_vecItems->IsVirtualDirectoryRoot() ||
366 m_vecItems->IsSourcesPath()) && IsActive())
368 int iItem = m_viewControl.GetSelectedItem();
370 m_viewControl.SetSelectedItem(iItem);
374 else if (message.GetParam1()==GUI_MSG_UPDATE && IsActive())
376 if (message.GetNumStringParams())
378 if (message.GetParam2()) // param2 is used for resetting the history
379 SetHistoryForPath(message.GetStringParam());
381 CFileItemList list(message.GetStringParam());
382 list.RemoveDiscCache(GetID());
383 Update(message.GetStringParam());
386 Refresh(true); // refresh the listing
388 else if (message.GetParam1()==GUI_MSG_UPDATE_ITEM && message.GetItem())
390 CFileItemPtr newItem = boost::static_pointer_cast<CFileItem>(message.GetItem());
393 if (m_vecItems->UpdateItem(newItem.get()) && message.GetParam2() == 1)
394 { // need the list updated as well
399 { // need to remove the disc cache
402 URIUtils::GetDirectory(newItem->GetPath(), path);
404 items.RemoveDiscCache(GetID());
407 else if (message.GetParam1()==GUI_MSG_UPDATE_PATH)
411 if((message.GetStringParam() == m_vecItems->GetPath()) ||
412 (m_vecItems->IsMultiPath() && XFILE::CMultiPathDirectory::HasPath(m_vecItems->GetPath(), message.GetStringParam())))
416 else if (message.GetParam1() == GUI_MSG_FILTER_ITEMS && IsActive())
418 CStdString filter = GetProperty("filter").asString();
419 // check if this is meant for advanced filtering
420 if (message.GetParam2() != 10)
422 if (message.GetParam2() == 1) // append
423 filter += message.GetStringParam();
424 else if (message.GetParam2() == 2)
427 filter = filter.Left(filter.size() - 1);
430 filter = message.GetStringParam();
432 OnFilterItems(filter);
436 return CGUIWindow::OnMessage(message);
441 case GUI_MSG_PLAYBACK_STARTED:
442 case GUI_MSG_PLAYBACK_ENDED:
443 case GUI_MSG_PLAYBACK_STOPPED:
444 case GUI_MSG_PLAYLIST_CHANGED:
445 case GUI_MSG_PLAYLISTPLAYER_STOPPED:
446 case GUI_MSG_PLAYLISTPLAYER_STARTED:
447 case GUI_MSG_PLAYLISTPLAYER_CHANGED:
448 { // send a notify all to all controls on this window
449 CGUIMessage msg(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_REFRESH_LIST);
453 case GUI_MSG_CHANGE_VIEW_MODE:
456 if (message.GetParam1()) // we have an id
457 viewMode = m_viewControl.GetViewModeByID(message.GetParam1());
458 else if (message.GetParam2())
459 viewMode = m_viewControl.GetNextViewMode((int)message.GetParam2());
461 if (m_guiState.get())
462 m_guiState->SaveViewAsControl(viewMode);
467 case GUI_MSG_CHANGE_SORT_METHOD:
469 if (m_guiState.get())
471 if (message.GetParam1())
472 m_guiState->SetCurrentSortMethod((int)message.GetParam1());
473 else if (message.GetParam2())
474 m_guiState->SetNextSortMethod((int)message.GetParam2());
480 case GUI_MSG_CHANGE_SORT_DIRECTION:
482 if (m_guiState.get())
483 m_guiState->SetNextSortOrder();
488 case GUI_MSG_WINDOW_INIT:
490 if (m_vecItems->GetPath() == "?")
491 m_vecItems->SetPath("");
492 CStdString dir = message.GetStringParam(0);
493 const CStdString &ret = message.GetStringParam(1);
494 bool returning = ret.CompareNoCase("return") == 0;
497 m_history.ClearPathHistory();
498 // ensure our directory is valid
499 dir = GetStartFolder(dir);
500 if (!returning || m_vecItems->GetPath().Left(dir.GetLength()) != dir)
501 { // we're not returning to the same path, so set our directory to the requested path
502 m_vecItems->SetPath(dir);
504 // check for network up
505 if (URIUtils::IsRemote(m_vecItems->GetPath()) && !WaitForNetwork())
506 m_vecItems->SetPath("");
507 SetHistoryForPath(m_vecItems->GetPath());
509 if (message.GetParam1() != WINDOW_INVALID)
510 { // first time to this window - make sure we set the root path
511 m_startDirectory = returning ? dir : "";
517 return CGUIWindow::OnMessage(message);
520 // \brief Updates the states (enable, disable, visible...)
521 // of the controls defined by this window
522 // Override this function in a derived class to add new controls
523 void CGUIMediaWindow::UpdateButtons()
525 if (m_guiState.get())
527 // Update sorting controls
528 if (m_guiState->GetDisplaySortOrder() == SortOrderNone)
530 CONTROL_DISABLE(CONTROL_BTNSORTASC);
534 CONTROL_ENABLE(CONTROL_BTNSORTASC);
535 if (m_guiState->GetDisplaySortOrder() == SortOrderAscending)
537 CGUIMessage msg(GUI_MSG_DESELECTED, GetID(), CONTROL_BTNSORTASC);
538 g_windowManager.SendMessage(msg);
542 CGUIMessage msg(GUI_MSG_SELECTED, GetID(), CONTROL_BTNSORTASC);
543 g_windowManager.SendMessage(msg);
547 // Update list/thumb control
548 m_viewControl.SetCurrentView(m_guiState->GetViewAsControl());
550 // Update sort by button
551 if (m_guiState->GetSortMethod()==SORT_METHOD_NONE)
553 CONTROL_DISABLE(CONTROL_BTNSORTBY);
557 CONTROL_ENABLE(CONTROL_BTNSORTBY);
559 CStdString sortLabel;
560 sortLabel.Format(g_localizeStrings.Get(550).c_str(), g_localizeStrings.Get(m_guiState->GetSortMethodLabel()).c_str());
561 SET_CONTROL_LABEL(CONTROL_BTNSORTBY, sortLabel);
565 items.Format("%i %s", m_vecItems->GetObjectCount(), g_localizeStrings.Get(127).c_str());
566 SET_CONTROL_LABEL(CONTROL_LABELFILES, items);
568 SET_CONTROL_LABEL2(CONTROL_BTN_FILTER, GetProperty("filter").asString());
571 void CGUIMediaWindow::ClearFileItems()
573 m_viewControl.Clear();
575 m_unfilteredItems->Clear();
578 // \brief Sorts Fileitems based on the sort method and sort oder provided by guiViewState
579 void CGUIMediaWindow::SortItems(CFileItemList &items)
581 auto_ptr<CGUIViewState> guiState(CGUIViewState::GetViewState(GetID(), items));
586 SORT_METHOD sortMethod = guiState->GetSortMethod();
587 // If the sort method is "sort by playlist" and we have a specific
588 // sort order available we can use the specified sort order to do the sorting
589 // We do this as the new SortBy methods are a superset of the SORT_METHOD methods, thus
590 // not all are available. This may be removed once SORT_METHOD_* have been replaced by
592 if ((sortMethod == SORT_METHOD_PLAYLIST_ORDER) && items.HasProperty(PROPERTY_SORT_ORDER))
594 SortBy sortBy = (SortBy)items.GetProperty(PROPERTY_SORT_ORDER).asInteger();
595 if (sortBy != SortByNone && sortBy != SortByPlaylistOrder && sortBy != SortByProgramCount)
597 SortDescription sorting;
598 sorting.sortBy = sortBy;
599 sorting.sortOrder = items.GetProperty(PROPERTY_SORT_ASCENDING).asBoolean() ? SortOrderAscending : SortOrderDescending;
600 sorting.sortAttributes = g_guiSettings.GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone;
602 // if the sort order is descending, we need to switch the original sort order, as we assume
603 // in CGUIViewState::AddPlaylistOrder that SORT_METHOD_PLAYLIST_ORDER is ascending.
604 if (guiState->GetDisplaySortOrder() == SortOrderDescending)
605 sorting.sortOrder = sorting.sortOrder == SortOrderDescending ? SortOrderAscending : SortOrderDescending;
613 items.Sort(sortMethod, guiState->GetDisplaySortOrder());
617 // \brief Formats item labels based on the formatting provided by guiViewState
618 void CGUIMediaWindow::FormatItemLabels(CFileItemList &items, const LABEL_MASKS &labelMasks)
620 CLabelFormatter fileFormatter(labelMasks.m_strLabelFile, labelMasks.m_strLabel2File);
621 CLabelFormatter folderFormatter(labelMasks.m_strLabelFolder, labelMasks.m_strLabel2Folder);
622 for (int i=0; i<items.Size(); ++i)
624 CFileItemPtr pItem=items[i];
626 if (pItem->IsLabelPreformated())
629 if (pItem->m_bIsFolder)
630 folderFormatter.FormatLabels(pItem.get());
632 fileFormatter.FormatLabels(pItem.get());
635 if(items.GetSortMethod() == SORT_METHOD_LABEL_IGNORE_THE
636 || items.GetSortMethod() == SORT_METHOD_LABEL)
637 items.ClearSortState();
640 // \brief Prepares and adds the fileitems list/thumb panel
641 void CGUIMediaWindow::FormatAndSort(CFileItemList &items)
643 auto_ptr<CGUIViewState> viewState(CGUIViewState::GetViewState(GetID(), items));
647 LABEL_MASKS labelMasks;
648 viewState->GetSortMethodLabelMasks(labelMasks);
649 FormatItemLabels(items, labelMasks);
651 items.Sort(viewState->GetSortMethod(), viewState->GetDisplaySortOrder());
656 \brief Overwrite to fill fileitems from a source
657 \param strDirectory Path to read
658 \param items Fill with items specified in \e strDirectory
660 bool CGUIMediaWindow::GetDirectory(const CStdString &strDirectory, CFileItemList &items)
666 CStdString strParentPath = m_history.GetParentPath();
668 CLog::Log(LOGDEBUG,"CGUIMediaWindow::GetDirectory (%s)", strDirectory.c_str());
669 CLog::Log(LOGDEBUG," ParentPath = [%s]", strParentPath.c_str());
671 // see if we can load a previously cached folder
672 CFileItemList cachedItems(strDirectory);
673 if (!strDirectory.IsEmpty() && cachedItems.Load(GetID()))
675 items.Assign(cachedItems);
679 unsigned int time = XbmcThreads::SystemClockMillis();
681 if (strDirectory.IsEmpty())
684 if (!m_rootDir.GetDirectory(strDirectory, items))
687 // took over a second, and not normally cached, so cache it
688 if ((XbmcThreads::SystemClockMillis() - time) > 1000 && items.CacheToDiscIfSlow())
691 // if these items should replace the current listing, then pop it off the top
692 if (items.GetReplaceListing())
693 m_history.RemoveParentPath();
696 if (m_guiState.get() && !m_guiState->HideParentDirItems() && !items.GetPath().IsEmpty())
698 CFileItemPtr pItem(new CFileItem(".."));
699 pItem->SetPath(strParentPath);
700 pItem->m_bIsFolder = true;
701 pItem->m_bIsShareOrDrive = false;
702 items.AddFront(pItem, 0);
705 int iWindow = GetID();
706 CStdStringArray regexps;
708 // TODO: Do we want to limit the directories we apply the video ones to?
709 if (iWindow == WINDOW_VIDEO_NAV)
710 regexps = g_advancedSettings.m_videoExcludeFromListingRegExps;
711 if (iWindow == WINDOW_MUSIC_FILES)
712 regexps = g_advancedSettings.m_audioExcludeFromListingRegExps;
713 if (iWindow == WINDOW_PICTURES)
714 regexps = g_advancedSettings.m_pictureExcludeFromListingRegExps;
718 for (int i=0; i < items.Size();)
720 if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps))
728 SetProperty("filter", "");
729 m_canFilterAdvanced = false;
734 // \brief Set window to a specific directory
735 // \param strDirectory The directory to be displayed in list/thumb control
736 // This function calls OnPrepareFileItems() and OnFinalizeFileItems()
737 bool CGUIMediaWindow::Update(const CStdString &strDirectory, bool updateFilterPath /* = true */)
739 // TODO: OnInitWindow calls Update() before window path has been set properly.
740 if (strDirectory == "?")
744 int iItem = m_viewControl.GetSelectedItem();
745 CStdString strSelectedItem = "";
746 if (iItem >= 0 && iItem < m_vecItems->Size())
748 CFileItemPtr pItem = m_vecItems->Get(iItem);
749 if (!pItem->IsParentFolder())
751 GetDirectoryHistoryString(pItem.get(), strSelectedItem);
755 CStdString strCurrentDirectory = m_vecItems->GetPath();
756 m_history.SetSelectedItem(strSelectedItem, strCurrentDirectory);
758 CStdString directory = strDirectory;
759 // check if the path contains a filter and temporarily remove it
760 // so that the retrieved list of items is unfiltered
761 bool canfilter = CanContainFilter(directory);
764 if (canfilter && url.HasOption("filter"))
766 filter = url.GetOption("filter");
767 directory = RemoveParameterFromPath(directory, "filter");
771 if (!GetDirectory(directory, items))
773 CLog::Log(LOGERROR,"CGUIMediaWindow::GetDirectory(%s) failed", strDirectory.c_str());
774 // Try to return to the previous directory, if not the same
775 // else fallback to root
776 if (strDirectory.Equals(strCurrentDirectory) || !Update(m_history.RemoveParentPath()))
777 Update(""); // Fallback to root
779 // Return false to be able to eg. show
784 if (items.GetLabel().IsEmpty())
785 items.SetLabel(CUtil::GetTitleFromPath(items.GetPath(), true));
788 m_vecItems->Copy(items);
790 // only set the filter path if it hasn't been marked
791 // as preset or if it's empty
792 if (updateFilterPath || m_strFilterPath.empty())
794 if (items.HasProperty(PROPERTY_PATH_DB))
795 m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString();
797 m_strFilterPath = items.GetPath();
800 // maybe the filter path can contain a filter
801 if (!canfilter && CanContainFilter(m_strFilterPath))
804 // check if the filter path contains a filter
805 CURL filterPathUrl(m_strFilterPath);
806 if (canfilter && filter.empty())
808 if (filterPathUrl.HasOption("filter"))
809 filter = filterPathUrl.GetOption("filter");
812 // check if there is a filter and re-apply it
813 if (canfilter && !filter.empty())
815 if (!m_filter.LoadFromJson(filter))
817 CLog::Log(LOGWARNING, "CGUIMediaWindow::Update: unable to load existing filter (%s)", filter.c_str());
819 m_strFilterPath = m_vecItems->GetPath();
823 // add the filter to the filter path
824 filterPathUrl.SetOption("filter", filter);
825 m_strFilterPath = filterPathUrl.Get();
829 // if we're getting the root source listing
830 // make sure the path history is clean
831 if (strDirectory.IsEmpty())
832 m_history.ClearPathHistory();
834 int iWindow = GetID();
836 if (strDirectory.IsEmpty())
838 if (iWindow == WINDOW_PICTURES)
840 else if (iWindow == WINDOW_MUSIC_FILES)
842 else if (iWindow == WINDOW_FILES || iWindow == WINDOW_PROGRAMS)
845 if (strDirectory.Equals("sources://video/"))
847 if (showLabel && (m_vecItems->Size() == 0 || !m_guiState->DisableAddSourceButtons())) // add 'add source button'
849 CStdString strLabel = g_localizeStrings.Get(showLabel);
850 CFileItemPtr pItem(new CFileItem(strLabel));
851 pItem->SetPath("add");
852 pItem->SetIconImage("DefaultAddSource.png");
853 pItem->SetLabel(strLabel);
854 pItem->SetLabelPreformated(true);
855 pItem->m_bIsFolder = true;
856 pItem->SetSpecialSort(SortSpecialOnBottom);
857 m_vecItems->Add(pItem);
859 m_iLastControl = GetFocusedControlID();
861 // Check whether to enabled advanced filtering based on the content type
862 m_canFilterAdvanced = CheckFilterAdvanced(*m_vecItems);
863 if (m_canFilterAdvanced)
864 m_filter.SetType(m_vecItems->GetContent());
866 // Ask the derived class if it wants to load additional info
867 // for the fileitems like media info or additional
868 // filtering on the items, setting thumbs.
869 OnPrepareFileItems(*m_vecItems);
871 m_vecItems->FillInDefaultIcons();
873 m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems));
875 // remember the original (untouched) list of items (for filtering etc)
876 m_unfilteredItems->SetPath(m_vecItems->GetPath()); // use the original path - it'll likely be relied on for other things later.
877 m_unfilteredItems->Append(*m_vecItems);
879 // Cache the list of items if possible
880 OnCacheFileItems(*m_vecItems);
882 // Filter and group the items if necessary
883 OnFilterItems(GetProperty("filter").asString());
885 // Ask the devived class if it wants to do custom list operations,
886 // eg. changing the label
887 OnFinalizeFileItems(*m_vecItems);
890 strSelectedItem = m_history.GetSelectedItem(m_vecItems->GetPath());
892 bool bSelectedFound = false;
893 //int iSongInDirectory = -1;
894 for (int i = 0; i < m_vecItems->Size(); ++i)
896 CFileItemPtr pItem = m_vecItems->Get(i);
898 // Update selected item
899 CStdString strHistory;
900 GetDirectoryHistoryString(pItem.get(), strHistory);
901 if (strHistory == strSelectedItem)
903 m_viewControl.SetSelectedItem(i);
904 bSelectedFound = true;
909 // if we haven't found the selected item, select the first item
911 m_viewControl.SetSelectedItem(0);
913 if (iWindow != WINDOW_PVR || (iWindow == WINDOW_PVR && m_vecItems->GetPath().Left(17) == "pvr://recordings/"))
914 m_history.AddPath(m_vecItems->GetPath(), m_strFilterPath);
916 //m_history.DumpPathHistory();
921 bool CGUIMediaWindow::Refresh(bool clearCache /* = false */)
923 CStdString strCurrentDirectory = m_vecItems->GetPath();
924 if (strCurrentDirectory.Equals("?"))
928 m_vecItems->RemoveDiscCache(GetID());
930 // get the original number of items
931 if (!Update(strCurrentDirectory, false))
937 // \brief This function will be called by Update() before the
938 // labels of the fileitems are formatted. Override this function
939 // to set custom thumbs or load additional media info.
940 // It's used to load tag info for music.
941 void CGUIMediaWindow::OnPrepareFileItems(CFileItemList &items)
946 // \brief This function will be called by Update() before
947 // any additional formatting, filtering or sorting is applied.
948 // Override this function to define a custom caching behaviour.
949 void CGUIMediaWindow::OnCacheFileItems(CFileItemList &items)
951 // Should these items be saved to the hdd
952 if (items.CacheToDiscAlways() && !IsFiltered())
956 // \brief This function will be called by Update() after the
957 // labels of the fileitems are formatted. Override this function
958 // to modify the fileitems. Eg. to modify the item label
959 void CGUIMediaWindow::OnFinalizeFileItems(CFileItemList &items)
964 // \brief With this function you can react on a users click in the list/thumb panel.
965 // It returns true, if the click is handled.
966 // This function calls OnPlayMedia()
967 bool CGUIMediaWindow::OnClick(int iItem)
969 if ( iItem < 0 || iItem >= (int)m_vecItems->Size() ) return true;
970 CFileItemPtr pItem = m_vecItems->Get(iItem);
972 if (pItem->IsParentFolder())
977 if (pItem->GetPath() == "add" || pItem->GetPath() == "sources://add/") // 'add source button' in empty root
979 OnContextButton(iItem, CONTEXT_BUTTON_ADD_SOURCE);
983 if (!pItem->m_bIsFolder && pItem->IsFileFolder())
985 XFILE::IFileDirectory *pFileDirectory = NULL;
986 pFileDirectory = XFILE::CFileDirectoryFactory::Create(pItem->GetPath(), pItem.get(), "");
988 pItem->m_bIsFolder = true;
989 else if(pItem->m_bIsFolder)
990 pItem->m_bIsFolder = false;
991 delete pFileDirectory;
994 if (pItem->IsScript())
996 // execute the script
997 CURL url(pItem->GetPath());
999 if (CAddonMgr::Get().GetAddon(url.GetHostName(), addon, ADDON_SCRIPT))
1002 if (!g_pythonParser.StopScript(addon->LibPath()))
1003 g_pythonParser.evalFile(addon->LibPath(),addon);
1009 if (pItem->m_bIsFolder)
1011 if ( pItem->m_bIsShareOrDrive )
1013 const CStdString& strLockType=m_guiState->GetLockType();
1014 if (CProfilesManager::Get().GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE)
1015 if (!strLockType.IsEmpty() && !g_passwordManager.IsItemUnlocked(pItem.get(), strLockType))
1018 if (!HaveDiscOrConnection(pItem->GetPath(), pItem->m_iDriveType))
1022 // check for the partymode playlist items - they may not exist yet
1023 if ((pItem->GetPath() == CProfilesManager::Get().GetUserDataItem("PartyMode.xsp")) ||
1024 (pItem->GetPath() == CProfilesManager::Get().GetUserDataItem("PartyMode-Video.xsp")))
1026 // party mode playlist item - if it doesn't exist, prompt for user to define it
1027 if (!XFILE::CFile::Exists(pItem->GetPath()))
1029 m_vecItems->RemoveDiscCache(GetID());
1030 if (CGUIDialogSmartPlaylistEditor::EditPlaylist(pItem->GetPath()))
1036 // remove the directory cache if the folder is not normally cached
1037 CFileItemList items(pItem->GetPath());
1038 if (!items.AlwaysCache())
1039 items.RemoveDiscCache(GetID());
1041 // if we have a filtered list, we need to add the filtered
1042 // path to be able to come back to the filtered view
1043 CStdString strCurrentDirectory = m_vecItems->GetPath();
1044 if (m_canFilterAdvanced && !m_filter.IsEmpty() &&
1045 !m_strFilterPath.Equals(strCurrentDirectory))
1047 m_history.RemoveParentPath();
1048 m_history.AddPath(strCurrentDirectory, m_strFilterPath);
1051 CFileItem directory(*pItem);
1052 if (!Update(directory.GetPath()))
1053 ShowShareErrorMessage(&directory);
1057 else if (pItem->IsPlugin() && !pItem->GetProperty("isplayable").asBoolean())
1059 return XFILE::CPluginDirectory::RunScriptWithParams(pItem->GetPath());
1061 #if defined(TARGET_ANDROID)
1062 else if (pItem->IsAndroidApp())
1064 CStdString appName = URIUtils::GetFileName(pItem->GetPath());
1065 CLog::Log(LOGDEBUG, "CGUIMediaWindow::OnClick Trying to run: %s",appName.c_str());
1066 return CXBMCApp::StartActivity(appName);
1071 m_iSelectedItem = m_viewControl.GetSelectedItem();
1073 if (pItem->GetPath() == "newplaylist://")
1075 m_vecItems->RemoveDiscCache(GetID());
1076 g_windowManager.ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR,"newplaylist://");
1079 else if (pItem->GetPath().Left(19).Equals("newsmartplaylist://"))
1081 m_vecItems->RemoveDiscCache(GetID());
1082 if (CGUIDialogSmartPlaylistEditor::NewPlaylist(pItem->GetPath().Mid(19)))
1086 else if (pItem->GetPath().Left(14).Equals("addons://more/"))
1088 CBuiltins::Execute("ActivateWindow(AddonBrowser,addons://all/xbmc.addon." + pItem->GetPath().Mid(14) + ",return)");
1092 // If karaoke song is being played AND popup autoselector is enabled, the playlist should not be added
1093 bool do_not_add_karaoke = g_guiSettings.GetBool("karaoke.enabled") &&
1094 g_guiSettings.GetBool("karaoke.autopopupselector") && pItem->IsKaraoke();
1095 bool autoplay = m_guiState.get() && m_guiState->AutoPlayNextItem();
1097 if (m_vecItems->IsPlugin())
1099 CURL url(m_vecItems->GetPath());
1101 if (CAddonMgr::Get().GetAddon(url.GetHostName(),addon))
1103 PluginPtr plugin = boost::dynamic_pointer_cast<CPluginSource>(addon);
1104 if (plugin && plugin->Provides(CPluginSource::AUDIO))
1106 CFileItemList items;
1107 auto_ptr<CGUIViewState> state(CGUIViewState::GetViewState(GetID(), items));
1108 autoplay = state.get() && state->AutoPlayNextItem();
1113 if (autoplay && !g_partyModeManager.IsEnabled() &&
1114 !pItem->IsPlayList() && !do_not_add_karaoke)
1116 return OnPlayAndQueueMedia(pItem);
1120 return OnPlayMedia(iItem);
1127 bool CGUIMediaWindow::OnSelect(int item)
1129 return OnClick(item);
1132 // \brief Checks if there is a disc in the dvd drive and whether the
1133 // network is connected or not.
1134 bool CGUIMediaWindow::HaveDiscOrConnection(const CStdString& strPath, int iDriveType)
1136 if (iDriveType==CMediaSource::SOURCE_TYPE_DVD)
1138 if (!g_mediaManager.IsDiscInDrive(strPath))
1140 CGUIDialogOK::ShowAndGetInput(218, 219, 0, 0);
1144 else if (iDriveType==CMediaSource::SOURCE_TYPE_REMOTE)
1146 // TODO: Handle not connected to a remote share
1147 if ( !g_application.getNetwork().IsConnected() )
1149 CGUIDialogOK::ShowAndGetInput(220, 221, 0, 0);
1157 // \brief Shows a standard errormessage for a given pItem.
1158 void CGUIMediaWindow::ShowShareErrorMessage(CFileItem* pItem)
1160 int idMessageText = 0;
1161 CURL url(pItem->GetPath());
1162 const CStdString& strHostName = url.GetHostName();
1164 if (url.GetProtocol() == "smb" && strHostName.IsEmpty()) // smb workgroup
1165 idMessageText = 15303; // Workgroup not found
1166 else if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_REMOTE || URIUtils::IsRemote(pItem->GetPath()))
1167 idMessageText = 15301; // Could not connect to network server
1169 idMessageText = 15300; // Path not found or invalid
1171 CGUIDialogOK::ShowAndGetInput(220, idMessageText, 0, 0);
1174 // \brief The functon goes up one level in the directory tree
1175 void CGUIMediaWindow::GoParentFolder()
1177 //m_history.DumpPathHistory();
1179 // remove current directory if its on the stack
1180 // there were some issues due some folders having a trailing slash and some not
1181 // so just add a trailing slash to all of them for comparison.
1182 CStdString strPath = m_vecItems->GetPath();
1183 URIUtils::AddSlashAtEnd(strPath);
1184 CStdString strParent = m_history.GetParentPath();
1185 // in case the path history is messed up and the current folder is on
1186 // the stack more than once, keep going until there's nothing left or they
1187 // dont match anymore.
1188 while (!strParent.IsEmpty())
1190 URIUtils::AddSlashAtEnd(strParent);
1191 if (strParent.Equals(strPath))
1192 m_history.RemoveParentPath();
1195 strParent = m_history.GetParentPath();
1198 // remove the current filter but only if the parent
1199 // item doesn't have a filter as well
1200 CURL filterUrl(m_strFilterPath);
1201 if (filterUrl.HasOption("filter"))
1203 CURL parentUrl(m_history.GetParentPath(true));
1204 if (!parentUrl.HasOption("filter"))
1206 // we need to overwrite m_strFilterPath because
1207 // Refresh() will set updateFilterPath to false
1208 m_strFilterPath.clear();
1214 // if vector is not empty, pop parent
1215 // if vector is empty, parent is root source listing
1216 m_strFilterPath = m_history.GetParentPath(true);
1217 strParent = m_history.RemoveParentPath();
1218 if (!Update(strParent, false))
1221 // No items to show so go another level up
1222 if (!m_vecItems->GetPath().empty() && (m_filter.IsEmpty() ? m_vecItems->Size() : m_unfilteredItems->Size()) <= 0)
1224 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(2080), g_localizeStrings.Get(2081));
1229 // \brief Override the function to change the default behavior on how
1230 // a selected item history should look like
1231 void CGUIMediaWindow::GetDirectoryHistoryString(const CFileItem* pItem, CStdString& strHistoryString)
1233 if (pItem->m_bIsShareOrDrive)
1235 // We are in the virual directory
1237 // History string of the DVD drive
1238 // must be handel separately
1239 if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_DVD)
1241 // Remove disc label from item label
1242 // and use as history string, m_strPath
1243 // can change for new discs
1244 CStdString strLabel = pItem->GetLabel();
1245 int nPosOpen = strLabel.Find('(');
1246 int nPosClose = strLabel.ReverseFind(')');
1247 if (nPosOpen > -1 && nPosClose > -1 && nPosClose > nPosOpen)
1249 strLabel.Delete(nPosOpen + 1, (nPosClose) - (nPosOpen + 1));
1250 strHistoryString = strLabel;
1253 strHistoryString = strLabel;
1257 // Other items in virual directory
1258 CStdString strPath = pItem->GetPath();
1259 URIUtils::RemoveSlashAtEnd(strPath);
1261 strHistoryString = pItem->GetLabel() + strPath;
1264 else if (pItem->m_lEndOffset>pItem->m_lStartOffset && pItem->m_lStartOffset != -1)
1266 // Could be a cue item, all items of a cue share the same filename
1267 // so add the offsets to build the history string
1268 strHistoryString.Format("%ld%ld", pItem->m_lStartOffset, pItem->m_lEndOffset);
1269 strHistoryString += pItem->GetPath();
1273 // Normal directory items
1274 strHistoryString = pItem->GetPath();
1277 // remove any filter
1278 if (CanContainFilter(strHistoryString))
1279 strHistoryString = RemoveParameterFromPath(strHistoryString, "filter");
1281 URIUtils::RemoveSlashAtEnd(strHistoryString);
1282 strHistoryString.ToLower();
1285 // \brief Call this function to create a directory history for the
1286 // path given by strDirectory.
1287 void CGUIMediaWindow::SetHistoryForPath(const CStdString& strDirectory)
1289 // Make sure our shares are configured
1291 if (!strDirectory.IsEmpty())
1293 // Build the directory history for default path
1294 CStdString strPath, strParentPath;
1295 strPath = strDirectory;
1296 URIUtils::RemoveSlashAtEnd(strPath);
1298 CFileItemList items;
1299 m_rootDir.GetDirectory("", items);
1301 m_history.ClearPathHistory();
1303 while (URIUtils::GetParentPath(strPath, strParentPath))
1305 for (int i = 0; i < (int)items.Size(); ++i)
1307 CFileItemPtr pItem = items[i];
1308 CStdString path(pItem->GetPath());
1309 URIUtils::RemoveSlashAtEnd(path);
1310 if (path == strPath)
1312 CStdString strHistory;
1313 GetDirectoryHistoryString(pItem.get(), strHistory);
1314 m_history.SetSelectedItem(strHistory, "");
1315 URIUtils::AddSlashAtEnd(strPath);
1316 m_history.AddPathFront(strPath);
1317 m_history.AddPathFront("");
1319 //m_history.DumpPathHistory();
1324 if (URIUtils::IsVideoDb(strPath))
1326 CURL url(strParentPath);
1327 url.SetOptions(""); // clear any URL options from recreated parent path
1328 strParentPath = url.Get();
1331 URIUtils::AddSlashAtEnd(strPath);
1332 m_history.AddPathFront(strPath);
1333 m_history.SetSelectedItem(strPath, strParentPath);
1334 strPath = strParentPath;
1335 URIUtils::RemoveSlashAtEnd(strPath);
1339 m_history.ClearPathHistory();
1341 //m_history.DumpPathHistory();
1344 // \brief Override if you want to change the default behavior, what is done
1345 // when the user clicks on a file.
1346 // This function is called by OnClick()
1347 bool CGUIMediaWindow::OnPlayMedia(int iItem)
1349 // Reset Playlistplayer, playback started now does
1350 // not use the playlistplayer.
1351 g_playlistPlayer.Reset();
1352 g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_NONE);
1353 CFileItemPtr pItem=m_vecItems->Get(iItem);
1355 CLog::Log(LOGDEBUG, "%s %s", __FUNCTION__, pItem->GetPath().c_str());
1357 bool bResult = false;
1358 if (pItem->IsInternetStream() || pItem->IsPlayList())
1359 bResult = g_application.PlayMedia(*pItem, m_guiState->GetPlaylist());
1361 bResult = g_application.PlayFile(*pItem);
1363 if (pItem->m_lStartOffset == STARTOFFSET_RESUME)
1364 pItem->m_lStartOffset = 0;
1369 // \brief Override if you want to change the default behavior of what is done
1370 // when the user clicks on a file in a "folder" with similar files.
1371 // This function is called by OnClick()
1372 bool CGUIMediaWindow::OnPlayAndQueueMedia(const CFileItemPtr &item)
1374 //play and add current directory to temporary playlist
1375 int iPlaylist = m_guiState->GetPlaylist();
1376 if (iPlaylist != PLAYLIST_NONE)
1378 g_playlistPlayer.ClearPlaylist(iPlaylist);
1379 g_playlistPlayer.Reset();
1380 int mediaToPlay = 0;
1381 for ( int i = 0; i < m_vecItems->Size(); i++ )
1383 CFileItemPtr nItem = m_vecItems->Get(i);
1385 if (nItem->m_bIsFolder)
1388 if (!nItem->IsPlayList() && !nItem->IsZIP() && !nItem->IsRAR())
1389 g_playlistPlayer.Add(iPlaylist, nItem);
1391 if (item->IsSamePath(nItem.get()))
1392 { // item that was clicked
1393 mediaToPlay = g_playlistPlayer.GetPlaylist(iPlaylist).size() - 1;
1397 // Save current window and directory to know where the selected item was
1398 if (m_guiState.get())
1399 m_guiState->SetPlaylistDirectory(m_vecItems->GetPath());
1401 // figure out where we start playback
1402 if (g_playlistPlayer.IsShuffled(iPlaylist))
1404 int iIndex = g_playlistPlayer.GetPlaylist(iPlaylist).FindOrder(mediaToPlay);
1405 g_playlistPlayer.GetPlaylist(iPlaylist).Swap(0, iIndex);
1410 g_playlistPlayer.SetCurrentPlaylist(iPlaylist);
1411 g_playlistPlayer.Play(mediaToPlay);
1416 // \brief Synchonize the fileitems with the playlistplayer
1417 // It recreated the playlist of the playlistplayer based
1418 // on the fileitems of the window
1419 void CGUIMediaWindow::UpdateFileList()
1421 int nItem = m_viewControl.GetSelectedItem();
1422 CStdString strSelected;
1424 strSelected = m_vecItems->Get(nItem)->GetPath();
1426 FormatAndSort(*m_vecItems);
1429 m_viewControl.SetItems(*m_vecItems);
1430 m_viewControl.SetSelectedItem(strSelected);
1432 // set the currently playing item as selected, if its in this directory
1433 if (m_guiState.get() && m_guiState->IsCurrentPlaylistDirectory(m_vecItems->GetPath()))
1435 int iPlaylist=m_guiState->GetPlaylist();
1436 int nSong = g_playlistPlayer.GetCurrentSong();
1437 CFileItem playlistItem;
1438 if (nSong > -1 && iPlaylist > -1)
1439 playlistItem=*g_playlistPlayer.GetPlaylist(iPlaylist)[nSong];
1441 g_playlistPlayer.ClearPlaylist(iPlaylist);
1442 g_playlistPlayer.Reset();
1444 for (int i = 0; i < m_vecItems->Size(); i++)
1446 CFileItemPtr pItem = m_vecItems->Get(i);
1447 if (pItem->m_bIsFolder)
1450 if (!pItem->IsPlayList() && !pItem->IsZIP() && !pItem->IsRAR())
1451 g_playlistPlayer.Add(iPlaylist, pItem);
1453 if (pItem->GetPath() == playlistItem.GetPath() &&
1454 pItem->m_lStartOffset == playlistItem.m_lStartOffset)
1455 g_playlistPlayer.SetCurrentSong(g_playlistPlayer.GetPlaylist(iPlaylist).size() - 1);
1460 void CGUIMediaWindow::OnDeleteItem(int iItem)
1462 if ( iItem < 0 || iItem >= m_vecItems->Size()) return;
1463 CFileItemPtr item = m_vecItems->Get(iItem);
1465 if (item->IsPlayList())
1466 item->m_bIsFolder = false;
1468 if (CProfilesManager::Get().GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && CProfilesManager::Get().GetCurrentProfile().filesLocked())
1469 if (!g_passwordManager.IsMasterLockUnlocked(true))
1472 if (!CFileUtils::DeleteItem(item))
1475 m_viewControl.SetSelectedItem(iItem);
1478 void CGUIMediaWindow::OnRenameItem(int iItem)
1480 if ( iItem < 0 || iItem >= m_vecItems->Size()) return;
1482 if (CProfilesManager::Get().GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && CProfilesManager::Get().GetCurrentProfile().filesLocked())
1483 if (!g_passwordManager.IsMasterLockUnlocked(true))
1486 if (!CFileUtils::RenameFile(m_vecItems->Get(iItem)->GetPath()))
1489 m_viewControl.SetSelectedItem(iItem);
1492 void CGUIMediaWindow::OnInitWindow()
1494 // initial fetch is done unthreaded to ensure the items are setup prior to skin animations kicking off
1495 m_rootDir.SetAllowThreads(false);
1497 m_rootDir.SetAllowThreads(true);
1499 if (m_iSelectedItem > -1)
1500 m_viewControl.SetSelectedItem(m_iSelectedItem);
1502 CGUIWindow::OnInitWindow();
1505 CGUIControl *CGUIMediaWindow::GetFirstFocusableControl(int id)
1507 if (m_viewControl.HasControl(id))
1508 id = m_viewControl.GetCurrentControl();
1509 return CGUIWindow::GetFirstFocusableControl(id);
1512 void CGUIMediaWindow::SetupShares()
1514 // Setup shares and filemasks for this window
1515 CFileItemList items;
1516 CGUIViewState* viewState=CGUIViewState::GetViewState(GetID(), items);
1519 m_rootDir.SetMask(viewState->GetExtensions());
1520 m_rootDir.SetSources(viewState->GetSources());
1525 bool CGUIMediaWindow::OnPopupMenu(int iItem)
1527 // popup the context menu
1528 // grab our context menu
1529 CContextButtons buttons;
1530 GetContextButtons(iItem, buttons);
1535 if (iItem >= 0 && iItem < m_vecItems->Size())
1536 m_vecItems->Get(iItem)->Select(true);
1538 int choice = CGUIDialogContextMenu::ShowAndGetChoice(buttons);
1540 // deselect our item
1541 if (iItem >= 0 && iItem < m_vecItems->Size())
1542 m_vecItems->Get(iItem)->Select(false);
1545 return OnContextButton(iItem, (CONTEXT_BUTTON)choice);
1550 void CGUIMediaWindow::GetContextButtons(int itemNumber, CContextButtons &buttons)
1552 CFileItemPtr item = (itemNumber >= 0 && itemNumber < m_vecItems->Size()) ? m_vecItems->Get(itemNumber) : CFileItemPtr();
1557 // user added buttons
1560 for (int i = CONTEXT_BUTTON_USER1; i <= CONTEXT_BUTTON_USER10; i++)
1562 label.Format("contextmenulabel(%i)", i - CONTEXT_BUTTON_USER1);
1563 if (item->GetProperty(label).empty())
1566 action.Format("contextmenuaction(%i)", i - CONTEXT_BUTTON_USER1);
1567 if (item->GetProperty(action).empty())
1570 buttons.Add((CONTEXT_BUTTON)i, item->GetProperty(label).asString());
1573 if (item->GetProperty("pluginreplacecontextitems").asBoolean())
1576 // TODO: FAVOURITES Conditions on masterlock and localisation
1577 if (!item->IsParentFolder() && !item->GetPath().Equals("add") && !item->GetPath().Equals("newplaylist://") &&
1578 !item->GetPath().Left(19).Equals("newsmartplaylist://") && !item->GetPath().Left(9).Equals("newtag://"))
1580 if (CFavourites::IsFavourite(item.get(), GetID()))
1581 buttons.Add(CONTEXT_BUTTON_ADD_FAVOURITE, 14077); // Remove Favourite
1583 buttons.Add(CONTEXT_BUTTON_ADD_FAVOURITE, 14076); // Add To Favourites;
1587 bool CGUIMediaWindow::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
1591 case CONTEXT_BUTTON_ADD_FAVOURITE:
1593 CFileItemPtr item = m_vecItems->Get(itemNumber);
1594 CFavourites::AddOrRemove(item.get(), GetID());
1597 case CONTEXT_BUTTON_PLUGIN_SETTINGS:
1599 CFileItemPtr item = m_vecItems->Get(itemNumber);
1600 // CONTEXT_BUTTON_PLUGIN_SETTINGS can be called for plugin item
1601 // or script item; or for the plugin directory current listing.
1602 bool isPluginOrScriptItem = (item && (item->IsPlugin() || item->IsScript()));
1603 CURL plugin(isPluginOrScriptItem ? item->GetPath() : m_vecItems->GetPath());
1604 ADDON::AddonPtr addon;
1605 if (CAddonMgr::Get().GetAddon(plugin.GetHostName(), addon))
1606 if (CGUIDialogAddonSettings::ShowAndGetInput(addon))
1610 case CONTEXT_BUTTON_USER1:
1611 case CONTEXT_BUTTON_USER2:
1612 case CONTEXT_BUTTON_USER3:
1613 case CONTEXT_BUTTON_USER4:
1614 case CONTEXT_BUTTON_USER5:
1615 case CONTEXT_BUTTON_USER6:
1616 case CONTEXT_BUTTON_USER7:
1617 case CONTEXT_BUTTON_USER8:
1618 case CONTEXT_BUTTON_USER9:
1619 case CONTEXT_BUTTON_USER10:
1622 action.Format("contextmenuaction(%i)", button - CONTEXT_BUTTON_USER1);
1623 CApplicationMessenger::Get().ExecBuiltIn(m_vecItems->Get(itemNumber)->GetProperty(action).asString());
1632 const CGUIViewState *CGUIMediaWindow::GetViewState() const
1634 return m_guiState.get();
1637 const CFileItemList& CGUIMediaWindow::CurrentDirectory() const
1642 bool CGUIMediaWindow::WaitForNetwork() const
1644 if (g_application.getNetwork().IsAvailable())
1647 CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
1651 CURL url(m_vecItems->GetPath());
1652 progress->SetHeading(1040); // Loading Directory
1653 progress->SetLine(1, url.GetWithoutUserDetails());
1654 progress->ShowProgressBar(false);
1655 progress->StartModal();
1656 while (!g_application.getNetwork().IsAvailable())
1658 progress->Progress();
1659 if (progress->IsCanceled())
1669 void CGUIMediaWindow::OnFilterItems(const CStdString &filter)
1671 CFileItemPtr currentItem;
1672 CStdString currentItemPath;
1673 int item = m_viewControl.GetSelectedItem();
1674 if (item >= 0 && item < m_vecItems->Size())
1676 currentItem = m_vecItems->Get(item);
1677 currentItemPath = currentItem->GetPath();
1680 m_viewControl.Clear();
1682 CFileItemList items;
1683 items.Copy(*m_vecItems, false); // use the original path - it'll likely be relied on for other things later.
1684 items.Append(*m_unfilteredItems);
1685 bool filtered = GetFilteredItems(filter, items);
1687 m_vecItems->ClearItems();
1688 // we need to clear the sort state and re-sort the items
1689 m_vecItems->ClearSortState();
1690 m_vecItems->Append(items);
1692 // if the filter has changed, get the new filter path
1693 if (filtered && m_canFilterAdvanced)
1695 if (items.HasProperty(PROPERTY_PATH_DB))
1696 m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString();
1697 // only set m_strFilterPath if it hasn't been set before
1698 // otherwise we might overwrite it with a non-filter path
1699 // in case GetFilteredItems() returns true even though no
1700 // db-based filter (e.g. watched filter) has been applied
1701 else if (m_strFilterPath.empty())
1702 m_strFilterPath = items.GetPath();
1705 GetGroupedItems(*m_vecItems);
1706 FormatAndSort(*m_vecItems);
1708 // get the "filter" option
1709 CStdString filterOption;
1710 CURL filterUrl(m_strFilterPath);
1711 if (filterUrl.HasOption("filter"))
1712 filterOption = filterUrl.GetOption("filter");
1714 // apply the "filter" option to any folder item so that
1715 // the filter can be passed down to the sub-directory
1716 for (int index = 0; index < m_vecItems->Size(); index++)
1718 CFileItemPtr pItem = m_vecItems->Get(index);
1719 // if the item is a folder we need to copy the path of
1720 // the filtered item to be able to keep the applied filters
1721 if (pItem->m_bIsFolder)
1723 CURL itemUrl(pItem->GetPath());
1724 if (!filterOption.empty())
1725 itemUrl.SetOption("filter", filterOption);
1727 itemUrl.RemoveOption("filter");
1728 pItem->SetPath(itemUrl.Get());
1732 SetProperty("filter", filter);
1733 if (filtered && m_canFilterAdvanced)
1735 // to be able to select the same item as before we need to adjust
1736 // the path of the item i.e. add or remove the "filter=" URL option
1737 // but that's only necessary for folder items
1738 if (currentItem.get() != NULL && currentItem->m_bIsFolder)
1740 CURL curUrl(currentItemPath), newUrl(m_strFilterPath);
1741 if (newUrl.HasOption("filter"))
1742 curUrl.SetOption("filter", newUrl.GetOption("filter"));
1743 else if (curUrl.HasOption("filter"))
1744 curUrl.RemoveOption("filter");
1746 currentItemPath = curUrl.Get();
1750 // The idea here is to ensure we have something to focus if our file list
1751 // is empty. As such, this check MUST be last and ignore the hide parent
1752 // fileitems settings.
1753 if (m_vecItems->IsEmpty())
1755 CFileItemPtr pItem(new CFileItem(".."));
1756 pItem->SetPath(m_history.GetParentPath());
1757 pItem->m_bIsFolder = true;
1758 pItem->m_bIsShareOrDrive = false;
1759 m_vecItems->AddFront(pItem, 0);
1762 // and update our view control + buttons
1763 m_viewControl.SetItems(*m_vecItems);
1764 m_viewControl.SetSelectedItem(currentItemPath);
1768 bool CGUIMediaWindow::GetFilteredItems(const CStdString &filter, CFileItemList &items)
1770 bool result = false;
1771 if (m_canFilterAdvanced)
1772 result = GetAdvanceFilteredItems(items);
1774 CStdString trimmedFilter(filter);
1775 trimmedFilter.TrimLeft().ToLower();
1777 if (trimmedFilter.IsEmpty())
1780 CFileItemList filteredItems(items.GetPath()); // use the original path - it'll likely be relied on for other things later.
1781 bool numericMatch = StringUtils::IsNaturalNumber(trimmedFilter);
1782 for (int i = 0; i < items.Size(); i++)
1784 CFileItemPtr item = items.Get(i);
1785 if (item->IsParentFolder())
1787 filteredItems.Add(item);
1790 // TODO: Need to update this to get all labels, ideally out of the displayed info (ie from m_layout and m_focusedLayout)
1791 // though that isn't practical. Perhaps a better idea would be to just grab the info that we should filter on based on
1792 // where we are in the library tree.
1793 // Another idea is tying the filter string to the current level of the tree, so that going deeper disables the filter,
1794 // but it's re-enabled on the way back out.
1796 /* if (item->GetFocusedLayout())
1797 match = item->GetFocusedLayout()->GetAllText();
1798 else if (item->GetLayout())
1799 match = item->GetLayout()->GetAllText();
1801 match = item->GetLabel(); // Filter label only for now
1804 StringUtils::WordToDigits(match);
1806 size_t pos = StringUtils::FindWords(match.c_str(), trimmedFilter.c_str());
1807 if (pos != CStdString::npos)
1808 filteredItems.Add(item);
1812 items.Append(filteredItems);
1814 return items.GetObjectCount() > 0;
1817 bool CGUIMediaWindow::GetAdvanceFilteredItems(CFileItemList &items)
1819 // don't run the advanced filter if the filter is empty
1820 // and there hasn't been a filter applied before which
1821 // would have to be removed
1822 CURL url(m_strFilterPath);
1823 if (m_filter.IsEmpty() && !url.HasOption("filter"))
1826 CFileItemList resultItems;
1827 XFILE::CSmartPlaylistDirectory::GetDirectory(m_filter, resultItems, m_strFilterPath, true);
1829 // put together a lookup map for faster path comparison
1830 map<CStdString, CFileItemPtr> lookup;
1831 for (int j = 0; j < resultItems.Size(); j++)
1833 CStdString itemPath = RemoveParameterFromPath(resultItems[j]->GetPath(), "filter");
1836 lookup[itemPath] = resultItems[j];
1839 // loop through all the original items and find
1840 // those which are still part of the filter
1841 CFileItemList filteredItems;
1842 for (int i = 0; i < items.Size(); i++)
1844 CFileItemPtr item = items.Get(i);
1845 if (item->IsParentFolder())
1847 filteredItems.Add(item);
1851 // check if the item is part of the resultItems list
1852 // by comparing their paths (but ignoring any special
1853 // options because they differ from filter to filter)
1854 CStdString path = RemoveParameterFromPath(item->GetPath(), "filter");
1857 map<CStdString, CFileItemPtr>::iterator itItem = lookup.find(path);
1858 if (itItem != lookup.end())
1860 // add the item to the list of filtered items
1861 filteredItems.Add(item);
1863 // remove the item from the lists
1864 resultItems.Remove(itItem->second.get());
1865 lookup.erase(itItem);
1869 if (resultItems.Size() > 0)
1870 CLog::Log(LOGWARNING, "CGUIMediaWindow::GetAdvanceFilteredItems(): %d unknown items", resultItems.Size());
1873 items.Append(filteredItems);
1874 items.SetPath(resultItems.GetPath());
1875 if (resultItems.HasProperty(PROPERTY_PATH_DB))
1876 items.SetProperty(PROPERTY_PATH_DB, resultItems.GetProperty(PROPERTY_PATH_DB));
1880 bool CGUIMediaWindow::IsFiltered()
1882 return (!m_canFilterAdvanced && !GetProperty("filter").empty()) ||
1883 (m_canFilterAdvanced && !m_filter.IsEmpty());
1886 bool CGUIMediaWindow::Filter(bool advanced /* = true */)
1889 if (!m_canFilterAdvanced || !advanced)
1891 const CGUIControl *btnFilter = GetControl(CONTROL_BTN_FILTER);
1892 if (btnFilter != NULL && btnFilter->GetControlType() == CGUIControl::GUICONTROL_EDIT)
1894 CGUIMessage selected(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTN_FILTER);
1895 OnMessage(selected);
1896 OnFilterItems(selected.GetLabel());
1899 if (GetProperty("filter").empty())
1901 CStdString filter = GetProperty("filter").asString();
1902 CGUIKeyboardFactory::ShowAndGetFilter(filter, false);
1903 SetProperty("filter", filter);
1908 // advanced filtering
1910 CGUIDialogMediaFilter::ShowAndEditMediaFilter(m_strFilterPath, m_filter);
1915 CStdString CGUIMediaWindow::GetStartFolder(const CStdString &dir)
1917 if (dir.Equals("$ROOT") || dir.Equals("Root"))
1922 CStdString CGUIMediaWindow::RemoveParameterFromPath(const CStdString &strDirectory, const CStdString &strParameter)
1924 CURL url(strDirectory);
1925 if (url.HasOption(strParameter))
1927 url.RemoveOption(strParameter);
1931 return strDirectory;