Fix umph integration
[nomnom.git] / src / i / MainWindow.cpp
blob5ebd2a14a9d6cd7710586924aa4965467a126c55
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 QStringList formats;
260 if (have_quvi_feature_query_formats)
262 bool failed = false;
264 if (!queryFormats(formats, q_args, url, failed))
265 formats.clear();
267 if (failed)
269 nn::NErrorWhileDialog *d =
270 new nn::NErrorWhileDialog(q_args,
271 _proc->errmsg(),
272 _proc->errcode(),
273 this);
274 d->exec();
275 return;
278 if (_proc->canceled())
279 return;
282 _json.clear();
284 // Choose format.
286 QString fmt;
287 if (!selectFormat(formats, fmt))
288 return;
290 // Query media stream data.
292 q_args.replaceInStrings("%u", url);
293 q_args << "-f" << fmt;
295 #ifdef ENABLE_VERBOSE
296 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "q_args=" << q_args;
297 #endif
299 _proc->setLabelText(tr("Checking..."));
300 _proc->setMaximum(0);
301 _proc->start(q_args);
303 if (_proc->canceled())
304 return;
306 // Check for quvi errors.
308 QString errmsg;
309 if (parseOK(errmsg))
311 // Update recent entry. Media URL is set already. Update title
312 // only.
314 e.setTitle(_media.get(Media::PageTitle).toString().simplified());
315 recent.update(e);
317 // Download media or pass media stream URL.
319 if (modeCBox->currentIndex() == StreamMedia)
320 streamMedia();
321 else
322 downloadMedia();
324 else
326 nn::NErrorWhileDialog *d =
327 new nn::NErrorWhileDialog(q_args,
328 errmsg,
329 _proc->errcode(),
330 this);
331 d->exec();
335 bool MainWindow::queryFormats(QStringList& formats,
336 const QStringList& q_args,
337 const QString& url,
338 bool& failed)
340 QStringList args = q_args;
341 args.replaceInStrings("%u", url);
342 args << "-F";
344 #ifdef ENABLE_VERBOSE
345 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "args=" << args;
346 #endif
348 _json.clear();
350 _proc->setLabelText(tr("Checking..."));
351 _proc->setMaximum(0);
352 _proc->start(args);
354 failed = _proc->failed();
355 if (failed)
356 return false;
358 #ifdef ENABLE_VERBOSE
359 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "failed=" << failed;
360 #endif
362 QStringList lns = _json.split("\n");
363 lns.removeDuplicates();
365 const QRegExp rx("^(.*)\\s+:\\s+");
366 foreach (const QString s, lns)
368 if (rx.indexIn(s) != -1)
370 formats = QStringList()
371 << "default"
372 << "best"
373 << rx.cap(1).simplified().split("|");
375 #ifdef ENABLE_VERBOSE
376 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "formats=" << formats;
377 #endif
379 return true;
382 return false;
385 bool MainWindow::selectFormat(QStringList& formats, QString& fmt)
387 // Prompt only if count exceeds 1 ("default)".
388 if (formats.size() < 2)
390 fmt = "default"; // Do not translate. quvi understands only this.
391 return true;
394 bool ok = false;
395 fmt = QInputDialog::getItem(this,
396 tr("Choose format"),
397 tr("Format:"),
398 formats << tr("Enter your own"),
400 false,
401 &ok);
402 if (!ok)
403 return false; // Cancel
405 if (fmt == tr("Enter your own"))
407 fmt = QInputDialog::getText(this,
408 tr("Enter format"),
409 tr("Format:"),
410 QLineEdit::Normal,
411 "default",
412 &ok);
414 return ok && !fmt.isEmpty();
417 void MainWindow::streamMedia()
419 const QString p = settings.eitherValue(nn::PlayUsing,
420 nn::PlayUsingOther)
421 .toString()
422 .simplified();
424 QStringList args = nn::to_cmd_args(p);
425 args.replaceInStrings("%m", _media.get(Media::StreamURL).toString());
427 const QString cmd = args.takeFirst();
429 if (!QProcess::startDetached(cmd, args))
431 nn::NErrorWhileDialog *d =
432 new nn::NErrorWhileDialog(QStringList() << cmd << args,
433 tr("Unknown error while attempting to "
434 "start a detached process"),
436 this);
437 d->exec();
441 void MainWindow::downloadMedia()
443 QString fname = settings.value(nn::FilenameFormat).toString();
445 const QString suffix =
446 _media.get(Media::FileSuffix).toString().simplified();
448 bool ok = nn::format_filename(
449 settings.value(nn::FilenameRegExp).toString(),
450 _media.get(Media::PageTitle).toString().simplified(),
451 _media.get(Media::MediaID).toString().simplified(),
452 _media.get(Media::Host).toString().simplified(),
453 suffix,
454 fname
457 if (!ok)
458 return;
460 QString fpath = settings.value(nn::SaveMediaDirectory).toString();
462 if (fpath.isEmpty())
463 fpath = QDir::homePath();
465 fpath = QDir::toNativeSeparators(fpath +"/"+ fname);
467 const bool ask_where_to_save =
468 settings.value(nn::AskWhereToSaveMediaFile).toBool();
470 if (ask_where_to_save)
472 const QFileDialog::Options opts =
473 QFileDialog::DontConfirmOverwrite;
475 fpath = QFileDialog::getSaveFileName(this,
476 tr("Save media as"),
477 fpath,
478 suffix, // Filter.
479 0, // Selected filter.
480 opts);
481 if (fpath.isEmpty())
482 return;
485 if (settings.value(nn::ReplaceExistingMedia).toBool())
486 QDir().remove(fpath);
488 const qint64 expected_bytes =
489 _media.get(Media::LengthBytes).toLongLong();
491 if (QFileInfo(fpath).size() < expected_bytes)
493 const QString p = settings.eitherValue(nn::DownloadUsing,
494 nn::DownloadUsingOther)
495 .toString()
496 .simplified();
498 QStringList args = nn::to_cmd_args(p);
499 args.replaceInStrings("%u", _media.get(Media::StreamURL).toString());
500 args.replaceInStrings("%f", fpath);
502 _download->start(args);
504 if (_download->canceled())
505 return;
507 if (_download->failed())
509 nn::NErrorWhileDialog *d =
510 new nn::NErrorWhileDialog(args,
511 _download->errmsg(),
512 _download->errcode(),
513 this);
514 d->exec();
515 return;
519 const bool completeFile =
520 QFileInfo(fpath).size() >= expected_bytes;
522 const bool playWhenDone =
523 settings.value(nn::PlayWhenDoneDownloading).toBool();
525 #ifdef ENABLE_VERBOSE
526 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "file="
527 << QFileInfo(fpath).size()
528 << expected_bytes
529 << completeFile
530 << playWhenDone;
531 #endif
533 if (completeFile && playWhenDone)
535 const QString p = settings.eitherValue(nn::PlayUsing,
536 nn::PlayUsingOther)
537 .toString()
538 .simplified();
540 QStringList args = nn::to_cmd_args(p);
541 args.replaceInStrings("%m", fpath);
543 #ifdef ENABLE_VERBOSE
544 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "args=" << args;
545 #endif
547 const QString cmd = args.takeFirst();
549 if (!QProcess::startDetached(cmd, args))
551 nn::NErrorWhileDialog *d =
552 new nn::NErrorWhileDialog(QStringList() << cmd << args,
553 tr("Unknown error while attempting to "
554 "start a detached process"),
556 this);
557 d->exec();
562 void MainWindow::changeProgramIcon()
564 #ifdef _1
565 const bool customProgramIcon =
566 shPrefs.get(SharedPreferences::CustomProgramIcon).toBool();
568 QString html = textBrowser->toHtml();
570 const QString iconPath =
571 customProgramIcon
572 ? shPrefs.get(SharedPreferences::ProgramIconPath).toString()
573 : ":img/nomnom.png";
575 html.replace(QRegExp("img src=\".*\""), "img src=\"" +iconPath+ "\"");
577 textBrowser->setHtml(html);
579 QIcon icon = QIcon(iconPath);
580 trayIcon->setIcon(icon);
581 setWindowIcon(icon);
582 #endif // _1
585 bool MainWindow::parseOK(QString& errmsg)
587 if (_proc->failed())
589 errmsg = _proc->errmsg();
590 return false;
592 const int n = _json.indexOf("{");
593 if (n == -1)
595 errmsg = tr("quvi returned unexpected data");
596 return false;
598 return _media.fromJSON(_json.mid(n), errmsg);
601 static void check_window_flags(QWidget *w)
603 Qt::WindowFlags flags = w->windowFlags();
604 if (settings.value(nn::KeepApplicationWindowOnTop).toBool())
606 if (!(flags & Qt::WindowStaysOnTopHint))
607 flags |= Qt::WindowStaysOnTopHint;
609 else
611 if (flags & Qt::WindowStaysOnTopHint)
612 flags &= ~Qt::WindowStaysOnTopHint;
615 if (flags != w->windowFlags())
617 w->setWindowFlags(flags);
618 if (w->isHidden())
619 w->show();
623 static bool check_language(QWidget *w, const QString& lang)
625 #ifdef ENABLE_VERBOSE
626 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "lang="
627 << lang
628 << settings.value(nn::Language).toString();
629 #endif
631 if (lang != settings.value(nn::Language).toString())
633 if (nn::ask(w, qApp->translate("MainWindow",
634 "Language will be changed after "
635 "you restart the application. "
636 "Restart now?"))
637 == QMessageBox::No)
639 return false;
641 return true;
643 return false;
646 void MainWindow::onSettings()
648 QString lang = settings.value(nn::Language).toString();
650 if (lang.isEmpty()) // NSettingsDialog uses "English" instead of ""
651 lang = "English";
653 nn::NSettingsDialog *d = new nn::NSettingsDialog(this);
654 if (d->exec() == QDialog::Accepted)
656 if (check_language(this, lang))
658 onTerminate();
659 QProcess::startDetached(QCoreApplication::applicationFilePath());
660 return;
662 check_window_flags(this);
666 #define WWW "http://nomnom.sourceforge.net/"
668 void MainWindow::onAbout()
670 nn::NAboutDialog *d = new nn::NAboutDialog(VN, WWW, this);
671 d->exec();
674 #undef WWW
676 void MainWindow::onRecent()
678 nn::NLogDialog *d = new nn::NLogDialog(this);
679 if (d->exec() == QDialog::Accepted)
680 handleURL(d->selected());
683 void MainWindow::onAddress()
685 const QString url =
686 QInputDialog::getText(this, tr("Address"), tr("Media page URL:"));
688 if (url.isEmpty())
689 return;
691 handleURL(url);
694 void MainWindow::onFeed()
696 const QString p = settings.eitherValue(nn::FeedUsing,
697 nn::FeedUsingOther)
698 .toString()
699 .simplified();
701 QStringList args = nn::to_cmd_args(p);
702 #ifdef ENABLE_VERBOSE
703 qDebug() << __PRETTY_FUNCTION__ << __LINE__ << "args=" << args;
704 #endif
705 const QString r = args.first();
707 if (r.isEmpty())
709 nn::info(this, tr("Please configure the path to a feed reader. "
710 "See under the \"commands\" in the settings."));
711 onSettings();
712 return;
715 have_umph_feature_all =
716 nn::check_for_cmd_feature(nn::FeedUsing,
717 nn::FeedUsingOther,
718 "-a",
719 0x0);
721 nn::NFeedDialog *d = new nn::NFeedDialog(this, args);
722 if (d->exec() == QDialog::Accepted)
723 handleURL(d->selected());
726 void MainWindow::onProcFinished(QString output)
728 _json = output;
731 void MainWindow::dragEnterEvent(QDragEnterEvent *e)
733 QUrl url(e->mimeData()->text());
734 if (url.isValid() && url.scheme().toLower() == "http")
735 e->acceptProposedAction();
738 void MainWindow::dropEvent(QDropEvent *e)
740 handleURL(e->mimeData()->text().simplified());
741 e->acceptProposedAction();
744 static void update_show_state(QWidget *w)
746 const bool v = !w->isVisible();
748 QAction *a = systray->findTrayMenuAction(
749 qApp->translate("MainWindow", "Show"));
751 if (a)
752 a->setChecked(v);
754 w->setVisible(v);
757 void MainWindow::closeEvent(QCloseEvent *e)
759 #ifdef ENABLE_VERBOSE
760 qDebug() << __PRETTY_FUNCTION__ << __LINE__;
761 #endif
763 if (settings.value(nn::TerminateInstead).toBool()
764 || !systray->isVisible())
766 if (settings.value(nn::ClearURLRecordAtExit).toBool())
767 recent.clear();
768 save();
769 e->accept();
771 else
773 update_show_state(this);
774 e->ignore();
778 void MainWindow::activated(QSystemTrayIcon::ActivationReason r)
780 if (r == QSystemTrayIcon::Trigger)
781 update_show_state(this);
784 void MainWindow::onTerminate()
786 // When systray icon is visible: the default behaviour is to ignore
787 // calls to 'close' mainwindow unless "terminate instead" is true.
788 #ifdef ENABLE_VERBOSE
789 qDebug() << __PRETTY_FUNCTION__ << __LINE__;
790 #endif
791 // Although the line below uses "settings" value "TerminateInstead",
792 // it is not written to config.
793 settings.setValue(nn::TerminateInstead, true);
794 close();
797 static void tweak_window_flags(QWidget *w)
799 Qt::WindowFlags flags = w->windowFlags();
801 // Remove buttons.
803 flags &= ~Qt::WindowMinimizeButtonHint;
804 flags &= ~Qt::WindowMaximizeButtonHint;
806 // Stay on top?
808 if (settings.value(nn::KeepApplicationWindowOnTop).toBool())
809 flags |= Qt::WindowStaysOnTopHint;
811 w->setWindowFlags(flags);
814 void MainWindow::restore()
816 QSettings s;
817 s.beginGroup(QSETTINGS_GROUP);
818 modeCBox->setCurrentIndex(s.value("modeCBox").toInt());
819 restoreGeometry(s.value("geometry").toByteArray());
820 restoreState(s.value("state").toByteArray());
821 s.endGroup();
823 tweak_window_flags(this);
824 createContextMenu();
825 createTrayIcon();
828 void MainWindow::save()
830 QSettings s;
831 s.beginGroup(QSETTINGS_GROUP);
832 s.setValue("modeCBox", modeCBox->currentIndex());
833 s.setValue("geometry", saveGeometry());
834 s.setValue("state", saveState());
835 s.endGroup();
836 recent.write();
839 // vim: set ts=2 sw=2 tw=72 expandtab: