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[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
189 View::draw(); // draw pallet
\r
191 // Moved all the dynamic data drawing to a seperate function
\r
193 // Display the status and key stuff at the bottom
\r
194 int keyx = chanListbox.getOffsetX();
\r
195 int keyy = chanListbox.getOffsetY() + chanListbox.getHeight() + 2;
\r
196 surface->fillblt(keyx, keyy, 605, Surface::getFontHeight() * 2 + 14, surface->rgba(100, 100, 100, 255));
\r
199 w.setSurface(surface);
\r
201 w.nextSymbol = WSymbol::LEFTARROW;
\r
202 w.setSurfaceOffset(keyx + 1, keyy + 20);
\r
205 w.nextSymbol = WSymbol::UP;
\r
206 w.setSurfaceOffset(keyx + 26, keyy + 3);
\r
209 w.nextSymbol = WSymbol::DOWN;
\r
210 w.setSurfaceOffset(keyx + 26, keyy + 36);
\r
213 w.nextSymbol = WSymbol::RIGHTARROW;
\r
214 w.setSurfaceOffset(keyx + 50, keyy + 20);
\r
217 drawText(tr("OK"), keyx + 18, keyy + 20, Colour::LIGHTTEXT);
\r
219 surface->fillblt(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 0, 0, 255));
\r
220 drawText(tr("Page up"), keyx + 74, keyy + 5, Colour::LIGHTTEXT);
\r
222 surface->fillblt(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba(0, 200, 0, 255));
\r
223 drawText(tr("Page down"), keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
225 surface->fillblt(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 200, 0, 255));
\r
226 drawText(tr("-24 hours"), keyx + 182, keyy + 5, Colour::LIGHTTEXT);
\r
228 surface->fillblt(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba( 0, 0, 200, 255));
\r
229 drawText(tr("+24 hours"), keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
231 surface->fillblt(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
232 drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, Colour::LIGHTTEXT);
\r
234 surface->fillblt(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
235 Colour red = Colour(130, 0, 0);
\r
236 drawText(tr("Rec: Set timer"), keyx + 292, keyy + Surface::getFontHeight() + 9, red);
\r
238 surface->fillblt(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
239 w.nextSymbol = WSymbol::PLAY;
\r
240 w.setSurfaceOffset(keyx + 476, keyy + 5);
\r
242 drawText(tr("Sel channel"), keyx + 496, keyy + 5, Colour::LIGHTTEXT);
\r
244 surface->fillblt(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255));
\r
245 drawText(tr("Go: Preview"), keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
247 // Draw all the dynamic data
\r
251 void VEpg::drawData()
\r
253 // Not doing View::draw() every time causes
\r
254 // things not to be cleared off the surface properly
\r
255 // So, blank out the data area first
\r
258 chanListbox.getOffsetX(),
\r
259 chanListbox.getOffsetY() - Surface::getFontHeight() - 3,
\r
260 155 + WINDOW_WIDTH * MINUTE_SCALE,
\r
261 chanListbox.getHeight() + Surface::getFontHeight() + 3,
\r
264 chanListbox.draw();
\r
266 chanName.draw(); // TODO this should be dealt with by vvideolive
\r
272 int VEpg::handleCommand(int command)
\r
276 case Remote::DF_UP:
\r
278 { // cursor up the channel list
\r
281 viewman->updateView(this);
\r
284 case Remote::DF_DOWN:
\r
286 { // cursor down the channel list
\r
287 chanListbox.down();
\r
289 viewman->updateView(this);
\r
292 case Remote::DF_LEFT:
\r
294 { // cursor left through time
\r
295 selTime = thisEvent.time - 1;
\r
297 viewman->updateView(this);
\r
300 case Remote::DF_RIGHT:
\r
301 case Remote::RIGHT:
\r
303 // cursor right through time
\r
304 selTime = thisEvent.time + thisEvent.duration;
\r
306 viewman->updateView(this);
\r
311 // cursor up one page
\r
312 chanListbox.pageUp();
\r
314 viewman->updateView(this);
\r
317 case Remote::GREEN:
\r
319 // cursor down one page
\r
320 chanListbox.pageDown();
\r
322 viewman->updateView(this);
\r
327 // step forward 24 hours
\r
328 selTime += 24 * 60 * 60;
\r
330 viewman->updateView(this);
\r
333 case Remote::YELLOW:
\r
335 // step forward 24 hours
\r
336 selTime -= 24 * 60 * 60;
\r
338 viewman->updateView(this);
\r
341 case Remote::RECORD:
\r
343 Log::getInstance()->log("VEPG", Log::DEBUG, "ID %lu TIME %lu DURATION %lu TITLE %s", thisEvent.id, thisEvent.time, thisEvent.duration, thisEvent.title);
344 VEpgSetTimer* vs = new VEpgSetTimer(&thisEvent, (*chanList)[chanListbox.getCurrentOption()]);
347 viewman->updateView(vs);
353 { // select programme and display menu TODO currently just changes to selected channel
\r
354 videoLive->channelChange(VVideoLive::NUMBER, (*chanList)[chanListbox.getCurrentOption()]->number);
356 if(command == Remote::GO)
\r
358 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
\r
361 case Remote::GUIDE:
\r
363 // return to normal TV mode
\r
364 if (videoLive) // ptr check done in case being tested from videorec
\r
366 Message* m = new Message(); // Must be done after this view deleted
369 m->message = Message::EPG_CLOSE;
370 ViewMan::getInstance()->postMessage(m);
374 case Remote::CHANNELUP:
376 videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::UP);
379 case Remote::CHANNELDOWN:
381 videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::DOWN);
385 // stop command getting to any more views
\r
389 void VEpg::drawgrid() // redraws grid and select programme
\r
391 // draw the grid of programmes
\r
392 char timeString[20];
\r
394 time(&t); // set t = now
\r
396 selTime = t; // don't allow cursor in the past
\r
397 if(listTop != chanListbox.getTopOption())
\r
399 // chanListbox has scrolled TODO speed up by changing only rows that have changed
\r
400 listTop = chanListbox.getTopOption();
\r
403 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
\r
405 // we have cursored back before left time of window
\r
406 //TODO check that this and above don't happen together
\r
407 ltime = prevHour(&selTime);
\r
413 tms = localtime(&t);
\r
414 strftime(timeString, 19, "%a %e %b", tms);
\r
415 int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;
\r
417 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
\r
418 strftime(timeString, 19, "%H:%M", tms);
\r
419 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
\r
420 surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
422 tms = localtime(&t);
\r
423 strftime(timeString, 19, "%H:%M", tms);
\r
424 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
\r
425 surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
427 tms = localtime(&t);
\r
428 strftime(timeString, 19, "%H:%M", tms);
\r
429 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
\r
430 surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255));
\r
431 // pointer to selTime
\r
432 surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255));
\r
434 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
\r
436 Event noevent; // an event to use if there are gaps in the epg
\r
437 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
438 thisEvent.duration = WINDOW_WIDTH * 60;
\r
439 thisEvent.time = ltime;
\r
440 thisEvent.settitle(tr("No programme details"));
\r
442 bool swapColour = FALSE; // alternate cell colour
\r
443 bool currentRow = FALSE;
\r
444 int y = chanListbox.getOffsetY() + 5; // vertical position of cell
\r
445 Colour bg, fg; // background colour of cells in grid
\r
446 // for each displayed channel, find programmes that fall in 2.5 hour time window
\r
447 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
449 if (listTop + (int)listIndex >= chanListbox.getBottomOption())
\r
450 continue; // ensure nothing populates grid below last channel
\r
451 currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
\r
452 noevent.time = ltime;
\r
453 noevent.duration = WINDOW_WIDTH * 60;
\r
454 noevent.settitle("");
\r
455 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
\r
458 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
459 thisEvent.duration = WINDOW_WIDTH * 60;
\r
460 thisEvent.time = ltime;
\r
461 thisEvent.settitle(tr("No programme details"));
\r
464 if (eventLista[listIndex])
\r
466 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
\r
467 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
\r
469 fg = Colour::LIGHTTEXT;
\r
470 event = (*eventLista[listIndex])[e];
\r
473 UINT end = event->time + event->duration; // programme end time
\r
474 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
\r
475 continue; // that's enough of this channel's events
\r
476 if(end <= UINT(ltime)) // programme ends before LHS of window
\r
477 continue; // this event is before the window - let's try the next event
\r
478 // this event is one we are interested in
\r
479 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
\r
480 swapColour = !swapColour; // it wil be the other colour next time
\r
481 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
\r
483 // this is the selected programme
\r
484 thisEvent.setdescription(event->description);
\r
485 thisEvent.duration = event->duration;
\r
486 thisEvent.time = event->time;
\r
487 thisEvent.settitle(event->title);
\r
488 thisEvent.id = event->id;
\r
489 if(thisEvent.id == 0)
\r
491 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
492 fg = Colour::DARKTEXT;
\r
496 if (currentRow && thisEvent.id == 0)
\r
498 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
\r
499 thisEvent.time = end;
\r
500 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
\r
501 thisEvent.duration = event->time - thisEvent.time;
\r
504 paintCell(event, y, bg, fg);
\r
510 // no event list for this channel. Already painted noevent colour so just highlight if selected
\r
513 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
514 fg = Colour::DARKTEXT;
\r
515 paintCell(&thisEvent, y, bg, fg);
\r
519 bg = Colour::NOPROGRAMME;
\r
520 fg = Colour::LIGHTTEXT;
\r
521 noevent.settitle(tr("No programme details"));
\r
522 paintCell(&noevent, y, bg, fg);
\r
525 y += Surface::getFontHeight() + 2;
\r
527 setInfo(&thisEvent);
\r
530 void VEpg::updateEventList()
\r
533 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
535 if (eventLista[listIndex])
\r
537 if ((eventLista[listIndex])->empty())
\r
538 (eventLista)[listIndex]->clear();
\r
540 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
\r
542 chan = (*chanList)[listTop + listIndex];
\r
543 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
547 void VEpg::setCurrentChannel(char* chname)
\r
549 chanName.setText(chname);
\r
551 viewman->updateView(this);
\r
554 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
\r
557 w = x = 0; // keep compiler happy
560 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
\r
561 UINT end = event->time + event->duration; // programme end time
\r
562 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
\r
564 x = 155; // LHS of window
\r
565 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
\r
566 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
\r
568 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
\r
570 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
\r
572 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
\r
573 w = MINUTE_SCALE * event->duration / 60;
\r
574 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
\r
575 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
\r
577 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
\r
578 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
\r
579 surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha));
\r
580 char* tt = new char[strlen(event->title) + 1];
\r
581 strcpy (tt, event->title);
\r
583 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
\r
585 int thisCharWidth = surface->getCharWidth(tt[textPos]);
\r
586 if (textWidth + thisCharWidth > w) // text will not fit in cell
\r
588 textWidth = textPos;
\r
591 textWidth += thisCharWidth;
\r
593 char* tT = new char[textWidth];
\r
596 strncpy(tT, tt, textWidth - 1);
\r
597 tT[textWidth - 1] = '\0';
\r
598 surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue);
\r
604 time_t VEpg::prevHour(time_t* t)
\r
607 tms = localtime(t);
\r
610 return mktime(tms);
\r