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