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