/* * Copyright (C) 2005-2013 Team XBMC * http://xbmc.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC; see the file COPYING. If not, see * . * */ #include "threads/SystemClock.h" #include "GUIMediaWindow.h" #include "GUIUserMessages.h" #include "Util.h" #include "PlayListPlayer.h" #include "addons/AddonManager.h" #include "addons/PluginSource.h" #include "filesystem/PluginDirectory.h" #include "filesystem/MultiPathDirectory.h" #include "GUIPassword.h" #include "Application.h" #include "ApplicationMessenger.h" #include "network/Network.h" #include "utils/RegExp.h" #include "PartyModeManager.h" #include "dialogs/GUIDialogMediaSource.h" #include "GUIWindowFileManager.h" #include "filesystem/FavouritesDirectory.h" #include "utils/LabelFormatter.h" #include "dialogs/GUIDialogProgress.h" #include "profiles/ProfilesManager.h" #include "settings/AdvancedSettings.h" #include "settings/Settings.h" #include "URL.h" #include "dialogs/GUIDialogSmartPlaylistEditor.h" #include "addons/GUIDialogAddonSettings.h" #include "dialogs/GUIDialogYesNo.h" #include "guilib/GUIWindowManager.h" #include "dialogs/GUIDialogOK.h" #include "playlists/PlayList.h" #include "storage/MediaManager.h" #include "utils/MarkWatchedJob.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "guilib/Key.h" #include "guilib/LocalizeStrings.h" #include "utils/TimeUtils.h" #include "filesystem/File.h" #include "filesystem/FileDirectoryFactory.h" #include "utils/log.h" #include "utils/FileUtils.h" #include "guilib/GUIEditControl.h" #include "guilib/GUIKeyboardFactory.h" #include "interfaces/Builtins.h" #include "interfaces/generic/ScriptInvocationManager.h" #include "dialogs/GUIDialogKaiToast.h" #include "dialogs/GUIDialogMediaFilter.h" #include "filesystem/SmartPlaylistDirectory.h" #if defined(TARGET_ANDROID) #include "xbmc/android/activity/XBMCApp.h" #endif #include "FileItemListModification.h" #define CONTROL_BTNVIEWASICONS 2 #define CONTROL_BTNSORTBY 3 #define CONTROL_BTNSORTASC 4 #define CONTROL_BTN_FILTER 19 #define CONTROL_LABELFILES 12 #define PROPERTY_PATH_DB "path.db" #define PROPERTY_SORT_ORDER "sort.order" #define PROPERTY_SORT_ASCENDING "sort.ascending" using namespace std; using namespace ADDON; CGUIMediaWindow::CGUIMediaWindow(int id, const char *xmlFile) : CGUIWindow(id, xmlFile) { m_loadType = KEEP_IN_MEMORY; m_vecItems = new CFileItemList; m_unfilteredItems = new CFileItemList; m_vecItems->SetPath("?"); m_iLastControl = -1; m_iSelectedItem = -1; m_canFilterAdvanced = false; m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems)); } CGUIMediaWindow::~CGUIMediaWindow() { delete m_vecItems; delete m_unfilteredItems; } #define CONTROL_VIEW_START 50 #define CONTROL_VIEW_END 59 void CGUIMediaWindow::LoadAdditionalTags(TiXmlElement *root) { CGUIWindow::LoadAdditionalTags(root); // configure our view control m_viewControl.Reset(); m_viewControl.SetParentWindow(GetID()); TiXmlElement *element = root->FirstChildElement("views"); if (element && element->FirstChild()) { // format is 50,29,51,95 const std::string &allViews = element->FirstChild()->ValueStr(); vector views = StringUtils::Split(allViews, ","); for (vector::const_iterator i = views.begin(); i != views.end(); ++i) { int controlID = atol(i->c_str()); CGUIControl *control = GetControl(controlID); if (control && control->IsContainer()) m_viewControl.AddView(control); } } else { // backward compatibility vector controls; GetContainers(controls); for (ciControls it = controls.begin(); it != controls.end(); it++) { CGUIControl *control = *it; if (control->GetID() >= CONTROL_VIEW_START && control->GetID() <= CONTROL_VIEW_END) m_viewControl.AddView(control); } } m_viewControl.SetViewControlID(CONTROL_BTNVIEWASICONS); } void CGUIMediaWindow::OnWindowLoaded() { SendMessage(GUI_MSG_SET_TYPE, CONTROL_BTN_FILTER, CGUIEditControl::INPUT_TYPE_FILTER); CGUIWindow::OnWindowLoaded(); SetupShares(); } void CGUIMediaWindow::OnWindowUnload() { CGUIWindow::OnWindowUnload(); m_viewControl.Reset(); } CFileItemPtr CGUIMediaWindow::GetCurrentListItem(int offset) { int item = m_viewControl.GetSelectedItem(); if (!m_vecItems->Size() || item < 0) return CFileItemPtr(); item = (item + offset) % m_vecItems->Size(); if (item < 0) item += m_vecItems->Size(); return m_vecItems->Get(item); } bool CGUIMediaWindow::OnAction(const CAction &action) { if (action.GetID() == ACTION_PARENT_DIR) { GoParentFolder(); return true; } // the non-contextual menu can be called at any time if (action.GetID() == ACTION_CONTEXT_MENU && !m_viewControl.HasControl(GetFocusedControlID())) { OnPopupMenu(-1); return true; } if (CGUIWindow::OnAction(action)) return true; if (action.GetID() == ACTION_FILTER) return Filter(); // live filtering if (action.GetID() == ACTION_FILTER_CLEAR) { CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS); message.SetStringParam(""); OnMessage(message); return true; } if (action.GetID() == ACTION_BACKSPACE) { CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 2); // 2 for delete OnMessage(message); return true; } if (action.GetID() >= ACTION_FILTER_SMS2 && action.GetID() <= ACTION_FILTER_SMS9) { std::string filter = StringUtils::Format("%i", (int)(action.GetID() - ACTION_FILTER_SMS2 + 2)); CGUIMessage message(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_FILTER_ITEMS, 1); // 1 for append message.SetStringParam(filter); OnMessage(message); return true; } return false; } bool CGUIMediaWindow::OnBack(int actionID) { CURL filterUrl(m_strFilterPath); if (actionID == ACTION_NAV_BACK && !m_vecItems->IsVirtualDirectoryRoot() && (m_vecItems->GetPath() != m_startDirectory || (m_canFilterAdvanced && filterUrl.HasOption("filter")))) { GoParentFolder(); return true; } return CGUIWindow::OnBack(actionID); } bool CGUIMediaWindow::OnMessage(CGUIMessage& message) { switch ( message.GetMessage() ) { case GUI_MSG_WINDOW_DEINIT: { m_iSelectedItem = m_viewControl.GetSelectedItem(); m_iLastControl = GetFocusedControlID(); CGUIWindow::OnMessage(message); CGUIDialogContextMenu* pDlg = (CGUIDialogContextMenu*)g_windowManager.GetWindow(WINDOW_DIALOG_CONTEXT_MENU); if (pDlg && pDlg->IsActive()) pDlg->Close(); // get rid of any active filtering if (m_canFilterAdvanced) { m_canFilterAdvanced = false; m_filter.Reset(); } m_strFilterPath.clear(); // Call ClearFileItems() after our window has finished doing any WindowClose // animations ClearFileItems(); return true; } break; case GUI_MSG_CLICKED: { int iControl = message.GetSenderId(); if (iControl == CONTROL_BTNVIEWASICONS) { // view as control could be a select button int viewMode = 0; const CGUIControl *control = GetControl(CONTROL_BTNVIEWASICONS); if (control && control->GetControlType() != CGUIControl::GUICONTROL_BUTTON) { CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTNVIEWASICONS); OnMessage(msg); viewMode = m_viewControl.GetViewModeNumber(msg.GetParam1()); } else viewMode = m_viewControl.GetNextViewMode(); if (m_guiState.get()) m_guiState->SaveViewAsControl(viewMode); UpdateButtons(); return true; } else if (iControl == CONTROL_BTNSORTASC) // sort asc { if (m_guiState.get()) m_guiState->SetNextSortOrder(); UpdateFileList(); return true; } else if (iControl == CONTROL_BTNSORTBY) // sort by { if (m_guiState.get()) m_guiState->SetNextSortMethod(); UpdateFileList(); return true; } else if (iControl == CONTROL_BTN_FILTER) return Filter(false); else if (m_viewControl.HasControl(iControl)) // list/thumb control { int iItem = m_viewControl.GetSelectedItem(); int iAction = message.GetParam1(); if (iItem < 0) break; if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) { OnSelect(iItem); } else if (iAction == ACTION_CONTEXT_MENU || iAction == ACTION_MOUSE_RIGHT_CLICK) { OnPopupMenu(iItem); return true; } } } break; case GUI_MSG_SETFOCUS: { if (m_viewControl.HasControl(message.GetControlId()) && m_viewControl.GetCurrentControl() != message.GetControlId()) { m_viewControl.SetFocused(); return true; } } break; case GUI_MSG_NOTIFY_ALL: { // Message is received even if this window is inactive if (message.GetParam1() == GUI_MSG_WINDOW_RESET) { m_vecItems->SetPath("?"); return true; } else if ( message.GetParam1() == GUI_MSG_REFRESH_THUMBS ) { for (int i = 0; i < m_vecItems->Size(); i++) m_vecItems->Get(i)->FreeMemory(true); break; // the window will take care of any info images } else if (message.GetParam1() == GUI_MSG_REMOVED_MEDIA) { if ((m_vecItems->IsVirtualDirectoryRoot() || m_vecItems->IsSourcesPath()) && IsActive()) { int iItem = m_viewControl.GetSelectedItem(); Refresh(); m_viewControl.SetSelectedItem(iItem); } else if (m_vecItems->IsRemovable()) { // check that we have this removable share still if (!m_rootDir.IsInSource(m_vecItems->GetPath())) { // don't have this share any more if (IsActive()) Update(""); else { m_history.ClearPathHistory(); m_vecItems->SetPath(""); } } } return true; } else if (message.GetParam1()==GUI_MSG_UPDATE_SOURCES) { // State of the sources changed, so update our view if ((m_vecItems->IsVirtualDirectoryRoot() || m_vecItems->IsSourcesPath()) && IsActive()) { int iItem = m_viewControl.GetSelectedItem(); Refresh(true); m_viewControl.SetSelectedItem(iItem); } return true; } else if (message.GetParam1()==GUI_MSG_UPDATE && IsActive()) { if (message.GetNumStringParams()) { if (message.GetParam2()) // param2 is used for resetting the history SetHistoryForPath(message.GetStringParam()); CFileItemList list(message.GetStringParam()); list.RemoveDiscCache(GetID()); Update(message.GetStringParam()); } else Refresh(true); // refresh the listing } else if (message.GetParam1()==GUI_MSG_UPDATE_ITEM && message.GetItem()) { CFileItemPtr newItem = boost::static_pointer_cast(message.GetItem()); if (IsActive()) { if (m_vecItems->UpdateItem(newItem.get()) && message.GetParam2() == 1) { // need the list updated as well UpdateFileList(); } } else if (newItem) { // need to remove the disc cache CFileItemList items; items.SetPath(URIUtils::GetDirectory(newItem->GetPath())); items.RemoveDiscCache(GetID()); } } else if (message.GetParam1()==GUI_MSG_UPDATE_PATH) { if (IsActive()) { if((message.GetStringParam() == m_vecItems->GetPath()) || (m_vecItems->IsMultiPath() && XFILE::CMultiPathDirectory::HasPath(m_vecItems->GetPath(), message.GetStringParam()))) Refresh(); } } else if (message.GetParam1() == GUI_MSG_FILTER_ITEMS && IsActive()) { std::string filter = GetProperty("filter").asString(); // check if this is meant for advanced filtering if (message.GetParam2() != 10) { if (message.GetParam2() == 1) // append filter += message.GetStringParam(); else if (message.GetParam2() == 2) { // delete if (filter.size()) filter.erase(filter.size() - 1); } else filter = message.GetStringParam(); } OnFilterItems(filter); return true; } else return CGUIWindow::OnMessage(message); return true; } break; case GUI_MSG_PLAYBACK_STARTED: case GUI_MSG_PLAYBACK_ENDED: case GUI_MSG_PLAYBACK_STOPPED: case GUI_MSG_PLAYLIST_CHANGED: case GUI_MSG_PLAYLISTPLAYER_STOPPED: case GUI_MSG_PLAYLISTPLAYER_STARTED: case GUI_MSG_PLAYLISTPLAYER_CHANGED: { // send a notify all to all controls on this window CGUIMessage msg(GUI_MSG_NOTIFY_ALL, GetID(), 0, GUI_MSG_REFRESH_LIST); OnMessage(msg); break; } case GUI_MSG_CHANGE_VIEW_MODE: { int viewMode = 0; if (message.GetParam1()) // we have an id viewMode = m_viewControl.GetViewModeByID(message.GetParam1()); else if (message.GetParam2()) viewMode = m_viewControl.GetNextViewMode((int)message.GetParam2()); if (m_guiState.get()) m_guiState->SaveViewAsControl(viewMode); UpdateButtons(); return true; } break; case GUI_MSG_CHANGE_SORT_METHOD: { if (m_guiState.get()) { if (message.GetParam1()) m_guiState->SetCurrentSortMethod((int)message.GetParam1()); else if (message.GetParam2()) m_guiState->SetNextSortMethod((int)message.GetParam2()); } UpdateFileList(); return true; } break; case GUI_MSG_CHANGE_SORT_DIRECTION: { if (m_guiState.get()) m_guiState->SetNextSortOrder(); UpdateFileList(); return true; } break; case GUI_MSG_WINDOW_INIT: { if (m_vecItems->GetPath() == "?") m_vecItems->SetPath(""); std::string dir = message.GetStringParam(0); const std::string &ret = message.GetStringParam(1); bool returning = StringUtils::EqualsNoCase(ret, "return"); if (!dir.empty()) { // ensure our directory is valid dir = GetStartFolder(dir); bool resetHistory = false; if (!returning || !m_history.IsInHistory(dir)) { // we're not returning to the same path, so set our directory to the requested path m_vecItems->SetPath(dir); resetHistory = true; } // check for network up if (URIUtils::IsRemote(m_vecItems->GetPath()) && !WaitForNetwork()) { m_vecItems->SetPath(""); resetHistory = true; } if (resetHistory) SetHistoryForPath(m_vecItems->GetPath()); } if (message.GetParam1() != WINDOW_INVALID) { // first time to this window - make sure we set the root path m_startDirectory = returning ? dir : ""; } } break; } return CGUIWindow::OnMessage(message); } // \brief Updates the states (enable, disable, visible...) // of the controls defined by this window // Override this function in a derived class to add new controls void CGUIMediaWindow::UpdateButtons() { if (m_guiState.get()) { // Update sorting controls if (m_guiState->GetDisplaySortOrder() == SortOrderNone) { CONTROL_DISABLE(CONTROL_BTNSORTASC); } else { CONTROL_ENABLE(CONTROL_BTNSORTASC); SET_CONTROL_SELECTED(GetID(), CONTROL_BTNSORTASC, m_guiState->GetDisplaySortOrder() != SortOrderAscending); } // Update list/thumb control m_viewControl.SetCurrentView(m_guiState->GetViewAsControl()); // Update sort by button if (!m_guiState->HasMultipleSortMethods()) CONTROL_DISABLE(CONTROL_BTNSORTBY); else CONTROL_ENABLE(CONTROL_BTNSORTBY); std::string sortLabel = StringUtils::Format(g_localizeStrings.Get(550).c_str(), g_localizeStrings.Get(m_guiState->GetSortMethodLabel()).c_str()); SET_CONTROL_LABEL(CONTROL_BTNSORTBY, sortLabel); } std::string items = StringUtils::Format("%i %s", m_vecItems->GetObjectCount(), g_localizeStrings.Get(127).c_str()); SET_CONTROL_LABEL(CONTROL_LABELFILES, items); SET_CONTROL_LABEL2(CONTROL_BTN_FILTER, GetProperty("filter").asString()); } void CGUIMediaWindow::ClearFileItems() { m_viewControl.Clear(); m_vecItems->Clear(); m_unfilteredItems->Clear(); } // \brief Sorts Fileitems based on the sort method and sort oder provided by guiViewState void CGUIMediaWindow::SortItems(CFileItemList &items) { auto_ptr guiState(CGUIViewState::GetViewState(GetID(), items)); if (guiState.get()) { SortDescription sorting = guiState->GetSortMethod(); sorting.sortOrder = guiState->GetDisplaySortOrder(); // If the sort method is "sort by playlist" and we have a specific // sort order available we can use the specified sort order to do the sorting // We do this as the new SortBy methods are a superset of the SORT_METHOD methods, thus // not all are available. This may be removed once SORT_METHOD_* have been replaced by // SortBy. if ((sorting.sortBy == SortByPlaylistOrder) && items.HasProperty(PROPERTY_SORT_ORDER)) { SortBy sortBy = (SortBy)items.GetProperty(PROPERTY_SORT_ORDER).asInteger(); if (sortBy != SortByNone && sortBy != SortByPlaylistOrder && sortBy != SortByProgramCount) { sorting.sortBy = sortBy; sorting.sortOrder = items.GetProperty(PROPERTY_SORT_ASCENDING).asBoolean() ? SortOrderAscending : SortOrderDescending; sorting.sortAttributes = CSettings::Get().GetBool("filelists.ignorethewhensorting") ? SortAttributeIgnoreArticle : SortAttributeNone; // if the sort order is descending, we need to switch the original sort order, as we assume // in CGUIViewState::AddPlaylistOrder that SortByPlaylistOrder is ascending. if (guiState->GetDisplaySortOrder() == SortOrderDescending) sorting.sortOrder = sorting.sortOrder == SortOrderDescending ? SortOrderAscending : SortOrderDescending; } } items.Sort(sorting); } } // \brief Formats item labels based on the formatting provided by guiViewState void CGUIMediaWindow::FormatItemLabels(CFileItemList &items, const LABEL_MASKS &labelMasks) { CLabelFormatter fileFormatter(labelMasks.m_strLabelFile, labelMasks.m_strLabel2File); CLabelFormatter folderFormatter(labelMasks.m_strLabelFolder, labelMasks.m_strLabel2Folder); for (int i=0; iIsLabelPreformated()) continue; if (pItem->m_bIsFolder) folderFormatter.FormatLabels(pItem.get()); else fileFormatter.FormatLabels(pItem.get()); } if (items.GetSortMethod() == SortByLabel) items.ClearSortState(); } // \brief Prepares and adds the fileitems list/thumb panel void CGUIMediaWindow::FormatAndSort(CFileItemList &items) { auto_ptr viewState(CGUIViewState::GetViewState(GetID(), items)); if (viewState.get()) { LABEL_MASKS labelMasks; viewState->GetSortMethodLabelMasks(labelMasks); FormatItemLabels(items, labelMasks); items.Sort(viewState->GetSortMethod().sortBy, viewState->GetDisplaySortOrder(), viewState->GetSortMethod().sortAttributes); } } /*! \brief Overwrite to fill fileitems from a source \param strDirectory Path to read \param items Fill with items specified in \e strDirectory */ bool CGUIMediaWindow::GetDirectory(const std::string &strDirectory, CFileItemList &items) { const CURL pathToUrl(strDirectory); // cleanup items if (items.Size()) items.Clear(); std::string strParentPath = m_history.GetParentPath(); CLog::Log(LOGDEBUG,"CGUIMediaWindow::GetDirectory (%s)", CURL::GetRedacted(strDirectory).c_str()); CLog::Log(LOGDEBUG," ParentPath = [%s]", CURL::GetRedacted(strParentPath).c_str()); // see if we can load a previously cached folder CFileItemList cachedItems(strDirectory); if (!strDirectory.empty() && cachedItems.Load(GetID())) { items.Assign(cachedItems); } else { unsigned int time = XbmcThreads::SystemClockMillis(); if (strDirectory.empty()) SetupShares(); if (!m_rootDir.GetDirectory(pathToUrl, items)) return false; // took over a second, and not normally cached, so cache it if ((XbmcThreads::SystemClockMillis() - time) > 1000 && items.CacheToDiscIfSlow()) items.Save(GetID()); // if these items should replace the current listing, then pop it off the top if (items.GetReplaceListing()) m_history.RemoveParentPath(); } // update the view state to the currently fetched items // TODO we should remove the second call m_guiState.reset() in Update() and pass the right file item ref here m_guiState.reset(CGUIViewState::GetViewState(GetID(), items)); if (m_guiState.get() && !m_guiState->HideParentDirItems() && !items.GetPath().empty()) { CFileItemPtr pItem(new CFileItem("..")); pItem->SetPath(strParentPath); pItem->m_bIsFolder = true; pItem->m_bIsShareOrDrive = false; items.AddFront(pItem, 0); } int iWindow = GetID(); vector regexps; // TODO: Do we want to limit the directories we apply the video ones to? if (iWindow == WINDOW_VIDEO_NAV) regexps = g_advancedSettings.m_videoExcludeFromListingRegExps; if (iWindow == WINDOW_MUSIC_FILES) regexps = g_advancedSettings.m_audioExcludeFromListingRegExps; if (iWindow == WINDOW_PICTURES) regexps = g_advancedSettings.m_pictureExcludeFromListingRegExps; if (regexps.size()) { for (int i=0; i < items.Size();) { if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps)) items.Remove(i); else i++; } } // clear the filter SetProperty("filter", ""); m_canFilterAdvanced = false; m_filter.Reset(); return true; } // \brief Set window to a specific directory // \param strDirectory The directory to be displayed in list/thumb control // This function calls OnPrepareFileItems() and OnFinalizeFileItems() bool CGUIMediaWindow::Update(const std::string &strDirectory, bool updateFilterPath /* = true */) { // TODO: OnInitWindow calls Update() before window path has been set properly. if (strDirectory == "?") return false; // get selected item int iItem = m_viewControl.GetSelectedItem(); std::string strSelectedItem; if (iItem >= 0 && iItem < m_vecItems->Size()) { CFileItemPtr pItem = m_vecItems->Get(iItem); if (!pItem->IsParentFolder()) { GetDirectoryHistoryString(pItem.get(), strSelectedItem); } } std::string strCurrentDirectory = m_vecItems->GetPath(); m_history.SetSelectedItem(strSelectedItem, strCurrentDirectory); std::string directory = strDirectory; // check if the path contains a filter and temporarily remove it // so that the retrieved list of items is unfiltered bool canfilter = CanContainFilter(directory); CURL url(directory); if (canfilter && url.HasOption("filter")) directory = RemoveParameterFromPath(directory, "filter"); CFileItemList items; if (!GetDirectory(directory, items)) { CLog::Log(LOGERROR,"CGUIMediaWindow::GetDirectory(%s) failed", url.GetRedacted().c_str()); // Try to return to the previous directory, if not the same // else fallback to root if (URIUtils::PathEquals(strDirectory, strCurrentDirectory) || !Update(m_history.RemoveParentPath())) Update(""); // Fallback to root // Return false to be able to eg. show // an error message. return false; } if (items.GetLabel().empty()) items.SetLabel(CUtil::GetTitleFromPath(items.GetPath(), true)); ClearFileItems(); m_vecItems->Copy(items); // check the given path for filter data UpdateFilterPath(strDirectory, items, updateFilterPath); // if we're getting the root source listing // make sure the path history is clean if (strDirectory.empty()) m_history.ClearPathHistory(); int iWindow = GetID(); int showLabel = 0; if (strDirectory.empty()) { if (iWindow == WINDOW_PICTURES) showLabel = 997; else if (iWindow == WINDOW_MUSIC_FILES) showLabel = 998; else if (iWindow == WINDOW_FILES || iWindow == WINDOW_PROGRAMS) showLabel = 1026; } if (m_vecItems->IsPath("sources://video/")) showLabel = 999; else if (m_vecItems->IsPath("sources://music/")) showLabel = 998; else if (m_vecItems->IsPath("sources://pictures/")) showLabel = 997; else if (m_vecItems->IsPath("sources://programs/") || m_vecItems->IsPath("sources://files/")) showLabel = 1026; if (showLabel && (m_vecItems->Size() == 0 || !m_guiState->DisableAddSourceButtons())) // add 'add source button' { std::string strLabel = g_localizeStrings.Get(showLabel); CFileItemPtr pItem(new CFileItem(strLabel)); pItem->SetPath("add"); pItem->SetIconImage("DefaultAddSource.png"); pItem->SetLabel(strLabel); pItem->SetLabelPreformated(true); pItem->m_bIsFolder = true; pItem->SetSpecialSort(SortSpecialOnBottom); m_vecItems->Add(pItem); } m_iLastControl = GetFocusedControlID(); // Check whether to enabled advanced filtering based on the content type m_canFilterAdvanced = CheckFilterAdvanced(*m_vecItems); if (m_canFilterAdvanced) m_filter.SetType(m_vecItems->GetContent()); // Ask the derived class if it wants to load additional info // for the fileitems like media info or additional // filtering on the items, setting thumbs. OnPrepareFileItems(*m_vecItems); m_vecItems->FillInDefaultIcons(); m_guiState.reset(CGUIViewState::GetViewState(GetID(), *m_vecItems)); // remember the original (untouched) list of items (for filtering etc) m_unfilteredItems->SetPath(m_vecItems->GetPath()); // use the original path - it'll likely be relied on for other things later. m_unfilteredItems->Append(*m_vecItems); // Cache the list of items if possible OnCacheFileItems(*m_vecItems); // Filter and group the items if necessary OnFilterItems(GetProperty("filter").asString()); // Ask the devived class if it wants to do custom list operations, // eg. changing the label OnFinalizeFileItems(*m_vecItems); UpdateButtons(); strSelectedItem = m_history.GetSelectedItem(m_vecItems->GetPath()); bool bSelectedFound = false; //int iSongInDirectory = -1; for (int i = 0; i < m_vecItems->Size(); ++i) { CFileItemPtr pItem = m_vecItems->Get(i); // Update selected item std::string strHistory; GetDirectoryHistoryString(pItem.get(), strHistory); if (strHistory == strSelectedItem) { m_viewControl.SetSelectedItem(i); bSelectedFound = true; break; } } // if we haven't found the selected item, select the first item if (!bSelectedFound) m_viewControl.SetSelectedItem(0); m_history.AddPath(m_vecItems->GetPath(), m_strFilterPath); //m_history.DumpPathHistory(); return true; } bool CGUIMediaWindow::Refresh(bool clearCache /* = false */) { std::string strCurrentDirectory = m_vecItems->GetPath(); if (strCurrentDirectory == "?") return false; if (clearCache) m_vecItems->RemoveDiscCache(GetID()); // get the original number of items if (!Update(strCurrentDirectory, false)) return false; return true; } // \brief This function will be called by Update() before the // labels of the fileitems are formatted. Override this function // to set custom thumbs or load additional media info. // It's used to load tag info for music. void CGUIMediaWindow::OnPrepareFileItems(CFileItemList &items) { CFileItemListModification::Get().Modify(items); } // \brief This function will be called by Update() before // any additional formatting, filtering or sorting is applied. // Override this function to define a custom caching behaviour. void CGUIMediaWindow::OnCacheFileItems(CFileItemList &items) { // Should these items be saved to the hdd if (items.CacheToDiscAlways() && !IsFiltered()) items.Save(GetID()); } // \brief This function will be called by Update() after the // labels of the fileitems are formatted. Override this function // to modify the fileitems. Eg. to modify the item label void CGUIMediaWindow::OnFinalizeFileItems(CFileItemList &items) { } // \brief With this function you can react on a users click in the list/thumb panel. // It returns true, if the click is handled. // This function calls OnPlayMedia() bool CGUIMediaWindow::OnClick(int iItem) { if ( iItem < 0 || iItem >= (int)m_vecItems->Size() ) return true; CFileItemPtr pItem = m_vecItems->Get(iItem); if (pItem->IsParentFolder()) { GoParentFolder(); return true; } if (pItem->GetPath() == "add" || pItem->GetPath() == "sources://add/") // 'add source button' in empty root { OnContextButton(iItem, CONTEXT_BUTTON_ADD_SOURCE); return true; } if (!pItem->m_bIsFolder && pItem->IsFileFolder(EFILEFOLDER_MASK_ONCLICK)) { XFILE::IFileDirectory *pFileDirectory = NULL; pFileDirectory = XFILE::CFileDirectoryFactory::Create(pItem->GetURL(), pItem.get(), ""); if(pFileDirectory) pItem->m_bIsFolder = true; else if(pItem->m_bIsFolder) pItem->m_bIsFolder = false; delete pFileDirectory; } if (pItem->IsScript()) { // execute the script CURL url(pItem->GetPath()); AddonPtr addon; if (CAddonMgr::Get().GetAddon(url.GetHostName(), addon, ADDON_SCRIPT)) { if (!CScriptInvocationManager::Get().Stop(addon->LibPath())) CScriptInvocationManager::Get().Execute(addon->LibPath(), addon); return true; } } if (pItem->m_bIsFolder) { if ( pItem->m_bIsShareOrDrive ) { const std::string& strLockType=m_guiState->GetLockType(); if (CProfilesManager::Get().GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE) if (!strLockType.empty() && !g_passwordManager.IsItemUnlocked(pItem.get(), strLockType)) return true; if (!HaveDiscOrConnection(pItem->GetPath(), pItem->m_iDriveType)) return true; } // check for the partymode playlist items - they may not exist yet if ((pItem->GetPath() == CProfilesManager::Get().GetUserDataItem("PartyMode.xsp")) || (pItem->GetPath() == CProfilesManager::Get().GetUserDataItem("PartyMode-Video.xsp"))) { // party mode playlist item - if it doesn't exist, prompt for user to define it if (!XFILE::CFile::Exists(pItem->GetPath())) { m_vecItems->RemoveDiscCache(GetID()); if (CGUIDialogSmartPlaylistEditor::EditPlaylist(pItem->GetPath())) Refresh(); return true; } } // remove the directory cache if the folder is not normally cached CFileItemList items(pItem->GetPath()); if (!items.AlwaysCache()) items.RemoveDiscCache(GetID()); // if we have a filtered list, we need to add the filtered // path to be able to come back to the filtered view std::string strCurrentDirectory = m_vecItems->GetPath(); if (m_canFilterAdvanced && !m_filter.IsEmpty() && !URIUtils::PathEquals(m_strFilterPath, strCurrentDirectory)) { m_history.RemoveParentPath(); m_history.AddPath(strCurrentDirectory, m_strFilterPath); } CFileItem directory(*pItem); if (!Update(directory.GetPath())) ShowShareErrorMessage(&directory); return true; } else if (pItem->IsPlugin() && !pItem->GetProperty("isplayable").asBoolean()) { return XFILE::CPluginDirectory::RunScriptWithParams(pItem->GetPath()); } #if defined(TARGET_ANDROID) else if (pItem->IsAndroidApp()) { std::string appName = URIUtils::GetFileName(pItem->GetPath()); CLog::Log(LOGDEBUG, "CGUIMediaWindow::OnClick Trying to run: %s",appName.c_str()); return CXBMCApp::StartActivity(appName); } #endif else { m_iSelectedItem = m_viewControl.GetSelectedItem(); if (pItem->GetPath() == "newplaylist://") { m_vecItems->RemoveDiscCache(GetID()); g_windowManager.ActivateWindow(WINDOW_MUSIC_PLAYLIST_EDITOR,"newplaylist://"); return true; } else if (StringUtils::StartsWithNoCase(pItem->GetPath(), "newsmartplaylist://")) { m_vecItems->RemoveDiscCache(GetID()); if (CGUIDialogSmartPlaylistEditor::NewPlaylist(pItem->GetPath().substr(19))) Refresh(); return true; } else if (StringUtils::StartsWithNoCase(pItem->GetPath(), "addons://more/")) { CBuiltins::Execute("ActivateWindow(AddonBrowser,addons://all/xbmc.addon." + pItem->GetPath().substr(14) + ",return)"); return true; } // If karaoke song is being played AND popup autoselector is enabled, the playlist should not be added bool do_not_add_karaoke = CSettings::Get().GetBool("karaoke.enabled") && CSettings::Get().GetBool("karaoke.autopopupselector") && pItem->IsKaraoke(); bool autoplay = m_guiState.get() && m_guiState->AutoPlayNextItem(); if (m_vecItems->IsPlugin()) { CURL url(m_vecItems->GetPath()); AddonPtr addon; if (CAddonMgr::Get().GetAddon(url.GetHostName(),addon)) { PluginPtr plugin = boost::dynamic_pointer_cast(addon); if (plugin && plugin->Provides(CPluginSource::AUDIO)) { CFileItemList items; auto_ptr state(CGUIViewState::GetViewState(GetID(), items)); autoplay = state.get() && state->AutoPlayNextItem(); } } } if (autoplay && !g_partyModeManager.IsEnabled() && !pItem->IsPlayList() && !do_not_add_karaoke) { return OnPlayAndQueueMedia(pItem); } else { return OnPlayMedia(iItem); } } return false; } bool CGUIMediaWindow::OnSelect(int item) { return OnClick(item); } // \brief Checks if there is a disc in the dvd drive and whether the // network is connected or not. bool CGUIMediaWindow::HaveDiscOrConnection(const std::string& strPath, int iDriveType) { if (iDriveType==CMediaSource::SOURCE_TYPE_DVD) { if (!g_mediaManager.IsDiscInDrive(strPath)) { CGUIDialogOK::ShowAndGetInput(218, 219, 0, 0); return false; } } else if (iDriveType==CMediaSource::SOURCE_TYPE_REMOTE) { // TODO: Handle not connected to a remote share if ( !g_application.getNetwork().IsConnected() ) { CGUIDialogOK::ShowAndGetInput(220, 221, 0, 0); return false; } } return true; } // \brief Shows a standard errormessage for a given pItem. void CGUIMediaWindow::ShowShareErrorMessage(CFileItem* pItem) { if (!pItem->m_bIsShareOrDrive) return; int idMessageText = 0; CURL url(pItem->GetPath()); if (url.IsProtocol("smb") && url.GetHostName().empty()) // smb workgroup idMessageText = 15303; // Workgroup not found else if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_REMOTE || URIUtils::IsRemote(pItem->GetPath())) idMessageText = 15301; // Could not connect to network server else idMessageText = 15300; // Path not found or invalid CGUIDialogOK::ShowAndGetInput(220, idMessageText, 0, 0); } // \brief The functon goes up one level in the directory tree void CGUIMediaWindow::GoParentFolder() { //m_history.DumpPathHistory(); // remove current directory if its on the stack // there were some issues due some folders having a trailing slash and some not // so just add a trailing slash to all of them for comparison. std::string strPath = m_vecItems->GetPath(); URIUtils::AddSlashAtEnd(strPath); std::string strParent = m_history.GetParentPath(); // in case the path history is messed up and the current folder is on // the stack more than once, keep going until there's nothing left or they // dont match anymore. while (!strParent.empty()) { URIUtils::AddSlashAtEnd(strParent); if (URIUtils::PathEquals(strParent, strPath)) m_history.RemoveParentPath(); else break; strParent = m_history.GetParentPath(); } // remove the current filter but only if the parent // item doesn't have a filter as well CURL filterUrl(m_strFilterPath); if (filterUrl.HasOption("filter")) { CURL parentUrl(m_history.GetParentPath(true)); if (!parentUrl.HasOption("filter")) { // we need to overwrite m_strFilterPath because // Refresh() will set updateFilterPath to false m_strFilterPath.clear(); Refresh(); return; } } // if vector is not empty, pop parent // if vector is empty, parent is root source listing m_strFilterPath = m_history.GetParentPath(true); strParent = m_history.RemoveParentPath(); if (!Update(strParent, false)) return; // No items to show so go another level up if (!m_vecItems->GetPath().empty() && (m_filter.IsEmpty() ? m_vecItems->Size() : m_unfilteredItems->Size()) <= 0) { CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(2080), g_localizeStrings.Get(2081)); GoParentFolder(); } } // \brief Override the function to change the default behavior on how // a selected item history should look like void CGUIMediaWindow::GetDirectoryHistoryString(const CFileItem* pItem, std::string& strHistoryString) { if (pItem->m_bIsShareOrDrive) { // We are in the virual directory // History string of the DVD drive // must be handel separately if (pItem->m_iDriveType == CMediaSource::SOURCE_TYPE_DVD) { // Remove disc label from item label // and use as history string, m_strPath // can change for new discs std::string strLabel = pItem->GetLabel(); size_t nPosOpen = strLabel.find('('); size_t nPosClose = strLabel.rfind(')'); if (nPosOpen != std::string::npos && nPosClose != std::string::npos && nPosClose > nPosOpen) { strLabel.erase(nPosOpen + 1, (nPosClose) - (nPosOpen + 1)); strHistoryString = strLabel; } else strHistoryString = strLabel; } else { // Other items in virual directory std::string strPath = pItem->GetPath(); URIUtils::RemoveSlashAtEnd(strPath); strHistoryString = pItem->GetLabel() + strPath; } } else if (pItem->m_lEndOffset>pItem->m_lStartOffset && pItem->m_lStartOffset != -1) { // Could be a cue item, all items of a cue share the same filename // so add the offsets to build the history string strHistoryString = StringUtils::Format("%i%i", pItem->m_lStartOffset, pItem->m_lEndOffset); strHistoryString += pItem->GetPath(); } else { // Normal directory items strHistoryString = pItem->GetPath(); } // remove any filter if (CanContainFilter(strHistoryString)) strHistoryString = RemoveParameterFromPath(strHistoryString, "filter"); URIUtils::RemoveSlashAtEnd(strHistoryString); StringUtils::ToLower(strHistoryString); } // \brief Call this function to create a directory history for the // path given by strDirectory. void CGUIMediaWindow::SetHistoryForPath(const std::string& strDirectory) { // Make sure our shares are configured SetupShares(); if (!strDirectory.empty()) { // Build the directory history for default path std::string strPath, strParentPath; strPath = strDirectory; URIUtils::RemoveSlashAtEnd(strPath); CFileItemList items; m_rootDir.GetDirectory(CURL(), items); m_history.ClearPathHistory(); while (URIUtils::GetParentPath(strPath, strParentPath)) { for (int i = 0; i < (int)items.Size(); ++i) { CFileItemPtr pItem = items[i]; std::string path(pItem->GetPath()); URIUtils::RemoveSlashAtEnd(path); if (URIUtils::PathEquals(path, strPath)) { std::string strHistory; GetDirectoryHistoryString(pItem.get(), strHistory); m_history.SetSelectedItem(strHistory, ""); URIUtils::AddSlashAtEnd(strPath); m_history.AddPathFront(strPath); m_history.AddPathFront(""); //m_history.DumpPathHistory(); return ; } } if (URIUtils::IsVideoDb(strPath)) { CURL url(strParentPath); url.SetOptions(""); // clear any URL options from recreated parent path strParentPath = url.Get(); } URIUtils::AddSlashAtEnd(strPath); m_history.AddPathFront(strPath); m_history.SetSelectedItem(strPath, strParentPath); strPath = strParentPath; URIUtils::RemoveSlashAtEnd(strPath); } } else m_history.ClearPathHistory(); //m_history.DumpPathHistory(); } // \brief Override if you want to change the default behavior, what is done // when the user clicks on a file. // This function is called by OnClick() bool CGUIMediaWindow::OnPlayMedia(int iItem) { // Reset Playlistplayer, playback started now does // not use the playlistplayer. g_playlistPlayer.Reset(); g_playlistPlayer.SetCurrentPlaylist(PLAYLIST_NONE); CFileItemPtr pItem=m_vecItems->Get(iItem); CLog::Log(LOGDEBUG, "%s %s", __FUNCTION__, CURL::GetRedacted(pItem->GetPath()).c_str()); bool bResult = false; if (pItem->IsInternetStream() || pItem->IsPlayList()) bResult = g_application.PlayMedia(*pItem, m_guiState->GetPlaylist()); else bResult = g_application.PlayFile(*pItem) == PLAYBACK_OK; if (pItem->m_lStartOffset == STARTOFFSET_RESUME) pItem->m_lStartOffset = 0; return bResult; } // \brief Override if you want to change the default behavior of what is done // when the user clicks on a file in a "folder" with similar files. // This function is called by OnClick() bool CGUIMediaWindow::OnPlayAndQueueMedia(const CFileItemPtr &item) { //play and add current directory to temporary playlist int iPlaylist = m_guiState->GetPlaylist(); if (iPlaylist != PLAYLIST_NONE) { g_playlistPlayer.ClearPlaylist(iPlaylist); g_playlistPlayer.Reset(); int mediaToPlay = 0; // first try to find mainDVD file (VIDEO_TS.IFO). // If we find this we should not allow to queue VOB files std::string mainDVD; for (int i = 0; i < m_vecItems->Size(); i++) { std::string path = URIUtils::GetFileName(m_vecItems->Get(i)->GetPath()); if (StringUtils::EqualsNoCase(path, "VIDEO_TS.IFO")) { mainDVD = path; break; } } // now queue... for ( int i = 0; i < m_vecItems->Size(); i++ ) { CFileItemPtr nItem = m_vecItems->Get(i); if (nItem->m_bIsFolder) continue; if (!nItem->IsPlayList() && !nItem->IsZIP() && !nItem->IsRAR() && (!nItem->IsDVDFile() || (URIUtils::GetFileName(nItem->GetPath()) == mainDVD))) g_playlistPlayer.Add(iPlaylist, nItem); if (item->IsSamePath(nItem.get())) { // item that was clicked mediaToPlay = g_playlistPlayer.GetPlaylist(iPlaylist).size() - 1; } } // Save current window and directory to know where the selected item was if (m_guiState.get()) m_guiState->SetPlaylistDirectory(m_vecItems->GetPath()); // figure out where we start playback if (g_playlistPlayer.IsShuffled(iPlaylist)) { int iIndex = g_playlistPlayer.GetPlaylist(iPlaylist).FindOrder(mediaToPlay); g_playlistPlayer.GetPlaylist(iPlaylist).Swap(0, iIndex); mediaToPlay = 0; } // play g_playlistPlayer.SetCurrentPlaylist(iPlaylist); g_playlistPlayer.Play(mediaToPlay); } return true; } // \brief Synchonize the fileitems with the playlistplayer // It recreated the playlist of the playlistplayer based // on the fileitems of the window void CGUIMediaWindow::UpdateFileList() { int nItem = m_viewControl.GetSelectedItem(); std::string strSelected; if (nItem >= 0) strSelected = m_vecItems->Get(nItem)->GetPath(); FormatAndSort(*m_vecItems); UpdateButtons(); m_viewControl.SetItems(*m_vecItems); m_viewControl.SetSelectedItem(strSelected); // set the currently playing item as selected, if its in this directory if (m_guiState.get() && m_guiState->IsCurrentPlaylistDirectory(m_vecItems->GetPath())) { int iPlaylist=m_guiState->GetPlaylist(); int nSong = g_playlistPlayer.GetCurrentSong(); CFileItem playlistItem; if (nSong > -1 && iPlaylist > -1) playlistItem=*g_playlistPlayer.GetPlaylist(iPlaylist)[nSong]; g_playlistPlayer.ClearPlaylist(iPlaylist); g_playlistPlayer.Reset(); for (int i = 0; i < m_vecItems->Size(); i++) { CFileItemPtr pItem = m_vecItems->Get(i); if (pItem->m_bIsFolder) continue; if (!pItem->IsPlayList() && !pItem->IsZIP() && !pItem->IsRAR()) g_playlistPlayer.Add(iPlaylist, pItem); if (pItem->GetPath() == playlistItem.GetPath() && pItem->m_lStartOffset == playlistItem.m_lStartOffset) g_playlistPlayer.SetCurrentSong(g_playlistPlayer.GetPlaylist(iPlaylist).size() - 1); } } } void CGUIMediaWindow::OnDeleteItem(int iItem) { if ( iItem < 0 || iItem >= m_vecItems->Size()) return; CFileItemPtr item = m_vecItems->Get(iItem); if (item->IsPlayList()) item->m_bIsFolder = false; if (CProfilesManager::Get().GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && CProfilesManager::Get().GetCurrentProfile().filesLocked()) if (!g_passwordManager.IsMasterLockUnlocked(true)) return; if (!CFileUtils::DeleteItem(item)) return; Refresh(true); m_viewControl.SetSelectedItem(iItem); } void CGUIMediaWindow::OnRenameItem(int iItem) { if ( iItem < 0 || iItem >= m_vecItems->Size()) return; if (CProfilesManager::Get().GetCurrentProfile().getLockMode() != LOCK_MODE_EVERYONE && CProfilesManager::Get().GetCurrentProfile().filesLocked()) if (!g_passwordManager.IsMasterLockUnlocked(true)) return; if (!CFileUtils::RenameFile(m_vecItems->Get(iItem)->GetPath())) return; Refresh(true); m_viewControl.SetSelectedItem(iItem); } void CGUIMediaWindow::OnInitWindow() { // initial fetch is done unthreaded to ensure the items are setup prior to skin animations kicking off m_rootDir.SetAllowThreads(false); // the start directory may change during Refresh bool updateStartDirectory = (m_startDirectory == m_vecItems->GetPath()); Refresh(); if (updateStartDirectory) m_startDirectory = m_vecItems->GetPath(); m_rootDir.SetAllowThreads(true); if (m_iSelectedItem > -1) m_viewControl.SetSelectedItem(m_iSelectedItem); CGUIWindow::OnInitWindow(); } CGUIControl *CGUIMediaWindow::GetFirstFocusableControl(int id) { if (m_viewControl.HasControl(id)) id = m_viewControl.GetCurrentControl(); return CGUIWindow::GetFirstFocusableControl(id); } void CGUIMediaWindow::SetupShares() { // Setup shares and filemasks for this window CFileItemList items; CGUIViewState* viewState=CGUIViewState::GetViewState(GetID(), items); if (viewState) { m_rootDir.SetMask(viewState->GetExtensions()); m_rootDir.SetSources(viewState->GetSources()); delete viewState; } } bool CGUIMediaWindow::OnPopupMenu(int iItem) { // popup the context menu // grab our context menu CContextButtons buttons; GetContextButtons(iItem, buttons); if (buttons.size()) { // mark the item if (iItem >= 0 && iItem < m_vecItems->Size()) m_vecItems->Get(iItem)->Select(true); int choice = CGUIDialogContextMenu::ShowAndGetChoice(buttons); // deselect our item if (iItem >= 0 && iItem < m_vecItems->Size()) m_vecItems->Get(iItem)->Select(false); if (choice >= 0) return OnContextButton(iItem, (CONTEXT_BUTTON)choice); } return false; } void CGUIMediaWindow::GetContextButtons(int itemNumber, CContextButtons &buttons) { CFileItemPtr item = (itemNumber >= 0 && itemNumber < m_vecItems->Size()) ? m_vecItems->Get(itemNumber) : CFileItemPtr(); if (!item) return; // user added buttons std::string label; std::string action; for (int i = CONTEXT_BUTTON_USER1; i <= CONTEXT_BUTTON_USER10; i++) { label = StringUtils::Format("contextmenulabel(%i)", i - CONTEXT_BUTTON_USER1); if (item->GetProperty(label).empty()) break; action = StringUtils::Format("contextmenuaction(%i)", i - CONTEXT_BUTTON_USER1); if (item->GetProperty(action).empty()) break; buttons.Add((CONTEXT_BUTTON)i, item->GetProperty(label).asString()); } if (item->GetProperty("pluginreplacecontextitems").asBoolean()) return; // TODO: FAVOURITES Conditions on masterlock and localisation if (!item->IsParentFolder() && !item->IsPath("add") && !item->IsPath("newplaylist://") && !URIUtils::IsProtocol(item->GetPath(), "newsmartplaylist") && !URIUtils::IsProtocol(item->GetPath(), "newtag") && !URIUtils::PathStarts(item->GetPath(), "addons://more/") && !URIUtils::IsProtocol(item->GetPath(), "musicsearch")) { if (XFILE::CFavouritesDirectory::IsFavourite(item.get(), GetID())) buttons.Add(CONTEXT_BUTTON_ADD_FAVOURITE, 14077); // Remove Favourite else buttons.Add(CONTEXT_BUTTON_ADD_FAVOURITE, 14076); // Add To Favourites; } if (item->IsFileFolder(EFILEFOLDER_MASK_ONBROWSE)) buttons.Add(CONTEXT_BUTTON_BROWSE_INTO, 37015); } bool CGUIMediaWindow::OnContextButton(int itemNumber, CONTEXT_BUTTON button) { switch (button) { case CONTEXT_BUTTON_MARK_WATCHED: case CONTEXT_BUTTON_MARK_UNWATCHED: { CFileItemPtr item = m_vecItems->Get(itemNumber); m_viewControl.SetSelectedItem(m_viewControl.GetSelectedItem() + 1); CMarkWatchedQueue::Get().AddJob(new CMarkWatchedJob(item, (button == CONTEXT_BUTTON_MARK_WATCHED))); return true; } case CONTEXT_BUTTON_ADD_FAVOURITE: { CFileItemPtr item = m_vecItems->Get(itemNumber); XFILE::CFavouritesDirectory::AddOrRemove(item.get(), GetID()); return true; } case CONTEXT_BUTTON_PLUGIN_SETTINGS: { CFileItemPtr item = m_vecItems->Get(itemNumber); // CONTEXT_BUTTON_PLUGIN_SETTINGS can be called for plugin item // or script item; or for the plugin directory current listing. bool isPluginOrScriptItem = (item && (item->IsPlugin() || item->IsScript())); CURL plugin(isPluginOrScriptItem ? item->GetPath() : m_vecItems->GetPath()); ADDON::AddonPtr addon; if (CAddonMgr::Get().GetAddon(plugin.GetHostName(), addon)) if (CGUIDialogAddonSettings::ShowAndGetInput(addon)) Refresh(); return true; } case CONTEXT_BUTTON_BROWSE_INTO: { CFileItemPtr item = m_vecItems->Get(itemNumber); if(Update(item->GetPath())) return true; return true; } case CONTEXT_BUTTON_USER1: case CONTEXT_BUTTON_USER2: case CONTEXT_BUTTON_USER3: case CONTEXT_BUTTON_USER4: case CONTEXT_BUTTON_USER5: case CONTEXT_BUTTON_USER6: case CONTEXT_BUTTON_USER7: case CONTEXT_BUTTON_USER8: case CONTEXT_BUTTON_USER9: case CONTEXT_BUTTON_USER10: { std::string action = StringUtils::Format("contextmenuaction(%i)", button - CONTEXT_BUTTON_USER1); CApplicationMessenger::Get().ExecBuiltIn(m_vecItems->Get(itemNumber)->GetProperty(action).asString()); return true; } default: break; } return false; } const CGUIViewState *CGUIMediaWindow::GetViewState() const { return m_guiState.get(); } const CFileItemList& CGUIMediaWindow::CurrentDirectory() const { return *m_vecItems; } bool CGUIMediaWindow::WaitForNetwork() const { if (g_application.getNetwork().IsAvailable()) return true; CGUIDialogProgress *progress = (CGUIDialogProgress *)g_windowManager.GetWindow(WINDOW_DIALOG_PROGRESS); if (!progress) return true; CURL url(m_vecItems->GetPath()); progress->SetHeading(1040); // Loading Directory progress->SetLine(1, url.GetWithoutUserDetails()); progress->ShowProgressBar(false); progress->StartModal(); while (!g_application.getNetwork().IsAvailable()) { progress->Progress(); if (progress->IsCanceled()) { progress->Close(); return false; } } progress->Close(); return true; } void CGUIMediaWindow::UpdateFilterPath(const std::string &strDirectory, const CFileItemList &items, bool updateFilterPath) { bool canfilter = CanContainFilter(strDirectory); std::string filter; CURL url(strDirectory); if (canfilter && url.HasOption("filter")) filter = url.GetOption("filter"); // only set the filter path if it hasn't been marked // as preset or if it's empty if (updateFilterPath || m_strFilterPath.empty()) { if (items.HasProperty(PROPERTY_PATH_DB)) m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString(); else m_strFilterPath = items.GetPath(); } // maybe the filter path can contain a filter if (!canfilter && CanContainFilter(m_strFilterPath)) canfilter = true; // check if the filter path contains a filter CURL filterPathUrl(m_strFilterPath); if (canfilter && filter.empty()) { if (filterPathUrl.HasOption("filter")) filter = filterPathUrl.GetOption("filter"); } // check if there is a filter and re-apply it if (canfilter && !filter.empty()) { if (!m_filter.LoadFromJson(filter)) { CLog::Log(LOGWARNING, "CGUIMediaWindow::UpdateFilterPath(): unable to load existing filter (%s)", filter.c_str()); m_filter.Reset(); m_strFilterPath = m_vecItems->GetPath(); } else { // add the filter to the filter path filterPathUrl.SetOption("filter", filter); m_strFilterPath = filterPathUrl.Get(); } } } void CGUIMediaWindow::OnFilterItems(const std::string &filter) { CFileItemPtr currentItem; std::string currentItemPath; int item = m_viewControl.GetSelectedItem(); if (item >= 0 && item < m_vecItems->Size()) { currentItem = m_vecItems->Get(item); currentItemPath = currentItem->GetPath(); } m_viewControl.Clear(); CFileItemList items; items.Copy(*m_vecItems, false); // use the original path - it'll likely be relied on for other things later. items.Append(*m_unfilteredItems); bool filtered = GetFilteredItems(filter, items); m_vecItems->ClearItems(); // we need to clear the sort state and re-sort the items m_vecItems->ClearSortState(); m_vecItems->Append(items); // if the filter has changed, get the new filter path if (filtered && m_canFilterAdvanced) { if (items.HasProperty(PROPERTY_PATH_DB)) m_strFilterPath = items.GetProperty(PROPERTY_PATH_DB).asString(); // only set m_strFilterPath if it hasn't been set before // otherwise we might overwrite it with a non-filter path // in case GetFilteredItems() returns true even though no // db-based filter (e.g. watched filter) has been applied else if (m_strFilterPath.empty()) m_strFilterPath = items.GetPath(); } GetGroupedItems(*m_vecItems); FormatAndSort(*m_vecItems); // get the "filter" option std::string filterOption; CURL filterUrl(m_strFilterPath); if (filterUrl.HasOption("filter")) filterOption = filterUrl.GetOption("filter"); // apply the "filter" option to any folder item so that // the filter can be passed down to the sub-directory for (int index = 0; index < m_vecItems->Size(); index++) { CFileItemPtr pItem = m_vecItems->Get(index); // if the item is a folder we need to copy the path of // the filtered item to be able to keep the applied filters if (pItem->m_bIsFolder) { CURL itemUrl(pItem->GetPath()); if (!filterOption.empty()) itemUrl.SetOption("filter", filterOption); else itemUrl.RemoveOption("filter"); pItem->SetPath(itemUrl.Get()); } } SetProperty("filter", filter); if (filtered && m_canFilterAdvanced) { // to be able to select the same item as before we need to adjust // the path of the item i.e. add or remove the "filter=" URL option // but that's only necessary for folder items if (currentItem.get() != NULL && currentItem->m_bIsFolder) { CURL curUrl(currentItemPath), newUrl(m_strFilterPath); if (newUrl.HasOption("filter")) curUrl.SetOption("filter", newUrl.GetOption("filter")); else if (curUrl.HasOption("filter")) curUrl.RemoveOption("filter"); currentItemPath = curUrl.Get(); } } // The idea here is to ensure we have something to focus if our file list // is empty. As such, this check MUST be last and ignore the hide parent // fileitems settings. if (m_vecItems->IsEmpty()) { CFileItemPtr pItem(new CFileItem("..")); pItem->SetPath(m_history.GetParentPath()); pItem->m_bIsFolder = true; pItem->m_bIsShareOrDrive = false; m_vecItems->AddFront(pItem, 0); } // and update our view control + buttons m_viewControl.SetItems(*m_vecItems); m_viewControl.SetSelectedItem(currentItemPath); UpdateButtons(); } bool CGUIMediaWindow::GetFilteredItems(const std::string &filter, CFileItemList &items) { bool result = false; if (m_canFilterAdvanced) result = GetAdvanceFilteredItems(items); std::string trimmedFilter(filter); StringUtils::TrimLeft(trimmedFilter); StringUtils::ToLower(trimmedFilter); if (trimmedFilter.empty()) return result; CFileItemList filteredItems(items.GetPath()); // use the original path - it'll likely be relied on for other things later. bool numericMatch = StringUtils::IsNaturalNumber(trimmedFilter); for (int i = 0; i < items.Size(); i++) { CFileItemPtr item = items.Get(i); if (item->IsParentFolder()) { filteredItems.Add(item); continue; } // TODO: Need to update this to get all labels, ideally out of the displayed info (ie from m_layout and m_focusedLayout) // though that isn't practical. Perhaps a better idea would be to just grab the info that we should filter on based on // where we are in the library tree. // Another idea is tying the filter string to the current level of the tree, so that going deeper disables the filter, // but it's re-enabled on the way back out. std::string match; /* if (item->GetFocusedLayout()) match = item->GetFocusedLayout()->GetAllText(); else if (item->GetLayout()) match = item->GetLayout()->GetAllText(); else*/ match = item->GetLabel(); // Filter label only for now if (numericMatch) StringUtils::WordToDigits(match); size_t pos = StringUtils::FindWords(match.c_str(), trimmedFilter.c_str()); if (pos != std::string::npos) filteredItems.Add(item); } items.ClearItems(); items.Append(filteredItems); return items.GetObjectCount() > 0; } bool CGUIMediaWindow::GetAdvanceFilteredItems(CFileItemList &items) { // don't run the advanced filter if the filter is empty // and there hasn't been a filter applied before which // would have to be removed CURL url(m_strFilterPath); if (m_filter.IsEmpty() && !url.HasOption("filter")) return false; CFileItemList resultItems; XFILE::CSmartPlaylistDirectory::GetDirectory(m_filter, resultItems, m_strFilterPath, true); // put together a lookup map for faster path comparison map lookup; for (int j = 0; j < resultItems.Size(); j++) { std::string itemPath = RemoveParameterFromPath(resultItems[j]->GetPath(), "filter"); StringUtils::ToLower(itemPath); lookup[itemPath] = resultItems[j]; } // loop through all the original items and find // those which are still part of the filter CFileItemList filteredItems; for (int i = 0; i < items.Size(); i++) { CFileItemPtr item = items.Get(i); if (item->IsParentFolder()) { filteredItems.Add(item); continue; } // check if the item is part of the resultItems list // by comparing their paths (but ignoring any special // options because they differ from filter to filter) std::string path = RemoveParameterFromPath(item->GetPath(), "filter"); StringUtils::ToLower(path); map::iterator itItem = lookup.find(path); if (itItem != lookup.end()) { // add the item to the list of filtered items filteredItems.Add(item); // remove the item from the lists resultItems.Remove(itItem->second.get()); lookup.erase(itItem); } } if (resultItems.Size() > 0) CLog::Log(LOGWARNING, "CGUIMediaWindow::GetAdvanceFilteredItems(): %d unknown items", resultItems.Size()); items.ClearItems(); items.Append(filteredItems); items.SetPath(resultItems.GetPath()); if (resultItems.HasProperty(PROPERTY_PATH_DB)) items.SetProperty(PROPERTY_PATH_DB, resultItems.GetProperty(PROPERTY_PATH_DB)); return true; } bool CGUIMediaWindow::IsFiltered() { return (!m_canFilterAdvanced && !GetProperty("filter").empty()) || (m_canFilterAdvanced && !m_filter.IsEmpty()); } bool CGUIMediaWindow::IsSameStartFolder(const std::string &dir) { const std::string startFolder = GetStartFolder(dir); return StringUtils::StartsWith(m_vecItems->GetPath(), startFolder); } bool CGUIMediaWindow::Filter(bool advanced /* = true */) { // basic filtering if (!m_canFilterAdvanced || !advanced) { const CGUIControl *btnFilter = GetControl(CONTROL_BTN_FILTER); if (btnFilter != NULL && btnFilter->GetControlType() == CGUIControl::GUICONTROL_EDIT) { // filter updated CGUIMessage selected(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_BTN_FILTER); OnMessage(selected); OnFilterItems(selected.GetLabel()); return true; } if (GetProperty("filter").empty()) { std::string filter = GetProperty("filter").asString(); CGUIKeyboardFactory::ShowAndGetFilter(filter, false); SetProperty("filter", filter); } else OnFilterItems(""); } // advanced filtering else CGUIDialogMediaFilter::ShowAndEditMediaFilter(m_strFilterPath, m_filter); return true; } std::string CGUIMediaWindow::GetStartFolder(const std::string &dir) { std::string lower(dir); StringUtils::ToLower(lower); if (lower == "$root" || lower == "root") return ""; return dir; } std::string CGUIMediaWindow::RemoveParameterFromPath(const std::string &strDirectory, const std::string &strParameter) { CURL url(strDirectory); if (url.HasOption(strParameter)) { url.RemoveOption(strParameter); return url.Get(); } return strDirectory; }