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