mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 03:18:07 +00:00
Chat message content
This commit is contained in:
parent
af2350cd16
commit
9d5935fb53
72 changed files with 6660 additions and 1266 deletions
|
|
@ -263,16 +263,16 @@ else()
|
|||
include(cmake/TasksMacos.cmake)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT_KEYCHAIN)
|
||||
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${PROJECT_SOURCE_DIR}/external/qtkeychain)
|
||||
target_link_libraries(${TARGET_NAME} PUBLIC ${QTKEYCHAIN_TARGET_NAME})
|
||||
message(STATUS "link libraries: ${TARGET_NAME} ${QTKEYCHAIN_TARGET_NAME}")
|
||||
endif()
|
||||
# if (ENABLE_QT_KEYCHAIN)
|
||||
# target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${PROJECT_SOURCE_DIR}/external/qtkeychain)
|
||||
# target_link_libraries(${TARGET_NAME} PUBLIC ${QTKEYCHAIN_TARGET_NAME})
|
||||
# message(STATUS "link libraries: ${TARGET_NAME} ${QTKEYCHAIN_TARGET_NAME}")
|
||||
# message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
# message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
|
||||
# message(STATUS "Contents of qtkeychain:")
|
||||
# file(GLOB QTKEYCHAIN_HEADERS "${PROJECT_SOURCE_DIR}/external/qtkeychain/qtkeychain/*.h")
|
||||
# message(STATUS "Found headers: ${QTKEYCHAIN_HEADERS}")
|
||||
# endif()
|
||||
|
||||
message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
|
||||
message(STATUS "Contents of qtkeychain:")
|
||||
file(GLOB QTKEYCHAIN_HEADERS "${PROJECT_SOURCE_DIR}/external/qtkeychain/qtkeychain/*.h")
|
||||
message(STATUS "Found headers: ${QTKEYCHAIN_HEADERS}")
|
||||
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/cmake/hook/pre-commit" DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/.git/hooks/")
|
||||
|
|
|
|||
|
|
@ -54,10 +54,12 @@
|
|||
#include "core/call/CallProxy.hpp"
|
||||
#include "core/camera/CameraGui.hpp"
|
||||
#include "core/chat/ChatProxy.hpp"
|
||||
#include "core/chat/message/EventLogGui.hpp"
|
||||
#include "core/chat/message/ChatMessageGui.hpp"
|
||||
#include "core/chat/message/EventLogGui.hpp"
|
||||
#include "core/chat/message/EventLogList.hpp"
|
||||
#include "core/chat/message/EventLogProxy.hpp"
|
||||
#include "core/chat/message/content/ChatMessageContentGui.hpp"
|
||||
#include "core/chat/message/content/ChatMessageContentProxy.hpp"
|
||||
#include "core/conference/ConferenceGui.hpp"
|
||||
#include "core/conference/ConferenceInfoGui.hpp"
|
||||
#include "core/conference/ConferenceInfoProxy.hpp"
|
||||
|
|
@ -96,9 +98,11 @@
|
|||
#include "tool/providers/EmojiProvider.hpp"
|
||||
#include "tool/providers/ImageProvider.hpp"
|
||||
#include "tool/providers/ScreenProvider.hpp"
|
||||
#include "tool/providers/ThumbnailProvider.hpp"
|
||||
#include "tool/request/CallbackHelper.hpp"
|
||||
#include "tool/request/RequestDialog.hpp"
|
||||
#include "tool/thread/Thread.hpp"
|
||||
#include "tool/ui/DashRectangle.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(App)
|
||||
|
||||
|
|
@ -495,6 +499,7 @@ void App::initCore() {
|
|||
mEngine->addImageProvider(ScreenProvider::ProviderId, new ScreenProvider());
|
||||
mEngine->addImageProvider(WindowProvider::ProviderId, new WindowProvider());
|
||||
mEngine->addImageProvider(WindowIconProvider::ProviderId, new WindowIconProvider());
|
||||
mEngine->addImageProvider(ThumbnailProvider::ProviderId, new ThumbnailProvider());
|
||||
|
||||
// Enable notifications.
|
||||
mNotifier = new Notifier(mEngine);
|
||||
|
|
@ -551,7 +556,7 @@ void App::initCore() {
|
|||
static bool firstOpen = true;
|
||||
if (!firstOpen || !mParser->isSet("minimized")) {
|
||||
lDebug() << log().arg("Openning window");
|
||||
window->show();
|
||||
if (window) window->show();
|
||||
} else lInfo() << log().arg("Stay minimized");
|
||||
firstOpen = false;
|
||||
}
|
||||
|
|
@ -639,6 +644,7 @@ void App::initCppInterfaces() {
|
|||
"SettingsCpp", 1, 0, "SettingsCpp",
|
||||
[this](QQmlEngine *engine, QJSEngine *) -> QObject * { return mSettings.get(); });
|
||||
|
||||
qmlRegisterType<DashRectangle>(Constants::MainQmlUri, 1, 0, "DashRectangle");
|
||||
qmlRegisterType<PhoneNumberProxy>(Constants::MainQmlUri, 1, 0, "PhoneNumberProxy");
|
||||
qmlRegisterType<VariantObject>(Constants::MainQmlUri, 1, 0, "VariantObject");
|
||||
qmlRegisterType<VariantList>(Constants::MainQmlUri, 1, 0, "VariantList");
|
||||
|
|
@ -663,9 +669,11 @@ void App::initCppInterfaces() {
|
|||
qmlRegisterType<ChatProxy>(Constants::MainQmlUri, 1, 0, "ChatProxy");
|
||||
qmlRegisterType<ChatGui>(Constants::MainQmlUri, 1, 0, "ChatGui");
|
||||
qmlRegisterType<EventLogGui>(Constants::MainQmlUri, 1, 0, "EventLogGui");
|
||||
qmlRegisterType<ChatMessageGui>(Constants::MainQmlUri, 1, 0, "ChatMessageGui");
|
||||
qmlRegisterType<ChatMessageGui>(Constants::MainQmlUri, 1, 0, "ChatMessageGui");
|
||||
qmlRegisterType<EventLogList>(Constants::MainQmlUri, 1, 0, "EventLogList");
|
||||
qmlRegisterType<EventLogProxy>(Constants::MainQmlUri, 1, 0, "EventLogProxy");
|
||||
qmlRegisterType<ChatMessageContentProxy>(Constants::MainQmlUri, 1, 0, "ChatMessageContentProxy");
|
||||
qmlRegisterType<ChatMessageContentGui>(Constants::MainQmlUri, 1, 0, "ChatMessageContentGui");
|
||||
qmlRegisterUncreatableType<ConferenceCore>(Constants::MainQmlUri, 1, 0, "ConferenceCore",
|
||||
QLatin1String("Uncreatable"));
|
||||
qmlRegisterType<ConferenceGui>(Constants::MainQmlUri, 1, 0, "ConferenceGui");
|
||||
|
|
@ -1284,3 +1292,11 @@ QString App::getSdkVersion() {
|
|||
return tr('unknown');
|
||||
#endif
|
||||
}
|
||||
|
||||
float App::getScreenRatio() const {
|
||||
return mScreenRatio;
|
||||
}
|
||||
|
||||
void App::setScreenRatio(float ratio) {
|
||||
mScreenRatio = ratio;
|
||||
}
|
||||
|
|
@ -154,6 +154,9 @@ public:
|
|||
QString getGitBranchName();
|
||||
QString getSdkVersion();
|
||||
|
||||
float getScreenRatio() const;
|
||||
Q_INVOKABLE void setScreenRatio(float ratio);
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
Q_INVOKABLE void exportDesktopFile();
|
||||
|
||||
|
|
@ -200,6 +203,7 @@ private:
|
|||
DefaultTranslatorCore *mDefaultTranslatorCore = nullptr;
|
||||
QTimer mDateUpdateTimer;
|
||||
QDate mCurrentDate;
|
||||
float mScreenRatio = 1;
|
||||
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
core/chat/message/EventLogGui.cpp
|
||||
core/chat/message/EventLogList.cpp
|
||||
core/chat/message/EventLogProxy.cpp
|
||||
core/chat/message/content/ChatMessageContentCore.cpp
|
||||
core/chat/message/content/ChatMessageContentGui.cpp
|
||||
core/chat/message/content/ChatMessageContentList.cpp
|
||||
core/chat/message/content/ChatMessageContentProxy.cpp
|
||||
core/emoji/EmojiModel.cpp
|
||||
core/fps-counter/FPSCounter.cpp
|
||||
core/friend/FriendCore.cpp
|
||||
|
|
|
|||
|
|
@ -68,10 +68,11 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
|
|||
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
|
||||
mChatMessageModel = Utils::makeQObject_ptr<ChatMessageModel>(chatmessage);
|
||||
mChatMessageModel->setSelf(mChatMessageModel);
|
||||
mText = mChatMessageModel->getText();
|
||||
mText = ToolModel::getMessageFromContent(chatmessage->getContents());
|
||||
mUtf8Text = mChatMessageModel->getUtf8Text();
|
||||
mHasTextContent = mChatMessageModel->getHasTextContent();
|
||||
mTimestamp = QDateTime::fromSecsSinceEpoch(chatmessage->getTime());
|
||||
mIsOutgoing = chatmessage->isOutgoing();
|
||||
mIsRemoteMessage = !chatmessage->isOutgoing();
|
||||
mPeerAddress = Utils::coreStringToAppString(chatmessage->getPeerAddress()->asStringUriOnly());
|
||||
mPeerName = ToolModel::getDisplayName(chatmessage->getPeerAddress()->clone());
|
||||
|
|
@ -87,10 +88,8 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
|
|||
mMessageState = LinphoneEnums::fromLinphone(chatmessage->getState());
|
||||
mMessageId = Utils::coreStringToAppString(chatmessage->getMessageId());
|
||||
for (auto content : chatmessage->getContents()) {
|
||||
if (content->isIcalendar()) {
|
||||
auto conferenceInfo = linphone::Factory::get()->createConferenceInfoFromIcalendarContent(content);
|
||||
mConferenceInfo = ConferenceInfoCore::create(conferenceInfo);
|
||||
}
|
||||
auto contentCore = ChatMessageContentCore::create(content, mChatMessageModel);
|
||||
mChatMessageContentList.push_back(contentCore);
|
||||
}
|
||||
auto reac = chatmessage->getOwnReaction();
|
||||
mOwnReaction = reac ? Utils::coreStringToAppString(reac->getBody()) : QString();
|
||||
|
|
@ -201,6 +200,61 @@ void ChatMessageCore::setSelf(QSharedPointer<ChatMessageCore> me) {
|
|||
auto msgState = LinphoneEnums::fromLinphone(state);
|
||||
mChatMessageModelConnection->invokeToCore([this, msgState] { setMessageState(msgState); });
|
||||
});
|
||||
mChatMessageModelConnection->makeConnectToModel(
|
||||
&ChatMessageModel::fileTransferProgressIndication,
|
||||
[this](const std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &content,
|
||||
size_t offset, size_t total) {
|
||||
mChatMessageModelConnection->invokeToCore([this, content, offset, total] {
|
||||
auto it =
|
||||
std::find_if(mChatMessageContentList.begin(), mChatMessageContentList.end(),
|
||||
[content](QSharedPointer<ChatMessageContentCore> item) {
|
||||
return item->getContentModel()->getContent()->getName() == content->getName();
|
||||
});
|
||||
if (it != mChatMessageContentList.end()) {
|
||||
auto contentCore = mChatMessageContentList.at(std::distance(mChatMessageContentList.begin(), it));
|
||||
assert(contentCore);
|
||||
contentCore->setFileOffset(offset);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
mChatMessageModelConnection->makeConnectToModel(
|
||||
&ChatMessageModel::fileTransferTerminated, [this](const std::shared_ptr<linphone::ChatMessage> &message,
|
||||
const std::shared_ptr<linphone::Content> &content) {
|
||||
mChatMessageModelConnection->invokeToCore([this, content] {
|
||||
auto it =
|
||||
std::find_if(mChatMessageContentList.begin(), mChatMessageContentList.end(),
|
||||
[content](QSharedPointer<ChatMessageContentCore> item) {
|
||||
return item->getContentModel()->getContent()->getName() == content->getName();
|
||||
});
|
||||
if (it != mChatMessageContentList.end()) {
|
||||
auto contentCore = mChatMessageContentList.at(std::distance(mChatMessageContentList.begin(), it));
|
||||
assert(contentCore);
|
||||
contentCore->setWasDownloaded(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
mChatMessageModelConnection->makeConnectToModel(
|
||||
&ChatMessageModel::fileTransferRecv,
|
||||
[this](const std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &content,
|
||||
const std::shared_ptr<const linphone::Buffer> &buffer) { qDebug() << "transfer received"; });
|
||||
mChatMessageModelConnection->makeConnectToModel(
|
||||
&ChatMessageModel::fileTransferSend,
|
||||
[this](const std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &content,
|
||||
size_t offset, size_t size) { qDebug() << "transfer send"; });
|
||||
mChatMessageModelConnection->makeConnectToModel(
|
||||
&ChatMessageModel::fileTransferSendChunk,
|
||||
[this](const std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &content,
|
||||
size_t offset, size_t size,
|
||||
const std::shared_ptr<linphone::Buffer> &buffer) { qDebug() << "transfer send chunk"; });
|
||||
mChatMessageModelConnection->makeConnectToModel(
|
||||
&ChatMessageModel::participantImdnStateChanged,
|
||||
[this](const std::shared_ptr<linphone::ChatMessage> &message,
|
||||
const std::shared_ptr<const linphone::ParticipantImdnState> &state) {});
|
||||
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::ephemeralMessageTimerStarted,
|
||||
[this](const std::shared_ptr<linphone::ChatMessage> &message) {});
|
||||
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::ephemeralMessageDeleted,
|
||||
[this](const std::shared_ptr<linphone::ChatMessage> &message) {});
|
||||
}
|
||||
|
||||
QDateTime ChatMessageCore::getTimestamp() const {
|
||||
|
|
@ -286,6 +340,10 @@ QList<QVariant> ChatMessageCore::getReactionsSingleton() const {
|
|||
return mReactionsSingletonMap;
|
||||
}
|
||||
|
||||
QList<QSharedPointer<ChatMessageContentCore>> ChatMessageCore::getChatMessageContentList() const {
|
||||
return mChatMessageContentList;
|
||||
}
|
||||
|
||||
void ChatMessageCore::setReactions(const QList<Reaction> &reactions) {
|
||||
mustBeInMainThread(log().arg(Q_FUNC_INFO));
|
||||
mReactions = reactions;
|
||||
|
|
@ -370,6 +428,6 @@ std::shared_ptr<ChatMessageModel> ChatMessageCore::getModel() const {
|
|||
return mChatMessageModel;
|
||||
}
|
||||
|
||||
ConferenceInfoGui *ChatMessageCore::getConferenceInfoGui() const {
|
||||
return mConferenceInfo ? new ConferenceInfoGui(mConferenceInfo) : nullptr;
|
||||
}
|
||||
// ConferenceInfoGui *ChatMessageCore::getConferenceInfoGui() const {
|
||||
// return mConferenceInfo ? new ConferenceInfoGui(mConferenceInfo) : nullptr;
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
#define CHATMESSAGECORE_H_
|
||||
|
||||
#include "EventLogCore.hpp"
|
||||
#include "core/chat/message/content/ChatMessageContentCore.hpp"
|
||||
#include "core/chat/message/content/ChatMessageContentProxy.hpp"
|
||||
#include "core/conference/ConferenceInfoCore.hpp"
|
||||
#include "core/conference/ConferenceInfoGui.hpp"
|
||||
#include "model/chat/message/ChatMessageModel.hpp"
|
||||
|
|
@ -67,7 +69,6 @@ class ChatMessageCore : public QObject, public AbstractObject {
|
|||
Q_PROPERTY(bool isRemoteMessage READ isRemoteMessage CONSTANT)
|
||||
Q_PROPERTY(bool isFromChatGroup READ isFromChatGroup CONSTANT)
|
||||
Q_PROPERTY(bool isRead READ isRead WRITE setIsRead NOTIFY isReadChanged)
|
||||
Q_PROPERTY(ConferenceInfoGui *conferenceInfo READ getConferenceInfoGui CONSTANT)
|
||||
Q_PROPERTY(QString ownReaction READ getOwnReaction WRITE setOwnReaction NOTIFY messageReactionChanged)
|
||||
Q_PROPERTY(QList<Reaction> reactions READ getReactions WRITE setReactions NOTIFY messageReactionChanged)
|
||||
Q_PROPERTY(QList<QVariant> reactionsSingleton READ getReactionsSingleton NOTIFY singletonReactionMapChanged)
|
||||
|
|
@ -106,6 +107,7 @@ public:
|
|||
void setOwnReaction(const QString &reaction);
|
||||
QList<Reaction> getReactions() const;
|
||||
QList<QVariant> getReactionsSingleton() const;
|
||||
QList<QSharedPointer<ChatMessageContentCore>> getChatMessageContentList() const;
|
||||
void removeOneReactionFromSingletonMap(const QString &body);
|
||||
void resetReactionsSingleton();
|
||||
void setReactions(const QList<Reaction> &reactions);
|
||||
|
|
@ -116,7 +118,7 @@ public:
|
|||
void setMessageState(LinphoneEnums::ChatMessageState state);
|
||||
|
||||
std::shared_ptr<ChatMessageModel> getModel() const;
|
||||
ConferenceInfoGui *getConferenceInfoGui() const;
|
||||
// ConferenceInfoGui *getConferenceInfoGui() const;
|
||||
|
||||
signals:
|
||||
void timestampChanged(QDateTime timestamp);
|
||||
|
|
@ -136,7 +138,8 @@ signals:
|
|||
void lRemoveReaction();
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT QString mText;
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
QString mText;
|
||||
QString mUtf8Text;
|
||||
bool mHasTextContent;
|
||||
QString mPeerAddress;
|
||||
|
|
@ -158,8 +161,10 @@ private:
|
|||
bool mIsCalendarInvite = false;
|
||||
bool mIsVoiceRecording = false;
|
||||
|
||||
bool mIsOutgoing = false;
|
||||
LinphoneEnums::ChatMessageState mMessageState;
|
||||
QSharedPointer<ConferenceInfoCore> mConferenceInfo = nullptr;
|
||||
QList<QSharedPointer<ChatMessageContentCore>> mChatMessageContentList;
|
||||
// QSharedPointer<ConferenceInfoCore> mConferenceInfo = nullptr;
|
||||
|
||||
std::shared_ptr<ChatMessageModel> mChatMessageModel;
|
||||
QSharedPointer<SafeConnection<ChatMessageCore, ChatMessageModel>> mChatMessageModelConnection;
|
||||
|
|
|
|||
259
Linphone/core/chat/message/content/ChatMessageContentCore.cpp
Normal file
259
Linphone/core/chat/message/content/ChatMessageContentCore.cpp
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatMessageContentCore.hpp"
|
||||
#include "core/App.hpp"
|
||||
#include "core/chat/ChatCore.hpp"
|
||||
#include "model/tool/ToolModel.hpp"
|
||||
#include "tool/providers/ThumbnailProvider.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(ChatMessageContentCore)
|
||||
|
||||
QSharedPointer<ChatMessageContentCore>
|
||||
ChatMessageContentCore::create(const std::shared_ptr<linphone::Content> &content,
|
||||
std::shared_ptr<ChatMessageModel> chatMessageModel) {
|
||||
auto sharedPointer = QSharedPointer<ChatMessageContentCore>(new ChatMessageContentCore(content, chatMessageModel),
|
||||
&QObject::deleteLater);
|
||||
sharedPointer->setSelf(sharedPointer);
|
||||
sharedPointer->moveToThread(App::getInstance()->thread());
|
||||
return sharedPointer;
|
||||
}
|
||||
|
||||
ChatMessageContentCore::ChatMessageContentCore(const std::shared_ptr<linphone::Content> &content,
|
||||
std::shared_ptr<ChatMessageModel> chatMessageModel) {
|
||||
if (content) {
|
||||
mName = Utils::coreStringToAppString(content->getName());
|
||||
if (mName.isEmpty()) { // Try to find the name from file Path
|
||||
QString fileName = Utils::coreStringToAppString(content->getFilePath());
|
||||
if (!fileName.isEmpty()) {
|
||||
mName = QFileInfo(fileName).baseName();
|
||||
}
|
||||
}
|
||||
mFilePath = Utils::coreStringToAppString(content->getFilePath());
|
||||
mIsFile = content->isFile();
|
||||
mIsFileEncrypted = content->isFileEncrypted();
|
||||
mIsFileTransfer = content->isFileTransfer();
|
||||
mIsCalendar = content->isIcalendar();
|
||||
if (content->isIcalendar()) {
|
||||
auto conferenceInfo = linphone::Factory::get()->createConferenceInfoFromIcalendarContent(content);
|
||||
mConferenceInfo = ConferenceInfoCore::create(conferenceInfo);
|
||||
}
|
||||
mIsMultipart = content->isMultipart();
|
||||
mIsText = content->isText();
|
||||
mIsVoiceRecording = content->isVoiceRecording();
|
||||
mIsVideo = Utils::isVideo(mFilePath);
|
||||
mFileSize = (quint64)content->getFileSize();
|
||||
mFileDuration = content->getFileDuration();
|
||||
mFileOffset = 0;
|
||||
mUtf8Text = Utils::coreStringToAppString(content->getUtf8Text());
|
||||
mWasDownloaded = !mFilePath.isEmpty() && QFileInfo(mFilePath).isFile();
|
||||
mThumbnail = mFilePath.isEmpty()
|
||||
? QString()
|
||||
: QStringLiteral("image://%1/%2").arg(ThumbnailProvider::ProviderId).arg(mFilePath);
|
||||
mChatMessageContentModel = Utils::makeQObject_ptr<ChatMessageContentModel>(content, chatMessageModel);
|
||||
}
|
||||
}
|
||||
|
||||
ChatMessageContentCore ::~ChatMessageContentCore() {
|
||||
}
|
||||
|
||||
void ChatMessageContentCore::setSelf(QSharedPointer<ChatMessageContentCore> me) {
|
||||
mChatMessageContentModelConnection =
|
||||
SafeConnection<ChatMessageContentCore, ChatMessageContentModel>::create(me, mChatMessageContentModel);
|
||||
|
||||
auto updateThumbnailType = [this] {
|
||||
if (Utils::isVideo(mFilePath)) mIsVideo = true;
|
||||
emit isVideoChanged();
|
||||
};
|
||||
|
||||
mChatMessageContentModelConnection->makeConnectToCore(
|
||||
&ChatMessageContentCore::lCreateThumbnail, [this](const bool &force = false) {
|
||||
mChatMessageContentModelConnection->invokeToModel(
|
||||
[this, force] { mChatMessageContentModel->createThumbnail(); });
|
||||
});
|
||||
mChatMessageContentModelConnection->makeConnectToModel(
|
||||
&ChatMessageContentModel::thumbnailChanged, [this, updateThumbnailType](QString thumbnail) {
|
||||
mChatMessageContentModelConnection->invokeToCore([this, thumbnail] { setThumbnail(thumbnail); });
|
||||
});
|
||||
|
||||
mChatMessageContentModelConnection->makeConnectToCore(&ChatMessageContentCore::lDownloadFile, [this]() {
|
||||
mChatMessageContentModelConnection->invokeToModel([this] { mChatMessageContentModel->downloadFile(mName); });
|
||||
});
|
||||
mChatMessageContentModelConnection->makeConnectToModel(
|
||||
&ChatMessageContentModel::wasDownloadedChanged,
|
||||
[this](const std::shared_ptr<linphone::Content> &content, bool downloaded) {
|
||||
mChatMessageContentModelConnection->invokeToCore([this, downloaded] { setWasDownloaded(downloaded); });
|
||||
});
|
||||
mChatMessageContentModelConnection->makeConnectToModel(
|
||||
&ChatMessageContentModel::filePathChanged,
|
||||
[this](const std::shared_ptr<linphone::Content> &content, QString filePath) {
|
||||
auto isFile = content->isFile();
|
||||
auto isFileTransfer = content->isFileTransfer();
|
||||
auto isFileEncrypted = content->isFileEncrypted();
|
||||
mChatMessageContentModelConnection->invokeToCore([this, filePath, isFile, isFileTransfer, isFileEncrypted] {
|
||||
setIsFile(isFile || QFileInfo(filePath).isFile());
|
||||
setIsFileTransfer(isFileTransfer);
|
||||
setIsFileEncrypted(isFileEncrypted);
|
||||
setFilePath(filePath);
|
||||
});
|
||||
});
|
||||
|
||||
mChatMessageContentModelConnection->makeConnectToCore(&ChatMessageContentCore::lCancelDownloadFile, [this]() {
|
||||
mChatMessageContentModelConnection->invokeToModel([this] { mChatMessageContentModel->cancelDownloadFile(); });
|
||||
});
|
||||
mChatMessageContentModelConnection->makeConnectToCore(
|
||||
&ChatMessageContentCore::lOpenFile, [this](bool showDirectory = false) {
|
||||
if (!QFileInfo(mFilePath).exists()) {
|
||||
//: Error
|
||||
Utils::showInformationPopup(tr("popup_error_title"),
|
||||
//: Could not open file : unknown path %1
|
||||
tr("popup_open_file_error_does_not_exist_message").arg(mFilePath), false);
|
||||
} else {
|
||||
mChatMessageContentModelConnection->invokeToModel([this, showDirectory] {
|
||||
mChatMessageContentModel->openFile(mName, mWasDownloaded, showDirectory);
|
||||
});
|
||||
}
|
||||
});
|
||||
mChatMessageContentModelConnection->makeConnectToModel(
|
||||
&ChatMessageContentModel::messageStateChanged, [this](linphone::ChatMessage::State state) {
|
||||
mChatMessageContentModelConnection->invokeToCore(
|
||||
[this, msgState = LinphoneEnums::fromLinphone(state)] { emit msgStateChanged(msgState); });
|
||||
});
|
||||
}
|
||||
|
||||
bool ChatMessageContentCore::isFile() const {
|
||||
return mIsFile;
|
||||
}
|
||||
|
||||
void ChatMessageContentCore::setIsFile(bool isFile) {
|
||||
if (mIsFile != isFile) {
|
||||
mIsFile = isFile;
|
||||
emit isFileChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatMessageContentCore::isVideo() const {
|
||||
return mIsVideo;
|
||||
}
|
||||
|
||||
bool ChatMessageContentCore::isFileEncrypted() const {
|
||||
return mIsFileEncrypted;
|
||||
}
|
||||
|
||||
void ChatMessageContentCore::setIsFileEncrypted(bool isFileEncrypted) {
|
||||
if (mIsFileEncrypted != isFileEncrypted) {
|
||||
mIsFileEncrypted = isFileEncrypted;
|
||||
emit isFileEncryptedChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatMessageContentCore::isFileTransfer() const {
|
||||
return mIsFileTransfer;
|
||||
}
|
||||
|
||||
void ChatMessageContentCore::setIsFileTransfer(bool isFileTransfer) {
|
||||
if (mIsFileTransfer != isFileTransfer) {
|
||||
mIsFileTransfer = isFileTransfer;
|
||||
emit isFileTransferChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatMessageContentCore::isCalendar() const {
|
||||
return mIsCalendar;
|
||||
}
|
||||
|
||||
bool ChatMessageContentCore::isMultipart() const {
|
||||
return mIsMultipart;
|
||||
}
|
||||
|
||||
bool ChatMessageContentCore::isText() const {
|
||||
return mIsText;
|
||||
}
|
||||
|
||||
bool ChatMessageContentCore::isVoiceRecording() const {
|
||||
return mIsVoiceRecording;
|
||||
}
|
||||
|
||||
QString ChatMessageContentCore::getFilePath() const {
|
||||
return mFilePath;
|
||||
}
|
||||
|
||||
void ChatMessageContentCore::setFilePath(QString path) {
|
||||
if (mFilePath != path) {
|
||||
mFilePath = path;
|
||||
emit filePathChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString ChatMessageContentCore::getUtf8Text() const {
|
||||
return mUtf8Text;
|
||||
}
|
||||
|
||||
QString ChatMessageContentCore::getName() const {
|
||||
return mName;
|
||||
}
|
||||
|
||||
quint64 ChatMessageContentCore::getFileSize() const {
|
||||
return mFileSize;
|
||||
}
|
||||
|
||||
quint64 ChatMessageContentCore::getFileOffset() const {
|
||||
return mFileOffset;
|
||||
}
|
||||
|
||||
void ChatMessageContentCore::setFileOffset(quint64 fileOffset) {
|
||||
if (mFileOffset != fileOffset) {
|
||||
mFileOffset = fileOffset;
|
||||
emit fileOffsetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int ChatMessageContentCore::getFileDuration() const {
|
||||
return mFileDuration;
|
||||
}
|
||||
|
||||
ConferenceInfoGui *ChatMessageContentCore::getConferenceInfoGui() const {
|
||||
return mConferenceInfo ? new ConferenceInfoGui(mConferenceInfo) : nullptr;
|
||||
}
|
||||
|
||||
bool ChatMessageContentCore::wasDownloaded() const {
|
||||
return mWasDownloaded;
|
||||
}
|
||||
|
||||
QString ChatMessageContentCore::getThumbnail() const {
|
||||
return mThumbnail;
|
||||
}
|
||||
|
||||
void ChatMessageContentCore::setThumbnail(const QString &data) {
|
||||
if (mThumbnail != data) {
|
||||
mThumbnail = data;
|
||||
emit thumbnailChanged();
|
||||
}
|
||||
}
|
||||
void ChatMessageContentCore::setWasDownloaded(bool wasDownloaded) {
|
||||
if (mWasDownloaded != wasDownloaded) {
|
||||
mWasDownloaded = wasDownloaded;
|
||||
emit wasDownloadedChanged(wasDownloaded);
|
||||
}
|
||||
}
|
||||
|
||||
const std::shared_ptr<ChatMessageContentModel> &ChatMessageContentCore::getContentModel() const {
|
||||
return mChatMessageContentModel;
|
||||
}
|
||||
136
Linphone/core/chat/message/content/ChatMessageContentCore.hpp
Normal file
136
Linphone/core/chat/message/content/ChatMessageContentCore.hpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CHAT_MESSAGE_CONTENT_CORE_H_
|
||||
#define CHAT_MESSAGE_CONTENT_CORE_H_
|
||||
|
||||
#include "core/conference/ConferenceInfoCore.hpp"
|
||||
#include "core/conference/ConferenceInfoGui.hpp"
|
||||
#include "model/chat/message/content/ChatMessageContentModel.hpp"
|
||||
#include "tool/AbstractObject.hpp"
|
||||
#include "tool/thread/SafeConnection.hpp"
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include <linphone++/linphone.hh>
|
||||
|
||||
class ChatMessageContentCore : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString name READ getName CONSTANT)
|
||||
Q_PROPERTY(quint64 fileOffset READ getFileOffset WRITE setFileOffset NOTIFY fileOffsetChanged)
|
||||
|
||||
Q_PROPERTY(QString thumbnail READ getThumbnail WRITE setThumbnail NOTIFY thumbnailChanged)
|
||||
Q_PROPERTY(bool wasDownloaded READ wasDownloaded WRITE setWasDownloaded NOTIFY wasDownloadedChanged)
|
||||
Q_PROPERTY(QString filePath READ getFilePath WRITE setFilePath NOTIFY filePathChanged)
|
||||
Q_PROPERTY(QString utf8Text READ getUtf8Text CONSTANT)
|
||||
Q_PROPERTY(bool isFile READ isFile WRITE setIsFile NOTIFY isFileChanged)
|
||||
Q_PROPERTY(bool isFileEncrypted READ isFileEncrypted WRITE setIsFileEncrypted NOTIFY isFileEncryptedChanged)
|
||||
Q_PROPERTY(bool isFileTransfer READ isFileTransfer WRITE setIsFileTransfer NOTIFY isFileTransferChanged)
|
||||
Q_PROPERTY(bool isCalendar READ isCalendar CONSTANT)
|
||||
Q_PROPERTY(ConferenceInfoGui *conferenceInfo READ getConferenceInfoGui CONSTANT)
|
||||
Q_PROPERTY(bool isMultipart READ isMultipart CONSTANT)
|
||||
Q_PROPERTY(bool isText READ isText CONSTANT)
|
||||
Q_PROPERTY(bool isVideo READ isVideo NOTIFY isVideoChanged)
|
||||
Q_PROPERTY(bool isVoiceRecording READ isVoiceRecording CONSTANT)
|
||||
Q_PROPERTY(int fileDuration READ getFileDuration CONSTANT)
|
||||
Q_PROPERTY(quint64 fileSize READ getFileSize CONSTANT)
|
||||
|
||||
public:
|
||||
static QSharedPointer<ChatMessageContentCore> create(const std::shared_ptr<linphone::Content> &content,
|
||||
std::shared_ptr<ChatMessageModel> chatMessageModel);
|
||||
ChatMessageContentCore(const std::shared_ptr<linphone::Content> &content,
|
||||
std::shared_ptr<ChatMessageModel> chatMessageModel);
|
||||
~ChatMessageContentCore();
|
||||
void setSelf(QSharedPointer<ChatMessageContentCore> me);
|
||||
|
||||
bool isFile() const;
|
||||
void setIsFile(bool isFile);
|
||||
bool isFileEncrypted() const;
|
||||
void setIsFileEncrypted(bool isFileEncrypted);
|
||||
bool isFileTransfer() const;
|
||||
void setIsFileTransfer(bool isFileTransfer);
|
||||
|
||||
bool isVideo() const;
|
||||
bool isCalendar() const;
|
||||
bool isMultipart() const;
|
||||
bool isText() const;
|
||||
bool isVoiceRecording() const;
|
||||
|
||||
QString getUtf8Text() const;
|
||||
QString getName() const;
|
||||
quint64 getFileSize() const;
|
||||
quint64 getFileOffset() const;
|
||||
void setFileOffset(quint64 fileOffset);
|
||||
QString getFilePath() const;
|
||||
void setFilePath(QString path);
|
||||
int getFileDuration() const;
|
||||
ConferenceInfoGui *getConferenceInfoGui() const;
|
||||
|
||||
void setThumbnail(const QString &data);
|
||||
QString getThumbnail() const;
|
||||
|
||||
bool wasDownloaded() const;
|
||||
void setWasDownloaded(bool downloaded);
|
||||
|
||||
const std::shared_ptr<ChatMessageContentModel> &getContentModel() const;
|
||||
|
||||
signals:
|
||||
void msgStateChanged(LinphoneEnums::ChatMessageState state);
|
||||
void thumbnailChanged();
|
||||
void fileOffsetChanged();
|
||||
void filePathChanged();
|
||||
void isFileChanged();
|
||||
void isFileTransferChanged();
|
||||
void isFileEncryptedChanged();
|
||||
void wasDownloadedChanged(bool downloaded);
|
||||
void isVideoChanged();
|
||||
|
||||
void lCreateThumbnail(const bool &force = false);
|
||||
void lRemoveDownloadedFile();
|
||||
void lDownloadFile();
|
||||
void lCancelDownloadFile();
|
||||
void lOpenFile(bool showDirectory = false);
|
||||
bool lSaveAs(const QString &path);
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
bool mIsFile;
|
||||
bool mIsVideo;
|
||||
bool mIsFileEncrypted;
|
||||
bool mIsFileTransfer;
|
||||
bool mIsCalendar;
|
||||
bool mIsMultipart;
|
||||
bool mIsText;
|
||||
bool mIsVoiceRecording;
|
||||
int mFileDuration;
|
||||
QString mThumbnail;
|
||||
QString mUtf8Text;
|
||||
QString mFilePath;
|
||||
QString mName;
|
||||
quint64 mFileSize;
|
||||
quint64 mFileOffset;
|
||||
bool mWasDownloaded;
|
||||
QSharedPointer<ConferenceInfoCore> mConferenceInfo = nullptr;
|
||||
|
||||
std::shared_ptr<ChatMessageContentModel> mChatMessageContentModel;
|
||||
QSharedPointer<SafeConnection<ChatMessageContentCore, ChatMessageContentModel>> mChatMessageContentModelConnection;
|
||||
};
|
||||
|
||||
#endif // CHAT_MESSAGE_CONTENT_CORE_H_
|
||||
38
Linphone/core/chat/message/content/ChatMessageContentGui.cpp
Normal file
38
Linphone/core/chat/message/content/ChatMessageContentGui.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatMessageContentGui.hpp"
|
||||
#include "core/App.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(ChatMessageContentGui)
|
||||
|
||||
ChatMessageContentGui::ChatMessageContentGui(QSharedPointer<ChatMessageContentCore> core) {
|
||||
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);
|
||||
mCore = core;
|
||||
if (isInLinphoneThread()) moveToThread(App::getInstance()->thread());
|
||||
}
|
||||
|
||||
ChatMessageContentGui::~ChatMessageContentGui() {
|
||||
mustBeInMainThread("~" + getClassName());
|
||||
}
|
||||
|
||||
ChatMessageContentCore *ChatMessageContentGui::getCore() const {
|
||||
return mCore.get();
|
||||
}
|
||||
41
Linphone/core/chat/message/content/ChatMessageContentGui.hpp
Normal file
41
Linphone/core/chat/message/content/ChatMessageContentGui.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CHAT_MESSAGE_CONTENT_GUI_H_
|
||||
#define CHAT_MESSAGE_CONTENT_GUI_H_
|
||||
|
||||
#include "core/chat/message/content/ChatMessageContentCore.hpp"
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
|
||||
class ChatMessageContentGui : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(ChatMessageContentCore *core READ getCore CONSTANT)
|
||||
|
||||
public:
|
||||
ChatMessageContentGui(QSharedPointer<ChatMessageContentCore> core);
|
||||
~ChatMessageContentGui();
|
||||
ChatMessageContentCore *getCore() const;
|
||||
QSharedPointer<ChatMessageContentCore> mCore;
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
||||
#endif
|
||||
164
Linphone/core/chat/message/content/ChatMessageContentList.cpp
Normal file
164
Linphone/core/chat/message/content/ChatMessageContentList.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatMessageContentList.hpp"
|
||||
#include "core/App.hpp"
|
||||
#include "core/chat/ChatCore.hpp"
|
||||
#include "core/chat/message/content/ChatMessageContentGui.hpp"
|
||||
|
||||
#include <QMimeDatabase>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include <linphone++/linphone.hh>
|
||||
|
||||
// =============================================================================
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(ChatMessageContentList)
|
||||
|
||||
QSharedPointer<ChatMessageContentList> ChatMessageContentList::create() {
|
||||
auto model = QSharedPointer<ChatMessageContentList>(new ChatMessageContentList(), &QObject::deleteLater);
|
||||
model->moveToThread(App::getInstance()->thread());
|
||||
model->setSelf(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
ChatMessageContentList::ChatMessageContentList(QObject *parent) : ListProxy(parent) {
|
||||
mustBeInMainThread(getClassName());
|
||||
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
|
||||
}
|
||||
|
||||
ChatMessageContentList::~ChatMessageContentList() {
|
||||
mustBeInMainThread("~" + getClassName());
|
||||
mModelConnection = nullptr;
|
||||
}
|
||||
|
||||
ChatMessageGui *ChatMessageContentList::getChatMessage() const {
|
||||
if (mChatMessageCore) return new ChatMessageGui(mChatMessageCore);
|
||||
else return nullptr;
|
||||
}
|
||||
|
||||
QSharedPointer<ChatMessageCore> ChatMessageContentList::getChatMessageCore() const {
|
||||
return mChatMessageCore;
|
||||
}
|
||||
|
||||
void ChatMessageContentList::setChatMessageCore(QSharedPointer<ChatMessageCore> core) {
|
||||
if (mChatMessageCore != core) {
|
||||
// if (mChatMessageCore) disconnect(mChatMessageCore.get(), &ChatCore::, this, nullptr);
|
||||
mChatMessageCore = core;
|
||||
// if (mChatMessageCore)
|
||||
// connect(mChatMessageCore.get(), &ChatCore::messageListChanged, this, &ChatMessageContentList::lUpdate);
|
||||
emit chatMessageChanged();
|
||||
lUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatMessageContentList::setChatMessageGui(ChatMessageGui *chat) {
|
||||
auto chatCore = chat ? chat->mCore : nullptr;
|
||||
setChatMessageCore(chatCore);
|
||||
}
|
||||
|
||||
int ChatMessageContentList::findFirstUnreadIndex() {
|
||||
auto chatList = getSharedList<ChatMessageCore>();
|
||||
auto it = std::find_if(chatList.begin(), chatList.end(),
|
||||
[](const QSharedPointer<ChatMessageCore> item) { return !item->isRead(); });
|
||||
return it == chatList.end() ? -1 : std::distance(chatList.begin(), it);
|
||||
}
|
||||
|
||||
void ChatMessageContentList::setSelf(QSharedPointer<ChatMessageContentList> me) {
|
||||
mModelConnection = SafeConnection<ChatMessageContentList, CoreModel>::create(me, CoreModel::getInstance());
|
||||
|
||||
mModelConnection->makeConnectToCore(&ChatMessageContentList::lUpdate, [this]() {
|
||||
for (auto &content : getSharedList<ChatMessageContentCore>()) {
|
||||
if (content) disconnect(content.get());
|
||||
}
|
||||
if (!mChatMessageCore) return;
|
||||
auto contents = mChatMessageCore->getChatMessageContentList();
|
||||
for (auto &content : contents) {
|
||||
connect(content.get(), &ChatMessageContentCore::wasDownloadedChanged, this,
|
||||
[this, content](bool wasDownloaded) {
|
||||
if (wasDownloaded) {
|
||||
content->lCreateThumbnail();
|
||||
}
|
||||
});
|
||||
connect(content.get(), &ChatMessageContentCore::thumbnailChanged, this, [this] { emit lUpdate(); });
|
||||
}
|
||||
resetData<ChatMessageContentCore>(contents);
|
||||
});
|
||||
mModelConnection->makeConnectToCore(&ChatMessageContentList::lAddFile, [this](const QString &path) {
|
||||
QFile file(path);
|
||||
// #ifdef _WIN32
|
||||
// // A bug from FileDialog suppose that the file is local and overwrite the uri by removing "\\".
|
||||
// if (!file.exists()) {
|
||||
// path.prepend("\\\\");
|
||||
// file.setFileName(path);
|
||||
// }
|
||||
// #endif
|
||||
if (!file.exists()) return;
|
||||
if (rowCount() >= 12) {
|
||||
//: Error
|
||||
Utils::showInformationPopup(tr("popup_error_title"),
|
||||
//: You can add 12 files maximum
|
||||
tr("popup_error_max_files_count_message"), false);
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 fileSize = file.size();
|
||||
if (fileSize > Constants::FileSizeLimit) {
|
||||
qWarning() << QStringLiteral("Unable to send file. (Size limit=%1)").arg(Constants::FileSizeLimit);
|
||||
return;
|
||||
}
|
||||
auto name = QFileInfo(file).fileName().toStdString();
|
||||
mModelConnection->invokeToModel([this, path, fileSize, name] {
|
||||
std::shared_ptr<linphone::Content> content = CoreModel::getInstance()->getCore()->createContent();
|
||||
{
|
||||
QStringList mimeType = QMimeDatabase().mimeTypeForFile(path).name().split('/');
|
||||
if (mimeType.length() != 2) {
|
||||
qWarning() << QStringLiteral("Unable to get supported mime type for: `%1`.").arg(path);
|
||||
return;
|
||||
}
|
||||
content->setType(Utils::appStringToCoreString(mimeType[0]));
|
||||
content->setSubtype(Utils::appStringToCoreString(mimeType[1]));
|
||||
}
|
||||
content->setSize(size_t(fileSize));
|
||||
content->setName(name);
|
||||
content->setFilePath(Utils::appStringToCoreString(path));
|
||||
auto contentCore = ChatMessageContentCore::create(content, nullptr);
|
||||
mModelConnection->invokeToCore([this, contentCore] {
|
||||
connect(contentCore.get(), &ChatMessageContentCore::isFileChanged, this, [this, contentCore] {
|
||||
int i = -1;
|
||||
get(contentCore.get(), &i);
|
||||
emit dataChanged(index(i), index(i));
|
||||
});
|
||||
add(contentCore);
|
||||
contentCore->lCreateThumbnail(
|
||||
true); // Was not created because linphone::Content is not considered as a file (yet)
|
||||
});
|
||||
});
|
||||
});
|
||||
emit lUpdate();
|
||||
}
|
||||
|
||||
QVariant ChatMessageContentList::data(const QModelIndex &index, int role) const {
|
||||
int row = index.row();
|
||||
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();
|
||||
if (role == Qt::DisplayRole)
|
||||
return QVariant::fromValue(new ChatMessageContentGui(mList[row].objectCast<ChatMessageContentCore>()));
|
||||
return QVariant();
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CHAT_MESSAGE_CONTENT_LIST_H_
|
||||
#define CHAT_MESSAGE_CONTENT_LIST_H_
|
||||
|
||||
#include "core/proxy/ListProxy.hpp"
|
||||
#include "tool/AbstractObject.hpp"
|
||||
#include "tool/thread/SafeConnection.hpp"
|
||||
#include <QLocale>
|
||||
|
||||
class ChatMessageGui;
|
||||
class ChatMessageCore;
|
||||
// =============================================================================
|
||||
|
||||
class ChatMessageContentList : public ListProxy, public AbstractObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static QSharedPointer<ChatMessageContentList> create();
|
||||
ChatMessageContentList(QObject *parent = Q_NULLPTR);
|
||||
~ChatMessageContentList();
|
||||
|
||||
QSharedPointer<ChatMessageCore> getChatMessageCore() const;
|
||||
ChatMessageGui *getChatMessage() const;
|
||||
void setChatMessageCore(QSharedPointer<ChatMessageCore> core);
|
||||
void setChatMessageGui(ChatMessageGui *chat);
|
||||
|
||||
int findFirstUnreadIndex();
|
||||
|
||||
void setSelf(QSharedPointer<ChatMessageContentList> me);
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
signals:
|
||||
void lAddFile(QString path);
|
||||
void isFileChanged();
|
||||
void lUpdate();
|
||||
void chatMessageChanged();
|
||||
|
||||
private:
|
||||
QSharedPointer<ChatMessageCore> mChatMessageCore;
|
||||
QSharedPointer<SafeConnection<ChatMessageContentList, CoreModel>> mModelConnection;
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
||||
#endif
|
||||
106
Linphone/core/chat/message/content/ChatMessageContentProxy.cpp
Normal file
106
Linphone/core/chat/message/content/ChatMessageContentProxy.cpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatMessageContentProxy.hpp"
|
||||
#include "ChatMessageContentGui.hpp"
|
||||
#include "core/App.hpp"
|
||||
#include "core/chat/message/ChatMessageGui.hpp"
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(ChatMessageContentProxy)
|
||||
|
||||
ChatMessageContentProxy::ChatMessageContentProxy(QObject *parent) : LimitProxy(parent) {
|
||||
mList = ChatMessageContentList::create();
|
||||
setSourceModel(mList.get());
|
||||
}
|
||||
|
||||
ChatMessageContentProxy::~ChatMessageContentProxy() {
|
||||
}
|
||||
|
||||
void ChatMessageContentProxy::setSourceModel(QAbstractItemModel *model) {
|
||||
auto oldChatMessageContentList = getListModel<ChatMessageContentList>();
|
||||
if (oldChatMessageContentList) {
|
||||
// disconnect(oldChatMessageContentList);
|
||||
}
|
||||
auto newChatMessageContentList = dynamic_cast<ChatMessageContentList *>(model);
|
||||
if (newChatMessageContentList) {
|
||||
// connect(newChatMessageContentList, &ChatMessageContentList::chatChanged, this,
|
||||
// &ChatMessageContentProxy::chatChanged);
|
||||
}
|
||||
setSourceModels(new SortFilterList(model));
|
||||
sort(0);
|
||||
}
|
||||
|
||||
ChatMessageGui *ChatMessageContentProxy::getChatMessageGui() {
|
||||
auto model = getListModel<ChatMessageContentList>();
|
||||
if (!mChatMessageGui && model) mChatMessageGui = model->getChatMessage();
|
||||
return mChatMessageGui;
|
||||
}
|
||||
|
||||
void ChatMessageContentProxy::setChatMessageGui(ChatMessageGui *chat) {
|
||||
getListModel<ChatMessageContentList>()->setChatMessageGui(chat);
|
||||
}
|
||||
|
||||
// ChatMessageGui *ChatMessageContentProxy::getChatMessageAtIndex(int i) {
|
||||
// auto model = getListModel<ChatMessageContentList>();
|
||||
// auto sourceIndex = mapToSource(index(i, 0)).row();
|
||||
// if (model) {
|
||||
// auto chat = model->getAt<ChatMessageCore>(sourceIndex);
|
||||
// if (chat) return new ChatMessageGui(chat);
|
||||
// else return nullptr;
|
||||
// }
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
void ChatMessageContentProxy::addFile(const QString &path) {
|
||||
auto model = getListModel<ChatMessageContentList>();
|
||||
if (model) emit model->lAddFile(path.toUtf8());
|
||||
}
|
||||
|
||||
void ChatMessageContentProxy::removeContent(ChatMessageContentGui *contentGui) {
|
||||
auto model = getListModel<ChatMessageContentList>();
|
||||
if (model && contentGui) model->remove(contentGui->mCore);
|
||||
}
|
||||
|
||||
void ChatMessageContentProxy::clear() {
|
||||
auto model = getListModel<ChatMessageContentList>();
|
||||
if (model) model->clearData();
|
||||
}
|
||||
|
||||
bool ChatMessageContentProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
|
||||
auto contentCore = getItemAtSource<ChatMessageContentList, ChatMessageContentCore>(sourceRow);
|
||||
if (contentCore) {
|
||||
if (mFilterType == (int)FilterContentType::Unknown) return false;
|
||||
else if (mFilterType == (int)FilterContentType::File) {
|
||||
return contentCore->isFile() || contentCore->isFileTransfer();
|
||||
} else if (mFilterType == (int)FilterContentType::Text) return contentCore->isText();
|
||||
else if (mFilterType == (int)FilterContentType::Voice) return contentCore->isVoiceRecording();
|
||||
else if (mFilterType == (int)FilterContentType::Conference) return contentCore->isCalendar();
|
||||
else if (mFilterType == (int)FilterContentType::All) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChatMessageContentProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft,
|
||||
const QModelIndex &sourceRight) const {
|
||||
auto l = getItemAtSource<ChatMessageContentList, ChatMessageCore>(sourceLeft.row());
|
||||
auto r = getItemAtSource<ChatMessageContentList, ChatMessageCore>(sourceRight.row());
|
||||
if (l && r) return l->getTimestamp() <= r->getTimestamp();
|
||||
else return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CHAT_MESSAGE_CONENT_PROXY_H_
|
||||
#define CHAT_MESSAGE_CONENT_PROXY_H_
|
||||
|
||||
#include "ChatMessageContentList.hpp"
|
||||
#include "core/proxy/LimitProxy.hpp"
|
||||
#include "tool/AbstractObject.hpp"
|
||||
|
||||
// =============================================================================
|
||||
|
||||
class ChatMessageGui;
|
||||
class ChatMessageContentGui;
|
||||
|
||||
class ChatMessageContentProxy : public LimitProxy, public AbstractObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(ChatMessageGui *chatMessageGui READ getChatMessageGui WRITE setChatMessageGui NOTIFY chatChanged)
|
||||
|
||||
public:
|
||||
enum class FilterContentType { Unknown = 0, File = 1, Text = 2, Voice = 3, Conference = 4, All = 5 };
|
||||
Q_ENUM(FilterContentType)
|
||||
|
||||
DECLARE_SORTFILTER_CLASS(ChatMessageContentProxy *mHideListProxy = nullptr;)
|
||||
ChatMessageContentProxy(QObject *parent = Q_NULLPTR);
|
||||
~ChatMessageContentProxy();
|
||||
|
||||
ChatMessageGui *getChatMessageGui();
|
||||
void setChatMessageGui(ChatMessageGui *chat);
|
||||
|
||||
void setSourceModel(QAbstractItemModel *sourceModel) override;
|
||||
|
||||
Q_INVOKABLE void addFile(const QString &path);
|
||||
Q_INVOKABLE void removeContent(ChatMessageContentGui *contentGui);
|
||||
Q_INVOKABLE void clear();
|
||||
|
||||
signals:
|
||||
void chatChanged();
|
||||
void filterChanged();
|
||||
void messageInserted(int index, ChatMessageGui *message);
|
||||
|
||||
protected:
|
||||
QSharedPointer<ChatMessageContentList> mList;
|
||||
ChatMessageGui *mChatMessageGui = nullptr;
|
||||
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -269,7 +269,8 @@ QString Paths::getFriendsListFilePath() {
|
|||
}
|
||||
|
||||
QString Paths::getDownloadDirPath() {
|
||||
return getWritableDirPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + QDir::separator());
|
||||
return getWritableDirPath(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) +
|
||||
Constants::PathFiles);
|
||||
}
|
||||
|
||||
QString Paths::getLimeDatabasePath() {
|
||||
|
|
|
|||
|
|
@ -123,7 +123,9 @@ public:
|
|||
}
|
||||
|
||||
virtual void clearData() override {
|
||||
beginResetModel();
|
||||
mList.clear();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
virtual void resetData(QList<T> newData = QList<T>()) {
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ SettingsCore::SettingsCore(QObject *parent) : QObject(parent) {
|
|||
|
||||
// Chat
|
||||
mEmojiFont = settingsModel->getEmojiFont();
|
||||
mTextMessageFont = settingsModel->getTextMessageFont();
|
||||
|
||||
// Ui
|
||||
INIT_CORE_MEMBER(DisableChatFeature, settingsModel)
|
||||
|
|
@ -539,6 +540,26 @@ void SettingsCore::setVfsEnabled(bool enabled) {
|
|||
}
|
||||
}
|
||||
|
||||
bool SettingsCore::getVfsEncrypted() {
|
||||
mAppSettings.beginGroup("keychain");
|
||||
return mAppSettings.value("enabled", false).toBool();
|
||||
}
|
||||
|
||||
void SettingsCore::setVfsEncrypted(bool encrypted, const bool deleteUserData) {
|
||||
#ifdef ENABLE_QT_KEYCHAIN
|
||||
if (getVfsEncrypted() != encrypted) {
|
||||
if (encrypted) {
|
||||
mVfsUtils.newEncryptionKeyAsync();
|
||||
shared_ptr<linphone::Factory> factory = linphone::Factory::get();
|
||||
factory->setDownloadDir(Utils::appStringToCoreString(getDownloadFolder()));
|
||||
} else { // Remove key, stop core, delete data and initiate reboot
|
||||
mVfsUtils.needToDeleteUserData(deleteUserData);
|
||||
mVfsUtils.deleteKey(mVfsUtils.getApplicationVfsEncryptionKey());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SettingsCore::setVideoEnabled(bool enabled) {
|
||||
if (mVideoEnabled != enabled) {
|
||||
mVideoEnabled = enabled;
|
||||
|
|
|
|||
|
|
@ -112,6 +112,9 @@ public:
|
|||
}
|
||||
void setVfsEnabled(bool enabled);
|
||||
|
||||
bool getVfsEncrypted();
|
||||
void setVfsEncrypted(bool encrypted, const bool deleteUserData);
|
||||
|
||||
// Call. --------------------------------------------------------------------
|
||||
|
||||
bool getVideoEnabled() {
|
||||
|
|
@ -234,6 +237,7 @@ public:
|
|||
DECLARE_CORE_GETSET_MEMBER(bool, disableCallForward, DisableCallForward)
|
||||
DECLARE_CORE_GETSET_MEMBER(QString, callForwardToAddress, CallForwardToAddress)
|
||||
DECLARE_CORE_GET_CONSTANT(QFont, emojiFont, EmojiFont)
|
||||
DECLARE_CORE_GET_CONSTANT(QFont, textMessageFont, TextMessageFont)
|
||||
|
||||
signals:
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ void VariantList::setModel(QList<QVariant> list) {
|
|||
emit modelChanged();
|
||||
}
|
||||
|
||||
QList<QVariant> VariantList::getModel() const {
|
||||
return mList;
|
||||
}
|
||||
|
||||
void VariantList::replace(int index, QVariant newValue) {
|
||||
mList.replace(index, newValue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ public:
|
|||
~VariantList();
|
||||
|
||||
void setModel(QList<QVariant> list);
|
||||
QList<QVariant> getModel() const;
|
||||
|
||||
void replace(int index, QVariant newValue);
|
||||
|
||||
|
|
|
|||
1
Linphone/data/image/download-simple.svg
Normal file
1
Linphone/data/image/download-simple.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M224,144v64a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V144a8,8,0,0,1,16,0v56H208V144a8,8,0,0,1,16,0Zm-101.66,5.66a8,8,0,0,0,11.32,0l40-40a8,8,0,0,0-11.32-11.32L136,124.69V32a8,8,0,0,0-16,0v92.69L93.66,98.34a8,8,0,0,0-11.32,11.32Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 341 B |
3
Linphone/data/image/file-image.svg
Normal file
3
Linphone/data/image/file-image.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.25 3.75H3.75C3.35218 3.75 2.97064 3.90804 2.68934 4.18934C2.40804 4.47064 2.25 4.85218 2.25 5.25V18.75C2.25 19.1478 2.40804 19.5294 2.68934 19.8107C2.97064 20.092 3.35218 20.25 3.75 20.25H20.25C20.6478 20.25 21.0294 20.092 21.3107 19.8107C21.592 19.5294 21.75 19.1478 21.75 18.75V5.25C21.75 4.85218 21.592 4.47064 21.3107 4.18934C21.0294 3.90804 20.6478 3.75 20.25 3.75ZM20.25 5.25V14.8828L17.8059 12.4397C17.6666 12.3004 17.5013 12.1898 17.3193 12.1144C17.1372 12.039 16.9422 12.0002 16.7452 12.0002C16.5481 12.0002 16.3531 12.039 16.1711 12.1144C15.989 12.1898 15.8237 12.3004 15.6844 12.4397L13.8094 14.3147L9.68438 10.1897C9.4031 9.9086 9.02172 9.7507 8.62406 9.7507C8.22641 9.7507 7.84503 9.9086 7.56375 10.1897L3.75 14.0034V5.25H20.25ZM3.75 16.125L8.625 11.25L16.125 18.75H3.75V16.125ZM20.25 18.75H18.2466L14.8716 15.375L16.7466 13.5L20.25 17.0044V18.75ZM13.5 9.375C13.5 9.1525 13.566 8.93499 13.6896 8.74998C13.8132 8.56498 13.9889 8.42078 14.1945 8.33564C14.4 8.25049 14.6262 8.22821 14.8445 8.27162C15.0627 8.31502 15.2632 8.42217 15.4205 8.5795C15.5778 8.73684 15.685 8.93729 15.7284 9.15552C15.7718 9.37375 15.7495 9.59995 15.6644 9.80552C15.5792 10.0111 15.435 10.1868 15.25 10.3104C15.065 10.434 14.8475 10.5 14.625 10.5C14.3266 10.5 14.0405 10.3815 13.8295 10.1705C13.6185 9.95952 13.5 9.67337 13.5 9.375Z" fill="#343330"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
Linphone/data/image/file-pdf.svg
Normal file
1
Linphone/data/image/file-pdf.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M224,152a8,8,0,0,1-8,8H192v16h16a8,8,0,0,1,0,16H192v16a8,8,0,0,1-16,0V152a8,8,0,0,1,8-8h32A8,8,0,0,1,224,152ZM92,172a28,28,0,0,1-28,28H56v8a8,8,0,0,1-16,0V152a8,8,0,0,1,8-8H64A28,28,0,0,1,92,172Zm-16,0a12,12,0,0,0-12-12H56v24h8A12,12,0,0,0,76,172Zm88,8a36,36,0,0,1-36,36H112a8,8,0,0,1-8-8V152a8,8,0,0,1,8-8h16A36,36,0,0,1,164,180Zm-16,0a20,20,0,0,0-20-20h-8v40h8A20,20,0,0,0,148,180ZM40,112V40A16,16,0,0,1,56,24h96a8,8,0,0,1,5.66,2.34l56,56A8,8,0,0,1,216,88v24a8,8,0,0,1-16,0V96H152a8,8,0,0,1-8-8V40H56v72a8,8,0,0,1-16,0ZM160,80h28.69L160,51.31Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 669 B |
1
Linphone/data/image/file-plus.svg
Normal file
1
Linphone/data/image/file-plus.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Zm-40-64a8,8,0,0,1-8,8H136v16a8,8,0,0,1-16,0V160H104a8,8,0,0,1,0-16h16V128a8,8,0,0,1,16,0v16h16A8,8,0,0,1,160,152Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 433 B |
1
Linphone/data/image/file.svg
Normal file
1
Linphone/data/image/file.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M213.66,82.34l-56-56A8,8,0,0,0,152,24H56A16,16,0,0,0,40,40V216a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V88A8,8,0,0,0,213.66,82.34ZM160,51.31,188.69,80H160ZM200,216H56V40h88V88a8,8,0,0,0,8,8h48V216Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 320 B |
3
Linphone/data/image/play-fill.svg
Normal file
3
Linphone/data/image/play-fill.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22 11.955C22.0006 12.2019 21.9373 12.4448 21.8162 12.66C21.6951 12.8753 21.5204 13.0555 21.3091 13.1832L8.21091 21.1959C7.99008 21.3311 7.73715 21.4049 7.47825 21.4097C7.21935 21.4145 6.96387 21.3501 6.73818 21.2232C6.51465 21.0982 6.32843 20.9159 6.19869 20.6951C6.06896 20.4743 6.00037 20.2229 6 19.9668V3.94318C6.00037 3.68708 6.06896 3.43569 6.19869 3.21488C6.32843 2.99407 6.51465 2.8118 6.73818 2.68682C6.96387 2.55986 7.21935 2.49545 7.47825 2.50025C7.73715 2.50504 7.99008 2.57887 8.21091 2.71409L21.3091 10.7268C21.5204 10.8545 21.6951 11.0347 21.8162 11.25C21.9373 11.4652 22.0006 11.7081 22 11.955Z" fill="#343330"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 741 B |
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -13,6 +13,7 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
|
||||
model/chat/ChatModel.cpp
|
||||
model/chat/message/ChatMessageModel.cpp
|
||||
model/chat/message/content/ChatMessageContentModel.cpp
|
||||
|
||||
model/conference/ConferenceInfoModel.cpp
|
||||
model/conference/ConferenceModel.cpp
|
||||
|
|
@ -42,6 +43,7 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
|
||||
|
||||
model/tool/ToolModel.cpp
|
||||
model/tool/VfsUtils.cpp
|
||||
|
||||
model/videoSource/VideoSourceDescriptorModel.cpp
|
||||
|
||||
|
|
|
|||
|
|
@ -130,4 +130,4 @@ private:
|
|||
void onEphemeralMessageDeleted(const std::shared_ptr<linphone::ChatMessage> &message) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
159
Linphone/model/chat/message/content/ChatMessageContentModel.cpp
Normal file
159
Linphone/model/chat/message/content/ChatMessageContentModel.cpp
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatMessageContentModel.hpp"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QQmlApplicationEngine>
|
||||
|
||||
#include "core/App.hpp"
|
||||
#include "tool/providers/ExternalImageProvider.hpp"
|
||||
#include "tool/providers/ThumbnailProvider.hpp"
|
||||
|
||||
#include "model/chat/message/ChatMessageModel.hpp"
|
||||
#include "model/conference/ConferenceInfoModel.hpp"
|
||||
#include "model/core/CoreModel.hpp"
|
||||
#include "model/setting/SettingsModel.hpp"
|
||||
|
||||
#include "tool/Utils.hpp"
|
||||
|
||||
// =============================================================================
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(ChatMessageContentModel)
|
||||
|
||||
ChatMessageContentModel::ChatMessageContentModel(std::shared_ptr<linphone::Content> content,
|
||||
std::shared_ptr<ChatMessageModel> chatMessageModel) {
|
||||
mChatMessageModel = chatMessageModel;
|
||||
mContent = content;
|
||||
assert(content);
|
||||
if (content->isFile() || content->isFileEncrypted() || content->isFileTransfer()) {
|
||||
createThumbnail();
|
||||
}
|
||||
if (mChatMessageModel)
|
||||
connect(mChatMessageModel.get(), &ChatMessageModel::msgStateChanged, this,
|
||||
[this] { emit messageStateChanged(mChatMessageModel->getState()); });
|
||||
}
|
||||
|
||||
ChatMessageContentModel::~ChatMessageContentModel() {
|
||||
mustBeInLinphoneThread("~" + getClassName());
|
||||
}
|
||||
|
||||
// Create a thumbnail from the first content that have a file
|
||||
void ChatMessageContentModel::createThumbnail() {
|
||||
auto path = Utils::coreStringToAppString(mContent->getFilePath());
|
||||
if (!path.isEmpty()) {
|
||||
auto isVideo = Utils::isVideo(path);
|
||||
emit wasDownloadedChanged(mContent, QFileInfo(path).isFile());
|
||||
emit thumbnailChanged(QStringLiteral("image://%1/%2").arg(ThumbnailProvider::ProviderId).arg(path));
|
||||
emit filePathChanged(mContent, path);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatMessageContentModel::removeDownloadedFile(QString filePath) {
|
||||
if (!filePath.isEmpty()) {
|
||||
QFile(filePath).remove();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatMessageContentModel::downloadFile(const QString &name) {
|
||||
if (!mChatMessageModel) return;
|
||||
switch (mChatMessageModel->getState()) {
|
||||
case linphone::ChatMessage::State::Delivered:
|
||||
case linphone::ChatMessage::State::DeliveredToUser:
|
||||
case linphone::ChatMessage::State::Displayed:
|
||||
case linphone::ChatMessage::State::FileTransferDone:
|
||||
break;
|
||||
case linphone::ChatMessage::State::FileTransferInProgress:
|
||||
return;
|
||||
default:
|
||||
qWarning() << QStringLiteral("Wrong message state when requesting downloading, state=.")
|
||||
<< LinphoneEnums::fromLinphone(mChatMessageModel->getState());
|
||||
}
|
||||
bool soFarSoGood;
|
||||
const QString safeFilePath = Utils::getSafeFilePath(
|
||||
QStringLiteral("%1%2").arg(App::getInstance()->getSettings()->getDownloadFolder()).arg(name), &soFarSoGood);
|
||||
|
||||
if (!soFarSoGood) {
|
||||
qWarning() << QStringLiteral("Unable to create safe file path for: %1.").arg(name);
|
||||
return;
|
||||
}
|
||||
mContent->setFilePath(Utils::appStringToCoreString(safeFilePath));
|
||||
|
||||
if (!mContent->isFileTransfer()) {
|
||||
Utils::showInformationPopup(
|
||||
//: Error
|
||||
tr("popup_error_title"),
|
||||
//: This file was already downloaded and is no more on the server. Your peer have to resend it if you want
|
||||
//: to get it
|
||||
tr("popup_download_error_message"), false);
|
||||
} else {
|
||||
if (!mChatMessageModel->getMonitor()->downloadContent(mContent))
|
||||
qWarning() << QStringLiteral("Unable to download file of entry %1.").arg(name);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatMessageContentModel::cancelDownloadFile() {
|
||||
if (mChatMessageModel && mChatMessageModel->getMonitor()) {
|
||||
mChatMessageModel->getMonitor()->cancelFileTransfer();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatMessageContentModel::openFile(const QString &name, bool wasDownloaded, bool showDirectory) {
|
||||
if (mChatMessageModel &&
|
||||
((!wasDownloaded && !mChatMessageModel->getMonitor()->isOutgoing()) || mContent->getFilePath() == "")) {
|
||||
downloadFile(name);
|
||||
} else {
|
||||
QFileInfo info(Utils::coreStringToAppString(mContent->getFilePath()));
|
||||
showDirectory = showDirectory || !info.exists();
|
||||
if (!QDesktopServices::openUrl(QUrl(
|
||||
QStringLiteral("file:///%1").arg(showDirectory ? info.absolutePath() : info.absoluteFilePath()))) &&
|
||||
!showDirectory) {
|
||||
QDesktopServices::openUrl(QUrl(QStringLiteral("file:///%1").arg(info.absolutePath())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatMessageContentModel::saveAs(const QString &path) {
|
||||
QString cachePath = Utils::coreStringToAppString(mContent->exportPlainFile());
|
||||
bool toDelete = true;
|
||||
bool result = false;
|
||||
if (cachePath.isEmpty()) {
|
||||
cachePath = Utils::coreStringToAppString(mContent->getFilePath());
|
||||
toDelete = false;
|
||||
}
|
||||
if (!cachePath.isEmpty()) {
|
||||
QString decodedPath = QUrl::fromPercentEncoding(path.toUtf8());
|
||||
QFile file(cachePath);
|
||||
QFile newFile(decodedPath);
|
||||
if (newFile.exists()) newFile.remove();
|
||||
result = file.copy(decodedPath);
|
||||
if (toDelete) file.remove();
|
||||
if (result) QDesktopServices::openUrl(QUrl(QStringLiteral("file:///%1").arg(decodedPath)));
|
||||
}
|
||||
|
||||
emit fileSavedChanged(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::shared_ptr<linphone::Content> &ChatMessageContentModel::getContent() const {
|
||||
return mContent;
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CHAT_MESSAGE_CONTENT_MODEL_H_
|
||||
#define CHAT_MESSAGE_CONTENT_MODEL_H_
|
||||
|
||||
#include "tool/AbstractObject.hpp"
|
||||
#include <linphone++/linphone.hh>
|
||||
|
||||
// =============================================================================
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
||||
class ChatMessageModel;
|
||||
class ConferenceInfoModel;
|
||||
|
||||
class ChatMessageContentModel : public QObject, public AbstractObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ChatMessageContentModel(std::shared_ptr<linphone::Content> content,
|
||||
std::shared_ptr<ChatMessageModel> chatMessageModel);
|
||||
~ChatMessageContentModel();
|
||||
|
||||
QString getThumbnail() const;
|
||||
|
||||
void setThumbnail(const QString &data);
|
||||
void setWasDownloaded(bool wasDownloaded);
|
||||
|
||||
void createThumbnail();
|
||||
void removeDownloadedFile(QString filePath);
|
||||
|
||||
void downloadFile(const QString &name);
|
||||
void cancelDownloadFile();
|
||||
void openFile(const QString &name, bool wasDownloaded, bool showDirectory = false);
|
||||
bool saveAs(const QString &path);
|
||||
|
||||
const std::shared_ptr<linphone::Content> &getContent() const;
|
||||
|
||||
signals:
|
||||
void thumbnailChanged(QString thumbnail);
|
||||
void fileOffsetChanged();
|
||||
void wasDownloadedChanged(const std::shared_ptr<linphone::Content> &content, bool downloaded);
|
||||
void fileSavedChanged(bool success);
|
||||
void filePathChanged(const std::shared_ptr<linphone::Content> &content, QString filePath);
|
||||
void messageStateChanged(linphone::ChatMessage::State state);
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
std::shared_ptr<linphone::Content> mContent;
|
||||
std::shared_ptr<ChatMessageModel> mChatMessageModel;
|
||||
QSharedPointer<ConferenceInfoModel> mConferenceInfoModel;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -87,30 +87,30 @@ void MagicSearchModel::onSearchResultsReceived(const std::shared_ptr<linphone::M
|
|||
auto f = result->getFriend();
|
||||
auto friendsManager = FriendsManager::getInstance();
|
||||
if (f) {
|
||||
qDebug() << "friend exists, append to unknown map";
|
||||
auto friendAddress = f->getAddress() ? f->getAddress()->clone() : nullptr;
|
||||
if (friendAddress) {
|
||||
friendAddress->clean();
|
||||
qDebug() << "friend exists, append to unknown map";
|
||||
friendsManager->appendUnknownFriend(friendAddress, f);
|
||||
if (friendsManager->isInOtherAddresses(
|
||||
Utils::coreStringToAppString(friendAddress->asStringUriOnly()))) {
|
||||
friendsManager->removeOtherAddress(Utils::coreStringToAppString(friendAddress->asStringUriOnly()));
|
||||
}
|
||||
}
|
||||
}
|
||||
auto fList = f ? f->getFriendList() : nullptr;
|
||||
auto fList = f->getFriendList();
|
||||
|
||||
// qDebug() << log().arg("") << (f ? f->getName().c_str() : "NoFriend") << ", "
|
||||
// << (result->getAddress() ? result->getAddress()->asString().c_str() : "NoAddr") << " / "
|
||||
// << (fList ? fList->getDisplayName().c_str() : "NoList") << result->getSourceFlags() << " /
|
||||
//"
|
||||
// << (f ? f.get() : nullptr);
|
||||
bool isLdap = (result->getSourceFlags() & (int)linphone::MagicSearch::Source::LdapServers) != 0;
|
||||
// Do not add it into ldap_friends if it already exists in app_friends.
|
||||
if (isLdap && f &&
|
||||
(!fList || fList->getDisplayName() != "app_friends")) { // Double check because of SDK merging that lead to
|
||||
// use a ldap result as of app_friends/ldap_friends.
|
||||
updateFriendListWithFriend(f, ldapFriends);
|
||||
// qDebug() << log().arg("") << (f ? f->getName().c_str() : "NoFriend") << ", "
|
||||
// << (result->getAddress() ? result->getAddress()->asString().c_str() : "NoAddr") << " / "
|
||||
// << (fList ? fList->getDisplayName().c_str() : "NoList") << result->getSourceFlags() << " /
|
||||
//"
|
||||
// << (f ? f.get() : nullptr);
|
||||
bool isLdap = (result->getSourceFlags() & (int)linphone::MagicSearch::Source::LdapServers) != 0;
|
||||
// Do not add it into ldap_friends if it already exists in app_friends.
|
||||
if (isLdap && (!fList || fList->getDisplayName() !=
|
||||
"app_friends")) { // Double check because of SDK merging that lead to
|
||||
// use a ldap result as of app_friends/ldap_friends.
|
||||
updateFriendListWithFriend(f, ldapFriends);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@
|
|||
*/
|
||||
|
||||
#include "SettingsModel.hpp"
|
||||
#include "core/App.hpp"
|
||||
#include "core/path/Paths.hpp"
|
||||
#include "model/core/CoreModel.hpp"
|
||||
#include "model/tool/ToolModel.hpp"
|
||||
// #include "model/tool/VfsUtils.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
|
||||
// =============================================================================
|
||||
|
|
@ -486,6 +488,26 @@ void SettingsModel::setVfsEnabled(bool enabled) {
|
|||
emit vfsEnabledChanged(enabled);
|
||||
}
|
||||
|
||||
bool SettingsModel::getVfsEncrypted() const {
|
||||
return false;
|
||||
// mAppSettings.beginGroup("keychain");
|
||||
// return mAppSettings.value("enabled", false).toBool();
|
||||
}
|
||||
|
||||
// void SettingsModel::setVfsEncrypted(bool encrypted, const bool deleteUserData) {
|
||||
// #ifdef ENABLE_QT_KEYCHAIN
|
||||
// if (getVfsEncrypted() != encrypted) {
|
||||
// if (encrypted) {
|
||||
// mVfsUtils.newEncryptionKeyAsync();
|
||||
// shared_ptr<linphone::Factory> factory = linphone::Factory::get();
|
||||
// factory->setDownloadDir(Utils::appStringToCoreString(getDownloadFolder()));
|
||||
// } else { // Remove key, stop core, delete data and initiate reboot
|
||||
// mVfsUtils.needToDeleteUserData(deleteUserData);
|
||||
// mVfsUtils.deleteKey(mVfsUtils.getApplicationVfsEncryptionKey());
|
||||
// }
|
||||
// }
|
||||
// #endif
|
||||
// }
|
||||
// =============================================================================
|
||||
// Logs.
|
||||
// =============================================================================
|
||||
|
|
@ -740,6 +762,17 @@ int SettingsModel::getEmojiFontSize() const {
|
|||
return mConfig->getInt(UiSection, "emoji_font_size", Constants::DefaultEmojiFontPointSize);
|
||||
}
|
||||
|
||||
QFont SettingsModel::getTextMessageFont() const {
|
||||
QString family = Utils::coreStringToAppString(mConfig->getString(
|
||||
UiSection, "text_message_font", Utils::appStringToCoreString(App::getInstance()->font().family())));
|
||||
int pointSize = getTextMessageFontSize();
|
||||
return QFont(family, pointSize);
|
||||
}
|
||||
|
||||
int SettingsModel::getTextMessageFontSize() const {
|
||||
return mConfig->getInt(UiSection, "text_message_font_size", Constants::DefaultFontPointSize);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
void SettingsModel::notifyConfigReady(){
|
||||
DEFINE_NOTIFY_CONFIG_READY(disableChatFeature, DisableChatFeature)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ public:
|
|||
std::shared_ptr<linphone::Config> mConfig;
|
||||
|
||||
bool getVfsEnabled() const;
|
||||
bool getVfsEncrypted() const;
|
||||
void setVfsEnabled(const bool enabled);
|
||||
|
||||
bool getVideoEnabled() const;
|
||||
|
|
@ -159,6 +160,8 @@ public:
|
|||
|
||||
QFont getEmojiFont() const;
|
||||
int getEmojiFontSize() const;
|
||||
QFont getTextMessageFont() const;
|
||||
int getTextMessageFontSize() const;
|
||||
|
||||
// UI
|
||||
DECLARE_GETSET(bool, disableChatFeature, DisableChatFeature)
|
||||
|
|
@ -240,6 +243,10 @@ private:
|
|||
MediastreamerUtils::SimpleCaptureGraph *mSimpleCaptureGraph = nullptr;
|
||||
int mCaptureGraphListenerCount = 0;
|
||||
|
||||
#ifdef ENABLE_QT_KEYCHAIN
|
||||
VfsUtils mVfsUtils;
|
||||
#endif
|
||||
|
||||
void enableCallForward(QString destination);
|
||||
void disableCallForward();
|
||||
|
||||
|
|
|
|||
186
Linphone/model/tool/VfsUtils.cpp
Normal file
186
Linphone/model/tool/VfsUtils.cpp
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
// /*
|
||||
// * Copyright (c) 2010-2022 Belledonne Communications SARL.
|
||||
// *
|
||||
// * This file is part of linphone-desktop
|
||||
// * (see https://www.linphone.org).
|
||||
// *
|
||||
// * This program is free software: you can redistribute it and/or modify
|
||||
// * it under the terms of the GNU 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 General Public License for more details.
|
||||
// *
|
||||
// * You should have received a copy of the GNU General Public License
|
||||
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// */
|
||||
|
||||
// #include "VfsUtils.hpp"
|
||||
|
||||
// #include <bctoolbox/crypto.hh>
|
||||
// #include <linphone++/factory.hh>
|
||||
// #include <linphone/api/c-factory.h>
|
||||
|
||||
// #include <core/path/Paths.hpp>
|
||||
// #include <model/setting/SettingsModel.hpp>
|
||||
// #include <tool/Constants.hpp>
|
||||
// #include <tool/Utils.hpp>
|
||||
|
||||
// #include <QCoreApplication>
|
||||
// #include <QDebug>
|
||||
|
||||
// // =============================================================================
|
||||
|
||||
// VfsUtils::VfsUtils(QObject *parent)
|
||||
// : QObject(parent), mReadCredentialJob(QLatin1String(APPLICATION_ID)),
|
||||
// mWriteCredentialJob(QLatin1String(APPLICATION_ID)), mDeleteCredentialJob(QLatin1String(APPLICATION_ID)) {
|
||||
// mReadCredentialJob.setAutoDelete(false);
|
||||
// mWriteCredentialJob.setAutoDelete(false);
|
||||
// mDeleteCredentialJob.setAutoDelete(false);
|
||||
// }
|
||||
|
||||
// void VfsUtils::deleteKey(const QString &key) {
|
||||
// mDeleteCredentialJob.setKey(key);
|
||||
|
||||
// QObject::connect(&mDeleteCredentialJob, &QKeychain::DeletePasswordJob::finished, [=]() {
|
||||
// if (mDeleteCredentialJob.error()) {
|
||||
// emit error(tr("Delete key failed: %1").arg(qPrintable(mDeleteCredentialJob.errorString())));
|
||||
// return;
|
||||
// }
|
||||
// emit keyDeleted(key);
|
||||
// });
|
||||
|
||||
// mDeleteCredentialJob.start();
|
||||
// }
|
||||
|
||||
// void VfsUtils::readKey(const QString &key) {
|
||||
// mReadCredentialJob.setKey(key);
|
||||
// QObject::connect(&mReadCredentialJob, &QKeychain::ReadPasswordJob::finished, [=]() {
|
||||
// if (mReadCredentialJob.error()) {
|
||||
// emit error(tr("Read key failed: %1").arg(qPrintable(mReadCredentialJob.errorString())));
|
||||
// return;
|
||||
// }
|
||||
// emit keyRead(key, mReadCredentialJob.textData());
|
||||
// });
|
||||
|
||||
// mReadCredentialJob.start();
|
||||
// }
|
||||
|
||||
// void VfsUtils::writeKey(const QString &key, const QString &value) {
|
||||
// mWriteCredentialJob.setKey(key);
|
||||
|
||||
// QObject::connect(&mWriteCredentialJob, &QKeychain::WritePasswordJob::finished, [=]() {
|
||||
// if (mWriteCredentialJob.error()) {
|
||||
// emit error(tr("Write key failed: %1").arg(qPrintable(mWriteCredentialJob.errorString())));
|
||||
// return;
|
||||
// }
|
||||
// if (key == getApplicationVfsEncryptionKey()) updateSDKWithKey(value);
|
||||
// emit keyWritten(key);
|
||||
// });
|
||||
|
||||
// mWriteCredentialJob.setTextData(value);
|
||||
// mWriteCredentialJob.start();
|
||||
// }
|
||||
|
||||
// bool VfsUtils::needToDeleteUserData() const {
|
||||
// return mNeedToDeleteUserData;
|
||||
// }
|
||||
|
||||
// void VfsUtils::needToDeleteUserData(const bool &need) {
|
||||
// mNeedToDeleteUserData = need;
|
||||
// }
|
||||
|
||||
// //-----------------------------------------------------------------------------------------------
|
||||
|
||||
// void VfsUtils::newEncryptionKeyAsync() {
|
||||
// QString value;
|
||||
// bctoolbox::RNG rng;
|
||||
// auto key = rng.randomize(32);
|
||||
// size_t keySize = key.size();
|
||||
// uint8_t *shaKey = new uint8_t[keySize];
|
||||
// bctbx_sha256(&key[0], key.size(), keySize, shaKey);
|
||||
// for (int i = 0; i < keySize; ++i)
|
||||
// value += QString::number(shaKey[i], 16);
|
||||
// writeKey(getApplicationVfsEncryptionKey(), value);
|
||||
// }
|
||||
|
||||
// bool VfsUtils::newEncryptionKey() {
|
||||
// int argc = 1;
|
||||
// const char *argv = "dummy";
|
||||
// QCoreApplication vfsSetter(argc, (char **)&argv);
|
||||
// VfsUtils vfs;
|
||||
// QObject::connect(
|
||||
// &vfs, &VfsUtils::keyWritten, &vfsSetter, [&vfsSetter, &vfs](const QString &key) { vfsSetter.quit(); },
|
||||
// Qt::QueuedConnection);
|
||||
// QObject::connect(
|
||||
// &vfs, &VfsUtils::error, &vfsSetter,
|
||||
// [&vfsSetter](const QString &errorText) {
|
||||
// qCritical() << "[VFS] " << errorText;
|
||||
// vfsSetter.exit(-1);
|
||||
// },
|
||||
// Qt::QueuedConnection);
|
||||
// vfs.newEncryptionKeyAsync();
|
||||
// return vfsSetter.exec() != -1;
|
||||
// }
|
||||
// bool VfsUtils::updateSDKWithKey(int argc, char *argv[]) {
|
||||
// QCoreApplication core(argc, argv);
|
||||
// AppController::initQtAppDetails(); // Set settings context.
|
||||
// QSettings settings;
|
||||
// return updateSDKWithKey(&settings);
|
||||
// }
|
||||
// bool VfsUtils::updateSDKWithKey() {
|
||||
// QSettings settings;
|
||||
// return updateSDKWithKey(&settings);
|
||||
// }
|
||||
|
||||
// bool VfsUtils::updateSDKWithKey(QSettings *settings) { // Update SDK if key exists. Return true if encrypted.
|
||||
// bool isEnabled = false;
|
||||
// // Check in factory if it is mandatory.
|
||||
// auto config = linphone::Factory::get()->createConfigWithFactory("", Paths::getFactoryConfigFilePath());
|
||||
// if (config->getBool(SettingsModel::UiSection, "vfs_encryption_enabled", false)) {
|
||||
// isEnabled = true;
|
||||
// }
|
||||
|
||||
// settings->beginGroup("keychain");
|
||||
// bool settingsValue = settings->value("enabled", false).toBool();
|
||||
// if (isEnabled && !settingsValue) settings->setValue("enabled", isEnabled);
|
||||
// else if (!isEnabled) isEnabled = settingsValue;
|
||||
// if (isEnabled) {
|
||||
// int argc = 1;
|
||||
// const char *argv = "dummy";
|
||||
// QCoreApplication vfsSetter(argc, (char **)&argv);
|
||||
// VfsUtils vfs;
|
||||
// QObject::connect(
|
||||
// &vfs, &VfsUtils::keyRead, &vfsSetter,
|
||||
// [&vfsSetter, &vfs](const QString &key, const QString &value) {
|
||||
// VfsUtils::updateSDKWithKey(value);
|
||||
// vfs.mVfsEncrypted = true;
|
||||
// vfsSetter.quit();
|
||||
// },
|
||||
// Qt::QueuedConnection);
|
||||
// QObject::connect(
|
||||
// &vfs, &VfsUtils::error, &vfsSetter, [&vfsSetter](const QString &errorText) { vfsSetter.quit(); },
|
||||
// Qt::QueuedConnection);
|
||||
// vfs.readKey(vfs.getApplicationVfsEncryptionKey());
|
||||
// vfsSetter.exec();
|
||||
|
||||
// if (!vfs.mVfsEncrypted) { // Doesn't have key.
|
||||
// return VfsUtils::newEncryptionKey(); // Return false on error.
|
||||
// }
|
||||
|
||||
// return vfs.mVfsEncrypted;
|
||||
// } else return false;
|
||||
// }
|
||||
|
||||
// void VfsUtils::updateSDKWithKey(const QString &key) {
|
||||
// std::string value = Utils::appStringToCoreString(key);
|
||||
// linphone::Factory::get()->setVfsEncryption(LINPHONE_VFS_ENCRYPTION_AES256GCM128_SHA256,
|
||||
// (const uint8_t *)value.c_str(), std::min(32, (int)value.length()));
|
||||
// }
|
||||
|
||||
// QString VfsUtils::getApplicationVfsEncryptionKey() const {
|
||||
// return QString(APPLICATION_ID) + "VfsEncryption";
|
||||
// }
|
||||
78
Linphone/model/tool/VfsUtils.hpp
Normal file
78
Linphone/model/tool/VfsUtils.hpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// /*
|
||||
// * Copyright (c) 2010-2022 Belledonne Communications SARL.
|
||||
// *
|
||||
// * This file is part of linphone-desktop
|
||||
// * (see https://www.linphone.org).
|
||||
// *
|
||||
// * This program is free software: you can redistribute it and/or modify
|
||||
// * it under the terms of the GNU 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 General Public License for more details.
|
||||
// *
|
||||
// * You should have received a copy of the GNU General Public License
|
||||
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// */
|
||||
|
||||
// #ifndef VFS_UTILS_H_
|
||||
// #define VFS_UTILS_H_
|
||||
|
||||
// #include "config.h"
|
||||
// #include <QObject>
|
||||
// // #ifdef QTKEYCHAIN_USE_BUILD_INTERFACE
|
||||
// // #include <keychain.h>
|
||||
// // #elif defined(QTKEYCHAIN_TARGET_NAME)
|
||||
// // #define KEYCHAIN_HEADER <qtkeychain/keychain.h>
|
||||
// // #include KEYCHAIN_HEADER
|
||||
// // #else
|
||||
// // #include <EQt6Keychain/keychain.h>
|
||||
// // #endif
|
||||
// #include <QSettings>
|
||||
// // #include <qtkeychain/keychain.h>
|
||||
|
||||
// // =============================================================================
|
||||
|
||||
// class VfsUtils : public QObject {
|
||||
// Q_OBJECT
|
||||
|
||||
// public:
|
||||
// VfsUtils(QObject *parent = Q_NULLPTR);
|
||||
|
||||
// Q_INVOKABLE void deleteKey(const QString &key); // Delete a key and send error() or keyDeleted()
|
||||
// Q_INVOKABLE void readKey(const QString &key); // Read a key, send error() or keyStored()
|
||||
// Q_INVOKABLE void writeKey(const QString &key, const QString &value); // Write a key and send error() or keyWritten()
|
||||
|
||||
// void newEncryptionKeyAsync(); // Generate a key, store it and update SDK. Wait for keyWritten() or error().
|
||||
|
||||
// static bool newEncryptionKey(); // Generate a key, store it and update SDK.
|
||||
// static bool updateSDKWithKey(int argc, char *argv[]); // Can be calle outside application.
|
||||
// static bool updateSDKWithKey(QSettings *settings); // Update SDK if key exists. Return true if encrypted.
|
||||
// static bool updateSDKWithKey(); // Need it to pass QSettings
|
||||
// static void updateSDKWithKey(const QString &key); // SDK->setVfsEncryption(key)
|
||||
|
||||
// QString getApplicationVfsEncryptionKey() const; // Get the key in store keys for VFS encryyption
|
||||
|
||||
// bool needToDeleteUserData() const;
|
||||
// void needToDeleteUserData(const bool &need);
|
||||
|
||||
// signals:
|
||||
// void keyDeleted(const QString &key);
|
||||
// void keyRead(const QString &key, const QString &value);
|
||||
// void keyWritten(const QString &key);
|
||||
|
||||
// void error(const QString &errorText);
|
||||
|
||||
// private:
|
||||
// // QKeychain::ReadPasswordJob mReadCredentialJob;
|
||||
// // QKeychain::WritePasswordJob mWriteCredentialJob;
|
||||
// // QKeychain::DeletePasswordJob mDeleteCredentialJob;
|
||||
|
||||
// bool mNeedToDeleteUserData = false;
|
||||
// bool mVfsEncrypted = false;
|
||||
// };
|
||||
|
||||
// #endif
|
||||
|
|
@ -3,6 +3,7 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
tool/EnumsToString.cpp
|
||||
tool/Utils.cpp
|
||||
tool/UriTools.cpp
|
||||
tool/QExifImageHeader.cpp
|
||||
|
||||
tool/LinphoneEnums.cpp
|
||||
tool/thread/SafeSharedPointer.hpp
|
||||
|
|
@ -10,8 +11,11 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
tool/thread/Thread.cpp
|
||||
tool/providers/AvatarProvider.cpp
|
||||
tool/providers/EmojiProvider.cpp
|
||||
tool/providers/ExternalImageProvider.cpp
|
||||
tool/providers/ImageProvider.cpp
|
||||
tool/providers/ScreenProvider.cpp
|
||||
tool/providers/ThumbnailProvider.cpp
|
||||
tool/providers/VideoFrameGrabber.cpp
|
||||
|
||||
tool/native/DesktopTools.hpp
|
||||
|
||||
|
|
@ -21,6 +25,8 @@ list(APPEND _LINPHONEAPP_SOURCES
|
|||
tool/file/FileDownloader.cpp
|
||||
tool/file/FileExtractor.cpp
|
||||
|
||||
tool/ui/DashRectangle.cpp
|
||||
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ public:
|
|||
|
||||
static constexpr char PathAssistantConfig[] = "/" EXECUTABLE_NAME "/assistant/";
|
||||
static constexpr char PathAvatars[] = "/avatars/";
|
||||
static constexpr char PathFiles[] = "/files/";
|
||||
static constexpr char PathCaptures[] = "/" EXECUTABLE_NAME "/captures/";
|
||||
static constexpr char PathCodecs[] = "/codecs/";
|
||||
static constexpr char PathData[] = "/" EXECUTABLE_NAME;
|
||||
|
|
|
|||
1775
Linphone/tool/QExifImageHeader.cpp
Normal file
1775
Linphone/tool/QExifImageHeader.cpp
Normal file
File diff suppressed because it is too large
Load diff
336
Linphone/tool/QExifImageHeader.hpp
Normal file
336
Linphone/tool/QExifImageHeader.hpp
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
|
||||
** All rights reserved.
|
||||
** Contact: Nokia Corporation (qt-info@nokia.com)
|
||||
**
|
||||
** This file is part of the Qt scene graph research project.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** No Commercial Usage
|
||||
** This file contains pre-release code and may not be distributed.
|
||||
** You may use this file in accordance with the terms and conditions
|
||||
** contained in the Technology Preview License Agreement accompanying
|
||||
** this package.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Nokia gives you certain additional
|
||||
** rights. These rights are described in the Nokia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** If you have questions regarding the use of this file, please contact
|
||||
** Nokia at qt-info@nokia.com.
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
// This file was copied from Qt Extended 4.5
|
||||
|
||||
#ifndef QEXIFIMAGEHEADER_H_
|
||||
#define QEXIFIMAGEHEADER_H_
|
||||
|
||||
#include <QPair>
|
||||
#include <QVector>
|
||||
#include <QSharedData>
|
||||
#include <QVariant>
|
||||
#include <QSysInfo>
|
||||
#include <QIODevice>
|
||||
|
||||
typedef QPair<quint32, quint32> QExifURational;
|
||||
typedef QPair<qint32, qint32> QExifSRational;
|
||||
|
||||
class QExifValuePrivate;
|
||||
|
||||
class QExifValue {
|
||||
public:
|
||||
enum Type {
|
||||
Byte = 1,
|
||||
Ascii = 2,
|
||||
Short = 3,
|
||||
Long = 4,
|
||||
Rational = 5,
|
||||
Undefined = 7,
|
||||
SignedLong = 9,
|
||||
SignedRational = 10
|
||||
};
|
||||
|
||||
enum TextEncoding {
|
||||
NoEncoding,
|
||||
AsciiEncoding,
|
||||
JisEncoding,
|
||||
UnicodeEncoding,
|
||||
UndefinedEncoding
|
||||
};
|
||||
|
||||
QExifValue ();
|
||||
QExifValue (quint8 value);
|
||||
QExifValue (const QVector<quint8> &value);
|
||||
QExifValue (const QString &value, TextEncoding encoding = NoEncoding);
|
||||
QExifValue (quint16 value);
|
||||
QExifValue (const QVector<quint16> &value);
|
||||
QExifValue (quint32 value);
|
||||
QExifValue (const QVector<quint32> &value);
|
||||
QExifValue (const QExifURational &value);
|
||||
QExifValue (const QVector<QExifURational> &value);
|
||||
QExifValue (const QByteArray &value);
|
||||
QExifValue (qint32 value);
|
||||
QExifValue (const QVector<qint32> &value);
|
||||
QExifValue (const QExifSRational &value);
|
||||
QExifValue (const QVector<QExifSRational> &value);
|
||||
QExifValue (const QDateTime &value);
|
||||
QExifValue (const QExifValue &other);
|
||||
QExifValue &operator= (const QExifValue &other);
|
||||
~QExifValue ();
|
||||
|
||||
bool operator== (const QExifValue &other) const;
|
||||
|
||||
bool isNull () const;
|
||||
|
||||
int type () const;
|
||||
int count () const;
|
||||
|
||||
TextEncoding encoding () const;
|
||||
|
||||
quint8 toByte () const;
|
||||
QVector<quint8> toByteVector () const;
|
||||
QString toString () const;
|
||||
quint16 toShort () const;
|
||||
QVector<quint16> toShortVector () const;
|
||||
quint32 toLong () const;
|
||||
QVector<quint32> toLongVector () const;
|
||||
QExifURational toRational () const;
|
||||
QVector<QExifURational> toRationalVector () const;
|
||||
QByteArray toByteArray () const;
|
||||
qint32 toSignedLong () const;
|
||||
QVector<qint32> toSignedLongVector () const;
|
||||
QExifSRational toSignedRational () const;
|
||||
QVector<QExifSRational> toSignedRationalVector () const;
|
||||
QDateTime toDateTime () const;
|
||||
|
||||
private:
|
||||
QExplicitlySharedDataPointer<QExifValuePrivate> d;
|
||||
};
|
||||
|
||||
struct ExifIfdHeader;
|
||||
|
||||
class QExifImageHeaderPrivate;
|
||||
|
||||
class QExifImageHeader {
|
||||
Q_DISABLE_COPY(QExifImageHeader)
|
||||
|
||||
public:
|
||||
enum ImageTag {
|
||||
ImageWidth = 0x0100,
|
||||
ImageLength = 0x0101,
|
||||
BitsPerSample = 0x0102,
|
||||
Compression = 0x0103,
|
||||
PhotometricInterpretation = 0x0106,
|
||||
Orientation = 0x0112,
|
||||
SamplesPerPixel = 0x0115,
|
||||
PlanarConfiguration = 0x011C,
|
||||
YCbCrSubSampling = 0x0212,
|
||||
XResolution = 0x011A,
|
||||
YResolution = 0x011B,
|
||||
ResolutionUnit = 0x0128,
|
||||
StripOffsets = 0x0111,
|
||||
RowsPerStrip = 0x0116,
|
||||
StripByteCounts = 0x0117,
|
||||
TransferFunction = 0x012D,
|
||||
WhitePoint = 0x013E,
|
||||
PrimaryChromaciticies = 0x013F,
|
||||
YCbCrCoefficients = 0x0211,
|
||||
ReferenceBlackWhite = 0x0214,
|
||||
DateTime = 0x0132,
|
||||
ImageDescription = 0x010E,
|
||||
Make = 0x010F,
|
||||
Model = 0x0110,
|
||||
Software = 0x0131,
|
||||
Artist = 0x013B,
|
||||
Copyright = 0x8298
|
||||
};
|
||||
|
||||
enum ExifExtendedTag {
|
||||
ExifVersion = 0x9000,
|
||||
FlashPixVersion = 0xA000,
|
||||
ColorSpace = 0xA001,
|
||||
ComponentsConfiguration = 0x9101,
|
||||
CompressedBitsPerPixel = 0x9102,
|
||||
PixelXDimension = 0xA002,
|
||||
PixelYDimension = 0xA003,
|
||||
MakerNote = 0x927C,
|
||||
UserComment = 0x9286,
|
||||
RelatedSoundFile = 0xA004,
|
||||
DateTimeOriginal = 0x9003,
|
||||
DateTimeDigitized = 0x9004,
|
||||
SubSecTime = 0x9290,
|
||||
SubSecTimeOriginal = 0x9291,
|
||||
SubSecTimeDigitized = 0x9292,
|
||||
ImageUniqueId = 0xA420,
|
||||
ExposureTime = 0x829A,
|
||||
FNumber = 0x829D,
|
||||
ExposureProgram = 0x8822,
|
||||
SpectralSensitivity = 0x8824,
|
||||
ISOSpeedRatings = 0x8827,
|
||||
Oecf = 0x8828,
|
||||
ShutterSpeedValue = 0x9201,
|
||||
ApertureValue = 0x9202,
|
||||
BrightnessValue = 0x9203,
|
||||
ExposureBiasValue = 0x9204,
|
||||
MaxApertureValue = 0x9205,
|
||||
SubjectDistance = 0x9206,
|
||||
MeteringMode = 0x9207,
|
||||
LightSource = 0x9208,
|
||||
Flash = 0x9209,
|
||||
FocalLength = 0x920A,
|
||||
SubjectArea = 0x9214,
|
||||
FlashEnergy = 0xA20B,
|
||||
SpatialFrequencyResponse = 0xA20C,
|
||||
FocalPlaneXResolution = 0xA20E,
|
||||
FocalPlaneYResolution = 0xA20F,
|
||||
FocalPlaneResolutionUnit = 0xA210,
|
||||
SubjectLocation = 0xA214,
|
||||
ExposureIndex = 0xA215,
|
||||
SensingMethod = 0xA217,
|
||||
FileSource = 0xA300,
|
||||
SceneType = 0xA301,
|
||||
CfaPattern = 0xA302,
|
||||
CustomRendered = 0xA401,
|
||||
ExposureMode = 0xA402,
|
||||
WhiteBalance = 0xA403,
|
||||
DigitalZoomRatio = 0xA404,
|
||||
FocalLengthIn35mmFilm = 0xA405,
|
||||
SceneCaptureType = 0xA406,
|
||||
GainControl = 0xA407,
|
||||
Contrast = 0xA408,
|
||||
Saturation = 0xA409,
|
||||
Sharpness = 0xA40A,
|
||||
DeviceSettingDescription = 0xA40B,
|
||||
SubjectDistanceRange = 0x40C
|
||||
};
|
||||
|
||||
enum GpsTag {
|
||||
GpsVersionId = 0x0000,
|
||||
GpsLatitudeRef = 0x0001,
|
||||
GpsLatitude = 0x0002,
|
||||
GpsLongitudeRef = 0x0003,
|
||||
GpsLongitude = 0x0004,
|
||||
GpsAltitudeRef = 0x0005,
|
||||
GpsAltitude = 0x0006,
|
||||
GpsTimeStamp = 0x0007,
|
||||
GpsSatellites = 0x0008,
|
||||
GpsStatus = 0x0009,
|
||||
GpsMeasureMode = 0x000A,
|
||||
GpsDop = 0x000B,
|
||||
GpsSpeedRef = 0x000C,
|
||||
GpsSpeed = 0x000D,
|
||||
GpsTrackRef = 0x000E,
|
||||
GpsTrack = 0x000F,
|
||||
GpsImageDirectionRef = 0x0010,
|
||||
GpsImageDirection = 0x0011,
|
||||
GpsMapDatum = 0x0012,
|
||||
GpsDestLatitudeRef = 0x0013,
|
||||
GpsDestLatitude = 0x0014,
|
||||
GpsDestLongitudeRef = 0x0015,
|
||||
GpsDestLongitude = 0x0016,
|
||||
GpsDestBearingRef = 0x0017,
|
||||
GpsDestBearing = 0x0018,
|
||||
GpsDestDistanceRef = 0x0019,
|
||||
GpsDestDistance = 0x001A,
|
||||
GpsProcessingMethod = 0x001B,
|
||||
GpsAreaInformation = 0x001C,
|
||||
GpsDateStamp = 0x001D,
|
||||
GpsDifferential = 0x001E
|
||||
};
|
||||
|
||||
QExifImageHeader ();
|
||||
explicit QExifImageHeader (const QString &fileName);
|
||||
~QExifImageHeader ();
|
||||
|
||||
bool loadFromJpeg (const QString &fileName);
|
||||
bool loadFromJpeg (QIODevice *device);
|
||||
bool saveToJpeg (const QString &fileName) const;
|
||||
bool saveToJpeg (QIODevice *device) const;
|
||||
|
||||
bool read (QIODevice *device);
|
||||
qint64 write (QIODevice *device) const;
|
||||
|
||||
qint64 size () const;
|
||||
|
||||
QSysInfo::Endian byteOrder () const;
|
||||
|
||||
void clear ();
|
||||
|
||||
QList<ImageTag> imageTags () const;
|
||||
QList<ExifExtendedTag> extendedTags () const;
|
||||
QList<GpsTag> gpsTags () const;
|
||||
|
||||
bool contains (ImageTag tag) const;
|
||||
bool contains (ExifExtendedTag tag) const;
|
||||
bool contains (GpsTag tag) const;
|
||||
|
||||
void remove (ImageTag tag);
|
||||
void remove (ExifExtendedTag tag);
|
||||
void remove (GpsTag tag);
|
||||
|
||||
QExifValue value (ImageTag tag) const;
|
||||
QExifValue value (ExifExtendedTag tag) const;
|
||||
QExifValue value (GpsTag tag) const;
|
||||
|
||||
void setValue (ImageTag tag, const QExifValue &value);
|
||||
void setValue (ExifExtendedTag tag, const QExifValue &value);
|
||||
void setValue (GpsTag tag, const QExifValue &value);
|
||||
|
||||
QImage thumbnail () const;
|
||||
void setThumbnail (const QImage &thumbnail);
|
||||
|
||||
private:
|
||||
enum PrivateTag {
|
||||
ExifIfdPointer = 0x8769,
|
||||
GpsInfoIfdPointer = 0x8825,
|
||||
InteroperabilityIfdPointer = 0xA005,
|
||||
JpegInterchangeFormat = 0x0201,
|
||||
JpegInterchangeFormatLength = 0x0202
|
||||
};
|
||||
|
||||
QByteArray extractExif (QIODevice *device) const;
|
||||
|
||||
QList<ExifIfdHeader> readIfdHeaders (QDataStream &stream) const;
|
||||
|
||||
QExifValue readIfdValue (QDataStream &stream, int startPos, const ExifIfdHeader &header) const;
|
||||
template<typename T>
|
||||
QMap<T, QExifValue> readIfdValues (QDataStream &stream, int startPos, const QList<ExifIfdHeader> &headers) const;
|
||||
template<typename T>
|
||||
QMap<T, QExifValue> readIfdValues (QDataStream &stream, int startPos, const QExifValue &pointer) const;
|
||||
|
||||
quint32 writeExifHeader (QDataStream &stream, quint16 tag, const QExifValue &value, quint32 offset) const;
|
||||
void writeExifValue (QDataStream &stream, const QExifValue &value) const;
|
||||
|
||||
template<typename T>
|
||||
quint32 writeExifHeaders (QDataStream &stream, const QMap<T, QExifValue> &values, quint32 offset) const;
|
||||
template<typename T>
|
||||
void writeExifValues (QDataStream &target, const QMap<T, QExifValue> &values) const;
|
||||
|
||||
quint32 sizeOf (const QExifValue &value) const;
|
||||
|
||||
template<typename T>
|
||||
quint32 calculateSize (const QMap<T, QExifValue> &values) const;
|
||||
|
||||
QExifImageHeaderPrivate *d;
|
||||
};
|
||||
|
||||
#endif // ifndef QEXIFIMAGEHEADER_H_
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
#include <QDesktopServices>
|
||||
#include <QHostAddress>
|
||||
#include <QImageReader>
|
||||
#include <QMimeDatabase>
|
||||
#include <QProcess>
|
||||
#include <QQmlComponent>
|
||||
#include <QQmlProperty>
|
||||
|
|
@ -59,6 +60,10 @@
|
|||
|
||||
DEFINE_ABSTRACT_OBJECT(Utils)
|
||||
|
||||
namespace {
|
||||
constexpr int SafeFilePathLimit = 100;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
char *Utils::rstrstr(const char *a, const char *b) {
|
||||
|
|
@ -347,6 +352,12 @@ QString Utils::formatTime(const QDateTime &date) {
|
|||
return date.time().toString("hh:mm");
|
||||
}
|
||||
|
||||
QString Utils::formatDuration(int durationMs) {
|
||||
QTime duration(0, 0);
|
||||
duration = duration.addMSecs(durationMs);
|
||||
return duration.hour() > 0 ? duration.toString("hh:mm:ss") : duration.toString("mm:ss");
|
||||
}
|
||||
|
||||
QString Utils::formatDateElapsedTime(const QDateTime &date) {
|
||||
// auto y = floor(seconds / 31104000);
|
||||
// if (y > 0) return QString::number(y) + " years";
|
||||
|
|
@ -1870,6 +1881,18 @@ QString Utils::encodeEmojiToQmlRichFormat(const QString &body) {
|
|||
return fmtBody;
|
||||
}
|
||||
|
||||
static bool codepointIsVisible(uint code) {
|
||||
return code > 0x00020;
|
||||
}
|
||||
|
||||
bool Utils::isOnlyEmojis(const QString &text) {
|
||||
if (text.isEmpty()) return false;
|
||||
QVector<uint> utf32_string = text.toUcs4();
|
||||
for (auto &code : utf32_string)
|
||||
if (codepointIsVisible(code) && !Utils::codepointIsEmoji(code)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Utils::getFilename(QUrl url) {
|
||||
return url.fileName();
|
||||
}
|
||||
|
|
@ -1906,3 +1929,79 @@ QString Utils::toTimeString(QDateTime date, const QString &format) {
|
|||
// Issue : date.toString() will not print the good time in timezones. Get it from date and add ourself the offset.
|
||||
return getOffsettedUTC(date).toString(format);
|
||||
}
|
||||
QString Utils::getSafeFilePath(const QString &filePath, bool *soFarSoGood) {
|
||||
if (soFarSoGood) *soFarSoGood = true;
|
||||
|
||||
QFileInfo info(filePath);
|
||||
if (!info.exists()) return filePath;
|
||||
|
||||
const QString prefix = QStringLiteral("%1/%2").arg(info.absolutePath()).arg(info.baseName());
|
||||
const QString ext = info.completeSuffix();
|
||||
|
||||
for (int i = 1; i < SafeFilePathLimit; ++i) {
|
||||
QString safePath = QStringLiteral("%1 (%3).%4").arg(prefix).arg(i).arg(ext);
|
||||
if (!QFileInfo::exists(safePath)) return safePath;
|
||||
}
|
||||
|
||||
if (soFarSoGood) *soFarSoGood = false;
|
||||
|
||||
return QString("");
|
||||
}
|
||||
|
||||
bool Utils::isVideo(const QString &path) {
|
||||
if (path.isEmpty()) return false;
|
||||
return QMimeDatabase().mimeTypeForFile(path).name().contains("video/");
|
||||
}
|
||||
|
||||
bool Utils::isPdf(const QString &path) {
|
||||
if (path.isEmpty()) return false;
|
||||
return QMimeDatabase().mimeTypeForFile(path).name().contains("application/pdf");
|
||||
}
|
||||
|
||||
bool Utils::isText(const QString &path) {
|
||||
if (path.isEmpty()) return false;
|
||||
return QMimeDatabase().mimeTypeForFile(path).name().contains("text");
|
||||
}
|
||||
|
||||
bool Utils::isImage(const QString &path) {
|
||||
if (path.isEmpty()) return false;
|
||||
QFileInfo info(path);
|
||||
if (!info.exists() || SettingsModel::getInstance()->getVfsEncrypted()) {
|
||||
return QMimeDatabase().mimeTypeForFile(path).name().contains("image/");
|
||||
} else {
|
||||
if (!QMimeDatabase().mimeTypeForFile(info).name().contains("image/")) return false;
|
||||
QImageReader reader(path);
|
||||
return reader.canRead() && reader.imageCount() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool Utils::isAnimatedImage(const QString &path) {
|
||||
if (path.isEmpty()) return false;
|
||||
QFileInfo info(path);
|
||||
if (!info.exists() || !QMimeDatabase().mimeTypeForFile(info).name().contains("image/")) return false;
|
||||
QImageReader reader(path);
|
||||
return reader.canRead() && reader.supportsAnimation() && reader.imageCount() > 1;
|
||||
}
|
||||
|
||||
bool Utils::canHaveThumbnail(const QString &path) {
|
||||
if (path.isEmpty()) return false;
|
||||
return isImage(path) || isAnimatedImage(path) /*|| isPdf(path)*/ || isVideo(path);
|
||||
}
|
||||
|
||||
QImage Utils::getImage(const QString &pUri) {
|
||||
QImage image(pUri);
|
||||
QImageReader reader(pUri);
|
||||
reader.setAutoTransform(true);
|
||||
if (image.isNull()) { // Try to determine format from headers instead of using suffix
|
||||
reader.setDecideFormatFromContent(true);
|
||||
}
|
||||
return reader.read();
|
||||
}
|
||||
|
||||
void Utils::setGlobalCursor(Qt::CursorShape cursor) {
|
||||
App::getInstance()->setOverrideCursor(QCursor(cursor));
|
||||
}
|
||||
|
||||
void Utils::restoreGlobalCursor() {
|
||||
App::getInstance()->restoreOverrideCursor();
|
||||
}
|
||||
|
|
@ -95,6 +95,7 @@ public:
|
|||
QString format = ""); // Return the date formated
|
||||
Q_INVOKABLE static QString formatDateElapsedTime(const QDateTime &date);
|
||||
Q_INVOKABLE static QString formatTime(const QDateTime &date); // Return the time formated
|
||||
Q_INVOKABLE static QString formatDuration(int durationMs); // Return the duration formated
|
||||
Q_INVOKABLE static QStringList generateSecurityLettersArray(int arraySize, int correctIndex, QString correctCode);
|
||||
Q_INVOKABLE static int getRandomIndex(int size);
|
||||
Q_INVOKABLE static bool copyToClipboard(const QString &text);
|
||||
|
|
@ -154,9 +155,20 @@ public:
|
|||
Q_INVOKABLE static QString encodeTextToQmlRichFormat(const QString &text,
|
||||
const QVariantMap &options = QVariantMap());
|
||||
Q_INVOKABLE static QString encodeEmojiToQmlRichFormat(const QString &body);
|
||||
Q_INVOKABLE static bool isOnlyEmojis(const QString &text);
|
||||
|
||||
Q_INVOKABLE static QString getFilename(QUrl url);
|
||||
static bool codepointIsEmoji(uint code);
|
||||
Q_INVOKABLE static bool isVideo(const QString &path);
|
||||
static QString getSafeFilePath(const QString &filePath, bool *soFarSoGood);
|
||||
Q_INVOKABLE static bool isAnimatedImage(const QString &path);
|
||||
Q_INVOKABLE static bool canHaveThumbnail(const QString &path);
|
||||
Q_INVOKABLE static bool isImage(const QString &path);
|
||||
Q_INVOKABLE static bool isPdf(const QString &path);
|
||||
Q_INVOKABLE static bool isText(const QString &path);
|
||||
Q_INVOKABLE static QImage getImage(const QString &pUri);
|
||||
Q_INVOKABLE static void setGlobalCursor(Qt::CursorShape cursor);
|
||||
Q_INVOKABLE static void restoreGlobalCursor();
|
||||
|
||||
Q_INVOKABLE static QString toDateTimeString(QDateTime date, const QString &format = "yyyy/MM/dd hh:mm:ss");
|
||||
static QDateTime getOffsettedUTC(const QDateTime &date);
|
||||
|
|
|
|||
48
Linphone/tool/providers/ExternalImageProvider.cpp
Normal file
48
Linphone/tool/providers/ExternalImageProvider.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "core/path/Paths.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
|
||||
#include "ExternalImageProvider.hpp"
|
||||
|
||||
#include <QImageReader>
|
||||
|
||||
// =============================================================================
|
||||
|
||||
const QString ExternalImageProvider::ProviderId = "external";
|
||||
|
||||
ExternalImageProvider::ExternalImageProvider()
|
||||
: QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading) {
|
||||
}
|
||||
|
||||
QImage ExternalImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) {
|
||||
QImage image(Utils::getImage(QUrl::fromPercentEncoding(id.toUtf8())));
|
||||
double requestedFactor = 1.0;
|
||||
double factor = image.width() / (double)image.height();
|
||||
if (requestedSize.isValid()) requestedFactor = requestedSize.width() / (double)requestedSize.height();
|
||||
if (factor < 0.2) { // too height
|
||||
image = image.copy(0, 0, image.width(), image.width() / requestedFactor);
|
||||
} else if (factor > 5) { // too large
|
||||
image = image.copy(0, 0, image.height() * requestedFactor, image.height());
|
||||
}
|
||||
*size = image.size();
|
||||
return image;
|
||||
}
|
||||
37
Linphone/tool/providers/ExternalImageProvider.hpp
Normal file
37
Linphone/tool/providers/ExternalImageProvider.hpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EXTERNAL_IMAGE_PROVIDER_H_
|
||||
#define EXTERNAL_IMAGE_PROVIDER_H_
|
||||
|
||||
#include <QQuickImageProvider>
|
||||
|
||||
// =============================================================================
|
||||
|
||||
class ExternalImageProvider : public QQuickImageProvider {
|
||||
public:
|
||||
ExternalImageProvider();
|
||||
|
||||
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
|
||||
|
||||
static const QString ProviderId;
|
||||
};
|
||||
|
||||
#endif // EXTERNAL_IMAGE_PROVIDER_H_
|
||||
130
Linphone/tool/providers/ThumbnailProvider.cpp
Normal file
130
Linphone/tool/providers/ThumbnailProvider.cpp
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ThumbnailProvider.hpp"
|
||||
#include "model/setting/SettingsModel.hpp"
|
||||
#include "tool/QExifImageHeader.hpp"
|
||||
#include "tool/Utils.hpp"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QImageReader>
|
||||
#include <QPainter>
|
||||
#include <QSvgRenderer>
|
||||
|
||||
DEFINE_ABSTRACT_OBJECT(ThumbnailAsyncImageResponse)
|
||||
|
||||
// =============================================================================
|
||||
|
||||
const QString ThumbnailProvider::ProviderId = "thumbnail";
|
||||
|
||||
ThumbnailAsyncImageResponse::ThumbnailAsyncImageResponse(const QString &id, const QSize &requestedSize) {
|
||||
mPath = id;
|
||||
connect(&mListener, &VideoFrameGrabberListener::imageGrabbed, this, &ThumbnailAsyncImageResponse::imageGrabbed);
|
||||
|
||||
if (QFileInfo(mPath).isFile()) {
|
||||
bool removeExportedFile = SettingsModel::getInstance()->getVfsEncrypted();
|
||||
if (removeExportedFile) {
|
||||
std::shared_ptr<linphone::Content> content =
|
||||
linphone::Factory::get()->createContentFromFile(Utils::appStringToCoreString(mPath));
|
||||
mPath = Utils::coreStringToAppString(content->exportPlainFile());
|
||||
}
|
||||
QImage originalImage(mPath);
|
||||
if (originalImage.isNull()) { // Try to determine format from headers
|
||||
QImageReader reader(mPath);
|
||||
reader.setDecideFormatFromContent(true);
|
||||
QByteArray format = reader.format();
|
||||
if (!format.isEmpty()) {
|
||||
originalImage = QImage(mPath, format);
|
||||
} else if (Utils::isVideo(mPath)) {
|
||||
VideoFrameGrabber *grabber = new VideoFrameGrabber(removeExportedFile);
|
||||
removeExportedFile = false;
|
||||
connect(grabber, &VideoFrameGrabber::grabFinished, &mListener,
|
||||
&VideoFrameGrabberListener::imageGrabbed);
|
||||
grabber->requestFrame(mPath);
|
||||
}
|
||||
}
|
||||
if (removeExportedFile) QFile(mPath).remove();
|
||||
if (!originalImage.isNull()) {
|
||||
emit imageGrabbed(originalImage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QImage ThumbnailAsyncImageResponse::createThumbnail(const QString &path, QImage originalImage) {
|
||||
QImage thumbnail;
|
||||
if (!originalImage.isNull()) {
|
||||
int rotation = 0;
|
||||
QExifImageHeader exifImageHeader;
|
||||
if (exifImageHeader.loadFromJpeg(path))
|
||||
rotation = int(exifImageHeader.value(QExifImageHeader::ImageTag::Orientation).toShort());
|
||||
double factor = originalImage.width() / (double)originalImage.height();
|
||||
Qt::AspectRatioMode aspectRatio = Qt::KeepAspectRatio;
|
||||
if (factor < 0.2 || factor > 5) aspectRatio = Qt::KeepAspectRatioByExpanding;
|
||||
QImageReader reader(path);
|
||||
if (reader.format() == "svg") {
|
||||
QSvgRenderer svgRenderer(path);
|
||||
if (svgRenderer.isValid()) {
|
||||
thumbnail = QImage(Constants::ThumbnailImageFileWidth, Constants::ThumbnailImageFileHeight,
|
||||
originalImage.format());
|
||||
thumbnail.fill(QColor(Qt::transparent));
|
||||
QPainter painter(&thumbnail);
|
||||
svgRenderer.setAspectRatioMode(aspectRatio);
|
||||
svgRenderer.render(&painter);
|
||||
}
|
||||
}
|
||||
if (thumbnail.isNull()) {
|
||||
QImage image(originalImage.size(), originalImage.format());
|
||||
// Fill with color to replace transparency with white color instead of black (default).
|
||||
image.fill(QColor(Qt::white).rgb());
|
||||
QPainter painter(&image);
|
||||
painter.drawImage(0, 0, originalImage);
|
||||
//--------------------
|
||||
thumbnail = image.scaled(Constants::ThumbnailImageFileWidth, Constants::ThumbnailImageFileHeight,
|
||||
aspectRatio, Qt::SmoothTransformation);
|
||||
if (aspectRatio == Qt::KeepAspectRatioByExpanding) // Cut
|
||||
thumbnail =
|
||||
thumbnail.copy(0, 0, Constants::ThumbnailImageFileWidth, Constants::ThumbnailImageFileHeight);
|
||||
}
|
||||
|
||||
if (rotation != 0) {
|
||||
QTransform transform;
|
||||
if (rotation == 3 || rotation == 4) transform.rotate(180);
|
||||
else if (rotation == 5 || rotation == 6) transform.rotate(90);
|
||||
else if (rotation == 7 || rotation == 8) transform.rotate(-90);
|
||||
thumbnail = thumbnail.transformed(transform);
|
||||
if (rotation == 2 || rotation == 4 || rotation == 5 || rotation == 7)
|
||||
thumbnail = thumbnail.flipped(Qt::Horizontal);
|
||||
}
|
||||
}
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
void ThumbnailAsyncImageResponse::imageGrabbed(QImage image) {
|
||||
mImage = createThumbnail(mPath, image);
|
||||
emit finished();
|
||||
}
|
||||
|
||||
QQuickTextureFactory *ThumbnailAsyncImageResponse::textureFactory() const {
|
||||
return QQuickTextureFactory::textureFactoryForImage(mImage);
|
||||
}
|
||||
QQuickImageResponse *ThumbnailProvider::requestImageResponse(const QString &id, const QSize &requestedSize) {
|
||||
ThumbnailAsyncImageResponse *response = new ThumbnailAsyncImageResponse(id, requestedSize);
|
||||
return response;
|
||||
}
|
||||
59
Linphone/tool/providers/ThumbnailProvider.hpp
Normal file
59
Linphone/tool/providers/ThumbnailProvider.hpp
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef THUMBNAIL_PROVIDER_H_
|
||||
#define THUMBNAIL_PROVIDER_H_
|
||||
|
||||
#include <QQuickAsyncImageProvider>
|
||||
|
||||
#include "VideoFrameGrabber.hpp"
|
||||
#include "tool/AbstractObject.hpp"
|
||||
|
||||
// Thumbnails are created asynchronously with QQuickAsyncImageProvider and not QQuickImageProvider.
|
||||
// This ensure to have async objects like QMediaPlayer and QAbstractVideoSurface while keeping them in the main thread
|
||||
// (mandatory for VideoSurface). If not, there seems to have some deadlocks in Qt library when GUI objects are deleted
|
||||
// while still playing media.
|
||||
// =============================================================================
|
||||
class ThumbnailAsyncImageResponse : public QQuickImageResponse, public AbstractObject {
|
||||
public:
|
||||
ThumbnailAsyncImageResponse(const QString &id, const QSize &requestedSize);
|
||||
|
||||
QQuickTextureFactory *textureFactory() const override; // Convert QImage into texture. If Image is null, then
|
||||
// sourceSize will be egal to 0. So there will be no errors.
|
||||
|
||||
void imageGrabbed(QImage image);
|
||||
QImage createThumbnail(const QString &path, QImage originalImage);
|
||||
|
||||
QImage mImage;
|
||||
QString mPath;
|
||||
VideoFrameGrabberListener mListener;
|
||||
|
||||
private:
|
||||
DECLARE_ABSTRACT_OBJECT
|
||||
};
|
||||
|
||||
class ThumbnailProvider : public QQuickAsyncImageProvider {
|
||||
public:
|
||||
virtual QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
|
||||
|
||||
static const QString ProviderId;
|
||||
};
|
||||
|
||||
#endif // THUMBNAIL_PROVIDER_H_
|
||||
118
Linphone/tool/providers/VideoFrameGrabber.cpp
Normal file
118
Linphone/tool/providers/VideoFrameGrabber.cpp
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "VideoFrameGrabber.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QVideoFrame>
|
||||
|
||||
VideoFrameGrabberListener::VideoFrameGrabberListener() {
|
||||
}
|
||||
|
||||
VideoFrameGrabber::VideoFrameGrabber(bool deleteFile, QObject *parent) : QVideoSink(parent) {
|
||||
mDeleteFile = deleteFile;
|
||||
mPlayer = new QMediaPlayer();
|
||||
mVideoSink = new QVideoSink();
|
||||
connect(
|
||||
mPlayer, &QMediaPlayer::errorOccurred, this,
|
||||
[this](QMediaPlayer::Error error, const QString &errorString) { end(); }, Qt::DirectConnection);
|
||||
QObject::connect(
|
||||
mPlayer, &QMediaPlayer::mediaStatusChanged, this,
|
||||
[this](QMediaPlayer::MediaStatus status) mutable {
|
||||
switch (status) {
|
||||
case QMediaPlayer::LoadedMedia:
|
||||
if (!mLoadedMedia) {
|
||||
mLoadedMedia = true;
|
||||
if (mPlayer->hasVideo()) {
|
||||
mPlayer->setPosition(mPlayer->duration() / 2);
|
||||
mPlayer->play();
|
||||
} else {
|
||||
end();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case QMediaPlayer::InvalidMedia:
|
||||
case QMediaPlayer::EndOfMedia:
|
||||
case QMediaPlayer::NoMedia:
|
||||
end();
|
||||
break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
},
|
||||
Qt::DirectConnection);
|
||||
|
||||
connect(mVideoSink, &QVideoSink::videoFrameChanged, this, [this](const QVideoFrame &frame) {
|
||||
if (isFormatSupported(frame)) mResult = frame.toImage().copy();
|
||||
});
|
||||
|
||||
mPlayer->setVideoSink(mVideoSink);
|
||||
mPlayer->setVideoOutput(this);
|
||||
}
|
||||
|
||||
VideoFrameGrabber::~VideoFrameGrabber() {
|
||||
if (mDeleteFile) QFile(mPath).remove();
|
||||
}
|
||||
|
||||
void VideoFrameGrabber::requestFrame(const QString &path) {
|
||||
mLoadedMedia = false;
|
||||
mPath = path;
|
||||
// mPlayer->set(QUrl::fromLocalFile(mPath));
|
||||
}
|
||||
|
||||
void VideoFrameGrabber::end() {
|
||||
if (mPlayer->mediaStatus() != QMediaPlayer::NoMedia) {
|
||||
// mPlayer->setMedia(QUrl());
|
||||
} else if (!mResultSent) { // Avoid sending multiple times before destroying the object
|
||||
mResultSent = true;
|
||||
emit grabFinished(mResult);
|
||||
deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
QList<QVideoFrameFormat::PixelFormat> VideoFrameGrabber::supportedPixelFormats() const {
|
||||
return QList<QVideoFrameFormat::PixelFormat>()
|
||||
<< QVideoFrameFormat::Format_YUV420P << QVideoFrameFormat::Format_YV12 << QVideoFrameFormat::Format_UYVY
|
||||
<< QVideoFrameFormat::Format_YUYV << QVideoFrameFormat::Format_NV12 << QVideoFrameFormat::Format_NV21
|
||||
<< QVideoFrameFormat::Format_IMC1 << QVideoFrameFormat::Format_IMC2 << QVideoFrameFormat::Format_IMC3
|
||||
<< QVideoFrameFormat::Format_IMC4 << QVideoFrameFormat::Format_Y8 << QVideoFrameFormat::Format_Y16
|
||||
<< QVideoFrameFormat::Format_Jpeg << QVideoFrameFormat::Format_ABGR8888 << QVideoFrameFormat::Format_ARGB8888
|
||||
<< QVideoFrameFormat::Format_ARGB8888_Premultiplied << QVideoFrameFormat::Format_AYUV
|
||||
<< QVideoFrameFormat::Format_AYUV_Premultiplied << QVideoFrameFormat::Format_BGRA8888
|
||||
<< QVideoFrameFormat::Format_BGRA8888_Premultiplied << QVideoFrameFormat::Format_BGRA8888_Premultiplied
|
||||
<< QVideoFrameFormat::Format_BGRX8888;
|
||||
}
|
||||
|
||||
bool VideoFrameGrabber::isFormatSupported(const QVideoFrame &frame) const {
|
||||
const QImage::Format imageFormat = QVideoFrameFormat::imageFormatFromPixelFormat(frame.pixelFormat());
|
||||
const QSize size = frame.size();
|
||||
|
||||
return imageFormat != QImage::Format_Invalid && !size.isEmpty() &&
|
||||
frame.handleType() == QVideoFrame::HandleType::NoHandle;
|
||||
}
|
||||
|
||||
bool VideoFrameGrabber::start(const QVideoFrameFormat::PixelFormat &format) {
|
||||
return true;
|
||||
// return QVideoSink::start(format);
|
||||
}
|
||||
|
||||
void VideoFrameGrabber::stop() {
|
||||
// QVideoSink::stop();
|
||||
}
|
||||
68
Linphone/tool/providers/VideoFrameGrabber.hpp
Normal file
68
Linphone/tool/providers/VideoFrameGrabber.hpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef VIDEO_FRAME_GRABBER_H
|
||||
#define VIDEO_FRAME_GRABBER_H
|
||||
|
||||
#include <QMediaPlayer>
|
||||
#include <QVideoFrameFormat>
|
||||
#include <QVideoSink>
|
||||
|
||||
// Call VideoFrameGrabber::requestFrame() and wait for imageGrabbed() to get the image.
|
||||
// You will need to link your listener with connect(grabber, &VideoFrameGrabber::grabFinished, listener,
|
||||
// &VideoFrameGrabberListener::imageGrabbed);
|
||||
class VideoFrameGrabberListener : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
VideoFrameGrabberListener();
|
||||
signals:
|
||||
void imageGrabbed(QImage image);
|
||||
};
|
||||
|
||||
class VideoFrameGrabber : public QVideoSink {
|
||||
Q_OBJECT
|
||||
public:
|
||||
VideoFrameGrabber(bool deleteFile = false, QObject *parent = 0);
|
||||
~VideoFrameGrabber();
|
||||
|
||||
void requestFrame(const QString &path); // Function to call.
|
||||
|
||||
void end();
|
||||
|
||||
QList<QVideoFrameFormat::PixelFormat> supportedPixelFormats() const;
|
||||
bool isFormatSupported(const QVideoFrame &frame) const;
|
||||
|
||||
bool start(const QVideoFrameFormat::PixelFormat &format);
|
||||
void stop();
|
||||
|
||||
QMediaPlayer *mPlayer = nullptr;
|
||||
QVideoSink *mVideoSink = nullptr;
|
||||
bool mLoadedMedia = false;
|
||||
bool mResultSent = false;
|
||||
bool mDeleteFile = false;
|
||||
QString mPath;
|
||||
QImage mResult;
|
||||
|
||||
signals:
|
||||
void frameAvailable(QImage frame);
|
||||
void grabFinished(QImage frame);
|
||||
};
|
||||
|
||||
#endif
|
||||
57
Linphone/tool/ui/DashRectangle.cpp
Normal file
57
Linphone/tool/ui/DashRectangle.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DashRectangle.hpp"
|
||||
#include "core/App.hpp"
|
||||
|
||||
DashRectangle::DashRectangle(QQuickItem *parent) : QQuickPaintedItem(parent) {
|
||||
connect(this, &DashRectangle::radiusChanged, this, [this] { update(); });
|
||||
connect(this, &DashRectangle::colorChanged, this, [this] { update(); });
|
||||
}
|
||||
|
||||
void DashRectangle::paint(QPainter *painter) {
|
||||
QPen pen(Qt::DotLine);
|
||||
pen.setColor(mColor);
|
||||
pen.setWidthF(4 * App::getInstance()->getScreenRatio());
|
||||
painter->setPen(pen);
|
||||
painter->drawRoundedRect(x(), y(), width(), height(), mRadius, mRadius);
|
||||
}
|
||||
|
||||
float DashRectangle::getRadius() const {
|
||||
return mRadius;
|
||||
}
|
||||
|
||||
void DashRectangle::setRadius(float radius) {
|
||||
if (mRadius != radius) {
|
||||
mRadius = radius;
|
||||
emit radiusChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QColor DashRectangle::getColor() const {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
void DashRectangle::setColor(QColor Color) {
|
||||
if (mColor != Color) {
|
||||
mColor = Color;
|
||||
emit colorChanged();
|
||||
}
|
||||
}
|
||||
52
Linphone/tool/ui/DashRectangle.hpp
Normal file
52
Linphone/tool/ui/DashRectangle.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2024 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DASHRECTANGLE_H
|
||||
#define DASHRECTANGLE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QPainter>
|
||||
#include <QQuickPaintedItem>
|
||||
|
||||
class DashRectangle : public QQuickPaintedItem {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(float radius READ getRadius WRITE setRadius NOTIFY radiusChanged)
|
||||
Q_PROPERTY(QColor color READ getColor WRITE setColor NOTIFY colorChanged)
|
||||
public:
|
||||
explicit DashRectangle(QQuickItem *parent = nullptr);
|
||||
|
||||
virtual void paint(QPainter *painter);
|
||||
|
||||
float getRadius() const;
|
||||
void setRadius(float radius);
|
||||
|
||||
QColor getColor() const;
|
||||
void setColor(QColor color);
|
||||
|
||||
signals:
|
||||
void radiusChanged();
|
||||
void colorChanged();
|
||||
|
||||
private:
|
||||
float mRadius = 0;
|
||||
QColor mColor;
|
||||
};
|
||||
|
||||
#endif // DASHRECTANGLE_H
|
||||
|
|
@ -35,6 +35,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
|
|||
view/Control/Container/Call/CallLayout.qml
|
||||
view/Control/Container/Call/CallGridLayout.qml
|
||||
view/Control/Container/Call/Mosaic.qml
|
||||
view/Control/Container/Chat/ChatFilesGridLayout.qml
|
||||
view/Control/Container/Contact/ContactLayout.qml
|
||||
view/Control/Container/Contact/PresenceNoteLayout.qml
|
||||
view/Control/Container/Main/MainRightPanel.qml
|
||||
|
|
@ -46,6 +47,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
|
|||
view/Control/Display/TemporaryText.qml
|
||||
view/Control/Display/ProgressBar.qml
|
||||
view/Control/Display/RoundedPane.qml
|
||||
view/Control/Display/RoundProgressBar.qml
|
||||
view/Control/Display/Sticker.qml
|
||||
view/Control/Display/Text.qml
|
||||
view/Control/Display/ToolTip.qml
|
||||
|
|
@ -53,11 +55,15 @@ list(APPEND _LINPHONEAPP_QML_FILES
|
|||
view/Control/Display/Call/CallHistoryListView.qml
|
||||
view/Control/Display/Call/CallStatistics.qml
|
||||
view/Control/Display/Chat/Emoji/EmojiPicker.qml
|
||||
view/Control/Display/Chat/ChatMessageContent.qml
|
||||
view/Control/Display/Chat/ChatAudioContent.qml
|
||||
view/Control/Display/Chat/ChatTextContent.qml
|
||||
view/Control/Display/Chat/ChatListView.qml
|
||||
view/Control/Display/Chat/ChatMessage.qml
|
||||
view/Control/Display/Chat/ChatMessageInvitationBubble.qml
|
||||
view/Control/Display/Chat/ChatMessagesListView.qml
|
||||
view/Control/Display/Chat/Event.qml
|
||||
view/Control/Display/Chat/FileView.qml
|
||||
view/Control/Display/Contact/Avatar.qml
|
||||
view/Control/Display/Contact/Contact.qml
|
||||
view/Control/Display/Contact/Presence.qml
|
||||
|
|
@ -78,6 +84,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
|
|||
view/Control/Form/Settings/MultimediaSettings.qml
|
||||
view/Control/Form/Settings/ScreencastSettings.qml
|
||||
|
||||
view/Control/Input/Chat/ChatDroppableTextArea.qml
|
||||
view/Control/Input/Calendar.qml
|
||||
view/Control/Input/DecoratedTextField.qml
|
||||
view/Control/Input/DigitInput.qml
|
||||
|
|
@ -167,6 +174,7 @@ list(APPEND _LINPHONEAPP_QML_SINGLETONS
|
|||
view/Style/AppIcons.qml
|
||||
view/Style/buttonStyle.js
|
||||
view/Style/DefaultStyle.qml
|
||||
view/Style/FileViewStyle.qml
|
||||
view/Style/Typography.qml
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ Button {
|
|||
id: mainItem
|
||||
textSize: Typography.p1s.pixelSize
|
||||
textWeight: Typography.p1s.weight
|
||||
topPadding: Math.round(16 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(16 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(16 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(16 * DefaultStyle.dp)
|
||||
icon.width: Math.round(24 * DefaultStyle.dp)
|
||||
icon.height: Math.round(24 * DefaultStyle.dp)
|
||||
radius: Math.round(40 * DefaultStyle.dp)
|
||||
width: Math.round(24 * DefaultStyle.dp)
|
||||
height: Math.round(24 * DefaultStyle.dp)
|
||||
padding: Math.round(16 * DefaultStyle.dp)
|
||||
// bottomPadding: Math.round(16 * DefaultStyle.dp)
|
||||
// leftPadding: Math.round(16 * DefaultStyle.dp)
|
||||
// rightPadding: Math.round(16 * DefaultStyle.dp)
|
||||
icon.width: width
|
||||
icon.height: width
|
||||
radius: width * 2
|
||||
// width: Math.round(24 * DefaultStyle.dp)
|
||||
height: width
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,6 @@ Mosaic {
|
|||
qmlName: "G"
|
||||
Component.onCompleted: console.log("Loaded : " +allDevices + " = " +allDevices.count)
|
||||
}
|
||||
property AccountProxy accounts: AccountProxy {
|
||||
id: accountProxy
|
||||
sourceModel: AppCpp.accounts
|
||||
}
|
||||
model: grid.call && grid.call.core.isConference ? participantDevices: [0,1]
|
||||
delegate: Item{
|
||||
id: avatarCell
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ import QtQuick.Controls.Basic
|
|||
import QtQuick.Layouts
|
||||
import QtQml.Models
|
||||
|
||||
|
||||
// =============================================================================
|
||||
ColumnLayout{
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
property alias delegateModel: grid.model
|
||||
property alias cellHeight: grid.cellHeight
|
||||
|
|
|
|||
61
Linphone/view/Control/Container/Chat/ChatFilesGridLayout.qml
Normal file
61
Linphone/view/Control/Container/Chat/ChatFilesGridLayout.qml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQml.Models
|
||||
|
||||
import Linphone
|
||||
import UtilsCpp
|
||||
|
||||
// =============================================================================
|
||||
GridLayout {
|
||||
id: mainItem
|
||||
property ChatMessageGui chatMessageGui: null
|
||||
property bool isHoveringFile: false
|
||||
property int itemCount: delModel.count
|
||||
property int itemWidth: Math.round(95 * DefaultStyle.dp)
|
||||
// cellWidth:
|
||||
// cellHeight: Math.round(105 * DefaultStyle.dp)
|
||||
property real maxWidth: 3 * 105 * DefaultStyle.dp
|
||||
columns: optimalColumns
|
||||
|
||||
|
||||
property int optimalColumns: {
|
||||
let maxCols = Math.floor(maxWidth / itemWidth);
|
||||
let bestCols = 1;
|
||||
let minRows = Number.MAX_VALUE;
|
||||
let minEmptySlots = Number.MAX_VALUE;
|
||||
|
||||
for (let cols = maxCols; cols >= 1; cols--) {
|
||||
let rows = Math.ceil(itemCount / cols);
|
||||
let emptySlots = cols * rows - itemCount;
|
||||
|
||||
if (
|
||||
rows < minRows ||
|
||||
(rows === minRows && emptySlots < minEmptySlots)
|
||||
) {
|
||||
bestCols = cols;
|
||||
minRows = rows;
|
||||
minEmptySlots = emptySlots;
|
||||
}
|
||||
}
|
||||
|
||||
return bestCols;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: delModel
|
||||
model: ChatMessageContentProxy {
|
||||
id: contentProxy
|
||||
filterType: ChatMessageContentProxy.FilterContentType.File
|
||||
chatMessageGui: mainItem.chatMessageGui
|
||||
}
|
||||
|
||||
delegate: FileView {
|
||||
id: avatarCell
|
||||
contentGui: modelData
|
||||
visible: modelData
|
||||
height: mainItem.itemWidth
|
||||
width: mainItem.itemWidth
|
||||
onIsHoveringChanged: mainItem.isHoveringFile = isHovering
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Linphone/view/Control/Display/Chat/ChatAudioContent.qml
Normal file
127
Linphone/view/Control/Display/Chat/ChatAudioContent.qml
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Linphone
|
||||
|
||||
import UtilsCpp
|
||||
|
||||
// =============================================================================
|
||||
|
||||
Loader{
|
||||
id: mainItem
|
||||
property ChatMessageContentGui chatMessageContentGui
|
||||
property int availableWidth : parent.width
|
||||
property int width: active ? Math.max(availableWidth - ChatAudioMessageStyle.emptySpace, ChatAudioMessageStyle.minWidth) : 0
|
||||
property int fitHeight: active ? 60 : 0
|
||||
|
||||
property font customFont : SettingsModel.textMessageFont
|
||||
property bool isActive: active
|
||||
|
||||
property string filePath : tempFile.filePath
|
||||
|
||||
active: chatMessageContentGui && chatMessageContentGui.core.isVoiceRecording()
|
||||
|
||||
onChatMessageContentGuiChanged: if(chatMessageContentGui){
|
||||
tempFile.createFileFromContentModel(chatMessageContentGui, false);
|
||||
}
|
||||
|
||||
TemporaryFile {
|
||||
id: tempFile
|
||||
}
|
||||
|
||||
sourceComponent: Item{
|
||||
id: loadedItem
|
||||
property bool isPlaying : vocalPlayer.item && vocalPlayer.item.playbackState === SoundPlayer.PlayingState
|
||||
onIsPlayingChanged: isPlaying ? mediaProgressBar.resume() : mediaProgressBar.stop()
|
||||
|
||||
width: availableWidth < 0 || availableWidth > mainItem.width ? mainItem.width : availableWidth
|
||||
height: mainItem.fitHeight
|
||||
|
||||
clip: false
|
||||
Loader {
|
||||
id: vocalPlayer
|
||||
|
||||
active: false
|
||||
function play(){
|
||||
if(!vocalPlayer.active)
|
||||
vocalPlayer.active = true
|
||||
else {
|
||||
if(loadedItem.isPlaying){// Pause the play
|
||||
vocalPlayer.item.pause()
|
||||
}else{// Play the audio
|
||||
vocalPlayer.item.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceComponent: SoundPlayer {
|
||||
source: mainItem.chatMessageContentGui && mainItem.filePath
|
||||
onStopped:{
|
||||
mediaProgressBar.value = 101
|
||||
}
|
||||
Component.onCompleted: {
|
||||
play()// This will open the file and allow seeking
|
||||
pause()
|
||||
mediaProgressBar.value = 0
|
||||
mediaProgressBar.refresh()
|
||||
}
|
||||
}
|
||||
onStatusChanged: if (loader.status == Loader.Ready) play()
|
||||
}
|
||||
RowLayout{
|
||||
id: lineLayout
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
ActionButton{
|
||||
id: playButton
|
||||
Layout.preferredHeight: iconSize
|
||||
Layout.preferredWidth: iconSize
|
||||
Layout.rightMargin: 5
|
||||
Layout.leftMargin: 15
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
isCustom: true
|
||||
backgroundRadius: width
|
||||
colorSet: (loadedItem.isPlaying ? ChatAudioMessageStyle.pauseAction
|
||||
: ChatAudioMessageStyle.playAction)
|
||||
onClicked:{
|
||||
vocalPlayer.play()
|
||||
}
|
||||
}
|
||||
Item{
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.rightMargin: 10
|
||||
Layout.topMargin: 10
|
||||
Layout.bottomMargin: 10
|
||||
MediaProgressBar{
|
||||
id: mediaProgressBar
|
||||
anchors.fill: parent
|
||||
progressDuration: vocalPlayer.item ? vocalPlayer.item.duration : chatMessageContentGui.core.getFileDuration()
|
||||
progressPosition: 0
|
||||
value: 0
|
||||
stopAtEnd: true
|
||||
resetAtEnd: false
|
||||
backgroundColor: ChatAudioMessageStyle.backgroundColor.color
|
||||
colorSet: ChatAudioMessageStyle.progressionWave
|
||||
function refresh(){
|
||||
if( vocalPlayer.item){
|
||||
progressPosition = vocalPlayer.item.getPosition()
|
||||
value = 100 * ( progressPosition / vocalPlayer.item.duration)
|
||||
}
|
||||
}
|
||||
onEndReached:{
|
||||
if(vocalPlayer.item)
|
||||
vocalPlayer.item.stop()
|
||||
}
|
||||
onRefreshPositionRequested: refresh()
|
||||
onSeekRequested: if( vocalPlayer.item){
|
||||
vocalPlayer.item.seek(ms)
|
||||
progressPosition = vocalPlayer.item.getPosition()
|
||||
value = 100 * (progressPosition / vocalPlayer.item.duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -311,8 +311,8 @@ ListView {
|
|||
unread: modelData.core.unreadMessagesCount
|
||||
}
|
||||
EffectImage {
|
||||
visible: modelData != undefined && lastMessageText.visible && modelData?.core.lastMessage && modelData?.core.lastMessageState !== LinphoneEnums.ChatMessageState.StateIdle
|
||||
&& !modelData?.core.lastMessage.core.isRemoteMessage
|
||||
visible: modelData?.core.lastMessage && modelData?.core.lastMessageState !== LinphoneEnums.ChatMessageState.StateIdle
|
||||
&& !modelData.core.lastMessage.core.isRemoteMessage
|
||||
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
|
||||
Layout.preferredHeight: 14 * DefaultStyle.dp
|
||||
colorizationColor: DefaultStyle.main1_500_main
|
||||
|
|
|
|||
|
|
@ -14,37 +14,23 @@ Control.Control {
|
|||
property color backgroundColor
|
||||
property bool isFirstMessage
|
||||
|
||||
property string imgUrl
|
||||
|
||||
property ChatMessageGui chatMessage
|
||||
property string ownReaction: chatMessage? chatMessage.core.ownReaction : ""
|
||||
property string fromAddress: chatMessage? chatMessage.core.fromAddress : ""
|
||||
property bool isRemoteMessage: chatMessage? chatMessage.core.isRemoteMessage : false
|
||||
property bool isFromChatGroup: chatMessage? chatMessage.core.isFromChatGroup : false
|
||||
property var msgState: chatMessage ? chatMessage.core.messageState : LinphoneEnums.ChatMessageState.StateIdle
|
||||
property string richFormatText: chatMessage.core.hasTextContent ? UtilsCpp.encodeTextToQmlRichFormat(chatMessage.core.utf8Text) : ""
|
||||
hoverEnabled: true
|
||||
property bool linkHovered: false
|
||||
property real maxWidth: parent?.width || Math.round(300 * DefaultStyle.dp)
|
||||
|
||||
leftPadding: isRemoteMessage ? Math.round(5 * DefaultStyle.dp) : 0
|
||||
|
||||
signal messageDeletionRequested()
|
||||
signal isFileHoveringChanged(bool isFileHovering)
|
||||
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
Text {
|
||||
id: fromNameText
|
||||
visible: mainItem.isFromChatGroup && mainItem.isRemoteMessage && mainItem.isFirstMessage
|
||||
anchors.top: parent.top
|
||||
maximumLineCount: 1
|
||||
width: implicitWidth
|
||||
x: chatBubble.x
|
||||
text: mainItem.chatMessage.core.fromName
|
||||
color: DefaultStyle.main2_500main
|
||||
font {
|
||||
pixelSize: Typography.p4.pixelSize
|
||||
weight: Typography.p4.weight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleDefaultMouseEvent(event) {
|
||||
|
|
@ -66,179 +52,162 @@ Control.Control {
|
|||
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
||||
_address: chatMessage ? chatMessage.core.fromAddress : ""
|
||||
}
|
||||
Item {
|
||||
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
||||
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
Layout.preferredWidth: childrenRect.width
|
||||
Control.Control {
|
||||
id: chatBubble
|
||||
spacing: Math.round(2 * DefaultStyle.dp)
|
||||
topPadding: Math.round(12 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(6 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(12 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(12 * DefaultStyle.dp)
|
||||
width: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth)
|
||||
|
||||
MouseArea { // Default mouse area. Each sub bubble can control the mouse and pass on to the main mouse handler. Child bubble mouse area must cover the entire bubble.
|
||||
id: defaultMouseArea
|
||||
visible: invitationLoader.status !== Loader.Ready // Add other bubbles here that could control the mouse themselves, then add in bubble a signal onMouseEvent
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: (mouse) => mainItem.handleDefaultMouseEvent(mouse)
|
||||
cursorShape: mainItem.linkHovered ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: mainItem.backgroundColor
|
||||
radius: Math.round(16 * DefaultStyle.dp)
|
||||
}
|
||||
Rectangle {
|
||||
visible: mainItem.isFirstMessage && mainItem.isRemoteMessage
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: Math.round(parent.width / 4)
|
||||
height: Math.round(parent.height / 4)
|
||||
color: mainItem.backgroundColor
|
||||
}
|
||||
Rectangle {
|
||||
visible: mainItem.isFirstMessage && !mainItem.isRemoteMessage
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: Math.round(parent.width / 4)
|
||||
height: Math.round(parent.height / 4)
|
||||
color: mainItem.backgroundColor
|
||||
}
|
||||
}
|
||||
contentItem: ColumnLayout {
|
||||
id: contentLayout
|
||||
Image {
|
||||
visible: mainItem.imgUrl != undefined
|
||||
id: contentimage
|
||||
}
|
||||
Text { // Uses default mouse area for link hovering.
|
||||
id: textElement
|
||||
visible: mainItem.richFormatText !== ""
|
||||
text: mainItem.richFormatText
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
color: DefaultStyle.main2_700
|
||||
textFormat: Text.RichText
|
||||
font {
|
||||
pixelSize: Typography.p1.pixelSize
|
||||
weight: Typography.p1.weight
|
||||
}
|
||||
onLinkActivated: {
|
||||
if (link.startsWith('sip'))
|
||||
UtilsCpp.createCall(link)
|
||||
else
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
onHoveredLinkChanged: {
|
||||
mainItem.linkHovered = hoveredLink !== ""
|
||||
}
|
||||
}
|
||||
|
||||
// Meeting invitation bubble
|
||||
/////////////////////////////
|
||||
Loader {
|
||||
id: invitationLoader
|
||||
active: mainItem.chatMessage.core.conferenceInfo !== null
|
||||
sourceComponent: invitationComponent
|
||||
}
|
||||
Component {
|
||||
id: invitationComponent
|
||||
ChatMessageInvitationBubble {
|
||||
conferenceInfoGui: mainItem.chatMessage.core.conferenceInfo
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
onMouseEvent: mainItem.handleDefaultMouseEvent(event)
|
||||
}
|
||||
}
|
||||
/////////////////////////////
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: mainItem.isRemoteMessage ? Qt.AlignLeft : Qt.AlignRight
|
||||
Text {
|
||||
text: UtilsCpp.formatDate(mainItem.chatMessage.core.timestamp, true, false)
|
||||
color: DefaultStyle.main2_500main
|
||||
font {
|
||||
pixelSize: Typography.p3.pixelSize
|
||||
weight: Typography.p3.weight
|
||||
}
|
||||
}
|
||||
EffectImage {
|
||||
visible: !mainItem.isRemoteMessage
|
||||
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
|
||||
Layout.preferredHeight: 14 * DefaultStyle.dp
|
||||
colorizationColor: DefaultStyle.main1_500_main
|
||||
imageSource: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDelivered
|
||||
? AppIcons.envelope
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDeliveredToUser
|
||||
? AppIcons.check
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateNotDelivered
|
||||
? AppIcons.warningCircle
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDisplayed
|
||||
? AppIcons.checks
|
||||
: ""
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
spacing: 0
|
||||
Text {
|
||||
id: fromNameText
|
||||
Layout.alignment: Qt.AlignTop
|
||||
visible: mainItem.isFromChatGroup && mainItem.isRemoteMessage && mainItem.isFirstMessage
|
||||
// anchors.top: parent.top
|
||||
// anchors.left: parent.left
|
||||
// anchors.leftMargin: avatar.width// mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
|
||||
maximumLineCount: 1
|
||||
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
|
||||
width: implicitWidth
|
||||
x: mapToItem(this, chatBubble.x, chatBubble.y).x
|
||||
text: mainItem.chatMessage.core.fromName
|
||||
color: DefaultStyle.main2_500main
|
||||
font {
|
||||
pixelSize: Typography.p4.pixelSize
|
||||
weight: Typography.p4.weight
|
||||
}
|
||||
}
|
||||
Button {
|
||||
id: reactionsButton
|
||||
visible: reactionList.count > 0
|
||||
anchors.top: chatBubble.bottom
|
||||
Binding {
|
||||
target: reactionsButton
|
||||
when: !mainItem.isRemoteMessage
|
||||
property: "anchors.left"
|
||||
value: chatBubble.left
|
||||
}
|
||||
Binding {
|
||||
target: reactionsButton
|
||||
when: mainItem.isRemoteMessage
|
||||
property: "anchors.right"
|
||||
value: chatBubble.right
|
||||
}
|
||||
anchors.topMargin: Math.round(-6 * DefaultStyle.dp)
|
||||
topPadding: Math.round(8 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(8 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(8 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(8 * DefaultStyle.dp)
|
||||
background: Rectangle {
|
||||
color: DefaultStyle.grey_100
|
||||
border.color: DefaultStyle.grey_0
|
||||
border.width: Math.round(2 * DefaultStyle.dp)
|
||||
radius: Math.round(20 * DefaultStyle.dp)
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
spacing: Math.round(6 * DefaultStyle.dp)
|
||||
Repeater {
|
||||
id: reactionList
|
||||
model: mainItem.chatMessage ? mainItem.chatMessage.core.reactionsSingleton : []
|
||||
delegate: RowLayout {
|
||||
spacing: Math.round(3 * DefaultStyle.dp)
|
||||
Item {
|
||||
// Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
||||
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
Layout.preferredWidth: childrenRect.width
|
||||
Control.Control {
|
||||
id: chatBubble
|
||||
spacing: Math.round(2 * DefaultStyle.dp)
|
||||
topPadding: Math.round(12 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(6 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(12 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(12 * DefaultStyle.dp)
|
||||
width: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth)
|
||||
|
||||
MouseArea { // Default mouse area. Each sub bubble can control the mouse and pass on to the main mouse handler. Child bubble mouse area must cover the entire bubble.
|
||||
id: defaultMouseArea
|
||||
// visible: invitationLoader.status !== Loader.Ready // Add other bubbles here that could control the mouse themselves, then add in bubble a signal onMouseEvent
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: (mouse) => mainItem.handleDefaultMouseEvent(mouse)
|
||||
}
|
||||
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: mainItem.backgroundColor
|
||||
radius: Math.round(16 * DefaultStyle.dp)
|
||||
}
|
||||
Rectangle {
|
||||
visible: mainItem.isFirstMessage && mainItem.isRemoteMessage
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: Math.round(parent.width / 4)
|
||||
height: Math.round(parent.height / 4)
|
||||
color: mainItem.backgroundColor
|
||||
}
|
||||
Rectangle {
|
||||
visible: mainItem.isFirstMessage && !mainItem.isRemoteMessage
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: Math.round(parent.width / 4)
|
||||
height: Math.round(parent.height / 4)
|
||||
color: mainItem.backgroundColor
|
||||
}
|
||||
}
|
||||
contentItem: ColumnLayout {
|
||||
spacing: Math.round(5 * DefaultStyle.dp)
|
||||
ChatMessageContent {
|
||||
id: chatBubbleContent
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
chatMessageGui: mainItem.chatMessage
|
||||
onMouseEvent: (event) => {
|
||||
mainItem.handleDefaultMouseEvent(event)
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.alignment: mainItem.isRemoteMessage ? Qt.AlignLeft : Qt.AlignRight
|
||||
Text {
|
||||
text: UtilsCpp.encodeEmojiToQmlRichFormat(modelData.body)
|
||||
textFormat: Text.RichText
|
||||
text: UtilsCpp.formatDate(mainItem.chatMessage.core.timestamp, true, false, "dd/MM")
|
||||
color: DefaultStyle.main2_500main
|
||||
font {
|
||||
pixelSize: Math.round(15 * DefaultStyle.dp)
|
||||
weight: Math.round(400 * DefaultStyle.dp)
|
||||
pixelSize: Typography.p3.pixelSize
|
||||
weight: Typography.p3.weight
|
||||
}
|
||||
}
|
||||
Text {
|
||||
visible: modelData.count > 1
|
||||
text: modelData.count
|
||||
verticalAlignment: Text.AlignBottom
|
||||
font {
|
||||
pixelSize: Typography.p4.pixelSize
|
||||
weight: Typography.p4.weight
|
||||
EffectImage {
|
||||
visible: !mainItem.isRemoteMessage
|
||||
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
|
||||
Layout.preferredHeight: 14 * DefaultStyle.dp
|
||||
colorizationColor: DefaultStyle.main1_500_main
|
||||
imageSource: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDelivered
|
||||
? AppIcons.envelope
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDeliveredToUser
|
||||
? AppIcons.check
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateNotDelivered
|
||||
? AppIcons.warningCircle
|
||||
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDisplayed
|
||||
? AppIcons.checks
|
||||
: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Button {
|
||||
id: reactionsButton
|
||||
visible: reactionList.count > 0
|
||||
anchors.top: chatBubble.bottom
|
||||
Binding {
|
||||
target: reactionsButton
|
||||
when: !mainItem.isRemoteMessage
|
||||
property: "anchors.left"
|
||||
value: chatBubble.left
|
||||
}
|
||||
Binding {
|
||||
target: reactionsButton
|
||||
when: mainItem.isRemoteMessage
|
||||
property: "anchors.right"
|
||||
value: chatBubble.right
|
||||
}
|
||||
anchors.topMargin: Math.round(-6 * DefaultStyle.dp)
|
||||
topPadding: Math.round(8 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(8 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(8 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(8 * DefaultStyle.dp)
|
||||
background: Rectangle {
|
||||
color: DefaultStyle.grey_100
|
||||
border.color: DefaultStyle.grey_0
|
||||
border.width: Math.round(2 * DefaultStyle.dp)
|
||||
radius: Math.round(20 * DefaultStyle.dp)
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
spacing: Math.round(6 * DefaultStyle.dp)
|
||||
Repeater {
|
||||
id: reactionList
|
||||
model: mainItem.chatMessage ? mainItem.chatMessage.core.reactionsSingleton : []
|
||||
delegate: RowLayout {
|
||||
spacing: Math.round(3 * DefaultStyle.dp)
|
||||
Text {
|
||||
text: UtilsCpp.encodeEmojiToQmlRichFormat(modelData.body)
|
||||
textFormat: Text.RichText
|
||||
font {
|
||||
pixelSize: Math.round(15 * DefaultStyle.dp)
|
||||
weight: Math.round(400 * DefaultStyle.dp)
|
||||
}
|
||||
}
|
||||
Text {
|
||||
visible: modelData.count > 1
|
||||
text: modelData.count
|
||||
verticalAlignment: Text.AlignBottom
|
||||
font {
|
||||
pixelSize: Typography.p4.pixelSize
|
||||
weight: Typography.p4.weight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -262,14 +231,17 @@ Control.Control {
|
|||
spacing: 0
|
||||
IconLabelButton {
|
||||
inverseLayout: true
|
||||
//: "Copy"
|
||||
text: qsTr("chat_message_copy")
|
||||
text: chatBubbleContent.selectedText != ""
|
||||
//: "Copy selection"
|
||||
? qsTr("chat_message_copy_selection")
|
||||
//: "Copy"
|
||||
: qsTr("chat_message_copy")
|
||||
icon.source: AppIcons.copy
|
||||
// spacing: Math.round(10 * DefaultStyle.dp)
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 45 * DefaultStyle.dp
|
||||
onClicked: {
|
||||
var success = UtilsCpp.copyToClipboard(mainItem.chatMessage.core.text)
|
||||
var success = UtilsCpp.copyToClipboard(chatBubbleContent.selectedText != "" ? chatBubbleContent.selectedText : mainItem.chatMessage.core.text)
|
||||
//: Copied
|
||||
if (success) UtilsCpp.showInformationPopup(qsTr("chat_message_copied_to_clipboard_title"),
|
||||
//: "to clipboard"
|
||||
|
|
|
|||
105
Linphone/view/Control/Display/Chat/ChatMessageContent.qml
Normal file
105
Linphone/view/Control/Display/Chat/ChatMessageContent.qml
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls.Basic as Control
|
||||
import Linphone
|
||||
|
||||
// =============================================================================
|
||||
// Simple content display without reply and forward. These modules need to be splitted because of cyclic dependencies.
|
||||
// See ChatFullContent
|
||||
|
||||
ColumnLayout {
|
||||
id: mainItem
|
||||
property ChatMessageGui chatMessageGui: null
|
||||
|
||||
signal isFileHoveringChanged(bool isFileHovering)
|
||||
signal lastSelectedTextChanged(string selectedText)
|
||||
// signal conferenceIcsCopied()
|
||||
signal mouseEvent(MouseEvent event)
|
||||
property string selectedText
|
||||
|
||||
property color textColor
|
||||
|
||||
property int fileBorderWidth : 0
|
||||
|
||||
spacing: Math.round(5 * DefaultStyle.dp)
|
||||
property int padding: Math.round(10 * DefaultStyle.dp)
|
||||
|
||||
// VOICE MESSAGES
|
||||
// ListView {
|
||||
// id: messagesVoicesList
|
||||
// width: parent.width-2*mainItem.padding
|
||||
// visible: count > 0
|
||||
// spacing: 0
|
||||
// clip: false
|
||||
// model: ChatMessageContentProxy {
|
||||
// filterType: ChatMessageContentProxy.FilterContentType.Voice
|
||||
// chatMessageGui: mainItem.chatMessageGui
|
||||
// }
|
||||
// height: contentHeight
|
||||
// boundsBehavior: Flickable.StopAtBounds
|
||||
// interactive: false
|
||||
// function updateBestWidth(){
|
||||
// var newWidth = mainItem.updateListBestWidth(messagesVoicesList)
|
||||
// mainItem.voicesCount = newWidth[0]
|
||||
// mainItem.voicesBestWidth = newWidth[1]
|
||||
// }
|
||||
// delegate: ChatAudioMessage{
|
||||
// id: audioMessage
|
||||
// contentModel: $modelData
|
||||
// visible: contentModel
|
||||
// z: 1
|
||||
// Component.onCompleted: messagesVoicesList.updateBestWidth()
|
||||
// }
|
||||
// Component.onCompleted: messagesVoicesList.updateBestWidth
|
||||
// }
|
||||
// CONFERENCE
|
||||
Repeater {
|
||||
id: conferenceList
|
||||
visible: count > 0
|
||||
model: ChatMessageContentProxy{
|
||||
filterType: ChatMessageContentProxy.FilterContentType.Conference
|
||||
chatMessageGui: mainItem.chatMessageGui
|
||||
}
|
||||
delegate: ChatMessageInvitationBubble {
|
||||
Layout.fillWidth: true
|
||||
conferenceInfoGui: modelData.core.conferenceInfo
|
||||
// width: conferenceList.width
|
||||
onMouseEvent: (event) => mainItem.mouseEvent(event)
|
||||
}
|
||||
}
|
||||
// FILES
|
||||
ChatFilesGridLayout {
|
||||
id: messageFilesList
|
||||
visible: itemCount > 0
|
||||
Layout.fillWidth: true
|
||||
maxWidth: Math.round(115*3 * DefaultStyle.dp)
|
||||
Layout.fillHeight: true
|
||||
// Layout.preferredHeight: contentHeight
|
||||
chatMessageGui: mainItem.chatMessageGui
|
||||
// onIsHoveringFileChanged: mainItem.isHoveringFile = isHoveringFile
|
||||
onIsHoveringFileChanged: mainItem.isFileHoveringChanged(isHoveringFile)
|
||||
// borderWidth: mainItem.fileBorderWidth
|
||||
// property int availableSection: mainItem.availableWidth / mainItem.filesBestWidth
|
||||
// property int bestFitSection: mainItem.bestWidth / mainItem.filesBestWidth
|
||||
// columns: Math.max(1, Math.min(availableSection , bestFitSection))
|
||||
// columnSpacing: 0
|
||||
// rowSpacing: ChatStyle.entry.message.file.spacing
|
||||
}
|
||||
// TEXTS
|
||||
Repeater {
|
||||
id: messagesTextsList
|
||||
visible: count > 0
|
||||
model: ChatMessageContentProxy {
|
||||
filterType: ChatMessageContentProxy.FilterContentType.Text
|
||||
chatMessageGui: mainItem.chatMessageGui
|
||||
}
|
||||
delegate: ChatTextContent {
|
||||
Layout.fillWidth: true
|
||||
// height: implicitHeight
|
||||
contentGui: modelData
|
||||
onLastTextSelectedChanged: mainItem.selectedText = selectedText
|
||||
// onRightClicked: mainItem.rightClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ Rectangle {
|
|||
clip: true
|
||||
antialiasing: true
|
||||
|
||||
property var conferenceInfoGui: ConferenceInfoGui
|
||||
property ConferenceInfoGui conferenceInfoGui
|
||||
property var conferenceInfo: conferenceInfoGui?.core
|
||||
property string timeRangeText: ""
|
||||
property bool linkHovered: false
|
||||
|
|
|
|||
74
Linphone/view/Control/Display/Chat/ChatTextContent.qml
Normal file
74
Linphone/view/Control/Display/Chat/ChatTextContent.qml
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Linphone
|
||||
|
||||
import UtilsCpp
|
||||
|
||||
|
||||
// TODO : into Loader
|
||||
// =============================================================================
|
||||
TextEdit {
|
||||
id: message
|
||||
property ChatMessageContentGui contentGui
|
||||
property string lastTextSelected : ''
|
||||
color: DefaultStyle.main2_700
|
||||
font {
|
||||
pixelSize: (contentGui && UtilsCpp.isOnlyEmojis(contentGui.core.text)) ? Typography.h1.pixelSize : Typography.p1.pixelSize
|
||||
weight: Typography.p1.weight
|
||||
}
|
||||
// property int removeWarningFromBindingLoop : implicitWidth // Just a dummy variable to remove meaningless binding loop on implicitWidth
|
||||
|
||||
visible: contentGui && contentGui.core.isText
|
||||
textMargin: 0
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
|
||||
text: visible ? UtilsCpp.encodeTextToQmlRichFormat(contentGui.core.utf8Text)
|
||||
: ''
|
||||
|
||||
textFormat: Text.RichText // To supports links and imgs.
|
||||
wrapMode: TextEdit.Wrap
|
||||
|
||||
onLinkActivated: (link) => {
|
||||
if (link.startsWith('sip'))
|
||||
UtilsCpp.createCall(link)
|
||||
else
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
onSelectedTextChanged:{
|
||||
if(selectedText != '') lastTextSelected = selectedText
|
||||
// else {
|
||||
// if(mouseArea.keepLastSelection) {
|
||||
// mouseArea.keepLastSelection = false
|
||||
// }
|
||||
// }
|
||||
}
|
||||
onActiveFocusChanged: {
|
||||
if(activeFocus) {
|
||||
lastTextSelected = ''
|
||||
}
|
||||
else mouseArea.keepLastSelection = false
|
||||
// deselect()
|
||||
}
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
property bool keepLastSelection: false
|
||||
property int lastStartSelection:0
|
||||
property int lastEndSelection:0
|
||||
anchors.fill: parent
|
||||
propagateComposedEvents: true
|
||||
hoverEnabled: true
|
||||
scrollGestureEnabled: false
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: (mouse) => {
|
||||
// if(!keepLastSelection) {
|
||||
// lastStartSelection = parent.selectionStart
|
||||
// lastEndSelection = parent.selectionEnd
|
||||
// }
|
||||
keepLastSelection = true
|
||||
mouse.accepted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
294
Linphone/view/Control/Display/Chat/FileView.qml
Normal file
294
Linphone/view/Control/Display/Chat/FileView.qml
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls as Control
|
||||
import QtQuick.Layouts
|
||||
import QtMultimedia
|
||||
|
||||
import Linphone
|
||||
import UtilsCpp
|
||||
import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
|
||||
import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils
|
||||
|
||||
// =============================================================================
|
||||
|
||||
|
||||
Item {
|
||||
id: mainItem
|
||||
property ChatMessageContentGui contentGui
|
||||
property string thumbnail: contentGui && contentGui.core.thumbnail
|
||||
property string name: contentGui && contentGui.core.name
|
||||
property string filePath: contentGui && contentGui.core.filePath
|
||||
property bool active: true
|
||||
property real animationScale : FileViewStyle.animation.to
|
||||
property alias imageScale: thumbnailProvider.scale
|
||||
property bool wasDownloaded: contentGui && contentGui.core.wasDownloaded
|
||||
property bool isAnimatedImage : contentGui && contentGui.core.wasDownloaded && UtilsCpp.isAnimatedImage(filePath)
|
||||
property bool haveThumbnail: contentGui && UtilsCpp.canHaveThumbnail(filePath)
|
||||
property int fileSize: contentGui ? contentGui.core.fileSize : 0
|
||||
property bool isTransferring
|
||||
|
||||
Connections {
|
||||
enabled: contentGui
|
||||
target: contentGui.core
|
||||
function onMsgStateChanged(state) {
|
||||
isTransferring = state === LinphoneEnums.ChatMessageState.StateFileTransferInProgress
|
||||
|| state === LinphoneEnums.ChatMessageState.StateInProgress
|
||||
}
|
||||
}
|
||||
|
||||
property bool isHovering: thumbnailProvider.state == 'hovered'
|
||||
property bool isOutgoing: false
|
||||
|
||||
MouseArea {
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
// Changing of cursor seems not to work with the Loader
|
||||
// Use override cursor for this case
|
||||
onEntered: {
|
||||
UtilsCpp.setGlobalCursor(Qt.PointingHandCursor)
|
||||
thumbnailProvider.state = 'hovered'
|
||||
}
|
||||
onExited: {
|
||||
UtilsCpp.restoreGlobalCursor()
|
||||
thumbnailProvider.state = ''
|
||||
}
|
||||
anchors.fill: parent
|
||||
onClicked: (mouse) => {
|
||||
mouse.accepted = false
|
||||
if(mainItem.isTransferring) {
|
||||
mainItem.contentGui.core.lCancelDownloadFile()
|
||||
mouse.accepted = true
|
||||
}
|
||||
else if(!mainItem.contentGui.core.wasDownloaded) {
|
||||
mouse.accepted = true
|
||||
thumbnailProvider.state = ''
|
||||
mainItem.contentGui.core.lDownloadFile()
|
||||
} else if (Utils.pointIsInItem(this, thumbnailProvider, mouse)) {
|
||||
mouse.accepted = true
|
||||
// if(SettingsModel.isVfsEncrypted){
|
||||
// window.attachVirtualWindow(Utils.buildCommonDialogUri('FileViewDialog'), {
|
||||
// contentGui: mainItem.contentGui,
|
||||
// }, function (status) {
|
||||
// })
|
||||
// }else
|
||||
mainItem.contentGui.core.lOpenFile()
|
||||
} else if (mainItem.contentGui) {
|
||||
mouse.accepted = true
|
||||
thumbnailProvider.state = ''
|
||||
mainItem.contentGui.core.lOpenFile(true)// Show directory
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Thumbnail
|
||||
// ---------------------------------------------------------------------
|
||||
Component {
|
||||
id: thumbnailImage
|
||||
Item {
|
||||
id: thumbnailSource
|
||||
property bool isVideo: UtilsCpp.isVideo(mainItem.filePath)
|
||||
property bool isImage: UtilsCpp.isImage(mainItem.filePath)
|
||||
property bool isPdf: UtilsCpp.isPdf(mainItem.filePath)
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
visible: thumbnailSource.isPdf
|
||||
source: AppIcons.filePdf
|
||||
sourceSize.width: mainItem.width
|
||||
sourceSize.height: mainItem.height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
Image {
|
||||
visible: thumbnailSource.isImage
|
||||
mipmap: false//SettingsModel.mipmapEnabled
|
||||
source: mainItem.thumbnail
|
||||
sourceSize.width: mainItem.width
|
||||
sourceSize.height: mainItem.height
|
||||
autoTransform: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
anchors.fill: parent
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
visible: parent.status !== Image.Ready
|
||||
source: AppIcons.fileImage
|
||||
sourceSize.width: mainItem.width
|
||||
sourceSize.height: mainItem.height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
visible: thumbnailSource.isVideo
|
||||
color: DefaultStyle.grey_1000
|
||||
anchors.fill: parent
|
||||
Video {
|
||||
id: videoThumbnail
|
||||
anchors.fill: parent
|
||||
position: 100
|
||||
source: "file:///" + mainItem.filePath
|
||||
fillMode: playbackState === MediaPlayer.PlayingState ? VideoOutput.PreserveAspectFit : VideoOutput.PreserveAspectCrop
|
||||
MouseArea {
|
||||
propagateComposedEvents: false
|
||||
enabled: videoThumbnail.visible
|
||||
anchors.fill: parent
|
||||
hoverEnabled: false
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: (mouse) => {
|
||||
mouse.accepted = true
|
||||
videoThumbnail.playbackState === MediaPlayer.PlayingState ? videoThumbnail.pause() : videoThumbnail.play()
|
||||
}
|
||||
}
|
||||
EffectImage {
|
||||
anchors.centerIn: parent
|
||||
visible: videoThumbnail.playbackState !== MediaPlayer.PlayingState
|
||||
width: Math.round(24 * DefaultStyle.dp)
|
||||
height: Math.round(24 * DefaultStyle.dp)
|
||||
imageSource: AppIcons.playFill
|
||||
colorizationColor: DefaultStyle.main2_0
|
||||
}
|
||||
Text {
|
||||
z: parent.z + 1
|
||||
RectangleTest{anchors.fill: parent}
|
||||
property int timeDisplayed: videoThumbnail.playbackState === MediaPlayer.PlayingState ? videoThumbnail.position : videoThumbnail.duration
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.bottomMargin: Math.round(6 * DefaultStyle.dp)
|
||||
anchors.leftMargin: Math.round(6 * DefaultStyle.dp)
|
||||
text: UtilsCpp.formatDuration(timeDisplayed)
|
||||
color: DefaultStyle.grey_0
|
||||
font {
|
||||
pixelSize: Typography.d1.pixelSize
|
||||
weight: Typography.d1.weight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: animatedImage
|
||||
AnimatedImage {
|
||||
id: animatedImageSource
|
||||
mipmap: false//SettingsModel.mipmapEnabled
|
||||
source: 'file:/'+ mainItem.filePath
|
||||
autoTransform: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Extension
|
||||
// ---------------------------------------------------------------------
|
||||
Component {
|
||||
id: defaultFileView
|
||||
|
||||
Control.Control {
|
||||
id: defaultView
|
||||
leftPadding: Math.round(4 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(4 * DefaultStyle.dp)
|
||||
topPadding: Math.round(23 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(4 * DefaultStyle.dp)
|
||||
hoverEnabled: false
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: FileViewStyle.extension.background.color
|
||||
radius: FileViewStyle.extension.radius
|
||||
|
||||
Rectangle {
|
||||
color: DefaultStyle.main2_200
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: Math.round(23 * DefaultStyle.dp)
|
||||
EffectImage {
|
||||
anchors.centerIn: parent
|
||||
imageSource: contentGui
|
||||
? UtilsCpp.isImage(mainItem.filePath)
|
||||
? AppIcons.fileImage
|
||||
: UtilsCpp.isPdf(mainItem.filePath)
|
||||
? AppIcons.filePdf
|
||||
: UtilsCpp.isText(mainItem.filePath)
|
||||
? AppIcons.fileText
|
||||
: AppIcons.file
|
||||
: ''
|
||||
imageWidth: Math.round(14 * DefaultStyle.dp)
|
||||
imageHeight: Math.round(14 * DefaultStyle.dp)
|
||||
colorizationColor: DefaultStyle.main2_600
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
Text {
|
||||
id: fileName
|
||||
visible: !progressBar.visible
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
// visible: mainItem.contentGui && !mainItem.isAnimatedImage
|
||||
font.pixelSize: Typography.f1.pixelSize
|
||||
font.weight: Typography.f1l.weight
|
||||
wrapMode: Text.WrapAnywhere
|
||||
maximumLineCount: 2
|
||||
text: mainItem.name
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
Text {
|
||||
id: fileSizeText
|
||||
visible: !progressBar.visible
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
text: Utils.formatSize(mainItem.fileSize)
|
||||
font.pixelSize: Typography.f1l.pixelSize
|
||||
font.weight: Typography.f1l.weight
|
||||
}
|
||||
RoundProgressBar {
|
||||
id: progressBar
|
||||
anchors.centerIn: parent
|
||||
to: 100
|
||||
value: mainItem.contentGui ? (mainItem.fileSize>0 ? Math.floor(100 * mainItem.contentGui.core.fileOffset / mainItem.fileSize) : 0) : to
|
||||
visible: mainItem.isTransferring && value != 0
|
||||
/* Change format? Current is %
|
||||
text: if(mainRow.contentGui){
|
||||
var mainItem.fileSize = Utils.formatSize(mainRow.contentGui.core.mainItem.fileSize)
|
||||
return progressBar.visible
|
||||
? Utils.formatSize(mainRow.contentGui.core.fileOffset) + '/' + mainItem.fileSize
|
||||
: mainItem.fileSize
|
||||
}else
|
||||
return ''
|
||||
*/
|
||||
}
|
||||
Rectangle {
|
||||
visible: thumbnailProvider.state === 'hovered' && mainItem.contentGui && (/*!mainItem.isOutgoing &&*/ !mainItem.contentGui.core.wasDownloaded)
|
||||
color: DefaultStyle.grey_0
|
||||
opacity: 0.5
|
||||
anchors.fill: parent
|
||||
}
|
||||
EffectImage {
|
||||
visible: thumbnailProvider.state === 'hovered' && mainItem.contentGui && (/*!mainItem.isOutgoing &&*/ !mainItem.contentGui.core.wasDownloaded)
|
||||
anchors.centerIn: parent
|
||||
imageSource: AppIcons.download
|
||||
width: Math.round(24 * DefaultStyle.dp)
|
||||
height: Math.round(24 * DefaultStyle.dp)
|
||||
colorizationColor: DefaultStyle.main2_600
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: thumbnailProvider
|
||||
anchors.fill: parent
|
||||
sourceComponent: mainItem.contentGui
|
||||
? mainItem.isAnimatedImage
|
||||
? animatedImage
|
||||
: mainItem.haveThumbnail
|
||||
? thumbnailImage
|
||||
: defaultFileView
|
||||
: undefined
|
||||
|
||||
states: State {
|
||||
name: 'hovered'
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Linphone/view/Control/Display/RoundProgressBar.qml
Normal file
78
Linphone/view/Control/Display/RoundProgressBar.qml
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Shapes
|
||||
import Linphone
|
||||
|
||||
|
||||
ProgressBar{
|
||||
id: mainItem
|
||||
property string text: value + '%'
|
||||
implicitHeight: 35
|
||||
implicitWidth: 35
|
||||
to: 100
|
||||
value: 0
|
||||
background: Item {}
|
||||
Timer{
|
||||
id: animationTest
|
||||
repeat: true
|
||||
onTriggered: value = (value + 1) % to
|
||||
interval: 5
|
||||
}
|
||||
contentItem: Item{
|
||||
Shape {
|
||||
id: shape
|
||||
anchors.fill: parent
|
||||
anchors.margins: Math.round(2 * DefaultStyle.dp)
|
||||
|
||||
property real progressionRadius : Math.min(shape.width / 2, shape.height / 2) - Math.round(3 * DefaultStyle.dp) / 2
|
||||
|
||||
layer.enabled: true
|
||||
layer.samples: 8
|
||||
layer.smooth: true
|
||||
vendorExtensionsEnabled: false
|
||||
|
||||
ShapePath {
|
||||
id: pathDial
|
||||
strokeColor: DefaultStyle.main1_100
|
||||
fillColor: 'transparent'
|
||||
strokeWidth: Math.round(3 * DefaultStyle.dp)
|
||||
capStyle: Qt.RoundCap
|
||||
|
||||
PathAngleArc {
|
||||
radiusX: shape.progressionRadius
|
||||
radiusY: shape.progressionRadius
|
||||
centerX: shape.width / 2
|
||||
centerY: shape.height / 2
|
||||
startAngle: -90 // top start
|
||||
sweepAngle: 360
|
||||
}
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
id: pathProgress
|
||||
strokeColor: DefaultStyle.main1_500_main
|
||||
fillColor: 'transparent'
|
||||
strokeWidth: Math.round(3 * DefaultStyle.dp)
|
||||
capStyle: Qt.RoundCap
|
||||
|
||||
PathAngleArc {
|
||||
radiusX: shape.progressionRadius
|
||||
radiusY: shape.progressionRadius
|
||||
centerX: shape.width / 2
|
||||
centerY: shape.height / 2
|
||||
startAngle: -90 // top start
|
||||
sweepAngle: (360/ mainItem.to * mainItem.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
Text{
|
||||
anchors.centerIn: parent
|
||||
text: mainItem.text
|
||||
color: DefaultStyle.main1_500_main
|
||||
font.pixelSize: Typography.p4.pixelSize
|
||||
font.weight: Typography.p2.weight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
223
Linphone/view/Control/Input/Chat/ChatDroppableTextArea.qml
Normal file
223
Linphone/view/Control/Input/Chat/ChatDroppableTextArea.qml
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls.Basic as Control
|
||||
import QtQuick.Layouts
|
||||
import Linphone
|
||||
import UtilsCpp
|
||||
|
||||
import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
|
||||
import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils
|
||||
|
||||
Control.Control {
|
||||
id: mainItem
|
||||
|
||||
property alias placeholderText: sendingTextArea.placeholderText
|
||||
property alias text: sendingTextArea.text
|
||||
property alias textArea: sendingTextArea
|
||||
property alias cursorPosition: sendingTextArea.cursorPosition
|
||||
property alias emojiPickerButtonChecked: emojiPickerButton.checked
|
||||
|
||||
property bool dropEnabled: true
|
||||
property string dropDisabledReason
|
||||
property bool isEphemeral : false
|
||||
property bool emojiVisible: false
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
signal dropped (var files)
|
||||
signal validText (string text)
|
||||
signal sendText()
|
||||
signal audioRecordRequest()
|
||||
signal emojiClicked()
|
||||
signal composing()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function _emitFiles (files) {
|
||||
// Filtering files, other urls are forbidden.
|
||||
files = files.reduce(function (files, file) {
|
||||
console.log("dropping", file.toString())
|
||||
if (file.toString().startsWith("file:")) {
|
||||
files.push(Utils.getSystemPathFromUri(file))
|
||||
}
|
||||
|
||||
return files
|
||||
}, [])
|
||||
if (files.length > 0) {
|
||||
dropped(files)
|
||||
}
|
||||
}
|
||||
|
||||
// width: mainItem.implicitWidth
|
||||
// height: mainItem.height
|
||||
leftPadding: Math.round(15 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(15 * DefaultStyle.dp)
|
||||
topPadding: Math.round(24 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(16 * DefaultStyle.dp)
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: DefaultStyle.grey_100
|
||||
MediumButton {
|
||||
id: expandButton
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Math.round(4 * DefaultStyle.dp)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
style: ButtonStyle.noBackgroundOrange
|
||||
icon.source: checked ? AppIcons.downArrow : AppIcons.upArrow
|
||||
checkable: true
|
||||
}
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
spacing: Math.round(20 * DefaultStyle.dp)
|
||||
RowLayout {
|
||||
spacing: Math.round(16 * DefaultStyle.dp)
|
||||
BigButton {
|
||||
id: emojiPickerButton
|
||||
style: ButtonStyle.noBackground
|
||||
checkable: true
|
||||
icon.source: checked ? AppIcons.closeX : AppIcons.smiley
|
||||
}
|
||||
BigButton {
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.paperclip
|
||||
onClicked: {
|
||||
console.log("TODO : open explorer to attach file")
|
||||
}
|
||||
}
|
||||
Control.Control {
|
||||
Layout.fillWidth: true
|
||||
leftPadding: Math.round(15 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(15 * DefaultStyle.dp)
|
||||
topPadding: Math.round(15 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(15 * DefaultStyle.dp)
|
||||
background: Rectangle {
|
||||
id: inputBackground
|
||||
anchors.fill: parent
|
||||
radius: Math.round(35 * DefaultStyle.dp)
|
||||
color: DefaultStyle.grey_0
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: sendingTextArea.forceActiveFocus()
|
||||
cursorShape: Qt.IBeamCursor
|
||||
}
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
Flickable {
|
||||
id: sendingAreaFlickable
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: parent.width - stackButton.width
|
||||
Layout.preferredHeight: Math.min(Math.round(60 * DefaultStyle.dp), contentHeight)
|
||||
Binding {
|
||||
target: sendingAreaFlickable
|
||||
when: expandButton.checked
|
||||
property: "Layout.preferredHeight"
|
||||
value: Math.round(250 * DefaultStyle.dp)
|
||||
restoreMode: Binding.RestoreBindingOrValue
|
||||
}
|
||||
Layout.fillHeight: true
|
||||
contentHeight: sendingTextArea.contentHeight
|
||||
contentWidth: width
|
||||
|
||||
function ensureVisible(r) {
|
||||
if (contentX >= r.x)
|
||||
contentX = r.x;
|
||||
else if (contentX+width <= r.x+r.width)
|
||||
contentX = r.x+r.width-width;
|
||||
if (contentY >= r.y)
|
||||
contentY = r.y;
|
||||
else if (contentY+height <= r.y+r.height)
|
||||
contentY = r.y+r.height-height;
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: sendingTextArea
|
||||
width: sendingAreaFlickable.width
|
||||
height: sendingAreaFlickable.height
|
||||
textFormat: TextEdit.AutoText
|
||||
//: Say something… : placeholder text for sending message text area
|
||||
placeholderText: qsTr("chat_view_send_area_placeholder_text")
|
||||
placeholderTextColor: DefaultStyle.main2_400
|
||||
color: DefaultStyle.main2_700
|
||||
font {
|
||||
pixelSize: Typography.p1.pixelSize
|
||||
weight: Typography.p1.weight
|
||||
}
|
||||
onCursorRectangleChanged: sendingAreaFlickable.ensureVisible(cursorRectangle)
|
||||
wrapMode: TextEdit.WordWrap
|
||||
Keys.onPressed: (event) => {
|
||||
if ((event.key == Qt.Key_Enter || event.key == Qt.Key_Return)
|
||||
&& (!(event.modifier & Qt.ShiftModifier))) {
|
||||
mainItem.sendText()
|
||||
sendingTextArea.clear()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StackLayout {
|
||||
id: stackButton
|
||||
currentIndex: sendingTextArea.text.length === 0 ? 0 : 1
|
||||
BigButton {
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.microphone
|
||||
onClicked: {
|
||||
console.log("TODO : go to record message")
|
||||
}
|
||||
}
|
||||
BigButton {
|
||||
style: ButtonStyle.noBackgroundOrange
|
||||
icon.source: AppIcons.paperPlaneRight
|
||||
onClicked: {
|
||||
mainItem.sendText()
|
||||
sendingTextArea.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: hoverContent
|
||||
anchors.fill: parent
|
||||
color: DefaultStyle.main2_0
|
||||
visible: false
|
||||
radius: Math.round(20 * DefaultStyle.dp)
|
||||
|
||||
EffectImage {
|
||||
anchors.centerIn: parent
|
||||
imageSource: AppIcons.filePlus
|
||||
width: Math.round(37 * DefaultStyle.dp)
|
||||
height: Math.round(37 * DefaultStyle.dp)
|
||||
colorizationColor: DefaultStyle.main2_500main
|
||||
}
|
||||
|
||||
DashRectangle {
|
||||
x: parent.x
|
||||
y: parent.y
|
||||
radius: hoverContent.radius
|
||||
color: DefaultStyle.main2_500main
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
}
|
||||
}
|
||||
DropArea {
|
||||
anchors.fill: parent
|
||||
keys: [ 'text/uri-list' ]
|
||||
visible: mainItem.dropEnabled
|
||||
|
||||
onDropped: (drop) => {
|
||||
state = ''
|
||||
if (drop.hasUrls) {
|
||||
_emitFiles(drop.urls)
|
||||
}
|
||||
}
|
||||
onEntered: state = 'hover'
|
||||
onExited: state = ''
|
||||
|
||||
states: State {
|
||||
name: 'hover'
|
||||
PropertyChanges { target: hoverContent; visible: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -76,210 +76,164 @@ RowLayout {
|
|||
}
|
||||
]
|
||||
|
||||
content: [
|
||||
ChatMessagesListView {
|
||||
id: chatMessagesListView
|
||||
clip: true
|
||||
height: contentHeight
|
||||
backgroundColor: splitPanel.panelColor
|
||||
width: parent.width - anchors.leftMargin - anchors.rightMargin
|
||||
chat: mainItem.chat
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: messageSender.top
|
||||
anchors.leftMargin: Math.round(18 * DefaultStyle.dp)
|
||||
anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
|
||||
Control.ScrollBar.vertical: scrollbar
|
||||
|
||||
Popup {
|
||||
id: emojiPickerPopup
|
||||
y: Math.round(chatMessagesListView.y + chatMessagesListView.height - height - 8*DefaultStyle.dp)
|
||||
x: Math.round(chatMessagesListView.x + 8*DefaultStyle.dp)
|
||||
width: Math.round(393 * DefaultStyle.dp)
|
||||
height: Math.round(291 * DefaultStyle.dp)
|
||||
visible: emojiPickerButton.checked
|
||||
closePolicy: Popup.CloseOnPressOutside
|
||||
onClosed: emojiPickerButton.checked = false
|
||||
padding: 10 * DefaultStyle.dp
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
id: buttonBackground
|
||||
anchors.fill: parent
|
||||
color: DefaultStyle.grey_0
|
||||
radius: Math.round(20 * DefaultStyle.dp)
|
||||
}
|
||||
MultiEffect {
|
||||
anchors.fill: buttonBackground
|
||||
source: buttonBackground
|
||||
shadowEnabled: true
|
||||
shadowColor: DefaultStyle.grey_1000
|
||||
shadowBlur: 0.1
|
||||
shadowOpacity: 0.5
|
||||
}
|
||||
}
|
||||
contentItem: EmojiPicker {
|
||||
id: emojiPicker
|
||||
editor: sendingTextArea
|
||||
}
|
||||
}
|
||||
},
|
||||
ScrollBar {
|
||||
id: scrollbar
|
||||
visible: chatMessagesListView.contentHeight > parent.height
|
||||
active: visible
|
||||
anchors.top: chatMessagesListView.top
|
||||
anchors.bottom: chatMessagesListView.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Math.round(5 * DefaultStyle.dp)
|
||||
policy: Control.ScrollBar.AsNeeded
|
||||
},
|
||||
Control.Control {
|
||||
id: messageSender
|
||||
visible: !mainItem.chat.core.isReadOnly
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
leftPadding: Math.round(15 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(15 * DefaultStyle.dp)
|
||||
topPadding: Math.round(24 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(16 * DefaultStyle.dp)
|
||||
background: Rectangle {
|
||||
content: ColumnLayout {
|
||||
spacing: 0
|
||||
anchors.fill: parent
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
ChatMessagesListView {
|
||||
id: chatMessagesListView
|
||||
clip: true
|
||||
height: contentHeight
|
||||
backgroundColor: splitPanel.panelColor
|
||||
width: parent.width - anchors.leftMargin - anchors.rightMargin
|
||||
chat: mainItem.chat
|
||||
anchors.fill: parent
|
||||
color: DefaultStyle.grey_100
|
||||
MediumButton {
|
||||
id: expandButton
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Math.round(4 * DefaultStyle.dp)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
style: ButtonStyle.noBackgroundOrange
|
||||
icon.source: checked ? AppIcons.downArrow : AppIcons.upArrow
|
||||
checkable: true
|
||||
anchors.leftMargin: Math.round(18 * DefaultStyle.dp)
|
||||
anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
|
||||
Control.ScrollBar.vertical: scrollbar
|
||||
|
||||
Popup {
|
||||
id: emojiPickerPopup
|
||||
y: Math.round(chatMessagesListView.y + chatMessagesListView.height - height - 8*DefaultStyle.dp)
|
||||
x: Math.round(chatMessagesListView.x + 8*DefaultStyle.dp)
|
||||
width: Math.round(393 * DefaultStyle.dp)
|
||||
height: Math.round(291 * DefaultStyle.dp)
|
||||
visible: messageSender.emojiPickerButtonChecked
|
||||
closePolicy: Popup.CloseOnPressOutside
|
||||
onClosed: messageSender.emojiPickerButtonChecked = false
|
||||
padding: 10 * DefaultStyle.dp
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
id: buttonBackground
|
||||
anchors.fill: parent
|
||||
color: DefaultStyle.grey_0
|
||||
radius: Math.round(20 * DefaultStyle.dp)
|
||||
}
|
||||
MultiEffect {
|
||||
anchors.fill: buttonBackground
|
||||
source: buttonBackground
|
||||
shadowEnabled: true
|
||||
shadowColor: DefaultStyle.grey_1000
|
||||
shadowBlur: 0.1
|
||||
shadowOpacity: 0.5
|
||||
}
|
||||
}
|
||||
contentItem: EmojiPicker {
|
||||
id: emojiPicker
|
||||
editor: messageSender.textArea
|
||||
}
|
||||
}
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
spacing: Math.round(20 * DefaultStyle.dp)
|
||||
RowLayout {
|
||||
spacing: Math.round(16 * DefaultStyle.dp)
|
||||
BigButton {
|
||||
id: emojiPickerButton
|
||||
style: ButtonStyle.noBackground
|
||||
checkable: true
|
||||
icon.source: checked ? AppIcons.closeX : AppIcons.smiley
|
||||
}
|
||||
BigButton {
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.paperclip
|
||||
onClicked: {
|
||||
console.log("TODO : open explorer to attach file")
|
||||
}
|
||||
}
|
||||
Control.Control {
|
||||
Layout.fillWidth: true
|
||||
leftPadding: Math.round(15 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(15 * DefaultStyle.dp)
|
||||
topPadding: Math.round(15 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(15 * DefaultStyle.dp)
|
||||
background: Rectangle {
|
||||
id: inputBackground
|
||||
anchors.fill: parent
|
||||
radius: Math.round(35 * DefaultStyle.dp)
|
||||
color: DefaultStyle.grey_0
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: sendingTextArea.forceActiveFocus()
|
||||
cursorShape: Qt.IBeamCursor
|
||||
}
|
||||
}
|
||||
contentItem: RowLayout {
|
||||
Flickable {
|
||||
id: sendingAreaFlickable
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: parent.width - stackButton.width
|
||||
Layout.preferredHeight: Math.min(Math.round(60 * DefaultStyle.dp), contentHeight)
|
||||
Binding {
|
||||
target: sendingAreaFlickable
|
||||
when: expandButton.checked
|
||||
property: "Layout.preferredHeight"
|
||||
value: Math.round(250 * DefaultStyle.dp)
|
||||
restoreMode: Binding.RestoreBindingOrValue
|
||||
}
|
||||
Layout.fillHeight: true
|
||||
contentHeight: sendingTextArea.contentHeight
|
||||
contentWidth: width
|
||||
ScrollBar {
|
||||
id: scrollbar
|
||||
visible: chatMessagesListView.contentHeight > parent.height
|
||||
active: visible
|
||||
anchors.top: chatMessagesListView.top
|
||||
anchors.bottom: chatMessagesListView.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Math.round(5 * DefaultStyle.dp)
|
||||
policy: Control.ScrollBar.AsNeeded
|
||||
}
|
||||
}
|
||||
Control.Control {
|
||||
id: selectedFilesArea
|
||||
visible: selectedFiles.count > 0
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(104 * DefaultStyle.dp)
|
||||
topPadding: Math.round(12 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(12 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(19 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(19 * DefaultStyle.dp)
|
||||
|
||||
function ensureVisible(r) {
|
||||
if (contentX >= r.x)
|
||||
contentX = r.x;
|
||||
else if (contentX+width <= r.x+r.width)
|
||||
contentX = r.x+r.width-width;
|
||||
if (contentY >= r.y)
|
||||
contentY = r.y;
|
||||
else if (contentY+height <= r.y+r.height)
|
||||
contentY = r.y+r.height-height;
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: sendingTextArea
|
||||
width: sendingAreaFlickable.width
|
||||
height: sendingAreaFlickable.height
|
||||
textFormat: TextEdit.AutoText
|
||||
//: Say something… : placeholder text for sending message text area
|
||||
placeholderText: qsTr("chat_view_send_area_placeholder_text")
|
||||
placeholderTextColor: DefaultStyle.main2_400
|
||||
color: DefaultStyle.main2_700
|
||||
font {
|
||||
pixelSize: Typography.p1.pixelSize
|
||||
weight: Typography.p1.weight
|
||||
}
|
||||
onCursorRectangleChanged: sendingAreaFlickable.ensureVisible(cursorRectangle)
|
||||
wrapMode: TextEdit.WordWrap
|
||||
Component.onCompleted: {
|
||||
if (mainItem.chat) text = mainItem.chat.core.sendingText
|
||||
}
|
||||
onTextChanged: {
|
||||
if (text !== "" && mainItem.chat.core.composingName !== "") {
|
||||
mainItem.chat.core.lCompose()
|
||||
}
|
||||
mainItem.chat.core.sendingText = text
|
||||
}
|
||||
Keys.onPressed: (event) => {
|
||||
event.accepted = false
|
||||
if (UtilsCpp.isEmptyMessage(sendingTextArea.text)) return
|
||||
if (!(event.modifiers & Qt.ShiftModifier) && (event.key == Qt.Key_Return || event.key == Qt.Key_Enter)) {
|
||||
mainItem.chat.core.lSendTextMessage(sendingTextArea.text)
|
||||
sendingTextArea.clear()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StackLayout {
|
||||
id: stackButton
|
||||
currentIndex: sendingTextArea.text.length === 0 ? 0 : 1
|
||||
BigButton {
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.microphone
|
||||
onClicked: {
|
||||
console.log("TODO : go to record message")
|
||||
}
|
||||
}
|
||||
BigButton {
|
||||
style: ButtonStyle.noBackgroundOrange
|
||||
icon.source: AppIcons.paperPlaneRight
|
||||
onClicked: {
|
||||
mainItem.chat.core.lSendTextMessage(sendingTextArea.text)
|
||||
sendingTextArea.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function addFile(path) {
|
||||
contents.addFile(path)
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: selectedFilesArea.topPadding
|
||||
anchors.rightMargin: selectedFilesArea.rightPadding
|
||||
icon.source: AppIcons.closeX
|
||||
style: ButtonStyle.noBackground
|
||||
onClicked: {
|
||||
contents.clear()
|
||||
}
|
||||
}
|
||||
background: Item{
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
color: DefaultStyle.grey_0
|
||||
border.color: DefaultStyle.main2_100
|
||||
border.width: Math.round(2 * DefaultStyle.dp)
|
||||
radius: Math.round(20 * DefaultStyle.dp)
|
||||
height: parent.height / 2
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 2 * parent.height / 3
|
||||
}
|
||||
}
|
||||
contentItem: ListView {
|
||||
id: selectedFiles
|
||||
orientation: ListView.Horizontal
|
||||
spacing: Math.round(16 * DefaultStyle.dp)
|
||||
model: ChatMessageContentProxy {
|
||||
id: contents
|
||||
filterType: ChatMessageContentProxy.FilterContentType.File
|
||||
}
|
||||
delegate: Item {
|
||||
width: Math.round(80 * DefaultStyle.dp)
|
||||
height: Math.round(80 * DefaultStyle.dp)
|
||||
FileView {
|
||||
contentGui: modelData
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
width: Math.round(69 * DefaultStyle.dp)
|
||||
height: Math.round(69 * DefaultStyle.dp)
|
||||
}
|
||||
RoundButton {
|
||||
icon.source: AppIcons.closeX
|
||||
icon.width: Math.round(12 * DefaultStyle.dp)
|
||||
icon.height: Math.round(12 * DefaultStyle.dp)
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
style: ButtonStyle.numericPad
|
||||
shadowEnabled: true
|
||||
padding: Math.round(3 * DefaultStyle.dp)
|
||||
onClicked: contents.removeContent(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
ChatDroppableTextArea {
|
||||
id: messageSender
|
||||
visible: !mainItem.chat.core.isReadOnly
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: height
|
||||
Component.onCompleted: {
|
||||
if (mainItem.chat) text = mainItem.chat.core.sendingText
|
||||
}
|
||||
onTextChanged: {
|
||||
if (text !== "" && mainItem.chat.core.composingName !== "") {
|
||||
mainItem.chat.core.lCompose()
|
||||
}
|
||||
mainItem.chat.core.sendingText = text
|
||||
}
|
||||
onSendText: mainItem.chat.core.lSendTextMessage(text)
|
||||
onDropped: (files) => {
|
||||
files.forEach(selectedFilesArea.addFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Rectangle {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,13 @@ QtObject {
|
|||
property string avatar: "image://internal/randomAvatar.png"
|
||||
property string pause: "image://internal/pause.svg"
|
||||
property string play: "image://internal/play.svg"
|
||||
property string playFill: "image://internal/play-fill.svg"
|
||||
property string filePdf: "image://internal/file-pdf.svg"
|
||||
property string fileText: "image://internal/file-text.svg"
|
||||
property string fileImage: "image://internal/file-image.svg"
|
||||
property string filePlus: "image://internal/file-plus.svg"
|
||||
property string download: "image://internal/download-simple.svg"
|
||||
property string file: "image://internal/file.svg"
|
||||
property string paperclip: "image://internal/paperclip.svg"
|
||||
property string paperPlaneRight: "image://internal/paper-plane-right.svg"
|
||||
property string smiley: "image://internal/smiley.svg"
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ QtObject {
|
|||
|
||||
onDpChanged: {
|
||||
console.log("Screen ratio changed", dp)
|
||||
AppCpp.setScreenRatio(dp)
|
||||
}
|
||||
|
||||
// Warning: Qt 6.8.1 (current version) and previous versions, Qt only support COLRv0 fonts. Don't try to use v1.
|
||||
|
|
|
|||
73
Linphone/view/Style/FileViewStyle.qml
Normal file
73
Linphone/view/Style/FileViewStyle.qml
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
pragma Singleton
|
||||
import QtQml
|
||||
|
||||
import Linphone
|
||||
|
||||
// =============================================================================
|
||||
|
||||
QtObject {
|
||||
property string sectionName : 'FileView'
|
||||
|
||||
property int height: 120
|
||||
property int heightbetter: 200
|
||||
property int iconSize: 18
|
||||
property int margins: 8
|
||||
property int spacing: 8
|
||||
property int width: 100
|
||||
|
||||
property QtObject name: QtObject{
|
||||
property int pointSize: Math.round(DefaultStyle.dp * 7)
|
||||
}
|
||||
|
||||
property QtObject download: QtObject{
|
||||
property string icon: AppIcons.download
|
||||
property int height: 20
|
||||
property int pointSize: Math.round(DefaultStyle.dp * 8)
|
||||
property int iconSize: 30
|
||||
}
|
||||
property QtObject thumbnailVideoIcon: QtObject {
|
||||
property int iconSize: 40
|
||||
property string name : 'play'
|
||||
property string icon : AppIcons.playFill
|
||||
}
|
||||
property QtObject animation: QtObject {
|
||||
property int duration: 300
|
||||
property real to: 1.7
|
||||
property real thumbnailTo: 2
|
||||
}
|
||||
|
||||
property QtObject extension: QtObject {
|
||||
property string icon: AppIcons.file
|
||||
property string imageIcon: AppIcons.fileImage
|
||||
property int iconSize: 60
|
||||
property int internalSize: 37
|
||||
property int radius: Math.round(5 * DefaultStyle.dp)
|
||||
|
||||
property QtObject background: QtObject {
|
||||
property var color: DefaultStyle.grey_0
|
||||
property var borderColor: DefaultStyle.grey_0
|
||||
}
|
||||
|
||||
property QtObject text: QtObject {
|
||||
property var color: DefaultStyle.grey_0
|
||||
property int pointSize: Math.round(DefaultStyle.dp * 9)
|
||||
}
|
||||
}
|
||||
|
||||
property QtObject status: QtObject {
|
||||
property int spacing: 4
|
||||
|
||||
property QtObject bar: QtObject {
|
||||
property int height: 6
|
||||
property int radius: 3
|
||||
|
||||
property QtObject background: QtObject {
|
||||
property var color: DefaultStyle.grey_0
|
||||
}
|
||||
|
||||
property QtObject contentItem: QtObject {
|
||||
property var color: DefaultStyle.grey_0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ QtObject {
|
|||
weight: Math.min(Math.round(700 * DefaultStyle.dp), 1000)
|
||||
})
|
||||
|
||||
// Text/P2 - Large Bold, reduced paragraph text
|
||||
// Text/P2l - Large Bold, reduced paragraph text
|
||||
property font p2l: Qt.font( {
|
||||
family: DefaultStyle.defaultFont,
|
||||
pixelSize: Math.round(14 * DefaultStyle.dp),
|
||||
|
|
@ -73,7 +73,7 @@ QtObject {
|
|||
weight: Math.min(Math.round(400 * DefaultStyle.dp), 1000)
|
||||
})
|
||||
|
||||
// Text/P1 - Paragraph text
|
||||
// Text/P1s - Paragraph text
|
||||
property font p1s: Qt.font( {
|
||||
family: DefaultStyle.defaultFont,
|
||||
pixelSize: Math.round(13 * DefaultStyle.dp),
|
||||
|
|
@ -101,4 +101,24 @@ QtObject {
|
|||
weight: Math.min(Math.round(600 * DefaultStyle.dp), 1000)
|
||||
})
|
||||
|
||||
// FileView/F1 - File View name text
|
||||
property font f1: Qt.font( {
|
||||
family: DefaultStyle.defaultFont,
|
||||
pixelSize: Math.round(11 * DefaultStyle.dp),
|
||||
weight: Math.min(Math.round(700 * DefaultStyle.dp), 1000)
|
||||
})
|
||||
|
||||
// FileView/F1light - File View size text
|
||||
property font f1l: Qt.font( {
|
||||
family: DefaultStyle.defaultFont,
|
||||
pixelSize: Math.round(10 * DefaultStyle.dp),
|
||||
weight: Math.min(Math.round(500 * DefaultStyle.dp), 1000)
|
||||
})
|
||||
|
||||
// FileView/F1light - Duration text
|
||||
property font d1: Qt.font( {
|
||||
family: DefaultStyle.defaultFont,
|
||||
pixelSize: Math.round(8 * DefaultStyle.dp),
|
||||
weight: Math.min(Math.round(600 * DefaultStyle.dp), 1000)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
// No background light
|
||||
var noBackgroundLight = {
|
||||
color: {
|
||||
normal: "#00000000",
|
||||
hovered: "#00000000",
|
||||
pressed: "#00000000",
|
||||
checked: "#00000000"
|
||||
},
|
||||
text: {
|
||||
normal: Linphone.DefaultStyle.main2_200,
|
||||
hovered: Linphone.DefaultStyle.main2_300,
|
||||
pressed: Linphone.DefaultStyle.main2_400,
|
||||
checked: Linphone.DefaultStyle.main1_500main
|
||||
},
|
||||
image: {
|
||||
normal: Linphone.DefaultStyle.main2_200,
|
||||
hovered: Linphone.DefaultStyle.main2_300,
|
||||
pressed: Linphone.DefaultStyle.main2_400,
|
||||
checked: Linphone.DefaultStyle.main1_500main,
|
||||
}
|
||||
}
|
||||
|
||||
// No background
|
||||
var noBackground = {
|
||||
color: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue