Merge branch 'master' of github.com:martinrotter/rssguard

This commit is contained in:
Martin Rotter 2020-07-31 20:12:32 +02:00
commit b89fda7b6b
78 changed files with 1693 additions and 575 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.

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

@ -0,0 +1,883 @@
#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) { 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) { 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);
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<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

@ -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_
}
QList<ServiceRoot*>FeedsModel::serviceRoots() const {
QList<ServiceRoot*>roots;
QList<ServiceRoot*> 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 @@ QList<ServiceRoot*>FeedsModel::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 @@ QList<Feed*>FeedsModel::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 @@ QList<Feed*>FeedsModel::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<const RootItem*> 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();
});
}

View File

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

View File

@ -48,7 +48,7 @@ class FeedsProxyModel : public QSortFilterProxyModel {
const RootItem* m_selectedItem;
bool m_showUnreadOnly;
QList<QPair<int, QModelIndex>> m_hiddenIndices;
QList<RootItemKind::Kind> m_priorities;
QList<RootItem::Kind> m_priorities;
};
#endif // FEEDSPROXYMODEL_H

View File

@ -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<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);
}
@ -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<Message, RootItem::Importance> 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<Message, RootItem::Importance>(msg, message_importance == RootItem::Important ?
RootItem::NotImportant :
RootItem::Important));
message_states.append(QPair<Message, RootItem::Importance>(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 {

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

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

View File

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

View File

@ -95,7 +95,7 @@ void FeedsView::saveAllExpandStates() {
void FeedsView::saveExpandStates(RootItem* item) {
Settings* settings = qApp->settings();
QList<RootItem*> items = item->getSubTree(RootItemKind::Category | RootItemKind::ServiceRoot);
QList<RootItem*> 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<RootItem*> 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<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();
@ -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<Message> messages = m_sourceModel->messagesForItem(item);
if (!messages.isEmpty()) {

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,11 +215,32 @@ 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) {
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<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);
}
}
}
@ -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) {

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

@ -5,6 +5,7 @@
#include "definitions/definitions.h"
#include "gui/plaintoolbutton.h"
#include "miscellaneous/settings.h"
#include "miscellaneous/templates.h"
#include <QMouseEvent>
#include <QStyle>
@ -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);
}

View File

