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