From bc26e1fc280470a7e45bf6030b75b06aed7689f7 Mon Sep 17 00:00:00 2001 From: Chris Tallon Date: Thu, 1 Dec 2005 19:21:21 +0000 Subject: [PATCH] EPG & vvideolive clean up (inc new bugs!) --- Makefile | 4 +- colour.cc | 3 + colour.h | 7 + event.cc | 22 ++ event.h | 4 + vchannellist.cc | 2 +- vdr.cc | 18 +- vdr.h | 1 + vepg.cc | 550 ++++++++++++++++++++++++++++++++++++++++++++++ vepg.h | 82 +++++++ viewman.cc | 2 +- vlivebanner.cc | 13 +- vlivebanner.h | 3 +- vrecordinglist.cc | 2 +- vvideolive.cc | 158 +++++++++---- vvideolive.h | 29 ++- widget.cc | 10 + widget.h | 3 + wtextbox.cc | 61 +++++ wtextbox.h | 48 ++++ 20 files changed, 961 insertions(+), 61 deletions(-) create mode 100644 vepg.cc create mode 100644 vepg.h create mode 100644 wtextbox.cc create mode 100644 wtextbox.h diff --git a/Makefile b/Makefile index dd29a01..0b7c21c 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,8 @@ OBJECTS = main.o command.o log.o remote.o led.o mtd.o video.o audio.o tcp.o dire recording.o channel.o message.o playervideo.o playerradio.o messagequeue.o \ view.o vinfo.o vwallpaper.o vvolume.o vrecordinglist.o vlivebanner.o vmute.o \ vrecordingmenu.o vquestion.o vchannellist.o vwelcome.o vvideolive.o vvideorec.o vradiolive.o \ - vchannelselect.o vserverselect.o colour.o vconnect.o voptions.o region.o \ - widget.o wselectlist.o wjpeg.o wsymbol.o wbutton.o woptionbox.o i18n.o \ + vchannelselect.o vserverselect.o colour.o vconnect.o voptions.o vepg.o region.o \ + widget.o wselectlist.o wjpeg.o wsymbol.o wbutton.o woptionbox.o wtextbox.o i18n.o \ fonts/helvB24.o fonts/helvB18.o .PHONY: clean fresh all install strip diff --git a/colour.cc b/colour.cc index 4472229..cf1d38a 100644 --- a/colour.cc +++ b/colour.cc @@ -33,6 +33,9 @@ Colour Colour::LIGHTTEXT(255, 255, 255); Colour Colour::DARKTEXT(0, 0, 100); Colour Colour::DANGER(200, 0, 0); Colour Colour::BUTTONBACKGROUND(0, 0, 150); +Colour Colour::PROGRAMMEB(80, 80, 240); // two colours used as alterating backgrounds for individual programmes in EPG +Colour Colour::PROGRAMMEA(40, 40, 120); // TODO fit epg style (colours) in with rest of application +Colour Colour::NOPROGRAMME(180, 180, 180); // no programme details colour /* diff --git a/colour.h b/colour.h index 3abb27f..84de2d6 100644 --- a/colour.h +++ b/colour.h @@ -41,6 +41,9 @@ class Colour static Colour BLACK; static Colour RED; + static Colour GREEN; + static Colour YELLOW; + static Colour BLUE; static Colour VIDEOBLUE; static Colour VIEWBACKGROUND; static Colour TITLEBARBACKGROUND; @@ -49,6 +52,10 @@ class Colour static Colour DARKTEXT; static Colour DANGER; static Colour BUTTONBACKGROUND; + static Colour PROGRAMMEA; + static Colour PROGRAMMEB; + static Colour NOPROGRAMME; + }; diff --git a/event.cc b/event.cc index 614eaa1..e37a0f6 100644 --- a/event.cc +++ b/event.cc @@ -39,3 +39,25 @@ Event::~Event() if (subtitle) delete[] subtitle; if (description) delete[] description; } + +void Event::settitle(char* s) +{ + delete title; + title = new char[strlen(s) + 1]; + strcpy(title, s); +} + +void Event::setdescription(char* s) +{ + delete description; + description = new char[strlen(s) + 1]; + strcpy(description, s); +} + +void Event::setsubtitle(char* s) +{ + delete subtitle; + subtitle = new char[strlen(s) + 1]; + strcpy(subtitle, s); +} + diff --git a/event.h b/event.h index 3d595bc..f73f1a6 100644 --- a/event.h +++ b/event.h @@ -31,6 +31,10 @@ class Event Event(); ~Event(); + void settitle(char* title); + void setsubtitle(char* subtitle); + void setdescription(char* description); + ULONG id; ULONG time; ULONG duration; diff --git a/vchannellist.cc b/vchannellist.cc index 82d0e05..83ae18b 100644 --- a/vchannellist.cc +++ b/vchannellist.cc @@ -201,7 +201,7 @@ int VChannelList::handleCommand(int command) // if (chan->type == VDR::RADIO) return 2; VVideoLive* v = new VVideoLive(chanList, chan->type); - v->setChannel(chan->number); + v->channelChange(VVideoLive::NUMBER, chan->number); ViewMan::getInstance()->addNoLock(v); v->draw(); diff --git a/vdr.cc b/vdr.cc index 3776d04..696750b 100644 --- a/vdr.cc +++ b/vdr.cc @@ -628,18 +628,28 @@ ULLONG VDR::rescanRecording() EventList* VDR::getChannelSchedule(ULONG number) { + time_t now; + time(&now); + return getChannelSchedule(number, now, 24 * 60 * 60); +} + +EventList* VDR::getChannelSchedule(ULONG number, time_t start, ULONG duration) +{ +// retrieve event list (vector of events) from vdr within filter window. duration is in seconds if (!connected) return 0; - UCHAR buffer[12]; + UCHAR buffer[20]; - *(unsigned long*)&buffer[0] = htonl(8); + *(unsigned long*)&buffer[0] = htonl(16); *(unsigned long*)&buffer[4] = htonl(VDR_GETCHANNELSCHEDULE); *(unsigned long*)&buffer[8] = htonl(number); + *(unsigned long*)&buffer[12] = htonl(start); + *(unsigned long*)&buffer[16] = htonl(duration); pthread_mutex_lock(&mutex); - int a = tcp->sendPacket(buffer, 12); + int a = tcp->sendPacket(buffer, 20); - if (a != 12) + if (a != 20) { pthread_mutex_unlock(&mutex); return NULL; diff --git a/vdr.h b/vdr.h index 7232293..48c4c6e 100644 --- a/vdr.h +++ b/vdr.h @@ -76,6 +76,7 @@ class VDR UCHAR* getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived); int stopStreaming(); EventList* getChannelSchedule(ULONG number); + EventList* getChannelSchedule(ULONG number, time_t start, ULONG duration); int configSave(char* section, char* key, const char* value); char* configLoad(char* section, char* key); diff --git a/vepg.cc b/vepg.cc new file mode 100644 index 0000000..4bb9fa7 --- /dev/null +++ b/vepg.cc @@ -0,0 +1,550 @@ +/* + Copyright 2004-2005 Brian Walton + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/* + vepg presents a 2 dimensional electronic programme guide with channels down + the y axis and time along the x axis. + Programmes are layed on the x axis as alterate coloured blocks with as much + of the programme title as will fit inside the block shown as text. + Up and down commands step through the channels whilst left and right commands + move through the programmes of the currently selected channel. + When a programme is selected, it highlights in the grid and full programe details + (start time, title and description) are displayed in an area at te top left of the screen. + Any currently programmed timers will display in the grid and in the orogramme detail window as red + It is possible to select a programme to be recorded by pressing the record button. + The video stream currently being viewed is shown as quarter screen in the top right. +*/ + +#include "vepg.h" + +VEpg::VEpg(VVideoLive* v, UINT currentChannel) +{ + for(UINT listIndex = 0; listIndex < 7; listIndex++) + { + // initialise array of pointers to eventlist structures + eventLista[listIndex] = NULL; + } + + +// Create pallet on which to paint our epg view and position it in centre of screen. +// Need to reduce size to deal with overscanning TVs. + videoLive = v; +// create(640, 530); +//TODO: have size for ntsc + if (Video::getInstance()->getFormat() == Video::PAL) + { + create(632, 520); + setScreenPos(44, 30); + } + else + { + // what can we do about poor ntsc? + create(512, 384); + setScreenPos(64, 51); + } + +// beautify + Colour transparent = Colour(0, 0, 0, 0); + setBackgroundColour(transparent); +// initialise variables and pointers + eventList = NULL; + chanList = VDR::getInstance()->getChannelsList(VDR::VIDEO); //TODO want to be able to display video and radio together + e = 0; + progTitle.setSurface(surface); + progTitle.setSurfaceOffset(0,0); + progTitle.setDimensions(300,(Surface::getFontHeight() + 6) * 2 + 2); //paragraph line seperation is 6 pixels + progTitle.setBackgroundColour(Colour::TITLEBARBACKGROUND); + progInfo.setSurface(surface); + progInfo.setSurfaceOffset(0, progTitle.getOffsetY() + progTitle.getHeight()); + progInfo.setDimensions(300,(Surface::getFontHeight() + 6) * 8 + 2); + chanName.setSurface(surface); + chanName.setDimensions(510, (Surface::getFontHeight() + 4)); + chanName.setSurfaceOffset(305,230); + chanName.setBackgroundColour(Colour(0, 0, 0, 90)); +// create area to display list of channels + chanListbox.setSurface(surface); // add channel list + chanListbox.setSurfaceOffset(0, progInfo.getOffsetY() + progInfo.getHeight() + Surface::getFontHeight() + 8); // position channel list + chanListbox.setDimensions(150, (Surface::getFontHeight() + 1) * 7 + 5); //listbox line seperation is 1 pixel +// populate channel list + Channel* chan; + int first = 1; + for (UINT i = 0; i < chanList->size(); i++) + { + chan = (*chanList)[i]; + if (i == currentChannel) + first = 1; + chan->index = chanListbox.addOption(chan->name, first); + first = 0; + } + listTop = chanListbox.getTopOption(); + chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work + chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name); + time(<ime); // set ltime to now + ltime = prevHour(<ime); // set ltime to previous hour TODO make this half hour? + time(&selTime); // set selTime to now + updateEventList(); // get list of programmes +} + +VEpg::~VEpg() +{ + for(int listIndex = 0; listIndex < 7; listIndex++) + { + if (eventLista[listIndex]) + { + (eventLista)[listIndex]->clear(); + delete eventLista[listIndex]; + } + } +// delete [] eventLista; + +// destroy dynamically allocated memory +} + +void VEpg::setInfo(Event* event) +{ + time_t t; + struct tm* btime; // to hold programme start and end time + char* timeString = new char[9]; // to hold programme start and end time + int length = strlen(event->title); // calculate length of programme title string + char* title = new char[length + 15]; // create string to hold start time, end time and programme title + btime = localtime((time_t*)&event->time); //get programme start time + strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm - + strcpy(title, timeString); // put it in our buffer + t = event->time + event->duration; //get programme end time + btime = localtime(&t); + strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm - + strcat(title, timeString); // put it in our buffer + strcat(title, event->title); // then add the programme title + progTitle.setText(title); // sput this sring in our text box + length = strlen(event->description); + char* info = new char[length + 1]; // create programme detail string + strcpy(info, event->description); + progInfo.setText(info); // show programme detail string +// destroy dynamically allocated memory + delete info; + delete title; + delete timeString; +} + +void VEpg::draw() +{ + View::draw(); // draw pallet + chanListbox.draw(); + drawgrid(); + chanName.draw(); // TODO this should be dealt with by vvideolive + progTitle.draw(); + progInfo.draw(); + +// Display the status and key stuff at the bottom + int keyx = chanListbox.getOffsetX(); + int keyy = chanListbox.getOffsetY() + chanListbox.getHeight() + 2; + surface->fillblt(keyx, keyy, 610, Surface::getFontHeight() * 2 + 14, surface->rgba(100, 100, 100, 255)); + + WSymbol w; + w.setSurface(surface); + + w.nextSymbol = WSymbol::LEFTARROW; + w.setSurfaceOffset(keyx + 1, keyy + 20); + w.draw(); + + w.nextSymbol = WSymbol::UP; + w.setSurfaceOffset(keyx + 26, keyy + 3); + w.draw(); + + w.nextSymbol = WSymbol::DOWN; + w.setSurfaceOffset(keyx + 26, keyy + 36); + w.draw(); + + w.nextSymbol = WSymbol::RIGHTARROW; + w.setSurfaceOffset(keyx + 50, keyy + 20); + w.draw(); + + drawText("OK", keyx + 18, keyy + 20, Colour::LIGHTTEXT); + + surface->fillblt(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 0, 0, 255)); + drawText("Page up", keyx + 74, keyy + 5, Colour::LIGHTTEXT); + + surface->fillblt(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba(0, 200, 0, 255)); + drawText("Page down", keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT); + + surface->fillblt(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, surface->rgba(200, 200, 0, 255)); + drawText("-24 hours", keyx + 182, keyy + 5, Colour::LIGHTTEXT); + + surface->fillblt(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, surface->rgba( 0, 0, 200, 255)); + drawText("+24 hours", keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT); + + surface->fillblt(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255)); + drawText("Guide / Back: Close", keyx + 292 , keyy + 5, Colour::LIGHTTEXT); + + surface->fillblt(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255)); + Colour red = Colour(130, 0, 0); + drawText("Rec: Set timer", keyx + 292, keyy + Surface::getFontHeight() + 9, red); + + surface->fillblt(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255)); + w.nextSymbol = WSymbol::PLAY; + w.setSurfaceOffset(keyx + 476, keyy + 5); + w.draw(); + drawText("Sel channel", keyx + 496, keyy + 5, Colour::LIGHTTEXT); + + surface->fillblt(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, surface->rgba( 180, 180, 180, 255)); + drawText("Go: Preview", keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT); +} + +int VEpg::handleCommand(int command) +{ + switch(command) + { + case Remote::DF_UP: + case Remote::UP: + { // cursor up the channel list + chanListbox.up(); + draw(); + show(); + return 2; + } + case Remote::DF_DOWN: + case Remote::DOWN: + { // cursor down the channel list + chanListbox.down(); + draw(); + show(); + return 2; + } + case Remote::DF_LEFT: + case Remote::LEFT: + { // cursor left through time + selTime = thisEvent.time - 1; + draw(); + show(); + return 2; + } + case Remote::DF_RIGHT: + case Remote::RIGHT: + { + // cursor right through time + selTime = thisEvent.time + thisEvent.duration; + draw(); + show(); + return 2; + } + case Remote::RED: + { + // cursor up one page + chanListbox.pageUp(); + draw(); + show(); + return 2; + } + case Remote::GREEN: + { + // cursor down one page + chanListbox.pageDown(); + draw(); + show(); + return 2; + } + case Remote::BLUE: + { + // step forward 24 hours + selTime += 24 * 60 * 60; + draw(); + show(); + return 2; + } + case Remote::YELLOW: + { + // step forward 24 hours + selTime -= 24 * 60 * 60; + draw(); + show(); + return 2; + } + case Remote::RECORD: + { + //TODO + return 2; + } + case Remote::PLAY: + case Remote::GO: + case Remote::OK: + { // select programme and display menu TODO currently just changes to selected channel + Message* m = new Message(); + m->from = this; + m->to = videoLive; + m->message = Message::CHANNEL_CHANGE; + m->parameter = (*chanList)[chanListbox.getCurrentOption()]->number; + ViewMan::getInstance()->postMessage(m); + if(command == Remote::GO) + return 2; + // GO just changes channel in preview, PLAY changes channel and returns to normal TV + } + case Remote::BACK: + case Remote::GUIDE: + { + // return to normal TV mode TODO vvideolive should handle this (via message?) + Video::getInstance()->setMode(Video::NORMAL); + videoLive->setEpgMode(FALSE); + return 4; + } + case Remote::CHANNELUP: + { + // change up channel on live TV TODO vvideolive should handle this + Message* m = new Message(); + m->from = this; + m->to = videoLive; + m->message = Message::CHANNEL_UP; + ViewMan::getInstance()->postMessage(m); + return 2; + } + case Remote::CHANNELDOWN: + { // change down channel on live TV TODO vvideolive should handle this + Message* m = new Message(); + m->from = this; + m->to = videoLive; + m->message = Message::CHANNEL_DOWN; + ViewMan::getInstance()->postMessage(m); + return 2; + } + } + // stop command getting to any more views + return 1; +} + +void VEpg::drawgrid() // redraws grid and select programme +{ +// draw the grid of programmes + char timeString[20]; + time_t t; + time(&t); // set t = now + if(selTime < t) + selTime = t; // don't allow cursor in the past + if(listTop != chanListbox.getTopOption()) + { + // chanListbox has scrolled TODO speed up by changing only rows that have changed + listTop = chanListbox.getTopOption(); + updateEventList(); + } + if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime)) + { + // we have cursored back before left time of window + //TODO check that this and above don't happen together + ltime = prevHour(&selTime); + updateEventList(); + } + // draw time scale + t = ltime; + struct tm* tms; + tms = localtime(&t); + strftime(timeString, 19, "%a %e %b", tms); + int timey = chanListbox.getOffsetY() - Surface::getFontHeight() - 3; + int timex = 135; + drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date + strftime(timeString, 19, "%H:%M", tms); + drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time + surface->fillblt(155, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255)); + t = t + 3600; + tms = localtime(&t); + strftime(timeString, 19, "%H:%M", tms); + drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time + surface->fillblt(335, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255)); + t = t + 3600; + tms = localtime(&t); + strftime(timeString, 19, "%H:%M", tms); + drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time + surface->fillblt(515, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 255, 255, 255)); + // pointer to selTime + surface->fillblt(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, surface->rgba(255, 50, 50, 255)); + + // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice? + Event* event; + Event noevent; // an event to use if there are gaps in the epg + thisEvent.setdescription("There are no programme details available for this period"); + thisEvent.duration = WINDOW_WIDTH * 60; + thisEvent.time = ltime; + thisEvent.settitle("No programme details"); + thisEvent.id = 0; + bool swapColour = FALSE; // alternate cell colour + bool currentRow = FALSE; + int y = chanListbox.getOffsetY() + 5; // vertical position of cell + Colour bg, fg; // background colour of cells in grid + // for each displayed channel, find programmes that fall in 2.5 hour time window + for(int listIndex = 0; listIndex < 7; listIndex++) + { + if (listTop + listIndex >= chanListbox.getBottomOption()) + continue; // ensure nothing populates grid below last channel + currentRow = (listTop + listIndex == chanListbox.getCurrentOption()); + noevent.time = ltime; + noevent.duration = WINDOW_WIDTH * 60; + noevent.settitle(""); + paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes + if (currentRow) + { + thisEvent.setdescription("There are no programme details available for this period"); + thisEvent.duration = WINDOW_WIDTH * 60; + thisEvent.time = ltime; + thisEvent.settitle("No programme details"); + thisEvent.id = 0; + } + if (eventLista[listIndex]) + { + sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter()); + for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel + { + fg = Colour::LIGHTTEXT; + event = (*eventLista[listIndex])[e]; + if (event) + { + UINT end = event->time + event->duration; // programme end time + if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window + continue; // that's enough of this channel's events + if(end <= UINT(ltime)) // programme ends before LHS of window + continue; // this event is before the window - let's try the next event + // this event is one we are interested in + bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour + swapColour = !swapColour; // it wil be the other colour next time + if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow) + { + // this is the selected programme + thisEvent.setdescription(event->description); + thisEvent.duration = event->duration; + thisEvent.time = event->time; + thisEvent.settitle(event->title); + thisEvent.id = event->id; + if(thisEvent.id == 0) + thisEvent.id = 1; + bg = Colour::SELECTHIGHLIGHT; // highlight cell + fg = Colour::DARKTEXT; + } + else + { + if (currentRow && thisEvent.id == 0) + { + if (end <= UINT(selTime) && end > UINT(thisEvent.time)) + thisEvent.time = end; + if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration) + thisEvent.duration = event->time - thisEvent.time; + } + } + paintCell(event, y, bg, fg); + } + } + } + else + { + // no event list for this channel. Already painted noevent colour so just highlight if selected + if (currentRow) + { + bg = Colour::SELECTHIGHLIGHT; // highlight cell + fg = Colour::DARKTEXT; + paintCell(&thisEvent, y, bg, fg); + } + else + { + bg = Colour::NOPROGRAMME; + fg = Colour::LIGHTTEXT; + noevent.settitle("No programme details"); + paintCell(&noevent, y, bg, fg); + } + } + y += Surface::getFontHeight() + 1; + } + setInfo(&thisEvent); +} + +void VEpg::updateEventList() +{ + Channel* chan; + for(UINT listIndex = 0; listIndex < 7; listIndex++) + { + if (eventLista[listIndex]) + { + if ((eventLista[listIndex])->empty()) + (eventLista)[listIndex]->clear(); + } + if(listTop + listIndex >= UINT(chanListbox.getBottomOption())) + continue; + chan = (*chanList)[listTop + listIndex]; + 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 + } +} + +void VEpg::setCurrentChannel(char* chname) +{ + chanName.setText(chname); + chanName.draw(); + show(); +} + +void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg) +{ + int w, x, y, h; + y =yOffset; + h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height + UINT end = event->time + event->duration; // programme end time + if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window + { + x = 155; // LHS of window + if (end > (UINT(ltime) + (WINDOW_WIDTH * 60))) + w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window + else + w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme + } + if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window + { + x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60); + w = MINUTE_SCALE * event->duration / 60; + //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x) + // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window + } + if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x) + w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window + surface->fillblt(x, y, w, h, surface->rgba(bg.red, bg.green, bg.blue, bg.alpha)); + char* tt = new char[strlen(event->title) + 1]; + strcpy (tt, event->title); + int textWidth = 0; + for (UINT textPos = 0; textPos < strlen(tt); textPos++) + { + int thisCharWidth = surface->getCharWidth(tt[textPos]); + if (textWidth + thisCharWidth > w) // text will not fit in cell + { + textWidth = textPos; + break; + } + textWidth += thisCharWidth; + } + char* tT = new char[textWidth]; + if(textWidth > 1) + { + strncpy(tT, tt, textWidth - 1); + tT[textWidth - 1] = '\0'; + surface->drawText(tT, x+2, y, fg.red, fg.green, fg.blue); + } + delete tT; + +} + +time_t VEpg::prevHour(time_t* t) +{ + struct tm* tms; + tms = localtime(t); + tms->tm_sec = 0; + tms->tm_min = 0; + return mktime(tms); +} + diff --git a/vepg.h b/vepg.h new file mode 100644 index 0000000..b4e5a0b --- /dev/null +++ b/vepg.h @@ -0,0 +1,82 @@ +/* + Copyright 2004-2005 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef VEPG_H +#define VEPG_H +#define WINDOW_WIDTH (150) +#define MINUTE_SCALE (3) +#include +#include + +#include "view.h" +#include "remote.h" +#include "wselectlist.h" +#include "viewman.h" +#include "vdr.h" +#include "vchannellist.h" +#include "command.h" +#include "message.h" +#include "colour.h" +#include "video.h" +#include "wtextbox.h" +#include "event.h" +#include "message.h" +#include "vvideolive.h" + +class VVideoLive; + +class VEpg : public View +{ + public: + VEpg(VVideoLive* v, UINT currentChannel = 0); + ~VEpg(); + + int handleCommand(int command); // deal with commands (from remote control) + void draw(); // draw epg view + void setCurrentChannel(char* chname); + + private: + void setInfo(Event* event); // display details of selected programme + void drawgrid(); // redraws grid and select programme + + WSelectList chanListbox; // listbox to display available channels + WTextbox progTitle; // area to display time and title of selected programme + WTextbox progInfo; // area to display details of selected programme + EventList* eventList; // list of events (programmes) for a channel + Event thisEvent; // the selected event + time_t selTime; // current selection time + UINT e; // temp used to point to an event + ChannelList* chanList; // list of available channels + tm* epgtime; // selected time within epg + tm* Ltime; // time of LHS of epg view + time_t ltime; // time of LHS of epg view + time_t lastEnd; // end time of last painted cell - used to look for gaps in epg + WTextbox chanName; + EventList* eventLista[7]; + int listTop; + int listWindowSize; + void updateChanList(); + void updateEventList(); + void paintCell(Event* event, int yOffset, Colour bg, Colour fg); + time_t prevHour(time_t* t); + VVideoLive* videoLive; +}; + +#endif diff --git a/viewman.cc b/viewman.cc index 703788f..c10a8fd 100644 --- a/viewman.cc +++ b/viewman.cc @@ -637,7 +637,7 @@ void ViewMan::startAutoDeleteThread3() if (resetThreadFlag) continue; // timer ran out - Log::getInstance()->log("ViewMan", Log::DEBUG, "About to remove %p", nextToDelete); + Log::getInstance()->log("ViewMan", Log::DEBUG, "AutoDel: About to remove %p", nextToDelete); removeView(nextToDelete, 1); // enter this method with mutex locked resetThreadFlag = 1; diff --git a/vlivebanner.cc b/vlivebanner.cc index 8c7cc9b..3d3639e 100644 --- a/vlivebanner.cc +++ b/vlivebanner.cc @@ -20,10 +20,11 @@ #include "vlivebanner.h" -VLiveBanner::VLiveBanner(View* tparent, Channel* channel) +VLiveBanner::VLiveBanner(View* tparent, Channel* channel, bool bannerTakesCommands) { eventList = NULL; parent = tparent; + takeCommands = bannerTakesCommands; create(500, 120); if (Video::getInstance()->getFormat() == Video::PAL) @@ -131,6 +132,9 @@ int VLiveBanner::handleCommand(int command) return 4; } case Remote::DF_UP: + { + if (!takeCommands) return 0; // banner was auto, old remote + } // else drop through case Remote::UP: { sl.up(); @@ -140,6 +144,9 @@ int VLiveBanner::handleCommand(int command) return 2; } case Remote::DF_DOWN: + { + if (!takeCommands) return 0; // banner was auto, old remote + } // else drop through case Remote::DOWN: { sl.down(); @@ -150,6 +157,8 @@ int VLiveBanner::handleCommand(int command) } case Remote::CHANNELUP: { + ViewMan::getInstance()->timedDelete(this, 0, 0); // cancel timer so this view is still here later + Message* m = new Message(); m->from = this; m->to = parent; @@ -159,6 +168,8 @@ int VLiveBanner::handleCommand(int command) } case Remote::CHANNELDOWN: { + ViewMan::getInstance()->timedDelete(this, 0, 0); + Message* m = new Message(); m->from = this; m->to = parent; diff --git a/vlivebanner.h b/vlivebanner.h index 9bb8741..d719f26 100644 --- a/vlivebanner.h +++ b/vlivebanner.h @@ -40,7 +40,7 @@ class VLiveBanner : public View { public: - VLiveBanner(View* parent, Channel* channel); + VLiveBanner(View* parent, Channel* channel, bool bannerTakesCommands); ~VLiveBanner(); void delData(); @@ -54,6 +54,7 @@ class VLiveBanner : public View WSelectList sl; Channel* currentChannel; EventList* eventList; + bool takeCommands; }; diff --git a/vrecordinglist.cc b/vrecordinglist.cc index c0b5443..c7f2b7b 100644 --- a/vrecordinglist.cc +++ b/vrecordinglist.cc @@ -418,7 +418,7 @@ int VRecordingList::handleCommand(int command) } case Remote::PLAY: { - if (doPlay()) return 2; + if (doResume()) return 2; return 1; } diff --git a/vvideolive.cc b/vvideolive.cc index e0475c7..95694dc 100644 --- a/vvideolive.cc +++ b/vvideolive.cc @@ -29,9 +29,12 @@ VVideoLive::VVideoLive(ChannelList* tchanList, ULONG tstreamType) viewman = ViewMan::getInstance(); chanList = tchanList; currentChannel = 0; + previousChannel = 0; unavailable = 0; unavailableView = NULL; streamType = tstreamType; + vlb = (VLiveBanner*)1; // Can't be NULL because then that is sent to ViewMan::remove and it takes the top view off .. FIXME! + epgmode=false; if (streamType == VDR::RADIO) player = new PlayerVideo(Command::getInstance(), 0, 1); else player = new PlayerVideo(Command::getInstance(), 0, 0); @@ -83,8 +86,7 @@ int VVideoLive::handleCommand(int command) { if (unavailable) showUnavailable(0); else stop(); - upChannel(); - play(); + channelChange(OFFSET, UP); return 2; } case Remote::DF_DOWN: @@ -92,13 +94,22 @@ int VVideoLive::handleCommand(int command) { if (unavailable) showUnavailable(0); else stop(); - downChannel(); - play(); + channelChange(OFFSET, DOWN); return 2; } case Remote::OK: { - doBanner(); + doBanner(true); + return 2; + } + case Remote::GUIDE: + case Remote::RED: + { + if (!epgmode) + { + showEPG(); + } + epgmode=!epgmode; return 2; } @@ -115,37 +126,70 @@ int VVideoLive::handleCommand(int command) return 1; } +void VVideoLive::channelChange(UCHAR changeType, UINT newData) +{ + UINT newChannel = 0; + + if (changeType == INDEX) + { + newChannel = newData; + } + else if (changeType == NUMBER) + { + newChannel = channelIndexFromNumber(newData); + } + else if (changeType == OFFSET) + { + if (newData == UP) newChannel = upChannel(); + else newChannel = downChannel(); + } + else if (changeType == PREVIOUS) + { + newChannel = previousChannel; + } + else + { + return; // bad input! + } + + previousChannel = currentChannel; + currentChannel = newChannel; + + if (unavailable) showUnavailable(0); + else stop(1); + if(epgmode) + if(vepg) + vepg->setCurrentChannel((*chanList)[currentChannel]->name); + play(); +} + void VVideoLive::processMessage(Message* m) { if (m->message == Message::CHANNEL_CHANGE) { - // check channel number is valid - currentChannel = channelIndexFromNumber(m->parameter); - if (unavailable) showUnavailable(0); - else stop(); - play(); + channelChange(NUMBER, m->parameter); } else if (m->message == Message::CHANNEL_UP) { - // from vlivebanner - if (unavailable) showUnavailable(0); - else stop(1); - upChannel(); - play(1); - vlb->setChannel((*chanList)[currentChannel]); - vlb->draw(); - vlb->show(); + // this message is from vlivebanner + channelChange(OFFSET, UP); + if(!epgmode) + { + vlb->setChannel((*chanList)[currentChannel]); + vlb->draw(); + vlb->show(); + } } else if (m->message == Message::CHANNEL_DOWN) { - // from vlivebanner - if (unavailable) showUnavailable(0); - else stop(1); - downChannel(); - play(1); - vlb->setChannel((*chanList)[currentChannel]); - vlb->draw(); - vlb->show(); + // this message is from vlivebanner + channelChange(OFFSET, DOWN); + if(!epgmode) + { + vlb->setChannel((*chanList)[currentChannel]); + vlb->draw(); + vlb->show(); + } } else if (m->message == Message::STREAM_END) { @@ -155,9 +199,11 @@ void VVideoLive::processMessage(Message* m) } } -void VVideoLive::doBanner() +void VVideoLive::doBanner(bool bannerTakesCommands) { - vlb = new VLiveBanner(this, (*chanList)[currentChannel]); + if(epgmode) + return; + vlb = new VLiveBanner(this, (*chanList)[currentChannel], bannerTakesCommands); viewman->timedDelete(vlb, 4, 0); Message* m = new Message(); @@ -207,12 +253,6 @@ void VVideoLive::showUnavailable(int active) } } -void VVideoLive::setChannel(int number) -{ - currentChannel = channelIndexFromNumber(number); - play(); -} - void VVideoLive::play(int noShowVLB) { showUnavailable(0); @@ -222,38 +262,43 @@ void VVideoLive::play(int noShowVLB) if (!available) { showUnavailable(1); - if (!noShowVLB) doBanner(); + if (!noShowVLB) doBanner(false); } else { - if (!noShowVLB) doBanner(); + if (!noShowVLB) doBanner(false); player->play(); } } void VVideoLive::stop(int noRemoveVLB) { +printf("1\n"); if (unavailable) return; +printf("2\n"); if (!noRemoveVLB) viewman->removeView(vlb, 1); // if live banner is present, remove it. won't cause damage if its not present +printf("3\n"); player->stop(); +printf("4\n"); vdr->stopStreaming(); +printf("5\n"); } -int VVideoLive::upChannel() +UINT VVideoLive::upChannel() { - if (currentChannel == (chanList->size() - 1)) return 0; // at the end - - ++currentChannel; - return 1; + if (currentChannel == (chanList->size() - 1)) // at the end + return 0; // so go to start + else + return currentChannel + 1; } -int VVideoLive::downChannel() +UINT VVideoLive::downChannel() { - if (currentChannel == 0) return 0; // at the start - - --currentChannel; - return 1; + if (currentChannel == 0) // at the start + return chanList->size() - 1; // so go to end + else + return currentChannel - 1; } UINT VVideoLive::channelIndexFromNumber(int number) @@ -265,3 +310,24 @@ UINT VVideoLive::channelIndexFromNumber(int number) return 0; } +UINT VVideoLive::getCurrentChannelIndex() +{ + return channelIndexFromNumber(currentChannel); +} + +void VVideoLive::showEPG() +{ + if (unavailable) showUnavailable(0); + vepg = new VEpg(this, currentChannel); + ViewMan::getInstance()->addNoLock(vepg); + Video::getInstance()->setMode(Video::QUARTER); + Video::getInstance()->setPosition(170, 5); //TODO need to deal with 4:3 switching + vepg->draw(); + vepg->show(); +} + +void VVideoLive::setEpgMode(bool mode) +{ + epgmode = mode; +} + diff --git a/vvideolive.h b/vvideolive.h index 7ef9cb5..60e7e73 100644 --- a/vvideolive.h +++ b/vvideolive.h @@ -37,6 +37,10 @@ #include "vinfo.h" #include "command.h" #include "i18n.h" +#include "vepg.h" +#include "wtextbox.h" + +class VEpg; class VVideoLive : public View { @@ -48,10 +52,22 @@ class VVideoLive : public View int handleCommand(int command); void processMessage(Message* m); - void setChannel(int number); + void channelChange(UCHAR changeType, UINT newData); + // changeType = INDEX = (newData is a channel index in the list) + // = NUMBER = (newData is a real channel number) + // = OFFSET = (newData is UP or DOWN) void play(int noShowVLB = 0); void stop(int noRemoveVLB = 0); + UINT getCurrentChannelIndex(); + void setEpgMode(bool mode); + + const static UCHAR INDEX = 1; + const static UCHAR NUMBER = 2; + const static UCHAR OFFSET = 3; + const static UCHAR PREVIOUS = 4; + const static UCHAR UP = 1; + const static UCHAR DOWN = 2; private: static VVideoLive* instance; @@ -60,16 +76,21 @@ class VVideoLive : public View Player* player; ChannelList* chanList; UINT currentChannel; + UINT previousChannel; int unavailable; VInfo* unavailableView; ULONG streamType; VLiveBanner* vlb; - int upChannel(); - int downChannel(); - void doBanner(); + UINT upChannel(); + UINT downChannel(); + void doBanner(bool takesCommands); void showUnavailable(int active); UINT channelIndexFromNumber(int number); + VEpg* vepg; + int xpos; + bool epgmode; + void showEPG(); }; #endif diff --git a/widget.cc b/widget.cc index 3e833fe..a525519 100644 --- a/widget.cc +++ b/widget.cc @@ -38,3 +38,13 @@ void Widget::setDimensions(int twidth, int theight) area.w = twidth; area.h = theight; } + +int Widget::getOffsetY() +{ + return offsetY; +} + +int Widget::getOffsetX() +{ + return offsetX; +} diff --git a/widget.h b/widget.h index 29f12e5..fee77dd 100644 --- a/widget.h +++ b/widget.h @@ -34,6 +34,9 @@ class Widget : public Box virtual void draw()=0; void setSurface(Surface* tsurface); void setDimensions(int width, int height); + int getOffsetY(); + int getOffsetX(); + private: diff --git a/wtextbox.cc b/wtextbox.cc new file mode 100644 index 0000000..d64f5e9 --- /dev/null +++ b/wtextbox.cc @@ -0,0 +1,61 @@ +/* + Copyright 2004-2005 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "wtextbox.h" + +WTextbox::WTextbox() +{ + int fontHeight = Surface::getFontHeight(); + setDimensions(70, fontHeight); + setDimensions(100,100); + text = NULL; + foreColour = Colour::LIGHTTEXT; + backColour = Colour::VIEWBACKGROUND; +} + +WTextbox::~WTextbox() +{ + if (text) delete[] text; //TODO is this needed? +} + +void WTextbox::setText(char* takeText) +{ + int length = strlen(takeText); + text = new char[length + 1]; + strcpy(text, takeText); +} + +void WTextbox::setBackgroundColour(Colour bcolour) +{ + backColour = bcolour; +} + +void WTextbox::setForegroundColour(Colour fcolour) +{ + foreColour = fcolour; +} + +void WTextbox::draw() +{ + fillColour(backColour); + if (text) + drawPara(text, 5, 2, foreColour); +} + diff --git a/wtextbox.h b/wtextbox.h new file mode 100644 index 0000000..bfbf865 --- /dev/null +++ b/wtextbox.h @@ -0,0 +1,48 @@ +/* + Copyright 2004-2005 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef WTEXTBOX_H +#define WTEXTBOX_H + +#include +#include + +#include "defines.h" +#include "widget.h" +#include "colour.h" + +class WTextbox : public Widget +{ + public: + WTextbox(); + ~WTextbox(); + void setText(char* text); + void draw(); + void setBackgroundColour(Colour bcolour); + void setForegroundColour(Colour fcolour); + + private: + + char* text; + Colour foreColour; + Colour backColour; +}; + +#endif -- 2.39.2