]> git.vomp.tv Git - vompclient-marten.git/blob - vepg.cc
Demuxer changes/fixes/improvements
[vompclient-marten.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       chanListbox.down();
357       drawData();
358       boxstack->update(this);
359       return 2;
360     }
361     case Remote::DF_LEFT:
362     case Remote::LEFT:
363     { // cursor left through time
364       selTime = thisEvent.time - 1;
365       drawData();
366       boxstack->update(this);
367       return 2;
368     }
369     case Remote::DF_RIGHT:
370     case Remote::RIGHT:
371     {
372     // cursor right through time
373       selTime = thisEvent.time + thisEvent.duration;
374       drawData();
375       boxstack->update(this);
376       return 2;
377     }
378     case Remote::RED:
379     {
380     // cursor up one page
381       chanListbox.pageUp();
382       drawData();
383       boxstack->update(this);
384       return 2;
385     }
386     case Remote::GREEN:
387     {
388     // cursor down one page
389       chanListbox.pageDown();
390       drawData();
391       boxstack->update(this);
392       return 2;
393     }
394     case Remote::BLUE:
395     {
396     // step forward 24 hours
397       selTime += 24 * 60 * 60;
398       drawData();
399       boxstack->update(this);
400       return 2;
401     }
402     case Remote::YELLOW:
403     {
404     // step forward 24 hours
405       selTime -= 24 * 60 * 60;
406       drawData();
407       boxstack->update(this);
408       return 2;
409     }
410     case Remote::RECORD:
411     {
412       if (!chanList) return 2;
413       Log::getInstance()->log("VEPG", Log::DEBUG, "ID %lu TIME %lu DURATION %lu TITLE %s", thisEvent.id, thisEvent.time, thisEvent.duration, thisEvent.title);
414       VEpgSetTimer* vs = new VEpgSetTimer(&thisEvent, (*chanList)[chanListbox.getCurrentOption()]);
415       vs->draw();
416       boxstack->add(vs);
417       boxstack->update(vs);
418       return 2;
419     }
420     case Remote::PLAY:
421     case Remote::GO:
422     case Remote::OK:
423     {
424       if (!chanList) return 2;
425
426       // select programme and display menu TODO currently just changes to selected channel
427       videoLive->channelChange(VVideoLive::NUMBER, (*chanList)[chanListbox.getCurrentOption()]->number);
428
429       if(command == Remote::GO)
430         return 2;
431       // GO just changes channel in preview, PLAY changes channel and returns to normal TV
432     }
433     case Remote::BACK:
434     case Remote::GUIDE:
435     {
436       // return to normal TV mode
437       if (videoLive) // ptr check done in case being tested from videorec
438       {
439         Message* m = new Message(); // Must be done after this view deleted
440         m->from = this;
441         m->to = videoLive;
442         m->message = Message::EPG_CLOSE;
443         Command::getInstance()->postMessageNoLock(m);
444       }
445       return 4;
446     }
447     case Remote::CHANNELUP:
448     {
449       videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::UP);
450       return 2;
451     }
452     case Remote::CHANNELDOWN:
453     {
454       videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::DOWN);
455       return 2;
456     }
457   }
458   // stop command getting to any more views
459   return 1;
460 }
461
462 void VEpg::drawgrid() // redraws grid and select programme
463 {
464   // draw the grid of programmes
465   char timeString[20];
466   time_t t;
467   time(&t); // set t = now
468   if(selTime < t)
469     selTime = t; // don't allow cursor in the past
470   if(listTop != chanListbox.getTopOption())
471   {
472   // chanListbox has scrolled TODO speed up by changing only rows that have changed
473     listTop = chanListbox.getTopOption();
474     updateEventList();
475   }
476   if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
477   {
478   // we have cursored back before left time of window
479   //TODO check that this and above don't happen together
480     ltime = prevHour(&selTime);
481     updateEventList();
482   }
483   // draw time scale
484   Colour white = Colour(255, 255, 255, 255);
485   
486   t = ltime;
487   struct tm* tms;
488   tms = localtime(&t);
489   strftime(timeString, 19, "%a %e %b", tms);
490   int timey = chanListbox.getRootBoxOffsetY() - Surface::getFontHeight() - 3;
491   int timex = 135;
492   drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
493   strftime(timeString, 19, "%H:%M", tms);
494   drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
495   rectangle(155, timey + Surface::getFontHeight(), 2, 7, white);
496   t = t + 3600;
497   tms = localtime(&t);
498   strftime(timeString, 19, "%H:%M", tms);
499   drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
500   rectangle(335, timey + Surface::getFontHeight(), 2, 7, white);
501   t = t + 3600;
502   tms = localtime(&t);
503   strftime(timeString, 19, "%H:%M", tms);
504   drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
505   rectangle(515, timey + Surface::getFontHeight(), 2, 7, white);
506   // pointer to selTime
507   //rectangle(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, Colour(255, 50, 50, 255));
508
509   // current time line
510   time(&t);
511   if ((t >= ltime) && (t < (ltime + 9000)))
512   {
513     rectangle(155 + (t - ltime) / 20, timey + Surface::getFontHeight(), 2, ((Surface::getFontHeight() + 2) * 7) + 7 + 2, Colour::RED);
514   }
515
516   // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
517   Event* event;
518   Event noevent; // an event to use if there are gaps in the epg
519   thisEvent.setdescription(tr("There are no programme details available for this period"));
520   thisEvent.duration = WINDOW_WIDTH * 60;
521   thisEvent.time = ltime;
522   thisEvent.settitle(tr("No programme details"));
523   thisEvent.id = 0;
524   bool swapColour = FALSE; // alternate cell colour
525   bool currentRow = FALSE;
526   int y = chanListbox.getRootBoxOffsetY() + 5; // vertical position of cell
527   Colour bg, fg; // background colour of cells in grid
528   // for each displayed channel, find programmes that fall in 2.5 hour time window
529   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
530   {
531     if (listTop + (int)listIndex >= chanListbox.getBottomOption())
532       continue; // ensure nothing populates grid below last channel
533     currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
534     noevent.time = ltime;
535     noevent.duration = WINDOW_WIDTH * 60;
536     noevent.settitle("");
537     paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
538     if (currentRow)
539     {
540       thisEvent.setdescription(tr("There are no programme details available for this period"));
541       thisEvent.duration = WINDOW_WIDTH * 60;
542       thisEvent.time = ltime;
543       thisEvent.settitle(tr("No programme details"));
544       thisEvent.id = 0;
545     }
546     if (eventLista[listIndex])
547     {
548       sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
549       for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
550       {
551         fg = Colour::LIGHTTEXT;
552         event = (*eventLista[listIndex])[e];
553         if (event)
554         {
555           UINT end = event->time + event->duration; // programme end time
556           if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
557             continue; // that's enough of this channel's events
558           if(end <= UINT(ltime)) // programme ends before LHS of window
559             continue; // this event is before the window - let's try the next event
560           // this event is one we are interested in
561           bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
562           swapColour = !swapColour; // it wil be the other colour next time
563           if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
564           {
565             // this is the selected programme
566             thisEvent.setdescription(event->description);
567             thisEvent.duration = event->duration;
568             thisEvent.time = event->time;
569             thisEvent.settitle(event->title);
570             thisEvent.id = event->id;
571             if(thisEvent.id == 0)
572               thisEvent.id = 1;
573             bg = Colour::SELECTHIGHLIGHT; // highlight cell
574             fg = Colour::DARKTEXT;
575           }
576           else
577           {
578             if (currentRow && thisEvent.id == 0)
579             {
580               if (end <= UINT(selTime) && end > UINT(thisEvent.time))
581                 thisEvent.time = end;
582               if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
583                 thisEvent.duration = event->time - thisEvent.time;
584             }
585           }
586           paintCell(event, y, bg, fg);
587         }
588       }
589     }
590     else
591     {
592       // no event list for this channel. Already painted noevent colour so just highlight if selected
593       if (currentRow)
594       {
595         bg = Colour::SELECTHIGHLIGHT; // highlight cell
596         fg = Colour::DARKTEXT;
597         paintCell(&thisEvent, y, bg, fg);
598       }
599       else
600       {
601         bg = Colour::NOPROGRAMME;
602         fg = Colour::LIGHTTEXT;
603         noevent.settitle(tr("No programme details"));
604         paintCell(&noevent, y, bg, fg);
605       }
606     }
607     y += Surface::getFontHeight() + 2;
608   }
609   setInfo(&thisEvent);
610 }
611
612 void VEpg::updateEventList()
613 {
614   if (!chanList) return;
615   Channel* chan;
616   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
617   {
618     if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
619       continue;
620     chan = (*chanList)[listTop + listIndex];
621
622     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
623   }
624 }
625
626 void VEpg::setCurrentChannel(char* chname)
627 {
628   chanName.setText(chname);
629   chanName.draw();
630   boxstack->update(this);
631 }
632
633 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
634 {
635   int w, x, y, h;
636   w = x = 0; // keep compiler happy
637
638   y =yOffset;
639   h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
640   UINT end = event->time + event->duration; // programme end time
641   if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
642   {
643     x = 155; // LHS of window
644     if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
645       w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
646     else
647       w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
648   }
649   if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
650   {
651     x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
652     w = MINUTE_SCALE * event->duration / 60;
653     //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
654      // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
655   }
656   if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
657     w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
658   rectangle(x, y, w, h, bg);
659   char* tt = new char[strlen(event->title) + 1];
660   strcpy (tt, event->title);
661   int textWidth = 0;
662   for (UINT textPos = 0; textPos < strlen(tt); textPos++)
663   {
664     int thisCharWidth = surface->getCharWidth(tt[textPos]);
665     if (textWidth + thisCharWidth > w) // text will not fit in cell
666     {
667       textWidth = textPos;
668       break;
669     }
670     textWidth += thisCharWidth;
671   }
672   char* tT = new char[textWidth];
673   if(textWidth > 1)
674   {
675     strncpy(tT, tt, textWidth - 1);
676     tT[textWidth - 1] =  '\0';
677     surface->drawText(tT, x+2, y, fg.rgba());
678   }
679   delete tT;
680
681 }
682
683 time_t VEpg::prevHour(time_t* t)
684 {
685   struct tm* tms;
686   tms = localtime(t);
687   tms->tm_sec = 0;
688   tms->tm_min = 0;
689   return mktime(tms);
690 }
691
692 void VEpg::processMessage(Message* m)
693 {
694   if (m->message == Message::MOUSE_MOVE)
695   {
696     if (chanListbox.mouseMove((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
697     {
698       drawData();
699       boxstack->update(this);
700     }
701   }
702   else if (m->message == Message::MOUSE_LBDOWN)
703   {
704     if (chanListbox.mouseLBDOWN((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
705     {
706       boxstack->handleCommand(Remote::OK); //simulate OK press
707     }
708     else
709     {
710       //check if press is outside this view! then simulate cancel
711       int x=(m->parameter>>16)-getScreenX();
712       int y=(m->parameter&0xFFFF)-getScreenY();
713       int keyx = chanListbox.getRootBoxOffsetX();
714       int keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
715
716       if (x<0 || y <0 || x>(int)getWidth() || y>(int)getHeight())
717       {
718         boxstack->handleCommand(Remote::BACK); //simulate cancel press
719       }
720       else if (x>=(keyx+72) && y>=(keyy+4) &&x<=(keyx+72+104) &&y<=(keyy+4+Surface::getFontHeight() + 2))
721       {
722         boxstack->handleCommand(Remote::RED);
723       }
724       else if (x>=(keyx+72) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+72+104) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
725       {
726         boxstack->handleCommand(Remote::GREEN);
727       }
728       else if (x>=(keyx+180) && y>=(keyy+4) &&x<=(keyx+180+104) &&y<=(keyy+4+Surface::getFontHeight() + 2))
729       {
730         boxstack->handleCommand(Remote::YELLOW);
731       }
732       else if (x>=(keyx+180) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+180+104) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
733       {
734         boxstack->handleCommand(Remote::BLUE);
735       }
736       else if (x>=(keyx+290) && y>=(keyy+4) &&x<=(keyx+180+290) &&y<=(keyy+4+Surface::getFontHeight() + 2))
737       {
738         boxstack->handleCommand(Remote::BACK);
739       }
740       else if (x>=(keyx+290) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+290+180) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
741       {
742         boxstack->handleCommand(Remote::RECORD);
743       }
744       else if (x>=(keyx+474) && y>=(keyy+4) &&x<=(keyx+128+474) &&y<=(keyy+4+Surface::getFontHeight() + 2))
745       {
746         boxstack->handleCommand(Remote::PLAY);
747       }
748       else if (x>=(keyx+474) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+238+474) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
749       {
750         boxstack->handleCommand(Remote::GO);
751       }
752       else if ( x>=(chanListbox.getRootBoxOffsetX())
753                 && y>=(chanListbox.getRootBoxOffsetY() + 5)
754                 // &&x<=(chanListbox.getOffsetX()+155 + WINDOW_WIDTH * MINUTE_SCALE)
755                 &&y<=(chanListbox.getRootBoxOffsetY() - Surface::getFontHeight()
756                 - 3+(int)chanListbox.getHeight() + Surface::getFontHeight() + 3)
757               )
758       {
759         int cy=y-(chanListbox.getRootBoxOffsetY() + 5);
760         int row=cy/(Surface::getFontHeight()+2);
761         int clistTop = chanListbox.getTopOption();
762         chanListbox.hintSetCurrent(clistTop+row);
763         int cx=x-155;
764         time_t ttime = cx*60/MINUTE_SCALE+ltime;
765         //x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
766
767         selTime = ttime;
768         drawData();
769         boxstack->update(this);
770       }
771     }
772   }
773 }