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