]> git.vomp.tv Git - vompclient.git/blob - vepg.cc
Add first support for TVScraper in VVideolive also some optimization for raspberry...
[vompclient.git] / vepg.cc
1 /*
2     Copyright 2005 Brian Walton
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     vepg presents a 2 dimensional electronic programme guide with channels down
22     the y axis and time along the x axis.
23     Programmes are layed on the x axis as alterate coloured blocks with as much
24     of the programme title as will fit inside the block shown as text.
25     Up and down commands step through the channels whilst left and right commands
26     move through the programmes of the currently selected channel.
27     When a programme is selected, it highlights in the grid and full programe details
28     (start time, title and description) are displayed in an area at te top left of the screen.
29     Any currently programmed timers will display in the grid and in the orogramme detail window as red
30     It is possible to select a programme to be recorded by pressing the record button.
31     The video stream currently being viewed is shown as quarter screen in the top right.
32 */
33
34 #include "vepg.h"
35
36 #include "remote.h"
37 #include "vchannellist.h"
38 #include "command.h"
39 #include "video.h"
40 #include "vepgsettimer.h"
41 #include "timers.h"
42 #include "wsymbol.h"
43 #include "message.h"
44 #include "colour.h"
45 #include "boxstack.h"
46 #include "channel.h"
47 #include "i18n.h"
48 #include "log.h"
49
50
51 VEpg* VEpg::instance = NULL;
52
53 VEpg::VEpg(void* tparent, UINT tcurrentChannelIndex, ULONG streamType)
54 {
55   instance = this;
56   currentChannelIndex = tcurrentChannelIndex;
57
58   // PAL / NTSC sizes -----------------------
59
60   int xpos, ypos;
61   //int summaryLines, summaryLowerPadding;
62   //int chanNameYpos;
63   int fontHeight=getFontHeight() + 4;
64   //UINT gridRows; // is a class member
65
66
67   if (Video::getInstance()->getFormat() == Video::PAL)
68   {
69     //xsize = 647;
70     //ysize = 541;
71     xpos = 30;
72     ypos = 16;
73
74     //summaryLines = 8;
75
76     //chanNameYpos = 244;
77     gridRows = 7;
78   }
79   else
80   {
81     xpos = 50;
82     ypos = 20;
83     //summaryLines = 6;
84     //summaryLowerPadding = 28;
85     //chanNameYpos = 206;
86     gridRows = 5;
87   }
88   int screenwidthhalf=Video::getInstance()->getScreenWidth()/2;
89   int screenheighthalf=Video::getInstance()->getScreenHeight()/2;
90  // summaryLines = ((float)screenheighthalf)/((float)fontHeight))-1;
91  // summaryLowerPadding = screenheighthalf-summaryLines*(fontHeight);
92   gridRows = (screenheighthalf-fontHeight*2-40)/fontHeight;
93
94
95   // initialise variables and pointers
96   boxstack = BoxStack::getInstance();
97   parent = tparent;
98   eventList = NULL;
99   chanList = VDR::getInstance()->getChannelsList(streamType); //TODO want to be able to display video and radio together
100   e = 0;
101
102   eventLista.resize(gridRows);
103   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
104   {
105     // initialise array of pointers to eventlist structures
106     eventLista[listIndex] = NULL;
107   }
108
109   // Create pallet on which to paint our epg view and position it in centre of screen.
110   // Need to reduce size to deal with overscanning TVs.
111
112   setSize(Video::getInstance()->getScreenWidth(), Video::getInstance()->getScreenHeight());
113   createBuffer();
114   setPosition(0, 0);
115
116   // beautify
117 //  DrawStyle transparent = DrawStyle(0, 0, 0, 0);
118 //  setBackgroundColour(transparent);
119
120 //  progTitle.setSurface(surface);
121   progTitle.setPosition(0,0);
122   progTitle.setSize(screenwidthhalf,(fontHeight) * 1 + ypos); //paragraph line seperation is 4 pixels
123   progTitle.setBackgroundColour(DrawStyle::TITLEBARBACKGROUND);
124   progTitle.setTextPos(xpos, ypos);
125   progTitle.setGap(4);
126   add(&progTitle);
127
128 //  progInfo.setSurface(surface);
129   progInfo.setBackgroundColour(DrawStyle::VIEWBACKGROUND);
130   progInfo.setPosition(0, progTitle.getY2());
131   progInfo.setSize(screenwidthhalf,screenheighthalf-progTitle.getY2()+10);
132   progInfo.setTextPos(xpos, 0);
133   progInfo.setGap(4);
134   add(&progInfo);
135
136 //  chanName.setSurface(surface);
137   chanName.setSize(510, (fontHeight));
138   chanName.setPosition(screenwidthhalf, screenheighthalf - 2*fontHeight);
139   DrawStyle t1(0, 0, 0, 90);
140   chanName.setBackgroundColour(t1);
141   add(&chanName);
142
143   // create area to display list of channels
144 //  chanListbox.setSurface(surface); // add channel list
145   chanListbox.setPosition(0, progInfo.getY2() + fontHeight + 4); // position channel list
146   chanListbox.setSize(150, ((getFontHeight() + 2) * gridRows) + 5); //listbox line seperation is 2 pixels
147   chanListbox.setGap(2);
148   chanListbox.addColumn(xpos);
149   add(&chanListbox);
150
151   // populate channel list
152   if (chanList)
153   {
154     Channel* chan;
155     int first = 1;
156     for (UINT i = 0; i < chanList->size(); i++)
157     {
158       chan = (*chanList)[i];
159       if (i == currentChannelIndex)
160         first = 1;
161       chan->index = chanListbox.addOption(chan->name, 0, first);
162       first = 0;
163     }
164     chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
165   }
166
167   window_x= chanListbox.getRootBoxOffsetX() + chanListbox.getWidth() + 5;
168   window_width=(Video::getInstance()->getScreenWidth() - window_x +3)/3;
169
170   listTop = chanListbox.getTopOption();
171   chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
172   time(&ltime); // set ltime to now
173   ltime = prevHour(&ltime); // set ltime to previous hour TODO make this half hour?
174   time(&selTime); // set selTime to now
175   updateEventList(); // get list of programmes
176 }
177
178 void VEpg::preDelete()
179 {
180   Timers::getInstance()->cancelTimer(this, 1);
181 }
182
183 VEpg::~VEpg()
184 {
185
186   instance = NULL;
187
188   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
189   {
190     if (eventLista[listIndex])
191     {
192       (eventLista)[listIndex]->clear();
193       delete eventLista[listIndex];
194     }
195   }
196   //  delete [] eventLista; // FIXME
197
198   // destroy dynamically allocated memory
199 }
200
201 VEpg* VEpg::getInstance()
202 {
203   return instance;
204 }
205
206 void VEpg::setInfo(Event* event)
207 {
208   time_t t;
209   struct tm* btime; // to hold programme start and end time
210   char timeString[9]; // to hold programme start and end time
211   int length = strlen(event->title); // calculate length of programme title string
212   char* title = new char[length + 15]; // create string to hold start time, end time and programme title
213   btime = localtime((time_t*)&event->time); //get programme start time
214 #ifndef _MSC_VER
215   strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -
216 #else
217   strftime(timeString, 9, "%H:%M - ", btime); // and format it as hh:mm -
218 #endif
219   strcpy(title, timeString); // put it in our buffer
220   t = event->time + event->duration; //get programme end time
221   btime = localtime(&t);
222 #ifndef _MSC_VER
223   strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -
224 #else
225   strftime(timeString, 7, "%H:%M ", btime); // and format it as hh:mm -
226 #endif
227   strcat(title, timeString); // put it in our buffer
228   strcat(title, event->title); // then add the programme title
229   progTitle.setText(title); // sput this sring in our text box
230   length = strlen(event->description);
231   char* info = new char[length + 1]; // create programme detail string
232   strcpy(info, event->description);
233   progInfo.setText(info); // show programme detail string
234 // destroy dynamically allocated memory
235   delete[] info;
236   delete[] title;
237 }
238
239 void VEpg::draw()
240 {
241 //  View::draw(); // draw pallet
242   // beautify
243   DrawStyle transparent = DrawStyle(0, 0, 0, 0);
244   fillColour(transparent);
245   
246   
247   // Moved all the dynamic data drawing to a seperate function
248
249   // Display the status and key stuff at the bottom
250
251   UINT keyx = chanListbox.getRootBoxOffsetX() + chanListbox.getColumn(0);
252   UINT keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
253   rectangle (0, keyy, Video::getInstance()->getScreenWidth() ,
254                   Video::getInstance()->getScreenHeight()-keyy, DrawStyle::DARKGREY);
255
256   WSymbol w;
257   TEMPADD(&w);
258   
259   w.nextSymbol = WSymbol::LEFTARROW;
260   w.setPosition(keyx + 1, keyy + 20);
261   w.draw();
262
263   w.nextSymbol = WSymbol::UP;
264   w.setPosition(keyx + 26, keyy + 3);
265   w.draw();
266
267   w.nextSymbol = WSymbol::DOWN;
268   w.setPosition(keyx + 26, keyy + 36);
269   w.draw();
270
271   w.nextSymbol = WSymbol::RIGHTARROW;
272   w.setPosition(keyx + 50, keyy + 20);
273   w.draw();
274
275   drawTextCentre(tr("OK"), keyx + 35, keyy + 20, DrawStyle::LIGHTTEXT);
276
277   rectangle(keyx + 72, keyy + 4, 104, getFontHeight() + 2, DrawStyle::RED);
278   drawText(tr("Page up"), keyx + 74, keyy + 5, DrawStyle::LIGHTTEXT);
279
280   rectangle(keyx + 72, keyy + getFontHeight() + 8, 104, getFontHeight() + 2, DrawStyle::GREEN);
281   drawText(tr("Page down"), keyx + 74, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
282
283   rectangle(keyx + 180, keyy + 4, 104, getFontHeight() + 2, DrawStyle::YELLOW);
284   drawText(tr("-24 hours"), keyx + 182, keyy + 5, DrawStyle::LIGHTTEXT);
285
286   rectangle(keyx + 180, keyy + getFontHeight() + 8, 104, getFontHeight() + 2, DrawStyle::BLUE);
287   drawText(tr("+24 hours"), keyx + 182, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
288
289   rectangle(keyx + 290, keyy + 4, 180, getFontHeight() + 2, DrawStyle::GREY);
290   drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, DrawStyle::LIGHTTEXT);
291
292   rectangle(keyx + 290, keyy + getFontHeight() + 8, 180, getFontHeight() + 2, DrawStyle::GREY);
293   DrawStyle red = DrawStyle(130, 0, 0);
294   drawText(tr("Rec: Set timer"), keyx + 292, keyy + getFontHeight() + 9, red);
295
296   rectangle(keyx + 474, keyy + 4, 128, getFontHeight() + 2, DrawStyle::GREY);
297   w.nextSymbol = WSymbol::PLAY;
298   w.setPosition(keyx + 476, keyy + 5);
299   w.draw();
300   drawText(tr("Sel channel"), keyx + 496, keyy + 5, DrawStyle::LIGHTTEXT);
301
302   rectangle(keyx + 474, keyy + getFontHeight() + 8, 128, getFontHeight() + 2, DrawStyle::GREY);
303   drawText(tr("Go: Preview"), keyx + 476, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
304
305
306   // Draw all the dynamic data
307   drawData();
308 }
309
310 void VEpg::drawData()
311 {
312   // Not doing View::draw() every time causes
313   // things not to be cleared off the surface properly
314   // So, blank out the data area first
315   int screenwidth=Video::getInstance()->getScreenWidth();
316   rectangle(
317     chanListbox.getRootBoxOffsetX(),
318     chanListbox.getRootBoxOffsetY() - getFontHeight() - 3,
319     window_width * MINUTE_SCALE,
320     chanListbox.getHeight() + getFontHeight() + 4,
321     DrawStyle::BLACK);
322
323   chanListbox.draw();
324   drawgrid();
325   chanName.draw(); // TODO this should be dealt with by vvideolive
326
327   progTitle.draw();
328   progInfo.draw();
329
330   // Set timer to redraw to move the current time bar
331   time_t t, dt;
332   time(&t);
333   dt = 60 - (t % 60);
334   if (dt == 0) dt = 60;
335   dt += t;
336   Timers::getInstance()->setTimerT(this, 1, dt);
337 }
338
339 void VEpg::timercall(int clientReference)
340 {
341   drawData();
342   boxstack->update(this);
343 }
344
345 int VEpg::handleCommand(int command)
346 {
347   switch(command)
348   {
349     case Remote::DF_UP:
350     case Remote::UP:
351     { // cursor up the channel list
352       chanListbox.up();
353       drawData();
354       boxstack->update(this);
355       return 2;
356     }
357     case Remote::DF_DOWN:
358     case Remote::DOWN:
359     { // cursor down the channel list
360       Log::getInstance()->log("VEPG", Log::DEBUG, "Down start");
361       
362       chanListbox.down();
363       drawData();
364       boxstack->update(this);
365       Log::getInstance()->log("VEPG", Log::DEBUG, "Down end");
366
367       return 2;
368     }
369     case Remote::DF_LEFT:
370     case Remote::LEFT:
371     { // cursor left through time
372       selTime = thisEvent.time - 1;
373       drawData();
374       boxstack->update(this);
375       return 2;
376     }
377     case Remote::DF_RIGHT:
378     case Remote::RIGHT:
379     {
380     // cursor right through time
381       selTime = thisEvent.time + thisEvent.duration;
382       drawData();
383       boxstack->update(this);
384       return 2;
385     }
386     case Remote::RED:
387     {
388     // cursor up one page
389       chanListbox.pageUp();
390       drawData();
391       boxstack->update(this);
392       return 2;
393     }
394     case Remote::GREEN:
395     {
396     // cursor down one page
397       chanListbox.pageDown();
398       drawData();
399       boxstack->update(this);
400       return 2;
401     }
402     case Remote::BLUE:
403     {
404     // step forward 24 hours
405       selTime += 24 * 60 * 60;
406       drawData();
407       boxstack->update(this);
408       return 2;
409     }
410     case Remote::YELLOW:
411     {
412     // step forward 24 hours
413       selTime -= 24 * 60 * 60;
414       drawData();
415       boxstack->update(this);
416       return 2;
417     }
418     case Remote::RECORD:
419     {
420       if (!chanList) return 2;
421       Log::getInstance()->log("VEPG", Log::DEBUG, "ID %lu TIME %lu DURATION %lu TITLE %s", thisEvent.id, thisEvent.time, thisEvent.duration, thisEvent.title);
422       VEpgSetTimer* vs = new VEpgSetTimer(&thisEvent, (*chanList)[chanListbox.getCurrentOption()]);
423       vs->draw();
424       boxstack->add(vs);
425       boxstack->update(vs);
426       return 2;
427     }
428     case Remote::PLAY:
429     case Remote::GO:
430     case Remote::OK:
431     {
432       if (!chanList) return 2;
433
434       // select programme and display menu TODO currently just changes to selected channel
435
436       currentChannelIndex = chanListbox.getCurrentOption();
437
438       if (parent)
439       {
440         Message* m = new Message(); // Must be done after this view deleted
441         m->from = this;
442         m->to = parent;
443         m->message = Message::CHANNEL_CHANGE;
444         m->parameter = (*chanList)[currentChannelIndex]->number;
445         Command::getInstance()->postMessageNoLock(m);
446       }
447       
448       setCurrentChannel();
449
450       if(command == Remote::GO)
451         return 2;
452       // GO just changes channel in preview, PLAY changes channel and returns to normal TV
453     }
454     case Remote::BACK:
455     case Remote::GUIDE:
456     {
457       // return to normal TV mode
458       if (parent) // ptr check done in case being tested from videorec
459       {
460         Message* m = new Message(); // Must be done after this view deleted
461         m->from = this;
462         m->to = parent;
463         m->message = Message::EPG_CLOSE;
464         Command::getInstance()->postMessageNoLock(m);
465       }
466       return 4;
467     }
468     case Remote::CHANNELUP:
469     {
470       if (currentChannelIndex == (chanList->size() - 1)) // at the end
471         currentChannelIndex = 0;
472       else
473         ++currentChannelIndex;
474       
475       if (parent)
476       {
477         Message* m = new Message(); // Must be done after this view deleted
478         m->from = this;
479         m->to = parent;
480         m->message = Message::CHANNEL_CHANGE;
481         m->parameter = (*chanList)[currentChannelIndex]->number;
482         Command::getInstance()->postMessageNoLock(m);
483       }
484       
485       setCurrentChannel();
486
487       return 2;
488     }
489     case Remote::CHANNELDOWN:
490     {
491       if (currentChannelIndex == 0) // at the start
492         currentChannelIndex = chanList->size() - 1; // so go to end
493       else
494         --currentChannelIndex;
495
496       if (parent)
497       {
498         Message* m = new Message(); // Must be done after this view deleted
499         m->from = this;
500         m->to = parent;
501         m->message = Message::CHANNEL_CHANGE;
502         m->parameter = (*chanList)[currentChannelIndex]->number;
503         Command::getInstance()->postMessageNoLock(m);
504       }
505       
506       setCurrentChannel();
507
508       return 2;
509     }
510   }
511   // stop command getting to any more views
512   return 1;
513 }
514
515 void VEpg::drawgrid() // redraws grid and select programme
516 {
517   // draw the grid of programmes
518   char timeString[20];
519   time_t t;
520   time(&t); // set t = now
521   if(selTime < t)
522     selTime = t; // don't allow cursor in the past
523   if(listTop != chanListbox.getTopOption())
524   {
525   // chanListbox has scrolled TODO speed up by changing only rows that have changed
526     listTop = chanListbox.getTopOption();
527     updateEventList();
528   }
529   if ((selTime >= ltime + window_width * 60) || (selTime <= ltime))
530   {
531   // we have cursored back before left time of window
532   //TODO check that this and above don't happen together
533     ltime = prevHour(&selTime);
534     updateEventList();
535   }
536   // draw time scale
537   DrawStyle white = DrawStyle(255, 255, 255, 255);
538   
539
540   t = ltime;
541   struct tm* tms;
542   tms = localtime(&t);
543   strftime(timeString, 19, "%a %d %b", tms);
544   int timey = chanListbox.getRootBoxOffsetY() - getFontHeight() - 3;
545   int timex = 135;
546   drawTextRJ(timeString, timex - 10, timey, DrawStyle::LIGHTTEXT); // print date
547   strftime(timeString, 19, "%H:%M", tms);
548   drawText(timeString, timex, timey, DrawStyle::LIGHTTEXT); // print left time
549
550   rectangle(155, timey + getFontHeight(), 2, 7, white);
551   t = t + 3600;
552   tms = localtime(&t);
553   strftime(timeString, 19, "%H:%M", tms);
554   drawText(timeString, timex + 180, timey, DrawStyle::LIGHTTEXT); // print middle time
555   rectangle(335, timey + getFontHeight(), 2, 7, white);
556   t = t + 3600;
557   tms = localtime(&t);
558   strftime(timeString, 19, "%H:%M", tms);
559   drawText(timeString, timex + 360, timey, DrawStyle::LIGHTTEXT); // print right time
560   rectangle(515, timey + getFontHeight(), 2, 7, white);
561   // pointer to selTime
562   //rectangle(155 + (selTime - ltime) / 20, timey + getFontHeight(), 2, 7, DrawStyle(255, 50, 50, 255));
563
564   // current time line
565   time(&t);
566   if ((t >= ltime) && (t < (ltime + 9000)))
567   {
568     rectangle(155 + (t - ltime) / 20, timey + getFontHeight(), 2, ((getFontHeight() + 2) * gridRows) + 7 + 2, DrawStyle::RED);
569   }
570
571   // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
572   Event* event;
573   Event noevent; // an event to use if there are gaps in the epg
574   thisEvent.setdescription(tr("There are no programme details available for this period"));
575   thisEvent.duration = window_width * 60;
576   thisEvent.time = ltime;
577   thisEvent.settitle(tr("No programme details"));
578   thisEvent.id = 0;
579   bool swapColour = false; // alternate cell colour
580   bool currentRow = false;
581   int y = chanListbox.getRootBoxOffsetY() + 5; // vertical position of cell
582   DrawStyle bg, fg; // background colour of cells in grid
583   // for each displayed channel, find programmes that fall in 2.5 hour time window
584   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
585   {
586     if (listTop + (int)listIndex >= chanListbox.getBottomOption())
587       continue; // ensure nothing populates grid below last channel
588
589     currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
590     noevent.time = ltime;
591     noevent.duration = window_width * 60;
592     noevent.settitle("");
593     paintCell(&noevent, y, DrawStyle::NOPROGRAMME, DrawStyle::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
594     if (currentRow)
595     {
596       thisEvent.setdescription(tr("There are no programme details available for this period"));
597       thisEvent.duration = window_width * 60;
598       thisEvent.time = ltime;
599       thisEvent.settitle(tr("No programme details"));
600       thisEvent.id = 0;
601     }
602     if (eventLista[listIndex])
603     {
604       sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
605       for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
606       {
607         fg = DrawStyle::LIGHTTEXT;
608         event = (*eventLista[listIndex])[e];
609         if (event)
610         {
611           UINT end = event->time + event->duration; // programme end time
612           if(event->time >= UINT(ltime) + (window_width * 60)) // programme starts after RHS of window
613             continue; // that's enough of this channel's events
614           if(end <= UINT(ltime)) // programme ends before LHS of window
615             continue; // this event is before the window - let's try the next event
616           // this event is one we are interested in
617           bg = (swapColour)?DrawStyle::PROGRAMMEA:DrawStyle::PROGRAMMEB; // alternate cell colour
618           swapColour = !swapColour; // it wil be the other colour next time
619           if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
620           {
621             // this is the selected programme
622             thisEvent.setdescription(event->description);
623             thisEvent.duration = event->duration;
624             thisEvent.time = event->time;
625             thisEvent.settitle(event->title);
626             thisEvent.id = event->id;
627             if(thisEvent.id == 0)
628               thisEvent.id = 1;
629             bg = DrawStyle::SELECTHIGHLIGHT; // highlight cell
630             fg = DrawStyle::DARKTEXT;
631           }
632           else
633           {
634             if (currentRow && thisEvent.id == 0)
635             {
636               if (end <= UINT(selTime) && end > UINT(thisEvent.time))
637                 thisEvent.time = end;
638               if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
639                 thisEvent.duration = event->time - thisEvent.time;
640             }
641           }
642           paintCell(event, y, bg, fg);
643         }
644       }
645     }
646     else
647     {
648       // no event list for this channel. Already painted noevent colour so just highlight if selected
649       if (currentRow)
650       {
651         bg = DrawStyle::SELECTHIGHLIGHT; // highlight cell
652         fg = DrawStyle::DARKTEXT;
653         paintCell(&thisEvent, y, bg, fg);
654       }
655       else
656       {
657         bg = DrawStyle::NOPROGRAMME;
658         fg = DrawStyle::LIGHTTEXT;
659         noevent.settitle(tr("No programme details"));
660         paintCell(&noevent, y, bg, fg);
661       }
662     }
663     y += getFontHeight() + 2;
664   }
665   setInfo(&thisEvent);
666 }
667
668 void VEpg::updateEventList()
669 {
670   if (!chanList) return;
671   Channel* chan;
672   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
673   {
674     if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
675       continue;
676     chan = (*chanList)[listTop + listIndex];
677     eventLista[listIndex] = VDR::getInstance()->getChannelSchedule(chan->number, ltime - 1, window_width * 60 + 2); // ltime - 1 to get prog before window (allows cursor left past ltime). + 2 to get prog after window
678   }
679 }
680
681 void VEpg::setCurrentChannel()
682 {
683   chanName.setText((*chanList)[currentChannelIndex]->name);
684   chanName.draw();
685   Region r;
686   chanName.getRootBoxRegion(&r);
687   boxstack->update(this, &r);
688 }
689
690 void VEpg::paintCell(Event* event, int yOffset, const DrawStyle& bg, const DrawStyle& fg)
691 {
692   int w, x, y, h;
693   w = x = 0; // keep compiler happy
694   y =yOffset;
695   h = getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
696   UINT end = event->time + event->duration; // programme end time
697   if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
698   {
699     x = 155; // LHS of window
700     if (end > (UINT(ltime) + (window_width * 60)))
701       w = window_width * MINUTE_SCALE; // spans full 2 hour window
702     else
703       w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
704   }
705   if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (window_width * 60))) // starts within window
706   {
707     x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
708     w = MINUTE_SCALE * event->duration / 60;
709     //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
710      // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
711   }
712   if (w > 155 + window_width * MINUTE_SCALE - x)
713     w = 155 + window_width * MINUTE_SCALE -x; // limit cells to RHS of window
714   rectangle(x, y, w, h, bg);
715   char* tt = new char[strlen(event->title) + 1];
716   strcpy (tt, event->title);
717   float textWidth = 0;
718   unsigned int cur_length=1;
719   unsigned int text_max=strlen(tt);
720   UINT textPos;
721   for (textPos = 0; textPos <text_max; textPos+=cur_length)
722   {
723         wchar_t cur_char=getWChar(tt+textPos,&cur_length);
724     float thisCharWidth = charWidth(cur_char);
725     if (textWidth + thisCharWidth > w) // text will not fit in cell
726     {
727       break;
728     }
729     textWidth += thisCharWidth;
730   }
731   char* tT = new char[textPos+1];
732   if(textPos > 0)
733   {
734     strncpy(tT, tt, textPos );
735     tT[textPos ] =  '\0';
736     surface->drawText(tT, x+2, y, fg);
737   }
738   delete tT;
739
740 }
741
742 time_t VEpg::prevHour(time_t* t)
743 {
744   struct tm* tms;
745   tms = localtime(t);
746   tms->tm_sec = 0;
747   tms->tm_min = 0;
748   return mktime(tms);
749 }
750
751 void VEpg::processMessage(Message* m)
752 {
753   if (m->message == Message::MOUSE_MOVE)
754   {
755     if (chanListbox.mouseMove((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
756     {
757       drawData();
758       boxstack->update(this);
759     }
760   }
761   else if (m->message == Message::MOUSE_LBDOWN)
762   {
763     if (chanListbox.mouseLBDOWN((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
764     {
765       boxstack->handleCommand(Remote::OK); //simulate OK press
766     }
767     else
768     {
769       //check if press is outside this view! then simulate cancel
770       int x=(m->parameter>>16)-getScreenX();
771       int y=(m->parameter&0xFFFF)-getScreenY();
772       int keyx = chanListbox.getRootBoxOffsetX();
773       int keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
774
775       if (x<0 || y <0 || x>(int)getWidth() || y>(int)getHeight())
776       {
777         boxstack->handleCommand(Remote::BACK); //simulate cancel press
778       }
779       else if (x>=(keyx+72) && y>=(keyy+4) &&x<=(keyx+72+104) &&y<=(keyy+4+getFontHeight() + 2))
780       {
781         boxstack->handleCommand(Remote::RED);
782       }
783       else if (x>=(keyx+72) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+72+104) &&y<=(keyy+8+2*getFontHeight() + 2))
784       {
785         boxstack->handleCommand(Remote::GREEN);
786       }
787       else if (x>=(keyx+180) && y>=(keyy+4) &&x<=(keyx+180+104) &&y<=(keyy+4+getFontHeight() + 2))
788       {
789         boxstack->handleCommand(Remote::YELLOW);
790       }
791       else if (x>=(keyx+180) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+180+104) &&y<=(keyy+8+2*getFontHeight() + 2))
792       {
793         boxstack->handleCommand(Remote::BLUE);
794       }
795       else if (x>=(keyx+290) && y>=(keyy+4) &&x<=(keyx+180+290) &&y<=(keyy+4+getFontHeight() + 2))
796       {
797         boxstack->handleCommand(Remote::BACK);
798       }
799       else if (x>=(keyx+290) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+290+180) &&y<=(keyy+8+2*getFontHeight() + 2))
800       {
801         boxstack->handleCommand(Remote::RECORD);
802       }
803       else if (x>=(keyx+474) && y>=(keyy+4) &&x<=(keyx+128+474) &&y<=(keyy+4+getFontHeight() + 2))
804       {
805         boxstack->handleCommand(Remote::PLAY);
806       }
807       else if (x>=(keyx+474) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+238+474) &&y<=(keyy+8+2*getFontHeight() + 2))
808       {
809         boxstack->handleCommand(Remote::GO);
810       }
811       else if ( x>=(chanListbox.getRootBoxOffsetX())
812                 && y>=(chanListbox.getRootBoxOffsetY() + 5)
813                 // &&x<=(chanListbox.getOffsetX()+155 + window_width * MINUTE_SCALE)
814                 &&y<=(chanListbox.getRootBoxOffsetY() - getFontHeight()
815                 - 3+(int)chanListbox.getHeight() + getFontHeight() + 3)
816               )
817       {
818         int cy=y-(chanListbox.getRootBoxOffsetY() + 5);
819         int row=cy/(getFontHeight()+2);
820         int clistTop = chanListbox.getTopOption();
821         chanListbox.hintSetCurrent(clistTop+row);
822         int cx=x-155;
823         time_t ttime = cx*60/MINUTE_SCALE+ltime;
824         //x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
825
826         selTime = ttime;
827         drawData();
828         boxstack->update(this);
829       }
830     }
831   }
832 }
833