merged upstream/master
@ -1 +1 @@
|
||||
Subproject commit c064abd74b27e1cc440917e9dbac800316bb8470
|
||||
Subproject commit 879d5dfe8dcd8995be753120cf1b8bab4dd2ec82
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
};
|
||||
|
||||
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
|
103
retroshare-gui/src/gui/Posted/PhotoView.cpp
Normal 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.")) ;
|
||||
}
|
||||
}
|
65
retroshare-gui/src/gui/Posted/PhotoView.h
Normal 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
|
||||
|
229
retroshare-gui/src/gui/Posted/PhotoView.ui
Normal 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>
|
553
retroshare-gui/src/gui/Posted/PostedCardView.cpp
Normal 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);
|
||||
}
|
||||
}
|
98
retroshare-gui/src/gui/Posted/PostedCardView.h
Normal 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
|
526
retroshare-gui/src/gui/Posted/PostedCardView.ui
Normal 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>
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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! */
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ private slots:
|
||||
void toggle() override;
|
||||
void copyMessageLink();
|
||||
void toggleNotes();
|
||||
void viewPicture();
|
||||
|
||||
signals:
|
||||
void vote(const RsGxsGrpMsgIdPair& msgId, bool up);
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
BIN
retroshare-gui/src/gui/Posted/images/card.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
retroshare-gui/src/gui/Posted/images/classic.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.6 KiB |
BIN
retroshare-gui/src/gui/Posted/images/down-hover.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
retroshare-gui/src/gui/Posted/images/linkext.png
Normal file
After Width: | Height: | Size: 660 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
retroshare-gui/src/gui/Posted/images/trashcan.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.5 KiB |
BIN
retroshare-gui/src/gui/Posted/images/up-hover.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 \
|
||||
|
59
retroshare-gui/src/util/AspectRatioPixmapLabel.cpp
Normal 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;
|
||||
}
|
44
retroshare-gui/src/util/AspectRatioPixmapLabel.h
Normal 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
|
35
retroshare-gui/src/util/ClickableLabel.cpp
Normal 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();
|
||||
}
|
47
retroshare-gui/src/util/ClickableLabel.h
Normal 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
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|