]> git.vomp.tv Git - vompclient.git/blob - vepg.cc
Bitmap and VPictureBanner CWFs
[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 VEpg* VEpg::instance = NULL;
50
51 VEpg::VEpg(void* tparent, UINT tcurrentChannelIndex, ChannelList* tchanList)
52 {
53   instance = this;
54   currentChannelIndex = tcurrentChannelIndex;
55
56   // PAL / NTSC sizes -----------------------
57
58   int xpos, ypos;
59   //int summaryLines, summaryLowerPadding;
60   //int chanNameYpos;
61   int fontHeight=getFontHeight() + 4;
62   //UINT gridRows; // is a class member
63
64
65   if (Video::getInstance()->getFormat() == Video::PAL)
66   {
67     //xsize = 647;
68     //ysize = 541;
69     xpos = 30;
70     ypos = 16;
71
72     //summaryLines = 8;
73
74     //chanNameYpos = 244;
75     gridRows = 7;
76   }
77   else
78   {
79     xpos = 50;
80     ypos = 20;
81     //summaryLines = 6;
82     //summaryLowerPadding = 28;
83     //chanNameYpos = 206;
84     gridRows = 5;
85   }
86   int screenwidthhalf=Video::getInstance()->getScreenWidth()/2;
87   int screenheighthalf=Video::getInstance()->getScreenHeight()/2;
88  // summaryLines = ((float)screenheighthalf)/((float)fontHeight))-1;
89  // summaryLowerPadding = screenheighthalf-summaryLines*(fontHeight);
90   gridRows = (screenheighthalf-fontHeight*3-50)/fontHeight;
91
92
93   // initialise variables and pointers
94   boxstack = BoxStack::getInstance();
95   parent = tparent;
96   eventList = NULL;
97   chanList = tchanList;
98   e = 0;
99
100   eventLista.resize(gridRows);
101   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
102   {
103     // initialise array of pointers to eventlist structures
104     eventLista[listIndex] = NULL;
105   }
106
107   // Create pallet on which to paint our epg view and position it in centre of screen.
108   // Need to reduce size to deal with overscanning TVs.
109
110   setSize(Video::getInstance()->getScreenWidth(), Video::getInstance()->getScreenHeight());
111   createBuffer();
112   setPosition(0, 0);
113
114   // beautify
115 //  DrawStyle transparent = DrawStyle(0, 0, 0, 0);
116 //  setBackgroundColour(transparent);
117
118 //  progTitle.setSurface(surface);
119   progTitle.setPosition(0,0);
120   progTitle.setSize(screenwidthhalf,(fontHeight) * 1 + ypos); //paragraph line seperation is 4 pixels
121   progTitle.setBackgroundColour(DrawStyle::TITLEBARBACKGROUND);
122   progTitle.setTextPos(xpos, ypos);
123   progTitle.setGap(4);
124   progTitle.setParaMode(false);
125   add(&progTitle);
126
127 //  progInfo.setSurface(surface);
128   progInfo.setBackgroundColour(DrawStyle::VIEWBACKGROUND);
129   progInfo.setPosition(0, progTitle.getY2());
130   progInfo.setSize(screenwidthhalf,screenheighthalf-progTitle.getY2()+10);
131   progInfo.setTextPos(xpos, 0);
132   progInfo.setGap(4);
133   add(&progInfo);
134
135 //  chanName.setSurface(surface);
136   chanName.setSize(510, (fontHeight));
137   chanName.setPosition(screenwidthhalf, screenheighthalf - 2*fontHeight);
138   DrawStyle t1(0, 0, 0, 90);
139   chanName.setBackgroundColour(t1);
140   chanName.setParaMode(false);
141   add(&chanName);
142
143   // create area to display list of channels
144 //  chanListbox.setSurface(surface); // add channel list
145   chanListbox.setPosition(0, progInfo.getY2() + fontHeight + 4); // position channel list
146   chanListbox.setSize(150, ((getFontHeight() + 2) * gridRows) + 5); //listbox line seperation is 2 pixels
147   chanListbox.setGap(2);
148   chanListbox.addColumn(xpos);
149   add(&chanListbox);
150
151   // populate channel list
152   if (chanList)
153   {
154     Channel* chan;
155     int first = 1;
156     for (UINT i = 0; i < chanList->size(); i++)
157     {
158       chan = (*chanList)[i];
159       if (i == currentChannelIndex)
160         first = 1;
161       chan->index = chanListbox.addOption(chan->name, 0, first);
162       first = 0;
163     }
164     chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
165   }
166
167   window_x= chanListbox.getRootBoxOffsetX() + chanListbox.getWidth() + 5;
168   window_width=(Video::getInstance()->getScreenWidth() - window_x +3)/3;
169
170   listTop = chanListbox.getTopOption();
171   chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
172   time(&ltime); // set ltime to now
173   ltime = prevHour(&ltime); // set ltime to previous hour TODO make this half hour?
174   time(&selTime); // set selTime to now
175   updateEventList(); // get list of programmes
176
177   vdisplay.mode=Window;
178   vdisplay.fallbackMode=Quarter;
179   vdisplay.x=Video::getInstance()->getScreenWidth()/2;
180   vdisplay.y=10;
181   vdisplay.width=Video::getInstance()->getScreenWidth()/2;
182   vdisplay.height=Video::getInstance()->getScreenHeight()/2;
183 }
184
185 void VEpg::preDelete()
186 {
187   Timers::getInstance()->cancelTimer(this, 1);
188 }
189
190 VEpg::~VEpg()
191 {
192
193   instance = NULL;
194
195   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
196   {
197     if (eventLista[listIndex])
198     {
199       (eventLista)[listIndex]->clear();
200       delete eventLista[listIndex];
201     }
202   }
203   //  delete [] eventLista; // FIXME
204
205   // destroy dynamically allocated memory
206 }
207
208 VEpg* VEpg::getInstance()
209 {
210   return instance;
211 }
212
213 void VEpg::setInfo(Event* event)
214 {
215   time_t t;
216   struct tm btime; // to hold programme start and end time
217   char timeString[9]; // to hold programme start and end time
218   int length = strlen(event->title.c_str()); // calculate length of programme title string
219   char* title = new char[length + 15]; // create string to hold start time, end time and programme title
220   time_t eventtime = event->time;
221   LOCALTIME_R(&eventtime, &btime); //get programme start time
222 #ifndef _MSC_VER
223   strftime(timeString, 9, "%0H:%0M - ", &btime); // and format it as hh:mm -
224 #else
225   strftime(timeString, 9, "%H:%M - ", &btime); // and format it as hh:mm -
226 #endif
227   strcpy(title, timeString); // put it in our buffer
228   t = event->time + event->duration; //get programme end time
229   LOCALTIME_R(&t, &btime);
230 #ifndef _MSC_VER
231   strftime(timeString, 7, "%0H:%0M ", &btime); // and format it as hh:mm -
232 #else
233   strftime(timeString, 7, "%H:%M ", &btime); // and format it as hh:mm -
234 #endif
235   strcat(title, timeString); // put it in our buffer
236
237   strcat(title, event->title.c_str()); // then add the programme title
238   progTitle.setText(title); // sput this sring in our text box
239   length = event->description.length();
240   char* info = new char[length + 1]; // create programme detail string
241   strcpy(info, event->description.c_str());
242   progInfo.setText(info); // show programme detail string
243 // destroy dynamically allocated memory
244   delete[] info;
245   delete[] title;
246 }
247
248 void VEpg::draw()
249 {
250 //  View::draw(); // draw pallet
251   // beautify
252   DrawStyle transparent = DrawStyle(0, 0, 0, 0);
253   fillColour(transparent);
254   
255   
256   // Moved all the dynamic data drawing to a seperate function
257
258   // Display the status and key stuff at the bottom
259
260   UINT keyx = chanListbox.getRootBoxOffsetX() + chanListbox.getColumn(0);
261   UINT keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
262   rectangle (0, keyy, Video::getInstance()->getScreenWidth() ,
263                   Video::getInstance()->getScreenHeight()-keyy, DrawStyle::DARKGREY);
264
265   WSymbol w;
266   TEMPADD(&w);
267   
268   w.nextSymbol = WSymbol::LEFTARROW;
269   w.setPosition(keyx + 1, keyy + 20);
270   w.draw();
271
272   w.nextSymbol = WSymbol::UP;
273   w.setPosition(keyx + 26, keyy + 3);
274   w.draw();
275
276   w.nextSymbol = WSymbol::DOWN;
277   w.setPosition(keyx + 26, keyy + 36);
278   w.draw();
279
280   w.nextSymbol = WSymbol::RIGHTARROW;
281   w.setPosition(keyx + 50, keyy + 20);
282   w.draw();
283
284   drawTextCentre(tr("OK"), keyx + 35, keyy + 20, DrawStyle::LIGHTTEXT);
285
286   rectangle(keyx + 72, keyy + 4, 104, getFontHeight() + 2, DrawStyle::RED);
287   drawText(tr("Page up"), keyx + 74, keyy + 5, DrawStyle::LIGHTTEXT);
288
289   rectangle(keyx + 72, keyy + getFontHeight() + 8, 104, getFontHeight() + 2, DrawStyle::GREEN);
290   drawText(tr("Page down"), keyx + 74, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
291
292   rectangle(keyx + 180, keyy + 4, 104, getFontHeight() + 2, DrawStyle::YELLOW);
293   drawText(tr("-24 hours"), keyx + 182, keyy + 5, DrawStyle::LIGHTTEXT);
294
295   rectangle(keyx + 180, keyy + getFontHeight() + 8, 104, getFontHeight() + 2, DrawStyle::BLUE);
296   drawText(tr("+24 hours"), keyx + 182, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
297
298   rectangle(keyx + 290, keyy + 4, 180, getFontHeight() + 2, DrawStyle::GREY);
299   drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, DrawStyle::LIGHTTEXT);
300
301   rectangle(keyx + 290, keyy + getFontHeight() + 8, 180, getFontHeight() + 2, DrawStyle::GREY);
302   DrawStyle red = DrawStyle(130, 0, 0);
303   drawText(tr("Rec: Set timer"), keyx + 292, keyy + getFontHeight() + 9, red);
304
305   rectangle(keyx + 474, keyy + 4, 128, getFontHeight() + 2, DrawStyle::GREY);
306   w.nextSymbol = WSymbol::PLAY;
307   w.setPosition(keyx + 476, keyy + 5);
308   w.draw();
309   drawText(tr("Sel channel"), keyx + 496, keyy + 5, DrawStyle::LIGHTTEXT);
310
311   rectangle(keyx + 474, keyy + getFontHeight() + 8, 128, getFontHeight() + 2, DrawStyle::GREY);
312   drawText(tr("Go: Preview"), keyx + 476, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
313
314
315   // Draw all the dynamic data
316   drawData();
317 }
318
319 void VEpg::drawData()
320 {
321   // Not doing View::draw() every time causes
322   // things not to be cleared off the surface properly
323   // So, blank out the data area first
324   //int screenwidth=Video::getInstance()->getScreenWidth();
325   rectangle(
326     chanListbox.getRootBoxOffsetX(),
327     chanListbox.getRootBoxOffsetY() - getFontHeight() - 3,
328     window_width * MINUTE_SCALE,
329     chanListbox.getHeight() + getFontHeight() + 4,
330     DrawStyle::BLACK);
331
332   chanListbox.draw();
333   drawgrid();
334   chanName.draw(); // TODO this should be dealt with by vvideolive
335
336   progTitle.draw();
337   progInfo.draw();
338
339   // Set timer to redraw to move the current time bar
340   time_t t, dt;
341   time(&t);
342   dt = 60 - (t % 60);
343   if (dt == 0) dt = 60;
344   dt += t;
345   Timers::getInstance()->setTimerT(this, 1, dt);
346 }
347
348 void VEpg::timercall(int /*clientReference*/)
349 {
350   drawData();
351   boxstack->update(this);
352 }
353
354 int VEpg::handleCommand(int command)
355 {
356   switch(command)
357   {
358     case Input::UP:
359     { // cursor up the channel list
360       chanListbox.up();
361       drawData();
362       boxstack->update(this);
363       return 2;
364     }
365     case Input::DOWN:
366     { // cursor down the channel list
367       Log::getInstance()->log("VEPG", Log::DEBUG, "Down start");
368       
369       chanListbox.down();
370       drawData();
371       boxstack->update(this);
372       Log::getInstance()->log("VEPG", Log::DEBUG, "Down end");
373
374       return 2;
375     }
376     case Input::LEFT:
377     { // cursor left through time
378       selTime = thisEvent.time - 1;
379       drawData();
380       boxstack->update(this);
381       return 2;
382     }
383     case Input::RIGHT:
384     {
385     // cursor right through time
386       selTime = thisEvent.time + thisEvent.duration;
387       drawData();
388       boxstack->update(this);
389       return 2;
390     }
391     case Input::RED:
392     {
393     // cursor up one page
394       chanListbox.pageUp();
395       drawData();
396       boxstack->update(this);
397       return 2;
398     }
399     case Input::GREEN:
400     {
401     // cursor down one page
402       chanListbox.pageDown();
403       drawData();
404       boxstack->update(this);
405       return 2;
406     }
407     case Input::BLUE:
408     {
409     // step forward 24 hours
410       selTime += 24 * 60 * 60;
411       drawData();
412       boxstack->update(this);
413       return 2;
414     }
415     case Input::YELLOW:
416     {
417     // step forward 24 hours
418       selTime -= 24 * 60 * 60;
419       drawData();
420       boxstack->update(this);
421       return 2;
422     }
423     case Input::RECORD:
424     {
425       if (!chanList) return 2;
426       Log::getInstance()->log("VEPG", Log::DEBUG, "ID %lu TIME %lu DURATION %lu TITLE %s", thisEvent.id, thisEvent.time, thisEvent.duration, thisEvent.title);
427       VEpgSetTimer* vs = new VEpgSetTimer(&thisEvent, (*chanList)[chanListbox.getCurrentOption()]);
428       vs->draw();
429       boxstack->add(vs);
430       boxstack->update(vs);
431       return 2;
432     }
433     case Input::PLAY:
434     case Input::GO:
435     case Input::OK:
436     {
437       if (!chanList) return 2;
438
439       // select programme and display menu TODO currently just changes to selected channel
440
441       currentChannelIndex = chanListbox.getCurrentOption();
442
443       if (parent)
444       {
445         Message* m = new Message(); // Must be done after this view deleted
446         m->from = this;
447         m->to = parent;
448         m->message = Message::CHANNEL_CHANGE;
449         m->parameter = (*chanList)[currentChannelIndex]->number;
450         MessageQueue::getInstance()->postMessage(m);
451       }
452       
453       setCurrentChannel();
454
455       if(command == Input::GO)
456         return 2;
457       // GO just changes channel in preview, PLAY changes channel and returns to normal TV
458     }
459     case Input::BACK:
460     case Input::GUIDE:
461     {
462       return 4;
463     }
464     case Input::CHANNELUP:
465     {
466       if (currentChannelIndex == (chanList->size() - 1)) // at the end
467         currentChannelIndex = 0;
468       else
469         ++currentChannelIndex;
470       
471       if (parent)
472       {
473         Message* m = new Message(); // Must be done after this view deleted
474         m->from = this;
475         m->to = parent;
476         m->message = Message::CHANNEL_CHANGE;
477         m->parameter = (*chanList)[currentChannelIndex]->number;
478         MessageQueue::getInstance()->postMessage(m);
479       }
480       
481       setCurrentChannel();
482
483       return 2;
484     }
485     case Input::CHANNELDOWN:
486     {
487       if (currentChannelIndex == 0) // at the start
488         currentChannelIndex = chanList->size() - 1; // so go to end
489       else
490         --currentChannelIndex;
491
492       if (parent)
493       {
494         Message* m = new Message(); // Must be done after this view deleted
495         m->from = this;
496         m->to = parent;
497         m->message = Message::CHANNEL_CHANGE;
498         m->parameter = (*chanList)[currentChannelIndex]->number;
499         MessageQueue::getInstance()->postMessage(m);
500       }
501       
502       setCurrentChannel();
503
504       return 2;
505     }
506   }
507   // stop command getting to any more views
508   return 1;
509 }
510
511 void VEpg::drawgrid() // redraws grid and select programme
512 {
513   // draw the grid of programmes
514   char timeString[20];
515   time_t t;
516   time(&t); // set t = now
517   if(selTime < t)
518     selTime = t; // don't allow cursor in the past
519   if(listTop != chanListbox.getTopOption())
520   {
521   // chanListbox has scrolled TODO speed up by changing only rows that have changed
522     listTop = chanListbox.getTopOption();
523     updateEventList();
524   }
525   if ((selTime >= ltime + (int)window_width * 60) || (selTime <= ltime))
526   {
527   // we have cursored back before left time of window
528   //TODO check that this and above don't happen together
529     ltime = prevHour(&selTime);
530     updateEventList();
531   }
532   // draw time scale
533   DrawStyle white = DrawStyle(255, 255, 255, 255);
534   
535
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, 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, 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, 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       boxstack->handleCommand(Input::OK); //simulate OK press
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         boxstack->handleCommand(Input::BACK); //simulate cancel press
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         boxstack->handleCommand(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