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