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