2 Copyright 2005 Brian Walton
\r
4 This file is part of VOMP.
\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
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
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
\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
36 VEpg::VEpg(VVideoLive* v, UINT currentChannel)
\r
38 for(UINT listIndex = 0; listIndex < 7; listIndex++)
\r
40 // initialise array of pointers to eventlist structures
\r
41 eventLista[listIndex] = NULL;
\r
45 // Create pallet on which to paint our epg view and position it in centre of screen.
\r
46 // Need to reduce size to deal with overscanning TVs.
\r
48 // create(640, 530);
\r
49 //TODO: have size for ntsc
\r
50 if (Video::getInstance()->getFormat() == Video::PAL)
\r
53 setScreenPos(44, 30);
\r
57 // what can we do about poor ntsc?
\r
59 setScreenPos(64, 51);
\r
63 Colour transparent = Colour(0, 0, 0, 0);
\r
64 setBackgroundColour(transparent);
\r
65 // initialise variables and pointers
\r
67 chanList = VDR::getInstance()->getChannelsList(VDR::VIDEO); //TODO want to be able to display video and radio together
\r
69 progTitle.setSurface(surface);
\r
70 progTitle.setSurfaceOffset(0,0);
\r
71 progTitle.setDimensions(300,(Surface::getFontHeight() + 6) * 2 + 2); //paragraph line seperation is 6 pixels
\r
72 progTitle.setBackgroundColour(Colour::TITLEBARBACKGROUND);
\r
73 progInfo.setSurface(surface);
\r
74 progInfo.setSurfaceOffset(0, progTitle.getOffsetY() + progTitle.getHeight());
\r
75 progInfo.setDimensions(300,(Surface::getFontHeight() + 6) * 8 + 2);
\r
76 chanName.setSurface(surface);
\r
77 chanName.setDimensions(510, (Surface::getFontHeight() + 4));
\r
78 chanName.setSurfaceOffset(305,230);
\r
79 chanName.setBackgroundColour(Colour(0, 0, 0, 90));
\r
80 // create area to display list of channels
\r
81 chanListbox.setSurface(surface); // add channel list
\r
82 chanListbox.setSurfaceOffset(0, progInfo.getOffsetY() + progInfo.getHeight() + Surface::getFontHeight() + 8); // position channel list
\r
83 chanListbox.setDimensions(150, (Surface::getFontHeight() + 1) * 7 + 5); //listbox line seperation is 1 pixel
\r
84 // populate channel list
\r
87 for (UINT i = 0; i < chanList->size(); i++)
\r
89 chan = (*chanList)[i];
\r
90 if (i == currentChannel)
\r
92 chan->index = chanListbox.addOption(chan->name, first);
\r
95 listTop = chanListbox.getTopOption();
\r
96 chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
\r
97 chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
\r
98 time(<ime); // set ltime to now
\r
99 ltime = prevHour(<ime); // set ltime to previous hour TODO make this half hour?
\r
100 time(&selTime); // set selTime to now
\r
101 updateEventList(); // get list of programmes
\r
106 for(int listIndex = 0; listIndex < 7; listIndex++)
\r
108 if (eventLista[listIndex])
\r
110 (eventLista)[listIndex]->clear();
\r
111 delete eventLista[listIndex];
\r
114 // delete [] eventLista;
\r
116 // destroy dynamically allocated memory
\r
119 void VEpg::setInfo(Event* event)
\r
122 struct tm* btime; // to hold programme start and end time
\r
123 char* timeString = new char[9]; // to hold programme start and end time
\r
124 int length = strlen(event->title); // calculate length of programme title string
\r
125 char* title = new char[length + 15]; // create string to hold start time, end time and programme title
\r
126 btime = localtime((time_t*)&event->time); //get programme start time
\r
127 strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -
\r
128 strcpy(title, timeString); // put it in our buffer
\r
129 t = event->time + event->duration; //get programme end time
\r
130 btime = localtime(&t);
\r
131 strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -
\r
132 strcat(title, timeString); // put it in our buffer
\r
133 strcat(title, event->title); // then add the programme title
\r
134 progTitle.setText(title); // sput this sring in our text box
\r
135 length = strlen(event->description);
\r
136 char* info = new char[length + 1]; // create programme detail string
\r
137 strcpy(info, event->description);
\r
138 progInfo.setText(info); // show programme detail string
\r
139 // destroy dynamically allocated memory
\r
147 View::draw(); // draw pallet
\r
148 chanListbox.draw();
\r
150 chanName.draw(); // TODO this should be dealt with by vvideolive
\r
154 // Display the status and key stuff at the bottom
\r
155 int keyx = chanListbox.getOffsetX();
\r
156 int keyy = chanListbox.getOffsetY() + chanListbox.getHeight() + 2;
\r
157 surface->fillblt(keyx, keyy, 610, Surface::getFontHeight() * 2 + 14, surface->rgba(100, 100, 100, 255));
\r
160 w.setSurface(surface);
\r
162 w.nextSymbol = WSymbol::LEFTARROW;
\r
163 w.setSurfaceOffset(keyx + 1, keyy + 20);
\r
166 w.nextSymbol = WSymbol::UP;
\r
167 w.setSurfaceOffset(keyx + 26, keyy + 3);
\r
170 w.nextSymbol = WSymbol::DOWN;
\r
171 w.setSurfaceOffset(keyx + 26, keyy + 36);
\r
174 w.nextSymbol = WSymbol::RIGHTARROW;
\r
175 w.setSurfaceOffset(keyx + 50, keyy + 20);
\r
178 drawText("OK", keyx + 18, keyy + 20, Colour::LIGHTTEXT);
\r
180 surface->fillblt(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 0, 0, 255));
\r
181 drawText("Page up", keyx + 74, keyy + 5, Colour::LIGHTTEXT);
\r
183 surface->fillblt(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba(0, 200, 0, 255));
\r
184 drawText("Page down", keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
186 surface->fillblt(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 200, 0, 255));
\r
187 drawText("-24 hours", keyx + 182, keyy + 5, Colour::LIGHTTEXT);
\r
189 surface->fillblt(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba( 0, 0, 200, 255));
\r
190 drawText("+24 hours", keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
192 surface->fillblt(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
193 drawText("Guide / Back: Close", keyx + 292 , keyy + 5, Colour::LIGHTTEXT);
\r
195 surface->fillblt(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
196 Colour red = Colour(130, 0, 0);
\r
197 drawText("Rec: Set timer", keyx + 292, keyy + Surface::getFontHeight() + 9, red);
\r
199 surface->fillblt(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
200 w.nextSymbol = WSymbol::PLAY;
\r
201 w.setSurfaceOffset(keyx + 476, keyy + 5);
\r
203 drawText("Sel channel", keyx + 496, keyy + 5, Colour::LIGHTTEXT);
\r
205 surface->fillblt(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
206 drawText("Go: Preview", keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
209 int VEpg::handleCommand(int command)
\r
213 case Remote::DF_UP:
\r
215 { // cursor up the channel list
\r
221 case Remote::DF_DOWN:
\r
223 { // cursor down the channel list
\r
224 chanListbox.down();
\r
229 case Remote::DF_LEFT:
\r
231 { // cursor left through time
\r
232 selTime = thisEvent.time - 1;
\r
237 case Remote::DF_RIGHT:
\r
238 case Remote::RIGHT:
\r
240 // cursor right through time
\r
241 selTime = thisEvent.time + thisEvent.duration;
\r
248 // cursor up one page
\r
249 chanListbox.pageUp();
\r
254 case Remote::GREEN:
\r
256 // cursor down one page
\r
257 chanListbox.pageDown();
\r
264 // step forward 24 hours
\r
265 selTime += 24 * 60 * 60;
\r
270 case Remote::YELLOW:
\r
272 // step forward 24 hours
\r
273 selTime -= 24 * 60 * 60;
\r
278 case Remote::RECORD:
\r
286 { // select programme and display menu TODO currently just changes to selected channel
\r
287 Message* m = new Message();
\r
290 m->message = Message::CHANNEL_CHANGE;
\r
291 m->parameter = (*chanList)[chanListbox.getCurrentOption()]->number;
\r
292 ViewMan::getInstance()->postMessage(m);
\r
293 if(command == Remote::GO)
\r
295 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
\r
298 case Remote::GUIDE:
\r
300 // return to normal TV mode TODO vvideolive should handle this (via message?)
\r
301 Video::getInstance()->setMode(Video::NORMAL);
\r
302 videoLive->setEpgMode(FALSE);
\r
305 case Remote::CHANNELUP:
\r
307 // change up channel on live TV TODO vvideolive should handle this
\r
308 Message* m = new Message();
\r
311 m->message = Message::CHANNEL_UP;
\r
312 ViewMan::getInstance()->postMessage(m);
\r
315 case Remote::CHANNELDOWN:
\r
316 { // change down channel on live TV TODO vvideolive should handle this
\r
317 Message* m = new Message();
\r
320 m->message = Message::CHANNEL_DOWN;
\r
321 ViewMan::getInstance()->postMessage(m);
\r
325 // stop command getting to any more views
\r
329 void VEpg::drawgrid() // redraws grid and select programme
\r
331 // draw the grid of programmes
\r
332 char timeString[20];
\r
334 time(&t); // set t = now
\r
336 selTime = t; // don't allow cursor in the past
\r
337 if(listTop != chanListbox.getTopOption())
\r
339 // chanListbox has scrolled TODO speed up by changing only rows that have changed
\r
340 listTop = chanListbox.getTopOption();
\r
343 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
\r
345 // we have cursored back before left time of window
\r
346 //TODO check that this and above don't happen together
\r
347 ltime = prevHour(&selTime);
\r
353 tms = localtime(&t);
\r
354 strftime(timeString, 19, "%a %e %b", tms);
\r
355 int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;
\r
357 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
\r
358 strftime(timeString, 19, "%H:%M", tms);
\r
359 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
\r
360 surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
362 tms = localtime(&t);
\r
363 strftime(timeString, 19, "%H:%M", tms);
\r
364 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
\r
365 surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
367 tms = localtime(&t);
\r
368 strftime(timeString, 19, "%H:%M", tms);
\r
369 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
\r
370 surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
371 // pointer to selTime
\r
372 surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255));
\r
374 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
\r
376 Event noevent; // an event to use if there are gaps in the epg
\r
377 thisEvent.setdescription("There are no programme details available for this period");
\r
378 thisEvent.duration = WINDOW_WIDTH * 60;
\r
379 thisEvent.time = ltime;
\r
380 thisEvent.settitle("No programme details");
\r
382 bool swapColour = FALSE; // alternate cell colour
\r
383 bool currentRow = FALSE;
\r
384 int y = chanListbox.getOffsetY() + 5; // vertical position of cell
\r
385 Colour bg, fg; // background colour of cells in grid
\r
386 // for each displayed channel, find programmes that fall in 2.5 hour time window
\r
387 for(int listIndex = 0; listIndex < 7; listIndex++)
\r
389 if (listTop + listIndex >= chanListbox.getBottomOption())
\r
390 continue; // ensure nothing populates grid below last channel
\r
391 currentRow = (listTop + listIndex == chanListbox.getCurrentOption());
\r
392 noevent.time = ltime;
\r
393 noevent.duration = WINDOW_WIDTH * 60;
\r
394 noevent.settitle("");
\r
395 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
\r
398 thisEvent.setdescription("There are no programme details available for this period");
\r
399 thisEvent.duration = WINDOW_WIDTH * 60;
\r
400 thisEvent.time = ltime;
\r
401 thisEvent.settitle("No programme details");
\r
404 if (eventLista[listIndex])
\r
406 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
\r
407 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
\r
409 fg = Colour::LIGHTTEXT;
\r
410 event = (*eventLista[listIndex])[e];
\r
413 UINT end = event->time + event->duration; // programme end time
\r
414 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
\r
415 continue; // that's enough of this channel's events
\r
416 if(end <= UINT(ltime)) // programme ends before LHS of window
\r
417 continue; // this event is before the window - let's try the next event
\r
418 // this event is one we are interested in
\r
419 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
\r
420 swapColour = !swapColour; // it wil be the other colour next time
\r
421 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
\r
423 // this is the selected programme
\r
424 thisEvent.setdescription(event->description);
\r
425 thisEvent.duration = event->duration;
\r
426 thisEvent.time = event->time;
\r
427 thisEvent.settitle(event->title);
\r
428 thisEvent.id = event->id;
\r
429 if(thisEvent.id == 0)
\r
431 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
432 fg = Colour::DARKTEXT;
\r
436 if (currentRow && thisEvent.id == 0)
\r
438 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
\r
439 thisEvent.time = end;
\r
440 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
\r
441 thisEvent.duration = event->time - thisEvent.time;
\r
444 paintCell(event, y, bg, fg);
\r
450 // no event list for this channel. Already painted noevent colour so just highlight if selected
\r
453 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
454 fg = Colour::DARKTEXT;
\r
455 paintCell(&thisEvent, y, bg, fg);
\r
459 bg = Colour::NOPROGRAMME;
\r
460 fg = Colour::LIGHTTEXT;
\r
461 noevent.settitle("No programme details");
\r
462 paintCell(&noevent, y, bg, fg);
\r
465 y += Surface::getFontHeight() + 1;
\r
467 setInfo(&thisEvent);
\r
470 void VEpg::updateEventList()
\r
473 for(UINT listIndex = 0; listIndex < 7; listIndex++)
\r
475 if (eventLista[listIndex])
\r
477 if ((eventLista[listIndex])->empty())
\r
478 (eventLista)[listIndex]->clear();
\r
480 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
\r
482 chan = (*chanList)[listTop + listIndex];
\r
483 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
487 void VEpg::setCurrentChannel(char* chname)
\r
489 chanName.setText(chname);
\r
494 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
\r
498 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
\r
499 UINT end = event->time + event->duration; // programme end time
\r
500 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
\r
502 x = 155; // LHS of window
\r
503 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
\r
504 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
\r
506 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
\r
508 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
\r
510 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
\r
511 w = MINUTE_SCALE * event->duration / 60;
\r
512 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
\r
513 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
\r
515 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
\r
516 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
\r
517 surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha));
\r
518 char* tt = new char[strlen(event->title) + 1];
\r
519 strcpy (tt, event->title);
\r
521 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
\r
523 int thisCharWidth = surface->getCharWidth(tt[textPos]);
\r
524 if (textWidth + thisCharWidth > w) // text will not fit in cell
\r
526 textWidth = textPos;
\r
529 textWidth += thisCharWidth;
\r
531 char* tT = new char[textWidth];
\r
534 strncpy(tT, tt, textWidth - 1);
\r
535 tT[textWidth - 1] = '\0';
\r
536 surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue);
\r
542 time_t VEpg::prevHour(time_t* t)
\r
545 tms = localtime(t);
\r
548 return mktime(tms);
\r