@ -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<TabBar::TabType>(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<TabBar::TabType>(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

View File

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

View File

@ -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<TabBar*>(QTabWidget::tabBar());
}
TabBar* tabBar() const;
// Returns the central widget of this tab.
inline TabContent* widget(int index) const {
return static_cast<TabContent*>(QTabWidget::widget(index));
}
TabContent* widget(int index) const;
inline TabContent* currentWidget() const {
return static_cast<TabContent*>(QTabWidget::currentWidget());
}
TabContent* currentWidget() const;
// Initializes TabWidget with tabs, this includes initialization
// of main "Feeds" widget.
@ -58,21 +52,7 @@ class TabWidget : public QTabWidget {
void setupIcons();
// Accessor to feed/message viewer.
inline FeedMessageViewer* feedMessageViewer() const {
return m_feedMessageViewer;
}
protected:
// Creates necesary connections.
void createConnections();
// Sets up properties of custom corner button.
void setupMainMenuButton();
// Handlers of insertin/removing of tabs.
void tabInserted(int index);
void tabRemoved(int index);
FeedMessageViewer* feedMessageViewer() const;
public slots:
@ -117,10 +97,31 @@ class TabWidget : public QTabWidget {
private:
void indentTabText(int index);
void createConnections();
void setupMainMenuButton();
void tabInserted(int index);
void tabRemoved(int index);
PlainToolButton* m_btnMainMenu;
QMenu* m_menuMain;
FeedMessageViewer* m_feedMessageViewer;
};
inline TabBar* TabWidget::tabBar() const {
return static_cast<TabBar*>(QTabWidget::tabBar());
}
inline TabContent* TabWidget::widget(int index) const {
return static_cast<TabContent*>(QTabWidget::widget(index));
}
inline TabContent* TabWidget::currentWidget() const {
return static_cast<TabContent*>(QTabWidget::currentWidget());
}
inline FeedMessageViewer* TabWidget::feedMessageViewer() const {
return m_feedMessageViewer;
}
#endif // TABWIDGET_H

View File

@ -22,7 +22,7 @@
#include <QMouseEvent>
TreeWidget::TreeWidget(QWidget* parent)
: QTreeWidget(parent), m_refreshAllItemsNeeded(true), m_showMode(ItemsCollapsed) {
: QTreeWidget(parent), m_refreshAllItemsNeeded(true), m_showMode(ItemShowMode::ItemsCollapsed) {
connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(sheduleRefresh()));
}
@ -123,7 +123,7 @@ void TreeWidget::filterString(const QString& string) {
parentItem->setHidden(false);
if (stringIsEmpty) {
parentItem->setExpanded(m_showMode == ItemsExpanded);
parentItem->setExpanded(m_showMode == ItemShowMode::ItemsExpanded);
}
else {
parentItem->setExpanded(true);

View File

@ -28,7 +28,10 @@ class TreeWidget : public QTreeWidget {
public:
explicit TreeWidget(QWidget* parent = 0);
enum ItemShowMode { ItemsCollapsed = 0, ItemsExpanded = 1 };
enum class ItemShowMode {
ItemsCollapsed = 0,
ItemsExpanded = 1
};
ItemShowMode defaultItemShowMode() {
return m_showMode;
@ -69,7 +72,6 @@ class TreeWidget : public QTreeWidget {
void iterateAllItems(QTreeWidgetItem* parent);
bool m_refreshAllItemsNeeded;
QList<QTreeWidgetItem*> m_allTreeItems;
ItemShowMode m_showMode;
};

View File

@ -156,19 +156,19 @@ bool WebBrowser::eventFilter(QObject* watched, QEvent* event) {
void WebBrowser::receiveMessageStatusChangeRequest(int message_id, WebPage::MessageStatusChange change) {
switch (change) {
case WebPage::MarkRead:
case WebPage::MessageStatusChange::MarkRead:
markMessageAsRead(message_id, true);
break;
case WebPage::MarkUnread:
case WebPage::MessageStatusChange::MarkUnread:
markMessageAsRead(message_id, false);
break;
case WebPage::MarkStarred:
case WebPage::MessageStatusChange::MarkStarred:
switchMessageImportance(message_id, true);
break;
case WebPage::MarkUnstarred:
case WebPage::MessageStatusChange::MarkUnstarred:
switchMessageImportance(message_id, false);
break;
@ -265,16 +265,18 @@ void WebBrowser::markMessageAsRead(int id, bool read) {
if (msg != nullptr && m_root->getParentServiceRoot()->onBeforeSetMessagesRead(m_root.data(),
QList<Message>() << *msg,
read ? RootItem::Read : RootItem::Unread)) {
read
? RootItem::ReadStatus::Read
: RootItem::ReadStatus::Unread)) {
DatabaseQueries::markMessagesReadUnread(qApp->database()->connection(objectName()),
QStringList() << QString::number(msg->m_id),
read ? RootItem::Read : RootItem::Unread);
read ? RootItem::ReadStatus::Read : RootItem::ReadStatus::Unread);
m_root->getParentServiceRoot()->onAfterSetMessagesRead(m_root.data(),
QList<Message>() << *msg,
read ? RootItem::Read : RootItem::Unread);
emit markMessageRead(msg->m_id, read ? RootItem::Read : RootItem::Unread);
read ? RootItem::ReadStatus::Read : RootItem::ReadStatus::Unread);
emit markMessageRead(msg->m_id, read ? RootItem::ReadStatus::Read : RootItem::ReadStatus::Unread);
msg->m_isRead = read ? RootItem::Read : RootItem::Unread;
msg->m_isRead = read;
}
}
}
@ -283,23 +285,24 @@ void WebBrowser::switchMessageImportance(int id, bool checked) {
if (!m_root.isNull()) {
Message* msg = findMessage(id);
if (msg != nullptr && m_root->getParentServiceRoot()->onBeforeSwitchMessageImportance(m_root.data(),
QList<ImportanceChange>() <<
ImportanceChange(*msg,
msg
->m_isImportant ?
RootItem
::NotImportant :
RootItem
::Important))) {
if (msg != nullptr &&
m_root->getParentServiceRoot()->onBeforeSwitchMessageImportance(m_root.data(),
QList<ImportanceChange>()
<< ImportanceChange(*msg,
msg->m_isImportant
? RootItem::Importance::NotImportant
: RootItem::Importance::Important))) {
DatabaseQueries::switchMessagesImportance(qApp->database()->connection(objectName()),
QStringList() << QString::number(msg->m_id));
m_root->getParentServiceRoot()->onAfterSwitchMessageImportance(m_root.data(),
QList<ImportanceChange>() << ImportanceChange(*msg,
msg->m_isImportant ?
RootItem::NotImportant :
RootItem::Important));
emit markMessageImportant(msg->m_id, msg->m_isImportant ? RootItem::NotImportant : RootItem::Important);
QList<ImportanceChange>()
<< ImportanceChange(*msg,
msg->m_isImportant ?
RootItem::Importance::NotImportant :
RootItem::Importance::Important));
emit markMessageImportant(msg->m_id, msg->m_isImportant
? RootItem::Importance::NotImportant
: RootItem::Importance::Important);
msg->m_isImportant = checked;
}

View File

@ -109,6 +109,7 @@ HEADERS += core/feeddownloader.h \
miscellaneous/simplecrypt/simplecrypt.h \
miscellaneous/skinfactory.h \
miscellaneous/systemfactory.h \
miscellaneous/templates.h \
miscellaneous/textfactory.h \
network-web/basenetworkaccessmanager.h \
network-web/downloader.h \
@ -417,6 +418,9 @@ else {
SOURCES += $$files(3rd-party/mimesis/*.cpp, false)
HEADERS += $$files(3rd-party/mimesis/*.hpp, false)
# Add boolinq.
HEADERS += $$files(3rd-party/boolinq/*.h, false)
INCLUDEPATH += $$PWD/. \
$$PWD/gui \
$$PWD/gui/dialogs \

View File

@ -57,6 +57,8 @@ Application::Application(const QString& id, int& argc, char** argv)
// Setup debug output system.
qInstallMessageHandler(Debugging::debugHandler);
determineFirstRuns();
//: Abbreviation of language, e.g. en.
//: Use ISO 639-1 code here combined with ISO 3166-1 (alpha-2) code.
//: Examples: "cs", "en", "it", "cs_CZ", "en_GB", "en_US".
@ -129,7 +131,7 @@ void Application::showPolls() const {
}
void Application::offerChanges() const {
if (isFirstRun() || isFirstRun(APP_VERSION)) {
if (isFirstRunCurrentVersion()) {
qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1.\n\nPlease, check NEW stuff included in this\n"
"version by clicking this popup notification.").arg(APP_LONG_NAME),
QSystemTrayIcon::NoIcon, nullptr, false, [] {
@ -159,17 +161,11 @@ QList<QAction*> Application::userActions() {
}
bool Application::isFirstRun() const {
return settings()->value(GROUP(General), SETTING(General::FirstRun)).toBool();
return m_firstRunEver;
}
bool Application::isFirstRun(const QString& version) const {
if (version == APP_VERSION) {
// Check this only if checked version is equal to actual version.
return settings()->value(GROUP(General), QString(General::FirstRun) + QL1C('_') + version, true).toBool();
}
else {
return false;
}
bool Application::isFirstRunCurrentVersion() const {
return m_firstRunCurrentVersion;
}
WebFactory* Application::web() const {
@ -192,12 +188,9 @@ DatabaseFactory* Application::database() {
return m_database;
}
void Application::eliminateFirstRun() {
void Application::eliminateFirstRuns() {
settings()->setValue(GROUP(General), General::FirstRun, false);
}
void Application::eliminateFirstRun(const QString& version) {
settings()->setValue(GROUP(General), QString(General::FirstRun) + QL1C('_') + version, false);
settings()->setValue(GROUP(General), QString(General::FirstRun) + QL1C('_') + APP_VERSION, false);
}
void Application::setFeedReader(FeedReader* feed_reader) {
@ -253,7 +246,7 @@ QString Application::userDataAppFolder() {
}
QString Application::userDataFolder() {
if (settings()->type() == SettingsProperties::Portable) {
if (settings()->type() == SettingsProperties::SettingsType::Portable) {
return userDataAppFolder();
}
else {
@ -447,9 +440,6 @@ void Application::onAboutToQuit() {
m_quitLogicDone = true;
eliminateFirstRun();
eliminateFirstRun(APP_VERSION);
#if defined(USE_WEBENGINE)
AdBlockManager::instance()->save();
#endif
@ -527,3 +517,13 @@ void Application::onFeedUpdatesFinished(const FeedDownloadResults& results) {
nullptr, false);
}
}
void Application::determineFirstRuns() {
m_firstRunEver = settings()->value(GROUP(General),
SETTING(General::FirstRun)).toBool();
m_firstRunCurrentVersion = settings()->value(GROUP(General),
QString(General::FirstRun) + QL1C('_') + APP_VERSION,
true).toBool();
eliminateFirstRuns();
}

View File

@ -55,7 +55,6 @@ class RSSGUARD_DLLSPEC Application : public QtSingleApplication {
bool isAlreadyRunning();
FeedReader* feedReader();
void setFeedReader(FeedReader* feed_reader);
// Globally accessible actions.
@ -64,8 +63,8 @@ class RSSGUARD_DLLSPEC Application : public QtSingleApplication {
// Check whether this application starts for the first time (ever).
bool isFirstRun() const;
// Check whether GIVEN VERSION of the application starts for the first time.
bool isFirstRun(const QString& version) const;
// Check whether CURRENT VERSION of the application starts for the first time.
bool isFirstRunCurrentVersion() const;
WebFactory* web() const;
SystemFactory* system();
@ -139,8 +138,8 @@ class RSSGUARD_DLLSPEC Application : public QtSingleApplication {
void onFeedUpdatesFinished(const FeedDownloadResults& results);
private:
void eliminateFirstRun();
void eliminateFirstRun(const QString& version);
void determineFirstRuns();
void eliminateFirstRuns();
#if defined(USE_WEBENGINE)
NetworkUrlInterceptor* m_urlInterceptor;
@ -176,6 +175,8 @@ class RSSGUARD_DLLSPEC Application : public QtSingleApplication {
DatabaseFactory* m_database;
DownloadManager* m_downloadManager;
bool m_shouldRestart;
bool m_firstRunEver;
bool m_firstRunCurrentVersion;
};
inline Application* Application::instance() {

View File

@ -37,7 +37,7 @@ bool DatabaseQueries::markImportantMessagesReadUnread(const QSqlDatabase& db, in
q.setForwardOnly(true);
q.prepare("UPDATE Messages SET is_read = :read "
"WHERE is_important = 1 AND is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id;");
q.bindValue(QSL(":read"), read == RootItem::Read ? 1 : 0);
q.bindValue(QSL(":read"), read == RootItem::ReadStatus::Read ? 1 : 0);
q.bindValue(QSL(":account_id"), account_id);
return q.exec();
}
@ -47,7 +47,7 @@ bool DatabaseQueries::markMessagesReadUnread(const QSqlDatabase& db, const QStri
q.setForwardOnly(true);
return q.exec(QString(QSL("UPDATE Messages SET is_read = %2 WHERE id IN (%1);"))
.arg(ids.join(QSL(", ")), read == RootItem::Read ? QSL("1") : QSL("0")));
.arg(ids.join(QSL(", ")), read == RootItem::ReadStatus::Read ? QSL("1") : QSL("0")));
}
bool DatabaseQueries::markMessageImportant(const QSqlDatabase& db, int id, RootItem::Importance importance) {
@ -73,7 +73,7 @@ bool DatabaseQueries::markFeedsReadUnread(const QSqlDatabase& db, const QStringL
q.setForwardOnly(true);
q.prepare(QString("UPDATE Messages SET is_read = :read "
"WHERE feed IN (%1) AND is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id;").arg(ids.join(QSL(", "))));
q.bindValue(QSL(":read"), read == RootItem::Read ? 1 : 0);
q.bindValue(QSL(":read"), read == RootItem::ReadStatus::Read ? 1 : 0);
q.bindValue(QSL(":account_id"), account_id);
return q.exec();
}
@ -84,7 +84,7 @@ bool DatabaseQueries::markBinReadUnread(const QSqlDatabase& db, int account_id,
q.setForwardOnly(true);
q.prepare("UPDATE Messages SET is_read = :read "
"WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;");
q.bindValue(QSL(":read"), read == RootItem::Read ? 1 : 0);
q.bindValue(QSL(":read"), read == RootItem::ReadStatus::Read ? 1 : 0);
q.bindValue(QSL(":account_id"), account_id);
return q.exec();
}
@ -95,7 +95,7 @@ bool DatabaseQueries::markAccountReadUnread(const QSqlDatabase& db, int account_
q.setForwardOnly(true);
q.prepare(QSL("UPDATE Messages SET is_read = :read WHERE is_pdeleted = 0 AND account_id = :account_id;"));
q.bindValue(QSL(":account_id"), account_id);
q.bindValue(QSL(":read"), read == RootItem::Read ? 1 : 0);
q.bindValue(QSL(":read"), read == RootItem::ReadStatus::Read ? 1 : 0);
return q.exec();
}
@ -915,7 +915,7 @@ bool DatabaseQueries::storeAccountTree(const QSqlDatabase& db, RootItem* tree_ro
// Iterate all children.
for (RootItem* child : tree_root->getSubTree()) {
if (child->kind() == RootItemKind::Category) {
if (child->kind() == RootItem::Kind::Category) {
query_category.bindValue(QSL(":parent_id"), child->parent()->id());
query_category.bindValue(QSL(":title"), child->title());
query_category.bindValue(QSL(":account_id"), account_id);
@ -928,7 +928,7 @@ bool DatabaseQueries::storeAccountTree(const QSqlDatabase& db, RootItem* tree_ro
return false;
}
}
else if (child->kind() == RootItemKind::Feed) {
else if (child->kind() == RootItem::Kind::Feed) {
Feed* feed = child->toFeed();
query_feed.bindValue(QSL(":title"), feed->title());
@ -1400,7 +1400,7 @@ bool DatabaseQueries::editStandardFeed(const QSqlDatabase& db, int parent_id, in
q.bindValue(QSL(":update_type"), int(auto_update_type));
q.bindValue(QSL(":update_interval"), auto_update_interval);
q.bindValue(QSL(":type"), feed_format);
q.bindValue(QSL(":type"), int(feed_format));
q.bindValue(QSL(":id"), feed_id);
bool suc = q.exec();
@ -1720,6 +1720,28 @@ bool DatabaseQueries::createTtRssAccount(const QSqlDatabase& db, int id_to_assig
}
}
QStringList DatabaseQueries::getAllRecipients(const QSqlDatabase& db, int account_id) {
QSqlQuery query(db);
QStringList rec;
query.prepare(QSL("SELECT DISTINCT author "
"FROM Messages "
"WHERE account_id = :account_id AND author IS NOT NULL AND author != '' "
"ORDER BY lower(author) ASC;"));
query.bindValue(QSL(":account_id"), account_id);
if (query.exec()) {
while (query.next()) {
rec.append(query.value(0).toString());
}
}
else {
qWarningNN << "Query for all recipients failed: '" << query.lastError().text() << "'.";
}
return rec;
}
QList<ServiceRoot*> DatabaseQueries::getGmailAccounts(const QSqlDatabase& db, bool* ok) {
QSqlQuery query(db);
QList<ServiceRoot*> roots;

View File

@ -145,6 +145,7 @@ class DatabaseQueries {
bool force_server_side_feed_update, bool download_only_unread_messages);
// Gmail account.
static QStringList getAllRecipients(const QSqlDatabase& db, int account_id);
static bool deleteGmailAccount(const QSqlDatabase& db, int account_id);
static QList<ServiceRoot*> getGmailAccounts(const QSqlDatabase& db, bool* ok = nullptr);
static bool overwriteGmailAccount(const QSqlDatabase& db, const QString& username, const QString& app_id,
@ -182,10 +183,10 @@ inline void DatabaseQueries::fillFeedData(StandardFeed* feed, const QSqlRecord&
StandardFeed::Type type = static_cast<StandardFeed::Type>(sql_record.value(FDS_DB_TYPE_INDEX).toInt());
switch (type) {
case StandardFeed::Atom10:
case StandardFeed::Rdf:
case StandardFeed::Rss0X:
case StandardFeed::Rss2X: {
case StandardFeed::Type::Atom10:
case StandardFeed::Type::Rdf:
case StandardFeed::Type::Rss0X:
case StandardFeed::Type::Rss2X: {
feed->setType(type);
break;
}

View File

@ -270,7 +270,7 @@ DVALUE(QString) Downloads::TargetDirectoryDef = IOFactory::getSystemFolder(QStan
DKEY Downloads::RemovePolicy = "remove_policy";
DVALUE(int) Downloads::RemovePolicyDef = DownloadManager::Never;
DVALUE(int) Downloads::RemovePolicyDef = int(DownloadManager::RemovePolicy::Never);
DKEY Downloads::TargetExplicitDirectory = "target_explicit_directory";
@ -436,7 +436,7 @@ Settings* Settings::setupSettings(QObject* parent) {
new_settings = new Settings(properties.m_absoluteSettingsFileName, QSettings::IniFormat, properties.m_type, parent);
// Check if portable settings are available.
if (properties.m_type == SettingsProperties::Portable) {
if (properties.m_type == SettingsProperties::SettingsType::Portable) {
qDebug("Initializing settings in '%s' (portable way).", qPrintable(QDir::toNativeSeparators(properties.m_absoluteSettingsFileName)));
}
else {
@ -468,11 +468,11 @@ SettingsProperties Settings::determineProperties() {
#endif
if (will_we_use_portable_settings) {
properties.m_type = SettingsProperties::Portable;
properties.m_type = SettingsProperties::SettingsType::Portable;
properties.m_baseDirectory = app_path;
}
else {
properties.m_type = SettingsProperties::NonPortable;
properties.m_type = SettingsProperties::SettingsType::NonPortable;
properties.m_baseDirectory = home_path;
}

View File

@ -7,10 +7,11 @@
// Describes characteristics of settings.
struct SettingsProperties {
enum SettingsType {
enum class SettingsType {
Portable,
NonPortable
};
SettingsType m_type;
QString m_baseDirectory;
QString m_settingsSuffix;

View File

@ -0,0 +1,4 @@
#ifndef TEMPLATES_H
#define TEMPLATES_H
#endif // TEMPLATES_H

View File

@ -30,7 +30,7 @@
AdBlockTreeWidget::AdBlockTreeWidget(AdBlockSubscription* subscription, QWidget* parent)
: TreeWidget(parent), m_subscription(subscription), m_topItem(nullptr), m_itemChangingBlock(false) {
setContextMenuPolicy(Qt::CustomContextMenu);
setDefaultItemShowMode(TreeWidget::ItemsExpanded);
setDefaultItemShowMode(TreeWidget::ItemShowMode::ItemsExpanded);
setHeaderHidden(true);
setAlternatingRowColors(true);
setLayoutDirection(Qt::LeftToRight);

View File

@ -135,17 +135,14 @@ QString DownloadItem::saveFileName(const QString& directory) const {
QString path;
if (m_reply->hasRawHeader("Content-Disposition")) {
const QString value = QLatin1String(m_reply->rawHeader("Content-Disposition"));
const int pos = value.indexOf(QL1S("filename="));
QString value = QLatin1String(m_reply->rawHeader("Content-Disposition"));
QRegularExpression exp(".*filename=?\"([^\"]+)\"?");
QRegularExpressionMatch match = exp.match(value);
if (pos != -1) {
QString name = value.mid(pos + 9);
if (match.isValid()) {
QString name = match.captured(1);
if (name.startsWith(QL1C('"')) && name.endsWith(QL1C('"'))) {
name = name.mid(1, name.size() - 2);
}
path = name;
path = QUrl::fromPercentEncoding(name.toLocal8Bit());
}
}
@ -427,7 +424,7 @@ void DownloadItem::updateInfoAndUrlLabel() {
DownloadManager::DownloadManager(QWidget* parent) : TabContent(parent), m_ui(new Ui::DownloadManager),
m_autoSaver(new AutoSaver(this)), m_model(new DownloadModel(this)),
m_networkManager(new SilentNetworkAccessManager(this)), m_iconProvider(nullptr), m_removePolicy(Never) {
m_networkManager(new SilentNetworkAccessManager(this)), m_iconProvider(nullptr), m_removePolicy(RemovePolicy::Never) {
m_ui->setupUi(this);
m_ui->m_viewDownloads->setShowGrid(false);
m_ui->m_viewDownloads->verticalHeader()->hide();
@ -585,7 +582,7 @@ void DownloadManager::updateRow(DownloadItem* item) {
// a) It is not downloading and private browsing is enabled.
// OR
// b) Item is already downloaded and it should be remove from downloader list.
bool remove = item->downloadedSuccessfully() && removePolicy() == DownloadManager::OnSuccessfullDownload;
bool remove = item->downloadedSuccessfully() && removePolicy() == RemovePolicy::OnSuccessfullDownload;
if (remove) {
m_model->removeRow(row);
@ -608,7 +605,7 @@ void DownloadManager::setRemovePolicy(RemovePolicy policy) {
}
void DownloadManager::save() const {
if (m_removePolicy == OnExit) {
if (m_removePolicy == RemovePolicy::OnExit) {
// No saving.
return;
}

View File

@ -94,7 +94,7 @@ class DownloadManager : public TabContent {
friend class DownloadModel;
public:
enum RemovePolicy {
enum class RemovePolicy {
Never,
OnExit,
OnSuccessfullDownload

View File

@ -27,16 +27,16 @@ void WebPage::javaScriptAlert(const QUrl& securityOrigin, const QString& msg) {
const QString& action = parts.at(1);
if (action == QSL("read")) {
emit messageStatusChangeRequested(message_id, MarkRead);
emit messageStatusChangeRequested(message_id, MessageStatusChange::MarkRead);
}
else if (action == QSL("unread")) {
emit messageStatusChangeRequested(message_id, MarkUnread);
emit messageStatusChangeRequested(message_id, MessageStatusChange::MarkUnread);
}
else if (action == QSL("starred")) {
emit messageStatusChangeRequested(message_id, MarkStarred);
emit messageStatusChangeRequested(message_id, MessageStatusChange::MarkStarred);
}
else if (action == QSL("unstarred")) {
emit messageStatusChangeRequested(message_id, MarkUnstarred);
emit messageStatusChangeRequested(message_id, MessageStatusChange::MarkUnstarred);
}
else {
QWebEnginePage::javaScriptAlert(securityOrigin, msg);

View File

@ -11,7 +11,7 @@ class WebPage : public QWebEnginePage {
Q_OBJECT
public:
enum MessageStatusChange {
enum class MessageStatusChange {
MarkRead,
MarkUnread,
MarkStarred,

View File

@ -44,7 +44,7 @@ void AccountCheckModel::setRootItem(RootItem* root_item, bool delete_previous_ro
void AccountCheckModel::checkAllItems() {
if (m_rootItem != nullptr) {
for (RootItem* root_child : m_rootItem->childItems()) {
if (root_child->kind() == RootItemKind::Feed || root_child->kind() == RootItemKind::Category) {
if (root_child->kind() == RootItem::Kind::Feed || root_child->kind() == RootItem::Kind::Category) {
setItemChecked(root_child, Qt::Checked);
}
}
@ -54,7 +54,7 @@ void AccountCheckModel::checkAllItems() {
void AccountCheckModel::uncheckAllItems() {
if (m_rootItem != nullptr) {
for (RootItem* root_child : m_rootItem->childItems()) {
if (root_child->kind() == RootItemKind::Feed || root_child->kind() == RootItemKind::Category) {
if (root_child->kind() == RootItem::Kind::Feed || root_child->kind() == RootItem::Kind::Category) {
setData(indexForItem(root_child), Qt::Unchecked, Qt::CheckStateRole);
}
}
@ -78,7 +78,7 @@ QModelIndex AccountCheckModel::index(int row, int column, const QModelIndex& par
}
QModelIndex AccountCheckModel::indexForItem(RootItem* item) const {
if (item == nullptr || item->kind() == RootItemKind::ServiceRoot || item->kind() == RootItemKind::Root) {
if (item == nullptr || item->kind() == RootItem::Kind::ServiceRoot || item->kind() == RootItem::Kind::Root) {
// Root item lies on invalid index.
return QModelIndex();
}
@ -107,7 +107,7 @@ QModelIndex AccountCheckModel::indexForItem(RootItem* item) const {
for (int i = 0; i < row_count; i++) {
RootItem* possible_category = active_item->child(i);
if (possible_category->kind() == RootItemKind::Category) {
if (possible_category->kind() == RootItem::Kind::Category) {
parents << index(i, 0, active_index);
}
}
@ -180,10 +180,10 @@ QVariant AccountCheckModel::data(const QModelIndex& index, int role) const {
}
else if (role == Qt::DisplayRole) {
switch (item->kind()) {
case RootItemKind::Category:
case RootItem::Kind::Category:
return QVariant(item->data(index.column(), role).toString() + QSL(" ") + tr("(category)"));
case RootItemKind::Feed:
case RootItem::Kind::Feed:
return QVariant(item->data(index.column(), role).toString() + QSL(" ") + tr("(feed)"));
default:
@ -261,8 +261,8 @@ bool AccountCheckModel::setData(const QModelIndex& index, const QVariant& value,
}
Qt::ItemFlags AccountCheckModel::flags(const QModelIndex& index) const {
if (!index.isValid() || (itemForIndex(index)->kind() != RootItemKind::Kind::Category &&
itemForIndex(index)->kind() != RootItemKind::Kind::Feed)) {
if (!index.isValid() || (itemForIndex(index)->kind() != RootItem::Kind::Category &&
itemForIndex(index)->kind() != RootItem::Kind::Feed)) {
return Qt::NoItemFlags;
}

View File

@ -18,7 +18,9 @@ void CacheForServiceRoot::addMessageStatesToCache(const QList<Message>& ids_of_m
m_cacheSaveMutex->lock();
QList<Message>& list_act = m_cachedStatesImportant[importance];
QList<Message>& list_other = m_cachedStatesImportant[importance == RootItem::Important ? RootItem::NotImportant : RootItem::Important];
QList<Message>& list_other = m_cachedStatesImportant[importance == RootItem::Importance::Important
? RootItem::Importance::NotImportant
: RootItem::Importance::Important];
// Store changes, they will be sent to server later.
list_act.append(ids_of_messages);
@ -45,7 +47,9 @@ void CacheForServiceRoot::addMessageStatesToCache(const QStringList& ids_of_mess
m_cacheSaveMutex->lock();
QStringList& list_act = m_cachedStatesRead[read];
QStringList& list_other = m_cachedStatesRead[read == RootItem::Read ? RootItem::Unread : RootItem::Read];
QStringList& list_other = m_cachedStatesRead[read == RootItem::ReadStatus::Read
? RootItem::ReadStatus::Unread
: RootItem::ReadStatus::Read];
// Store changes, they will be sent to server later.
list_act.append(ids_of_messages);
@ -134,9 +138,11 @@ QPair<QMap<RootItem::ReadStatus, QStringList>, QMap<RootItem::Importance, QList<
// Make copy of changes.
QMap<RootItem::ReadStatus, QStringList> cached_data_read = m_cachedStatesRead;
cached_data_read.detach();
QMap<RootItem::Importance, QList<Message>> cached_data_imp = m_cachedStatesImportant;
cached_data_imp.detach();
clearCache();

View File

@ -11,11 +11,11 @@
#include "services/abstract/serviceroot.h"
Category::Category(RootItem* parent) : RootItem(parent) {
setKind(RootItemKind::Category);
setKind(RootItem::Kind::Category);
}
Category::Category(const Category& other) : RootItem(other) {
setKind(RootItemKind::Category);
setKind(RootItem::Kind::Category);
}
Category::Category(const QSqlRecord& record) : Category(nullptr) {
@ -38,10 +38,10 @@ void Category::updateCounts(bool including_total_count) {
QList<Feed*> feeds;
for (RootItem* child : getSubTree()) {
if (child->kind() == RootItemKind::Feed) {
if (child->kind() == RootItem::Kind::Feed) {
feeds.append(child->toFeed());
}
else if (child->kind() != RootItemKind::Category && child->kind() != RootItemKind::ServiceRoot) {
else if (child->kind() != RootItem::Kind::Category && child->kind() != RootItem::Kind::ServiceRoot) {
child->updateCounts(including_total_count);
}
}

View File

@ -17,10 +17,10 @@
#include <QThread>
Feed::Feed(RootItem* parent)
: RootItem(parent), m_url(QString()), m_status(Normal), m_autoUpdateType(DefaultAutoUpdate),
: RootItem(parent), m_url(QString()), m_status(Status::Normal), m_autoUpdateType(AutoUpdateType::DefaultAutoUpdate),
m_autoUpdateInitialInterval(DEFAULT_AUTO_UPDATE_INTERVAL), m_autoUpdateRemainingInterval(DEFAULT_AUTO_UPDATE_INTERVAL),
m_messageFilters(QList<QPointer<MessageFilter>>()) {
setKind(RootItemKind::Feed);
setKind(RootItem::Kind::Feed);
}
Feed::Feed(const QSqlRecord& record) : Feed(nullptr) {
@ -43,7 +43,7 @@ Feed::Feed(const QSqlRecord& record) : Feed(nullptr) {
}
Feed::Feed(const Feed& other) : RootItem(other) {
setKind(RootItemKind::Feed);
setKind(RootItem::Kind::Feed);
setCountOfAllMessages(other.countOfAllMessages());
setCountOfUnreadMessages(other.countOfUnreadMessages());
@ -67,13 +67,13 @@ QVariant Feed::data(int column, int role) const {
switch (role) {
case Qt::ForegroundRole:
switch (status()) {
case NewMessages:
case Status::NewMessages:
return QColor(Qt::blue);
case NetworkError:
case ParsingError:
case AuthError:
case OtherError:
case Status::NetworkError:
case Status::ParsingError:
case Status::AuthError:
case Status::OtherError:
return QColor(Qt::red);
default:
@ -102,8 +102,8 @@ void Feed::setCountOfAllMessages(int count_all_messages) {
}
void Feed::setCountOfUnreadMessages(int count_unread_messages) {
if (status() == NewMessages && count_unread_messages < countOfUnreadMessages()) {
setStatus(Normal);
if (status() == Status::NewMessages && count_unread_messages < countOfUnreadMessages()) {
setStatus(Status::Normal);
}
m_unreadCount = count_unread_messages;
@ -209,7 +209,7 @@ int Feed::updateMessages(const QList<Message>& messages, bool error_during_obtai
}
if (ok) {
setStatus(updated_messages > 0 ? NewMessages : Normal);
setStatus(updated_messages > 0 ? Status::NewMessages : Status::Normal);
updateCounts(true);
if (getParentServiceRoot()->recycleBin() != nullptr && anything_updated) {
@ -238,13 +238,13 @@ QString Feed::getAutoUpdateStatusDescription() const {
QString auto_update_string;
switch (autoUpdateType()) {
case DontAutoUpdate:
case AutoUpdateType::DontAutoUpdate:
//: Describes feed auto-update status.
auto_update_string = tr("does not use auto-update");
break;
case DefaultAutoUpdate:
case AutoUpdateType::DefaultAutoUpdate:
//: Describes feed auto-update status.
auto_update_string = qApp->feedReader()->autoUpdateEnabled()
@ -254,7 +254,7 @@ QString Feed::getAutoUpdateStatusDescription() const {
: tr("uses global settings (global feed auto-updating is disabled)");
break;
case SpecificAutoUpdate:
case AutoUpdateType::SpecificAutoUpdate:
default:
//: Describes feed auto-update status.

View File

@ -18,7 +18,7 @@ class Feed : public RootItem {
public:
// Specifies the auto-update strategy for the feed.
enum AutoUpdateType {
enum class AutoUpdateType {
DontAutoUpdate = 0,
DefaultAutoUpdate = 1,
SpecificAutoUpdate = 2
@ -27,7 +27,7 @@ class Feed : public RootItem {
// Specifies the actual "status" of the feed.
// For example if it has new messages, error
// occurred, and so on.
enum Status {
enum class Status {
Normal = 0,
NewMessages = 1,
NetworkError = 2,

View File

@ -59,10 +59,10 @@ int FormFeedDetails::addEditFeed(Feed* input_feed, RootItem* parent_to_select, c
}
if (parent_to_select != nullptr) {
if (parent_to_select->kind() == RootItemKind::Category) {
if (parent_to_select->kind() == RootItem::Kind::Category) {
m_ui->m_cmbParentCategory->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select)));
}
else if (parent_to_select->kind() == RootItemKind::Feed) {
else if (parent_to_select->kind() == RootItem::Kind::Feed) {
int target_item = m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select->parent()));
if (target_item >= 0) {
@ -153,8 +153,8 @@ void FormFeedDetails::onAutoUpdateTypeChanged(int new_index) {
Feed::AutoUpdateType auto_update_type = static_cast<Feed::AutoUpdateType>(m_ui->m_cmbAutoUpdateType->itemData(new_index).toInt());
switch (auto_update_type) {
case Feed::DontAutoUpdate:
case Feed::DefaultAutoUpdate:
case Feed::AutoUpdateType::DontAutoUpdate:
case Feed::AutoUpdateType::DefaultAutoUpdate:
m_ui->m_spinAutoUpdateInterval->setEnabled(false);
break;
@ -315,10 +315,10 @@ void FormFeedDetails::initialize() {
m_ui->m_txtPassword->lineEdit()->setToolTip(tr("Set password to access the feed."));
// Add standard feed types.
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Atom10), QVariant::fromValue((int) StandardFeed::Atom10));
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Rdf), QVariant::fromValue((int) StandardFeed::Rdf));
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Rss0X), QVariant::fromValue((int) StandardFeed::Rss0X));
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Rss2X), QVariant::fromValue((int) StandardFeed::Rss2X));
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Atom10), QVariant::fromValue(int(StandardFeed::Type::Atom10)));
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rdf), QVariant::fromValue(int(StandardFeed::Type::Rdf)));
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rss0X), QVariant::fromValue(int(StandardFeed::Type::Rss0X)));
m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rss2X), QVariant::fromValue(int(StandardFeed::Type::Rss2X)));
// Load available encodings.
const QList<QByteArray> encodings = QTextCodec::availableCodecs();
@ -358,9 +358,12 @@ void FormFeedDetails::initialize() {
// Setup auto-update options.
m_ui->m_spinAutoUpdateInterval->setValue(DEFAULT_AUTO_UPDATE_INTERVAL);
m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update using global interval"), QVariant::fromValue((int) Feed::DefaultAutoUpdate));
m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update every"), QVariant::fromValue((int) Feed::SpecificAutoUpdate));
m_ui->m_cmbAutoUpdateType->addItem(tr("Do not auto-update at all"), QVariant::fromValue((int) Feed::DontAutoUpdate));
m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update using global interval"),
QVariant::fromValue(int(Feed::AutoUpdateType::DefaultAutoUpdate)));
m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update every"),
QVariant::fromValue(int(Feed::AutoUpdateType::SpecificAutoUpdate)));
m_ui->m_cmbAutoUpdateType->addItem(tr("Do not auto-update at all"),
QVariant::fromValue(int(Feed::AutoUpdateType::DontAutoUpdate)));
// Set tab order.
setTabOrder(m_ui->m_cmbParentCategory, m_ui->m_cmbType);

View File

@ -11,7 +11,7 @@
#include <QThread>
ImportantNode::ImportantNode(RootItem* parent_item) : RootItem(parent_item) {
setKind(RootItemKind::Important);
setKind(RootItem::Kind::Important);
setId(ID_IMPORTANT);
setIcon(qApp->icons()->fromTheme(QSL("mail-mark-important")));
setTitle(tr("Important messages"));
@ -67,7 +67,7 @@ bool ImportantNode::markAsReadUnread(RootItem::ReadStatus status) {
if (DatabaseQueries::markImportantMessagesReadUnread(database, service->accountId(), status)) {
service->updateCounts(true);
service->itemChanged(getSubTree());
service->requestReloadMessageList(status == RootItem::Read);
service->requestReloadMessageList(status == RootItem::ReadStatus::Read);
return true;
}
else {

View File

@ -13,7 +13,7 @@
RecycleBin::RecycleBin(RootItem* parent_item) : RootItem(parent_item), m_totalCount(0),
m_unreadCount(0) {
setKind(RootItemKind::Bin);
setKind(RootItem::Kind::Bin);
setId(ID_RECYCLE_BIN);
setIcon(qApp->icons()->fromTheme(QSL("user-trash")));
setTitle(tr("Recycle bin"));
@ -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"),
@ -84,7 +84,7 @@ bool RecycleBin::markAsReadUnread(RootItem::ReadStatus status) {
if (DatabaseQueries::markBinReadUnread(database, parent_root->accountId(), status)) {
updateCounts(false);
parent_root->itemChanged(QList<RootItem*>() << this);
parent_root->requestReloadMessageList(status == RootItem::Read);
parent_root->requestReloadMessageList(status == RootItem::ReadStatus::Read);
return true;
}
else {

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

@ -2,6 +2,7 @@
#include "services/abstract/rootitem.h"
#include "3rd-party/boolinq/boolinq.h"
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include "services/abstract/category.h"
@ -12,7 +13,7 @@
#include <QVariant>
RootItem::RootItem(RootItem* parent_item)
: QObject(nullptr), m_kind(RootItemKind::Root), m_id(NO_PARENT_CATEGORY), m_customId(QL1S("")),
: QObject(nullptr), m_kind(RootItem::Kind::Root), m_id(NO_PARENT_CATEGORY), m_customId(QL1S("")),
m_title(QString()), m_description(QString()), m_keepOnTop(false), m_parentItem(parent_item) {}
RootItem::RootItem(const RootItem& other) : RootItem(nullptr) {
@ -36,7 +37,7 @@ QString RootItem::hashCode() const {
return
QString::number(acc_id) + QL1S("-") +
QString::number(kind()) + QL1S("-") +
QString::number(int(kind())) + QL1S("-") +
QString::number(id());
}
@ -44,7 +45,7 @@ QString RootItem::additionalTooltip() const {
return QString();
}
QList<QAction*> RootItem::contextMenu() {
QList<QAction*> RootItem::contextMenuFeedsList() {
return QList<QAction*>();
}
@ -88,7 +89,7 @@ bool RootItem::cleanMessages(bool clear_only_read) {
bool result = true;
for (RootItem* child : m_childItems) {
if (child->kind() != RootItemKind::Bin) {
if (child->kind() != RootItem::Kind::Bin) {
result &= child->cleanMessages(clear_only_read);
}
}
@ -173,10 +174,10 @@ QVariant RootItem::data(int column, int role) const {
QIcon ico = icon();
if (ico.isNull()) {
if (kind() == RootItemKind::Feed) {
if (kind() == RootItem::Kind::Feed) {
return qApp->icons()->fromTheme(QSL("application-rss+xml"));
}
else if (kind() == RootItemKind::Category) {
else if (kind() == RootItem::Kind::Category) {
return qApp->icons()->fromTheme(QSL("folder"));
}
}
@ -210,27 +211,15 @@ bool RootItem::performDragDropChange(RootItem* target_item) {
}
int RootItem::countOfUnreadMessages() const {
int total_count = 0;
for (RootItem* child_item : m_childItems) {
if (child_item->kind() != RootItemKind::Kind::Important) {
total_count += child_item->countOfUnreadMessages();
}
}
return total_count;
return boolinq::from(m_childItems).sum([](RootItem* it) {
return it->kind() == RootItem::Kind::Important ? 0 : it->countOfUnreadMessages();
});
}
int RootItem::countOfAllMessages() const {
int total_count = 0;
for (RootItem* child_item : m_childItems) {
if (child_item->kind() != RootItemKind::Kind::Important) {
total_count += child_item->countOfAllMessages();
}
}
return total_count;
return boolinq::from(m_childItems).sum([](RootItem* it) {
return it->kind() == RootItem::Kind::Important ? 0 : it->countOfAllMessages();
});
}
bool RootItem::isChildOf(const RootItem* root) const {
@ -240,7 +229,7 @@ bool RootItem::isChildOf(const RootItem* root) const {
const RootItem* this_item = this;
while (this_item->kind() != RootItemKind::Root) {
while (this_item->kind() != RootItem::Kind::Root) {
if (root->childItems().contains(const_cast<RootItem* const>(this_item))) {
return true;
}
@ -278,7 +267,7 @@ QList<RootItem*> RootItem::getSubTree() const {
return children;
}
QList<RootItem*> RootItem::getSubTree(RootItemKind::Kind kind_of_item) const {
QList<RootItem*> RootItem::getSubTree(RootItem::Kind kind_of_item) const {
QList<RootItem*> children;
QList<RootItem*> traversable_items;
@ -288,7 +277,7 @@ QList<RootItem*> RootItem::getSubTree(RootItemKind::Kind kind_of_item) const {
while (!traversable_items.isEmpty()) {
RootItem* active_item = traversable_items.takeFirst();
if ((active_item->kind() & kind_of_item) > 0) {
if (int(active_item->kind() & kind_of_item) > 0) {
children.append(active_item);
}
@ -308,7 +297,7 @@ QList<Category*> RootItem::getSubTreeCategories() const {
while (!traversable_items.isEmpty()) {
RootItem* active_item = traversable_items.takeFirst();
if (active_item->kind() == RootItemKind::Category) {
if (active_item->kind() == RootItem::Kind::Category) {
children.append(active_item->toCategory());
}
@ -328,7 +317,7 @@ QHash<int, Category*> RootItem::getHashedSubTreeCategories() const {
while (!traversable_items.isEmpty()) {
RootItem* active_item = traversable_items.takeFirst();
if (active_item->kind() == RootItemKind::Category && !children.contains(active_item->id())) {
if (active_item->kind() == RootItem::Kind::Category && !children.contains(active_item->id())) {
children.insert(active_item->id(), active_item->toCategory());
}
@ -348,7 +337,7 @@ QHash<QString, Feed*> RootItem::getHashedSubTreeFeeds() const {
while (!traversable_items.isEmpty()) {
RootItem* active_item = traversable_items.takeFirst();
if (active_item->kind() == RootItemKind::Feed && !children.contains(active_item->customId())) {
if (active_item->kind() == RootItem::Kind::Feed && !children.contains(active_item->customId())) {
children.insert(active_item->customId(), active_item->toFeed());
}
@ -368,7 +357,7 @@ QList<Feed*> RootItem::getSubTreeFeeds() const {
while (!traversable_items.isEmpty()) {
RootItem* active_item = traversable_items.takeFirst();
if (active_item->kind() == RootItemKind::Feed) {
if (active_item->kind() == RootItem::Kind::Feed) {
children.append(active_item->toFeed());
}
@ -381,8 +370,8 @@ QList<Feed*> RootItem::getSubTreeFeeds() const {
ServiceRoot* RootItem::getParentServiceRoot() const {
const RootItem* working_parent = this;
while (working_parent->kind() != RootItemKind::Root) {
if (working_parent->kind() == RootItemKind::ServiceRoot) {
while (working_parent->kind() != RootItem::Kind::Root) {
if (working_parent->kind() == RootItem::Kind::ServiceRoot) {
return working_parent->toServiceRoot();
}
else {
@ -393,11 +382,11 @@ ServiceRoot* RootItem::getParentServiceRoot() const {
return nullptr;
}
RootItemKind::Kind RootItem::kind() const {
RootItem::Kind RootItem::kind() const {
return m_kind;
}
void RootItem::setKind(RootItemKind::Kind kind) {
void RootItem::setKind(RootItem::Kind kind) {
m_kind = kind;
}
@ -518,3 +507,11 @@ QDataStream& operator<<(QDataStream& out, const RootItem::Importance& myObj) {
return out;
}
RootItem::Kind operator|(RootItem::Kind a, RootItem::Kind b) {
return static_cast<RootItem::Kind>(static_cast<int>(a) | static_cast<int>(b));
}
RootItem::Kind operator&(RootItem::Kind a, RootItem::Kind b) {
return static_cast<RootItem::Kind>(static_cast<int>(a) & static_cast<int>(b));
}

View File

@ -14,24 +14,6 @@ class Feed;
class ServiceRoot;
class QAction;
namespace RootItemKind {
// Describes the kind of the item.
enum Kind {
Root = 1,
Bin = 2,
Feed = 4,
Category = 8,
ServiceRoot = 16,
Labels = 32,
Important = 64
};
inline Kind operator|(Kind a, Kind b) {
return static_cast<Kind>(static_cast<int>(a) | static_cast<int>(b));
}
}
// Represents ROOT item of FeedsModel.
// NOTE: This class is derived to add functionality for
// all other non-root items of FeedsModel.
@ -39,18 +21,29 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
Q_OBJECT
public:
enum ReadStatus {
enum class ReadStatus {
Unread = 0,
Read = 1
};
// Holds statuses for messages
// to be switched importance (starred).
enum Importance {
enum class Importance {
NotImportant = 0,
Important = 1
};
// Describes the kind of the item.
enum class Kind {
Root = 1,
Bin = 2,
Feed = 4,
Category = 8,
ServiceRoot = 16,
Labels = 32,
Important = 64
};
// Constructors and destructors.
explicit RootItem(RootItem* parent_item = nullptr);
explicit RootItem(const RootItem& other);
@ -63,7 +56,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;
@ -159,7 +152,7 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
// Returns flat list of all items from subtree where this item is a root.
// Returned list includes this item too.
QList<RootItem*> getSubTree() const;
QList<RootItem*> getSubTree(RootItemKind::Kind kind_of_item) const;
QList<RootItem*> getSubTree(RootItem::Kind kind_of_item) const;
QList<Category*> getSubTreeCategories() const;
// Returns list of categories complemented by their own integer primary ID.
@ -172,8 +165,8 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
// Returns the service root node which is direct or indirect parent of current item.
ServiceRoot* getParentServiceRoot() const;
RootItemKind::Kind kind() const;
void setKind(RootItemKind::Kind kind);
RootItem::Kind kind() const;
void setKind(RootItem::Kind kind);
// Each item can have icon.
QIcon icon() const;
@ -208,7 +201,7 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
void setKeepOnTop(bool keep_on_top);
private:
RootItemKind::Kind m_kind;
RootItem::Kind m_kind;
int m_id;
QString m_customId;
QString m_title;
@ -220,6 +213,9 @@ class RSSGUARD_DLLSPEC RootItem : public QObject {
RootItem* m_parentItem;
};
RootItem::Kind operator|(RootItem::Kind a, RootItem::Kind b);
RootItem::Kind operator&(RootItem::Kind a, RootItem::Kind b);
QDataStream& operator<<(QDataStream& out, const RootItem::Importance& myObj);
QDataStream& operator>>(QDataStream& in, RootItem::Importance& myObj);
QDataStream& operator<<(QDataStream& out, const RootItem::ReadStatus& myObj);

View File

@ -16,7 +16,7 @@
ServiceRoot::ServiceRoot(RootItem* parent)
: RootItem(parent), m_recycleBin(new RecycleBin(this)), m_importantNode(new ImportantNode(this)), m_accountId(NO_PARENT_CATEGORY) {
setKind(RootItemKind::ServiceRoot);
setKind(RootItem::Kind::ServiceRoot);
setCreationDate(QDateTime::currentDateTime());
}
@ -47,7 +47,7 @@ bool ServiceRoot::markAsReadUnread(RootItem::ReadStatus status) {
if (DatabaseQueries::markAccountReadUnread(database, accountId(), status)) {
updateCounts(false);
itemChanged(getSubTree());
requestReloadMessageList(status == RootItem::Read);
requestReloadMessageList(status == RootItem::ReadStatus::Read);
return true;
}
else {
@ -68,12 +68,27 @@ bool ServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const {
return false;
}
QList<QAction*> ServiceRoot::contextMenu() {
QList<QAction*> ServiceRoot::contextMenuFeedsList() {
return serviceMenu();
}
QList<QAction*> ServiceRoot::contextMenuMessagesList(const QList<Message>& messages) {
Q_UNUSED(messages)
return {};
}
QList<QAction*> ServiceRoot::serviceMenu() {
return QList<QAction*>();
if (m_serviceMenu.isEmpty() && isSyncable()) {
m_actionSyncIn = new QAction(qApp->icons()->fromTheme(QSL("view-refresh")), tr("Sync in"), this);
connect(m_actionSyncIn, &QAction::triggered, this, &ServiceRoot::syncIn);
m_serviceMenu.append(m_actionSyncIn);
}
return m_serviceMenu;
}
bool ServiceRoot::isSyncable() const {
return false;
}
void ServiceRoot::start(bool freshly_activated) {
@ -86,10 +101,10 @@ void ServiceRoot::updateCounts(bool including_total_count) {
QList<Feed*> feeds;
for (RootItem* child : getSubTree()) {
if (child->kind() == RootItemKind::Feed) {
if (child->kind() == RootItem::Kind::Feed) {
feeds.append(child->toFeed());
}
else if (child->kind() != RootItemKind::Category && child->kind() != RootItemKind::ServiceRoot) {
else if (child->kind() != RootItem::Kind::Category && child->kind() != RootItem::Kind::ServiceRoot) {
child->updateCounts(including_total_count);
}
}
@ -139,7 +154,7 @@ void ServiceRoot::removeOldAccountFromDatabase(bool including_messages) {
void ServiceRoot::cleanAllItemsFromModel() {
for (RootItem* top_level_item : childItems()) {
if (top_level_item->kind() != RootItemKind::Bin && top_level_item->kind() != RootItemKind::Important) {
if (top_level_item->kind() != RootItem::Kind::Bin && top_level_item->kind() != RootItem::Kind::Important) {
requestItemRemoval(top_level_item);
}
}
@ -264,7 +279,7 @@ QMap<QString, QVariantMap> ServiceRoot::storeCustomFeedsData() {
QVariantMap feed_custom_data;
feed_custom_data.insert(QSL("auto_update_interval"), feed->autoUpdateInitialInterval());
feed_custom_data.insert(QSL("auto_update_type"), feed->autoUpdateType());
feed_custom_data.insert(QSL("auto_update_type"), int(feed->autoUpdateType()));
feed_custom_data.insert(QSL("msg_filters"), QVariant::fromValue(feed->messageFilters()));
custom_data.insert(feed->customId(), feed_custom_data);
}
@ -351,7 +366,7 @@ QStringList ServiceRoot::customIDSOfMessagesForItem(RootItem* item) {
QStringList list;
switch (item->kind()) {
case RootItemKind::Category: {
case RootItem::Kind::Category: {
for (RootItem* child : item->childItems()) {
list.append(customIDSOfMessagesForItem(child));
}
@ -359,28 +374,28 @@ QStringList ServiceRoot::customIDSOfMessagesForItem(RootItem* item) {
return list;
}
case RootItemKind::ServiceRoot: {
case RootItem::Kind::ServiceRoot: {
QSqlDatabase database = qApp->database()->connection(metaObject()->className());
list = DatabaseQueries::customIdsOfMessagesFromAccount(database, accountId());
break;
}
case RootItemKind::Bin: {
case RootItem::Kind::Bin: {
QSqlDatabase database = qApp->database()->connection(metaObject()->className());
list = DatabaseQueries::customIdsOfMessagesFromBin(database, accountId());
break;
}
case RootItemKind::Feed: {
case RootItem::Kind::Feed: {
QSqlDatabase database = qApp->database()->connection(metaObject()->className());
list = DatabaseQueries::customIdsOfMessagesFromFeed(database, item->customId(), accountId());
break;
}
case RootItemKind::Important: {
case RootItem::Kind::Important: {
QSqlDatabase database = qApp->database()->connection(metaObject()->className());
list = DatabaseQueries::customIdsOfImportantMessages(database, accountId());
@ -415,7 +430,7 @@ bool ServiceRoot::markFeedsReadUnread(QList<Feed*> items, RootItem::ReadStatus r
}
itemChanged(itemss);
requestReloadMessageList(read == RootItem::Read);
requestReloadMessageList(read == RootItem::ReadStatus::Read);
return true;
}
else {
@ -476,11 +491,11 @@ void ServiceRoot::setAccountId(int account_id) {
}
bool ServiceRoot::loadMessagesForItem(RootItem* item, MessagesModel* model) {
if (item->kind() == RootItemKind::Bin) {
if (item->kind() == RootItem::Kind::Bin) {
model->setFilter(QString("Messages.is_deleted = 1 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1")
.arg(QString::number(accountId())));
}
else if (item->kind() == RootItemKind::Kind::Important) {
else if (item->kind() == RootItem::Kind::Important) {
model->setFilter(QString("Messages.is_important = 1 AND Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1")
.arg(QString::number(accountId())));
}
@ -554,7 +569,7 @@ bool ServiceRoot::onBeforeSwitchMessageImportance(RootItem* selected_item, const
QList<Message> mark_unstarred_msgs;
for (const ImportanceChange& pair : changes) {
if (pair.second == RootItem::Important) {
if (pair.second == RootItem::Importance::Important) {
mark_starred_msgs.append(pair.first);
}
else {
@ -563,11 +578,11 @@ bool ServiceRoot::onBeforeSwitchMessageImportance(RootItem* selected_item, const
}
if (!mark_starred_msgs.isEmpty()) {
cache->addMessageStatesToCache(mark_starred_msgs, RootItem::Important);
cache->addMessageStatesToCache(mark_starred_msgs, RootItem::Importance::Important);
}
if (!mark_unstarred_msgs.isEmpty()) {
cache->addMessageStatesToCache(mark_unstarred_msgs, RootItem::NotImportant);
cache->addMessageStatesToCache(mark_unstarred_msgs, RootItem::Importance::NotImportant);
}
}

View File

@ -53,14 +53,20 @@ class ServiceRoot : public RootItem {
// NOTE: Caller does NOT take ownership of created menu!
virtual QList<QAction*> addItemMenu();
// Returns actions to display as context menu.
QList<QAction*> contextMenu();
// 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'".
// NOTE: Caller does NOT take ownership of created menu!
virtual QList<QAction*> serviceMenu();
// If plugin uses online synchronization, then returns true.
virtual bool isSyncable() const;
// Start/stop services.
// Start method is called when feed model gets initialized OR after user adds new service.
// Account should synchronously initialize its children (load them from DB is recommended
@ -204,10 +210,12 @@ class ServiceRoot : public RootItem {
virtual QMap<QString, QVariantMap> storeCustomFeedsData();
virtual void restoreCustomFeedsData(const QMap<QString, QVariantMap>& data, const QHash<QString, Feed*>& feeds);
private:
protected:
RecycleBin* m_recycleBin;
ImportantNode* m_importantNode;
int m_accountId;
QAction* m_actionSyncIn;
QList<QAction*> m_serviceMenu;
};
#endif // SERVICEROOT_H

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,8 +133,26 @@ bool GmailServiceRoot::downloadAttachmentOnMyOwn(const QUrl& url) const {
}
}
QList<QAction*> GmailServiceRoot::contextMenuMessagesList(const QList<Message>& messages) {
if (messages.size() == 1) {
m_replyToMessage = messages.at(0);
if (m_actionReply == nullptr) {
m_actionReply = new QAction(qApp->icons()->fromTheme(QSL("mail-reply-sender")), tr("Reply to this message"), this);
connect(m_actionReply, &QAction::triggered, this, &GmailServiceRoot::replyToEmail);
}
return { m_actionReply };
}
else {
return {};
}
}
QList<QAction*> GmailServiceRoot::serviceMenu() {
if (m_serviceMenu.isEmpty()) {
ServiceRoot::serviceMenu();
QAction* act_new_email = new QAction(qApp->icons()->fromTheme(QSL("mail-message-new")), tr("Write new e-mail message"), this);
connect(act_new_email, &QAction::triggered, this, &GmailServiceRoot::writeNewEmail);
@ -139,6 +162,10 @@ QList<QAction*> GmailServiceRoot::serviceMenu() {
return m_serviceMenu;
}
bool GmailServiceRoot::isSyncable() const {
return true;
}
bool GmailServiceRoot::canBeEdited() const {
return true;
}

View File

@ -22,7 +22,9 @@ 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;
bool editViaGui();
bool canBeDeleted() const;
@ -40,6 +42,9 @@ class GmailServiceRoot : public ServiceRoot, public CacheForServiceRoot {
public slots:
void updateTitle();
private slots:
void replyToEmail();
protected:
RootItem* obtainNewTreeForSyncIn() const;
@ -48,9 +53,9 @@ class GmailServiceRoot : public ServiceRoot, public CacheForServiceRoot {
void loadFromDatabase();
private:
QList<QAction*> m_serviceMenu;
GmailNetworkFactory* m_network;
QAction* m_actionReply;
Message m_replyToMessage;
};
inline void GmailServiceRoot::setNetwork(GmailNetworkFactory* network) {

View File

@ -61,5 +61,9 @@ void EmailRecipientControl::setPossibleRecipients(const QStringList& rec) {
QCompleter* cmpl = new QCompleter(rec, m_txtRecipient);
cmpl->setFilterMode(Qt::MatchFlag::MatchContains);
cmpl->setCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive);
cmpl->setCompletionMode(QCompleter::CompletionMode::UnfilteredPopupCompletion);
m_txtRecipient->setCompleter(cmpl);
}

View File

@ -7,12 +7,18 @@
#include "gui/guiutilities.h"
#include "gui/messagebox.h"
#include "miscellaneous/application.h"
#include "miscellaneous/databasequeries.h"
#include "miscellaneous/iconfactory.h"
#include "services/gmail/gmailserviceroot.h"
#include "services/gmail/gui/emailrecipientcontrol.h"
#include "services/gmail/network/gmailnetworkfactory.h"
FormAddEditEmail::FormAddEditEmail(GmailServiceRoot* root, QWidget* parent) : QDialog(parent), m_root(root) {
#include <QtConcurrent/QtConcurrentRun>
#include <QCloseEvent>
FormAddEditEmail::FormAddEditEmail(GmailServiceRoot* root, QWidget* parent)
: QDialog(parent), m_root(root), m_originalMessage(nullptr), m_possibleRecipients({}) {
m_ui.setupUi(this);
GuiUtilities::applyDialogProperties(*this, qApp->icons()->fromTheme(QSL("mail-message-new")));
@ -32,6 +38,14 @@ FormAddEditEmail::FormAddEditEmail(GmailServiceRoot* root, QWidget* parent) : QD
&QPushButton::clicked,
this,
&FormAddEditEmail::onOkClicked);
QSqlDatabase db = qApp->database()->connection(metaObject()->className());
m_possibleRecipients = DatabaseQueries::getAllRecipients(db, m_root->accountId());
for (auto* rec: recipientControls()) {
rec->setPossibleRecipients(m_possibleRecipients);
}
}
void FormAddEditEmail::execForAdd() {
@ -39,6 +53,14 @@ void FormAddEditEmail::execForAdd() {
exec();
}
void FormAddEditEmail::execForReply(Message* original_message) {
m_originalMessage = original_message;
addRecipientRow(m_originalMessage->m_author);
m_ui.m_txtSubject->setText(QSL("Re: %1").arg(m_originalMessage->m_title));
exec();
}
void FormAddEditEmail::removeRecipientRow() {
auto* sndr = static_cast<EmailRecipientControl*>(sender());
@ -103,15 +125,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) {
@ -127,13 +142,7 @@ void FormAddEditEmail::addRecipientRow(const QString& recipient) {
connect(mail_rec, &EmailRecipientControl::removalRequested, this, &FormAddEditEmail::removeRecipientRow);
try {
QStringList rec = m_root->network()->getAllRecipients();
mail_rec->setPossibleRecipients(rec);
}
catch (const ApplicationException& ex) {}
mail_rec->setPossibleRecipients(m_possibleRecipients);
m_ui.m_layout->insertRow(m_ui.m_layout->count() - 5, mail_rec);
}

View File

@ -23,6 +23,7 @@ class FormAddEditEmail : public QDialog {
public slots:
void execForAdd();
void execForReply(Message* original_message);
private slots:
void removeRecipientRow();
@ -38,6 +39,7 @@ class FormAddEditEmail : public QDialog {
Ui::FormAddEditEmail m_ui;
QList<EmailRecipientControl*> m_recipientControls;
Message* m_originalMessage;
QStringList m_possibleRecipients;
};
#endif // FORMADDEDITEMAIL_H

View File

@ -52,11 +52,29 @@ 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"));
//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")).toStdString();
msg["In-Reply-To"] = metadata.value(QSL("Message-ID")).toStdString();
}
}
QString rfc_email = QString::fromStdString(msg.to_string());
@ -415,89 +433,45 @@ bool GmailNetworkFactory::fillFullMessage(Message& msg, const QJsonObject& json,
return true;
}
QStringList GmailNetworkFactory::getAllRecipients() {
QString bearer = m_oauth2->bearer().toLocal8Bit();
QMap<QString, QString> GmailNetworkFactory::getMessageMetadata(const QString& msg_id, const QStringList& metadata) {
QString bearer = m_oauth2->bearer();
if (bearer.isEmpty()) {
throw ApplicationException(tr("not logged-in"));
throw ApplicationException(tr("you are not logged in"));
}
QStringList recipients;
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(),
m_oauth2->bearer().toLocal8Bit()));
headers.append(QPair<QByteArray, QByteArray>(QString(HTTP_HEADERS_CONTENT_TYPE).toLocal8Bit(),
QString(GMAIL_CONTENT_TYPE_JSON).toLocal8Bit()));
bearer.toLocal8Bit()));
int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
QByteArray msg_list_data;
// TODO: Cyklicky!!
auto list_res = NetworkFactory::performNetworkOperation(GMAIL_API_MSGS_LIST,
timeout,
QByteArray(),
msg_list_data,
QNetworkAccessManager::Operation::GetOperation,
headers);
if (list_res.first != QNetworkReply::NetworkError::NoError) {
throw ApplicationException(tr("comm error when asking for recipients"));
}
QJsonDocument json_list = QJsonDocument::fromJson(msg_list_data);
QStringList message_ids;
for (const auto& msg_nod : json_list.object()["messages"].toArray()) {
message_ids.append(msg_nod.toObject()["id"].toString());
}
auto* multi = new QHttpMultiPart();
multi->setContentType(QHttpMultiPart::ContentType::MixedType);
for (const QString& msg : message_ids) {
QHttpPart part;
part.setRawHeader(HTTP_HEADERS_CONTENT_TYPE, GMAIL_CONTENT_TYPE_HTTP);
QString full_msg_endpoint = QString("GET /gmail/v1/users/me/messages/%1?metadataHeaders=From&metadataHeaders=To&format=metadata\r\n").arg(msg);
part.setBody(full_msg_endpoint.toUtf8());
multi->append(part);
}
QList<HttpResponse> output;
headers.removeLast();
NetworkResult res = NetworkFactory::performNetworkOperation(GMAIL_API_BATCH,
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,
multi,
QByteArray(),
output,
QNetworkAccessManager::Operation::PostOperation,
QNetworkAccessManager::Operation::GetOperation,
headers);
if (res.first == QNetworkReply::NetworkError::NoError) {
// We parse each part of HTTP response (it contains HTTP headers and payload with msg full data).
for (const HttpResponse& part : output) {
QJsonObject msg_doc = QJsonDocument::fromJson(part.body().toUtf8()).object();
auto headers = msg_doc["payload"].toObject()["headers"].toArray();
QJsonDocument doc = QJsonDocument::fromJson(output);
QMap<QString, QString> result;
auto headers = doc.object()["payload"].toObject()["headers"].toArray();
if (headers.size() >= 2) {
for (const auto& head : headers) {
auto val = head.toObject()["value"].toString();
for (const auto& header : headers) {
QJsonObject obj_header = header.toObject();
if (!recipients.contains(val)) {
recipients.append(val);
}
}
}
result.insert(obj_header["name"].toString(), obj_header["value"].toString());
}
return recipients;
return result;
}
else {
throw ApplicationException(tr("comm error when asking for recipients"));
throw ApplicationException(tr("failed to get metadata"));
}
}

View File

@ -36,10 +36,7 @@ class GmailNetworkFactory : public QObject {
void setBatchSize(int batch_size);
// Sends e-mail, returns its ID.
QString sendEmail(const Mimesis::Message& msg);
// Returns all possible recipients.
QStringList getAllRecipients();
QString sendEmail(Mimesis::Message msg, Message* reply_to_message = nullptr);
Downloader* downloadAttachment(const QString& msg_id, const QString& attachment_id);
@ -53,6 +50,7 @@ class GmailNetworkFactory : public QObject {
private:
bool fillFullMessage(Message& msg, const QJsonObject& json, const QString& feed_id);
QMap<QString, QString> 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

@ -82,6 +82,10 @@ void InoreaderServiceRoot::saveAccountDataToDatabase() {
}
}
bool InoreaderServiceRoot::isSyncable() const {
return true;
}
bool InoreaderServiceRoot::canBeEdited() const {
return true;
}
@ -119,17 +123,6 @@ void InoreaderServiceRoot::stop() {
saveCacheToFile(accountId());
}
QList<QAction*> InoreaderServiceRoot::serviceMenu() {
if (m_serviceMenu.isEmpty()) {
QAction* act_sync_in = new QAction(qApp->icons()->fromTheme(QSL("view-refresh")), tr("Sync in"), this);
connect(act_sync_in, &QAction::triggered, this, &InoreaderServiceRoot::syncIn);
m_serviceMenu.append(act_sync_in);
}
return m_serviceMenu;
}
QString InoreaderServiceRoot::code() const {
return InoreaderEntryPoint().code();
}

View File

@ -20,6 +20,7 @@ class InoreaderServiceRoot : public ServiceRoot, public CacheForServiceRoot {
void setNetwork(InoreaderNetworkFactory* network);
InoreaderNetworkFactory* network() const;
bool isSyncable() const;
bool canBeEdited() const;
bool editViaGui();
bool canBeDeleted() const;
@ -43,10 +44,8 @@ class InoreaderServiceRoot : public ServiceRoot, public CacheForServiceRoot {
private:
void loadFromDatabase();
QList<QAction*> serviceMenu();
private:
QList<QAction*> m_serviceMenu;
InoreaderNetworkFactory* m_network;
};

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

@ -52,7 +52,7 @@ void FormOwnCloudFeedDetails::apply() {
else {
const RootItem* parent = static_cast<RootItem*>(m_ui->m_cmbParentCategory->itemData(
m_ui->m_cmbParentCategory->currentIndex()).value<void*>());
const int category_id = parent->kind() == RootItemKind::ServiceRoot ? 0 : parent->customId().toInt();
const int category_id = parent->kind() == RootItem::Kind::ServiceRoot ? 0 : parent->customId().toInt();
const bool response = qobject_cast<OwnCloudServiceRoot*>(m_serviceRoot)->network()->createFeed(m_ui->m_txtUrl->lineEdit()->text(),
category_id);

View File

@ -120,6 +120,8 @@ OwnCloudStatusResponse OwnCloudNetworkFactory::status() {
headers);
OwnCloudStatusResponse status_response(QString::fromUtf8(result_raw));
qDebugNN << "Raw status data is:" << result_raw;
if (network_reply.first != QNetworkReply::NoError) {
qWarning("Nextcloud: Obtaining status info failed with error %d.", network_reply.first);
}
@ -329,7 +331,7 @@ void OwnCloudNetworkFactory::markMessagesRead(RootItem::ReadStatus status, const
QJsonArray ids;
QString final_url;
if (status == RootItem::Read) {
if (status == RootItem::ReadStatus::Read) {
final_url = m_fixedUrl + OWNCLOUD_API_PATH + "items/read/multiple";
}
else {
@ -375,7 +377,7 @@ void OwnCloudNetworkFactory::markMessagesStarred(RootItem::Importance importance
QJsonArray ids;
QString final_url;
if (importance == RootItem::Important) {
if (importance == RootItem::Importance::Important) {
final_url = m_fixedUrl + OWNCLOUD_API_PATH + "items/star/multiple";
}
else {

View File

@ -70,8 +70,8 @@ OwnCloudServiceRoot* OwnCloudFeed::serviceRoot() const {
QList<Message> OwnCloudFeed::obtainNewMessages(bool* error_during_obtaining) {
OwnCloudGetMessagesResponse messages = serviceRoot()->network()->getMessages(customNumericId());
if (serviceRoot()->network()->lastError() != QNetworkReply::NoError) {
setStatus(Feed::NetworkError);
if (serviceRoot()->network()->lastError() != QNetworkReply::NetworkError::NoError) {
setStatus(Feed::Status::NetworkError);
*error_during_obtaining = true;
serviceRoot()->itemChanged(QList<RootItem*>() << this);
return QList<Message>();

View File

@ -17,7 +17,7 @@
#include "services/owncloud/owncloudserviceentrypoint.h"
OwnCloudServiceRoot::OwnCloudServiceRoot(RootItem* parent)
: ServiceRoot(parent), m_actionSyncIn(nullptr), m_network(new OwnCloudNetworkFactory()) {
: ServiceRoot(parent), m_network(new OwnCloudNetworkFactory()) {
setIcon(OwnCloudServiceEntryPoint().icon());
}
@ -25,6 +25,10 @@ OwnCloudServiceRoot::~OwnCloudServiceRoot() {
delete m_network;
}
bool OwnCloudServiceRoot::isSyncable() const {
return true;
}
bool OwnCloudServiceRoot::canBeEdited() const {
return true;
}
@ -59,16 +63,6 @@ bool OwnCloudServiceRoot::supportsCategoryAdding() const {
return false;
}
QList<QAction*> OwnCloudServiceRoot::serviceMenu() {
if (m_serviceMenu.isEmpty()) {
m_actionSyncIn = new QAction(qApp->icons()->fromTheme(QSL("view-refresh")), tr("Sync in"), this);
connect(m_actionSyncIn, &QAction::triggered, this, &OwnCloudServiceRoot::syncIn);
m_serviceMenu.append(m_actionSyncIn);
}
return m_serviceMenu;
}
void OwnCloudServiceRoot::start(bool freshly_activated) {
Q_UNUSED(freshly_activated)
loadFromDatabase();

View File

@ -18,13 +18,13 @@ class OwnCloudServiceRoot : public ServiceRoot, public CacheForServiceRoot {
explicit OwnCloudServiceRoot(RootItem* parent = nullptr);
virtual ~OwnCloudServiceRoot();
bool isSyncable() const;
bool canBeEdited() const;
bool canBeDeleted() const;
bool editViaGui();
bool deleteViaGui();
bool supportsFeedAdding() const;
bool supportsCategoryAdding() const;
QList<QAction*> serviceMenu();
void start(bool freshly_activated);
void stop();
@ -42,12 +42,8 @@ class OwnCloudServiceRoot : public ServiceRoot, public CacheForServiceRoot {
private:
RootItem* obtainNewTreeForSyncIn() const;
void loadFromDatabase();
QAction* m_actionSyncIn;
QList<QAction*> m_serviceMenu;
OwnCloudNetworkFactory* m_network;
};

View File

@ -70,10 +70,10 @@ int FormStandardCategoryDetails::addEditCategory(StandardCategory* input_categor
// Load parent from suggested item.
if (parent_to_select != nullptr) {
if (parent_to_select->kind() == RootItemKind::Category) {
if (parent_to_select->kind() == RootItem::Kind::Category) {
m_ui->m_cmbParentCategory->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select)));
}
else if (parent_to_select->kind() == RootItemKind::Feed) {
else if (parent_to_select->kind() == RootItem::Kind::Feed) {
int target_item = m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select->parent()));
if (target_item >= 0) {

View File

@ -143,14 +143,14 @@ void FormStandardImportExport::selectExportFile() {
if (!selected_file.isEmpty()) {
if (selected_filter == filter_opml20) {
m_conversionType = OPML20;
m_conversionType = ConversionType::OPML20;
if (!selected_file.endsWith(QL1S(".opml"))) {
selected_file += QL1S(".opml");
}
}
else if (selected_filter == filter_txt_url_per_line) {
m_conversionType = TXTUrlPerLine;
m_conversionType = ConversionType::TxtUrlPerLine;
if (!selected_file.endsWith(QL1S(".txt"))) {
selected_file += QL1S(".txt");
@ -178,10 +178,10 @@ void FormStandardImportExport::selectImportFile() {
if (!selected_file.isEmpty()) {
if (selected_filter == filter_opml20) {
m_conversionType = OPML20;
m_conversionType = ConversionType::OPML20;
}
else if (selected_filter == filter_txt_url_per_line) {
m_conversionType = TXTUrlPerLine;
m_conversionType = ConversionType::TxtUrlPerLine;
}
m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::StatusType::Ok, QDir::toNativeSeparators(selected_file), tr("File is selected."));
@ -213,11 +213,11 @@ void FormStandardImportExport::parseImportFile(const QString& file_name, bool fe
}
switch (m_conversionType) {
case OPML20:
case ConversionType::OPML20:
m_model->importAsOPML20(input_data, fetch_metadata_online);
break;
case TXTUrlPerLine:
case ConversionType::TxtUrlPerLine:
m_model->importAsTxtURLPerLine(input_data, fetch_metadata_online);
break;
@ -246,11 +246,11 @@ void FormStandardImportExport::exportFeeds() {
bool result_export = false;
switch (m_conversionType) {
case OPML20:
case ConversionType::OPML20:
result_export = m_model->exportToOMPL20(result_data);
break;
case TXTUrlPerLine:
case ConversionType::TxtUrlPerLine:
result_export = m_model->exportToTxtURLPerLine(result_data);
break;

View File

@ -19,9 +19,9 @@ class FormStandardImportExport : public QDialog {
Q_OBJECT
public:
enum ConversionType {
enum class ConversionType {
OPML20 = 0,
TXTUrlPerLine = 1
TxtUrlPerLine = 1
};
// Constructors.

View File

@ -73,10 +73,10 @@ bool StandardCategory::removeItself() {
// Remove all child items (feeds and categories)
// from the database.
for (RootItem* child : childItems()) {
if (child->kind() == RootItemKind::Category) {
if (child->kind() == RootItem::Kind::Category) {
children_removed &= dynamic_cast<StandardCategory*>(child)->removeItself();
}
else if (child->kind() == RootItemKind::Feed) {
else if (child->kind() == RootItem::Kind::Feed) {
children_removed &= dynamic_cast<StandardFeed*>(child)->removeItself();
}
}

View File

@ -33,7 +33,7 @@ StandardFeed::StandardFeed(RootItem* parent_item)
m_username = QString();
m_password = QString();
m_networkError = QNetworkReply::NoError;
m_type = Rss0X;
m_type = Type::Rss0X;
m_encoding = QString();
}
@ -51,7 +51,7 @@ StandardFeed::~StandardFeed() {
qDebug("Destroying Feed instance.");
}
QList<QAction*> StandardFeed::contextMenu() {
QList<QAction*> StandardFeed::contextMenuFeedsList() {
return serviceRoot()->getContextMenuForFeed(this);
}
@ -77,6 +77,7 @@ StandardServiceRoot* StandardFeed::serviceRoot() const {
bool StandardFeed::editViaGui() {
QScopedPointer<FormStandardFeedDetails> form_pointer(new FormStandardFeedDetails(serviceRoot(), qApp->mainFormWidget()));
form_pointer.data()->addEditFeed(this, nullptr);
return false;
}
@ -93,16 +94,16 @@ bool StandardFeed::deleteViaGui() {
QString StandardFeed::typeToString(StandardFeed::Type type) {
switch (type) {
case Atom10:
case Type::Atom10:
return QSL("ATOM 1.0");
case Rdf:
case Type::Rdf:
return QSL("RDF (RSS 1.0)");
case Rss0X:
case Type::Rss0X:
return QSL("RSS 0.91/0.92/0.93");
case Rss2X:
case Type::Rss2X:
default:
return QSL("RSS 2.0/2.0.1");
}
@ -111,7 +112,7 @@ QString StandardFeed::typeToString(StandardFeed::Type type) {
void StandardFeed::fetchMetadataForItself() {
QPair<StandardFeed*, QNetworkReply::NetworkError> metadata = guessFeed(url(), username(), password());
if (metadata.first != nullptr && metadata.second == QNetworkReply::NoError) {
if (metadata.first != nullptr && metadata.second == QNetworkReply::NetworkError::NoError) {
// Some properties are not updated when new metadata are fetched.
metadata.first->setParent(parent());
metadata.first->setUrl(url());
@ -138,10 +139,11 @@ QPair<StandardFeed*, QNetworkReply::NetworkError> StandardFeed::guessFeed(const
const QString& username,
const QString& password) {
QPair<StandardFeed*, QNetworkReply::NetworkError> result;
result.first = nullptr;
QByteArray feed_contents;
QList<QPair<QByteArray, QByteArray>> headers;
headers << NetworkFactory::generateBasicAuthHeader(username, password);
NetworkResult network_result = NetworkFactory::performNetworkOperation(url,
@ -208,15 +210,15 @@ QPair<StandardFeed*, QNetworkReply::NetworkError> StandardFeed::guessFeed(const
QDomElement root_element = xml_document.documentElement();
QString root_tag_name = root_element.tagName();
QList<QString> icon_possible_locations;
icon_possible_locations.append(url);
if (root_tag_name == QL1S("rdf:RDF")) {
// We found RDF feed.
QDomElement channel_element = root_element.namedItem(QSL("channel")).toElement();
result.first->setType(Rdf);
result.first->setType(Type::Rdf);
result.first->setTitle(channel_element.namedItem(QSL("title")).toElement().text());
result.first->setDescription(channel_element.namedItem(QSL("description")).toElement().text());
QString source_link = channel_element.namedItem(QSL("link")).toElement().text();
@ -230,10 +232,10 @@ QPair<StandardFeed*, QNetworkReply::NetworkError> StandardFeed::guessFeed(const
QString rss_type = root_element.attribute("version", "2.0");
if (rss_type == QL1S("0.91") || rss_type == QL1S("0.92") || rss_type == QL1S("0.93")) {
result.first->setType(Rss0X);
result.first->setType(Type::Rss0X);
}
else {
result.first->setType(Rss2X);
result.first->setType(Type::Rss2X);
}
QDomElement channel_element = root_element.namedItem(QSL("channel")).toElement();
@ -248,7 +250,7 @@ QPair<StandardFeed*, QNetworkReply::NetworkError> StandardFeed::guessFeed(const
}
else if (root_tag_name == QL1S("feed")) {
// We found ATOM feed.
result.first->setType(Atom10);
result.first->setType(Type::Atom10);
result.first->setTitle(root_element.namedItem(QSL("title")).toElement().text());
result.first->setDescription(root_element.namedItem(QSL("subtitle")).toElement().text());
QString source_link = root_element.namedItem(QSL("link")).toElement().text();
@ -308,8 +310,8 @@ bool StandardFeed::addItself(RootItem* parent) {
QSqlDatabase database = qApp->database()->connection(metaObject()->className());
bool ok;
int new_id = DatabaseQueries::addStandardFeed(database, parent->id(), parent->getParentServiceRoot()->accountId(), title(),
description(), creationDate(), icon(), encoding(), url(), passwordProtected(),
username(), password(), autoUpdateType(), autoUpdateInitialInterval(), type(), &ok);
description(), creationDate(), icon(), encoding(), url(), passwordProtected(),
username(), password(), autoUpdateType(), autoUpdateInitialInterval(), type(), &ok);
if (!ok) {
// Query failed.
@ -329,12 +331,13 @@ bool StandardFeed::editItself(StandardFeed* new_feed_data) {
RootItem* new_parent = new_feed_data->parent();
if (!DatabaseQueries::editStandardFeed(database, new_parent->id(), original_feed->id(), new_feed_data->title(),
new_feed_data->description(), new_feed_data->icon(),
new_feed_data->encoding(), new_feed_data->url(), new_feed_data->passwordProtected(),
new_feed_data->username(), new_feed_data->password(),
new_feed_data->autoUpdateType(), new_feed_data->autoUpdateInitialInterval(),
new_feed_data->type())) {
new_feed_data->description(), new_feed_data->icon(),
new_feed_data->encoding(), new_feed_data->url(), new_feed_data->passwordProtected(),
new_feed_data->username(), new_feed_data->password(),
new_feed_data->autoUpdateType(), new_feed_data->autoUpdateInitialInterval(),
new_feed_data->type())) {
// Persistent storage update failed, no way to continue now.
qWarning("Self-editing of standard feed failed.");
return false;
}
@ -399,8 +402,8 @@ void StandardFeed::setEncoding(const QString& encoding) {
QList<Message> StandardFeed::obtainNewMessages(bool* error_during_obtaining) {
QByteArray feed_contents;
int download_timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt();
QList<QPair<QByteArray, QByteArray>> headers;
headers << NetworkFactory::generateBasicAuthHeader(username(), password());
m_networkError = NetworkFactory::performNetworkOperation(url(),
@ -412,7 +415,7 @@ QList<Message> StandardFeed::obtainNewMessages(bool* error_during_obtaining) {
if (m_networkError != QNetworkReply::NoError) {
qWarning("Error during fetching of new messages for feed '%s' (id %d).", qPrintable(url()), id());
setStatus(NetworkError);
setStatus(Status::NetworkError);
*error_during_obtaining = true;
return QList<Message>();
}
@ -438,16 +441,16 @@ QList<Message> StandardFeed::obtainNewMessages(bool* error_during_obtaining) {
QList<Message> messages;
switch (type()) {
case StandardFeed::Rss0X:
case StandardFeed::Rss2X:
case StandardFeed::Type::Rss0X:
case StandardFeed::Type::Rss2X:
messages = RssParser(formatted_feed_contents).messages();
break;
case StandardFeed::Rdf:
case StandardFeed::Type::Rdf:
messages = RdfParser().parseXmlData(formatted_feed_contents);
break;
case StandardFeed::Atom10:
case StandardFeed::Type::Atom10:
messages = AtomParser(formatted_feed_contents).messages();
default:

View File

@ -20,7 +20,7 @@ class StandardFeed : public Feed {
Q_OBJECT
public:
enum Type {
enum class Type {
Rss0X = 0,
Rss2X = 1,
Rdf = 2, // Sometimes denoted as RSS 1.0.
@ -35,7 +35,7 @@ class StandardFeed : public Feed {
StandardServiceRoot* serviceRoot() const;
QList<QAction*> contextMenu();
QList<QAction*> contextMenuFeedsList();
QString additionalTooltip() const;

View File

@ -70,7 +70,7 @@ bool FeedsImportExportModel::exportToOMPL20(QByteArray& result) {
}
switch (child_item->kind()) {
case RootItemKind::Category: {
case RootItem::Kind::Category: {
QDomElement outline_category = opml_document.createElement(QSL("outline"));
outline_category.setAttribute(QSL("text"), child_item->title());
@ -82,7 +82,7 @@ bool FeedsImportExportModel::exportToOMPL20(QByteArray& result) {
break;
}
case RootItemKind::Feed: {
case RootItem::Kind::Feed: {
auto* child_feed = dynamic_cast<StandardFeed*>(child_item);
QDomElement outline_feed = opml_document.createElement("outline");
@ -95,16 +95,16 @@ bool FeedsImportExportModel::exportToOMPL20(QByteArray& result) {
outline_feed.setAttribute(QSL("rssguard:icon"), QString(qApp->icons()->toByteArray(child_feed->icon())));
switch (child_feed->type()) {
case StandardFeed::Rss0X:
case StandardFeed::Rss2X:
case StandardFeed::Type::Rss0X:
case StandardFeed::Type::Rss2X:
outline_feed.setAttribute(QSL("version"), QSL("RSS"));
break;
case StandardFeed::Rdf:
case StandardFeed::Type::Rdf:
outline_feed.setAttribute(QSL("version"), QSL("RSS1"));
break;
case StandardFeed::Atom10:
case StandardFeed::Type::Atom10:
outline_feed.setAttribute(QSL("version"), QSL("ATOM"));
break;
@ -200,13 +200,13 @@ void FeedsImportExportModel::importAsOPML20(const QByteArray& data, bool fetch_m
new_feed->setIcon(feed_icon);
if (feed_type == QL1S("RSS1")) {
new_feed->setType(StandardFeed::Rdf);
new_feed->setType(StandardFeed::Type::Rdf);
}
else if (feed_type == QL1S("ATOM")) {
new_feed->setType(StandardFeed::Atom10);
new_feed->setType(StandardFeed::Type::Atom10);
}
else {
new_feed->setType(StandardFeed::Rss2X);
new_feed->setType(StandardFeed::Type::Rss2X);
}
active_model_item->appendChild(new_feed);

View File

@ -27,22 +27,20 @@
#include <QStack>
StandardServiceRoot::StandardServiceRoot(RootItem* parent)
: ServiceRoot(parent),
m_actionExportFeeds(nullptr), m_actionImportFeeds(nullptr), m_actionFeedFetchMetadata(nullptr) {
: ServiceRoot(parent) {
setTitle(qApp->system()->loggedInUser() + QSL(" (RSS/RDF/ATOM)"));
setIcon(StandardServiceEntryPoint().icon());
setDescription(tr("This is obligatory service account for standard RSS/RDF/ATOM feeds."));
}
StandardServiceRoot::~StandardServiceRoot() {
qDeleteAll(m_serviceMenu);
qDeleteAll(m_feedContextMenu);
}
void StandardServiceRoot::start(bool freshly_activated) {
loadFromDatabase();
if (freshly_activated && getSubTree(RootItemKind::Feed).isEmpty()) {
if (freshly_activated && getSubTree(RootItem::Kind::Feed).isEmpty()) {
// In other words, if there are no feeds or categories added.
if (MessageBox::show(qApp->mainFormWidget(), QMessageBox::Question, QObject::tr("Load initial set of feeds"),
tr("This new account does not include any feeds. You can now add default set of feeds."),
@ -176,13 +174,19 @@ void StandardServiceRoot::checkArgumentForFeedAdding(const QString& argument) {
QList<QAction*> StandardServiceRoot::getContextMenuForFeed(StandardFeed* feed) {
if (m_feedContextMenu.isEmpty()) {
// Initialize.
m_actionFeedFetchMetadata = new QAction(qApp->icons()->fromTheme(QSL("emblem-downloads")), tr("Fetch metadata"), nullptr);
m_feedContextMenu.append(m_actionFeedFetchMetadata);
auto* action_metadata = new QAction(qApp->icons()->fromTheme(QSL("emblem-downloads")),
tr("Fetch metadata"),
this);
m_feedContextMenu.append(action_metadata);
connect(action_metadata, &QAction::triggered, this, [this]() {
m_feedForMetadata->fetchMetadataForItself();
});
}
// Make connections.
disconnect(m_actionFeedFetchMetadata, &QAction::triggered, nullptr, nullptr);
connect(m_actionFeedFetchMetadata, &QAction::triggered, feed, &StandardFeed::fetchMetadataForItself);
m_feedForMetadata = feed;
return m_feedContextMenu;
}
@ -207,7 +211,7 @@ bool StandardServiceRoot::mergeImportExportModel(FeedsImportExportModel* model,
continue;
}
if (source_item->kind() == RootItemKind::Category) {
if (source_item->kind() == RootItem::Kind::Category) {
auto* source_category = dynamic_cast<StandardCategory*>(source_item);
auto* new_category = new StandardCategory(*source_category);
QString new_category_title = new_category->title();
@ -231,7 +235,7 @@ bool StandardServiceRoot::mergeImportExportModel(FeedsImportExportModel* model,
RootItem* existing_category = nullptr;
for (RootItem* child : target_parent->childItems()) {
if (child->kind() == RootItemKind::Category && child->title() == new_category_title) {
if (child->kind() == RootItem::Kind::Category && child->title() == new_category_title) {
existing_category = child;
}
}
@ -245,7 +249,7 @@ bool StandardServiceRoot::mergeImportExportModel(FeedsImportExportModel* model,
}
}
}
else if (source_item->kind() == RootItemKind::Feed) {
else if (source_item->kind() == RootItem::Kind::Feed) {
auto* source_feed = dynamic_cast<StandardFeed*>(source_item);
auto* new_feed = new StandardFeed(*source_feed);
@ -306,12 +310,16 @@ void StandardServiceRoot::exportFeeds() {
QList<QAction*> StandardServiceRoot::serviceMenu() {
if (m_serviceMenu.isEmpty()) {
m_actionExportFeeds = new QAction(qApp->icons()->fromTheme("document-export"), tr("Export feeds"), this);
m_actionImportFeeds = new QAction(qApp->icons()->fromTheme("document-import"), tr("Import feeds"), this);
connect(m_actionExportFeeds, &QAction::triggered, this, &StandardServiceRoot::exportFeeds);
connect(m_actionImportFeeds, &QAction::triggered, this, &StandardServiceRoot::importFeeds);
m_serviceMenu.append(m_actionExportFeeds);
m_serviceMenu.append(m_actionImportFeeds);
ServiceRoot::serviceMenu();
auto* action_export_feeds = new QAction(qApp->icons()->fromTheme("document-export"), tr("Export feeds"), this);
auto* action_import_feeds = new QAction(qApp->icons()->fromTheme("document-import"), tr("Import feeds"), this);
connect(action_export_feeds, &QAction::triggered, this, &StandardServiceRoot::exportFeeds);
connect(action_import_feeds, &QAction::triggered, this, &StandardServiceRoot::importFeeds);
m_serviceMenu.append(action_export_feeds);
m_serviceMenu.append(action_import_feeds);
}
return m_serviceMenu;

View File

@ -5,11 +5,12 @@
#include "services/abstract/serviceroot.h"
#include "services/standard/standardfeed.h"
#include <QCoreApplication>
#include <QPair>
class StandardCategory;
class StandardFeed;
class FeedsImportExportModel;
class QMenu;
@ -58,12 +59,8 @@ class StandardServiceRoot : public ServiceRoot {
QString processFeedUrl(const QString& feed_url);
void checkArgumentsForFeedAdding();
QAction* m_actionExportFeeds;
QAction* m_actionImportFeeds;
QList<QAction*> m_serviceMenu;
QList<QAction*> m_feedContextMenu;
QAction* m_actionFeedFetchMetadata;
QPointer<StandardFeed> m_feedForMetadata = {};
QList<QAction*> m_feedContextMenu = {};
};
#endif // STANDARDSERVICEROOT_H

View File

@ -37,7 +37,7 @@ void FormTtRssFeedDetails::apply() {
RootItem* parent = static_cast<RootItem*>(m_ui->m_cmbParentCategory->itemData(
m_ui->m_cmbParentCategory->currentIndex()).value<void*>());
auto* root = qobject_cast<TtRssServiceRoot*>(parent->getParentServiceRoot());
const int category_id = parent->kind() == RootItemKind::ServiceRoot ?
const int category_id = parent->kind() == RootItem::Kind::ServiceRoot ?
0 :
parent->customId().toInt();
const TtRssSubscribeToFeedResponse response = root->network()->subscribeToFeed(m_ui->m_txtUrl->lineEdit()->text(),

View File

@ -86,12 +86,12 @@ class TtRssUnsubscribeFeedResponse : public TtRssResponse {
};
namespace UpdateArticle {
enum Mode {
enum class Mode {
SetToFalse = 0,
SetToTrue = 1,
Togggle = 2
};
enum OperatingField {
enum class OperatingField {
Starred = 0,
Published = 1,
Unread = 2

View File

@ -79,7 +79,7 @@ QList<Message> TtRssFeed::obtainNewMessages(bool* error_during_obtaining) {
serviceRoot()->network()->downloadOnlyUnreadMessages());
if (serviceRoot()->network()->lastError() != QNetworkReply::NoError) {
setStatus(Feed::NetworkError);
setStatus(Feed::Status::NetworkError);
*error_during_obtaining = true;
serviceRoot()->itemChanged(QList<RootItem*>() << this);
return QList<Message>();

View File

@ -23,7 +23,7 @@
#include <QSqlTableModel>
TtRssServiceRoot::TtRssServiceRoot(RootItem* parent)
: ServiceRoot(parent), m_actionSyncIn(nullptr), m_network(new TtRssNetworkFactory()) {
: ServiceRoot(parent), m_network(new TtRssNetworkFactory()) {
setIcon(TtRssServiceEntryPoint().icon());
}
@ -52,6 +52,10 @@ QString TtRssServiceRoot::code() const {
return TtRssServiceEntryPoint().code();
}
bool TtRssServiceRoot::isSyncable() const {
return true;
}
bool TtRssServiceRoot::editViaGui() {
QScopedPointer<FormEditTtRssAccount> form_pointer(new FormEditTtRssAccount(qApp->mainFormWidget()));
@ -123,8 +127,10 @@ void TtRssServiceRoot::saveAllCachedData(bool async) {
if (!ids.isEmpty()) {
network()->updateArticles(ids,
UpdateArticle::Unread,
key == RootItem::Unread ? UpdateArticle::SetToTrue : UpdateArticle::SetToFalse,
UpdateArticle::OperatingField::Unread,
key == RootItem::ReadStatus::Unread
? UpdateArticle::Mode::SetToTrue
: UpdateArticle::Mode::SetToFalse,
async);
}
}
@ -141,23 +147,15 @@ void TtRssServiceRoot::saveAllCachedData(bool async) {
QStringList ids = customIDsOfMessages(messages);
network()->updateArticles(ids,
UpdateArticle::Starred,
key == RootItem::Important ? UpdateArticle::SetToTrue : UpdateArticle::SetToFalse,
UpdateArticle::OperatingField::Starred,
key == RootItem::Importance::Important
? UpdateArticle::Mode::SetToTrue
: UpdateArticle::Mode::SetToFalse,
async);
}
}
}
QList<QAction*> TtRssServiceRoot::serviceMenu() {
if (m_serviceMenu.isEmpty()) {
m_actionSyncIn = new QAction(qApp->icons()->fromTheme(QSL("view-refresh")), tr("Sync in"), this);
connect(m_actionSyncIn, &QAction::triggered, this, &TtRssServiceRoot::syncIn);
m_serviceMenu.append(m_actionSyncIn);
}
return m_serviceMenu;
}
QString TtRssServiceRoot::additionalTooltip() const {
return tr("Username: %1\nServer: %2\n"
"Last error: %3\nLast login on: %4").arg(m_network->username(),

View File

@ -22,13 +22,14 @@ class TtRssServiceRoot : public ServiceRoot, public CacheForServiceRoot {
void start(bool freshly_activated);
void stop();
QString code() const;
bool isSyncable() const;
bool canBeEdited() const;
bool canBeDeleted() const;
bool editViaGui();
bool deleteViaGui();
bool supportsFeedAdding() const;
bool supportsCategoryAdding() const;
QList<QAction*> serviceMenu();
QString additionalTooltip() const;
@ -46,12 +47,8 @@ class TtRssServiceRoot : public ServiceRoot, public CacheForServiceRoot {
private:
RootItem* obtainNewTreeForSyncIn() const;
void loadFromDatabase();
QAction* m_actionSyncIn;
QList<QAction*> m_serviceMenu;
TtRssNetworkFactory* m_network;
};