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