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