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