diff --git a/resources/icons.qrc b/resources/icons.qrc
index 2c39d2853..3320fda80 100644
--- a/resources/icons.qrc
+++ b/resources/icons.qrc
@@ -37,6 +37,7 @@
./graphics/Faenza/actions/64/mail-mark-read.png
./graphics/Faenza/actions/64/mail-mark-unread.png
./graphics/Faenza/actions/64/mail-message-new.png
+ ./graphics/Faenza/actions/64/mail-reply-sender.png
./graphics/Faenza/actions/64/mail-send.png
./graphics/Faenza/actions/64/mail-sent.png
./graphics/Faenza/actions/64/media-playback-start.png
diff --git a/resources/rssguard.qrc b/resources/rssguard.qrc
index feb8bc999..6367a2995 100755
--- a/resources/rssguard.qrc
+++ b/resources/rssguard.qrc
@@ -2,6 +2,7 @@
text/CHANGELOG
text/COPYING_BSD
+ text/COPYING_MIT
text/COPYING_GNU_GPL
text/COPYING_GNU_GPL_HTML
diff --git a/resources/text/COPYING_MIT b/resources/text/COPYING_MIT
new file mode 100755
index 000000000..58363dadb
--- /dev/null
+++ b/resources/text/COPYING_MIT
@@ -0,0 +1,19 @@
+Copyright (C) 2019 by Anton Bukov (k06aaa@gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/src/librssguard/3rd-party/boolinq/boolinq.h b/src/librssguard/3rd-party/boolinq/boolinq.h
new file mode 100755
index 000000000..c1c25696d
--- /dev/null
+++ b/src/librssguard/3rd-party/boolinq/boolinq.h
@@ -0,0 +1,883 @@
+#pragma once
+
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+//
+
+namespace boolinq {
+
+ struct LinqEndException {};
+
+ enum BytesDirection {
+ BytesFirstToLast,
+ BytesLastToFirst,
+ };
+
+ enum BitsDirection {
+ BitsHighToLow,
+ BitsLowToHigh,
+ };
+
+ template
+ class Linq {
+ std::function nextFunc;
+ S storage;
+
+ public:
+ typedef T value_type;
+
+ Linq() : nextFunc(), storage()
+ {
+ }
+
+ Linq(S storage, std::function nextFunc) : nextFunc(nextFunc), storage(storage)
+ {
+ }
+
+ T next()
+ {
+ return nextFunc(storage);
+ }
+
+ void for_each_i(std::function apply) const
+ {
+ Linq linq = *this;
+ try {
+ for (int i = 0; ; i++) {
+ apply(linq.next(), i);
+ }
+ }
+ catch (LinqEndException &) {}
+ }
+
+ void for_each(std::function apply) const
+ {
+ return for_each_i([apply](T value, int) { return apply(value); });
+ }
+
+ Linq, int>, T> where_i(std::function filter) const
+ {
+ return Linq, int>, T>(
+ std::make_tuple(*this, 0),
+ [filter](std::tuple, int> &tuple) {
+ Linq &linq = std::get<0>(tuple);
+ int &index = std::get<1>(tuple);
+
+ while (true) {
+ T ret = linq.next();
+ if (filter(ret, index++)) {
+ return ret;
+ }
+ }
+ }
+ );
+ }
+
+ Linq, int>, T> where(std::function filter) const
+ {
+ return where_i([filter](T value, int) { return filter(value); });
+ }
+
+ Linq, int>, T> take(int count) const
+ {
+ return where_i([count](T /*value*/, int i) {
+ if (i == count) {
+ throw LinqEndException();
+ }
+ return true;
+ });
+ }
+
+ Linq, int>, T> takeWhile_i(std::function predicate) const
+ {
+ return where_i([predicate](T value, int i) {
+ if (!predicate(value, i)) {
+ throw LinqEndException();
+ }
+ return true;
+ });
+ }
+
+ Linq, int>, T> takeWhile(std::function predicate) const
+ {
+ return takeWhile_i([predicate](T value, int /*i*/) { return predicate(value); });
+ }
+
+ Linq, int>, T> skip(int count) const
+ {
+ return where_i([count](T value, int i) { return i >= count; });
+ }
+
+ Linq, int, bool>, T> skipWhile_i(std::function predicate) const
+ {
+ return Linq, int, bool>, T>(
+ std::make_tuple(*this, 0, false),
+ [predicate](std::tuple, int, bool> &tuple) {
+ Linq &linq = std::get<0>(tuple);
+ int &index = std::get<1>(tuple);
+ bool &flag = std::get<2>(tuple);
+
+ if (flag) {
+ return linq.next();
+ }
+ while (true) {
+ T ret = linq.next();
+ if (!predicate(ret, index++)) {
+ flag = true;
+ return ret;
+ }
+ }
+ }
+ );
+ }
+
+ Linq, int, bool>, T> skipWhile(std::function predicate) const
+ {
+ return skipWhile_i([predicate](T value, int /*i*/) { return predicate(value); });
+ }
+
+ template
+ Linq, std::vector, int>, T> append(Types ... newValues) const
+ {
+ return Linq, std::vector, int>, T>(
+ std::make_tuple(*this, std::vector{ newValues... }, -1),
+ [](std::tuple, std::vector, int> &tuple) {
+ Linq &linq = std::get<0>(tuple);
+ std::vector &values = std::get<1>(tuple);
+ int &index = std::get<2>(tuple);
+
+ if (index == -1) {
+ try {
+ return linq.next();
+ }
+ catch (LinqEndException &) {
+ index = 0;
+ }
+ }
+
+ if (index < values.size()) {
+ return values[index++];
+ }
+
+ throw LinqEndException();
+ }
+ );
+ }
+
+ template
+ Linq, std::vector, int>, T> prepend(Types ... newValues) const
+ {
+ return Linq, std::vector, int>, T>(
+ std::make_tuple(*this, std::vector{ newValues... }, 0),
+ [](std::tuple, std::vector, int> &tuple) {
+ Linq &linq = std::get<0>(tuple);
+ std::vector &values = std::get<1>(tuple);
+ int &index = std::get<2>(tuple);
+
+ if (index < values.size()) {
+ return values[index++];
+ }
+ return linq.next();
+ }
+ );
+ }
+
+ template::type>
+ Linq, int>, _TRet> select_i(F apply) const
+ {
+ return Linq, int>, _TRet>(
+ std::make_tuple(*this, 0),
+ [apply](std::tuple, int> &tuple) {
+ Linq &linq = std::get<0>(tuple);
+ int &index = std::get<1>(tuple);
+
+ return apply(linq.next(), index++);
+ }
+ );
+ }
+
+ template::type>
+ Linq, int>, _TRet> select(F apply) const
+ {
+ return select_i([apply](T value, int /*index*/) { return apply(value); });
+ }
+
+ template
+ Linq, int>, TRet> cast() const
+ {
+ return select_i([](T value, int /*i*/) { return TRet(value); });
+ }
+
+ template
+ Linq, Linq, bool>, T> concat(const Linq & rhs) const
+ {
+ return Linq, Linq, bool>, T>(
+ std::make_tuple(*this, rhs, false),
+ [](std::tuple, Linq, bool> &tuple){
+ Linq &first = std::get<0>(tuple);
+ Linq &second = std::get<1>(tuple);
+ bool &flag = std::get<2>(tuple);
+
+ if (!flag) {
+ try {
+ return first.next();
+ }
+ catch (LinqEndException &) {}
+ }
+ return second.next();
+ }
+ );
+ }
+
+ template<
+ typename F,
+ typename _TRet = typename std::result_of::type,
+ typename _TRetVal = typename _TRet::value_type
+ >
+ Linq, _TRet, int, bool>, _TRetVal> selectMany_i(F apply) const
+ {
+ return Linq, _TRet, int, bool>, _TRetVal>(
+ std::make_tuple(*this, _TRet(), 0, true),
+ [apply](std::tuple, _TRet, int, bool> &tuple) {
+ Linq &linq = std::get<0>(tuple);
+ _TRet ¤t = std::get<1>(tuple);
+ int &index = std::get<2>(tuple);
+ bool &finished = std::get<3>(tuple);
+
+ while (true) {
+ if (finished) {
+ current = apply(linq.next(), index++);
+ finished = false;
+ }
+ try {
+ return current.next();
+ }
+ catch (LinqEndException &) {
+ finished = true;
+ }
+ }
+ }
+ );
+ }
+
+ template<
+ typename F,
+ typename _TRet = typename std::result_of::type,
+ typename _TRetVal = typename _TRet::value_type
+ >
+ Linq, _TRet, int, bool>, _TRetVal> selectMany(F apply) const
+ {
+ return selectMany_i([apply](T value, int index) { return apply(value); });
+ }
+
+ template<
+ typename F,
+ typename _TKey = typename std::result_of::type,
+ typename _TValue = Linq, int>, T> // where(predicate)
+ >
+ Linq, Linq, std::unordered_set<_TKey> >, std::pair<_TKey, _TValue> > groupBy(F apply) const
+ {
+ return Linq, Linq, std::unordered_set<_TKey> >, std::pair<_TKey, _TValue> >(
+ std::make_tuple(*this, *this, std::unordered_set<_TKey>()),
+ [apply](std::tuple, Linq, std::unordered_set<_TKey> > &tuple){
+ Linq &linq = std::get<0>(tuple);
+ Linq &linqCopy = std::get<1>(tuple);
+ std::unordered_set<_TKey> &set = std::get<2>(tuple);
+
+ while (true) {
+ _TKey key = apply(linq.next());
+ if (set.insert(key).second) {
+ return std::make_pair(key, linqCopy.where([apply, key](T v){
+ return apply(v) == key;
+ }));
+ }
+ }
+ }
+ );
+ }
+
+ template::type>
+ Linq, std::unordered_set<_TRet> >, T> distinct(F transform) const
+ {
+ return Linq, std::unordered_set<_TRet> >, T>(
+ std::make_tuple(*this, std::unordered_set<_TRet>()),
+ [transform](std::tuple, std::unordered_set<_TRet> > &tuple) {
+ Linq &linq = std::get<0>(tuple);
+ std::unordered_set<_TRet> &set = std::get<1>(tuple);
+
+ while (true) {
+ T value = linq.next();
+ if (set.insert(transform(value)).second) {
+ return value;
+ }
+ }
+ }
+ );
+ }
+
+ Linq, std::unordered_set >, T> distinct() const
+ {
+ return distinct([](T value) { return value; });
+ }
+
+ template::const_iterator>
+ Linq, _TIter, bool>, T> orderBy(F transform) const
+ {
+ std::vector items = toStdVector();
+ std::sort(items.begin(), items.end(), [transform](const T &a, const T &b) {
+ return transform(a) < transform(b);
+ });
+
+ return Linq, _TIter, bool>, T>(
+ std::make_tuple(items, _TIter(), false),
+ [](std::tuple, _TIter, bool> &tuple) {
+ std::vector &vec = std::get<0>(tuple);
+ _TIter &it = std::get<1>(tuple);
+ bool &flag = std::get<2>(tuple);
+
+ if (!flag) {
+ flag = true;
+ it = vec.cbegin();
+ }
+ if (it == vec.cend()) {
+ throw LinqEndException();
+ }
+ return *(it++);
+ }
+ );
+ }
+
+ Linq, typename std::vector::const_iterator, bool>, T> orderBy() const
+ {
+ return orderBy([](T value) { return value; });
+ }
+
+ template::const_reverse_iterator>
+ Linq, _TIter, bool>, T> reverse() const
+ {
+ return Linq, _TIter, bool>, T>(
+ std::make_tuple(toStdList(), _TIter(), false),
+ [](std::tuple, _TIter, bool> &tuple) {
+ std::list &list = std::get<0>(tuple);
+ _TIter &it = std::get<1>(tuple);
+ bool &flag = std::get<2>(tuple);
+
+ if (!flag) {
+ flag = true;
+ it = list.crbegin();
+ }
+ if (it == list.crend()) {
+ throw LinqEndException();
+ }
+ return *(it++);
+ }
+ );
+ }
+
+ // Aggregators
+
+ template
+ TRet aggregate(TRet start, std::function accumulate) const
+ {
+ Linq linq = *this;
+ try {
+ while (true) {
+ start = accumulate(start, linq.next());
+ }
+ }
+ catch (LinqEndException &) {}
+ return start;
+ }
+
+ template::type>
+ _TRet sum(F transform) const
+ {
+ return aggregate<_TRet>(_TRet(), [transform](_TRet accumulator, T value) {
+ return accumulator + transform(value);
+ });
+ }
+
+ template
+ TRet sum() const
+ {
+ return sum([](T value) { return TRet(value); });
+ }
+
+ template::type>
+ _TRet avg(F transform) const
+ {
+ int count = 0;
+ _TRet res = sum([transform, &count](T value) {
+ count++;
+ return transform(value);
+ });
+ return res / count;
+ }
+
+ template
+ TRet avg() const
+ {
+ return avg([](T value) { return TRet(value); });
+ }
+
+ int count() const
+ {
+ int index = 0;
+ for_each([&index](T /*a*/) { index++; });
+ return index;
+ }
+
+ int count(std::function predicate) const
+ {
+ return where(predicate).count();
+ }
+
+ int count(const T &item) const
+ {
+ return count([item](T value) { return item == value; });
+ }
+
+ // Bool aggregators
+
+ bool any(std::function predicate) const
+ {
+ Linq linq = *this;
+ try {
+ while (true) {
+ if (predicate(linq.next()))
+ return true;
+ }
+ }
+ catch (LinqEndException &) {}
+ return false;
+ }
+
+ bool any() const
+ {
+ return any([](T value) { return static_cast(value); });
+ }
+
+ bool all(std::function predicate) const
+ {
+ return !any([predicate](T value) { return !predicate(value); });
+ }
+
+ bool all() const
+ {
+ return all([](T value) { return static_cast(value); });
+ }
+
+ bool contains(const T &item) const
+ {
+ return any([&item](T value) { return value == item; });
+ }
+
+ // Election aggregators
+
+ T elect(std::function accumulate) const
+ {
+ T result;
+ for_each_i([accumulate, &result](T value, int i) {
+ if (i == 0) {
+ result = value;
+ } else {
+ result = accumulate(result, value);
+ }
+ });
+ return result;
+ }
+
+ template
+ T max(F transform) const
+ {
+ return elect([transform](const T &a, const T &b) {
+ return (transform(a) < transform(b)) ? b : a;
+ });
+ }
+
+ T max() const
+ {
+ return max([](T value) { return value; });
+ }
+
+ template
+ T min(F transform) const
+ {
+ return elect([transform](const T &a, const T &b) {
+ return (transform(a) < transform(b)) ? a : b;
+ });
+ }
+
+ T min() const
+ {
+ return min([](T value) { return value; });
+ }
+
+ // Single object returners
+
+ T elementAt(int index) const
+ {
+ return skip(index).next();
+ }
+
+ T first(std::function predicate) const
+ {
+ return where(predicate).next();
+ }
+
+ T first() const
+ {
+ return Linq(*this).next();
+ }
+
+ T firstOrDefault(std::function predicate, T const& defaultValue = T()) const
+ {
+ try {
+ return where(predicate).next();
+ }
+ catch (LinqEndException &) {}
+ return defaultValue;
+ }
+
+ T firstOrDefault(T const& defaultValue = T()) const
+ {
+ try {
+ return Linq(*this).next();
+ }
+ catch (LinqEndException &) {}
+ return defaultValue;
+ }
+
+ T last(std::function predicate) const
+ {
+ T res;
+ int index = -1;
+ where(predicate).for_each_i([&res, &index](T value, int i) {
+ res = value;
+ index = i;
+ });
+
+ if (index == -1) {
+ throw LinqEndException();
+ }
+ return res;
+ }
+
+ T last() const
+ {
+ return last([](T /*value*/) { return true; });
+ }
+
+ T lastOrDefault(std::function predicate, T const& defaultValue = T()) const
+ {
+ T res = defaultValue;
+ where(predicate).for_each([&res](T value) {
+ res = value;
+ });
+ return res;
+ }
+
+ T lastOrDefault(T const& defaultValue = T()) const
+ {
+ return lastOrDefault([](T /*value*/) { return true; }, defaultValue);
+ }
+
+ // Export to containers
+
+ std::vector toStdVector() const
+ {
+ std::vector items;
+ for_each([&items](T value) {
+ items.push_back(value);
+ });
+ return items;
+ }
+
+ std::list toStdList() const
+ {
+ std::list items;
+ for_each([&items](T value) {
+ items.push_back(value);
+ });
+ return items;
+ }
+
+ std::deque toStdDeque() const
+ {
+ std::deque items;
+ for_each([&items](T value) {
+ items.push_back(value);
+ });
+ return items;
+ }
+
+ std::set toStdSet() const
+ {
+ std::set items;
+ for_each([&items](T value) {
+ items.insert(value);
+ });
+ return items;
+ }
+
+ std::unordered_set toStdUnorderedSet() const
+ {
+ std::unordered_set items;
+ for_each([&items](T value) {
+ items.insert(value);
+ });
+ return items;
+ }
+
+ // Bits and bytes
+
+ Linq, BytesDirection, T, int>, int> bytes(BytesDirection direction = BytesFirstToLast) const
+ {
+ return Linq, BytesDirection, T, int>, int>(
+ std::make_tuple(*this, direction, T(), sizeof(T)),
+ [](std::tuple, BytesDirection, T, int> &tuple) {
+ Linq &linq = std::get<0>(tuple);
+ BytesDirection &bytesDirection = std::get<1>(tuple);
+ T &value = std::get<2>(tuple);
+ int &index = std::get<3>(tuple);
+
+ if (index == sizeof(T)) {
+ value = linq.next();
+ index = 0;
+ }
+
+ unsigned char *ptr = reinterpret_cast(&value);
+
+ int byteIndex = index;
+ if (bytesDirection == BytesLastToFirst) {
+ byteIndex = sizeof(T) - 1 - byteIndex;
+ }
+
+ index++;
+ return ptr[byteIndex];
+ }
+ );
+ }
+
+ template
+ Linq, BytesDirection, int>, TRet> unbytes(BytesDirection direction = BytesFirstToLast) const
+ {
+ return Linq, BytesDirection, int>, TRet>(
+ std::make_tuple(*this, direction, 0),
+ [](std::tuple, BytesDirection, int> &tuple) {
+ Linq &linq = std::get<0>(tuple);
+ BytesDirection &bytesDirection = std::get<1>(tuple);
+ int &index = std::get<2>(tuple);
+
+ TRet value;
+ unsigned char *ptr = reinterpret_cast(&value);
+
+ for (int i = 0; i < sizeof(TRet); i++) {
+ int byteIndex = i;
+ if (bytesDirection == BytesLastToFirst) {
+ byteIndex = sizeof(TRet) - 1 - byteIndex;
+ }
+
+ ptr[byteIndex] = linq.next();
+ }
+
+ return value;
+ }
+ );
+ }
+
+ Linq, BytesDirection, BitsDirection, T, int>, int> bits(BitsDirection bitsDir = BitsHighToLow, BytesDirection bytesDir = BytesFirstToLast) const
+ {
+ return Linq, BytesDirection, BitsDirection, T, int>, int>(
+ std::make_tuple(*this, bytesDir, bitsDir, T(), sizeof(T) * CHAR_BIT),
+ [](std::tuple, BytesDirection, BitsDirection, T, int> &tuple) {
+ Linq &linq = std::get<0>(tuple);
+ BytesDirection &bytesDirection = std::get<1>(tuple);
+ BitsDirection &bitsDirection = std::get<2>(tuple);
+ T &value = std::get<3>(tuple);
+ int &index = std::get<4>(tuple);
+
+ if (index == sizeof(T) * CHAR_BIT) {
+ value = linq.next();
+ index = 0;
+ }
+
+ unsigned char *ptr = reinterpret_cast(&value);
+
+ int byteIndex = index / CHAR_BIT;
+ if (bytesDirection == BytesLastToFirst) {
+ byteIndex = sizeof(T) - 1 - byteIndex;
+ }
+
+ int bitIndex = index % CHAR_BIT;
+ if (bitsDirection == BitsHighToLow) {
+ bitIndex = CHAR_BIT - 1 - bitIndex;
+ }
+
+ index++;
+ return (ptr[byteIndex] >> bitIndex) & 1;
+ }
+ );
+ }
+
+ template
+ Linq, BytesDirection, BitsDirection, int>, TRet> unbits(BitsDirection bitsDir = BitsHighToLow, BytesDirection bytesDir = BytesFirstToLast) const
+ {
+ return Linq, BytesDirection, BitsDirection, int>, TRet>(
+ std::make_tuple(*this, bytesDir, bitsDir, 0),
+ [](std::tuple, BytesDirection, BitsDirection, int> &tuple) {
+ Linq &linq = std::get<0>(tuple);
+ BytesDirection &bytesDirection = std::get<1>(tuple);
+ BitsDirection &bitsDirection = std::get<2>(tuple);
+ int &index = std::get<3>(tuple);
+
+ TRet value = TRet();
+ unsigned char *ptr = reinterpret_cast(&value);
+
+ for (int i = 0; i < sizeof(TRet) * CHAR_BIT; i++) {
+ int byteIndex = i / CHAR_BIT;
+ if (bytesDirection == BytesLastToFirst) {
+ byteIndex = sizeof(TRet) - 1 - byteIndex;
+ }
+
+ int bitIndex = i % CHAR_BIT;
+ if (bitsDirection == BitsHighToLow) {
+ bitIndex = CHAR_BIT - 1 - bitIndex;
+ }
+
+ ptr[byteIndex] &= ~(1 << bitIndex);
+ ptr[byteIndex] |= bool(linq.next()) << bitIndex;
+ }
+
+ return value;
+ }
+ );
+ }
+ };
+
+ template
+ std::ostream &operator<<(std::ostream &stream, Linq linq)
+ {
+ try {
+ while (true) {
+ stream << linq.next() << ' ';
+ }
+ }
+ catch (LinqEndException &) {}
+ return stream;
+ }
+
+ ////////////////////////////////////////////////////////////////
+ // Linq Creators
+ ////////////////////////////////////////////////////////////////
+
+ template
+ Linq, typename std::iterator_traits::value_type> from(const T & begin, const T & end)
+ {
+ return Linq, typename std::iterator_traits::value_type>(
+ std::make_pair(begin, end),
+ [](std::pair &pair) {
+ if (pair.first == pair.second) {
+ throw LinqEndException();
+ }
+ return *(pair.first++);
+ }
+ );
+ }
+
+ template
+ Linq, typename std::iterator_traits::value_type> from(const T & it, int n)
+ {
+ return from(it, it + n);
+ }
+
+ template
+ Linq, T> from(T (&array)[N])
+ {
+ return from((const T *)(&array), (const T *)(&array) + N);
+ }
+
+ template class TV, typename TT>
+ auto from(const TV & container)
+ -> decltype(from(container.cbegin(), container.cend()))
+ {
+ return from(container.cbegin(), container.cend());
+ }
+
+ // std::list, std::vector, std::dequeue
+ template class TV, typename TT, typename TU>
+ auto from(const TV & container)
+ -> decltype(from(container.cbegin(), container.cend()))
+ {
+ return from(container.cbegin(), container.cend());
+ }
+
+ // std::set
+ template class TV, typename TT, typename TS, typename TU>
+ auto from(const TV & container)
+ -> decltype(from(container.cbegin(), container.cend()))
+ {
+ return from(container.cbegin(), container.cend());
+ }
+
+ // std::map
+ template class TV, typename TK, typename TT, typename TS, typename TU>
+ auto from(const TV & container)
+ -> decltype(from(container.cbegin(), container.cend()))
+ {
+ return from(container.cbegin(), container.cend());
+ }
+
+ // std::array
+ template class TV, typename TT, size_t TL>
+ auto from(const TV & container)
+ -> decltype(from(container.cbegin(), container.cend()))
+ {
+ return from(container.cbegin(), container.cend());
+ }
+
+ template
+ Linq, T> repeat(const T & value, int count) {
+ return Linq, T>(
+ std::make_pair(value, count),
+ [](std::pair &pair) {
+ if (pair.second > 0) {
+ pair.second--;
+ return pair.first;
+ }
+ throw LinqEndException();
+ }
+ );
+ }
+
+ template
+ Linq, T> range(const T & start, const T & end, const T & step) {
+ return Linq, T>(
+ std::make_tuple(start, end, step),
+ [](std::tuple &tuple) {
+ T &start = std::get<0>(tuple);
+ T &end = std::get<1>(tuple);
+ T &step = std::get<2>(tuple);
+
+ T value = start;
+ if (value < end) {
+ start += step;
+ return value;
+ }
+ throw LinqEndException();
+ }
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/librssguard/core/feedsmodel.cpp b/src/librssguard/core/feedsmodel.cpp
index 73a9ec008..69201010f 100644
--- a/src/librssguard/core/feedsmodel.cpp
+++ b/src/librssguard/core/feedsmodel.cpp
@@ -2,6 +2,7 @@
#include "core/feedsmodel.h"
+#include "3rd-party/boolinq/boolinq.h"
#include "definitions/definitions.h"
#include "gui/dialogs/formmain.h"
#include "miscellaneous/databasefactory.h"
@@ -68,7 +69,7 @@ QMimeData* FeedsModel::mimeData(const QModelIndexList& indexes) const {
RootItem* item_for_index = itemForIndex(index);
- if (item_for_index->kind() != RootItemKind::Root) {
+ if (item_for_index->kind() != RootItem::Kind::Root) {
stream << quintptr(item_for_index);
}
}
@@ -291,10 +292,10 @@ void FeedsModel::reassignNodeToNewParent(RootItem* original_node, RootItem* new_
}
QListFeedsModel::serviceRoots() const {
- QListroots;
+ QList roots;
for (RootItem* root : m_rootItem->childItems()) {
- if (root->kind() == RootItemKind::ServiceRoot) {
+ if (root->kind() == RootItem::Kind::ServiceRoot) {
roots.append(root->toServiceRoot());
}
}
@@ -303,13 +304,9 @@ QListFeedsModel::serviceRoots() const {
}
bool FeedsModel::containsServiceRootFromEntryPoint(const ServiceEntryPoint* point) const {
- for (const ServiceRoot* root : serviceRoots()) {
- if (root->code() == point->code()) {
- return true;
- }
- }
-
- return false;
+ return boolinq::from(serviceRoots()).any([=](ServiceRoot* root) {
+ return root->code() == point->code();
+ });
}
StandardServiceRoot* FeedsModel::standardServiceRoot() const {
@@ -329,12 +326,12 @@ QListFeedsModel::feedsForScheduledUpdate(bool auto_update_now) {
for (Feed* feed : m_rootItem->getSubTreeFeeds()) {
switch (feed->autoUpdateType()) {
- case Feed::DontAutoUpdate:
+ case Feed::AutoUpdateType::DontAutoUpdate:
// Do not auto-update this feed ever.
continue;
- case Feed::DefaultAutoUpdate:
+ case Feed::AutoUpdateType::DefaultAutoUpdate:
if (auto_update_now) {
feeds_for_update.append(feed);
@@ -342,7 +339,7 @@ QListFeedsModel::feedsForScheduledUpdate(bool auto_update_now) {
break;
- case Feed::SpecificAutoUpdate:
+ case Feed::AutoUpdateType::SpecificAutoUpdate:
default:
int remaining_interval = feed->autoUpdateRemainingInterval();
@@ -383,7 +380,7 @@ RootItem* FeedsModel::itemForIndex(const QModelIndex& index) const {
}
QModelIndex FeedsModel::indexForItem(const RootItem* item) const {
- if (item == nullptr || item->kind() == RootItemKind::Root) {
+ if (item == nullptr || item->kind() == RootItem::Kind::Root) {
// Root item lies on invalid index.
return QModelIndex();
@@ -391,7 +388,7 @@ QModelIndex FeedsModel::indexForItem(const RootItem* item) const {
QStack chain;
- while (item->kind() != RootItemKind::Root) {
+ while (item->kind() != RootItem::Kind::Root) {
chain.push(item);
item = item->parent();
}
@@ -412,13 +409,9 @@ QModelIndex FeedsModel::indexForItem(const RootItem* item) const {
}
bool FeedsModel::hasAnyFeedNewMessages() const {
- for (const Feed* feed : m_rootItem->getSubTreeFeeds()) {
- if (feed->status() == Feed::NewMessages) {
- return true;
- }
- }
-
- return false;
+ return boolinq::from(m_rootItem->getSubTreeFeeds()).any([](const Feed* feed) {
+ return feed->status() == Feed::Status::NewMessages;
+ });
}
RootItem* FeedsModel::rootItem() const {
@@ -545,9 +538,7 @@ void FeedsModel::loadActivatedServiceAccounts() {
}
if (serviceRoots().isEmpty()) {
- QTimer::singleShot(3000,
- qApp->mainForm(),
- []() {
+ QTimer::singleShot(3000, qApp->mainForm(), []() {
qApp->mainForm()->showAddAccountDialog();
});
}
diff --git a/src/librssguard/core/feedsproxymodel.cpp b/src/librssguard/core/feedsproxymodel.cpp
index bac2d7d69..157872344 100644
--- a/src/librssguard/core/feedsproxymodel.cpp
+++ b/src/librssguard/core/feedsproxymodel.cpp
@@ -26,11 +26,11 @@ FeedsProxyModel::FeedsProxyModel(FeedsModel* source_model, QObject* parent)
// means it should be more on top when sorting
// in ascending order.
m_priorities = {
- RootItemKind::Kind::Category,
- RootItemKind::Kind::Feed,
- RootItemKind::Kind::Labels,
- RootItemKind::Kind::Important,
- RootItemKind::Kind::Bin
+ RootItem::Kind::Category,
+ RootItem::Kind::Feed,
+ RootItem::Kind::Labels,
+ RootItem::Kind::Important,
+ RootItem::Kind::Bin
};
}
@@ -218,7 +218,7 @@ bool FeedsProxyModel::filterAcceptsRowInternal(int source_row, const QModelIndex
const RootItem* item = m_sourceModel->itemForIndex(idx);
- if (item->kind() != RootItemKind::Category && item->kind() != RootItemKind::Feed) {
+ if (item->kind() != RootItem::Kind::Category && item->kind() != RootItem::Kind::Feed) {
// Some items are always visible.
return true;
}
diff --git a/src/librssguard/core/feedsproxymodel.h b/src/librssguard/core/feedsproxymodel.h
index 75c5c268c..f69b8e9ff 100644
--- a/src/librssguard/core/feedsproxymodel.h
+++ b/src/librssguard/core/feedsproxymodel.h
@@ -48,7 +48,7 @@ class FeedsProxyModel : public QSortFilterProxyModel {
const RootItem* m_selectedItem;
bool m_showUnreadOnly;
QList> m_hiddenIndices;
- QList m_priorities;
+ QList m_priorities;
};
#endif // FEEDSPROXYMODEL_H
diff --git a/src/librssguard/core/messagesmodel.cpp b/src/librssguard/core/messagesmodel.cpp
index 7c75b9948..df9ad79ab 100644
--- a/src/librssguard/core/messagesmodel.cpp
+++ b/src/librssguard/core/messagesmodel.cpp
@@ -111,7 +111,7 @@ bool MessagesModel::setMessageImportantById(int id, RootItem::Importance importa
int found_id = data(i, MSG_DB_ID_INDEX, Qt::EditRole).toInt();
if (found_id == id) {
- bool set = setData(index(i, MSG_DB_IMPORTANT_INDEX), important);
+ bool set = setData(index(i, MSG_DB_IMPORTANT_INDEX), int(important));
if (set) {
emit dataChanged(index(i, 0), index(i, MSG_DB_CUSTOM_HASH_INDEX));
@@ -214,6 +214,16 @@ Qt::ItemFlags MessagesModel::flags(const QModelIndex& index) const {
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemNeverHasChildren;
}
+QList MessagesModel::messagesAt(QList row_indices) const {
+ QList msgs;
+
+ for (int idx : row_indices) {
+ msgs << messageAt(idx);
+ }
+
+ return msgs;
+}
+
QVariant MessagesModel::data(int row, int column, int role) const {
return data(index(row, column), role);
}
@@ -337,7 +347,7 @@ QVariant MessagesModel::data(const QModelIndex& idx, int role) const {
}
bool MessagesModel::setMessageRead(int row_index, RootItem::ReadStatus read) {
- if (data(row_index, MSG_DB_READ_INDEX, Qt::EditRole).toInt() == read) {
+ if (data(row_index, MSG_DB_READ_INDEX, Qt::EditRole).toInt() == int(read)) {
// Read status is the same is the one currently set.
// In that case, no extra work is needed.
return true;
@@ -351,7 +361,7 @@ bool MessagesModel::setMessageRead(int row_index, RootItem::ReadStatus read) {
}
// Rewrite "visible" data in the model.
- bool working_change = setData(index(row_index, MSG_DB_READ_INDEX), read);
+ bool working_change = setData(index(row_index, MSG_DB_READ_INDEX), int(read));
if (!working_change) {
// If rewriting in the model failed, then cancel all actions.
@@ -372,7 +382,7 @@ bool MessagesModel::setMessageReadById(int id, RootItem::ReadStatus read) {
int found_id = data(i, MSG_DB_ID_INDEX, Qt::EditRole).toInt();
if (found_id == id) {
- bool set = setData(index(i, MSG_DB_READ_INDEX), read);
+ bool set = setData(index(i, MSG_DB_READ_INDEX), int(read));
if (set) {
emit dataChanged(index(i, 0), index(i, MSG_DB_CUSTOM_HASH_INDEX));
@@ -388,8 +398,9 @@ bool MessagesModel::setMessageReadById(int id, RootItem::ReadStatus read) {
bool MessagesModel::switchMessageImportance(int row_index) {
const QModelIndex target_index = index(row_index, MSG_DB_IMPORTANT_INDEX);
const RootItem::Importance current_importance = (RootItem::Importance) data(target_index, Qt::EditRole).toInt();
- const RootItem::Importance next_importance = current_importance == RootItem::Important ?
- RootItem::NotImportant : RootItem::Important;
+ const RootItem::Importance next_importance = current_importance == RootItem::Importance::Important
+ ? RootItem::Importance::NotImportant
+ : RootItem::Importance::Important;
const Message message = messageAt(row_index);
const QPair pair(message, next_importance);
@@ -399,7 +410,7 @@ bool MessagesModel::switchMessageImportance(int row_index) {
}
// Rewrite "visible" data in the model.
- const bool working_change = setData(target_index, next_importance);
+ const bool working_change = setData(target_index, int(next_importance));
if (!working_change) {
// If rewriting in the model failed, then cancel all actions.
@@ -429,15 +440,15 @@ bool MessagesModel::switchBatchMessageImportance(const QModelIndexList& messages
RootItem::Importance message_importance = messageImportance((message.row()));
- message_states.append(QPair(msg, message_importance == RootItem::Important ?
- RootItem::NotImportant :
- RootItem::Important));
+ message_states.append(QPair(msg, message_importance == RootItem::Importance::Important
+ ? RootItem::Importance::NotImportant
+ : RootItem::Importance::Important));
message_ids.append(QString::number(msg.m_id));
QModelIndex idx_msg_imp = index(message.row(), MSG_DB_IMPORTANT_INDEX);
- setData(idx_msg_imp, message_importance == RootItem::Important ?
- (int) RootItem::NotImportant :
- (int) RootItem::Important);
+ setData(idx_msg_imp, message_importance == RootItem::Importance::Important
+ ? int(RootItem::Importance::NotImportant)
+ : int(RootItem::Importance::Important));
}
reloadWholeLayout();
@@ -481,7 +492,7 @@ bool MessagesModel::setBatchMessagesDeleted(const QModelIndexList& messages) {
bool deleted;
- if (m_selectedItem->kind() != RootItemKind::Bin) {
+ if (m_selectedItem->kind() != RootItem::Kind::Bin) {
deleted = DatabaseQueries::deleteOrRestoreMessagesToFromBin(m_db, message_ids, true);
}
else {
diff --git a/src/librssguard/core/messagesmodel.h b/src/librssguard/core/messagesmodel.h
index 176d19667..b7ef9149d 100644
--- a/src/librssguard/core/messagesmodel.h
+++ b/src/librssguard/core/messagesmodel.h
@@ -44,6 +44,8 @@ class MessagesModel : public QSqlQueryModel, public MessagesModelSqlLayer {
Qt::ItemFlags flags(const QModelIndex& index) const;
// Returns message at given index.
+
+ QList messagesAt(QList row_indices) const;
Message messageAt(int row_index) const;
int messageId(int row_index) const;
RootItem::Importance messageImportance(int row_index) const;
diff --git a/src/librssguard/definitions/definitions.h b/src/librssguard/definitions/definitions.h
index 504f16c17..26d960562 100755
--- a/src/librssguard/definitions/definitions.h
+++ b/src/librssguard/definitions/definitions.h
@@ -141,6 +141,26 @@
#define APP_NO_THEME ""
#define APP_THEME_SUFFIX ".png"
+#ifndef qDebugNN
+#define qDebugNN qDebug().noquote().nospace()
+#endif
+
+#ifndef qWarningNN
+#define qWarningNN qWarning().noquote().nospace()
+#endif
+
+#ifndef qCriticalNN
+#define qCriticalNN qCritical().noquote().nospace()
+#endif
+
+#ifndef qFatalNN
+#define qFatalNN qFatal().noquote().nospace()
+#endif
+
+#ifndef qInfoNN
+#define qInfoNN qInfo().noquote().nospace()
+#endif
+
#ifndef QSL
// Thin macro wrapper for literal strings.
diff --git a/src/librssguard/gui/dialogs/formabout.cpp b/src/librssguard/gui/dialogs/formabout.cpp
index cb7fb9695..b8ee98b67 100644
--- a/src/librssguard/gui/dialogs/formabout.cpp
+++ b/src/librssguard/gui/dialogs/formabout.cpp
@@ -25,7 +25,7 @@ FormAbout::~FormAbout() {
}
void FormAbout::loadSettingsAndPaths() {
- if (qApp->settings()->type() == SettingsProperties::Portable) {
+ if (qApp->settings()->type() == SettingsProperties::SettingsType::Portable) {
m_ui.m_txtPathsSettingsType->setText(tr("FULLY portable"));
}
else {
@@ -68,6 +68,13 @@ void FormAbout::loadLicenseAndInformation() {
m_ui.m_txtLicenseBsd->setText(tr("License not found."));
}
+ try {
+ m_ui.m_txtLicenseMit->setText(IOFactory::readFile(APP_INFO_PATH + QL1S("/COPYING_MIT")));
+ }
+ catch (...) {
+ m_ui.m_txtLicenseMit->setText(tr("License not found."));
+ }
+
// Set other informative texts.
m_ui.m_lblDesc->setText(tr("%8
" "Version: %1 (built on %2/%3)
" "Revision: %4
" "Build date: %5
"
"Qt: %6 (compiled against %7)
").arg(
diff --git a/src/librssguard/gui/dialogs/formabout.ui b/src/librssguard/gui/dialogs/formabout.ui
index 84959f6dd..dba81fca3 100644
--- a/src/librssguard/gui/dialogs/formabout.ui
+++ b/src/librssguard/gui/dialogs/formabout.ui
@@ -160,7 +160,7 @@ p, li { white-space: pre-wrap; }
0
0
685
- 184
+ 157
@@ -235,8 +235,8 @@ p, li { white-space: pre-wrap; }
0
0
- 98
- 69
+ 685
+ 157
@@ -288,6 +288,68 @@ p, li { white-space: pre-wrap; }
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
</style></head><body style=" font-family:'DejaVu Sans Mono'; font-size:8.25pt; font-weight:400; font-style:normal;">
+<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html>
+
+
+ Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse
+
+
+ true
+
+
+
+
+
+
+
+ MIT License (applies to boolinq source code)
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+ DejaVu Sans Mono
+
+
+
+
+
+
+ QFrame::NoFrame
+
+
+ Qt::ScrollBarAlwaysOff
+
+
+ QTextEdit::AutoNone
+
+
+ false
+
+
+ QTextEdit::WidgetWidth
+
+
+ true
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'DejaVu Sans Mono'; font-size:8.25pt; font-weight:400; font-style:normal;">
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html>
diff --git a/src/librssguard/gui/dialogs/formmain.cpp b/src/librssguard/gui/dialogs/formmain.cpp
index ebd6ae5bc..5df1ef52a 100755
--- a/src/librssguard/gui/dialogs/formmain.cpp
+++ b/src/librssguard/gui/dialogs/formmain.cpp
@@ -312,7 +312,7 @@ void FormMain::updateRecycleBinMenu() {
no_action->setEnabled(false);
root_menu->addAction(no_action);
}
- else if ((context_menu = bin->contextMenu()).isEmpty()) {
+ else if ((context_menu = bin->contextMenuFeedsList()).isEmpty()) {
QAction* no_action = new QAction(qApp->icons()->fromTheme(QSL("dialog-error")),
tr("No actions possible"),
m_ui->m_menuRecycleBin);
@@ -391,7 +391,7 @@ void FormMain::updateMessageButtonsAvailability() {
const bool one_message_selected = tabWidget()->feedMessageViewer()->messagesView()->selectionModel()->selectedRows().size() == 1;
const bool atleast_one_message_selected = !tabWidget()->feedMessageViewer()->messagesView()->selectionModel()->selectedRows().isEmpty();
const bool bin_loaded = tabWidget()->feedMessageViewer()->messagesView()->sourceModel()->loadedItem() != nullptr
- && tabWidget()->feedMessageViewer()->messagesView()->sourceModel()->loadedItem()->kind() == RootItemKind::Bin;
+ && tabWidget()->feedMessageViewer()->messagesView()->sourceModel()->loadedItem()->kind() == RootItem::Kind::Bin;
m_ui->m_actionDeleteSelectedMessages->setEnabled(atleast_one_message_selected);
m_ui->m_actionRestoreSelectedMessages->setEnabled(atleast_one_message_selected && bin_loaded);
@@ -408,9 +408,9 @@ void FormMain::updateFeedButtonsAvailability() {
const bool critical_action_running = qApp->feedUpdateLock()->isLocked();
const RootItem* selected_item = tabWidget()->feedMessageViewer()->feedsView()->selectedItem();
const bool anything_selected = selected_item != nullptr;
- const bool feed_selected = anything_selected && selected_item->kind() == RootItemKind::Feed;
- const bool category_selected = anything_selected && selected_item->kind() == RootItemKind::Category;
- const bool service_selected = anything_selected && selected_item->kind() == RootItemKind::ServiceRoot;
+ const bool feed_selected = anything_selected && selected_item->kind() == RootItem::Kind::Feed;
+ const bool category_selected = anything_selected && selected_item->kind() == RootItem::Kind::Category;
+ const bool service_selected = anything_selected && selected_item->kind() == RootItem::Kind::ServiceRoot;
m_ui->m_actionStopRunningItemsUpdate->setEnabled(is_update_running);
m_ui->m_actionBackupDatabaseSettings->setEnabled(!critical_action_running);
diff --git a/src/librssguard/gui/feedsview.cpp b/src/librssguard/gui/feedsview.cpp
index 9fcbc0f82..dc66aa8f5 100755
--- a/src/librssguard/gui/feedsview.cpp
+++ b/src/librssguard/gui/feedsview.cpp
@@ -95,7 +95,7 @@ void FeedsView::saveAllExpandStates() {
void FeedsView::saveExpandStates(RootItem* item) {
Settings* settings = qApp->settings();
- QList items = item->getSubTree(RootItemKind::Category | RootItemKind::ServiceRoot);
+ QList items = item->getSubTree(RootItem::Kind::Category | RootItem::Kind::ServiceRoot);
// Iterate all categories and save their expand statuses.
for (const RootItem* it : items) {
@@ -113,7 +113,7 @@ void FeedsView::loadAllExpandStates() {
const Settings* settings = qApp->settings();
QList expandable_items;
- expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItemKind::Category | RootItemKind::ServiceRoot));
+ expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItem::Kind::Category | RootItem::Kind::ServiceRoot));
// Iterate all categories and save their expand statuses.
for (const RootItem* item : expandable_items) {
@@ -305,11 +305,11 @@ void FeedsView::markSelectedItemReadStatus(RootItem::ReadStatus read) {
}
void FeedsView::markSelectedItemRead() {
- markSelectedItemReadStatus(RootItem::Read);
+ markSelectedItemReadStatus(RootItem::ReadStatus::Read);
}
void FeedsView::markSelectedItemUnread() {
- markSelectedItemReadStatus(RootItem::Unread);
+ markSelectedItemReadStatus(RootItem::ReadStatus::Unread);
}
void FeedsView::markAllItemsReadStatus(RootItem::ReadStatus read) {
@@ -317,7 +317,7 @@ void FeedsView::markAllItemsReadStatus(RootItem::ReadStatus read) {
}
void FeedsView::markAllItemsRead() {
- markAllItemsReadStatus(RootItem::Read);
+ markAllItemsReadStatus(RootItem::ReadStatus::Read);
}
void FeedsView::openSelectedItemsInNewspaperMode() {
@@ -430,7 +430,7 @@ QMenu* FeedsView::initializeContextMenuBin(RootItem* clicked_item) {
m_contextMenuBin->clear();
}
- QList specific_actions = clicked_item->contextMenu();
+ QList specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuBin->addActions(QList() <<
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode <<
@@ -453,7 +453,7 @@ QMenu* FeedsView::initializeContextMenuService(RootItem* clicked_item) {
m_contextMenuService->clear();
}
- QList specific_actions = clicked_item->contextMenu();
+ QList specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuService->addActions(QList() <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedItems <<
@@ -511,7 +511,7 @@ QMenu* FeedsView::initializeContextMenuCategories(RootItem* clicked_item) {
m_contextMenuCategories->clear();
}
- QList specific_actions = clicked_item->contextMenu();
+ QList specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuCategories->addActions(QList() <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedItems <<
@@ -538,7 +538,7 @@ QMenu* FeedsView::initializeContextMenuFeeds(RootItem* clicked_item) {
m_contextMenuFeeds->clear();
}
- QList specific_actions = clicked_item->contextMenu();
+ QList specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuFeeds->addActions(QList() <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedItems <<
@@ -565,7 +565,7 @@ QMenu* FeedsView::initializeContextMenuImportant(RootItem* clicked_item) {
m_contextMenuImportant->clear();
}
- QList specific_actions = clicked_item->contextMenu();
+ QList specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuImportant->addActions(QList() <<
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode <<
@@ -598,7 +598,7 @@ QMenu* FeedsView::initializeContextMenuOtherItem(RootItem* clicked_item) {
m_contextMenuOtherItems->clear();
}
- QList specific_actions = clicked_item->contextMenu();
+ QList specific_actions = clicked_item->contextMenuFeedsList();
if (!specific_actions.isEmpty()) {
m_contextMenuOtherItems->addSeparator();
@@ -660,21 +660,21 @@ void FeedsView::contextMenuEvent(QContextMenuEvent* event) {
const QModelIndex mapped_index = model()->mapToSource(clicked_index);
RootItem* clicked_item = sourceModel()->itemForIndex(mapped_index);
- if (clicked_item->kind() == RootItemKind::Category) {
+ if (clicked_item->kind() == RootItem::Kind::Category) {
// Display context menu for categories.
initializeContextMenuCategories(clicked_item)->exec(event->globalPos());
}
- else if (clicked_item->kind() == RootItemKind::Feed) {
+ else if (clicked_item->kind() == RootItem::Kind::Feed) {
// Display context menu for feeds.
initializeContextMenuFeeds(clicked_item)->exec(event->globalPos());
}
- else if (clicked_item->kind() == RootItemKind::Important) {
+ else if (clicked_item->kind() == RootItem::Kind::Important) {
initializeContextMenuImportant(clicked_item)->exec(event->globalPos());
}
- else if (clicked_item->kind() == RootItemKind::Bin) {
+ else if (clicked_item->kind() == RootItem::Kind::Bin) {
initializeContextMenuBin(clicked_item)->exec(event->globalPos());
}
- else if (clicked_item->kind() == RootItemKind::ServiceRoot) {
+ else if (clicked_item->kind() == RootItem::Kind::ServiceRoot) {
initializeContextMenuService(clicked_item)->exec(event->globalPos());
}
else {
@@ -693,7 +693,7 @@ void FeedsView::mouseDoubleClickEvent(QMouseEvent* event) {
if (idx.isValid()) {
RootItem* item = m_sourceModel->itemForIndex(m_proxyModel->mapToSource(idx));
- if (item->kind() == RootItemKind::Feed || item->kind() == RootItemKind::Bin) {
+ if (item->kind() == RootItem::Kind::Feed || item->kind() == RootItem::Kind::Bin) {
const QList messages = m_sourceModel->messagesForItem(item);
if (!messages.isEmpty()) {
diff --git a/src/librssguard/gui/messagesview.cpp b/src/librssguard/gui/messagesview.cpp
index 99febc4e0..62a8d68bf 100644
--- a/src/librssguard/gui/messagesview.cpp
+++ b/src/librssguard/gui/messagesview.cpp
@@ -2,6 +2,7 @@
#include "gui/messagesview.h"
+#include "3rd-party/boolinq/boolinq.h"
#include "core/messagesmodel.h"
#include "core/messagesproxymodel.h"
#include "gui/dialogs/formmain.h"
@@ -13,6 +14,7 @@
#include "miscellaneous/settings.h"
#include "network-web/networkfactory.h"
#include "network-web/webfactory.h"
+#include "services/abstract/serviceroot.h"
#include
#include
@@ -213,11 +215,32 @@ void MessagesView::initializeContextMenu() {
m_contextMenu->addMenu(menu);
m_contextMenu->addActions(
- QList() << qApp->mainForm()->m_ui->m_actionSendMessageViaEmail << qApp->mainForm()->m_ui->m_actionOpenSelectedSourceArticlesExternally << qApp->mainForm()->m_ui->m_actionOpenSelectedMessagesInternally << qApp->mainForm()->m_ui->m_actionMarkSelectedMessagesAsRead << qApp->mainForm()->m_ui->m_actionMarkSelectedMessagesAsUnread << qApp->mainForm()->m_ui->m_actionSwitchImportanceOfSelectedMessages <<
- qApp->mainForm()->m_ui->m_actionDeleteSelectedMessages);
+ QList()
+ << qApp->mainForm()->m_ui->m_actionSendMessageViaEmail
+ << qApp->mainForm()->m_ui->m_actionOpenSelectedSourceArticlesExternally
+ << qApp->mainForm()->m_ui->m_actionOpenSelectedMessagesInternally
+ << qApp->mainForm()->m_ui->m_actionMarkSelectedMessagesAsRead
+ << qApp->mainForm()->m_ui->m_actionMarkSelectedMessagesAsUnread
+ << qApp->mainForm()->m_ui->m_actionSwitchImportanceOfSelectedMessages
+ << qApp->mainForm()->m_ui->m_actionDeleteSelectedMessages);
- if (m_sourceModel->loadedItem() != nullptr && m_sourceModel->loadedItem()->kind() == RootItemKind::Bin) {
- m_contextMenu->addAction(qApp->mainForm()->m_ui->m_actionRestoreSelectedMessages);
+ if (m_sourceModel->loadedItem() != nullptr) {
+ if (m_sourceModel->loadedItem()->kind() == RootItem::Kind::Bin) {
+ m_contextMenu->addAction(qApp->mainForm()->m_ui->m_actionRestoreSelectedMessages);
+ }
+
+ QModelIndexList selected_indexes = selectionModel()->selectedRows();
+ const QModelIndexList mapped_indexes = m_proxyModel->mapListToSource(selected_indexes);
+ auto rows = boolinq::from(mapped_indexes).select([](const QModelIndex& idx) {
+ return idx.row();
+ }).toStdList();
+ auto messages = m_sourceModel->messagesAt(QList::fromStdList(rows));
+ auto extra_context_menu = m_sourceModel->loadedItem()->getParentServiceRoot()->contextMenuMessagesList(messages);
+
+ if (!extra_context_menu.isEmpty()) {
+ m_contextMenu->addSeparator();
+ m_contextMenu->addActions(extra_context_menu);
+ }
}
}
@@ -285,7 +308,7 @@ void MessagesView::selectionChanged(const QItemSelection& selected, const QItemS
// Set this message as read only if current item
// wasn't changed by "mark selected messages unread" action.
- m_sourceModel->setMessageRead(mapped_current_index.row(), RootItem::Read);
+ m_sourceModel->setMessageRead(mapped_current_index.row(), RootItem::ReadStatus::Read);
message.m_isRead = true;
emit currentMessageChanged(message, m_sourceModel->loadedItem());
@@ -366,11 +389,11 @@ void MessagesView::sendSelectedMessageViaEmail() {
}
void MessagesView::markSelectedMessagesRead() {
- setSelectedMessagesReadStatus(RootItem::Read);
+ setSelectedMessagesReadStatus(RootItem::ReadStatus::Read);
}
void MessagesView::markSelectedMessagesUnread() {
- setSelectedMessagesReadStatus(RootItem::Unread);
+ setSelectedMessagesReadStatus(RootItem::ReadStatus::Unread);
}
void MessagesView::setSelectedMessagesReadStatus(RootItem::ReadStatus read) {
diff --git a/src/librssguard/gui/messagesview.h b/src/librssguard/gui/messagesview.h
index 5f948ee22..487019da1 100644
--- a/src/librssguard/gui/messagesview.h
+++ b/src/librssguard/gui/messagesview.h
@@ -19,14 +19,8 @@ class MessagesView : public QTreeView {
explicit MessagesView(QWidget* parent = nullptr);
virtual ~MessagesView();
- // Model accessors.
- inline MessagesProxyModel* model() const {
- return m_proxyModel;
- }
-
- inline MessagesModel* sourceModel() const {
- return m_sourceModel;
- }
+ MessagesProxyModel* model() const;
+ MessagesModel* sourceModel() const;
void reloadFontSettings();
@@ -110,4 +104,12 @@ class MessagesView : public QTreeView {
bool m_columnsAdjusted;
};
+inline MessagesProxyModel* MessagesView::model() const {
+ return m_proxyModel;
+}
+
+inline MessagesModel* MessagesView::sourceModel() const {
+ return m_sourceModel;
+}
+
#endif // MESSAGESVIEW_H
diff --git a/src/librssguard/gui/tabbar.cpp b/src/librssguard/gui/tabbar.cpp
index 9858e1531..fde45d09b 100644
--- a/src/librssguard/gui/tabbar.cpp
+++ b/src/librssguard/gui/tabbar.cpp
@@ -5,6 +5,7 @@
#include "definitions/definitions.h"
#include "gui/plaintoolbutton.h"
#include "miscellaneous/settings.h"
+#include "miscellaneous/templates.h"
#include
#include
@@ -25,8 +26,8 @@ void TabBar::setTabType(int index, const TabBar::TabType& type) {
this));
switch (type) {
- case TabBar::DownloadManager:
- case TabBar::Closable: {
+ case TabBar::TabType::DownloadManager:
+ case TabBar::TabType::Closable: {
auto* close_button = new PlainToolButton(this);
close_button->setIcon(qApp->icons()->fromTheme(QSL("application-exit")));
@@ -45,7 +46,7 @@ void TabBar::setTabType(int index, const TabBar::TabType& type) {
break;
}
- setTabData(index, QVariant(type));
+ setTabData(index, QVariant(int(type)));
}
void TabBar::closeTabViaButton() {
@@ -99,7 +100,7 @@ void TabBar::mousePressEvent(QMouseEvent* event) {
// destination does not know the original event.
if ((event->button() & Qt::MiddleButton) == Qt::MiddleButton &&
qApp->settings()->value(GROUP(GUI), SETTING(GUI::TabCloseMiddleClick)).toBool()) {
- if (tabType(tab_index) == TabBar::Closable || tabType(tab_index) == TabBar::DownloadManager) {
+ if (tabType(tab_index) == TabBar::TabType::Closable || tabType(tab_index) == TabBar::TabType::DownloadManager) {
// This tab is closable, so we can close it.
emit tabCloseRequested(tab_index);
}
@@ -118,7 +119,7 @@ void TabBar::mouseDoubleClickEvent(QMouseEvent* event) {
// destination does not know the original event.
if ((event->button() & Qt::LeftButton) == Qt::LeftButton &&
qApp->settings()->value(GROUP(GUI), SETTING(GUI::TabCloseDoubleClick)).toBool()) {
- if ((tabType(tab_index) & (TabBar::Closable | TabBar::DownloadManager)) > 0) {
+ if (int(tabType(tab_index) & (TabBar::TabType::Closable | TabBar::TabType::DownloadManager)) > 0) {
// This tab is closable, so we can close it.
emit tabCloseRequested(tab_index);
}
@@ -128,3 +129,19 @@ void TabBar::mouseDoubleClickEvent(QMouseEvent* event) {
emit emptySpaceDoubleClicked();
}
}
+
+TabBar::TabType& operator&=(TabBar::TabType& a, TabBar::TabType b) {
+ return (TabBar::TabType&)((int&)a &= (int)b);
+}
+
+TabBar::TabType& operator|=(TabBar::TabType& a, TabBar::TabType b) {
+ return (TabBar::TabType&)((int&)a |= (int)b);
+}
+
+TabBar::TabType operator&(TabBar::TabType a, TabBar::TabType b) {
+ return (TabBar::TabType)((int)a & (int)b);
+}
+
+TabBar::TabType operator|(TabBar::TabType a, TabBar::TabType b) {
+ return (TabBar::TabType)((int)a | (int)b);
+}
diff --git a/src/librssguard/gui/tabbar.h b/src/librssguard/gui/tabbar.h
index c54a98432..5aa9b4c33 100644
--- a/src/librssguard/gui/tabbar.h
+++ b/src/librssguard/gui/tabbar.h
@@ -12,7 +12,7 @@ class TabBar : public QTabBar {
Q_OBJECT
public:
- enum TabType {
+ enum class TabType {
FeedReader = 1,
DownloadManager = 2,
NonClosable = 4,
@@ -20,15 +20,12 @@ class TabBar : public QTabBar {
};
// Constructors.
- explicit TabBar(QWidget* parent = 0);
+ explicit TabBar(QWidget* parent = nullptr);
virtual ~TabBar();
// Getter/setter for tab type.
void setTabType(int index, const TabBar::TabType& type);
-
- inline TabBar::TabType tabType(int index) const {
- return static_cast(tabData(index).toInt());
- }
+ TabBar::TabType tabType(int index) const;
private slots:
@@ -48,4 +45,13 @@ class TabBar : public QTabBar {
void emptySpaceDoubleClicked();
};
+inline TabBar::TabType TabBar::tabType(int index) const {
+ return static_cast(tabData(index).toInt());
+}
+
+TabBar::TabType operator| (TabBar::TabType a, TabBar::TabType b);
+TabBar::TabType operator& (TabBar::TabType a, TabBar::TabType b);
+TabBar::TabType& operator|= (TabBar::TabType& a, TabBar::TabType b);
+TabBar::TabType& operator&= (TabBar::TabType& a, TabBar::TabType b);
+
#endif // TABBAR_H
diff --git a/src/librssguard/gui/tabwidget.cpp b/src/librssguard/gui/tabwidget.cpp
index 6475ab14a..71eeab61b 100644
--- a/src/librssguard/gui/tabwidget.cpp
+++ b/src/librssguard/gui/tabwidget.cpp
@@ -77,7 +77,10 @@ void TabWidget::showDownloadManager() {
// Download manager is not opened. Create tab with it.
qApp->downloadManager()->setParent(this);
- addTab(qApp->downloadManager(), qApp->icons()->fromTheme(QSL("emblem-downloads")), tr("Downloads"), TabBar::DownloadManager);
+ addTab(qApp->downloadManager(),
+ qApp->icons()->fromTheme(QSL("emblem-downloads")),
+ tr("Downloads"),
+ TabBar::TabType::DownloadManager);
setCurrentIndex(count() - 1);
}
@@ -130,7 +133,7 @@ void TabWidget::createConnections() {
void TabWidget::initializeTabs() {
// Create widget for "Feeds" page and add it.
m_feedMessageViewer = new FeedMessageViewer(this);
- const int index_of_browser = addTab(m_feedMessageViewer, QIcon(), tr("Feeds"), TabBar::FeedReader);
+ const int index_of_browser = addTab(m_feedMessageViewer, QIcon(), tr("Feeds"), TabBar::TabType::FeedReader);
setTabToolTip(index_of_browser, tr("Browse your feeds and messages"));
}
@@ -140,18 +143,18 @@ void TabWidget::setupIcons() {
// accordingly.
for (int index = 0; index < count(); index++) {
// Index 0 usually contains widget which displays feeds & messages.
- if (tabBar()->tabType(index) == TabBar::FeedReader) {
+ if (tabBar()->tabType(index) == TabBar::TabType::FeedReader) {
setTabIcon(index, qApp->icons()->fromTheme(QSL("application-rss+xml")));
}
}
}
bool TabWidget::closeTab(int index) {
- if (tabBar()->tabType(index) == TabBar::Closable) {
+ if (tabBar()->tabType(index) == TabBar::TabType::Closable) {
removeTab(index, true);
return true;
}
- else if (tabBar()->tabType(index) == TabBar::DownloadManager) {
+ else if (tabBar()->tabType(index) == TabBar::TabType::DownloadManager) {
removeTab(index, false);
return true;
}
@@ -198,7 +201,10 @@ int TabWidget::addNewspaperView(RootItem* root, const QList& messages)
m_feedMessageViewer->messagesView()->sourceModel(), &MessagesModel::setMessageImportantById);
#endif
- int index = addTab(prev, qApp->icons()->fromTheme(QSL("format-justify-fill")), tr("Newspaper view"), TabBar::Closable);
+ int index = addTab(prev,
+ qApp->icons()->fromTheme(QSL("format-justify-fill")),
+ tr("Newspaper view"),
+ TabBar::TabType::Closable);
// NOTE: Do not bring "newspaper" tabs to front anymore.
//setCurrentIndex(index);
@@ -236,13 +242,13 @@ int TabWidget::addBrowser(bool move_after_current, bool make_active, const QUrl&
if (move_after_current) {
// Insert web browser after current tab.
final_index = insertTab(currentIndex() + 1, browser, qApp->icons()->fromTheme(QSL("text-html")),
- browser_tab_name, TabBar::Closable);
+ browser_tab_name, TabBar::TabType::Closable);
}
else {
// Add new browser as the last tab.
final_index = addTab(browser, qApp->icons()->fromTheme(QSL("text-html")),
browser_tab_name,
- TabBar::Closable);
+ TabBar::TabType::Closable);
}
// Make connections.
diff --git a/src/librssguard/gui/tabwidget.h b/src/librssguard/gui/tabwidget.h
index 7a3c91c4a..ff9b8fa88 100644
--- a/src/librssguard/gui/tabwidget.h
+++ b/src/librssguard/gui/tabwidget.h
@@ -22,33 +22,27 @@ class TabWidget : public QTabWidget {
public:
// Constructors and destructors.
- explicit TabWidget(QWidget* parent = 0);
+ explicit TabWidget(QWidget* parent = nullptr);
virtual ~TabWidget();
// Manimulators for tabs.
int addTab(TabContent* widget, const QString&,
- const TabBar::TabType& type = TabBar::NonClosable);
+ const TabBar::TabType& type = TabBar::TabType::NonClosable);
int addTab(TabContent* widget, const QIcon& icon,
- const QString& label, const TabBar::TabType& type = TabBar::NonClosable);
+ const QString& label, const TabBar::TabType& type = TabBar::TabType::NonClosable);
int insertTab(int index, QWidget* widget, const QString& label,
- const TabBar::TabType& type = TabBar::Closable);
+ const TabBar::TabType& type = TabBar::TabType::Closable);
int insertTab(int index, QWidget* widget, const QIcon& icon,
- const QString& label, const TabBar::TabType& type = TabBar::NonClosable);
+ const QString& label, const TabBar::TabType& type = TabBar::TabType::NonClosable);
void removeTab(int index, bool clear_from_memory);
// Returns tab bar.
- inline TabBar* tabBar() const {
- return static_cast