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
125 for (UINT i = 0; i < chanList->size(); i++)
\r
127 chan = (*chanList)[i];
\r
128 if (i == currentChannel)
\r
130 chan->index = chanListbox.addOption(chan->name, first);
\r
133 chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
136 listTop = chanListbox.getTopOption();
\r
137 chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
\r
138 time(<ime); // set ltime to now
\r
139 ltime = prevHour(<ime); // set ltime to previous hour TODO make this half hour?
\r
140 time(&selTime); // set selTime to now
\r
141 updateEventList(); // get list of programmes
\r
148 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
150 if (eventLista[listIndex])
\r
152 (eventLista)[listIndex]->clear();
\r
153 delete eventLista[listIndex];
\r
156 // delete [] eventLista;
\r
158 // destroy dynamically allocated memory
\r
161 VEpg* VEpg::getInstance()
166 void VEpg::setInfo(Event* event)
\r
169 struct tm* btime; // to hold programme start and end time
\r
170 char timeString[9]; // to hold programme start and end time
\r
171 int length = strlen(event->title); // calculate length of programme title string
\r
172 char* title = new char[length + 15]; // create string to hold start time, end time and programme title
\r
173 btime = localtime((time_t*)&event->time); //get programme start time
\r
174 strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -
\r
175 strcpy(title, timeString); // put it in our buffer
\r
176 t = event->time + event->duration; //get programme end time
\r
177 btime = localtime(&t);
\r
178 strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -
\r
179 strcat(title, timeString); // put it in our buffer
\r
180 strcat(title, event->title); // then add the programme title
\r
181 progTitle.setText(title); // sput this sring in our text box
\r
182 length = strlen(event->description);
\r
183 char* info = new char[length + 1]; // create programme detail string
\r
184 strcpy(info, event->description);
\r
185 progInfo.setText(info); // show programme detail string
\r
186 // destroy dynamically allocated memory
\r
193 View::draw(); // draw pallet
\r
195 // Moved all the dynamic data drawing to a seperate function
\r
197 // Display the status and key stuff at the bottom
\r
198 int keyx = chanListbox.getOffsetX();
\r
199 int keyy = chanListbox.getOffsetY() + chanListbox.getHeight() + 2;
\r
200 rectangle(keyx, keyy, 605, Surface::getFontHeight() * 2 + 14, Colour(100, 100, 100, 255));
\r
203 w.setSurface(surface);
\r
205 w.nextSymbol = WSymbol::LEFTARROW;
\r
206 w.setSurfaceOffset(keyx + 1, keyy + 20);
\r
209 w.nextSymbol = WSymbol::UP;
\r
210 w.setSurfaceOffset(keyx + 26, keyy + 3);
\r
213 w.nextSymbol = WSymbol::DOWN;
\r
214 w.setSurfaceOffset(keyx + 26, keyy + 36);
\r
217 w.nextSymbol = WSymbol::RIGHTARROW;
\r
218 w.setSurfaceOffset(keyx + 50, keyy + 20);
\r
221 drawText(tr("OK"), keyx + 18, keyy + 20, Colour::LIGHTTEXT);
\r
223 rectangle(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, Colour(200, 0, 0, 255));
\r
224 drawText(tr("Page up"), keyx + 74, keyy + 5, Colour::LIGHTTEXT);
\r
226 rectangle(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, Colour(0, 200, 0, 255));
\r
227 drawText(tr("Page down"), keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
229 rectangle(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, Colour(200, 200, 0, 255));
\r
230 drawText(tr("-24 hours"), keyx + 182, keyy + 5, Colour::LIGHTTEXT);
\r
232 rectangle(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, Colour(0, 0, 200, 255));
\r
233 drawText(tr("+24 hours"), keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
235 rectangle(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, Colour(180, 180, 180, 255));
\r
236 drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, Colour::LIGHTTEXT);
\r
238 rectangle(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, Colour(180, 180, 180, 255));
\r
239 Colour red = Colour(130, 0, 0);
\r
240 drawText(tr("Rec: Set timer"), keyx + 292, keyy + Surface::getFontHeight() + 9, red);
\r
242 rectangle(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, Colour(180, 180, 180, 255));
\r
243 w.nextSymbol = WSymbol::PLAY;
\r
244 w.setSurfaceOffset(keyx + 476, keyy + 5);
\r
246 drawText(tr("Sel channel"), keyx + 496, keyy + 5, Colour::LIGHTTEXT);
\r
248 rectangle(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, Colour(180, 180, 180, 255));
\r
249 drawText(tr("Go: Preview"), keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
\r
251 // Draw all the dynamic data
\r
255 void VEpg::drawData()
\r
257 // Not doing View::draw() every time causes
\r
258 // things not to be cleared off the surface properly
\r
259 // So, blank out the data area first
\r
262 chanListbox.getOffsetX(),
\r
263 chanListbox.getOffsetY() - Surface::getFontHeight() - 3,
\r
264 155 + WINDOW_WIDTH * MINUTE_SCALE,
\r
265 chanListbox.getHeight() + Surface::getFontHeight() + 3,
\r
268 chanListbox.draw();
\r
270 chanName.draw(); // TODO this should be dealt with by vvideolive
\r
276 int VEpg::handleCommand(int command)
\r
280 case Remote::DF_UP:
\r
282 { // cursor up the channel list
\r
285 viewman->updateView(this);
\r
288 case Remote::DF_DOWN:
\r
290 { // cursor down the channel list
\r
291 chanListbox.down();
\r
293 viewman->updateView(this);
\r
296 case Remote::DF_LEFT:
\r
298 { // cursor left through time
\r
299 selTime = thisEvent.time - 1;
\r
301 viewman->updateView(this);
\r
304 case Remote::DF_RIGHT:
\r
305 case Remote::RIGHT:
\r
307 // cursor right through time
\r
308 selTime = thisEvent.time + thisEvent.duration;
\r
310 viewman->updateView(this);
\r
315 // cursor up one page
\r
316 chanListbox.pageUp();
\r
318 viewman->updateView(this);
\r
321 case Remote::GREEN:
\r
323 // cursor down one page
\r
324 chanListbox.pageDown();
\r
326 viewman->updateView(this);
\r
331 // step forward 24 hours
\r
332 selTime += 24 * 60 * 60;
\r
334 viewman->updateView(this);
\r
337 case Remote::YELLOW:
\r
339 // step forward 24 hours
\r
340 selTime -= 24 * 60 * 60;
\r
342 viewman->updateView(this);
\r
345 case Remote::RECORD:
\r
347 if (!chanList) return 2;
348 Log::getInstance()->log("VEPG", Log::DEBUG, "ID %lu TIME %lu DURATION %lu TITLE %s", thisEvent.id, thisEvent.time, thisEvent.duration, thisEvent.title);
349 VEpgSetTimer* vs = new VEpgSetTimer(&thisEvent, (*chanList)[chanListbox.getCurrentOption()]);
352 viewman->updateView(vs);
359 if (!chanList) return 2;
361 // select programme and display menu TODO currently just changes to selected channel
\r
362 videoLive->channelChange(VVideoLive::NUMBER, (*chanList)[chanListbox.getCurrentOption()]->number);
364 if(command == Remote::GO)
\r
366 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
\r
369 case Remote::GUIDE:
\r
371 // return to normal TV mode
\r
372 if (videoLive) // ptr check done in case being tested from videorec
\r
374 Message* m = new Message(); // Must be done after this view deleted
377 m->message = Message::EPG_CLOSE;
378 ViewMan::getInstance()->postMessage(m);
382 case Remote::CHANNELUP:
384 videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::UP);
387 case Remote::CHANNELDOWN:
389 videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::DOWN);
393 // stop command getting to any more views
\r
397 void VEpg::drawgrid() // redraws grid and select programme
\r
399 // draw the grid of programmes
\r
400 char timeString[20];
\r
402 time(&t); // set t = now
\r
404 selTime = t; // don't allow cursor in the past
\r
405 if(listTop != chanListbox.getTopOption())
\r
407 // chanListbox has scrolled TODO speed up by changing only rows that have changed
\r
408 listTop = chanListbox.getTopOption();
\r
411 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
\r
413 // we have cursored back before left time of window
\r
414 //TODO check that this and above don't happen together
\r
415 ltime = prevHour(&selTime);
\r
421 tms = localtime(&t);
\r
422 strftime(timeString, 19, "%a %e %b", tms);
\r
423 int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3;
\r
425 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
\r
426 strftime(timeString, 19, "%H:%M", tms);
\r
427 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
\r
428 rectangle(155, timey + Surface::getFontHeight(), 2, 7, Colour(255, 255, 255, 255));
\r
430 tms = localtime(&t);
\r
431 strftime(timeString, 19, "%H:%M", tms);
\r
432 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
\r
433 rectangle(335, timey + Surface::getFontHeight(), 2, 7, Colour(255, 255, 255, 255));
\r
435 tms = localtime(&t);
\r
436 strftime(timeString, 19, "%H:%M", tms);
\r
437 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
\r
438 rectangle(515, timey + Surface::getFontHeight(), 2, 7, Colour(255, 255, 255, 255));
\r
439 // pointer to selTime
\r
440 rectangle(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, Colour(255, 50, 50, 255));
\r
442 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
\r
444 Event noevent; // an event to use if there are gaps in the epg
\r
445 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
446 thisEvent.duration = WINDOW_WIDTH * 60;
\r
447 thisEvent.time = ltime;
\r
448 thisEvent.settitle(tr("No programme details"));
\r
450 bool swapColour = FALSE; // alternate cell colour
\r
451 bool currentRow = FALSE;
\r
452 int y = chanListbox.getOffsetY() + 5; // vertical position of cell
\r
453 Colour bg, fg; // background colour of cells in grid
\r
454 // for each displayed channel, find programmes that fall in 2.5 hour time window
\r
455 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
457 if (listTop + (int)listIndex >= chanListbox.getBottomOption())
\r
458 continue; // ensure nothing populates grid below last channel
\r
459 currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
\r
460 noevent.time = ltime;
\r
461 noevent.duration = WINDOW_WIDTH * 60;
\r
462 noevent.settitle("");
\r
463 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
\r
466 thisEvent.setdescription(tr("There are no programme details available for this period"));
\r
467 thisEvent.duration = WINDOW_WIDTH * 60;
\r
468 thisEvent.time = ltime;
\r
469 thisEvent.settitle(tr("No programme details"));
\r
472 if (eventLista[listIndex])
\r
474 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
\r
475 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
\r
477 fg = Colour::LIGHTTEXT;
\r
478 event = (*eventLista[listIndex])[e];
\r
481 UINT end = event->time + event->duration; // programme end time
\r
482 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
\r
483 continue; // that's enough of this channel's events
\r
484 if(end <= UINT(ltime)) // programme ends before LHS of window
\r
485 continue; // this event is before the window - let's try the next event
\r
486 // this event is one we are interested in
\r
487 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
\r
488 swapColour = !swapColour; // it wil be the other colour next time
\r
489 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
\r
491 // this is the selected programme
\r
492 thisEvent.setdescription(event->description);
\r
493 thisEvent.duration = event->duration;
\r
494 thisEvent.time = event->time;
\r
495 thisEvent.settitle(event->title);
\r
496 thisEvent.id = event->id;
\r
497 if(thisEvent.id == 0)
\r
499 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
500 fg = Colour::DARKTEXT;
\r
504 if (currentRow && thisEvent.id == 0)
\r
506 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
\r
507 thisEvent.time = end;
\r
508 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
\r
509 thisEvent.duration = event->time - thisEvent.time;
\r
512 paintCell(event, y, bg, fg);
\r
518 // no event list for this channel. Already painted noevent colour so just highlight if selected
\r
521 bg = Colour::SELECTHIGHLIGHT; // highlight cell
\r
522 fg = Colour::DARKTEXT;
\r
523 paintCell(&thisEvent, y, bg, fg);
\r
527 bg = Colour::NOPROGRAMME;
\r
528 fg = Colour::LIGHTTEXT;
\r
529 noevent.settitle(tr("No programme details"));
\r
530 paintCell(&noevent, y, bg, fg);
\r
533 y += Surface::getFontHeight() + 2;
\r
535 setInfo(&thisEvent);
\r
538 void VEpg::updateEventList()
\r
540 if (!chanList) return;
542 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
\r
544 if (eventLista[listIndex])
\r
546 if ((eventLista[listIndex])->empty())
\r
547 (eventLista)[listIndex]->clear();
\r
549 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
\r
551 chan = (*chanList)[listTop + listIndex];
\r
553 EventList* newEventList = 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
554 if (newEventList) eventLista[listIndex] = newEventList;
\r
558 void VEpg::setCurrentChannel(char* chname)
\r
560 chanName.setText(chname);
\r
562 viewman->updateView(this);
\r
565 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
\r
568 w = x = 0; // keep compiler happy
571 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
\r
572 UINT end = event->time + event->duration; // programme end time
\r
573 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
\r
575 x = 155; // LHS of window
\r
576 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
\r
577 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
\r
579 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
\r
581 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
\r
583 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
\r
584 w = MINUTE_SCALE * event->duration / 60;
\r
585 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
\r
586 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
\r
588 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
\r
589 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
\r
590 rectangle(x, y, w, h, bg);
\r
591 char* tt = new char[strlen(event->title) + 1];
\r
592 strcpy (tt, event->title);
\r
594 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
\r
596 int thisCharWidth = surface->getCharWidth(tt[textPos]);
\r
597 if (textWidth + thisCharWidth > w) // text will not fit in cell
\r
599 textWidth = textPos;
\r
602 textWidth += thisCharWidth;
\r
604 char* tT = new char[textWidth];
\r
607 strncpy(tT, tt, textWidth - 1);
\r
608 tT[textWidth - 1] = '\0';
\r
609 surface->drawText(tT, x+2, y, fg.rgba());
\r
615 time_t VEpg::prevHour(time_t* t)
\r
618 tms = localtime(t);
\r
621 return mktime(tms);
\r