273aadc7669c9c97d9d6e19107a1a9abb7d189da
[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 }
123
124
125 MainWindow::~MainWindow()
126 {
127 }
128
129 // ===================== Implements PluginInterface =========================
130 /// @todo add PluginInterface parameter to request plugin name 
131 bool MainWindow::setGui(QWidget* widget, PluginWidgetType type, qtrapids::PluginInterface* plugin)
132 {
133 #ifdef QTRAPIDS_DEBUG
134         qDebug() << "MainWindow::setGui():" << dlView_->currentItem();
135 #endif
136
137         if (plugin && plugin->identifier() == "SearchPlugin") {
138                 searchWidget_ = widget;
139         } else {
140                 return false;
141         }
142         
143         tabWidget_->addTab(widget, tr("Search"));
144         return true;
145 }
146
147 /// @todo Add PluginInterface parameter to check which plugin gives the widget, to handle appropriately
148 void MainWindow::addPluginWidget(QWidget* widget, PluginWidgetType type)
149 {
150 #ifdef QTRAPIDS_DEBUG
151         qDebug() << "MainWindow::addPluginWidget():" << dlView_->currentItem();
152 #endif
153
154         if (type == qtrapids::PluginHostInterface::TAB_PAGE) {
155                 int index = tabWidget_->addTab(widget, tr("Results"));
156                 tabWidget_->setCurrentIndex(index);
157                 //layout_->addWidget(widget);
158         }
159 }
160 void MainWindow::addToolbar(QWidget* widget, PluginWidgetType type)
161 {
162 }
163
164 void MainWindow::addToolItem(QWidget* widget, PluginWidgetType type)
165 {
166 }
167
168 void MainWindow::addMenu(QWidget* widget, PluginWidgetType type)
169 {
170 }
171
172 void MainWindow::addMenuItem(QWidget* widget, PluginWidgetType type)
173 {
174 }
175
176 bool MainWindow::eventRequest(QVariant param, PluginRequest req)
177 {
178         if (req == qtrapids::PluginHostInterface::OPEN_FILE) {
179                 QString sourceFile = param.toString();
180                 
181                 // Get the source files name from the full path:
182                 QFileInfo fInfo(sourceFile);
183                 QString targetFile = fInfo.fileName();
184                 targetFile = settings_.value("download/directory").toString() + "/" + targetFile;
185                 
186                 // Copy temoporary file to Downloads directory...
187                 if (!QFile::copy(sourceFile, targetFile)) {
188                         qDebug() << "File copying failed";
189                         return false;
190                 } else {
191                         // If copying was successful, remove the original temporary file.
192                         QFile::remove(sourceFile);
193                 }
194                 
195                 /// @todo Torrent bencoding validity should be checked before starting(?)
196                 // ...and start the torrent:
197                 on_torrentFileSelected(targetFile);
198         
199         } else if (req == qtrapids::PluginHostInterface::READ_BUFFER) {
200                 // Create torrent information from char* buffer and start.
201                 StartTorrentFromBufferData(param.toByteArray().constData(), param.toByteArray().size());
202         }
203         
204         return true;
205 }
206
207
208 //=========================== PRIVATE ================================
209
210 void MainWindow::LoadPlugins()
211 {
212         // Get plugin directories from 
213         QStringList pluginDirsTmp = settings_.value("plugins/path").toStringList();
214         QStringList nameFilters("*.so");
215         
216         /// @todo enable "application directory" for plugin search in development/debug mode only. In release version
217         /// search plugins directory under $HOME/.qtrapids or system library paths
218         pluginDirsTmp << qApp->applicationDirPath();
219         pluginDirsTmp.removeDuplicates();
220         
221         foreach (QString dir, pluginDirsTmp) {
222                 pluginDirs_.append(QDir(dir));
223         }
224         
225         foreach (QDir dir, pluginDirs_) {
226                 
227                 if (dir.cd(PLUGINS_DIR)) {
228                         
229                         foreach (QString fileName, dir.entryList(nameFilters, QDir::Files)) {
230                                 QPluginLoader pluginLoader(dir.absoluteFilePath(fileName));
231
232                                 // If plugin not loaded from another directory, then load
233                                 if (!pluginFileNames_.contains(fileName) && QLibrary::isLibrary(fileName)) {
234
235                                         if (pluginLoader.load()) {
236                                                 qDebug() << "Plugin loaded: "  << fileName;
237                                         } else {
238                                                 qWarning() << "Plugin load failed: " << pluginLoader.errorString();
239                                         }
240
241                                         QObject *baseInstance = pluginLoader.instance();
242                                         if (!baseInstance) {
243                                                 qDebug() << "Base instance = NULL.";
244                                         }
245
246                                         qtrapids::PluginInterface *plugin = qobject_cast<qtrapids::PluginInterface*>(baseInstance);
247
248                                         if (!plugin) {
249                                                 qDebug() << "Cast failed.";
250                                         } else {
251                                                 qtrapids::PluginInterface::Info info;
252                                                 info.directory = dir.path();
253                                                 qDebug() << dir.path();
254                                                 plugin->initialize(this, info);
255                                                 pluginFileNames_ += fileName;
256                                         }
257                                 } else {
258                                         qWarning() << "Plugin " 
259                                                 << fileName 
260                                                 << " already loaded from another directory, or not a valid library file";
261                                 }
262                         }
263                         
264                 } else {
265                         qWarning() << PLUGINS_DIR <<  "directory not accessible or does not exist in "  << dir.path();
266                 }
267         }
268 }
269
270
271
272 // Opens torrent information from buffer data and adds torrent to session 
273 void MainWindow::StartTorrentFromBufferData(char const* data, int size)
274 {
275         // For params, see: http://www.rasterbar.com/products/libtorrent/manual.html#add-torrent
276         /// @todo Should typedef libtorrent::torrent_info to something
277         AddTorrentParams addParams;
278         boost::intrusive_ptr<libtorrent::torrent_info> tiTmp =
279             new libtorrent::torrent_info(data, size);
280         addParams.ti = tiTmp;
281         // save_path is the only mandatory parameter, rest are optional.
282         addParams.save_path = boost::filesystem::path(settings_.value("download/directory").toString().toStdString());
283         //addParams.storage_mode = libtorrent::storage_mode_allocate;
284         qtrapids::QTorrentHandle handle = btSession_.addTorrent(addParams);
285         dlView_->newItem(handle);
286 //      torrentHandles_.push_back(handlePtr);
287 #ifdef QTRAPIDS_DEBUG
288         qDebug() << "Is valid: " << handle.isValid();
289 #endif
290 }
291
292 // =========================== PRIVATE SLOTS =================================
293 void MainWindow::on_openAction_clicked()
294 {
295         QFileDialog *dialog = new QFileDialog( this, "Open torrent file", QString(), tr("Torrent files (*.torrent)"));
296         dialog->setFileMode(QFileDialog::ExistingFile);
297         connect(dialog, SIGNAL(fileSelected(const QString&)), this, SLOT(on_torrentFileSelected(const QString&)));
298         dialog->show();
299 }
300
301 void MainWindow::on_removeAction_clicked()
302 {
303         qtrapids::QTorrentHandle handle = dlView_->removeSelected();
304         btSession_.removeTorrent(handle);
305 }
306
307
308 void MainWindow::on_quitAction_clicked()
309 {
310         close();
311 }
312
313
314 void MainWindow::on_preferencesAction_clicked()
315 {
316         if (!preferencesDialog_) {
317                 preferencesDialog_ = new PreferencesDialog(this);
318         }
319         preferencesDialog_->show();
320         preferencesDialog_->raise();
321         preferencesDialog_->activateWindow();
322 }
323
324
325 void MainWindow::on_aboutAction_clicked()
326 {
327         QMessageBox::about(this, tr("About QtRapids"), ABOUT_TEXT);
328 }
329
330
331 void MainWindow::on_aboutQtAction_clicked()
332 {
333         QMessageBox::aboutQt (this, tr("About Qt"));
334 }
335
336
337 void MainWindow::on_tabWidget_tabCloseRequested(int index)
338 {
339         
340         int searchWidgetIndex = tabWidget_->indexOf(searchWidget_);
341         
342         // Allow closing other tabs than the first two
343         // TODO The first two may well be closable, just add "show tabs" action for these in the menu
344         if (index != 0 && index != 1 && index != searchWidgetIndex) {
345                 QWidget *remove = tabWidget_->widget(index);
346                 tabWidget_->removeTab(index);
347                 delete remove;
348                 remove = NULL;
349         }
350 }
351
352
353 void MainWindow::on_downloadItemSelectionChanged()
354 {
355 #ifdef QTRAPIDS_DEBUG
356         qDebug() << "MainWindow::on_seedItemSelectionChanged():" << dlView_->currentItem();
357 #endif
358         if (dlView_->currentItem() != NULL) {
359                 emit(itemSelected(true));
360         } else {
361                 emit(itemSelected(false));
362         }
363 }
364
365
366 void MainWindow::on_seedItemSelectionChanged()
367 {
368 #ifdef QTRAPIDS_DEBUG
369         qDebug() << "MainWindow::on_seedItemSelectionChanged():" << seedView_->currentItem();
370 #endif
371         if (seedView_->currentItem() != NULL) {
372                 emit(itemSelected(true));
373         } else {
374                 emit(itemSelected(false));
375         }
376 }
377
378
379 void MainWindow::handleToolBarAction(QAction* action)
380 {
381         if (action->text() == "Open") {
382                 on_openAction_clicked();
383         } else if (action->text() == "Remove") {
384                 on_removeAction_clicked();
385         }
386 }
387
388
389 void MainWindow::on_torrentFileSelected(const QString& file)
390 {
391 #ifdef QTRAPIDS_DEBUG
392         qDebug() << " MainWindow::on_torrentFileSelected(): " << file;
393 #endif
394         // Torrent filename empty, do nothing.
395         if (file == "") {
396                 return;
397         }
398
399         // Otherwise add torrent
400         // For params, see: http://www.rasterbar.com/products/libtorrent/manual.html#add-torrent
401         /// @todo Should typedef libtorrent::torrent_info to something
402         AddTorrentParams addParams;
403         boost::intrusive_ptr<libtorrent::torrent_info> tiTmp =
404             new libtorrent::torrent_info(boost::filesystem::path(file.toStdString()));
405         addParams.ti = tiTmp;
406         // save_path is the only mandatory parameter, rest are optional.
407         addParams.save_path = boost::filesystem::path(settings_.value("download/directory").toString().toStdString());
408         //addParams.storage_mode = libtorrent::storage_mode_allocate;
409         qtrapids::QTorrentHandle handle = btSession_.addTorrent(addParams);
410         dlView_->newItem(handle);
411 //      torrentHandles_.push_back(handlePtr);
412 #ifdef QTRAPIDS_DEBUG
413         qDebug() << "Is valid: " << handle.isValid();
414 #endif
415 }
416
417 void MainWindow::on_alert(std::auto_ptr<Alert> al)
418 {
419         if (al.get() != NULL) {
420 //              qDebug()
421 //                              << "MainWindow::on_torrentAlert(): "
422 //                              << QString::fromStdString(al->message());
423
424                 TorrentAlert *torrentAlert
425                 = dynamic_cast<TorrentAlert*> (al.get());
426
427                 if (torrentAlert) {
428                         qtrapids::QTorrentHandle torrentHandle = qtrapids::QTorrentHandle(torrentAlert->handle);
429                         dlView_->updateItem(qtrapids::QTorrentHandle(torrentAlert->handle));
430                 }
431
432         }
433 }
434
435 /*
436 bool MainWindow::IsNewTorrent(std::auto_ptr<qtrapids::QTorrentHandle> handlePtr)
437 {
438         for (unsigned i = 0; i < torrentHandles_.size(); ++i) {
439     if (torrentHandles_.at(i).get() == handlePtr.get()) {
440                         return false;
441                 } else {
442                         return true;
443                 }
444         }
445 }
446 */