]> git.vomp.tv Git - vompclient.git/blob - vepg.cc
Big code cleanup
[vompclient.git] / vepg.cc
1 /*\r
2     Copyright 2005 Brian Walton\r
3 \r
4     This file is part of VOMP.\r
5 \r
6     VOMP is free software; you can redistribute it and/or modify\r
7     it under the terms of the GNU General Public License as published by\r
8     the Free Software Foundation; either version 2 of the License, or\r
9     (at your option) any later version.\r
10 \r
11     VOMP is distributed in the hope that it will be useful,\r
12     but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14     GNU General Public License for more details.\r
15 \r
16     You should have received a copy of the GNU General Public License\r
17     along with VOMP; if not, write to the Free Software\r
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
19 */\r
20 /*\r
21     vepg presents a 2 dimensional electronic programme guide with channels down\r
22     the y axis and time along the x axis.\r
23     Programmes are layed on the x axis as alterate coloured blocks with as much\r
24     of the programme title as will fit inside the block shown as text.\r
25     Up and down commands step through the channels whilst left and right commands\r
26     move through the programmes of the currently selected channel.\r
27     When a programme is selected, it highlights in the grid and full programe details\r
28     (start time, title and description) are displayed in an area at te top left of the screen.\r
29     Any currently programmed timers will display in the grid and in the orogramme detail window as red\r
30     It is possible to select a programme to be recorded by pressing the record button.\r
31     The video stream currently being viewed is shown as quarter screen in the top right.\r
32 */\r
33 \r
34 #include "vepg.h"\r
35
36 VEpg* VEpg::instance = NULL;
37 \r
38 VEpg::VEpg(VVideoLive* v, UINT currentChannel)\r
39 {\r
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();\r
75   videoLive = v;
76   eventList = NULL;
77   chanList = VDR::getInstance()->getChannelsList(VDR::VIDEO); //TODO want to be able to display video and radio together
78   e = 0;
79 \r
80   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)\r
81   {\r
82     // initialise array of pointers to eventlist structures\r
83     eventLista[listIndex] = NULL;\r
84   }\r
85 \r
86   // Create pallet on which to paint our epg view and position it in centre of screen.\r
87   // Need to reduce size to deal with overscanning TVs.\r
88
89   create(xsize, ysize);\r
90   setScreenPos(xpos, ypos);\r
91 \r
92   // beautify\r
93   Colour transparent = Colour(0, 0, 0, 0);\r
94   setBackgroundColour(transparent);\r
95
96   progTitle.setSurface(surface);\r
97   progTitle.setSurfaceOffset(0,0);\r
98   progTitle.setDimensions(300,(Surface::getFontHeight() + 4) * 2 + 16); //paragraph line seperation is 4 pixels\r
99   progTitle.setBackgroundColour(Colour::TITLEBARBACKGROUND);\r
100   progTitle.setTextPos(5, 16);
101   progTitle.setGap(4);
102
103   progInfo.setSurface(surface);\r
104   progInfo.setSurfaceOffset(0, progTitle.getOffsetY() + progTitle.getHeight());\r
105   progInfo.setDimensions(300,((Surface::getFontHeight() + 4) * summaryLines) + summaryLowerPadding);\r
106   progInfo.setGap(4);
107
108   chanName.setSurface(surface);\r
109   chanName.setDimensions(510, (Surface::getFontHeight() + 4));\r
110   chanName.setSurfaceOffset(305, chanNameYpos);\r
111   chanName.setBackgroundColour(Colour(0, 0, 0, 90));\r
112
113   // create area to display list of channels\r
114   chanListbox.setSurface(surface); // add channel list\r
115   chanListbox.setSurfaceOffset(0, progInfo.getOffsetY() + progInfo.getHeight() + Surface::getFontHeight() + 8); // position channel list\r
116   chanListbox.setDimensions(150, ((Surface::getFontHeight() + 2) * gridRows) + 5); //listbox line seperation is 2 pixels\r
117   chanListbox.setGap(2);
118
119
120   // populate channel list\r
121   Channel* chan;\r
122   int first = 1;\r
123   for (UINT i = 0; i < chanList->size(); i++)\r
124   {\r
125     chan = (*chanList)[i];\r
126     if (i == currentChannel)\r
127       first = 1;\r
128     chan->index = chanListbox.addOption(chan->name, first);\r
129     first = 0;\r
130   }\r
131   listTop = chanListbox.getTopOption();\r
132   chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work\r
133   chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);\r
134   time(&ltime); // set ltime to now\r
135   ltime = prevHour(&ltime); // set ltime to previous hour TODO make this half hour?\r
136   time(&selTime); // set selTime to now\r
137   updateEventList(); // get list of programmes\r
138 }\r
139 \r
140 VEpg::~VEpg()\r
141 {\r
142   instance = NULL;
143
144   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)\r
145   {\r
146     if (eventLista[listIndex])\r
147     {\r
148       (eventLista)[listIndex]->clear();\r
149       delete eventLista[listIndex];\r
150     }\r
151   }\r
152   //  delete [] eventLista;\r
153 \r
154   // destroy dynamically allocated memory\r
155 }\r
156
157 VEpg* VEpg::getInstance()
158 {
159   return instance;
160 }
161 \r
162 void VEpg::setInfo(Event* event)\r
163 {\r
164   time_t t;\r
165   struct tm* btime; // to hold programme start and end time\r
166   char* timeString = new char[9]; // to hold programme start and end time\r
167   int length = strlen(event->title); // calculate length of programme title string\r
168   char* title = new char[length + 15]; // create string to hold start time, end time and programme title\r
169   btime = localtime((time_t*)&event->time); //get programme start time\r
170   strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -\r
171   strcpy(title, timeString); // put it in our buffer\r
172   t = event->time + event->duration; //get programme end time\r
173   btime = localtime(&t);\r
174   strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -\r
175   strcat(title, timeString); // put it in our buffer\r
176   strcat(title, event->title); // then add the programme title\r
177   progTitle.setText(title); // sput this sring in our text box\r
178   length = strlen(event->description);\r
179   char* info = new char[length + 1]; // create programme detail string\r
180   strcpy(info, event->description);\r
181   progInfo.setText(info); // show programme detail string\r
182 // destroy dynamically allocated memory\r
183   delete info;\r
184   delete title;\r
185   delete timeString;\r
186 }\r
187 \r
188 void VEpg::draw()\r
189 {\r
190   View::draw(); // draw pallet\r
191 \r
192   // Moved all the dynamic data drawing to a seperate function\r
193 \r
194   // Display the status and key stuff at the bottom\r
195   int keyx = chanListbox.getOffsetX();\r
196   int keyy = chanListbox.getOffsetY() + chanListbox.getHeight() + 2;\r
197   surface->fillblt(keyx, keyy, 605, Surface::getFontHeight() * 2 + 14, surface->rgba(100, 100, 100, 255));\r
198
199   WSymbol w;\r
200   w.setSurface(surface);\r
201 \r
202   w.nextSymbol = WSymbol::LEFTARROW;\r
203   w.setSurfaceOffset(keyx + 1, keyy + 20);\r
204   w.draw();\r
205 \r
206   w.nextSymbol = WSymbol::UP;\r
207   w.setSurfaceOffset(keyx + 26, keyy + 3);\r
208   w.draw();\r
209 \r
210   w.nextSymbol = WSymbol::DOWN;\r
211   w.setSurfaceOffset(keyx + 26, keyy + 36);\r
212   w.draw();\r
213 \r
214   w.nextSymbol = WSymbol::RIGHTARROW;\r
215   w.setSurfaceOffset(keyx + 50, keyy + 20);\r
216   w.draw();\r
217 \r
218   drawText(tr("OK"), keyx + 18, keyy + 20, Colour::LIGHTTEXT);\r
219 \r
220   surface->fillblt(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 0, 0, 255));\r
221   drawText(tr("Page up"), keyx + 74, keyy + 5, Colour::LIGHTTEXT);\r
222 \r
223   surface->fillblt(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba(0, 200, 0, 255));\r
224   drawText(tr("Page down"), keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);\r
225 \r
226   surface->fillblt(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 200, 0, 255));\r
227   drawText(tr("-24 hours"), keyx + 182, keyy + 5, Colour::LIGHTTEXT);\r
228 \r
229   surface->fillblt(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba( 0, 0, 200, 255));\r
230   drawText(tr("+24 hours"), keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);\r
231 \r
232   surface->fillblt(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));\r
233   drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, Colour::LIGHTTEXT);\r
234 \r
235   surface->fillblt(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));\r
236 //  Colour red = Colour(130, 0, 0);\r
237 //  drawText(tr("Rec: Set timer"), keyx + 292, keyy + Surface::getFontHeight() + 9, red);\r
238 \r
239   surface->fillblt(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));\r
240   w.nextSymbol = WSymbol::PLAY;\r
241   w.setSurfaceOffset(keyx + 476, keyy + 5);\r
242   w.draw();\r
243   drawText(tr("Sel channel"), keyx + 496, keyy + 5, Colour::LIGHTTEXT);\r
244 \r
245   surface->fillblt(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));\r
246   drawText(tr("Go: Preview"), keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);\r
247 \r
248   // Draw all the dynamic data\r
249   drawData();\r
250 }\r
251 \r
252 void VEpg::drawData()\r
253 {\r
254   // Not doing View::draw() every time causes\r
255   // things not to be cleared off the surface properly\r
256   // So, blank out the data area first\r
257 \r
258   rectangle(\r
259     chanListbox.getOffsetX(),\r
260     chanListbox.getOffsetY() - Surface::getFontHeight() - 3,\r
261     155 + WINDOW_WIDTH * MINUTE_SCALE,\r
262     chanListbox.getHeight() + Surface::getFontHeight() + 3,\r
263     Colour::BLACK);\r
264 \r
265   chanListbox.draw();\r
266   drawgrid();\r
267   chanName.draw(); // TODO this should be dealt with by vvideolive\r
268   progTitle.draw();\r
269   progInfo.draw();\r
270 }
271
272 \r
273 int VEpg::handleCommand(int command)\r
274 {\r
275   switch(command)\r
276   {\r
277     case Remote::DF_UP:\r
278     case Remote::UP:\r
279     { // cursor up the channel list\r
280       chanListbox.up();\r
281       drawData();\r
282       viewman->updateView(this);\r
283       return 2;\r
284     }\r
285     case Remote::DF_DOWN:\r
286     case Remote::DOWN:\r
287     { // cursor down the channel list\r
288       chanListbox.down();\r
289       drawData();\r
290       viewman->updateView(this);\r
291       return 2;\r
292     }\r
293     case Remote::DF_LEFT:\r
294     case Remote::LEFT:\r
295     { // cursor left through time\r
296       selTime = thisEvent.time - 1;\r
297       drawData();\r
298       viewman->updateView(this);\r
299       return 2;\r
300     }\r
301     case Remote::DF_RIGHT:\r
302     case Remote::RIGHT:\r
303     {\r
304     // cursor right through time\r
305       selTime = thisEvent.time + thisEvent.duration;\r
306       drawData();\r
307       viewman->updateView(this);\r
308       return 2;\r
309     }\r
310     case Remote::RED:\r
311     {\r
312     // cursor up one page\r
313       chanListbox.pageUp();\r
314       drawData();\r
315       viewman->updateView(this);\r
316       return 2;\r
317     }\r
318     case Remote::GREEN:\r
319     {\r
320     // cursor down one page\r
321       chanListbox.pageDown();\r
322       drawData();\r
323       viewman->updateView(this);\r
324       return 2;\r
325     }\r
326     case Remote::BLUE:\r
327     {\r
328     // step forward 24 hours\r
329       selTime += 24 * 60 * 60;\r
330       drawData();\r
331       viewman->updateView(this);\r
332       return 2;\r
333     }\r
334     case Remote::YELLOW:\r
335     {\r
336     // step forward 24 hours\r
337       selTime -= 24 * 60 * 60;\r
338       drawData();\r
339       viewman->updateView(this);\r
340       return 2;\r
341     }\r
342     case Remote::RECORD:\r
343     {\r
344       //TODO FIXME\r
345       return 2;\r
346     }\r
347     case Remote::PLAY:\r
348     case Remote::GO:\r
349     case Remote::OK:\r
350     { // select programme and display menu TODO currently just changes to selected channel\r
351       videoLive->channelChange(VVideoLive::NUMBER, (*chanList)[chanListbox.getCurrentOption()]->number);
352
353       if(command == Remote::GO)\r
354         return 2;\r
355       // GO just changes channel in preview, PLAY changes channel and returns to normal TV\r
356     }\r
357     case Remote::BACK:\r
358     case Remote::GUIDE:\r
359     {\r
360       // return to normal TV mode\r
361       if (videoLive) // ptr check done in case being tested from videorec\r
362       {\r
363         Message* m = new Message(); // Must be done after this view deleted
364         m->from = this;
365         m->to = videoLive;
366         m->message = Message::EPG_CLOSE;
367         ViewMan::getInstance()->postMessage(m);
368       }
369       return 4;\r
370     }\r
371     case Remote::CHANNELUP:
372     {
373       videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::UP);
374       return 2;
375     }
376     case Remote::CHANNELDOWN:
377     {
378       videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::DOWN);
379       return 2;
380     }
381   }\r
382   // stop command getting to any more views\r
383   return 1;\r
384 }\r
385 \r
386 void VEpg::drawgrid() // redraws grid and select programme\r
387 {\r
388   // draw the grid of programmes\r
389   char timeString[20];\r
390   time_t t;\r
391   time(&t); // set t = now\r
392   if(selTime < t)\r
393     selTime = t; // don't allow cursor in the past\r
394   if(listTop != chanListbox.getTopOption())\r
395   {\r
396   // chanListbox has scrolled TODO speed up by changing only rows that have changed\r
397     listTop = chanListbox.getTopOption();\r
398     updateEventList();\r
399   }\r
400   if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))\r
401   {\r
402   // we have cursored back before left time of window\r
403   //TODO check that this and above don't happen together\r
404     ltime = prevHour(&selTime);\r
405     updateEventList();\r
406   }\r
407   // draw time scale\r
408   t = ltime;\r
409   struct tm* tms;\r
410   tms = localtime(&t);\r
411   strftime(timeString, 19, "%a %e %b", tms);\r
412   int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;\r
413   int timex = 135;\r
414   drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date\r
415   strftime(timeString, 19, "%H:%M", tms);\r
416   drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time\r
417   surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));\r
418   t = t + 3600;\r
419   tms = localtime(&t);\r
420   strftime(timeString, 19, "%H:%M", tms);\r
421   drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time\r
422   surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));\r
423   t = t + 3600;\r
424   tms = localtime(&t);\r
425   strftime(timeString, 19, "%H:%M", tms);\r
426   drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time\r
427   surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));\r
428   // pointer to selTime\r
429   surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255));\r
430 \r
431   // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?\r
432   Event* event;\r
433   Event noevent; // an event to use if there are gaps in the epg\r
434   thisEvent.setdescription(tr("There are no programme details available for this period"));\r
435   thisEvent.duration = WINDOW_WIDTH * 60;\r
436   thisEvent.time = ltime;\r
437   thisEvent.settitle(tr("No programme details"));\r
438   thisEvent.id = 0;\r
439   bool swapColour = FALSE; // alternate cell colour\r
440   bool currentRow = FALSE;\r
441   int y = chanListbox.getOffsetY() + 5; // vertical position of cell\r
442   Colour bg, fg; // background colour of cells in grid\r
443   // for each displayed channel, find programmes that fall in 2.5 hour time window\r
444   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)\r
445   {\r
446     if (listTop + (int)listIndex >= chanListbox.getBottomOption())\r
447       continue; // ensure nothing populates grid below last channel\r
448     currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());\r
449     noevent.time = ltime;\r
450     noevent.duration = WINDOW_WIDTH * 60;\r
451     noevent.settitle("");\r
452     paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes\r
453     if (currentRow)\r
454     {\r
455       thisEvent.setdescription(tr("There are no programme details available for this period"));\r
456       thisEvent.duration = WINDOW_WIDTH * 60;\r
457       thisEvent.time = ltime;\r
458       thisEvent.settitle(tr("No programme details"));\r
459       thisEvent.id = 0;\r
460     }\r
461     if (eventLista[listIndex])\r
462     {\r
463       sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());\r
464       for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel\r
465       {\r
466         fg = Colour::LIGHTTEXT;\r
467         event = (*eventLista[listIndex])[e];\r
468         if (event)\r
469         {\r
470           UINT end = event->time + event->duration; // programme end time\r
471           if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window\r
472             continue; // that's enough of this channel's events\r
473           if(end <= UINT(ltime)) // programme ends before LHS of window\r
474             continue; // this event is before the window - let's try the next event\r
475           // this event is one we are interested in\r
476           bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour\r
477           swapColour = !swapColour; // it wil be the other colour next time\r
478           if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)\r
479           {\r
480             // this is the selected programme\r
481             thisEvent.setdescription(event->description);\r
482             thisEvent.duration = event->duration;\r
483             thisEvent.time = event->time;\r
484             thisEvent.settitle(event->title);\r
485             thisEvent.id = event->id;\r
486             if(thisEvent.id == 0)\r
487               thisEvent.id = 1;\r
488             bg = Colour::SELECTHIGHLIGHT; // highlight cell\r
489             fg = Colour::DARKTEXT;\r
490           }\r
491           else\r
492           {\r
493             if (currentRow && thisEvent.id == 0)\r
494             {\r
495               if (end <= UINT(selTime) && end > UINT(thisEvent.time))\r
496                 thisEvent.time = end;\r
497               if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)\r
498                 thisEvent.duration = event->time - thisEvent.time;\r
499             }\r
500           }\r
501           paintCell(event, y, bg, fg);\r
502         }\r
503       }\r
504     }\r
505     else\r
506     {\r
507       // no event list for this channel. Already painted noevent colour so just highlight if selected\r
508       if (currentRow)\r
509       {\r
510         bg = Colour::SELECTHIGHLIGHT; // highlight cell\r
511         fg = Colour::DARKTEXT;\r
512         paintCell(&thisEvent, y, bg, fg);\r
513       }\r
514       else\r
515       {\r
516         bg = Colour::NOPROGRAMME;\r
517         fg = Colour::LIGHTTEXT;\r
518         noevent.settitle(tr("No programme details"));\r
519         paintCell(&noevent, y, bg, fg);\r
520       }\r
521     }\r
522     y += Surface::getFontHeight() + 2;\r
523   }\r
524   setInfo(&thisEvent);\r
525 }\r
526 \r
527 void VEpg::updateEventList()\r
528 {\r
529   Channel* chan;\r
530   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)\r
531   {\r
532     if (eventLista[listIndex])\r
533     {\r
534       if ((eventLista[listIndex])->empty())\r
535         (eventLista)[listIndex]->clear();\r
536     }\r
537     if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))\r
538       continue;\r
539     chan = (*chanList)[listTop + listIndex];\r
540     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\r
541   }\r
542 }\r
543 \r
544 void VEpg::setCurrentChannel(char* chname)\r
545 {\r
546   chanName.setText(chname);\r
547   chanName.draw();\r
548   viewman->updateView(this);\r
549 }\r
550 \r
551 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)\r
552 {\r
553   int w, x, y, h;\r
554   w = x = 0; // keep compiler happy
555
556   y =yOffset;\r
557   h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height\r
558   UINT end = event->time + event->duration; // programme end time\r
559   if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window\r
560   {\r
561     x = 155; // LHS of window\r
562     if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))\r
563       w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window\r
564     else\r
565       w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme\r
566   }\r
567   if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window\r
568   {\r
569     x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);\r
570     w = MINUTE_SCALE * event->duration / 60;\r
571     //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)\r
572      // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window\r
573   }\r
574   if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)\r
575     w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window\r
576   surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha));\r
577   char* tt = new char[strlen(event->title) + 1];\r
578   strcpy (tt, event->title);\r
579   int textWidth = 0;\r
580   for (UINT textPos = 0; textPos < strlen(tt); textPos++)\r
581   {\r
582     int thisCharWidth = surface->getCharWidth(tt[textPos]);\r
583     if (textWidth + thisCharWidth > w) // text will not fit in cell\r
584     {\r
585       textWidth = textPos;\r
586       break;\r
587     }\r
588     textWidth += thisCharWidth;\r
589   }\r
590   char* tT = new char[textWidth];\r
591   if(textWidth > 1)\r
592   {\r
593     strncpy(tT, tt, textWidth - 1);\r
594     tT[textWidth - 1] =  '\0';\r
595     surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue);\r
596   }\r
597   delete tT;\r
598 \r
599 }\r
600 \r
601 time_t VEpg::prevHour(time_t* t)\r
602 {\r
603   struct tm* tms;\r
604   tms = localtime(t);\r
605   tms->tm_sec = 0;\r
606   tms->tm_min = 0;\r
607   return mktime(tms);\r
608 }\r
609 \r