00001 #include <qtimer.h>
00002 #include <qapplication.h>
00003
00004 #include <mythtv/mythcontext.h>
00005 #include <mythtv/uitypes.h>
00006 #include <mythtv/compat.h>
00007
00008 #include <qprocess.h>
00009 #include <unistd.h>
00010 #include <cstdlib>
00011
00012 #include "moviesui.h"
00013
00014 namespace
00015 {
00016 struct sAscendingMovieOrder
00017 {
00018 bool operator()(const Movie& start,const Movie& end)
00019 {
00020 return start.name < end.name;
00021 }
00022 };
00023
00024
00025
00026
00027
00028 QString executeExternal(const QStringList &args, const QString &purpose)
00029 {
00030 QString ret = "";
00031 QString err = "";
00032
00033 VERBOSE(VB_GENERAL, QString("%1: Executing '%2'").arg(purpose).
00034 arg(args.join(" ")).local8Bit() );
00035 QProcess proc(args);
00036
00037 QString cmd = args[0];
00038 QFileInfo info(cmd);
00039
00040 if (!info.exists())
00041 {
00042 err = QString("\"%1\" failed: does not exist").arg(cmd.local8Bit());
00043 }
00044 else if (!info.isExecutable())
00045 {
00046 err = QString("\"%1\" failed: not executable").arg(cmd.local8Bit());
00047 }
00048 else if (proc.start())
00049 {
00050 while (true)
00051 {
00052 while (proc.canReadLineStdout() || proc.canReadLineStderr())
00053 {
00054 if (proc.canReadLineStdout())
00055 {
00056 ret +=
00057 QString::fromUtf8(proc.readLineStdout(), -1) + "\n";
00058 }
00059
00060 if (proc.canReadLineStderr())
00061 {
00062 if (err == "")
00063 {
00064 err = cmd + ": ";
00065 }
00066
00067 err +=
00068 QString::fromUtf8(proc.readLineStderr(), -1) + "\n";
00069 }
00070 }
00071
00072 if (proc.isRunning())
00073 {
00074 qApp->processEvents();
00075 usleep(10000);
00076 }
00077 else
00078 {
00079 if (!proc.normalExit())
00080 {
00081 err = QString("\"%1\" failed: Process exited "
00082 "abnormally").arg(cmd.local8Bit());
00083 }
00084
00085 break;
00086 }
00087 }
00088 }
00089 else
00090 {
00091 err = QString("\"%1\" failed: Could not start process")
00092 .arg(cmd.local8Bit());
00093 }
00094
00095 while (proc.canReadLineStdout() || proc.canReadLineStderr())
00096 {
00097 if (proc.canReadLineStdout())
00098 {
00099 ret += QString::fromUtf8(proc.readLineStdout(),-1) + "\n";
00100 }
00101
00102 if (proc.canReadLineStderr())
00103 {
00104 if (err == "")
00105 {
00106 err = cmd + ": ";
00107 }
00108
00109 err += QString::fromUtf8(proc.readLineStderr(), -1) + "\n";
00110 }
00111 }
00112
00113 if (err != "")
00114 {
00115 QString tempPurpose(purpose);
00116
00117 if (tempPurpose == "")
00118 tempPurpose = "Command";
00119
00120 VERBOSE(VB_IMPORTANT, err);
00121 MythPopupBox::showOkPopup(gContext->GetMainWindow(),
00122 QObject::tr(tempPurpose + " failed"),
00123 QObject::tr(err + "\n\nCheck MythMovies Settings"));
00124 ret = "#ERROR";
00125 }
00126
00127
00128 return ret;
00129 }
00130 }
00131
00132 MoviesUI::MoviesUI(MythMainWindow *parent, QString windowName,
00133 QString themeFilename, const char *name)
00134 : MythThemedDialog(parent, windowName, themeFilename, name)
00135 {
00136 query = new MSqlQuery(MSqlQuery::InitCon());
00137 subQuery = new MSqlQuery(MSqlQuery::InitCon());
00138 aboutPopup = NULL;
00139 menuPopup = NULL;
00140
00141 m_currentMode = "Undefined";
00142 setupTheme();
00143 }
00144
00145 MoviesUI::~MoviesUI()
00146 {
00147 delete query;
00148 delete subQuery;
00149 }
00150
00151 void MoviesUI::setupTheme(void)
00152 {
00153 m_movieTreeUI = getUIManagedTreeListType("movietreelist");
00154 m_currentNode = NULL;
00155 m_movieTreeUI->showWholeTree(true);
00156 m_movieTreeUI->colorSelectables(true);
00157
00158 connect(m_movieTreeUI, SIGNAL(nodeSelected(int, IntVector*)),
00159 this, SLOT(handleTreeListSelection(int, IntVector*)));
00160 connect(m_movieTreeUI, SIGNAL(nodeEntered(int, IntVector*)),
00161 this, SLOT(handleTreeListEntry(int, IntVector*)));
00162
00163
00164 m_movieTitle = getUITextType("movietitle");
00165 if (!m_movieTitle)
00166 VERBOSE(VB_IMPORTANT, "moviesui.o: Couldn't find text area movietitle");
00167
00168 m_movieRating = getUITextType("ratingvalue");
00169 if (!m_movieRating)
00170 VERBOSE(VB_IMPORTANT,
00171 "moviesui.o: Couldn't find text area ratingvalue");
00172
00173 m_movieRunningTime = getUITextType("runningtimevalue");
00174 if (!m_movieRunningTime)
00175 VERBOSE(VB_IMPORTANT,
00176 "moviesui.o: Couldn't find text area runningtimevalue");
00177
00178 m_movieShowTimes = getUITextType("showtimesvalue");
00179 if (!m_movieShowTimes)
00180 VERBOSE(VB_IMPORTANT,
00181 "moviesui.o: Couldn't find text area showtimesvalue");
00182
00183 m_theaterName = getUITextType("theatername");
00184 if (!m_theaterName)
00185 VERBOSE(VB_IMPORTANT,
00186 "moviesui.o: Couldn't find text area theatername");
00187 gContext->ActivateSettingsCache(false);
00188 QString currentDate = QDate::currentDate().toString();
00189 QString lastDate = gContext->GetSetting("MythMovies.LastGrabDate");
00190 if (currentDate != lastDate)
00191 {
00192 VERBOSE(VB_IMPORTANT, "Movie Data Has Expired. Refreshing.");
00193 updateMovieTimes();
00194 }
00195
00196 gContext->ActivateSettingsCache(true);
00197
00198 updateDataTrees();
00199 drawDisplayTree();
00200 updateForeground();
00201 }
00202
00203 void MoviesUI::updateMovieTimes()
00204 {
00205 gContext->ActivateSettingsCache(false);
00206 QString currentDate = QDate::currentDate().toString();
00207 query->exec("truncate table movies_showtimes");
00208 query->exec("truncate table movies_movies");
00209 query->exec("truncate table movies_theaters");
00210 QString grabber = gContext->GetSetting("MythMovies.Grabber");
00211 grabber.replace("%z", gContext->GetSetting("MythMovies.ZipCode"));
00212 grabber.replace("%r", gContext->GetSetting("MythMovies.Radius"));
00213 QStringList args = QStringList::split(' ', grabber);
00214 QString ret = executeExternal(args, "MythMovies Data Grabber");
00215 VERBOSE(VB_IMPORTANT, "Grabber Finished. Processing Data.");
00216 if (populateDatabaseFromGrabber(ret))
00217 gContext->SaveSetting("MythMovies.LastGrabDate", currentDate);
00218 else
00219 {
00220 MythPopupBox::showOkPopup(gContext->GetMainWindow(),
00221 "Error", tr("Failed to process the grabber data!"));
00222 VERBOSE(VB_IMPORTANT, "Failed to process the grabber data!");
00223 }
00224
00225 gContext->ActivateSettingsCache(true);
00226 }
00227
00228 MovieVector MoviesUI::buildMovieDataTree()
00229 {
00230 MovieVector ret;
00231 if (query->exec("select id, moviename, rating, runningtime from movies_movies order by moviename asc"))
00232 {
00233 while (query->next())
00234 {
00235 Movie m;
00236 m.name = query->value(1).toString();
00237 m.rating = query->value(2).toString();
00238 m.runningTime = query->value(3).toString();
00239 subQuery->prepare("select theatername, theateraddress, showtimes "
00240 "from movies_showtimes left join movies_theaters "
00241 "on movies_showtimes.theaterid = movies_theaters.id "
00242 "where movies_showtimes.movieid = :MOVIEID");
00243 subQuery->bindValue(":MOVIEID", query->value(0).toString());
00244
00245 if (subQuery->exec())
00246 {
00247 while (subQuery->next())
00248 {
00249 Theater t;
00250 t.name = subQuery->value(0).toString();
00251 t.address = subQuery->value(1).toString();
00252 t.showTimes = subQuery->value(2).toString();
00253 m.theaters.push_back(t);
00254 }
00255 }
00256 ret.push_back(m);
00257 }
00258 }
00259 return ret;
00260 }
00261
00262 TheaterVector MoviesUI::buildTheaterDataTree()
00263 {
00264 TheaterVector ret;
00265 if (query->exec("select id, theatername, theateraddress from movies_theaters order by theatername asc"))
00266 {
00267 while (query->next())
00268 {
00269 Theater t;
00270 t.name = query->value(1).toString();
00271 t.address = query->value(2).toString();
00272 subQuery->prepare("select moviename, rating, runningtime, showtimes "
00273 "from movies_showtimes left join movies_movies "
00274 "on movies_showtimes.movieid = movies_movies.id "
00275 "where movies_showtimes.theaterid = :THEATERID");
00276 subQuery->bindValue(":THEATERID", query->value(0).toString());
00277
00278 if (subQuery->exec())
00279 {
00280 while (subQuery->next())
00281 {
00282 Movie m;
00283 m.name = subQuery->value(0).toString();
00284 m.rating = subQuery->value(1).toString();
00285 m.runningTime = subQuery->value(2).toString();
00286 m.showTimes = subQuery->value(3).toString();
00287 t.movies.push_back(m);
00288 }
00289 }
00290
00291 ret.push_back(t);
00292 }
00293 }
00294 return ret;
00295 }
00296
00297 void MoviesUI::keyPressEvent(QKeyEvent *e)
00298 {
00299 bool handled = false;
00300 QStringList actions;
00301 gContext->GetMainWindow()->TranslateKeyPress("Movies", e, actions);
00302
00303 for (unsigned int i = 0; i < actions.size() && !handled; i++)
00304 {
00305 QString action = actions[i];
00306
00307 handled = true;
00308 if (action == "SELECT")
00309 m_movieTreeUI->select();
00310 else if (action == "MENU")
00311 showMenu();
00312 else if (action == "INFO")
00313
00314 showAbout();
00315 else if (action == "UP")
00316 m_movieTreeUI->moveUp();
00317 else if (action == "DOWN")
00318 m_movieTreeUI->moveDown();
00319 else if (action == "LEFT")
00320 m_movieTreeUI->popUp();
00321 else if (action == "RIGHT")
00322 m_movieTreeUI->pushDown();
00323 else if (action == "PAGEUP")
00324 m_movieTreeUI->pageUp();
00325 else if (action == "PAGEDOWN")
00326 m_movieTreeUI->pageDown();
00327 else if (action == "INCSEARCH")
00328 m_movieTreeUI->incSearchStart();
00329 else if (action == "INCSEARCHNEXT")
00330 m_movieTreeUI->incSearchNext();
00331 else
00332 handled = false;
00333 }
00334
00335 if (!handled)
00336 MythThemedDialog::keyPressEvent(e);
00337 }
00338
00339 void MoviesUI::showMenu()
00340 {
00341 if (menuPopup)
00342 return;
00343 menuPopup = new MythPopupBox(gContext->GetMainWindow(), "menuPopup");
00344 menuPopup->addLabel("MythMovies Menu");
00345 updateButton = menuPopup->addButton("Update Movie Times", this, SLOT(slotUpdateMovieTimes()));
00346 OKButton = menuPopup->addButton("Close Menu", this, SLOT(closeMenu()));
00347 OKButton->setFocus();
00348 menuPopup->ShowPopup(this, SLOT(closeMenu()));
00349 }
00350
00351 void MoviesUI::slotUpdateMovieTimes()
00352 {
00353 VERBOSE(VB_IMPORTANT, "Doing Manual Movie Times Update");
00354
00355 menuPopup->hide();
00356 menuPopup->deleteLater();
00357 menuPopup = NULL;
00358
00359 updateMovieTimes();
00360 updateDataTrees();
00361 drawDisplayTree();
00362 }
00363
00364 void MoviesUI::closeMenu(void)
00365 {
00366 if (menuPopup)
00367 {
00368 menuPopup->deleteLater();
00369 menuPopup = NULL;
00370 }
00371 }
00372
00373 void MoviesUI::showAbout()
00374 {
00375 if (aboutPopup)
00376 return;
00377
00378 aboutPopup = new MythPopupBox(gContext->GetMainWindow(), "aboutPopup");
00379 aboutPopup->addLabel("MythMovies");
00380 aboutPopup->addLabel("Copyright (c) 2006 Josh Lefler.");
00381 aboutPopup->addLabel("Released under GNU GPL v2");
00382 aboutPopup->addLabel("Special Thanks to Ignyte.com for\nproviding the "
00383 "listings data.\n and the #mythtv IRC channel for "
00384 "assistance.");
00385 OKButton = aboutPopup->addButton(QString("Close"), this,
00386 SLOT(closeAboutPopup()));
00387 OKButton->setFocus();
00388 aboutPopup->ShowPopup(this,SLOT(closeAboutPopup()));
00389 }
00390
00391 void MoviesUI::closeAboutPopup(void)
00392 {
00393 if (aboutPopup)
00394 {
00395 aboutPopup->deleteLater();
00396 aboutPopup = NULL;
00397 }
00398 }
00399
00400 void MoviesUI::handleTreeListEntry(int nodeInt, IntVector *)
00401 {
00402 m_currentNode = m_movieTreeUI->getCurrentNode();
00403 if (nodeInt == 0)
00404 {
00405 m_currentMode = m_currentNode->getString();
00406 m_theaterName->SetText("");
00407 m_movieTitle->SetText("");
00408 m_movieRunningTime->SetText("");
00409 }
00410 else
00411 {
00412 if (m_currentMode == "By Theater")
00413 {
00414 if (nodeInt < 0)
00415 {
00416 int theaterInt = -nodeInt;
00417 m_currentTheater = &m_dataTreeByTheater.at(theaterInt - 1);
00418 m_theaterName->SetText(m_currentTheater->name + " - " +
00419 m_currentTheater->address);
00420 m_movieTitle->SetText("");
00421 m_movieRating->SetText("");
00422 m_movieShowTimes->SetText("");
00423 m_movieRunningTime->SetText("");
00424 }
00425 else
00426 {
00427 int theaterInt = nodeInt / 100;
00428 int movieInt = nodeInt - (theaterInt * 100);
00429 Theater t = m_dataTreeByTheater.at(theaterInt - 1);
00430 Movie m = t.movies.at(movieInt - 1);
00431 m_movieTitle->SetText(m.name);
00432 m_movieRating->SetText(m.rating);
00433 m_movieRunningTime->SetText(m.runningTime);
00434 QStringList st = QStringList::split("|", m.showTimes);
00435 QString buf;
00436 int i = 0;
00437 for (QStringList::Iterator it = st.begin(); it != st.end();
00438 ++it)
00439 {
00440 buf += (*it).stripWhiteSpace() + " ";
00441 i++;
00442 }
00443 m_movieShowTimes->SetText(buf);
00444 }
00445 }
00446 else if (m_currentMode == "By Movie")
00447 {
00448 if (nodeInt < 0)
00449 {
00450 int movieInt = -nodeInt;
00451 m_currentMovie = &m_dataTreeByMovie.at(movieInt - 1);
00452 m_movieTitle->SetText(m_currentMovie->name);
00453 m_movieRating->SetText(m_currentMovie->rating);
00454 m_movieRunningTime->SetText(m_currentMovie->runningTime);
00455 m_movieShowTimes->SetText("");
00456 m_theaterName->SetText("");
00457 }
00458 else
00459 {
00460 int movieInt = nodeInt / 100;
00461 int theaterInt = nodeInt - (movieInt * 100);
00462 Movie m = m_dataTreeByMovie.at(movieInt - 1);
00463 Theater t = m.theaters.at(theaterInt - 1);
00464 QStringList st = QStringList::split("|", t.showTimes);
00465 QString buf;
00466 int i = 0;
00467 for (QStringList::Iterator it = st.begin(); it != st.end();
00468 ++it)
00469 {
00470 if (i % 4 == 0 && i != 0)
00471 buf+= "\n";
00472 buf += (*it).stripWhiteSpace() + " ";
00473 i++;
00474 }
00475 m_movieShowTimes->SetText(buf);
00476 m_theaterName->SetText(t.name + " - " + t.address);
00477 }
00478 }
00479 else
00480 {
00481
00482 }
00483 }
00484 }
00485
00486 void MoviesUI::handleTreeListSelection(int nodeInt, IntVector *)
00487 {
00488 (void) nodeInt;
00489
00490
00491 }
00492
00493 GenericTree* MoviesUI::getDisplayTreeByTheater()
00494 {
00495 TheaterVector *theaters;
00496 theaters = &m_dataTreeByTheater;
00497 int tbase = 0;
00498 GenericTree *parent = new GenericTree("By Theater", 0, false);
00499 for (unsigned int i = 0; i < theaters->size(); i++)
00500 {
00501 int mbase = 0;
00502 Theater x = theaters->at(i);
00503 GenericTree *node = new GenericTree(x.name, --tbase, false);
00504 for (unsigned int m =0; m < x.movies.size(); m++)
00505 {
00506 Movie y = x.movies.at(m);
00507 node->addNode(y.name, (tbase * -100) + ++mbase, true);
00508 }
00509 parent->addNode(node);
00510 }
00511 return parent;
00512 }
00513
00514 GenericTree* MoviesUI::getDisplayTreeByMovie()
00515 {
00516 MovieVector *movies;
00517 movies = &m_dataTreeByMovie;
00518 int mbase = 0;
00519 GenericTree *parent = new GenericTree("By Movie", 0, false);
00520 for (unsigned int i = 0; i < movies->size(); i++)
00521 {
00522 int tbase = 0;
00523 Movie x = movies->at(i);
00524 GenericTree *node = new GenericTree(x.name, --mbase, false);
00525 for (unsigned int m = 0; m < x.theaters.size(); m++)
00526 {
00527 Theater y = x.theaters.at(m);
00528 node->addNode(y.name, (mbase * -100) + ++tbase, true);
00529 }
00530 parent->addNode(node);
00531 }
00532 return parent;
00533 }
00534 void MoviesUI::updateDataTrees()
00535 {
00536 m_dataTreeByTheater = buildTheaterDataTree();
00537 m_dataTreeByMovie = buildMovieDataTree();
00538 }
00539
00540 void MoviesUI::drawDisplayTree()
00541 {
00542 m_movieTree = new GenericTree("Theaters", 0, false);
00543 m_movieTree->addNode(getDisplayTreeByTheater());
00544 m_movieTree->addNode(getDisplayTreeByMovie());
00545 m_movieTreeUI->assignTreeData(m_movieTree);
00546 m_movieTreeUI->popUp();
00547 m_movieTreeUI->popUp();
00548 m_movieTreeUI->popUp();
00549 m_movieTreeUI->enter();
00550 m_currentMode = m_movieTreeUI->getCurrentNode()->getString();
00551 }
00552
00553 bool MoviesUI::populateDatabaseFromGrabber(QString ret)
00554 {
00555
00556 QString error;
00557 int errorLine;
00558 int errorColumn;
00559 QDomDocument doc;
00560 QDomNode n;
00561 if (!doc.setContent(ret, false, &error, &errorLine, &errorColumn))
00562 {
00563 VERBOSE(VB_IMPORTANT, QString("Error parsing data from grabber: "
00564 "Error: %1 Location Line: %2 Column %3")
00565 .arg(error) .arg(errorLine) .arg(errorColumn));
00566 return false;
00567 }
00568 QDomElement root = doc.documentElement();
00569 n = root.firstChild();
00570
00571 while (!n.isNull())
00572 {
00573 processTheatre(n);
00574
00575 n = n.nextSibling();
00576 }
00577
00578 return true;
00579 }
00580
00581 void MoviesUI::processTheatre(QDomNode &n)
00582 {
00583 Theater t;
00584
00585 QDomNode movieNode;
00586 const QDomElement theater = n.toElement();
00587 QDomNode child = theater.firstChild();
00588 while (!child.isNull())
00589 {
00590 if (!child.isNull())
00591 {
00592 if (child.toElement().tagName() == "Name")
00593 {
00594 t.name = child.firstChild().toText().data();
00595 if (t.name.isNull())
00596 t.name = "";
00597 }
00598
00599 if (child.toElement().tagName() == "Address")
00600 {
00601 t.address = child.firstChild().toText().data();
00602 if (t.address.isNull())
00603 t.address = "";
00604 }
00605 if (child.toElement().tagName() == "Movies")
00606 {
00607 query->prepare("INSERT INTO movies_theaters "
00608 "(theatername, theateraddress)"
00609 "values (:NAME,:ADDRESS)");
00610
00611 query->bindValue(":NAME", t.name.utf8());
00612 query->bindValue(":ADDRESS", t.address.utf8());
00613 if (!query->exec())
00614 {
00615 VERBOSE(VB_IMPORTANT, "Failure to Insert Theater");
00616 }
00617 int lastid = query->lastInsertId().toInt();
00618 movieNode = child.firstChild();
00619 while (!movieNode.isNull())
00620 {
00621 processMovie(movieNode, lastid);
00622
00623 movieNode = movieNode.nextSibling();
00624 }
00625 }
00626
00627 child = child.nextSibling();
00628 }
00629 }
00630 }
00631
00632 void MoviesUI::processMovie(QDomNode &n, int theaterId)
00633 {
00634 Movie m;
00635 QDomNode mi = n.firstChild();
00636 int movieId = 0;
00637 while (!mi.isNull())
00638 {
00639 if (mi.toElement().tagName() == "Name")
00640 {
00641 m.name = mi.firstChild().toText().data();
00642 if (m.name.isNull())
00643 m.name = "";
00644 }
00645 if (mi.toElement().tagName() == "Rating")
00646 {
00647 m.rating = mi.firstChild().toText().data();
00648 if (m.rating.isNull())
00649 m.rating = "";
00650 }
00651 if (mi.toElement().tagName() == "ShowTimes")
00652 {
00653 m.showTimes = mi.firstChild().toText().data();
00654 if (m.showTimes.isNull())
00655 m.showTimes = "";
00656 }
00657 if (mi.toElement().tagName() == "RunningTime")
00658 {
00659 m.runningTime = mi.firstChild().toText().data();
00660 if (m.runningTime.isNull())
00661 m.runningTime = "";
00662 }
00663 mi = mi.nextSibling();
00664 }
00665
00666 query->prepare("SELECT id FROM movies_movies Where moviename = :NAME");
00667 query->bindValue(":NAME", m.name.utf8());
00668 if (query->exec() && query->next())
00669 {
00670 movieId = query->value(0).toInt();
00671 }
00672 else
00673 {
00674 query->prepare("INSERT INTO movies_movies ("
00675 "moviename, rating, runningtime) values ("
00676 ":NAME, :RATING, :RUNNINGTIME)");
00677 query->bindValue(":NAME", m.name.utf8());
00678 query->bindValue(":RATING", m.rating.utf8());
00679 query->bindValue(":RUNNINGTIME", m.runningTime.utf8());
00680 if (query->exec())
00681 {
00682 movieId = query->lastInsertId().toInt();
00683 }
00684 else
00685 {
00686 VERBOSE(VB_IMPORTANT, "Failure to Insert Movie");
00687 }
00688 }
00689 query->prepare("INSERT INTO movies_showtimes ("
00690 "theaterid, movieid, showtimes) values ("
00691 ":THEATERID, :MOVIEID, :SHOWTIMES)");
00692 query->bindValue(":THEATERID", theaterId);
00693 query->bindValue(":MOVIEID", movieId);
00694 query->bindValue(":SHOWTIMES", m.showTimes);
00695
00696 if (!query->exec())
00697 {
00698 VERBOSE(VB_IMPORTANT, "Failure to Link Movie to Theater");
00699 }
00700 }
00701
00702