debug next status

This commit is contained in:
Martin Rotter 2020-07-29 07:51:48 +02:00
parent 0b2e1df83d
commit a3d142034f
27 changed files with 1130 additions and 46 deletions

View file

@ -37,6 +37,7 @@
<file>./graphics/Faenza/actions/64/mail-mark-read.png</file>
<file>./graphics/Faenza/actions/64/mail-mark-unread.png</file>
<file>./graphics/Faenza/actions/64/mail-message-new.png</file>
<file>./graphics/Faenza/actions/64/mail-reply-sender.png</file>
<file>./graphics/Faenza/actions/64/mail-send.png</file>
<file>./graphics/Faenza/actions/64/mail-sent.png</file>
<file>./graphics/Faenza/actions/64/media-playback-start.png</file>

View file

@ -2,6 +2,7 @@
<qresource prefix="/">
<file>text/CHANGELOG</file>
<file>text/COPYING_BSD</file>
<file>text/COPYING_MIT</file>
<file>text/COPYING_GNU_GPL</file>
<file>text/COPYING_GNU_GPL_HTML</file>

19
resources/text/COPYING_MIT Executable file
View file

@ -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.

882
src/librssguard/3rd-party/boolinq/boolinq.h vendored Executable file
View file

@ -0,0 +1,882 @@
#pragma once
#include <limits.h>
#include <functional>
#include <tuple>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
#include <deque>
#include <list>
#include <set>
#include <unordered_set>
//
namespace boolinq {
struct LinqEndException {};
enum BytesDirection {
BytesFirstToLast,
BytesLastToFirst,
};
enum BitsDirection {
BitsHighToLow,
BitsLowToHigh,
};
template<typename S, typename T>
class Linq {
std::function<T(S &)> nextFunc;
S storage;
public:
typedef T value_type;
Linq() : nextFunc(), storage()
{
}
Linq(S storage, std::function<T(S &)> nextFunc) : nextFunc(nextFunc), storage(storage)
{
}
T next()
{
return nextFunc(storage);
}
void for_each_i(std::function<void(T, int)> apply) const
{
Linq<S, T> linq = *this;
try {
for (int i = 0; ; i++) {
apply(linq.next(), i);
}
}
catch (LinqEndException &) {}
}
void for_each(std::function<void(T)> apply) const
{
return for_each_i([apply](T value, int index) { return apply(value); });
}
Linq<std::tuple<Linq<S, T>, int>, T> where_i(std::function<bool(T, int)> filter) const
{
return Linq<std::tuple<Linq<S, T>, int>, T>(
std::make_tuple(*this, 0),
[filter](std::tuple<Linq<S, T>, int> &tuple) {
Linq<S, T> &linq = std::get<0>(tuple);
int &index = std::get<1>(tuple);
while (true) {
T ret = linq.next();
if (filter(ret, index++)) {
return ret;
}
}
}
);
}
Linq<std::tuple<Linq<S, T>, int>, T> where(std::function<bool(T)> filter) const
{
return where_i([filter](T value, int index) { return filter(value); });
}
Linq<std::tuple<Linq<S, T>, int>, T> take(int count) const
{
return where_i([count](T /*value*/, int i) {
if (i == count) {
throw LinqEndException();
}
return true;
});
}
Linq<std::tuple<Linq<S, T>, int>, T> takeWhile_i(std::function<bool(T, int)> predicate) const
{
return where_i([predicate](T value, int i) {
if (!predicate(value, i)) {
throw LinqEndException();
}
return true;
});
}
Linq<std::tuple<Linq<S, T>, int>, T> takeWhile(std::function<bool(T)> predicate) const
{
return takeWhile_i([predicate](T value, int /*i*/) { return predicate(value); });
}
Linq<std::tuple<Linq<S, T>, int>, T> skip(int count) const
{
return where_i([count](T value, int i) { return i >= count; });
}
Linq<std::tuple<Linq<S, T>, int, bool>, T> skipWhile_i(std::function<bool(T, int)> predicate) const
{
return Linq<std::tuple<Linq<S, T>, int, bool>, T>(
std::make_tuple(*this, 0, false),
[predicate](std::tuple<Linq<S, T>, int, bool> &tuple) {
Linq<S, T> &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<std::tuple<Linq<S, T>, int, bool>, T> skipWhile(std::function<bool(T)> predicate) const
{
return skipWhile_i([predicate](T value, int /*i*/) { return predicate(value); });
}
template<typename ... Types>
Linq<std::tuple<Linq<S, T>, std::vector<T>, int>, T> append(Types ... newValues) const
{
return Linq<std::tuple<Linq<S, T>, std::vector<T>, int>, T>(
std::make_tuple(*this, std::vector<T>{ newValues... }, -1),
[](std::tuple<Linq<S, T>, std::vector<T>, int> &tuple) {
Linq<S, T> &linq = std::get<0>(tuple);
std::vector<T> &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<typename ... Types>
Linq<std::tuple<Linq<S, T>, std::vector<T>, int>, T> prepend(Types ... newValues) const
{
return Linq<std::tuple<Linq<S, T>, std::vector<T>, int>, T>(
std::make_tuple(*this, std::vector<T>{ newValues... }, 0),
[](std::tuple<Linq<S, T>, std::vector<T>, int> &tuple) {
Linq<S, T> &linq = std::get<0>(tuple);
std::vector<T> &values = std::get<1>(tuple);
int &index = std::get<2>(tuple);
if (index < values.size()) {
return values[index++];
}
return linq.next();
}
);
}
template<typename F, typename _TRet = typename std::result_of<F(T, int)>::type>
Linq<std::tuple<Linq<S, T>, int>, _TRet> select_i(F apply) const
{
return Linq<std::tuple<Linq<S, T>, int>, _TRet>(
std::make_tuple(*this, 0),
[apply](std::tuple<Linq<S, T>, int> &tuple) {
Linq<S, T> &linq = std::get<0>(tuple);
int &index = std::get<1>(tuple);
return apply(linq.next(), index++);
}
);
}
template<typename F, typename _TRet = typename std::result_of<F(T)>::type>
Linq<std::tuple<Linq<S, T>, int>, _TRet> select(F apply) const
{
return select_i([apply](T value, int /*index*/) { return apply(value); });
}
template<typename TRet>
Linq<std::tuple<Linq<S, T>, int>, TRet> cast() const
{
return select_i([](T value, int /*i*/) { return TRet(value); });
}
template<typename S2, typename T2>
Linq<std::tuple<Linq<S, T>, Linq<S2, T2>, bool>, T> concat(const Linq<S2, T2> & rhs) const
{
return Linq<std::tuple<Linq<S, T>, Linq<S2, T2>, bool>, T>(
std::make_tuple(*this, rhs, false),
[](std::tuple<Linq<S, T>, Linq<S2, T2>, bool> &tuple){
Linq<S, T> &first = std::get<0>(tuple);
Linq<S2, T2> &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<F(T, int)>::type,
typename _TRetVal = typename _TRet::value_type
>
Linq<std::tuple<Linq<S, T>, _TRet, int, bool>, _TRetVal> selectMany_i(F apply) const
{
return Linq<std::tuple<Linq<S, T>, _TRet, int, bool>, _TRetVal>(
std::make_tuple(*this, _TRet(), 0, true),
[apply](std::tuple<Linq<S, T>, _TRet, int, bool> &tuple) {
Linq<S, T> &linq = std::get<0>(tuple);
_TRet &current = 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<F(T)>::type,
typename _TRetVal = typename _TRet::value_type
>
Linq<std::tuple<Linq<S, T>, _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<F(T)>::type,
typename _TValue = Linq<std::tuple<Linq<S, T>, int>, T> // where(predicate)
>
Linq<std::tuple<Linq<S, T>, Linq<S, T>, std::unordered_set<_TKey> >, std::pair<_TKey, _TValue> > groupBy(F apply) const
{
return Linq<std::tuple<Linq<S, T>, Linq<S, T>, std::unordered_set<_TKey> >, std::pair<_TKey, _TValue> >(
std::make_tuple(*this, *this, std::unordered_set<_TKey>()),
[apply](std::tuple<Linq<S, T>, Linq<S, T>, std::unordered_set<_TKey> > &tuple){
Linq<S, T> &linq = std::get<0>(tuple);
Linq<S, T> &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<typename F, typename _TRet = typename std::result_of<F(T)>::type>
Linq<std::tuple<Linq<S, T>, std::unordered_set<_TRet> >, T> distinct(F transform) const
{
return Linq<std::tuple<Linq<S, T>, std::unordered_set<_TRet> >, T>(
std::make_tuple(*this, std::unordered_set<_TRet>()),
[transform](std::tuple<Linq<S, T>, std::unordered_set<_TRet> > &tuple) {
Linq<S, T> &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::tuple<Linq<S, T>, std::unordered_set<T> >, T> distinct() const
{
return distinct([](T value) { return value; });
}
template<typename F, typename _TIter = typename std::vector<T>::const_iterator>
Linq<std::tuple<std::vector<T>, _TIter, bool>, T> orderBy(F transform) const
{
std::vector<T> items = toStdVector();
std::sort(items.begin(), items.end(), [transform](const T &a, const T &b) {
return transform(a) < transform(b);
});
return Linq<std::tuple<std::vector<T>, _TIter, bool>, T>(
std::make_tuple(items, _TIter(), false),
[](std::tuple<std::vector<T>, _TIter, bool> &tuple) {
std::vector<T> &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<std::tuple<std::vector<T>, typename std::vector<T>::const_iterator, bool>, T> orderBy() const
{
return orderBy([](T value) { return value; });
}
template<typename _TIter = typename std::list<T>::const_reverse_iterator>
Linq<std::tuple<std::list<T>, _TIter, bool>, T> reverse() const
{
return Linq<std::tuple<std::list<T>, _TIter, bool>, T>(
std::make_tuple(toStdList(), _TIter(), false),
[](std::tuple<std::list<T>, _TIter, bool> &tuple) {
std::list<T> &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<typename TRet>
TRet aggregate(TRet start, std::function<TRet(TRet, T)> accumulate) const
{
Linq<S, T> linq = *this;
try {
while (true) {
start = accumulate(start, linq.next());
}
}
catch (LinqEndException &) {}
return start;
}
template<typename F, typename _TRet = typename std::result_of<F(T)>::type>
_TRet sum(F transform) const
{
return aggregate<_TRet>(_TRet(), [transform](_TRet accumulator, T value) {
return accumulator + transform(value);
});
}
template<typename TRet = T>
TRet sum() const
{
return sum([](T value) { return TRet(value); });
}
template<typename F, typename _TRet = typename std::result_of<F(T)>::type>
_TRet avg(F transform) const
{
int count = 0;
_TRet res = sum([transform, &count](T value) {
count++;
return transform(value);
});
return res / count;
}
template<typename TRet = T>
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<bool(T)> 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<bool(T)> predicate) const
{
Linq<S, T> 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<bool>(value); });
}
bool all(std::function<bool(T)> predicate) const
{
return !any([predicate](T value) { return !predicate(value); });
}
bool all() const
{
return all([](T value) { return static_cast<bool>(value); });
}
bool contains(const T &item) const
{
return any([&item](T value) { return value == item; });
}
// Election aggregators
T elect(std::function<T(T, T)> 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<typename F>
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<typename F>
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<bool(T)> predicate) const
{
return where(predicate).next();
}
T first() const
{
return Linq<S, T>(*this).next();
}
T firstOrDefault(std::function<bool(T)> 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<S, T>(*this).next();
}
catch (LinqEndException &) {}
return defaultValue;
}
T last(std::function<bool(T)> 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<bool(T)> 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<T> toStdVector() const
{
std::vector<T> items;
for_each([&items](T value) {
items.push_back(value);
});
return items;
}
std::list<T> toStdList() const
{
std::list<T> items;
for_each([&items](T value) {
items.push_back(value);
});
return items;
}
std::deque<T> toStdDeque() const
{
std::deque<T> items;
for_each([&items](T value) {
items.push_back(value);
});
return items;
}
std::set<T> toStdSet() const
{
std::set<T> items;
for_each([&items](T value) {
items.insert(value);
});
return items;
}
std::unordered_set<T> toStdUnorderedSet() const
{
std::unordered_set<T> items;
for_each([&items](T value) {
items.insert(value);
});
return items;
}
// Bits and bytes
Linq<std::tuple<Linq<S, T>, BytesDirection, T, int>, int> bytes(BytesDirection direction = BytesFirstToLast) const
{
return Linq<std::tuple<Linq<S, T>, BytesDirection, T, int>, int>(
std::make_tuple(*this, direction, T(), sizeof(T)),
[](std::tuple<Linq<S, T>, BytesDirection, T, int> &tuple) {
Linq<S, T> &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<unsigned char *>(&value);
int byteIndex = index;
if (bytesDirection == BytesLastToFirst) {
byteIndex = sizeof(T) - 1 - byteIndex;
}
index++;
return ptr[byteIndex];
}
);
}
template<typename TRet>
Linq<std::tuple<Linq<S, T>, BytesDirection, int>, TRet> unbytes(BytesDirection direction = BytesFirstToLast) const
{
return Linq<std::tuple<Linq<S, T>, BytesDirection, int>, TRet>(
std::make_tuple(*this, direction, 0),
[](std::tuple<Linq<S, T>, BytesDirection, int> &tuple) {
Linq<S, T> &linq = std::get<0>(tuple);
BytesDirection &bytesDirection = std::get<1>(tuple);
int &index = std::get<2>(tuple);
TRet value;
unsigned char *ptr = reinterpret_cast<unsigned char *>(&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<std::tuple<Linq<S, T>, BytesDirection, BitsDirection, T, int>, int> bits(BitsDirection bitsDir = BitsHighToLow, BytesDirection bytesDir = BytesFirstToLast) const
{
return Linq<std::tuple<Linq<S, T>, BytesDirection, BitsDirection, T, int>, int>(
std::make_tuple(*this, bytesDir, bitsDir, T(), sizeof(T) * CHAR_BIT),
[](std::tuple<Linq<S, T>, BytesDirection, BitsDirection, T, int> &tuple) {
Linq<S, T> &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<unsigned char *>(&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<typename TRet = unsigned char>
Linq<std::tuple<Linq<S, T>, BytesDirection, BitsDirection, int>, TRet> unbits(BitsDirection bitsDir = BitsHighToLow, BytesDirection bytesDir = BytesFirstToLast) const
{
return Linq<std::tuple<Linq<S, T>, BytesDirection, BitsDirection, int>, TRet>(
std::make_tuple(*this, bytesDir, bitsDir, 0),
[](std::tuple<Linq<S, T>, BytesDirection, BitsDirection, int> &tuple) {
Linq<S, T> &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<unsigned char *>(&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<typename S, typename T>
std::ostream &operator<<(std::ostream &stream, Linq<S, T> linq)
{
try {
while (true) {
stream << linq.next() << ' ';
}
}
catch (LinqEndException &) {}
return stream;
}
////////////////////////////////////////////////////////////////
// Linq Creators
////////////////////////////////////////////////////////////////
template<typename T>
Linq<std::pair<T, T>, typename std::iterator_traits<T>::value_type> from(const T & begin, const T & end)
{
return Linq<std::pair<T, T>, typename std::iterator_traits<T>::value_type>(
std::make_pair(begin, end),
[](std::pair<T, T> &pair) {
if (pair.first == pair.second) {
throw LinqEndException();
}
return *(pair.first++);
}
);
}
template<typename T>
Linq<std::pair<T, T>, typename std::iterator_traits<T>::value_type> from(const T & it, int n)
{
return from(it, it + n);
}
template<typename T, int N>
Linq<std::pair<const T *, const T *>, T> from(T (&array)[N])
{
return from((const T *)(&array), (const T *)(&array) + N);
}
template<template<class> class TV, typename TT>
auto from(const TV<TT> & container)
-> decltype(from(container.cbegin(), container.cend()))
{
return from(container.cbegin(), container.cend());
}
// std::list, std::vector, std::dequeue
template<template<class, class> class TV, typename TT, typename TU>
auto from(const TV<TT, TU> & container)
-> decltype(from(container.cbegin(), container.cend()))
{
return from(container.cbegin(), container.cend());
}
// std::set
template<template<class, class, class> class TV, typename TT, typename TS, typename TU>
auto from(const TV<TT, TS, TU> & container)
-> decltype(from(container.cbegin(), container.cend()))
{
return from(container.cbegin(), container.cend());
}
// std::map
template<template<class, class, class, class> class TV, typename TK, typename TT, typename TS, typename TU>
auto from(const TV<TK, TT, TS, TU> & container)
-> decltype(from(container.cbegin(), container.cend()))
{
return from(container.cbegin(), container.cend());
}
// std::array
template<template<class, size_t> class TV, typename TT, size_t TL>
auto from(const TV<TT, TL> & container)
-> decltype(from(container.cbegin(), container.cend()))
{
return from(container.cbegin(), container.cend());
}
template<typename T>
Linq<std::pair<T, int>, T> repeat(const T & value, int count) {
return Linq<std::pair<T, int>, T>(
std::make_pair(value, count),
[](std::pair<T, int> &pair) {
if (pair.second > 0) {
pair.second--;
return pair.first;
}
throw LinqEndException();
}
);
}
template<typename T>
Linq<std::tuple<T, T, T>, T> range(const T & start, const T & end, const T & step) {
return Linq<std::tuple<T, T, T>, T>(
std::make_tuple(start, end, step),
[](std::tuple<T, T, T> &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();
}
);
}
}

View file

@ -214,6 +214,16 @@ Qt::ItemFlags MessagesModel::flags(const QModelIndex& index) const {
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemNeverHasChildren;
}
QList<Message> MessagesModel::messagesAt(QList<int> row_indices) const {
QList<Message> 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);
}

View file

@ -44,6 +44,8 @@ class MessagesModel : public QSqlQueryModel, public MessagesModelSqlLayer {
Qt::ItemFlags flags(const QModelIndex& index) const;
// Returns message at given index.
QList<Message> messagesAt(QList<int> row_indices) const;
Message messageAt(int row_index) const;
int messageId(int row_index) const;
RootItem::Importance messageImportance(int row_index) const;

View file

@ -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("<b>%8</b><br>" "<b>Version:</b> %1 (built on %2/%3)<br>" "<b>Revision:</b> %4<br>" "<b>Build date:</b> %5<br>"
"<b>Qt:</b> %6 (compiled against %7)<br>").arg(

View file

@ -160,7 +160,7 @@ p, li { white-space: pre-wrap; }
<x>0</x>
<y>0</y>
<width>685</width>
<height>184</height>
<height>157</height>
</rect>
</property>
<property name="autoFillBackground">
@ -235,8 +235,8 @@ p, li { white-space: pre-wrap; }
<rect>
<x>0</x>
<y>0</y>
<width>98</width>
<height>69</height>
<width>685</width>
<height>157</height>
</rect>
</property>
<attribute name="label">
@ -288,6 +288,68 @@ p, li { white-space: pre-wrap; }
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_MIT">
<attribute name="label">
<string>MIT License (applies to boolinq source code)</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTextBrowser" name="m_txtLicenseMit">
<property name="font">
<font>
<family>DejaVu Sans Mono</family>
</font>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="autoFormatting">
<set>QTextEdit::AutoNone</set>
</property>
<property name="undoRedoEnabled">
<bool>false</bool>
</property>
<property name="lineWrapMode">
<enum>QTextEdit::WidgetWidth</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="html">
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">

View file

@ -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);

View file

@ -430,7 +430,7 @@ QMenu* FeedsView::initializeContextMenuBin(RootItem* clicked_item) {
m_contextMenuBin->clear();
}
QList<QAction*> specific_actions = clicked_item->contextMenu();
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuBin->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode <<
@ -453,7 +453,7 @@ QMenu* FeedsView::initializeContextMenuService(RootItem* clicked_item) {
m_contextMenuService->clear();
}
QList<QAction*> specific_actions = clicked_item->contextMenu();
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuService->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedItems <<
@ -511,7 +511,7 @@ QMenu* FeedsView::initializeContextMenuCategories(RootItem* clicked_item) {
m_contextMenuCategories->clear();
}
QList<QAction*> specific_actions = clicked_item->contextMenu();
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuCategories->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedItems <<
@ -538,7 +538,7 @@ QMenu* FeedsView::initializeContextMenuFeeds(RootItem* clicked_item) {
m_contextMenuFeeds->clear();
}
QList<QAction*> specific_actions = clicked_item->contextMenu();
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuFeeds->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionUpdateSelectedItems <<
@ -565,7 +565,7 @@ QMenu* FeedsView::initializeContextMenuImportant(RootItem* clicked_item) {
m_contextMenuImportant->clear();
}
QList<QAction*> specific_actions = clicked_item->contextMenu();
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList();
m_contextMenuImportant->addActions(QList<QAction*>() <<
qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode <<
@ -598,7 +598,7 @@ QMenu* FeedsView::initializeContextMenuOtherItem(RootItem* clicked_item) {
m_contextMenuOtherItems->clear();
}
QList<QAction*> specific_actions = clicked_item->contextMenu();
QList<QAction*> specific_actions = clicked_item->contextMenuFeedsList();
if (!specific_actions.isEmpty()) {
m_contextMenuOtherItems->addSeparator();

View file

@ -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 <QFileIconProvider>
#include <QKeyEvent>
@ -213,12 +215,33 @@ void MessagesView::initializeContextMenu() {
m_contextMenu->addMenu(menu);
m_contextMenu->addActions(
QList<QAction*>() << 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<QAction*>()
<< 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) {
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<int>::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);
}
}
}
void MessagesView::mousePressEvent(QMouseEvent* event) {

View file

@ -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

View file

@ -46,7 +46,7 @@ void RecycleBin::updateCounts(bool update_total_count) {
}
}
QList<QAction*> RecycleBin::contextMenu() {
QList<QAction*> RecycleBin::contextMenuFeedsList() {
if (m_contextMenu.isEmpty()) {
QAction* restore_action = new QAction(qApp->icons()->fromTheme(QSL("view-refresh")),
tr("Restore recycle bin"),

View file

@ -14,7 +14,7 @@ class RecycleBin : public RootItem {
QString additionalTooltip() const;
QList<QAction*> contextMenu();
QList<QAction*> contextMenuFeedsList();
QList<Message> undeletedMessages() const;
bool markAsReadUnread(ReadStatus status);

View file

@ -44,7 +44,7 @@ QString RootItem::additionalTooltip() const {
return QString();
}
QList<QAction*> RootItem::contextMenu() {
QList<QAction*> RootItem::contextMenuFeedsList() {
return QList<QAction*>();
}

View file

@ -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<QAction*> contextMenu();
virtual QList<QAction*> contextMenuFeedsList();
// Can properties of this item be edited?
virtual bool canBeEdited() const;

View file

@ -68,11 +68,11 @@ bool ServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const {
return false;
}
QList<QAction*> ServiceRoot::contextMenu() {
QList<QAction*> ServiceRoot::contextMenuFeedsList() {
return serviceMenu();
}
QList<QAction*> ServiceRoot::contextMenuForMessages(const QList<Message*>& messages) {
QList<QAction*> ServiceRoot::contextMenuMessagesList(const QList<Message>& messages) {
Q_UNUSED(messages)
return {};
}

View file

@ -53,9 +53,11 @@ class ServiceRoot : public RootItem {
// NOTE: Caller does NOT take ownership of created menu!
virtual QList<QAction*> addItemMenu();
// Returns actions to display as context menu.
virtual QList<QAction*> contextMenu();
virtual QList<QAction*> contextMenuForMessages(const QList<Message*>& messages);
// NOTE: Caller does NOT take ownership of created menu!
virtual QList<QAction*> contextMenuFeedsList();
// NOTE: Caller does NOT take ownership of created menu!
virtual QList<QAction*> contextMenuMessagesList(const QList<Message>& messages);
// Returns list of specific actions to be shown in main window menu
// bar in sections "Services -> 'this service'".

View file

@ -18,7 +18,8 @@
#include <QFileDialog>
GmailServiceRoot::GmailServiceRoot(GmailNetworkFactory* network, RootItem* parent) : ServiceRoot(parent), m_network(network) {
GmailServiceRoot::GmailServiceRoot(GmailNetworkFactory* network, RootItem* parent)
: ServiceRoot(parent), m_network(network), m_actionReply(nullptr) {
if (network == nullptr) {
m_network = new GmailNetworkFactory(this);
}
@ -36,6 +37,10 @@ void GmailServiceRoot::updateTitle() {
setTitle(m_network->username() + QSL(" (Gmail)"));
}
void GmailServiceRoot::replyToEmail() {
FormAddEditEmail(this, qApp->mainFormWidget()).execForReply(&m_replyToMessage);
}
RootItem* GmailServiceRoot::obtainNewTreeForSyncIn() const {
auto* root = new RootItem();
GmailFeed* inbox = new GmailFeed(tr("Inbox"), QSL(GMAIL_SYSTEM_LABEL_INBOX), qApp->icons()->fromTheme(QSL("mail-inbox")), root);
@ -128,6 +133,22 @@ bool GmailServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const {
}
}
QList<QAction*> GmailServiceRoot::contextMenuMessagesList(const QList<Message>& messages) {
if (messages.size() == 1) {
if (m_actionReply == nullptr) {
m_actionReply = new QAction(qApp->icons()->fromTheme(QSL("mail-reply-sender")), tr("Reply"), this);
m_replyToMessage = messages.at(0);
connect(m_actionReply, &QAction::triggered, this, &GmailServiceRoot::replyToEmail);
}
return { m_actionReply };
}
else {
return {};
}
}
QList<QAction*> GmailServiceRoot::serviceMenu() {
if (m_serviceMenu.isEmpty()) {
ServiceRoot::serviceMenu();

View file

@ -22,6 +22,7 @@ class GmailServiceRoot : public ServiceRoot, public CacheForServiceRoot {
void setNetwork(GmailNetworkFactory* network);
GmailNetworkFactory* network() const;
QList<QAction*> contextMenuMessagesList(const QList<Message>& messages);
QList<QAction*> serviceMenu();
bool isSyncable() const;
bool canBeEdited() const;
@ -41,6 +42,9 @@ class GmailServiceRoot : public ServiceRoot, public CacheForServiceRoot {
public slots:
void updateTitle();
private slots:
void replyToEmail();
protected:
RootItem* obtainNewTreeForSyncIn() const;
@ -50,6 +54,8 @@ class GmailServiceRoot : public ServiceRoot, public CacheForServiceRoot {
private:
GmailNetworkFactory* m_network;
QAction* m_actionReply;
Message m_replyToMessage;
};
inline void GmailServiceRoot::setNetwork(GmailNetworkFactory* network) {

View file

@ -112,15 +112,8 @@ void FormAddEditEmail::onOkClicked() {
msg.set_plain(m_ui.m_txtMessage->toPlainText().toStdString());
msg.set_header("Content-Type", "text/plain; charset=utf-8");
if (m_originalMessage == nullptr) {
// Send completely new message.
}
else {
// TODO: Reply to existing message.
}
try {
m_root->network()->sendEmail(msg);
m_root->network()->sendEmail(msg, m_originalMessage);
accept();
}
catch (const ApplicationException& ex) {
@ -141,7 +134,9 @@ void FormAddEditEmail::addRecipientRow(const QString& recipient) {
mail_rec->setPossibleRecipients(rec);
}
catch (const ApplicationException& ex) {}
catch (const ApplicationException& ex) {
qWarning("Failed to get recipients: '%s'.", qPrintable(ex.message()));
}
m_ui.m_layout->insertRow(m_ui.m_layout->count() - 5, mail_rec);
}

View file

@ -52,13 +52,31 @@ void GmailNetworkFactory::setBatchSize(int batch_size) {
m_batchSize = batch_size;
}
QString GmailNetworkFactory::sendEmail(const Mimesis::Message& msg) {
QString GmailNetworkFactory::sendEmail(Mimesis::Message msg, Message* reply_to_message) {
QString bearer = m_oauth2->bearer().toLocal8Bit();
if (bearer.isEmpty()) {
throw ApplicationException(tr("you aren't logged in"));
}
if (reply_to_message != nullptr) {
// We need to obtain some extra information.
auto metadata = getMessageMetadata(reply_to_message->m_customId, {
QSL("References"),
QSL("Message-ID")
});
/*if (metadata.contains(QSL("References"))) {
}*/
if (metadata.contains(QSL("Message-ID"))) {
msg["References"] = metadata.value(QSL("Message-ID")).toString().toStdString();
msg["In-Reply-To"] = metadata.value(QSL("Message-ID")).toString().toStdString();
}
}
QString rfc_email = QString::fromStdString(msg.to_string());
QByteArray input_data = rfc_email.toUtf8();
QList<QPair<QByteArray, QByteArray>> headers;
@ -501,6 +519,36 @@ QStringList GmailNetworkFactory::getAllRecipients() {
}
}
QVariantMap GmailNetworkFactory::getMessageMetadata(const QString& msg_id, const QStringList& metadata) {
QString bearer = m_oauth2->bearer();
if (bearer.isEmpty()) {
throw ApplicationException(tr("you are not logged in"));
}
QList<QPair<QByteArray, QByteArray>> headers;
QByteArray output;
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
headers.append(QPair<QByteArray, QByteArray>(QString(HTTP_HEADERS_AUTHORIZATION).toLocal8Bit(),
bearer.toLocal8Bit()));
QString query = QString("%1/%2?format=metadata&metadataHeaders=%3").arg(GMAIL_API_MSGS_LIST,
msg_id,
metadata.join(QSL("&metadataHeaders=")));
NetworkResult res = NetworkFactory::performNetworkOperation(query,
timeout,
QByteArray(),
output,
QNetworkAccessManager::Operation::GetOperation,
headers);
if (res.first == QNetworkReply::NetworkError::NoError) {}
else {
throw ApplicationException(tr("failed to get metadata"));
}
}
bool GmailNetworkFactory::obtainAndDecodeFullMessages(const QList<Message>& lite_messages,
const QString& feed_id,
QList<Message>& full_messages) {

View file

@ -36,7 +36,7 @@ class GmailNetworkFactory : public QObject {
void setBatchSize(int batch_size);
// Sends e-mail, returns its ID.
QString sendEmail(const Mimesis::Message& msg);
QString sendEmail(Mimesis::Message msg, Message* reply_to_message = nullptr);
// Returns all possible recipients.
QStringList getAllRecipients();
@ -53,6 +53,7 @@ class GmailNetworkFactory : public QObject {
private:
bool fillFullMessage(Message& msg, const QJsonObject& json, const QString& feed_id);
QVariantMap getMessageMetadata(const QString& msg_id, const QStringList& metadata);
bool obtainAndDecodeFullMessages(const QList<Message>& lite_messages, const QString& feed_id, QList<Message>& full_messages);
QList<Message> decodeLiteMessages(const QString& messages_json_data, const QString& stream_id, QString& next_page_token);

View file

@ -107,7 +107,7 @@ void FormEditOwnCloudAccount::performTest() {
if (!SystemFactory::isVersionEqualOrNewer(result.version(), OWNCLOUD_MIN_VERSION)) {
m_ui->m_lblTestResult->setStatus(WidgetWithStatus::StatusType::Error,
tr(
"Selected Nextcloud News server is running unsupported version (%1). At least version %2 is required.").arg(
"Selected Nextcloud News server is running unsupported version %1. At least version %2 is required.").arg(
result.version(),
OWNCLOUD_MIN_VERSION),
tr("Selected Nextcloud News server is running unsupported version."));

View file

@ -120,6 +120,8 @@ OwnCloudStatusResponse OwnCloudNetworkFactory::status() {
headers);
OwnCloudStatusResponse status_response(QString::fromUtf8(result_raw));
qDebug().noquote().nospace() << "Raw status data is:" << result_raw;
if (network_reply.first != QNetworkReply::NoError) {
qWarning("Nextcloud: Obtaining status info failed with error %d.", network_reply.first);
}

View file

@ -51,7 +51,7 @@ StandardFeed::~StandardFeed() {
qDebug("Destroying Feed instance.");
}
QList<QAction*> StandardFeed::contextMenu() {
QList<QAction*> StandardFeed::contextMenuFeedsList() {
return serviceRoot()->getContextMenuForFeed(this);
}

View file

@ -35,7 +35,7 @@ class StandardFeed : public Feed {
StandardServiceRoot* serviceRoot() const;
QList<QAction*> contextMenu();
QList<QAction*> contextMenuFeedsList();
QString additionalTooltip() const;