00001 #include <qlayout.h>
00002 #include <qpushbutton.h>
00003 #include <qbuttongroup.h>
00004 #include <qlabel.h>
00005 #include <qcursor.h>
00006 #include <qsqldatabase.h>
00007 #include <qdatetime.h>
00008 #include <qapplication.h>
00009 #include <qregexp.h>
00010 #include <qheader.h>
00011
00012 #include <iostream>
00013 #include <map>
00014 #include <vector>
00015 #include <algorithm>
00016 #include <cassert>
00017 using namespace std;
00018
00019 #include "exitcodes.h"
00020 #include "previouslist.h"
00021 #include "proglist.h"
00022 #include "scheduledrecording.h"
00023 #include "customedit.h"
00024 #include "dialogbox.h"
00025 #include "mythcontext.h"
00026 #include "mythdbcon.h"
00027 #include "remoteutil.h"
00028
00029 PreviousList::PreviousList(MythMainWindow *parent, const char *name,
00030 int recid, QString ltitle)
00031 : MythDialog(parent, name)
00032 {
00033 m_recid = recid;
00034 m_title = ltitle;
00035
00036 view = "";
00037 startTime = QDateTime::currentDateTime();
00038 searchTime = startTime;
00039
00040 dayFormat = gContext->GetSetting("DateFormat");
00041 hourFormat = gContext->GetSetting("TimeFormat");
00042 timeFormat = gContext->GetSetting("ShortDateFormat") + " " + hourFormat;
00043 fullDateFormat = dayFormat + " " + hourFormat;
00044 channelFormat = gContext->GetSetting("ChannelFormat", "<num> <sign>");
00045
00046 allowEvents = true;
00047 allowUpdates = true;
00048 updateAll = false;
00049 refillAll = false;
00050
00051 fullRect = QRect(0, 0, size().width(), size().height());
00052 viewRect = QRect(0, 0, 0, 0);
00053 listRect = QRect(0, 0, 0, 0);
00054 infoRect = QRect(0, 0, 0, 0);
00055 theme = new XMLParse();
00056 theme->SetWMult(wmult);
00057 theme->SetHMult(hmult);
00058
00059 if (!theme->LoadTheme(xmldata, "programlist"))
00060 {
00061 DialogBox *dlg = new DialogBox(
00062 gContext->GetMainWindow(),
00063 QObject::tr(
00064 "The theme you are using does not contain the "
00065 "%1 element. Please contact the theme creator "
00066 "and ask if they could please update it.<br><br>"
00067 "The next screen will be empty. "
00068 "Escape out of it to return to the menu.")
00069 .arg("'programlist'"));
00070
00071 dlg->AddButton("OK");
00072 dlg->exec();
00073 dlg->deleteLater();
00074
00075 return;
00076 }
00077
00078 LoadWindow(xmldata);
00079
00080 LayerSet *container = theme->GetSet("selector");
00081 assert(container);
00082 UIListType *ltype = (UIListType *)container->GetType("proglist");
00083 if (ltype)
00084 listsize = ltype->GetItems();
00085
00086 choosePopup = NULL;
00087 chooseListBox = NULL;
00088 chooseLineEdit = NULL;
00089 chooseOkButton = NULL;
00090 chooseDeleteButton = NULL;
00091 chooseRecordButton = NULL;
00092 chooseDay = NULL;
00093 chooseHour = NULL;
00094
00095 curView = -1;
00096 fillViewList("time");
00097
00098 curItem = -1;
00099 fillItemList();
00100
00101 if (curView < 0)
00102 QApplication::postEvent(this, new MythEvent("CHOOSE_VIEW"));
00103
00104 updateBackground();
00105
00106 setNoErase();
00107
00108 gContext->addListener(this);
00109 gContext->addCurrentLocation("PreviousList");
00110 }
00111
00112 PreviousList::~PreviousList()
00113 {
00114 itemList.clear();
00115
00116 gContext->removeListener(this);
00117 gContext->removeCurrentLocation();
00118 delete theme;
00119 }
00120
00121 void PreviousList::keyPressEvent(QKeyEvent *e)
00122 {
00123 if (!allowEvents)
00124 return;
00125
00126 allowEvents = false;
00127 bool handled = false;
00128
00129 QStringList actions;
00130 gContext->GetMainWindow()->TranslateKeyPress("TV Frontend", e, actions);
00131
00132 for (unsigned int i = 0; i < actions.size() && !handled; i++)
00133 {
00134 QString action = actions[i];
00135 handled = true;
00136
00137 if (action == "UP")
00138 cursorUp(false);
00139 else if (action == "DOWN")
00140 cursorDown(false);
00141 else if (action == "PAGEUP")
00142 cursorUp(true);
00143 else if (action == "PAGEDOWN")
00144 cursorDown(true);
00145 else if (action == "PREVVIEW")
00146 prevView();
00147 else if (action == "NEXTVIEW")
00148 nextView();
00149 else if (action == "MENU")
00150 chooseView();
00151 else if (action == "SELECT" || action == "RIGHT")
00152 select();
00153 else if (action == "DELETE")
00154 deleteItem();
00155 else if (action == "LEFT")
00156 accept();
00157 else if (action == "INFO")
00158 edit();
00159 else if (action == "CUSTOMEDIT")
00160 customEdit();
00161 else if (action == "UPCOMING")
00162 upcoming();
00163 else if (action == "DETAILS")
00164 details();
00165 else if (action == "1")
00166 {
00167 if (viewList[curView] == "sort by time")
00168 curView = viewList.findIndex("reverse time");
00169 else
00170 curView = viewList.findIndex("sort by time");
00171
00172 refillAll = true;
00173 }
00174 else if (action == "2")
00175 {
00176 if (viewList[curView] == "sort by title")
00177 curView = viewList.findIndex("reverse title");
00178 else
00179 curView = viewList.findIndex("sort by title");
00180
00181 refillAll = true;
00182 }
00183 else
00184 handled = false;
00185 }
00186
00187 if (!handled)
00188 MythDialog::keyPressEvent(e);
00189
00190 if (refillAll)
00191 {
00192 allowUpdates = false;
00193 do
00194 {
00195 refillAll = false;
00196 fillItemList();
00197 } while (refillAll);
00198 allowUpdates = true;
00199 update(fullRect);
00200 }
00201
00202 allowEvents = true;
00203 }
00204
00205 void PreviousList::LoadWindow(QDomElement &element)
00206 {
00207 QString name;
00208 int context;
00209 QRect area;
00210
00211 for (QDomNode child = element.firstChild(); !child.isNull();
00212 child = child.nextSibling())
00213 {
00214 QDomElement e = child.toElement();
00215 if (!e.isNull())
00216 {
00217 if (e.tagName() == "font")
00218 theme->parseFont(e);
00219 else if (e.tagName() == "container")
00220 {
00221 theme->parseContainer(e, name, context, area);
00222 if (name.lower() == "view")
00223 viewRect = area;
00224 if (name.lower() == "selector")
00225 listRect = area;
00226 if (name.lower() == "program_info")
00227 infoRect = area;
00228 }
00229 else
00230 {
00231 VERBOSE(VB_IMPORTANT,
00232 QString("PreviousList: Unknown child element: %1. "
00233 "Ignoring.").arg(e.tagName()));
00234 }
00235 }
00236 }
00237 }
00238
00239 void PreviousList::updateBackground(void)
00240 {
00241 QPixmap bground(size());
00242 bground.fill(this, 0, 0);
00243
00244 QPainter tmp(&bground);
00245
00246 LayerSet *container = theme->GetSet("background");
00247 if (container)
00248 {
00249 UITextType *ltype = (UITextType *)container->GetType("sched");
00250 if (ltype)
00251 {
00252 QString value = tr("Previously Recorded");
00253 ltype->SetText(value);
00254 }
00255 container->Draw(&tmp, 0, 0);
00256 }
00257
00258 tmp.end();
00259
00260 setPaletteBackgroundPixmap(bground);
00261 }
00262
00263 void PreviousList::paintEvent(QPaintEvent *e)
00264 {
00265 if (!allowUpdates)
00266 {
00267 updateAll = true;
00268 return;
00269 }
00270
00271 QRect r = e->rect();
00272 QPainter p(this);
00273
00274 if (updateAll || r.intersects(listRect))
00275 updateList(&p);
00276 if (updateAll || r.intersects(infoRect))
00277 updateInfo(&p);
00278 if (updateAll || r.intersects(viewRect))
00279 updateView(&p);
00280
00281 updateAll = false;
00282 }
00283
00284 void PreviousList::cursorDown(bool page)
00285 {
00286 if (curItem < (int)itemList.count() - 1)
00287 {
00288 curItem += (page ? listsize : 1);
00289 if (curItem > (int)itemList.count() - 1)
00290 curItem = itemList.count() - 1;
00291 update(fullRect);
00292 }
00293 }
00294
00295 void PreviousList::cursorUp(bool page)
00296 {
00297 if (curItem > 0)
00298 {
00299 curItem -= (page ? listsize : 1);
00300 if (curItem < 0)
00301 curItem = 0;
00302 update(fullRect);
00303 }
00304 }
00305
00306 void PreviousList::prevView(void)
00307 {
00308 if (viewList.count() < 2)
00309 return;
00310
00311 curView--;
00312 if (curView < 0)
00313 curView = viewList.count() - 1;
00314
00315 curItem = -1;
00316 refillAll = true;
00317 }
00318
00319 void PreviousList::nextView(void)
00320 {
00321 if (viewList.count() < 2)
00322 return;
00323
00324 curView++;
00325 if (curView >= (int)viewList.count())
00326 curView = 0;
00327
00328 curItem = -1;
00329 refillAll = true;
00330 }
00331
00332 void PreviousList::setViewFromList(void)
00333 {
00334 if (!choosePopup || !chooseListBox)
00335 return;
00336
00337 int view = chooseListBox->currentItem();
00338
00339 choosePopup->AcceptItem(view);
00340
00341 if (view == curView)
00342 return;
00343
00344 curView = view;
00345
00346 curItem = -1;
00347 refillAll = true;
00348 }
00349
00350 void PreviousList::chooseView(void)
00351 {
00352 if (viewList.count() < 2)
00353 return;
00354
00355 choosePopup = new MythPopupBox(gContext->GetMainWindow(), "");
00356 choosePopup->addLabel(tr("Select Sort Order"));
00357
00358 chooseListBox = new MythListBox(choosePopup);
00359 chooseListBox->setScrollBar(false);
00360 chooseListBox->setBottomScrollBar(false);
00361 chooseListBox->insertStringList(viewTextList);
00362 if (curView < 0)
00363 chooseListBox->setCurrentItem(0);
00364 else
00365 chooseListBox->setCurrentItem(curView);
00366 choosePopup->addWidget(chooseListBox);
00367
00368 connect(chooseListBox, SIGNAL(accepted(int)), this, SLOT(setViewFromList()));
00369
00370 chooseListBox->setFocus();
00371 choosePopup->ExecPopup();
00372
00373 delete chooseListBox;
00374 chooseListBox = NULL;
00375
00376 choosePopup->hide();
00377 choosePopup->deleteLater();
00378 choosePopup = NULL;
00379 }
00380
00381 void PreviousList::select()
00382 {
00383 removalDialog();
00384 }
00385
00386 void PreviousList::edit()
00387 {
00388 ProgramInfo *pi = itemList.at(curItem);
00389
00390 if (!pi)
00391 return;
00392
00393 pi->EditScheduled();
00394 }
00395
00396 void PreviousList::customEdit()
00397 {
00398 ProgramInfo *pi = itemList.at(curItem);
00399
00400 if (!pi)
00401 return;
00402
00403 CustomEdit *ce = new CustomEdit(gContext->GetMainWindow(),
00404 "customedit", pi);
00405 ce->exec();
00406 delete ce;
00407 }
00408
00409 void PreviousList::upcoming()
00410 {
00411 ProgramInfo *pi = itemList.at(curItem);
00412
00413 ProgLister *pl = new ProgLister(plTitle, pi->title, "",
00414 gContext->GetMainWindow(), "proglist");
00415 pl->exec();
00416 delete pl;
00417 }
00418
00419 void PreviousList::details()
00420 {
00421 ProgramInfo *pi = itemList.at(curItem);
00422
00423 if (pi)
00424 pi->showDetails();
00425 }
00426
00427 void PreviousList::fillViewList(const QString &view)
00428 {
00429 viewList.clear();
00430 viewTextList.clear();
00431
00432 viewList << "sort by time";
00433 viewTextList << tr("Time");
00434
00435 viewList << "reverse time";
00436 viewTextList << tr("Reverse Time");
00437
00438 viewList << "sort by title";
00439 viewTextList << tr("Title");
00440
00441 viewList << "reverse title";
00442 viewTextList << tr("Reverse Title");
00443
00444 curView = viewList.findIndex(view);
00445
00446 if (curView < 0)
00447 curView = 0;
00448 }
00449
00450 class pbTitleSort
00451 {
00452 public:
00453 pbTitleSort(bool reverseSort = false) {m_reverse = reverseSort;}
00454
00455 bool operator()(const ProgramInfo *a, const ProgramInfo *b)
00456 {
00457 if (a->sortTitle == b->sortTitle)
00458 {
00459 if (a->programid == b->programid)
00460 return (a->startts < b->startts);
00461 else
00462 return (a->programid < b->programid);
00463 }
00464 else if (m_reverse)
00465 return (a->sortTitle > b->sortTitle);
00466 else
00467 return (a->sortTitle < b->sortTitle);
00468 }
00469
00470 private:
00471 bool m_reverse;
00472 };
00473
00474 class pbTimeSort
00475 {
00476 public:
00477 pbTimeSort(bool reverseSort = false) {m_reverse = reverseSort;}
00478
00479 bool operator()(const ProgramInfo *a, const ProgramInfo *b)
00480 {
00481 if (m_reverse)
00482 return (a->startts < b->startts);
00483 else
00484 return (a->startts > b->startts);
00485 }
00486
00487 private:
00488 bool m_reverse;
00489 };
00490
00491 void PreviousList::fillItemList(void)
00492 {
00493 if (curView < 0)
00494 return;
00495
00496 ProgramInfo *s;
00497 MSqlBindings bindings;
00498
00499 QString sql = "";
00500 if (m_recid > 0 && m_title > "")
00501 {
00502 sql = QString("WHERE recordid = %1 OR title = :MTITLE ").arg(m_recid);
00503 bindings[":MTITLE"] = m_title;
00504 }
00505 else if (m_title > "")
00506 {
00507 sql = QString("WHERE title = :MTITLE ");
00508 bindings[":MTITLE"] = m_title;
00509 }
00510 itemList.FromOldRecorded(sql, bindings);
00511
00512 vector<ProgramInfo *> sortedList;
00513 while (itemList.count())
00514 {
00515 s = itemList.take();
00516 s->sortTitle = s->title;
00517 s->sortTitle.remove(QRegExp("^(The |A |An )"));
00518 sortedList.push_back(s);
00519 }
00520
00521 if (viewList[curView] == "reverse time")
00522 sort(sortedList.begin(), sortedList.end(), pbTimeSort(true));
00523 else if (viewList[curView] == "sort by time")
00524 sort(sortedList.begin(), sortedList.end(), pbTimeSort(false));
00525 else if (viewList[curView] == "reverse title")
00526 sort(sortedList.begin(), sortedList.end(), pbTitleSort(true));
00527 else
00528 sort(sortedList.begin(), sortedList.end(), pbTitleSort(false));
00529
00530 vector<ProgramInfo *>::iterator i = sortedList.begin();
00531 for (; i != sortedList.end(); i++)
00532 itemList.append(*i);
00533
00534 if (curItem < 0 && itemList.count() > 0)
00535 curItem = 0;
00536 else if (curItem >= (int)itemList.count())
00537 curItem = itemList.count() - 1;
00538 }
00539
00540 void PreviousList::updateView(QPainter *p)
00541 {
00542 QRect pr = viewRect;
00543 QPixmap pix(pr.size());
00544 pix.fill(this, pr.topLeft());
00545 QPainter tmp(&pix);
00546
00547 LayerSet *container = NULL;
00548
00549 container = theme->GetSet("view");
00550 if (container)
00551 {
00552 UITextType *type = (UITextType *)container->GetType("curview");
00553 if (type && curView >= 0)
00554 type->SetText(viewTextList[curView]);
00555
00556 container->Draw(&tmp, 4, 0);
00557 container->Draw(&tmp, 5, 0);
00558 container->Draw(&tmp, 6, 0);
00559 container->Draw(&tmp, 7, 0);
00560 container->Draw(&tmp, 8, 0);
00561 }
00562
00563 tmp.end();
00564 p->drawPixmap(pr.topLeft(), pix);
00565 }
00566
00567 void PreviousList::updateList(QPainter *p)
00568 {
00569 QRect pr = listRect;
00570 QPixmap pix(pr.size());
00571 pix.fill(this, pr.topLeft());
00572 QPainter tmp(&pix);
00573
00574 QString tmptitle;
00575
00576 LayerSet *container = theme->GetSet("selector");
00577 if (container)
00578 {
00579 UIListType *ltype = (UIListType *)container->GetType("proglist");
00580 if (ltype)
00581 {
00582 ltype->ResetList();
00583 ltype->SetActive(true);
00584
00585 int skip;
00586 if ((int)itemList.count() <= listsize || curItem <= listsize/2)
00587 skip = 0;
00588 else if (curItem >= (int)itemList.count() - listsize + listsize/2)
00589 skip = itemList.count() - listsize;
00590 else
00591 skip = curItem - listsize / 2;
00592 ltype->SetUpArrow(skip > 0);
00593 ltype->SetDownArrow(skip + listsize < (int)itemList.count());
00594
00595 int i;
00596 for (i = 0; i < listsize; i++)
00597 {
00598 if (i + skip >= (int)itemList.count())
00599 break;
00600
00601 ProgramInfo *pi = itemList.at(i+skip);
00602
00603 ltype->SetItemText(i, 1, pi->startts.toString(timeFormat));
00604 ltype->SetItemText(i, 2, pi->ChannelText(channelFormat));
00605
00606 if (pi->subtitle == "")
00607 tmptitle = pi->title;
00608 else
00609 {
00610 tmptitle = QString("%1 - \"%2\"")
00611 .arg(pi->title)
00612 .arg(pi->subtitle);
00613 }
00614
00615 ltype->SetItemText(i, 3, tmptitle);
00616 ltype->SetItemText(i, 4, pi->RecStatusChar());
00617
00618 if (pi->recstatus == rsRecording)
00619 ltype->EnableForcedFont(i, "recording");
00620 else if (pi->recstatus < rsRecorded ||
00621 pi->recstatus == rsConflict ||
00622 pi->recstatus == rsOffLine)
00623 ltype->EnableForcedFont(i, "conflicting");
00624 else if (!pi->duplicate)
00625 ltype->EnableForcedFont(i, "inactive");
00626
00627
00628
00629
00630
00631
00632
00633 if (i + skip == curItem)
00634 ltype->SetItemCurrent(i);
00635 }
00636 }
00637 }
00638
00639 if (itemList.count() == 0)
00640 container = theme->GetSet("noprograms_list");
00641
00642 if (container)
00643 {
00644 container->Draw(&tmp, 0, 0);
00645 container->Draw(&tmp, 1, 0);
00646 container->Draw(&tmp, 2, 0);
00647 container->Draw(&tmp, 3, 0);
00648 container->Draw(&tmp, 4, 0);
00649 container->Draw(&tmp, 5, 0);
00650 container->Draw(&tmp, 6, 0);
00651 container->Draw(&tmp, 7, 0);
00652 container->Draw(&tmp, 8, 0);
00653 }
00654
00655 tmp.end();
00656 p->drawPixmap(pr.topLeft(), pix);
00657 }
00658
00659 void PreviousList::updateInfo(QPainter *p)
00660 {
00661 QRect pr = infoRect;
00662 QPixmap pix(pr.size());
00663 pix.fill(this, pr.topLeft());
00664 QPainter tmp(&pix);
00665
00666 LayerSet *container = NULL;
00667 ProgramInfo *pi = itemList.at(curItem);
00668
00669 if (pi)
00670 {
00671 container = theme->GetSet("program_info");
00672 if (container)
00673 {
00674 QMap<QString, QString> infoMap;
00675 pi->ToMap(infoMap, true);
00676 container->ClearAllText();
00677 container->SetText(infoMap);
00678 }
00679 }
00680 else
00681 container = theme->GetSet("norecordings_info");
00682
00683 if (container)
00684 {
00685 container->Draw(&tmp, 4, 0);
00686 container->Draw(&tmp, 5, 0);
00687 container->Draw(&tmp, 6, 0);
00688 container->Draw(&tmp, 7, 0);
00689 container->Draw(&tmp, 8, 0);
00690 }
00691
00692 tmp.end();
00693 p->drawPixmap(pr.topLeft(), pix);
00694 }
00695
00696 void PreviousList::removalDialog()
00697 {
00698 ProgramInfo *pi = itemList.at(curItem);
00699
00700 if (!pi)
00701 return;
00702
00703 QString message = pi->title;
00704
00705 if (pi->subtitle != "")
00706 message += QString(" - \"%1\"").arg(pi->subtitle);
00707
00708 if (pi->description != "")
00709 message += "\n\n" + pi->description;
00710
00711 message += "\n\n\n" + tr("NOTE: removing items from this list will not "
00712 "delete any recordings.");
00713
00714 DialogBox *dlg = new DialogBox(gContext->GetMainWindow(), message);
00715 int button = 0, ok = -1, cleardup = -1, setdup = -1, rm_episode = -1,
00716 rm_title = -1;
00717
00718
00719 dlg->AddButton(tr("OK"));
00720 ok = button++;
00721
00722 if (pi->duplicate)
00723 {
00724 dlg->AddButton(tr("Allow this episode to re-record"));
00725 cleardup = button++;
00726 }
00727 else
00728 {
00729 dlg->AddButton(tr("Never record this episode"));
00730 setdup = button++;
00731 }
00732 dlg->AddButton(tr("Remove this episode from the list"));
00733 rm_episode = button++;
00734
00735 dlg->AddButton(tr("Remove all episodes for this title"));
00736 rm_title = button++;
00737
00738
00739
00740
00741
00742 DialogCode code = dlg->exec();
00743 int ret = MythDialog::CalcItemIndex(code);
00744 dlg->deleteLater();
00745 dlg = NULL;
00746
00747 if (ret == rm_episode)
00748 {
00749 deleteItem();
00750 }
00751 else if (ret == rm_title)
00752 {
00753 MSqlQuery query(MSqlQuery::InitCon());
00754 query.prepare("DELETE FROM oldrecorded WHERE title = :TITLE ;");
00755 query.bindValue(":TITLE", pi->title.utf8());
00756 query.exec();
00757
00758 ScheduledRecording::signalChange(0);
00759 fillItemList();
00760 }
00761 else if (ret == cleardup)
00762 pi->ForgetHistory();
00763 else if (ret == setdup)
00764 pi->SetDupHistory();
00765 }
00766
00767 void PreviousList::deleteItem()
00768 {
00769 ProgramInfo *pi = itemList.at(curItem);
00770
00771 if (!pi)
00772 return;
00773
00774 MSqlQuery query(MSqlQuery::InitCon());
00775 query.prepare("DELETE FROM oldrecorded "
00776 "WHERE chanid = :CHANID AND starttime = :STARTTIME ;");
00777 query.bindValue(":CHANID", pi->chanid);
00778 query.bindValue(":STARTTIME", pi->startts.toString(Qt::ISODate));
00779 query.exec();
00780 ScheduledRecording::signalChange(0);
00781 fillItemList();
00782 }
00783
00784 void PreviousList::customEvent(QCustomEvent *e)
00785 {
00786 if ((MythEvent::Type)(e->type()) != MythEvent::MythEventMessage)
00787 return;
00788
00789 MythEvent *me = (MythEvent *)e;
00790 QString message = me->Message();
00791 if (message != "SCHEDULE_CHANGE" && message != "CHOOSE_VIEW")
00792 return;
00793
00794 if (message == "CHOOSE_VIEW")
00795 {
00796 chooseView();
00797 if (curView < 0)
00798 {
00799 reject();
00800 return;
00801 }
00802 }
00803
00804 refillAll = true;
00805
00806 if (!allowEvents)
00807 return;
00808
00809 allowEvents = false;
00810
00811 allowUpdates = false;
00812 do
00813 {
00814 refillAll = false;
00815 fillItemList();
00816 } while (refillAll);
00817 allowUpdates = true;
00818 update(fullRect);
00819
00820 allowEvents = true;
00821 }