]> 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     case Input::BACK:
463     case Input::GUIDE:
464     {
465       return BoxStack::DELETE_ME;
466     }
467     case Input::CHANNELUP:
468     {
469       if (currentChannelIndex == (chanList->size() - 1)) // at the end
470         currentChannelIndex = 0;
471       else
472         ++currentChannelIndex;
473       
474       if (parent)
475       {
476         Message* m = new Message(); // Must be done after this view deleted
477         m->from = this;
478         m->to = parent;
479         m->message = Message::CHANNEL_CHANGE;
480         m->parameter = (*chanList)[currentChannelIndex]->number;
481         MessageQueue::getInstance()->postMessage(m);
482       }
483       
484       setCurrentChannel();
485
486       return BoxStack::COMMAND_HANDLED;
487     }
488     case Input::CHANNELDOWN:
489     {
490       if (currentChannelIndex == 0) // at the start
491         currentChannelIndex = chanList->size() - 1; // so go to end
492       else
493         --currentChannelIndex;
494
495       if (parent)
496       {
497         Message* m = new Message(); // Must be done after this view deleted
498         m->from = this;
499         m->to = parent;
500         m->message = Message::CHANNEL_CHANGE;
501         m->parameter = (*chanList)[currentChannelIndex]->number;
502         MessageQueue::getInstance()->postMessage(m);
503       }
504       
505       setCurrentChannel();
506
507       return BoxStack::COMMAND_HANDLED;
508     }
509   }
510   // stop command getting to any more views
511   return BoxStack::ABANDON_COMMAND;
512 }
513
514 void VEpg::drawgrid() // redraws grid and select programme
515 {
516   // draw the grid of programmes
517   char timeString[20];
518   time_t t;
519   time(&t); // set t = now
520   if(selTime < t)
521     selTime = t; // don't allow cursor in the past
522   if(listTop != chanListbox.getTopOption())
523   {
524   // chanListbox has scrolled TODO speed up by changing only rows that have changed
525     listTop = chanListbox.getTopOption();
526     updateEventList();
527   }
528   if ((selTime >= ltime + (int)window_width * 60) || (selTime <= ltime))
529   {
530   // we have cursored back before left time of window
531   //TODO check that this and above don't happen together
532     ltime = prevHour(&selTime);
533     updateEventList();
534   }
535
536   // draw time scale
537   t = ltime;
538   struct tm tms;
539   LOCALTIME_R(&t, &tms);
540   strftime(timeString, 19, "%a %d %b", &tms);
541   int timey = chanListbox.getRootBoxOffsetY() - getFontHeight() - 3;
542   int timex = 135;
543   drawTextRJ(timeString, timex - 10, timey, DrawStyle::LIGHTTEXT); // print date
544   strftime(timeString, 19, "%H:%M", &tms);
545   drawText(timeString, timex, timey, DrawStyle::LIGHTTEXT); // print left time
546
547   rectangle(155, timey + getFontHeight(), 2, 7, DrawStyle::WHITE);
548   t = t + 3600;
549   LOCALTIME_R(&t, &tms);
550   strftime(timeString, 19, "%H:%M", &tms);
551   drawText(timeString, timex + 180, timey, DrawStyle::LIGHTTEXT); // print middle time
552   rectangle(335, timey + getFontHeight(), 2, 7, DrawStyle::WHITE);
553   t = t + 3600;
554   LOCALTIME_R(&t, &tms);
555   strftime(timeString, 19, "%H:%M", &tms);
556   drawText(timeString, timex + 360, timey, DrawStyle::LIGHTTEXT); // print right time
557   rectangle(515, timey + getFontHeight(), 2, 7, DrawStyle::WHITE);
558   // pointer to selTime
559   //rectangle(155 + (selTime - ltime) / 20, timey + getFontHeight(), 2, 7, DrawStyle(255, 50, 50, 255));
560
561   // current time line
562   time(&t);
563   if ((t >= ltime) && (t < (ltime + 9000)))
564   {
565     rectangle(155 + (t - ltime) / 20, timey + getFontHeight(), 2, ((getFontHeight() + 2) * gridRows) + 7 + 2, DrawStyle::RED);
566   }
567
568   // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
569   Event* event;
570   Event noevent; // an event to use if there are gaps in the epg
571   thisEvent.description = tr("There are no programme details available for this period");
572   thisEvent.duration = window_width * 60;
573   thisEvent.time = ltime;
574   thisEvent.title = tr("No programme details");
575   thisEvent.id = 0;
576   bool swapColour = false; // alternate cell colour
577   bool currentRow = false;
578   int y = chanListbox.getRootBoxOffsetY() + 5; // vertical position of cell
579   DrawStyle bg, fg; // background colour of cells in grid
580   // for each displayed channel, find programmes that fall in 2.5 hour time window
581   for(u4 listIndex = 0; listIndex < gridRows; listIndex++)
582   {
583     if (listTop + (int)listIndex >= chanListbox.getBottomOption())
584       continue; // ensure nothing populates grid below last channel
585
586     currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
587     noevent.time = ltime;
588     noevent.duration = window_width * 60;
589     noevent.title = "";
590     paintCell(&noevent, y, DrawStyle::NOPROGRAMME, DrawStyle::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
591     if (currentRow)
592     {
593       thisEvent.description = tr("There are no programme details available for this period");
594       thisEvent.duration = window_width * 60;
595       thisEvent.time = ltime;
596       thisEvent.title = tr("No programme details");
597       thisEvent.id = 0;
598     }
599     if (eventLista[listIndex])
600     {
601       sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
602       for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
603       {
604         fg = DrawStyle::LIGHTTEXT;
605         event = (*eventLista[listIndex])[e];
606         if (event)
607         {
608           u4 end = event->time + event->duration; // programme end time
609           if(event->time >= u4(ltime) + (window_width * 60)) // programme starts after RHS of window
610             continue; // that's enough of this channel's events
611           if(end <= u4(ltime)) // programme ends before LHS of window
612             continue; // this event is before the window - let's try the next event
613           // this event is one we are interested in
614           bg = (swapColour)?DrawStyle::PROGRAMMEA:DrawStyle::PROGRAMMEB; // alternate cell colour
615           swapColour = !swapColour; // it wil be the other colour next time
616           if(event->time <= u4(selTime) && end > u4(selTime) && currentRow)
617           {
618             // this is the selected programme
619             thisEvent.description = event->description;
620             thisEvent.duration = event->duration;
621             thisEvent.time = event->time;
622             thisEvent.title = event->title;
623             thisEvent.id = event->id;
624             if(thisEvent.id == 0)
625               thisEvent.id = 1;
626             bg = DrawStyle::SELECTHIGHLIGHT; // highlight cell
627             fg = DrawStyle::DARKTEXT;
628           }
629           else
630           {
631             if (currentRow && thisEvent.id == 0)
632             {
633               if (end <= u4(selTime) && end > u4(thisEvent.time))
634                 thisEvent.time = end;
635               if (event->time > u4(selTime) && event->time < thisEvent.time + thisEvent.duration)
636                 thisEvent.duration = event->time - thisEvent.time;
637             }
638           }
639           paintCell(event, y, bg, fg);
640         }
641       }
642     }
643     else
644     {
645       // no event list for this channel. Already painted noevent colour so just highlight if selected
646       if (currentRow)
647       {
648         bg = DrawStyle::SELECTHIGHLIGHT; // highlight cell
649         fg = DrawStyle::DARKTEXT;
650         paintCell(&thisEvent, y, bg, fg);
651       }
652       else
653       {
654         bg = DrawStyle::NOPROGRAMME;
655         fg = DrawStyle::LIGHTTEXT;
656         noevent.title = tr("No programme details");
657         paintCell(&noevent, y, bg, fg);
658       }
659     }
660     y += getFontHeight() + 2;
661   }
662   setInfo(&thisEvent);
663 }
664
665 void VEpg::updateEventList()
666 {
667   if (!chanList) return;
668   Channel* chan;
669   for(u4 listIndex = 0; listIndex < gridRows; listIndex++)
670   {
671     if(listTop + listIndex >= u4(chanListbox.getBottomOption()))
672       continue;
673     chan = (*chanList)[listTop + listIndex];
674     if (eventLista[listIndex])
675     {
676         (eventLista)[listIndex]->clear();
677         delete eventLista[listIndex];
678     }
679     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
680   }
681 }
682
683 void VEpg::setCurrentChannel()
684 {
685   chanName.setText((*chanList)[currentChannelIndex]->name);
686   chanName.draw();
687   Region r;
688   chanName.getRootBoxRegion(&r);
689   boxstack->update(this, &r);
690 }
691
692 void VEpg::paintCell(Event* event, int yOffset, const DrawStyle& bg, const DrawStyle& fg)
693 {
694   int w, x, y, h;
695   w = x = 0; // keep compiler happy
696   y =yOffset;
697   h = getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
698   u4 end = event->time + event->duration; // programme end time
699   if(event->time <= u4(ltime) && end > u4(ltime)) // spans start of displayed window
700   {
701     x = 155; // LHS of window
702     if (end > (u4(ltime) + (window_width * 60)))
703       w = window_width * MINUTE_SCALE; // spans full 2 hour window
704     else
705       w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
706   }
707   if((event->time >= u4(ltime)) && (event->time <= u4(ltime) + (window_width * 60))) // starts within window
708   {
709     x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
710     w = MINUTE_SCALE * event->duration / 60;
711     //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
712      // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
713   }
714   if (w > 155 + (int)window_width * MINUTE_SCALE - x)
715     w = 155 + window_width * MINUTE_SCALE -x; // limit cells to RHS of window
716   rectangle(x, y, w, h, bg);
717   char* tt = new char[strlen(event->title.c_str()) + 1];
718   strcpy (tt, event->title.c_str());
719   float textWidth = 0;
720   unsigned int cur_length=1;
721   unsigned int text_max=strlen(tt);
722   bool mchar=false;
723   if (Osd::getInstance()->charSet()!=1) mchar=true;
724   mbstate_t state;
725   memset((void*)&state,0,sizeof(state));
726
727   u4 textPos;
728   for (textPos = 0; textPos <text_max; textPos+=cur_length)
729   {
730         wchar_t cur_char;
731         if (mchar) {
732                 cur_length = mbrtowc(&cur_char, tt + textPos, text_max-textPos, &state);
733                 if (cur_length <= 0){
734                         break;
735                 }
736         } else cur_char= *(tt+textPos);
737     float thisCharWidth = charWidth(cur_char);
738     if (textWidth + thisCharWidth > w) // text will not fit in cell
739     {
740       break;
741     }
742     textWidth += thisCharWidth;
743   }
744   char* tT = new char[textPos+1];
745   if(textPos > 0)
746   {
747     strncpy(tT, tt, textPos );
748     tT[textPos ] =  '\0';
749     surface->drawText(tT, x+2, y, fg);
750   }
751   delete[] tT;
752
753 }
754
755 time_t VEpg::prevHour(time_t* t)
756 {
757   struct tm tms;
758   LOCALTIME_R(t, &tms);
759   tms.tm_sec = 0;
760   tms.tm_min = 0;
761   return mktime(&tms);
762 }
763
764 void VEpg::processMessage(Message* m)
765 {
766   if (m->message == Message::MOUSE_MOVE)
767   {
768     if (chanListbox.mouseMove(m->parameter - getScreenX(), m->tag - getScreenY()))
769     {
770       drawData();
771       boxstack->update(this);
772     }
773   }
774   else if (m->message == Message::MOUSE_LBDOWN)
775   {
776     if (chanListbox.mouseLBDOWN(m->parameter - getScreenX(), m->tag - getScreenY()))
777     {
778       Input::sendInputKey(Input::OK);
779     }
780     else
781     {
782       //check if press is outside this view! then simulate cancel
783       int x = m->parameter - getScreenX();
784       int y = m->tag - getScreenY();
785       int keyx = chanListbox.getRootBoxOffsetX();
786       int keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
787
788       if (x<0 || y <0 || x>(int)getWidth() || y>(int)getHeight())
789       {
790         Input::sendInputKey(Input::BACK);
791       }
792       else if (x>=(keyx+72) && y>=(keyy+4) &&x<=(keyx+72+104) &&y<=(keyy+4+getFontHeight() + 2))
793       {
794         boxstack->handleCommand(Input::RED);
795       }
796       else if (x>=(keyx+72) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+72+104) &&y<=(keyy+8+2*getFontHeight() + 2))
797           {
798         boxstack->handleCommand(Input::GREEN);
799       }
800       else if (x>=(keyx+180) && y>=(keyy+4) &&x<=(keyx+180+104) &&y<=(keyy+4+getFontHeight() + 2))
801       {
802         boxstack->handleCommand(Input::YELLOW);
803       }
804       else if (x>=(keyx+180) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+180+104) &&y<=(keyy+8+2*getFontHeight() + 2))
805       {
806         boxstack->handleCommand(Input::BLUE);
807       }
808       else if (x>=(keyx+290) && y>=(keyy+4) &&x<=(keyx+180+290) &&y<=(keyy+4+getFontHeight() + 2))
809       {
810         Input::sendInputKey(Input::BACK);
811       }
812       else if (x>=(keyx+290) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+290+180) &&y<=(keyy+8+2*getFontHeight() + 2))
813       {
814         boxstack->handleCommand(Input::RECORD);
815       }
816       else if (x>=(keyx+474) && y>=(keyy+4) &&x<=(keyx+128+474) &&y<=(keyy+4+getFontHeight() + 2))
817       {
818         boxstack->handleCommand(Input::PLAY);
819       }
820       else if (x>=(keyx+474) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+238+474) &&y<=(keyy+8+2*getFontHeight() + 2))
821       {
822         boxstack->handleCommand(Input::GO);
823       }
824       else if ( x>=(chanListbox.getRootBoxOffsetX())
825                 && y>=(chanListbox.getRootBoxOffsetY() + 5)
826                 // &&x<=(chanListbox.getOffsetX()+155 + window_width * MINUTE_SCALE)
827                 &&y<=(chanListbox.getRootBoxOffsetY() - getFontHeight()
828                 - 3+(int)chanListbox.getHeight() + getFontHeight() + 3)
829               )
830       {
831         int cy=y-(chanListbox.getRootBoxOffsetY() + 5);
832         int row=cy/(getFontHeight()+2);
833         int clistTop = chanListbox.getTopOption();
834         chanListbox.hintSetCurrent(clistTop+row);
835         int cx=x-155;
836         time_t ttime = cx*60/MINUTE_SCALE+ltime;
837         //x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
838
839         selTime = ttime;
840         drawData();
841         boxstack->update(this);
842       }
843     }
844   }
845 }
846