merged upstream/master

This commit is contained in:
csoler 2020-02-08 20:04:27 +01:00
commit 62c5cda7f0
No known key found for this signature in database
GPG Key ID: 7BCA522266C0804C
52 changed files with 3618 additions and 621 deletions

@ -1 +1 @@
Subproject commit c064abd74b27e1cc440917e9dbac800316bb8470
Subproject commit 879d5dfe8dcd8995be753120cf1b8bab4dd2ec82

View File

@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>
*
* SPDX-FileCopyrightText: 2004-2019 RetroShare Team <contact@retroshare.cc>
* SPDX-FileCopyrightText: 2004-2020 RetroShare Team <contact@retroshare.cc>
* SPDX-License-Identifier: AGPL-3.0-only
*/
@ -73,6 +73,8 @@ JsonApiServer::corsOptionsHeaders =
{ "Content-Length", "0" }
};
/* static */ const RsJsonApiErrorCategory RsJsonApiErrorCategory::instance;
#define INITIALIZE_API_CALL_JSON_CONTEXT \
RsGenericSerializer::SerializeContext cReq( \
nullptr, 0, \
@ -124,32 +126,45 @@ JsonApiServer::corsOptionsHeaders =
return false;
}
void JsonApiServer::unProtectedRestart()
{
/* Extremely sensitive stuff!
* Make sure you read documentation in header before changing or use!! */
fullstop();
RsThread::start("JSON API Server");
}
bool RsJsonApi::parseToken(
const std::string& clear_token, std::string& user,std::string& passwd )
{
uint32_t colonIndex = 0;
uint32_t colonCounter = 0;
auto colonIndex = std::string::npos;
const auto tkLen = clear_token.length();
for(uint32_t i=0; i < tkLen && colonCounter < 2; ++i)
if(clear_token[i] == ':') { ++colonCounter; colonIndex = i; }
else if(!librs::util::is_alphanumeric(clear_token[i])) return false;
for(uint32_t i=0; i < tkLen; ++i)
if(clear_token[i] == ':') { colonIndex = i; break; }
if(colonCounter != 1) return false;
user = clear_token.substr(0, colonIndex);
user = clear_token.substr(0, colonIndex);
passwd = clear_token.substr(colonIndex + 1);
if(colonIndex < tkLen)
passwd = clear_token.substr(colonIndex + 1);
return true;
}
JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"),
mService(std::make_shared<restbed::Service>()),
mServiceMutex("JsonApiServer restbed ptr"),
mService(nullptr),
mListeningPort(RsJsonApi::DEFAULT_PORT),
mBindingAddress(RsJsonApi::DEFAULT_BINDING_ADDRESS)
mBindingAddress(RsJsonApi::DEFAULT_BINDING_ADDRESS),
mRestartReqTS(0)
{
#if defined(RS_THREAD_FORCE_STOP) && defined(RS_JSONAPI_DEBUG_SERVICE_STOP)
/* When called in bursts it seems that Restbed::Service::stop() doesn't
* always does the job, to debug those cases it has been useful to ask
* RsThread to force it to stop for us. */
RsThread::setStopTimeout(10);
#endif
registerHandler("/rsLoginHelper/createLocation",
[this](const std::shared_ptr<rb::Session> session)
{
@ -248,7 +263,13 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"),
{
INITIALIZE_API_CALL_JSON_CONTEXT;
DEFAULT_API_CALL_JSON_RETURN(rb::OK);
rsControl->rsGlobalShutDown();
/* Wrap inside RsThread::async because this call
* RsThread::fullstop() also on JSON API server thread.
* Calling RsThread::fullstop() from it's own thread should never
* happen and if it happens an error message is printed
* accordingly by RsThread::fullstop() */
RsThread::async([](){ rsControl->rsGlobalShutDown(); });
} );
}, true);
@ -388,6 +409,41 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"),
session->yield(message.str());
} );
}, true);
registerHandler("/rsJsonApi/restart",
[this](const std::shared_ptr<rb::Session> session)
{
auto reqSize = session->get_request()->get_header("Content-Length", 0);
session->fetch( static_cast<size_t>(reqSize), [this](
const std::shared_ptr<rb::Session> session,
const rb::Bytes& body )
{
INITIALIZE_API_CALL_JSON_CONTEXT;
std::error_condition retval;
const auto now = time(nullptr);
if(mRestartReqTS.exchange(now) + RESTART_BURST_PROTECTION > now)
retval = RsJsonApiErrorNum::NOT_A_MACHINE_GUN;
// serialize out parameters and return value to JSON
{
RsGenericSerializer::SerializeContext& ctx(cAns);
RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);
RS_SERIAL_PROCESS(retval);
}
DEFAULT_API_CALL_JSON_RETURN(rb::OK);
/* Wrap inside RsThread::async because this call fullstop() on
* JSON API server thread.
* Calling RsThread::fullstop() from it's own thread should never
* happen and if it happens an error message is printed
* accordingly by RsThread::fullstop() */
if(!retval) RsThread::async([this](){ unProtectedRestart(); });
} );
}, true);
// Generated at compile time
#include "jsonapi-wrappers.inl"
}
@ -405,10 +461,22 @@ void JsonApiServer::registerHandler(
if(requiresAutentication)
resource->set_authentication_handler(
[this](
[this, path](
const std::shared_ptr<rb::Session> session,
const std::function<void (const std::shared_ptr<rb::Session>)>& callback )
{
const auto authFail =
[&path, &session](int status) -> RsWarn::stream_type&
{
/* Capture session by reference as it is cheaper then copying
* shared_ptr by value which is not needed in this case */
session->close(status, corsOptionsHeaders);
return RsWarn() << "JsonApiServer authentication handler "
"blocked an attempt to call JSON API "
"authenticated method: " << path;
};
if(session->get_request()->get_method() == "OPTIONS")
{
callback(session);
@ -417,7 +485,8 @@ void JsonApiServer::registerHandler(
if(!rsLoginHelper->isLoggedIn())
{
session->close(rb::CONFLICT, corsOptionsHeaders);
authFail(rb::CONFLICT) << " before RetroShare login"
<< std::endl;
return;
}
@ -429,15 +498,24 @@ void JsonApiServer::registerHandler(
if(authToken != "Basic")
{
session->close(rb::UNAUTHORIZED, corsOptionsHeaders);
authFail(rb::UNAUTHORIZED)
<< " with wrong Authorization header: "
<< authHeader.str() << std::endl;
return;
}
std::getline(authHeader, authToken, ' ');
authToken = decodeToken(authToken);
if(isAuthTokenValid(authToken)) callback(session);
else session->close(rb::UNAUTHORIZED, corsOptionsHeaders);
std::error_condition ec;
if(isAuthTokenValid(authToken, ec)) callback(session);
else
{
std::string tUser;
parseToken(authToken, tUser, RS_DEFAULT_STORAGE_PARAM(std::string));
authFail(rb::UNAUTHORIZED)
<< " user: " << tUser << ec << std::endl;
}
} );
mResources.push_back(resource);
@ -447,24 +525,62 @@ void JsonApiServer::setNewAccessRequestCallback(
const std::function<bool (const std::string&, const std::string&)>& callback )
{ mNewAccessRequestCallback = callback; }
bool JsonApiServer::requestNewTokenAutorization(
/*static*/ std::error_condition JsonApiServer::badApiCredientalsFormat(
const std::string& user, const std::string& passwd )
{
if(rsLoginHelper->isLoggedIn() && mNewAccessRequestCallback(user, passwd))
return authorizeUser(user, passwd);
if(user.find(':') < std::string::npos)
return RsJsonApiErrorNum::API_USER_CONTAIN_COLON;
return false;
if(user.empty())
RsWarn() << __PRETTY_FUNCTION__ << " User is empty, are you sure "
<< "this what you wanted?" << std::endl;
if(passwd.empty())
RsWarn() << __PRETTY_FUNCTION__ << " Password is empty, are you sure "
<< "this what you wanted?" << std::endl;
return std::error_condition();
}
bool JsonApiServer::isAuthTokenValid(const std::string& token)
std::error_condition JsonApiServer::requestNewTokenAutorization(
const std::string& user, const std::string& passwd )
{
auto ec = badApiCredientalsFormat(user, passwd);
if(ec) return ec;
if(!rsLoginHelper->isLoggedIn())
return RsJsonApiErrorNum::CANNOT_EXECUTE_BEFORE_RS_LOGIN;
if(mNewAccessRequestCallback(user, passwd))
return authorizeUser(user, passwd);
return RsJsonApiErrorNum::AUTHORIZATION_REQUEST_DENIED;
}
bool JsonApiServer::isAuthTokenValid(
const std::string& token, std::error_condition& error )
{
RS_STACK_MUTEX(configMutex);
const auto failure = [&error](RsJsonApiErrorNum e) -> bool
{
error = e;
return false;
};
const auto success = [&error]()
{
error.clear();
return true;
};
std::string user,passwd;
if(!parseToken(token,user,passwd)) return false;
if(!parseToken(token, user, passwd))
return failure(RsJsonApiErrorNum::TOKEN_FORMAT_INVALID);
auto it = mAuthTokenStorage.mAuthorizedTokens.find(user);
if(it == mAuthTokenStorage.mAuthorizedTokens.end()) return false;
if(it == mAuthTokenStorage.mAuthorizedTokens.end())
return failure(RsJsonApiErrorNum::UNKNOWN_API_USER);
// attempt avoiding +else CRYPTO_memcmp+ being optimized away
int noOptimiz = 1;
@ -476,12 +592,16 @@ bool JsonApiServer::isAuthTokenValid(const std::string& token)
if( passwd.size() == it->second.size() &&
( noOptimiz = CRYPTO_memcmp(
passwd.data(), it->second.data(), it->second.size() ) ) == 0 )
return true;
return success();
// Make token size guessing harder
else noOptimiz = CRYPTO_memcmp(passwd.data(), passwd.data(), passwd.size());
// attempt avoiding +else CRYPTO_memcmp+ being optimized away
return static_cast<uint32_t>(noOptimiz) + 1 == 0;
/* At this point we are sure password is wrong, and one could think to
* plainly `return false` still this ugly and apparently unuseful extra
* calculation is here to avoid `else CRYPTO_memcmp` being optimized away,
* so a pontential attacker cannot guess password size based on timing */
return static_cast<uint32_t>(noOptimiz) + 1 == 0 ?
success() : failure(RsJsonApiErrorNum::WRONG_API_PASSWORD);
}
std::map<std::string, std::string> JsonApiServer::getAuthorizedTokens()
@ -509,22 +629,11 @@ void JsonApiServer::connectToConfigManager(p3ConfigMgr& cfgmgr)
loadConfiguration(hash);
}
bool JsonApiServer::authorizeUser(
std::error_condition JsonApiServer::authorizeUser(
const std::string& user, const std::string& passwd )
{
if(!librs::util::is_alphanumeric(user))
{
RsErr() << __PRETTY_FUNCTION__ << " User name is not alphanumeric"
<< std::endl;
return false;
}
if(passwd.empty())
{
RsWarn() << __PRETTY_FUNCTION__ << " Password is empty, are you sure "
<< "this what you wanted?" << std::endl;
return false;
}
auto ec = badApiCredientalsFormat(user, passwd);
if(ec) return ec;
RS_STACK_MUTEX(configMutex);
@ -534,7 +643,7 @@ bool JsonApiServer::authorizeUser(
p = passwd;
IndicateConfigChanged();
}
return true;
return ec;
}
/*static*/ std::string JsonApiServer::decodeToken(const std::string& radix64_token)
@ -600,21 +709,21 @@ std::vector<std::shared_ptr<rb::Resource> > JsonApiServer::getResources() const
return tab;
}
void JsonApiServer::restart()
std::error_condition JsonApiServer::restart()
{
/* It is important to wrap into async(...) because fullstop() method can't
* be called from same thread of execution hence from JSON API thread! */
RsThread::async([this]()
{
fullstop();
RsThread::start("JSON API Server");
});
const auto now = time(nullptr);
if(mRestartReqTS.exchange(now) + RESTART_BURST_PROTECTION > now)
return RsJsonApiErrorNum::NOT_A_MACHINE_GUN;
unProtectedRestart();
return std::error_condition();
}
void JsonApiServer::onStopRequested()
{
RS_STACK_MUTEX(mServiceMutex);
mService->stop();
auto tService = std::atomic_exchange(
&mService, std::shared_ptr<rb::Service>(nullptr) );
if(tService) tService->stop();
}
uint16_t JsonApiServer::listeningPort() const { return mListeningPort; }
@ -630,14 +739,9 @@ void JsonApiServer::run()
settings->set_bind_address(mBindingAddress);
settings->set_default_header("Connection", "close");
/* re-allocating mService is important because it deletes the existing
* service and therefore leaves the listening port open */
{
RS_STACK_MUTEX(mServiceMutex);
mService = std::make_shared<restbed::Service>();
}
auto tService = std::make_shared<restbed::Service>();
for(auto& r: getResources()) mService->publish(r);
for(auto& r: getResources()) tService->publish(r);
try
{
@ -645,7 +749,20 @@ void JsonApiServer::run()
.setPort(mListeningPort);
RsInfo() << __PRETTY_FUNCTION__ << " JSON API server listening on "
<< apiUrl.toString() << std::endl;
mService->start(settings);
/* re-allocating mService is important because it deletes the existing
* service and therefore leaves the listening port open */
auto tExpected = std::shared_ptr<rb::Service>(nullptr);
if(atomic_compare_exchange_strong(&mService, &tExpected, tService))
tService->start(settings);
else
{
RsErr() << __PRETTY_FUNCTION__ << " mService was expected to be "
<< " null, instead we got: " << tExpected
<< " something wrong happened JsonApiServer won't start"
<< std::endl;
print_stacktrace();
}
}
catch(std::exception& e)
{
@ -668,3 +785,19 @@ void JsonApiServer::run()
extra = RS_EXTRA_VERSION;
human = RS_HUMAN_READABLE_VERSION;
}
std::error_condition RsJsonApiErrorCategory::default_error_condition(int ev) const noexcept
{
switch(static_cast<RsJsonApiErrorNum>(ev))
{
case RsJsonApiErrorNum::TOKEN_FORMAT_INVALID: // fallthrough
case RsJsonApiErrorNum::UNKNOWN_API_USER: // fallthrough
case RsJsonApiErrorNum::WRONG_API_PASSWORD: // fallthrough
case RsJsonApiErrorNum::AUTHORIZATION_REQUEST_DENIED:
return std::errc::permission_denied;
case RsJsonApiErrorNum::API_USER_CONTAIN_COLON:
return std::errc::invalid_argument;
default:
return std::error_condition(ev, *this);
}
}

View File

@ -30,6 +30,7 @@
#include <set>
#include <functional>
#include <vector>
#include <atomic>
#include "util/rsthreads.h"
#include "pqi/p3cfgmgr.h"
@ -40,6 +41,7 @@
namespace rb = restbed;
/** Interface to provide addotional resources to JsonApiServer */
class JsonApiResourceProvider
{
public:
@ -66,7 +68,7 @@ public:
void fullstop() override { RsThread::fullstop(); }
/// @see RsJsonApi
void restart() override;
std::error_condition restart() override;
/// @see RsJsonApi
void askForStop() override { RsThread::askForStop(); }
@ -90,9 +92,8 @@ public:
void connectToConfigManager(p3ConfigMgr& cfgmgr) override;
/// @see RsJsonApi
virtual bool authorizeUser(
const std::string& alphanumeric_user,
const std::string& alphanumeric_passwd ) override;
virtual std::error_condition authorizeUser(
const std::string& user, const std::string& passwd ) override;
/// @see RsJsonApi
std::map<std::string,std::string> getAuthorizedTokens() override;
@ -101,10 +102,13 @@ public:
bool revokeAuthToken(const std::string& user) override;
/// @see RsJsonApi
bool isAuthTokenValid(const std::string& token) override;
bool isAuthTokenValid(
const std::string& token,
std::error_condition& error = RS_DEFAULT_STORAGE_PARAM(std::error_condition)
) override;
/// @see RsJsonAPI
bool requestNewTokenAutorization(
std::error_condition requestNewTokenAutorization(
const std::string& user, const std::string& password ) override;
/// @see RsJsonApi
@ -147,6 +151,11 @@ protected:
/// @see RsThread
void onStopRequested() override;
static const RsJsonApiErrorCategory sErrorCategory;
static std::error_condition badApiCredientalsFormat(
const std::string& user, const std::string& passwd );
private:
/// @see RsThread
void run() override;
@ -195,13 +204,33 @@ private:
std::reference_wrapper<const JsonApiResourceProvider>,
std::less<const JsonApiResourceProvider> > mResourceProviders;
/**
* This pointer should be accessed via std::atomic_* operations, up until
* now only very critical operations like reallocation, are done that way,
* but this is not still 100% thread safe, but seems to handle all of the
* test cases (no crash, no deadlock), once we switch to C++20 we shoud
* change this into std::atomic<std::shared_ptr<restbed::Service>> which
* will automatically handle atomic access properly all the times
*/
std::shared_ptr<restbed::Service> mService;
/** Protect service only during very critical operation like resetting the
* pointer, still not 100% thread safe, but hopefully we can avoid
* crashes/freeze with this */
RsMutex mServiceMutex;
uint16_t mListeningPort;
std::string mBindingAddress;
/// @see unProtectedRestart()
std::atomic<rstime_t> mRestartReqTS;
/// @see unProtectedRestart()
constexpr static rstime_t RESTART_BURST_PROTECTION = 7;
/** It is very important to protect this method from being called in bursts,
* because Restbed::Service::stop() together with
* Restbed::Service::start(...), which are called internally, silently fails
* if combined in bursts, probably because they have to deal with
* listening/releasing TCP port.
* @see JsonApiServer::restart() and @see JsonApiServer::JsonApiServer()
* implementation to understand how correctly use this.
*/
void unProtectedRestart();
};

View File

@ -1,8 +1,9 @@
/*
* RetroShare JSON API public header
*
* Copyright (C) 2018-2019 Gioacchino Mazzurco <gio.eigenlab.org>
* Copyright (C) 2018-2020 Gioacchino Mazzurco <gio.eigenlab.org>
* Copyright (C) 2019 Cyril Soler <csoler@users.sourceforge.net>
* Copyright (C) 2020 Asociación Civil Altermundi <info@altermundi.net>
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
@ -25,9 +26,11 @@
#include <map>
#include <string>
#include <cstdint>
#include <system_error>
#include "util/rsmemory.h"
class p3ConfigMgr;
class JsonApiResourceProvider;
class RsJsonApi;
/**
@ -36,6 +39,71 @@ class RsJsonApi;
*/
extern RsJsonApi* rsJsonApi;
enum class RsJsonApiErrorNum : int32_t
{
TOKEN_FORMAT_INVALID = 2004,
UNKNOWN_API_USER = 2005,
WRONG_API_PASSWORD = 2006,
API_USER_CONTAIN_COLON = 2007,
AUTHORIZATION_REQUEST_DENIED = 2008,
CANNOT_EXECUTE_BEFORE_RS_LOGIN = 2009,
NOT_A_MACHINE_GUN = 2010
};
struct RsJsonApiErrorCategory: std::error_category
{
const char* name() const noexcept override
{ return "RetroShare JSON API"; }
std::string message(int ev) const override
{
switch (static_cast<RsJsonApiErrorNum>(ev))
{
case RsJsonApiErrorNum::TOKEN_FORMAT_INVALID:
return "Invalid token format, must be alphanumeric_user:password";
case RsJsonApiErrorNum::UNKNOWN_API_USER:
return "Unknown API user";
case RsJsonApiErrorNum::WRONG_API_PASSWORD:
return "Wrong API password";
case RsJsonApiErrorNum::API_USER_CONTAIN_COLON:
return "API user cannot contain colon character";
case RsJsonApiErrorNum::AUTHORIZATION_REQUEST_DENIED:
return "User denied new token autorization";
case RsJsonApiErrorNum::CANNOT_EXECUTE_BEFORE_RS_LOGIN:
return "This operation cannot be executed bedore RetroShare login";
case RsJsonApiErrorNum::NOT_A_MACHINE_GUN:
return "Method must not be called in burst";
default:
return "Error message for error: " + std::to_string(ev) +
" not available in category: " + name();
}
}
std::error_condition default_error_condition(int ev) const noexcept override;
const static RsJsonApiErrorCategory instance;
};
namespace std
{
/** Register RsJsonApiErrorNum as an error condition enum, must be in std
* namespace */
template<> struct is_error_condition_enum<RsJsonApiErrorNum> : true_type {};
}
/** Provide RsJsonApiErrorNum conversion to std::error_condition, must be in
* same namespace of RsJsonApiErrorNum */
inline std::error_condition make_error_condition(RsJsonApiErrorNum e) noexcept
{
return std::error_condition(
static_cast<int>(e), RsJsonApiErrorCategory::instance );
};
class p3ConfigMgr;
class JsonApiResourceProvider;
class RsJsonApi
{
public:
@ -43,10 +111,12 @@ public:
static const std::string DEFAULT_BINDING_ADDRESS; // 127.0.0.1
/**
* @brief Restart RsJsonApi server asynchronously.
* @jsonapi{development}
* @brief Restart RsJsonApi server.
* This method is asyncronous when called from JSON API.
* @jsonapi{development,manualwrapper}
* @return Success or error details
*/
virtual void restart() = 0;
virtual std::error_condition restart() = 0;
/** @brief Request RsJsonApi to stop and wait until it has stopped.
* Do not expose this method to JSON API as fullstop must not be called from
@ -124,9 +194,9 @@ public:
* @jsonapi{development,unauthenticated}
* @param[in] user user name to authorize
* @param[in] password password for the new user
* @return true if authorization succeded, false otherwise.
* @return if an error occurred details about it.
*/
virtual bool requestNewTokenAutorization(
virtual std::error_condition requestNewTokenAutorization(
const std::string& user, const std::string& password) = 0;
/** Split a token in USER:PASSWORD format into user and password */
@ -135,14 +205,13 @@ public:
std::string& user, std::string& passwd );
/**
* Add new API auth (user,passwd) token to the authorized set.
* Add new API auth user, passwd to the authorized set.
* @jsonapi{development}
* @param[in] user user name to autorize, must be alphanumerinc
* @param[in] password password for the user, must be alphanumerinc
* @return true if the token has been added to authorized, false if error
* occurred
* @param[in] user user name to autorize, must not contain ':'
* @param[in] password password for the user
* @return if some error occurred return details about it
*/
virtual bool authorizeUser(
virtual std::error_condition authorizeUser(
const std::string& user, const std::string& password ) = 0;
/**
@ -164,9 +233,13 @@ public:
* @brief Check if given JSON API auth token is authorized
* @jsonapi{development,unauthenticated}
* @param[in] token decoded
* @return tru if authorized, false otherwise
* @param[out] error optional storage for error details
* @return true if authorized, false otherwise
*/
virtual bool isAuthTokenValid(const std::string& token) = 0;
virtual bool isAuthTokenValid(
const std::string& token,
std::error_condition& error = RS_DEFAULT_STORAGE_PARAM(std::error_condition)
) = 0;
/**
* @brief Write version information to given paramethers
@ -182,3 +255,4 @@ public:
virtual ~RsJsonApi() = default;
};

View File

@ -397,8 +397,8 @@ int RsInit::InitRetroShare(const RsConfigOptions& conf)
#ifdef RS_JSONAPI
// We create the JsonApiServer this early, because it is needed *before* login
RsInfo() << __PRETTY_FUNCTION__
<< "Allocating jsonAPI server (not launched yet)" << std::endl;
RsDbg() << __PRETTY_FUNCTION__
<< " Allocating JSON API server (not launched yet)" << std::endl;
JsonApiServer* jas = new JsonApiServer();
jas->setListeningPort(conf.jsonApiPort);
jas->setBindingAddress(conf.jsonApiBindAddress);

View File

@ -768,3 +768,37 @@ bool RsTypeSerializer::from_JSON( const std::string& memberName,
}
return ret;
}
void RsTypeSerializer::ErrConditionWrapper::serial_process(
RsGenericSerializer::SerializeJob j,
RsGenericSerializer::SerializeContext& ctx )
{
switch(j)
{
case RsGenericSerializer::SIZE_ESTIMATE: // fallthrough
case RsGenericSerializer::DESERIALIZE: // fallthrough
case RsGenericSerializer::SERIALIZE: // fallthrough
case RsGenericSerializer::FROM_JSON:
RsFatal() << __PRETTY_FUNCTION__ << " SerializeJob: " << j
<< "is not supported on std::error_condition " << std::endl;
print_stacktrace();
exit(-2);
case RsGenericSerializer::PRINT: // fallthrough
case RsGenericSerializer::TO_JSON:
{
constexpr RsGenericSerializer::SerializeJob rj =
RsGenericSerializer::TO_JSON;
int32_t tNum = mec.value();
RsTypeSerializer::serial_process(rj, ctx, tNum, "errorNumber");
std::string tStr = mec.category().name();
RsTypeSerializer::serial_process(rj, ctx, tStr, "errorCategory");
tStr = mec.message();
RsTypeSerializer::serial_process(rj, ctx, tStr, "errorMessage");
break;
}
default: RsTypeSerializer::fatalUnknownSerialJob(j);
}
}

View File

@ -22,6 +22,12 @@
*******************************************************************************/
#pragma once
#include <typeinfo> // for typeid
#include <type_traits>
#include <errno.h>
#include <system_error>
#include "serialiser/rsserial.h"
#include "serialiser/rstlvbase.h"
#include "serialiser/rstlvlist.h"
@ -34,11 +40,6 @@
#include "util/rsjson.h"
#include "util/rsdebug.h"
#include <typeinfo> // for typeid
#include <type_traits>
#include <errno.h>
/* INTERNAL ONLY helper to avoid copy paste code for std::{vector,list,set}<T>
* Can't use a template function because T is needed for const_cast */
#define RsTypeSerializer_PRIVATE_TO_JSON_ARRAY() do \
@ -132,7 +133,12 @@ struct RsTypeSerializer
/// Generic types
template<typename T>
typename std::enable_if<std::is_same<RsTlvItem,T>::value || !(std::is_base_of<RsSerializable,T>::value || std::is_enum<T>::value || std::is_base_of<RsTlvItem,T>::value )>::type
typename
std::enable_if< std::is_same<RsTlvItem,T>::value || !(
std::is_base_of<RsSerializable,T>::value ||
std::is_enum<T>::value ||
std::is_base_of<RsTlvItem,T>::value ||
std::is_same<std::error_condition,T>::value ) >::type
static /*void*/ serial_process( RsGenericSerializer::SerializeJob j,
RsGenericSerializer::SerializeContext& ctx,
T& member, const std::string& member_name )
@ -160,11 +166,7 @@ struct RsTypeSerializer
ctx.mOk &= (ctx.mOk || ctx.mFlags & RsGenericSerializer::SERIALIZATION_FLAG_YIELDING)
&& from_JSON(member_name, member, ctx.mJson);
break;
default:
std::cerr << __PRETTY_FUNCTION__ << " Unknown serial job: "
<< static_cast<std::underlying_type<decltype(j)>::type>(j)
<< std::endl;
exit(EINVAL);
default: fatalUnknownSerialJob(j);
}
}
@ -200,11 +202,7 @@ struct RsTypeSerializer
(ctx.mOk || ctx.mFlags & RsGenericSerializer::SERIALIZATION_FLAG_YIELDING)
&& from_JSON(member_name, type_id, member, ctx.mJson);
break;
default:
std::cerr << __PRETTY_FUNCTION__ << " Unknown serial job: "
<< static_cast<std::underlying_type<decltype(j)>::type>(j)
<< std::endl;
exit(EINVAL);
default: fatalUnknownSerialJob(j);
}
}
@ -362,11 +360,7 @@ struct RsTypeSerializer
}
break;
}
default:
std::cerr << __PRETTY_FUNCTION__ << " Unknown serial job: "
<< static_cast<std::underlying_type<decltype(j)>::type>(j)
<< std::endl;
exit(EINVAL);
default: fatalUnknownSerialJob(j);
}
}
@ -441,12 +435,7 @@ struct RsTypeSerializer
break;
}
default:
std::cerr << __PRETTY_FUNCTION__ << " Unknown serial job: "
<< static_cast<std::underlying_type<decltype(j)>::type>(j)
<< std::endl;
exit(EINVAL);
break;
default: fatalUnknownSerialJob(j);
}
}
@ -505,7 +494,7 @@ struct RsTypeSerializer
case RsGenericSerializer::FROM_JSON:
RsTypeSerializer_PRIVATE_FROM_JSON_ARRAY(push_back);
break;
default: break;
default: fatalUnknownSerialJob(j);
}
}
@ -564,7 +553,7 @@ struct RsTypeSerializer
case RsGenericSerializer::FROM_JSON:
RsTypeSerializer_PRIVATE_FROM_JSON_ARRAY(insert);
break;
default: break;
default: fatalUnknownSerialJob(j);
}
}
@ -620,7 +609,7 @@ struct RsTypeSerializer
case RsGenericSerializer::FROM_JSON:
RsTypeSerializer_PRIVATE_FROM_JSON_ARRAY(push_back);
break;
default: break;
default: fatalUnknownSerialJob(j);
}
}
@ -663,7 +652,7 @@ struct RsTypeSerializer
&& (v = t_RsFlags32<N>(f), true);
break;
}
default: break;
default: fatalUnknownSerialJob(j);
}
}
@ -772,18 +761,15 @@ struct RsTypeSerializer
break;
}
default:
std::cerr << __PRETTY_FUNCTION__ << " Unknown serial job: "
<< static_cast<std::underlying_type<decltype(j)>::type>(j)
<< std::endl;
exit(EINVAL);
break;
default: fatalUnknownSerialJob(j);
}
}
/// RsTlvItem derivatives only
template<typename T>
typename std::enable_if<std::is_base_of<RsTlvItem,T>::value && !std::is_same<RsTlvItem,T>::value>::type
typename std::enable_if<
std::is_base_of<RsTlvItem,T>::value && !std::is_same<RsTlvItem,T>::value
>::type
static /*void*/ serial_process( RsGenericSerializer::SerializeJob j,
RsGenericSerializer::SerializeContext& ctx,
T& member,
@ -792,6 +778,21 @@ struct RsTypeSerializer
serial_process(j, ctx, static_cast<RsTlvItem&>(member), memberName);
}
/** std::error_condition
* supports only TO_JSON ErrConditionWrapper::serial_process will explode
* at runtime if a different SerializeJob is passed down */
template<typename T>
typename std::enable_if< std::is_base_of<std::error_condition,T>::value >::type
static /*void*/ serial_process(
RsGenericSerializer::SerializeJob j,
RsGenericSerializer::SerializeContext& ctx,
const T& cond,
const std::string& member_name )
{
ErrConditionWrapper ew(cond);
serial_process(j, ctx, ew, member_name);
}
protected:
//============================================================================//
@ -909,6 +910,27 @@ protected:
t_RsTlvList<TLV_CLASS,TLV_TYPE>& member,
RsJson& jDoc );
[[noreturn]] static void fatalUnknownSerialJob(int j)
{
RsFatal() << " Unknown serial job: " << j << std::endl;
print_stacktrace();
exit(EINVAL);
}
struct ErrConditionWrapper : RsSerializable
{
ErrConditionWrapper(const std::error_condition& ec): mec(ec) {}
/** supports only TO_JSON if a different SerializeJob is passed it will
* explode at runtime */
void serial_process(
RsGenericSerializer::SerializeJob j,
RsGenericSerializer::SerializeContext& ctx ) override;
private:
const std::error_condition& mec;
};
RS_SET_CONTEXT_DEBUG_LEVEL(1)
};

