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
;
260 if (have_quvi_feature_query_formats
)
264 if (!queryFormats(formats
, q_args
, url
, failed
))
269 nn::NErrorWhileDialog
*d
=
270 new nn::NErrorWhileDialog(q_args
,
278 if (_proc
->canceled())
287 if (!selectFormat(formats
, fmt
))
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
;
299 _proc
->setLabelText(tr("Checking..."));
300 _proc
->setMaximum(0);
301 _proc
->start(q_args
);
303 if (_proc
->canceled())
306 // Check for quvi errors.
311 // Update recent entry. Media URL is set already. Update title
314 e
.setTitle(_media
.get(Media::PageTitle
).toString().simplified());
317 // Download media or pass media stream URL.
319 if (modeCBox
->currentIndex() == StreamMedia
)
326 nn::NErrorWhileDialog
*d
=
327 new nn::NErrorWhileDialog(q_args
,
335 bool MainWindow::queryFormats(QStringList
& formats
,
336 const QStringList
& q_args
,
340 QStringList args
= q_args
;
341 args
.replaceInStrings("%u", url
);
344 #ifdef ENABLE_VERBOSE
345 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "args=" << args
;
350 _proc
->setLabelText(tr("Checking..."));
351 _proc
->setMaximum(0);
354 failed
= _proc
->failed();
358 #ifdef ENABLE_VERBOSE
359 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "failed=" << failed
;
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()
373 << rx
.cap(1).simplified().split("|");
375 #ifdef ENABLE_VERBOSE
376 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "formats=" << formats
;
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.
395 fmt
= QInputDialog::getItem(this,
398 formats
<< tr("Enter your own"),
403 return false; // Cancel
405 if (fmt
== tr("Enter your own"))
407 fmt
= QInputDialog::getText(this,
414 return ok
&& !fmt
.isEmpty();
417 void MainWindow::streamMedia()
419 const QString p
= settings
.eitherValue(nn::PlayUsing
,
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"),
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(),
460 QString fpath
= settings
.value(nn::SaveMediaDirectory
).toString();
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,
479 0, // Selected filter.
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
)
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())
507 if (_download
->failed())
509 nn::NErrorWhileDialog
*d
=
510 new nn::NErrorWhileDialog(args
,
512 _download
->errcode(),
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()
533 if (completeFile
&& playWhenDone
)
535 const QString p
= settings
.eitherValue(nn::PlayUsing
,
540 QStringList args
= nn::to_cmd_args(p
);
541 args
.replaceInStrings("%m", fpath
);
543 #ifdef ENABLE_VERBOSE
544 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "args=" << args
;
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"),
562 void MainWindow::changeProgramIcon()
565 const bool customProgramIcon
=
566 shPrefs
.get(SharedPreferences::CustomProgramIcon
).toBool();
568 QString html
= textBrowser
->toHtml();
570 const QString iconPath
=
572 ? shPrefs
.get(SharedPreferences::ProgramIconPath
).toString()
575 html
.replace(QRegExp("img src=\".*\""), "img src=\"" +iconPath
+ "\"");
577 textBrowser
->setHtml(html
);
579 QIcon icon
= QIcon(iconPath
);
580 trayIcon
->setIcon(icon
);
585 bool MainWindow::parseOK(QString
& errmsg
)
589 errmsg
= _proc
->errmsg();
592 const int n
= _json
.indexOf("{");
595 errmsg
= tr("quvi returned unexpected data");
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
;
611 if (flags
& Qt::WindowStaysOnTopHint
)
612 flags
&= ~Qt::WindowStaysOnTopHint
;
615 if (flags
!= w
->windowFlags())
617 w
->setWindowFlags(flags
);
623 static bool check_language(QWidget
*w
, const QString
& lang
)
625 #ifdef ENABLE_VERBOSE
626 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "lang="
628 << settings
.value(nn::Language
).toString();
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. "
646 void MainWindow::onSettings()
648 QString lang
= settings
.value(nn::Language
).toString();
650 if (lang
.isEmpty()) // NSettingsDialog uses "English" instead of ""
653 nn::NSettingsDialog
*d
= new nn::NSettingsDialog(this);
654 if (d
->exec() == QDialog::Accepted
)
656 if (check_language(this, lang
))
659 QProcess::startDetached(QCoreApplication::applicationFilePath());
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);
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()
686 QInputDialog::getText(this, tr("Address"), tr("Media page URL:"));
694 void MainWindow::onFeed()
696 const QString p
= settings
.eitherValue(nn::FeedUsing
,
701 QStringList args
= nn::to_cmd_args(p
);
702 #ifdef ENABLE_VERBOSE
703 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
<< "args=" << args
;
705 const QString r
= args
.first();
709 nn::info(this, tr("Please configure the path to a feed reader. "
710 "See under the \"commands\" in the settings."));
715 have_umph_feature_all
=
716 nn::check_for_cmd_feature(nn::FeedUsing
,
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
)
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"));
757 void MainWindow::closeEvent(QCloseEvent
*e
)
759 #ifdef ENABLE_VERBOSE
760 qDebug() << __PRETTY_FUNCTION__
<< __LINE__
;
763 if (settings
.value(nn::TerminateInstead
).toBool()
764 || !systray
->isVisible())
766 if (settings
.value(nn::ClearURLRecordAtExit
).toBool())
773 update_show_state(this);
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__
;
791 // Although the line below uses "settings" value "TerminateInstead",
792 // it is not written to config.
793 settings
.setValue(nn::TerminateInstead
, true);
797 static void tweak_window_flags(QWidget
*w
)
799 Qt::WindowFlags flags
= w
->windowFlags();
803 flags
&= ~Qt::WindowMinimizeButtonHint
;
804 flags
&= ~Qt::WindowMaximizeButtonHint
;
808 if (settings
.value(nn::KeepApplicationWindowOnTop
).toBool())
809 flags
|= Qt::WindowStaysOnTopHint
;
811 w
->setWindowFlags(flags
);
814 void MainWindow::restore()
817 s
.beginGroup(QSETTINGS_GROUP
);
818 modeCBox
->setCurrentIndex(s
.value("modeCBox").toInt());
819 restoreGeometry(s
.value("geometry").toByteArray());
820 restoreState(s
.value("state").toByteArray());
823 tweak_window_flags(this);
828 void MainWindow::save()
831 s
.beginGroup(QSETTINGS_GROUP
);
832 s
.setValue("modeCBox", modeCBox
->currentIndex());
833 s
.setValue("geometry", saveGeometry());
834 s
.setValue("state", saveState());
839 // vim: set ts=2 sw=2 tw=72 expandtab: