2 Copyright 2005 Brian Walton
4 This file is part of VOMP.
6 VOMP is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 VOMP is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with VOMP; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 vepg presents a 2 dimensional electronic programme guide with channels down
22 the y axis and time along the x axis.
23 Programmes are layed on the x axis as alterate coloured blocks with as much
24 of the programme title as will fit inside the block shown as text.
25 Up and down commands step through the channels whilst left and right commands
26 move through the programmes of the currently selected channel.
27 When a programme is selected, it highlights in the grid and full programe details
28 (start time, title and description) are displayed in an area at te top left of the screen.
29 Any currently programmed timers will display in the grid and in the orogramme detail window as red
30 It is possible to select a programme to be recorded by pressing the record button.
31 The video stream currently being viewed is shown as quarter screen in the top right.
37 #include "vchannellist.h"
40 #include "vepgsettimer.h"
45 #include "vvideolive.h"
50 VEpg* VEpg::instance = NULL;
52 VEpg::VEpg(VVideoLive* v, UINT currentChannel, ULONG streamType)
56 // PAL / NTSC sizes -----------------------
60 int summaryLines, summaryLowerPadding;
62 //UINT gridRows; // is a class member
64 if (Video::getInstance()->getFormat() == Video::PAL)
71 summaryLowerPadding = 16;
82 summaryLowerPadding = 26;
87 // initialise variables and pointers
88 boxstack = BoxStack::getInstance();
91 chanList = VDR::getInstance()->getChannelsList(streamType); //TODO want to be able to display video and radio together
94 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
96 // initialise array of pointers to eventlist structures
97 eventLista[listIndex] = NULL;
100 // Create pallet on which to paint our epg view and position it in centre of screen.
101 // Need to reduce size to deal with overscanning TVs.
103 setSize(xsize, ysize);
105 setPosition(xpos, ypos);
108 // Colour transparent = Colour(0, 0, 0, 0);
109 // setBackgroundColour(transparent);
111 // progTitle.setSurface(surface);
112 progTitle.setPosition(0,0);
113 progTitle.setSize(300,(Surface::getFontHeight() + 4) * 2 + 16); //paragraph line seperation is 4 pixels
114 progTitle.setBackgroundColour(Colour::TITLEBARBACKGROUND);
115 progTitle.setTextPos(5, 16);
119 // progInfo.setSurface(surface);
120 progInfo.setBackgroundColour(Colour::VIEWBACKGROUND);
121 progInfo.setPosition(0, progTitle.getY() + progTitle.getHeight());
122 progInfo.setSize(300,((Surface::getFontHeight() + 4) * summaryLines) + summaryLowerPadding);
126 // chanName.setSurface(surface);
127 chanName.setSize(510, (Surface::getFontHeight() + 4));
128 chanName.setPosition(305, chanNameYpos);
129 Colour t1(0, 0, 0, 90);
130 chanName.setBackgroundColour(t1);
133 // create area to display list of channels
134 // chanListbox.setSurface(surface); // add channel list
135 chanListbox.setPosition(0, progInfo.getY() + progInfo.getHeight() + Surface::getFontHeight() + 8); // position channel list
136 chanListbox.setSize(150, ((Surface::getFontHeight() + 2) * gridRows) + 5); //listbox line seperation is 2 pixels
137 chanListbox.setGap(2);
140 // populate channel list
145 for (UINT i = 0; i < chanList->size(); i++)
147 chan = (*chanList)[i];
148 if (i == currentChannel)
150 chan->index = chanListbox.addOption(chan->name, 0, first);
153 chanName.setText((*chanList)[chanListbox.getCurrentOption()]->name);
156 listTop = chanListbox.getTopOption();
157 chanListbox.draw(); // doing this to allow chanListbox.getBottomOption() in updateEventList() to work
158 time(<ime); // set ltime to now
159 ltime = prevHour(<ime); // set ltime to previous hour TODO make this half hour?
160 time(&selTime); // set selTime to now
161 updateEventList(); // get list of programmes
166 Timers::getInstance()->cancelTimer(this, 1);
170 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
172 if (eventLista[listIndex])
174 (eventLista)[listIndex]->clear();
175 delete eventLista[listIndex];
178 // delete [] eventLista; // FIXME
180 // destroy dynamically allocated memory
183 VEpg* VEpg::getInstance()
188 void VEpg::setInfo(Event* event)
191 struct tm* btime; // to hold programme start and end time
192 char timeString[9]; // to hold programme start and end time
193 int length = strlen(event->title); // calculate length of programme title string
194 char* title = new char[length + 15]; // create string to hold start time, end time and programme title
195 btime = localtime((time_t*)&event->time); //get programme start time
197 strftime(timeString, 9, "%0H:%0M - ", btime); // and format it as hh:mm -
199 strftime(timeString, 9, "%H:%M - ", btime); // and format it as hh:mm -
201 strcpy(title, timeString); // put it in our buffer
202 t = event->time + event->duration; //get programme end time
203 btime = localtime(&t);
205 strftime(timeString, 7, "%0H:%0M ", btime); // and format it as hh:mm -
207 strftime(timeString, 7, "%H:%M ", btime); // and format it as hh:mm -
209 strcat(title, timeString); // put it in our buffer
210 strcat(title, event->title); // then add the programme title
211 progTitle.setText(title); // sput this sring in our text box
212 length = strlen(event->description);
213 char* info = new char[length + 1]; // create programme detail string
214 strcpy(info, event->description);
215 progInfo.setText(info); // show programme detail string
216 // destroy dynamically allocated memory
223 // View::draw(); // draw pallet
225 Colour transparent = Colour(0, 0, 0, 0);
226 fillColour(transparent);
229 // Moved all the dynamic data drawing to a seperate function
231 // Display the status and key stuff at the bottom
233 UINT keyx = chanListbox.getRootBoxOffsetX();
234 UINT keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
235 Colour ref1 = Colour(100, 100, 100, 255);
236 rectangle(keyx, keyy, 605, Surface::getFontHeight() * 2 + 14, ref1);
241 w.nextSymbol = WSymbol::LEFTARROW;
242 w.setPosition(keyx + 1, keyy + 20);
245 w.nextSymbol = WSymbol::UP;
246 w.setPosition(keyx + 26, keyy + 3);
249 w.nextSymbol = WSymbol::DOWN;
250 w.setPosition(keyx + 26, keyy + 36);
253 w.nextSymbol = WSymbol::RIGHTARROW;
254 w.setPosition(keyx + 50, keyy + 20);
257 drawText(tr("OK"), keyx + 18, keyy + 20, Colour::LIGHTTEXT);
259 Colour ref2 = Colour(200, 0, 0, 255);
260 rectangle(keyx + 72, keyy + 4, 104, Surface::getFontHeight() + 2, ref2);
261 drawText(tr("Page up"), keyx + 74, keyy + 5, Colour::LIGHTTEXT);
263 Colour ref3 = Colour(0, 200, 0, 255);
264 rectangle(keyx + 72, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, ref3);
265 drawText(tr("Page down"), keyx + 74, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
267 Colour ref4 = Colour(200, 200, 0, 255);
268 rectangle(keyx + 180, keyy + 4, 104, Surface::getFontHeight() + 2, ref4);
269 drawText(tr("-24 hours"), keyx + 182, keyy + 5, Colour::LIGHTTEXT);
271 Colour ref5 = Colour(0, 0, 200, 255);
272 rectangle(keyx + 180, keyy + Surface::getFontHeight() + 8, 104, Surface::getFontHeight() + 2, ref5);
273 drawText(tr("+24 hours"), keyx + 182, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
275 Colour ref6 = Colour(180, 180, 180, 255);
276 rectangle(keyx + 290, keyy + 4, 180, Surface::getFontHeight() + 2, ref6);
277 drawText(tr("Guide / Back: Close"), keyx + 292 , keyy + 5, Colour::LIGHTTEXT);
279 Colour ref7 = Colour(180, 180, 180, 255);
280 rectangle(keyx + 290, keyy + Surface::getFontHeight() + 8, 180, Surface::getFontHeight() + 2, ref7);
281 Colour red = Colour(130, 0, 0);
282 drawText(tr("Rec: Set timer"), keyx + 292, keyy + Surface::getFontHeight() + 9, red);
284 Colour ref8 = Colour(180, 180, 180, 255);
285 rectangle(keyx + 474, keyy + 4, 128, Surface::getFontHeight() + 2, ref8);
286 w.nextSymbol = WSymbol::PLAY;
287 w.setPosition(keyx + 476, keyy + 5);
289 drawText(tr("Sel channel"), keyx + 496, keyy + 5, Colour::LIGHTTEXT);
291 Colour ref9 = Colour(180, 180, 180, 255);
292 rectangle(keyx + 474, keyy + Surface::getFontHeight() + 8, 128, Surface::getFontHeight() + 2, ref9);
293 drawText(tr("Go: Preview"), keyx + 476, keyy + Surface::getFontHeight() + 9, Colour::LIGHTTEXT);
296 // Draw all the dynamic data
300 void VEpg::drawData()
302 // Not doing View::draw() every time causes
303 // things not to be cleared off the surface properly
304 // So, blank out the data area first
307 chanListbox.getRootBoxOffsetX(),
308 chanListbox.getRootBoxOffsetY() - Surface::getFontHeight() - 3,
309 155 + WINDOW_WIDTH * MINUTE_SCALE,
310 chanListbox.getHeight() + Surface::getFontHeight() + 4,
315 chanName.draw(); // TODO this should be dealt with by vvideolive
320 // Set timer to redraw to move the current time bar
324 if (dt == 0) dt = 60;
326 Timers::getInstance()->setTimerT(this, 1, dt);
329 void VEpg::timercall(int clientReference)
332 // Put updateView through master mutex since boxstack is not mutex protected
333 Message* m = new Message();
334 m->message = Message::REDRAW;
338 Command::getInstance()->postMessageFromOuterSpace(m);
341 int VEpg::handleCommand(int command)
347 { // cursor up the channel list
350 boxstack->update(this);
353 case Remote::DF_DOWN:
355 { // cursor down the channel list
358 boxstack->update(this);
361 case Remote::DF_LEFT:
363 { // cursor left through time
364 selTime = thisEvent.time - 1;
366 boxstack->update(this);
369 case Remote::DF_RIGHT:
372 // cursor right through time
373 selTime = thisEvent.time + thisEvent.duration;
375 boxstack->update(this);
380 // cursor up one page
381 chanListbox.pageUp();
383 boxstack->update(this);
388 // cursor down one page
389 chanListbox.pageDown();
391 boxstack->update(this);
396 // step forward 24 hours
397 selTime += 24 * 60 * 60;
399 boxstack->update(this);
404 // step forward 24 hours
405 selTime -= 24 * 60 * 60;
407 boxstack->update(this);
412 if (!chanList) return 2;
413 Log::getInstance()->log("VEPG", Log::DEBUG, "ID %lu TIME %lu DURATION %lu TITLE %s", thisEvent.id, thisEvent.time, thisEvent.duration, thisEvent.title);
414 VEpgSetTimer* vs = new VEpgSetTimer(&thisEvent, (*chanList)[chanListbox.getCurrentOption()]);
417 boxstack->update(vs);
424 if (!chanList) return 2;
426 // select programme and display menu TODO currently just changes to selected channel
427 videoLive->channelChange(VVideoLive::NUMBER, (*chanList)[chanListbox.getCurrentOption()]->number);
429 if(command == Remote::GO)
431 // GO just changes channel in preview, PLAY changes channel and returns to normal TV
436 // return to normal TV mode
437 if (videoLive) // ptr check done in case being tested from videorec
439 Message* m = new Message(); // Must be done after this view deleted
442 m->message = Message::EPG_CLOSE;
443 Command::getInstance()->postMessageNoLock(m);
447 case Remote::CHANNELUP:
449 videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::UP);
452 case Remote::CHANNELDOWN:
454 videoLive->channelChange(VVideoLive::OFFSET, VVideoLive::DOWN);
458 // stop command getting to any more views
462 void VEpg::drawgrid() // redraws grid and select programme
464 // draw the grid of programmes
467 time(&t); // set t = now
469 selTime = t; // don't allow cursor in the past
470 if(listTop != chanListbox.getTopOption())
472 // chanListbox has scrolled TODO speed up by changing only rows that have changed
473 listTop = chanListbox.getTopOption();
476 if ((selTime >= ltime + WINDOW_WIDTH * 60) || (selTime <= ltime))
478 // we have cursored back before left time of window
479 //TODO check that this and above don't happen together
480 ltime = prevHour(&selTime);
484 Colour white = Colour(255, 255, 255, 255);
489 strftime(timeString, 19, "%a %e %b", tms);
490 int timey = chanListbox.getRootBoxOffsetY() - Surface::getFontHeight() - 3;
492 drawTextRJ(timeString, timex - 10, timey, Colour::LIGHTTEXT); // print date
493 strftime(timeString, 19, "%H:%M", tms);
494 drawText(timeString, timex, timey, Colour::LIGHTTEXT); // print left time
495 rectangle(155, timey + Surface::getFontHeight(), 2, 7, white);
498 strftime(timeString, 19, "%H:%M", tms);
499 drawText(timeString, timex + 180, timey, Colour::LIGHTTEXT); // print middle time
500 rectangle(335, timey + Surface::getFontHeight(), 2, 7, white);
503 strftime(timeString, 19, "%H:%M", tms);
504 drawText(timeString, timex + 360, timey, Colour::LIGHTTEXT); // print right time
505 rectangle(515, timey + Surface::getFontHeight(), 2, 7, white);
506 // pointer to selTime
507 //rectangle(155 + (selTime - ltime) / 20, timey + Surface::getFontHeight(), 2, 7, Colour(255, 50, 50, 255));
511 if ((t >= ltime) && (t < (ltime + 9000)))
513 rectangle(155 + (t - ltime) / 20, timey + Surface::getFontHeight(), 2, ((Surface::getFontHeight() + 2) * 7) + 7 + 2, Colour::RED);
516 // TODO should the above two comditional statements be combined to avoid calling updateEventList() twice?
518 Event noevent; // an event to use if there are gaps in the epg
519 thisEvent.setdescription(tr("There are no programme details available for this period"));
520 thisEvent.duration = WINDOW_WIDTH * 60;
521 thisEvent.time = ltime;
522 thisEvent.settitle(tr("No programme details"));
524 bool swapColour = FALSE; // alternate cell colour
525 bool currentRow = FALSE;
526 int y = chanListbox.getRootBoxOffsetY() + 5; // vertical position of cell
527 Colour bg, fg; // background colour of cells in grid
528 // for each displayed channel, find programmes that fall in 2.5 hour time window
529 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
531 if (listTop + (int)listIndex >= chanListbox.getBottomOption())
532 continue; // ensure nothing populates grid below last channel
533 currentRow = (listTop + (int)listIndex == chanListbox.getCurrentOption());
534 noevent.time = ltime;
535 noevent.duration = WINDOW_WIDTH * 60;
536 noevent.settitle("");
537 paintCell(&noevent, y, Colour::NOPROGRAMME, Colour::LIGHTTEXT); // fill row with no programme colour to be painted ove with valid programmes
540 thisEvent.setdescription(tr("There are no programme details available for this period"));
541 thisEvent.duration = WINDOW_WIDTH * 60;
542 thisEvent.time = ltime;
543 thisEvent.settitle(tr("No programme details"));
546 if (eventLista[listIndex])
548 sort(eventLista[listIndex]->begin(), eventLista[listIndex]->end(), EventSorter());
549 for(e = 0; e < (eventLista[listIndex])->size(); e++) // step through events for this channel
551 fg = Colour::LIGHTTEXT;
552 event = (*eventLista[listIndex])[e];
555 UINT end = event->time + event->duration; // programme end time
556 if(event->time >= UINT(ltime) + (WINDOW_WIDTH * 60)) // programme starts after RHS of window
557 continue; // that's enough of this channel's events
558 if(end <= UINT(ltime)) // programme ends before LHS of window
559 continue; // this event is before the window - let's try the next event
560 // this event is one we are interested in
561 bg = (swapColour)?Colour::PROGRAMMEA:Colour::PROGRAMMEB; // alternate cell colour
562 swapColour = !swapColour; // it wil be the other colour next time
563 if(event->time <= UINT(selTime) && end > UINT(selTime) && currentRow)
565 // this is the selected programme
566 thisEvent.setdescription(event->description);
567 thisEvent.duration = event->duration;
568 thisEvent.time = event->time;
569 thisEvent.settitle(event->title);
570 thisEvent.id = event->id;
571 if(thisEvent.id == 0)
573 bg = Colour::SELECTHIGHLIGHT; // highlight cell
574 fg = Colour::DARKTEXT;
578 if (currentRow && thisEvent.id == 0)
580 if (end <= UINT(selTime) && end > UINT(thisEvent.time))
581 thisEvent.time = end;
582 if (event->time > UINT(selTime) && event->time < thisEvent.time + thisEvent.duration)
583 thisEvent.duration = event->time - thisEvent.time;
586 paintCell(event, y, bg, fg);
592 // no event list for this channel. Already painted noevent colour so just highlight if selected
595 bg = Colour::SELECTHIGHLIGHT; // highlight cell
596 fg = Colour::DARKTEXT;
597 paintCell(&thisEvent, y, bg, fg);
601 bg = Colour::NOPROGRAMME;
602 fg = Colour::LIGHTTEXT;
603 noevent.settitle(tr("No programme details"));
604 paintCell(&noevent, y, bg, fg);
607 y += Surface::getFontHeight() + 2;
612 void VEpg::updateEventList()
614 if (!chanList) return;
616 for(UINT listIndex = 0; listIndex < gridRows; listIndex++)
618 if(listTop + listIndex >= UINT(chanListbox.getBottomOption()))
620 chan = (*chanList)[listTop + listIndex];
622 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
626 void VEpg::setCurrentChannel(char* chname)
628 chanName.setText(chname);
630 boxstack->update(this);
633 void VEpg::paintCell(Event* event, int yOffset, Colour bg, Colour fg)
636 w = x = 0; // keep compiler happy
639 h = Surface::getFontHeight(); // TODO if want border around text, need to increae this and wselectlist line height
640 UINT end = event->time + event->duration; // programme end time
641 if(event->time <= UINT(ltime) && end > UINT(ltime)) // spans start of displayed window
643 x = 155; // LHS of window
644 if (end > (UINT(ltime) + (WINDOW_WIDTH * 60)))
645 w = WINDOW_WIDTH * MINUTE_SCALE; // spans full 2 hour window
647 w = MINUTE_SCALE * (event->time + event->duration - ltime ) / 60; // get width of remaining programme
649 if((event->time >= UINT(ltime)) && (event->time <= UINT(ltime) + (WINDOW_WIDTH * 60))) // starts within window
651 x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
652 w = MINUTE_SCALE * event->duration / 60;
653 //if (w > 155 + MINUTE_SCALE * WINDOW_WIDTH -x)
654 // w = w + x - 155 - MINUTE_SCALE * WINDOW_WIDTH; // ends outside window
656 if (w > 155 + WINDOW_WIDTH * MINUTE_SCALE - x)
657 w = 155 + WINDOW_WIDTH * MINUTE_SCALE -x; // limit cells to RHS of window
658 rectangle(x, y, w, h, bg);
659 char* tt = new char[strlen(event->title) + 1];
660 strcpy (tt, event->title);
662 for (UINT textPos = 0; textPos < strlen(tt); textPos++)
664 int thisCharWidth = surface->getCharWidth(tt[textPos]);
665 if (textWidth + thisCharWidth > w) // text will not fit in cell
670 textWidth += thisCharWidth;
672 char* tT = new char[textWidth];
675 strncpy(tT, tt, textWidth - 1);
676 tT[textWidth - 1] = '\0';
677 surface->drawText(tT, x+2, y, fg.rgba());
683 time_t VEpg::prevHour(time_t* t)
692 void VEpg::processMessage(Message* m)
694 if (m->message == Message::MOUSE_MOVE)
696 if (chanListbox.mouseMove((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
699 boxstack->update(this);
702 else if (m->message == Message::MOUSE_LBDOWN)
704 if (chanListbox.mouseLBDOWN((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
706 boxstack->handleCommand(Remote::OK); //simulate OK press
710 //check if press is outside this view! then simulate cancel
711 int x=(m->parameter>>16)-getScreenX();
712 int y=(m->parameter&0xFFFF)-getScreenY();
713 int keyx = chanListbox.getRootBoxOffsetX();
714 int keyy = chanListbox.getRootBoxOffsetY() + chanListbox.getHeight() + 2;
716 if (x<0 || y <0 || x>(int)getWidth() || y>(int)getHeight())
718 boxstack->handleCommand(Remote::BACK); //simulate cancel press
720 else if (x>=(keyx+72) && y>=(keyy+4) &&x<=(keyx+72+104) &&y<=(keyy+4+Surface::getFontHeight() + 2))
722 boxstack->handleCommand(Remote::RED);
724 else if (x>=(keyx+72) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+72+104) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
726 boxstack->handleCommand(Remote::GREEN);
728 else if (x>=(keyx+180) && y>=(keyy+4) &&x<=(keyx+180+104) &&y<=(keyy+4+Surface::getFontHeight() + 2))
730 boxstack->handleCommand(Remote::YELLOW);
732 else if (x>=(keyx+180) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+180+104) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
734 boxstack->handleCommand(Remote::BLUE);
736 else if (x>=(keyx+290) && y>=(keyy+4) &&x<=(keyx+180+290) &&y<=(keyy+4+Surface::getFontHeight() + 2))
738 boxstack->handleCommand(Remote::BACK);
740 else if (x>=(keyx+290) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+290+180) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
742 boxstack->handleCommand(Remote::RECORD);
744 else if (x>=(keyx+474) && y>=(keyy+4) &&x<=(keyx+128+474) &&y<=(keyy+4+Surface::getFontHeight() + 2))
746 boxstack->handleCommand(Remote::PLAY);
748 else if (x>=(keyx+474) && y>=(keyy+ Surface::getFontHeight() + 8) &&x<=(keyx+238+474) &&y<=(keyy+8+2*Surface::getFontHeight() + 2))
750 boxstack->handleCommand(Remote::GO);
752 else if ( x>=(chanListbox.getRootBoxOffsetX())
753 && y>=(chanListbox.getRootBoxOffsetY() + 5)
754 // &&x<=(chanListbox.getOffsetX()+155 + WINDOW_WIDTH * MINUTE_SCALE)
755 &&y<=(chanListbox.getRootBoxOffsetY() - Surface::getFontHeight()
756 - 3+(int)chanListbox.getHeight() + Surface::getFontHeight() + 3)
759 int cy=y-(chanListbox.getRootBoxOffsetY() + 5);
760 int row=cy/(Surface::getFontHeight()+2);
761 int clistTop = chanListbox.getTopOption();
762 chanListbox.hintSetCurrent(clistTop+row);
764 time_t ttime = cx*60/MINUTE_SCALE+ltime;
765 //x = 155 + (MINUTE_SCALE * (event->time - ltime) / 60);
769 boxstack->update(this);