Compare commits

..

1 Commits

Author SHA1 Message Date
Christophe Dumez
19d4709c94 - Tagged v2.0.0 release 2009-12-10 19:41:10 +00:00
19 changed files with 195 additions and 236 deletions

View File

@@ -1,19 +1,3 @@
* Fri Dec 18 2009 - Christophe Dumez <chris@qbittorrent.org> - v2.0.2
- BUGFIX: Fix .qbittorrent folder not being created (critical bug introduced in v2.0.1 that makes qBittorrent unusuable for new users)
- BUGFIX: Fix RSS Feed downloader for some feeds
- BUGFIX: Do not use home folder as a fallback when the save path is not accessible
- BUGFIX: Fix Mininova, ThePirateBay search engine plugins
- BUGFIX: Read RSS articles are remembered on restart for feeds with no torrents attached
* Sun Dec 13 2009 - Christophe Dumez <chris@qbittorrent.org> - v2.0.1
- BUGFIX: µTorrent user-agent is now spoofed correctly
- BUGFIX: Fix column hiding behavior when queueing system is disabled
- BUGFIX: Fix link to plugins.qbittorrent.org in plugins dialog
- BUGFIX: ~/qBT_dir is created only when it is actually used
- BUGFIX: Fix possible missing slot message (toggleSelectedTorrentsSuperSeeding)
- BUGFIX: Fix possible crash in torrent properties (files)
- BUGFIX: Added Hex Magnet Links support (Thanks Haypo)
* Thu Dec 10 2009 - Christophe Dumez <chris@qbittorrent.org> - v2.0.0
- FEATURE: Added program option to disable splash screen
- FEATURE: Dropped dependency on libcurl and libzzip

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

View File

