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