View File

@ -3,7 +3,9 @@
* *
* libretroshare: retroshare core library *
* *
* Copyright 2004-2008 by Robert Fernie <retroshare@lunamutt.com> *
* Copyright (C) 2004-2008 by Robert Fernie <retroshare@lunamutt.com> *
* Copyright (C) 2020 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2020 Asociación Civil Altermundi <info@altermundi.net> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as *
@ -21,11 +23,25 @@
*******************************************************************************/
#include "util/rsdebug.h"
#include "util/rsthreads.h"
#include "util/rsdir.h"
std::ostream &operator<<(std::ostream& out, const std::error_condition& err)
{
return out << " error: " << err.value() << " " << err.message()
<< " category: " << err.category().name();
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/// All the following lines are DEPRECATED!!
#include <map>
#include <stdio.h>
#include <cstdio>
#include "util/rsthreads.h"
#include "util/rsdir.h"
#include "util/rstime.h"
const int RS_DEBUG_STDERR = 1; /* stuff goes to stderr */
@ -186,6 +202,3 @@ void rslog(const RsLog::logLvl lvl, RsLog::logInfo *info, const std::string &msg
lineCount++;
}
}

View File

@ -2,7 +2,8 @@
* RetroShare debugging utilities *
* *
* Copyright (C) 2004-2008 Robert Fernie <retroshare@lunamutt.com> *
* Copyright (C) 2019 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2019-2020 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2020 Asociación Civil Altermundi <info@altermundi.net> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as *
@ -21,6 +22,10 @@
#pragma once
#include <ostream>
#include <system_error>
/** Stream helper for std::error_condition */
std::ostream &operator<<(std::ostream& out, const std::error_condition& err);
#ifdef __ANDROID__
# include <android/log.h>
@ -41,12 +46,14 @@ struct t_RsLogger
{
inline t_RsLogger() = default;
typedef t_RsLogger stream_type;
template<typename T>
inline t_RsLogger& operator<<(const T& val)
inline stream_type& operator<<(const T& val)
{ ostr << val; return *this; }
/// needed for manipulators and things like std::endl
t_RsLogger& operator<<(std::ostream& (*pf)(std::ostream&))
stream_type& operator<<(std::ostream& (*pf)(std::ostream&))
{
if(pf == static_cast<std::ostream& (*)(std::ostream&)>(
&std::endl< char, std::char_traits<char> > ))
@ -84,8 +91,10 @@ struct t_RsLogger
{
inline t_RsLogger() = default;
typedef decltype(std::cerr) stream_type;
template<typename T>
inline std::ostream& operator<<(const T& val)
inline stream_type& operator<<(const T& val)
{
return std::cerr << static_cast<char>(CATEGORY) << " " << time(nullptr)
<< " " << val;
@ -97,7 +106,7 @@ struct t_RsLogger
/**
* Comfortable debug message loggin, supports chaining like std::cerr but can
* be easly and selectively disabled at compile time to reduce generated binary
* size and performance impact without too many #ifdef around.
* size and performance impact without too many \#ifdef around.
*
* To selectively debug your context you can just add something like this in
* in that context, as an example for a class you can just add a line like this

View File

@ -254,7 +254,13 @@ int RsDirUtil::breakupDirList(const std::string& path,
/**** Copied and Tweaked from ftcontroller ***/
bool RsDirUtil::fileExists(const std::string& filename)
{
#ifdef WINDOWS_SYS
std::wstring wfilename;
librs::util::ConvertUtf8ToUtf16(filename, wfilename);
return ( _waccess( wfilename.c_str(), F_OK ) != -1 );
#else
return ( access( filename.c_str(), F_OK ) != -1 );
#endif
}
bool RsDirUtil::moveFile(const std::string& source,const std::string& dest)

View File

@ -205,6 +205,7 @@ bool ConvertUtf16ToUtf8(const std::wstring& source, std::string& dest)
return true;
}
#if 0
bool is_alphanumeric(char c)
{
return (c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z');
@ -216,6 +217,7 @@ bool is_alphanumeric(const std::string& s)
if(!is_alphanumeric(s[i])) return false;
return true;
}
#endif
} } // librs::util

View File

@ -30,10 +30,10 @@ namespace librs {
bool ConvertUtf8ToUtf16(const std::string& source, std::wstring& dest);
bool ConvertUtf16ToUtf8(const std::wstring& source, std::string& dest);
#if 0
bool is_alphanumeric(char c) ;
bool is_alphanumeric(const std::string& s);
#endif
} } // librs::util
#ifdef WIN32

View File

@ -4,7 +4,7 @@
* libretroshare: retroshare core library *
* *
* Copyright (C) 2004-2007 Robert Fernie <retroshare@lunamutt.com> *
* Copyright (C) 2016-2019 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2016-2020 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2019-2020 Asociación Civil Altermundi <info@altermundi.net> *
* *
* This program is free software: you can redistribute it and/or modify *
@ -23,7 +23,7 @@
*******************************************************************************/
#include <iostream>
#include <time.h>
#include <ctime>
#include <thread>
#include <chrono>
@ -95,6 +95,9 @@ void RsThread::resetTid()
}
RsThread::RsThread() : mHasStopped(true), mShouldStop(false), mLastTid()
#ifdef RS_THREAD_FORCE_STOP
, mStopTimeout(0)
#endif
{ resetTid(); }
bool RsThread::isRunning() { return !mHasStopped; }
@ -117,6 +120,10 @@ void RsThread::wrapRun()
void RsThread::fullstop()
{
#ifdef RS_THREAD_FORCE_STOP
const rstime_t stopRequTS = time(nullptr);
#endif
askForStop();
const pthread_t callerTid = pthread_self();
@ -141,6 +148,32 @@ void RsThread::fullstop()
RsDbg() << __PRETTY_FUNCTION__ << " " << i*0.2 << " seconds passed"
<< " waiting for thread: " << std::hex << mLastTid
<< std::dec << " " << mFullName << " to stop" << std::endl;
#ifdef RS_THREAD_FORCE_STOP
if(mStopTimeout && time(nullptr) > stopRequTS + mStopTimeout)
{
RsErr() << __PRETTY_FUNCTION__ << " thread mLastTid: " << std::hex
<< mLastTid << " mTid: " << mTid << std::dec << " "
<< mFullName
<< " ignored our nice stop request for more then "
<< mStopTimeout
<< " seconds, will be forcefully stopped. "
<< "Please submit a report to RetroShare developers"
<< std::endl;
const auto terr = pthread_cancel(mTid);
if(terr == 0) mHasStopped = true;
else
{
RsErr() << __PRETTY_FUNCTION__ << " pthread_cancel("
<< std::hex << mTid << std::dec <<") returned "
<< terr << " " << rsErrnoName(terr) << std::endl;
print_stacktrace();
}
break;
}
#endif // def RS_THREAD_FORCE_STOP
}
}

View File

@ -35,6 +35,9 @@
#include "util/rsmemory.h"
#include "util/rsdeprecate.h"
#ifdef RS_THREAD_FORCE_STOP
# include "util/rstime.h"
#endif
//#define RSMUTEX_DEBUG
@ -249,6 +252,13 @@ protected:
* of this method, @see JsonApiServer for an usage example. */
virtual void onStopRequested() {}
#ifdef RS_THREAD_FORCE_STOP
/** Set last resort timeout to forcefully kill thread if it didn't stop
* nicely, one should never use this, still we needed to introduce this
* to investigate some bugs in external libraries */
void setStopTimeout(rstime_t timeout) { mStopTimeout = timeout; }
#endif
private:
/** Call @see run() setting the appropriate flags around it*/
void wrapRun();
@ -277,6 +287,11 @@ private:
* and that might happens concurrently (or just before) a debug message
* being printed, thus causing the debug message to print a mangled value.*/
pthread_t mLastTid;
#ifdef RS_THREAD_FORCE_STOP
/// @see setStopTimeout
rstime_t mStopTimeout;
#endif
};
/**

View File

@ -0,0 +1,103 @@
/*******************************************************************************
* retroshare-gui/src/gui/Posted/PhotoView.cpp *
* *
* Copyright (C) 2020 by RetroShare Team <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include "PhotoView.h"
#include <QMenu>
#include <QFileDialog>
#include <QMessageBox>
#include "gui/gxs/GxsIdDetails.h"
#include "gui/RetroShareLink.h"
#include <retroshare/rsidentity.h>
#include <retroshare/rsposted.h>
/** Constructor */
PhotoView::PhotoView(QWidget *parent)
: QDialog(parent, Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint),
ui(new Ui::PhotoView)
{
/* Invoke the Qt Designer generated object setup routine */
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
connect(ui->shareButton, SIGNAL(clicked()), this, SLOT(copyMessageLink()));
}
/** Destructor */
PhotoView::~PhotoView()
{
delete ui;
}
void PhotoView::setPixmap(const QPixmap& pixmap)
{
ui->photoLabel->setPixmap(pixmap);
this->adjustSize();
}
void PhotoView::setTitle(const QString& text)
{
ui->titleLabel->setText(text);
}
void PhotoView::setName(const RsGxsId& authorID)
{
ui->nameLabel->setId(authorID);
RsIdentityDetails idDetails ;
rsIdentity->getIdDetails(authorID,idDetails);
QPixmap pixmap ;
if(idDetails.mAvatar.mSize == 0 || !GxsIdDetails::loadPixmapFromData(idDetails.mAvatar.mData, idDetails.mAvatar.mSize, pixmap,GxsIdDetails::SMALL))
pixmap = GxsIdDetails::makeDefaultIcon(authorID,GxsIdDetails::SMALL);
ui->avatarWidget->setPixmap(pixmap);
}
void PhotoView::setTime(const QString& text)
{
ui->timeLabel->setText(text);
}
void PhotoView::setGroupId(const RsGxsGroupId &groupId)
{
mGroupId = groupId;
}
void PhotoView::setMessageId(const RsGxsMessageId& messageId)
{
mMessageId = messageId ;
}
void PhotoView::copyMessageLink()
{
RetroShareLink link = RetroShareLink::createGxsMessageLink(RetroShareLink::TYPE_POSTED, mGroupId, mMessageId, ui->titleLabel->text());
if (link.valid()) {
QList<RetroShareLink> urls;
urls.push_back(link);
RSLinkClipboard::copyLinks(urls);
QMessageBox::information(NULL,tr("information"),tr("The Retrohare link was copied to your clipboard.")) ;
}
}

View File

@ -0,0 +1,65 @@
/*******************************************************************************
* retroshare-gui/src/gui/Posted/PhotoView.h *
* *
* Copyright (C) 2020 by RetroShare Team <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#ifndef _PHOTO_VIEW_H
#define _PHOTO_VIEW_H
#include "ui_PhotoView.h"
#include <QDialog>
namespace Ui {
class PhotoView;
}
class PhotoView : public QDialog
{
Q_OBJECT
public:
/** Default Constructor */
PhotoView(QWidget *parent = 0);
/** Default Destructor */
~PhotoView();
public slots:
void setPixmap(const QPixmap& pixmap);
void setTitle (const QString &text);
void setName(const RsGxsId& authorID);
void setTime(const QString& text);
void setGroupId(const RsGxsGroupId &groupId);
void setMessageId(const RsGxsMessageId& messageId);
private slots:
void copyMessageLink();
private:
RsGxsMessageId mMessageId;
RsGxsGroupId mGroupId;
/** Qt Designer generated object */
Ui::PhotoView *ui;
};
#endif

View File

