Merge pull request #4896 from sportica/mac_fix_space
[vuplus_xbmc] / xbmc / pvr / windows / GUIWindowPVRBase.cpp
1 /*
2  *      Copyright (C) 2012-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "GUIWindowPVRBase.h"
22
23 #include "Application.h"
24 #include "ApplicationMessenger.h"
25 #include "dialogs/GUIDialogNumeric.h"
26 #include "dialogs/GUIDialogKaiToast.h"
27 #include "dialogs/GUIDialogOK.h"
28 #include "dialogs/GUIDialogYesNo.h"
29 #include "filesystem/StackDirectory.h"
30 #include "guilib/Key.h"
31 #include "guilib/GUIMessage.h"
32 #include "guilib/GUIWindowManager.h"
33 #include "dialogs/GUIDialogKaiToast.h"
34 #include "dialogs/GUIDialogSelect.h"
35 #include "pvr/PVRManager.h"
36 #include "pvr/addons/PVRClients.h"
37 #include "pvr/dialogs/GUIDialogPVRGuideInfo.h"
38 #include "pvr/dialogs/GUIDialogPVRRecordingInfo.h"
39 #include "pvr/timers/PVRTimers.h"
40 #include "epg/Epg.h"
41 #include "epg/GUIEPGGridContainer.h"
42 #include "settings/MediaSettings.h"
43 #include "settings/Settings.h"
44 #include "threads/SingleLock.h"
45 #include "utils/StringUtils.h"
46 #include "utils/Observer.h"
47
48 using namespace std;
49 using namespace PVR;
50 using namespace EPG;
51
52 CGUIWindowPVRBase::CGUIWindowPVRBase(bool bRadio, int id, const std::string &xmlFile) :
53   CGUIMediaWindow(id, xmlFile.c_str()),
54   m_bRadio(bRadio)
55 {
56 }
57
58 CGUIWindowPVRBase::~CGUIWindowPVRBase(void)
59 {
60 }
61
62 void CGUIWindowPVRBase::Notify(const Observable &obs, const ObservableMessage msg)
63 {
64   CGUIMessage m(GUI_MSG_REFRESH_LIST, GetID(), 0, msg);
65   CApplicationMessenger::Get().SendGUIMessage(m);
66 }
67
68 bool CGUIWindowPVRBase::OnAction(const CAction &action)
69 {
70   switch (action.GetID())
71   {
72     case ACTION_PREVIOUS_CHANNELGROUP:
73     case ACTION_NEXT_CHANNELGROUP:
74       // switch to next or previous group
75       SetGroup(ACTION_NEXT_CHANNELGROUP ? m_group->GetNextGroup() : m_group->GetPreviousGroup());
76       return true;
77   }
78
79   return CGUIMediaWindow::OnAction(action);
80 }
81
82 bool CGUIWindowPVRBase::OnBack(int actionID)
83 {
84   if (actionID == ACTION_NAV_BACK)
85   {
86     // don't call CGUIMediaWindow as it will attempt to go to the parent folder which we don't want.
87     if (GetPreviousWindow() != WINDOW_FULLSCREEN_LIVETV)
88       g_windowManager.ActivateWindow(WINDOW_HOME);
89     else
90       return CGUIWindow::OnBack(actionID);
91   }
92   return CGUIMediaWindow::OnBack(actionID);
93 }
94
95 void CGUIWindowPVRBase::OnInitWindow(void)
96 {
97   if (!g_PVRManager.IsStarted() || !g_PVRClients->HasConnectedClients())
98   {
99     g_windowManager.PreviousWindow();
100     CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning,
101         g_localizeStrings.Get(19045),
102         g_localizeStrings.Get(19044));
103     return;
104   }
105
106   CGUIMediaWindow::OnInitWindow();
107 }
108
109 bool CGUIWindowPVRBase::OnMessage(CGUIMessage& message)
110 {
111   switch (message.GetMessage())
112   {
113     case GUI_MSG_WINDOW_INIT:
114     {
115       m_group = g_PVRManager.GetPlayingGroup(m_bRadio);
116       SetProperty("IsRadio", m_bRadio ? "true" : "");
117     }
118     break;
119       
120     case GUI_MSG_CLICKED:
121     {
122       switch (message.GetSenderId())
123       {
124         case CONTROL_BTNCHANNELGROUPS:
125           return OpenGroupSelectionDialog();
126       }
127     }
128     break;
129   }
130
131   return CGUIMediaWindow::OnMessage(message);
132 }
133
134 void CGUIWindowPVRBase::SetInvalid()
135 {
136   VECFILEITEMS items = m_vecItems->GetList();
137   for (VECFILEITEMS::iterator it = items.begin(); it != items.end(); ++it)
138     (*it)->SetInvalid();
139   CGUIMediaWindow::SetInvalid();
140 }
141
142 bool CGUIWindowPVRBase::OpenGroupSelectionDialog(void)
143 {
144   CGUIDialogSelect *dialog = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT);
145   if (!dialog)
146     return false;
147
148   CFileItemList options;
149   g_PVRChannelGroups->Get(m_bRadio)->GetGroupList(&options);
150
151   dialog->Reset();
152   dialog->SetHeading(g_localizeStrings.Get(19146));
153   dialog->SetItems(&options);
154   dialog->SetMultiSelection(false);
155   dialog->SetSelected(m_group->GroupName());
156   dialog->DoModal();
157
158   if (!dialog->IsConfirmed())
159     return false;
160
161   const CFileItemPtr item = dialog->GetSelectedItem();
162   if (!item)
163     return false;
164
165   SetGroup(g_PVRChannelGroups->Get(m_bRadio)->GetByName(item->m_strTitle));
166
167   return true;
168 }
169
170 CPVRChannelGroupPtr CGUIWindowPVRBase::GetGroup(void)
171 {
172   CSingleLock lock(m_critSection);
173   return m_group;
174 }
175
176 void CGUIWindowPVRBase::SetGroup(CPVRChannelGroupPtr group)
177 {
178   CSingleLock lock(m_critSection);
179   if (!group)
180     return;
181
182   if (m_group != group)
183   {
184     if (m_group)
185       m_group->UnregisterObserver(this);
186     m_group = group;
187     // we need to register the window to receive changes from the new group
188     m_group->RegisterObserver(this);
189     g_PVRManager.SetPlayingGroup(m_group);
190     Refresh();
191   }
192 }
193
194 bool CGUIWindowPVRBase::PlayFile(CFileItem *item, bool bPlayMinimized /* = false */)
195 {
196   if (item->m_bIsFolder)
197   {
198     return false;
199   }
200
201   if (item->GetPath() == g_application.CurrentFile())
202   {
203     CGUIMessage msg(GUI_MSG_FULLSCREEN, 0, GetID());
204     g_windowManager.SendMessage(msg);
205     return true;
206   }
207
208   CMediaSettings::Get().SetVideoStartWindowed(bPlayMinimized);
209
210   if (item->HasPVRRecordingInfoTag())
211   {
212     return PlayRecording(item, bPlayMinimized);
213   }
214   else
215   {
216     bool bSwitchSuccessful(false);
217
218     CPVRChannel *channel = item->HasPVRChannelInfoTag() ? item->GetPVRChannelInfoTag() : NULL;
219
220     if (channel && g_PVRManager.CheckParentalLock(*channel))
221     {
222       /* try a fast switch */
223       if (channel && (g_PVRManager.IsPlayingTV() || g_PVRManager.IsPlayingRadio()) &&
224          (channel->IsRadio() == g_PVRManager.IsPlayingRadio()))
225       {
226         if (channel->StreamURL().empty())
227           bSwitchSuccessful = g_application.m_pPlayer->SwitchChannel(*channel);
228       }
229
230       if (!bSwitchSuccessful)
231       {
232         CApplicationMessenger::Get().PlayFile(*item, false);
233         return true;
234       }
235     }
236
237     if (!bSwitchSuccessful)
238     {
239       CStdString channelName = g_localizeStrings.Get(19029); // Channel
240       if (channel)
241         channelName = channel->ChannelName();
242       CStdString msg = StringUtils::Format(g_localizeStrings.Get(19035).c_str(), channelName.c_str()); // CHANNELNAME could not be played. Check the log for details.
243
244       CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error,
245               g_localizeStrings.Get(19166), // PVR information
246               msg);
247       return false;
248     }
249   }
250
251   return true;
252 }
253
254 bool CGUIWindowPVRBase::StartRecordFile(const CFileItem &item)
255 {
256   if (!item.HasEPGInfoTag())
257     return false;
258
259   const CEpgInfoTag *tag = item.GetEPGInfoTag();
260   CPVRChannelPtr channel;
261   if (tag)
262     channel = tag->ChannelTag();
263
264   if (!channel || !g_PVRManager.CheckParentalLock(*channel))
265     return false;
266
267   CFileItemPtr timer = g_PVRTimers->GetTimerForEpgTag(&item);
268   if (timer && timer->HasPVRTimerInfoTag())
269   {
270     CGUIDialogOK::ShowAndGetInput(19033,19034,0,0);
271     return false;
272   }
273
274   // ask for confirmation before starting a timer
275   CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO);
276   if (!pDialog)
277     return false;
278   pDialog->SetHeading(264);
279   pDialog->SetLine(0, tag->PVRChannelName());
280   pDialog->SetLine(1, "");
281   pDialog->SetLine(2, tag->Title());
282   pDialog->DoModal();
283
284   if (!pDialog->IsConfirmed())
285     return false;
286
287   CPVRTimerInfoTag *newTimer = CPVRTimerInfoTag::CreateFromEpg(*tag);
288   bool bReturn(false);
289   if (newTimer)
290   {
291     bReturn = g_PVRTimers->AddTimer(*newTimer);
292     delete newTimer;
293   }
294   return bReturn;
295 }
296
297 bool CGUIWindowPVRBase::StopRecordFile(const CFileItem &item)
298 {
299   if (!item.HasEPGInfoTag())
300     return false;
301
302   const CEpgInfoTag *tag = item.GetEPGInfoTag();
303   if (!tag || !tag->HasPVRChannel())
304     return false;
305
306   CFileItemPtr timer = g_PVRTimers->GetTimerForEpgTag(&item);
307   if (!timer || !timer->HasPVRTimerInfoTag() || timer->GetPVRTimerInfoTag()->m_bIsRepeating)
308     return false;
309
310   return g_PVRTimers->DeleteTimer(*timer);
311 }
312
313 bool CGUIWindowPVRBase::PlayRecording(CFileItem *item, bool bPlayMinimized /* = false */)
314 {
315   if (!item->HasPVRRecordingInfoTag())
316     return false;
317
318   CStdString stream = item->GetPVRRecordingInfoTag()->m_strStreamURL;
319   if (stream.empty())
320   {
321     CApplicationMessenger::Get().PlayFile(*item, false);
322     return true;
323   }
324
325   /* Isolate the folder from the filename */
326   size_t found = stream.find_last_of("/");
327   if (found == CStdString::npos)
328     found = stream.find_last_of("\\");
329
330   if (found != CStdString::npos)
331   {
332     /* Check here for asterisk at the begin of the filename */
333     if (stream[found+1] == '*')
334     {
335       /* Create a "stack://" url with all files matching the extension */
336       CStdString ext = URIUtils::GetExtension(stream);
337       CStdString dir = stream.substr(0, found).c_str();
338
339       CFileItemList items;
340       XFILE::CDirectory::GetDirectory(dir, items);
341       items.Sort(SortByFile, SortOrderAscending);
342
343       vector<int> stack;
344       for (int i = 0; i < items.Size(); ++i)
345       {
346         if (URIUtils::HasExtension(items[i]->GetPath(), ext))
347           stack.push_back(i);
348       }
349
350       if (stack.empty())
351       {
352         /* If we have a stack change the path of the item to it */
353         XFILE::CStackDirectory dir;
354         CStdString stackPath = dir.ConstructStackPath(items, stack);
355         item->SetPath(stackPath);
356       }
357     }
358     else
359     {
360       /* If no asterisk is present play only the given stream URL */
361       item->SetPath(stream);
362     }
363   }
364   else
365   {
366     CLog::Log(LOGERROR, "CGUIWindowPVRCommon - %s - can't open recording: no valid filename", __FUNCTION__);
367     CGUIDialogOK::ShowAndGetInput(19033,0,19036,0);
368     return false;
369   }
370
371   CApplicationMessenger::Get().PlayFile(*item, false);
372
373   return true;
374 }
375
376 void CGUIWindowPVRBase::ShowRecordingInfo(CFileItem *item)
377 {
378   CGUIDialogPVRRecordingInfo* pDlgInfo = (CGUIDialogPVRRecordingInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_RECORDING_INFO);
379   if (item->IsPVRRecording() && pDlgInfo)
380   {
381     pDlgInfo->SetRecording(item);
382     pDlgInfo->DoModal();
383   }
384 }
385
386 void CGUIWindowPVRBase::ShowEPGInfo(CFileItem *item)
387 {
388   CFileItem *tag = NULL;
389   bool bHasChannel(false);
390   CPVRChannel channel;
391   if (item->IsEPG())
392   {
393     tag = new CFileItem(*item);
394     if (item->GetEPGInfoTag()->HasPVRChannel())
395     {
396       channel = *item->GetEPGInfoTag()->ChannelTag();
397       bHasChannel = true;
398     }
399   }
400   else if (item->IsPVRChannel())
401   {
402     CEpgInfoTag epgnow;
403     channel = *item->GetPVRChannelInfoTag();
404     bHasChannel = true;
405     if (!item->GetPVRChannelInfoTag()->GetEPGNow(epgnow))
406     {
407       CGUIDialogOK::ShowAndGetInput(19033,0,19055,0);
408       return;
409     }
410     tag = new CFileItem(epgnow);
411   }
412
413   CGUIDialogPVRGuideInfo* pDlgInfo = (CGUIDialogPVRGuideInfo*)g_windowManager.GetWindow(WINDOW_DIALOG_PVR_GUIDE_INFO);
414   if (tag && (!bHasChannel || g_PVRManager.CheckParentalLock(channel)) && pDlgInfo)
415   {
416     pDlgInfo->SetProgInfo(tag);
417     pDlgInfo->DoModal();
418   }
419
420   delete tag;
421 }
422
423 bool CGUIWindowPVRBase::ActionInputChannelNumber(int input)
424 {
425   CStdString strInput = StringUtils::Format("%i", input);
426   if (CGUIDialogNumeric::ShowAndGetNumber(strInput, g_localizeStrings.Get(19103)))
427   {
428     int iChannelNumber = atoi(strInput.c_str());
429     if (iChannelNumber >= 0)
430     {
431       int itemIndex = 0;
432       VECFILEITEMS items = m_vecItems->GetList();
433       for (VECFILEITEMS::iterator it = items.begin(); it != items.end(); ++it)
434       {
435         if(((*it)->HasPVRChannelInfoTag() && (*it)->GetPVRChannelInfoTag()->ChannelNumber() == iChannelNumber) ||
436            ((*it)->HasEPGInfoTag() && (*it)->GetEPGInfoTag()->HasPVRChannel() && (*it)->GetEPGInfoTag()->PVRChannelNumber() == iChannelNumber))
437         {
438           // different handling for guide grid
439           if ((GetID() == WINDOW_TV_GUIDE || GetID() == WINDOW_RADIO_GUIDE) &&
440               m_viewControl.GetCurrentControl() == GUIDE_VIEW_TIMELINE)
441           {
442             CGUIEPGGridContainer* epgGridContainer = (CGUIEPGGridContainer*) GetControl(m_viewControl.GetCurrentControl());
443             epgGridContainer->SetChannel((*(*it)->GetEPGInfoTag()->ChannelTag()));
444           }
445           else
446             m_viewControl.SetSelectedItem(itemIndex);
447           return true;
448         }
449         itemIndex++;
450       }
451     }
452   }
453
454   return false;
455 }
456
457 bool CGUIWindowPVRBase::ActionPlayChannel(CFileItem *item)
458 {
459   bool bReturn = false;
460
461   if (item->GetPath() == "pvr://channels/.add.channel")
462   {
463     /* show "add channel" dialog */
464     CGUIDialogOK::ShowAndGetInput(19033,0,19038,0);
465     bReturn = true;
466   }
467   else
468   {
469     /* open channel */
470     bReturn = PlayFile(item, CSettings::Get().GetBool("pvrplayback.playminimized"));
471   }
472
473   return bReturn;
474 }
475
476 bool CGUIWindowPVRBase::ActionPlayEpg(CFileItem *item)
477 {
478   if (!item || !item->HasEPGInfoTag())
479     return false;
480
481   CPVRChannelPtr channel;
482   CEpgInfoTag *epgTag = item->GetEPGInfoTag();
483   if (epgTag->HasPVRChannel())
484     channel = epgTag->ChannelTag();
485
486   if (!channel || !g_PVRManager.CheckParentalLock(*channel))
487     return false;
488
489   CFileItem fileItem;
490   if (epgTag->HasRecording())
491     fileItem = CFileItem(*epgTag->Recording());
492   else
493     fileItem = CFileItem(*channel);
494
495   g_application.SwitchToFullScreen();
496   if (!PlayFile(&fileItem))
497   {
498     // CHANNELNAME could not be played. Check the log for details.
499     CStdString msg = StringUtils::Format(g_localizeStrings.Get(19035).c_str(), channel->ChannelName().c_str());
500     CGUIDialogOK::ShowAndGetInput(19033, 0, msg, 0);
501     return false;
502   }
503
504   return true;
505 }
506
507 bool CGUIWindowPVRBase::ActionDeleteChannel(CFileItem *item)
508 {
509   CPVRChannel *channel = item->GetPVRChannelInfoTag();
510
511   /* check if the channel tag is valid */
512   if (!channel || channel->ChannelNumber() <= 0)
513     return false;
514
515   /* show a confirmation dialog */
516   CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*) g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO);
517   if (!pDialog)
518     return false;
519   pDialog->SetHeading(19039);
520   pDialog->SetLine(0, "");
521   pDialog->SetLine(1, channel->ChannelName());
522   pDialog->SetLine(2, "");
523   pDialog->DoModal();
524
525   /* prompt for the user's confirmation */
526   if (!pDialog->IsConfirmed())
527     return false;
528
529   g_PVRChannelGroups->GetGroupAll(channel->IsRadio())->RemoveFromGroup(*channel);
530   Refresh(true);
531
532   return true;
533 }
534
535 bool CGUIWindowPVRBase::ActionDeleteRecording(CFileItem *item)
536 {
537   bool bReturn = false;
538
539   /* check if the recording tag is valid */
540   CPVRRecording *recTag = (CPVRRecording *) item->GetPVRRecordingInfoTag();
541   if (!recTag || recTag->m_strRecordingId.empty())
542     return bReturn;
543
544   /* show a confirmation dialog */
545   CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*)g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO);
546   if (!pDialog)
547     return bReturn;
548
549   pDialog->SetHeading(122); // Confirm delete
550   pDialog->SetLine(0, item->m_bIsFolder ? 19113 : 19112); // Are you sure?
551   pDialog->SetLine(1, "");
552   pDialog->SetLine(2, item->GetLabel());
553   pDialog->SetChoice(1, 117); // Delete
554
555   /* prompt for the user's confirmation */
556   pDialog->DoModal();
557   if (!pDialog->IsConfirmed())
558     return bReturn;
559
560   /* delete the recording */
561   if (g_PVRRecordings->Delete(*item))
562   {
563     g_PVRManager.TriggerRecordingsUpdate();
564     bReturn = true;
565   }
566
567   return bReturn;
568 }
569
570 bool CGUIWindowPVRBase::ActionRecord(CFileItem *item)
571 {
572   bool bReturn = false;
573
574   CEpgInfoTag *epgTag = item->GetEPGInfoTag();
575   if (!epgTag)
576     return bReturn;
577
578   CPVRChannelPtr channel = epgTag->ChannelTag();
579   if (!channel || !g_PVRManager.CheckParentalLock(*channel))
580     return bReturn;
581
582   if (epgTag->Timer() == NULL)
583   {
584     /* create a confirmation dialog */
585     CGUIDialogYesNo* pDialog = (CGUIDialogYesNo*) g_windowManager.GetWindow(WINDOW_DIALOG_YES_NO);
586     if (!pDialog)
587       return bReturn;
588
589     pDialog->SetHeading(264);
590     pDialog->SetLine(0, "");
591     pDialog->SetLine(1, epgTag->Title());
592     pDialog->SetLine(2, "");
593     pDialog->DoModal();
594
595     /* prompt for the user's confirmation */
596     if (!pDialog->IsConfirmed())
597       return bReturn;
598
599     CPVRTimerInfoTag *newTimer = CPVRTimerInfoTag::CreateFromEpg(*epgTag);
600     if (newTimer)
601     {
602       bReturn = g_PVRTimers->AddTimer(*newTimer);
603       delete newTimer;
604     }
605     else
606     {
607       bReturn = false;
608     }
609   }
610   else
611   {
612     CGUIDialogOK::ShowAndGetInput(19033,19034,0,0);
613     bReturn = true;
614   }
615
616   return bReturn;
617 }
618
619 bool CGUIWindowPVRBase::UpdateEpgForChannel(CFileItem *item)
620 {
621   CPVRChannel *channel = item->GetPVRChannelInfoTag();
622   CEpg *epg = channel->GetEPG();
623   if (!epg)
624     return false;
625
626   epg->ForceUpdate();
627   return true;
628 }
629
630 bool CGUIWindowPVRBase::Update(const std::string &strDirectory, bool updateFilterPath /* = true */)
631 {
632   return CGUIMediaWindow::Update(strDirectory, updateFilterPath);
633 }
634
635 void CGUIWindowPVRBase::UpdateButtons(void)
636 {
637   CGUIMediaWindow::UpdateButtons();
638   SET_CONTROL_LABEL(CONTROL_BTNCHANNELGROUPS, g_localizeStrings.Get(19141) + ": " + (m_group->GroupType() == PVR_GROUP_TYPE_INTERNAL ? g_localizeStrings.Get(19287) : m_group->GroupName()));
639 }