6ab93c6c841d1432d112d3125deab828516d602e
[qtrapids] / src / gui / MainWindow.cpp
1 /***************************************************************************
2  *   Copyright (C) 2009 by Lassi Väätämöinen   *
3  *   lassi.vaatamoinen@ixonos.com   *
4  *                                                                         *
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 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
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.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20
21
22 #include <QDebug>
23
24 #include <QMenuBar>
25 #include <QToolBar>
26 #include <QAction>
27 #include <QFileDialog>
28 #include <QMessageBox>
29 #include <QApplication>
30 #include <QPluginLoader>
31
32 #include "DownloadView.h"
33 #include "SeedView.h"
34 #include "PreferencesDialog.h"
35
36 #include "MainWindow.h"
37
38 const QString ABOUT_TEXT
39 = QString(QObject::trUtf8("QtRapids, a simple BitTorrent client based on"
40                           "\nQt and Libtorrent."
41                           "\n\nURL: http://qtrapids.garage.maemo.org/"
42                           "\n\nAuthors:\nLassi Väätämöinen, lassi.vaatamoinen@ixonos.com"
43                           "\nDenis Zalevskiy, denis.zalewsky@ixonos.com"
44                           "\n\nIxonos Plc, Finland\n"));
45
46 const QString PLUGINS_DIR = "plugins";
47
48 // Consturctor
49 MainWindow::MainWindow():
50                 QMainWindow(), // Superclass
51                 tabWidget_(NULL),
52                 dlView_(NULL),
53                 seedView_(NULL),
54                 searchWidget_(NULL),
55                 preferencesDialog_(NULL),
56                 settings_(),
57                 pluginDirs_(),
58 //      torrentHandles_(),
59                 btSession_()
60 {
61         // MENUBAR
62         QMenuBar *menuBar = new QMenuBar();
63         QMenu *tempMenu = NULL;
64
65         tempMenu = menuBar->addMenu(tr("&File"));
66         QAction *openAction = tempMenu->addAction(tr("&Open"));
67         QAction *removeAction = tempMenu->addAction(tr("&Remove"));
68         removeAction->setEnabled(false);
69         QAction *quitAction = tempMenu->addAction(tr("&Quit"));
70
71         tempMenu = menuBar->addMenu(tr("&Settings"));
72         QAction *preferencesAction = tempMenu->addAction(tr("&Preferences"));
73
74         tempMenu = menuBar->addMenu(tr("&Help"));
75         QAction *aboutAction = tempMenu->addAction(tr("&About"));
76         QAction *aboutQtAction = tempMenu->addAction(tr("About &Qt"));
77
78         setMenuBar(menuBar);
79         connect(openAction, SIGNAL(triggered()), this, SLOT(on_openAction_clicked()));
80         connect(removeAction, SIGNAL(triggered()), this, SLOT(on_removeAction_clicked()));
81         connect(this, SIGNAL(itemSelected(bool)), removeAction, SLOT(setEnabled(bool)));
82         connect(quitAction, SIGNAL(triggered()), this, SLOT(on_quitAction_clicked()));
83         connect(preferencesAction, SIGNAL(triggered()), this, SLOT(on_preferencesAction_clicked()));
84         connect(aboutAction, SIGNAL(triggered()), this, SLOT(on_aboutAction_clicked()));
85         connect(aboutQtAction, SIGNAL(triggered()), this, SLOT(on_aboutQtAction_clicked()));
86
87         // TABWIDGET (central widget)
88         tabWidget_ = new QTabWidget();
89         tabWidget_->setTabsClosable(true);
90
91         /// @todo Exception handling
92         dlView_ = new DownloadView(this);
93         seedView_ = new SeedView(this);
94         tabWidget_->addTab(dlView_, tr("Downloads"));
95         tabWidget_->addTab(seedView_, tr("Seeds"));
96         connect(dlView_, SIGNAL(itemSelectionChanged()), this,
97                 SLOT(on_downloadItemSelectionChanged()));
98         connect(seedView_, SIGNAL(itemSelectionChanged()), this,
99                 SLOT(on_seedItemSelectionChanged()));
100
101
102         // Tab widget as central widget.
103         setCentralWidget(tabWidget_);
104
105         // TOOLBAR
106         QToolBar *toolBar = new QToolBar();
107         toolBar->addAction(tr("Open"));
108         removeAction = toolBar->addAction(tr("Remove"));
109         removeAction->setEnabled(false);
110         addToolBar(Qt::TopToolBarArea, toolBar);
111
112         connect(this, SIGNAL(itemSelected(bool)), removeAction,
113                 SLOT(setEnabled(bool)));
114         connect(toolBar, SIGNAL(actionTriggered(QAction*)), this,
115                 SLOT(handleToolBarAction(QAction*)));
116         connect (tabWidget_, SIGNAL(tabCloseRequested(int)), this, SLOT(on_tabWidget_tabCloseRequested(int)));
117         
118         connect(&btSession_, SIGNAL(alert(std::auto_ptr<Alert>)),
119                 this, SLOT(on_alert(std::auto_ptr<Alert>)));
120
121         LoadPlugins();
122         RestoreSettings();
123 }
124
125
126 MainWindow::~MainWindow()
127 {
128 }
129
130 // ===================== Implements PluginInterface =========================
131 /// @todo add PluginInterface parameter to request plugin name 
132 bool MainWindow::setGui(QWidget* widget, PluginWidgetType type, qtrapids::PluginInterface* plugin)
133 {
134 #ifdef QTRAPIDS_DEBUG
135         qDebug() << "MainWindow::setGui():" << dlView_->currentItem();
136 #endif
137
138         if (plugin && plugin->identifier() == "SearchPlugin") {
139                 searchWidget_ = widget;
140         } else {
141                 return false;
142         }
143         
144         tabWidget_->addTab(widget, tr("Search"));
145         return true;
146 }
147
148 /// @todo Add PluginInterface parameter to check which plugin gives the widget, to handle appropriately
149 void MainWindow::addPluginWidget(QWidget* widget, PluginWidgetType type)
150 {
151 #ifdef QTRAPIDS_DEBUG
152         qDebug() << "MainWindow::addPluginWidget():" << dlView_->currentItem();
153 #endif
154
155         if (type == qtrapids::PluginHostInterface::TAB_PAGE) {
156                 int index = tabWidget_->addTab(widget, tr("Results"));
157                 tabWidget_->setCurrentIndex(index);
158                 //layout_->addWidget(widget);
159         }
160 }
161 void MainWindow::addToolbar(QWidget* widget, PluginWidgetType type)
162 {
163 }
164
165 void MainWindow::addToolItem(QWidget* widget, PluginWidgetType type)
166 {
167 }
168
169 void MainWindow::addMenu(QWidget* widget, PluginWidgetType type)
170 {
171 }
172
173 void MainWindow::addMenuItem(QWidget* widget, PluginWidgetType type)
174 {
175 }
176
177 bool MainWindow::eventRequest(QVariant param, PluginRequest req)
178 {
179         if (req == qtrapids::PluginHostInterface::OPEN_FILE) {
180                 QString sourceFile = param.toString();
181                 
182                 // Get the source files name from the full path:
183                 QFileInfo fInfo(sourceFile);
184                 QString targetFile = fInfo.fileName();
185                 targetFile = settings_.value("download/directory").toString() + "/" + targetFile;
186                 
187                 // Copy temoporary file to Downloads directory...
188                 if (!QFile::copy(sourceFile, targetFile)) {
189                         qDebug() << "File copying failed";
190                         return false;
191                 } else {
192                         // If copying was successful, remove the original temporary file.
193                         QFile::remove(sourceFile);
194                 }
195                 
196                 /// @todo Torrent bencoding validity should be checked before starting(?)
197                 // ...and start the torrent:
198                 on_torrentFileSelected(targetFile);
199         
200         } else if (req == qtrapids::PluginHostInterface::READ_BUFFER) {
201                 // Create torrent information from char* buffer and start.
202                 StartTorrentFromBufferData(param.toByteArray().constData(), param.toByteArray().size());
203         }
204         
205         return true;
206 }
207
208
209 //=========================== PRIVATE ================================
210
211 void MainWindow::LoadPlugins()
212 {
213         // Get plugin directories from 
214         QStringList pluginDirsTmp = settings_.value("plugins/path").toStringList();
215         QStringList nameFilters("*.so");
216         
217         /// @todo enable "application directory" for plugin search in development/debug mode only. In release version
218         /// search plugins directory under $HOME/.qtrapids or system library paths
219         pluginDirsTmp << qApp->applicationDirPath();
220         pluginDirsTmp.removeDuplicates();
221         
222         foreach (QString dir, pluginDirsTmp) {
223                 pluginDirs_.append(QDir(dir));
224         }
225         
226         foreach (QDir dir, pluginDirs_) {
227                 
228                 if (dir.cd(PLUGINS_DIR)) {
229                         
230                         foreach (QString fileName, dir.entryList(nameFilters, QDir::Files)) {
231                                 QPluginLoader pluginLoader(dir.absoluteFilePath(fileName));
232
233                                 // If plugin not loaded from another directory, then load
234                                 if (!pluginFileNames_.contains(fileName) && QLibrary::isLibrary(fileName)) {
235
236                                         if (pluginLoader.load()) {
237                                                 qDebug() << "Plugin loaded: "  << fileName;
238                                         } else {
239                                                 qWarning() << "Plugin load failed: " << pluginLoader.errorString();
240                                         }
241
242                                         QObject *baseInstance = pluginLoader.instance();
243                                         if (!baseInstance) {
244                                                 qDebug() << "Base instance = NULL.";
245                                         }
246
247                                         qtrapids::PluginInterface *plugin = qobject_cast<qtrapids::PluginInterface*>(baseInstance);
248
249                                         if (!plugin) {
250                                                 qDebug() << "Cast failed.";
251                                         } else {
252                                                 qtrapids::PluginInterface::Info info;
253                                                 info.directory = dir.path();
254                                                 qDebug() << dir.path();
255                                                 plugin->initialize(this, info);
256                                                 pluginFileNames_ += fileName;
257                                         }
258                                 } else {
259                                         qWarning() << "Plugin " 
260                                                 << fileName 
261                                                 << " already loaded from another directory, or not a valid library file";
262                                 }
263                         }
264                         
265                 } else {
266                         qWarning() << PLUGINS_DIR <<  "directory not accessible or does not exist in "  << dir.path();
267                 }
268         }
269 }
270
271
272 void MainWindow::RestoreSettings()
273 {
274         btSession_.setUploadRateLimit(settings_.value("network/uploadRate").toInt());
275         btSession_.setUploadRateLimit(settings_.value("network/downloadRate").toInt());
276 }
277
278
279 // Opens torrent information from buffer data and adds torrent to session 
280 void MainWindow::StartTorrentFromBufferData(char const* data, int size)
281 {
282         // For params, see: http://www.rasterbar.com/products/libtorrent/manual.html#add-torrent
283         /// @todo Should typedef libtorrent::torrent_info to something
284         AddTorrentParams addParams;
285         boost::intrusive_ptr<libtorrent::torrent_info> tiTmp =
286             new libtorrent::torrent_info(data, size);
287         addParams.ti = tiTmp;
288         // save_path is the only mandatory parameter, rest are optional.
289         addParams.save_path = boost::filesystem::path(settings_.value("download/directory").toString().toStdString());
290         //addParams.storage_mode = libtorrent::storage_mode_allocate;
291         qtrapids::QTorrentHandle handle = btSession_.addTorrent(addParams);
292         dlView_->newItem(handle);
293 //      torrentHandles_.push_back(handlePtr);
294 #ifdef QTRAPIDS_DEBUG
295         qDebug() << "Is valid: " << handle.isValid();
296 #endif
297 }
298
299 // =========================== PRIVATE SLOTS =================================
300 void MainWindow::on_openAction_clicked()
301 {
302         QFileDialog *dialog = new QFileDialog( this, "Open torrent file", QString(), tr("Torrent files (*.torrent)"));
303         dialog->setFileMode(QFileDialog::ExistingFile);
304         connect(dialog, SIGNAL(fileSelected(const QString&)), this, SLOT(on_torrentFileSelected(const QString&)));
305         dialog->show();
306 }
307
308 void MainWindow::on_removeAction_clicked()
309 {
310         qtrapids::QTorrentHandle handle = dlView_->removeSelected();
311         btSession_.removeTorrent(handle);
312 }
313
314
315 void MainWindow::on_quitAction_clicked()
316 {
317         close();
318 }
319
320
321 void MainWindow::on_preferencesAction_clicked()
322 {
323         if (!preferencesDialog_) {
324                 preferencesDialog_ = new PreferencesDialog(this, NULL, &btSession_);
325         }
326
327         preferencesDialog_->show();
328         preferencesDialog_->raise();
329         preferencesDialog_->activateWindow();
330 }
331
332
333 void MainWindow::on_aboutAction_clicked()
334 {
335         QMessageBox::about(this, tr("About QtRapids"), ABOUT_TEXT);
336 }
337
338
339 void MainWindow::on_aboutQtAction_clicked()
340 {
341         QMessageBox::aboutQt (this, tr("About Qt"));
342 }
343
344
345 void MainWindow::on_tabWidget_tabCloseRequested(int index)
346 {
347         
348         int searchWidgetIndex = tabWidget_->indexOf(searchWidget_);
349         
350         // Allow closing other tabs than the first two
351         // TODO The first two may well be closable, just add "show tabs" action for these in the menu
352         if (index != 0 && index != 1 && index != searchWidgetIndex) {
353                 QWidget *remove = tabWidget_->widget(index);
354                 tabWidget_->removeTab(index);
355                 delete remove;
356                 remove = NULL;
357         }
358 }
359
360
361 void MainWindow::on_downloadItemSelectionChanged()
362 {
363 #ifdef QTRAPIDS_DEBUG
364         qDebug() << "MainWindow::on_seedItemSelectionChanged():" << dlView_->currentItem();
365 #endif
366         if (dlView_->currentItem() != NULL) {
367                 emit(itemSelected(true));
368         } else {
369                 emit(itemSelected(false));
370         }
371 }
372
373
374 void MainWindow::on_seedItemSelectionChanged()
375 {
376 #ifdef QTRAPIDS_DEBUG
377         qDebug() << "MainWindow::on_seedItemSelectionChanged():" << seedView_->currentItem();
378 #endif
379         if (seedView_->currentItem() != NULL) {
380                 emit(itemSelected(true));
381         } else {
382                 emit(itemSelected(false));
383         }
384 }
385
386
387 void MainWindow::handleToolBarAction(QAction* action)
388 {
389         if (action->text() == "Open") {
390                 on_openAction_clicked();
391         } else if (action->text() == "Remove") {
392                 on_removeAction_clicked();
393         }
394 }
395
396
397 void MainWindow::on_torrentFileSelected(const QString& file)
398 {
399 #ifdef QTRAPIDS_DEBUG
400         qDebug() << " MainWindow::on_torrentFileSelected(): " << file;
401 #endif
402         // Torrent filename empty, do nothing.
403         if (file == "") {
404                 return;
405         }
406
407         // Otherwise add torrent
408         // For params, see: http://www.rasterbar.com/products/libtorrent/manual.html#add-torrent
409         /// @todo Should typedef libtorrent::torrent_info to something
410         AddTorrentParams addParams;
411         boost::intrusive_ptr<libtorrent::torrent_info> tiTmp =
412             new libtorrent::torrent_info(boost::filesystem::path(file.toStdString()));
413         addParams.ti = tiTmp;
414         // save_path is the only mandatory parameter, rest are optional.
415         addParams.save_path = boost::filesystem::path(settings_.value("download/directory").toString().toStdString());
416         //addParams.storage_mode = libtorrent::storage_mode_allocate;
417         qtrapids::QTorrentHandle handle = btSession_.addTorrent(addParams);
418         dlView_->newItem(handle);
419 //      torrentHandles_.push_back(handlePtr);
420 #ifdef QTRAPIDS_DEBUG
421         qDebug() << "Is valid: " << handle.isValid();
422 #endif
423 }
424
425 void MainWindow::on_alert(std::auto_ptr<Alert> al)
426 {
427         if (al.get() != NULL) {
428 //              qDebug()
429 //                              << "MainWindow::on_torrentAlert(): "
430 //                              << QString::fromStdString(al->message());
431
432                 TorrentAlert *torrentAlert
433                 = dynamic_cast<TorrentAlert*> (al.get());
434
435                 if (torrentAlert) {
436                         qtrapids::QTorrentHandle torrentHandle = qtrapids::QTorrentHandle(torrentAlert->handle);
437                         dlView_->updateItem(qtrapids::QTorrentHandle(torrentAlert->handle));
438                 }
439
440         }
441 }
442
443 /*
444 bool MainWindow::IsNewTorrent(std::auto_ptr<qtrapids::QTorrentHandle> handlePtr)
445 {
446         for (unsigned i = 0; i < torrentHandles_.size(); ++i) {
447     if (torrentHandles_.at(i).get() == handlePtr.get()) {
448                         return false;
449                 } else {
450                         return true;
451                 }
452         }
453 }
454 */