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