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::instance = NULL;
38 VEpg::VEpg(VVideoLive* v, UINT currentChannel)
\r
42 // PAL / NTSC sizes -----------------------
46 int summaryLines, summaryLowerPadding;
48 //UINT gridRows; // is a class member
50 if (Video::getInstance()->getFormat() == Video::PAL)
57 summaryLowerPadding = 16;
68 summaryLowerPadding = 26;
73 // initialise variables and pointers
74 viewman = ViewMan::getInstance();
\r
77 chanList = VDR::getInstance()->getChannelsList(VDR::VIDEO); //TODO want to be able to display video and radio together
80 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
82 // initialise array of pointers to eventlist structures
\r
83 eventLista[listIndex] = NULL;
\r
86 // Create pallet on which to paint our epg view and position it in centre of screen.
\r
87 // Need to reduce size to deal with overscanning TVs.
\r
89 create(xsize, ysize);
\r
90 setScreenPos(xpos, ypos);
\r
93 Colour transparent = Colour(0, 0, 0, 0);
\r
94 setBackgroundColour(transparent);
\r
96 progTitle.setSurface(surface);
\r
97 progTitle.setSurfaceOffset(0,0);
\r
98 progTitle.setDimensions(300,(Surface::getFontHeight() + 4) * 2 + 16); //paragraph line seperation is 4 pixels
\r
99 progTitle.setBackgroundColour(Colour::TITLEBARBACKGROUND);
\r
100 progTitle.setTextPos(5, 16);
103 progInfo.setSurface(surface);
\r
104 progInfo.setSurfaceOffset(0, progTitle.getOffsetY() + progTitle.getHeight());
\r
105 progInfo.setDimensions(300,((Surface::getFontHeight() + 4) * summaryLines) + summaryLowerPadding);
\r
108 chanName.setSurface(surface);
\r
109 chanName.setDimensions(510, (Surface::getFontHeight() + 4));
\r
110 chanName.setSurfaceOffset(305, chanNameYpos);
\r
111 chanName.setBackgroundColour(Colour(0, 0, 0, 90));
\r
113 // create area to display list of channels
\r
114 chanListbox.setSurface(surface); // add channel list
\r
115 chanListbox.setSurfaceOffset(0, progInfo.getOffsetY() + progInfo.getHeight() + Surface::getFontHeight() + 8); // position channel list
\r
116 chanListbox.setDimensions(150, ((Surface::getFontHeight() + 2) * gridRows) + 5); //listbox line seperation is 2 pixels
\r
117 chanListbox.setGap(2);
120 // populate channel list
\r
123 for (UINT i = 0; i < chanList->size(); i++)
\r
125 chan = (*chanList)[i];
\r
126 if (i == currentChannel)
\r
128 chan->index = chanListbox.addOption(chan->name, first);
\r
131 listTop = chanListbox.getTopOption();
\r
132 chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
\r
133 chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
\r
134 time(<ime); // set ltime to now
\r
135 ltime = prevHour(<ime); // set ltime to previous hour TODO make this half hour?
\r
136 time(&selTime); // set selTime to now
\r
137 updateEventList(); // get list of programmes
\r
144 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
146 if (eventLista[listIndex])
\r
148 (eventLista)[listIndex]->clear();
\r
149 delete eventLista[listIndex];
\r
152 // delete [] eventLista;
\r
154 // destroy dynamically allocated memory
\r
157 VEpg* VEpg::getInstance()
162 void VEpg::setInfo(Event* event)
\r
165 struct tm* btime; // to hold programme start and end time
\r
166 char* timeString = new char[9]; // to hold programme start and end time
\r
167 int length = strlen(event->title); // calculate length of programme title string
\r
168 char* title = new char[length + 15]; // create string to hold start time, end time and programme title
\r
169 btime = localtime((time_t*)&event->time); //get programme start time
\r
170 strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -
\r
171 strcpy(title, timeString); // put it in our buffer
\r
172 t = event->time + event->duration; //get programme end time
\r
173 btime = localtime(&t);
\r
174 strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -
\r
175 strcat(title, timeString); // put it in our buffer
\r
176 strcat(title, event->title); // then add the programme title
\r
177 progTitle.setText(title); // sput this sring in our text box
\r
178 length = strlen(event->description);
\r
179 char* info = new char[length + 1]; // create programme detail string
\r
180 strcpy(info, event->description);
\r
181 progInfo.setText(info); // show programme detail string
\r
182 // destroy dynamically allocated memory
\r
190 View::draw(); // draw pallet
\r
192 // Moved all the dynamic data drawing to a seperate function
\r
194 // Display the status and key stuff at the bottom
\r
195 int keyx = chanListbox.getOffsetX();
\r
196 int keyy = chanListbox.getOffsetY() + chanListbox.getHeight() + 2;
\r
197 surface->fillblt(keyx, keyy, 605, Surface::getFontHeight() * 2 + 14, surface->rgba(100, 100, 100, 255));
\r
200 w.setSurface(surface);
\r
202 w.nextSymbol = WSymbol::LEFTARROW;
\r
203 w.setSurfaceOffset(keyx + 1, keyy + 20);
\r
206 w.nextSymbol = WSymbol::UP;
\r
207 w.setSurfaceOffset(keyx + 26, keyy + 3);
\r
210 w.nextSymbol = WSymbol::DOWN;
\r
211 w.setSurfaceOffset(keyx + 26, keyy + 36);
\r
214 w.nextSymbol = WSymbol::RIGHTARROW;
\r
215 w.setSurfaceOffset(keyx + 50, keyy + 20);
\r
218 drawText(tr("OK"), keyx + 18, keyy + 20, Colour::LIGHTTEXT);
\r
220 surface->fillblt(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 0, 0, 255));
\r
221 drawText(tr("Page up"), keyx + 74, keyy + 5, Colour::LIGHTTEXT);
\r
223 surface->fillblt(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba(0, 200, 0, 255));
\r
224 drawText(tr("Page down"), keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
226 surface->fillblt(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 200, 0, 255));
\r
227 drawText(tr("-24 hours"), keyx + 182, keyy + 5, Colour::LIGHTTEXT);
\r
229 surface->fillblt(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba( 0, 0, 200, 255));
\r
230 drawText(tr("+24 hours"), keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
232 surface->fillblt(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
233 drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, Colour::LIGHTTEXT);
\r
235 surface->fillblt(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
236 // Colour red = Colour(130, 0, 0);
\r
237 // drawText(tr("Rec: Set timer"), keyx + 292, keyy + Surface::getFontHeight() + 9, red);
\r
239 surface->fillblt(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
240 w.nextSymbol = WSymbol::PLAY;
\r
241 w.setSurfaceOffset(keyx + 476, keyy + 5);
\r
243 drawText(tr("Sel channel"), keyx + 496, keyy + 5, Colour::LIGHTTEXT);
\r
245 surface->fillblt(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
246 drawText(tr("Go: Preview"), keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
248 // Draw all the dynamic data
\r
252 void VEpg::drawData()
\r
254 // Not doing View::draw() every time causes
\r
255 // things not to be cleared off the surface properly
\r
256 // So, blank out the data area first
\r
259 chanListbox.getOffsetX(),
\r
260 chanListbox.getOffsetY() - Surface::getFontHeight() - 3,
\r
261 155 + WINDOW_WIDTH * MINUTE_SCALE,
\r
262 chanListbox.getHeight() + Surface::getFontHeight() + 3,
\r
265 chanListbox.draw();
\r
267 chanName.draw(); // TODO this should be dealt with by vvideolive
\r
273 int VEpg::handleCommand(int command)
\r
277 case Remote::DF_UP:
\r
279 { // cursor up the channel list
\r
282 viewman->updateView(this);
\r
285 case Remote::DF_DOWN:
\r
287 { // cursor down the channel list
\r
288 chanListbox.down();
\r
290 viewman->updateView(this);
\r
293 case Remote::DF_LEFT:
\r
295 { // cursor left through time
\r
296 selTime = thisEvent.time - 1;
\r
298 viewman->updateView(this);
\r
301 case Remote::DF_RIGHT:
\r
302 case Remote::RIGHT:
\r
304 // cursor right through time
\r
305 selTime = thisEvent.time + thisEvent.duration;
\r
307 viewman->updateView(this);
\r
312 // cursor up one page
\r
313 chanListbox.pageUp();
\r
315 viewman->updateView(this);
\r
318 case Remote::GREEN:
\r
320 // cursor down one page
\r
321 chanListbox.pageDown();
\r
323 viewman->updateView(this);
\r
328 // step forward 24 hours
\r
329 selTime += 24 * 60 * 60;
\r
331 viewman->updateView(this);
\r
334 case Remote::YELLOW:
\r
336 // step forward 24 hours
\r
337 selTime -= 24 * 60 * 60;
\r
339 viewman->updateView(this);
\r
342 case Remote::RECORD:
\r
350 { // select programme and display menu TODO currently just changes to selected channel
\r
351 Message* m = new Message();
\r
354 m->message = Message::CHANNEL_CHANGE;
\r
355 m->parameter = (*chanList)[chanListbox.getCurrentOption()]->number;
\r
356 ViewMan::getInstance()->postMessage(m);
\r
357 if(command == Remote::GO)
\r
359 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
\r
362 case Remote::GUIDE:
\r
364 // return to normal TV mode
\r
365 if (videoLive) // ptr check done in case being tested from videorec
\r
367 Message* m = new Message();
370 m->message = Message::EPG_CLOSE;
371 ViewMan::getInstance()->postMessage(m);
375 case Remote::CHANNELUP:
\r
377 // change up channel on live TV
\r
378 Message* m = new Message();
\r
381 m->message = Message::CHANNEL_UP;
\r
382 ViewMan::getInstance()->postMessage(m);
\r
385 case Remote::CHANNELDOWN:
\r
386 { // change down channel on live TV
\r
387 Message* m = new Message();
\r
390 m->message = Message::CHANNEL_DOWN;
\r
391 ViewMan::getInstance()->postMessage(m);
\r
395 // stop command getting to any more views
\r
399 void VEpg::drawgrid() // redraws grid and select programme
\r
401 // draw the grid of programmes
\r
402 char timeString[20];
\r
404 time(&t); // set t = now
\r
406 selTime = t; // don't allow cursor in the past
\r
407 if(listTop != chanListbox.getTopOption())
\r
409 // chanListbox has scrolled TODO speed up by changing only rows that have changed
\r
410 listTop = chanListbox.getTopOption();
\r
413 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
\r
415 // we have cursored back before left time of window
\r
416 //TODO check that this and above don't happen together
\r
417 ltime = prevHour(&selTime);
\r
423 tms = localtime(&t);
\r
424 strftime(timeString, 19, "%a %e %b", tms);
\r
425 int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;
\r
427 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
\r
428 strftime(timeString, 19, "%H:%M", tms);
\r
429 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
\r
430 surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
432 tms = localtime(&t);
\r
433 strftime(timeString, 19, "%H:%M", tms);
\r
434 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
\r
435 surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
437 tms = localtime(&t);
\r
438 strftime(timeString, 19, "%H:%M", tms);
\r
439 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
\r
440 surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
441 // pointer to selTime
\r
442 surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255));
\r
444 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
\r
446 Event noevent; // an event to use if there are gaps in the epg
\r
447 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
448 thisEvent.duration = WINDOW_WIDTH * 60;
\r
449 thisEvent.time = ltime;
\r
450 thisEvent.settitle(tr("No programme details"));
\r
452 bool swapColour = FALSE; // alternate cell colour
\r
453 bool currentRow = FALSE;
\r
454 int y = chanListbox.getOffsetY() + 5; // vertical position of cell
\r
455 Colour bg, fg; // background colour of cells in grid
\r
456 // for each displayed channel, find programmes that fall in 2.5 hour time window
\r
457 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
459 if (listTop + (int)listIndex >= chanListbox.getBottomOption())
\r
460 continue; // ensure nothing populates grid below last channel
\r
461 currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
\r
462 noevent.time = ltime;
\r
463 noevent.duration = WINDOW_WIDTH * 60;
\r
464 noevent.settitle("");
\r
465 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
\r
468 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
469 thisEvent.duration = WINDOW_WIDTH * 60;
\r
470 thisEvent.time = ltime;
\r
471 thisEvent.settitle(tr("No programme details"));
\r
474 if (eventLista[listIndex])
\r
476 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
\r
477 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
\r
479 fg = Colour::LIGHTTEXT;
\r
480 event = (*eventLista[listIndex])[e];
\r
483 UINT end = event->time + event->duration; // programme end time
\r
484 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
\r
485 continue; // that's enough of this channel's events
\r
486 if(end <= UINT(ltime)) // programme ends before LHS of window
\r
487 continue; // this event is before the window - let's try the next event
\r
488 // this event is one we are interested in
\r
489 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
\r
490 swapColour = !swapColour; // it wil be the other colour next time
\r
491 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
\r
493 // this is the selected programme
\r
494 thisEvent.setdescription(event->description);
\r
495 thisEvent.duration = event->duration;
\r
496 thisEvent.time = event->time;
\r
497 thisEvent.settitle(event->title);
\r
498 thisEvent.id = event->id;
\r
499 if(thisEvent.id == 0)
\r
501 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
502 fg = Colour::DARKTEXT;
\r
506 if (currentRow && thisEvent.id == 0)
\r
508 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
\r
509 thisEvent.time = end;
\r
510 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
\r
511 thisEvent.duration = event->time - thisEvent.time;
\r
514 paintCell(event, y, bg, fg);
\r
520 // no event list for this channel. Already painted noevent colour so just highlight if selected
\r
523 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
524 fg = Colour::DARKTEXT;
\r
525 paintCell(&thisEvent, y, bg, fg);
\r
529 bg = Colour::NOPROGRAMME;
\r
530 fg = Colour::LIGHTTEXT;
\r
531 noevent.settitle(tr("No programme details"));
\r
532 paintCell(&noevent, y, bg, fg);
\r
535 y += Surface::getFontHeight() + 2;
\r
537 setInfo(&thisEvent);
\r
540 void VEpg::updateEventList()
\r
543 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
545 if (eventLista[listIndex])
\r
547 if ((eventLista[listIndex])->empty())
\r
548 (eventLista)[listIndex]->clear();
\r
550 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
\r
552 chan = (*chanList)[listTop + listIndex];
\r
553 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
557 void VEpg::setCurrentChannel(char* chname)
\r
559 chanName.setText(chname);
\r
561 viewman->updateView(this);
\r
564 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
\r
567 w = x = 0; // keep compiler happy
570 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
\r
571 UINT end = event->time + event->duration; // programme end time
\r
572 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
\r
574 x = 155; // LHS of window
\r
575 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
\r
576 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
\r
578 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
\r
580 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
\r
582 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
\r
583 w = MINUTE_SCALE * event->duration / 60;
\r
584 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
\r
585 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
\r
587 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
\r
588 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
\r
589 surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha));
\r
590 char* tt = new char[strlen(event->title) + 1];
\r
591 strcpy (tt, event->title);
\r
593 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
\r
595 int thisCharWidth = surface->getCharWidth(tt[textPos]);
\r
596 if (textWidth + thisCharWidth > w) // text will not fit in cell
\r
598 textWidth = textPos;
\r
601 textWidth += thisCharWidth;
\r
603 char* tT = new char[textWidth];
\r
606 strncpy(tT, tt, textWidth - 1);
\r
607 tT[textWidth - 1] = '\0';
\r
608 surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue);
\r
614 time_t VEpg::prevHour(time_t* t)
\r
617 tms = localtime(t);
\r
620 return mktime(tms);
\r