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