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