Use "register" in for-int loops.
[abby.git] / src / mainwnd.cpp
blobffea9d2ad2a50e75ea302a57e05810018af33f22
1 /*
2 * abby Copyright (C) 2009 Toni Gundogdu.
3 * This file is part of abby.
5 * abby 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 * abby 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/>.
18 #include <QMainWindow>
19 #include <QSettings>
20 #include <QCloseEvent>
21 #include <QMessageBox>
22 #include <QFileDialog>
23 #include <QIcon>
24 #include <QClipboard>
25 #include <QDebug>
27 #include "mainwnd.h"
28 #include "prefsdlg.h"
29 #include "passdlg.h"
30 #include "aboutdlg.h"
32 MainWindow::MainWindow():
33 cancelled(false)
36 The word "English" is not meant to be translated literally.
37 Replace the word "English" with target language, e.g. Suomi
38 or Deutsch. Note that the only purpose of this string is to
39 list it as an available language in the Preferences dialog.
41 const QString lang = tr("English");
43 setupUi(this);
44 readSettings();
46 setWindowIcon(QIcon(":/rc/abby.png"));
48 connect(&process, SIGNAL(started()),
49 this, SLOT(onProcStarted()));
50 connect(&process, SIGNAL(error(QProcess::ProcessError)),
51 this, SLOT(onProcError(QProcess::ProcessError)));
53 // NOTE: We read both channels stdout and stderr.
54 connect(&process, SIGNAL(readyReadStandardOutput()),
55 this, SLOT(onProcStdoutReady()));
56 connect(&process, SIGNAL(readyReadStandardError()),
57 this, SLOT(onProcStdoutReady()));
59 connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)),
60 this, SLOT(onProcFinished(int,QProcess::ExitStatus)));
62 prefs = new PreferencesDialog(this);
63 updateWidgets();
66 bool
67 MainWindow::isCclive(const QString& path, QString& output) {
68 QProcess process;
69 process.setProcessChannelMode(QProcess::MergedChannels);
70 process.start(path, QStringList() << "--version");
72 bool state = false;
73 if (!process.waitForFinished())
74 qDebug() << path << ": " << process.errorString();
75 else {
76 output = QString::fromLocal8Bit(process.readAll());
77 QStringList lst = output.split(" ", QString::SkipEmptyParts);
78 state = lst[0] == "cclive";
80 return state;
83 bool
84 MainWindow::ccliveSupports(const QString& buildOption) {
85 QString output, path = prefs->ccliveEdit->text();
86 const bool _isCclive = isCclive(path,output);
88 if (!_isCclive && buildOption == "--with-perl")
89 return false; // To keep the titleBox hidden always
91 return output.contains(buildOption);
94 void
95 MainWindow::updateWidgets() {
96 // Enable widgets based on preferences and other settings.
97 QString s;
99 s = prefs->streamEdit->text();
100 streamBox->setEnabled(!s.isEmpty());
101 if (s.isEmpty())
102 streamBox->setCheckState(Qt::Unchecked);
104 s = prefs->commandEdit->text();
105 commandBox->setEnabled(!s.isEmpty());
106 if (s.isEmpty())
107 commandBox->setCheckState(Qt::Unchecked);
109 if (ccliveSupports("--with-perl")) {
110 titleBox->show();
111 } else {
112 titleBox->setCheckState(Qt::Unchecked);
113 titleBox->hide();
117 void
118 MainWindow::closeEvent(QCloseEvent *event) {
119 writeSettings();
120 event->accept();
123 void
124 MainWindow::writeSettings() {
125 QSettings s;
126 s.beginGroup("MainWindow");
127 s.setValue("size",size());
128 s.setValue("pos",pos());
129 s.setValue("titleBox",titleBox->checkState());
130 s.endGroup();
133 void
134 MainWindow::readSettings() {
135 QSettings s;
136 s.beginGroup("MainWindow");
137 resize(s.value("size",QSize(525,265)).toSize());
138 move(s.value("pos",QPoint(200,200)).toPoint());
139 titleBox->setCheckState(
140 s.value("titleBox").toBool()
141 ? Qt::Checked
142 : Qt::Unchecked);
143 s.endGroup();
146 void
147 MainWindow::updateLog(const QString& newText) {
148 QString text = logEdit->toPlainText() + newText;
149 logEdit->setPlainText(text);
152 void
153 MainWindow::updateFormats() {
155 // Alter widgets dynamically based on the video URL.
157 QString url = urlEdit->text();
158 if (url.isEmpty())
159 return;
161 struct lookup_s {
162 const char *host;
163 const char *formats;
165 static const struct lookup_s lookup[] = {
166 {"youtube.com", "fmt17|fmt18|fmt22|fmt35"},
167 // {"video.google.", "mp4"}, // missing in cclive 0.4.3+
168 {"dailymotion.com", "spak-mini|vp6-hq|vp6-hd|vp6|h264"},
171 const int c = sizeof(lookup)/sizeof(struct lookup_s);
173 QStringList formats;
174 formats << "flv" << "best";
176 for (register int i=0; i<c; ++i) {
177 if (url.contains(lookup[i].host)) {
178 QString s = lookup[i].formats;
179 formats << s.split("|");
180 break;
184 QString last = formatCombo->currentText();
186 formatCombo->clear();
187 formatCombo->addItems(formats);
189 int n = formatCombo->findText(last);
190 if (n != -1)
191 formatCombo->setCurrentIndex(n);
195 // Slots
197 void
198 MainWindow::onPreferences() {
199 prefs->exec();
200 updateWidgets();
203 void
204 MainWindow::onSaveasStateChanged(int state) {
205 saveasEdit ->setEnabled(state != 0);
206 saveasButton->setEnabled(state != 0);
208 if (state != 0 && titleBox->isChecked())
209 titleBox->setCheckState(Qt::Unchecked);
211 titleBox->setEnabled(state == 0);
214 void
215 MainWindow::onStreamStateChanged(int state) {
216 streamSpin->setEnabled(state != 0);
219 void
220 MainWindow::onSaveasBrowse() {
221 QString fname = QFileDialog::getSaveFileName(this,tr("Save as"));
222 if (!fname.isEmpty())
223 saveasEdit->setText(fname);
226 void
227 MainWindow::onStart() {
228 // Check video URL
230 QString url = urlEdit->text();
232 if (url.isEmpty()) {
233 statusBar()->showMessage(tr("Enter a video link."));
234 return;
237 url = url.trimmed();
239 if (!url.startsWith("http://",Qt::CaseInsensitive))
240 url.insert(0,"http://");
242 // Check cclive
244 QString path = prefs->ccliveEdit->text();
245 if (path.isEmpty()) {
246 QMessageBox::information(this,QCoreApplication::applicationName(),
247 tr("Path to cclive (or clive) command undefined. "
248 "See preferences."));
249 onPreferences();
250 return;
253 QString output;
254 const bool _isCclive = isCclive(path,output);
256 // Check video save directory
258 // NOTE: cclive itself does not support this concept so we
259 // work around it by changing the working directory to the
260 // save directory.
262 QString savedir = prefs->savedirEdit->text();
263 if (savedir.isEmpty()) {
264 QMessageBox::information(this,QCoreApplication::applicationName(),
265 tr("Save directory undefined. See preferences."));
266 onPreferences();
267 return;
270 // clive can use this same approach even if --savedir option exists.
271 process.setWorkingDirectory(savedir);
273 // Construct cclive/clive args
275 QStringList args;
276 QStringList env;
278 if (_isCclive) {
279 args << "--print-fname";
280 } else {
281 args << "--renew" << "--stderr";
282 // Set environment variables for clive
283 env << "COLUMNS=80" << "LINES=24" // Term::ReadKey
284 << QString("HOME=%1").arg(QDir::homePath()); // $env{HOME}
287 QString s = prefs->additionalEdit->text();
288 if (!s.isEmpty())
289 args << s;
291 s = prefs->streamEdit->text();
292 if (!s.isEmpty() && streamBox->isChecked()) {
293 args << QString("--stream-exec=%1").arg(s);
294 args << QString("--stream=%1").arg(streamSpin->value());
297 s = prefs->commandEdit->text();
298 if (!s.isEmpty() && commandBox->isChecked()) {
299 if (!s.endsWith(";"))
300 s += ";";
301 args << QString("--exec=%1").arg(s);
304 if (prefs->proxyCombo->currentIndex() == 0)
305 args << "--no-proxy";
306 else {
307 s = prefs->proxyEdit->text();
308 if (!s.isEmpty())
309 args << QString("--proxy=%1").arg(s);
312 if (prefs->limitBox->checkState()) {
313 int n = prefs->limitSpin->value();
314 args << QString("--limit-rate=%1").arg(n);
317 if (prefs->timeoutBox->checkState()) {
318 int n = prefs->timeoutSpin->value();
319 if (!prefs->socksBox->checkState())
320 args << QString("--connect-timeout=%1").arg(n);
321 else
322 args << QString("--connect-timeout-socks=%1").arg(n);
325 if (prefs->youtubeGroup->isChecked()) {
326 QString user = prefs->ytuserEdit->text();
327 QString pass = prefs->ytpassEdit->text();
328 if (pass.isEmpty()) {
329 PasswordDialog pwd(this);
330 if (pwd.exec())
331 pass = pwd.passEdit->text();
333 if (!user.isEmpty() && !pass.isEmpty()) {
334 args << QString("--youtube-user=%1").arg(user);
335 args << QString("--youtube-pass=%1").arg(pass);
339 s = saveasEdit->text();
340 if (!s.isEmpty() && saveasBox->isChecked())
341 args << QString("--output-video=%1").arg(s);
343 if (continueBox->isChecked())
344 args << "--continue";
346 if (_isCclive) { // clive defaults to this
347 if (titleBox->isChecked()) {
348 args << "--title";
349 s = prefs->cclassEdit->text();
350 if (!s.isEmpty())
351 args << QString("--title-cclass=%1").arg(s);
353 } else { // this clive can use
354 s = prefs->cclassEdit->text();
355 if (!s.isEmpty())
356 args << QString("--cclass=%1").arg(s);
358 s = formatCombo->currentText();
359 if (s.isEmpty())
360 s = "flv";
361 args << QString("--%1=%2").arg("format").arg(s);
362 args << QString("%1").arg(url);
364 // Prepare log
366 logEdit->clear();
367 updateLog("% " +path+ " " +args.join(" ")+ "\n");
369 // And finally start the process
371 cancelled = false;
372 process.setEnvironment(env);
373 process.setProcessChannelMode(QProcess::MergedChannels);
374 process.start(path,args);
377 void
378 MainWindow::onCancel() {
379 cancelled = true;
380 process.kill();
383 void
384 MainWindow::onAbout() {
385 AboutDialog about(this,prefs->ccliveEdit->text());
386 about.exec();
389 void
390 MainWindow::onURLEditingFinished() {
391 updateFormats();
394 void
395 MainWindow::onURLReturnPressed() {
396 onStart();
399 void
400 MainWindow::onFormatStateChanged(int) {
402 // Disable --continue for the specified hosts if format is "flv".
403 // Make changes based on the video URL.
405 QString url = urlEdit->text();
406 if (url.isEmpty())
407 return;
409 struct lookup_s {
410 const char *host;
412 static const struct lookup_s lookup[] = {
413 {"youtube.com"},
414 {"video.google."},
417 const int c = sizeof(lookup)/sizeof(struct lookup_s);
418 bool enable = true;
420 for (register int i=0; i<c; ++i) {
421 if (url.contains(lookup[i].host)) {
422 if (formatCombo->currentText() == "flv") {
423 enable = false;
424 break;
429 continueBox->setEnabled(enable);
431 if (continueBox->isChecked() && !enable)
432 continueBox->setCheckState(Qt::Unchecked);
435 void
436 MainWindow::onPasteURL() {
437 QClipboard *cb = QApplication::clipboard();
438 urlEdit->setText(cb->text());
439 updateFormats();
442 void
443 MainWindow::onProcStarted() {
444 statusBar() ->clearMessage();
445 fileLabel ->setText("-");
446 sizeLabel ->setText("-- / --");
447 rateLabel ->setText("--.-");
448 etaLabel ->setText("--:--");
449 progressBar ->setValue(0);
451 startButton ->setEnabled(false);
452 cancelButton->setEnabled(true);
454 errorOccurred = false;
457 void
458 MainWindow::onProcError(QProcess::ProcessError err) {
459 if (err == QProcess::FailedToStart) {
460 QString msg = tr("error: failed to start process");
461 statusBar()->showMessage(msg);
462 updateLog(msg);
466 void
467 MainWindow::onProcStdoutReady() {
468 // NOTE: We read both channels stdout and stderr.
469 QString newText =
470 QString::fromLocal8Bit(process.readAll());
472 QStringList tmp = newText.split("\n", QString::SkipEmptyParts);
473 if (tmp.isEmpty())
474 return;
476 QString status, last = tmp.last();
477 //qDebug() << last;
479 if (last.startsWith("fetch"))
480 status = tr("Fetching link...");
481 else if (last.startsWith("verify") || last.startsWith("query length") )
482 status = tr("Verifying video link...");
483 else if (last.startsWith("error:"))
484 errorOccurred = true;
486 if (newText.contains("file:")) {
487 QStringList tmp = newText.split(" ", QString::SkipEmptyParts);
488 fileLabel->setText(tmp[1].remove("\n"));
489 status = tr("Extracting video...");
492 if (!status.isEmpty())
493 statusBar()->showMessage(status.remove("\n"));
495 if (last.contains("%")) {
496 newText.replace("\n", " ");
498 QStringList tmp = newText.split(" ", QString::SkipEmptyParts);
499 QString percent = tmp[1].remove(QChar('%'));
500 QString now = tmp[2];
501 QString expected= tmp[4];
502 QString rate = tmp[5];
503 QString eta = tmp[6];
505 sizeLabel->setText(now +" / "+ expected);
506 progressBar->setValue(percent.toInt());
507 rateLabel->setText(rate);
508 etaLabel->setText(eta);
510 else
511 updateLog(newText);
514 void
515 MainWindow::onProcFinished(int exitCode, QProcess::ExitStatus exitStatus) {
516 QString status;
517 if (!errorOccurred) {
518 if (exitStatus == QProcess::NormalExit) {
519 status = exitCode != 0
520 ? tr("Process exited with an error; see log")
521 : tr("Process exited normally");
522 } else {
523 status = cancelled
524 ? tr("Process terminated")
525 : tr("Process crashed; see log");
527 updateLog(status + ".");
529 else
530 status = tr("error: see log for details");
532 statusBar()->showMessage(status);
534 startButton ->setEnabled(true);
535 cancelButton->setEnabled(false);