]> git.vomp.tv Git - vompclient.git/blob - vvideolivetv.cc
Summary display for live tv/radio
[vompclient.git] / vvideolivetv.cc
1 /*
2     Copyright 2007-2008 Chris Tallon
3
4     This file is part of VOMP.
5
6     VOMP is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     VOMP is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with VOMP; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 */
20
21 #include "vvideolivetv.h"
22
23 #include "vchannellist.h"
24 #include "video.h"
25 #include "playerlive.h"
26 #include "playerlivetv.h"
27 #include "playerliveradio.h"
28 #include "channel.h"
29 #include "boxstack.h"
30 #include "colour.h"
31 #include "osd.h"
32 #include "command.h"
33 #include "i18n.h"
34 #include "wtextbox.h"
35 #include "remote.h"
36 #include "vaudioselector.h"
37 #include "colour.h"
38 #include "event.h"
39 #include "timers.h"
40 #include "vepg.h"
41
42 VVideoLiveTV::VVideoLiveTV(ChannelList* tchanList, ULONG initialChannelNumber, VChannelList* tvchannelList)
43 {
44   vdr = VDR::getInstance();
45   boxstack = BoxStack::getInstance();
46   video = Video::getInstance();
47
48   chanList = tchanList;
49   vchannelList = tvchannelList;
50   numberWidth = (int)VDR::getInstance()->getChannelNumberWidth();
51
52   currentChannelIndex = 0;
53   previousChannelIndex = 0;
54   osdChannelIndex = 0;
55   keying = 0;
56
57   playing = false;
58
59   // Convert channel number to index
60   UINT i;
61   for(i = 0; i < chanList->size(); i++)
62   {
63     if ((*chanList)[i]->number == (UINT)initialChannelNumber)
64     {
65       currentChannelIndex = i;
66       osdChannelIndex = i;
67       break;
68     }
69   }
70
71   eventList = NULL;
72
73   videoMode = video->getMode();
74   
75   if ((*chanList)[currentChannelIndex]->type == VDR::VIDEO)
76   {
77     player = new PlayerLiveTV(Command::getInstance(), this, chanList);
78   }
79   else
80   {
81     player = new PlayerLiveRadio(Command::getInstance(), this, chanList);
82   }
83   player->init();
84
85   setSize(video->getScreenWidth(), video->getScreenHeight());
86   createBuffer();
87   Colour transparent(0, 0, 0, 0);
88   setBackgroundColour(transparent);
89
90   dowss = false;
91   char* optionWSS = vdr->configLoad("General", "WSS");
92   if (optionWSS)
93   {
94     if (strstr(optionWSS, "Yes")) dowss = true;
95     delete[] optionWSS;
96   }
97   Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Do WSS: %u", dowss);
98
99   if (dowss)
100   {
101     wss.setFormat(video->getFormat());
102     wss.setWide(true);
103     add(&wss);
104     
105     wssRegion.x = 0;
106     wssRegion.y = 6;
107     wssRegion.w = video->getScreenWidth();
108     wssRegion.h = 2;
109   }
110   
111   Colour osdBack = Colour(0, 0, 0, 128);
112   
113   osd.setBackgroundColour(osdBack);
114   osd.setPosition(0, video->getScreenHeight() - 150);
115   osd.setSize(video->getScreenWidth(), 150);
116   osd.setVisible(false);
117   add(&osd);
118   
119   clock.setBackgroundColour(osdBack);
120   clock.setPosition(osd.getWidth() - 100, 4);
121   clock.setSize(90, 30);
122   osd.add(&clock);
123
124   osdChanNum.setBackgroundColour(osdBack);
125   osdChanNum.setPosition(40, 4);
126   osdChanNum.setSize((numberWidth*10) + 22, 30); // 10 px = width of number chars in font
127   osd.add(&osdChanNum);  
128
129   osdChanName.setBackgroundColour(osdBack);
130   osdChanName.setPosition(osdChanNum.getX2() + 10, 4);
131   osdChanName.setSize(300, 30);
132   osd.add(&osdChanName);
133   
134   boxRed.setBackgroundColour(Colour::RED);
135   boxRed.setPosition(50, 104);
136   boxRed.setSize(18, 16);
137   osd.add(&boxRed);
138
139   boxGreen.setBackgroundColour(Colour::GREEN);
140   boxGreen.setPosition(220, 104);
141   boxGreen.setSize(18, 16);
142   osd.add(&boxGreen);
143
144   boxYellow.setBackgroundColour(Colour::YELLOW);
145   boxYellow.setPosition(390, 104);
146   boxYellow.setSize(18, 16);
147   osd.add(&boxYellow);
148
149   boxBlue.setBackgroundColour(Colour::BLUE);
150   boxBlue.setPosition(560, 104);
151   boxBlue.setSize(18, 16);
152   osd.add(&boxBlue);  
153   
154   textRed.setBackgroundColour(osdBack);
155   textRed.setPosition(boxRed.getX()+18, 98);
156   textRed.setSize(120, 30);
157   textRed.setText("Summary");
158   osd.add(&textRed);  
159     
160   textGreen.setBackgroundColour(osdBack);
161   textGreen.setPosition(boxGreen.getX()+18, 98);
162   textGreen.setSize(120, 30);
163   textGreen.setText("Audio");
164   osd.add(&textGreen);  
165     
166   textYellow.setBackgroundColour(osdBack);
167   textYellow.setPosition(boxYellow.getX()+18, 98);
168   textYellow.setSize(120, 30);
169   textYellow.setText("");
170   osd.add(&textYellow);  
171     
172   textBlue.setBackgroundColour(osdBack);
173   textBlue.setPosition(boxBlue.getX()+18, 98);
174   textBlue.setSize(90, 30);
175   textBlue.setText("EPG");
176   osd.add(&textBlue);  
177     
178   sl.setBackgroundColour(osdBack);
179   sl.setPosition(70, 36);
180   sl.setSize(500, 58);
181   sl.setNoLoop();
182   osd.add(&sl);
183   
184   // Summary Box
185   summary.setBackgroundColour(osdBack);
186   summary.setPosition(0, video->getScreenHeight() - 300);
187   summary.setSize(video->getScreenWidth(), 150);
188   summary.setVisible(false);
189   add(&summary);  
190   
191   textSummary.setBackgroundColour(osdBack);
192   textSummary.setPosition(30, 10);
193   textSummary.setSize(video->getScreenWidth() - 60, 130);
194   textSummary.setParaMode(true);
195   summary.add(&textSummary);
196   
197   summaryBlackLine.setBackgroundColour(Colour::BLACK);
198   summaryBlackLine.setPosition(0, summary.getHeight() - 4);
199   summaryBlackLine.setSize(summary.getWidth(), 4);
200   summary.add(&summaryBlackLine);
201   
202   // FIXME painful
203   Region r1 = summary.getRegionR();
204   Region r2 = osd.getRegionR();
205   osdSummaryRegion = r1 + r2;
206 }
207
208 VVideoLiveTV::~VVideoLiveTV()
209 {
210   if (playing) stop();
211
212   delete player;
213   video->setDefaultAspect();
214   delData();
215 }
216
217 void VVideoLiveTV::delData()
218 {
219   if (eventList)
220   {
221     int eventListSize = eventList->size();
222     for(int i = 0; i < eventListSize; i++)
223     {
224       delete (*eventList)[i];
225     }
226     eventList->clear();
227     delete eventList;
228
229   }
230   sl.clear();
231 }
232
233 int VVideoLiveTV::handleCommand(int command)
234 {
235   switch(command)
236   {
237     case Remote::BACK:
238     {
239       if (osd.getVisible())
240       {
241         clearScreen();
242         return 2;
243       }
244       // else drop through to stop
245     }
246     case Remote::STOP:
247     {
248       stop();
249       vchannelList->highlightChannel((*chanList)[currentChannelIndex]);
250       return 4;
251     }
252     case Remote::UP:
253     {
254       // New remote only
255       // epg data up
256       doUpDown(false);
257       return 2;
258     }
259     case Remote::DOWN:
260     {
261       // New remote only
262       // epg data down
263       doUpDown(true);
264       return 2;
265     }
266     case Remote::LEFT:
267     {
268       // New remote only
269       // epg data ch down
270       doLeftRight(false);
271       return 2;
272     }
273     case Remote::RIGHT:
274     {
275       // New remote only
276       // epg data ch up
277       doLeftRight(true);
278       return 2;
279     }
280     case Remote::DF_UP:
281     case Remote::CHANNELUP:
282     {
283       doChanUpDown(UP);
284       return 2;
285     }
286     case Remote::DF_DOWN:
287     case Remote::CHANNELDOWN:
288     {
289       doChanUpDown(DOWN);
290       return 2;
291     }
292     case Remote::PREVCHANNEL:
293     {
294       channelChange(PREVIOUS, 0);
295       return 2;
296     }
297     case Remote::OK:
298     {
299       doOK();
300       return 2;
301     }
302     case Remote::RED:
303     case Remote::MENU:
304     {
305       doSummary();
306       return 2;
307     }
308     case Remote::FULL:
309     case Remote::TV:
310     {
311       toggleChopSides();
312       return 2;
313     }
314
315     case Remote::ZERO:
316     case Remote::ONE:
317     case Remote::TWO:
318     case Remote::THREE:
319     case Remote::FOUR:
320     case Remote::FIVE:
321     case Remote::SIX:
322     case Remote::SEVEN:
323     case Remote::EIGHT:
324     case Remote::NINE:
325     {
326       // key in channel number
327       doKey(command);
328       return 2;
329     }
330
331     case Remote::GREEN:
332     {
333       /*
334       VAudioSelector* vas = new VAudioSelector(this, (*chanList)[currentChannelIndex], ((Player*)player)->getCurrentAudioChannel());
335       vas->setBackgroundColour(Colour::VIEWBACKGROUND);
336       vas->setPosition(0, getHeight()-200);
337       vas->draw();
338       BoxStack::getInstance()->add(vas);
339       BoxStack::getInstance()->update(vas);        
340       */
341     }
342     case Remote::YELLOW:
343     {
344       return 2;
345     }
346     case Remote::GUIDE:
347     case Remote::BLUE:
348     {
349       doEPG();
350       return 2;
351     }
352   }
353
354   return 1;
355 }
356
357 void VVideoLiveTV::go()
358 {
359   playing = true;
360   draw();
361   boxstack->update(this);
362
363   setClock();
364   displayOSD(true);
365   
366   player->go(currentChannelIndex);
367 }
368
369 void VVideoLiveTV::stop()
370 {
371   Timers::getInstance()->cancelTimer(this, 1);
372   Timers::getInstance()->cancelTimer(this, 2);
373   player->stop();
374   playing = false;
375 }
376
377 void VVideoLiveTV::doLeftRight(bool right)
378 {
379   if (osd.getVisible())
380   {
381     if (right) osdChannelIndex = upChannel(osdChannelIndex);
382     else       osdChannelIndex = downChannel(osdChannelIndex);
383   }
384   else
385   {
386     osdChannelIndex = currentChannelIndex;
387   }
388   displayOSD(true);
389 }
390
391 void VVideoLiveTV::doUpDown(bool down)
392 {
393   if (osd.getVisible())
394   {
395     if (down) sl.down();
396     else      sl.up();
397     sl.draw();
398     
399     displayOSD(false);
400   }
401   else
402   {
403     displayOSD(true);
404   }
405 }
406
407 void VVideoLiveTV::doChanUpDown(int which)
408 {
409   channelChange(OFFSET, which);
410   osdChannelIndex = currentChannelIndex;
411   displayOSD(true);
412 }
413
414 void VVideoLiveTV::doOK()
415 {
416   if (osd.getVisible())
417   {
418     if (keying)
419     {
420       UINT newChannel = 0;
421       for(int i = keying - 1; i >= 0; i--) newChannel += keyingInput[i] * (ULONG)pow(10, i);
422       
423       channelChange(NUMBER, newChannel);
424       osdChannelIndex = currentChannelIndex;
425       displayOSD(true);
426     }
427     else if (osdChannelIndex == currentChannelIndex)
428     {
429       clearScreen();
430     }
431     else
432     {
433       channelChange(INDEX, osdChannelIndex);
434       displayOSD(true);
435     }
436   }
437   else
438   {
439     osdChannelIndex = currentChannelIndex;
440     displayOSD(true);
441   }
442 }
443
444 void VVideoLiveTV::doSummary()
445 {
446   if (summary.getVisible())
447   {
448     summary.setVisible(false);
449     draw();
450     boxstack->update(this, summary.getRegion());
451     Timers::getInstance()->setTimerD(this, 1, 8); // Restart a timer to get rid of osd
452     return;
453   }
454
455   summary.setVisible(true);
456
457   if (osd.getVisible())
458   {
459     Timers::getInstance()->cancelTimer(this, 1);
460     displayOSD(false);
461   }
462   else
463   {
464     displayOSD(true);
465   }
466 }
467
468 void VVideoLiveTV::doKey(int command)
469 {
470   if (!osd.getVisible()) // First key. prep the data
471   {
472     setNowNextData();
473     keying = 0;    
474   }
475
476   int i;
477   for (i = keying - 1; i >= 0; i--) keyingInput[i+1] = keyingInput[i];
478   keyingInput[0] = command;
479   keying++;
480
481   char* keyingString = new char[numberWidth + 1];
482   for (i = 0; i < numberWidth; i++) keyingString[i] = '_';
483   keyingString[numberWidth] = '\0';
484
485   for (i = 0; i < keying; i++) keyingString[i] = keyingInput[keying - 1 - i] + 48;
486   
487   if (keying == numberWidth)
488   {
489     UINT newChannel = 0;
490     for(i = keying - 1; i >= 0; i--) newChannel += keyingInput[i] * (ULONG)pow(10, i);
491     
492     channelChange(NUMBER, newChannel);
493     osdChannelIndex = currentChannelIndex;
494     Timers::getInstance()->cancelTimer(this, 1); // cancel the timer to react to keying input,
495     displayOSD(true); // this will put one back if required
496   }
497   else
498   {
499     osdChanNum.setText(keyingString);
500
501     if (!osd.getVisible())
502     {
503       osd.setVisible(true);
504       draw();
505     }
506     else
507     {
508       osdChanNum.draw();
509     }
510     boxstack->update(this, osd.getRegion());
511     Timers::getInstance()->setTimerD(this, 1, 3);  // 3s for keying input
512   }
513   delete[] keyingString;
514 }
515
516 void VVideoLiveTV::doEPG()
517 {
518   if (osd.getVisible()) clearScreen();
519
520   video->setMode(Video::QUARTER);
521   video->setPosition(170, 5); //TODO need to deal with 4:3 switching
522
523   VEpg* vepg = new VEpg(this, currentChannelIndex, VDR::VIDEO);
524   vepg->draw();
525   boxstack->add(vepg);
526   boxstack->update(vepg);
527 }
528
529 void VVideoLiveTV::setNowNextData()
530 {
531   delData();
532   
533   Channel* currentChannel = (*chanList)[osdChannelIndex];
534
535   char formatChanNum[20];
536   SNPRINTF(formatChanNum, 19, "%0*lu", numberWidth, currentChannel->number);
537   osdChanNum.setText(formatChanNum);
538   osdChanName.setText(currentChannel->name);
539
540   eventList = VDR::getInstance()->getChannelSchedule(currentChannel->number);
541
542   if (!eventList)
543   {
544     sl.addOption(tr("No channel data available"), 0, 1);
545   }
546   else
547   {
548     sort(eventList->begin(), eventList->end(), EventSorter());
549
550     char tempString[300];
551     char tempString2[300];
552     struct tm* btime;
553     Event* event;
554     int eventListSize = eventList->size();
555     for(int i = 0; i < eventListSize; i++)
556     {
557       event = (*eventList)[i];
558
559       //btime = localtime((time_t*)&event->time);
560       time_t etime = event->time;
561       btime = localtime(&etime);
562 #ifndef _MSC_VER
563       strftime(tempString2, 299, "%0H:%0M ", btime);
564 #else
565       strftime(tempString2, 299, "%H:%M ", btime);
566 #endif
567       SNPRINTF(tempString, 299, "%s %s", tempString2, event->title);
568       
569       sl.addOption(tempString, (ULONG)event, (i==0));
570     }
571   }
572 }
573
574 void VVideoLiveTV::setSummaryData()
575 {
576   // If osd is not being displayed, sl will be filled with now, current channel
577   // If the display was already on, sl will have programme to show summary for, not necessarily current channel and now
578   Event* selectedEvent = (Event*)sl.getCurrentOptionData();
579   
580   if (!selectedEvent)
581   {
582     Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "No summary");  
583     textSummary.setText(tr("No summary available"));
584   }
585   else
586   {
587     Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Summary: %s", selectedEvent->description);  
588     textSummary.setText(selectedEvent->description);
589   }
590 }
591
592 void VVideoLiveTV::displayOSD(bool newNowNextData)
593 {
594   osd.setVisible(true);
595   if (newNowNextData)
596   {
597     setNowNextData();
598     keying = 0;  
599   }
600   osd.draw();
601   
602   if (summary.getVisible())
603   {
604     setSummaryData();
605     summary.draw();
606     boxstack->update(this, &osdSummaryRegion);
607   }
608   else
609   {
610     boxstack->update(this, osd.getRegion());
611     Timers::getInstance()->setTimerD(this, 1, 4);
612   }
613 }
614
615 void VVideoLiveTV::clearScreen()
616 {  
617   if (!summary.getVisible()) Timers::getInstance()->cancelTimer(this, 1);
618
619   osd.setVisible(false);
620
621   if (summary.getVisible())
622   {
623     summary.setVisible(false);
624     draw();
625     boxstack->update(this, &osdSummaryRegion);
626   }
627   else
628   {
629     draw();
630     boxstack->update(this, osd.getRegion());
631   }
632 }
633
634 void VVideoLiveTV::setClock()
635 {
636   char timeString[20];
637   time_t t;
638   time(&t);
639   struct tm* tms = localtime(&t);
640   strftime(timeString, 19, "%H:%M", tms);
641   clock.setText(timeString);
642
643   time_t dt = 60 - (t % 60);  // seconds to the next minute
644   if (dt == 0) dt = 60; // advance a whole minute if necessary
645   dt += t;  // get a time_t value for it rather than using duration
646   // (so it will occur at the actual second and not second and a half)
647
648   Timers::getInstance()->setTimerT(this, 2, dt);
649 }
650
651 void VVideoLiveTV::timercall(int ref)
652 {
653   if (ref == 1)
654   {
655     if (keying)
656     {
657       // Really, now that cancelTimer basically protects us from deletion, why can't we execute gui stuff here?
658       
659       UINT newChannel = 0;
660       for(int i = keying - 1; i >= 0; i--) newChannel += keyingInput[i] * (ULONG)pow(10, i);
661       
662       Message* m = new Message();
663       m->message = Message::CHANNEL_CHANGE;
664       m->to = this;
665       m->parameter = newChannel;
666       m->tag = 1; // signal to call displayOSD();
667       Command::getInstance()->postMessageFromOuterSpace(m);  // FIXME cjt yeah you know what.
668     }
669     else
670     {
671       osd.setVisible(false);
672       draw();
673       Message* m = new Message();
674       m->message = Message::REDRAW;
675       m->to = BoxStack::getInstance();
676       m->from = this;
677       m->parameter = (ULONG)osd.getRegion();
678       Command::getInstance()->postMessageFromOuterSpace(m);  // FIXME cjt yeah you know what.
679     }
680   }
681   else if (ref == 2)
682   {
683     setClock();
684     if (osd.getVisible())
685     {
686       clock.draw();
687       Message* m = new Message();
688       m->message = Message::REDRAW;
689       m->to = BoxStack::getInstance();
690       m->from = this;
691       m->parameter = (ULONG)osd.getRegion();
692       Command::getInstance()->postMessageFromOuterSpace(m);  // FIXME cjt yeah you know what.    
693     }
694   }
695 }
696
697 bool VVideoLiveTV::channelChange(UCHAR changeType, UINT newData)
698 {
699   UINT newChannel = 0;
700
701   if (changeType == INDEX)
702   {
703     newChannel = newData;
704   }
705   else if (changeType == NUMBER)
706   {
707     UINT i;
708     for(i = 0; i < chanList->size(); i++)
709     {
710       if ((*chanList)[i]->number == (UINT)newData)
711       {
712         newChannel = i;
713         break;
714       }
715     }
716
717     if (i == chanList->size())
718     {
719       // no such channel
720       return false;
721     }
722   }
723   else if (changeType == OFFSET)
724   {
725     if (newData == UP) newChannel = upChannel(currentChannelIndex);
726     else newChannel = downChannel(currentChannelIndex);
727   }
728   else if (changeType == PREVIOUS)
729   {
730     newChannel = previousChannelIndex;
731   }
732   else
733   {
734     return false; // bad input
735   }
736
737   if (newChannel == currentChannelIndex) return true;
738
739   previousChannelIndex = currentChannelIndex;
740   currentChannelIndex = newChannel;
741   
742   Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Set player to channel %u", currentChannelIndex);
743   player->setChannel(currentChannelIndex);
744   Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Done Set player to channel %u", currentChannelIndex);
745
746   return true;
747 }
748
749 void VVideoLiveTV::processMessage(Message* m)
750 {
751   if (m->message == Message::MOUSE_LBDOWN)
752   {
753     BoxStack::getInstance()->handleCommand(Remote::OK); //simulate rok press
754   }
755   else if (m->message == Message::CHANNEL_CHANGE)
756   {
757     channelChange(NUMBER, m->parameter);
758     osdChannelIndex = currentChannelIndex;
759     if (m->tag == 1) displayOSD(true);
760   }
761   else if (m->message == Message::EPG_CLOSE)
762   {
763     video->setMode(videoMode);
764   }
765   else if (m->message == Message::AUDIO_CHANGE_CHANNEL)
766   {
767     Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Received change audio channel to %i", m->parameter);
768     player->setAudioChannel(m->parameter);
769   }
770   else if (m->message == Message::PLAYER_EVENT)
771   {
772     switch(m->parameter)
773     {
774       case PlayerLiveTV::CONNECTION_LOST: // connection lost detected
775       {
776         Log::getInstance()->log("VVideoLiveTV", Log::DEBUG, "Received connection lost from player");
777         // I can't handle this, send it to command
778         Message* m2 = new Message();
779         m2->to = Command::getInstance();
780         m2->message = Message::CONNECTION_LOST;
781         Command::getInstance()->postMessageNoLock(m2);
782         break;
783       }
784       
785       /*
786       case PlayerLiveTV::STREAM_END:
787       {
788         // I can't handle this, send it to command - improve this
789         Message* m2 = new Message(); // Must be done after this thread finishes, and must break into master mutex
790         m2->to = Command::getInstance();
791         m2->message = Message::STREAM_END;
792         Command::getInstance()->postMessageNoLock(m2);
793         break;
794       }
795       */
796       
797       case PlayerLiveTV::ASPECT43:
798       {
799         if (dowss)
800         {
801           Log::getInstance()->log("VVideoRec", Log::DEBUG, "Received do WSS 43");
802           wss.setWide(false);
803           wss.draw();
804           BoxStack::getInstance()->update(this, &wssRegion);
805         }
806         break;
807       }
808       case PlayerLiveTV::ASPECT169:
809       {
810         if (dowss)
811         {
812           Log::getInstance()->log("VVideoRec", Log::DEBUG, "Received do WSS 169");
813           wss.setWide(true);
814           wss.draw();
815           BoxStack::getInstance()->update(this, &wssRegion);
816         }
817         break;
818       }
819     }
820   }
821 }
822
823 UINT VVideoLiveTV::upChannel(UINT index)
824 {
825   if (index == (chanList->size() - 1)) // at the end
826     return 0; // so go to start
827   else
828     return index + 1;
829 }
830
831 UINT VVideoLiveTV::downChannel(UINT index)
832 {
833   if (index == 0) // at the start
834     return chanList->size() - 1; // so go to end
835   else
836     return index - 1;
837 }
838
839 void VVideoLiveTV::toggleChopSides()
840 {
841   if (video->getTVsize() == Video::ASPECT16X9) return; // Means nothing for 16:9 TVs
842
843   if (videoMode == Video::NORMAL)
844   {
845     videoMode = Video::LETTERBOX;
846     video->setMode(Video::LETTERBOX);
847   }
848   else
849   {
850     videoMode = Video::NORMAL;
851     video->setMode(Video::NORMAL);
852   }
853 }
854