Compare commits
149 Commits
release-3.
...
release-3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb5174bfa2 | ||
|
|
9138156968 | ||
|
|
8a0b1fe0be | ||
|
|
3bbe304856 | ||
|
|
0356172a1d | ||
|
|
80f3b19356 | ||
|
|
39a569b438 | ||
|
|
edaa7e85a7 | ||
|
|
f9f7a8cbf2 | ||
|
|
7aef9828c9 | ||
|
|
18ad972936 | ||
|
|
8b5c275934 | ||
|
|
59765954b8 | ||
|
|
f5fff855bb | ||
|
|
26c713851e | ||
|
|
f9b64c4e73 | ||
|
|
edf6c30cd8 | ||
|
|
83860afa60 | ||
|
|
4b7362aa6b | ||
|
|
dd0537d8d2 | ||
|
|
3c50cc1d2c | ||
|
|
78fdb68457 | ||
|
|
bb1bd34ec0 | ||
|
|
1841834c89 | ||
|
|
30b70a99f1 | ||
|
|
f0f2e93e4d | ||
|
|
5852c5e318 | ||
|
|
b38827bb8e | ||
|
|
240046f720 | ||
|
|
78b1194da1 | ||
|
|
c1a66dad51 | ||
|
|
4a10d246c2 | ||
|
|
125ab378a4 | ||
|
|
4cd8a36b40 | ||
|
|
364df2e18c | ||
|
|
f8f2476aa5 | ||
|
|
d685c9739b | ||
|
|
6fdfcf38f1 | ||
|
|
7c85866881 | ||
|
|
37673cd86f | ||
|
|
a4a38d633c | ||
|
|
93bdc81e3c | ||
|
|
827d5a22fb | ||
|
|
34e56eade8 | ||
|
|
4a23e7da37 | ||
|
|
f577a26fe2 | ||
|
|
3f176d8265 | ||
|
|
cbc001e059 | ||
|
|
a66ed05ecd | ||
|
|
ec347d8dbe | ||
|
|
f0acafb853 | ||
|
|
1a12e891ec | ||
|
|
88ba8e2ceb | ||
|
|
2e403277f3 | ||
|
|
89349f60b0 | ||
|
|
0604d3729a | ||
|
|
cf16e3b8a1 | ||
|
|
dcb3496651 | ||
|
|
9d10da3ed2 | ||
|
|
0524deb496 | ||
|
|
787ae43d54 | ||
|
|
67bb2cc150 | ||
|
|
51995c80d1 | ||
|
|
5f43741b09 | ||
|
|
3328d8efd2 | ||
|
|
a54a9e5487 | ||
|
|
0181ca70f4 | ||
|
|
ff665af3f7 | ||
|
|
9275cdc5bf | ||
|
|
b7c3bdd443 | ||
|
|
5fd08f8664 | ||
|
|
84a4d323c7 | ||
|
|
8efb13bfaf | ||
|
|
0187b55610 | ||
|
|
2f606b2728 | ||
|
|
025f75beca | ||
|
|
4711cafd20 | ||
|
|
8d12ca9477 | ||
|
|
67b90bfb51 | ||
|
|
ab05c0c326 | ||
|
|
c5ea453438 | ||
|
|
4f041c16e1 | ||
|
|
28cfee7bd3 | ||
|
|
6272287fbb | ||
|
|
e7a1542902 | ||
|
|
5e371d8195 | ||
|
|
6bb189ea13 | ||
|
|
8496f31e39 | ||
|
|
08f634f748 | ||
|
|
2b5dc5c4a6 | ||
|
|
0a2f0aefb3 | ||
|
|
66e137b8ee | ||
|
|
f9be39545b | ||
|
|
789b8046a2 | ||
|
|
129bf497c8 | ||
|
|
a6d7693d62 | ||
|
|
4e9fbc4da5 | ||
|
|
23f6ff4673 | ||
|
|
3c0dfa6444 | ||
|
|
694311b2bd | ||
|
|
b77626897f | ||
|
|
b8081feac1 | ||
|
|
8b805f4518 | ||
|
|
8bb4f021f1 | ||
|
|
97c79050dc | ||
|
|
ac62a708de | ||
|
|
8d9789f51b | ||
|
|
559d0228fd | ||
|
|
1296e7b891 | ||
|
|
0333e23710 | ||
|
|
7320a80caa | ||
|
|
0579bfc069 | ||
|
|
d20d04299e | ||
|
|
50b2009e9c | ||
|
|
721d29edda | ||
|
|
bc9cae199b | ||
|
|
c38b250667 | ||
|
|
101b2f3ad2 | ||
|
|
9c4f798d93 | ||
|
|
249ff21738 | ||
|
|
f1149097b6 | ||
|
|
111b0df307 | ||
|
|
8041af72cd | ||
|
|
4be6d0b30f | ||
|
|
8c757969f2 | ||
|
|
ae6a82f814 | ||
|
|
eed3f0559a | ||
|
|
c7884e7621 | ||
|
|
2946ab7e7a | ||
|
|
ddb8e4d21a | ||
|
|
00d4f6141f | ||
|
|
7971a25c2a | ||
|
|
ab2411930a | ||
|
|
93f972bfca | ||
|
|
db638844d0 | ||
|
|
ba99eddc91 | ||
|
|
92428cee5d | ||
|
|
2c7d836925 | ||
|
|
7703dcf626 | ||
|
|
157520c4fc | ||
|
|
5bc728fa33 | ||
|
|
35fdc43b3f | ||
|
|
ae6ea29f2f | ||
|
|
4eac2cab31 | ||
|
|
87f4f57f8e | ||
|
|
a6e250fa43 | ||
|
|
b118079379 | ||
|
|
cb2d39f2a7 | ||
|
|
4cf549ff25 |
14
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
**Please provide the following information**
|
||||
|
||||
### qBittorrent version and Operating System:
|
||||
|
||||
### If on linux, libtorrent and Qt version:
|
||||
|
||||
### What is the problem:
|
||||
|
||||
### What is the expected behavior:
|
||||
|
||||
### Steps to reproduce:
|
||||
|
||||
### Extra info(if any):
|
||||
|
||||
21
.travis.yml
@@ -17,6 +17,12 @@ env:
|
||||
global:
|
||||
- secure: "OI9CUjj4lTb0HwwIZU5PbECU3hLlAL6KC8KsbwohG8/O3j5fLcnmDsK4Ad9us5cC39sS11Jcd1kDP2qRcCuST/glVNhLkcjKkiQerOfd5nQ/qL4JYfz/1mfP5mdpz9jHKzpLUIG+TXkbSTjP6VVmsb5KPT+3pKEdRFZB+Pu9+J8="
|
||||
- coverity_branch: coverity_scan
|
||||
matrix:
|
||||
allow_failures:
|
||||
- os: osx
|
||||
env: lt_branch=RC_1_0 qt=4 gui=true
|
||||
- os: osx
|
||||
env: lt_branch=RC_1_0 qt=4 gui=false
|
||||
|
||||
branches:
|
||||
except:
|
||||
@@ -120,14 +126,18 @@ install:
|
||||
fi
|
||||
- |
|
||||
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
|
||||
mkdir -p "$HOME/hombebrew_cache" ;
|
||||
wget https://builds.shiki.hu/homebrew/version ;
|
||||
if ! cmp --quiet "version" "$HOME/hombebrew_cache/version" ; then
|
||||
echo "Cached files are different from server. Downloading new ones." ;
|
||||
# First delete old files
|
||||
rm -r "$HOME/hombebrew_cache" ;
|
||||
mkdir "$HOME/hombebrew_cache";
|
||||
cp "version" $HOME/hombebrew_cache ;
|
||||
cd "$HOME/hombebrew_cache" ;
|
||||
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar.rb ;
|
||||
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar-1.0.10.el_capitan.bottle.tar.gz ;
|
||||
wget https://builds.shiki.hu/homebrew/qt5.rb ;
|
||||
wget https://builds.shiki.hu/homebrew/qt5-5.7.1_1.el_capitan.bottle.tar.gz ;
|
||||
fi
|
||||
|
||||
# dependencies
|
||||
@@ -142,7 +152,14 @@ install:
|
||||
|
||||
# Qt
|
||||
if [ "$qt" = 4 ]; then brew install qt && ln -s /usr/local/Cellar/qt/4.8.7_2/plugins /usr/local ; fi ;
|
||||
if [ "$qt" = 5 ]; then brew install qt5 && brew link --force qt5 && ln -s /usr/local/Cellar/qt5/5.7.0/plugins /usr/local ; fi ;
|
||||
if [ "$qt" = 5 ]; then
|
||||
# Copy custom qt5 bottle to homebrew's cache so it can find and install it
|
||||
# Also install our custom qt5 formula by passing the local path to it
|
||||
# These 2 files are restored from Travis' cache.
|
||||
cp "$HOME/hombebrew_cache/qt5-5.7.1_1.el_capitan.bottle.tar.gz" "$(brew --cache)" ;
|
||||
brew install "$HOME/hombebrew_cache/qt5.rb" ;
|
||||
brew link --force qt5 ;
|
||||
fi
|
||||
|
||||
# ccache
|
||||
if [ "$TRAVIS_BRANCH" != "$coverity_branch" ]; then
|
||||
|
||||
87
5B7CC9A2.asc
@@ -26,28 +26,67 @@ HiShCTSSDBJqFmhfjrCo0nISKnzyxgO/rY9vFlwXsKkTyL7s53ONkjwK34WmGnya
|
||||
tXdjBWShzAiTfF5hephfBSszmoBG2C8Jcu6P5n4buBY4RCsEa+6jE0R1vCtmpVwx
|
||||
WrXOeN2kGYMpAkPK1L69Le0FofgUDKlaFMv7KRl4R367xNRukYrsKwVlontJ+Y72
|
||||
X5t1BeRn8VSp0IzhssNXM8a4bTE8lvs889DOS2vgWEHIi0iyIesJYWPs4AKUw4rG
|
||||
EDwWxtTS0a7Rfx3DxLkCDQRXDSCMARAAqMIVJizEJp205c546IN75xeYiFszNXcs
|
||||
3768IY8bOoWj+rTwt2wIwtL/3O5K2dG79CSt2H5o6BPKmq43tOO60YW3Yk3m9BB/
|
||||
gnAVqk0QOPr5O8+yeBzdElU8CZh6y6zZMWugSkNmTDm6jZzPhgNjcjrit/dl9+0D
|
||||
GqJQcqoD8WzEWNcWrMHVz9cDewnLSVkwR758mZMaIiL7R10MZ++tNrC0j69UINqx
|
||||
+9z1r1J07+NNnxqSTxVRcbjPYtM9E+tUiVFS2HPWN9ShVDkBAEdoWh90qzRaMiFl
|
||||
2NGNGOD1iHx/xr06RMeGEEXt2vhSlhfMW2YQW+UD2jzlFbARf53v39MUKKscGuIp
|
||||
BhxGw3JCq4l6qLW/bDkgnoXlOhZDmhQm6OpsjAyk9IEdd3ponSc7yYD3mUkJKR9e
|
||||
TaALD5t6TQGyNHakb4UfoXtE2RR78cbPlLIwag7eQ8GsNA+dfjowmOZdojx3ROsH
|
||||
ZdGQwb0YFLjuKAusA3TY+lCfbS6kzE2iI2DuaW+3dICcLrYuibbVb0CBNHyD+8KE
|
||||
tczdur/wm0lhqyVJkGyZKZT8C2cPxywKgy1Rn6F8Yfmj0Lna3nvtaZu0ZUS4/8Li
|
||||
t5PcOso1lSmYBuD6yq+GEAMCnUmn1Pm8eZRMlxxQuTPvyJKQrRDhbtAAr472MSno
|
||||
JKlS4SfaUF0AEQEAAYkCHwQYAQgACQUCVw0gjAIbDAAKCRBuSi0CW3zJomZDD/9I
|
||||
Jmzd5hiEzntlp84pyIJcfyIRe4KImvldAy6T02OSIbF1HzCNnwmqIPob6MOdMZ+K
|
||||
NwMK0htRkrRr/zM034+lBiWKZt+tVYHu49ioTYXEjAc5qDJE09Sq7HceQnhgE48f
|
||||
1n54XGT5G2w5gw+/a8Qn1SceE44VwXafL3E1gKaOrrsb1UH/AJhp+W4VMu+7bLXu
|
||||
7h1tN6v2PhvCYvBt3zyy8Q8xfJ2x7/D1lbF8ATJAiZ/km9x5bRm7OGRliVYaUe1n
|
||||
yR42fZOj3CBmAR0+lZLgjriqdMXrs+qlBbrmAhkn0XPQXAeaPifKoKIGDAUWIsqD
|
||||
HqM7imMGT+MR9APfSw8M4enOJWL+HnKpVBEARCEDpaFpJ3u7QRucFybpEhvIymoN
|
||||
ftyw+urId2Eg2K33NypeZo3M1K2LC65f2Ta7f/sZcIDUTbgW+m334fgVl1KptDA5
|
||||
DX3U9lTci7mi4uPuAFtbWrB1di4jYrxXYuzFm5g4xTb0Hw3kYIB6WXF+I7i0JaGO
|
||||
THxPC5X5lIAZrYrkxh+1n1Y1CY+TC8JcTzwORJIbFFm9tD/BHXa4849k4DVvFYCZ
|
||||
khq+/56FKZfoVByhB+x+2GaMlsBm1uPniO4lAakFPpIi0kaap4UVayQ/7ak+Bhsc
|
||||
AIHZUy6NtgZkuvW3xdpwp07LYo2ilhMI8RnzmtoRmg==
|
||||
=tDGM
|
||||
EDwWxtTS0a7Rfx3DxLRWc2xlZGdlaGFtbWVyOTk5IChVc2VkIGZvciBzaWduaW5n
|
||||
IGdpdCBjb21taXRzL3RhZ3MvZXRjLikgPGhhbW1lcmVkOTk5QHFiaXR0b3JyZW50
|
||||
Lm9yZz6JAh8EMAEIAAkFAlhie1ICHQAACgkQbkotAlt8yaILIhAAp25o1BbUG2Zk
|
||||
At3cSrTFnZSCA7nEygbSUv1Uek33JZfY0Apw5qEM8lQCMZk+mhdrSQCYUJcQlruN
|
||||
zJcJf4CH+VGE23xkI3Kf0nGp9Cjn/q6b1hLIPe5rimvw5pTAejFtebcYY/ZJIB8Z
|
||||
H1ebuzfqBZ/9k7eYTarZ/ZsgG8YptB0RXBQWOMaSEKwdeo2m7HXHgK3blQiqbuJJ
|
||||
uyPbid01Wus4AVN47/FKgDNswPs8irYZsu5yakgpi2KLycGDtSiN5XFHI4xbC0zM
|
||||
srR7Cz0/fC+klhGcuxbw0V0It7UUIitgCcTPHXkukUU8i2+AGMyKa1HjchsXDdLg
|
||||
DIs6KIurp2ve7znKOz7h1aX8cOBmB/QYeYAx9jRRkePMIRT8V1lRwfvJlJxx1+G3
|
||||
e2gJLjqTN8a08KHHjdY/S0ZFERxSlmOym2uf/y6di1ipDPxo8xvDuS5kDbdZLC0t
|
||||
XijlsH8ONK27KNuWhucG8zHzKQvnPw2qN06SZq4FjbSmAkkuYs56heLEXMzFr75k
|
||||
SE8rUoQQ+ABG9gU46GEvKlZxqSwXgGnb1X6K7h8svjMh/NlAU358p8Sra4Ru5tz4
|
||||
jUu9MoVEw5Lbjcrsnp6/4Kk1Q2ckBNt43nv8/+C7NsC3xi6BrOInuaKHZ4QsTuzJ
|
||||
m1/A4zlKRnUi6T98DXfIYnNuV9NSmAWJAjkEEwEIACMFAlhiemMCGwMHCwkIBwMC
|
||||
AQYVCAIJCgsEFgIDAQIeAQIXgAAKCRBuSi0CW3zJor6yD/9N2U0INx0nYpGkmvah
|
||||
yVG/vw2S6hhKK+03AN+RrtddNRg4aBf/gmOvRWQhAmFnXOBA7fO09wgcljaV5tVb
|
||||
MYyYZvHhK0o2/sli2p/M5N8ZxchRHypjxUSEyG9ZQ06QG5DVhh4HtM8nIN+UcwTV
|
||||
C5QjyoWZvHf+tNroyFeh7zT+w4kX1VxgynTQr5LGdYsrVA3CFyT3zsBWV3dMae23
|
||||
22CHOirsBBLwairHUsWW+BdThT3MkKYpTEV0jkH4OyAXhJYcS5IjjtKQ8UpZE9dw
|
||||
f4saJ0TnXNe7goPRZtH7UjPwfVbtYK4y8QklWUTRxgoBxNwSC5X7Flg+3xXxE/VU
|
||||
U4cehyRkH64i7MJDoFkqh5JtjkgIz+kuTTXb7xR0Wf+JXrGMybZTR8xth2TEMC20
|
||||
1FT5L5+0vH1WRzL7bhlaU3EXyCnoH8sDvMEClZbibbew+rf7fC3tFU41ohUT0HDl
|
||||
zlyfVjRvBHWMTgfpWKBV2m/qP941xTJ9VHxOlAB02XKUZYwFt07CpH+yjMOCOzA4
|
||||
cTPBD3mGRuft0V0BJ8bA5bcTly/GBciRX0Y5oIeHZGgq2czb0sywSYT6mPoQMFNM
|
||||
B+Cwr4pm90r1DMMfW518onF2itwyN/Id0FsWDhsLJHKluBJw52C3OnxCuToVutTm
|
||||
xntqpPVv62LaeVeWQqxIieTJErRQc2xlZGdlaGFtbWVyXzk5OSAoVXNlZCBmb3Ig
|
||||
c2lnbmluZyBnaXQgY29tbWl0cy90YWdzL2V0YykgPGhhbW1lcmVkOTk5QGdtYWls
|
||||
LmNvbT6JAjkEEwEIACMFAlhifeICGwMHCwkIBwMCAQYVCAIJCgsEFgIDAQIeAQIX
|
||||
gAAKCRBuSi0CW3zJolcCD/9xPBNEkFtnhTW89th0TFZnB5oykCQjyefquvQs8KWT
|
||||
C92/1VizHi4ZxDehHWP9IKVWT3ZJthj5ZXBSedyl1tHnwkyrUYBW9roQwtDWPncK
|
||||
pXl/HsE6p3q6EIus+g6YJo4UvYachJFAZATZp1WDBPIswziHGzaL0tndFWZuVM8V
|
||||
QD0tfPQsS1qCDVv6+B1JWZDnA1JzdSG/uzPhL95q/ff6JmNbfSAVedK2PyqYshnC
|
||||
KWBx6Yna/0ColBuDFho8+bDuHPQcM35xyjPosVD7moXQiY4yMAJ+VzwEBaCFleI0
|
||||
RBWw8/+qyoFqfIKwdq8G+7I9LjWpBiN2+uQBZ+OAvsMWyRShLopxt3JluPTtL6xb
|
||||
Ca6dglOdlaOS/A6FK7u05k/8kQMDS5Jq2/rpfTPRl1/weCaJZgfRIBosk1Mon/pR
|
||||
p1zd0abM4t7BcGQpwSkKAmqlKCrWf886EFQT0CJTBo8q7pzgpVraWWPVsmAOdkfU
|
||||
YcKBgz1A2uMSAxypkSzaDZkIVj6I7gwiGk7IMYx1OK7Ev46h/x4Z7kgT0y3DYYOq
|
||||
ggVEKQ+15Krn7bZ35s8vbZdfnVKPSXdCC8jkIMBmGmRX6cgZZ3OXZlrrHht5icgJ
|
||||
5Z2d1M4JUoEZVUr2xNZkkaMk01NAIpGgKvIS6yHuj6vE4GMJ+A/qEW6J60/3YHRe
|
||||
0bkCDQRXDSCMARAAqMIVJizEJp205c546IN75xeYiFszNXcs3768IY8bOoWj+rTw
|
||||
t2wIwtL/3O5K2dG79CSt2H5o6BPKmq43tOO60YW3Yk3m9BB/gnAVqk0QOPr5O8+y
|
||||
eBzdElU8CZh6y6zZMWugSkNmTDm6jZzPhgNjcjrit/dl9+0DGqJQcqoD8WzEWNcW
|
||||
rMHVz9cDewnLSVkwR758mZMaIiL7R10MZ++tNrC0j69UINqx+9z1r1J07+NNnxqS
|
||||
TxVRcbjPYtM9E+tUiVFS2HPWN9ShVDkBAEdoWh90qzRaMiFl2NGNGOD1iHx/xr06
|
||||
RMeGEEXt2vhSlhfMW2YQW+UD2jzlFbARf53v39MUKKscGuIpBhxGw3JCq4l6qLW/
|
||||
bDkgnoXlOhZDmhQm6OpsjAyk9IEdd3ponSc7yYD3mUkJKR9eTaALD5t6TQGyNHak
|
||||
b4UfoXtE2RR78cbPlLIwag7eQ8GsNA+dfjowmOZdojx3ROsHZdGQwb0YFLjuKAus
|
||||
A3TY+lCfbS6kzE2iI2DuaW+3dICcLrYuibbVb0CBNHyD+8KEtczdur/wm0lhqyVJ
|
||||
kGyZKZT8C2cPxywKgy1Rn6F8Yfmj0Lna3nvtaZu0ZUS4/8Lit5PcOso1lSmYBuD6
|
||||
yq+GEAMCnUmn1Pm8eZRMlxxQuTPvyJKQrRDhbtAAr472MSnoJKlS4SfaUF0AEQEA
|
||||
AYkCHwQYAQgACQUCVw0gjAIbDAAKCRBuSi0CW3zJomZDD/9IJmzd5hiEzntlp84p
|
||||
yIJcfyIRe4KImvldAy6T02OSIbF1HzCNnwmqIPob6MOdMZ+KNwMK0htRkrRr/zM0
|
||||
34+lBiWKZt+tVYHu49ioTYXEjAc5qDJE09Sq7HceQnhgE48f1n54XGT5G2w5gw+/
|
||||
a8Qn1SceE44VwXafL3E1gKaOrrsb1UH/AJhp+W4VMu+7bLXu7h1tN6v2PhvCYvBt
|
||||
3zyy8Q8xfJ2x7/D1lbF8ATJAiZ/km9x5bRm7OGRliVYaUe1nyR42fZOj3CBmAR0+
|
||||
lZLgjriqdMXrs+qlBbrmAhkn0XPQXAeaPifKoKIGDAUWIsqDHqM7imMGT+MR9APf
|
||||
Sw8M4enOJWL+HnKpVBEARCEDpaFpJ3u7QRucFybpEhvIymoNftyw+urId2Eg2K33
|
||||
NypeZo3M1K2LC65f2Ta7f/sZcIDUTbgW+m334fgVl1KptDA5DX3U9lTci7mi4uPu
|
||||
AFtbWrB1di4jYrxXYuzFm5g4xTb0Hw3kYIB6WXF+I7i0JaGOTHxPC5X5lIAZrYrk
|
||||
xh+1n1Y1CY+TC8JcTzwORJIbFFm9tD/BHXa4849k4DVvFYCZkhq+/56FKZfoVByh
|
||||
B+x+2GaMlsBm1uPniO4lAakFPpIi0kaap4UVayQ/7ak+BhscAIHZUy6NtgZkuvW3
|
||||
xdpwp07LYo2ilhMI8RnzmtoRmg==
|
||||
=UBeB
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_policy(VERSION 3.5)
|
||||
|
||||
project(qBittorrent VERSION 3.4.0.0)
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules)
|
||||
include(FunctionReadVersion)
|
||||
|
||||
set(VER_MAJOR ${qBittorrent_VERSION_MAJOR})
|
||||
set(VER_MINOR ${qBittorrent_VERSION_MINOR})
|
||||
set(VER_BUGFIX ${qBittorrent_VERSION_PATCH})
|
||||
set(VER_BUILD ${qBittorrent_VERSION_TWEAK})
|
||||
set(VER_STATUS "alpha") # Should be empty for stable releases!
|
||||
read_version("${CMAKE_CURRENT_SOURCE_DIR}/version.pri" VER_MAJOR VER_MINOR VER_BUGFIX VER_BUILD VER_STATUS)
|
||||
# message(STATUS "Project version is: ${VER_MAJOR}.${VER_MINOR}.${VER_BUGFIX}.${VER_BUILD} (${VER_STATUS})")
|
||||
|
||||
project(qBittorrent VERSION ${VER_MAJOR}.${VER_MINOR}.${VER_BUGFIX}.${VER_BUILD})
|
||||
|
||||
# Don't touch the rest part
|
||||
set(PROJECT_VERSION "${VER_MAJOR}.${VER_MINOR}.${VER_BUGFIX}")
|
||||
|
||||
if (NOT VER_BUILD EQUAL 0)
|
||||
@@ -28,7 +27,6 @@ add_definitions(-DVERSION_BUILD=${VER_BUILD})
|
||||
# } else {
|
||||
add_definitions(-DVERSION="v${PROJECT_VERSION}")
|
||||
# }
|
||||
list(APPEND CMAKE_MODULE_PATH ${qBittorrent_SOURCE_DIR}/cmake/Modules)
|
||||
|
||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Og")
|
||||
if (UNIX AND NOT APPLE)
|
||||
|
||||
84
Changelog
@@ -1,3 +1,87 @@
|
||||
* Fri Mar 03 2017 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v3.3.11
|
||||
- FEATURE: Always show progress and remaining bytes for unselected files. (sledgehammer999)
|
||||
- FEATURE: Allow to change priority for unselected files through the combobox like it is done via the context menu. (sledgehammer999)
|
||||
- FEATURE: Remove settings to exchange trackers. It wasn't used by non-libtorrent clients. Also it has a privacy risk and you might be DDoSing someone. (sledgehammer999)
|
||||
- FEATURE: Put temp files in .qBittorrent directory. Closes #4462. (Chocobo1)
|
||||
- FEATURE: Use the numbers from tracker scrape response. Closes #5048, #6117. (Chocobo1)
|
||||
- FEATURE: Implement category filter widget. Show categories in tree mode when subcategories are enabled. (glassez)
|
||||
- FEATURE: Allow to toggle columns in searchtab (thalieht)
|
||||
- FEATURE: PeerList: allow to hide zero values for the "uploaded" and "downloaded" columns (thalieht)
|
||||
- FEATURE: Display more information in tracker tab (ngosang)
|
||||
- FEATURE: Use Ctrl+F to search torrents. Closes #5797. (Tim Delaney)
|
||||
- FEATURE: Transferlist: add hotkeys for double click and recheck selected torrents (thalieht)
|
||||
- FEATURE: Add hotkey for execution log tab, Trackerlist, Peerlist etc (thalieht)
|
||||
- FEATURE: Seperate seeds from peers for DHT, PeX and LSD (thalieht)
|
||||
- BUGFIX: Do not remove added files unconditionally. Closes #6248 (Eugene Shalygin)
|
||||
- BUGFIX: Ignore mouse wheel events in Advanced Settings. Closes #866. (Chocobo1)
|
||||
- BUGFIX: Add queue repair code. It should fix missing torrents after restarting. (Eugene Shalygin, nxd4)
|
||||
- BUGFIX: Fetch torrent status when generating final fastresume data. It should fix missing torrents after restarting. (Eugene Shalygin)
|
||||
- BUGFIX: Fix queue overload for add torrent at session start. It should fix missing torrents after restarting. (falco)
|
||||
- BUGFIX: After files relocate, don't remove the old folder even if it is empty. (Chocobo1)
|
||||
- BUGFIX: Fix finding 'English' item in language dropdown menu when an unrecognized locale is requested. Closes #6109. (sledgehammer999)
|
||||
- BUGIFX: Speedlimitdlg: raise slider default value to 10000. Closes #6150. (Chocobo1)
|
||||
- BUGFIX: TransferListWidget: keep columns width even if they are hidden on qBittorrent startup (unless something goes wrong) (thalieht)
|
||||
- BUGFIX: fix index overflow for torrents with invalid meta data or empty progress (Falco)
|
||||
- BUGFIX: Immediately update torrent_status after manipulating super seeding mode. Partially fixes #6072. (sledgehammer999)
|
||||
- BUGFIX: Use case-insensitive comparsion for torrent content window. Closes #6327. (Chocobo1)
|
||||
- BUGFIX: Fixed sort order for datetime columns with empty values (closes #2988) (Vladimir Sinenko)
|
||||
- BUGFIX: Disable proxy in WebUI HTTP server. Closes #6349. (Eugene Shalygin)
|
||||
- COSMETIC: Use a disabled progressbar's palette for unselected files. (sledgehammer999)
|
||||
- COSMETIC: Support fallback when selecting theme icons (Eugene Shalygin)
|
||||
- COSMETIC: Do not resize SVG icons (Eugene Shalygin)
|
||||
- COSMETIC: Align text to the right in columns that handle numbers for PeerList and SearchTab (thalieht)
|
||||
- COSMETIC: Increased number of digits after the decimal point for Gibibytes and above (thalieht)
|
||||
- COSMETIC: Use non-breaking spaces between numbers and units (thalieht)
|
||||
- WEBUI: Fix proxy type bug (Oke Atime)
|
||||
- WEBUI: Use the correct value for KEY_TORRENT_NUM_COMPLETE/KEY_TORRENT_NUM_INCOMPLETE (Chocobo1)
|
||||
- WEBUI: Make torrents table scrollable horizontally (buinsky)
|
||||
- WEBUI: Make torrent peers table scrollable horizontally (buinsky)
|
||||
- WEBUI: Add tooltips to dynamic table header (buinsky)
|
||||
- WEBUI: Implement dynamic table columns resizing, reordering and hiding (buinsky)
|
||||
- WEBUI: Add some missing columns to dynamic tables (buinsky)
|
||||
- WEBUI: Make too tall menus scrollable (buinksy)
|
||||
- WEBUI: Prevent text wrapping in menus (buinsky)
|
||||
- WEBUI: Add a vertical separator between columns (buinsky)
|
||||
- WEBUI: Implement resizable progress bar in "Done" column (buinsky)
|
||||
- WEBUI: Fix scrollbar covers menu item with long text (buinsky)
|
||||
- WEBUI: Remove 300px limit of column width (buinsky)
|
||||
- WEBUI: Avoid lags in firefox on resizing progress column (buinsky)
|
||||
- WEBUI: Fix category in torrent upload. Closes #6260 (ngosang)
|
||||
- WEBUI: Turn off port forwarding of WebUI by default for GUI users (Chocobo1)
|
||||
- WEBUI: Exclude insecure ciphers. Fixes security issues reported by @beardog108 privately. (Chocobo1)
|
||||
- WEBUI: Avoid clickjacking attacks. Fixes security issues reported by @beardog108 privately. (ngosang)
|
||||
- WEBUI: Add X-XSS-Protection, X-Content-Type-Options, CSP header. Fixes security issues reported by @beardog108 privately. (Chocobo1)
|
||||
- WEBUI: Escape various values that might contain injected html. Fixes security issues reported by @beardog108 privately. (Chocobo1)
|
||||
- WEBUI: Bump API_VERSION to 12.
|
||||
- SEARCH: Update extratorrent plugin. Closes #6261 (ngosang)
|
||||
- SEARCH: SearchTab: can now save sorting column changes (thalieht)
|
||||
- SEARCH: Use case-insensitive sort for Name column in Search tab. Closes #407. (Chocobo1)
|
||||
- RSS: Fix tab order in RSS downloader. Closes #6164. (Tim Delaney)
|
||||
- RSS: Move old RSS items to separate config file. Closes #6167. (Tim Delaney)
|
||||
- RSS: Episode filter code refactoring (Tim Delaney)
|
||||
- RSS: Allow resetting rule to no category. Closes #5539. (Tim Delaney)
|
||||
- RSS: Save rule on enable/disable even if not selected. Closes #6163. (Tim Delaney)
|
||||
- RSS: Allow | in RSS must contain. Closes #6171. (Tim Delaney)
|
||||
- RSS: RSS use red text to indicate invalid filter. Closes #6165. (Tim Delaney)
|
||||
- RSS: Allow episode zero (special) and leading zeroes in RSS episode filter. (Tim Delaney)
|
||||
- RSS: RSS parse torrent episodes like 1x01 as well as S01E01. Closes #2749. (Tim Delaney)
|
||||
- RSS: RSS allow infinite range to extend beyond current season. Closes #800, #3876, #6170. (Tim Delaney)
|
||||
- RSS: Improve UI responsiveness during RSS downloading. Closes #873, #1089, #1235, #5423. (Tim Delaney)
|
||||
- RSS: Show name of feed list and sort rules in editor. Closes #3782, #6281. (Tim Delaney)
|
||||
- RSS: Fix regex matching. Closes #6337. (Tim Delaney)
|
||||
- MACOS: Fix qbittorrent-nox build (Oke Atime)
|
||||
- LINUX: fixes default indicator name (Bilal Elmoussaoui)
|
||||
- OTHER: Workaround problem with moc from Qt4 and #if (Eugene Shalygin)
|
||||
- OTHER: Print warning to the user if stacktrace contains no function names (Eugene Shalygin)
|
||||
- OTHER: Various cmake fixes (Eugene Shalygin)
|
||||
- OTHER: Fix finding qmake in configure when cross-compiling (Zach Bacon)
|
||||
|
||||
* Sat Dec 17 2016 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v3.3.10
|
||||
- BUGFIX: Fix share ratio limiting. Broken by commit 259b5e51c49b744. Closes #6039 #6048. (sledgehammer999)
|
||||
- BUGFIX: Case insensitive sort for client column. Closes #6054. (Oke Atime)
|
||||
- BUGFIX: Make resume/pause menu items clickable. Closes #6040. (Oke Atime)
|
||||
- WINDOWS: Make the updater to look for the x64 installer if running x64 version. (sledgehammer999)
|
||||
|
||||
* Wed Dec 14 2016 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v3.3.9
|
||||
- BUGFIX: Fix slider for per torrent speed limits when no global speed limit has been set. Closes #6046. (sledgehammer999)
|
||||
- BUGFIX: Fix GUI for proxy settings. Closes #6045. (sledgehammer999)
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(PkgConfig QUIET)
|
||||
|
||||
macro(_detect_boost_components _outComponets librariesList)
|
||||
string(REGEX MATCHALL "boost_[a-z_]+[-a-z]*" _boost_libraries "${librariesList}")
|
||||
string(REGEX REPLACE "boost_([a-z_]+)[-a-z]*" "\\1" ${_outComponets} "${_boost_libraries}")
|
||||
endmacro()
|
||||
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_LIBTORRENT_RASTERBAR QUIET libtorrent-rasterbar)
|
||||
endif()
|
||||
@@ -62,13 +67,33 @@ endif()
|
||||
set(LibtorrentRasterbar_LIBRARIES ${LibtorrentRasterbar_LIBRARY} ${CMAKE_THREAD_LIBS_INIT})
|
||||
set(LibtorrentRasterbar_INCLUDE_DIRS ${LibtorrentRasterbar_INCLUDE_DIR})
|
||||
|
||||
if(NOT Boost_SYSTEM_FOUND OR NOT Boost_CHRONO_FOUND OR NOT Boost_RANDOM_FOUND)
|
||||
find_package(Boost REQUIRED COMPONENTS date_time system chrono random thread)
|
||||
set(LibtorrentRasterbar_LIBRARIES
|
||||
${LibtorrentRasterbar_LIBRARIES} ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
set(LibtorrentRasterbar_INCLUDE_DIRS
|
||||
${LibtorrentRasterbar_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
|
||||
endif()
|
||||
# Without pkg-config, we can't possibly figure out the correct boost dependencies
|
||||
if (LibtorrentRasterbar_CUSTOM_BOOST_DEPENDENCIES)
|
||||
set(_boost_components "${LibtorrentRasterbar_CUSTOM_BOOST_DEPENDENCIES}")
|
||||
else(LibtorrentRasterbar_CUSTOM_BOOST_DEPENDENCIES)
|
||||
if(PC_LIBTORRENT_RASTERBAR_FOUND)
|
||||
_detect_boost_components(_boost_components "${PC_LIBTORRENT_RASTERBAR_LIBRARIES}")
|
||||
else()
|
||||
# all possible boost dependencies
|
||||
set(_boost_components
|
||||
date_time
|
||||
system
|
||||
chrono
|
||||
random
|
||||
thread
|
||||
)
|
||||
endif()
|
||||
endif(LibtorrentRasterbar_CUSTOM_BOOST_DEPENDENCIES)
|
||||
|
||||
list(SORT _boost_components)
|
||||
message(STATUS "Libtorrent Boost dependencies: ${_boost_components}")
|
||||
find_package(Boost REQUIRED COMPONENTS ${_boost_components})
|
||||
set(LibtorrentRasterbar_LIBRARIES ${LibtorrentRasterbar_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
|
||||
foreach(_boost_cmpnt IN LISTS _boost_components)
|
||||
list(APPEND LibtorrentRasterbar_LIBRARIES "Boost::${_boost_cmpnt}")
|
||||
endforeach(_boost_cmpnt)
|
||||
|
||||
set(LibtorrentRasterbar_INCLUDE_DIRS ${LibtorrentRasterbar_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
|
||||
|
||||
list(FIND LibtorrentRasterbar_DEFINITIONS -DTORRENT_USE_OPENSSL LibtorrentRasterbar_ENCRYPTION_INDEX)
|
||||
if(LibtorrentRasterbar_ENCRYPTION_INDEX GREATER -1)
|
||||
@@ -83,10 +108,7 @@ include(FindPackageHandleStandardArgs)
|
||||
# if all listed variables are TRUE
|
||||
find_package_handle_standard_args(LibtorrentRasterbar DEFAULT_MSG
|
||||
LibtorrentRasterbar_LIBRARY
|
||||
LibtorrentRasterbar_INCLUDE_DIR
|
||||
Boost_SYSTEM_FOUND
|
||||
Boost_CHRONO_FOUND
|
||||
Boost_RANDOM_FOUND)
|
||||
LibtorrentRasterbar_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(LibtorrentRasterbar_INCLUDE_DIR LibtorrentRasterbar_LIBRARY
|
||||
LibtorrentRasterbar_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES
|
||||
|
||||
28
cmake/Modules/FunctionReadVersion.cmake
Normal file
@@ -0,0 +1,28 @@
|
||||
# function for parsing version variables that are set in version.pri file
|
||||
# the version identifiers there are defined as follows:
|
||||
# VER_MAJOR = 3
|
||||
# VER_MINOR = 4
|
||||
# VER_BUGFIX = 0
|
||||
# VER_BUILD = 0
|
||||
# VER_STATUS = alpha
|
||||
|
||||
function(read_version priFile outMajor outMinor outBugfix outBuild outStatus)
|
||||
file(STRINGS ${priFile} _priFileContents REGEX "^VER_.+")
|
||||
# message(STATUS "version.pri version contents: ${_priFileContents}")
|
||||
# the _priFileContents variable contains something like the following:
|
||||
# VER_MAJOR = 3;VER_MINOR = 4;VER_BUGFIX = 0;VER_BUILD = 0;VER_STATUS = alpha # Should be empty for stable releases!
|
||||
set(_regex "VER_MAJOR += +([0-9]+);VER_MINOR += +([0-9]+);VER_BUGFIX += +([0-9]+);VER_BUILD += +([0-9]+);VER_STATUS += +([0-9A-Za-z]+)?")
|
||||
# note quotes around _regex, they are needed because the variable contains semicolons
|
||||
string(REGEX MATCH "${_regex}" _tmp "${_priFileContents}")
|
||||
if (NOT _tmp)
|
||||
message(FATAL_ERROR "Could not detect project version number from ${priFile}")
|
||||
endif()
|
||||
|
||||
# message(STATUS "Matched version string: ${_tmp}")
|
||||
|
||||
set(${outMajor} ${CMAKE_MATCH_1} PARENT_SCOPE)
|
||||
set(${outMinor} ${CMAKE_MATCH_2} PARENT_SCOPE)
|
||||
set(${outBugfix} ${CMAKE_MATCH_3} PARENT_SCOPE)
|
||||
set(${outBuild} ${CMAKE_MATCH_4} PARENT_SCOPE)
|
||||
set(${outStatus} ${CMAKE_MATCH_5} PARENT_SCOPE)
|
||||
endfunction()
|
||||
@@ -5,11 +5,23 @@
|
||||
macro (target_link_qt_components target)
|
||||
if (QT4_FOUND)
|
||||
foreach(_cmp ${ARGN})
|
||||
list(APPEND _QT_CMPNTS "Qt4::Qt${_cmp}")
|
||||
if ("${_cmp}" STREQUAL "PRIVATE" OR
|
||||
"${_cmp}" STREQUAL "PUBLIC" OR
|
||||
"${_cmp}" STREQUAL "INTERFACE")
|
||||
list(APPEND _QT_CMPNTS "${_cmp}")
|
||||
else()
|
||||
list(APPEND _QT_CMPNTS "Qt4::Qt${_cmp}")
|
||||
endif()
|
||||
endforeach()
|
||||
else (QT4_FOUND)
|
||||
foreach(_cmp ${ARGN})
|
||||
list(APPEND _QT_CMPNTS "Qt5::${_cmp}")
|
||||
if ("${_cmp}" STREQUAL "PRIVATE" OR
|
||||
"${_cmp}" STREQUAL "PUBLIC" OR
|
||||
"${_cmp}" STREQUAL "INTERFACE")
|
||||
list(APPEND _QT_CMPNTS "${_cmp}")
|
||||
else()
|
||||
list(APPEND _QT_CMPNTS "Qt5::${_cmp}")
|
||||
endif()
|
||||
endforeach()
|
||||
endif (QT4_FOUND)
|
||||
target_link_libraries(${target} ${_QT_CMPNTS})
|
||||
|
||||
@@ -9,7 +9,10 @@ set(LibtorrentRasterbar_CUSTOM_DEFINITIONS
|
||||
-DBOOST_EXCEPTION_DISABLE
|
||||
-DBOOST_SYSTEM_STATIC_LINK=1
|
||||
-DTORRENT_USE_OPENSSL
|
||||
-DUNICODE
|
||||
-D__USE_W32_SOCKETS
|
||||
-D_FILE_OFFSET_BITS=64)
|
||||
|
||||
add_definitions(-DUNICODE
|
||||
-D_UNICODE
|
||||
-DWIN32
|
||||
-D_WIN32
|
||||
@@ -18,9 +21,7 @@ set(LibtorrentRasterbar_CUSTOM_DEFINITIONS
|
||||
-D_WIN32_IE=0x0500
|
||||
-D_CRT_SECURE_NO_DEPRECATE
|
||||
-D_SCL_SECURE_NO_DEPRECATE
|
||||
-D__USE_W32_SOCKETS
|
||||
-D_FILE_OFFSET_BITS=64)
|
||||
|
||||
)
|
||||
# and boost
|
||||
set(Boost_USE_STATIC_LIBS True)
|
||||
# set(Boost_USE_STATIC_RUNTIME True)
|
||||
@@ -29,16 +30,17 @@ set(Boost_USE_STATIC_LIBS True)
|
||||
# with usual unix subdirectories (bin, lib, include)
|
||||
# if so, we just need to set CMAKE_SYSTEM_PREFIX_PATH
|
||||
# If it is not the case, individual paths need to be specified manually (see below)
|
||||
set(COMMON_INSTALL_PREFIX "c:/usr")
|
||||
set(COMMON_INSTALL_PREFIX "c:/usr" CACHE PATH "Prefix used to install all the required libraries")
|
||||
list(APPEND CMAKE_SYSTEM_PREFIX_PATH "${COMMON_INSTALL_PREFIX}")
|
||||
|
||||
# If two version of Qt are installed, separate prefixes are needed most likely
|
||||
set(QT4_INSTALL_PREFIX "${COMMON_INSTALL_PREFIX}/lib/qt4")
|
||||
set(QT5_INSTALL_PREFIX "${COMMON_INSTALL_PREFIX}/lib/qt5")
|
||||
set(QT4_INSTALL_PREFIX "${COMMON_INSTALL_PREFIX}/lib/qt4" CACHE PATH "Prefix where Qt4 is installed")
|
||||
set(QT5_INSTALL_PREFIX "${COMMON_INSTALL_PREFIX}/lib/qt5" CACHE PATH "Prefix where Qt5 is installed")
|
||||
|
||||
# it is safe to set Qt dirs even if their files are directly in the prefix
|
||||
# Qt4
|
||||
if(NOT QT5)
|
||||
# for qt 4 we need qmake, Qt5 provides cmake config files
|
||||
LIST(APPEND CMAKE_PROGRAM_PATH "${QT4_INSTALL_PREFIX}/bin/")
|
||||
endif(NOT QT5)
|
||||
|
||||
|
||||
85
configure
vendored
@@ -4504,53 +4504,17 @@ fi
|
||||
|
||||
fi
|
||||
|
||||
as_ac_File=`$as_echo "ac_cv_file_$QT_QMAKE/qmake" | $as_tr_sh`
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $QT_QMAKE/qmake" >&5
|
||||
$as_echo_n "checking for $QT_QMAKE/qmake... " >&6; }
|
||||
if eval \${$as_ac_File+:} false; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
test "$cross_compiling" = yes &&
|
||||
as_fn_error $? "cannot check for file existence when cross compiling" "$LINENO" 5
|
||||
if test -r "$QT_QMAKE/qmake"; then
|
||||
eval "$as_ac_File=yes"
|
||||
else
|
||||
eval "$as_ac_File=no"
|
||||
fi
|
||||
fi
|
||||
eval ac_res=\$$as_ac_File
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
|
||||
$as_echo "$ac_res" >&6; }
|
||||
if eval test \"x\$"$as_ac_File"\" = x"yes"; then :
|
||||
if test -f "$QT_QMAKE/qmake"; then :
|
||||
QT_QMAKE="$QT_QMAKE/qmake"
|
||||
else
|
||||
as_ac_File=`$as_echo "ac_cv_file_$QT_QMAKE/qmake-qt5" | $as_tr_sh`
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $QT_QMAKE/qmake-qt5" >&5
|
||||
$as_echo_n "checking for $QT_QMAKE/qmake-qt5... " >&6; }
|
||||
if eval \${$as_ac_File+:} false; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
test "$cross_compiling" = yes &&
|
||||
as_fn_error $? "cannot check for file existence when cross compiling" "$LINENO" 5
|
||||
if test -r "$QT_QMAKE/qmake-qt5"; then
|
||||
eval "$as_ac_File=yes"
|
||||
else
|
||||
eval "$as_ac_File=no"
|
||||
fi
|
||||
fi
|
||||
eval ac_res=\$$as_ac_File
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
|
||||
$as_echo "$ac_res" >&6; }
|
||||
if eval test \"x\$"$as_ac_File"\" = x"yes"; then :
|
||||
if test -f "$QT_QMAKE/qmake-qt5"; then :
|
||||
QT_QMAKE="$QT_QMAKE/qmake-qt5"
|
||||
else
|
||||
QT_QMAKE=""
|
||||
fi
|
||||
|
||||
|
||||
fi
|
||||
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Qt5 qmake >= 5.2.0" >&5
|
||||
$as_echo_n "checking for Qt5 qmake >= 5.2.0... " >&6; }
|
||||
if test "x$QT_QMAKE" != "x"; then :
|
||||
@@ -4621,53 +4585,17 @@ fi
|
||||
|
||||
fi
|
||||
|
||||
as_ac_File=`$as_echo "ac_cv_file_$QT_QMAKE/qmake" | $as_tr_sh`
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $QT_QMAKE/qmake" >&5
|
||||
$as_echo_n "checking for $QT_QMAKE/qmake... " >&6; }
|
||||
if eval \${$as_ac_File+:} false; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
test "$cross_compiling" = yes &&
|
||||
as_fn_error $? "cannot check for file existence when cross compiling" "$LINENO" 5
|
||||
if test -r "$QT_QMAKE/qmake"; then
|
||||
eval "$as_ac_File=yes"
|
||||
else
|
||||
eval "$as_ac_File=no"
|
||||
fi
|
||||
fi
|
||||
eval ac_res=\$$as_ac_File
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
|
||||
$as_echo "$ac_res" >&6; }
|
||||
if eval test \"x\$"$as_ac_File"\" = x"yes"; then :
|
||||
if test -f "$QT_QMAKE/qmake"; then :
|
||||
QT_QMAKE="$QT_QMAKE/qmake"
|
||||
else
|
||||
as_ac_File=`$as_echo "ac_cv_file_$QT_QMAKE/qmake-qt4" | $as_tr_sh`
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $QT_QMAKE/qmake-qt4" >&5
|
||||
$as_echo_n "checking for $QT_QMAKE/qmake-qt4... " >&6; }
|
||||
if eval \${$as_ac_File+:} false; then :
|
||||
$as_echo_n "(cached) " >&6
|
||||
else
|
||||
test "$cross_compiling" = yes &&
|
||||
as_fn_error $? "cannot check for file existence when cross compiling" "$LINENO" 5
|
||||
if test -r "$QT_QMAKE/qmake-qt4"; then
|
||||
eval "$as_ac_File=yes"
|
||||
else
|
||||
eval "$as_ac_File=no"
|
||||
fi
|
||||
fi
|
||||
eval ac_res=\$$as_ac_File
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
|
||||
$as_echo "$ac_res" >&6; }
|
||||
if eval test \"x\$"$as_ac_File"\" = x"yes"; then :
|
||||
if test -f "$QT_QMAKE/qmake-qt4"; then :
|
||||
QT_QMAKE="$QT_QMAKE/qmake-qt4"
|
||||
else
|
||||
QT_QMAKE=""
|
||||
fi
|
||||
|
||||
|
||||
fi
|
||||
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for Qt4 qmake >= 4.8.0" >&5
|
||||
$as_echo_n "checking for Qt4 qmake >= 4.8.0... " >&6; }
|
||||
if test "x$QT_QMAKE" != "x"; then :
|
||||
@@ -5044,8 +4972,8 @@ fi
|
||||
if test "x$BOOST_CPPFLAGS" = "x"; then :
|
||||
as_fn_error $? "Could not find Boost" "$LINENO" 5
|
||||
else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: Boost CPPFLGAS: $BOOST_CPPFLAGS" >&5
|
||||
$as_echo "$as_me: Boost CPPFLGAS: $BOOST_CPPFLAGS" >&6;}
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: Boost CPPFLAGS: $BOOST_CPPFLAGS" >&5
|
||||
$as_echo "$as_me: Boost CPPFLAGS: $BOOST_CPPFLAGS" >&6;}
|
||||
CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS"
|
||||
LDFLAGS="$BOOST_LDFLAGS $LDFLAGS"
|
||||
fi
|
||||
@@ -5094,6 +5022,7 @@ ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ex
|
||||
ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
|
||||
|
||||
CXXFLAGS_SAVE=$CXXFLAGS
|
||||
CXXFLAGS=
|
||||
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
@@ -162,7 +162,7 @@ AX_BOOST_BASE([1.35])
|
||||
# how to test for a set vs unset variable.
|
||||
AS_IF([test "x$BOOST_CPPFLAGS" = "x"],
|
||||
[AC_MSG_ERROR([Could not find Boost])],
|
||||
[AC_MSG_NOTICE([Boost CPPFLGAS: $BOOST_CPPFLAGS])
|
||||
[AC_MSG_NOTICE([Boost CPPFLAGS: $BOOST_CPPFLAGS])
|
||||
CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS"
|
||||
LDFLAGS="$BOOST_LDFLAGS $LDFLAGS"])
|
||||
|
||||
|
||||
8
dist/mac/Info.plist
vendored
@@ -37,7 +37,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleName</key>
|
||||
<string>qBittorrent</string>
|
||||
<string>@EXECUTABLE@</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>qbittorrent_mac.icns</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
@@ -45,11 +45,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.3.9</string>
|
||||
<string>3.3.11</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>qBit</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>qbittorrent</string>
|
||||
<string>@EXECUTABLE@</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.qbittorrent</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
@@ -59,7 +59,7 @@
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<string>YES</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2006-2016 The qBittorrent project</string>
|
||||
<string>Copyright © 2006-2017 The qBittorrent project</string>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
||||
BIN
dist/unix/menuicons/128x128/status/qbittorrent-tray.png
vendored
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
dist/unix/menuicons/16x16/status/qbittorrent-tray.png
vendored
Normal file
|
After Width: | Height: | Size: 893 B |
BIN
dist/unix/menuicons/192x192/status/qbittorrent-tray.png
vendored
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
dist/unix/menuicons/22x22/status/qbittorrent-tray.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
dist/unix/menuicons/24x24/status/qbittorrent-tray.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
dist/unix/menuicons/32x32/status/qbittorrent-tray.png
vendored
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
dist/unix/menuicons/36x36/status/qbittorrent-tray.png
vendored
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
dist/unix/menuicons/48x48/status/qbittorrent-tray.png
vendored
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
dist/unix/menuicons/64x64/status/qbittorrent-tray.png
vendored
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
dist/unix/menuicons/72x72/status/qbittorrent-tray.png
vendored
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
dist/unix/menuicons/96x96/status/qbittorrent-tray.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
4
dist/windows/options.nsi
vendored
@@ -19,7 +19,7 @@ XPStyle on
|
||||
!define CSIDL_APPDATA '0x1A' ;Application Data path
|
||||
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
|
||||
|
||||
!define PROG_VERSION "3.3.9"
|
||||
!define PROG_VERSION "3.3.11"
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
||||
!define MUI_FINISHPAGE_RUN_TEXT $(launch_qbt)
|
||||
@@ -33,7 +33,7 @@ OutFile "qbittorrent_${PROG_VERSION}_setup.exe"
|
||||
;Installer Version Information
|
||||
VIAddVersionKey "ProductName" "qBittorrent"
|
||||
VIAddVersionKey "CompanyName" "The qBittorrent project"
|
||||
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2016 The qBittorrent project"
|
||||
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2017 The qBittorrent project"
|
||||
VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client"
|
||||
VIAddVersionKey "FileVersion" "${PROG_VERSION}"
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ AC_DEFUN([FIND_QT4],
|
||||
[QT_QMAKE=`AS_DIRNAME(["$QT_QMAKE"])`])
|
||||
])
|
||||
|
||||
AC_CHECK_FILE([$QT_QMAKE/qmake],
|
||||
AS_IF([test -f "$QT_QMAKE/qmake"],
|
||||
[QT_QMAKE="$QT_QMAKE/qmake"],
|
||||
[AC_CHECK_FILE([$QT_QMAKE/qmake-qt4],
|
||||
[AS_IF([test -f "$QT_QMAKE/qmake-qt4"],
|
||||
[QT_QMAKE="$QT_QMAKE/qmake-qt4"],
|
||||
[QT_QMAKE=""])
|
||||
])
|
||||
@@ -36,9 +36,9 @@ AC_DEFUN([FIND_QT5],
|
||||
[host_bins])
|
||||
])
|
||||
|
||||
AC_CHECK_FILE([$QT_QMAKE/qmake],
|
||||
AS_IF([test -f "$QT_QMAKE/qmake"],
|
||||
[QT_QMAKE="$QT_QMAKE/qmake"],
|
||||
[AC_CHECK_FILE([$QT_QMAKE/qmake-qt5],
|
||||
[AS_IF([test -f "$QT_QMAKE/qmake-qt5"],
|
||||
[QT_QMAKE="$QT_QMAKE/qmake-qt5"],
|
||||
[QT_QMAKE=""])
|
||||
])
|
||||
|
||||
@@ -161,10 +161,15 @@ endif (GUI AND WIN32)
|
||||
target_link_libraries(${QBT_TARGET_NAME} ${QBT_TARGET_LIBRARIES} QtSingleApplication::QtSingleApplication)
|
||||
|
||||
if (APPLE)
|
||||
set(qbt_BUNDLE_NAME "${CMAKE_PROJECT_NAME}")
|
||||
set(qbt_BUNDLE_NAME "${QBT_TARGET_NAME}")
|
||||
|
||||
# substitute @EXECUTABLE@ in dist/mac/Info.plist
|
||||
set(EXECUTABLE ${qbt_BUNDLE_NAME})
|
||||
configure_file(${qBittorrent_SOURCE_DIR}/dist/mac/Info.plist ${qBittorrent_BINARY_DIR}/dist/mac/Info.plist @ONLY)
|
||||
|
||||
set_target_properties(${QBT_TARGET_NAME} PROPERTIES
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "${qbt_BUNDLE_NAME}"
|
||||
MACOSX_BUNDLE_INFO_PLIST ${qBittorrent_SOURCE_DIR}/dist/mac/Info.plist
|
||||
MACOSX_BUNDLE_INFO_PLIST ${qBittorrent_BINARY_DIR}/dist/mac/Info.plist
|
||||
)
|
||||
endif (APPLE)
|
||||
|
||||
|
||||
@@ -511,36 +511,27 @@ void Application::initializeTranslation()
|
||||
{
|
||||
Preferences* const pref = Preferences::instance();
|
||||
// Load translation
|
||||
QString locale = pref->getLocale();
|
||||
QString localeStr = pref->getLocale();
|
||||
QLocale::setDefault(QLocale(localeStr));
|
||||
|
||||
if (locale.isEmpty()) {
|
||||
locale = QLocale::system().name();
|
||||
pref->setLocale(locale);
|
||||
}
|
||||
|
||||
if (m_qtTranslator.load(
|
||||
if (
|
||||
#ifdef QBT_USES_QT5
|
||||
QString::fromUtf8("qtbase_") + locale, QLibraryInfo::location(QLibraryInfo::TranslationsPath)) ||
|
||||
m_qtTranslator.load(
|
||||
m_qtTranslator.load(QString::fromUtf8("qtbase_") + localeStr, QLibraryInfo::location(QLibraryInfo::TranslationsPath)) ||
|
||||
#endif
|
||||
QString::fromUtf8("qt_") + locale, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
|
||||
qDebug("Qt %s locale recognized, using translation.", qPrintable(locale));
|
||||
}
|
||||
else {
|
||||
qDebug("Qt %s locale unrecognized, using default (en).", qPrintable(locale));
|
||||
}
|
||||
m_qtTranslator.load(QString::fromUtf8("qt_") + localeStr, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
|
||||
qDebug("Qt %s locale recognized, using translation.", qPrintable(localeStr));
|
||||
else
|
||||
qDebug("Qt %s locale unrecognized, using default (en).", qPrintable(localeStr));
|
||||
installTranslator(&m_qtTranslator);
|
||||
|
||||
if (m_translator.load(QString::fromUtf8(":/lang/qbittorrent_") + locale)) {
|
||||
qDebug("%s locale recognized, using translation.", qPrintable(locale));
|
||||
}
|
||||
else {
|
||||
qDebug("%s locale unrecognized, using default (en).", qPrintable(locale));
|
||||
}
|
||||
if (m_translator.load(QString::fromUtf8(":/lang/qbittorrent_") + localeStr))
|
||||
qDebug("%s locale recognized, using translation.", qPrintable(localeStr));
|
||||
else
|
||||
qDebug("%s locale unrecognized, using default (en).", qPrintable(localeStr));
|
||||
installTranslator(&m_translator);
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
if (locale.startsWith("ar") || locale.startsWith("he")) {
|
||||
if (localeStr.startsWith("ar") || localeStr.startsWith("he")) {
|
||||
qDebug("Right to Left mode");
|
||||
setLayoutDirection(Qt::RightToLeft);
|
||||
}
|
||||
@@ -629,6 +620,7 @@ void Application::cleanup()
|
||||
delete m_fileLogger;
|
||||
Logger::freeInstance();
|
||||
IconProvider::freeInstance();
|
||||
Utils::Fs::removeDirRecursive(Utils::Fs::tempPath());
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
@@ -141,6 +141,10 @@ int main(int argc, char *argv[])
|
||||
macMigratePlists();
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
migrateRSS();
|
||||
#endif
|
||||
|
||||
// Create Application
|
||||
QString appId = QLatin1String("qBittorrent-") + Utils::Misc::getUserIDString();
|
||||
QScopedPointer<Application> app(new Application(appId, argc, argv));
|
||||
|
||||
@@ -15,78 +15,86 @@ static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames
|
||||
fprintf(out, "stack trace:\n");
|
||||
|
||||
// storage array for stack trace address data
|
||||
void* addrlist[max_frames+1];
|
||||
void *addrlist[max_frames + 1];
|
||||
|
||||
// retrieve current stack addresses
|
||||
int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));
|
||||
int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void *));
|
||||
|
||||
if (addrlen == 0) {
|
||||
fprintf(out, " <empty, possibly corrupt>\n");
|
||||
return;
|
||||
fprintf(out, " <empty, possibly corrupt>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// resolve addresses into strings containing "filename(function+address)",
|
||||
// this array must be free()-ed
|
||||
char** symbollist = backtrace_symbols(addrlist, addrlen);
|
||||
char * *symbollist = backtrace_symbols(addrlist, addrlen);
|
||||
|
||||
// allocate string which will be filled with the demangled function name
|
||||
size_t funcnamesize = 256;
|
||||
char* funcname = (char*)malloc(funcnamesize);
|
||||
char *funcname = (char *)malloc(funcnamesize);
|
||||
|
||||
int functionNamesFound = 0;
|
||||
// iterate over the returned symbol lines. skip the first, it is the
|
||||
// address of this function.
|
||||
for (int i = 2; i < addrlen; i++)
|
||||
{
|
||||
char *begin_name = 0, *begin_offset = 0, *end_offset = 0;
|
||||
for (int i = 2; i < addrlen; i++) {
|
||||
char *begin_name = 0, *begin_offset = 0, *end_offset = 0;
|
||||
|
||||
// find parentheses and +address offset surrounding the mangled name:
|
||||
// ./module(function+0x15c) [0x8048a6d]
|
||||
//fprintf(out, "%s TT\n", symbollist[i]);
|
||||
for (char *p = symbollist[i]; *p; ++p)
|
||||
{
|
||||
if (*p == '(')
|
||||
begin_name = p;
|
||||
else if (*p == '+')
|
||||
begin_offset = p;
|
||||
else if (*p == ')' && begin_offset) {
|
||||
end_offset = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// find parentheses and +address offset surrounding the mangled name:
|
||||
// ./module(function+0x15c) [0x8048a6d]
|
||||
// fprintf(out, "%s TT\n", symbollist[i]);
|
||||
for (char *p = symbollist[i]; *p; ++p) {
|
||||
if (*p == '(') {
|
||||
begin_name = p;
|
||||
}
|
||||
else if (*p == '+') {
|
||||
begin_offset = p;
|
||||
}
|
||||
else if ((*p == ')') && begin_offset) {
|
||||
end_offset = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (begin_name && begin_offset && end_offset
|
||||
&& begin_name < begin_offset)
|
||||
{
|
||||
*begin_name++ = '\0';
|
||||
*begin_offset++ = '\0';
|
||||
*end_offset = '\0';
|
||||
if (begin_name && begin_offset && end_offset
|
||||
&& (begin_name < begin_offset)) {
|
||||
*begin_name++ = '\0';
|
||||
*begin_offset++ = '\0';
|
||||
*end_offset = '\0';
|
||||
|
||||
// mangled name is now in [begin_name, begin_offset) and caller
|
||||
// offset in [begin_offset, end_offset). now apply
|
||||
// __cxa_demangle():
|
||||
// mangled name is now in [begin_name, begin_offset) and caller
|
||||
// offset in [begin_offset, end_offset). now apply
|
||||
// __cxa_demangle():
|
||||
|
||||
int status;
|
||||
char* ret = abi::__cxa_demangle(begin_name,
|
||||
funcname, &funcnamesize, &status);
|
||||
if (status == 0) {
|
||||
funcname = ret; // use possibly realloc()-ed string
|
||||
fprintf(out, " %s : %s+%s %s\n",
|
||||
symbollist[i], funcname, begin_offset, ++end_offset);
|
||||
}
|
||||
else {
|
||||
// demangling failed. Output function name as a C function with
|
||||
// no arguments.
|
||||
fprintf(out, " %s : %s()+%s %s\n",
|
||||
symbollist[i], begin_name, begin_offset, ++end_offset);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// couldn't parse the line? print the whole line.
|
||||
fprintf(out, " %s\n", symbollist[i]);
|
||||
}
|
||||
int status;
|
||||
char *ret = abi::__cxa_demangle(begin_name,
|
||||
funcname, &funcnamesize, &status);
|
||||
if (status == 0) {
|
||||
funcname = ret; // use possibly realloc()-ed string
|
||||
fprintf(out, " %s : %s+%s %s\n",
|
||||
symbollist[i], funcname, begin_offset, ++end_offset);
|
||||
}
|
||||
else {
|
||||
// demangling failed. Output function name as a C function with
|
||||
// no arguments.
|
||||
fprintf(out, " %s : %s()+%s %s\n",
|
||||
symbollist[i], begin_name, begin_offset, ++end_offset);
|
||||
}
|
||||
++functionNamesFound;
|
||||
}
|
||||
else {
|
||||
// couldn't parse the line? print the whole line.
|
||||
fprintf(out, " %s\n", symbollist[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!functionNamesFound) {
|
||||
fprintf(out, "There were no function names found in the stack trace\n."
|
||||
"Seems like debug symbols are not installed, and the stack trace is useless.\n");
|
||||
}
|
||||
if (functionNamesFound < addrlen - 2) {
|
||||
fprintf(out, "Consider installing debug symbols for packages containing files with empty"
|
||||
" function names (i.e. empty braces \"()\") to make your stack trace more useful\n");
|
||||
}
|
||||
free(funcname);
|
||||
free(symbollist);
|
||||
}
|
||||
|
||||
@@ -228,7 +228,6 @@ bool upgrade(bool ask = true)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
void migratePlistToIni(const QString &application)
|
||||
{
|
||||
@@ -257,5 +256,22 @@ void macMigratePlists()
|
||||
}
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
void migrateRSS()
|
||||
{
|
||||
// Copy old feed items to new file if needed
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss-feeds");
|
||||
if (!qBTRSS.allKeys().isEmpty()) return; // We move the contents of RSS old_items only if inifile does not exist (is empty).
|
||||
|
||||
QIniSettings qBTRSSLegacy("qBittorrent", "qBittorrent-rss");
|
||||
QHash<QString, QVariant> allOldItems = qBTRSSLegacy.value("old_items", QHash<QString, QVariant>()).toHash();
|
||||
|
||||
if (!allOldItems.empty()) {
|
||||
qDebug("Moving %d old items for feeds to qBittorrent-rss-feeds", allOldItems.size());
|
||||
qBTRSS.setValue("old_items", allOldItems);
|
||||
qBTRSSLegacy.remove("old_items");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // UPGRADE_H
|
||||
|
||||
@@ -117,24 +117,24 @@ tristatebool.cpp
|
||||
)
|
||||
|
||||
add_library(qbt_base STATIC ${QBT_BASE_HEADERS} ${QBT_BASE_SOURCES})
|
||||
target_link_libraries(qbt_base ZLIB::ZLIB LibtorrentRasterbar::LibTorrent)
|
||||
target_link_qt_components(qbt_base Core Network Xml)
|
||||
target_link_libraries(qbt_base PRIVATE ZLIB::ZLIB PUBLIC LibtorrentRasterbar::LibTorrent)
|
||||
target_link_qt_components(qbt_base PUBLIC Core Network Xml)
|
||||
if (QT4_FOUND)
|
||||
if (GUI)
|
||||
target_link_libraries(qbt_base Qt4::QtGui)
|
||||
target_link_libraries(qbt_base PUBLIC Qt4::QtGui)
|
||||
endif (GUI)
|
||||
else (QT4_FOUND)
|
||||
if (GUI)
|
||||
target_link_libraries(qbt_base Qt5::Gui Qt5::Widgets)
|
||||
target_link_libraries(qbt_base PUBLIC Qt5::Gui Qt5::Widgets)
|
||||
endif (GUI)
|
||||
endif (QT4_FOUND)
|
||||
|
||||
if (DBUS)
|
||||
target_link_qt_components(qbt_base DBus)
|
||||
target_link_qt_components(qbt_base PRIVATE DBus)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
find_library(IOKit_LIBRARY IOKit)
|
||||
find_library(Carbon_LIBRARY Carbon)
|
||||
target_link_libraries(qbt_base ${Carbon_LIBRARY} ${IOKit_LIBRARY})
|
||||
target_link_libraries(qbt_base PRIVATE ${Carbon_LIBRARY} ${IOKit_LIBRARY})
|
||||
endif (APPLE)
|
||||
|
||||
@@ -49,11 +49,6 @@ class FilterParserThread : public QThread
|
||||
public:
|
||||
FilterParserThread(libtorrent::session *s, QObject *parent = 0);
|
||||
~FilterParserThread();
|
||||
|
||||
int parseDATFilterFile(QString filePath, libtorrent::ip_filter &filter);
|
||||
int parseP2PFilterFile(QString filePath, libtorrent::ip_filter &filter);
|
||||
int getlineInStream(QDataStream &stream, std::string &name, char delim);
|
||||
int parseP2BFilterFile(QString filePath, libtorrent::ip_filter &filter);
|
||||
void processFilterFile(QString filePath);
|
||||
|
||||
signals:
|
||||
@@ -65,6 +60,11 @@ protected:
|
||||
void run();
|
||||
|
||||
private:
|
||||
int parseDATFilterFile(QString filePath, libtorrent::ip_filter &filter);
|
||||
int parseP2PFilterFile(QString filePath, libtorrent::ip_filter &filter);
|
||||
int getlineInStream(QDataStream &stream, std::string &name, char delim);
|
||||
int parseP2BFilterFile(QString filePath, libtorrent::ip_filter &filter);
|
||||
|
||||
libtorrent::session *m_session;
|
||||
bool m_abort;
|
||||
QString m_filePath;
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/error_code.hpp>
|
||||
#include <libtorrent/extensions/ut_metadata.hpp>
|
||||
#include <libtorrent/extensions/lt_trackers.hpp>
|
||||
#include <libtorrent/extensions/ut_pex.hpp>
|
||||
#include <libtorrent/extensions/smart_ban.hpp>
|
||||
#include <libtorrent/identify_client.hpp>
|
||||
@@ -213,7 +212,6 @@ Session::Session(QObject *parent)
|
||||
, m_isDHTEnabled(BITTORRENT_SESSION_KEY("DHTEnabled"), true)
|
||||
, m_isLSDEnabled(BITTORRENT_SESSION_KEY("LSDEnabled"), true)
|
||||
, m_isPeXEnabled(BITTORRENT_SESSION_KEY("PeXEnabled"), true)
|
||||
, m_isTrackerExchangeEnabled(BITTORRENT_SESSION_KEY("TrackerExchangeEnabled"), false)
|
||||
, m_isIPFilteringEnabled(BITTORRENT_SESSION_KEY("IPFilteringEnabled"), false)
|
||||
, m_isTrackerFilteringEnabled(BITTORRENT_SESSION_KEY("TrackerFilteringEnabled"), false)
|
||||
, m_IPFilterFile(BITTORRENT_SESSION_KEY("IPFilter"))
|
||||
@@ -278,7 +276,6 @@ Session::Session(QObject *parent)
|
||||
, m_isTrackerEnabled(BITTORRENT_KEY("TrackerEnabled"), false)
|
||||
, m_bannedIPs("State/BannedIPs")
|
||||
, m_wasPexEnabled(m_isPeXEnabled)
|
||||
, m_wasTrackerExchangeEnabled(m_isTrackerExchangeEnabled)
|
||||
, m_numResumeData(0)
|
||||
, m_extraLimit(0)
|
||||
, m_useProxy(false)
|
||||
@@ -365,8 +362,6 @@ Session::Session(QObject *parent)
|
||||
// Enabling plugins
|
||||
//m_nativeSession->add_extension(&libt::create_metadata_plugin);
|
||||
m_nativeSession->add_extension(&libt::create_ut_metadata_plugin);
|
||||
if (isTrackerExchangeEnabled())
|
||||
m_nativeSession->add_extension(&libt::create_lt_trackers_plugin);
|
||||
if (isPeXEnabled())
|
||||
m_nativeSession->add_extension(&libt::create_ut_pex_plugin);
|
||||
m_nativeSession->add_extension(&libt::create_smart_ban_plugin);
|
||||
@@ -475,18 +470,6 @@ void Session::setPeXEnabled(bool enabled)
|
||||
Logger::instance()->addMessage(tr("Restart is required to toggle PeX support"), Log::WARNING);
|
||||
}
|
||||
|
||||
bool Session::isTrackerExchangeEnabled() const
|
||||
{
|
||||
return m_isTrackerExchangeEnabled;
|
||||
}
|
||||
|
||||
void Session::setTrackerExchangeEnabled(bool enabled)
|
||||
{
|
||||
m_isTrackerExchangeEnabled = enabled;
|
||||
if (m_wasTrackerExchangeEnabled != enabled)
|
||||
Logger::instance()->addMessage(tr("Restart is required to toggle Tracker Exchange support"), Log::WARNING);
|
||||
}
|
||||
|
||||
bool Session::isTempPathEnabled() const
|
||||
{
|
||||
return m_isTempPathEnabled;
|
||||
@@ -1056,6 +1039,11 @@ void Session::configure(libtorrent::settings_pack &settingsPack)
|
||||
settingsPack.set_int(libt::settings_pack::active_tracker_limit, -1);
|
||||
settingsPack.set_int(libt::settings_pack::active_dht_limit, -1);
|
||||
settingsPack.set_int(libt::settings_pack::active_lsd_limit, -1);
|
||||
// 1 active torrent force 2 connections. If you have more active torrents * 2 than connection limit,
|
||||
// connection limit will get extended. Multiply max connections or active torrents by 10 for queue.
|
||||
// Ignore -1 values because we don't want to set a max int message queue
|
||||
settingsPack.set_int(libt::settings_pack::alert_queue_size, std::max(1000,
|
||||
10 * std::max(maxActiveTorrents() * 2, maxConnections())));
|
||||
|
||||
// Outgoing ports
|
||||
settingsPack.set_int(libt::settings_pack::outgoing_port, outgoingPortsMin());
|
||||
@@ -1197,6 +1185,11 @@ void Session::configure(libtorrent::session_settings &sessionSettings)
|
||||
sessionSettings.active_tracker_limit = -1;
|
||||
sessionSettings.active_dht_limit = -1;
|
||||
sessionSettings.active_lsd_limit = -1;
|
||||
// 1 active torrent force 2 connections. If you have more active torrents * 2 than connection limit,
|
||||
// connection limit will get extended. Multiply max connections or active torrents by 10 for queue.
|
||||
// Ignore -1 values because we don't want to set a max int message queue
|
||||
sessionSettings.alert_queue_size = std::max(1000,
|
||||
10 * std::max(maxActiveTorrents() * 2, maxConnections()));
|
||||
|
||||
// Outgoing ports
|
||||
sessionSettings.outgoing_ports = std::make_pair(outgoingPortsMin(), outgoingPortsMax());
|
||||
@@ -1292,7 +1285,8 @@ void Session::processBigRatios()
|
||||
qreal globalMaxRatio = this->globalMaxRatio();
|
||||
foreach (TorrentHandle *const torrent, m_torrents) {
|
||||
if (torrent->isSeed()
|
||||
&& ((torrent->ratioLimit() != TorrentHandle::NO_RATIO_LIMIT) || !torrent->isForced())) {
|
||||
&& (torrent->ratioLimit() != TorrentHandle::NO_RATIO_LIMIT)
|
||||
&& !torrent->isForced()) {
|
||||
const qreal ratio = torrent->realRatio();
|
||||
qreal ratioLimit = torrent->ratioLimit();
|
||||
if (ratioLimit == TorrentHandle::USE_GLOBAL_RATIO) {
|
||||
@@ -1575,8 +1569,10 @@ bool Session::addTorrent(QString source, const AddTorrentParams ¶ms)
|
||||
}
|
||||
else {
|
||||
TorrentFileGuard guard(source);
|
||||
guard.markAsAddedToSession();
|
||||
return addTorrent_impl(params, MagnetUri(), TorrentInfo::loadFromFile(source));
|
||||
if (addTorrent_impl(params, MagnetUri(), TorrentInfo::loadFromFile(source))) {
|
||||
guard.markAsAddedToSession();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1855,7 +1851,7 @@ void Session::generateResumeData(bool final)
|
||||
if (torrent->isChecking() || torrent->hasError()) continue;
|
||||
if (!final && !torrent->needSaveResumeData()) continue;
|
||||
|
||||
saveTorrentResumeData(torrent);
|
||||
saveTorrentResumeData(torrent, final);
|
||||
qDebug("Saving fastresume data for %s", qPrintable(torrent->name()));
|
||||
}
|
||||
}
|
||||
@@ -1884,9 +1880,7 @@ void Session::saveResumeData()
|
||||
switch (a->type()) {
|
||||
case libt::save_resume_data_failed_alert::alert_type:
|
||||
case libt::save_resume_data_alert::alert_type:
|
||||
TorrentHandle *torrent = m_torrents.take(static_cast<libt::torrent_alert *>(a)->handle.info_hash());
|
||||
if (torrent)
|
||||
torrent->handleAlert(a);
|
||||
dispatchTorrentAlert(a);
|
||||
break;
|
||||
}
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
@@ -1938,7 +1932,7 @@ void Session::networkConfigurationChange(const QNetworkConfiguration& cfg)
|
||||
|
||||
// workaround for QTBUG-52633: check interface IPs, react only if the IPs have changed
|
||||
// seems to be present only with NetworkManager, hence Q_OS_LINUX
|
||||
#if defined Q_OS_LINUX && QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) // && QT_VERSION <= QT_VERSION_CHECK(5, ?, ?)
|
||||
#if defined Q_OS_LINUX && QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) && QT_VERSION < QT_VERSION_CHECK(5, 7, 1)
|
||||
static QStringList boundIPs = getListeningIPs();
|
||||
const QStringList newBoundIPs = getListeningIPs();
|
||||
if ((configuredInterfaceName == changedInterface) && (boundIPs != newBoundIPs)) {
|
||||
@@ -2779,9 +2773,9 @@ void Session::handleTorrentRatioLimitChanged(TorrentHandle *const torrent)
|
||||
updateRatioTimer();
|
||||
}
|
||||
|
||||
void Session::saveTorrentResumeData(TorrentHandle *const torrent)
|
||||
void Session::saveTorrentResumeData(TorrentHandle *const torrent, bool finalSave)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
torrent->saveResumeData(finalSave);
|
||||
++m_numResumeData;
|
||||
}
|
||||
|
||||
@@ -3060,13 +3054,19 @@ void Session::startUpTorrents()
|
||||
QByteArray data;
|
||||
} TorrentResumeData;
|
||||
|
||||
auto startupTorrent = [this, logger, resumeDataDir](const TorrentResumeData ¶ms)
|
||||
int resumedTorrentsCount = 0;
|
||||
const auto startupTorrent = [this, logger, &resumeDataDir, &resumedTorrentsCount](const TorrentResumeData ¶ms)
|
||||
{
|
||||
QString filePath = resumeDataDir.filePath(QString("%1.torrent").arg(params.hash));
|
||||
qDebug() << "Starting up torrent" << params.hash << "...";
|
||||
if (!addTorrent_impl(params.addTorrentData, params.magnetUri, TorrentInfo::loadFromFile(filePath), params.data))
|
||||
logger->addMessage(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
|
||||
.arg(params.hash), Log::CRITICAL);
|
||||
|
||||
// process add torrent messages before message queue overflow
|
||||
if (resumedTorrentsCount % 100 == 0) readAlerts();
|
||||
|
||||
++resumedTorrentsCount;
|
||||
};
|
||||
|
||||
qDebug("Starting up torrents");
|
||||
@@ -3074,6 +3074,7 @@ void Session::startUpTorrents()
|
||||
// Resume downloads
|
||||
QMap<int, TorrentResumeData> queuedResumeData;
|
||||
int nextQueuePosition = 1;
|
||||
int numOfRemappedFiles = 0;
|
||||
QRegExp rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
|
||||
foreach (const QString &fastresumeName, fastresumes) {
|
||||
if (rx.indexIn(fastresumeName) == -1) continue;
|
||||
@@ -3097,11 +3098,23 @@ void Session::startUpTorrents()
|
||||
}
|
||||
}
|
||||
else {
|
||||
queuedResumeData[queuePosition] = { hash, magnetUri, resumeData, data };
|
||||
int q = queuePosition;
|
||||
for(; queuedResumeData.contains(q); ++q) {
|
||||
}
|
||||
if (q != queuePosition) {
|
||||
++numOfRemappedFiles;
|
||||
}
|
||||
queuedResumeData[q] = { hash, magnetUri, resumeData, data };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numOfRemappedFiles > 0) {
|
||||
logger->addMessage(
|
||||
QString(tr("Queue positions were corrected in %1 resume files")).arg(numOfRemappedFiles),
|
||||
Log::CRITICAL);
|
||||
}
|
||||
|
||||
// starting up downloading torrents (queue position > 0)
|
||||
foreach (const TorrentResumeData &torrentResumeData, queuedResumeData)
|
||||
startupTorrent(torrentResumeData);
|
||||
|
||||
@@ -218,8 +218,6 @@ namespace BitTorrent
|
||||
void setLSDEnabled(bool enabled);
|
||||
bool isPeXEnabled() const;
|
||||
void setPeXEnabled(bool enabled);
|
||||
bool isTrackerExchangeEnabled() const;
|
||||
void setTrackerExchangeEnabled(bool enabled);
|
||||
bool isAddTorrentPaused() const;
|
||||
void setAddTorrentPaused(bool value);
|
||||
bool isTrackerEnabled() const;
|
||||
@@ -467,7 +465,7 @@ namespace BitTorrent
|
||||
|
||||
void updateRatioTimer();
|
||||
void exportTorrentFile(TorrentHandle *const torrent, TorrentExportFolder folder = TorrentExportFolder::Regular);
|
||||
void saveTorrentResumeData(TorrentHandle *const torrent);
|
||||
void saveTorrentResumeData(TorrentHandle *const torrent, bool finalSave = false);
|
||||
|
||||
void handleAlert(libtorrent::alert *a);
|
||||
void dispatchTorrentAlert(libtorrent::alert *a);
|
||||
@@ -507,7 +505,6 @@ namespace BitTorrent
|
||||
CachedSettingValue<bool> m_isDHTEnabled;
|
||||
CachedSettingValue<bool> m_isLSDEnabled;
|
||||
CachedSettingValue<bool> m_isPeXEnabled;
|
||||
CachedSettingValue<bool> m_isTrackerExchangeEnabled;
|
||||
CachedSettingValue<bool> m_isIPFilteringEnabled;
|
||||
CachedSettingValue<bool> m_isTrackerFilteringEnabled;
|
||||
CachedSettingValue<QString> m_IPFilterFile;
|
||||
@@ -572,11 +569,10 @@ namespace BitTorrent
|
||||
CachedSettingValue<bool> m_isTrackerEnabled;
|
||||
CachedSettingValue<QStringList> m_bannedIPs;
|
||||
|
||||
// Order is important. These need to be declared after their CachedSettingsValue
|
||||
// counterparts, because they use them for initialization in the constructor
|
||||
// Order is important. This needs to be declared after its CachedSettingsValue
|
||||
// counterpart, because it uses it for initialization in the constructor
|
||||
// initialization list.
|
||||
const bool m_wasPexEnabled;
|
||||
const bool m_wasTrackerExchangeEnabled;
|
||||
|
||||
int m_numResumeData;
|
||||
int m_extraLimit;
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QStringList>
|
||||
#include <QFile>
|
||||
@@ -192,6 +194,31 @@ const qreal TorrentHandle::NO_RATIO_LIMIT = -1.;
|
||||
|
||||
const qreal TorrentHandle::MAX_RATIO = 9999.;
|
||||
|
||||
// The new libtorrent::create_torrent constructor appeared after 1.0.11 in RC_1_0
|
||||
// and after 1.1.1 in RC_1_1. Since it fixed an ABI incompatibility with previous versions
|
||||
// distros might choose to backport it onto 1.0.11 and 1.1.1 respectively.
|
||||
// So we need a way to detect its presence without relying solely on the LIBTORRENT_VERSION_NUM.
|
||||
// Relevant links:
|
||||
// 1. https://github.com/arvidn/libtorrent/issues/1696
|
||||
// 2. https://github.com/qbittorrent/qBittorrent/issues/6406
|
||||
// The following can be removed after one or two libtorrent releases on each branch.
|
||||
namespace
|
||||
{
|
||||
// new constructor is available
|
||||
template<typename T, typename std::enable_if<std::is_constructible<T, libt::torrent_info, bool>::value, int>::type = 0>
|
||||
T makeTorrentCreator(const libtorrent::torrent_info & ti)
|
||||
{
|
||||
return T(ti, true);
|
||||
}
|
||||
|
||||
// new constructor isn't available
|
||||
template<typename T, typename std::enable_if<!std::is_constructible<T, libt::torrent_info, bool>::value, int>::type = 0>
|
||||
T makeTorrentCreator(const libtorrent::torrent_info & ti)
|
||||
{
|
||||
return T(ti);
|
||||
}
|
||||
}
|
||||
|
||||
TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle &nativeHandle,
|
||||
const AddTorrentData &data)
|
||||
: QObject(session)
|
||||
@@ -485,8 +512,11 @@ bool TorrentHandle::needSaveResumeData() const
|
||||
SAFE_RETURN(bool, need_save_resume_data, false);
|
||||
}
|
||||
|
||||
void TorrentHandle::saveResumeData()
|
||||
void TorrentHandle::saveResumeData(bool updateStatus)
|
||||
{
|
||||
if (updateStatus) // to update queue_position, see discussion in PR #6154
|
||||
this->updateStatus();
|
||||
|
||||
SAFE_CALL(save_resume_data);
|
||||
m_needSaveResumeData = false;
|
||||
}
|
||||
@@ -915,26 +945,29 @@ int TorrentHandle::leechsCount() const
|
||||
|
||||
int TorrentHandle::totalSeedsCount() const
|
||||
{
|
||||
return m_nativeStatus.list_seeds;
|
||||
return (m_nativeStatus.num_complete > 0) ? m_nativeStatus.num_complete : m_nativeStatus.list_seeds;
|
||||
}
|
||||
|
||||
int TorrentHandle::totalPeersCount() const
|
||||
{
|
||||
return m_nativeStatus.list_peers;
|
||||
int peers = m_nativeStatus.num_complete + m_nativeStatus.num_incomplete;
|
||||
return (peers > 0) ? peers : m_nativeStatus.list_peers;
|
||||
}
|
||||
|
||||
int TorrentHandle::totalLeechersCount() const
|
||||
{
|
||||
return (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
|
||||
return (m_nativeStatus.num_incomplete > 0) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
|
||||
}
|
||||
|
||||
int TorrentHandle::completeCount() const
|
||||
{
|
||||
// additional info: https://github.com/qbittorrent/qBittorrent/pull/5300#issuecomment-267783646
|
||||
return m_nativeStatus.num_complete;
|
||||
}
|
||||
|
||||
int TorrentHandle::incompleteCount() const
|
||||
{
|
||||
// additional info: https://github.com/qbittorrent/qBittorrent/pull/5300#issuecomment-267783646
|
||||
return m_nativeStatus.num_incomplete;
|
||||
}
|
||||
|
||||
@@ -1280,7 +1313,7 @@ void TorrentHandle::moveStorage(const QString &newPath)
|
||||
m_queuedPath = newPath;
|
||||
}
|
||||
else {
|
||||
QString oldPath = nativeActualSavePath();
|
||||
const QString oldPath = nativeActualSavePath();
|
||||
if (QDir(oldPath) == QDir(newPath)) return;
|
||||
|
||||
qDebug("move storage: %s to %s", qPrintable(oldPath), qPrintable(newPath));
|
||||
@@ -1312,7 +1345,7 @@ bool TorrentHandle::saveTorrentFile(const QString &path)
|
||||
{
|
||||
if (!m_torrentInfo.isValid()) return false;
|
||||
|
||||
libt::create_torrent torrentCreator(*(m_torrentInfo.nativeInfo()));
|
||||
libt::create_torrent torrentCreator = makeTorrentCreator<libt::create_torrent>(*(m_torrentInfo.nativeInfo()));
|
||||
libt::entry torrentEntry = torrentCreator.generate();
|
||||
|
||||
QVector<char> out;
|
||||
@@ -1347,7 +1380,7 @@ void TorrentHandle::handleStorageMovedAlert(libtorrent::storage_moved_alert *p)
|
||||
return;
|
||||
}
|
||||
|
||||
QString newPath = Utils::String::fromStdString(p->path);
|
||||
const QString newPath = Utils::String::fromStdString(p->path);
|
||||
if (newPath != m_newPath) {
|
||||
qWarning() << Q_FUNC_INFO << ": New path doesn't match a path in a queue.";
|
||||
return;
|
||||
@@ -1367,13 +1400,6 @@ void TorrentHandle::handleStorageMovedAlert(libtorrent::storage_moved_alert *p)
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
}
|
||||
|
||||
// Attempt to remove old folder if empty
|
||||
QDir oldSaveDir(Utils::Fs::fromNativePath(m_oldPath));
|
||||
if (oldSaveDir != QDir(m_session->defaultSavePath())) {
|
||||
qDebug("Attempting to remove %s", qPrintable(m_oldPath));
|
||||
QDir().rmpath(m_oldPath);
|
||||
}
|
||||
|
||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
}
|
||||
@@ -1702,6 +1728,11 @@ void TorrentHandle::manageIncompleteFiles()
|
||||
{
|
||||
const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled();
|
||||
QVector<qreal> fp = filesProgress();
|
||||
if( fp.size() != filesCount() ) {
|
||||
qDebug() << "skip manageIncompleteFiles because of invalid torrent meta-data or empty file-progress";
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < filesCount(); ++i) {
|
||||
QString name = filePath(i);
|
||||
if (isAppendExtensionEnabled && (fileSize(i) > 0) && (fp[i] < 1)) {
|
||||
@@ -1815,6 +1846,8 @@ void TorrentHandle::setDownloadLimit(int limit)
|
||||
void TorrentHandle::setSuperSeeding(bool enable)
|
||||
{
|
||||
SAFE_CALL(super_seeding, enable)
|
||||
if (superSeeding() != enable)
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
void TorrentHandle::flushCache()
|
||||
|
||||
@@ -351,7 +351,7 @@ namespace BitTorrent
|
||||
void handleTempPathChanged();
|
||||
void handleCategorySavePathChanged();
|
||||
void handleAppendExtensionToggled();
|
||||
void saveResumeData();
|
||||
void saveResumeData(bool updateStatus = false);
|
||||
|
||||
private:
|
||||
typedef boost::function<void ()> EventTrigger;
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#include <QUrlQuery>
|
||||
#endif
|
||||
#include <QDir>
|
||||
#include <QTemporaryFile>
|
||||
#include <QDebug>
|
||||
#include "requestparser.h"
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#else
|
||||
#include <QTcpSocket>
|
||||
#endif
|
||||
#include <QNetworkProxy>
|
||||
#include "connection.h"
|
||||
#include "server.h"
|
||||
|
||||
@@ -45,6 +46,10 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
||||
, m_https(false)
|
||||
#endif
|
||||
{
|
||||
setProxy(QNetworkProxy::NoProxy);
|
||||
#ifndef QT_NO_OPENSSL
|
||||
QSslSocket::setDefaultCiphers(safeCipherList());
|
||||
#endif
|
||||
}
|
||||
|
||||
Server::~Server()
|
||||
@@ -84,15 +89,15 @@ void Server::incomingConnection(int socketDescriptor)
|
||||
if (serverSocket->setSocketDescriptor(socketDescriptor)) {
|
||||
#ifndef QT_NO_OPENSSL
|
||||
if (m_https) {
|
||||
static_cast<QSslSocket*>(serverSocket)->setProtocol(QSsl::SecureProtocols);
|
||||
static_cast<QSslSocket*>(serverSocket)->setPrivateKey(m_key);
|
||||
static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols);
|
||||
static_cast<QSslSocket *>(serverSocket)->setPrivateKey(m_key);
|
||||
#ifdef QBT_USES_QT5
|
||||
static_cast<QSslSocket*>(serverSocket)->setLocalCertificateChain(m_certificates);
|
||||
static_cast<QSslSocket *>(serverSocket)->setLocalCertificateChain(m_certificates);
|
||||
#else
|
||||
static_cast<QSslSocket*>(serverSocket)->setLocalCertificate(m_certificates.first());
|
||||
static_cast<QSslSocket *>(serverSocket)->setLocalCertificate(m_certificates.first());
|
||||
#endif
|
||||
static_cast<QSslSocket*>(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
static_cast<QSslSocket*>(serverSocket)->startServerEncryption();
|
||||
static_cast<QSslSocket *>(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
static_cast<QSslSocket *>(serverSocket)->startServerEncryption();
|
||||
}
|
||||
#endif
|
||||
new Connection(serverSocket, m_requestHandler, this);
|
||||
@@ -101,3 +106,26 @@ void Server::incomingConnection(int socketDescriptor)
|
||||
serverSocket->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
QList<QSslCipher> Server::safeCipherList() const
|
||||
{
|
||||
const QStringList badCiphers = {"idea", "rc4"};
|
||||
const QList<QSslCipher> allCiphers = QSslSocket::supportedCiphers();
|
||||
QList<QSslCipher> safeCiphers;
|
||||
foreach (const QSslCipher &cipher, allCiphers) {
|
||||
bool isSafe = true;
|
||||
foreach (const QString &badCipher, badCiphers) {
|
||||
if (cipher.name().contains(badCipher, Qt::CaseInsensitive)) {
|
||||
isSafe = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSafe)
|
||||
safeCiphers += cipher;
|
||||
}
|
||||
|
||||
return safeCiphers;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <QTcpServer>
|
||||
#ifndef QT_NO_OPENSSL
|
||||
#include <QSslCertificate>
|
||||
#include <QSslCipher>
|
||||
#include <QSslKey>
|
||||
#endif
|
||||
|
||||
@@ -44,7 +45,7 @@ namespace Http
|
||||
class IRequestHandler;
|
||||
class Connection;
|
||||
|
||||
class Server : public QTcpServer
|
||||
class Server: public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(Server)
|
||||
@@ -53,25 +54,27 @@ namespace Http
|
||||
Server(IRequestHandler *requestHandler, QObject *parent = 0);
|
||||
~Server();
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
#ifndef QT_NO_OPENSSL
|
||||
void enableHttps(const QList<QSslCertificate> &certificates, const QSslKey &key);
|
||||
void disableHttps();
|
||||
#endif
|
||||
|
||||
private:
|
||||
#ifdef QBT_USES_QT5
|
||||
void incomingConnection(qintptr socketDescriptor);
|
||||
#else
|
||||
void incomingConnection(int socketDescriptor);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
private:
|
||||
IRequestHandler *m_requestHandler;
|
||||
#ifndef QT_NO_OPENSSL
|
||||
|
||||
#ifdef QBT_USES_QT5
|
||||
void incomingConnection(qintptr socketDescriptor);
|
||||
#else
|
||||
void incomingConnection(int socketDescriptor);
|
||||
#endif
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
QList<QSslCipher> safeCipherList() const;
|
||||
|
||||
bool m_https;
|
||||
QList<QSslCertificate> m_certificates;
|
||||
QSslKey m_key;
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ namespace Http
|
||||
const QString HEADER_CONTENT_ENCODING = "Content-Encoding";
|
||||
const QString HEADER_CONTENT_LENGTH = "Content-Length";
|
||||
const QString HEADER_CACHE_CONTROL = "Cache-Control";
|
||||
const QString HEADER_X_FRAME_OPTIONS = "X-Frame-Options";
|
||||
const QString HEADER_X_XSS_PROTECTION = "X-XSS-Protection";
|
||||
const QString HEADER_X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options";
|
||||
const QString HEADER_CONTENT_SECURITY_POLICY = "Content-Security-Policy";
|
||||
|
||||
const QString CONTENT_TYPE_CSS = "text/css; charset=UTF-8";
|
||||
const QString CONTENT_TYPE_GIF = "image/gif";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "logger.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include "base/utils/string.h"
|
||||
|
||||
Logger* Logger::m_instance = 0;
|
||||
|
||||
@@ -36,7 +37,7 @@ void Logger::addMessage(const QString &message, const Log::MsgType &type)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
|
||||
Log::Msg temp = { msgCounter++, QDateTime::currentMSecsSinceEpoch(), type, message };
|
||||
Log::Msg temp = { msgCounter++, QDateTime::currentMSecsSinceEpoch(), type, Utils::String::toHtmlEscaped(message) };
|
||||
m_messages.push_back(temp);
|
||||
|
||||
if (m_messages.size() >= MAX_LOG_MESSAGES)
|
||||
@@ -49,7 +50,7 @@ void Logger::addPeer(const QString &ip, bool blocked, const QString &reason)
|
||||
{
|
||||
QWriteLocker locker(&lock);
|
||||
|
||||
Log::Peer temp = { peerCounter++, QDateTime::currentMSecsSinceEpoch(), ip, blocked, reason };
|
||||
Log::Peer temp = { peerCounter++, QDateTime::currentMSecsSinceEpoch(), Utils::String::toHtmlEscaped(ip), blocked, Utils::String::toHtmlEscaped(reason) };
|
||||
m_peers.push_back(temp);
|
||||
|
||||
if (m_peers.size() >= MAX_LOG_MESSAGES)
|
||||
|
||||
@@ -142,7 +142,7 @@ void DownloadHandler::init()
|
||||
|
||||
bool DownloadHandler::saveToFile(const QByteArray &replyData, QString &filePath)
|
||||
{
|
||||
QTemporaryFile *tmpfile = new QTemporaryFile;
|
||||
QTemporaryFile *tmpfile = new QTemporaryFile(Utils::Fs::tempPath() + "XXXXXX");
|
||||
if (!tmpfile->open()) {
|
||||
delete tmpfile;
|
||||
return false;
|
||||
|
||||
@@ -31,8 +31,9 @@
|
||||
*/
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QPair>
|
||||
#include <QDir>
|
||||
#include <QLocale>
|
||||
#include <QPair>
|
||||
#include <QSettings>
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
@@ -92,7 +93,7 @@ void Preferences::setValue(const QString &key, const QVariant &value)
|
||||
// General options
|
||||
QString Preferences::getLocale() const
|
||||
{
|
||||
return value("Preferences/General/Locale").toString();
|
||||
return value("Preferences/General/Locale", QLocale::system().name()).toString();
|
||||
}
|
||||
|
||||
void Preferences::setLocale(const QString &locale)
|
||||
@@ -450,7 +451,11 @@ void Preferences::setWebUiPort(quint16 port)
|
||||
|
||||
bool Preferences::useUPnPForWebUIPort() const
|
||||
{
|
||||
#ifdef DISABLE_GUI
|
||||
return value("Preferences/WebUI/UseUPnP", true).toBool();
|
||||
#else
|
||||
return value("Preferences/WebUI/UseUPnP", false).toBool();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Preferences::setUPnPForWebUIPort(bool enabled)
|
||||
@@ -1320,14 +1325,22 @@ void Preferences::setRssMainSplitterState(const QByteArray &state)
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Preferences::getSearchColsWidth() const
|
||||
QByteArray Preferences::getSearchTabHeaderState() const
|
||||
{
|
||||
return value("SearchResultsColsWidth").toString();
|
||||
#ifdef QBT_USES_QT5
|
||||
return value("SearchTab/qt5/HeaderState").toByteArray();
|
||||
#else
|
||||
return value("SearchTab/HeaderState").toByteArray();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Preferences::setSearchColsWidth(const QString &width)
|
||||
void Preferences::setSearchTabHeaderState(const QByteArray &state)
|
||||
{
|
||||
setValue("SearchResultsColsWidth", width);
|
||||
#ifdef QBT_USES_QT5
|
||||
setValue("SearchTab/qt5/HeaderState", state);
|
||||
#else
|
||||
setValue("SearchTab/HeaderState", state);
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList Preferences::getSearchEngDisabled() const
|
||||
|
||||
@@ -304,8 +304,8 @@ public:
|
||||
void setRssSideSplitterState(const QByteArray &state);
|
||||
QByteArray getRssMainSplitterState() const;
|
||||
void setRssMainSplitterState(const QByteArray &state);
|
||||
QString getSearchColsWidth() const;
|
||||
void setSearchColsWidth(const QString &width);
|
||||
QByteArray getSearchTabHeaderState() const;
|
||||
void setSearchTabHeaderState(const QByteArray &state);
|
||||
QStringList getSearchEngDisabled() const;
|
||||
void setSearchEngDisabled(const QStringList &engines);
|
||||
QString getCreateTorLastAddPath() const;
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#include <QRegExp>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/fs.h"
|
||||
@@ -48,53 +50,122 @@ DownloadRule::DownloadRule()
|
||||
{
|
||||
}
|
||||
|
||||
bool DownloadRule::matches(const QString &articleTitle, const QString &expression) const
|
||||
{
|
||||
static QRegExp whitespace("\\s+");
|
||||
|
||||
if (expression.isEmpty()) {
|
||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||
return true;
|
||||
}
|
||||
else if (m_useRegex) {
|
||||
QRegExp reg(expression, Qt::CaseInsensitive, QRegExp::RegExp);
|
||||
return reg.indexIn(articleTitle) > -1;
|
||||
}
|
||||
else {
|
||||
// Only match if every wildcard token (separated by spaces) is present in the article name.
|
||||
// Order of wildcard tokens is unimportant (if order is important, they should have used *).
|
||||
foreach (const QString &wildcard, expression.split(whitespace, QString::SplitBehavior::SkipEmptyParts)) {
|
||||
QRegExp reg(wildcard, Qt::CaseInsensitive, QRegExp::Wildcard);
|
||||
|
||||
if (reg.indexIn(articleTitle) == -1)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DownloadRule::matches(const QString &articleTitle) const
|
||||
{
|
||||
foreach (const QString &token, m_mustContain) {
|
||||
if (!token.isEmpty()) {
|
||||
QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard);
|
||||
if (reg.indexIn(articleTitle) < 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
qDebug("Checking not matching tokens");
|
||||
// Checking not matching
|
||||
foreach (const QString &token, m_mustNotContain) {
|
||||
if (!token.isEmpty()) {
|
||||
QRegExp reg(token, Qt::CaseInsensitive, m_useRegex ? QRegExp::RegExp : QRegExp::Wildcard);
|
||||
if (reg.indexIn(articleTitle) > -1)
|
||||
if (!m_mustContain.empty()) {
|
||||
bool logged = false;
|
||||
bool foundMustContain = false;
|
||||
|
||||
// Each expression is either a regex, or a set of wildcards separated by whitespace.
|
||||
// Accept if any complete expression matches.
|
||||
foreach (const QString &expression, m_mustContain) {
|
||||
if (!logged) {
|
||||
qDebug() << "Checking matching" << (m_useRegex ? "regex:" : "wildcard expressions:") << m_mustContain.join("|");
|
||||
logged = true;
|
||||
}
|
||||
|
||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||
foundMustContain = matches(articleTitle, expression);
|
||||
|
||||
if (foundMustContain) {
|
||||
qDebug() << "Found matching" << (m_useRegex ? "regex:" : "wildcard expression:") << expression;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundMustContain)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_mustNotContain.empty()) {
|
||||
bool logged = false;
|
||||
|
||||
// Each expression is either a regex, or a set of wildcards separated by whitespace.
|
||||
// Reject if any complete expression matches.
|
||||
foreach (const QString &expression, m_mustNotContain) {
|
||||
if (!logged) {
|
||||
qDebug() << "Checking not matching" << (m_useRegex ? "regex:" : "wildcard expressions:") << m_mustNotContain.join("|");
|
||||
logged = true;
|
||||
}
|
||||
|
||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||
if (matches(articleTitle, expression)) {
|
||||
qDebug() << "Found not matching" << (m_useRegex ? "regex:" : "wildcard expression:") << expression;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_episodeFilter.isEmpty()) {
|
||||
qDebug("Checking episode filter");
|
||||
qDebug() << "Checking episode filter:" << m_episodeFilter;
|
||||
QRegExp f("(^\\d{1,4})x(.*;$)");
|
||||
int pos = f.indexIn(m_episodeFilter);
|
||||
|
||||
if (pos < 0)
|
||||
return false;
|
||||
|
||||
QString s = f.cap(1);
|
||||
QStringList eps = f.cap(2).split(";");
|
||||
QString expStr;
|
||||
expStr += "s0?" + s + "[ -_\\.]?" + "e0?";
|
||||
int sOurs = s.toInt();
|
||||
|
||||
foreach (const QString &ep, eps) {
|
||||
foreach (QString ep, eps) {
|
||||
if (ep.isEmpty())
|
||||
continue;
|
||||
|
||||
// We need to trim leading zeroes, but if it's all zeros then we want episode zero.
|
||||
while (ep.size() > 1 && ep.startsWith("0"))
|
||||
ep = ep.right(ep.size() - 1);
|
||||
|
||||
if (ep.indexOf('-') != -1) { // Range detected
|
||||
QString partialPattern = "s0?" + s + "[ -_\\.]?" + "e(0?\\d{1,4})";
|
||||
QRegExp reg(partialPattern, Qt::CaseInsensitive);
|
||||
QString partialPattern1 = "\\bs0?(\\d{1,4})[ -_\\.]?e(0?\\d{1,4})(?:\\D|\\b)";
|
||||
QString partialPattern2 = "\\b(\\d{1,4})x(0?\\d{1,4})(?:\\D|\\b)";
|
||||
QRegExp reg(partialPattern1, Qt::CaseInsensitive);
|
||||
|
||||
if (ep.endsWith('-')) { // Infinite range
|
||||
int epOurs = ep.left(ep.size() - 1).toInt();
|
||||
|
||||
// Extract partial match from article and compare as digits
|
||||
pos = reg.indexIn(articleTitle);
|
||||
|
||||
if (pos == -1) {
|
||||
reg = QRegExp(partialPattern2, Qt::CaseInsensitive);
|
||||
pos = reg.indexIn(articleTitle);
|
||||
}
|
||||
|
||||
if (pos != -1) {
|
||||
int epTheirs = reg.cap(1).toInt();
|
||||
if (epTheirs >= epOurs)
|
||||
int sTheirs = reg.cap(1).toInt();
|
||||
int epTheirs = reg.cap(2).toInt();
|
||||
if (((sTheirs == sOurs) && (epTheirs >= epOurs)) || (sTheirs > sOurs)) {
|
||||
qDebug() << "Matched episode:" << ep;
|
||||
qDebug() << "Matched article:" << articleTitle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Normal range
|
||||
@@ -108,21 +179,38 @@ bool DownloadRule::matches(const QString &articleTitle) const
|
||||
|
||||
// Extract partial match from article and compare as digits
|
||||
pos = reg.indexIn(articleTitle);
|
||||
|
||||
if (pos == -1) {
|
||||
reg = QRegExp(partialPattern2, Qt::CaseInsensitive);
|
||||
pos = reg.indexIn(articleTitle);
|
||||
}
|
||||
|
||||
if (pos != -1) {
|
||||
int epTheirs = reg.cap(1).toInt();
|
||||
if (epOursFirst <= epTheirs && epOursLast >= epTheirs)
|
||||
int sTheirs = reg.cap(1).toInt();
|
||||
int epTheirs = reg.cap(2).toInt();
|
||||
if ((sTheirs == sOurs) && ((epOursFirst <= epTheirs) && (epOursLast >= epTheirs))) {
|
||||
qDebug() << "Matched episode:" << ep;
|
||||
qDebug() << "Matched article:" << articleTitle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Single number
|
||||
QRegExp reg(expStr + ep + "\\D", Qt::CaseInsensitive);
|
||||
if (reg.indexIn(articleTitle) != -1)
|
||||
QString expStr("\\b(?:s0?" + s + "[ -_\\.]?" + "e0?" + ep + "|" + s + "x" + "0?" + ep + ")(?:\\D|\\b)");
|
||||
QRegExp reg(expStr, Qt::CaseInsensitive);
|
||||
if (reg.indexIn(articleTitle) != -1) {
|
||||
qDebug() << "Matched episode:" << ep;
|
||||
qDebug() << "Matched article:" << articleTitle;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "Matched article:" << articleTitle;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -131,7 +219,11 @@ void DownloadRule::setMustContain(const QString &tokens)
|
||||
if (m_useRegex)
|
||||
m_mustContain = QStringList() << tokens;
|
||||
else
|
||||
m_mustContain = tokens.split(" ");
|
||||
m_mustContain = tokens.split("|");
|
||||
|
||||
// Check for single empty string - if so, no condition
|
||||
if ((m_mustContain.size() == 1) && m_mustContain[0].isEmpty())
|
||||
m_mustContain.clear();
|
||||
}
|
||||
|
||||
void DownloadRule::setMustNotContain(const QString &tokens)
|
||||
@@ -140,6 +232,10 @@ void DownloadRule::setMustNotContain(const QString &tokens)
|
||||
m_mustNotContain = QStringList() << tokens;
|
||||
else
|
||||
m_mustNotContain = tokens.split("|");
|
||||
|
||||
// Check for single empty string - if so, no condition
|
||||
if ((m_mustNotContain.size() == 1) && m_mustNotContain[0].isEmpty())
|
||||
m_mustNotContain.clear();
|
||||
}
|
||||
|
||||
QStringList DownloadRule::rssFeeds() const
|
||||
@@ -189,7 +285,7 @@ QVariantHash DownloadRule::toVariantHash() const
|
||||
{
|
||||
QVariantHash hash;
|
||||
hash["name"] = m_name;
|
||||
hash["must_contain"] = m_mustContain.join(" ");
|
||||
hash["must_contain"] = m_mustContain.join("|");
|
||||
hash["must_not_contain"] = m_mustNotContain.join("|");
|
||||
hash["save_path"] = m_savePath;
|
||||
hash["affected_feeds"] = m_rssFeeds;
|
||||
@@ -265,7 +361,7 @@ int DownloadRule::ignoreDays() const
|
||||
|
||||
QString DownloadRule::mustContain() const
|
||||
{
|
||||
return m_mustContain.join(" ");
|
||||
return m_mustContain.join("|");
|
||||
}
|
||||
|
||||
QString DownloadRule::mustNotContain() const
|
||||
@@ -300,8 +396,9 @@ QStringList DownloadRule::findMatchingArticles(const FeedPtr &feed) const
|
||||
|
||||
ArticleHash::ConstIterator artIt = feedArticles.begin();
|
||||
ArticleHash::ConstIterator artItend = feedArticles.end();
|
||||
for ( ; artIt != artItend ; ++artIt) {
|
||||
for (; artIt != artItend; ++artIt) {
|
||||
const QString title = artIt.value()->title();
|
||||
qDebug() << "Matching article:" << title;
|
||||
if (matches(title))
|
||||
ret << title;
|
||||
}
|
||||
|
||||
@@ -88,6 +88,8 @@ namespace Rss
|
||||
bool operator==(const DownloadRule &other) const;
|
||||
|
||||
private:
|
||||
bool matches(const QString &articleTitle, const QString &expression) const;
|
||||
|
||||
QString m_name;
|
||||
QStringList m_mustContain;
|
||||
QStringList m_mustNotContain;
|
||||
|
||||
@@ -46,6 +46,7 @@ DownloadRuleList::DownloadRuleList()
|
||||
DownloadRulePtr DownloadRuleList::findMatchingRule(const QString &feedUrl, const QString &articleTitle) const
|
||||
{
|
||||
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
|
||||
qDebug() << "Matching article:" << articleTitle;
|
||||
QStringList ruleNames = m_feedRules.value(feedUrl);
|
||||
foreach (const QString &rule_name, ruleNames) {
|
||||
DownloadRulePtr rule = m_rules[rule_name];
|
||||
|
||||
@@ -78,7 +78,7 @@ Feed::Feed(const QString &url, Manager *manager)
|
||||
|
||||
// Download the RSS Feed icon
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl(), true);
|
||||
connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(handleIconDownloadFinished(QString, QString)));
|
||||
connect(handler, SIGNAL(downloadFinished(QString,QString)), this, SLOT(handleIconDownloadFinished(QString,QString)));
|
||||
|
||||
// Load old RSS articles
|
||||
loadItemsFromDisk();
|
||||
@@ -99,24 +99,24 @@ void Feed::saveItemsToDisk()
|
||||
|
||||
m_dirty = false;
|
||||
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QIniSettings qBTRSSFeeds("qBittorrent", "qBittorrent-rss-feeds");
|
||||
QVariantList oldItems;
|
||||
|
||||
ArticleHash::ConstIterator it = m_articles.begin();
|
||||
ArticleHash::ConstIterator itend = m_articles.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
for (; it != itend; ++it)
|
||||
oldItems << it.value()->toHash();
|
||||
}
|
||||
qDebug("Saving %d old items for feed %s", oldItems.size(), qPrintable(displayName()));
|
||||
QHash<QString, QVariant> allOldItems = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
|
||||
QHash<QString, QVariant> allOldItems = qBTRSSFeeds.value("old_items", QHash<QString, QVariant>()).toHash();
|
||||
allOldItems[m_url] = oldItems;
|
||||
qBTRSS.setValue("old_items", allOldItems);
|
||||
qBTRSSFeeds.setValue("old_items", allOldItems);
|
||||
}
|
||||
|
||||
void Feed::loadItemsFromDisk()
|
||||
{
|
||||
QIniSettings qBTRSS("qBittorrent", "qBittorrent-rss");
|
||||
QHash<QString, QVariant> allOldItems = qBTRSS.value("old_items", QHash<QString, QVariant>()).toHash();
|
||||
QIniSettings qBTRSSFeeds("qBittorrent", "qBittorrent-rss-feeds");
|
||||
QHash<QString, QVariant> allOldItems = qBTRSSFeeds.value("old_items", QHash<QString, QVariant>()).toHash();
|
||||
|
||||
const QVariantList oldItems = allOldItems.value(m_url, QVariantList()).toList();
|
||||
qDebug("Loading %d old items for feed %s", oldItems.size(), qPrintable(displayName()));
|
||||
|
||||
@@ -155,20 +155,18 @@ void Feed::addArticle(const ArticlePtr &article)
|
||||
}
|
||||
|
||||
// Check if article was inserted at the end of the list and will break max_articles limit
|
||||
if (Preferences::instance()->isRssDownloadingEnabled()) {
|
||||
if (Preferences::instance()->isRssDownloadingEnabled())
|
||||
if ((lbIndex < maxArticles) && !article->isRead())
|
||||
downloadArticleTorrentIfMatching(article);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// m_articles.contains(article->guid())
|
||||
// Try to download skipped articles
|
||||
if (Preferences::instance()->isRssDownloadingEnabled()) {
|
||||
ArticlePtr skipped = m_articles.value(article->guid(), ArticlePtr());
|
||||
if (skipped) {
|
||||
if (skipped)
|
||||
if (!skipped->isRead())
|
||||
downloadArticleTorrentIfMatching(skipped);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,8 +180,8 @@ bool Feed::refresh()
|
||||
m_loading = true;
|
||||
// Download the RSS again
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(m_url);
|
||||
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), this, SLOT(handleRssDownloadFinished(QString, QByteArray)));
|
||||
connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(handleRssDownloadFailed(QString, QString)));
|
||||
connect(handler, SIGNAL(downloadFinished(QString,QByteArray)), this, SLOT(handleRssDownloadFinished(QString,QByteArray)));
|
||||
connect(handler, SIGNAL(downloadFailed(QString,QString)), this, SLOT(handleRssDownloadFailed(QString,QString)));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -206,10 +204,11 @@ void Feed::removeAllSettings()
|
||||
allFeedsFilters.remove(m_url);
|
||||
qBTRSS.setValue("feed_filters", allFeedsFilters);
|
||||
}
|
||||
QVariantHash allOldItems = qBTRSS.value("old_items", QVariantHash()).toHash();
|
||||
QIniSettings qBTRSSFeeds("qBittorrent", "qBittorrent-rss-feeds");
|
||||
QVariantHash allOldItems = qBTRSSFeeds.value("old_items", QVariantHash()).toHash();
|
||||
if (allOldItems.contains(m_url)) {
|
||||
allOldItems.remove(m_url);
|
||||
qBTRSS.setValue("old_items", allOldItems);
|
||||
qBTRSSFeeds.setValue("old_items", allOldItems);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +259,7 @@ bool Feed::hasCustomIcon() const
|
||||
void Feed::setIconPath(const QString &path)
|
||||
{
|
||||
QString nativePath = Utils::Fs::fromNativePath(path);
|
||||
if (nativePath == m_icon || nativePath.isEmpty() || !QFile::exists(nativePath)) return;
|
||||
if ((nativePath == m_icon) || nativePath.isEmpty() || !QFile::exists(nativePath)) return;
|
||||
|
||||
if (!m_icon.startsWith(":/") && QFile::exists(m_icon))
|
||||
Utils::Fs::forceRemove(m_icon);
|
||||
@@ -282,9 +281,8 @@ void Feed::markAsRead()
|
||||
{
|
||||
ArticleHash::ConstIterator it = m_articles.begin();
|
||||
ArticleHash::ConstIterator itend = m_articles.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
for (; it != itend; ++it)
|
||||
it.value()->markAsRead();
|
||||
}
|
||||
m_unreadCount = 0;
|
||||
m_manager->forwardFeedInfosChanged(m_url, displayName(), 0);
|
||||
}
|
||||
@@ -310,10 +308,9 @@ ArticleList Feed::unreadArticleListByDateDesc() const
|
||||
|
||||
ArticleList::ConstIterator it = m_articlesByDate.begin();
|
||||
ArticleList::ConstIterator itend = m_articlesByDate.end();
|
||||
for ( ; it != itend; ++it) {
|
||||
for (; it != itend; ++it)
|
||||
if (!(*it)->isRead())
|
||||
unreadNews << *it;
|
||||
}
|
||||
return unreadNews;
|
||||
}
|
||||
|
||||
@@ -364,6 +361,14 @@ void Feed::handleFeedTitle(const QString &title)
|
||||
void Feed::downloadArticleTorrentIfMatching(const ArticlePtr &article)
|
||||
{
|
||||
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
|
||||
qDebug().nospace() << Q_FUNC_INFO << " Deferring matching of " << article->title() << " from " << displayName() << " RSS feed";
|
||||
m_manager->downloadArticleTorrentIfMatching(m_url, article);
|
||||
}
|
||||
|
||||
void Feed::deferredDownloadArticleTorrentIfMatching(const ArticlePtr &article)
|
||||
{
|
||||
qDebug().nospace() << Q_FUNC_INFO << " Matching of " << article->title() << " from " << displayName() << " RSS feed";
|
||||
|
||||
DownloadRuleList *rules = m_manager->downloadRules();
|
||||
DownloadRulePtr matchingRule = rules->findMatchingRule(m_url, article->title());
|
||||
if (!matchingRule) return;
|
||||
@@ -407,10 +412,9 @@ void Feed::downloadArticleTorrentIfMatching(const ArticlePtr &article)
|
||||
void Feed::recheckRssItemsForDownload()
|
||||
{
|
||||
Q_ASSERT(Preferences::instance()->isRssDownloadingEnabled());
|
||||
foreach (const ArticlePtr &article, m_articlesByDate) {
|
||||
foreach (const ArticlePtr &article, m_articlesByDate)
|
||||
if (!article->isRead())
|
||||
downloadArticleTorrentIfMatching(article);
|
||||
}
|
||||
}
|
||||
|
||||
void Feed::handleNewArticle(const QVariantHash &articleData)
|
||||
@@ -426,7 +430,7 @@ void Feed::handleNewArticle(const QVariantHash &articleData)
|
||||
m_manager->forwardFeedInfosChanged(m_url, displayName(), m_unreadCount);
|
||||
// FIXME: We should forward the information here but this would seriously decrease
|
||||
// performance with current design.
|
||||
//m_manager->forwardFeedContentChanged(m_url);
|
||||
// m_manager->forwardFeedContentChanged(m_url);
|
||||
}
|
||||
|
||||
void Feed::handleParsingFinished(const QString &error)
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Rss
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Feed(const QString &url, Manager *manager);
|
||||
Feed(const QString &url, Manager * manager);
|
||||
~Feed();
|
||||
|
||||
bool refresh();
|
||||
@@ -98,10 +98,13 @@ namespace Rss
|
||||
void handleArticleRead();
|
||||
|
||||
private:
|
||||
friend class Manager;
|
||||
|
||||
QString iconUrl() const;
|
||||
void loadItemsFromDisk();
|
||||
void addArticle(const ArticlePtr &article);
|
||||
void downloadArticleTorrentIfMatching(const ArticlePtr &article);
|
||||
void deferredDownloadArticleTorrentIfMatching(const ArticlePtr &article);
|
||||
|
||||
private:
|
||||
Manager *m_manager;
|
||||
|
||||
@@ -54,6 +54,10 @@ Manager::Manager(QObject *parent)
|
||||
connect(&m_refreshTimer, SIGNAL(timeout()), SLOT(refresh()));
|
||||
m_refreshInterval = Preferences::instance()->getRSSRefreshInterval();
|
||||
m_refreshTimer.start(m_refreshInterval * MSECS_PER_MIN);
|
||||
|
||||
m_deferredDownloadTimer.setInterval(1);
|
||||
m_deferredDownloadTimer.setSingleShot(true);
|
||||
connect(&m_deferredDownloadTimer, SIGNAL(timeout()), SLOT(downloadNextArticleTorrentIfMatching()));
|
||||
}
|
||||
|
||||
Manager::~Manager()
|
||||
@@ -72,7 +76,7 @@ void Manager::updateRefreshInterval(uint val)
|
||||
{
|
||||
if (m_refreshInterval != val) {
|
||||
m_refreshInterval = val;
|
||||
m_refreshTimer.start(m_refreshInterval*60000);
|
||||
m_refreshTimer.start(m_refreshInterval * 60000);
|
||||
qDebug("New RSS refresh interval is now every %dmin", m_refreshInterval);
|
||||
}
|
||||
}
|
||||
@@ -81,7 +85,7 @@ void Manager::loadStreamList()
|
||||
{
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
const QStringList streamsUrl = pref->getRssFeedsUrls();
|
||||
const QStringList aliases = pref->getRssFeedsAliases();
|
||||
const QStringList aliases = pref->getRssFeedsAliases();
|
||||
if (streamsUrl.size() != aliases.size()) {
|
||||
Logger::instance()->addMessage("Corrupted RSS list, not loading it.", Log::WARNING);
|
||||
return;
|
||||
@@ -188,3 +192,27 @@ void Manager::refresh()
|
||||
{
|
||||
m_rootFolder->refresh();
|
||||
}
|
||||
|
||||
void Manager::downloadArticleTorrentIfMatching(const QString &url, const ArticlePtr &article)
|
||||
{
|
||||
m_deferredDownloads.append(qMakePair(url, article));
|
||||
m_deferredDownloadTimer.start();
|
||||
}
|
||||
|
||||
void Manager::downloadNextArticleTorrentIfMatching()
|
||||
{
|
||||
if (m_deferredDownloads.empty())
|
||||
return;
|
||||
|
||||
// Schedule to process the next article (if any)
|
||||
m_deferredDownloadTimer.start();
|
||||
|
||||
QPair<QString, ArticlePtr> urlArticle(m_deferredDownloads.takeFirst());
|
||||
FeedList streams = m_rootFolder->getAllFeeds();
|
||||
foreach (const FeedPtr &stream, streams) {
|
||||
if (stream->url() == urlArticle.first) {
|
||||
stream->deferredDownloadArticleTorrentIfMatching(urlArticle.second);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,19 +32,23 @@
|
||||
#ifndef RSSMANAGER_H
|
||||
#define RSSMANAGER_H
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QTimer>
|
||||
#include <QSharedPointer>
|
||||
#include <QThread>
|
||||
|
||||
namespace Rss
|
||||
{
|
||||
class Article;
|
||||
class DownloadRuleList;
|
||||
class File;
|
||||
class Folder;
|
||||
class Feed;
|
||||
class Manager;
|
||||
|
||||
typedef QSharedPointer<Article> ArticlePtr;
|
||||
typedef QSharedPointer<File> FilePtr;
|
||||
typedef QSharedPointer<Folder> FolderPtr;
|
||||
typedef QSharedPointer<Feed> FeedPtr;
|
||||
@@ -62,6 +66,7 @@ namespace Rss
|
||||
DownloadRuleList *downloadRules() const;
|
||||
FolderPtr rootFolder() const;
|
||||
QThread *workingThread() const;
|
||||
void downloadArticleTorrentIfMatching(const QString &url, const ArticlePtr &article);
|
||||
|
||||
public slots:
|
||||
void refresh();
|
||||
@@ -78,12 +83,17 @@ namespace Rss
|
||||
void feedInfosChanged(const QString &url, const QString &displayName, uint unreadCount);
|
||||
void feedIconChanged(const QString &url, const QString &iconPath);
|
||||
|
||||
private slots:
|
||||
void downloadNextArticleTorrentIfMatching();
|
||||
|
||||
private:
|
||||
QTimer m_refreshTimer;
|
||||
uint m_refreshInterval;
|
||||
DownloadRuleList *m_downloadRules;
|
||||
FolderPtr m_rootFolder;
|
||||
QThread *m_workingThread;
|
||||
QTimer m_deferredDownloadTimer;
|
||||
QList<QPair<QString, ArticlePtr>> m_deferredDownloads;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,6 @@ namespace
|
||||
{"BitTorrent/Session/DHTEnabled", "Preferences/Bittorrent/DHT"},
|
||||
{"BitTorrent/Session/LSDEnabled", "Preferences/Bittorrent/LSD"},
|
||||
{"BitTorrent/Session/PeXEnabled", "Preferences/Bittorrent/PeX"},
|
||||
{"BitTorrent/Session/TrackerExchangeEnabled", "Preferences/Advanced/LtTrackerExchange"},
|
||||
{"BitTorrent/Session/AddTrackersEnabled", "Preferences/Bittorrent/AddTrackers"},
|
||||
{"BitTorrent/Session/AdditionalTrackers", "Preferences/Bittorrent/TrackersList"},
|
||||
{"BitTorrent/Session/IPFilteringEnabled", "Preferences/IPFilter/Enabled"},
|
||||
|
||||
@@ -92,9 +92,11 @@ void TorrentFileGuard::setAutoDeleteMode(TorrentFileGuard::AutoDeleteMode mode)
|
||||
|
||||
QMetaEnum TorrentFileGuard::modeMetaEnum()
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
|
||||
return QMetaEnum::fromType<AutoDeleteMode>();
|
||||
#else
|
||||
return staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("AutoDeleteMode"));
|
||||
const int enumeratorIndex = staticMetaObject.indexOfEnumerator("AutoDeleteMode");
|
||||
Q_ASSERT(enumeratorIndex >= 0);
|
||||
return staticMetaObject.enumerator(enumeratorIndex);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#ifndef TOFFENTFILEGURAD_H
|
||||
#define TOFFENTFILEGURAD_H
|
||||
|
||||
#include <QString>
|
||||
#include <QMetaType>
|
||||
|
||||
@@ -50,6 +53,10 @@ private:
|
||||
class TorrentFileGuard
|
||||
{
|
||||
Q_GADGET
|
||||
// moc from Qt4 ignores Q_ENUMS when it is behind #if QT_VERSION check
|
||||
// this declaration is needed for Qt 4 only
|
||||
// TODO Qt5: remove when dropping Qt4 support
|
||||
Q_ENUMS(AutoDeleteMode)
|
||||
|
||||
public:
|
||||
TorrentFileGuard(const QString &path = QString());
|
||||
@@ -72,9 +79,7 @@ public:
|
||||
|
||||
private:
|
||||
static QMetaEnum modeMetaEnum();
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0)
|
||||
Q_ENUMS(AutoDeleteMode)
|
||||
#else
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)
|
||||
Q_ENUM(AutoDeleteMode)
|
||||
#endif
|
||||
AutoDeleteMode m_mode;
|
||||
@@ -87,9 +92,12 @@ private:
|
||||
// This problem is NOT present in Qt 5.7.0 and maybe in some older Qt 5 versions too
|
||||
// Qt 4.8.7 has it.
|
||||
// Therefore, we can't inherit FileGuard :(
|
||||
// TODO Qt5: port to private inheritance when dropping Qt 4 support
|
||||
FileGuard m_guard;
|
||||
};
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0)
|
||||
Q_DECLARE_METATYPE(TorrentFileGuard::AutoDeleteMode)
|
||||
#endif
|
||||
|
||||
#endif // TOFFENTFILEGURAD_H
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
// we put all problematic UTF-8 chars/strings in this file.
|
||||
// See issue #3059 for more details (https://github.com/qbittorrent/qBittorrent/issues/3059).
|
||||
const char C_INFINITY[] = "∞";
|
||||
const char C_NON_BREAKING_SPACE[] = " ";
|
||||
const char C_UP[] = "▲";
|
||||
const char C_DOWN[] = "▼";
|
||||
const char C_COPYRIGHT[] = "©";
|
||||
|
||||
@@ -491,3 +491,10 @@ QString Utils::Fs::cacheLocation()
|
||||
locationDir.mkpath(locationDir.absolutePath());
|
||||
return location;
|
||||
}
|
||||
|
||||
QString Utils::Fs::tempPath()
|
||||
{
|
||||
static const QString path = QDir::tempPath() + "/.qBittorrent/";
|
||||
QDir().mkdir(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ namespace Utils
|
||||
/* End of Qt4 code */
|
||||
|
||||
QString cacheLocation();
|
||||
QString tempPath();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,73 @@ static struct { const char *source; const char *comment; } units[] = {
|
||||
|
||||
void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
|
||||
{
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) && defined(QT_DBUS_LIB)
|
||||
#if defined(Q_OS_WIN)
|
||||
HANDLE hToken; // handle to process token
|
||||
TOKEN_PRIVILEGES tkp; // pointer to token structure
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
|
||||
return;
|
||||
// Get the LUID for shutdown privilege.
|
||||
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
|
||||
&tkp.Privileges[0].Luid);
|
||||
|
||||
tkp.PrivilegeCount = 1; // one privilege to set
|
||||
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
|
||||
// Get shutdown privilege for this process.
|
||||
|
||||
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
|
||||
(PTOKEN_PRIVILEGES) NULL, 0);
|
||||
|
||||
// Cannot test the return value of AdjustTokenPrivileges.
|
||||
|
||||
if (GetLastError() != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
if (action == ShutdownDialogAction::Suspend)
|
||||
SetSuspendState(false, false, false);
|
||||
else if (action == ShutdownDialogAction::Hibernate)
|
||||
SetSuspendState(true, false, false);
|
||||
else
|
||||
InitiateSystemShutdownA(0, QCoreApplication::translate("misc", "qBittorrent will shutdown the computer now because all downloads are complete.").toLocal8Bit().data(), 10, true, false);
|
||||
|
||||
// Disable shutdown privilege.
|
||||
tkp.Privileges[0].Attributes = 0;
|
||||
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES) NULL, 0);
|
||||
|
||||
#elif defined(Q_OS_MAC)
|
||||
AEEventID EventToSend;
|
||||
if (action != ShutdownDialogAction::Shutdown)
|
||||
EventToSend = kAESleep;
|
||||
else
|
||||
EventToSend = kAEShutDown;
|
||||
AEAddressDesc targetDesc;
|
||||
static const ProcessSerialNumber kPSNOfSystemProcess = { 0, kSystemProcess };
|
||||
AppleEvent eventReply = {typeNull, NULL};
|
||||
AppleEvent appleEventToSend = {typeNull, NULL};
|
||||
|
||||
OSStatus error = AECreateDesc(typeProcessSerialNumber, &kPSNOfSystemProcess,
|
||||
sizeof(kPSNOfSystemProcess), &targetDesc);
|
||||
|
||||
if (error != noErr)
|
||||
return;
|
||||
|
||||
error = AECreateAppleEvent(kCoreEventClass, EventToSend, &targetDesc,
|
||||
kAutoGenerateReturnID, kAnyTransactionID, &appleEventToSend);
|
||||
|
||||
AEDisposeDesc(&targetDesc);
|
||||
if (error != noErr)
|
||||
return;
|
||||
|
||||
error = AESend(&appleEventToSend, &eventReply, kAENoReply,
|
||||
kAENormalPriority, kAEDefaultTimeout, NULL, NULL);
|
||||
|
||||
AEDisposeDesc(&appleEventToSend);
|
||||
if (error != noErr)
|
||||
return;
|
||||
|
||||
AEDisposeDesc(&eventReply);
|
||||
|
||||
#elif (defined(Q_OS_UNIX) && defined(QT_DBUS_LIB))
|
||||
// Use dbus to power off / suspend the system
|
||||
if (action != ShutdownDialogAction::Shutdown) {
|
||||
// Some recent systems use systemd's logind
|
||||
@@ -147,73 +213,9 @@ void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
|
||||
QDBusConnection::systemBus());
|
||||
halIface.call("Shutdown");
|
||||
}
|
||||
#endif
|
||||
#ifdef Q_OS_MAC
|
||||
AEEventID EventToSend;
|
||||
if (action != ShutdownDialogAction::Shutdown)
|
||||
EventToSend = kAESleep;
|
||||
else
|
||||
EventToSend = kAEShutDown;
|
||||
AEAddressDesc targetDesc;
|
||||
static const ProcessSerialNumber kPSNOfSystemProcess = { 0, kSystemProcess };
|
||||
AppleEvent eventReply = {typeNull, NULL};
|
||||
AppleEvent appleEventToSend = {typeNull, NULL};
|
||||
|
||||
OSStatus error = AECreateDesc(typeProcessSerialNumber, &kPSNOfSystemProcess,
|
||||
sizeof(kPSNOfSystemProcess), &targetDesc);
|
||||
|
||||
if (error != noErr)
|
||||
return;
|
||||
|
||||
error = AECreateAppleEvent(kCoreEventClass, EventToSend, &targetDesc,
|
||||
kAutoGenerateReturnID, kAnyTransactionID, &appleEventToSend);
|
||||
|
||||
AEDisposeDesc(&targetDesc);
|
||||
if (error != noErr)
|
||||
return;
|
||||
|
||||
error = AESend(&appleEventToSend, &eventReply, kAENoReply,
|
||||
kAENormalPriority, kAEDefaultTimeout, NULL, NULL);
|
||||
|
||||
AEDisposeDesc(&appleEventToSend);
|
||||
if (error != noErr)
|
||||
return;
|
||||
|
||||
AEDisposeDesc(&eventReply);
|
||||
#endif
|
||||
#ifdef Q_OS_WIN
|
||||
HANDLE hToken; // handle to process token
|
||||
TOKEN_PRIVILEGES tkp; // pointer to token structure
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
|
||||
return;
|
||||
// Get the LUID for shutdown privilege.
|
||||
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
|
||||
&tkp.Privileges[0].Luid);
|
||||
|
||||
tkp.PrivilegeCount = 1; // one privilege to set
|
||||
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
|
||||
// Get shutdown privilege for this process.
|
||||
|
||||
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
|
||||
(PTOKEN_PRIVILEGES) NULL, 0);
|
||||
|
||||
// Cannot test the return value of AdjustTokenPrivileges.
|
||||
|
||||
if (GetLastError() != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
if (action == ShutdownDialogAction::Suspend)
|
||||
SetSuspendState(false, false, false);
|
||||
else if (action == ShutdownDialogAction::Hibernate)
|
||||
SetSuspendState(true, false, false);
|
||||
else
|
||||
InitiateSystemShutdownA(0, QCoreApplication::translate("misc", "qBittorrent will shutdown the computer now because all downloads are complete.").toLocal8Bit().data(), 10, true, false);
|
||||
|
||||
// Disable shutdown privilege.
|
||||
tkp.Privileges[0].Attributes = 0;
|
||||
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
|
||||
(PTOKEN_PRIVILEGES) NULL, 0);
|
||||
#else
|
||||
Q_UNUSED(action);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -356,14 +358,22 @@ QString Utils::Misc::friendlyUnit(qint64 bytesValue, bool isSpeed)
|
||||
return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
|
||||
QString ret;
|
||||
if (unit == SizeUnit::Byte)
|
||||
ret = QString::number(bytesValue) + " " + unitString(unit);
|
||||
ret = QString::number(bytesValue) + QString::fromUtf8(C_NON_BREAKING_SPACE) + unitString(unit);
|
||||
else
|
||||
ret = Utils::String::fromDouble(friendlyVal, 1) + " " + unitString(unit);
|
||||
ret = Utils::String::fromDouble(friendlyVal, friendlyUnitPrecision(unit)) + QString::fromUtf8(C_NON_BREAKING_SPACE) + unitString(unit);
|
||||
if (isSpeed)
|
||||
ret += QCoreApplication::translate("misc", "/s", "per second");
|
||||
return ret;
|
||||
}
|
||||
|
||||
int Utils::Misc::friendlyUnitPrecision(SizeUnit unit)
|
||||
{
|
||||
// friendlyUnit's number of digits after the decimal point
|
||||
if (unit <= SizeUnit::MebiByte) return 1;
|
||||
else if (unit == SizeUnit::GibiByte) return 2;
|
||||
else return 3;
|
||||
}
|
||||
|
||||
qlonglong Utils::Misc::sizeInBytes(qreal size, Utils::Misc::SizeUnit unit)
|
||||
{
|
||||
for (int i = 0; i < static_cast<int>(unit); ++i)
|
||||
@@ -430,21 +440,27 @@ QString Utils::Misc::userFriendlyDuration(qlonglong seconds)
|
||||
{
|
||||
if ((seconds < 0) || (seconds >= MAX_ETA))
|
||||
return QString::fromUtf8(C_INFINITY);
|
||||
|
||||
if (seconds == 0)
|
||||
return "0";
|
||||
|
||||
if (seconds < 60)
|
||||
return QCoreApplication::translate("misc", "< 1m", "< 1 minute");
|
||||
int minutes = seconds / 60;
|
||||
|
||||
qlonglong minutes = seconds / 60;
|
||||
if (minutes < 60)
|
||||
return QCoreApplication::translate("misc", "%1m", "e.g: 10minutes").arg(QString::number(minutes));
|
||||
int hours = minutes / 60;
|
||||
minutes = minutes - hours * 60;
|
||||
|
||||
qlonglong hours = minutes / 60;
|
||||
minutes -= hours * 60;
|
||||
if (hours < 24)
|
||||
return QCoreApplication::translate("misc", "%1h %2m", "e.g: 3hours 5minutes").arg(QString::number(hours)).arg(QString::number(minutes));
|
||||
int days = hours / 24;
|
||||
hours = hours - days * 24;
|
||||
|
||||
qlonglong days = hours / 24;
|
||||
hours -= days * 24;
|
||||
if (days < 100)
|
||||
return QCoreApplication::translate("misc", "%1d %2h", "e.g: 2days 10hours").arg(QString::number(days)).arg(QString::number(hours));
|
||||
|
||||
return QString::fromUtf8(C_INFINITY);
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ namespace Utils
|
||||
// value must be given in bytes
|
||||
bool friendlyUnit(qint64 sizeInBytes, qreal& val, SizeUnit& unit);
|
||||
QString friendlyUnit(qint64 bytesValue, bool isSpeed = false);
|
||||
int friendlyUnitPrecision(SizeUnit unit);
|
||||
qint64 sizeInBytes(qreal size, SizeUnit unit);
|
||||
|
||||
bool isPreviewable(const QString& extension);
|
||||
|
||||
@@ -177,8 +177,12 @@ QString Utils::String::fromStdString(const std::string &str)
|
||||
|
||||
std::string Utils::String::toStdString(const QString &str)
|
||||
{
|
||||
#ifdef QBT_USES_QT5
|
||||
return str.toStdString();
|
||||
#else
|
||||
QByteArray utf8 = str.toUtf8();
|
||||
return std::string(utf8.constData(), utf8.length());
|
||||
#endif
|
||||
}
|
||||
|
||||
// to send numbers instead of strings with suffixes
|
||||
@@ -207,3 +211,12 @@ bool Utils::String::slowEquals(const QByteArray &a, const QByteArray &b)
|
||||
|
||||
return (diff == 0);
|
||||
}
|
||||
|
||||
QString Utils::String::toHtmlEscaped(const QString &str)
|
||||
{
|
||||
#ifdef QBT_USES_QT5
|
||||
return str.toHtmlEscaped();
|
||||
#else
|
||||
return Qt::escape(str);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ namespace Utils
|
||||
// Taken from https://crackstation.net/hashing-security.htm
|
||||
bool slowEquals(const QByteArray &a, const QByteArray &b);
|
||||
|
||||
QString toHtmlEscaped(const QString &str);
|
||||
|
||||
bool naturalCompareCaseSensitive(const QString &left, const QString &right);
|
||||
bool naturalCompareCaseInsensitive(const QString &left, const QString &right);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ addnewtorrentdialog.h
|
||||
advancedsettings.h
|
||||
advancedsettings.h
|
||||
autoexpandabledialog.h
|
||||
categoryfiltermodel.h
|
||||
categoryfilterwidget.h
|
||||
cookiesdialog.h
|
||||
cookiesmodel.h
|
||||
deletionconfirmationdlg.h
|
||||
@@ -71,6 +73,8 @@ set(QBT_GUI_SOURCES
|
||||
addnewtorrentdialog.cpp
|
||||
advancedsettings.cpp
|
||||
autoexpandabledialog.cpp
|
||||
categoryfiltermodel.cpp
|
||||
categoryfilterwidget.cpp
|
||||
cookiesdialog.cpp
|
||||
cookiesmodel.cpp
|
||||
executionlog.cpp
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
"</table>"
|
||||
"</p>")
|
||||
.arg(tr("An advanced BitTorrent client programmed in C++, based on Qt toolkit and libtorrent-rasterbar."))
|
||||
.arg(tr("Copyright %1 2006-2016 The qBittorrent project").arg(QString::fromUtf8(C_COPYRIGHT)))
|
||||
.arg(tr("Copyright %1 2006-2017 The qBittorrent project").arg(QString::fromUtf8(C_COPYRIGHT)))
|
||||
.arg(tr("Home Page:"))
|
||||
.arg(tr("Forum:"))
|
||||
.arg(tr("Bug Tracker:"));
|
||||
|
||||
@@ -117,7 +117,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(QWidget *parent)
|
||||
// Signal / slots
|
||||
connect(ui->adv_button, SIGNAL(clicked(bool)), SLOT(showAdvancedSettings(bool)));
|
||||
connect(ui->doNotDeleteTorrentCheckBox, SIGNAL(clicked(bool)), SLOT(doNotDeleteTorrentClicked(bool)));
|
||||
editHotkey = new QShortcut(QKeySequence("F2"), ui->contentTreeView, 0, 0, Qt::WidgetShortcut);
|
||||
editHotkey = new QShortcut(Qt::Key_F2, ui->contentTreeView, 0, 0, Qt::WidgetShortcut);
|
||||
connect(editHotkey, SIGNAL(activated()), SLOT(renameSelectedFile()));
|
||||
connect(ui->contentTreeView, SIGNAL(doubleClicked(QModelIndex)), SLOT(renameSelectedFile()));
|
||||
|
||||
|
||||
@@ -87,7 +87,6 @@ enum AdvSettingsRows
|
||||
// seeding
|
||||
SUPER_SEEDING,
|
||||
// tracker
|
||||
TRACKER_EXCHANGE,
|
||||
ANNOUNCE_ALL_TRACKERS,
|
||||
ANNOUNCE_IP,
|
||||
|
||||
@@ -186,8 +185,6 @@ void AdvancedSettings::saveAdvancedSettings()
|
||||
pref->useSystemIconTheme(cb_use_icon_theme.isChecked());
|
||||
#endif
|
||||
pref->setConfirmTorrentRecheck(cb_confirm_torrent_recheck.isChecked());
|
||||
// Tracker exchange
|
||||
session->setTrackerExchangeEnabled(cb_enable_tracker_ext.isChecked());
|
||||
session->setAnnounceToAllTrackers(cb_announce_all_trackers.isChecked());
|
||||
}
|
||||
|
||||
@@ -380,9 +377,6 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
// Torrent recheck confirmation
|
||||
cb_confirm_torrent_recheck.setChecked(pref->confirmTorrentRecheck());
|
||||
addRow(CONFIRM_RECHECK_TORRENT, tr("Confirm torrent recheck"), &cb_confirm_torrent_recheck);
|
||||
// Tracker exchange
|
||||
cb_enable_tracker_ext.setChecked(session->isTrackerExchangeEnabled());
|
||||
addRow(TRACKER_EXCHANGE, tr("Exchange trackers with other peers"), &cb_enable_tracker_ext);
|
||||
// Announce to all trackers
|
||||
cb_announce_all_trackers.setChecked(session->announceToAllTrackers());
|
||||
addRow(ANNOUNCE_ALL_TRACKERS, tr("Always announce to all trackers"), &cb_announce_all_trackers);
|
||||
@@ -391,6 +385,10 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
template <typename T>
|
||||
void AdvancedSettings::addRow(int row, const QString &rowText, T* widget)
|
||||
{
|
||||
// ignore mouse wheel event
|
||||
static WheelEventEater filter;
|
||||
widget->installEventFilter(&filter);
|
||||
|
||||
setItem(row, PROPERTY, new QTableWidgetItem(rowText));
|
||||
setCellWidget(row, VALUE, widget);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#ifndef ADVANCEDSETTINGS_H
|
||||
#define ADVANCEDSETTINGS_H
|
||||
|
||||
#include <QEvent>
|
||||
#include <QLabel>
|
||||
#include <QSpinBox>
|
||||
#include <QCheckBox>
|
||||
@@ -37,6 +38,22 @@
|
||||
#include <QTableWidget>
|
||||
|
||||
|
||||
class WheelEventEater: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
bool eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case QEvent::Wheel:
|
||||
return true;
|
||||
default:
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AdvancedSettings: public QTableWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -62,7 +79,7 @@ private:
|
||||
QSpinBox spin_cache, spin_save_resume_data_interval, outgoing_ports_min, outgoing_ports_max, spin_list_refresh, spin_maxhalfopen, spin_tracker_port, spin_cache_ttl;
|
||||
QCheckBox cb_os_cache, cb_recheck_completed, cb_resolve_countries, cb_resolve_hosts, cb_super_seeding,
|
||||
cb_program_notifications, cb_torrent_added_notifications, cb_tracker_favicon, cb_tracker_status,
|
||||
cb_confirm_torrent_recheck, cb_enable_tracker_ext, cb_listen_ipv6, cb_announce_all_trackers;
|
||||
cb_confirm_torrent_recheck, cb_listen_ipv6, cb_announce_all_trackers;
|
||||
QComboBox combo_iface, combo_iface_address;
|
||||
QLineEdit txtAnnounceIP;
|
||||
|
||||
|
||||
@@ -18,15 +18,6 @@
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSlider" name="bandwidthSlider">
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="sliderPosition">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
|
||||
442
src/gui/categoryfiltermodel.cpp
Normal file
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "categoryfiltermodel.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QIcon>
|
||||
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "guiiconprovider.h"
|
||||
|
||||
class CategoryModelItem
|
||||
{
|
||||
public:
|
||||
CategoryModelItem()
|
||||
: m_parent(nullptr)
|
||||
, m_torrentsCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
CategoryModelItem(CategoryModelItem *parent, QString categoryName, int torrentsCount = 0)
|
||||
: m_parent(nullptr)
|
||||
, m_name(categoryName)
|
||||
, m_torrentsCount(torrentsCount)
|
||||
{
|
||||
if (parent)
|
||||
parent->addChild(m_name, this);
|
||||
}
|
||||
|
||||
~CategoryModelItem()
|
||||
{
|
||||
clear();
|
||||
if (m_parent) {
|
||||
m_parent->m_torrentsCount -= m_torrentsCount;
|
||||
const QString uid = m_parent->m_children.key(this);
|
||||
m_parent->m_children.remove(uid);
|
||||
m_parent->m_childUids.removeOne(uid);
|
||||
}
|
||||
}
|
||||
|
||||
QString name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString fullName() const
|
||||
{
|
||||
if (!m_parent || m_parent->name().isEmpty())
|
||||
return m_name;
|
||||
|
||||
return QString("%1/%2").arg(m_parent->fullName()).arg(m_name);
|
||||
}
|
||||
|
||||
CategoryModelItem *parent() const
|
||||
{
|
||||
return m_parent;
|
||||
}
|
||||
|
||||
int torrentsCount() const
|
||||
{
|
||||
return m_torrentsCount;
|
||||
}
|
||||
|
||||
void increaseTorrentsCount()
|
||||
{
|
||||
++m_torrentsCount;
|
||||
if (m_parent)
|
||||
m_parent->increaseTorrentsCount();
|
||||
}
|
||||
|
||||
void decreaseTorrentsCount()
|
||||
{
|
||||
--m_torrentsCount;
|
||||
if (m_parent)
|
||||
m_parent->decreaseTorrentsCount();
|
||||
}
|
||||
|
||||
int pos() const
|
||||
{
|
||||
if (!m_parent) return -1;
|
||||
|
||||
return m_parent->m_childUids.indexOf(m_name);
|
||||
}
|
||||
|
||||
bool hasChild(const QString &name) const
|
||||
{
|
||||
return m_children.contains(name);
|
||||
}
|
||||
|
||||
int childCount() const
|
||||
{
|
||||
return m_children.count();
|
||||
}
|
||||
|
||||
CategoryModelItem *child(const QString &uid) const
|
||||
{
|
||||
return m_children.value(uid);
|
||||
}
|
||||
|
||||
CategoryModelItem *childAt(int index) const
|
||||
{
|
||||
if ((index < 0) || (index >= m_childUids.count()))
|
||||
return nullptr;
|
||||
|
||||
return m_children[m_childUids[index]];
|
||||
}
|
||||
|
||||
void addChild(const QString &uid, CategoryModelItem *item)
|
||||
{
|
||||
Q_ASSERT(item);
|
||||
Q_ASSERT(!item->parent());
|
||||
Q_ASSERT(!m_children.contains(uid));
|
||||
|
||||
item->m_parent = this;
|
||||
m_children[uid] = item;
|
||||
auto pos = std::lower_bound(m_childUids.begin(), m_childUids.end(), uid);
|
||||
m_childUids.insert(pos, uid);
|
||||
m_torrentsCount += item->torrentsCount();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
// use copy of m_children for qDeleteAll
|
||||
// to avoid collision when child removes
|
||||
// itself from parent children
|
||||
qDeleteAll(decltype(m_children)(m_children));
|
||||
}
|
||||
|
||||
private:
|
||||
CategoryModelItem *m_parent;
|
||||
QString m_name;
|
||||
int m_torrentsCount;
|
||||
QHash<QString, CategoryModelItem *> m_children;
|
||||
QStringList m_childUids;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
QString shortName(const QString &fullName)
|
||||
{
|
||||
int pos = fullName.lastIndexOf(QLatin1Char('/'));
|
||||
if (pos >= 0)
|
||||
return fullName.mid(pos + 1);
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
|
||||
CategoryFilterModel::CategoryFilterModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, m_rootItem(new CategoryModelItem)
|
||||
{
|
||||
auto session = BitTorrent::Session::instance();
|
||||
|
||||
connect(session, SIGNAL(categoryAdded(QString)), SLOT(categoryAdded(QString)));
|
||||
connect(session, SIGNAL(categoryRemoved(QString)), SLOT(categoryRemoved(QString)));
|
||||
connect(session, SIGNAL(torrentCategoryChanged(BitTorrent::TorrentHandle *const, QString))
|
||||
, SLOT(torrentCategoryChanged(BitTorrent::TorrentHandle *const, QString)));
|
||||
connect(session, SIGNAL(subcategoriesSupportChanged()), SLOT(subcategoriesSupportChanged()));
|
||||
connect(session, SIGNAL(torrentAdded(BitTorrent::TorrentHandle *const))
|
||||
, SLOT(torrentAdded(BitTorrent::TorrentHandle *const)));
|
||||
connect(session, SIGNAL(torrentAboutToBeRemoved(BitTorrent::TorrentHandle *const))
|
||||
, SLOT(torrentAboutToBeRemoved(BitTorrent::TorrentHandle *const)));
|
||||
|
||||
populate();
|
||||
}
|
||||
|
||||
CategoryFilterModel::~CategoryFilterModel()
|
||||
{
|
||||
delete m_rootItem;
|
||||
}
|
||||
|
||||
int CategoryFilterModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
QVariant CategoryFilterModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) return QVariant();
|
||||
|
||||
auto item = static_cast<CategoryModelItem *>(index.internalPointer());
|
||||
|
||||
if ((index.column() == 0) && (role == Qt::DecorationRole)) {
|
||||
return GuiIconProvider::instance()->getIcon("inode-directory");
|
||||
}
|
||||
|
||||
if ((index.column() == 0) && (role == Qt::DisplayRole)) {
|
||||
return QString(QStringLiteral("%1 (%2)"))
|
||||
.arg(item->name()).arg(item->torrentsCount());
|
||||
}
|
||||
|
||||
if ((index.column() == 0) && (role == Qt::UserRole)) {
|
||||
return item->torrentsCount();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags CategoryFilterModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) return 0;
|
||||
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
|
||||
QVariant CategoryFilterModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole))
|
||||
if (section == 0)
|
||||
return tr("Categories");
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QModelIndex CategoryFilterModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (column > 0)
|
||||
return QModelIndex();
|
||||
|
||||
if (parent.isValid() && (parent.column() != 0))
|
||||
return QModelIndex();
|
||||
|
||||
auto parentItem = parent.isValid() ? static_cast<CategoryModelItem *>(parent.internalPointer())
|
||||
: m_rootItem;
|
||||
if (row < parentItem->childCount())
|
||||
return createIndex(row, column, parentItem->childAt(row));
|
||||
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex CategoryFilterModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QModelIndex();
|
||||
|
||||
auto item = static_cast<CategoryModelItem *>(index.internalPointer());
|
||||
if (!item) return QModelIndex();
|
||||
|
||||
return this->index(item->parent());
|
||||
}
|
||||
|
||||
int CategoryFilterModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.column() > 0)
|
||||
return 0;
|
||||
|
||||
if (!parent.isValid())
|
||||
return m_rootItem->childCount();
|
||||
|
||||
auto item = static_cast<CategoryModelItem *>(parent.internalPointer());
|
||||
if (!item) return 0;
|
||||
|
||||
return item->childCount();
|
||||
}
|
||||
|
||||
QModelIndex CategoryFilterModel::index(const QString &categoryName) const
|
||||
{
|
||||
return index(findItem(categoryName));
|
||||
}
|
||||
|
||||
QString CategoryFilterModel::categoryName(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) return QString();
|
||||
return static_cast<CategoryModelItem *>(index.internalPointer())->fullName();
|
||||
}
|
||||
|
||||
QModelIndex CategoryFilterModel::index(CategoryModelItem *item) const
|
||||
{
|
||||
if (!item || !item->parent()) return QModelIndex();
|
||||
|
||||
return index(item->pos(), 0, index(item->parent()));
|
||||
}
|
||||
|
||||
void CategoryFilterModel::categoryAdded(const QString &categoryName)
|
||||
{
|
||||
CategoryModelItem *parent = m_rootItem;
|
||||
|
||||
if (m_isSubcategoriesEnabled) {
|
||||
QStringList expanded = BitTorrent::Session::expandCategory(categoryName);
|
||||
if (expanded.count() > 1)
|
||||
parent = findItem(expanded[expanded.count() - 2]);
|
||||
}
|
||||
|
||||
auto item = new CategoryModelItem(
|
||||
parent, m_isSubcategoriesEnabled ? shortName(categoryName) : categoryName);
|
||||
|
||||
QModelIndex i = index(item);
|
||||
beginInsertRows(i.parent(), i.row(), i.row());
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void CategoryFilterModel::categoryRemoved(const QString &categoryName)
|
||||
{
|
||||
auto item = findItem(categoryName);
|
||||
if (item) {
|
||||
QModelIndex i = index(item);
|
||||
beginRemoveRows(i.parent(), i.row(), i.row());
|
||||
delete item;
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
void CategoryFilterModel::torrentAdded(BitTorrent::TorrentHandle *const torrent)
|
||||
{
|
||||
CategoryModelItem *item = findItem(torrent->category());
|
||||
Q_ASSERT(item);
|
||||
|
||||
item->increaseTorrentsCount();
|
||||
m_rootItem->childAt(0)->increaseTorrentsCount();
|
||||
}
|
||||
|
||||
void CategoryFilterModel::torrentAboutToBeRemoved(BitTorrent::TorrentHandle *const torrent)
|
||||
{
|
||||
CategoryModelItem *item = findItem(torrent->category());
|
||||
Q_ASSERT(item);
|
||||
|
||||
item->decreaseTorrentsCount();
|
||||
m_rootItem->childAt(0)->decreaseTorrentsCount();
|
||||
}
|
||||
|
||||
void CategoryFilterModel::torrentCategoryChanged(BitTorrent::TorrentHandle *const torrent, const QString &oldCategory)
|
||||
{
|
||||
QModelIndex i;
|
||||
|
||||
auto item = findItem(oldCategory);
|
||||
Q_ASSERT(item);
|
||||
|
||||
item->decreaseTorrentsCount();
|
||||
i = index(item);
|
||||
while (i.isValid()) {
|
||||
emit dataChanged(i, i);
|
||||
i = parent(i);
|
||||
}
|
||||
|
||||
item = findItem(torrent->category());
|
||||
Q_ASSERT(item);
|
||||
|
||||
item->increaseTorrentsCount();
|
||||
i = index(item);
|
||||
while (i.isValid()) {
|
||||
emit dataChanged(i, i);
|
||||
i = parent(i);
|
||||
}
|
||||
}
|
||||
|
||||
void CategoryFilterModel::subcategoriesSupportChanged()
|
||||
{
|
||||
beginResetModel();
|
||||
populate();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void CategoryFilterModel::populate()
|
||||
{
|
||||
m_rootItem->clear();
|
||||
|
||||
auto session = BitTorrent::Session::instance();
|
||||
auto torrents = session->torrents();
|
||||
m_isSubcategoriesEnabled = session->isSubcategoriesEnabled();
|
||||
|
||||
const QString UID_ALL;
|
||||
const QString UID_UNCATEGORIZED(QChar(1));
|
||||
|
||||
// All torrents
|
||||
m_rootItem->addChild(UID_ALL, new CategoryModelItem(nullptr, tr("All"), torrents.count()));
|
||||
|
||||
// Uncategorized torrents
|
||||
using Torrent = BitTorrent::TorrentHandle;
|
||||
m_rootItem->addChild(
|
||||
UID_UNCATEGORIZED
|
||||
, new CategoryModelItem(
|
||||
nullptr, tr("Uncategorized")
|
||||
, std::count_if(torrents.begin(), torrents.end()
|
||||
, [](Torrent *torrent) { return torrent->category().isEmpty(); })));
|
||||
|
||||
using Torrent = BitTorrent::TorrentHandle;
|
||||
foreach (const QString &category, session->categories()) {
|
||||
if (m_isSubcategoriesEnabled) {
|
||||
CategoryModelItem *parent = m_rootItem;
|
||||
foreach (const QString &subcat, session->expandCategory(category)) {
|
||||
const QString subcatName = shortName(subcat);
|
||||
if (!parent->hasChild(subcatName)) {
|
||||
new CategoryModelItem(
|
||||
parent, subcatName
|
||||
, std::count_if(torrents.begin(), torrents.end()
|
||||
, [subcat](Torrent *torrent) { return torrent->category() == subcat; }));
|
||||
}
|
||||
parent = parent->child(subcatName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
new CategoryModelItem(
|
||||
m_rootItem, category
|
||||
, std::count_if(torrents.begin(), torrents.end()
|
||||
, [category](Torrent *torrent) { return torrent->belongsToCategory(category); }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CategoryModelItem *CategoryFilterModel::findItem(const QString &fullName) const
|
||||
{
|
||||
if (fullName.isEmpty())
|
||||
return m_rootItem->childAt(1); // "Uncategorized" item
|
||||
|
||||
if (!m_isSubcategoriesEnabled)
|
||||
return m_rootItem->child(fullName);
|
||||
|
||||
CategoryModelItem *item = m_rootItem;
|
||||
foreach (const QString &subcat, BitTorrent::Session::expandCategory(fullName)) {
|
||||
const QString subcatName = shortName(subcat);
|
||||
if (!item->hasChild(subcatName)) return nullptr;
|
||||
item = item->child(subcatName);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
79
src/gui/categoryfiltermodel.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#ifndef CATEGORYFILTERMODEL_H
|
||||
#define CATEGORYFILTERMODEL_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QHash>
|
||||
#include <QModelIndex>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentHandle;
|
||||
}
|
||||
|
||||
class CategoryModelItem;
|
||||
|
||||
class CategoryFilterModel: public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CategoryFilterModel(QObject *parent = nullptr);
|
||||
~CategoryFilterModel();
|
||||
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
QModelIndex index(const QString &categoryName) const;
|
||||
QString categoryName(const QModelIndex &index) const;
|
||||
|
||||
private slots:
|
||||
void categoryAdded(const QString &categoryName);
|
||||
void categoryRemoved(const QString &categoryName);
|
||||
void torrentAdded(BitTorrent::TorrentHandle *const torrent);
|
||||
void torrentAboutToBeRemoved(BitTorrent::TorrentHandle *const torrent);
|
||||
void torrentCategoryChanged(BitTorrent::TorrentHandle *const torrent, const QString &oldCategory);
|
||||
void subcategoriesSupportChanged();
|
||||
|
||||
private:
|
||||
void populate();
|
||||
QModelIndex index(CategoryModelItem *item) const;
|
||||
CategoryModelItem *findItem(const QString &fullName) const;
|
||||
|
||||
bool m_isSubcategoriesEnabled;
|
||||
CategoryModelItem *m_rootItem;
|
||||
};
|
||||
|
||||
#endif // CATEGORYFILTERMODEL_H
|
||||
270
src/gui/categoryfilterwidget.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "categoryfilterwidget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QHeaderView>
|
||||
#include <QLayout>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "autoexpandabledialog.h"
|
||||
#include "categoryfiltermodel.h"
|
||||
#include "guiiconprovider.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
QString getCategoryFilter(const CategoryFilterModel *const model, const QModelIndex &index)
|
||||
{
|
||||
QString categoryFilter; // Defaults to All
|
||||
if (index.isValid()) {
|
||||
if (!index.parent().isValid() && (index.row() == 1))
|
||||
categoryFilter = ""; // Uncategorized
|
||||
else if (index.parent().isValid() || (index.row() > 1))
|
||||
categoryFilter = model->categoryName(index);
|
||||
}
|
||||
|
||||
return categoryFilter;
|
||||
}
|
||||
|
||||
bool isSpecialItem(const QModelIndex &index)
|
||||
{
|
||||
// the first two items at first level are special items:
|
||||
// 'All' and 'Uncategorized'
|
||||
return (!index.parent().isValid() && (index.row() <= 1));
|
||||
}
|
||||
}
|
||||
|
||||
CategoryFilterWidget::CategoryFilterWidget(QWidget *parent)
|
||||
: QTreeView(parent)
|
||||
{
|
||||
setModel(new CategoryFilterModel(this));
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
setUniformRowHeights(true);
|
||||
setHeaderHidden(true);
|
||||
setIconSize(Utils::Misc::smallIconSize());
|
||||
#if defined(Q_OS_MAC)
|
||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
#endif
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
setCurrentIndex(model()->index(0, 0));
|
||||
|
||||
connect(this, SIGNAL(collapsed(QModelIndex)), SLOT(callUpdateGeometry()));
|
||||
connect(this, SIGNAL(expanded(QModelIndex)), SLOT(callUpdateGeometry()));
|
||||
connect(this, SIGNAL(customContextMenuRequested(QPoint)), SLOT(showMenu(QPoint)));
|
||||
connect(selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex))
|
||||
, SLOT(onCurrentRowChanged(QModelIndex,QModelIndex)));
|
||||
connect(model(), SIGNAL(modelReset()), SLOT(callUpdateGeometry()));
|
||||
}
|
||||
|
||||
QString CategoryFilterWidget::currentCategory() const
|
||||
{
|
||||
QModelIndex current;
|
||||
auto selectedRows = selectionModel()->selectedRows();
|
||||
if (!selectedRows.isEmpty())
|
||||
current = selectedRows.first();
|
||||
|
||||
return getCategoryFilter(static_cast<CategoryFilterModel *>(model()), current);
|
||||
}
|
||||
|
||||
void CategoryFilterWidget::onCurrentRowChanged(const QModelIndex ¤t, const QModelIndex &previous)
|
||||
{
|
||||
Q_UNUSED(previous);
|
||||
|
||||
emit categoryChanged(getCategoryFilter(static_cast<CategoryFilterModel *>(model()), current));
|
||||
}
|
||||
|
||||
void CategoryFilterWidget::showMenu(QPoint)
|
||||
{
|
||||
QMenu menu(this);
|
||||
|
||||
QAction *addAct = menu.addAction(
|
||||
GuiIconProvider::instance()->getIcon("list-add")
|
||||
, tr("Add category..."));
|
||||
connect(addAct, SIGNAL(triggered()), SLOT(addCategory()));
|
||||
|
||||
auto selectedRows = selectionModel()->selectedRows();
|
||||
if (!selectedRows.empty() && !isSpecialItem(selectedRows.first())) {
|
||||
if (BitTorrent::Session::instance()->isSubcategoriesEnabled()) {
|
||||
QAction *addSubAct = menu.addAction(
|
||||
GuiIconProvider::instance()->getIcon("list-add")
|
||||
, tr("Add subcategory..."));
|
||||
connect(addSubAct, SIGNAL(triggered()), SLOT(addSubcategory()));
|
||||
}
|
||||
|
||||
QAction *removeAct = menu.addAction(
|
||||
GuiIconProvider::instance()->getIcon("list-remove")
|
||||
, tr("Remove category"));
|
||||
connect(removeAct, SIGNAL(triggered()), SLOT(removeCategory()));
|
||||
}
|
||||
|
||||
QAction *removeUnusedAct = menu.addAction(
|
||||
GuiIconProvider::instance()->getIcon("list-remove")
|
||||
, tr("Remove unused categories"));
|
||||
connect(removeUnusedAct, SIGNAL(triggered()), SLOT(removeUnusedCategories()));
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
QAction *startAct = menu.addAction(
|
||||
GuiIconProvider::instance()->getIcon("media-playback-start")
|
||||
, tr("Resume torrents"));
|
||||
connect(startAct, SIGNAL(triggered()), SIGNAL(actionResumeTorrentsTriggered()));
|
||||
|
||||
QAction *pauseAct = menu.addAction(
|
||||
GuiIconProvider::instance()->getIcon("media-playback-pause")
|
||||
, tr("Pause torrents"));
|
||||
connect(pauseAct, SIGNAL(triggered()), SIGNAL(actionPauseTorrentsTriggered()));
|
||||
|
||||
QAction *deleteTorrentsAct = menu.addAction(
|
||||
GuiIconProvider::instance()->getIcon("edit-delete")
|
||||
, tr("Delete torrents"));
|
||||
connect(deleteTorrentsAct, SIGNAL(triggered()), SIGNAL(actionDeleteTorrentsTriggered()));
|
||||
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void CategoryFilterWidget::callUpdateGeometry()
|
||||
{
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
QSize CategoryFilterWidget::sizeHint() const
|
||||
{
|
||||
#ifdef QBT_USES_QT5
|
||||
return viewportSizeHint();
|
||||
#else
|
||||
int lastRow = model()->rowCount() - 1;
|
||||
QModelIndex last = model()->index(lastRow, 0);
|
||||
while ((lastRow >= 0) && isExpanded(last)) {
|
||||
lastRow = model()->rowCount(last) - 1;
|
||||
last = model()->index(lastRow, 0, last);
|
||||
}
|
||||
const QRect deepestRect = visualRect(last);
|
||||
|
||||
if (!deepestRect.isValid())
|
||||
return viewport()->sizeHint();
|
||||
|
||||
return QSize(header()->length(), deepestRect.bottom() + 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
QSize CategoryFilterWidget::minimumSizeHint() const
|
||||
{
|
||||
QSize size = sizeHint();
|
||||
size.setWidth(6);
|
||||
return size;
|
||||
}
|
||||
|
||||
void CategoryFilterWidget::rowsInserted(const QModelIndex &parent, int start, int end)
|
||||
{
|
||||
QTreeView::rowsInserted(parent, start, end);
|
||||
|
||||
// Expand all parents if the parent(s) of the node are not expanded.
|
||||
QModelIndex p = parent;
|
||||
while (p.isValid()) {
|
||||
if (!isExpanded(p))
|
||||
expand(p);
|
||||
p = model()->parent(p);
|
||||
}
|
||||
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
QString CategoryFilterWidget::askCategoryName()
|
||||
{
|
||||
bool ok;
|
||||
QString category = "";
|
||||
bool invalid;
|
||||
do {
|
||||
invalid = false;
|
||||
category = AutoExpandableDialog::getText(
|
||||
this, tr("New Category"), tr("Category:"), QLineEdit::Normal, category, &ok);
|
||||
if (ok && !category.isEmpty()) {
|
||||
if (!BitTorrent::Session::isValidCategoryName(category)) {
|
||||
QMessageBox::warning(
|
||||
this, tr("Invalid category name")
|
||||
, tr("Category name must not contain '\\'.\n"
|
||||
"Category name must not start/end with '/'.\n"
|
||||
"Category name must not contain '//' sequence."));
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
} while (invalid);
|
||||
|
||||
return ok ? category : QString();
|
||||
}
|
||||
|
||||
void CategoryFilterWidget::addCategory()
|
||||
{
|
||||
const QString category = askCategoryName();
|
||||
if (category.isEmpty()) return;
|
||||
|
||||
if (BitTorrent::Session::instance()->categories().contains(category))
|
||||
QMessageBox::warning(this, tr("Category exists"), tr("Category name already exists."));
|
||||
else
|
||||
BitTorrent::Session::instance()->addCategory(category);
|
||||
}
|
||||
|
||||
void CategoryFilterWidget::addSubcategory()
|
||||
{
|
||||
const QString subcat = askCategoryName();
|
||||
if (subcat.isEmpty()) return;
|
||||
|
||||
const QString category = QString(QStringLiteral("%1/%2")).arg(currentCategory()).arg(subcat);
|
||||
|
||||
if (BitTorrent::Session::instance()->categories().contains(category))
|
||||
QMessageBox::warning(this, tr("Category exists")
|
||||
, tr("Subcategory name already exists in selected category."));
|
||||
else
|
||||
BitTorrent::Session::instance()->addCategory(category);
|
||||
}
|
||||
|
||||
void CategoryFilterWidget::removeCategory()
|
||||
{
|
||||
auto selectedRows = selectionModel()->selectedRows();
|
||||
if (!selectedRows.empty() && !isSpecialItem(selectedRows.first())) {
|
||||
BitTorrent::Session::instance()->removeCategory(
|
||||
static_cast<CategoryFilterModel *>(model())->categoryName(selectedRows.first()));
|
||||
updateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
void CategoryFilterWidget::removeUnusedCategories()
|
||||
{
|
||||
auto session = BitTorrent::Session::instance();
|
||||
foreach (const QString &category, session->categories())
|
||||
if (model()->data(static_cast<CategoryFilterModel *>(model())->index(category), Qt::UserRole) == 0)
|
||||
session->removeCategory(category);
|
||||
updateGeometry();
|
||||
}
|
||||
60
src/gui/categoryfilterwidget.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <QTreeView>
|
||||
|
||||
class CategoryFilterWidget: public QTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CategoryFilterWidget(QWidget *parent = nullptr);
|
||||
|
||||
QString currentCategory() const;
|
||||
|
||||
signals:
|
||||
void categoryChanged(const QString &categoryName);
|
||||
void actionResumeTorrentsTriggered();
|
||||
void actionPauseTorrentsTriggered();
|
||||
void actionDeleteTorrentsTriggered();
|
||||
|
||||
private slots:
|
||||
void onCurrentRowChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void showMenu(QPoint);
|
||||
void callUpdateGeometry();
|
||||
void addCategory();
|
||||
void addSubcategory();
|
||||
void removeCategory();
|
||||
void removeUnusedCategories();
|
||||
|
||||
private:
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
void rowsInserted(const QModelIndex &parent, int start, int end) override;
|
||||
QString askCategoryName();
|
||||
};
|
||||
@@ -35,8 +35,9 @@
|
||||
#include <QPushButton>
|
||||
#include "ui_confirmdeletiondlg.h"
|
||||
#include "base/preferences.h"
|
||||
#include "guiiconprovider.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "guiiconprovider.h"
|
||||
|
||||
class DeletionConfirmationDlg : public QDialog, private Ui::confirmDeletionDlg {
|
||||
Q_OBJECT
|
||||
@@ -45,7 +46,7 @@ class DeletionConfirmationDlg : public QDialog, private Ui::confirmDeletionDlg {
|
||||
DeletionConfirmationDlg(QWidget *parent, const int &size, const QString &name, bool defaultDeleteFiles): QDialog(parent) {
|
||||
setupUi(this);
|
||||
if (size == 1)
|
||||
label->setText(tr("Are you sure you want to delete '%1' from the transfer list?", "Are you sure you want to delete 'ubuntu-linux-iso' from the transfer list?").arg(name));
|
||||
label->setText(tr("Are you sure you want to delete '%1' from the transfer list?", "Are you sure you want to delete 'ubuntu-linux-iso' from the transfer list?").arg(Utils::String::toHtmlEscaped(name)));
|
||||
else
|
||||
label->setText(tr("Are you sure you want to delete these %1 torrents from the transfer list?", "Are you sure you want to delete these 5 torrents from the transfer list?").arg(QString::number(size)));
|
||||
// Icons
|
||||
|
||||
@@ -49,7 +49,9 @@ HEADERS += \
|
||||
$$PWD/search/searchlistdelegate.h \
|
||||
$$PWD/search/searchsortmodel.h \
|
||||
$$PWD/cookiesmodel.h \
|
||||
$$PWD/cookiesdialog.h
|
||||
$$PWD/cookiesdialog.h \
|
||||
$$PWD/categoryfiltermodel.h \
|
||||
$$PWD/categoryfilterwidget.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/mainwindow.cpp \
|
||||
@@ -89,7 +91,9 @@ SOURCES += \
|
||||
$$PWD/search/searchlistdelegate.cpp \
|
||||
$$PWD/search/searchsortmodel.cpp \
|
||||
$$PWD/cookiesmodel.cpp \
|
||||
$$PWD/cookiesdialog.cpp
|
||||
$$PWD/cookiesdialog.cpp \
|
||||
$$PWD/categoryfiltermodel.cpp \
|
||||
$$PWD/categoryfilterwidget.cpp
|
||||
|
||||
win32|macx {
|
||||
HEADERS += $$PWD/programupdater.h
|
||||
|
||||
@@ -57,13 +57,22 @@ GuiIconProvider *GuiIconProvider::instance()
|
||||
}
|
||||
|
||||
QIcon GuiIconProvider::getIcon(const QString &iconId)
|
||||
{
|
||||
return getIcon(iconId, iconId);
|
||||
}
|
||||
|
||||
QIcon GuiIconProvider::getIcon(const QString &iconId, const QString &fallback)
|
||||
{
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC))
|
||||
if (m_useSystemTheme) {
|
||||
QIcon icon = QIcon::fromTheme(iconId, QIcon(IconProvider::getIconPath(iconId)));
|
||||
QIcon icon = QIcon::fromTheme(iconId);
|
||||
if (icon.name() != iconId)
|
||||
icon = QIcon::fromTheme(fallback, QIcon(IconProvider::getIconPath(iconId)));
|
||||
icon = generateDifferentSizes(icon);
|
||||
return icon;
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(fallback)
|
||||
#endif
|
||||
return QIcon(IconProvider::getIconPath(iconId));
|
||||
}
|
||||
@@ -81,6 +90,13 @@ QIcon GuiIconProvider::getFlagIcon(const QString &countryIsoCode)
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC))
|
||||
QIcon GuiIconProvider::generateDifferentSizes(const QIcon &icon)
|
||||
{
|
||||
// if icon is loaded from SVG format, it already contains all the required sizes and we shall not resize it
|
||||
// In that case it will be available in the following sizes:
|
||||
// (QSize(16, 16), QSize(22, 22), QSize(32, 32), QSize(48, 48), QSize(64, 64), QSize(128, 128), QSize(256, 256))
|
||||
|
||||
if (icon.availableSizes(QIcon::Normal, QIcon::On).size() > 6)
|
||||
return icon;
|
||||
|
||||
QIcon newIcon;
|
||||
QList<QSize> requiredSizes;
|
||||
requiredSizes << QSize(16, 16) << QSize(24, 24) << QSize(32, 32);
|
||||
|
||||
@@ -44,6 +44,7 @@ public:
|
||||
static GuiIconProvider *instance();
|
||||
|
||||
QIcon getIcon(const QString &iconId);
|
||||
QIcon getIcon(const QString &iconId, const QString &fallback);
|
||||
QIcon getFlagIcon(const QString &countryIsoCode);
|
||||
QString getIconPath(const QString &iconId);
|
||||
|
||||
|
||||
@@ -117,8 +117,11 @@ namespace
|
||||
// Misc
|
||||
const QString KEY_DOWNLOAD_TRACKER_FAVICON = NOTIFICATIONS_SETTINGS_KEY("DownloadTrackerFavicon");
|
||||
|
||||
//just a shortcut
|
||||
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
|
||||
// just a shortcut
|
||||
inline SettingsStorage *settings()
|
||||
{
|
||||
return SettingsStorage::instance();
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
@@ -134,7 +137,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Preferences *const pref = Preferences::instance();
|
||||
m_uiLocked = pref->isUILocked();
|
||||
setWindowTitle("qBittorrent " VERSION);
|
||||
m_displaySpeedInTitle = pref->speedInTitleBar();
|
||||
@@ -144,7 +147,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
setWindowIcon(QIcon::fromTheme("qbittorrent", QIcon(":/icons/skin/qbittorrent32.png")));
|
||||
else
|
||||
#endif
|
||||
setWindowIcon(QIcon(":/icons/skin/qbittorrent32.png"));
|
||||
setWindowIcon(QIcon(":/icons/skin/qbittorrent32.png"));
|
||||
|
||||
addToolbarContextMenu();
|
||||
|
||||
@@ -174,12 +177,6 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(GuiIconProvider::instance()->getIcon("application-exit"));
|
||||
m_ui->actionManageCookies->setIcon(GuiIconProvider::instance()->getIcon("preferences-web-browser-cookies"));
|
||||
|
||||
QMenu *startAllMenu = new QMenu(this);
|
||||
startAllMenu->addAction(m_ui->actionStartAll);
|
||||
m_ui->actionStart->setMenu(startAllMenu);
|
||||
QMenu *pauseAllMenu = new QMenu(this);
|
||||
pauseAllMenu->addAction(m_ui->actionPauseAll);
|
||||
m_ui->actionPause->setMenu(pauseAllMenu);
|
||||
QMenu *lockMenu = new QMenu(this);
|
||||
QAction *defineUiLockPasswdAct = lockMenu->addAction(tr("&Set Password"));
|
||||
connect(defineUiLockPasswdAct, SIGNAL(triggered()), this, SLOT(defineUILockPassword()));
|
||||
@@ -188,21 +185,21 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
m_ui->actionLock->setMenu(lockMenu);
|
||||
|
||||
// Creating Bittorrent session
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(fullDiskError(BitTorrent::TorrentHandle *const, QString)), this, SLOT(fullDiskError(BitTorrent::TorrentHandle *const, QString)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(addTorrentFailed(const QString &)), this, SLOT(addTorrentFailed(const QString &)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(torrentNew(BitTorrent::TorrentHandle *const)), this, SLOT(torrentNew(BitTorrent::TorrentHandle *const)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(torrentFinished(BitTorrent::TorrentHandle *const)), this, SLOT(finishedTorrent(BitTorrent::TorrentHandle *const)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackerAuthenticationRequired(BitTorrent::TorrentHandle *const)), this, SLOT(trackerAuthenticationRequired(BitTorrent::TorrentHandle *const)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(downloadFromUrlFailed(QString, QString)), this, SLOT(handleDownloadFromUrlFailure(QString, QString)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(fullDiskError(BitTorrent::TorrentHandle * const,QString)), this, SLOT(fullDiskError(BitTorrent::TorrentHandle * const,QString)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(addTorrentFailed(const QString&)), this, SLOT(addTorrentFailed(const QString&)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(torrentNew(BitTorrent::TorrentHandle * const)), this, SLOT(torrentNew(BitTorrent::TorrentHandle * const)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(torrentFinished(BitTorrent::TorrentHandle * const)), this, SLOT(finishedTorrent(BitTorrent::TorrentHandle * const)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackerAuthenticationRequired(BitTorrent::TorrentHandle * const)), this, SLOT(trackerAuthenticationRequired(BitTorrent::TorrentHandle * const)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(downloadFromUrlFailed(QString,QString)), this, SLOT(handleDownloadFromUrlFailure(QString,QString)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(speedLimitModeChanged(bool)), this, SLOT(updateAltSpeedsBtn(bool)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(recursiveTorrentDownloadPossible(BitTorrent::TorrentHandle *const)), this, SLOT(askRecursiveTorrentDownloadConfirmation(BitTorrent::TorrentHandle *const)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(recursiveTorrentDownloadPossible(BitTorrent::TorrentHandle * const)), this, SLOT(askRecursiveTorrentDownloadConfirmation(BitTorrent::TorrentHandle * const)));
|
||||
|
||||
qDebug("create tabWidget");
|
||||
m_tabs = new HidableTabWidget(this);
|
||||
connect(m_tabs, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
|
||||
|
||||
m_splitter = new QSplitter(Qt::Horizontal, this);
|
||||
//vSplitter->setChildrenCollapsible(false);
|
||||
// vSplitter->setChildrenCollapsible(false);
|
||||
|
||||
QSplitter *hSplitter = new QSplitter(Qt::Vertical, this);
|
||||
hSplitter->setChildrenCollapsible(false);
|
||||
@@ -220,7 +217,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
|
||||
// Transfer List tab
|
||||
m_transferListWidget = new TransferListWidget(hSplitter, this);
|
||||
//transferList->setStyleSheet("QTreeView {border: none;}"); // borderless
|
||||
// transferList->setStyleSheet("QTreeView {border: none;}"); // borderless
|
||||
m_propertiesWidget = new PropertiesWidget(hSplitter, this, m_transferListWidget);
|
||||
m_transferListFiltersWidget = new TransferListFiltersWidget(m_splitter, m_transferListWidget);
|
||||
m_transferListFiltersWidget->setDownloadTrackerFavicon(isDownloadTrackerFavicon());
|
||||
@@ -233,15 +230,15 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
m_tabs->addTab(m_splitter, GuiIconProvider::instance()->getIcon("folder-remote"), tr("Transfers"));
|
||||
|
||||
connect(m_searchFilter, SIGNAL(textChanged(QString)), m_transferListWidget, SLOT(applyNameFilter(QString)));
|
||||
connect(hSplitter, SIGNAL(splitterMoved(int, int)), this, SLOT(writeSettings()));
|
||||
connect(m_splitter, SIGNAL(splitterMoved(int, int)), this, SLOT(writeSettings()));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackersChanged(BitTorrent::TorrentHandle *const)), m_propertiesWidget, SLOT(loadTrackers(BitTorrent::TorrentHandle *const)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackersAdded(BitTorrent::TorrentHandle *const, const QList<BitTorrent::TrackerEntry> &)), m_transferListFiltersWidget, SLOT(addTrackers(BitTorrent::TorrentHandle *const, const QList<BitTorrent::TrackerEntry> &)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackersRemoved(BitTorrent::TorrentHandle *const, const QList<BitTorrent::TrackerEntry> &)), m_transferListFiltersWidget, SLOT(removeTrackers(BitTorrent::TorrentHandle *const, const QList<BitTorrent::TrackerEntry> &)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackerlessStateChanged(BitTorrent::TorrentHandle *const, bool)), m_transferListFiltersWidget, SLOT(changeTrackerless(BitTorrent::TorrentHandle *const, bool)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackerSuccess(BitTorrent::TorrentHandle *const, const QString &)), m_transferListFiltersWidget, SLOT(trackerSuccess(BitTorrent::TorrentHandle *const, const QString &)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackerError(BitTorrent::TorrentHandle *const, const QString &)), m_transferListFiltersWidget, SLOT(trackerError(BitTorrent::TorrentHandle *const, const QString &)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackerWarning(BitTorrent::TorrentHandle *const, const QString &)), m_transferListFiltersWidget, SLOT(trackerWarning(BitTorrent::TorrentHandle *const, const QString &)));
|
||||
connect(hSplitter, SIGNAL(splitterMoved(int,int)), this, SLOT(writeSettings()));
|
||||
connect(m_splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(writeSettings()));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackersChanged(BitTorrent::TorrentHandle * const)), m_propertiesWidget, SLOT(loadTrackers(BitTorrent::TorrentHandle * const)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackersAdded(BitTorrent::TorrentHandle * const,const QList<BitTorrent::TrackerEntry> &)), m_transferListFiltersWidget, SLOT(addTrackers(BitTorrent::TorrentHandle * const,const QList<BitTorrent::TrackerEntry> &)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackersRemoved(BitTorrent::TorrentHandle * const,const QList<BitTorrent::TrackerEntry> &)), m_transferListFiltersWidget, SLOT(removeTrackers(BitTorrent::TorrentHandle * const,const QList<BitTorrent::TrackerEntry> &)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackerlessStateChanged(BitTorrent::TorrentHandle * const,bool)), m_transferListFiltersWidget, SLOT(changeTrackerless(BitTorrent::TorrentHandle * const,bool)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackerSuccess(BitTorrent::TorrentHandle * const,const QString&)), m_transferListFiltersWidget, SLOT(trackerSuccess(BitTorrent::TorrentHandle * const,const QString&)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackerError(BitTorrent::TorrentHandle * const,const QString&)), m_transferListFiltersWidget, SLOT(trackerError(BitTorrent::TorrentHandle * const,const QString&)));
|
||||
connect(BitTorrent::Session::instance(), SIGNAL(trackerWarning(BitTorrent::TorrentHandle * const,const QString&)), m_transferListFiltersWidget, SLOT(trackerWarning(BitTorrent::TorrentHandle * const,const QString&)));
|
||||
|
||||
m_ui->centralWidgetLayout->addWidget(m_tabs);
|
||||
|
||||
@@ -376,8 +373,8 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
|
||||
// Update the number of torrents (tab)
|
||||
updateNbTorrents();
|
||||
connect(m_transferListWidget->getSourceModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(updateNbTorrents()));
|
||||
connect(m_transferListWidget->getSourceModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(updateNbTorrents()));
|
||||
connect(m_transferListWidget->getSourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateNbTorrents()));
|
||||
connect(m_transferListWidget->getSourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateNbTorrents()));
|
||||
|
||||
connect(pref, SIGNAL(changed()), this, SLOT(optionsSaved()));
|
||||
|
||||
@@ -461,7 +458,7 @@ void MainWindow::setDownloadTrackerFavicon(bool value)
|
||||
|
||||
void MainWindow::addToolbarContextMenu()
|
||||
{
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
m_toolbarMenu = new QMenu(this);
|
||||
|
||||
m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
@@ -585,7 +582,7 @@ void MainWindow::clearUILockPassword()
|
||||
|
||||
void MainWindow::on_actionLock_triggered()
|
||||
{
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Preferences *const pref = Preferences::instance();
|
||||
// Check if there is a password
|
||||
if (pref->getUILockPasswordMD5().isEmpty()) {
|
||||
// Ask for a password
|
||||
@@ -615,7 +612,6 @@ void MainWindow::displayRSSTab(bool enable)
|
||||
else if (m_rssWidget) {
|
||||
delete m_rssWidget;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::updateRSSTabLabel(int count)
|
||||
@@ -636,7 +632,12 @@ void MainWindow::displaySearchTab(bool enable)
|
||||
else if (m_searchWidget) {
|
||||
delete m_searchWidget;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::focusSearchFilter()
|
||||
{
|
||||
m_searchFilter->setFocus();
|
||||
m_searchFilter->selectAll();
|
||||
}
|
||||
|
||||
void MainWindow::updateNbTorrents()
|
||||
@@ -671,7 +672,7 @@ void MainWindow::tabChanged(int newTab)
|
||||
|
||||
void MainWindow::writeSettings()
|
||||
{
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Preferences *const pref = Preferences::instance();
|
||||
pref->setMainGeometry(saveGeometry());
|
||||
// Splitter size
|
||||
pref->setMainVSplitterState(m_splitter->saveState());
|
||||
@@ -694,13 +695,13 @@ void MainWindow::cleanup()
|
||||
delete m_searchFilterAction;
|
||||
|
||||
// remove all child widgets
|
||||
while (QWidget *w = findChild<QWidget *>())
|
||||
while (QWidget *w = findChild<QWidget * >())
|
||||
delete w;
|
||||
}
|
||||
|
||||
void MainWindow::readSettings()
|
||||
{
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
const QByteArray mainGeo = pref->getMainGeometry();
|
||||
if (!mainGeo.isEmpty() && restoreGeometry(mainGeo))
|
||||
m_posInitialized = true;
|
||||
@@ -757,30 +758,33 @@ void MainWindow::createKeyboardShortcuts()
|
||||
{
|
||||
m_ui->actionCreateTorrent->setShortcut(QKeySequence::New);
|
||||
m_ui->actionOpen->setShortcut(QKeySequence::Open);
|
||||
m_ui->actionDownloadFromURL->setShortcut(QKeySequence("Ctrl+Shift+O"));
|
||||
m_ui->actionExit->setShortcut(QKeySequence("Ctrl+Q"));
|
||||
m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_O);
|
||||
m_ui->actionExit->setShortcut(Qt::CTRL + Qt::Key_Q);
|
||||
|
||||
QShortcut *switchTransferShortcut = new QShortcut(QKeySequence("Alt+1"), this);
|
||||
QShortcut *switchTransferShortcut = new QShortcut(Qt::ALT + Qt::Key_1, this);
|
||||
connect(switchTransferShortcut, SIGNAL(activated()), this, SLOT(displayTransferTab()));
|
||||
QShortcut *switchSearchShortcut = new QShortcut(QKeySequence("Alt+2"), this);
|
||||
QShortcut *switchSearchShortcut = new QShortcut(Qt::ALT + Qt::Key_2, this);
|
||||
connect(switchSearchShortcut, SIGNAL(activated()), this, SLOT(displaySearchTab()));
|
||||
QShortcut *switchSearchShortcut2 = new QShortcut(QKeySequence::Find, this);
|
||||
connect(switchSearchShortcut2, SIGNAL(activated()), this, SLOT(displaySearchTab()));
|
||||
QShortcut *switchRSSShortcut = new QShortcut(QKeySequence("Alt+3"), this);
|
||||
QShortcut *switchRSSShortcut = new QShortcut(Qt::ALT + Qt::Key_3, this);
|
||||
connect(switchRSSShortcut, SIGNAL(activated()), this, SLOT(displayRSSTab()));
|
||||
QShortcut *switchExecutionLogShortcut = new QShortcut(Qt::ALT + Qt::Key_4, this);
|
||||
connect(switchExecutionLogShortcut, SIGNAL(activated()), this, SLOT(displayExecutionLogTab()));
|
||||
|
||||
QShortcut *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, this);
|
||||
connect(switchSearchFilterShortcut, SIGNAL(activated()), this, SLOT(focusSearchFilter()));
|
||||
|
||||
m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
|
||||
m_ui->actionOptions->setShortcut(QKeySequence("Alt+O"));
|
||||
m_ui->actionStart->setShortcut(QKeySequence("Ctrl+S"));
|
||||
m_ui->actionStartAll->setShortcut(QKeySequence("Ctrl+Shift+S"));
|
||||
m_ui->actionPause->setShortcut(QKeySequence("Ctrl+P"));
|
||||
m_ui->actionPauseAll->setShortcut(QKeySequence("Ctrl+Shift+P"));
|
||||
m_ui->actionBottomPriority->setShortcut(QKeySequence("Ctrl+Shift+-"));
|
||||
m_ui->actionDecreasePriority->setShortcut(QKeySequence("Ctrl+-"));
|
||||
m_ui->actionIncreasePriority->setShortcut(QKeySequence("Ctrl++"));
|
||||
m_ui->actionTopPriority->setShortcut(QKeySequence("Ctrl+Shift++"));
|
||||
m_ui->actionOptions->setShortcut(Qt::ALT + Qt::Key_O);
|
||||
m_ui->actionStart->setShortcut(Qt::CTRL + Qt::Key_S);
|
||||
m_ui->actionStartAll->setShortcut(Qt::CTRL + Qt::SHIFT +Qt::Key_S);
|
||||
m_ui->actionPause->setShortcut(Qt::CTRL + Qt::Key_P);
|
||||
m_ui->actionPauseAll->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_P);
|
||||
m_ui->actionBottomPriority->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus);
|
||||
m_ui->actionDecreasePriority->setShortcut(Qt::CTRL + Qt::Key_Minus);
|
||||
m_ui->actionIncreasePriority->setShortcut(Qt::CTRL + Qt::Key_Plus);
|
||||
m_ui->actionTopPriority->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Plus);
|
||||
#ifdef Q_OS_MAC
|
||||
m_ui->actionMinimize->setShortcut(QKeySequence("Ctrl+M"));
|
||||
m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M);
|
||||
addAction(m_ui->actionMinimize);
|
||||
#endif
|
||||
}
|
||||
@@ -791,23 +795,41 @@ void MainWindow::displayTransferTab() const
|
||||
m_tabs->setCurrentWidget(m_transferListWidget);
|
||||
}
|
||||
|
||||
void MainWindow::displaySearchTab() const
|
||||
void MainWindow::displaySearchTab()
|
||||
{
|
||||
if (m_searchWidget)
|
||||
m_tabs->setCurrentWidget(m_searchWidget);
|
||||
if (!m_searchWidget) {
|
||||
m_ui->actionSearchWidget->setChecked(true);
|
||||
displaySearchTab(true);
|
||||
}
|
||||
|
||||
m_tabs->setCurrentWidget(m_searchWidget);
|
||||
}
|
||||
|
||||
void MainWindow::displayRSSTab() const
|
||||
void MainWindow::displayRSSTab()
|
||||
{
|
||||
if (m_rssWidget)
|
||||
m_tabs->setCurrentWidget(m_rssWidget);
|
||||
if (!m_rssWidget) {
|
||||
m_ui->actionRSSReader->setChecked(true);
|
||||
displayRSSTab(true);
|
||||
}
|
||||
|
||||
m_tabs->setCurrentWidget(m_rssWidget);
|
||||
}
|
||||
|
||||
void MainWindow::displayExecutionLogTab()
|
||||
{
|
||||
if (!m_executionLog) {
|
||||
m_ui->actionExecutionLogs->setChecked(true);
|
||||
on_actionExecutionLogs_triggered(true);
|
||||
}
|
||||
|
||||
m_tabs->setCurrentWidget(m_executionLog);
|
||||
}
|
||||
|
||||
// End of keyboard shortcuts slots
|
||||
|
||||
void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::TorrentHandle *const torrent)
|
||||
{
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Preferences *const pref = Preferences::instance();
|
||||
if (pref->recursiveDownloadDisabled()) return;
|
||||
// Get Torrent name
|
||||
QString torrentName = torrent->name();
|
||||
@@ -835,7 +857,7 @@ void MainWindow::on_actionSetGlobalUploadLimit_triggered()
|
||||
BitTorrent::Session *const session = BitTorrent::Session::instance();
|
||||
bool ok = false;
|
||||
const long newLimit = SpeedLimitDialog::askSpeedLimit(
|
||||
&ok, tr("Global Upload Speed Limit"), session->uploadSpeedLimit());
|
||||
&ok, tr("Global Upload Speed Limit"), session->uploadSpeedLimit());
|
||||
if (ok) {
|
||||
qDebug("Setting global upload rate limit to %.1fKb/s", newLimit / 1024.);
|
||||
session->setUploadSpeedLimit(newLimit);
|
||||
@@ -848,7 +870,7 @@ void MainWindow::on_actionSetGlobalDownloadLimit_triggered()
|
||||
BitTorrent::Session *const session = BitTorrent::Session::instance();
|
||||
bool ok = false;
|
||||
const long newLimit = SpeedLimitDialog::askSpeedLimit(
|
||||
&ok, tr("Global Download Speed Limit"), session->downloadSpeedLimit());
|
||||
&ok, tr("Global Download Speed Limit"), session->downloadSpeedLimit());
|
||||
if (ok) {
|
||||
qDebug("Setting global download rate limit to %.1fKb/s", newLimit / 1024.);
|
||||
session->setDownloadSpeedLimit(newLimit);
|
||||
@@ -860,16 +882,15 @@ void MainWindow::on_actionSetGlobalDownloadLimit_triggered()
|
||||
void MainWindow::on_actionExit_triggered()
|
||||
{
|
||||
// UI locking enforcement.
|
||||
if (isHidden() && m_uiLocked) {
|
||||
if (isHidden() && m_uiLocked)
|
||||
// Ask for UI lock password
|
||||
if (!unlockUI()) return;
|
||||
}
|
||||
|
||||
m_forceExit = true;
|
||||
close();
|
||||
}
|
||||
|
||||
QWidget* MainWindow::currentTabWidget() const
|
||||
QWidget *MainWindow::currentTabWidget() const
|
||||
{
|
||||
if (isMinimized() || !isVisible())
|
||||
return 0;
|
||||
@@ -895,7 +916,7 @@ bool MainWindow::unlockUI()
|
||||
m_unlockDlgShowing = false;
|
||||
if (!ok) return false;
|
||||
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Preferences *const pref = Preferences::instance();
|
||||
QString realPassMd5 = pref->getUILockPasswordMD5();
|
||||
QCryptographicHash md5(QCryptographicHash::Md5);
|
||||
md5.addData(clearPassword.toLocal8Bit());
|
||||
@@ -945,7 +966,7 @@ void MainWindow::toggleVisibility(QSystemTrayIcon::ActivationReason e)
|
||||
// Display About Dialog
|
||||
void MainWindow::on_actionAbout_triggered()
|
||||
{
|
||||
//About dialog
|
||||
// About dialog
|
||||
if (m_aboutDlg)
|
||||
m_aboutDlg->setFocus();
|
||||
else
|
||||
@@ -979,7 +1000,7 @@ void MainWindow::showEvent(QShowEvent *e)
|
||||
// Called when we close the program
|
||||
void MainWindow::closeEvent(QCloseEvent *e)
|
||||
{
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Preferences *const pref = Preferences::instance();
|
||||
const bool goToSystrayOnExit = pref->closeToTray();
|
||||
if (!m_forceExit && m_systrayIcon && goToSystrayOnExit && !this->isHidden()) {
|
||||
hide();
|
||||
@@ -1005,14 +1026,13 @@ void MainWindow::closeEvent(QCloseEvent *e)
|
||||
m_forceExit = false;
|
||||
return;
|
||||
}
|
||||
if (confirmBox.clickedButton() == alwaysBtn) {
|
||||
if (confirmBox.clickedButton() == alwaysBtn)
|
||||
// Remember choice
|
||||
Preferences::instance()->setConfirmOnExit(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//abort search if any
|
||||
// abort search if any
|
||||
if (m_searchWidget)
|
||||
delete m_searchWidget;
|
||||
|
||||
@@ -1036,10 +1056,10 @@ void MainWindow::on_actionCreateTorrent_triggered()
|
||||
|
||||
bool MainWindow::event(QEvent *e)
|
||||
{
|
||||
switch(e->type()) {
|
||||
switch (e->type()) {
|
||||
case QEvent::WindowStateChange: {
|
||||
qDebug("Window change event");
|
||||
//Now check to see if the window is minimised
|
||||
// Now check to see if the window is minimised
|
||||
if (isMinimized()) {
|
||||
qDebug("minimisation");
|
||||
if (m_systrayIcon && Preferences::instance()->minimizeToTray()) {
|
||||
@@ -1131,12 +1151,12 @@ void MainWindow::dragEnterEvent(QDragEnterEvent *event)
|
||||
// torrents to download list
|
||||
void MainWindow::on_actionOpen_triggered()
|
||||
{
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Preferences *const pref = Preferences::instance();
|
||||
// Open File Open Dialog
|
||||
// Note: it is possible to select more than one file
|
||||
const QStringList pathsList =
|
||||
QFileDialog::getOpenFileNames(0, tr("Open Torrent Files"), pref->getMainLastDir(),
|
||||
tr("Torrent Files") + " (*.torrent)");
|
||||
QFileDialog::getOpenFileNames(0, tr("Open Torrent Files"), pref->getMainLastDir(),
|
||||
tr("Torrent Files") + " (*.torrent)");
|
||||
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
||||
if (!pathsList.isEmpty()) {
|
||||
foreach (QString file, pathsList) {
|
||||
@@ -1172,7 +1192,7 @@ void MainWindow::optionsSaved()
|
||||
void MainWindow::loadPreferences(bool configureSession)
|
||||
{
|
||||
Logger::instance()->addMessage(tr("Options were saved successfully."));
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
const bool newSystrayIntegration = pref->systrayIntegration();
|
||||
m_ui->actionLock->setVisible(newSystrayIntegration);
|
||||
if (newSystrayIntegration != (m_systrayIcon != 0)) {
|
||||
@@ -1267,7 +1287,7 @@ void MainWindow::loadPreferences(bool configureSession)
|
||||
qDebug("GUI settings loaded");
|
||||
}
|
||||
|
||||
void MainWindow::addUnauthenticatedTracker(const QPair<BitTorrent::TorrentHandle*, QString> &tracker)
|
||||
void MainWindow::addUnauthenticatedTracker(const QPair<BitTorrent::TorrentHandle *, QString> &tracker)
|
||||
{
|
||||
// Trackers whose authentication was cancelled
|
||||
if (m_unauthenticatedTrackers.indexOf(tracker) < 0)
|
||||
@@ -1350,12 +1370,12 @@ void MainWindow::showNotificationBaloon(QString title, QString msg) const
|
||||
* *
|
||||
*****************************************************/
|
||||
|
||||
void MainWindow::downloadFromURLList(const QStringList& urlList)
|
||||
void MainWindow::downloadFromURLList(const QStringList &urlList)
|
||||
{
|
||||
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
||||
foreach (QString url, urlList) {
|
||||
if ((url.size() == 40 && !url.contains(QRegExp("[^0-9A-Fa-f]")))
|
||||
|| (url.size() == 32 && !url.contains(QRegExp("[^2-7A-Za-z]"))))
|
||||
if (((url.size() == 40) && !url.contains(QRegExp("[^0-9A-Fa-f]")))
|
||||
|| ((url.size() == 32) && !url.contains(QRegExp("[^2-7A-Za-z]"))))
|
||||
url = "magnet:?xt=urn:btih:" + url;
|
||||
|
||||
if (useTorrentAdditionDialog)
|
||||
@@ -1407,7 +1427,7 @@ void MainWindow::updateTrayIconMenu()
|
||||
m_ui->actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show"));
|
||||
}
|
||||
|
||||
QMenu* MainWindow::trayIconMenu()
|
||||
QMenu *MainWindow::trayIconMenu()
|
||||
{
|
||||
if (m_trayIconMenu) return m_trayIconMenu;
|
||||
|
||||
@@ -1463,14 +1483,14 @@ void MainWindow::on_actionOptions_triggered()
|
||||
|
||||
void MainWindow::on_actionTopToolBar_triggered()
|
||||
{
|
||||
bool isVisible = static_cast<QAction*>(sender())->isChecked();
|
||||
bool isVisible = static_cast<QAction * >(sender())->isChecked();
|
||||
m_ui->toolBar->setVisible(isVisible);
|
||||
Preferences::instance()->setToolbarDisplayed(isVisible);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionSpeedInTitleBar_triggered()
|
||||
{
|
||||
m_displaySpeedInTitle = static_cast<QAction*>(sender())->isChecked();
|
||||
m_displaySpeedInTitle = static_cast<QAction * >(sender())->isChecked();
|
||||
Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
|
||||
if (m_displaySpeedInTitle)
|
||||
updateGUI();
|
||||
@@ -1580,7 +1600,7 @@ void MainWindow::handleUpdateCheckFinished(bool updateAvailable, QString newVers
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
if (answer == QMessageBox::Yes) {
|
||||
// The user want to update, let's download the update
|
||||
ProgramUpdater* updater = dynamic_cast<ProgramUpdater*>(sender());
|
||||
ProgramUpdater *updater = dynamic_cast<ProgramUpdater * >(sender());
|
||||
updater->updateProgram();
|
||||
}
|
||||
}
|
||||
@@ -1596,6 +1616,7 @@ void MainWindow::handleUpdateCheckFinished(bool updateAvailable, QString newVers
|
||||
if (Preferences::instance()->isUpdateCheckEnabled() && (answer == QMessageBox::Yes))
|
||||
m_programUpdateTimer->start();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void MainWindow::on_actionDonateMoney_triggered()
|
||||
@@ -1705,7 +1726,7 @@ void MainWindow::checkForActiveTorrents()
|
||||
QIcon MainWindow::getSystrayIcon() const
|
||||
{
|
||||
TrayIcon::Style style = Preferences::instance()->trayIconStyle();
|
||||
switch(style) {
|
||||
switch (style) {
|
||||
case TrayIcon::MONO_DARK:
|
||||
return QIcon(":/icons/skin/qbittorrent_mono_dark.png");
|
||||
case TrayIcon::MONO_LIGHT:
|
||||
@@ -1717,7 +1738,7 @@ QIcon MainWindow::getSystrayIcon() const
|
||||
QIcon icon;
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC))
|
||||
if (Preferences::instance()->useSystemIconTheme())
|
||||
icon = QIcon::fromTheme("qbittorrent");
|
||||
icon = QIcon::fromTheme("qbittorrent-tray");
|
||||
|
||||
#endif
|
||||
if (icon.isNull()) {
|
||||
@@ -1735,11 +1756,12 @@ void MainWindow::checkProgramUpdate()
|
||||
m_ui->actionCheckForUpdates->setEnabled(false);
|
||||
m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
|
||||
m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
|
||||
bool invokedByUser = m_ui->actionCheckForUpdates == qobject_cast<QAction*>(sender());
|
||||
bool invokedByUser = m_ui->actionCheckForUpdates == qobject_cast<QAction * >(sender());
|
||||
ProgramUpdater *updater = new ProgramUpdater(this, invokedByUser);
|
||||
connect(updater, SIGNAL(updateCheckFinished(bool, QString, bool)), SLOT(handleUpdateCheckFinished(bool, QString, bool)));
|
||||
connect(updater, SIGNAL(updateCheckFinished(bool,QString,bool)), SLOT(handleUpdateCheckFinished(bool,QString,bool)));
|
||||
updater->checkForUpdates();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -1771,8 +1793,8 @@ void MainWindow::installPython()
|
||||
handler = Net::DownloadManager::instance()->downloadUrl("https://www.python.org/ftp/python/3.5.2/python-3.5.2.exe", true);
|
||||
else
|
||||
handler = Net::DownloadManager::instance()->downloadUrl("https://www.python.org/ftp/python/3.4.4/python-3.4.4.msi", true);
|
||||
connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(pythonDownloadSuccess(QString, QString)));
|
||||
connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(pythonDownloadFailure(QString, QString)));
|
||||
connect(handler, SIGNAL(downloadFinished(QString,QString)), this, SLOT(pythonDownloadSuccess(QString,QString)));
|
||||
connect(handler, SIGNAL(downloadFailed(QString,QString)), this, SLOT(pythonDownloadFailure(QString,QString)));
|
||||
}
|
||||
|
||||
void MainWindow::pythonDownloadSuccess(const QString &url, const QString &filePath)
|
||||
@@ -1818,4 +1840,5 @@ void MainWindow::pythonDownloadFailure(const QString &url, const QString &error)
|
||||
setCursor(QCursor(Qt::ArrowCursor));
|
||||
QMessageBox::warning(this, tr("Download error"), tr("Python setup could not be downloaded, reason: %1.\nPlease install it manually.").arg(error));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -51,7 +51,6 @@ class TransferListWidget;
|
||||
class TransferListFiltersWidget;
|
||||
class PropertiesWidget;
|
||||
class StatusBar;
|
||||
class about;
|
||||
class TorrentCreatorDlg;
|
||||
class downloadFromURL;
|
||||
class LineEdit;
|
||||
@@ -69,7 +68,7 @@ namespace Ui
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
class MainWindow: public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -77,10 +76,10 @@ public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
~MainWindow() override;
|
||||
|
||||
QWidget* currentTabWidget() const;
|
||||
TransferListWidget* transferListWidget() const;
|
||||
QWidget *currentTabWidget() const;
|
||||
TransferListWidget *transferListWidget() const;
|
||||
PropertiesWidget *propertiesWidget() const;
|
||||
QMenu* trayIconMenu();
|
||||
QMenu *trayIconMenu();
|
||||
|
||||
// ExecutionLog properties
|
||||
bool isExecutionLogEnabled() const;
|
||||
@@ -124,11 +123,13 @@ private slots:
|
||||
// Keyboard shortcuts
|
||||
void createKeyboardShortcuts();
|
||||
void displayTransferTab() const;
|
||||
void displaySearchTab() const;
|
||||
void displayRSSTab() const;
|
||||
void displaySearchTab();
|
||||
void displayRSSTab();
|
||||
void displayExecutionLogTab();
|
||||
void focusSearchFilter();
|
||||
void updateGUI();
|
||||
void loadPreferences(bool configureSession = true);
|
||||
void addUnauthenticatedTracker(const QPair<BitTorrent::TorrentHandle*, QString> &tracker);
|
||||
void addUnauthenticatedTracker(const QPair<BitTorrent::TorrentHandle *, QString> &tracker);
|
||||
void addTorrentFailed(const QString &error) const;
|
||||
void torrentNew(BitTorrent::TorrentHandle *const torrent) const;
|
||||
void finishedTorrent(BitTorrent::TorrentHandle *const torrent) const;
|
||||
@@ -199,7 +200,7 @@ private:
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void closeEvent(QCloseEvent *) override;
|
||||
void showEvent(QShowEvent *) override;
|
||||
bool event(QEvent * event) override;
|
||||
bool event(QEvent *event) override;
|
||||
void displayRSSTab(bool enable);
|
||||
void displaySearchTab(bool enable);
|
||||
|
||||
@@ -207,7 +208,7 @@ private:
|
||||
|
||||
QFileSystemWatcher *m_executableWatcher;
|
||||
// Bittorrent
|
||||
QList<QPair<BitTorrent::TorrentHandle*, QString>> m_unauthenticatedTrackers; // Still needed?
|
||||
QList<QPair<BitTorrent::TorrentHandle *, QString >> m_unauthenticatedTrackers; // Still needed?
|
||||
// GUI related
|
||||
bool m_posInitialized;
|
||||
QTabWidget *m_tabs;
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>914</width>
|
||||
<height>21</height>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuEdit">
|
||||
@@ -44,6 +44,8 @@
|
||||
</property>
|
||||
<addaction name="actionStart"/>
|
||||
<addaction name="actionPause"/>
|
||||
<addaction name="actionStartAll"/>
|
||||
<addaction name="actionPauseAll"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionDelete"/>
|
||||
<addaction name="actionTopPriority"/>
|
||||
@@ -191,6 +193,16 @@
|
||||
<string>&Pause</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionStartAll">
|
||||
<property name="text">
|
||||
<string>R&esume All</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPauseAll">
|
||||
<property name="text">
|
||||
<string>P&ause All</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDelete">
|
||||
<property name="text">
|
||||
<string>&Delete</string>
|
||||
@@ -334,16 +346,6 @@
|
||||
<string>If you like qBittorrent, please donate!</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionStartAll">
|
||||
<property name="text">
|
||||
<string>R&esume All</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPauseAll">
|
||||
<property name="text">
|
||||
<string>P&ause All</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAutoExit">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -845,16 +845,21 @@ void OptionsDialog::loadOptions()
|
||||
case ProxyType::SOCKS4:
|
||||
m_ui->comboProxyType->setCurrentIndex(1);
|
||||
break;
|
||||
|
||||
case ProxyType::SOCKS5_PW:
|
||||
useProxyAuth = true;
|
||||
// fallthrough
|
||||
case ProxyType::SOCKS5:
|
||||
m_ui->comboProxyType->setCurrentIndex(2);
|
||||
break;
|
||||
|
||||
case ProxyType::HTTP_PW:
|
||||
useProxyAuth = true;
|
||||
// fallthrough
|
||||
case ProxyType::HTTP:
|
||||
m_ui->comboProxyType->setCurrentIndex(3);
|
||||
break;
|
||||
|
||||
default:
|
||||
m_ui->comboProxyType->setCurrentIndex(0);
|
||||
}
|
||||
@@ -1311,7 +1316,7 @@ void OptionsDialog::setLocale(const QString &localeStr)
|
||||
}
|
||||
if (index < 0) {
|
||||
// Unrecognized, use US English
|
||||
index = m_ui->comboI18n->findData(QLocale("en").name(), Qt::UserRole);
|
||||
index = m_ui->comboI18n->findData("en", Qt::UserRole);
|
||||
Q_ASSERT(index >= 0);
|
||||
}
|
||||
m_ui->comboI18n->setCurrentIndex(index);
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tabOptionPage1">
|
||||
<widget class="QWidget" name="tabBehaviorPage">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_10">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
@@ -649,7 +649,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabOptionPage2">
|
||||
<widget class="QWidget" name="tabDownloadsPage">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_13">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
@@ -2184,7 +2184,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tabOptionPage4">
|
||||
<widget class="QWidget" name="tabBitTorrentPage">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_15">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
|
||||
@@ -45,6 +45,8 @@ namespace
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
const QString OS_TYPE("Mac OS X");
|
||||
#elif defined(Q_OS_WIN) && (defined(__x86_64__) || defined(_M_X64))
|
||||
const QString OS_TYPE("Windows x64");
|
||||
#else
|
||||
const QString OS_TYPE("Windows");
|
||||
#endif
|
||||
|
||||
@@ -33,55 +33,90 @@
|
||||
|
||||
#include <QItemDelegate>
|
||||
#include <QPainter>
|
||||
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
|
||||
class PeerListDelegate: public QItemDelegate {
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum PeerListColumns {COUNTRY, IP, PORT, CONNECTION, FLAGS, CLIENT, PROGRESS, DOWN_SPEED, UP_SPEED,
|
||||
TOT_DOWN, TOT_UP, RELEVANCE, DOWNLOADING_PIECE, IP_HIDDEN, COL_COUNT};
|
||||
enum PeerListColumns
|
||||
{
|
||||
COUNTRY,
|
||||
IP,
|
||||
PORT,
|
||||
CONNECTION,
|
||||
FLAGS,
|
||||
CLIENT,
|
||||
PROGRESS,
|
||||
DOWN_SPEED,
|
||||
UP_SPEED,
|
||||
TOT_DOWN,
|
||||
TOT_UP,
|
||||
RELEVANCE,
|
||||
DOWNLOADING_PIECE,
|
||||
IP_HIDDEN,
|
||||
|
||||
COL_COUNT
|
||||
};
|
||||
|
||||
public:
|
||||
PeerListDelegate(QObject *parent) : QItemDelegate(parent) {}
|
||||
PeerListDelegate(QObject *parent) : QItemDelegate(parent) {}
|
||||
|
||||
~PeerListDelegate() {}
|
||||
~PeerListDelegate() {}
|
||||
|
||||
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const {
|
||||
painter->save();
|
||||
QStyleOptionViewItem opt = QItemDelegate::setOptions(index, option);
|
||||
switch(index.column()) {
|
||||
case TOT_DOWN:
|
||||
case TOT_UP:
|
||||
QItemDelegate::drawBackground(painter, opt, index);
|
||||
QItemDelegate::drawDisplay(painter, opt, option.rect, Utils::Misc::friendlyUnit(index.data().toLongLong()));
|
||||
break;
|
||||
case DOWN_SPEED:
|
||||
case UP_SPEED:{
|
||||
QItemDelegate::drawBackground(painter, opt, index);
|
||||
qreal speed = index.data().toDouble();
|
||||
if (speed > 0.0)
|
||||
QItemDelegate::drawDisplay(painter, opt, opt.rect, Utils::Misc::friendlyUnit(speed, true));
|
||||
break;
|
||||
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
|
||||
{
|
||||
painter->save();
|
||||
const bool hideValues = Preferences::instance()->getHideZeroValues();
|
||||
QStyleOptionViewItem opt = QItemDelegate::setOptions(index, option);
|
||||
switch(index.column()) {
|
||||
case PORT: {
|
||||
QItemDelegate::drawBackground(painter, opt, index);
|
||||
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
|
||||
QItemDelegate::drawDisplay(painter, opt, option.rect, index.data().toString());
|
||||
}
|
||||
break;
|
||||
case TOT_DOWN:
|
||||
case TOT_UP: {
|
||||
qlonglong size = index.data().toLongLong();
|
||||
if (hideValues && (size <= 0))
|
||||
break;
|
||||
QItemDelegate::drawBackground(painter, opt, index);
|
||||
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
|
||||
QItemDelegate::drawDisplay(painter, opt, option.rect, Utils::Misc::friendlyUnit(size));
|
||||
}
|
||||
break;
|
||||
case DOWN_SPEED:
|
||||
case UP_SPEED:{
|
||||
QItemDelegate::drawBackground(painter, opt, index);
|
||||
qreal speed = index.data().toDouble();
|
||||
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
|
||||
if (speed > 0.0)
|
||||
QItemDelegate::drawDisplay(painter, opt, opt.rect, Utils::Misc::friendlyUnit(speed, true));
|
||||
}
|
||||
break;
|
||||
case PROGRESS:
|
||||
case RELEVANCE: {
|
||||
QItemDelegate::drawBackground(painter, opt, index);
|
||||
qreal progress = index.data().toDouble();
|
||||
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
|
||||
QItemDelegate::drawDisplay(painter, opt, opt.rect, Utils::String::fromDouble(progress*100.0, 1)+"%");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
QItemDelegate::paint(painter, option, index);
|
||||
}
|
||||
painter->restore();
|
||||
}
|
||||
case PROGRESS:
|
||||
case RELEVANCE:{
|
||||
QItemDelegate::drawBackground(painter, opt, index);
|
||||
qreal progress = index.data().toDouble();
|
||||
QItemDelegate::drawDisplay(painter, opt, opt.rect, Utils::String::fromDouble(progress*100.0, 1)+"%");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
QItemDelegate::paint(painter, option, index);
|
||||
}
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QWidget* createEditor(QWidget*, const QStyleOptionViewItem &, const QModelIndex &) const {
|
||||
// No editor here
|
||||
return 0;
|
||||
}
|
||||
QWidget* createEditor(QWidget*, const QStyleOptionViewItem &, const QModelIndex &) const
|
||||
{
|
||||
// No editor here
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ protected:
|
||||
case PeerListDelegate::CLIENT: {
|
||||
QString vL = left.data().toString();
|
||||
QString vR = right.data().toString();
|
||||
return Utils::String::naturalCompareCaseSensitive(vL, vR);
|
||||
return Utils::String::naturalCompareCaseInsensitive(vL, vR);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -83,6 +83,14 @@ PeerListWidget::PeerListWidget(PropertiesWidget *parent)
|
||||
m_listModel->setHeaderData(PeerListDelegate::TOT_UP, Qt::Horizontal, tr("Uploaded", "i.e: total data uploaded"));
|
||||
m_listModel->setHeaderData(PeerListDelegate::RELEVANCE, Qt::Horizontal, tr("Relevance", "i.e: How relevant this peer is to us. How many pieces it has that we don't."));
|
||||
m_listModel->setHeaderData(PeerListDelegate::DOWNLOADING_PIECE, Qt::Horizontal, tr("Files", "i.e. files that are being downloaded right now"));
|
||||
// Set header text alignment
|
||||
m_listModel->setHeaderData(PeerListDelegate::PORT, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
|
||||
m_listModel->setHeaderData(PeerListDelegate::PROGRESS, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
|
||||
m_listModel->setHeaderData(PeerListDelegate::DOWN_SPEED, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
|
||||
m_listModel->setHeaderData(PeerListDelegate::UP_SPEED, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
|
||||
m_listModel->setHeaderData(PeerListDelegate::TOT_DOWN, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
|
||||
m_listModel->setHeaderData(PeerListDelegate::TOT_UP, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
|
||||
m_listModel->setHeaderData(PeerListDelegate::RELEVANCE, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
|
||||
// Proxy model to support sorting without actually altering the underlying model
|
||||
m_proxyModel = new PeerListSortModel();
|
||||
m_proxyModel->setDynamicSortFilter(true);
|
||||
@@ -393,7 +401,7 @@ QStandardItem* PeerListWidget::addPeer(const QString& ip, BitTorrent::TorrentHan
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CONNECTION), peer.connectionType());
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flags());
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flagsDescription(), Qt::ToolTipRole);
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CLIENT), peer.client());
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CLIENT), Utils::String::toHtmlEscaped(peer.client()));
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::PROGRESS), peer.progress());
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::DOWN_SPEED), peer.payloadDownSpeed());
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::UP_SPEED), peer.payloadUpSpeed());
|
||||
@@ -424,7 +432,7 @@ void PeerListWidget::updatePeer(const QString &ip, BitTorrent::TorrentHandle *co
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::PORT), peer.address().port);
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flags());
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::FLAGS), peer.flagsDescription(), Qt::ToolTipRole);
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CLIENT), peer.client());
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::CLIENT), Utils::String::toHtmlEscaped(peer.client()));
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::PROGRESS), peer.progress());
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::DOWN_SPEED), peer.payloadDownSpeed());
|
||||
m_listModel->setData(m_listModel->index(row, PeerListDelegate::UP_SPEED), peer.payloadUpSpeed());
|
||||
|
||||
@@ -159,14 +159,14 @@ PropertiesWidget::PropertiesWidget(QWidget *parent, MainWindow *main_window, Tra
|
||||
refreshTimer = new QTimer(this);
|
||||
connect(refreshTimer, SIGNAL(timeout()), this, SLOT(loadDynamicData()));
|
||||
refreshTimer->start(3000); // 3sec
|
||||
editHotkeyFile = new QShortcut(QKeySequence("F2"), filesList, 0, 0, Qt::WidgetShortcut);
|
||||
editHotkeyFile = new QShortcut(Qt::Key_F2, filesList, 0, 0, Qt::WidgetShortcut);
|
||||
connect(editHotkeyFile, SIGNAL(activated()), SLOT(renameSelectedFile()));
|
||||
editHotkeyWeb = new QShortcut(QKeySequence("F2"), listWebSeeds, 0, 0, Qt::WidgetShortcut);
|
||||
editHotkeyWeb = new QShortcut(Qt::Key_F2, listWebSeeds, 0, 0, Qt::WidgetShortcut);
|
||||
connect(editHotkeyWeb, SIGNAL(activated()), SLOT(editWebSeed()));
|
||||
connect(listWebSeeds, SIGNAL(doubleClicked(QModelIndex)), SLOT(editWebSeed()));
|
||||
deleteHotkeyWeb = new QShortcut(QKeySequence::Delete, listWebSeeds, 0, 0, Qt::WidgetShortcut);
|
||||
connect(deleteHotkeyWeb, SIGNAL(activated()), SLOT(deleteSelectedUrlSeeds()));
|
||||
openHotkeyFile = new QShortcut(QKeySequence("Return"), filesList, 0, 0, Qt::WidgetShortcut);
|
||||
openHotkeyFile = new QShortcut(Qt::Key_Return, filesList, 0, 0, Qt::WidgetShortcut);
|
||||
connect(openHotkeyFile, SIGNAL(activated()), SLOT(openSelectedFile()));
|
||||
}
|
||||
|
||||
@@ -314,12 +314,12 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::TorrentHandle *const torrent
|
||||
label_total_size_val->setText(Utils::Misc::friendlyUnit(m_torrent->totalSize()));
|
||||
|
||||
// Comment
|
||||
comment_text->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment()));
|
||||
comment_text->setText(Utils::Misc::parseHtmlLinks(Utils::String::toHtmlEscaped(m_torrent->comment())));
|
||||
|
||||
// URL seeds
|
||||
loadUrlSeeds();
|
||||
|
||||
label_created_by_val->setText(m_torrent->creator());
|
||||
label_created_by_val->setText(Utils::String::toHtmlEscaped(m_torrent->creator()));
|
||||
|
||||
// List files in torrent
|
||||
PropListModel->model()->setupModelData(m_torrent->info());
|
||||
|
||||
@@ -28,14 +28,12 @@
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <QStyleOptionProgressBar>
|
||||
#include <QStyleOptionViewItem>
|
||||
#include <QStyleOptionComboBox>
|
||||
#include <QComboBox>
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QProgressBar>
|
||||
#include <QApplication>
|
||||
#include <QStyleOptionProgressBar>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifndef QBT_USES_QT5
|
||||
@@ -51,6 +49,23 @@
|
||||
#include "proplistdelegate.h"
|
||||
#include "torrentcontentmodelitem.h"
|
||||
|
||||
namespace {
|
||||
|
||||
QPalette progressBarDisabledPalette()
|
||||
{
|
||||
auto getPalette = []()
|
||||
{
|
||||
QProgressBar bar;
|
||||
bar.setEnabled(false);
|
||||
QStyleOptionProgressBar opt;
|
||||
opt.initFrom(&bar);
|
||||
return opt.palette;
|
||||
};
|
||||
static QPalette palette = getPalette();
|
||||
return palette;
|
||||
}
|
||||
}
|
||||
|
||||
PropListDelegate::PropListDelegate(PropertiesWidget *properties, QObject *parent)
|
||||
: QItemDelegate(parent)
|
||||
, m_properties(properties)
|
||||
@@ -69,12 +84,7 @@ void PropListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
|
||||
break;
|
||||
case REMAINING:
|
||||
QItemDelegate::drawBackground(painter, opt, index);
|
||||
if (index.sibling(index.row(), PRIORITY).data().toInt() == prio::IGNORED) {
|
||||
QItemDelegate::drawDisplay(painter, opt, option.rect, tr("N/A"));
|
||||
}
|
||||
else {
|
||||
QItemDelegate::drawDisplay(painter, opt, option.rect, Utils::Misc::friendlyUnit(index.data().toLongLong()));
|
||||
}
|
||||
QItemDelegate::drawDisplay(painter, opt, option.rect, Utils::Misc::friendlyUnit(index.data().toLongLong()));
|
||||
break;
|
||||
case PROGRESS:
|
||||
if (index.data().toDouble() >= 0) {
|
||||
@@ -85,8 +95,13 @@ void PropListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
|
||||
newopt.progress = (int)progress;
|
||||
newopt.maximum = 100;
|
||||
newopt.minimum = 0;
|
||||
newopt.state |= QStyle::State_Enabled;
|
||||
newopt.textVisible = true;
|
||||
if (index.sibling(index.row(), PRIORITY).data().toInt() == prio::IGNORED) {
|
||||
newopt.state &= ~QStyle::State_Enabled;
|
||||
newopt.palette = progressBarDisabledPalette();
|
||||
}
|
||||
else
|
||||
newopt.state |= QStyle::State_Enabled;
|
||||
#ifndef Q_OS_WIN
|
||||
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &newopt, painter);
|
||||
#else
|
||||
@@ -139,14 +154,17 @@ void PropListDelegate::setEditorData(QWidget *editor, const QModelIndex &index)
|
||||
QComboBox *combobox = static_cast<QComboBox*>(editor);
|
||||
// Set combobox index
|
||||
switch(index.data().toInt()) {
|
||||
case prio::HIGH:
|
||||
combobox->setCurrentIndex(1);
|
||||
case prio::IGNORED:
|
||||
combobox->setCurrentIndex(0);
|
||||
break;
|
||||
case prio::MAXIMUM:
|
||||
case prio::HIGH:
|
||||
combobox->setCurrentIndex(2);
|
||||
break;
|
||||
case prio::MAXIMUM:
|
||||
combobox->setCurrentIndex(3);
|
||||
break;
|
||||
default:
|
||||
combobox->setCurrentIndex(0);
|
||||
combobox->setCurrentIndex(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -161,13 +179,12 @@ QWidget *PropListDelegate::createEditor(QWidget *parent, const QStyleOptionViewI
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (index.data().toInt() <= 0) {
|
||||
// IGNORED or MIXED
|
||||
if (index.data().toInt() == prio::MIXED)
|
||||
return 0;
|
||||
}
|
||||
|
||||
QComboBox* editor = new QComboBox(parent);
|
||||
editor->setFocusPolicy(Qt::StrongFocus);
|
||||
editor->addItem(tr("Do not download", "Do not download (priority)"));
|
||||
editor->addItem(tr("Normal", "Normal (priority)"));
|
||||
editor->addItem(tr("High", "High (priority)"));
|
||||
editor->addItem(tr("Maximum", "Maximum (priority)"));
|
||||
@@ -181,10 +198,13 @@ void PropListDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
|
||||
qDebug("PropListDelegate: setModelData(%d)", value);
|
||||
|
||||
switch(value) {
|
||||
case 1:
|
||||
model->setData(index, prio::HIGH); // HIGH
|
||||
case 0:
|
||||
model->setData(index, prio::IGNORED); // IGNORED
|
||||
break;
|
||||
case 2:
|
||||
model->setData(index, prio::HIGH); // HIGH
|
||||
break;
|
||||
case 3:
|
||||
model->setData(index, prio::MAXIMUM); // MAX
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -44,29 +44,34 @@ PropTabBar::PropTabBar(QWidget *parent) :
|
||||
m_btnGroup = new QButtonGroup(this);
|
||||
// General tab
|
||||
QPushButton *main_infos_button = new QPushButton(GuiIconProvider::instance()->getIcon("document-properties"), tr("General"), parent);
|
||||
main_infos_button->setShortcut(QKeySequence(QString::fromUtf8("Alt+P")));
|
||||
main_infos_button->setShortcut(Qt::ALT + Qt::Key_G);
|
||||
addWidget(main_infos_button);
|
||||
m_btnGroup->addButton(main_infos_button, MAIN_TAB);
|
||||
// Trackers tab
|
||||
QPushButton *trackers_button = new QPushButton(GuiIconProvider::instance()->getIcon("network-server"), tr("Trackers"), parent);
|
||||
trackers_button->setShortcut(Qt::ALT + Qt::Key_C);
|
||||
addWidget(trackers_button);
|
||||
m_btnGroup->addButton(trackers_button, TRACKERS_TAB);
|
||||
// Peers tab
|
||||
QPushButton *peers_button = new QPushButton(GuiIconProvider::instance()->getIcon("edit-find-user"), tr("Peers"), parent);
|
||||
peers_button->setShortcut(Qt::ALT + Qt::Key_R);
|
||||
addWidget(peers_button);
|
||||
m_btnGroup->addButton(peers_button, PEERS_TAB);
|
||||
// URL seeds tab
|
||||
QPushButton *urlseeds_button = new QPushButton(GuiIconProvider::instance()->getIcon("network-server"), tr("HTTP Sources"), parent);
|
||||
urlseeds_button->setShortcut(Qt::ALT + Qt::Key_B);
|
||||
addWidget(urlseeds_button);
|
||||
m_btnGroup->addButton(urlseeds_button, URLSEEDS_TAB);
|
||||
// Files tab
|
||||
QPushButton *files_button = new QPushButton(GuiIconProvider::instance()->getIcon("inode-directory"), tr("Content"), parent);
|
||||
files_button->setShortcut(Qt::ALT + Qt::Key_Z);
|
||||
addWidget(files_button);
|
||||
m_btnGroup->addButton(files_button, FILES_TAB);
|
||||
// Spacer
|
||||
addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed));
|
||||
// Speed tab
|
||||
QPushButton *speed_button = new QPushButton(GuiIconProvider::instance()->getIcon("office-chart-line"), tr("Speed"), parent);
|
||||
speed_button->setShortcut(Qt::ALT + Qt::Key_D);
|
||||
addWidget(speed_button);
|
||||
m_btnGroup->addButton(speed_button, SPEED_TAB);
|
||||
// SIGNAL/SLOT
|
||||
|
||||
@@ -37,6 +37,7 @@ SpeedPlotView::SpeedPlotView(QWidget *parent)
|
||||
, m_data5Min(MIN5_BUF_SIZE)
|
||||
, m_data30Min(MIN30_BUF_SIZE)
|
||||
, m_data6Hour(HOUR6_BUF_SIZE)
|
||||
, m_period(MIN5)
|
||||
, m_viewablePointsCount(MIN5_SEC)
|
||||
, m_counter30Min(-1)
|
||||
, m_counter6Hour(-1)
|
||||
|
||||
@@ -68,21 +68,24 @@ TrackerList::TrackerList(PropertiesWidget *properties): QTreeWidget(), propertie
|
||||
header << "#";
|
||||
header << tr("URL");
|
||||
header << tr("Status");
|
||||
header << tr("Received");
|
||||
header << tr("Seeds");
|
||||
header << tr("Peers");
|
||||
header << tr("Downloaded");
|
||||
header << tr("Message");
|
||||
setHeaderItem(new QTreeWidgetItem(header));
|
||||
dht_item = new QTreeWidgetItem(QStringList() << "" << "** [DHT] **");
|
||||
dht_item = new QTreeWidgetItem({ "", "** [DHT] **", "", "0", "", "", "0" });
|
||||
insertTopLevelItem(0, dht_item);
|
||||
setRowColor(0, QColor("grey"));
|
||||
pex_item = new QTreeWidgetItem(QStringList() << "" << "** [PeX] **");
|
||||
pex_item = new QTreeWidgetItem({ "", "** [PeX] **", "", "0", "", "", "0" });
|
||||
insertTopLevelItem(1, pex_item);
|
||||
setRowColor(1, QColor("grey"));
|
||||
lsd_item = new QTreeWidgetItem(QStringList() << "" << "** [LSD] **");
|
||||
lsd_item = new QTreeWidgetItem({ "", "** [LSD] **", "", "0", "", "", "0" });
|
||||
insertTopLevelItem(2, lsd_item);
|
||||
setRowColor(2, QColor("grey"));
|
||||
editHotkey = new QShortcut(QKeySequence("F2"), this, SLOT(editSelectedTracker()), 0, Qt::WidgetShortcut);
|
||||
editHotkey = new QShortcut(Qt::Key_F2, this, SLOT(editSelectedTracker()), 0, Qt::WidgetShortcut);
|
||||
connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(editSelectedTracker()));
|
||||
deleteHotkey = new QShortcut(QKeySequence(QKeySequence::Delete), this, SLOT(deleteSelectedTrackers()), 0, Qt::WidgetShortcut);
|
||||
deleteHotkey = new QShortcut(QKeySequence::Delete, this, SLOT(deleteSelectedTrackers()), 0, Qt::WidgetShortcut);
|
||||
copyHotkey = new QShortcut(QKeySequence::Copy, this, SLOT(copyTrackerUrl()), 0, Qt::WidgetShortcut);
|
||||
|
||||
#ifdef QBT_USES_QT5
|
||||
@@ -199,18 +202,22 @@ void TrackerList::moveSelectionDown() {
|
||||
torrent->forceReannounce();
|
||||
}
|
||||
|
||||
void TrackerList::clear() {
|
||||
qDeleteAll(tracker_items.values());
|
||||
tracker_items.clear();
|
||||
dht_item->setText(COL_PEERS, "");
|
||||
dht_item->setText(COL_STATUS, "");
|
||||
dht_item->setText(COL_MSG, "");
|
||||
pex_item->setText(COL_PEERS, "");
|
||||
pex_item->setText(COL_STATUS, "");
|
||||
pex_item->setText(COL_MSG, "");
|
||||
lsd_item->setText(COL_PEERS, "");
|
||||
lsd_item->setText(COL_STATUS, "");
|
||||
lsd_item->setText(COL_MSG, "");
|
||||
void TrackerList::clear()
|
||||
{
|
||||
qDeleteAll(tracker_items.values());
|
||||
tracker_items.clear();
|
||||
dht_item->setText(COL_STATUS, "");
|
||||
dht_item->setText(COL_SEEDS, "");
|
||||
dht_item->setText(COL_PEERS, "");
|
||||
dht_item->setText(COL_MSG, "");
|
||||
pex_item->setText(COL_STATUS, "");
|
||||
pex_item->setText(COL_SEEDS, "");
|
||||
pex_item->setText(COL_PEERS, "");
|
||||
pex_item->setText(COL_MSG, "");
|
||||
lsd_item->setText(COL_STATUS, "");
|
||||
lsd_item->setText(COL_SEEDS, "");
|
||||
lsd_item->setText(COL_PEERS, "");
|
||||
lsd_item->setText(COL_MSG, "");
|
||||
}
|
||||
|
||||
void TrackerList::loadStickyItems(BitTorrent::TorrentHandle *const torrent) {
|
||||
@@ -242,20 +249,38 @@ void TrackerList::loadStickyItems(BitTorrent::TorrentHandle *const torrent) {
|
||||
lsd_item->setText(COL_MSG, privateMsg);
|
||||
}
|
||||
|
||||
// XXX: libtorrent should provide this info...
|
||||
// Count peers from DHT, LSD, PeX
|
||||
uint nb_dht = 0, nb_lsd = 0, nb_pex = 0;
|
||||
foreach (const BitTorrent::PeerInfo &peer, torrent->peers()) {
|
||||
if (peer.fromDHT())
|
||||
++nb_dht;
|
||||
if (peer.fromLSD())
|
||||
++nb_lsd;
|
||||
if (peer.fromPeX())
|
||||
++nb_pex;
|
||||
}
|
||||
dht_item->setText(COL_PEERS, QString::number(nb_dht));
|
||||
pex_item->setText(COL_PEERS, QString::number(nb_pex));
|
||||
lsd_item->setText(COL_PEERS, QString::number(nb_lsd));
|
||||
// XXX: libtorrent should provide this info...
|
||||
// Count peers from DHT, PeX, LSD
|
||||
uint seedsDHT = 0, seedsPeX = 0, seedsLSD = 0, peersDHT = 0, peersPeX = 0, peersLSD = 0;
|
||||
foreach (const BitTorrent::PeerInfo &peer, torrent->peers()) {
|
||||
if (peer.isConnecting()) continue;
|
||||
|
||||
if (peer.fromDHT()) {
|
||||
if (peer.isSeed())
|
||||
++seedsDHT;
|
||||
else
|
||||
++peersDHT;
|
||||
}
|
||||
if (peer.fromPeX()) {
|
||||
if (peer.isSeed())
|
||||
++seedsPeX;
|
||||
else
|
||||
++peersPeX;
|
||||
}
|
||||
if (peer.fromLSD()) {
|
||||
if (peer.isSeed())
|
||||
++seedsLSD;
|
||||
else
|
||||
++peersLSD;
|
||||
}
|
||||
}
|
||||
|
||||
dht_item->setText(COL_SEEDS, QString::number(seedsDHT));
|
||||
dht_item->setText(COL_PEERS, QString::number(peersDHT));
|
||||
pex_item->setText(COL_SEEDS, QString::number(seedsPeX));
|
||||
pex_item->setText(COL_PEERS, QString::number(peersPeX));
|
||||
lsd_item->setText(COL_SEEDS, QString::number(seedsLSD));
|
||||
lsd_item->setText(COL_PEERS, QString::number(peersLSD));
|
||||
}
|
||||
|
||||
void TrackerList::loadTrackers() {
|
||||
@@ -299,8 +324,18 @@ void TrackerList::loadTrackers() {
|
||||
item->setText(COL_MSG, "");
|
||||
break;
|
||||
}
|
||||
item->setText(COL_RECEIVED, QString::number(data.numPeers));
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM >= 10000
|
||||
item->setText(COL_SEEDS, QString::number(entry.nativeEntry().scrape_complete > 0 ? entry.nativeEntry().scrape_complete : 0));
|
||||
item->setText(COL_PEERS, QString::number(entry.nativeEntry().scrape_incomplete > 0 ? entry.nativeEntry().scrape_incomplete : 0));
|
||||
item->setText(COL_DOWNLOADED, QString::number(entry.nativeEntry().scrape_downloaded > 0 ? entry.nativeEntry().scrape_downloaded : 0));
|
||||
#else
|
||||
item->setText(COL_SEEDS, "0");
|
||||
item->setText(COL_PEERS, "0");
|
||||
item->setText(COL_DOWNLOADED, "0");
|
||||
#endif
|
||||
|
||||
item->setText(COL_PEERS, QString::number(trackers_data.value(trackerUrl).numPeers));
|
||||
}
|
||||
// Remove old trackers
|
||||
foreach (const QString &tracker, old_trackers_urls) {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
#include "propertieswidget.h"
|
||||
|
||||
enum TrackerListColumn {COL_TIER, COL_URL, COL_STATUS, COL_PEERS, COL_MSG};
|
||||
enum TrackerListColumn {COL_TIER, COL_URL, COL_STATUS, COL_RECEIVED, COL_SEEDS, COL_PEERS, COL_DOWNLOADED, COL_MSG};
|
||||
#define NB_STICKY_ITEM 3
|
||||
|
||||
namespace BitTorrent
|
||||
|
||||
@@ -23,6 +23,7 @@ rsssettingsdlg.ui
|
||||
)
|
||||
|
||||
add_library(qbt_rss STATIC ${QBT_RSS_HEADERS} ${QBT_RSS_SOURCE} ${QBT_RSS_FORMS})
|
||||
target_link_libraries(qbt_rss qbt_base)
|
||||
if (QT4_FOUND)
|
||||
target_link_libraries(qbt_rss Qt4::QtGui Qt4::QtNetwork)
|
||||
else (QT4_FOUND)
|
||||
|
||||
@@ -32,6 +32,11 @@
|
||||
#define AUTOMATEDRSSDOWNLOADER_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QHideEvent>
|
||||
#include <QPair>
|
||||
#include <QSet>
|
||||
#include <QShowEvent>
|
||||
#include <QString>
|
||||
#include <QWeakPointer>
|
||||
#include <QShortcut>
|
||||
#include <QRegExpValidator>
|
||||
@@ -40,7 +45,7 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui {
|
||||
class AutomatedRssDownloader;
|
||||
class AutomatedRssDownloader;
|
||||
}
|
||||
QT_END_NAMESPACE
|
||||
|
||||
@@ -54,54 +59,63 @@ QT_BEGIN_NAMESPACE
|
||||
class QListWidgetItem;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class AutomatedRssDownloader : public QDialog
|
||||
class AutomatedRssDownloader: public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AutomatedRssDownloader(const QWeakPointer<Rss::Manager>& manager, QWidget *parent = 0);
|
||||
~AutomatedRssDownloader();
|
||||
bool isRssDownloaderEnabled() const;
|
||||
explicit AutomatedRssDownloader(const QWeakPointer<Rss::Manager> &manager, QWidget *parent = 0);
|
||||
~AutomatedRssDownloader();
|
||||
bool isRssDownloaderEnabled() const;
|
||||
|
||||
protected:
|
||||
virtual void showEvent(QShowEvent *event) override;
|
||||
virtual void hideEvent(QHideEvent *event) override;
|
||||
|
||||
protected slots:
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
void loadRulesList();
|
||||
void handleFeedCheckStateChange(QListWidgetItem* feed_item);
|
||||
void updateRuleDefinitionBox();
|
||||
void clearRuleDefinitionBox();
|
||||
void saveEditedRule();
|
||||
void loadFeedList();
|
||||
void updateFeedList();
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
void loadRulesList();
|
||||
void handleRuleCheckStateChange(QListWidgetItem *rule_item);
|
||||
void handleFeedCheckStateChange(QListWidgetItem *feed_item);
|
||||
void updateRuleDefinitionBox(QListWidgetItem *selected = 0);
|
||||
void clearRuleDefinitionBox();
|
||||
void saveEditedRule();
|
||||
void loadFeedList();
|
||||
void updateFeedList(QListWidgetItem *selected = 0);
|
||||
|
||||
private slots:
|
||||
void displayRulesListMenu(const QPoint& pos);
|
||||
void on_addRuleBtn_clicked();
|
||||
void on_removeRuleBtn_clicked();
|
||||
void on_browseSP_clicked();
|
||||
void on_exportBtn_clicked();
|
||||
void on_importBtn_clicked();
|
||||
void renameSelectedRule();
|
||||
void updateMatchingArticles();
|
||||
void updateFieldsToolTips(bool regex);
|
||||
void updateMustLineValidity();
|
||||
void updateMustNotLineValidity();
|
||||
void onFinished(int result);
|
||||
void displayRulesListMenu(const QPoint &pos);
|
||||
void on_addRuleBtn_clicked();
|
||||
void on_removeRuleBtn_clicked();
|
||||
void on_browseSP_clicked();
|
||||
void on_exportBtn_clicked();
|
||||
void on_importBtn_clicked();
|
||||
void renameSelectedRule();
|
||||
void updateMatchingArticles();
|
||||
void updateFieldsToolTips(bool regex);
|
||||
void updateMustLineValidity();
|
||||
void updateMustNotLineValidity();
|
||||
void updateEpisodeFilterValidity();
|
||||
void onFinished(int result);
|
||||
|
||||
private:
|
||||
Rss::DownloadRulePtr getCurrentRule() const;
|
||||
void initCategoryCombobox();
|
||||
void addFeedArticlesToTree(const Rss::FeedPtr& feed, const QStringList& articles);
|
||||
Rss::DownloadRulePtr getCurrentRule() const;
|
||||
void initCategoryCombobox();
|
||||
void addFeedArticlesToTree(const Rss::FeedPtr &feed, const QStringList &articles);
|
||||
void disconnectRuleFeedSlots();
|
||||
void connectRuleFeedSlots();
|
||||
|
||||
private:
|
||||
Ui::AutomatedRssDownloader *ui;
|
||||
QWeakPointer<Rss::Manager> m_manager;
|
||||
QListWidgetItem* m_editedRule;
|
||||
Rss::DownloadRuleList *m_ruleList;
|
||||
Rss::DownloadRuleList *m_editableRuleList;
|
||||
QRegExpValidator *m_episodeValidator;
|
||||
QShortcut *editHotkey;
|
||||
QShortcut *deleteHotkey;
|
||||
Ui::AutomatedRssDownloader *ui;
|
||||
QWeakPointer<Rss::Manager> m_manager;
|
||||
QListWidgetItem *m_editedRule;
|
||||
Rss::DownloadRuleList *m_ruleList;
|
||||
Rss::DownloadRuleList *m_editableRuleList;
|
||||
QRegExp *m_episodeRegex;
|
||||
QShortcut *editHotkey;
|
||||
QShortcut *deleteHotkey;
|
||||
QSet<QPair<QString, QString >> m_treeListEntries;
|
||||
};
|
||||
|
||||
#endif // AUTOMATEDRSSDOWNLOADER_H
|
||||
|
||||
@@ -141,9 +141,6 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="lineContains"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="lineEFilter"/>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="lbl_must_stat">
|
||||
<property name="maximumSize">
|
||||
@@ -154,9 +151,22 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="lbl_epfilter_stat">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>18</width>
|
||||
<height>18</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineNotContains"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="lineEFilter"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
@@ -239,12 +249,18 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinIgnorePeriod">
|
||||
<property name="specialValueText">
|
||||
<string>Disabled</string>
|
||||
</property>
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> days</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>365</number>
|
||||
</property>
|
||||
@@ -304,7 +320,7 @@
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<widget class="QLabel" name="lblListFeeds">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
|
||||
@@ -65,13 +65,13 @@ namespace Article
|
||||
}
|
||||
|
||||
// display a right-click menu
|
||||
void RSSImp::displayRSSListMenu(const QPoint& pos)
|
||||
void RSSImp::displayRSSListMenu(const QPoint &pos)
|
||||
{
|
||||
if (!m_feedList->indexAt(pos).isValid())
|
||||
// No item under the mouse, clear selection
|
||||
m_feedList->clearSelection();
|
||||
QMenu myRSSListMenu(this);
|
||||
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
|
||||
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
|
||||
if (selectedItems.size() > 0) {
|
||||
myRSSListMenu.addAction(actionUpdate);
|
||||
myRSSListMenu.addAction(actionMark_items_read);
|
||||
@@ -104,16 +104,16 @@ void RSSImp::displayRSSListMenu(const QPoint& pos)
|
||||
myRSSListMenu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void RSSImp::displayItemsListMenu(const QPoint&)
|
||||
void RSSImp::displayItemsListMenu(const QPoint &)
|
||||
{
|
||||
QMenu myItemListMenu(this);
|
||||
QList<QListWidgetItem*> selectedItems = listArticles->selectedItems();
|
||||
QList<QListWidgetItem * > selectedItems = listArticles->selectedItems();
|
||||
if (selectedItems.size() <= 0)
|
||||
return;
|
||||
|
||||
bool hasTorrent = false;
|
||||
bool hasLink = false;
|
||||
foreach (const QListWidgetItem* item, selectedItems) {
|
||||
foreach (const QListWidgetItem *item, selectedItems) {
|
||||
if (!item) continue;
|
||||
Rss::FeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString());
|
||||
if (!feed) continue;
|
||||
@@ -137,7 +137,7 @@ void RSSImp::displayItemsListMenu(const QPoint&)
|
||||
|
||||
void RSSImp::askNewFolder()
|
||||
{
|
||||
QTreeWidgetItem* parent_item = 0;
|
||||
QTreeWidgetItem *parent_item = 0;
|
||||
Rss::FolderPtr rss_parent;
|
||||
if (m_feedList->selectedItems().size() > 0) {
|
||||
parent_item = m_feedList->selectedItems().at(0);
|
||||
@@ -154,7 +154,7 @@ void RSSImp::askNewFolder()
|
||||
|
||||
Rss::FolderPtr newFolder(new Rss::Folder(new_name));
|
||||
rss_parent->addFile(newFolder);
|
||||
QTreeWidgetItem* folderItem = createFolderListItem(newFolder);
|
||||
QTreeWidgetItem *folderItem = createFolderListItem(newFolder);
|
||||
if (parent_item)
|
||||
parent_item->addChild(folderItem);
|
||||
else
|
||||
@@ -164,6 +164,7 @@ void RSSImp::askNewFolder()
|
||||
// Expand parent folder to display new folder
|
||||
if (parent_item)
|
||||
parent_item->setExpanded(true);
|
||||
m_feedList->setCurrentItem(folderItem);
|
||||
m_rssManager->saveStreamList();
|
||||
}
|
||||
|
||||
@@ -172,7 +173,7 @@ void RSSImp::on_newFeedButton_clicked()
|
||||
{
|
||||
// Determine parent folder for new feed
|
||||
QTreeWidgetItem *parent_item = 0;
|
||||
QList<QTreeWidgetItem *> selected_items = m_feedList->selectedItems();
|
||||
QList<QTreeWidgetItem * > selected_items = m_feedList->selectedItems();
|
||||
if (!selected_items.empty()) {
|
||||
parent_item = selected_items.first();
|
||||
// Consider the case where the user clicked on Unread item
|
||||
@@ -212,41 +213,47 @@ void RSSImp::on_newFeedButton_clicked()
|
||||
Rss::FeedPtr stream(new Rss::Feed(newUrl, m_rssManager.data()));
|
||||
rss_parent->addFile(stream);
|
||||
// Create TreeWidget item
|
||||
QTreeWidgetItem* item = createFolderListItem(stream);
|
||||
QTreeWidgetItem *item = createFolderListItem(stream);
|
||||
if (parent_item)
|
||||
parent_item->addChild(item);
|
||||
else
|
||||
m_feedList->addTopLevelItem(item);
|
||||
// Notify TreeWidget
|
||||
m_feedList->itemAdded(item, stream);
|
||||
|
||||
// Expand parent folder to display new feed
|
||||
if (parent_item)
|
||||
parent_item->setExpanded(true);
|
||||
m_feedList->setCurrentItem(item);
|
||||
m_rssManager->saveStreamList();
|
||||
}
|
||||
|
||||
// delete a stream by a button
|
||||
void RSSImp::deleteSelectedItems()
|
||||
{
|
||||
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
|
||||
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
|
||||
if (selectedItems.isEmpty())
|
||||
return;
|
||||
if ((selectedItems.size() == 1) && (selectedItems.first() == m_feedList->stickyUnreadItem()))
|
||||
return;
|
||||
|
||||
QMessageBox::StandardButton answer = QMessageBox::question(this, tr("Deletion confirmation"),
|
||||
tr("Are you sure you want to delete the selected RSS feeds?"),
|
||||
QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
|
||||
tr("Are you sure you want to delete the selected RSS feeds?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (answer == QMessageBox::No)
|
||||
return;
|
||||
|
||||
foreach (QTreeWidgetItem* item, selectedItems) {
|
||||
QList<QString> deleted;
|
||||
|
||||
foreach (QTreeWidgetItem *item, selectedItems) {
|
||||
if (item == m_feedList->stickyUnreadItem())
|
||||
continue;
|
||||
Rss::FilePtr rss_item = m_feedList->getRSSItem(item);
|
||||
QTreeWidgetItem* parent = item->parent();
|
||||
QTreeWidgetItem *parent = item->parent();
|
||||
// Notify TreeWidget
|
||||
m_feedList->itemAboutToBeRemoved(item);
|
||||
// Actually delete the item
|
||||
rss_item->parentFolder()->removeChild(rss_item->id());
|
||||
deleted << rss_item->id();
|
||||
delete item;
|
||||
// Update parents count
|
||||
while (parent && (parent != m_feedList->invisibleRootItem())) {
|
||||
@@ -255,27 +262,30 @@ void RSSImp::deleteSelectedItems()
|
||||
}
|
||||
}
|
||||
m_rssManager->saveStreamList();
|
||||
|
||||
foreach (const QString &feed_id, deleted)
|
||||
m_rssManager->forwardFeedInfosChanged(feed_id, "", 0);
|
||||
|
||||
// Update Unread items
|
||||
updateItemInfos(m_feedList->stickyUnreadItem());
|
||||
if (m_feedList->currentItem() == m_feedList->stickyUnreadItem())
|
||||
populateArticleList(m_feedList->stickyUnreadItem());
|
||||
|
||||
}
|
||||
|
||||
void RSSImp::loadFoldersOpenState()
|
||||
{
|
||||
QStringList open_folders = Preferences::instance()->getRssOpenFolders();
|
||||
foreach (const QString& var_path, open_folders) {
|
||||
foreach (const QString &var_path, open_folders) {
|
||||
QStringList path = var_path.split("\\");
|
||||
QTreeWidgetItem* parent = 0;
|
||||
foreach (const QString& name, path) {
|
||||
QTreeWidgetItem *parent = 0;
|
||||
foreach (const QString &name, path) {
|
||||
int nbChildren = 0;
|
||||
if (parent)
|
||||
nbChildren = parent->childCount();
|
||||
else
|
||||
nbChildren = m_feedList->topLevelItemCount();
|
||||
for (int i = 0; i < nbChildren; ++i) {
|
||||
QTreeWidgetItem* child;
|
||||
QTreeWidgetItem *child;
|
||||
if (parent)
|
||||
child = parent->child(i);
|
||||
else
|
||||
@@ -294,8 +304,8 @@ void RSSImp::loadFoldersOpenState()
|
||||
void RSSImp::saveFoldersOpenState()
|
||||
{
|
||||
QStringList open_folders;
|
||||
QList<QTreeWidgetItem*> items = m_feedList->getAllOpenFolders();
|
||||
foreach (QTreeWidgetItem* item, items) {
|
||||
QList<QTreeWidgetItem * > items = m_feedList->getAllOpenFolders();
|
||||
foreach (QTreeWidgetItem *item, items) {
|
||||
QString path = m_feedList->getItemPath(item).join("\\");
|
||||
qDebug("saving open folder: %s", qPrintable(path));
|
||||
open_folders << path;
|
||||
@@ -306,17 +316,17 @@ void RSSImp::saveFoldersOpenState()
|
||||
// refresh all streams by a button
|
||||
void RSSImp::refreshAllFeeds()
|
||||
{
|
||||
foreach (QTreeWidgetItem* item, m_feedList->getAllFeedItems())
|
||||
foreach (QTreeWidgetItem *item, m_feedList->getAllFeedItems())
|
||||
item->setData(0, Qt::DecorationRole, QVariant(QIcon(":/icons/loading.png")));
|
||||
m_rssManager->refresh();
|
||||
}
|
||||
|
||||
void RSSImp::downloadSelectedTorrents()
|
||||
{
|
||||
QList<QListWidgetItem*> selected_items = listArticles->selectedItems();
|
||||
QList<QListWidgetItem * > selected_items = listArticles->selectedItems();
|
||||
if (selected_items.size() <= 0)
|
||||
return;
|
||||
foreach (QListWidgetItem* item, selected_items) {
|
||||
foreach (QListWidgetItem *item, selected_items) {
|
||||
if (!item) continue;
|
||||
Rss::FeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString());
|
||||
if (!feed) continue;
|
||||
@@ -343,10 +353,10 @@ void RSSImp::downloadSelectedTorrents()
|
||||
// open the url of the selected RSS articles in the Web browser
|
||||
void RSSImp::openSelectedArticlesUrls()
|
||||
{
|
||||
QList<QListWidgetItem *> selected_items = listArticles->selectedItems();
|
||||
QList<QListWidgetItem * > selected_items = listArticles->selectedItems();
|
||||
if (selected_items.size() <= 0)
|
||||
return;
|
||||
foreach (QListWidgetItem* item, selected_items) {
|
||||
foreach (QListWidgetItem *item, selected_items) {
|
||||
if (!item) continue;
|
||||
Rss::FeedPtr feed = m_feedList->getRSSItemFromUrl(item->data(Article::FeedUrlRole).toString());
|
||||
if (!feed) continue;
|
||||
@@ -367,13 +377,13 @@ void RSSImp::openSelectedArticlesUrls()
|
||||
updateItemInfos(m_feedList->getTreeItemFromUrl(selected_items.first()->data(Article::FeedUrlRole).toString()));
|
||||
}
|
||||
|
||||
//right-click on stream : give it an alias
|
||||
// right-click on stream : give it an alias
|
||||
void RSSImp::renameSelectedRssFile()
|
||||
{
|
||||
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
|
||||
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
|
||||
if (selectedItems.size() != 1)
|
||||
return;
|
||||
QTreeWidgetItem* item = selectedItems.first();
|
||||
QTreeWidgetItem *item = selectedItems.first();
|
||||
if (item == m_feedList->stickyUnreadItem())
|
||||
return;
|
||||
Rss::FilePtr rss_item = m_feedList->getRSSItem(item);
|
||||
@@ -394,6 +404,7 @@ void RSSImp::renameSelectedRssFile()
|
||||
} while (!ok);
|
||||
// Rename item
|
||||
rss_item->rename(newName);
|
||||
m_rssManager->saveStreamList();
|
||||
// Update TreeWidget
|
||||
updateItemInfos(item);
|
||||
}
|
||||
@@ -401,8 +412,8 @@ void RSSImp::renameSelectedRssFile()
|
||||
// right-click on stream : refresh it
|
||||
void RSSImp::refreshSelectedItems()
|
||||
{
|
||||
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
|
||||
foreach (QTreeWidgetItem* item, selectedItems) {
|
||||
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
|
||||
foreach (QTreeWidgetItem *item, selectedItems) {
|
||||
Rss::FilePtr file = m_feedList->getRSSItem(item);
|
||||
// Update icons
|
||||
if (item == m_feedList->stickyUnreadItem()) {
|
||||
@@ -428,8 +439,8 @@ void RSSImp::refreshSelectedItems()
|
||||
void RSSImp::copySelectedFeedsURL()
|
||||
{
|
||||
QStringList URLs;
|
||||
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
|
||||
QTreeWidgetItem* item;
|
||||
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
|
||||
QTreeWidgetItem *item;
|
||||
foreach (item, selectedItems)
|
||||
if (m_feedList->isFeed(item))
|
||||
URLs << m_feedList->getItemID(item);
|
||||
@@ -438,8 +449,8 @@ void RSSImp::copySelectedFeedsURL()
|
||||
|
||||
void RSSImp::on_markReadButton_clicked()
|
||||
{
|
||||
QList<QTreeWidgetItem*> selectedItems = m_feedList->selectedItems();
|
||||
foreach (QTreeWidgetItem* item, selectedItems) {
|
||||
QList<QTreeWidgetItem * > selectedItems = m_feedList->selectedItems();
|
||||
foreach (QTreeWidgetItem *item, selectedItems) {
|
||||
Rss::FilePtr rss_item = m_feedList->getRSSItem(item);
|
||||
Q_ASSERT(rss_item);
|
||||
rss_item->markAsRead();
|
||||
@@ -450,25 +461,25 @@ void RSSImp::on_markReadButton_clicked()
|
||||
populateArticleList(m_feedList->currentItem());
|
||||
}
|
||||
|
||||
QTreeWidgetItem* RSSImp::createFolderListItem(const Rss::FilePtr& rssFile)
|
||||
QTreeWidgetItem *RSSImp::createFolderListItem(const Rss::FilePtr &rssFile)
|
||||
{
|
||||
Q_ASSERT(rssFile);
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem;
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem;
|
||||
item->setData(0, Qt::DisplayRole, QVariant(rssFile->displayName() + QString::fromUtf8(" (") + QString::number(rssFile->unreadCount()) + QString(")")));
|
||||
item->setData(0, Qt::DecorationRole, QIcon(rssFile->iconPath()));
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void RSSImp::fillFeedsList(QTreeWidgetItem* parent, const Rss::FolderPtr& rss_parent)
|
||||
void RSSImp::fillFeedsList(QTreeWidgetItem *parent, const Rss::FolderPtr &rss_parent)
|
||||
{
|
||||
QList<Rss::FilePtr> children;
|
||||
if (parent)
|
||||
children = rss_parent->getContent();
|
||||
else
|
||||
children = m_rssManager->rootFolder()->getContent();
|
||||
foreach (const Rss::FilePtr& rssFile, children) {
|
||||
QTreeWidgetItem* item = createFolderListItem(rssFile);
|
||||
foreach (const Rss::FilePtr &rssFile, children) {
|
||||
QTreeWidgetItem *item = createFolderListItem(rssFile);
|
||||
Q_ASSERT(item);
|
||||
if (parent)
|
||||
parent->addChild(item);
|
||||
@@ -484,10 +495,10 @@ void RSSImp::fillFeedsList(QTreeWidgetItem* parent, const Rss::FolderPtr& rss_pa
|
||||
}
|
||||
}
|
||||
|
||||
QListWidgetItem* RSSImp::createArticleListItem(const Rss::ArticlePtr& article)
|
||||
QListWidgetItem *RSSImp::createArticleListItem(const Rss::ArticlePtr &article)
|
||||
{
|
||||
Q_ASSERT(article);
|
||||
QListWidgetItem* item = new QListWidgetItem;
|
||||
QListWidgetItem *item = new QListWidgetItem;
|
||||
|
||||
item->setData(Article::TitleRole, article->title());
|
||||
item->setData(Article::FeedUrlRole, article->parent()->url());
|
||||
@@ -505,7 +516,7 @@ QListWidgetItem* RSSImp::createArticleListItem(const Rss::ArticlePtr& article)
|
||||
}
|
||||
|
||||
// fills the newsList
|
||||
void RSSImp::populateArticleList(QTreeWidgetItem* item)
|
||||
void RSSImp::populateArticleList(QTreeWidgetItem *item)
|
||||
{
|
||||
if (!item) {
|
||||
listArticles->clear();
|
||||
@@ -529,8 +540,8 @@ void RSSImp::populateArticleList(QTreeWidgetItem* item)
|
||||
articles = rss_item->articleListByDateDesc();
|
||||
|
||||
qDebug("Got the list of news");
|
||||
foreach (const Rss::ArticlePtr& article, articles) {
|
||||
QListWidgetItem* articleItem = createArticleListItem(article);
|
||||
foreach (const Rss::ArticlePtr &article, articles) {
|
||||
QListWidgetItem *articleItem = createArticleListItem(article);
|
||||
listArticles->addItem(articleItem);
|
||||
}
|
||||
qDebug("Added all news to the GUI");
|
||||
@@ -539,7 +550,7 @@ void RSSImp::populateArticleList(QTreeWidgetItem* item)
|
||||
// display a news
|
||||
void RSSImp::refreshTextBrowser()
|
||||
{
|
||||
QList<QListWidgetItem*> selection = listArticles->selectedItems();
|
||||
QList<QListWidgetItem * > selection = listArticles->selectedItems();
|
||||
if (selection.empty()) return;
|
||||
QListWidgetItem *item = selection.first();
|
||||
Q_ASSERT(item);
|
||||
@@ -559,7 +570,7 @@ void RSSImp::refreshTextBrowser()
|
||||
html += "<div style='background-color: #efefef;'><b>" + tr("Author: ") + "</b>" + article->author() + "</div>";
|
||||
html += "</div>";
|
||||
html += "<div style='margin-left: 5px; margin-right: 5px;'>";
|
||||
if(Qt::mightBeRichText(article->description())) {
|
||||
if (Qt::mightBeRichText(article->description())) {
|
||||
html += article->description();
|
||||
}
|
||||
else {
|
||||
@@ -602,7 +613,7 @@ void RSSImp::refreshTextBrowser()
|
||||
void RSSImp::saveSlidersPosition()
|
||||
{
|
||||
// Remember sliders positions
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Preferences *const pref = Preferences::instance();
|
||||
pref->setRssSideSplitterState(splitterSide->saveState());
|
||||
pref->setRssMainSplitterState(splitterMain->saveState());
|
||||
qDebug("Splitters position saved");
|
||||
@@ -610,7 +621,7 @@ void RSSImp::saveSlidersPosition()
|
||||
|
||||
void RSSImp::restoreSlidersPosition()
|
||||
{
|
||||
const Preferences* const pref = Preferences::instance();
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
const QByteArray stateSide = pref->getRssSideSplitterState();
|
||||
if (!stateSide.isEmpty())
|
||||
splitterSide->restoreState(stateSide);
|
||||
@@ -619,9 +630,9 @@ void RSSImp::restoreSlidersPosition()
|
||||
splitterMain->restoreState(stateMain);
|
||||
}
|
||||
|
||||
void RSSImp::updateItemsInfos(const QList<QTreeWidgetItem*>& items)
|
||||
void RSSImp::updateItemsInfos(const QList<QTreeWidgetItem *> &items)
|
||||
{
|
||||
foreach (QTreeWidgetItem* item, items)
|
||||
foreach (QTreeWidgetItem *item, items)
|
||||
updateItemInfos(item);
|
||||
}
|
||||
|
||||
@@ -636,36 +647,43 @@ void RSSImp::updateItemInfos(QTreeWidgetItem *item)
|
||||
name = tr("Unread");
|
||||
emit updateRSSCount(rss_item->unreadCount());
|
||||
}
|
||||
else
|
||||
else {
|
||||
name = rss_item->displayName();
|
||||
}
|
||||
item->setText(0, name + QString::fromUtf8(" (") + QString::number(rss_item->unreadCount()) + QString(")"));
|
||||
// If item has a parent, update it too
|
||||
if (item->parent())
|
||||
updateItemInfos(item->parent());
|
||||
}
|
||||
|
||||
void RSSImp::updateFeedIcon(const QString& url, const QString& iconPath)
|
||||
void RSSImp::updateFeedIcon(const QString &url, const QString &iconPath)
|
||||
{
|
||||
QTreeWidgetItem* item = m_feedList->getTreeItemFromUrl(url);
|
||||
item->setData(0, Qt::DecorationRole, QVariant(QIcon(iconPath)));
|
||||
QTreeWidgetItem *item = m_feedList->getTreeItemFromUrl(url);
|
||||
|
||||
if (item)
|
||||
item->setData(0, Qt::DecorationRole, QVariant(QIcon(iconPath)));
|
||||
}
|
||||
|
||||
void RSSImp::updateFeedInfos(const QString& url, const QString& display_name, uint nbUnread)
|
||||
void RSSImp::updateFeedInfos(const QString &url, const QString &display_name, uint nbUnread)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << display_name;
|
||||
QTreeWidgetItem *item = m_feedList->getTreeItemFromUrl(url);
|
||||
Rss::FeedPtr stream = qSharedPointerCast<Rss::Feed>(m_feedList->getRSSItem(item));
|
||||
item->setText(0, display_name + QString::fromUtf8(" (") + QString::number(nbUnread) + QString(")"));
|
||||
if (!stream->isLoading())
|
||||
item->setData(0, Qt::DecorationRole, QIcon(stream->iconPath()));
|
||||
// Update parent
|
||||
if (item->parent())
|
||||
updateItemInfos(item->parent());
|
||||
|
||||
if (item) {
|
||||
Rss::FeedPtr stream = qSharedPointerCast<Rss::Feed>(m_feedList->getRSSItem(item));
|
||||
item->setText(0, display_name + QString::fromUtf8(" (") + QString::number(nbUnread) + QString(")"));
|
||||
if (!stream->isLoading())
|
||||
item->setData(0, Qt::DecorationRole, QIcon(stream->iconPath()));
|
||||
// Update parent
|
||||
if (item->parent())
|
||||
updateItemInfos(item->parent());
|
||||
}
|
||||
|
||||
// Update Unread item
|
||||
updateItemInfos(m_feedList->stickyUnreadItem());
|
||||
}
|
||||
|
||||
void RSSImp::onFeedContentChanged(const QString& url)
|
||||
void RSSImp::onFeedContentChanged(const QString &url)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << url;
|
||||
QTreeWidgetItem *item = m_feedList->getTreeItemFromUrl(url);
|
||||
@@ -682,8 +700,8 @@ void RSSImp::updateRefreshInterval(uint val)
|
||||
m_rssManager->updateRefreshInterval(val);
|
||||
}
|
||||
|
||||
RSSImp::RSSImp(QWidget *parent):
|
||||
QWidget(parent),
|
||||
RSSImp::RSSImp(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
m_rssManager(new Rss::Manager)
|
||||
{
|
||||
setupUi(this);
|
||||
@@ -706,23 +724,25 @@ RSSImp::RSSImp(QWidget *parent):
|
||||
|
||||
m_feedList = new FeedListWidget(splitterSide, m_rssManager);
|
||||
splitterSide->insertWidget(0, m_feedList);
|
||||
editHotkey = new QShortcut(QKeySequence("F2"), m_feedList, 0, 0, Qt::WidgetShortcut);
|
||||
editHotkey = new QShortcut(Qt::Key_F2, m_feedList, 0, 0, Qt::WidgetShortcut);
|
||||
connect(editHotkey, SIGNAL(activated()), SLOT(renameSelectedRssFile()));
|
||||
connect(m_feedList, SIGNAL(doubleClicked(QModelIndex)), SLOT(renameSelectedRssFile()));
|
||||
deleteHotkey = new QShortcut(QKeySequence::Delete, m_feedList, 0, 0, Qt::WidgetShortcut);
|
||||
connect(deleteHotkey, SIGNAL(activated()), SLOT(deleteSelectedItems()));
|
||||
|
||||
m_rssManager->loadStreamList();
|
||||
m_feedList->setSortingEnabled(false);
|
||||
fillFeedsList();
|
||||
m_feedList->setSortingEnabled(true);
|
||||
populateArticleList(m_feedList->currentItem());
|
||||
|
||||
loadFoldersOpenState();
|
||||
connect(m_rssManager.data(), SIGNAL(feedInfosChanged(QString, QString, unsigned int)), SLOT(updateFeedInfos(QString, QString, unsigned int)));
|
||||
connect(m_rssManager.data(), SIGNAL(feedInfosChanged(QString,QString,unsigned int)), SLOT(updateFeedInfos(QString,QString,unsigned int)));
|
||||
connect(m_rssManager.data(), SIGNAL(feedContentChanged(QString)), SLOT(onFeedContentChanged(QString)));
|
||||
connect(m_rssManager.data(), SIGNAL(feedIconChanged(QString, QString)), SLOT(updateFeedIcon(QString, QString)));
|
||||
connect(m_rssManager.data(), SIGNAL(feedIconChanged(QString,QString)), SLOT(updateFeedIcon(QString,QString)));
|
||||
|
||||
connect(m_feedList, SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(displayRSSListMenu(const QPoint &)));
|
||||
connect(listArticles, SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(displayItemsListMenu(const QPoint &)));
|
||||
connect(m_feedList, SIGNAL(customContextMenuRequested(const QPoint&)), SLOT(displayRSSListMenu(const QPoint&)));
|
||||
connect(listArticles, SIGNAL(customContextMenuRequested(const QPoint&)), SLOT(displayItemsListMenu(const QPoint&)));
|
||||
|
||||
// Feeds list actions
|
||||
connect(actionDelete, SIGNAL(triggered()), this, SLOT(deleteSelectedItems()));
|
||||
@@ -738,8 +758,8 @@ RSSImp::RSSImp(QWidget *parent):
|
||||
connect(actionOpen_news_URL, SIGNAL(triggered()), this, SLOT(openSelectedArticlesUrls()));
|
||||
connect(actionDownload_torrent, SIGNAL(triggered()), this, SLOT(downloadSelectedTorrents()));
|
||||
|
||||
connect(m_feedList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(populateArticleList(QTreeWidgetItem*)));
|
||||
connect(m_feedList, SIGNAL(foldersAltered(QList<QTreeWidgetItem*>)), this, SLOT(updateItemsInfos(QList<QTreeWidgetItem*>)));
|
||||
connect(m_feedList, SIGNAL(currentItemChanged(QTreeWidgetItem *,QTreeWidgetItem *)), this, SLOT(populateArticleList(QTreeWidgetItem *)));
|
||||
connect(m_feedList, SIGNAL(foldersAltered(QList<QTreeWidgetItem * >)), this, SLOT(updateItemsInfos(QList<QTreeWidgetItem * >)));
|
||||
|
||||
connect(listArticles, SIGNAL(itemSelectionChanged()), this, SLOT(refreshTextBrowser()));
|
||||
connect(listArticles, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(downloadSelectedTorrents()));
|
||||
@@ -747,8 +767,8 @@ RSSImp::RSSImp(QWidget *parent):
|
||||
// Restore sliders position
|
||||
restoreSlidersPosition();
|
||||
// Bind saveSliders slots
|
||||
connect(splitterMain, SIGNAL(splitterMoved(int, int)), this, SLOT(saveSlidersPosition()));
|
||||
connect(splitterSide, SIGNAL(splitterMoved(int, int)), this, SLOT(saveSlidersPosition()));
|
||||
connect(splitterMain, SIGNAL(splitterMoved(int,int)), this, SLOT(saveSlidersPosition()));
|
||||
connect(splitterSide, SIGNAL(splitterMoved(int,int)), this, SLOT(saveSlidersPosition()));
|
||||
|
||||
qDebug("RSSImp constructed");
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class RSSImp: public QWidget, public Ui::RSS
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RSSImp(QWidget *parent);
|
||||
RSSImp(QWidget * parent);
|
||||
~RSSImp();
|
||||
|
||||
public slots:
|
||||
@@ -64,21 +64,21 @@ private slots:
|
||||
void on_newFeedButton_clicked();
|
||||
void refreshAllFeeds();
|
||||
void on_markReadButton_clicked();
|
||||
void displayRSSListMenu(const QPoint&);
|
||||
void displayItemsListMenu(const QPoint&);
|
||||
void displayRSSListMenu(const QPoint &);
|
||||
void displayItemsListMenu(const QPoint &);
|
||||
void renameSelectedRssFile();
|
||||
void refreshSelectedItems();
|
||||
void copySelectedFeedsURL();
|
||||
void populateArticleList(QTreeWidgetItem* item);
|
||||
void populateArticleList(QTreeWidgetItem *item);
|
||||
void refreshTextBrowser();
|
||||
void updateFeedIcon(const QString &url, const QString &icon_path);
|
||||
void updateFeedInfos(const QString &url, const QString &display_name, uint nbUnread);
|
||||
void onFeedContentChanged(const QString& url);
|
||||
void updateItemsInfos(const QList<QTreeWidgetItem*> &items);
|
||||
void onFeedContentChanged(const QString &url);
|
||||
void updateItemsInfos(const QList<QTreeWidgetItem *> &items);
|
||||
void updateItemInfos(QTreeWidgetItem *item);
|
||||
void openSelectedArticlesUrls();
|
||||
void downloadSelectedTorrents();
|
||||
void fillFeedsList(QTreeWidgetItem *parent = 0, const Rss::FolderPtr& rss_parent = Rss::FolderPtr());
|
||||
void fillFeedsList(QTreeWidgetItem *parent = 0, const Rss::FolderPtr &rss_parent = Rss::FolderPtr());
|
||||
void saveSlidersPosition();
|
||||
void restoreSlidersPosition();
|
||||
void askNewFolder();
|
||||
@@ -88,16 +88,15 @@ private slots:
|
||||
void on_rssDownloaderBtn_clicked();
|
||||
|
||||
private:
|
||||
static QListWidgetItem* createArticleListItem(const Rss::ArticlePtr& article);
|
||||
static QTreeWidgetItem* createFolderListItem(const Rss::FilePtr& rssFile);
|
||||
static QListWidgetItem *createArticleListItem(const Rss::ArticlePtr &article);
|
||||
static QTreeWidgetItem *createFolderListItem(const Rss::FilePtr &rssFile);
|
||||
|
||||
private:
|
||||
Rss::ManagerPtr m_rssManager;
|
||||
FeedListWidget *m_feedList;
|
||||
QListWidgetItem* m_currentArticle;
|
||||
QListWidgetItem *m_currentArticle;
|
||||
QShortcut *editHotkey;
|
||||
QShortcut *deleteHotkey;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <QDropEvent>
|
||||
#include <QTemporaryFile>
|
||||
#include <QMimeData>
|
||||
#include <QClipboard>
|
||||
#ifdef QBT_USES_QT5
|
||||
|
||||
@@ -50,14 +50,17 @@ void SearchListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
|
||||
switch(index.column()) {
|
||||
case SearchSortModel::SIZE:
|
||||
QItemDelegate::drawBackground(painter, opt, index);
|
||||
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
|
||||
QItemDelegate::drawDisplay(painter, opt, option.rect, Utils::Misc::friendlyUnit(index.data().toLongLong()));
|
||||
break;
|
||||
case SearchSortModel::SEEDS:
|
||||
QItemDelegate::drawBackground(painter, opt, index);
|
||||
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
|
||||
QItemDelegate::drawDisplay(painter, opt, option.rect, (index.data().toLongLong() >= 0) ? index.data().toString() : tr("Unknown"));
|
||||
break;
|
||||
case SearchSortModel::LEECHES:
|
||||
QItemDelegate::drawBackground(painter, opt, index);
|
||||
opt.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
|
||||
QItemDelegate::drawDisplay(painter, opt, option.rect, (index.data().toLongLong() >= 0) ? index.data().toString() : tr("Unknown"));
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -112,7 +112,7 @@ bool SearchSortModel::lessThan(const QModelIndex &left, const QModelIndex &right
|
||||
case ENGINE_URL: {
|
||||
QString vL = left.data().toString();
|
||||
QString vR = right.data().toString();
|
||||
return Utils::String::naturalCompareCaseSensitive(vL, vR);
|
||||
return Utils::String::naturalCompareCaseInsensitive(vL, vR);
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QMenu>
|
||||
#include <QMetaEnum>
|
||||
#include <QTreeView>
|
||||
#include <QStandardItemModel>
|
||||
@@ -74,7 +75,9 @@ SearchTab::SearchTab(SearchWidget *parent)
|
||||
m_ui->resultsBrowser->header()->setParent(m_ui->resultsBrowser);
|
||||
unused.setVerticalHeader(new QHeaderView(Qt::Horizontal));
|
||||
#endif
|
||||
loadSettings();
|
||||
m_ui->resultsBrowser->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
header()->setStretchLastSection(false);
|
||||
|
||||
// Set Search results list model
|
||||
m_searchListModel = new QStandardItemModel(0, SearchSortModel::NB_SEARCH_COLUMNS, this);
|
||||
@@ -83,6 +86,10 @@ SearchTab::SearchTab(SearchWidget *parent)
|
||||
m_searchListModel->setHeaderData(SearchSortModel::SEEDS, Qt::Horizontal, tr("Seeders", "i.e: Number of full sources"));
|
||||
m_searchListModel->setHeaderData(SearchSortModel::LEECHES, Qt::Horizontal, tr("Leechers", "i.e: Number of partial sources"));
|
||||
m_searchListModel->setHeaderData(SearchSortModel::ENGINE_URL, Qt::Horizontal, tr("Search engine"));
|
||||
// Set columns text alignment
|
||||
m_searchListModel->setHeaderData(SearchSortModel::SIZE, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
|
||||
m_searchListModel->setHeaderData(SearchSortModel::SEEDS, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
|
||||
m_searchListModel->setHeaderData(SearchSortModel::LEECHES, Qt::Horizontal, QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole);
|
||||
|
||||
m_proxyModel = new SearchSortModel(this);
|
||||
m_proxyModel->setDynamicSortFilter(true);
|
||||
@@ -99,15 +106,31 @@ SearchTab::SearchTab(SearchWidget *parent)
|
||||
m_ui->resultsBrowser->setAllColumnsShowFocus(true);
|
||||
m_ui->resultsBrowser->setSortingEnabled(true);
|
||||
|
||||
//Ensure that at least one column is visible at all times
|
||||
bool atLeastOne = false;
|
||||
for (unsigned int i = 0; i < SearchSortModel::DL_LINK; i++) {
|
||||
if (!m_ui->resultsBrowser->isColumnHidden(i)) {
|
||||
atLeastOne = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!atLeastOne)
|
||||
m_ui->resultsBrowser->setColumnHidden(SearchSortModel::NAME, false);
|
||||
//To also mitigate the above issue, we have to resize each column when
|
||||
//its size is 0, because explicitly 'showing' the column isn't enough
|
||||
//in the above scenario.
|
||||
for (unsigned int i = 0; i < SearchSortModel::DL_LINK; i++)
|
||||
if ((m_ui->resultsBrowser->columnWidth(i) <= 0) && !m_ui->resultsBrowser->isColumnHidden(i))
|
||||
m_ui->resultsBrowser->resizeColumnToContents(i);
|
||||
|
||||
// Connect signals to slots (search part)
|
||||
connect(m_ui->resultsBrowser, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(downloadItem(const QModelIndex&)));
|
||||
|
||||
// Load last columns width for search results list
|
||||
if (!loadColWidthResultsList())
|
||||
m_ui->resultsBrowser->header()->resizeSection(0, 275);
|
||||
|
||||
// Sort by Seeds
|
||||
m_ui->resultsBrowser->sortByColumn(SearchSortModel::SEEDS, Qt::DescendingOrder);
|
||||
header()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(header(), SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(displayToggleColumnsMenu(const QPoint &)));
|
||||
connect(header(), SIGNAL(sectionResized(int, int, int)), this, SLOT(saveSettings()));
|
||||
connect(header(), SIGNAL(sectionMoved(int, int, int)), this, SLOT(saveSettings()));
|
||||
connect(header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(saveSettings()));
|
||||
|
||||
fillFilterComboBoxes();
|
||||
|
||||
@@ -128,6 +151,7 @@ SearchTab::SearchTab(SearchWidget *parent)
|
||||
|
||||
SearchTab::~SearchTab()
|
||||
{
|
||||
saveSettings();
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
@@ -144,21 +168,6 @@ QHeaderView* SearchTab::header() const
|
||||
return m_ui->resultsBrowser->header();
|
||||
}
|
||||
|
||||
bool SearchTab::loadColWidthResultsList()
|
||||
{
|
||||
QString line = Preferences::instance()->getSearchColsWidth();
|
||||
if (line.isEmpty()) return false;
|
||||
|
||||
QStringList widthList = line.split(' ');
|
||||
if (widthList.size() > m_searchListModel->columnCount())
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < widthList.size(); ++i)
|
||||
m_ui->resultsBrowser->header()->resizeSection(i, widthList.at(i).toInt());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QTreeView* SearchTab::getCurrentTreeView() const
|
||||
{
|
||||
return m_ui->resultsBrowser;
|
||||
@@ -303,3 +312,49 @@ SearchTab::NameFilteringMode SearchTab::filteringMode() const
|
||||
this->metaObject()->enumerator(this->metaObject()->indexOfEnumerator("NameFilteringMode"));
|
||||
return static_cast<NameFilteringMode>(metaEnum.keyToValue(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toByteArray()));
|
||||
}
|
||||
|
||||
void SearchTab::loadSettings()
|
||||
{
|
||||
header()->restoreState(Preferences::instance()->getSearchTabHeaderState());
|
||||
}
|
||||
|
||||
void SearchTab::saveSettings() const
|
||||
{
|
||||
Preferences::instance()->setSearchTabHeaderState(header()->saveState());
|
||||
}
|
||||
|
||||
void SearchTab::displayToggleColumnsMenu(const QPoint&)
|
||||
{
|
||||
QMenu hideshowColumn(this);
|
||||
hideshowColumn.setTitle(tr("Column visibility"));
|
||||
QList<QAction*> actions;
|
||||
for (int i = 0; i < SearchSortModel::DL_LINK; ++i) {
|
||||
QAction *myAct = hideshowColumn.addAction(m_searchListModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString());
|
||||
myAct->setCheckable(true);
|
||||
myAct->setChecked(!m_ui->resultsBrowser->isColumnHidden(i));
|
||||
actions.append(myAct);
|
||||
}
|
||||
int visibleCols = 0;
|
||||
for (unsigned int i = 0; i < SearchSortModel::DL_LINK; i++) {
|
||||
if (!m_ui->resultsBrowser->isColumnHidden(i))
|
||||
visibleCols++;
|
||||
|
||||
if (visibleCols > 1)
|
||||
break;
|
||||
}
|
||||
|
||||
// Call menu
|
||||
QAction *act = hideshowColumn.exec(QCursor::pos());
|
||||
if (act) {
|
||||
int col = actions.indexOf(act);
|
||||
Q_ASSERT(col >= 0);
|
||||
Q_ASSERT(visibleCols > 0);
|
||||
if ((!m_ui->resultsBrowser->isColumnHidden(col)) && (visibleCols == 1))
|
||||
return;
|
||||
qDebug("Toggling column %d visibility", col);
|
||||
m_ui->resultsBrowser->setColumnHidden(col, !m_ui->resultsBrowser->isColumnHidden(col));
|
||||
if ((!m_ui->resultsBrowser->isColumnHidden(col)) && (m_ui->resultsBrowser->columnWidth(col) <= 5))
|
||||
m_ui->resultsBrowser->setColumnWidth(col, 100);
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||