-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use tree for feeds in RSS downloader dialog. Closes #6281. #6345
Conversation
--HG-- branch : magao-dev
…orrent#6281. --HG-- branch : magao-dev
--HG-- branch : magao-dev
--HG-- branch : magao-dev
--HG-- branch : magao-dev
--HG-- branch : magao-dev
…ive. --HG-- branch : magao-dev
--HG-- branch : magao-dev
That's what I've tried to achieved when I was cleaning that up a long time ago. And I didn't opt to do so before is because I thought 2-pass for doing sorting is sorta a waste of computing resource, now I doubt my views. Can you also enlighten me what is the advantage of |
int posL = 0; | ||
int posR = 0; | ||
while (true) { | ||
while (true) { | ||
if ((posL == left.size()) || (posR == right.size())) | ||
return (left.size() < right.size()); // when a shorter string is another string's prefix, shorter string place before longer string | ||
return (left.size() < right.size()) ? -1 : 1; // when a shorter string is another string's prefix, shorter string place before longer string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should just use subtraction here and below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about that ... probably better. Although in theory that does potentially suffer underflow errors if right.size()
is large enough. Would need to first cast both to long
, then the result back to int
. Not sure if that would be more or less efficient than a branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You lost me here, how could the same type with only >=0 values, subtraction causes an integer underflow? care to provide an example here?
Not sure if that would be more or less efficient than a branch.
In human perspective on computer, should be faster; in reality, depends on how good the compiler optimization is...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
D'oh! For some reason it totally slipped my mind that they're both always positive values ... so no chance of underflow.
Without casting subtraction should definitely be more efficient.
thread_local NaturalCompare nCmp(true); | ||
return nCmp(left, right); | ||
#endif | ||
return NaturalCompare::naturalCompareCaseSensitive(left, right) < 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't really have to create this function in order to let Utils::String::naturalCompareCaseSemiSensitive
looks nice... or you could just propagate the return int up as I've done here: Chocobo1@20964ce
Just some ideas though...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It wasn't to make the functions look nice, but to avoid:
-
Duplicating the code again;
-
Having extra thread-locals when we only need the 2.
The cases where it would need to fall back to a case-sensitive sort are likely to be rare - I don't see the extra sort in those cases to be a performance killer. There may be cases where we genuinely want a case-insensitive sort e.g. a case-insensitive container, so having the case-insensitive sort automatically fall back to case-sensitive as a tie-breaker may not be correct in all cases (hence why I created a separate function). There are some widgets where you can't change the model ( Since I was going to use the behaviour in multiple places, I thought it best to create utility classes. |
I'm curious, for example?
This one too, a link to somewhere that explains this? |
I gave one - a case-insensitive container. Another example would be anything on a case-insensitive filesystem. In both of these cases, multiple entries with differing cases should compare equal. I guess it comes down to if I have a function that claims to do case-insensitive comparisons, then that's exactly what I expect it to do. The definition of "case-sensitive" might need explaining (in particular, whether it's locale-aware or not, follows unicode case definitions, etc). OTOH, if the functions were named in such a way as to make it clear that they're specifically for sorting (and commented that case-insensitive uses a tie-breaker), I wouldn't have a problem with it. I should note that currently all uses of Re: sorting items in a https://stackoverflow.com/questions/8493671/sorting-a-qlistwidget-using-sortitems |
I see your point, it's because the functions is called
OK, good point, just one more thing, to keep things tidy, you should create a new folder |
void handleFeedCheckStateChange(QListWidgetItem *feed_item); | ||
void updateRuleDefinitionBox(); | ||
void handleFeedCheckStateChange(QTreeWidgetItem *feed_item, int column); | ||
void updateRuleDefinitionBox(QListWidgetItem *selected = 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use nullptr
instead of 0
(and everywhere else).
@@ -647,20 +649,26 @@ void RSSImp::updateItemInfos(QTreeWidgetItem *item) | |||
void RSSImp::updateFeedIcon(const QString &url, const QString &iconPath) | |||
{ | |||
QTreeWidgetItem *item = m_feedList->getTreeItemFromUrl(url); | |||
item->setData(0, Qt::DecorationRole, QVariant(QIcon(iconPath))); | |||
|
|||
if (item != 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for consistency: if (!item) return;
if (item->parent()) | ||
updateItemInfos(item->parent()); | ||
|
||
if (item != 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for consistency: if (!item) return;
|
||
if (item != 0) { | ||
Rss::FeedPtr stream = qSharedPointerCast<Rss::Feed>(m_feedList->getRSSItem(item)); | ||
item->setText(0, display_name + QString::fromUtf8(" (") + QString::number(nbUnread) + QString(")")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you do us a favor:
(0, display_name + " (" + QString::number(nbUnread) + ")");
@@ -0,0 +1,129 @@ | |||
/* | |||
* Bittorrent Client using Qt4 and libtorrent. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove the 4
@@ -0,0 +1,129 @@ | |||
/* | |||
* Bittorrent Client using Qt4 and libtorrent. | |||
* Copyright (C) 2017 Christophe Dumez |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess he doesn't wrote this part, put your name here or "The qBittorrent team".
@@ -0,0 +1,77 @@ | |||
/* | |||
* Bittorrent Client using Qt4 and libtorrent. | |||
* Copyright (C) 2017 Christophe Dumez |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same the above 2 lines.
virtual bool operator<(const QTreeWidgetItem &other) const; | ||
|
||
private: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
redundant empty line
*/ | ||
|
||
#include "base/utils/string.h" | ||
#include "sortablewidgetitems.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#include "sortablewidgetitems.h"
// empty line
#include "base/utils/string.h"
return 0; | ||
} | ||
|
||
static int naturalCompareCaseSensitive(const QString &left, const QString &right) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if there is this rule: for each local function, you should create a function prototype
@@ -255,6 +262,10 @@ void RSSImp::deleteSelectedItems() | |||
} | |||
} | |||
m_rssManager->saveStreamList(); | |||
|
|||
foreach (const QString &feed_id, deleted) | |||
m_rssManager->forwardFeedInfosChanged(feed_id, "", 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
""
-> QString()
or {}
}; | ||
|
||
AutomatedRssDownloader::FeedListWidgetItem::FeedListWidgetItem(const Rss::FilePtr &rssFile) | ||
: SortableTreeWidgetItem(QStringList() << rssFile->displayName()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SortableTreeWidgetItem{ rssFile->displayName() }
AutomatedRssDownloader::FeedListWidgetItem::FeedListWidgetItem(const Rss::FilePtr &rssFile) | ||
: SortableTreeWidgetItem(QStringList() << rssFile->displayName()) | ||
{ | ||
bool folder = qSharedPointerDynamicCast<Rss::Folder>(rssFile) != 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nullptr
if (!manager) | ||
return; | ||
|
||
QList<Rss::FilePtr> children; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is personal preference:
const QList<Rss::FilePtr> children = rss_parent ? rss_parent->getContent() : manager->rootFolder()->getContent();
You save a few lines and no loss in readability and the best: you can stick on that const
.
bool all_enabled = false; | ||
disconnectRuleFeedSlots(); | ||
|
||
QList<QListWidgetItem *> selection; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here too.
bool anyFolderVisible = false; | ||
QTreeWidgetItemIterator it(ui->listFeeds); | ||
|
||
while (*it) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible?
for (; *it; ++it) {
break; | ||
|
||
if (!text.isEmpty()) { | ||
QStringList tokens; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could merge with the following lines...
@@ -659,7 +844,8 @@ void AutomatedRssDownloader::updateMustNotLineValidity() | |||
void AutomatedRssDownloader::updateEpisodeFilterValidity() | |||
{ | |||
const QString text = ui->lineEFilter->text(); | |||
bool valid = m_episodeRegex->indexIn(text) != -1; | |||
bool valid = text.isEmpty() || m_episodeRegex->indexIn(text) != -1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add parentheses
break; | ||
|
||
if (!text.isEmpty()) { | ||
QStringList tokens; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here.
m_editedRule = selection.first(); | ||
Rss::DownloadRulePtr rule = getCurrentRule(); | ||
|
||
if (!selected && (selection.count() == 1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... I find a little hard to follow what the code is trying to do, is it possible to refactor it?
I mean there are 2 boolean conditions so there are 4 outcomes, reading the code I cannot conclude that all possibilities are covered.
Or maybe: !selection.empty()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If no selected
parameter was passed into the function, and there is exactly one object selected in the UI, we set that as the selection - it's the rule we're currently editing. If there are either zero rules selected, or more than one, we want to clear the rule definition box and make sure we don't think we're editing a rule.
Currently selected
will only be passed in when creating a new rule. updateFeedList()
later ensures that it's actually selected in the UI.
The next branch if (selected)
is only followed if there is exactly one selected item.
Note that this code has already been merged as part of PR #6326.
This PR builds on PR #6326. It changes the RSS downloader dialog to use a tree for its feed list, as discussed in issue #6281.
This PR also introduces a new form of Natural Sort - the "case semi-sensitive" sort. This is a case insensitive sort, except where 2 strings compare equal ignoring case, in which case the strings are compared using a case-sensitive sort. This ensures a consistent ordering.
This PR also introduces subclasses of QListWidgetItem and QTreeWidgetItem that sort via natural sort (defaults to case semi-sensitive, but can be specified as case-sensitive or case-insensitive).
The RSS tab and downloader dialogs use these classes to sort, with a further enhancement that folders sort first (and for the RSS tab, the Unread item sorts before all others).
If there are no visible folders then the expander decorations are hidden, resulting in effectively the same UI as current.