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