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