/* * 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 "system.h" #include "GUIDialogContextMenu.h" #include "guilib/GUIButtonControl.h" #include "guilib/GUIControlGroupList.h" #include "GUIDialogFileBrowser.h" #include "GUIUserMessages.h" #include "Autorun.h" #include "GUIPassword.h" #include "Util.h" #include "utils/URIUtils.h" #include "settings/MediaSourceSettings.h" #include "settings/Settings.h" #include "GUIDialogMediaSource.h" #include "profiles/ProfilesManager.h" #include "profiles/dialogs/GUIDialogLockSettings.h" #include "storage/MediaManager.h" #include "guilib/GUIWindowManager.h" #include "guilib/Key.h" #include "GUIDialogYesNo.h" #include "addons/AddonManager.h" #include "FileItem.h" #include "filesystem/File.h" #include "guilib/LocalizeStrings.h" #include "TextureCache.h" #include "video/windows/GUIWindowVideoBase.h" #include "URL.h" #include "utils/StringUtils.h" #ifdef TARGET_WINDOWS #include "WIN32Util.h" #endif using namespace std; #define BACKGROUND_IMAGE 999 #define GROUP_LIST 996 #define BUTTON_TEMPLATE 1000 #define BUTTON_START 1001 #define BUTTON_END (BUTTON_START + (int)m_buttons.size() - 1) void CContextButtons::Add(unsigned int button, const std::string &label) { for (const_iterator i = begin(); i != end(); ++i) if (i->first == button) return; // already have this button push_back(pair(button, label)); } void CContextButtons::Add(unsigned int button, int label) { for (const_iterator i = begin(); i != end(); ++i) if (i->first == button) return; // already have added this button push_back(pair(button, g_localizeStrings.Get(label))); } CGUIDialogContextMenu::CGUIDialogContextMenu(void) : CGUIDialog(WINDOW_DIALOG_CONTEXT_MENU, "DialogContextMenu.xml") { m_clickedButton = -1; m_backgroundImageSize = 0; m_loadType = KEEP_IN_MEMORY; } CGUIDialogContextMenu::~CGUIDialogContextMenu(void) { } bool CGUIDialogContextMenu::OnMessage(CGUIMessage &message) { if (message.GetMessage() == GUI_MSG_CLICKED) { // someone has been clicked - deinit... if (message.GetSenderId() >= BUTTON_START && message.GetSenderId() <= BUTTON_END) m_clickedButton = (int)m_buttons[message.GetSenderId() - BUTTON_START].first; Close(); return true; } return CGUIDialog::OnMessage(message); } bool CGUIDialogContextMenu::OnAction(const CAction& action) { if (action.GetID() == ACTION_CONTEXT_MENU) { Close(); return true; } return CGUIDialog::OnAction(action); } void CGUIDialogContextMenu::OnInitWindow() { m_clickedButton = -1; // set initial control focus m_lastControlID = BUTTON_START; CGUIDialog::OnInitWindow(); } void CGUIDialogContextMenu::SetupButtons() { if (!m_buttons.size()) return; // disable the template button control CGUIButtonControl *pButtonTemplate = dynamic_cast(GetFirstFocusableControl(BUTTON_TEMPLATE)); if (!pButtonTemplate) pButtonTemplate = dynamic_cast(GetControl(BUTTON_TEMPLATE)); if (!pButtonTemplate) return; pButtonTemplate->SetVisible(false); CGUIControlGroupList* pGroupList = dynamic_cast(GetControl(GROUP_LIST)); // add our buttons for (unsigned int i = 0; i < m_buttons.size(); i++) { CGUIButtonControl *pButton = new CGUIButtonControl(*pButtonTemplate); if (pButton) { // set the button's ID and position int id = BUTTON_START + i; pButton->SetID(id); pButton->SetVisible(true); pButton->SetLabel(m_buttons[i].second); if (pGroupList) { pButton->SetPosition(pButtonTemplate->GetXPosition(), pButtonTemplate->GetYPosition()); // try inserting context buttons at position specified by template // button, if template button is not in grouplist fallback to adding // new buttons at the end of grouplist if (!pGroupList->InsertControl(pButton, pButtonTemplate)) pGroupList->AddControl(pButton); } } } // fix up background images placement and size CGUIControl *pControl = (CGUIControl *)GetControl(BACKGROUND_IMAGE); if (pControl) { // first set size of background image if (pGroupList) { if (pGroupList->GetOrientation() == VERTICAL) { // keep gap between bottom edges of grouplist and background image pControl->SetHeight(m_backgroundImageSize - pGroupList->Size() + pGroupList->GetHeight()); } else { // keep gap between right edges of grouplist and background image pControl->SetWidth(m_backgroundImageSize - pGroupList->Size() + pGroupList->GetWidth()); } } } // update our default control if (pGroupList) m_defaultControl = pGroupList->GetID(); } void CGUIDialogContextMenu::SetPosition(float posX, float posY) { if (posY + GetHeight() > m_coordsRes.iHeight) posY = m_coordsRes.iHeight - GetHeight(); if (posY < 0) posY = 0; if (posX + GetWidth() > m_coordsRes.iWidth) posX = m_coordsRes.iWidth - GetWidth(); if (posX < 0) posX = 0; CGUIDialog::SetPosition(posX, posY); } float CGUIDialogContextMenu::GetHeight() const { const CGUIControl *backMain = GetControl(BACKGROUND_IMAGE); if (backMain) return backMain->GetHeight(); else return CGUIDialog::GetHeight(); } float CGUIDialogContextMenu::GetWidth() const { const CGUIControl *pControl = GetControl(BACKGROUND_IMAGE); if (pControl) return pControl->GetWidth(); else return CGUIDialog::GetWidth(); } bool CGUIDialogContextMenu::SourcesMenu(const std::string &strType, const CFileItemPtr item, float posX, float posY) { // TODO: This should be callable even if we don't have any valid items if (!item) return false; // grab our context menu CContextButtons buttons; GetContextButtons(strType, item, buttons); int button = ShowAndGetChoice(buttons); if (button >= 0) return OnContextButton(strType, item, (CONTEXT_BUTTON)button); return false; } void CGUIDialogContextMenu::GetContextButtons(const std::string &type, const CFileItemPtr item, CContextButtons &buttons) { // Add buttons to the ContextMenu that should be visible for both sources and autosourced items if (item && item->IsRemovable()) { if (item->IsDVD() || item->IsCDDA()) { // We need to check if there is a detected is inserted! buttons.Add(CONTEXT_BUTTON_PLAY_DISC, 341); // Play CD/DVD! if (CGUIWindowVideoBase::HasResumeItemOffset(item.get())) buttons.Add(CONTEXT_BUTTON_RESUME_DISC, CGUIWindowVideoBase::GetResumeString(*(item.get()))); // Resume Disc buttons.Add(CONTEXT_BUTTON_EJECT_DISC, 13391); // Eject/Load CD/DVD! } else // Must be HDD { buttons.Add(CONTEXT_BUTTON_EJECT_DRIVE, 13420); // Eject Removable HDD! } } // Next, Add buttons to the ContextMenu that should ONLY be visible for sources and not autosourced items CMediaSource *share = GetShare(type, item.get()); if (CProfilesManager::Get().GetCurrentProfile().canWriteSources() || g_passwordManager.bMasterUser) { if (share) { // Note. from now on, remove source & disable plugin should mean the same thing //TODO might be smart to also combine editing source & plugin settings into one concept/dialog // Note. Temporarily disabled ability to remove plugin sources until installer is operational CURL url(share->strPath); bool isAddon = ADDON::TranslateContent(url.GetProtocol()) != CONTENT_NONE; if (!share->m_ignore && !isAddon) buttons.Add(CONTEXT_BUTTON_EDIT_SOURCE, 1027); // Edit Source else { ADDON::AddonPtr plugin; if (ADDON::CAddonMgr::Get().GetAddon(url.GetHostName(), plugin)) if (plugin->HasSettings()) buttons.Add(CONTEXT_BUTTON_PLUGIN_SETTINGS, 1045); // Plugin Settings } if (type != "video") buttons.Add(CONTEXT_BUTTON_SET_DEFAULT, 13335); // Set as Default if (!share->m_ignore && !isAddon) buttons.Add(CONTEXT_BUTTON_REMOVE_SOURCE, 522); // Remove Source buttons.Add(CONTEXT_BUTTON_SET_THUMB, 20019); } if (!GetDefaultShareNameByType(type).empty()) buttons.Add(CONTEXT_BUTTON_CLEAR_DEFAULT, 13403); // Clear Default } if (share && LOCK_MODE_EVERYONE != CProfilesManager::Get().GetMasterProfile().getLockMode()) { if (share->m_iHasLock == 0 && (CProfilesManager::Get().GetCurrentProfile().canWriteSources() || g_passwordManager.bMasterUser)) buttons.Add(CONTEXT_BUTTON_ADD_LOCK, 12332); else if (share->m_iHasLock == 1) buttons.Add(CONTEXT_BUTTON_REMOVE_LOCK, 12335); else if (share->m_iHasLock == 2) { buttons.Add(CONTEXT_BUTTON_REMOVE_LOCK, 12335); bool maxRetryExceeded = false; if (CSettings::Get().GetInt("masterlock.maxretries") != 0) maxRetryExceeded = (share->m_iBadPwdCount >= CSettings::Get().GetInt("masterlock.maxretries")); if (maxRetryExceeded) buttons.Add(CONTEXT_BUTTON_RESET_LOCK, 12334); else buttons.Add(CONTEXT_BUTTON_CHANGE_LOCK, 12356); } } if (share && !g_passwordManager.bMasterUser && item->m_iHasLock == 1) buttons.Add(CONTEXT_BUTTON_REACTIVATE_LOCK, 12353); } bool CGUIDialogContextMenu::OnContextButton(const std::string &type, const CFileItemPtr item, CONTEXT_BUTTON button) { // Add Source doesn't require a valid share if (button == CONTEXT_BUTTON_ADD_SOURCE) { if (CProfilesManager::Get().IsMasterProfile()) { if (!g_passwordManager.IsMasterLockUnlocked(true)) return false; } else if (!CProfilesManager::Get().GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) return false; return CGUIDialogMediaSource::ShowAndAddMediaSource(type); } // buttons that are available on both sources and autosourced items if (!item) return false; switch (button) { case CONTEXT_BUTTON_EJECT_DRIVE: return g_mediaManager.Eject(item->GetPath()); #ifdef HAS_DVD_DRIVE case CONTEXT_BUTTON_PLAY_DISC: return MEDIA_DETECT::CAutorun::PlayDisc(item->GetPath(), true, true); // restart case CONTEXT_BUTTON_RESUME_DISC: return MEDIA_DETECT::CAutorun::PlayDisc(item->GetPath(), true, false); // resume case CONTEXT_BUTTON_EJECT_DISC: g_mediaManager.ToggleTray(g_mediaManager.TranslateDevicePath(item->GetPath())[0]); #endif return true; default: break; } // the rest of the operations require a valid share CMediaSource *share = GetShare(type, item.get()); if (!share) return false; switch (button) { case CONTEXT_BUTTON_EDIT_SOURCE: if (CProfilesManager::Get().IsMasterProfile()) { if (!g_passwordManager.IsMasterLockUnlocked(true)) return false; } else if (!g_passwordManager.IsProfileLockUnlocked()) return false; return CGUIDialogMediaSource::ShowAndEditMediaSource(type, *share); case CONTEXT_BUTTON_REMOVE_SOURCE: { if (CProfilesManager::Get().IsMasterProfile()) { if (!g_passwordManager.IsMasterLockUnlocked(true)) return false; } else { if (!CProfilesManager::Get().GetCurrentProfile().canWriteSources() && !g_passwordManager.IsMasterLockUnlocked(false)) return false; if (CProfilesManager::Get().GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) return false; } // prompt user if they want to really delete the source if (!CGUIDialogYesNo::ShowAndGetInput(751, 0, 750, 0)) return false; // check default before we delete, as deletion will kill the share object std::string defaultSource(GetDefaultShareNameByType(type)); if (!defaultSource.empty()) { if (share->strName == defaultSource) ClearDefault(type); } CMediaSourceSettings::Get().DeleteSource(type, share->strName, share->strPath); return true; } case CONTEXT_BUTTON_SET_DEFAULT: if (CProfilesManager::Get().GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) return false; else if (!g_passwordManager.IsMasterLockUnlocked(true)) return false; // make share default SetDefault(type, share->strName); return true; case CONTEXT_BUTTON_CLEAR_DEFAULT: if (CProfilesManager::Get().GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) return false; else if (!g_passwordManager.IsMasterLockUnlocked(true)) return false; // remove share default ClearDefault(type); return true; case CONTEXT_BUTTON_SET_THUMB: { if (CProfilesManager::Get().GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked()) return false; else if (!g_passwordManager.IsMasterLockUnlocked(true)) return false; // setup our thumb list CFileItemList items; // add the current thumb, if available if (!share->m_strThumbnailImage.empty()) { CFileItemPtr current(new CFileItem("thumb://Current", false)); current->SetArt("thumb", share->m_strThumbnailImage); current->SetLabel(g_localizeStrings.Get(20016)); items.Add(current); } else if (item->HasArt("thumb")) { // already have a thumb that the share doesn't know about - must be a local one, so we mayaswell reuse it. CFileItemPtr current(new CFileItem("thumb://Current", false)); current->SetArt("thumb", item->GetArt("thumb")); current->SetLabel(g_localizeStrings.Get(20016)); items.Add(current); } // see if there's a local thumb for this item std::string folderThumb = item->GetFolderThumb(); if (XFILE::CFile::Exists(folderThumb)) { CFileItemPtr local(new CFileItem("thumb://Local", false)); local->SetArt("thumb", folderThumb); local->SetLabel(g_localizeStrings.Get(20017)); items.Add(local); } // and add a "no thumb" entry as well CFileItemPtr nothumb(new CFileItem("thumb://None", false)); nothumb->SetIconImage(item->GetIconImage()); nothumb->SetLabel(g_localizeStrings.Get(20018)); items.Add(nothumb); std::string strThumb; VECSOURCES shares; g_mediaManager.GetLocalDrives(shares); if (!CGUIDialogFileBrowser::ShowAndGetImage(items, shares, g_localizeStrings.Get(1030), strThumb)) return false; if (strThumb == "thumb://Current") return true; if (strThumb == "thumb://Local") strThumb = folderThumb; if (strThumb == "thumb://None") strThumb = ""; if (!share->m_ignore) { CMediaSourceSettings::Get().UpdateSource(type,share->strName,"thumbnail",strThumb); CMediaSourceSettings::Get().Save(); } else if (!strThumb.empty()) { // this is some sort of an auto-share, so store in the texture database CTextureDatabase db; if (db.Open()) db.SetTextureForPath(item->GetPath(), "thumb", strThumb); } CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES); g_windowManager.SendThreadMessage(msg); return true; } case CONTEXT_BUTTON_ADD_LOCK: { // prompt user for mastercode when changing lock settings) only for default user if (!g_passwordManager.IsMasterLockUnlocked(true)) return false; std::string strNewPassword = ""; if (!CGUIDialogLockSettings::ShowAndGetLock(share->m_iLockMode,strNewPassword)) return false; // password entry and re-entry succeeded, write out the lock data share->m_iHasLock = 2; CMediaSourceSettings::Get().UpdateSource(type, share->strName, "lockcode", strNewPassword); strNewPassword = StringUtils::Format("%i", share->m_iLockMode); CMediaSourceSettings::Get().UpdateSource(type, share->strName, "lockmode", strNewPassword); CMediaSourceSettings::Get().UpdateSource(type, share->strName, "badpwdcount", "0"); CMediaSourceSettings::Get().Save(); CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES); g_windowManager.SendThreadMessage(msg); return true; } case CONTEXT_BUTTON_RESET_LOCK: { // prompt user for profile lock when changing lock settings if (!g_passwordManager.IsMasterLockUnlocked(true)) return false; CMediaSourceSettings::Get().UpdateSource(type, share->strName, "badpwdcount", "0"); CMediaSourceSettings::Get().Save(); CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES); g_windowManager.SendThreadMessage(msg); return true; } case CONTEXT_BUTTON_REMOVE_LOCK: { if (!g_passwordManager.IsMasterLockUnlocked(true)) return false; if (!CGUIDialogYesNo::ShowAndGetInput(12335, 0, 750, 0)) return false; share->m_iHasLock = 0; CMediaSourceSettings::Get().UpdateSource(type, share->strName, "lockmode", "0"); CMediaSourceSettings::Get().UpdateSource(type, share->strName, "lockcode", "0"); CMediaSourceSettings::Get().UpdateSource(type, share->strName, "badpwdcount", "0"); CMediaSourceSettings::Get().Save(); CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES); g_windowManager.SendThreadMessage(msg); return true; } case CONTEXT_BUTTON_REACTIVATE_LOCK: { bool maxRetryExceeded = false; if (CSettings::Get().GetInt("masterlock.maxretries") != 0) maxRetryExceeded = (share->m_iBadPwdCount >= CSettings::Get().GetInt("masterlock.maxretries")); if (!maxRetryExceeded) { // don't prompt user for mastercode when reactivating a lock g_passwordManager.LockSource(type, share->strName, true); return true; } return false; } case CONTEXT_BUTTON_CHANGE_LOCK: { if (!g_passwordManager.IsMasterLockUnlocked(true)) return false; std::string strNewPW; std::string strNewLockMode; if (CGUIDialogLockSettings::ShowAndGetLock(share->m_iLockMode,strNewPW)) strNewLockMode = StringUtils::Format("%i",share->m_iLockMode); else return false; // password ReSet and re-entry succeeded, write out the lock data CMediaSourceSettings::Get().UpdateSource(type, share->strName, "lockcode", strNewPW); CMediaSourceSettings::Get().UpdateSource(type, share->strName, "lockmode", strNewLockMode); CMediaSourceSettings::Get().UpdateSource(type, share->strName, "badpwdcount", "0"); CMediaSourceSettings::Get().Save(); CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES); g_windowManager.SendThreadMessage(msg); return true; } default: break; } return false; } CMediaSource *CGUIDialogContextMenu::GetShare(const std::string &type, const CFileItem *item) { VECSOURCES *shares = CMediaSourceSettings::Get().GetSources(type); if (!shares || !item) return NULL; for (unsigned int i = 0; i < shares->size(); i++) { CMediaSource &testShare = shares->at(i); if (URIUtils::IsDVD(testShare.strPath)) { if (!item->IsDVD()) continue; } else { if (!URIUtils::CompareWithoutSlashAtEnd(testShare.strPath, item->GetPath())) continue; } // paths match, what about share name - only match the leftmost // characters as the label may contain other info (status for instance) if (StringUtils::StartsWithNoCase(item->GetLabel(), testShare.strName)) { return &testShare; } } return NULL; } void CGUIDialogContextMenu::OnWindowLoaded() { m_coordX = m_posX; m_coordY = m_posY; const CGUIControlGroupList* pGroupList = dynamic_cast(GetControl(GROUP_LIST)); const CGUIControl *pControl = GetControl(BACKGROUND_IMAGE); if (pControl && pGroupList) { if (pGroupList->GetOrientation() == VERTICAL) m_backgroundImageSize = pControl->GetHeight(); else m_backgroundImageSize = pControl->GetWidth(); } CGUIDialog::OnWindowLoaded(); } void CGUIDialogContextMenu::OnDeinitWindow(int nextWindowID) { //we can't be sure that controls are removed on window unload //we have to remove them to be sure that they won't stay for next use of context menu for (unsigned int i = 0; i < m_buttons.size(); i++) { const CGUIControl *control = GetControl(BUTTON_START + i); if (control) RemoveControl(control); } m_buttons.clear(); CGUIDialog::OnDeinitWindow(nextWindowID); } std::string CGUIDialogContextMenu::GetDefaultShareNameByType(const std::string &strType) { VECSOURCES *pShares = CMediaSourceSettings::Get().GetSources(strType); std::string strDefault = CMediaSourceSettings::Get().GetDefaultSource(strType); if (!pShares) return ""; bool bIsSourceName(false); int iIndex = CUtil::GetMatchingSource(strDefault, *pShares, bIsSourceName); if (iIndex < 0 || iIndex >= (int)pShares->size()) return ""; return pShares->at(iIndex).strName; } void CGUIDialogContextMenu::SetDefault(const std::string &strType, const std::string &strDefault) { CMediaSourceSettings::Get().SetDefaultSource(strType, strDefault); CMediaSourceSettings::Get().Save(); } void CGUIDialogContextMenu::ClearDefault(const std::string &strType) { SetDefault(strType, ""); } void CGUIDialogContextMenu::SwitchMedia(const std::string& strType, const std::string& strPath) { // create menu CContextButtons choices; if (strType != "music") choices.Add(WINDOW_MUSIC_FILES, 2); if (strType != "video") choices.Add(WINDOW_VIDEO_FILES, 3); if (strType != "pictures") choices.Add(WINDOW_PICTURES, 1); if (strType != "files") choices.Add(WINDOW_FILES, 7); int window = ShowAndGetChoice(choices); if (window >= 0) { CUtil::DeleteDirectoryCache(); g_windowManager.ChangeActiveWindow(window, strPath); } } int CGUIDialogContextMenu::ShowAndGetChoice(const CContextButtons &choices) { if (choices.size() == 0) return -1; CGUIDialogContextMenu *pMenu = (CGUIDialogContextMenu *)g_windowManager.GetWindow(WINDOW_DIALOG_CONTEXT_MENU); if (pMenu) { if (pMenu->IsDialogRunning()) return -1; pMenu->m_buttons = choices; pMenu->Initialize(); pMenu->SetInitialVisibility(); pMenu->SetupButtons(); pMenu->PositionAtCurrentFocus(); pMenu->DoModal(); return pMenu->m_clickedButton; } return -1; } void CGUIDialogContextMenu::PositionAtCurrentFocus() { CGUIWindow *window = g_windowManager.GetWindow(g_windowManager.GetFocusedWindow()); if (window) { const CGUIControl *focusedControl = window->GetFocusedControl(); if (focusedControl) { CPoint pos = focusedControl->GetRenderPosition() + CPoint(focusedControl->GetWidth() * 0.5f, focusedControl->GetHeight() * 0.5f) + window->GetRenderPosition(); SetPosition(m_coordX + pos.x - GetWidth() * 0.5f, m_coordY + pos.y - GetHeight() * 0.5f); return; } } // no control to center at, so just center the window CenterWindow(); }