1 /*
2  *  Copyright (C) 2005-2020 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "GUIDialogContextMenu.h"
10 
11 #include "FileItem.h"
12 #include "GUIDialogFileBrowser.h"
13 #include "GUIDialogMediaSource.h"
14 #include "GUIDialogYesNo.h"
15 #include "GUIPassword.h"
16 #include "GUIUserMessages.h"
17 #include "ServiceBroker.h"
18 #include "TextureCache.h"
19 #include "URL.h"
20 #include "Util.h"
21 #include "addons/Scraper.h"
22 #include "filesystem/File.h"
23 #include "guilib/GUIButtonControl.h"
24 #include "guilib/GUIComponent.h"
25 #include "guilib/GUIControlGroupList.h"
26 #include "guilib/GUIWindowManager.h"
27 #include "guilib/LocalizeStrings.h"
28 #include "input/Key.h"
29 #include "media/MediaLockState.h"
30 #include "profiles/ProfileManager.h"
31 #include "profiles/dialogs/GUIDialogLockSettings.h"
32 #include "settings/MediaSourceSettings.h"
33 #include "settings/Settings.h"
34 #include "settings/SettingsComponent.h"
35 #include "storage/MediaManager.h"
36 #include "utils/StringUtils.h"
37 #include "utils/URIUtils.h"
38 #include "utils/Variant.h"
39 
40 #define BACKGROUND_IMAGE       999
41 #define GROUP_LIST             996
42 #define BUTTON_TEMPLATE       1000
43 #define BUTTON_START          1001
44 #define BUTTON_END            (BUTTON_START + (int)m_buttons.size() - 1)
45 
Add(unsigned int button,const std::string & label)46 void CContextButtons::Add(unsigned int button, const std::string &label)
47 {
48   for (const auto& i : *this)
49     if (i.first == button)
50       return; // already have this button
51   push_back(std::pair<unsigned int, std::string>(button, label));
52 }
53 
Add(unsigned int button,int label)54 void CContextButtons::Add(unsigned int button, int label)
55 {
56   for (const auto& i : *this)
57     if (i.first == button)
58       return; // already have added this button
59   push_back(std::pair<unsigned int, std::string>(button, g_localizeStrings.Get(label)));
60 }
61 
CGUIDialogContextMenu(void)62 CGUIDialogContextMenu::CGUIDialogContextMenu(void)
63   : CGUIDialog(WINDOW_DIALOG_CONTEXT_MENU, "DialogContextMenu.xml")
64 {
65   m_clickedButton = -1;
66   m_backgroundImageSize = 0;
67   m_loadType = KEEP_IN_MEMORY;
68   m_coordX = 0.0f;
69   m_coordY = 0.0f;
70 }
71 
72 CGUIDialogContextMenu::~CGUIDialogContextMenu(void) = default;
73 
OnMessage(CGUIMessage & message)74 bool CGUIDialogContextMenu::OnMessage(CGUIMessage &message)
75 {
76   if (message.GetMessage() == GUI_MSG_CLICKED)
77   { // someone has been clicked - deinit...
78     if (message.GetSenderId() >= BUTTON_START && message.GetSenderId() <= BUTTON_END)
79       m_clickedButton = message.GetSenderId() - BUTTON_START;
80     Close();
81     return true;
82   }
83   return CGUIDialog::OnMessage(message);
84 }
85 
OnAction(const CAction & action)86 bool CGUIDialogContextMenu::OnAction(const CAction& action)
87 {
88   if (action.GetID() == ACTION_CONTEXT_MENU ||
89       action.GetID() == ACTION_SWITCH_PLAYER)
90   {
91     Close();
92     return true;
93   }
94 
95   return CGUIDialog::OnAction(action);
96 }
97 
OnInitWindow()98 void CGUIDialogContextMenu::OnInitWindow()
99 {
100   m_clickedButton = -1;
101   // set initial control focus
102   m_lastControlID = m_initiallyFocusedButtonIdx + BUTTON_START;
103   CGUIDialog::OnInitWindow();
104 }
105 
SetupButtons()106 void CGUIDialogContextMenu::SetupButtons()
107 {
108   if (!m_buttons.size())
109     return;
110 
111   // disable the template button control
112   CGUIButtonControl *pButtonTemplate = dynamic_cast<CGUIButtonControl *>(GetFirstFocusableControl(BUTTON_TEMPLATE));
113   if (!pButtonTemplate)
114     pButtonTemplate = dynamic_cast<CGUIButtonControl *>(GetControl(BUTTON_TEMPLATE));
115   if (!pButtonTemplate)
116     return;
117   pButtonTemplate->SetVisible(false);
118 
119   CGUIControlGroupList* pGroupList = dynamic_cast<CGUIControlGroupList *>(GetControl(GROUP_LIST));
120 
121   // add our buttons
122   for (unsigned int i = 0; i < m_buttons.size(); i++)
123   {
124     CGUIButtonControl *pButton = new CGUIButtonControl(*pButtonTemplate);
125     if (pButton)
126     { // set the button's ID and position
127       int id = BUTTON_START + i;
128       pButton->SetID(id);
129       pButton->SetVisible(true);
130       pButton->SetLabel(m_buttons[i].second);
131       if (pGroupList)
132       {
133         pButton->SetPosition(pButtonTemplate->GetXPosition(), pButtonTemplate->GetYPosition());
134         // try inserting context buttons at position specified by template
135         // button, if template button is not in grouplist fallback to adding
136         // new buttons at the end of grouplist
137         if (!pGroupList->InsertControl(pButton, pButtonTemplate))
138           pGroupList->AddControl(pButton);
139       }
140     }
141   }
142 
143   // fix up background images placement and size
144   CGUIControl *pControl = GetControl(BACKGROUND_IMAGE);
145   if (pControl)
146   {
147     // first set size of background image
148     if (pGroupList)
149     {
150       if (pGroupList->GetOrientation() == VERTICAL)
151       {
152         // keep gap between bottom edges of grouplist and background image
153         pControl->SetHeight(m_backgroundImageSize - pGroupList->Size() + pGroupList->GetHeight());
154       }
155       else
156       {
157         // keep gap between right edges of grouplist and background image
158         pControl->SetWidth(m_backgroundImageSize - pGroupList->Size() + pGroupList->GetWidth());
159       }
160     }
161   }
162 
163   // update our default control
164   if (pGroupList)
165     m_defaultControl = pGroupList->GetID();
166 }
167 
SetPosition(float posX,float posY)168 void CGUIDialogContextMenu::SetPosition(float posX, float posY)
169 {
170   if (posY + GetHeight() > m_coordsRes.iHeight)
171     posY = m_coordsRes.iHeight - GetHeight();
172   if (posY < 0) posY = 0;
173   if (posX + GetWidth() > m_coordsRes.iWidth)
174     posX = m_coordsRes.iWidth - GetWidth();
175   if (posX < 0) posX = 0;
176   CGUIDialog::SetPosition(posX, posY);
177 }
178 
GetHeight() const179 float CGUIDialogContextMenu::GetHeight() const
180 {
181   if (m_backgroundImage)
182     return m_backgroundImage->GetHeight();
183   else
184     return CGUIDialog::GetHeight();
185 }
186 
GetWidth() const187 float CGUIDialogContextMenu::GetWidth() const
188 {
189   if (m_backgroundImage)
190     return m_backgroundImage->GetWidth();
191   else
192     return CGUIDialog::GetWidth();
193 }
194 
SourcesMenu(const std::string & strType,const CFileItemPtr & item,float posX,float posY)195 bool CGUIDialogContextMenu::SourcesMenu(const std::string &strType, const CFileItemPtr& item, float posX, float posY)
196 {
197   //! @todo This should be callable even if we don't have any valid items
198   if (!item)
199     return false;
200 
201   // grab our context menu
202   CContextButtons buttons;
203   GetContextButtons(strType, item, buttons);
204 
205   int button = ShowAndGetChoice(buttons);
206   if (button >= 0)
207     return OnContextButton(strType, item, (CONTEXT_BUTTON)button);
208   return false;
209 }
210 
GetContextButtons(const std::string & type,const CFileItemPtr & item,CContextButtons & buttons)211 void CGUIDialogContextMenu::GetContextButtons(const std::string &type, const CFileItemPtr& item, CContextButtons &buttons)
212 {
213   // Add buttons to the ContextMenu that should be visible for both sources and autosourced items
214   if (item && item->IsRemovable())
215   {
216     if (item->IsDVD() || item->IsCDDA())
217     {
218       buttons.Add(CONTEXT_BUTTON_EJECT_DISC, 13391); // Eject / Load
219     }
220     else // Must be HDD
221     {
222       buttons.Add(CONTEXT_BUTTON_EJECT_DRIVE, 13420); // Remove safely
223     }
224   }
225 
226   // Next, Add buttons to the ContextMenu that should ONLY be visible for sources and not autosourced items
227   CMediaSource *share = GetShare(type, item.get());
228 
229   if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() || g_passwordManager.bMasterUser)
230   {
231     if (share)
232     {
233       // Note. from now on, remove source & disable plugin should mean the same thing
234       //! @todo might be smart to also combine editing source & plugin settings into one concept/dialog
235       // Note. Temporarily disabled ability to remove plugin sources until installer is operational
236 
237       CURL url(share->strPath);
238       bool isAddon = ADDON::TranslateContent(url.GetProtocol()) != CONTENT_NONE;
239       if (!share->m_ignore && !isAddon)
240         buttons.Add(CONTEXT_BUTTON_EDIT_SOURCE, 1027); // Edit Source
241       if (type != "video")
242         buttons.Add(CONTEXT_BUTTON_SET_DEFAULT, 13335); // Set as Default
243       if (!share->m_ignore && !isAddon)
244         buttons.Add(CONTEXT_BUTTON_REMOVE_SOURCE, 522); // Remove Source
245 
246       buttons.Add(CONTEXT_BUTTON_SET_THUMB, 20019);
247     }
248     if (!GetDefaultShareNameByType(type).empty())
249       buttons.Add(CONTEXT_BUTTON_CLEAR_DEFAULT, 13403); // Clear Default
250   }
251   if (share && LOCK_MODE_EVERYONE != CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetMasterProfile().getLockMode())
252   {
253     if (share->m_iHasLock == LOCK_STATE_NO_LOCK && (CServiceBroker::GetSettingsComponent()
254                                                         ->GetProfileManager()
255                                                         ->GetCurrentProfile()
256                                                         .canWriteSources() ||
257                                                     g_passwordManager.bMasterUser))
258       buttons.Add(CONTEXT_BUTTON_ADD_LOCK, 12332);
259     else if (share->m_iHasLock == LOCK_STATE_LOCK_BUT_UNLOCKED)
260       buttons.Add(CONTEXT_BUTTON_REMOVE_LOCK, 12335);
261     else if (share->m_iHasLock == LOCK_STATE_LOCKED)
262     {
263       buttons.Add(CONTEXT_BUTTON_REMOVE_LOCK, 12335);
264 
265       bool maxRetryExceeded = false;
266       if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MASTERLOCK_MAXRETRIES) != 0)
267         maxRetryExceeded = (share->m_iBadPwdCount >= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MASTERLOCK_MAXRETRIES));
268 
269       if (maxRetryExceeded)
270         buttons.Add(CONTEXT_BUTTON_RESET_LOCK, 12334);
271       else
272         buttons.Add(CONTEXT_BUTTON_CHANGE_LOCK, 12356);
273     }
274   }
275   if (share && !g_passwordManager.bMasterUser && item->m_iHasLock == LOCK_STATE_LOCK_BUT_UNLOCKED)
276     buttons.Add(CONTEXT_BUTTON_REACTIVATE_LOCK, 12353);
277 }
278 
OnContextButton(const std::string & type,const CFileItemPtr & item,CONTEXT_BUTTON button)279 bool CGUIDialogContextMenu::OnContextButton(const std::string &type, const CFileItemPtr& item, CONTEXT_BUTTON button)
280 {
281   // buttons that are available on both sources and autosourced items
282   if (!item)
283     return false;
284 
285   switch (button)
286   {
287     case CONTEXT_BUTTON_EJECT_DRIVE:
288       return CServiceBroker::GetMediaManager().Eject(item->GetPath());
289 #ifdef HAS_DVD_DRIVE
290     case CONTEXT_BUTTON_EJECT_DISC:
291       CServiceBroker::GetMediaManager().ToggleTray(
292           CServiceBroker::GetMediaManager().TranslateDevicePath(item->GetPath())[0]);
293 #endif
294       return true;
295     default:
296       break;
297   }
298 
299   // the rest of the operations require a valid share
300   CMediaSource *share = GetShare(type, item.get());
301   if (!share)
302     return false;
303 
304   switch (button)
305   {
306   case CONTEXT_BUTTON_EDIT_SOURCE:
307     if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->IsMasterProfile())
308     {
309       if (!g_passwordManager.IsMasterLockUnlocked(true))
310         return false;
311     }
312     else if (!g_passwordManager.IsProfileLockUnlocked())
313       return false;
314 
315     return CGUIDialogMediaSource::ShowAndEditMediaSource(type, *share);
316 
317   case CONTEXT_BUTTON_REMOVE_SOURCE:
318   {
319     if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->IsMasterProfile())
320     {
321       if (!g_passwordManager.IsMasterLockUnlocked(true))
322         return false;
323     }
324     else
325     {
326       if (!CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsMasterLockUnlocked(false))
327         return false;
328       if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked())
329         return false;
330     }
331     // prompt user if they want to really delete the source
332     if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{751}, CVariant{750}))
333       return false;
334 
335     // check default before we delete, as deletion will kill the share object
336     std::string defaultSource(GetDefaultShareNameByType(type));
337     if (!defaultSource.empty())
338     {
339       if (share->strName == defaultSource)
340         ClearDefault(type);
341     }
342     CMediaSourceSettings::GetInstance().DeleteSource(type, share->strName, share->strPath);
343     return true;
344   }
345   case CONTEXT_BUTTON_SET_DEFAULT:
346     if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked())
347       return false;
348     else if (!g_passwordManager.IsMasterLockUnlocked(true))
349       return false;
350 
351     // make share default
352     SetDefault(type, share->strName);
353     return true;
354 
355   case CONTEXT_BUTTON_CLEAR_DEFAULT:
356     if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked())
357       return false;
358     else if (!g_passwordManager.IsMasterLockUnlocked(true))
359       return false;
360     // remove share default
361     ClearDefault(type);
362     return true;
363 
364   case CONTEXT_BUTTON_SET_THUMB:
365     {
366       if (CServiceBroker::GetSettingsComponent()->GetProfileManager()->GetCurrentProfile().canWriteSources() && !g_passwordManager.IsProfileLockUnlocked())
367         return false;
368       else if (!g_passwordManager.IsMasterLockUnlocked(true))
369         return false;
370 
371       // setup our thumb list
372       CFileItemList items;
373 
374       // add the current thumb, if available
375       if (!share->m_strThumbnailImage.empty())
376       {
377         CFileItemPtr current(new CFileItem("thumb://Current", false));
378         current->SetArt("thumb", share->m_strThumbnailImage);
379         current->SetLabel(g_localizeStrings.Get(20016));
380         items.Add(current);
381       }
382       else if (item->HasArt("thumb"))
383       { // already have a thumb that the share doesn't know about - must be a local one, so we mayaswell reuse it.
384         CFileItemPtr current(new CFileItem("thumb://Current", false));
385         current->SetArt("thumb", item->GetArt("thumb"));
386         current->SetLabel(g_localizeStrings.Get(20016));
387         items.Add(current);
388       }
389       // see if there's a local thumb for this item
390       std::string folderThumb = item->GetFolderThumb();
391       if (XFILE::CFile::Exists(folderThumb))
392       {
393         CFileItemPtr local(new CFileItem("thumb://Local", false));
394         local->SetArt("thumb", folderThumb);
395         local->SetLabel(g_localizeStrings.Get(20017));
396         items.Add(local);
397       }
398       // and add a "no thumb" entry as well
399       CFileItemPtr nothumb(new CFileItem("thumb://None", false));
400       nothumb->SetArt("icon", item->GetArt("icon"));
401       nothumb->SetLabel(g_localizeStrings.Get(20018));
402       items.Add(nothumb);
403 
404       std::string strThumb;
405       VECSOURCES shares;
406       CServiceBroker::GetMediaManager().GetLocalDrives(shares);
407       if (!CGUIDialogFileBrowser::ShowAndGetImage(items, shares, g_localizeStrings.Get(1030), strThumb))
408         return false;
409 
410       if (strThumb == "thumb://Current")
411         return true;
412 
413       if (strThumb == "thumb://Local")
414         strThumb = folderThumb;
415 
416       if (strThumb == "thumb://None")
417         strThumb = "";
418 
419       if (!share->m_ignore)
420       {
421         CMediaSourceSettings::GetInstance().UpdateSource(type,share->strName,"thumbnail",strThumb);
422         CMediaSourceSettings::GetInstance().Save();
423       }
424       else if (!strThumb.empty())
425       { // this is some sort of an auto-share, so store in the texture database
426         CTextureDatabase db;
427         if (db.Open())
428           db.SetTextureForPath(item->GetPath(), "thumb", strThumb);
429       }
430 
431       CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES);
432       CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
433       return true;
434     }
435 
436   case CONTEXT_BUTTON_ADD_LOCK:
437     {
438       // prompt user for mastercode when changing lock settings) only for default user
439       if (!g_passwordManager.IsMasterLockUnlocked(true))
440         return false;
441 
442       std::string strNewPassword = "";
443       if (!CGUIDialogLockSettings::ShowAndGetLock(share->m_iLockMode,strNewPassword))
444         return false;
445       // password entry and re-entry succeeded, write out the lock data
446       share->m_iHasLock = LOCK_STATE_LOCKED;
447       CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockcode", strNewPassword);
448       strNewPassword = StringUtils::Format("%i", share->m_iLockMode);
449       CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockmode", strNewPassword);
450       CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "badpwdcount", "0");
451       CMediaSourceSettings::GetInstance().Save();
452 
453       CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES);
454       CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
455       return true;
456     }
457   case CONTEXT_BUTTON_RESET_LOCK:
458     {
459       // prompt user for profile lock when changing lock settings
460       if (!g_passwordManager.IsMasterLockUnlocked(true))
461         return false;
462 
463       CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "badpwdcount", "0");
464       CMediaSourceSettings::GetInstance().Save();
465       CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES);
466       CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
467       return true;
468     }
469   case CONTEXT_BUTTON_REMOVE_LOCK:
470     {
471       if (!g_passwordManager.IsMasterLockUnlocked(true))
472         return false;
473 
474       // prompt user if they want to really remove the lock
475       if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{12335}, CVariant{750}))
476         return false;
477 
478       share->m_iHasLock = LOCK_STATE_NO_LOCK;
479       CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockmode", "0");
480       CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockcode", "0");
481       CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "badpwdcount", "0");
482       CMediaSourceSettings::GetInstance().Save();
483       CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES);
484       CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
485       return true;
486     }
487   case CONTEXT_BUTTON_REACTIVATE_LOCK:
488     {
489       bool maxRetryExceeded = false;
490       if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MASTERLOCK_MAXRETRIES) != 0)
491         maxRetryExceeded = (share->m_iBadPwdCount >= CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MASTERLOCK_MAXRETRIES));
492       if (!maxRetryExceeded)
493       {
494         // don't prompt user for mastercode when reactivating a lock
495         g_passwordManager.LockSource(type, share->strName, true);
496         return true;
497       }
498       return false;
499     }
500   case CONTEXT_BUTTON_CHANGE_LOCK:
501     {
502       if (!g_passwordManager.IsMasterLockUnlocked(true))
503         return false;
504 
505       std::string strNewPW;
506       std::string strNewLockMode;
507       if (CGUIDialogLockSettings::ShowAndGetLock(share->m_iLockMode,strNewPW))
508         strNewLockMode = StringUtils::Format("%i",share->m_iLockMode);
509       else
510         return false;
511       // password ReSet and re-entry succeeded, write out the lock data
512       CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockcode", strNewPW);
513       CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "lockmode", strNewLockMode);
514       CMediaSourceSettings::GetInstance().UpdateSource(type, share->strName, "badpwdcount", "0");
515       CMediaSourceSettings::GetInstance().Save();
516       CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_UPDATE_SOURCES);
517       CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
518       return true;
519     }
520   default:
521     break;
522   }
523   return false;
524 }
525 
GetShare(const std::string & type,const CFileItem * item)526 CMediaSource *CGUIDialogContextMenu::GetShare(const std::string &type, const CFileItem *item)
527 {
528   VECSOURCES *shares = CMediaSourceSettings::GetInstance().GetSources(type);
529   if (!shares || !item)
530     return nullptr;
531   for (unsigned int i = 0; i < shares->size(); i++)
532   {
533     CMediaSource &testShare = shares->at(i);
534     if (URIUtils::IsDVD(testShare.strPath))
535     {
536       if (!item->IsDVD())
537         continue;
538     }
539     else
540     {
541       if (!URIUtils::CompareWithoutSlashAtEnd(testShare.strPath, item->GetPath()))
542         continue;
543     }
544     // paths match, what about share name - only match the leftmost
545     // characters as the label may contain other info (status for instance)
546     if (StringUtils::StartsWithNoCase(item->GetLabel(), testShare.strName))
547     {
548       return &testShare;
549     }
550   }
551   return nullptr;
552 }
553 
OnWindowLoaded()554 void CGUIDialogContextMenu::OnWindowLoaded()
555 {
556   m_coordX = m_posX;
557   m_coordY = m_posY;
558 
559   const CGUIControlGroupList* pGroupList = dynamic_cast<const CGUIControlGroupList *>(GetControl(GROUP_LIST));
560   m_backgroundImage = GetControl(BACKGROUND_IMAGE);
561   if (m_backgroundImage && pGroupList)
562   {
563     if (pGroupList->GetOrientation() == VERTICAL)
564       m_backgroundImageSize = m_backgroundImage->GetHeight();
565     else
566       m_backgroundImageSize = m_backgroundImage->GetWidth();
567   }
568 
569   CGUIDialog::OnWindowLoaded();
570 }
571 
OnDeinitWindow(int nextWindowID)572 void CGUIDialogContextMenu::OnDeinitWindow(int nextWindowID)
573 {
574   //we can't be sure that controls are removed on window unload
575   //we have to remove them to be sure that they won't stay for next use of context menu
576   for (unsigned int i = 0; i < m_buttons.size(); i++)
577   {
578     const CGUIControl *control = GetControl(BUTTON_START + i);
579     if (control)
580       RemoveControl(control);
581   }
582 
583   m_buttons.clear();
584   m_initiallyFocusedButtonIdx = 0;
585   CGUIDialog::OnDeinitWindow(nextWindowID);
586 }
587 
GetDefaultShareNameByType(const std::string & strType)588 std::string CGUIDialogContextMenu::GetDefaultShareNameByType(const std::string &strType)
589 {
590   VECSOURCES *pShares = CMediaSourceSettings::GetInstance().GetSources(strType);
591   std::string strDefault = CMediaSourceSettings::GetInstance().GetDefaultSource(strType);
592 
593   if (!pShares) return "";
594 
595   bool bIsSourceName(false);
596   int iIndex = CUtil::GetMatchingSource(strDefault, *pShares, bIsSourceName);
597   if (iIndex < 0 || iIndex >= (int)pShares->size())
598     return "";
599 
600   return pShares->at(iIndex).strName;
601 }
602 
SetDefault(const std::string & strType,const std::string & strDefault)603 void CGUIDialogContextMenu::SetDefault(const std::string &strType, const std::string &strDefault)
604 {
605   CMediaSourceSettings::GetInstance().SetDefaultSource(strType, strDefault);
606   CMediaSourceSettings::GetInstance().Save();
607 }
608 
ClearDefault(const std::string & strType)609 void CGUIDialogContextMenu::ClearDefault(const std::string &strType)
610 {
611   SetDefault(strType, "");
612 }
613 
SwitchMedia(const std::string & strType,const std::string & strPath)614 void CGUIDialogContextMenu::SwitchMedia(const std::string& strType, const std::string& strPath)
615 {
616   // create menu
617   CContextButtons choices;
618   if (strType != "music")
619     choices.Add(WINDOW_MUSIC_NAV, 2);
620   if (strType != "video")
621     choices.Add(WINDOW_VIDEO_NAV, 3);
622   if (strType != "pictures")
623     choices.Add(WINDOW_PICTURES, 1);
624   if (strType != "files")
625     choices.Add(WINDOW_FILES, 7);
626 
627   int window = ShowAndGetChoice(choices);
628   if (window >= 0)
629   {
630     CUtil::DeleteDirectoryCache();
631     CServiceBroker::GetGUI()->GetWindowManager().ChangeActiveWindow(window, strPath);
632   }
633 }
634 
Show(const CContextButtons & choices,int focusedButtonIdx)635 int CGUIDialogContextMenu::Show(const CContextButtons& choices, int focusedButtonIdx /* = 0 */)
636 {
637   auto dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogContextMenu>(WINDOW_DIALOG_CONTEXT_MENU);
638   if (!dialog)
639     return -1;
640 
641   dialog->m_buttons = choices;
642   dialog->Initialize();
643   dialog->SetInitialVisibility();
644   dialog->SetupButtons();
645   dialog->PositionAtCurrentFocus();
646   dialog->m_initiallyFocusedButtonIdx = focusedButtonIdx;
647   dialog->Open();
648   return dialog->m_clickedButton;
649 }
650 
ShowAndGetChoice(const CContextButtons & choices)651 int CGUIDialogContextMenu::ShowAndGetChoice(const CContextButtons &choices)
652 {
653   if (choices.empty())
654     return -1;
655 
656   CGUIDialogContextMenu *pMenu = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogContextMenu>(WINDOW_DIALOG_CONTEXT_MENU);
657   if (pMenu)
658   {
659     pMenu->m_buttons = choices;
660     pMenu->Initialize();
661     pMenu->SetInitialVisibility();
662     pMenu->SetupButtons();
663     pMenu->PositionAtCurrentFocus();
664     pMenu->Open();
665 
666     int idx = pMenu->m_clickedButton;
667     if (idx != -1)
668       return choices[idx].first;
669   }
670   return -1;
671 }
672 
PositionAtCurrentFocus()673 void CGUIDialogContextMenu::PositionAtCurrentFocus()
674 {
675   CGUIWindow *window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog());
676   if (window)
677   {
678     const CGUIControl *focusedControl = window->GetFocusedControl();
679     if (focusedControl)
680     {
681       CPoint pos = focusedControl->GetRenderPosition() + CPoint(focusedControl->GetWidth() * 0.5f, focusedControl->GetHeight() * 0.5f);
682       SetPosition(m_coordX + pos.x - GetWidth() * 0.5f, m_coordY + pos.y - GetHeight() * 0.5f);
683       return;
684     }
685   }
686   // no control to center at, so just center the window
687   CenterWindow();
688 }
689