Add vim modelines
[nomnom.git] / src / i / MainWindow.cpp
blob14e0f719c44f712aeb3b8b1f1aa9736891086c35
1 /*
2 * Copyright (C) 2010 Toni Gundogdu.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include <QMainWindow>
19 #include <QCloseEvent>
20 #include <QFileDialog>
21 #include <QRegExp>
22 #include <QDebug>
24 #include "util.h"
25 #include "Log.h"
26 #include "Recent.h"
27 // UI
28 #include "About.h"
29 #include "Preferences.h"
30 #include "LogView.h"
31 #include "Reminder.h"
32 #include "YoutubeFeed.h"
33 #include "MainWindow.h"
35 #define QSETTINGS_GROUP "MainWindow"
36 #define QSETTINGS_REMINDER_SHOWTIPS "showTips"
38 // main.cpp
40 extern QMap<QString,QStringList> hosts;
41 extern SharedPreferences shPrefs;
42 extern Recent recent;
43 extern Log log;
45 // Modes.
47 enum { StreamVideo=0, DownloadVideo };
49 // Ctor.
51 MainWindow::MainWindow () {
53 setupUi (this);
55 readSettings ();
57 // Stay on top?
59 if (shPrefs.get (SharedPreferences::StayOnTop).toBool ())
60 setWindowFlags (windowFlags () | Qt::WindowStaysOnTopHint);
62 // Create Video instance.
64 video = new Video;
66 // Create context menu.
68 createContextMenu ();
70 // Create system tray icon.
72 createTray ();
74 // Create Download dialog.
76 download = new DownloadDialog (this);
78 // Create Process Progress dialog for quvi.
80 proc = new ProcessProgressDialog (this);
82 connect (proc, SIGNAL (finished (QString)),
83 this, SLOT (onProcFinished (QString)));
85 QHash<QString,QRegExp> h;
87 #define _wrap(s,r) \
88 do { h[s] = QRegExp(r); } while (0)
90 _wrap(tr("Checking..."), "^:: Check");
91 _wrap(tr("Fetching..."), "^:: Fetch");
92 _wrap(tr("Verifying..."), "^:: Verify");
94 #undef _wrap
96 proc->setLabelRegExp (h);
98 log << tr ("Program started.") + "\n";
100 // Custom program icon.
102 if (shPrefs.get (SharedPreferences::CustomProgramIcon).toBool ())
103 changeProgramIcon ();
107 void
108 MainWindow::createContextMenu () {
110 #define creat_a(t,f,c) \
111 do { \
112 QAction *a = new QAction (t, textBrowser); \
113 if (c) { \
114 a->setCheckable (c); \
115 /*connect (a, SIGNAL(toggled (bool)), SLOT(f (bool)));*/ \
117 else \
118 connect (a, SIGNAL(triggered ()), SLOT(f ())); \
119 textBrowser->addAction (a); \
120 actions[t] = a; \
121 } while (0)
123 #define add_s \
124 do { \
125 QAction *a = new QAction (textBrowser); \
126 a->setSeparator (true); \
127 textBrowser->addAction (a); \
128 } while (0)
130 creat_a (tr("Address..."), onAddress, false);
131 creat_a (tr("Feed..."), onFeed, false);
132 creat_a (tr("Recent..."), onRecent, false);
133 add_s;
134 creat_a (tr("Overwrite"), _, true);
135 add_s;
136 creat_a (tr("Log..."), onLog, false);
137 creat_a (tr("Preferences..."), onPreferences, false);
138 add_s;
139 creat_a (tr("About..."), onAbout, false);
140 creat_a (tr("Quit"), close, false);
142 #undef add_s
143 #undef creat_a
145 // Add key shortcuts.
147 #define _wrap(s,k) \
148 do { actions[s]->setShortcut (QKeySequence(k)); } while (0)
150 _wrap (tr("Address..."), "Ctrl+A");
151 _wrap (tr("Feed..."), "Ctrl+F");
152 _wrap (tr("Recent..."), "Ctrl+R");
153 // --
154 _wrap (tr("Overwrite"), "Ctrl+W");
155 // --
156 _wrap (tr("Log..."), "Ctrl+L");
157 _wrap (tr("Preferences..."),"Ctrl+E");
158 // --
159 _wrap (tr("Quit"), "Ctrl+Q");
161 #undef _wrap
163 // Add the context menu.
165 textBrowser->setContextMenuPolicy (Qt::ActionsContextMenu);
168 // Create tray icon.
170 void
171 MainWindow::createTray () {
173 trayMenu = new QMenu(this);
175 #define creat_a(t,f) \
176 do { \
177 QAction *a = new QAction(t, this); \
178 connect(a, SIGNAL(triggered()), this, SLOT(f)); \
179 actions[t] = a; \
180 trayMenu->addAction(a); \
181 } while (0)
183 creat_a ( tr("Open"), showNormal() );
184 trayMenu->addSeparator();
185 creat_a ( tr("Quit"), close() );
187 #undef creat_a
189 trayIcon = new QSystemTrayIcon (this);
191 trayIcon->setContextMenu (trayMenu);
192 trayIcon->setIcon (QIcon (":img/nomnom.png"));
194 connect(trayIcon, SIGNAL( activated(QSystemTrayIcon::ActivationReason) ),
195 this, SLOT( onTrayActivated(QSystemTrayIcon::ActivationReason) ) );
198 // Show event.
200 void
201 MainWindow::showEvent (QShowEvent *e) {
202 trayIcon->hide();
203 e->accept();
206 // Hide event.
208 void
209 MainWindow::hideEvent (QHideEvent *e) {
211 if (QSystemTrayIcon::isSystemTrayAvailable()
212 && shPrefs.get(SharedPreferences::MinToTray).toBool())
214 trayIcon->show();
215 if (!isMinimized())
216 hide();
218 e->accept();
221 // Close event.
223 void
224 MainWindow::closeEvent (QCloseEvent *e) {
226 QSettings s;
227 NomNom::save_size (s, this, QSETTINGS_GROUP);
228 NomNom::save_pos (s, this, QSETTINGS_GROUP);
230 s.beginGroup (QSETTINGS_GROUP);
231 s.setValue ("modeCBox", modeCBox->currentIndex ());
232 s.endGroup ();
234 recent.write ();
236 e->accept ();
239 // Read.
241 void
242 MainWindow::readSettings () {
244 QSettings s;
246 NomNom::restore_size(s, this, QSETTINGS_GROUP, QSize(130,150));
247 NomNom::restore_pos(s, this, QSETTINGS_GROUP);
249 s.beginGroup(QSETTINGS_GROUP);
250 modeCBox->setCurrentIndex(s.value("modeCBox").toInt());
251 s.endGroup();
253 Reminder (this, "SharedPreferences").conditionalExec ();
255 // Re-read (now updated) "showReminder" value. This was previously done
256 // in main.cpp, but has to be done here if we expect the Preferences
257 // "show tips" box value to match the one set in the Reminder dialog.
259 shPrefs.read ();
262 // Handle (dropped) URL.
264 void
265 MainWindow::handleURL (const QString& url) {
267 // Check paths.
269 const QString quviPath =
270 shPrefs.get (SharedPreferences::QuviPath).toString ().simplified ();
272 if (quviPath.isEmpty ()) {
273 NomNom::crit (this, tr ("You must specify path to the quvi command."));
274 onPreferences ();
275 return;
278 if (hosts.isEmpty ()) {
279 QString errmsg;
280 if (!NomNom::parse_quvi_support (quviPath, errmsg)) {
281 NomNom::crit (this, errmsg);
282 return;
286 const QString playerPath =
287 shPrefs.get (SharedPreferences::PlayerPath).toString ().simplified ();
289 if (playerPath.isEmpty ()) {
291 NomNom::crit (this,
292 tr ("You must specify path to a stream-capable media "
293 "player command."));
295 onPreferences ();
297 return;
300 // Recent.
302 recent << url;
304 // quvi args.
306 QStringList args =
307 quviPath.split (" ").replaceInStrings ("%u", url);
309 // Choose format.
311 QStringList formats;
313 foreach (QString key, hosts.keys ()) {
314 QRegExp re (key);
315 if (re.indexIn (url) != -1) {
316 formats = hosts.value (key);
317 break;
321 if (formats.size () > 1) { // Assume "default" is always present.
322 bool ok = false;
323 QString s = QInputDialog::getItem (
324 this,
325 tr ("Choose format"),
326 tr ("Format:"),
327 formats,
329 false,
332 if (ok)
333 args << "-f" << s;
334 else
335 return;
338 log << args.join (" ") + "\n";
340 json.clear ();
342 proc->setLabelText (tr ("Checking ..."));
343 proc->setMaximum (0);
344 proc->start (args);
346 if (parseOK ()) {
347 if (modeCBox->currentIndex() == StreamVideo)
348 streamVideo ();
349 else
350 downloadVideo();
354 // View video (stream).
356 void
357 MainWindow::streamVideo () {
359 QStringList args =
360 shPrefs.get (SharedPreferences::PlayerPath)
361 .toString ().simplified ().split (" ");
363 args = args.replaceInStrings ("%u", video->get (Video::Link).toString ());
365 log << args.join(" ") + "\n";
367 const bool ok = QProcess::startDetached (args.takeFirst (), args);
369 if (!ok) {
370 NomNom::crit (this,
371 tr ("Unable to start player command, check the Preferences."));
375 // Download video (to a file).
377 void
378 MainWindow::downloadVideo () {
380 QString fname =
381 shPrefs.get (SharedPreferences::FilenameFormat).toString ();
383 const QString suffix =
384 video->get (Video::Suffix).toString ().simplified ();
386 bool ok = NomNom::format_filename (
387 this,
388 shPrefs.get (SharedPreferences::Regexp).toString (),
389 video->get (Video::Title).toString ().simplified (),
390 suffix,
391 video->get (Video::Host).toString ().simplified (),
392 video->get (Video::ID).toString ().simplified (),
393 fname
396 if (!ok)
397 return;
399 QString fpath =
400 shPrefs.get (SharedPreferences::SaveDir).toString ();
402 if (fpath.isEmpty ())
403 fpath = QDir::currentPath ();
405 fpath = QDir::toNativeSeparators (fpath +"/"+ fname);
407 const bool dont_prompt =
408 shPrefs.get (SharedPreferences::DontPromptFilename).toBool ();
410 if (!dont_prompt) {
412 const QFileDialog::Options opts =
413 QFileDialog::DontConfirmOverwrite;
415 fpath = QFileDialog::getSaveFileName (
416 this,
417 tr ("Save video as"),
418 fpath,
419 suffix, // Filter.
420 0, // Selected filter.
421 opts
424 if (fpath.isEmpty ())
425 return;
428 if (actions[tr("Overwrite")]->isChecked ())
429 QDir ().remove (fpath);
431 const qint64 expected_bytes =
432 video->get (Video::Length).toLongLong ();
434 if (QFileInfo (fpath).size () < expected_bytes) {
436 const QString cmd =
437 shPrefs.get (SharedPreferences::CurlPath).toString ().simplified ();
439 download->start (cmd, fpath, video);
442 const bool completeFile =
443 QFileInfo (fpath).size () >= expected_bytes;
445 const bool playWhenDone =
446 shPrefs.get (SharedPreferences::PlayWhenDone).toBool ();
448 #ifdef _0
449 qDebug ()
450 << QFileInfo (fpath).size ()
451 << expected_bytes
452 << completeFile
453 << playWhenDone;
454 #endif
456 if (completeFile && playWhenDone) {
458 QStringList args =
459 shPrefs.get (SharedPreferences::PlayerPath)
460 .toString ().simplified ().split (" ");
462 args = args.replaceInStrings ("%u", fpath);
464 log << args.join(" ") + "\n";
466 const bool ok = QProcess::startDetached (args.takeFirst (), args);
468 if (!ok) {
469 NomNom::crit (this,
470 tr ("Unable to start player command, check the Preferences."));
477 // Change program icon.
479 void
480 MainWindow::changeProgramIcon () {
482 const bool customProgramIcon =
483 shPrefs.get (SharedPreferences::CustomProgramIcon).toBool ();
485 QString html = textBrowser->toHtml ();
487 const QString iconPath =
488 customProgramIcon
489 ? shPrefs.get (SharedPreferences::ProgramIconPath).toString ()
490 : ":img/nomnom.png";
492 html.replace (QRegExp ("img src=\".*\""), "img src=\"" +iconPath+ "\"");
494 textBrowser->setHtml (html);
496 QIcon icon = QIcon (iconPath);
498 setWindowIcon (icon);
499 trayIcon->setIcon (icon);
502 // Parse JSON data returned by quvi.
504 bool
505 MainWindow::parseOK () {
507 const int n = json.indexOf ("{");
509 if (n == -1)
510 return false;
512 QString error;
514 if (!video->fromJSON (json.mid (n), error)) {
515 NomNom::crit (this, error);
516 return false;
519 return true;
522 // Slot: Icon activated.
524 void
525 MainWindow::onTrayActivated (QSystemTrayIcon::ActivationReason r) {
526 switch (r) {
527 case QSystemTrayIcon::Trigger: showNormal(); break;
528 default: break;
532 // Slot: Preferences.
534 void
535 MainWindow::onPreferences () {
537 const bool icon_state =
538 shPrefs.get (SharedPreferences::CustomProgramIcon).toBool ();
540 const QString icon_path =
541 shPrefs.get (SharedPreferences::ProgramIconPath).toString ();
543 Preferences dlg (this);
545 if (dlg.exec () == QDialog::Accepted) {
547 // User requested app restart.
549 if (dlg.restartAfter ()) {
550 close ();
551 QProcess::startDetached (QCoreApplication::applicationFilePath ());
552 return;
555 Qt::WindowFlags flags = windowFlags ();
557 // Update window flags: Stay on top.
559 if (shPrefs.get (SharedPreferences::StayOnTop).toBool () ) {
560 // Set flag.
561 if (! (flags & Qt::WindowStaysOnTopHint) )
562 flags |= Qt::WindowStaysOnTopHint;
564 else {
565 // Remove flag.
566 if (flags & Qt::WindowStaysOnTopHint)
567 flags &= ~Qt::WindowStaysOnTopHint;
570 if (flags != windowFlags ()) {
571 setWindowFlags (flags);
572 show ();
575 // Update program icon?
577 if (icon_state !=
578 shPrefs.get (SharedPreferences::CustomProgramIcon).toBool ()
579 || icon_path !=
580 shPrefs.get (SharedPreferences::ProgramIconPath).toString ())
582 changeProgramIcon ();
589 // Slot: About.
591 void
592 MainWindow::onAbout ()
593 { About (this).exec (); }
595 // Slot: Recent.
597 void
598 MainWindow::onRecent () {
600 const QStringList lst = recent.toStringList ();
602 if (lst.size () == 0) {
603 NomNom::info (this, tr ("No record of recently visited URLs found."));
604 return;
607 bool ok = false;
609 const QString s = QInputDialog::getItem (
610 this,
611 tr ("Recent URLs"),
612 tr ("Select URL:"),
613 lst,
615 false,
619 if (!ok)
620 return;
622 handleURL (s);
625 // Slot: Log.
627 void
628 MainWindow::onLog ()
629 { LogView (this).exec (); }
631 // Slot: Download.
633 void
634 MainWindow::onAddress() {
636 const QString url =
637 QInputDialog::getText (this, tr ("Address"), tr ("Video URL:"));
639 if (url.isEmpty ())
640 return;
642 handleURL (url);
645 // main.cpp
646 extern QHash<QString,QString> feed;
648 // Slot: on feed.
650 void
651 MainWindow::onFeed () {
653 const QString path =
654 shPrefs.get (SharedPreferences::UmphPath).toString ();
656 if (path.isEmpty ()) {
657 NomNom::crit (this,
658 tr ("Specify path to the umph(1) command in the Preferences."));
659 return;
662 QString url;
664 if (!feed.empty ()) {
666 const int rc = NomNom::ask (this, tr ("Choose from old results?"));
668 if (rc == QMessageBox::Yes) {
669 if (!NomNom::choose_from_feed (this, url))
670 return;
675 if (url.isEmpty ()) {
677 YoutubeFeed d (this);
679 if (d.exec () != QDialog::Accepted || !d.gotItems ())
680 return;
682 if (!NomNom::choose_from_feed (this, url))
683 return;
687 handleURL (url);
690 // Slot: quvi finished.
692 void
693 MainWindow::onProcFinished (QString output )
694 { json = output; }
696 // Event: DragEnter.
698 void
699 MainWindow::dragEnterEvent (QDragEnterEvent *e) {
701 QUrl url(e->mimeData()->text());
703 if (url.isValid() && url.scheme().toLower() == "http")
704 e->acceptProposedAction();
707 // Event: Drop.
709 void
710 MainWindow::dropEvent (QDropEvent *e) {
711 handleURL (e->mimeData ()->text ().simplified ());
712 e->acceptProposedAction ();
716 // vim: set ts=4 sw=4 tw=72 expandtab: