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..0d52c1167
--- /dev/null
+++ b/src/librssguard/3rd-party/boolinq/boolinq.h
@@ -0,0 +1,882 @@
+#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 index) { 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 index) { 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);
+
+ _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;
+ }));
+ }
+ throw LinqEndException();
+ }
+ );
+ }
+
+ 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/messagesmodel.cpp b/src/librssguard/core/messagesmodel.cpp
index 7c75b9948..1607146c6 100644
--- a/src/librssguard/core/messagesmodel.cpp
+++ b/src/librssguard/core/messagesmodel.cpp
@@ -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);
}
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/gui/dialogs/formabout.cpp b/src/librssguard/gui/dialogs/formabout.cpp
index cb7fb9695..632f27573 100644
--- a/src/librssguard/gui/dialogs/formabout.cpp
+++ b/src/librssguard/gui/dialogs/formabout.cpp
@@ -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..1f30788b5 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);
diff --git a/src/librssguard/gui/feedsview.cpp b/src/librssguard/gui/feedsview.cpp
index 9fcbc0f82..343ee9511 100755
--- a/src/librssguard/gui/feedsview.cpp
+++ b/src/librssguard/gui/feedsview.cpp
@@ -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();
diff --git a/src/librssguard/gui/messagesview.cpp b/src/librssguard/gui/messagesview.cpp
index 99febc4e0..ff789aaec 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() == RootItemKind::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);
+ }
}
}
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/services/abstract/recyclebin.cpp b/src/librssguard/services/abstract/recyclebin.cpp
index 525ef2529..5268d2f7e 100644
--- a/src/librssguard/services/abstract/recyclebin.cpp
+++ b/src/librssguard/services/abstract/recyclebin.cpp
@@ -46,7 +46,7 @@ void RecycleBin::updateCounts(bool update_total_count) {
}
}
-QList RecycleBin::contextMenu() {
+QList RecycleBin::contextMenuFeedsList() {
if (m_contextMenu.isEmpty()) {
QAction* restore_action = new QAction(qApp->icons()->fromTheme(QSL("view-refresh")),
tr("Restore recycle bin"),
diff --git a/src/librssguard/services/abstract/recyclebin.h b/src/librssguard/services/abstract/recyclebin.h
index 78c1014fb..b1b42b8f8 100644
--- a/src/librssguard/services/abstract/recyclebin.h
+++ b/src/librssguard/services/abstract/recyclebin.h
@@ -14,7 +14,7 @@ class RecycleBin : public RootItem {
QString additionalTooltip() const;
- QList contextMenu();
+ QList contextMenuFeedsList();
QList undeletedMessages() const;
bool markAsReadUnread(ReadStatus status);
diff --git a/src/librssguard/services/abstract/rootitem.cpp b/src/librssguard/services/abstract/rootitem.cpp
index c2c29450f..b6ad95294 100644
--- a/src/librssguard/services/abstract/rootitem.cpp
+++ b/src/librssguard/services/abstract/rootitem.cpp
@@ -44,7 +44,7 @@ QString RootItem::additionalTooltip() const {
return QString();
}
-QList RootItem::contextMenu() {
+QList RootItem::contextMenuFeedsList() {
return QList();
}
diff --git a/src/librssguard/services/abstract/rootitem.h b/src/librssguard/services/abstract/rootitem.h
index b8e7bc64a..1020c097c 100644
--- a/src/librssguard/services/abstract/rootitem.h
+++ b/src/librssguard/services/abstract/rootitem.h
@@ -63,7 +63,7 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
// Returns list of specific actions which can be done with the item.
// Do not include general actions here like actions: Mark as read, Update, ...
// NOTE: Ownership of returned actions is not switched to caller, free them when needed.
- virtual QList contextMenu();
+ virtual QList contextMenuFeedsList();
// Can properties of this item be edited?
virtual bool canBeEdited() const;
diff --git a/src/librssguard/services/abstract/serviceroot.cpp b/src/librssguard/services/abstract/serviceroot.cpp
index b2938252c..61c52919e 100644
--- a/src/librssguard/services/abstract/serviceroot.cpp
+++ b/src/librssguard/services/abstract/serviceroot.cpp
@@ -68,11 +68,11 @@ bool ServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const {
return false;
}
-QList ServiceRoot::contextMenu() {
+QList ServiceRoot::contextMenuFeedsList() {
return serviceMenu();
}
-QList ServiceRoot::contextMenuForMessages(const QList& messages) {
+QList ServiceRoot::contextMenuMessagesList(const QList& messages) {
Q_UNUSED(messages)
return {};
}
diff --git a/src/librssguard/services/abstract/serviceroot.h b/src/librssguard/services/abstract/serviceroot.h
index 3cf8d5f83..37328ce4c 100644
--- a/src/librssguard/services/abstract/serviceroot.h
+++ b/src/librssguard/services/abstract/serviceroot.h
@@ -53,9 +53,11 @@ class ServiceRoot : public RootItem {
// NOTE: Caller does NOT take ownership of created menu!
virtual QList addItemMenu();
- // Returns actions to display as context menu.
- virtual QList contextMenu();
- virtual QList contextMenuForMessages(const QList& messages);
+ // NOTE: Caller does NOT take ownership of created menu!
+ virtual QList contextMenuFeedsList();
+
+ // NOTE: Caller does NOT take ownership of created menu!
+ virtual QList contextMenuMessagesList(const QList