]> git.vomp.tv Git - vompclient.git/blob - vepg.cc
EPG tweaks, EPG made NTSC compatible
[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 = 12;
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\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       Message* m = new Message();\r
352       m->from = this;\r
353       m->to = videoLive;\r
354       m->message = Message::CHANNEL_CHANGE;\r
355       m->parameter = (*chanList)[chanListbox.getCurrentOption()]->number;\r
356       ViewMan::getInstance()->postMessage(m);\r
357       if(command == Remote::GO)\r
358         return 2;\r
359       // GO just changes channel in preview, PLAY changes channel and returns to normal TV\r
360     }\r
361     case Remote::BACK:\r
362     case Remote::GUIDE:\r
363     {\r
364       // return to normal TV mode\r
365       if (videoLive) videoLive->resetPictureSize(); // ptr check done in case being tested from videorec\r
366       return 4;\r
367     }\r
368     case Remote::CHANNELUP:\r
369     {\r
370     // change up channel on live TV\r
371       Message* m = new Message();\r
372       m->from = this;\r
373       m->to = videoLive;\r
374       m->message = Message::CHANNEL_UP;\r
375       ViewMan::getInstance()->postMessage(m);\r
376       return 2;\r
377     }\r
378     case Remote::CHANNELDOWN:\r
379     { // change down channel on live TV\r
380       Message* m = new Message();\r
381       m->from = this;\r
382       m->to = videoLive;\r
383       m->message = Message::CHANNEL_DOWN;\r
384       ViewMan::getInstance()->postMessage(m);\r
385       return 2;\r
386     }\r
387   }\r
388   // stop command getting to any more views\r
389   return 1;\r
390 }\r
391 \r
392 void VEpg::drawgrid() // redraws grid and select programme\r
393 {\r
394   // draw the grid of programmes\r
395   char timeString[20];\r
396   time_t t;\r
397   time(&t); // set t = now\r
398   if(selTime < t)\r
399     selTime = t; // don't allow cursor in the past\r
400   if(listTop != chanListbox.getTopOption())\r
401   {\r
402   // chanListbox has scrolled TODO speed up by changing only rows that have changed\r
403     listTop = chanListbox.getTopOption();\r
404     updateEventList();\r
405   }\r
406   if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))\r
407   {\r
408   // we have cursored back before left time of window\r
409   //TODO check that this and above don't happen together\r
410     ltime = prevHour(&selTime);\r
411     updateEventList();\r
412   }\r
413   // draw time scale\r
414   t = ltime;\r
415   struct tm* tms;\r
416   tms = localtime(&t);\r
417   strftime(timeString, 19, "%a %e %b", tms);\r
418   int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;\r
419   int timex = 135;\r
420   drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date\r
421   strftime(timeString, 19, "%H:%M", tms);\r
422   drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time\r
423   surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));\r
424   t = t + 3600;\r
425   tms = localtime(&t);\r
426   strftime(timeString, 19, "%H:%M", tms);\r
427   drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time\r
428   surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));\r
429   t = t + 3600;\r
430   tms = localtime(&t);\r
431   strftime(timeString, 19, "%H:%M", tms);\r
432   drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time\r
433   surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));\r
434   // pointer to selTime\r
435   surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255));\r
436 \r
437   // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?\r
438   Event* event;\r
439   Event noevent; // an event to use if there are gaps in the epg\r
440   thisEvent.setdescription(tr("There are no programme details available for this period"));\r
441   thisEvent.duration = WINDOW_WIDTH * 60;\r
442   thisEvent.time = ltime;\r
443   thisEvent.settitle(tr("No programme details"));\r
444   thisEvent.id = 0;\r
445   bool swapColour = FALSE; // alternate cell colour\r
446   bool currentRow = FALSE;\r
447   int y = chanListbox.getOffsetY() + 5; // vertical position of cell\r
448   Colour bg, fg; // background colour of cells in grid\r
449   // for each displayed channel, find programmes that fall in 2.5 hour time window\r
450   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)\r
451   {\r
452     if (listTop + (int)listIndex >= chanListbox.getBottomOption())\r
453       continue; // ensure nothing populates grid below last channel\r
454     currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());\r
455     noevent.time = ltime;\r
456     noevent.duration = WINDOW_WIDTH * 60;\r
457     noevent.settitle("");\r
458     paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes\r
459     if (currentRow)\r
460     {\r
461       thisEvent.setdescription(tr("There are no programme details available for this period"));\r
462       thisEvent.duration = WINDOW_WIDTH * 60;\r
463       thisEvent.time = ltime;\r
464       thisEvent.settitle(tr("No programme details"));\r
465       thisEvent.id = 0;\r
466     }\r
467     if (eventLista[listIndex])\r
468     {\r
469       sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());\r
470       for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel\r
471       {\r
472         fg = Colour::LIGHTTEXT;\r
473         event = (*eventLista[listIndex])[e];\r
474         if (event)\r
475         {\r
476           UINT end = event->time + event->duration; // programme end time\r
477           if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window\r
478             continue; // that's enough of this channel's events\r
479           if(end <= UINT(ltime)) // programme ends before LHS of window\r
480             continue; // this event is before the window - let's try the next event\r
481           // this event is one we are interested in\r
482           bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour\r
483           swapColour = !swapColour; // it wil be the other colour next time\r
484           if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)\r
485           {\r
486             // this is the selected programme\r
487             thisEvent.setdescription(event->description);\r
488             thisEvent.duration = event->duration;\r
489             thisEvent.time = event->time;\r
490             thisEvent.settitle(event->title);\r
491             thisEvent.id = event->id;\r
492             if(thisEvent.id == 0)\r
493               thisEvent.id = 1;\r
494             bg = Colour::SELECTHIGHLIGHT; // highlight cell\r
495             fg = Colour::DARKTEXT;\r
496           }\r
497           else\r
498           {\r
499             if (currentRow && thisEvent.id == 0)\r
500             {\r
501               if (end <= UINT(selTime) && end > UINT(thisEvent.time))\r
502                 thisEvent.time = end;\r
503               if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)\r
504                 thisEvent.duration = event->time - thisEvent.time;\r
505             }\r
506           }\r
507           paintCell(event, y, bg, fg);\r
508         }\r
509       }\r
510     }\r
511     else\r
512     {\r
513       // no event list for this channel. Already painted noevent colour so just highlight if selected\r
514       if (currentRow)\r
515       {\r
516         bg = Colour::SELECTHIGHLIGHT; // highlight cell\r
517         fg = Colour::DARKTEXT;\r
518         paintCell(&thisEvent, y, bg, fg);\r
519       }\r
520       else\r
521       {\r
522         bg = Colour::NOPROGRAMME;\r
523         fg = Colour::LIGHTTEXT;\r
524         noevent.settitle(tr("No programme details"));\r
525         paintCell(&noevent, y, bg, fg);\r
526       }\r
527     }\r
528     y += Surface::getFontHeight() + 2;\r
529   }\r
530   setInfo(&thisEvent);\r
531 }\r
532 \r
533 void VEpg::updateEventList()\r
534 {\r
535   Channel* chan;\r
536   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)\r
537   {\r
538     if (eventLista[listIndex])\r
539     {\r
540       if ((eventLista[listIndex])->empty())\r
541         (eventLista)[listIndex]->clear();\r
542     }\r
543     if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))\r
544       continue;\r
545     chan = (*chanList)[listTop + listIndex];\r
546     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
547   }\r
548 }\r
549 \r
550 void VEpg::setCurrentChannel(char* chname)\r
551 {\r
552   chanName.setText(chname);\r
553   chanName.draw();\r
554   viewman->updateView(this);\r
555 }\r
556 \r
557 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)\r
558 {\r
559   int w, x, y, h;\r
560   y =yOffset;\r
561   h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height\r
562   UINT end = event->time + event->duration; // programme end time\r
563   if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window\r
564   {\r
565     x = 155; // LHS of window\r
566     if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))\r
567       w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window\r
568     else\r
569       w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme\r
570   }\r
571   if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window\r
572   {\r
573     x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);\r
574     w = MINUTE_SCALE * event->duration / 60;\r
575     //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)\r
576      // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window\r
577   }\r
578   if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)\r
579     w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window\r
580   surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha));\r
581   char* tt = new char[strlen(event->title) + 1];\r
582   strcpy (tt, event->title);\r
583   int textWidth = 0;\r
584   for (UINT textPos = 0; textPos < strlen(tt); textPos++)\r
585   {\r
586     int thisCharWidth = surface->getCharWidth(tt[textPos]);\r
587     if (textWidth + thisCharWidth > w) // text will not fit in cell\r
588     {\r
589       textWidth = textPos;\r
590       break;\r
591     }\r
592     textWidth += thisCharWidth;\r
593   }\r
594   char* tT = new char[textWidth];\r
595   if(textWidth > 1)\r
596   {\r
597     strncpy(tT, tt, textWidth - 1);\r
598     tT[textWidth - 1] =  '\0';\r
599     surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue);\r
600   }\r
601   delete tT;\r
602 \r
603 }\r
604 \r
605 time_t VEpg::prevHour(time_t* t)\r
606 {\r
607   struct tm* tms;\r
608   tms = localtime(t);\r
609   tms->tm_sec = 0;\r
610   tms->tm_min = 0;\r
611   return mktime(tms);\r
612 }\r
613 \r