@@ -1,6 +1,6 @@
[Desktop Entry]
Categories=Qt;Network;P2P;
Comment=V2.0.2
Comment=V2.0.0
Exec=qbittorrent %f
GenericName=Bittorrent client
GenericName[bg]=Торент клиент

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -303,7 +303,7 @@ void Bittorrent::configureSession() {
// * Session settings
session_settings sessionSettings;
if(Preferences::isUtorrentSpoofingEnabled()) {
sessionSettings.user_agent = "uTorrent/1850(17414)";
sessionSettings.user_agent = "uTorrent/1850";
} else {
sessionSettings.user_agent = "qBittorrent "VERSION;
}
@@ -1671,7 +1671,7 @@ QString Bittorrent::getSavePath(QString hash) {
if(!saveDir.mkpath(saveDir.path())) {
std::cerr << "Couldn't create the save directory: " << saveDir.path().toLocal8Bit().data() << "\n";
// XXX: handle this better
//return QDir::homePath();
return QDir::homePath();
}
}
return savePath;

View File

@@ -144,6 +144,7 @@
<file>Icons/oxygen/encrypted.png</file>
<file>Icons/oxygen/edit_clear.png</file>
<file>Icons/oxygen/download.png</file>
<file>Icons/oxygen/application-x-kgetlist-no.png</file>
<file>Icons/oxygen/gear.png</file>
<file>Icons/oxygen/remove.png</file>
<file>Icons/oxygen/dialog-warning.png</file>
@@ -164,6 +165,7 @@
<file>Icons/oxygen/help-about.png</file>
<file>Icons/oxygen/list-add.png</file>
<file>Icons/oxygen/network-server.png</file>
<file>Icons/oxygen/application-x-kgetlist.png</file>
<file>Icons/oxygen/folder.png</file>
<file>Icons/oxygen/urlseed.png</file>
<file>Icons/oxygen/edit-cut.png</file>

View File

@@ -271,30 +271,14 @@ public:
static QString magnetUriToHash(QString magnet_uri) {
QString hash = "";
QRegExp regHex("urn:btih:([0-9A-Za-z]+)");
// Hex
int pos = regHex.indexIn(magnet_uri);
QRegExp reg("urn:btih:([A-Z2-7=]+)");
int pos = reg.indexIn(magnet_uri);
if(pos > -1) {
QString found = regHex.cap(1);
if(found.length() == 40) {
sha1_hash sha1;
sha1.assign(QString(QByteArray::fromHex(regHex.cap(1).toLocal8Bit())).toStdString());
qDebug("magnetUriToHash (Hex): hash: %s", misc::toString(sha1).c_str());
return misc::toQString(sha1);
}
sha1_hash sha1;
sha1.assign(base32decode(reg.cap(1).toStdString()));
hash = misc::toQString(sha1);
}
// Base 32
QRegExp regBase32("urn:btih:([A-Za-z2-7=]+)");
pos = regBase32.indexIn(magnet_uri);
if(pos > -1) {
QString found = regBase32.cap(1);
if(found.length() > 20 && (found.length()*5)%40 == 0) {
sha1_hash sha1;
sha1.assign(base32decode(regBase32.cap(1).toStdString()));
hash = misc::toQString(sha1);
}
}
qDebug("magnetUriToHash (base32): hash: %s", hash.toLocal8Bit().data());
qDebug("magnetUriToHash: hash: %s", hash.toLocal8Bit().data());
return hash;
}

View File

@@ -362,12 +362,7 @@ void PropertiesWidget::loadDynamicData() {
}
if(stackedProperties->currentIndex() == FILES_TAB) {
// Files progress
if(h.is_valid() && h.has_metadata()) {
if(PropListModel->rowCount() == 0) {
PropListModel->setupModelData(h.get_torrent_info());
// Expand first item if possible
filesList->expand(PropListModel->index(0, 0));
}
if(h.has_metadata()) {
std::vector<size_type> fp;
h.file_progress(fp);
PropListModel->updateFilesPriorities(h.file_priorities());

View File

@@ -376,6 +376,7 @@ void RssManager::saveStreamList(){
/** RssStream **/
RssStream::RssStream(RssFolder* parent, RssManager *rssmanager, Bittorrent *BTSession, QString _url): parent(parent), rssmanager(rssmanager), BTSession(BTSession), alias(""), iconPath(":/Icons/rss16.png"), refreshed(false), downloadFailure(false), currently_loading(false) {
has_attachments = false;
qDebug("RSSStream constructed");
QSettings qBTRSS("qBittorrent", "qBittorrent-rss");
url = QUrl(_url).toString();
@@ -387,6 +388,8 @@ RssStream::RssStream(RssFolder* parent, RssManager *rssmanager, Bittorrent *BTSe
RssItem *rss_item = RssItem::fromHash(this, item);
if(rss_item->isValid()) {
(*this)[rss_item->getTitle()] = rss_item;
if(rss_item->has_attachment())
has_attachments = true;
}
}
}
@@ -581,31 +584,29 @@ short RssStream::readDoc(const QDomDocument& doc) {
delete item;
item = this->value(title);
}
QString torrent_url;
if(item->has_attachment())
torrent_url = item->getTorrentUrl();
else
torrent_url = item->getLink();
// Check if the item should be automatically downloaded
if(!already_exists || !(*this)[item->getTitle()]->isRead()) {
FeedFilter * matching_filter = FeedFilters::getFeedFilters(url).matches(item->getTitle());
if(matching_filter != 0) {
// Download the torrent
BTSession->addConsoleMessage(tr("Automatically downloading %1 torrent from %2 RSS feed...").arg(item->getTitle()).arg(getName()));
if(matching_filter->isValid()) {
QString save_path = matching_filter->getSavePath();
if(save_path.isEmpty())
BTSession->downloadUrlAndSkipDialog(torrent_url);
else
BTSession->downloadUrlAndSkipDialog(torrent_url, save_path);
} else {
// All torrents are downloaded from this feed
BTSession->downloadUrlAndSkipDialog(torrent_url);
if(item->has_attachment()) {
has_attachments = true;
// Check if the item should be automatically downloaded
if(!already_exists || !(*this)[item->getTitle()]->isRead()) {
FeedFilter * matching_filter = FeedFilters::getFeedFilters(url).matches(item->getTitle());
if(matching_filter != 0) {
// Download the torrent
BTSession->addConsoleMessage(tr("Automatically downloading %1 torrent from %2 RSS feed...").arg(item->getTitle()).arg(getName()));
if(matching_filter->isValid()) {
QString save_path = matching_filter->getSavePath();
if(save_path.isEmpty())
BTSession->downloadUrlAndSkipDialog(item->getTorrentUrl());
else
BTSession->downloadUrlAndSkipDialog(item->getTorrentUrl(), save_path);
} else {
// All torrents are downloaded from this feed
BTSession->downloadUrlAndSkipDialog(item->getTorrentUrl());
}
// Item was downloaded, consider it as Read
(*this)[item->getTitle()]->setRead();
// Clean up
delete matching_filter;
}
// Item was downloaded, consider it as Read
(*this)[item->getTitle()]->setRead();
// Clean up
delete matching_filter;
}
}

View File

@@ -298,7 +298,7 @@ public:
RssItem(RssStream* parent, QString _title, QString _torrent_url, QString _news_link, QString _description, QDateTime _date, QString _author, bool _read):
parent(parent), title(_title), torrent_url(_torrent_url), news_link(_news_link), description(_description), date(_date), author(_author), read(_read){
if(!title.isEmpty()) {
if(!title.isEmpty() && !torrent_url.isEmpty()) {
is_valid = true;
} else {
std::cerr << "ERROR: an invalid RSS item was saved" << std::endl;
@@ -394,6 +394,7 @@ private:
bool refreshed;
bool downloadFailure;
bool currently_loading;
bool has_attachments;
public slots:
void processDownloadedFile(QString file_path);
@@ -429,6 +430,7 @@ public:
QList<RssItem*> getNewsList() const;
QList<RssItem*> getUnreadNewsList() const;
QString getIconUrl();
bool hasAttachments() const { return has_attachments; }
private:
short readDoc(const QDomDocument& doc);

View File

@@ -71,8 +71,10 @@ void RSSImp::displayRSSListMenu(const QPoint& pos){
myRSSListMenu.addSeparator();
myRSSListMenu.addAction(actionCopy_feed_URL);
if(selectedItems.size() == 1) {
myRSSListMenu.addSeparator();
myRSSListMenu.addAction(actionRSS_feed_downloader);
if(((RssStream*)listStreams->getRSSItem(selectedItems.first()))->hasAttachments()) {
myRSSListMenu.addSeparator();
myRSSListMenu.addAction(actionRSS_feed_downloader);
}
}
}
}else{
@@ -291,7 +293,9 @@ void RSSImp::downloadTorrent() {
if(article->has_attachment()) {
BTSession->downloadFromUrl(article->getTorrentUrl());
} else {
BTSession->downloadFromUrl(article->getLink());
QString link = article->getLink();
if(!link.isEmpty())
QDesktopServices::openUrl(QUrl(link));
}
}
}
@@ -440,6 +444,10 @@ void RSSImp::refreshNewsList(QTreeWidgetItem* item) {
foreach(RssItem* article, news){
QTreeWidgetItem* it = new QTreeWidgetItem(listNews);
it->setText(NEWS_TITLE_COL, article->getTitle());
if(article->has_attachment())
it->setData(NEWS_TITLE_COL, Qt::DecorationRole, QVariant(QIcon(":/Icons/oxygen/application-x-kgetlist.png")));
else
it->setData(NEWS_TITLE_COL, Qt::DecorationRole, QVariant(QIcon(":/Icons/oxygen/application-x-kgetlist-no.png")));
it->setText(NEWS_URL_COL, article->getParent()->getUrl());
if(article->isRead()){
it->setData(NEWS_TITLE_COL, Qt::ForegroundRole, QVariant(QColor("grey")));

View File

@@ -1,5 +1,5 @@
#VERSION: 1.40
#AUTHORS: Christophe Dumez (chris@qbittorrent.org)
#VERSION: 1.32
#AUTHORS: Fabien Devaux (fab@gnux.info)
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
@@ -27,85 +27,88 @@
from novaprinter import prettyPrinter
from helpers import retrieve_url, download_file
import sgmllib
from xml.dom import minidom
import re
class mininova(object):
# Mandatory properties
url = 'http://www.mininova.org'
name = 'Mininova'
supported_categories = {'all': '0', 'movies': '4', 'tv': '8', 'music': '5', 'games': '3', 'anime': '1', 'software': '7', 'pictures': '6', 'books': '2'}
# Mandatory properties
url = 'http://www.mininova.org'
name = 'Mininova'
supported_categories = {'all': '0', 'movies': '4', 'tv': '8', 'music': '5', 'games': '3', 'anime': '1', 'software': '7', 'pictures': '6', 'books': '2'}
def download_torrent(self, info):
print download_file(info)
def __init__(self):
self.results = []
self.parser = self.SimpleSGMLParser(self.results, self.url)
def search(self, what, cat='all'):
def download_torrent(self, info):
print download_file(info)
def get_link(lnk):
lnks = lnk.getElementsByTagName('a')
i = 0
try:
while not lnks.item(i).attributes.get('href').value.startswith('/get'):
i += 1
except:
return None
return (self.url+lnks.item(i).attributes.get('href').value).strip()
def get_name(lnk):
lnks = lnk.getElementsByTagName('a')
i = 0
try:
while not lnks.item(i).attributes.get('href').value.startswith('/tor'):
i += 1
except:
return None
name = ""
for node in lnks[i].childNodes:
if node.hasChildNodes():
name += node.firstChild.toxml()
else:
name += node.toxml()
return re.sub('<[a-zA-Z\/][^>]*>', '', name)
class SimpleSGMLParser(sgmllib.SGMLParser):
def __init__(self, results, url, *args):
sgmllib.SGMLParser.__init__(self)
self.url = url
self.td_counter = None
self.current_item = None
self.results = results
def start_a(self, attr):
params = dict(attr)
#print params
if params.has_key('href') and params['href'].startswith("/get/"):
self.current_item = {}
self.td_counter = 0
self.current_item['link']=self.url+params['href'].strip()
def handle_data(self, data):
if self.td_counter == 0:
if not self.current_item.has_key('name'):
self.current_item['name'] = ''
self.current_item['name']+= data
elif self.td_counter == 1:
if not self.current_item.has_key('size'):
self.current_item['size'] = ''
self.current_item['size']+= data.strip()
elif self.td_counter == 2:
if not self.current_item.has_key('seeds'):
self.current_item['seeds'] = ''
self.current_item['seeds']+= data.strip()
elif self.td_counter == 3:
if not self.current_item.has_key('leech'):
self.current_item['leech'] = ''
self.current_item['leech']+= data.strip()
def start_td(self,attr):
if isinstance(self.td_counter,int):
self.td_counter += 1
if self.td_counter > 4:
self.td_counter = None
# Display item
if self.current_item:
self.current_item['engine_url'] = self.url
if not self.current_item['seeds'].isdigit():
self.current_item['seeds'] = 0
if not self.current_item['leech'].isdigit():
self.current_item['leech'] = 0
prettyPrinter(self.current_item)
self.results.append('a')
def search(self, what, cat='all'):
ret = []
i = 1
while True and i<11:
results = []
parser = self.SimpleSGMLParser(results, self.url)
dat = retrieve_url(self.url+'/search/%s/%s/seeds/%d'%(what, self.supported_categories[cat], i))
results_re = re.compile('(?s)<h1>Search results for.*')
for match in results_re.finditer(dat):
res_tab = match.group(0)
parser.feed(res_tab)
parser.close()
break
if len(results) <= 0:
break
i += 1
def get_text(txt):
if txt.nodeType == txt.TEXT_NODE:
return txt.toxml()
else:
return ''.join([ get_text(n) for n in txt.childNodes])
if cat == 'all':
self.table_items = 'added cat name size seeds leech'.split()
else:
self.table_items = 'added name size seeds leech'.split()
page = 1
while True and page<11:
res = 0
dat = retrieve_url(self.url+'/search/%s/%s/seeds/%d'%(what, self.supported_categories[cat], page))
dat = re.sub("<a href=\"http://www.boardreader.com/index.php.*\"", "<a href=\"plop\"", dat)
dat = re.sub("<=", "&lt;=", dat)
dat = re.sub("&\s", "&amp; ", dat)
dat = re.sub("&(?!amp)", "&amp;", dat)
x = minidom.parseString(dat)
table = x.getElementsByTagName('table').item(0)
if not table: return
for tr in table.getElementsByTagName('tr'):
tds = tr.getElementsByTagName('td')
if tds:
i = 0
vals = {}
for td in tds:
if self.table_items[i] == 'name':
vals['link'] = get_link(td)
vals['name'] = get_name(td)
else:
vals[self.table_items[i]] = get_text(td).strip()
i += 1
vals['engine_url'] = self.url
if not vals['seeds'].isdigit():
vals['seeds'] = 0
if not vals['leech'].isdigit():
vals['leech'] = 0
if vals['link'] is None:
continue
prettyPrinter(vals)
res = res + 1
if res == 0:
break
page = page +1

View File

@@ -1,4 +1,4 @@
#VERSION: 1.30
#VERSION: 1.22
#AUTHORS: Fabien Devaux (fab@gnux.info)
#CONTRIBUTORS: Christophe Dumez (chris@qbittorrent.org)
@@ -50,35 +50,32 @@ class piratebay(object):
self.results = results
self.url = url
self.code = 0
self.in_name = None
def start_a(self, attr):
params = dict(attr)
if params['href'].startswith('/torrent/'):
if params['href'].startswith('/browse'):
self.current_item = {}
self.td_counter = 0
elif params['href'].startswith('/tor'):
self.code = params['href'].split('/')[2]
self.in_name = True
elif params['href'].startswith('http://torrents.thepiratebay.org/%s'%self.code):
self.current_item['link']=params['href'].strip()
self.in_name = False
self.td_counter = self.td_counter+1
def handle_data(self, data):
if self.td_counter == 0:
if self.in_name:
if not self.current_item.has_key('name'):
self.current_item['name'] = ''
self.current_item['name']+= data.strip()
else:
#Parse size
if 'Size' in data:
self.current_item['size'] = data[data.index("Size")+5:]
self.current_item['size'] = self.current_item['size'][:self.current_item['size'].index(',')]
elif self.td_counter == 1:
if self.td_counter == 1:
if not self.current_item.has_key('name'):
self.current_item['name'] = ''
self.current_item['name']+= data.strip()
if self.td_counter == 5:
if not self.current_item.has_key('size'):
self.current_item['size'] = ''
self.current_item['size']+= data.strip()
elif self.td_counter == 6:
if not self.current_item.has_key('seeds'):
self.current_item['seeds'] = ''
self.current_item['seeds']+= data.strip()
elif self.td_counter == 2:
elif self.td_counter == 7:
if not self.current_item.has_key('leech'):
self.current_item['leech'] = ''
self.current_item['leech']+= data.strip()
@@ -86,7 +83,7 @@ class piratebay(object):
def start_td(self,attr):
if isinstance(self.td_counter,int):
self.td_counter += 1
if self.td_counter > 3:
if self.td_counter > 7:
self.td_counter = None
# Display item
if self.current_item:
@@ -104,8 +101,7 @@ class piratebay(object):
while True and i<11:
results = []
parser = self.SimpleSGMLParser(results, self.url)
dat = retrieve_url(self.url+'/search/%s/%u/7/%s' % (what, i, self.supported_categories[cat]))
print self.url+'/search/%s/%u/7/%s' % (what, i, self.supported_categories[cat])
dat = retrieve_url(self.url+'/search/%s/%u/99/%s' % (what, i, self.supported_categories[cat]))
parser.feed(dat)
parser.close()
if len(results) <= 0:

View File

@@ -1,5 +1,5 @@
isohunt: 1.30
torrentreactor: 1.20
btjunkie: 2.21
mininova: 1.40
piratebay: 1.30
mininova: 1.32
piratebay: 1.22

View File

@@ -22,15 +22,16 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#VERSION: 1.2
#VERSION: 1.1
# Author:
# Christophe DUMEZ (chris@qbittorrent.org)
import re, htmlentitydefs
import urllib2
import tempfile
import os
import StringIO, gzip, urllib2
import StringIO, gzip, httplib
def htmlentitydecode(s):
# First convert alpha entities (such as &eacute;)
@@ -63,14 +64,12 @@ def retrieve_url(url):
dat = htmlentitydecode(dat)
return dat.encode('utf-8', 'replace')
def download_file(url, referer=None):
def download_file(url):
""" Download file at url and write it to a file, return the path to the file and the url """
file, path = tempfile.mkstemp()
file = os.fdopen(file, "w")
# Download url
req = urllib2.Request(url)
if referer is not None:
req.add_header('referer', referer)
response = urllib2.urlopen(req)
dat = response.read()
# Check if data is gzip encoded

View File

@@ -12,10 +12,10 @@ CONFIG += qt \
thread
# Update this VERSION for each release
DEFINES += VERSION=\\\"v2.0.2\\\"
DEFINES += VERSION=\\\"v2.0.0\\\"
DEFINES += VERSION_MAJOR=2
DEFINES += VERSION_MINOR=0
DEFINES += VERSION_BUGFIX=2
DEFINES += VERSION_BUGFIX=0
# !mac:QMAKE_LFLAGS += -Wl,--as-needed
contains(DEBUG_MODE, 1) {

View File

@@ -762,10 +762,7 @@ void TransferListWidget::displayDLHoSMenu(const QPoint&){
hideshowColumn.setTitle(tr("Column visibility"));
QList<QAction*> actions;
for(int i=0; i < TR_HASH; ++i) {
if(!BTSession->isQueueingEnabled() && i == TR_PRIORITY) {
actions.append(0);
continue;
}
if(!BTSession->isQueueingEnabled() && i == TR_PRIORITY) continue;
QIcon icon;
if(isColumnHidden(i))
icon = QIcon(QString::fromUtf8(":/Icons/oxygen/button_cancel.png"));
@@ -774,12 +771,9 @@ void TransferListWidget::displayDLHoSMenu(const QPoint&){
actions.append(hideshowColumn.addAction(icon, listModel->headerData(i, Qt::Horizontal).toString()));
}
// Call menu
QAction *act = 0;
act = hideshowColumn.exec(QCursor::pos());
if(act) {
int col = actions.indexOf(act);
setColumnHidden(col, !isColumnHidden(col));
}
QAction *act = hideshowColumn.exec(QCursor::pos());
int col = actions.indexOf(act);
setColumnHidden(col, !isColumnHidden(col));
}
#ifdef LIBTORRENT_0_15
@@ -846,10 +840,8 @@ void TransferListWidget::displayListMenu(const QPoint&) {
connect(&actionForce_recheck, SIGNAL(triggered()), this, SLOT(recheckSelectedTorrents()));
QAction actionCopy_magnet_link(QIcon(QString::fromUtf8(":/Icons/magnet.png")), tr("Copy magnet link"), 0);
connect(&actionCopy_magnet_link, SIGNAL(triggered()), this, SLOT(copySelectedMagnetURIs()));
#ifdef LIBTORRENT_0_15
QAction actionSuper_seeding_mode(tr("Super seeding mode"), 0);
connect(&actionSuper_seeding_mode, SIGNAL(triggered()), this, SLOT(toggleSelectedTorrentsSuperSeeding()));
#endif
QAction actionSequential_download(tr("Download in sequential order"), 0);
connect(&actionSequential_download, SIGNAL(triggered()), this, SLOT(toggleSelectedTorrentsSequentialDownload()));
QAction actionFirstLastPiece_prio(tr("Download first and last piece first"), 0);

View File

@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<ui version="4.0" >
<class>engineSelect</class>
<widget class="QDialog" name="engineSelect">
<property name="geometry">
<widget class="QDialog" name="engineSelect" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
@@ -10,100 +9,94 @@
<height>254</height>
</rect>
</property>
<property name="acceptDrops">
<property name="acceptDrops" >
<bool>true</bool>
</property>
<property name="windowTitle">
<property name="windowTitle" >
<string>Search plugins</string>
</property>
<layout class="QVBoxLayout">
<layout class="QVBoxLayout" >
<item>
<widget class="QLabel" name="lbl_engines">
<property name="font">
<widget class="QLabel" name="lbl_engines" >
<property name="font" >
<font>
<weight>75</weight>
<bold>true</bold>
<underline>true</underline>
</font>
</property>
<property name="text">
<property name="text" >
<string>Installed search engines:</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="pluginsTree">
<property name="contextMenuPolicy">
<widget class="QTreeWidget" name="pluginsTree" >
<property name="contextMenuPolicy" >
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<property name="selectionMode" >
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="uniformRowHeights">
<property name="uniformRowHeights" >
<bool>true</bool>
</property>
<property name="itemsExpandable">
<property name="itemsExpandable" >
<bool>false</bool>
</property>
<column>
<property name="text">
<property name="text" >
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<property name="text" >
<string>Url</string>
</property>
</column>
<column>
<property name="text">
<property name="text" >
<string>Enabled</string>
</property>
</column>
<column>
<property name="text">
<property name="text" >
<string/>
</property>
</column>
</widget>
</item>
<item>
<widget class="QLabel" name="getNewEngine_lbl">
<property name="font">
<widget class="QLabel" name="getNewEngine_lbl" >
<property name="font" >
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>You can get new search engine plugins here: &lt;a href=&quot;http://plugins.qbittorrent.org&quot;&gt;http://plugins.qbittorrent.org&lt;/a&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
<property name="text" >
<string>You can get new search engine plugins here: &lt;a href="http:plugins.qbittorrent.org">http://plugins.qbittorrent.org&lt;/a></string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<layout class="QHBoxLayout" >
<item>
<widget class="QPushButton" name="installButton">
<property name="text">
<widget class="QPushButton" name="installButton" >
<property name="text" >
<string>Install a new one</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="updateButton">
<property name="text">
<widget class="QPushButton" name="updateButton" >
<property name="text" >
<string>Check for updates</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="closeButton">
<property name="text">
<widget class="QPushButton" name="closeButton" >
<property name="text" >
<string>Close</string>
</property>
</widget>
@@ -111,18 +104,18 @@
</layout>
</item>
</layout>
<action name="actionEnable">
<property name="text">
<action name="actionEnable" >
<property name="text" >
<string>Enable</string>
</property>
</action>
<action name="actionDisable">
<property name="text">
<action name="actionDisable" >
<property name="text" >
<string>Disable</string>
</property>
</action>
<action name="actionUninstall">
<property name="text">
<action name="actionUninstall" >
<property name="text" >
<string>Uninstall</string>
</property>
</action>