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