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/>.
21 #include <QInputDialog>
22 #include <QMainWindow>
23 #include <QCloseEvent>
24 #include <QFileDialog>
25 #include <QMessageBox>
33 #include <NErrorWhileDialog>
34 #include <NSettingsMutator>
35 #include <NSettingsDialog>
36 #include <NRecentMutator>
37 #include <NRecentEntry>
38 #include <NAboutDialog>
39 #include <NFeedDialog>
46 #include "DownloadDiag.h"
47 #include "ProcProgDiag.h"
48 #include "MainWindow.h"
50 #define QSETTINGS_GROUP "MainWindow"
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()
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
)));
80 recent
.setMaxItems(25);
83 // Custom program icon.
86 if (shPrefs
.get(SharedPreferences::CustomProgramIcon
).toBool())
91 void MainWindow::createContextMenu()
93 #define creat_a(t,f,c) \
95 QAction *a = new QAction(t, textBrowser); \
98 /*connect(a, SIGNAL(toggled(bool)), SLOT(f(bool)));*/ \
101 connect(a, SIGNAL(triggered()), SLOT(f())); \
102 textBrowser->addAction(a); \
108 QAction *a = new QAction(textBrowser); \
109 a->setSeparator(true); \
110 textBrowser->addAction(a); \
113 creat_a(tr("Address..."), onAddress
, false);
114 creat_a(tr("Feed..."), onFeed
, false);
115 creat_a(tr("Recent..."), onRecent
, false);
117 creat_a(tr("Settings..."), onSettings
, false);
119 creat_a(tr("About..."), onAbout
, false);
120 creat_a(tr("Quit"), onTerminate
, false);
125 // Add key shortcuts.
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");
134 _wrap(tr("Settings..."), "Ctrl+E");
136 _wrap(tr("Quit"), "Ctrl+Q");
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)),
153 SLOT(setVisible(bool)),
158 // FIXME: If mainwindow is hidden, closing the NSettingsDialog causes
159 // the program to exit.
160 systray
->addTrayMenuAction(SIGNAL(triggered()),
166 systray
->addTrayMenuSeparator();
168 systray
->addTrayMenuAction(SIGNAL(triggered()),
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
);
182 startInTray
? hide() : show();
187 #ifdef ENABLE_VERBOSE
188 static void print_url(const nn::NRecentEntry
& r
)
190 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< r
.url();
194 void MainWindow::handleURL(const QString
& url
)
196 QString s
= settings
.eitherValue(nn::ParseUsing
,
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
;
210 nn::info(this, tr("Please configure the path to the quvi. "
211 "See under the \"commands\" in the settings."));
216 s
= settings
.eitherValue(nn::PlayUsing
,
221 QStringList p_args
= nn::to_cmd_args(s
);
222 const QString p
= p_args
.first();
226 nn::info(this, tr("Please configure the path to a media player. "
227 "See under the \"commands\" in the settings."));
239 #ifdef ENABLE_VERBOSE
240 recent
.for_each(print_url
);
243 // 0x1=invalid input, 0x3=no input
245 have_quvi_feature_query_formats
=
246 nn::check_for_cmd_feature(nn::ParseUsing
,
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
;
259 const bool get_best
= settings
.value(nn::GetBestFormat
).toBool();
262 if (have_quvi_feature_query_formats
&& !get_best
)
266 if (!queryFormats(formats
, q_args
, url
, failed
))
271 nn::NErrorWhileDialog
*d
=
272 new nn::NErrorWhileDialog(q_args
,
280 if (_proc
->canceled())
289 if (!selectFormat(formats
, fmt
, get_best
))
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
;
301 _proc
->setLabelText(tr("Checking..."));
302 _proc
->setMaximum(0);
303 _proc
->start(q_args
);
305 if (_proc
->canceled())
308 // Check for quvi errors.
313 // Update recent entry. Media URL is set already. Update title
316 e
.setTitle(_media
.get(Media::PageTitle
).toString().simplified());
319 // Download media or pass media stream URL.
321 if (modeCBox
->currentIndex() == StreamMedia
)
328 nn::NErrorWhileDialog
*d
=
329 new nn::NErrorWhileDialog(q_args
,
337 bool MainWindow::queryFormats(QStringList
& formats
,
338 const QStringList
& q_args
,
342 QStringList args
= q_args
;
343 args
.replaceInStrings("%u", url
);
346 #ifdef ENABLE_VERBOSE
347 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "args=" << args
;
352 _proc
->setLabelText(tr("Checking..."));
353 _proc
->setMaximum(0);
356 failed
= _proc
->failed();
360 #ifdef ENABLE_VERBOSE
361 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "failed=" << failed
;
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()
375 << rx
.cap(1).simplified().split("|");
377 #ifdef ENABLE_VERBOSE
378 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "formats=" << formats
;
387 bool MainWindow::selectFormat(QStringList
& formats
,
397 if (formats
.size() < 2) // Skip prompt unless >1 formats available
404 fmt
= QInputDialog::getItem(this,
407 formats
<< tr("Enter your own"),
412 return false; // Cancel
414 if (fmt
== tr("Enter your own"))
416 fmt
= QInputDialog::getText(this,
423 return ok
&& !fmt
.isEmpty();
426 void MainWindow::streamMedia()
428 const QString p
= settings
.eitherValue(nn::PlayUsing
,
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"),
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(),
469 QString fpath
= settings
.value(nn::SaveMediaDirectory
).toString();
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,
488 0, // Selected filter.
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
)
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())
516 if (_download
->failed())
518 nn::NErrorWhileDialog
*d
=
519 new nn::NErrorWhileDialog(args
,
521 _download
->errcode(),
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()
542 if (completeFile
&& playWhenDone
)
544 const QString p
= settings
.eitherValue(nn::PlayUsing
,
549 QStringList args
= nn::to_cmd_args(p
);
550 args
.replaceInStrings("%m", fpath
);
552 #ifdef ENABLE_VERBOSE
553 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "args=" << args
;
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"),
571 void MainWindow::changeProgramIcon()
574 const bool customProgramIcon
=
575 shPrefs
.get(SharedPreferences::CustomProgramIcon
).toBool();
577 QString html
= textBrowser
->toHtml();
579 const QString iconPath
=
581 ? shPrefs
.get(SharedPreferences::ProgramIconPath
).toString()
584 html
.replace(QRegExp("img src=\".*\""), "img src=\"" +iconPath
+ "\"");
586 textBrowser
->setHtml(html
);
588 QIcon icon
= QIcon(iconPath
);
589 trayIcon
->setIcon(icon
);
594 bool MainWindow::parseOK(QString
& errmsg
)
598 errmsg
= _proc
->errmsg();
601 const int n
= _json
.indexOf("{");
604 errmsg
= tr("quvi returned unexpected data");
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
;
620 if (flags
& Qt::WindowStaysOnTopHint
)
621 flags
&= ~Qt::WindowStaysOnTopHint
;
624 if (flags
!= w
->windowFlags())
626 w
->setWindowFlags(flags
);
632 static bool check_language(QWidget
*w
, const QString
& lang
)
634 #ifdef ENABLE_VERBOSE
635 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "lang="
637 << settings
.value(nn::Language
).toString();
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. "
655 void MainWindow::onSettings()
657 QString lang
= settings
.value(nn::Language
).toString();
659 if (lang
.isEmpty()) // NSettingsDialog uses "English" instead of ""
662 nn::NSettingsDialog
*d
= new nn::NSettingsDialog(this);
663 if (d
->exec() == QDialog::Accepted
)
665 if (check_language(this, lang
))
668 QProcess::startDetached(QCoreApplication::applicationFilePath());
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);
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()
695 QInputDialog::getText(this, tr("Address"), tr("Media page URL:"));
703 void MainWindow::onFeed()
705 const QString p
= settings
.eitherValue(nn::FeedUsing
,
710 QStringList args
= nn::to_cmd_args(p
);
711 #ifdef ENABLE_VERBOSE
712 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "args=" << args
;
714 const QString r
= args
.first();
718 nn::info(this, tr("Please configure the path to a feed reader. "
719 "See under the \"commands\" in the settings."));
724 have_umph_feature_all
=
725 nn::check_for_cmd_feature(nn::FeedUsing
,
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
)
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"));
766 void MainWindow::closeEvent(QCloseEvent
*e
)
768 #ifdef ENABLE_VERBOSE
769 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
;
772 if (settings
.value(nn::TerminateInstead
).toBool()
773 || !systray
->isVisible())
775 if (settings
.value(nn::ClearURLRecordAtExit
).toBool())
782 update_show_state(this);
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__
;
800 // Although the line below uses "settings" value "TerminateInstead",
801 // it is not written to config.
802 settings
.setValue(nn::TerminateInstead
, true);
806 static void tweak_window_flags(QWidget
*w
)
808 Qt::WindowFlags flags
= w
->windowFlags();
812 flags
&= ~Qt::WindowMinimizeButtonHint
;
813 flags
&= ~Qt::WindowMaximizeButtonHint
;
817 if (settings
.value(nn::KeepApplicationWindowOnTop
).toBool())
818 flags
|= Qt::WindowStaysOnTopHint
;
820 w
->setWindowFlags(flags
);
823 void MainWindow::restore()
826 s
.beginGroup(QSETTINGS_GROUP
);
827 modeCBox
->setCurrentIndex(s
.value("modeCBox").toInt());
828 restoreGeometry(s
.value("geometry").toByteArray());
829 restoreState(s
.value("state").toByteArray());
832 tweak_window_flags(this);
837 void MainWindow::save()
840 s
.beginGroup(QSETTINGS_GROUP
);
841 s
.setValue("modeCBox", modeCBox
->currentIndex());
842 s
.setValue("geometry", saveGeometry());
843 s
.setValue("state", saveState());
848 // vim: set ts=2 sw=2 tw=72 expandtab: