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