]> git.vomp.tv Git - vompclient.git/blob - vepg.cc
Change mutex locking in setvolume and mute
[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   DrawStyle ref1 = DrawStyle(100, 100, 100, 255); //to do global definition for skin
241   rectangle(keyx, keyy, 605, getFontHeight() * 2 + 14, ref1);
242
243   WSymbol w;
244   TEMPADD(&w);
245   
246   w.nextSymbol = WSymbol::LEFTARROW;
247   w.setPosition(keyx + 1, keyy + 20);
248   w.draw();
249
250   w.nextSymbol = WSymbol::UP;
251   w.setPosition(keyx + 26, keyy + 3);
252   w.draw();
253
254   w.nextSymbol = WSymbol::DOWN;
255   w.setPosition(keyx + 26, keyy + 36);
256   w.draw();
257
258   w.nextSymbol = WSymbol::RIGHTARROW;
259   w.setPosition(keyx + 50, keyy + 20);
260   w.draw();
261
262   drawText(tr("OK"), keyx + 18, keyy + 20, DrawStyle::LIGHTTEXT);
263
264   DrawStyle ref2 = DrawStyle(200, 0, 0, 255);
265   rectangle(keyx + 72, keyy + 4, 104, getFontHeight() + 2, ref2);
266   drawText(tr("Page up"), keyx + 74, keyy + 5, DrawStyle::LIGHTTEXT);
267
268   DrawStyle ref3 = DrawStyle(0, 200, 0, 255);
269   rectangle(keyx + 72, keyy + getFontHeight() + 8, 104, getFontHeight() + 2, ref3);
270   drawText(tr("Page down"), keyx + 74, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
271
272   DrawStyle ref4 = DrawStyle(200, 200, 0, 255);
273   rectangle(keyx + 180, keyy + 4, 104, getFontHeight() + 2, ref4);
274   drawText(tr("-24 hours"), keyx + 182, keyy + 5, DrawStyle::LIGHTTEXT);
275
276   DrawStyle ref5 = DrawStyle(0, 0, 200, 255);
277   rectangle(keyx + 180, keyy + getFontHeight() + 8, 104, getFontHeight() + 2, ref5);
278   drawText(tr("+24 hours"), keyx + 182, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
279
280   DrawStyle ref6 = DrawStyle(180, 180, 180, 255);
281   rectangle(keyx + 290, keyy + 4, 180, getFontHeight() + 2, ref6);
282   drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, DrawStyle::LIGHTTEXT);
283
284   DrawStyle ref7 = DrawStyle(180, 180, 180, 255);
285   rectangle(keyx + 290, keyy + getFontHeight() + 8, 180, getFontHeight() + 2, ref7);
286   DrawStyle red = DrawStyle(130, 0, 0);
287   drawText(tr("Rec: Set timer"), keyx + 292, keyy + getFontHeight() + 9, red);
288
289   DrawStyle ref8 = DrawStyle(180, 180, 180, 255);
290   rectangle(keyx + 474, keyy + 4, 128, getFontHeight() + 2, ref8);
291   w.nextSymbol = WSymbol::PLAY;
292   w.setPosition(keyx + 476, keyy + 5);
293   w.draw();
294   drawText(tr("Sel channel"), keyx + 496, keyy + 5, DrawStyle::LIGHTTEXT);
295
296   DrawStyle ref9 = DrawStyle(180, 180, 180, 255);
297   rectangle(keyx + 474, keyy + getFontHeight() + 8, 128, getFontHeight() + 2, ref9);
298   drawText(tr("Go: Preview"), keyx + 476, keyy + getFontHeight() + 9, DrawStyle::LIGHTTEXT);
299
300
301   // Draw all the dynamic data
302   drawData();
303 }
304
305 void VEpg::drawData()
306 {
307   // Not doing View::draw() every time causes
308   // things not to be cleared off the surface properly
309   // So, blank out the data area first
310
311   rectangle(
312     chanListbox.getRootBoxOffsetX(),
313     chanListbox.getRootBoxOffsetY() - getFontHeight() - 3,
314     155 + WINDOW_WIDTH * MINUTE_SCALE,
315     chanListbox.getHeight() + getFontHeight() + 4,
316     DrawStyle::BLACK);
317
318   chanListbox.draw();
319   drawgrid();
320   chanName.draw(); // TODO this should be dealt with by vvideolive
321
322   progTitle.draw();
323   progInfo.draw();
324
325   // Set timer to redraw to move the current time bar
326   time_t t, dt;
327   time(&t);
328   dt = 60 - (t % 60);
329   if (dt == 0) dt = 60;
330   dt += t;
331   Timers::getInstance()->setTimerT(this, 1, dt);
332 }
333
334 void VEpg::timercall(int clientReference)
335 {
336   drawData();
337   boxstack->update(this);
338 }
339
340 int VEpg::handleCommand(int command)
341 {
342   switch(command)
343   {
344     case Remote::DF_UP:
345     case Remote::UP:
346     { // cursor up the channel list
347       chanListbox.up();
348       drawData();
349       boxstack->update(this);
350       return 2;
351     }
352     case Remote::DF_DOWN:
353     case Remote::DOWN:
354     { // cursor down the channel list
355       Log::getInstance()->log("VEPG", Log::DEBUG, "Down start");
356       
357       chanListbox.down();
358       drawData();
359       boxstack->update(this);
360       Log::getInstance()->log("VEPG", Log::DEBUG, "Down end");
361
362       return 2;
363     }
364     case Remote::DF_LEFT:
365     case Remote::LEFT:
366     { // cursor left through time
367       selTime = thisEvent.time - 1;
368       drawData();
369       boxstack->update(this);
370       return 2;
371     }
372     case Remote::DF_RIGHT:
373     case Remote::RIGHT:
374     {
375     // cursor right through time
376       selTime = thisEvent.time + thisEvent.duration;
377       drawData();
378       boxstack->update(this);
379       return 2;
380     }
381     case Remote::RED:
382     {
383     // cursor up one page
384       chanListbox.pageUp();
385       drawData();
386       boxstack->update(this);
387       return 2;
388     }
389     case Remote::GREEN:
390     {
391     // cursor down one page
392       chanListbox.pageDown();
393       drawData();
394       boxstack->update(this);
395       return 2;
396     }
397     case Remote::BLUE:
398     {
399     // step forward 24 hours
400       selTime += 24 * 60 * 60;
401       drawData();
402       boxstack->update(this);
403       return 2;
404     }
405     case Remote::YELLOW:
406     {
407     // step forward 24 hours
408       selTime -= 24 * 60 * 60;
409       drawData();
410       boxstack->update(this);
411       return 2;
412     }
413     case Remote::RECORD:
414     {
415       if (!chanList) return 2;
416       Log::getInstance()->log("VEPG", Log::DEBUG, "ID %lu TIME %lu DURATION %lu TITLE %s", thisEvent.id, thisEvent.time, thisEvent.duration, thisEvent.title);
417       VEpgSetTimer* vs = new VEpgSetTimer(&thisEvent, (*chanList)[chanListbox.getCurrentOption()]);
418       vs->draw();
419       boxstack->add(vs);
420       boxstack->update(vs);
421       return 2;
422     }
423     case Remote::PLAY:
424     case Remote::GO:
425     case Remote::OK:
426     {
427       if (!chanList) return 2;
428
429       // select programme and display menu TODO currently just changes to selected channel
430
431       currentChannelIndex = chanListbox.getCurrentOption();
432
433       if (parent)
434       {
435         Message* m = new Message(); // Must be done after this view deleted
436         m->from = this;
437         m->to = parent;
438         m->message = Message::CHANNEL_CHANGE;
439         m->parameter = (*chanList)[currentChannelIndex]->number;
440         Command::getInstance()->postMessageNoLock(m);
441       }
442       
443       setCurrentChannel();
444
445       if(command == Remote::GO)
446         return 2;
447       // GO just changes channel in preview, PLAY changes channel and returns to normal TV
448     }
449     case Remote::BACK:
450     case Remote::GUIDE:
451     {
452       // return to normal TV mode
453       if (parent) // ptr check done in case being tested from videorec
454       {
455         Message* m = new Message(); // Must be done after this view deleted
456         m->from = this;
457         m->to = parent;
458         m->message = Message::EPG_CLOSE;
459         Command::getInstance()->postMessageNoLock(m);
460       }
461       return 4;
462     }
463     case Remote::CHANNELUP:
464     {
465       if (currentChannelIndex == (chanList->size() - 1)) // at the end
466         currentChannelIndex = 0;
467       else
468         ++currentChannelIndex;
469       
470       if (parent)
471       {
472         Message* m = new Message(); // Must be done after this view deleted
473         m->from = this;
474         m->to = parent;
475         m->message = Message::CHANNEL_CHANGE;
476         m->parameter = (*chanList)[currentChannelIndex]->number;
477         Command::getInstance()->postMessageNoLock(m);
478       }
479       
480       setCurrentChannel();
481
482       return 2;
483     }
484     case Remote::CHANNELDOWN:
485     {
486       if (currentChannelIndex == 0) // at the start
487         currentChannelIndex = chanList->size() - 1; // so go to end
488       else
489         --currentChannelIndex;
490
491       if (parent)
492       {
493         Message* m = new Message(); // Must be done after this view deleted
494         m->from = this;
495         m->to = parent;
496         m->message = Message::CHANNEL_CHANGE;
497         m->parameter = (*chanList)[currentChannelIndex]->number;
498         Command::getInstance()->postMessageNoLock(m);
499       }
500       
501       setCurrentChannel();
502
503       return 2;
504     }
505   }
506   // stop command getting to any more views
507   return 1;
508 }
509
510 void VEpg::drawgrid() // redraws grid and select programme
511 {
512   // draw the grid of programmes
513   char timeString[20];
514   time_t t;
515   time(&t); // set t = now
516   if(selTime < t)
517     selTime = t; // don't allow cursor in the past
518   if(listTop != chanListbox.getTopOption())
519   {
520   // chanListbox has scrolled TODO speed up by changing only rows that have changed
521     listTop = chanListbox.getTopOption();
522     updateEventList();
523   }
524   if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
525   {
526   // we have cursored back before left time of window
527   //TODO check that this and above don't happen together
528     ltime = prevHour(&selTime);
529     updateEventList();
530   }
531   // draw time scale
532   DrawStyle white = DrawStyle(255, 255, 255, 255);
533   
534   t = ltime;
535   struct tm* tms;
536   tms = localtime(&t);
537   strftime(timeString, 19, "%a %d %b", tms);
538   int timey = chanListbox.getRootBoxOffsetY() - getFontHeight() - 3;
539   int timex = 135;
540   drawTextRJ(timeString, timex - 10, timey, DrawStyle::LIGHTTEXT); // print date
541   strftime(timeString, 19, "%H:%M", tms);
542   drawText(timeString, timex, timey, DrawStyle::LIGHTTEXT); // print left time
543
544   rectangle(155, timey + getFontHeight(), 2, 7, white);
545   t = t + 3600;
546   tms = localtime(&t);
547   strftime(timeString, 19, "%H:%M", tms);
548   drawText(timeString, timex + 180, timey, DrawStyle::LIGHTTEXT); // print middle time
549   rectangle(335, timey + getFontHeight(), 2, 7, white);
550   t = t + 3600;
551   tms = localtime(&t);
552   strftime(timeString, 19, "%H:%M", tms);
553   drawText(timeString, timex + 360, timey, DrawStyle::LIGHTTEXT); // print right time
554   rectangle(515, timey + getFontHeight(), 2, 7, white);
555   // pointer to selTime
556   //rectangle(155 + (selTime - ltime) / 20, timey + getFontHeight(), 2, 7, DrawStyle(255, 50, 50, 255));
557
558   // current time line
559   time(&t);
560   if ((t >= ltime) && (t < (ltime + 9000)))
561   {
562     rectangle(155 + (t - ltime) / 20, timey + getFontHeight(), 2, ((getFontHeight() + 2) * 7) + 7 + 2, DrawStyle::RED);
563   }
564
565   // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
566   Event* event;
567   Event noevent; // an event to use if there are gaps in the epg
568   thisEvent.setdescription(tr("There are no programme details available for this period"));
569   thisEvent.duration = WINDOW_WIDTH * 60;
570   thisEvent.time = ltime;
571   thisEvent.settitle(tr("No programme details"));
572   thisEvent.id = 0;
573   bool swapColour = false; // alternate cell colour
574   bool currentRow = false;
575   int y = chanListbox.getRootBoxOffsetY() + 5; // vertical position of cell
576   DrawStyle bg, fg; // background colour of cells in grid
577   // for each displayed channel, find programmes that fall in 2.5 hour time window
578   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
579   {
580     if (listTop + (int)listIndex >= chanListbox.getBottomOption())
581       continue; // ensure nothing populates grid below last channel
582     currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
583     noevent.time = ltime;
584     noevent.duration = WINDOW_WIDTH * 60;
585     noevent.settitle("");
586     paintCell(&noevent, y, DrawStyle::NOPROGRAMME, DrawStyle::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
587     if (currentRow)
588     {
589       thisEvent.setdescription(tr("There are no programme details available for this period"));
590       thisEvent.duration = WINDOW_WIDTH * 60;
591       thisEvent.time = ltime;
592       thisEvent.settitle(tr("No programme details"));
593       thisEvent.id = 0;
594     }
595     if (eventLista[listIndex])
596     {
597       sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
598       for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
599       {
600         fg = DrawStyle::LIGHTTEXT;
601         event = (*eventLista[listIndex])[e];
602         if (event)
603         {
604           UINT end = event->time + event->duration; // programme end time
605           if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
606             continue; // that's enough of this channel's events
607           if(end <= UINT(ltime)) // programme ends before LHS of window
608             continue; // this event is before the window - let's try the next event
609           // this event is one we are interested in
610           bg = (swapColour)?DrawStyle::PROGRAMMEA:DrawStyle::PROGRAMMEB; // alternate cell colour
611           swapColour = !swapColour; // it wil be the other colour next time
612           if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
613           {
614             // this is the selected programme
615             thisEvent.setdescription(event->description);
616             thisEvent.duration = event->duration;
617             thisEvent.time = event->time;
618             thisEvent.settitle(event->title);
619             thisEvent.id = event->id;
620             if(thisEvent.id == 0)
621               thisEvent.id = 1;
622             bg = DrawStyle::SELECTHIGHLIGHT; // highlight cell
623             fg = DrawStyle::DARKTEXT;
624           }
625           else
626           {
627             if (currentRow && thisEvent.id == 0)
628             {
629               if (end <= UINT(selTime) && end > UINT(thisEvent.time))
630                 thisEvent.time = end;
631               if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
632                 thisEvent.duration = event->time - thisEvent.time;
633             }
634           }
635           paintCell(event, y, bg, fg);
636         }
637       }
638     }
639     else
640     {
641       // no event list for this channel. Already painted noevent colour so just highlight if selected
642       if (currentRow)
643       {
644         bg = DrawStyle::SELECTHIGHLIGHT; // highlight cell
645         fg = DrawStyle::DARKTEXT;
646         paintCell(&thisEvent, y, bg, fg);
647       }
648       else
649       {
650         bg = DrawStyle::NOPROGRAMME;
651         fg = DrawStyle::LIGHTTEXT;
652         noevent.settitle(tr("No programme details"));
653         paintCell(&noevent, y, bg, fg);
654       }
655     }
656     y += getFontHeight() + 2;
657   }
658   setInfo(&thisEvent);
659 }
660
661 void VEpg::updateEventList()
662 {
663   if (!chanList) return;
664   Channel* chan;
665   for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
666   {
667     if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
668       continue;
669     chan = (*chanList)[listTop + listIndex];
670
671     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
672   }
673 }
674
675 void VEpg::setCurrentChannel()
676 {
677   chanName.setText((*chanList)[currentChannelIndex]->name);
678   chanName.draw();
679   Region r;
680   chanName.getRootBoxRegion(&r);
681   boxstack->update(this, &r);
682 }
683
684 void VEpg::paintCell(Event* event, int yOffset, const DrawStyle& bg, const DrawStyle& fg)
685 {
686   int w, x, y, h;
687   w = x = 0; // keep compiler happy
688
689   y =yOffset;
690   h = getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
691   UINT end = event->time + event->duration; // programme end time
692   if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
693   {
694     x = 155; // LHS of window
695     if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
696       w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
697     else
698       w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
699   }
700   if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
701   {
702     x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
703     w = MINUTE_SCALE * event->duration / 60;
704     //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
705      // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
706   }
707   if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
708     w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
709   rectangle(x, y, w, h, bg);
710   char* tt = new char[strlen(event->title) + 1];
711   strcpy (tt, event->title);
712   float textWidth = 0;
713   unsigned int cur_length=1;
714   unsigned int text_max=strlen(tt);
715   UINT textPos;
716   for (textPos = 0; textPos <text_max; textPos+=cur_length)
717   {
718         wchar_t cur_char=getWChar(tt+textPos,&cur_length);
719     float thisCharWidth = charWidth(cur_char);
720     if (textWidth + thisCharWidth > w) // text will not fit in cell
721     {
722       break;
723     }
724     textWidth += thisCharWidth;
725   }
726   char* tT = new char[textPos+1];
727   if(textPos > 0)
728   {
729     strncpy(tT, tt, textPos );
730     tT[textPos ] =  '\0';
731     surface->drawText(tT, x+2, y, fg);
732   }
733   delete tT;
734
735 }
736
737 time_t VEpg::prevHour(time_t* t)
738 {
739   struct tm* tms;
740   tms = localtime(t);
741   tms->tm_sec = 0;
742   tms->tm_min = 0;
743   return mktime(tms);
744 }
745
746 void VEpg::processMessage(Message* m)
747 {
748   if (m->message == Message::MOUSE_MOVE)
749   {
750     if (chanListbox.mouseMove((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
751     {
752       drawData();
753       boxstack->update(this);
754     }
755   }
756   else if (m->message == Message::MOUSE_LBDOWN)
757   {
758     if (chanListbox.mouseLBDOWN((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
759     {
760       boxstack->handleCommand(Remote::OK); //simulate OK press
761     }
762     else
763     {
764       //check if press is outside this view! then simulate cancel
765       int x=(m->parameter>>16)-getScreenX();
766       int y=(m->parameter&0xFFFF)-getScreenY();
767       int keyx = chanListbox.getRootBoxOffsetX();
768       int keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
769
770       if (x<0 || y <0 || x>(int)getWidth() || y>(int)getHeight())
771       {
772         boxstack->handleCommand(Remote::BACK); //simulate cancel press
773       }
774       else if (x>=(keyx+72) && y>=(keyy+4) &&x<=(keyx+72+104) &&y<=(keyy+4+getFontHeight() + 2))
775       {
776         boxstack->handleCommand(Remote::RED);
777       }
778       else if (x>=(keyx+72) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+72+104) &&y<=(keyy+8+2*getFontHeight() + 2))
779       {
780         boxstack->handleCommand(Remote::GREEN);
781       }
782       else if (x>=(keyx+180) && y>=(keyy+4) &&x<=(keyx+180+104) &&y<=(keyy+4+getFontHeight() + 2))
783       {
784         boxstack->handleCommand(Remote::YELLOW);
785       }
786       else if (x>=(keyx+180) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+180+104) &&y<=(keyy+8+2*getFontHeight() + 2))
787       {
788         boxstack->handleCommand(Remote::BLUE);
789       }
790       else if (x>=(keyx+290) && y>=(keyy+4) &&x<=(keyx+180+290) &&y<=(keyy+4+getFontHeight() + 2))
791       {
792         boxstack->handleCommand(Remote::BACK);
793       }
794       else if (x>=(keyx+290) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+290+180) &&y<=(keyy+8+2*getFontHeight() + 2))
795       {
796         boxstack->handleCommand(Remote::RECORD);
797       }
798       else if (x>=(keyx+474) && y>=(keyy+4) &&x<=(keyx+128+474) &&y<=(keyy+4+getFontHeight() + 2))
799       {
800         boxstack->handleCommand(Remote::PLAY);
801       }
802       else if (x>=(keyx+474) && y>=(keyy+ getFontHeight() + 8) &&x<=(keyx+238+474) &&y<=(keyy+8+2*getFontHeight() + 2))
803       {
804         boxstack->handleCommand(Remote::GO);
805       }
806       else if ( x>=(chanListbox.getRootBoxOffsetX())
807                 && y>=(chanListbox.getRootBoxOffsetY() + 5)
808                 // &&x<=(chanListbox.getOffsetX()+155 + WINDOW_WIDTH * MINUTE_SCALE)
809                 &&y<=(chanListbox.getRootBoxOffsetY() - getFontHeight()
810                 - 3+(int)chanListbox.getHeight() + getFontHeight() + 3)
811               )
812       {
813         int cy=y-(chanListbox.getRootBoxOffsetY() + 5);
814         int row=cy/(getFontHeight()+2);
815         int clistTop = chanListbox.getTopOption();
816         chanListbox.hintSetCurrent(clistTop+row);
817         int cx=x-155;
818         time_t ttime = cx*60/MINUTE_SCALE+ltime;
819         //x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
820
821         selTime = ttime;
822         drawData();
823         boxstack->update(this);
824       }
825     }
826   }
827 }
828