@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PhotoView</class>
<widget class="QWidget" name="PhotoView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>490</width>
<height>316</height>
</rect>
</property>
<property name="windowTitle">
<string>Photo View</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="titleLabel">
<property name="font">
<font>
<family>MS Sans Serif</family>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<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 row="0" column="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="AspectRatioPixmapLabel" name="photoLabel">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<family>MS Sans Serif</family>
<pointsize>9</pointsize>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Posted by</string>
</property>
</widget>
</item>
<item>
<widget class="AvatarWidget" name="avatarWidget" native="true">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="GxsIdLabel" name="nameLabel">
<property name="font">
<font>
<family>MS Sans Serif</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="timeLabel">
<property name="font">
<font>
<family>MS Sans Serif</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="agoLabel">
<property name="font">
<font>
<family>MS Sans Serif</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string>ago</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="shareButton">
<property name="toolTip">
<string>Copy RetroShare link</string>
</property>
<property name="text">
<string>Share</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/share.png</normaloff>:/images/share.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>GxsIdLabel</class>
<extends>QLabel</extends>
<header>gui/gxs/GxsIdLabel.h</header>
</customwidget>
<customwidget>
<class>AvatarWidget</class>
<extends>QWidget</extends>
<header>gui/common/AvatarWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>AspectRatioPixmapLabel</class>
<extends>QLabel</extends>
<header>util/AspectRatioPixmapLabel.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="Posted_images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,553 @@
/*******************************************************************************
* retroshare-gui/src/gui/Posted/PostedCardView.cpp *
* *
* Copyright (C) 2019 Retroshare Team <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include <QDateTime>
#include <QMenu>
#include <QStyle>
#include <QTextDocument>
#include "rshare.h"
#include "PostedCardView.h"
#include "gui/feeds/FeedHolder.h"
#include "gui/gxs/GxsIdDetails.h"
#include "util/misc.h"
#include "util/HandleRichText.h"
#include "ui_PostedCardView.h"
#include <retroshare/rsposted.h>
#include <iostream>
#define LINK_IMAGE ":/images/thumb-link.png"
/** Constructor */
PostedCardView::PostedCardView(FeedHolder *feedHolder, uint32_t feedId, const RsGxsGroupId &groupId, const RsGxsMessageId &messageId, bool isHome, bool autoUpdate) :
GxsFeedItem(feedHolder, feedId, groupId, messageId, isHome, rsPosted, autoUpdate)
{
setup();
requestGroup();
requestMessage();
requestComment();
}
PostedCardView::PostedCardView(FeedHolder *feedHolder, uint32_t feedId, const RsPostedGroup &group, const RsPostedPost &post, bool isHome, bool autoUpdate) :
GxsFeedItem(feedHolder, feedId, post.mMeta.mGroupId, post.mMeta.mMsgId, isHome, rsPosted, autoUpdate)
{
setup();
mMessageId = post.mMeta.mMsgId;
setGroup(group, false);
setPost(post);
requestComment();
}
PostedCardView::PostedCardView(FeedHolder *feedHolder, uint32_t feedId, const RsPostedPost &post, bool isHome, bool autoUpdate) :
GxsFeedItem(feedHolder, feedId, post.mMeta.mGroupId, post.mMeta.mMsgId, isHome, rsPosted, autoUpdate)
{
setup();
requestGroup();
setPost(post);
requestComment();
}
PostedCardView::~PostedCardView()
{
delete(ui);
}
void PostedCardView::setup()
{
/* Invoke the Qt Designer generated object setup routine */
ui = new Ui::PostedCardView;
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
mInFill = false;
/* clear ui */
ui->titleLabel->setText(tr("Loading"));
ui->dateLabel->clear();
ui->fromLabel->clear();
ui->siteLabel->clear();
/* general ones */
connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(removeItem()));
/* specific */
connect(ui->readAndClearButton, SIGNAL(clicked()), this, SLOT(readAndClearItem()));
connect(ui->commentButton, SIGNAL( clicked()), this, SLOT(loadComments()));
connect(ui->voteUpButton, SIGNAL(clicked()), this, SLOT(makeUpVote()));
connect(ui->voteDownButton, SIGNAL(clicked()), this, SLOT( makeDownVote()));
connect(ui->readButton, SIGNAL(toggled(bool)), this, SLOT(readToggled(bool)));
QAction *CopyLinkAction = new QAction(QIcon(""),tr("Copy RetroShare Link"), this);
connect(CopyLinkAction, SIGNAL(triggered()), this, SLOT(copyMessageLink()));
int S = QFontMetricsF(font()).height() ;
ui->voteUpButton->setIconSize(QSize(S*1.5,S*1.5));
ui->voteDownButton->setIconSize(QSize(S*1.5,S*1.5));
ui->commentButton->setIconSize(QSize(S*1.5,S*1.5));
ui->readButton->setIconSize(QSize(S*1.5,S*1.5));
ui->shareButton->setIconSize(QSize(S*1.5,S*1.5));
QMenu *menu = new QMenu();
menu->addAction(CopyLinkAction);
ui->shareButton->setMenu(menu);
ui->clearButton->hide();
ui->readAndClearButton->hide();
}
bool PostedCardView::setGroup(const RsPostedGroup &group, bool doFill)
{
if (groupId() != group.mMeta.mGroupId) {
std::cerr << "PostedCardView::setGroup() - Wrong id, cannot set post";
std::cerr << std::endl;
return false;
}
mGroup = group;
if (doFill) {
fill();
}
return true;
}
bool PostedCardView::setPost(const RsPostedPost &post, bool doFill)
{
if (groupId() != post.mMeta.mGroupId || messageId() != post.mMeta.mMsgId) {
std::cerr << "PostedCardView::setPost() - Wrong id, cannot set post";
std::cerr << std::endl;
return false;
}
mPost = post;
if (doFill) {
fill();
}
return true;
}
void PostedCardView::loadGroup(const uint32_t &token)
{
std::vector<RsPostedGroup> groups;
if (!rsPosted->getGroupData(token, groups))
{
std::cerr << "PostedCardView::loadGroup() ERROR getting data";
std::cerr << std::endl;
return;
}
if (groups.size() != 1)
{
std::cerr << "PostedCardView::loadGroup() Wrong number of Items";
std::cerr << std::endl;
return;
}
setGroup(groups[0]);
}
void PostedCardView::loadMessage(const uint32_t &token)
{
std::vector<RsPostedPost> posts;
std::vector<RsGxsComment> cmts;
if (!rsPosted->getPostData(token, posts, cmts))
{
std::cerr << "GxsChannelPostItem::loadMessage() ERROR getting data";
std::cerr << std::endl;
return;
}
if (posts.size() == 1)
{
setPost(posts[0]);
}
else if (cmts.size() == 1)
{
RsGxsComment cmt = cmts[0];
//ui->newCommentLabel->show();
//ui->commLabel->show();
//ui->commLabel->setText(QString::fromUtf8(cmt.mComment.c_str()));
//Change this item to be uploaded with thread element.
setMessageId(cmt.mMeta.mThreadId);
requestMessage();
}
else
{
std::cerr << "GxsChannelPostItem::loadMessage() Wrong number of Items. Remove It.";
std::cerr << std::endl;
removeItem();
return;
}
}
void PostedCardView::loadComment(const uint32_t &token)
{
std::vector<RsGxsComment> cmts;
if (!rsPosted->getRelatedComments(token, cmts))
{
std::cerr << "GxsChannelPostItem::loadComment() ERROR getting data";
std::cerr << std::endl;
return;
}
size_t comNb = cmts.size();
QString sComButText = tr("Comment");
if (comNb == 1) {
sComButText = sComButText.append("(1)");
} else if (comNb > 1) {
sComButText = " " + tr("Comments").append(" (%1)").arg(comNb);
}
ui->commentButton->setText(sComButText);
}
void PostedCardView::fill()
{
if (isLoading()) {
/* Wait for all requests */
return;
}
QPixmap sqpixmap2 = QPixmap(":/images/thumb-default.png");
mInFill = true;
int desired_height = 1.5*(ui->voteDownButton->height() + ui->voteUpButton->height() + ui->scoreLabel->height());
int desired_width = sqpixmap2.width()*desired_height/(float)sqpixmap2.height();
QDateTime qtime;
qtime.setTime_t(mPost.mMeta.mPublishTs);
QString timestamp = qtime.toString("hh:mm dd-MMM-yyyy");
QString timestamp2 = misc::timeRelativeToNow(mPost.mMeta.mPublishTs);
ui->dateLabel->setText(timestamp2);
ui->dateLabel->setToolTip(timestamp);
ui->fromLabel->setId(mPost.mMeta.mAuthorId);
// Use QUrl to check/parse our URL
// The only combination that seems to work: load as EncodedUrl, extract toEncoded().
QByteArray urlarray(mPost.mLink.c_str());
QUrl url = QUrl::fromEncoded(urlarray.trimmed());
QString urlstr = "Invalid Link";
QString sitestr = "Invalid Link";
bool urlOkay = url.isValid();
if (urlOkay)
{
QString scheme = url.scheme();
if ((scheme != "https")
&& (scheme != "http")
&& (scheme != "ftp")
&& (scheme != "retroshare"))
{
urlOkay = false;
sitestr = "Invalid Link Scheme";
}
}
if (urlOkay)
{
urlstr = QString("<a href=\"");
urlstr += QString(url.toEncoded());
urlstr += QString("\" ><span style=\" text-decoration: underline; color:#2255AA;\"> ");
urlstr += messageName();
urlstr += QString(" </span></a>");
QString siteurl = url.toEncoded();
sitestr = QString("<a href=\"%1\" ><span style=\" text-decoration: underline; color:#0079d3;\"> %2 </span></a>").arg(siteurl).arg(siteurl);
ui->titleLabel->setText(urlstr);
}else
{
ui->titleLabel->setText(messageName());
}
if (urlarray.isEmpty())
{
ui->siteLabel->hide();
}
ui->siteLabel->setText(sitestr);
if(mPost.mImage.mData != NULL)
{
QPixmap pixmap;
GxsIdDetails::loadPixmapFromData(mPost.mImage.mData, mPost.mImage.mSize, pixmap,GxsIdDetails::ORIGINAL);
// Wiping data - as its been passed to thumbnail.
QPixmap scaledpixmap;
if(pixmap.width() > 800){
QPixmap scaledpixmap = pixmap.scaledToWidth(800, Qt::SmoothTransformation);
ui->pictureLabel->setPixmap(scaledpixmap);
}else{
ui->pictureLabel->setPixmap(pixmap);
}
}
else if (mPost.mImage.mData == NULL)
{
ui->picture_frame->hide();
}
else
{
ui->picture_frame->show();
}
//QString score = "Hot" + QString::number(post.mHotScore);
//score += " Top" + QString::number(post.mTopScore);
//score += " New" + QString::number(post.mNewScore);
QString score = QString::number(mPost.mTopScore);
ui->scoreLabel->setText(score);
// FIX THIS UP LATER.
ui->notes->setText(RsHtml().formatText(NULL, QString::fromUtf8(mPost.mNotes.c_str()), RSHTML_FORMATTEXT_EMBED_SMILEYS | RSHTML_FORMATTEXT_EMBED_LINKS));
QTextDocument doc;
doc.setHtml(ui->notes->text());
if(doc.toPlainText().trimmed().isEmpty())
ui->notes->hide();
// differences between Feed or Top of Comment.
if (mFeedHolder)
{
// feed.
//frame_comment->show();
ui->commentButton->show();
if (mPost.mComments)
{
QString commentText = QString::number(mPost.mComments);
commentText += " ";
commentText += tr("Comments");
ui->commentButton->setText(commentText);
}
else
{
ui->commentButton->setText(tr("Comment"));
}
setReadStatus(IS_MSG_NEW(mPost.mMeta.mMsgStatus), IS_MSG_UNREAD(mPost.mMeta.mMsgStatus) || IS_MSG_NEW(mPost.mMeta.mMsgStatus));
}
else
{
// no feed.
//frame_comment->hide();
ui->commentButton->hide();
ui->readButton->hide();
ui->newLabel->hide();
}
if (mIsHome)
{
ui->clearButton->hide();
ui->readAndClearButton->hide();
}
else
{
ui->clearButton->show();
ui->readAndClearButton->show();
}
// disable voting buttons - if they have already voted.
if (mPost.mMeta.mMsgStatus & GXS_SERV::GXS_MSG_STATUS_VOTE_MASK)
{
ui->voteUpButton->setEnabled(false);
ui->voteDownButton->setEnabled(false);
}
#if 0
uint32_t up, down, nComments;
bool ok = rsPosted->retrieveScores(mPost.mMeta.mServiceString, up, down, nComments);
if(ok)
{
int32_t vote = up - down;
scoreLabel->setText(QString::number(vote));
numCommentsLabel->setText("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px;"
"margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span"
"style=\" font-size:10pt; font-weight:600;\">#</span><span "
"style=\" font-size:8pt; font-weight:600;\"> Comments: "
+ QString::number(nComments) + "</span></p>");
}
#endif
mInFill = false;
emit sizeChanged(this);
}
const RsPostedPost &PostedCardView::getPost() const
{
return mPost;
}
RsPostedPost &PostedCardView::post()
{
return mPost;
}
QString PostedCardView::groupName()
{
return QString::fromUtf8(mGroup.mMeta.mGroupName.c_str());
}
QString PostedCardView::messageName()
{
return QString::fromUtf8(mPost.mMeta.mMsgName.c_str());
}
void PostedCardView::makeDownVote()
{
RsGxsGrpMsgIdPair msgId;
msgId.first = mPost.mMeta.mGroupId;
msgId.second = mPost.mMeta.mMsgId;
ui->voteUpButton->setEnabled(false);
ui->voteDownButton->setEnabled(false);
emit vote(msgId, false);
}
void PostedCardView::makeUpVote()
{
RsGxsGrpMsgIdPair msgId;
msgId.first = mPost.mMeta.mGroupId;
msgId.second = mPost.mMeta.mMsgId;
ui->voteUpButton->setEnabled(false);
ui->voteDownButton->setEnabled(false);
emit vote(msgId, true);
}
void PostedCardView::loadComments()
{
std::cerr << "PostedCardView::loadComments()";
std::cerr << std::endl;
if (mFeedHolder)
{
QString title = QString::fromUtf8(mPost.mMeta.mMsgName.c_str());
#warning (csoler) Posted item versions not handled yet. When it is the case, start here.
QVector<RsGxsMessageId> post_versions ;
post_versions.push_back(mPost.mMeta.mMsgId) ;
mFeedHolder->openComments(0, mPost.mMeta.mGroupId, post_versions,mPost.mMeta.mMsgId, title);
}
}
void PostedCardView::setReadStatus(bool isNew, bool isUnread)
{
if (isUnread)
{
ui->readButton->setChecked(true);
ui->readButton->setIcon(QIcon(":/images/message-state-unread.png"));
}
else
{
ui->readButton->setChecked(false);
ui->readButton->setIcon(QIcon(":/images/message-state-read.png"));
}
ui->newLabel->setVisible(isNew);
ui->mainFrame->setProperty("new", isNew);
ui->mainFrame->style()->unpolish(ui->mainFrame);
ui->mainFrame->style()->polish( ui->mainFrame);
}
void PostedCardView::readToggled(bool checked)
{
if (mInFill) {
return;
}
RsGxsGrpMsgIdPair msgPair = std::make_pair(groupId(), messageId());
uint32_t token;
rsPosted->setMessageReadStatus(token, msgPair, !checked);
setReadStatus(false, checked);
}
void PostedCardView::readAndClearItem()
{
#ifdef DEBUG_ITEM
std::cerr << "PostedCardView::readAndClearItem()";
std::cerr << std::endl;
#endif
readToggled(false);
removeItem();
}
void PostedCardView::doExpand(bool open)
{
/*if (open)
{
}
else
{
}
emit sizeChanged(this);*/
}
void PostedCardView::copyMessageLink()
{
if (groupId().isNull() || mMessageId.isNull()) {
return;
}
RetroShareLink link = RetroShareLink::createGxsMessageLink(RetroShareLink::TYPE_POSTED, groupId(), mMessageId, messageName());
if (link.valid()) {
QList<RetroShareLink> urls;
urls.push_back(link);
RSLinkClipboard::copyLinks(urls);
}
}

View File

@ -0,0 +1,98 @@
/*******************************************************************************
* retroshare-gui/src/gui/Posted/PostedCardView.h *
* *
* Copyright (C) 2019 by Retroshare Team <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#ifndef _POSTED_CARDVIEW_H
#define _POSTED_CARDVIEW_H
#include <QMetaType>
#include <retroshare/rsposted.h>
#include "gui/gxs/GxsFeedItem.h"
namespace Ui {
class PostedCardView;
}
class FeedHolder;
class RsPostedPost;
class PostedCardView : public GxsFeedItem
{
Q_OBJECT
public:
PostedCardView(FeedHolder *parent, uint32_t feedId, const RsGxsGroupId &groupId, const RsGxsMessageId &messageId, bool isHome, bool autoUpdate);
PostedCardView(FeedHolder *parent, uint32_t feedId, const RsPostedGroup &group, const RsPostedPost &post, bool isHome, bool autoUpdate);
PostedCardView(FeedHolder *parent, uint32_t feedId, const RsPostedPost &post, bool isHome, bool autoUpdate);
virtual ~PostedCardView();
bool setGroup(const RsPostedGroup& group, bool doFill = true);
bool setPost(const RsPostedPost& post, bool doFill = true);
const RsPostedPost &getPost() const;
RsPostedPost &post();
uint64_t uniqueIdentifier() const override { return hash_64bits("PostedItem " + mMessageId.toStdString()); }
protected:
/* FeedItem */
virtual void doExpand(bool open);
private slots:
void loadComments();
void makeUpVote();
void makeDownVote();
void readToggled(bool checked);
void readAndClearItem();
void copyMessageLink();
signals:
void vote(const RsGxsGrpMsgIdPair& msgId, bool up);
protected:
/* GxsGroupFeedItem */
virtual QString groupName();
virtual void loadGroup(const uint32_t &token);
virtual RetroShareLink::enumType getLinkType() { return RetroShareLink::TYPE_UNKNOWN; }
/* GxsFeedItem */
virtual QString messageName();
virtual void loadMessage(const uint32_t &token);
virtual void loadComment(const uint32_t &token);
private:
void setup();
void fill();
void setReadStatus(bool isNew, bool isUnread);
private:
bool mInFill;
RsPostedGroup mGroup;
RsPostedPost mPost;
RsGxsMessageId mMessageId;
/** Qt Designer generated object */
Ui::PostedCardView *ui;
};
//Q_DECLARE_METATYPE(RsPostedPost)
#endif

View File

@ -0,0 +1,526 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PostedCardView</class>
<widget class="QWidget" name="PostedCardView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>614</width>
<height>182</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true"/>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<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>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QFrame" name="mainFrame">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<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>2</number>
</property>
<property name="horizontalSpacing">
<number>0</number>
</property>
<property name="verticalSpacing">
<number>6</number>
</property>
<item row="1" column="1">
<widget class="StyledLabel" name="titleLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>10</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string notr="true">This is a very very very very loooooooooooooooonnnnnnnnnnnnnnnnng title don't you think? Yes it is and should wrap around I hope</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="siteLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">site</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<item>
<widget class="QLabel" name="fromBoldLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Posted by</string>
</property>
</widget>
</item>
<item>
<widget class="GxsIdLabel" name="fromLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">Signed by</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="dateLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">You eyes only</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="readButton">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Toggle Message Read Status</string>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<normaloff>:/images/message-state-unread.png</normaloff>:/images/message-state-unread.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="newLabel">
<property name="text">
<string>New</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>70</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="0" rowspan="6">
<widget class="QFrame" name="voteFrame">
<property name="minimumSize">
<size>
<width>37</width>
<height>0</height>
</size>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QToolButton" name="voteUpButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Vote up</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/up-arrow.png</normaloff>:/images/up-arrow.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="StyledLabel" name="scoreLabel">
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="text">
<string>0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="voteDownButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Vote down</string>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>\/</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/down-arrow.png</normaloff>:/images/down-arrow.png</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="commentButton">
<property name="text">
<string>Comments</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/comments.png</normaloff>:/images/comments.png</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="shareButton">
<property name="text">
<string>Share</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/share.png</normaloff>:/images/share.png</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>308</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="readAndClearButton">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Set as read and remove item</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/correct.png</normaloff>:/icons/png/correct.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearButton">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Remove Item</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/exit2.png</normaloff>:/icons/png/exit2.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="1">
<widget class="QLabel" name="notes">
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QFrame" name="picture_frame">
<layout class="QHBoxLayout" name="horizontalPictureLayout">
<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="QLabel" name="pictureLabel">
<property name="text">
<string>PictureLabel</string>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>268</width>
<height>17</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>StyledLabel</class>
<extends>QLabel</extends>
<header>gui/common/StyledLabel.h</header>
</customwidget>
<customwidget>
<class>GxsIdLabel</class>
<extends>QLabel</extends>
<header>gui/gxs/GxsIdLabel.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="Posted_images.qrc"/>
<include location="../images.qrc"/>
<include location="../icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -20,6 +20,8 @@
#include <QBuffer>
#include <QMessageBox>
#include <QByteArray>
#include <QStringList>
#include "PostedCreatePostDialog.h"
#include "ui_PostedCreatePostDialog.h"
@ -34,6 +36,10 @@
#include <iostream>
#include <util/imageutil.h>
#include <gui/RetroShareLink.h>
PostedCreatePostDialog::PostedCreatePostDialog(TokenQueue* tokenQ, RsPosted *posted, const RsGxsGroupId& grpId, QWidget *parent):
QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint),
mTokenQueue(tokenQ), mPosted(posted), mGrpId(grpId),
@ -44,7 +50,7 @@ PostedCreatePostDialog::PostedCreatePostDialog(TokenQueue* tokenQ, RsPosted *pos
connect(ui->submitButton, SIGNAL(clicked()), this, SLOT(createPost()));
connect(ui->buttonBox, SIGNAL(rejected()), this, SLOT(close()));
connect(ui->pushButton, SIGNAL(clicked() ), this , SLOT(addPicture()));
connect(ui->addPicButton, SIGNAL(clicked() ), this , SLOT(addPicture()));
ui->headerFrame->setHeaderImage(QPixmap(":/icons/png/postedlinks.png"));
ui->headerFrame->setHeaderText(tr("Create a new Post"));
@ -52,19 +58,59 @@ PostedCreatePostDialog::PostedCreatePostDialog(TokenQueue* tokenQ, RsPosted *pos
setAttribute ( Qt::WA_DeleteOnClose, true );
ui->RichTextEditWidget->setPlaceHolderTextPosted();
ui->hashBox->setAutoHide(true);
ui->hashBox->setDefaultTransferRequestFlags(RS_FILE_REQ_ANONYMOUS_ROUTING);
connect(ui->hashBox, SIGNAL(fileHashingFinished(QList<HashedFile>)), this, SLOT(fileHashingFinished(QList<HashedFile>)));
ui->sizeWarningLabel->setText(QString("Post size is limited to %1 KB, pictures will be downscaled.").arg(MAXMESSAGESIZE / 1024));
/* fill in the available OwnIds for signing */
ui->idChooser->loadIds(IDCHOOSER_ID_REQUIRED, RsGxsId());
ui->removeButton->hide();
/* load settings */
processSettings(true);
}
PostedCreatePostDialog::~PostedCreatePostDialog()
{
Settings->saveWidgetInformation(this);
// save settings
processSettings(false);
delete ui;
}
void PostedCreatePostDialog::processSettings(bool load)
{
Settings->beginGroup(QString("PostedCreatePostDialog"));
if (load) {
// load settings
// state of ID Chooser combobox
int index = Settings->value("IDChooser", 0).toInt();
ui->idChooser->setCurrentIndex(index);
} else {
// save settings
// state of ID Chooser combobox
Settings->setValue("IDChooser", ui->idChooser->currentIndex());
}
Settings->endGroup();
}
void PostedCreatePostDialog::createPost()
{
if(ui->titleEdit->text().isEmpty()) {
/* error message */
QMessageBox::warning(this, "RetroShare", tr("Please add a Title"), QMessageBox::Ok, QMessageBox::Ok);
return; //Don't add a empty title!!
}
RsGxsId authorId;
switch (ui->idChooser->getChosenId(authorId)) {
case GxsIdChooser::KnowId:
@ -85,37 +131,27 @@ void PostedCreatePostDialog::createPost()
post.mMeta.mGroupId = mGrpId;
post.mLink = std::string(ui->linkEdit->text().toUtf8());
QString text;
text = ui->RichTextEditWidget->toHtml();
post.mNotes = std::string(text.toUtf8());
if(!ui->RichTextEditWidget->toPlainText().trimmed().isEmpty()) {
QString text;
text = ui->RichTextEditWidget->toHtml();
post.mNotes = std::string(text.toUtf8());
}
post.mMeta.mAuthorId = authorId;
if(!ui->titleEdit->text().isEmpty())
{
post.mMeta.mMsgName = std::string(ui->titleEdit->text().toUtf8());
}else
{
post.mMeta.mMsgName = std::string(ui->titleEditLink->text().toUtf8());
}
QByteArray ba;
QBuffer buffer(&ba);
post.mMeta.mMsgName = std::string(ui->titleEdit->text().toUtf8());
if(!picture.isNull())
if(imagebytes.size() > 0)
{
// send posted image
post.mImage.copy((uint8_t *) imagebytes.data(), imagebytes.size());
}
buffer.open(QIODevice::WriteOnly);
picture.save(&buffer, "PNG"); // writes image into ba in PNG format
post.mImage.copy((uint8_t *) ba.data(), ba.size());
int msgsize = post.mLink.length() + post.mMeta.mMsgName.length() + post.mNotes.length() + imagebytes.size();
if(msgsize > MAXMESSAGESIZE) {
QString errormessage = QString(tr("Message is too large.<br />actual size: %1 bytes, maximum size: %2 bytes.")).arg(msgsize).arg(MAXMESSAGESIZE);
QMessageBox::warning(this, "RetroShare", errormessage, QMessageBox::Ok, QMessageBox::Ok);
return;
}
if(ui->titleEdit->text().isEmpty()&& ui->titleEditLink->text().isEmpty()) {
/* error message */
QMessageBox::warning(this, "RetroShare", tr("Please add a Title"), QMessageBox::Ok, QMessageBox::Ok);
return; //Don't add a empty title!!
}//if(ui->titleEdit->text().isEmpty())
uint32_t token;
mPosted->createPost(token, post);
@ -124,17 +160,66 @@ void PostedCreatePostDialog::createPost()
accept();
}
void PostedCreatePostDialog::addPicture()
void PostedCreatePostDialog::fileHashingFinished(QList<HashedFile> hashedFiles)
{
QPixmap img = misc::getOpenThumbnailedPicture(this, tr("Load thumbnail picture"), 800, 600);
if(hashedFiles.length() > 0) { //It seems like it returns 0 if hashing cancelled
HashedFile hashedFile = hashedFiles[0]; //Should be exactly one file
RetroShareLink link;
link = RetroShareLink::createFile(hashedFile.filename, hashedFile.size, QString::fromStdString(hashedFile.hash.toStdString()));
ui->linkEdit->setText(link.toString());
}
ui->submitButton->setEnabled(true);
ui->addPicButton->setEnabled(true);
}
if (img.isNull())
return;
void PostedCreatePostDialog::addPicture()
{
imagefilename = "";
imagebytes.clear();
QPixmap empty;
ui->imageLabel->setPixmap(empty);
picture = img;
// select a picture file
if (misc::getOpenFileName(window(), RshareSettings::LASTDIR_IMAGES, tr("Load Picture File"), "Pictures (*.png *.xpm *.jpg *.jpeg *.gif *.webp )", imagefilename)) {
QString encodedImage;
QImage image;
if (image.load(imagefilename) == false) {
fprintf (stderr, "RsHtml::makeEmbeddedImage() - image \"%s\" can't be load\n", imagefilename.toLatin1().constData());
imagefilename = "";
return;
}
QImage opt;
if(ImageUtil::optimizeSizeBytes(imagebytes, image, opt, 640*480, MAXMESSAGESIZE - 2000)) { //Leave space for other stuff
ui->imageLabel->setPixmap(QPixmap::fromImage(opt));
ui->stackedWidgetPicture->setCurrentIndex(1);
ui->removeButton->show();
} else {
imagefilename = "";
imagebytes.clear();
return;
}
}
//Do we need to hash the image?
QMessageBox::StandardButton answer;
answer = QMessageBox::question(this, tr("Post image"), tr("Do you want to share and link the original image?"), QMessageBox::Yes|QMessageBox::No);
if (answer == QMessageBox::Yes) {
if(!ui->linkEdit->text().trimmed().isEmpty()) {
answer = QMessageBox::question(this, tr("Post image"), tr("You already added a link.<br />Do you want to replace it?"), QMessageBox::Yes|QMessageBox::No);
}
}
//If still yes then link it
if(answer == QMessageBox::Yes) {
ui->submitButton->setEnabled(false);
ui->addPicButton->setEnabled(false);
QStringList files;
files.append(imagefilename);
ui->hashBox->addAttachments(files,RS_FILE_REQ_ANONYMOUS_ROUTING);
}
// to show the selected
ui->imageLabel->setPixmap(picture);
}
void PostedCreatePostDialog::on_postButton_clicked()
@ -151,3 +236,13 @@ void PostedCreatePostDialog::on_linkButton_clicked()
{
ui->stackedWidget->setCurrentIndex(2);
}
void PostedCreatePostDialog::on_removeButton_clicked()
{
imagefilename = "";
imagebytes.clear();
QPixmap empty;
ui->imageLabel->setPixmap(empty);
ui->removeButton->hide();
ui->stackedWidgetPicture->setCurrentIndex(0);
}

View File

@ -22,6 +22,7 @@
#define POSTEDCREATEPOSTDIALOG_H
#include <QDialog>
#include <gui/common/HashBox.h>
#include "retroshare/rsposted.h"
#include "util/RichTextEdit.h"
@ -43,7 +44,10 @@ public:
explicit PostedCreatePostDialog(TokenQueue* tokenQ, RsPosted* posted, const RsGxsGroupId& grpId, QWidget *parent = 0);
~PostedCreatePostDialog();
QPixmap picture;
private:
QString imagefilename;
QByteArray imagebytes;
const int MAXMESSAGESIZE = 199000;
private slots:
void createPost();
@ -51,8 +55,12 @@ private slots:
void on_postButton_clicked();
void on_imageButton_clicked();
void on_linkButton_clicked();
void on_removeButton_clicked();
void fileHashingFinished(QList<HashedFile> hashedFiles);
private:
void processSettings(bool load);
QString mLink;
QString mNotes;
TokenQueue* mTokenQueue;

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>575</width>
<height>429</height>
<height>518</height>
</rect>
</property>
<property name="windowTitle">
@ -48,6 +48,427 @@
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="0" colspan="3">
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="stackedWidgetPage1">
<layout class="QGridLayout" name="gridLayout_5">
<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 row="2" column="0">
<widget class="RichTextEdit" name="RichTextEditWidget" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="stackedWidgetPage2">
<layout class="QGridLayout" name="gridLayout_2">
<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 row="0" column="0" colspan="2">
<widget class="HashBox" name="hashBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Preview</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QStackedWidget" name="stackedWidgetPicture">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="PageAttach">
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout_7">
<property name="topMargin">
<number>9</number>
</property>
<item row="1" column="0">
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="addPicButton">
<property name="text">
<string>Add Picture</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/add-image.png</normaloff>:/icons/png/add-image.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QLabel" name="sizeWarningLabel">
<property name="text">
<string>Post size is limited to 32 KB, pictures will be downscaled.</string>
</property>
</widget>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0" colspan="3">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>267</width>
<height>138</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="PageImage">
<layout class="QGridLayout" name="gridLayout_4">
<property name="topMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<property name="horizontalSpacing">
<number>0</number>
</property>
<property name="verticalSpacing">
<number>2</number>
</property>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="imageLabel">
<property name="maximumSize">
<size>
<width>800</width>
<height>200</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>188</width>
<height>17</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QToolButton" name="removeButton">
<property name="toolTip">
<string>Remove image</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/trashcan.png</normaloff>:/images/trashcan.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="stackedWidgetPage3">
<layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLineEdit" name="linkEdit">
<property name="placeholderText">
<string>Url</string>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>248</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item row="5" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="signedLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Post as</string>
</property>
</widget>
</item>
<item>
<widget class="GxsIdChooser" name="idChooser">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="1">
<widget class="QPushButton" name="submitButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>Post</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<item>
<widget class="QPushButton" name="postButton">
<property name="text">
<string>Post</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/post.png</normaloff>:/images/post.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="imageButton">
<property name="text">
<string>Image</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/photo.png</normaloff>:/images/photo.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="linkButton">
<property name="text">
<string>Link</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/link.png</normaloff>:/images/link.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>298</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="0" column="0" colspan="3">
<widget class="StyledLabel" name="info_label">
<property name="palette">
@ -128,105 +549,7 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<item>
<widget class="QPushButton" name="postButton">
<property name="text">
<string>Post</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/post.png</normaloff>:/images/post.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="imageButton">
<property name="text">
<string>Image</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/photo.png</normaloff>:/images/photo.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="linkButton">
<property name="text">
<string>Link</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/link.png</normaloff>:/images/link.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>298</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="3" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="signedLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Post as</string>
</property>
</widget>
</item>
<item>
<widget class="GxsIdChooser" name="idChooser">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<item row="6" column="0">
<spacer name="buttonHSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -242,207 +565,28 @@
</property>
</spacer>
</item>
<item row="4" column="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="topMargin">
<number>6</number>
</property>
<widget class="QWidget" name="stackedWidgetPage1">
<layout class="QGridLayout" name="gridLayout_5">
<property name="leftMargin">
<number>0</number>
<item>
<widget class="QLineEdit" name="titleEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="topMargin">
<number>6</number>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="rightMargin">
<number>0</number>
<property name="placeholderText">
<string>Title</string>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="1" column="0">
<widget class="RichTextEdit" name="RichTextEditWidget" native="true"/>
</item>
<item row="0" column="0">
<widget class="QLineEdit" name="titleEdit">
<property name="placeholderText">
<string>Title</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="stackedWidgetPage2">
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>447</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="3">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Preview</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1">
<widget class="QLabel" name="imageLabel">
<property name="minimumSize">
<size>
<width>250</width>
<height>200</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>800</width>
<height>200</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Add Picture</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>Picture size is limited to 34 KB</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="stackedWidgetPage3">
<layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item row="1" column="0">
<widget class="QLineEdit" name="linkEdit">
<property name="placeholderText">
<string>Url</string>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>248</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QLineEdit" name="titleEditLink">
<property name="placeholderText">
<string>Title</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="submitButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>Post</string>
</property>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
@ -455,6 +599,12 @@
<extends>QLabel</extends>
<header>gui/common/StyledLabel.h</header>
</customwidget>
<customwidget>
<class>HashBox</class>
<extends>QScrollArea</extends>
<header location="global">gui/common/HashBox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>HeaderFrame</class>
<extends>QFrame</extends>
@ -475,7 +625,6 @@
</customwidgets>
<resources>
<include location="Posted_images.qrc"/>
<include location="../images.qrc"/>
<include location="../icons.qrc"/>
</resources>
<connections>

View File

@ -21,6 +21,7 @@
#include <QDateTime>
#include <QMenu>
#include <QStyle>
#include <QTextDocument>
#include "rshare.h"
#include "PostedItem.h"
@ -28,7 +29,7 @@
#include "gui/gxs/GxsIdDetails.h"
#include "util/misc.h"
#include "util/HandleRichText.h"
#include "PhotoView.h"
#include "ui_PostedItem.h"
#include <retroshare/rsposted.h>
@ -109,11 +110,12 @@ void PostedItem::setup()
connect(ui->notesButton, SIGNAL(clicked()), this, SLOT( toggleNotes()));
connect(ui->readButton, SIGNAL(toggled(bool)), this, SLOT(readToggled(bool)));
connect(ui->thumbnailLabel, SIGNAL(clicked()), this, SLOT(viewPicture()));
QAction *CopyLinkAction = new QAction(QIcon(""),tr("Copy RetroShare Link"), this);
connect(CopyLinkAction, SIGNAL(triggered()), this, SLOT(copyMessageLink()));
int S = QFontMetricsF(font()).height() ;
ui->voteUpButton->setIconSize(QSize(S*1.5,S*1.5));
@ -302,6 +304,11 @@ void PostedItem::fill()
}
if (urlarray.isEmpty())
{
ui->siteLabel->hide();
}
ui->siteLabel->setText(sitestr);
if(mPost.mImage.mData != NULL)
@ -312,7 +319,15 @@ void PostedItem::fill()
QPixmap sqpixmap = pixmap.scaled(desired_width,desired_height, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
ui->thumbnailLabel->setPixmap(sqpixmap);
ui->pictureLabel->setPixmap(pixmap);
ui->thumbnailLabel->setToolTip(tr("Click to view Picture"));
QPixmap scaledpixmap;
if(pixmap.width() > 800){
QPixmap scaledpixmap = pixmap.scaledToWidth(800, Qt::SmoothTransformation);
ui->pictureLabel->setPixmap(scaledpixmap);
}else{
ui->pictureLabel->setPixmap(pixmap);
}
}
else if (urlOkay && (mPost.mImage.mData == NULL))
{
@ -337,7 +352,10 @@ void PostedItem::fill()
// FIX THIS UP LATER.
ui->notes->setText(RsHtml().formatText(NULL, QString::fromUtf8(mPost.mNotes.c_str()), RSHTML_FORMATTEXT_EMBED_SMILEYS | RSHTML_FORMATTEXT_EMBED_LINKS));
if(ui->notes->text().isEmpty())
QTextDocument doc;
doc.setHtml(ui->notes->text());
if(doc.toPlainText().trimmed().isEmpty())
ui->notesButton->hide();
// differences between Feed or Top of Comment.
if (mFeedHolder)
@ -569,3 +587,28 @@ void PostedItem::toggleNotes()
}
}
void PostedItem::viewPicture()
{
if(mPost.mImage.mData == NULL) {
return;
}
QString timestamp = misc::timeRelativeToNow(mPost.mMeta.mPublishTs);
QPixmap pixmap;
GxsIdDetails::loadPixmapFromData(mPost.mImage.mData, mPost.mImage.mSize, pixmap,GxsIdDetails::ORIGINAL);
RsGxsId authorID = mPost.mMeta.mAuthorId;
PhotoView *PView = new PhotoView(this);
PView->setPixmap(pixmap);
PView->setTitle(messageName());
PView->setName(authorID);
PView->setTime(timestamp);
PView->setGroupId(groupId());
PView->setMessageId(mMessageId);
PView->show();
/* window will destroy itself! */
}

View File

@ -63,6 +63,7 @@ private slots:
void toggle() override;
void copyMessageLink();
void toggleNotes();
void viewPicture();
signals:
void vote(const RsGxsGrpMsgIdPair& msgId, bool up);

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>825</width>
<height>339</height>
<height>337</height>
</rect>
</property>
<property name="windowTitle">
@ -109,6 +109,9 @@
<property name="toolTip">
<string>Vote up</string>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string/>
</property>
@ -148,8 +151,8 @@
<property name="toolTip">
<string>Vote down</string>
</property>
<property name="text">
<string>\/</string>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
@ -185,7 +188,7 @@
<number>6</number>
</property>
<item>
<widget class="QLabel" name="thumbnailLabel">
<widget class="ClickableLabel" name="thumbnailLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -267,6 +270,9 @@
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
@ -415,6 +421,9 @@
</property>
<item>
<widget class="QToolButton" name="expandButton">
<property name="toolTip">
<string>Expand</string>
</property>
<property name="text">
<string/>
</property>
@ -428,7 +437,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="readButton">
<widget class="QToolButton" name="readButton">
<property name="maximumSize">
<size>
<width>24</width>
@ -451,7 +460,7 @@
<property name="checked">
<bool>false</bool>
</property>
<property name="flat">
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
@ -482,6 +491,18 @@
</item>
<item>
<widget class="QPushButton" name="shareButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>22</height>
</size>
</property>
<property name="text">
<string>Share</string>
</property>
@ -489,6 +510,9 @@
<iconset resource="Posted_images.qrc">
<normaloff>:/images/share.png</normaloff>:/images/share.png</iconset>
</property>
<property name="default">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
@ -529,24 +553,6 @@
</item>
<item>
<widget class="QPushButton" name="readAndClearButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>44</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>44</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
@ -561,24 +567,6 @@
</item>
<item>
<widget class="QPushButton" name="clearButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>44</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>44</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
@ -634,6 +622,9 @@
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
@ -714,11 +705,16 @@
<extends>QLabel</extends>
<header>gui/gxs/GxsIdLabel.h</header>
</customwidget>
<customwidget>
<class>ClickableLabel</class>
<extends>QLabel</extends>
<header>util/ClickableLabel.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../icons.qrc"/>
<include location="../images.qrc"/>
<include location="Posted_images.qrc"/>
<include location="../images.qrc"/>
<include location="../icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -19,6 +19,7 @@
*******************************************************************************/
#include <QMessageBox>
#include <QSignalMapper>
#include "PostedListWidget.h"
#include "ui_PostedListWidget.h"
@ -27,10 +28,12 @@
#include "gui/gxs/GxsIdDetails.h"
#include "PostedCreatePostDialog.h"
#include "PostedItem.h"
#include "PostedCardView.h"
#include "gui/common/UIStateHelper.h"
#include "gui/RetroShareLink.h"
#include "util/HandleRichText.h"
#include "util/DateTime.h"
#include "gui/settings/rsharesettings.h"
#include <retroshare/rsposted.h>
#include "retroshare/rsgxscircles.h"
@ -42,6 +45,10 @@
#define TOPIC_DEFAULT_IMAGE ":/icons/png/posted.png"
/* View mode */
#define VIEW_MODE_CLASSIC 1
#define VIEW_MODE_CARD 2
/** Constructor */
PostedListWidget::PostedListWidget(const RsGxsGroupId &postedId, QWidget *parent)
: GxsMessageFramePostWidget(rsPosted, parent),
@ -64,6 +71,12 @@ PostedListWidget::PostedListWidget(const RsGxsGroupId &postedId, QWidget *parent
connect(ui->comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(getRankings(int)));
QSignalMapper *signalMapper = new QSignalMapper(this);
connect(ui->classicViewButton, SIGNAL(clicked()), signalMapper, SLOT(map()));
connect(ui->cardViewButton, SIGNAL(clicked()), signalMapper, SLOT(map()));
signalMapper->setMapping(ui->classicViewButton, VIEW_MODE_CLASSIC);
signalMapper->setMapping(ui->cardViewButton, VIEW_MODE_CARD);
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(setViewMode(int)));
// default sort method.
mSortMethod = RsPosted::HotRankType;
@ -103,17 +116,23 @@ PostedListWidget::~PostedListWidget()
delete(ui);
}
void PostedListWidget::processSettings(bool /*load*/)
void PostedListWidget::processSettings(bool load)
{
// Settings->beginGroup(QString("PostedListWidget"));
//
// if (load) {
// // load settings
// } else {
// // save settings
// }
//
// Settings->endGroup();
Settings->beginGroup(QString("PostedListWidget"));
if (load) {
// load settings
/* View mode */
setViewMode(Settings->value("viewMode", VIEW_MODE_CLASSIC).toInt());
} else {
// save settings
/* View mode */
Settings->setValue("viewMode", viewMode());
}
Settings->endGroup();
}
QIcon PostedListWidget::groupIcon()
@ -143,6 +162,12 @@ QScrollArea *PostedListWidget::getScrollArea()
return ui->scrollArea;
}
// Overloaded from FeedHolder.
/*QScrollArea *PostedListWidget::getScrollArea()
{
return ui->scrollAreaCardView;
}*/
void PostedListWidget::deleteFeedItem(FeedItem *, uint32_t /*type*/)
{
#ifdef DEBUG_POSTED_LIST_WIDGET
@ -414,11 +439,23 @@ void PostedListWidget::loadPost(const RsPostedPost &post)
PostedItem *item = new PostedItem(this, 0, dummyGroup, post, true, false);
connect(item, SIGNAL(vote(RsGxsGrpMsgIdPair,bool)), this, SLOT(submitVote(RsGxsGrpMsgIdPair,bool)));
mPosts.insert(post.mMeta.mMsgId, item);
//QLayout *alayout = ui.scrollAreaWidgetContents->layout();
//alayout->addWidget(item);
mPostItems.push_back(item);
}
void PostedListWidget::loadPostCardView(const RsPostedPost &post)
{
/* Group is not always available because of the TokenQueue */
RsPostedGroup dummyGroup;
dummyGroup.mMeta.mGroupId = groupId();
PostedCardView *cvitem = new PostedCardView(this, 0, dummyGroup, post, true, false);
connect(cvitem, SIGNAL(vote(RsGxsGrpMsgIdPair,bool)), this, SLOT(submitVote(RsGxsGrpMsgIdPair,bool)));
mCVPosts.insert(post.mMeta.mMsgId, cvitem);
mPostCardView.push_back(cvitem);
}
static bool CmpPIHot(const GxsFeedItem *a, const GxsFeedItem *b)
{
const PostedItem *aa = dynamic_cast<const PostedItem*>(a);
@ -471,6 +508,58 @@ static bool CmpPINew(const GxsFeedItem *a, const GxsFeedItem *b)
return (aa->getPost().mNewScore > bb->getPost().mNewScore);
}
static bool CVHot(const GxsFeedItem *a, const GxsFeedItem *b)
{
const PostedCardView *aa = dynamic_cast<const PostedCardView*>(a);
const PostedCardView *bb = dynamic_cast<const PostedCardView*>(b);
if (!aa || !bb) {
return true;
}
const RsPostedPost &postA = aa->getPost();
const RsPostedPost &postB = bb->getPost();
if (postA.mHotScore == postB.mHotScore)
{
return (postA.mNewScore > postB.mNewScore);
}
return (postA.mHotScore > postB.mHotScore);
}
static bool CVTop(const GxsFeedItem *a, const GxsFeedItem *b)
{
const PostedCardView *aa = dynamic_cast<const PostedCardView*>(a);
const PostedCardView *bb = dynamic_cast<const PostedCardView*>(b);
if (!aa || !bb) {
return true;
}
const RsPostedPost &postA = aa->getPost();
const RsPostedPost &postB = bb->getPost();
if (postA.mTopScore == postB.mTopScore)
{
return (postA.mNewScore > postB.mNewScore);
}
return (postA.mTopScore > postB.mTopScore);
}
static bool CVNew(const GxsFeedItem *a, const GxsFeedItem *b)
{
const PostedCardView *aa = dynamic_cast<const PostedCardView*>(a);
const PostedCardView *bb = dynamic_cast<const PostedCardView*>(b);
if (!aa || !bb) {
return true;
}
return (aa->getPost().mNewScore > bb->getPost().mNewScore);
}
void PostedListWidget::applyRanking()
{
/* uses current settings to sort posts, then add to layout */
@ -491,6 +580,7 @@ void PostedListWidget::applyRanking()
std::cerr << std::endl;
#endif
qSort(mPostItems.begin(), mPostItems.end(), CmpPIHot);
qSort(mPostCardView.begin(), mPostCardView.end(), CVHot);
break;
case RsPosted::NewRankType:
#ifdef DEBUG_POSTED_LIST_WIDGET
@ -498,6 +588,7 @@ void PostedListWidget::applyRanking()
std::cerr << std::endl;
#endif
qSort(mPostItems.begin(), mPostItems.end(), CmpPINew);
qSort(mPostCardView.begin(), mPostCardView.end(), CVNew);
break;
case RsPosted::TopRankType:
#ifdef DEBUG_POSTED_LIST_WIDGET
@ -505,6 +596,7 @@ void PostedListWidget::applyRanking()
std::cerr << std::endl;
#endif
qSort(mPostItems.begin(), mPostItems.end(), CmpPITop);
qSort(mPostCardView.begin(), mPostCardView.end(), CVTop);
break;
}
mLastSortMethod = mSortMethod;
@ -516,8 +608,11 @@ void PostedListWidget::applyRanking()
/* go through list (skipping out-of-date items) to get */
QLayout *alayout = ui->scrollAreaWidgetContents->layout();
int counter = 0;
time_t min_ts = 0;
foreach (PostedItem *item, mPostItems)
{
#ifdef DEBUG_POSTED_LIST_WIDGET
@ -564,6 +659,46 @@ void PostedListWidget::applyRanking()
++counter;
}
// Card View
counter = 0;
QLayout *cviewlayout = ui->scrollAreaWidgetContentsCardView->layout();
foreach (PostedCardView *item, mPostCardView)
{
std::cerr << "PostedListWidget::applyRanking() Item: " << item;
std::cerr << std::endl;
if (item->getPost().mMeta.mPublishTs < min_ts)
{
std::cerr << "\t Skipping OLD";
std::cerr << std::endl;
item->hide();
continue;
}
if (counter >= mPostIndex + mPostShow)
{
std::cerr << "\t END - Counter too high";
std::cerr << std::endl;
item->hide();
}
else if (counter >= mPostIndex)
{
std::cerr << "\t Adding to Layout";
std::cerr << std::endl;
/* add it in! */
cviewlayout->addWidget(item);
item->show();
}
else
{
std::cerr << "\t Skipping to Low";
std::cerr << std::endl;
item->hide();
}
++counter;
}
#ifdef DEBUG_POSTED_LIST_WIDGET
std::cerr << "PostedListWidget::applyRanking() Loaded New Order";
std::cerr << std::endl;
@ -571,6 +706,8 @@ void PostedListWidget::applyRanking()
// trigger a redraw.
ui->scrollAreaWidgetContents->update();
ui->scrollAreaWidgetContentsCardView->update();
}
void PostedListWidget::blank()
@ -580,10 +717,17 @@ void PostedListWidget::blank()
}
void PostedListWidget::clearPosts()
{
/* clear all messages */
/* clear all classic view messages */
foreach (PostedItem *item, mPostItems) {
delete(item);
}
/* clear all card view messages */
foreach (PostedCardView *item, mPostCardView) {
delete(item);
}
mPostCardView.clear();
mPostItems.clear();
mPosts.clear();
}
@ -641,6 +785,45 @@ void PostedListWidget::shallowClearPosts()
PostedItem *item = *pit;
alayout->removeWidget(item);
}
//Posted Card view
std::list<PostedCardView *> postedCardViewItems;
std::list<PostedCardView *>::iterator pcvit;
QLayout *cviewlayout = ui->scrollAreaWidgetContentsCardView->layout();
int countcv = cviewlayout->count();
for(int i = 0; i < countcv; ++i)
{
QLayoutItem *litem = cviewlayout->itemAt(i);
if (!litem)
{
std::cerr << "PostedListWidget::shallowClearPosts() missing litem";
std::cerr << std::endl;
continue;
}
PostedCardView *item = dynamic_cast<PostedCardView *>(litem->widget());
if (item)
{
std::cerr << "PostedListWidget::shallowClearPosts() item: " << item;
std::cerr << std::endl;
postedCardViewItems.push_back(item);
}
else
{
std::cerr << "PostedListWidget::shallowClearPosts() Found Child, which is not a PostedItem???";
std::cerr << std::endl;
}
}
for(pcvit = postedCardViewItems.begin(); pcvit != postedCardViewItems.end(); ++pcvit)
{
PostedCardView *item = *pcvit;
cviewlayout->removeWidget(item);
}
}
bool PostedListWidget::insertGroupData(const uint32_t &token, RsGroupMetaData &metaData)
@ -668,6 +851,7 @@ void PostedListWidget::insertAllPosts(const uint32_t &token, GxsMessageFramePost
{
RsPostedPost& p = *vit;
loadPost(p);
loadPostCardView(p);
}
applyRanking();
@ -701,6 +885,7 @@ void PostedListWidget::insertPosts(const uint32_t &token)
#endif
/* insert new entry */
loadPost(p);
loadPostCardView(p);
}
}
@ -767,3 +952,39 @@ void PostedListWidget::loadRequest(const TokenQueue *queue, const TokenRequest &
GxsMessageFramePostWidget::loadRequest(queue, req);
}
int PostedListWidget::viewMode()
{
if (ui->classicViewButton->isChecked()) {
return VIEW_MODE_CLASSIC;
} else if (ui->cardViewButton->isChecked()) {
return VIEW_MODE_CARD;
}
/* Default */
return VIEW_MODE_CLASSIC;
}
void PostedListWidget::setViewMode(int viewMode)
{
switch (viewMode) {
case VIEW_MODE_CLASSIC:
ui->stackedWidget->setCurrentIndex(0);
ui->classicViewButton->setChecked(true);
ui->cardViewButton->setChecked(false);
break;
case VIEW_MODE_CARD:
ui->stackedWidget->setCurrentIndex(1);
ui->cardViewButton->setChecked(true);
ui->classicViewButton->setChecked(false);
break;
default:
setViewMode(VIEW_MODE_CLASSIC);
return;
}
}

View File

@ -29,6 +29,7 @@
class RsPostedGroup;
class RsPostedPost;
class PostedItem;
class PostedCardView;
namespace Ui {
class PostedListWidget;
@ -79,16 +80,21 @@ private slots:
void showNext();
void showPrev();
void setViewMode(int viewMode);
private:
void processSettings(bool load);
void updateShowText();
int viewMode();
/*!
* Only removes it from layout
*/
void shallowClearPosts();
void loadPost(const RsPostedPost &post);
void loadPostCardView(const RsPostedPost &post);
void insertPostedDetails(const RsPostedGroup &group);
@ -115,6 +121,9 @@ private:
QMap<RsGxsMessageId, PostedItem*> mPosts;
QList<PostedItem*> mPostItems;
QMap<RsGxsMessageId, PostedCardView*> mCVPosts;
QList<PostedCardView*> mPostCardView;
/* UI - from Designer */
Ui::PostedListWidget *ui;
};

View File

@ -7,16 +7,13 @@
<x>0</x>
<y>0</y>
<width>616</width>
<height>428</height>
<height>595</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>3</number>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
@ -29,7 +26,10 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QFrame" name="headerFrame">
<property name="frameShape">
<enum>QFrame::Box</enum>
@ -37,12 +37,9 @@
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>6</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>4</number>
<number>6</number>
</property>
<property name="topMargin">
<number>2</number>
@ -164,6 +161,59 @@
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="classicViewButton">
<property name="toolTip">
<string>Classic view</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/classic.png</normaloff>:/images/classic.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cardViewButton">
<property name="toolTip">
<string>Card View</string>
</property>
<property name="icon">
<iconset resource="Posted_images.qrc">
<normaloff>:/images/card.png</normaloff>:/images/card.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="prevButton">
<property name="toolTip">
@ -205,7 +255,7 @@
</layout>
</widget>
</item>
<item>
<item row="1" column="0">
<widget class="QFrame" name="infoframe">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
@ -478,30 +528,13 @@ p, li { white-space: pre-wrap; }
</layout>
</widget>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
<item row="2" column="0">
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>614</width>
<height>16</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
@ -514,6 +547,102 @@ p, li { white-space: pre-wrap; }
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>614</width>
<height>16</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<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>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QGridLayout" name="gridLayout_4">
<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 row="0" column="0">
<widget class="QScrollArea" name="scrollAreaCardView">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContentsCardView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>614</width>
<height>16</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<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>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>

View File

@ -1,6 +1,6 @@
<RCC>
<qresource prefix="/" >
<file>images/posted_16.png</file>
<file>images/posted_16.png</file>
<file>images/posted_24.png</file>
<file>images/posted_32.png</file>
<file>images/posted_48.png</file>
@ -21,7 +21,13 @@
<file>images/share.png</file>
<file>images/notes.png</file>
<file>images/link.png</file>
<file>images/linkext.png</file>
<file>images/post.png</file>
<file>images/photo.png</file>
<file>images/classic.png</file>
<file>images/card.png</file>
<file>images/down-hover.png</file>
<file>images/up-hover.png</file>
<file>images/trashcan.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -396,11 +396,6 @@ GxsChannelPostItem QLabel#newLabel {
border-radius: 3px;
}
GxsChannelPostItem QFrame#msgFrame {
border: 2px solid #238;
border-radius: 10px;
}
GxsChannelPostItem QLabel#logoLabel {
border: 2px solid #D3D3D3;
}
@ -430,12 +425,6 @@ ForumMsgItem QFrame#prevFrame {
border-radius: 10px;
}
SubFileItem > QFrame#frame {
border: 2px solid #238;
background: white;
border-radius: 10px;
}
SubFileItem QProgressBar#progressBar {
border: 1px solid black;
text-align: center;
@ -857,6 +846,22 @@ PostedItem QLabel#fromBoldLabel, QLabel#fromLabel, QLabel#dateLabel, QLabel#site
color: #787c7e;
}
PostedItem QLabel#newLabel {
border: 1px solid #167BE7;
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #2291E0, stop: 1 #3EB3FF);
border-radius: 3px;
}
PostedCardView QLabel#newLabel {
border: 1px solid #167BE7;
background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #2291E0, stop: 1 #3EB3FF);
border-radius: 3px;
}
PostedCardView QFrame#voteFrame {
background: #f8f9fa;
}
GxsCommentDialog QComboBox#sortBox {
font: bold;
color: #0099cc;

View File

@ -112,28 +112,41 @@ QString JsonApiPage::helpText() const { return ""; }
bool JsonApiPage::checkStartJsonApi()
{
if(!Settings->getJsonApiEnabled())
return false;
if(!Settings->getJsonApiEnabled()) return false;
rsJsonApi->setListeningPort(Settings->getJsonApiPort());
rsJsonApi->setBindingAddress(Settings->getJsonApiListenAddress().toStdString());
rsJsonApi->restart();
const auto rErr = rsJsonApi->restart();
if(rErr == RsJsonApiErrorNum::NOT_A_MACHINE_GUN)
{
RsDbg() << __PRETTY_FUNCTION__ << " apparently the user is attempting "
<< "to restart JSON API service in a burst. Re-scheduling "
<< "restart in a while..." << std::endl;
RsThread::async([]()
{
std::this_thread::sleep_for(std::chrono::seconds(10));
rsJsonApi->restart();
});
}
else if(rErr)
{
RsErr() << __PRETTY_FUNCTION__ << rErr << std::endl;
return false;
}
return true;
}
/*static*/ void JsonApiPage::checkShutdownJsonApi()
{
if(!rsJsonApi->isRunning()) return;
rsJsonApi->fullstop(); // this is a blocks until the thread is terminated.
}
void JsonApiPage::onApplyClicked()
{
// restart
checkShutdownJsonApi();
checkStartJsonApi();
// restart
checkStartJsonApi();
}
void JsonApiPage::checkToken(QString s)

View File

@ -934,6 +934,7 @@ QPushButton::menu-indicator {
subcontrol-origin: padding;
subcontrol-position: bottom right;
bottom: 4px;
}
QPushButton:pressed {
@ -1836,6 +1837,7 @@ QToolBox QScrollArea QWidget QWidget {
QFrame {
border-radius: 4px;
border: 1px solid #32414B;
}
QFrame[frameShape="0"] {
@ -2045,4 +2047,95 @@ GxsChannelPostsWidget QToolButton#subscribeToolButton::menu-arrow {
GxsChannelPostsWidget QToolButton#subscribeToolButton::menu-button {
image: none;
}
}
QTabBar#smTab::tab{
height: 32px;
width: 32px;
}
PostedCreatePostDialog QPushButton#submitButton {
font: bold;
font-size: 15px;
color: white;
background: #0099cc;
border-radius: 4px;
min-width: 2em;
}
PostedCreatePostDialog QPushButton#submitButton:hover {
background: #03b1f3;
border-radius: 4px;
min-width: 2em;
}
PostedItem QFrame#mainFrame {
border-radius: 4px;
border: 1px solid #32414B;
background-color: #19232D;
}
GxsChannelPostItem QFrame#mainFrame {
border-radius: 4px;
border: 1px solid #32414B;
background-color: #19232D;
}
PostedItem QPushButton#shareButton
{
background-color: transparent;
min-width: 80px;
max-height: 22px;
}
PostedItem QLabel#scoreLabel
{
background-color: transparent;
}
PostedItem QFrame#voteFrame {
background: #141415;
}
PostedItem QToolButton#voteDownButton, QToolButton#voteUpButton
{
border: none;
}
PostedItem QLabel#thumbnailLabel{
border: 2px solid #CCCCCC;
border-radius: 3px;
}
PostedCardView QPushButton#shareButton
{
background-color: transparent;
min-width: 80px;
max-height: 22px;
}
PostedCardView QFrame#voteFrame {
background: #141415;
}
PostedCardView QFrame#mainFrame {
background-color: #19232D;
}
PostedCardView QFrame#mainFrame [new=false]{
background: #19232D;
}
PostedCardView > QFrame#mainFrame[new=true] {
background-color: #005000;
}

View File

@ -996,7 +996,7 @@ QToolButton::menu-arrow:open {
QPushButton::menu-indicator {
subcontrol-origin: padding;
subcontrol-position: bottom right;
left: 8px;
}
QTableView
@ -1209,3 +1209,85 @@ GxsChannelPostsWidget QToolButton#subscribeToolButton::menu-button {
image: none;
}
QTabBar#smTab::tab{
height: 32px;
width: 32px;
}
PostedCreatePostDialog QPushButton#submitButton {
font: bold;
font-size: 15px;
color: white;
background: #0099cc;
border-radius: 4px;
min-width: 2em;
}
PostedCreatePostDialog QPushButton#submitButton:hover {
background: #03b1f3;
border-radius: 4px;
min-width: 2em;
}
GxsForumThreadWidget QLabel#forumName
{
qproperty-fontSizeFactor: 140;
color: #0099cc;
font-size: 15px;
font: bold;
}
PostedItem QPushButton#shareButton
{
background-color: transparent;
border: none;
min-width: 75px;
max-height: 22px;
}
PostedCardView QPushButton#shareButton
{
background-color: transparent;
border: none;
min-width: 75px;
}
PostedItem QFrame#voteFrame {
background: #141415;
}
PostedCardView QFrame#voteFrame {
background: #141415;
}
QPushButton#shareButton:hover, QPushButton#shareButton::menu-button:hover {
background-color: #4A4949;
border: 1px solid gray;
}
PostedItem QToolButton#voteDownButton, QToolButton#voteUpButton, QToolButton#expandButton, QToolButton#readButton,
QToolButton#commentButton, QToolButton#notesButton
{
border: none;
}
PostedItem QLabel#thumbnailLabel{
border: 2px solid #CCCCCC;
border-radius: 3px;
}
PostedCardView QToolButton#voteDownButton, QToolButton#voteUpButton
{
border: none;
}
PostedCardView QFrame#mainFrame [new=false]{
background: #302F2F;
}
PostedCardView > QFrame#mainFrame[new=true] {
background-color: #005000;
}

View File

@ -469,6 +469,8 @@ HEADERS += rshare.h \
util/QtVersion.h \
util/RsFile.h \
util/qtthreadsutils.h \
util/ClickableLabel.h \
util/AspectRatioPixmapLabel.h \
gui/profile/ProfileWidget.h \
gui/profile/ProfileManager.h \
gui/profile/StatusMessage.h \
@ -828,6 +830,8 @@ SOURCES += main.cpp \
util/ObjectPainter.cpp \
util/RsFile.cpp \
util/RichTextEdit.cpp \
util/ClickableLabel.cpp \
util/AspectRatioPixmapLabel.cpp \
gui/profile/ProfileWidget.cpp \
gui/profile/StatusMessage.cpp \
gui/profile/ProfileManager.cpp \
@ -1344,9 +1348,11 @@ posted {
HEADERS += gui/Posted/PostedDialog.h \
gui/Posted/PostedListWidget.h \
gui/Posted/PostedItem.h \
gui/Posted/PostedCardView.h \
gui/Posted/PostedGroupDialog.h \
gui/feeds/PostedGroupItem.h \
gui/Posted/PostedCreatePostDialog.h \
gui/Posted/PhotoView.h \
gui/Posted/PostedUserNotify.h
#gui/Posted/PostedCreateCommentDialog.h \
@ -1355,8 +1361,9 @@ posted {
FORMS += gui/Posted/PostedListWidget.ui \
gui/feeds/PostedGroupItem.ui \
gui/Posted/PostedItem.ui \
gui/Posted/PostedCardView.ui \
gui/Posted/PostedCreatePostDialog.ui \
gui/Posted/PhotoView.ui
#gui/Posted/PostedDialog.ui \
#gui/Posted/PostedComments.ui \
#gui/Posted/PostedCreateCommentDialog.ui
@ -1365,8 +1372,10 @@ posted {
gui/Posted/PostedListWidget.cpp \
gui/feeds/PostedGroupItem.cpp \
gui/Posted/PostedItem.cpp \
gui/Posted/PostedCardView.cpp \
gui/Posted/PostedGroupDialog.cpp \
gui/Posted/PostedCreatePostDialog.cpp \
gui/Posted/PhotoView.cpp \
gui/Posted/PostedUserNotify.cpp
#gui/Posted/PostedDialog.cpp \

View File

@ -0,0 +1,59 @@
/*******************************************************************************
* retroshare-gui/src/util/AspectRatioPixmapLabel.cpp *
* *
* Copyright (C) 2019 Retroshare Team <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include "AspectRatioPixmapLabel.h"
#include <iostream>
AspectRatioPixmapLabel::AspectRatioPixmapLabel(QWidget *parent) :
QLabel(parent)
{
this->setMinimumSize(1,1);
setScaledContents(false);
}
void AspectRatioPixmapLabel::setPixmap ( const QPixmap & p)
{
pix = p;
QLabel::setPixmap(pix);
//std::cout << "Information size: " << pix.width() << 'x' << pix.height() << std::endl;
}
int AspectRatioPixmapLabel::heightForWidth( int width ) const
{
return pix.isNull() ? this->height() : ((qreal)pix.height()*width)/pix.width();
}
QSize AspectRatioPixmapLabel::sizeHint() const
{
return QSize(pix.width(), pix.height());
}
QPixmap AspectRatioPixmapLabel::scaledPixmap() const
{
return pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
void AspectRatioPixmapLabel::resizeEvent(QResizeEvent * e)
{
if(!pix.isNull())
QLabel::setPixmap(scaledPixmap());
QLabel::resizeEvent(e);
//std::cout << "Information resized: " << e->oldSize().width() << 'x' << e->oldSize().height() << " to " << e->size().width() << 'x' << e->size().height() << std::endl;
}

View File

@ -0,0 +1,44 @@
/*******************************************************************************
* retroshare-gui/src/util/AspectRatioPixmapLabel.h *
* *
* Copyright (C) 2019 Retroshare Team <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#ifndef ASPECTRATIOPIXMAPLABEL_H
#define ASPECTRATIOPIXMAPLABEL_H
#include <QLabel>
#include <QPixmap>
#include <QResizeEvent>
class AspectRatioPixmapLabel : public QLabel
{
Q_OBJECT
public:
explicit AspectRatioPixmapLabel(QWidget *parent = nullptr);
virtual int heightForWidth( int width ) const override;
virtual QSize sizeHint() const override;
QPixmap scaledPixmap() const;
public slots:
void setPixmap ( const QPixmap & );
protected:
void resizeEvent(QResizeEvent *event) override;
private:
QPixmap pix;
};
#endif // ASPECTRATIOPIXMAPLABEL_H

View File

@ -0,0 +1,35 @@
/*******************************************************************************
* retroshare-gui/src/util/ClickableLabel.cpp *
* *
* Copyright (C) 2020 by RetroShare Team <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include "ClickableLabel.h"
/** Constructor */
ClickableLabel::ClickableLabel(QWidget* parent, Qt::WindowFlags f)
: QLabel(parent) {
}
ClickableLabel::~ClickableLabel() {
}
void ClickableLabel::mousePressEvent(QMouseEvent* event) {
emit clicked();
}

View File

@ -0,0 +1,47 @@
/*******************************************************************************
* retroshare-gui/src/util/ClickableLabel.h *
* *
* Copyright (C) 2020 by RetroShare Team <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#ifndef CLICKABLELABEL_H
#define CLICKABLELABEL_H
#include <QLabel>
#include <QWidget>
#include <Qt>
class ClickableLabel : public QLabel {
Q_OBJECT
public:
explicit ClickableLabel(QWidget* parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());
~ClickableLabel();
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent* event);
void enterEvent(QEvent *ev) override { setStyleSheet("QLabel { border: 1px solid #3A3939; }");}
void leaveEvent(QEvent *ev) override { setStyleSheet("QLabel { border: 2px solid #CCCCCC; border-radius: 3px; }");}
};
#endif // CLICKABLELABEL_H

View File

@ -1185,7 +1185,7 @@ bool RsHtml::makeEmbeddedImage(const QImage &originalImage, QString &embeddedIma
{
rstime::RsScopeTimer s("Embed image");
QImage opt;
return ImageUtil::optimizeSize(embeddedImage, originalImage, opt, maxPixels, maxBytes);
return ImageUtil::optimizeSizeHtml(embeddedImage, originalImage, opt, maxPixels, maxBytes);
}
QString RsHtml::plainText(const QString &text)

View File

@ -585,7 +585,7 @@ void RichTextEdit::setText(const QString& text) {
void RichTextEdit::insertImage() {
QString file;
if (misc::getOpenFileName(window(), RshareSettings::LASTDIR_IMAGES, tr("Load Picture File"), "Pictures (*.png *.xpm *.jpg *.jpeg)", file)) {
if (misc::getOpenFileName(window(), RshareSettings::LASTDIR_IMAGES, tr("Load Picture File"), "Pictures (*.png *.xpm *.jpg *.jpeg *.gif *.webp)", file)) {
QString encodedImage;
if (RsHtml::makeEmbeddedImage(file, encodedImage, 640*480, MAX_ALLOWED_GXS_MESSAGE_SIZE - 200)) {
QTextDocumentFragment fragment = QTextDocumentFragment::fromHtml(encodedImage);

View File

@ -68,12 +68,12 @@ void ImageUtil::extractImage(QWidget *window, QTextCursor cursor, QString file)
}
}
bool ImageUtil::optimizeSize(QString &html, const QImage& original, QImage &optimized, int maxPixels, int maxBytes)
bool ImageUtil::optimizeSizeBytes(QByteArray &bytearray, const QImage &original, QImage &optimized, int maxPixels, int maxBytes)
{
//nothing to do if it fits into the limits
optimized = original;
if ((maxPixels <= 0) || (optimized.width()*optimized.height() <= maxPixels)) {
int s = checkSize(html, optimized, maxBytes);
int s = checkSize(bytearray, optimized);
if((maxBytes <= 0) || (s <= maxBytes)) {
return true;
}
@ -92,7 +92,7 @@ bool ImageUtil::optimizeSize(QString &html, const QImage& original, QImage &opti
//if maxBytes not defined, do not reduce color space, just downscale
if(maxBytes <= 0) {
checkSize(html, optimized = original.scaledToWidth(maxwidth, Qt::SmoothTransformation), maxBytes);
checkSize(bytearray, optimized = original.scaledToWidth(maxwidth, Qt::SmoothTransformation));
return true;
}
@ -100,9 +100,9 @@ bool ImageUtil::optimizeSize(QString &html, const QImage& original, QImage &opti
quantization(original, ct);
//Use binary search to find a suitable image size + linear regression to guess the file size
double maxsize = (double)checkSize(html, optimized = original.scaledToWidth(maxwidth, Qt::SmoothTransformation).convertToFormat(QImage::Format_Indexed8, ct, Qt::ThresholdDither), maxBytes);
double maxsize = (double)checkSize(bytearray, optimized = original.scaledToWidth(maxwidth, Qt::SmoothTransformation).convertToFormat(QImage::Format_Indexed8, ct, Qt::ThresholdDither));
if(maxsize <= maxBytes) return true; //success
double minsize = (double)checkSize(html, optimized = original.scaledToWidth(minwidth, Qt::SmoothTransformation).convertToFormat(QImage::Format_Indexed8, ct, Qt::ThresholdDither), maxBytes);
double minsize = (double)checkSize(bytearray, optimized = original.scaledToWidth(minwidth, Qt::SmoothTransformation).convertToFormat(QImage::Format_Indexed8, ct, Qt::ThresholdDither));
if(minsize > maxBytes) return false; //impossible
// std::cout << "maxS: " << maxsize << " minS: " << minsize << std::endl;
@ -115,7 +115,7 @@ bool ImageUtil::optimizeSize(QString &html, const QImage& original, QImage &opti
double b = maxsize - m * ((double)maxwidth * (double)maxwidth / whratio);
double a = ((double)(maxBytes - region/2) - b) / m; //maxBytes - region/2 target the center of the accepted region
int nextwidth = (int)sqrt(a * whratio);
int nextsize = checkSize(html, optimized = original.scaledToWidth(nextwidth, Qt::SmoothTransformation).convertToFormat(QImage::Format_Indexed8, ct, Qt::ThresholdDither), maxBytes);
int nextsize = checkSize(bytearray, optimized = original.scaledToWidth(nextwidth, Qt::SmoothTransformation).convertToFormat(QImage::Format_Indexed8, ct, Qt::ThresholdDither));
if(nextsize <= maxBytes) {
minsize = nextsize;
minwidth = nextwidth;
@ -137,34 +137,41 @@ bool ImageUtil::optimizeSize(QString &html, const QImage& original, QImage &opti
//std::cout << html.toStdString() << std::endl;
}
int ImageUtil::checkSize(QString &embeddedImage, const QImage &img, int maxBytes)
bool ImageUtil::optimizeSizeHtml(QString &html, const QImage& original, QImage &optimized, int maxPixels, int maxBytes)
{
QByteArray bytearray;
if(maxBytes > 0){
maxBytes = maxBytes * 3/4 - 50; //base64 and html stuff
if(maxBytes < 1) maxBytes = 1;
}
if(optimizeSizeBytes(bytearray, original, optimized, maxPixels, maxBytes))
{
QByteArray encodedByteArray = bytearray.toBase64();
html = "<img src=\"data:image/png;base64,";
html.append(encodedByteArray);
html.append("\">");
return true;
}
return false;
}
int ImageUtil::checkSize(QByteArray &bytearray, const QImage &img)
{
rstime::RsScopeTimer st("Check size");
QByteArray bytearray;
bytearray.clear();
QBuffer buffer(&bytearray);
int size = 0;
//std::cout << QString("Trying image: format PNG, size %1x%2, colors %3\n").arg(img.width()).arg(img.height()).arg(img.colorCount()).toStdString();
if (buffer.open(QIODevice::WriteOnly)) {
if (img.save(&buffer, "PNG", 0)) {
size = bytearray.length() * 4/3;
if((maxBytes > 0) && (size > maxBytes)) // *4/3 for base64
{
//std::cout << QString("\tToo large, size: %1, limit: %2 bytes\n").arg(bytearray.length() * 4/3).arg(maxBytes).toStdString();
}else{
//std::cout << QString("\tOK, size: %1, limit: %2 bytes\n").arg(bytearray.length() * 4/3).arg(maxBytes).toStdString();
QByteArray encodedByteArray = bytearray.toBase64();
//embeddedImage = "<img width=\"%1\" src=\"data:image/png;base64,";
embeddedImage = "<img src=\"data:image/png;base64,";
embeddedImage.append(encodedByteArray);
embeddedImage.append("\">");
}
size = bytearray.length();
} else {
std::cerr << "ImageUtil: image can't be saved to buffer" << std::endl;
}
buffer.close();
bytearray.clear();
} else {
std::cerr << "ImageUtil: buffer can't be opened" << std::endl;
}

View File

@ -23,6 +23,7 @@
#include <QTextCursor>
#include <QWidget>
#include <QByteArray>
#include <qiterator.h>
class ImageUtil
@ -31,10 +32,11 @@ public:
ImageUtil();
static void extractImage(QWidget *window, QTextCursor cursor, QString file = "");
static bool optimizeSize(QString &html, const QImage& original, QImage &optimized, int maxPixels = -1, int maxBytes = -1);
static bool optimizeSizeHtml(QString &html, const QImage& original, QImage &optimized, int maxPixels = -1, int maxBytes = -1);
static bool optimizeSizeBytes(QByteArray &bytearray, const QImage& original, QImage &optimized, int maxPixels = -1, int maxBytes = -1);
private:
static int checkSize(QString& embeddedImage, const QImage& img, int maxBytes = -1);
static int checkSize(QByteArray& embeddedImage, const QImage& img);
static void quantization(const QImage& img, QVector<QRgb>& palette);
static void quantization(QList<QRgb>::iterator begin, QList<QRgb>::iterator end, int depth, QVector<QRgb>& palette);
static void avgbucket(QList<QRgb>::iterator begin, QList<QRgb>::iterator end, QVector<QRgb>& palette);