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