Add "Always get the best media format"
[nomnom.git] / src / i / MainWindow.cpp
bloba88f2f303c8aef12c23d5e6ff3c0697c3b810d53
1 /*
2 * NomNom
3 * Copyright (C) 2010-2011 Toni Gundogdu <legatvs@gmail.com>
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 #include "config.h"
21 #include <QInputDialog>
22 #include <QMainWindow>
23 #include <QCloseEvent>
24 #include <QFileDialog>
25 #include <QMessageBox>
26 #include <QSettings>
27 #include <QRegExp>
29 #ifdef ENABLE_VERBOSE
30 #include <QDebug>
31 #endif
33 #include <NErrorWhileDialog>
34 #include <NSettingsMutator>
35 #include <NSettingsDialog>
36 #include <NRecentMutator>
37 #include <NRecentEntry>
38 #include <NAboutDialog>
39 #include <NFeedDialog>
40 #include <NLogDialog>
41 #include <NSettings>
42 #include <NSysTray>
43 #include <NLang>
44 #include <NUtil>
46 #include "DownloadDiag.h"
47 #include "ProcProgDiag.h"
48 #include "MainWindow.h"
50 #define QSETTINGS_GROUP "MainWindow"
52 // main.cpp
54 extern bool have_quvi_feature_query_formats;
55 extern nn::NSettingsMutator settings;
56 extern bool have_umph_feature_all;
57 extern nn::NRecentMutator recent;
58 extern nn::NSysTray *systray;
60 enum { StreamMedia=0, DownloadMedia };
62 MainWindow::MainWindow()
64 setupUi(this);
65 restore();
67 // Create Download dialog.
69 _download = new DownloadDialog(this);
71 // Create Process Progress dialog for quvi.
73 _proc = new ProcessProgressDialog(this);
75 connect(_proc, SIGNAL(finished(QString)),
76 this, SLOT(onProcFinished(QString)));
78 // Recent URLs.
80 recent.setMaxItems(25);
81 recent.read();
83 // Custom program icon.
85 #ifdef _1
86 if (shPrefs.get(SharedPreferences::CustomProgramIcon).toBool())
87 changeProgramIcon();
88 #endif
91 void MainWindow::createContextMenu()
93 #define creat_a(t,f,c) \
94 do { \
95 QAction *a = new QAction(t, textBrowser); \
96 if (c) { \
97 a->setCheckable(c); \
98 /*connect(a, SIGNAL(toggled(bool)), SLOT(f(bool)));*/ \
99 } \
100 else \
101 connect(a, SIGNAL(triggered()), SLOT(f())); \
102 textBrowser->addAction(a); \
103 _actions[t] = a; \
104 } while (0)
106 #define add_s \
107 do { \
108 QAction *a = new QAction(textBrowser); \
109 a->setSeparator(true); \
110 textBrowser->addAction(a); \
111 } while (0)
113 creat_a(tr("Address..."), onAddress, false);
114 creat_a(tr("Feed..."), onFeed, false);
115 creat_a(tr("Recent..."), onRecent, false);
116 add_s;
117 creat_a(tr("Settings..."), onSettings, false);
118 add_s;
119 creat_a(tr("About..."), onAbout, false);
120 creat_a(tr("Quit"), onTerminate, false);
122 #undef add_s
123 #undef creat_a
125 // Add key shortcuts.
127 #define _wrap(s,k) \
128 do { _actions[s]->setShortcut(QKeySequence(k)); } while (0)
130 _wrap(tr("Address..."), "Ctrl+A");
131 _wrap(tr("Feed..."), "Ctrl+F");
132 _wrap(tr("Recent..."), "Ctrl+R");
133 // --
134 _wrap(tr("Settings..."), "Ctrl+E");
135 // --
136 _wrap(tr("Quit"), "Ctrl+Q");
137 #undef _wrap
139 // Add the context menu.
141 textBrowser->setContextMenuPolicy(Qt::ActionsContextMenu);
144 void MainWindow::createTrayIcon()
146 systray = new nn::NSysTray(this, QString("<b>NomNom</b> %1").arg(VN));
148 connect(systray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
149 this, SLOT(activated(QSystemTrayIcon::ActivationReason)));
151 systray->addTrayMenuAction(SIGNAL(toggled(bool)),
152 this,
153 SLOT(setVisible(bool)),
154 tr("Show"),
155 true);
157 #ifdef _1
158 // FIXME: If mainwindow is hidden, closing the NSettingsDialog causes
159 // the program to exit.
160 systray->addTrayMenuAction(SIGNAL(triggered()),
161 this,
162 SLOT(onSettings()),
163 tr("Settings..."));
164 #endif
166 systray->addTrayMenuSeparator();
168 systray->addTrayMenuAction(SIGNAL(triggered()),
169 this,
170 SLOT(onTerminate()),
171 tr("Quit"));
173 systray->setIcon(QIcon(":img/nomnom.png"));
174 systray->setTrayMenu();
176 const bool startInTray = settings.value(nn::StartInTrayIcon).toBool();
177 const bool showInTray = settings.value(nn::ShowInTrayIcon).toBool();
179 systray->setVisible(showInTray);
181 if (showInTray)
182 startInTray ? hide() : show();
183 else
184 show();
187 #ifdef ENABLE_VERBOSE
188 static void print_url(const nn::NRecentEntry& r)
190 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << r.url();
192 #endif
194 void MainWindow::handleURL(const QString& url)
196 QString s = settings.eitherValue(nn::ParseUsing,
197 nn::ParseUsingOther)
198 .toString()
199 .simplified();
201 QStringList q_args = nn::to_cmd_args(s);
202 const QString q = q_args.first();
204 #ifdef ENABLE_VERBOSE
205 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "quvi_path=" << q;
206 #endif
208 if (q.isEmpty())
210 nn::info(this, tr("Please configure the path to the quvi. "
211 "See under the \"commands\" in the settings."));
212 onSettings();
213 return;
216 s = settings.eitherValue(nn::PlayUsing,
217 nn::PlayUsingOther)
218 .toString()
219 .simplified();
221 QStringList p_args = nn::to_cmd_args(s);
222 const QString p = p_args.first();
224 if (p.isEmpty())
226 nn::info(this, tr("Please configure the path to a media player. "
227 "See under the \"commands\" in the settings."));
228 onSettings();
229 return;
232 // Recent.
234 nn::NRecentEntry e;
235 e.setURL(url);
237 recent << e;
239 #ifdef ENABLE_VERBOSE
240 recent.for_each(print_url);
241 #endif
243 // 0x1=invalid input, 0x3=no input
245 have_quvi_feature_query_formats =
246 nn::check_for_cmd_feature(nn::ParseUsing,
247 nn::ParseUsingOther,
248 "-F",
249 0x3);
251 // Query formats to an URL.
253 #ifdef ENABLE_VERBOSE
254 qDebug() << __PRETTY_FUNCTION__ << __LINE__
255 << "have_quvi_feature_query_formats="
256 << have_quvi_feature_query_formats;
257 #endif
259 const bool get_best = settings.value(nn::GetBestFormat).toBool();
261 QStringList formats;
262 if (have_quvi_feature_query_formats && !get_best)
264 bool failed = false;
266 if (!queryFormats(formats, q_args, url, failed))
267 formats.clear();
269 if (failed)
271 nn::NErrorWhileDialog *d =
272 new nn::NErrorWhileDialog(q_args,
273 _proc->errmsg(),
274 _proc->errcode(),
275 this);
276 d->exec();
277 return;
280 if (_proc->canceled())
281 return;
284 _json.clear();
286 // Choose format.
288 QString fmt;
289 if (!selectFormat(formats, fmt, get_best))
290 return;
292 // Query media stream data.
294 q_args.replaceInStrings("%u", url);
295 q_args << "-f" << fmt;
297 #ifdef ENABLE_VERBOSE
298 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "q_args=" << q_args;
299 #endif
301 _proc->setLabelText(tr("Checking..."));
302 _proc->setMaximum(0);
303 _proc->start(q_args);
305 if (_proc->canceled())
306 return;
308 // Check for quvi errors.
310 QString errmsg;
311 if (parseOK(errmsg))
313 // Update recent entry. Media URL is set already. Update title
314 // only.
316 e.setTitle(_media.get(Media::PageTitle).toString().simplified());
317 recent.update(e);
319 // Download media or pass media stream URL.
321 if (modeCBox->currentIndex() == StreamMedia)
322 streamMedia();
323 else
324 downloadMedia();
326 else
328 nn::NErrorWhileDialog *d =
329 new nn::NErrorWhileDialog(q_args,
330 errmsg,
331 _proc->errcode(),
332 this);
333 d->exec();
337 bool MainWindow::queryFormats(QStringList& formats,
338 const QStringList& q_args,
339 const QString& url,
340 bool& failed)
342 QStringList args = q_args;
343 args.replaceInStrings("%u", url);
344 args << "-F";
346 #ifdef ENABLE_VERBOSE
347 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "args=" << args;
348 #endif
350 _json.clear();
352 _proc->setLabelText(tr("Checking..."));
353 _proc->setMaximum(0);
354 _proc->start(args);
356 failed = _proc->failed();
357 if (failed)
358 return false;
360 #ifdef ENABLE_VERBOSE
361 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "failed=" << failed;
362 #endif
364 QStringList lns = _json.split("\n");
365 lns.removeDuplicates();
367 const QRegExp rx("^(.*)\\s+:\\s+");
368 foreach (const QString s, lns)
370 if (rx.indexIn(s) != -1)
372 formats = QStringList()
373 << "default"
374 << "best"
375 << rx.cap(1).simplified().split("|");
377 #ifdef ENABLE_VERBOSE
378 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "formats=" << formats;
379 #endif
381 return true;
384 return false;
387 bool MainWindow::selectFormat(QStringList& formats,
388 QString& fmt,
389 const bool get_best)
391 if (get_best)
393 fmt = "best";
394 return true;
397 if (formats.size() < 2) // Skip prompt unless >1 formats available
399 fmt = "default";
400 return true;
403 bool ok = false;
404 fmt = QInputDialog::getItem(this,
405 tr("Choose format"),
406 tr("Format:"),
407 formats << tr("Enter your own"),
409 false,
410 &ok);
411 if (!ok)
412 return false; // Cancel
414 if (fmt == tr("Enter your own"))
416 fmt = QInputDialog::getText(this,
417 tr("Enter format"),
418 tr("Format:"),
419 QLineEdit::Normal,
420 "default",
421 &ok);
423 return ok && !fmt.isEmpty();
426 void MainWindow::streamMedia()
428 const QString p = settings.eitherValue(nn::PlayUsing,
429 nn::PlayUsingOther)
430 .toString()
431 .simplified();
433 QStringList args = nn::to_cmd_args(p);
434 args.replaceInStrings("%m", _media.get(Media::StreamURL).toString());
436 const QString cmd = args.takeFirst();
438 if (!QProcess::startDetached(cmd, args))
440 nn::NErrorWhileDialog *d =
441 new nn::NErrorWhileDialog(QStringList() << cmd << args,
442 tr("Unknown error while attempting to "
443 "start a detached process"),
445 this);
446 d->exec();
450 void MainWindow::downloadMedia()
452 QString fname = settings.value(nn::FilenameFormat).toString();
454 const QString suffix =
455 _media.get(Media::FileSuffix).toString().simplified();
457 bool ok = nn::format_filename(
458 settings.value(nn::FilenameRegExp).toString(),
459 _media.get(Media::PageTitle).toString().simplified(),
460 _media.get(Media::MediaID).toString().simplified(),
461 _media.get(Media::Host).toString().simplified(),
462 suffix,
463 fname
466 if (!ok)
467 return;
469 QString fpath = settings.value(nn::SaveMediaDirectory).toString();
471 if (fpath.isEmpty())
472 fpath = QDir::homePath();
474 fpath = QDir::toNativeSeparators(fpath +"/"+ fname);
476 const bool ask_where_to_save =
477 settings.value(nn::AskWhereToSaveMediaFile).toBool();
479 if (ask_where_to_save)
481 const QFileDialog::Options opts =
482 QFileDialog::DontConfirmOverwrite;
484 fpath = QFileDialog::getSaveFileName(this,
485 tr("Save media as"),
486 fpath,
487 suffix, // Filter.
488 0, // Selected filter.
489 opts);
490 if (fpath.isEmpty())
491 return;
494 if (settings.value(nn::ReplaceExistingMedia).toBool())
495 QDir().remove(fpath);
497 const qint64 expected_bytes =
498 _media.get(Media::LengthBytes).toLongLong();
500 if (QFileInfo(fpath).size() < expected_bytes)
502 const QString p = settings.eitherValue(nn::DownloadUsing,
503 nn::DownloadUsingOther)
504 .toString()
505 .simplified();
507 QStringList args = nn::to_cmd_args(p);
508 args.replaceInStrings("%u", _media.get(Media::StreamURL).toString());
509 args.replaceInStrings("%f", fpath);
511 _download->start(args);
513 if (_download->canceled())
514 return;
516 if (_download->failed())
518 nn::NErrorWhileDialog *d =
519 new nn::NErrorWhileDialog(args,
520 _download->errmsg(),
521 _download->errcode(),
522 this);
523 d->exec();
524 return;
528 const bool completeFile =
529 QFileInfo(fpath).size() >= expected_bytes;
531 const bool playWhenDone =
532 settings.value(nn::PlayWhenDoneDownloading).toBool();
534 #ifdef ENABLE_VERBOSE
535 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "file="
536 << QFileInfo(fpath).size()
537 << expected_bytes
538 << completeFile
539 << playWhenDone;
540 #endif
542 if (completeFile && playWhenDone)
544 const QString p = settings.eitherValue(nn::PlayUsing,
545 nn::PlayUsingOther)
546 .toString()
547 .simplified();
549 QStringList args = nn::to_cmd_args(p);
550 args.replaceInStrings("%m", fpath);
552 #ifdef ENABLE_VERBOSE
553 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "args=" << args;
554 #endif
556 const QString cmd = args.takeFirst();
558 if (!QProcess::startDetached(cmd, args))
560 nn::NErrorWhileDialog *d =
561 new nn::NErrorWhileDialog(QStringList() << cmd << args,
562 tr("Unknown error while attempting to "
563 "start a detached process"),
565 this);
566 d->exec();
571 void MainWindow::changeProgramIcon()
573 #ifdef _1
574 const bool customProgramIcon =
575 shPrefs.get(SharedPreferences::CustomProgramIcon).toBool();
577 QString html = textBrowser->toHtml();
579 const QString iconPath =
580 customProgramIcon
581 ? shPrefs.get(SharedPreferences::ProgramIconPath).toString()
582 : ":img/nomnom.png";
584 html.replace(QRegExp("img src=\".*\""), "img src=\"" +iconPath+ "\"");
586 textBrowser->setHtml(html);
588 QIcon icon = QIcon(iconPath);
589 trayIcon->setIcon(icon);
590 setWindowIcon(icon);
591 #endif // _1
594 bool MainWindow::parseOK(QString& errmsg)
596 if (_proc->failed())
598 errmsg = _proc->errmsg();
599 return false;
601 const int n = _json.indexOf("{");
602 if (n == -1)
604 errmsg = tr("quvi returned unexpected data");
605 return false;
607 return _media.fromJSON(_json.mid(n), errmsg);
610 static void check_window_flags(QWidget *w)
612 Qt::WindowFlags flags = w->windowFlags();
613 if (settings.value(nn::KeepApplicationWindowOnTop).toBool())
615 if (!(flags & Qt::WindowStaysOnTopHint))
616 flags |= Qt::WindowStaysOnTopHint;
618 else
620 if (flags & Qt::WindowStaysOnTopHint)
621 flags &= ~Qt::WindowStaysOnTopHint;
624 if (flags != w->windowFlags())
626 w->setWindowFlags(flags);
627 if (w->isHidden())
628 w->show();
632 static bool check_language(QWidget *w, const QString& lang)
634 #ifdef ENABLE_VERBOSE
635 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "lang="
636 << lang
637 << settings.value(nn::Language).toString();
638 #endif
640 if (lang != settings.value(nn::Language).toString())
642 if (nn::ask(w, qApp->translate("MainWindow",
643 "Language will be changed after "
644 "you restart the application. "
645 "Restart now?"))
646 == QMessageBox::No)
648 return false;
650 return true;
652 return false;
655 void MainWindow::onSettings()
657 QString lang = settings.value(nn::Language).toString();
659 if (lang.isEmpty()) // NSettingsDialog uses "English" instead of ""
660 lang = "English";
662 nn::NSettingsDialog *d = new nn::NSettingsDialog(this);
663 if (d->exec() == QDialog::Accepted)
665 if (check_language(this, lang))
667 onTerminate();
668 QProcess::startDetached(QCoreApplication::applicationFilePath());
669 return;
671 check_window_flags(this);
675 #define WWW "http://nomnom.sourceforge.net/"
677 void MainWindow::onAbout()
679 nn::NAboutDialog *d = new nn::NAboutDialog(VN, WWW, this);
680 d->exec();
683 #undef WWW
685 void MainWindow::onRecent()
687 nn::NLogDialog *d = new nn::NLogDialog(this);
688 if (d->exec() == QDialog::Accepted)
689 handleURL(d->selected());
692 void MainWindow::onAddress()
694 const QString url =
695 QInputDialog::getText(this, tr("Address"), tr("Media page URL:"));
697 if (url.isEmpty())
698 return;
700 handleURL(url);
703 void MainWindow::onFeed()
705 const QString p = settings.eitherValue(nn::FeedUsing,
706 nn::FeedUsingOther)
707 .toString()
708 .simplified();
710 QStringList args = nn::to_cmd_args(p);
711 #ifdef ENABLE_VERBOSE
712 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "args=" << args;
713 #endif
714 const QString r = args.first();
716 if (r.isEmpty())
718 nn::info(this, tr("Please configure the path to a feed reader. "
719 "See under the \"commands\" in the settings."));
720 onSettings();
721 return;
724 have_umph_feature_all =
725 nn::check_for_cmd_feature(nn::FeedUsing,
726 nn::FeedUsingOther,
727 "-a",
728 0x0);
730 nn::NFeedDialog *d = new nn::NFeedDialog(this, args);
731 if (d->exec() == QDialog::Accepted)
732 handleURL(d->selected());
735 void MainWindow::onProcFinished(QString output)
737 _json = output;
740 void MainWindow::dragEnterEvent(QDragEnterEvent *e)
742 QUrl url(e->mimeData()->text());
743 if (url.isValid() && url.scheme().toLower() == "http")
744 e->acceptProposedAction();
747 void MainWindow::dropEvent(QDropEvent *e)
749 handleURL(e->mimeData()->text().simplified());
750 e->acceptProposedAction();
753 static void update_show_state(QWidget *w)
755 const bool v = !w->isVisible();
757 QAction *a = systray->findTrayMenuAction(
758 qApp->translate("MainWindow", "Show"));
760 if (a)
761 a->setChecked(v);
763 w->setVisible(v);
766 void MainWindow::closeEvent(QCloseEvent *e)
768 #ifdef ENABLE_VERBOSE
769 qDebug() << __PRETTY_FUNCTION__ << __LINE__;
770 #endif
772 if (settings.value(nn::TerminateInstead).toBool()
773 || !systray->isVisible())
775 if (settings.value(nn::ClearURLRecordAtExit).toBool())
776 recent.clear();
777 save();
778 e->accept();
780 else
782 update_show_state(this);
783 e->ignore();
787 void MainWindow::activated(QSystemTrayIcon::ActivationReason r)
789 if (r == QSystemTrayIcon::Trigger)
790 update_show_state(this);
793 void MainWindow::onTerminate()
795 // When systray icon is visible: the default behaviour is to ignore
796 // calls to 'close' mainwindow unless "terminate instead" is true.
797 #ifdef ENABLE_VERBOSE
798 qDebug() << __PRETTY_FUNCTION__ << __LINE__;
799 #endif
800 // Although the line below uses "settings" value "TerminateInstead",
801 // it is not written to config.
802 settings.setValue(nn::TerminateInstead, true);
803 close();
806 static void tweak_window_flags(QWidget *w)
808 Qt::WindowFlags flags = w->windowFlags();
810 // Remove buttons.
812 flags &= ~Qt::WindowMinimizeButtonHint;
813 flags &= ~Qt::WindowMaximizeButtonHint;
815 // Stay on top?
817 if (settings.value(nn::KeepApplicationWindowOnTop).toBool())
818 flags |= Qt::WindowStaysOnTopHint;
820 w->setWindowFlags(flags);
823 void MainWindow::restore()
825 QSettings s;
826 s.beginGroup(QSETTINGS_GROUP);
827 modeCBox->setCurrentIndex(s.value("modeCBox").toInt());
828 restoreGeometry(s.value("geometry").toByteArray());
829 restoreState(s.value("state").toByteArray());
830 s.endGroup();
832 tweak_window_flags(this);
833 createContextMenu();
834 createTrayIcon();
837 void MainWindow::save()
839 QSettings s;
840 s.beginGroup(QSETTINGS_GROUP);
841 s.setValue("modeCBox", modeCBox->currentIndex());
842 s.setValue("geometry", saveGeometry());
843 s.setValue("state", saveState());
844 s.endGroup();
845 recent.write();
848 // vim: set ts=2 sw=2 tw=72 expandtab: