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