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