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 // initialise variables and pointers
39 viewman = ViewMan::getInstance();
\r
42 chanList = VDR::getInstance()->getChannelsList(VDR::VIDEO); //TODO want to be able to display video and radio together
45 for(UINT listIndex = 0; listIndex < 7; listIndex++)
\r
47 // initialise array of pointers to eventlist structures
\r
48 eventLista[listIndex] = NULL;
\r
51 // Create pallet on which to paint our epg view and position it in centre of screen.
\r
52 // Need to reduce size to deal with overscanning TVs.
\r
54 // FIXME have size for ntsc
\r
55 if (Video::getInstance()->getFormat() == Video::PAL)
\r
58 setScreenPos(60, 16);
\r
63 setScreenPos(50, 10);
\r
67 Colour transparent = Colour(0, 0, 0, 0);
\r
68 setBackgroundColour(transparent);
\r
70 progTitle.setSurface(surface);
\r
71 progTitle.setSurfaceOffset(0,0);
\r
72 progTitle.setDimensions(300,(Surface::getFontHeight() + 6) * 2 + 16); //paragraph line seperation is 6 pixels
\r
73 progTitle.setBackgroundColour(Colour::TITLEBARBACKGROUND);
\r
74 progTitle.setTextPos(5, 16);
76 progInfo.setSurface(surface);
\r
77 progInfo.setSurfaceOffset(0, progTitle.getOffsetY() + progTitle.getHeight());
\r
78 progInfo.setDimensions(300,(Surface::getFontHeight() + 4) * 8 + 12);
\r
81 chanName.setSurface(surface);
\r
82 chanName.setDimensions(510, (Surface::getFontHeight() + 4));
\r
83 chanName.setSurfaceOffset(305,244);
\r
84 chanName.setBackgroundColour(Colour(0, 0, 0, 90));
\r
86 // create area to display list of channels
\r
87 chanListbox.setSurface(surface); // add channel list
\r
88 chanListbox.setSurfaceOffset(0, progInfo.getOffsetY() + progInfo.getHeight() + Surface::getFontHeight() + 8); // position channel list
\r
89 chanListbox.setDimensions(150, (Surface::getFontHeight() + 2) * 7 + 5); //listbox line seperation is 1 pixel
\r
90 chanListbox.setGap(2);
93 // populate channel list
\r
96 for (UINT i = 0; i < chanList->size(); i++)
\r
98 chan = (*chanList)[i];
\r
99 if (i == currentChannel)
\r
101 chan->index = chanListbox.addOption(chan->name, first);
\r
104 listTop = chanListbox.getTopOption();
\r
105 chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
\r
106 chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
\r
107 time(<ime); // set ltime to now
\r
108 ltime = prevHour(<ime); // set ltime to previous hour TODO make this half hour?
\r
109 time(&selTime); // set selTime to now
\r
110 updateEventList(); // get list of programmes
\r
115 for(int listIndex = 0; listIndex < 7; listIndex++)
\r
117 if (eventLista[listIndex])
\r
119 (eventLista)[listIndex]->clear();
\r
120 delete eventLista[listIndex];
\r
123 // delete [] eventLista;
\r
125 // destroy dynamically allocated memory
\r
128 void VEpg::setInfo(Event* event)
\r
131 struct tm* btime; // to hold programme start and end time
\r
132 char* timeString = new char[9]; // to hold programme start and end time
\r
133 int length = strlen(event->title); // calculate length of programme title string
\r
134 char* title = new char[length + 15]; // create string to hold start time, end time and programme title
\r
135 btime = localtime((time_t*)&event->time); //get programme start time
\r
136 strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -
\r
137 strcpy(title, timeString); // put it in our buffer
\r
138 t = event->time + event->duration; //get programme end time
\r
139 btime = localtime(&t);
\r
140 strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -
\r
141 strcat(title, timeString); // put it in our buffer
\r
142 strcat(title, event->title); // then add the programme title
\r
143 progTitle.setText(title); // sput this sring in our text box
\r
144 length = strlen(event->description);
\r
145 char* info = new char[length + 1]; // create programme detail string
\r
146 strcpy(info, event->description);
\r
147 progInfo.setText(info); // show programme detail string
\r
148 // destroy dynamically allocated memory
\r
156 View::draw(); // draw pallet
\r
158 // Moved all the dynamic data drawing to a seperate function
\r
160 // Display the status and key stuff at the bottom
\r
161 int keyx = chanListbox.getOffsetX();
\r
162 int keyy = chanListbox.getOffsetY() + chanListbox.getHeight() + 2;
\r
163 surface->fillblt(keyx, keyy, 605, Surface::getFontHeight() * 2 + 14, surface->rgba(100, 100, 100, 255));
\r
166 w.setSurface(surface);
\r
168 w.nextSymbol = WSymbol::LEFTARROW;
\r
169 w.setSurfaceOffset(keyx + 1, keyy + 20);
\r
172 w.nextSymbol = WSymbol::UP;
\r
173 w.setSurfaceOffset(keyx + 26, keyy + 3);
\r
176 w.nextSymbol = WSymbol::DOWN;
\r
177 w.setSurfaceOffset(keyx + 26, keyy + 36);
\r
180 w.nextSymbol = WSymbol::RIGHTARROW;
\r
181 w.setSurfaceOffset(keyx + 50, keyy + 20);
\r
184 drawText(tr("OK"), keyx + 18, keyy + 20, Colour::LIGHTTEXT);
\r
186 surface->fillblt(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 0, 0, 255));
\r
187 drawText(tr("Page up"), keyx + 74, keyy + 5, Colour::LIGHTTEXT);
\r
189 surface->fillblt(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba(0, 200, 0, 255));
\r
190 drawText(tr("Page down"), keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
192 surface->fillblt(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 200, 0, 255));
\r
193 drawText(tr("-24 hours"), keyx + 182, keyy + 5, Colour::LIGHTTEXT);
\r
195 surface->fillblt(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba( 0, 0, 200, 255));
\r
196 drawText(tr("+24 hours"), keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
198 surface->fillblt(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
199 drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, Colour::LIGHTTEXT);
\r
201 surface->fillblt(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
202 // Colour red = Colour(130, 0, 0);
\r
203 // drawText(tr("Rec: Set timer"), keyx + 292, keyy + Surface::getFontHeight() + 9, red);
\r
205 surface->fillblt(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
206 w.nextSymbol = WSymbol::PLAY;
\r
207 w.setSurfaceOffset(keyx + 476, keyy + 5);
\r
209 drawText(tr("Sel channel"), keyx + 496, keyy + 5, Colour::LIGHTTEXT);
\r
211 surface->fillblt(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
212 drawText(tr("Go: Preview"), keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
214 // Draw all the dynamic data
\r
218 void VEpg::drawData()
\r
220 // Not doing View::draw() every time causes
\r
221 // things not to be cleared off the surface properly
\r
222 // So, blank out the data area first
\r
225 chanListbox.getOffsetX(),
\r
226 chanListbox.getOffsetY() - Surface::getFontHeight() - 3,
\r
227 155 + WINDOW_WIDTH * MINUTE_SCALE,
\r
228 chanListbox.getHeight() + Surface::getFontHeight() + 3,
\r
231 chanListbox.draw();
\r
233 chanName.draw(); // TODO this should be dealt with by vvideolive
\r
239 int VEpg::handleCommand(int command)
\r
243 case Remote::DF_UP:
\r
245 { // cursor up the channel list
\r
248 viewman->updateView(this);
\r
251 case Remote::DF_DOWN:
\r
253 { // cursor down the channel list
\r
254 chanListbox.down();
\r
256 viewman->updateView(this);
\r
259 case Remote::DF_LEFT:
\r
261 { // cursor left through time
\r
262 selTime = thisEvent.time - 1;
\r
264 viewman->updateView(this);
\r
267 case Remote::DF_RIGHT:
\r
268 case Remote::RIGHT:
\r
270 // cursor right through time
\r
271 selTime = thisEvent.time + thisEvent.duration;
\r
273 viewman->updateView(this);
\r
278 // cursor up one page
\r
279 chanListbox.pageUp();
\r
281 viewman->updateView(this);
\r
284 case Remote::GREEN:
\r
286 // cursor down one page
\r
287 chanListbox.pageDown();
\r
289 viewman->updateView(this);
\r
294 // step forward 24 hours
\r
295 selTime += 24 * 60 * 60;
\r
297 viewman->updateView(this);
\r
300 case Remote::YELLOW:
\r
302 // step forward 24 hours
\r
303 selTime -= 24 * 60 * 60;
\r
305 viewman->updateView(this);
\r
308 case Remote::RECORD:
\r
316 { // select programme and display menu TODO currently just changes to selected channel
\r
317 Message* m = new Message();
\r
320 m->message = Message::CHANNEL_CHANGE;
\r
321 m->parameter = (*chanList)[chanListbox.getCurrentOption()]->number;
\r
322 ViewMan::getInstance()->postMessage(m);
\r
323 if(command == Remote::GO)
\r
325 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
\r
328 case Remote::GUIDE:
\r
330 // return to normal TV mode
\r
331 videoLive->setEpgMode(FALSE);
\r
334 case Remote::CHANNELUP:
\r
336 // change up channel on live TV
\r
337 Message* m = new Message();
\r
340 m->message = Message::CHANNEL_UP;
\r
341 ViewMan::getInstance()->postMessage(m);
\r
344 case Remote::CHANNELDOWN:
\r
345 { // change down channel on live TV
\r
346 Message* m = new Message();
\r
349 m->message = Message::CHANNEL_DOWN;
\r
350 ViewMan::getInstance()->postMessage(m);
\r
354 // stop command getting to any more views
\r
358 void VEpg::drawgrid() // redraws grid and select programme
\r
360 // draw the grid of programmes
\r
361 char timeString[20];
\r
363 time(&t); // set t = now
\r
365 selTime = t; // don't allow cursor in the past
\r
366 if(listTop != chanListbox.getTopOption())
\r
368 // chanListbox has scrolled TODO speed up by changing only rows that have changed
\r
369 listTop = chanListbox.getTopOption();
\r
372 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
\r
374 // we have cursored back before left time of window
\r
375 //TODO check that this and above don't happen together
\r
376 ltime = prevHour(&selTime);
\r
382 tms = localtime(&t);
\r
383 strftime(timeString, 19, "%a %e %b", tms);
\r
384 int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;
\r
386 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
\r
387 strftime(timeString, 19, "%H:%M", tms);
\r
388 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
\r
389 surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
391 tms = localtime(&t);
\r
392 strftime(timeString, 19, "%H:%M", tms);
\r
393 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
\r
394 surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
396 tms = localtime(&t);
\r
397 strftime(timeString, 19, "%H:%M", tms);
\r
398 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
\r
399 surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
400 // pointer to selTime
\r
401 surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255));
\r
403 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
\r
405 Event noevent; // an event to use if there are gaps in the epg
\r
406 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
407 thisEvent.duration = WINDOW_WIDTH * 60;
\r
408 thisEvent.time = ltime;
\r
409 thisEvent.settitle(tr("No programme details"));
\r
411 bool swapColour = FALSE; // alternate cell colour
\r
412 bool currentRow = FALSE;
\r
413 int y = chanListbox.getOffsetY() + 5; // vertical position of cell
\r
414 Colour bg, fg; // background colour of cells in grid
\r
415 // for each displayed channel, find programmes that fall in 2.5 hour time window
\r
416 for(int listIndex = 0; listIndex < 7; listIndex++)
\r
418 if (listTop + listIndex >= chanListbox.getBottomOption())
\r
419 continue; // ensure nothing populates grid below last channel
\r
420 currentRow = (listTop + listIndex == chanListbox.getCurrentOption());
\r
421 noevent.time = ltime;
\r
422 noevent.duration = WINDOW_WIDTH * 60;
\r
423 noevent.settitle("");
\r
424 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
\r
427 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
428 thisEvent.duration = WINDOW_WIDTH * 60;
\r
429 thisEvent.time = ltime;
\r
430 thisEvent.settitle(tr("No programme details"));
\r
433 if (eventLista[listIndex])
\r
435 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
\r
436 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
\r
438 fg = Colour::LIGHTTEXT;
\r
439 event = (*eventLista[listIndex])[e];
\r
442 UINT end = event->time + event->duration; // programme end time
\r
443 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
\r
444 continue; // that's enough of this channel's events
\r
445 if(end <= UINT(ltime)) // programme ends before LHS of window
\r
446 continue; // this event is before the window - let's try the next event
\r
447 // this event is one we are interested in
\r
448 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
\r
449 swapColour = !swapColour; // it wil be the other colour next time
\r
450 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
\r
452 // this is the selected programme
\r
453 thisEvent.setdescription(event->description);
\r
454 thisEvent.duration = event->duration;
\r
455 thisEvent.time = event->time;
\r
456 thisEvent.settitle(event->title);
\r
457 thisEvent.id = event->id;
\r
458 if(thisEvent.id == 0)
\r
460 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
461 fg = Colour::DARKTEXT;
\r
465 if (currentRow && thisEvent.id == 0)
\r
467 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
\r
468 thisEvent.time = end;
\r
469 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
\r
470 thisEvent.duration = event->time - thisEvent.time;
\r
473 paintCell(event, y, bg, fg);
\r
479 // no event list for this channel. Already painted noevent colour so just highlight if selected
\r
482 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
483 fg = Colour::DARKTEXT;
\r
484 paintCell(&thisEvent, y, bg, fg);
\r
488 bg = Colour::NOPROGRAMME;
\r
489 fg = Colour::LIGHTTEXT;
\r
490 noevent.settitle(tr("No programme details"));
\r
491 paintCell(&noevent, y, bg, fg);
\r
494 y += Surface::getFontHeight() + 2;
\r
496 setInfo(&thisEvent);
\r
499 void VEpg::updateEventList()
\r
502 for(UINT listIndex = 0; listIndex < 7; listIndex++)
\r
504 if (eventLista[listIndex])
\r
506 if ((eventLista[listIndex])->empty())
\r
507 (eventLista)[listIndex]->clear();
\r
509 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
\r
511 chan = (*chanList)[listTop + listIndex];
\r
512 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
516 void VEpg::setCurrentChannel(char* chname)
\r
518 chanName.setText(chname);
\r
520 viewman->updateView(this);
\r
523 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
\r
527 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
\r
528 UINT end = event->time + event->duration; // programme end time
\r
529 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
\r
531 x = 155; // LHS of window
\r
532 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
\r
533 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
\r
535 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
\r
537 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
\r
539 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
\r
540 w = MINUTE_SCALE * event->duration / 60;
\r
541 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
\r
542 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
\r
544 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
\r
545 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
\r
546 surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha));
\r
547 char* tt = new char[strlen(event->title) + 1];
\r
548 strcpy (tt, event->title);
\r
550 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
\r
552 int thisCharWidth = surface->getCharWidth(tt[textPos]);
\r
553 if (textWidth + thisCharWidth > w) // text will not fit in cell
\r
555 textWidth = textPos;
\r
558 textWidth += thisCharWidth;
\r
560 char* tT = new char[textWidth];
\r
563 strncpy(tT, tt, textWidth - 1);
\r
564 tT[textWidth - 1] = '\0';
\r
565 surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue);
\r
571 time_t VEpg::prevHour(time_t* t)
\r
574 tms = localtime(t);
\r
577 return mktime(tms);
\r