mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-05-01 17:16:24 +00:00
Display video thumbnails.
Crop thumbnail and pictures if distored.
This commit is contained in:
parent
c8b80c4282
commit
3d7a9acc25
20 changed files with 555 additions and 387 deletions
|
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
- Primary color for links in chat.
|
||||
- Replace double click on avatar by a simple click for copying address into the SmartSearchBar.
|
||||
- Bubble chat layout.
|
||||
|
||||
### Added
|
||||
- VFS Encryption
|
||||
|
|
@ -26,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Emojis picker.
|
||||
- Text edit in chat can now understand rich texts.
|
||||
- Create thumbnails into memory instead of disk.
|
||||
- Display video thumbnails.
|
||||
- Crop thumbnail and pictures if distored.
|
||||
|
||||
|
||||
### Removed
|
||||
- Picture zoom on mouse over.
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ if( WIN32)
|
|||
endif()
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)#useful for config.h
|
||||
|
||||
set(QT5_PACKAGES Core Gui Quick Widgets QuickControls2 Svg LinguistTools Concurrent Network Test Qml)
|
||||
set(QT5_PACKAGES Core Gui Quick Widgets QuickControls2 Svg LinguistTools Concurrent Network Test Qml Multimedia)
|
||||
|
||||
if(ENABLE_APP_OAUTH2)
|
||||
list(APPEND QT5_PACKAGES NetworkAuth)
|
||||
|
|
|
|||
46
linphone-app/assets/images/thumbnail_video_custom.svg
Normal file
46
linphone-app/assets/images/thumbnail_video_custom.svg
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 80 80"
|
||||
xml:space="preserve"
|
||||
id="svg8"
|
||||
sodipodi:docname="thumbnail_video_custom.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="3.2539062"
|
||||
inkscape:cx="117.55102"
|
||||
inkscape:cy="54.088836"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1043"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
|
||||
<defs
|
||||
id="defs2">
|
||||
</defs>
|
||||
<g
|
||||
style="opacity:1;fill:none;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
transform="matrix(0.55555556,0,0,0.55555556,15.40659,15.406593)"
|
||||
id="g6">
|
||||
<path
|
||||
d="M 45,0 C 20.147,0 0,20.147 0,45 0,69.853 20.147,90 45,90 69.853,90 90,69.853 90,45 90,20.147 69.853,0 45,0 Z M 62.251,46.633 37.789,60.756 C 36.531,61.482 34.96,60.575 34.96,59.123 V 30.877 c 0,-1.452 1.572,-2.36 2.829,-1.634 L 62.25,43.366 c 1.258,0.726 1.258,2.541 10e-4,3.267 z"
|
||||
style="opacity:1;fill:#000000;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
stroke-linecap="round"
|
||||
id="path4" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -164,6 +164,7 @@
|
|||
<file>assets/images/stop_fullscreen_custom.svg</file>
|
||||
<file>assets/images/timer_custom.svg</file>
|
||||
<file>assets/images/tel_keypad_voicemail_custom.svg</file>
|
||||
<file>assets/images/thumbnail_video_custom.svg</file>
|
||||
<file>assets/images/tooltip_arrow_bottom_custom.svg</file>
|
||||
<file>assets/images/tooltip_arrow_left_custom.svg</file>
|
||||
<file>assets/images/tooltip_arrow_right_custom.svg</file>
|
||||
|
|
@ -412,6 +413,7 @@
|
|||
<file>ui/modules/Linphone/Styles/Dialog/OnlineInstallerDialogStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/Dialog/SipAddressDialogStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/Dialog/ZrtpTokenAuthenticationDialogStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/File/FileViewStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/History/HistoryStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/Menus/SipAddressesMenuStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/Menus/IncallMenuStyle.qml</file>
|
||||
|
|
|
|||
|
|
@ -35,8 +35,17 @@ ExternalImageProvider::ExternalImageProvider () : QQuickImageProvider(
|
|||
) {
|
||||
}
|
||||
|
||||
QImage ExternalImageProvider::requestImage (const QString &id, QSize *size, const QSize &) {
|
||||
QImage image(Utils::getImage(QUrl::fromPercentEncoding(id.toUtf8())));
|
||||
*size = image.size();
|
||||
return image;
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <QQmlApplicationEngine>
|
||||
#include <QImageReader>
|
||||
#include <QPainter>
|
||||
#include <QMediaPlayer>
|
||||
#include "app/App.hpp"
|
||||
|
||||
#include "utils/Utils.hpp"
|
||||
|
|
@ -30,6 +31,75 @@
|
|||
#include "components/core/CoreManager.hpp"
|
||||
#include "utils/QExifImageHeader.hpp"
|
||||
|
||||
#include <QVideoSurfaceFormat>
|
||||
|
||||
VideoFrameGrabber::VideoFrameGrabber( QObject *parent)
|
||||
: QAbstractVideoSurface(parent){
|
||||
}
|
||||
|
||||
QList<QVideoFrame::PixelFormat> VideoFrameGrabber::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const {
|
||||
Q_UNUSED(handleType);
|
||||
return QList<QVideoFrame::PixelFormat>()
|
||||
<< QVideoFrame::Format_ARGB32
|
||||
<< QVideoFrame::Format_ARGB32_Premultiplied
|
||||
<< QVideoFrame::Format_RGB32
|
||||
<< QVideoFrame::Format_RGB24
|
||||
<< QVideoFrame::Format_RGB565
|
||||
<< QVideoFrame::Format_RGB555
|
||||
<< QVideoFrame::Format_ARGB8565_Premultiplied
|
||||
<< QVideoFrame::Format_BGRA32
|
||||
<< QVideoFrame::Format_BGRA32_Premultiplied
|
||||
<< QVideoFrame::Format_BGR32
|
||||
<< QVideoFrame::Format_BGR24
|
||||
<< QVideoFrame::Format_BGR565
|
||||
<< QVideoFrame::Format_BGR555
|
||||
<< QVideoFrame::Format_BGRA5658_Premultiplied
|
||||
<< QVideoFrame::Format_AYUV444
|
||||
<< QVideoFrame::Format_AYUV444_Premultiplied
|
||||
<< QVideoFrame::Format_YUV444
|
||||
<< QVideoFrame::Format_YUV420P
|
||||
<< QVideoFrame::Format_YV12
|
||||
<< QVideoFrame::Format_UYVY
|
||||
<< QVideoFrame::Format_YUYV
|
||||
<< QVideoFrame::Format_NV12
|
||||
<< QVideoFrame::Format_NV21
|
||||
<< QVideoFrame::Format_IMC1
|
||||
<< QVideoFrame::Format_IMC2
|
||||
<< QVideoFrame::Format_IMC3
|
||||
<< QVideoFrame::Format_IMC4
|
||||
<< QVideoFrame::Format_Y8
|
||||
<< QVideoFrame::Format_Y16
|
||||
<< QVideoFrame::Format_Jpeg
|
||||
<< QVideoFrame::Format_CameraRaw
|
||||
<< QVideoFrame::Format_AdobeDng;
|
||||
}
|
||||
|
||||
bool VideoFrameGrabber::isFormatSupported(const QVideoSurfaceFormat &format) const {
|
||||
const QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat());
|
||||
const QSize size = format.frameSize();
|
||||
|
||||
return imageFormat != QImage::Format_Invalid
|
||||
&& !size.isEmpty()
|
||||
&& format.handleType() == QAbstractVideoBuffer::NoHandle;
|
||||
}
|
||||
|
||||
bool VideoFrameGrabber::start(const QVideoSurfaceFormat &format){
|
||||
return QAbstractVideoSurface::start(format);
|
||||
}
|
||||
|
||||
void VideoFrameGrabber::stop() {
|
||||
QAbstractVideoSurface::stop();
|
||||
}
|
||||
|
||||
bool VideoFrameGrabber::present(const QVideoFrame &frame){
|
||||
if (frame.isValid()) {
|
||||
emit frameAvailable(frame.image());
|
||||
return true;
|
||||
}else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
|
||||
ImageModel::ImageModel (const QString& id, const QString& path, const QString& description, QObject * parent) : QObject(parent) {
|
||||
|
|
@ -88,6 +158,25 @@ QImage ImageModel::createThumbnail(const QString& path){
|
|||
QByteArray format = reader.format();
|
||||
if(!format.isEmpty())
|
||||
originalImage = QImage(path, format);
|
||||
else if(Utils::isVideo(path)){
|
||||
QMediaPlayer player;
|
||||
player.setMedia(QUrl::fromLocalFile(path));
|
||||
player.setPosition(player.duration() / 2);
|
||||
VideoFrameGrabber grabber;
|
||||
player.setVideoOutput(&grabber);
|
||||
QObject * context = new QObject();
|
||||
QObject::connect(&grabber, &VideoFrameGrabber::frameAvailable, context, [&context,&originalImage, &player](QImage frame) mutable{
|
||||
originalImage = frame.copy();
|
||||
player.stop();
|
||||
context->deleteLater();// This will destroy context and initializer
|
||||
context = nullptr;
|
||||
}, Qt::DirectConnection);
|
||||
player.play();
|
||||
do{
|
||||
qApp->processEvents();
|
||||
}while(player.state() != QMediaPlayer::State::StoppedState);
|
||||
if(context) context->deleteLater();
|
||||
}
|
||||
}
|
||||
if (!originalImage.isNull()){
|
||||
int rotation = 0;
|
||||
|
|
@ -101,26 +190,27 @@ QImage ImageModel::createThumbnail(const QString& path){
|
|||
painter.drawImage(0, 0, originalImage);
|
||||
//--------------------
|
||||
double factor = image.width() / (double)image.height();
|
||||
if(factor < 0.2 || factor > 5){
|
||||
qInfo() << QStringLiteral("Cannot create thumbnails because size factor (%1) is too low/much of: `%2`.").arg(factor).arg(path);
|
||||
}else {
|
||||
thumbnail = image.scaled(
|
||||
Qt::AspectRatioMode aspectRatio = Qt::KeepAspectRatio;
|
||||
if(factor < 0.2 || factor > 5)
|
||||
aspectRatio = Qt::KeepAspectRatioByExpanding;
|
||||
thumbnail = image.scaled(
|
||||
Constants::ThumbnailImageFileWidth, Constants::ThumbnailImageFileHeight,
|
||||
Qt::KeepAspectRatio, Qt::SmoothTransformation
|
||||
aspectRatio , Qt::SmoothTransformation
|
||||
);
|
||||
if(aspectRatio == Qt::KeepAspectRatioByExpanding)
|
||||
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.mirrored(true, false);
|
||||
}
|
||||
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.mirrored(true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,25 @@
|
|||
#include <QColor>
|
||||
|
||||
#include "utils/LinphoneEnums.hpp"
|
||||
#include <QAbstractVideoSurface>
|
||||
|
||||
|
||||
class VideoFrameGrabber : public QAbstractVideoSurface {
|
||||
Q_OBJECT
|
||||
public:
|
||||
VideoFrameGrabber(QObject *parent = 0);
|
||||
|
||||
QList<QVideoFrame::PixelFormat> supportedPixelFormats(
|
||||
QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const override;
|
||||
bool isFormatSupported(const QVideoSurfaceFormat &format) const override;
|
||||
|
||||
bool start(const QVideoSurfaceFormat &format) override;
|
||||
void stop() override;
|
||||
bool present(const QVideoFrame &frame) override;
|
||||
|
||||
signals:
|
||||
void frameAvailable(QImage frame);
|
||||
};
|
||||
|
||||
class ImageModel : public QObject {
|
||||
Q_OBJECT
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ ParticipantDeviceListModel::ParticipantDeviceListModel (CallModel * callModel, Q
|
|||
}
|
||||
}
|
||||
|
||||
ParticipantDeviceListModel::~ParticipantDeviceListModel(){
|
||||
}
|
||||
|
||||
void ParticipantDeviceListModel::initConferenceModel(){
|
||||
if(!mInitialized && mCallModel){
|
||||
auto conferenceModel = mCallModel->getConferenceSharedModel();
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class ParticipantDeviceListModel : public ProxyListModel {
|
|||
public:
|
||||
ParticipantDeviceListModel (std::shared_ptr<linphone::Participant> participant, QObject *parent = nullptr);
|
||||
ParticipantDeviceListModel (CallModel * callModel, QObject *parent = nullptr);
|
||||
|
||||
~ParticipantDeviceListModel();
|
||||
void initConferenceModel();
|
||||
void updateDevices(std::shared_ptr<linphone::Participant> participant);
|
||||
void updateDevices(const std::list<std::shared_ptr<linphone::ParticipantDevice>>& devices, const bool& isMe);
|
||||
|
|
|
|||
|
|
@ -650,6 +650,10 @@ bool Utils::isSupportedForDisplay(const QString& path){
|
|||
return !QMimeDatabase().mimeTypeForFile(path).name().contains("application");// "for pdf : "application/pdf". Note : Make an exception when supported.
|
||||
}
|
||||
|
||||
bool Utils::canHaveThumbnail(const QString& path){
|
||||
return isImage(path) || isAnimatedImage(path) || isPdf(path) || isVideo(path);
|
||||
}
|
||||
|
||||
bool Utils::isPhoneNumber(const QString& txt){
|
||||
auto core = CoreManager::getInstance()->getCore();
|
||||
if(!core)
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ public:
|
|||
Q_INVOKABLE static bool isVideo(const QString& path);
|
||||
Q_INVOKABLE static bool isPdf(const QString& path);
|
||||
Q_INVOKABLE static bool isSupportedForDisplay(const QString& path);
|
||||
Q_INVOKABLE static bool canHaveThumbnail(const QString& path);
|
||||
Q_INVOKABLE static bool isPhoneNumber(const QString& txt);
|
||||
Q_INVOKABLE static bool isUsername(const QString& txt); // Check with Regex
|
||||
Q_INVOKABLE QSize getImageSize(const QString& url);
|
||||
|
|
|
|||
|
|
@ -47,10 +47,6 @@ Loader{
|
|||
property int fitWidth: layout.fitWidth + ChatCalendarMessageStyle.leftMargin+ChatCalendarMessageStyle.rightMargin
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: ChatCalendarMessageStyle.widthMargin
|
||||
anchors.rightMargin: ChatCalendarMessageStyle.widthMargin
|
||||
anchors.topMargin: ChatCalendarMessageStyle.topMargin
|
||||
anchors.bottomMargin: ChatCalendarMessageStyle.bottomMargin
|
||||
|
||||
clip: false
|
||||
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically)
|
|||
id: mainItem
|
||||
property ChatMessageModel chatMessageModel: null
|
||||
property int availableWidth //const
|
||||
property int fileWidth: ChatStyle.entry.message.file.height * 4 / 3 + 2*ChatStyle.entry.message.file.margins
|
||||
property int fileWidth: FileViewStyle.height * 4 / 3 + 2*ChatStyle.entry.message.file.margins
|
||||
|
||||
// Readonly
|
||||
property int bestWidth: Math.min(availableWidth, Math.max(filesBestWidth, conferencesBestWidth, textsBestWidth, voicesBestWidth))
|
||||
property int bestWidth: Math.min(availableWidth, Math.max(filesCount*filesBestWidth, conferencesBestWidth, textsBestWidth, voicesBestWidth))
|
||||
property int filesBestWidth: 0
|
||||
property int filesCount: 0
|
||||
property int conferencesCount: 0
|
||||
|
|
@ -49,11 +49,12 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically)
|
|||
property int fileBackgroundRadius: ChatStyle.entry.message.file.extension.radius
|
||||
|
||||
active: chatMessageModel
|
||||
|
||||
|
||||
sourceComponent: Component{
|
||||
Column{
|
||||
id: mainComponent
|
||||
spacing: 0
|
||||
padding: 10
|
||||
function updateFilesBestWidth(){
|
||||
var newBestWidth = 0
|
||||
var count = 0
|
||||
|
|
@ -63,13 +64,10 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically)
|
|||
var a = item.fitWidth
|
||||
if(a) {
|
||||
++count
|
||||
newBestWidth = Math.max(newBestWidth,a)
|
||||
newBestWidth = Math.max(newBestWidth,a+2*ChatStyle.entry.message.file.margins)
|
||||
}
|
||||
}
|
||||
}
|
||||
if(count > 1){
|
||||
newBestWidth = Math.max(newBestWidth, mainItem.fileWidth*count)
|
||||
}
|
||||
mainItem.filesCount = count
|
||||
mainItem.filesBestWidth = newBestWidth
|
||||
}
|
||||
|
|
@ -87,7 +85,7 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically)
|
|||
}
|
||||
ListView {
|
||||
id: messagesVoicesList
|
||||
width: parent.width
|
||||
width: parent.width-2*mainComponent.padding
|
||||
visible: count > 0
|
||||
spacing: 0
|
||||
clip: false
|
||||
|
|
@ -115,7 +113,7 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically)
|
|||
// CONFERENCE
|
||||
ListView {
|
||||
id: messagesConferencesList
|
||||
width: parent.width
|
||||
width: parent.width-2*mainComponent.padding
|
||||
visible: count > 0
|
||||
spacing: 0
|
||||
clip: false
|
||||
|
|
@ -151,14 +149,14 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically)
|
|||
id: messageFilesList
|
||||
property alias count: repeater.count
|
||||
visible: count > 0
|
||||
clip: false
|
||||
clip: false
|
||||
width: parent.width-2*mainComponent.padding
|
||||
|
||||
property int availableSection: mainItem.availableWidth / mainItem.fileWidth
|
||||
property int bestFitSection: mainItem.bestWidth / mainItem.fileWidth
|
||||
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: 0
|
||||
width: parent.width
|
||||
rowSpacing: ChatStyle.entry.message.file.spacing
|
||||
Repeater{
|
||||
id: repeater
|
||||
model: ContentProxyModel{
|
||||
|
|
@ -166,6 +164,12 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically)
|
|||
chatMessageModel: mainItem.chatMessageModel
|
||||
}
|
||||
ChatFileMessage{
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: fitHeight
|
||||
Layout.preferredWidth: fitWidth
|
||||
Layout.maximumWidth: fitWidth
|
||||
Layout.maximumHeight: fitHeight
|
||||
contentModel: $modelData
|
||||
onIsHoveringChanged: mainItem.isFileHoveringChanged(isHovering)
|
||||
borderWidth: mainItem.fileBorderWidth
|
||||
|
|
@ -178,7 +182,7 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically)
|
|||
// TEXTS
|
||||
ListView {
|
||||
id: messagesTextsList
|
||||
width: parent.width
|
||||
width: parent.width-2*mainComponent.padding
|
||||
visible: count > 0
|
||||
spacing: 0
|
||||
clip: false
|
||||
|
|
@ -192,11 +196,14 @@ Loader{// Use of Loader because of Repeater (items cannot be loaded dynamically)
|
|||
function updateBestWidth(){
|
||||
var newWidth = mainComponent.updateListBestWidth(messagesTextsList)
|
||||
mainItem.textsCount = newWidth[0]
|
||||
mainItem.textsBestWidth = newWidth[1]
|
||||
// Padding is takken account because it is used for the whole bubble.
|
||||
// We add 1 pixel to avoid implicit new line computation (Guess : float computation from Qt)
|
||||
mainItem.textsBestWidth = newWidth[1] + 2*mainComponent.padding + 1
|
||||
}
|
||||
Component.onCompleted: messagesTextsList.updateBestWidth()
|
||||
delegate:
|
||||
ChatTextMessage {
|
||||
width: parent.width
|
||||
contentModel: $modelData
|
||||
onLastTextSelectedChanged: mainItem.lastTextSelectedChanged(lastTextSelected)
|
||||
color: mainItem.useTextColor
|
||||
|
|
|
|||
|
|
@ -13,270 +13,35 @@ import UtilsCpp 1.0
|
|||
|
||||
// =============================================================================
|
||||
// TODO : into Loader
|
||||
Row {
|
||||
Item {
|
||||
id:mainRow
|
||||
|
||||
property ChatMessageModel chatMessageModel: contentModel && contentModel.chatMessageModel
|
||||
property ContentModel contentModel
|
||||
property bool isOutgoing : chatMessageModel && ( chatMessageModel.isOutgoing || chatMessageModel.state == LinphoneEnums.ChatMessageStateIdle);
|
||||
property int fitHeight: mainRow.isAnimatedImage ? ChatStyle.entry.message.file.heightbetter : ChatStyle.entry.message.file.height
|
||||
property int fitWidth: fitHeight * 4 / 3 + 2*ChatStyle.entry.message.file.margins
|
||||
property int borderWidth : 0
|
||||
property color backgroundColor: ChatStyle.entry.message.file.extension.background.colorModel.color
|
||||
property int backgroundRadius: ChatStyle.entry.message.file.extension.radius
|
||||
/*
|
||||
property int fitWidth: visible
|
||||
? Math.max( Math.max((thumbnailProvider.sourceComponent == extension
|
||||
? thumbnailProvider.item.fitWidth
|
||||
: 0)
|
||||
, thumbnailProvider.width + 3*ChatStyle.entry.message.file.margins)
|
||||
, Math.max(ChatStyle.entry.message.file.width, ChatStyle.entry.message.outgoing.areaSize))
|
||||
: 0
|
||||
property int fitHeight: visible ? rectangle.height : 0
|
||||
*/
|
||||
property bool isAnimatedImage : mainRow.contentModel && mainRow.contentModel.wasDownloaded && UtilsCpp.isAnimatedImage(mainRow.contentModel.filePath)
|
||||
property bool haveThumbnail: mainRow.contentModel && mainRow.contentModel.thumbnail
|
||||
property bool isHovering: thumbnailProvider.state == 'hovered'
|
||||
property int fitHeight: fileView.fitHeight
|
||||
property int fitWidth: fileView.fitWidth
|
||||
property alias borderWidth: fileView.borderWidth
|
||||
property alias backgroundColor: fileView.backgroundColor
|
||||
property alias backgroundRadius: fileView.backgroundRadius
|
||||
property alias isHovering: fileView.isHovering
|
||||
|
||||
signal copyAllDone()
|
||||
signal copySelectionDone()
|
||||
signal forwardClicked()
|
||||
height: fitHeight
|
||||
width: fitWidth
|
||||
visible: true
|
||||
// ---------------------------------------------------------------------------
|
||||
// File message.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Item{
|
||||
width: mainRow.width
|
||||
height:rectangle.height
|
||||
Rectangle {
|
||||
id: rectangle
|
||||
color: 'transparent'
|
||||
anchors.fill: parent
|
||||
radius: ChatStyle.entry.message.radius
|
||||
|
||||
Rectangle {
|
||||
id: rectangle
|
||||
|
||||
readonly property bool isError: chatMessageModel && Utils.includes([
|
||||
LinphoneEnums.ChatMessageStateFileTransferError,
|
||||
LinphoneEnums.ChatMessageStateNotDelivered,
|
||||
], chatMessageModel.state)
|
||||
readonly property bool isUploaded: chatMessageModel && chatMessageModel.state == LinphoneEnums.ChatMessageStateDelivered
|
||||
readonly property bool isDelivered: chatMessageModel && chatMessageModel.state == LinphoneEnums.ChatMessageStateDeliveredToUser
|
||||
readonly property bool isRead: chatMessageModel && chatMessageModel.state == LinphoneEnums.ChatMessageStateDisplayed
|
||||
readonly property bool isTransferring: chatMessageModel && (chatMessageModel.state == LinphoneEnums.ChatMessageStateFileTransferInProgress || chatMessageModel.state == LinphoneEnums.ChatMessageStateInProgress )
|
||||
|
||||
property string thumbnail : mainRow.contentModel ? mainRow.contentModel.thumbnail : ''
|
||||
color: 'transparent'
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
height: 2*ChatStyle.entry.message.file.margins + (mainRow.isAnimatedImage
|
||||
? ChatStyle.entry.message.file.heightbetter
|
||||
: thumbnailProvider.sourceComponent == extension
|
||||
? ChatStyle.entry.message.file.height
|
||||
: ChatStyle.entry.message.file.height
|
||||
)
|
||||
radius: ChatStyle.entry.message.radius
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Thumbnail or extension.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
Component {
|
||||
id: thumbnailImage
|
||||
|
||||
Image {
|
||||
id: thumbnailImageSource
|
||||
property real scaleAnimatorTo : ChatStyle.entry.message.file.animation.thumbnailTo
|
||||
anchors.centerIn: parent
|
||||
mipmap: SettingsModel.mipmapEnabled
|
||||
source: mainRow.contentModel.thumbnail
|
||||
autoTransform: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
height: ChatStyle.entry.message.file.height
|
||||
width: height*4/3
|
||||
Component.onCompleted: mainRow.fitHeight = height
|
||||
|
||||
Loader{
|
||||
anchors.fill: parent
|
||||
sourceComponent: Image{// Better quality on zoom
|
||||
mipmap: SettingsModel.mipmapEnabled
|
||||
source:'image://external/'+mainRow.contentModel.filePath
|
||||
autoTransform: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
visible: status == Image.Ready
|
||||
}
|
||||
asynchronous: true
|
||||
active: thumbnailProvider.state == 'hovered'
|
||||
}
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: animatedImage
|
||||
|
||||
AnimatedImage {
|
||||
id: animatedImageSource
|
||||
property real scaleAnimatorTo : ChatStyle.entry.message.file.animation.to
|
||||
mipmap: SettingsModel.mipmapEnabled
|
||||
source: 'file:/'+mainRow.contentModel.filePath
|
||||
autoTransform: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
height: ChatStyle.entry.message.file.heightbetter
|
||||
width: height*4/3
|
||||
|
||||
Component.onCompleted: mainRow.fitHeight = height
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: extension
|
||||
|
||||
Rectangle {
|
||||
property int fitWidth: Math.max(downloadText.implicitWidth, Math.max(fileName.visible ? fileName.implicitWidth : 0, fileIcon.iconSize)) + 20
|
||||
//property int fitHeight: fileIcon.iconSize + (fileName.visible ? fileName.implicitHeight + ChatStyle.entry.message.file.spacing : 0 )
|
||||
// + (downloadText.visible? downloadText.implicitHeight + ChatStyle.entry.message.file.spacing : 0) + 2*ChatStyle.entry.message.file.margins
|
||||
property real scaleAnimatorTo : ChatStyle.entry.message.file.animation.to
|
||||
|
||||
anchors.centerIn: parent
|
||||
height: ChatStyle.entry.message.file.height
|
||||
width: height*4/3
|
||||
color: mainRow.backgroundColor
|
||||
radius: mainRow.backgroundRadius
|
||||
border.width: mainRow.borderWidth
|
||||
border.color: ChatStyle.entry.message.file.extension.background.borderColorModel.color
|
||||
|
||||
ColumnLayout{
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: ChatStyle.entry.message.file.margins
|
||||
anchors.bottomMargin: ChatStyle.entry.message.file.margins
|
||||
spacing: ChatStyle.entry.message.file.spacing
|
||||
Icon{
|
||||
id: fileIcon
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
icon: extensionText.text != '' ? ChatStyle.entry.message.file.extension.icon : ChatStyle.entry.message.file.extension.unknownIcon
|
||||
iconSize: ChatStyle.entry.message.file.extension.iconSize
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: iconSize
|
||||
Layout.preferredWidth: iconSize
|
||||
Text {
|
||||
id: extensionText
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottomMargin: ChatStyle.entry.message.file.spacing
|
||||
width: parent.width - 2*ChatStyle.entry.message.file.spacing
|
||||
color: ChatStyle.entry.message.file.extension.text.colorModel.color
|
||||
font.bold: true
|
||||
font.pointSize: ChatStyle.entry.message.file.extension.text.pointSize
|
||||
clip: true
|
||||
text: (mainRow.contentModel?Utils.getExtension(mainRow.contentModel.name).toUpperCase():'')
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
RoundProgressBar {
|
||||
id: progressBar
|
||||
anchors.centerIn: parent
|
||||
property int fileSize: mainRow.contentModel ? mainRow.contentModel.fileSize : 0
|
||||
to: 100
|
||||
value: mainRow.contentModel ? (fileSize>0 ? Math.floor(100 * mainRow.contentModel.fileOffset / fileSize) : 0) : to
|
||||
visible: rectangle.isTransferring && value != 0
|
||||
/* Change format? Current is %
|
||||
text: if(mainRow.contentModel){
|
||||
var fileSize = Utils.formatSize(mainRow.contentModel.fileSize)
|
||||
return progressBar.visible
|
||||
? Utils.formatSize(mainRow.contentModel.fileOffset) + '/' + fileSize
|
||||
: fileSize
|
||||
}else
|
||||
return ''
|
||||
*/
|
||||
}
|
||||
}
|
||||
Text {
|
||||
id: fileName
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: mainRow.contentModel && !mainRow.isAnimatedImage && !mainRow.haveThumbnail
|
||||
|
||||
color: ChatStyle.entry.message.file.extension.text.colorModel.color
|
||||
font.pointSize: ChatStyle.entry.message.file.name.pointSize
|
||||
wrapMode: Text.WrapAnywhere
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
maximumLineCount: 2
|
||||
|
||||
text: (mainRow.contentModel ? mainRow.contentModel.name : '')
|
||||
}
|
||||
Text{
|
||||
id: downloadText
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredHeight: visible ? contentHeight : 0
|
||||
//: 'Cancel' : Message link to cancel a transfer (upload/download)
|
||||
text: mainRow.contentModel ? rectangle.isTransferring ? qsTr('fileTransferCancel')
|
||||
//: 'Download' : Message link to download a file
|
||||
: qsTr('fileTransferDownload') +' ('+Utils.formatSize(mainRow.contentModel.fileSize)+')'
|
||||
: ''
|
||||
font.underline: true
|
||||
font.pointSize: ChatStyle.entry.message.file.download.pointSize
|
||||
color:ChatStyle.entry.message.file.extension.text.colorModel.color
|
||||
visible: (mainRow.contentModel? (!mainItem.isOutgoing && !mainRow.contentModel.wasDownloaded) || rectangle.isTransferring : false)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
id: thumbnailProvider
|
||||
anchors.centerIn: parent
|
||||
sourceComponent: (mainRow.contentModel ?
|
||||
(mainRow.isAnimatedImage ? animatedImage
|
||||
: (mainRow.haveThumbnail ? thumbnailImage : extension )
|
||||
) : undefined)
|
||||
|
||||
states: State {
|
||||
name: 'hovered'
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
function handleMouseMove (mouse) {
|
||||
thumbnailProvider.state = Utils.pointIsInItem(this, thumbnailProvider, mouse)
|
||||
? 'hovered'
|
||||
: ''
|
||||
}
|
||||
|
||||
FileView{
|
||||
id: fileView
|
||||
anchors.fill: parent
|
||||
visible: true
|
||||
|
||||
onClicked: {
|
||||
if(rectangle.isTransferring)
|
||||
mainRow.contentModel.cancelDownloadFile()
|
||||
else if( !mainRow.contentModel.wasDownloaded) {
|
||||
thumbnailProvider.state = ''
|
||||
mainRow.contentModel.downloadFile()
|
||||
}else if (Utils.pointIsInItem(this, thumbnailProvider, mouse)) {
|
||||
if(SettingsModel.isVfsEncrypted){
|
||||
window.attachVirtualWindow(Utils.buildCommonDialogUri('FileViewDialog'), {
|
||||
contentModel: mainRow.contentModel,
|
||||
}, function (status) {
|
||||
})
|
||||
}else
|
||||
mainRow.contentModel.openFile()
|
||||
} else if (mainRow.contentModel ) {
|
||||
thumbnailProvider.state = ''
|
||||
mainRow.contentModel.openFile(true)// Show directory
|
||||
} else {
|
||||
thumbnailProvider.state = ''
|
||||
mainRow.contentModel.downloadFile()
|
||||
|
||||
}
|
||||
}
|
||||
onExited: thumbnailProvider.state = ''
|
||||
onMouseXChanged: handleMouseMove.call(this, mouse)
|
||||
onMouseYChanged: handleMouseMove.call(this, mouse)
|
||||
contentModel: mainRow.contentModel
|
||||
thumbnail: mainRow.contentModel.thumbnail
|
||||
name: mainRow.contentModel && mainRow.contentModel.name
|
||||
filePath: mainRow.contentModel && mainRow.contentModel.filePath
|
||||
isTransferring: mainRow.chatMessageModel && (mainRow.chatMessageModel.state == LinphoneEnums.ChatMessageStateFileTransferInProgress || mainRow.chatMessageModel.state == LinphoneEnums.ChatMessageStateInProgress )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -49,12 +49,10 @@ Item{
|
|||
width: height * ChatFilePreviewStyle.filePreview.format
|
||||
anchors.verticalCenter: parent ? parent.verticalCenter : ScrollableListView.verticalCenter
|
||||
anchors.verticalCenterOffset: 7
|
||||
contentModel: $modelData
|
||||
thumbnail: $modelData.thumbnail
|
||||
name: $modelData.name
|
||||
animationScale: 1.1
|
||||
onClickOnFile: {
|
||||
$modelData.openFile()
|
||||
}
|
||||
ActionButton{
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: -height/2
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ TextEdit {
|
|||
property ContentModel contentModel
|
||||
property string lastTextSelected : ''
|
||||
property font customFont : SettingsModel.textMessageFont
|
||||
property int fitHeight: contentHeight + padding + 8
|
||||
property int fitWidth: implicitWidth + 2 // add 2 because there is a bug on border that lead to not fit text exactly
|
||||
property int fitHeight: contentHeight
|
||||
property int fitWidth: implicitWidth
|
||||
|
||||
signal rightClicked()
|
||||
|
||||
|
|
@ -33,9 +33,8 @@ TextEdit {
|
|||
|
||||
height: fitHeight
|
||||
width: parent && parent.width || 1
|
||||
visible: contentModel// && contentModel.isText()
|
||||
visible: contentModel
|
||||
clip: false
|
||||
padding: ChatStyle.entry.message.padding
|
||||
textMargin: 0
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
|
|
@ -70,7 +69,6 @@ TextEdit {
|
|||
}
|
||||
deselect()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
property bool keepLastSelection: false
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Linphone 1.0
|
|||
import LinphoneEnums 1.0
|
||||
import Linphone.Styles 1.0
|
||||
import Utils 1.0
|
||||
import UtilsCpp 1.0
|
||||
import Units 1.0
|
||||
import ColorsList 1.0
|
||||
|
||||
|
|
@ -15,118 +16,249 @@ import ColorsList 1.0
|
|||
|
||||
Item {
|
||||
id: mainItem
|
||||
property string thumbnail
|
||||
property string name
|
||||
property ContentModel contentModel
|
||||
property string thumbnail: contentModel && contentModel.thumbnail
|
||||
property string name: contentModel && contentModel.name
|
||||
property string filePath: contentModel && contentModel.filePath
|
||||
property int fileHeight: FileViewStyle.height
|
||||
property bool active: true
|
||||
property real animationScale : ChatStyle.entry.message.file.animation.to
|
||||
property real animationScale : FileViewStyle.animation.to
|
||||
property alias imageScale: thumbnailProvider.scale
|
||||
property int fitHeight: mainItem.isAnimatedImage ? FileViewStyle.heightbetter : FileViewStyle.height
|
||||
property int fitWidth: fitHeight*4/3
|
||||
property bool isAnimatedImage : contentModel && contentModel.wasDownloaded && UtilsCpp.isAnimatedImage(filePath)
|
||||
property bool haveThumbnail: contentModel && UtilsCpp.canHaveThumbnail(filePath)
|
||||
property int borderWidth : 0
|
||||
property color backgroundColor: FileViewStyle.extension.background.colorModel.color
|
||||
property int backgroundRadius: FileViewStyle.extension.radius
|
||||
|
||||
property bool isTransferring
|
||||
property bool isHovering: thumbnailProvider.state == 'hovered'
|
||||
|
||||
signal clickOnFile()
|
||||
// ---------------------------------------------------------------------
|
||||
// Thumbnail or extension.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
MouseArea {
|
||||
function handleMouseMove (mouse) {
|
||||
thumbnailProvider.state = Utils.pointIsInItem(this, thumbnailProvider, mouse)
|
||||
? 'hovered'
|
||||
: ''
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
visible: true
|
||||
|
||||
onClicked: {
|
||||
if(mainItem.isTransferring)
|
||||
mainItem.contentModel.cancelDownloadFile()
|
||||
else if( !mainItem.contentModel.wasDownloaded) {
|
||||
thumbnailProvider.state = ''
|
||||
mainItem.contentModel.downloadFile()
|
||||
}else if (Utils.pointIsInItem(this, thumbnailProvider, mouse)) {
|
||||
if(SettingsModel.isVfsEncrypted){
|
||||
window.attachVirtualWindow(Utils.buildCommonDialogUri('FileViewDialog'), {
|
||||
contentModel: mainItem.contentModel,
|
||||
}, function (status) {
|
||||
})
|
||||
}else
|
||||
mainItem.contentModel.openFile()
|
||||
} else if (mainItem.contentModel ) {
|
||||
thumbnailProvider.state = ''
|
||||
mainItem.contentModel.openFile(true)// Show directory
|
||||
} else {
|
||||
thumbnailProvider.state = ''
|
||||
mainItem.contentModel.downloadFile()
|
||||
|
||||
}
|
||||
mainItem.clickOnFile()
|
||||
}
|
||||
onExited: thumbnailProvider.state = ''
|
||||
onMouseXChanged: handleMouseMove.call(this, mouse)
|
||||
onMouseYChanged: handleMouseMove.call(this, mouse)
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Thumbnail
|
||||
// ---------------------------------------------------------------------
|
||||
Component {
|
||||
id: thumbnailImage
|
||||
|
||||
Image {
|
||||
id: thumbnailImageSource
|
||||
property real scaleAnimatorTo : FileViewStyle.animation.thumbnailTo
|
||||
property bool isVideo: UtilsCpp.isVideo(mainItem.filePath)
|
||||
mipmap: SettingsModel.mipmapEnabled
|
||||
source: mainItem.thumbnail
|
||||
autoTransform: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.fill: parent
|
||||
|
||||
Loader{
|
||||
anchors.fill: parent
|
||||
sourceComponent: Image{// Better quality on zoom
|
||||
mipmap: SettingsModel.mipmapEnabled
|
||||
source: !thumbnailImageSource.isVideo ? 'image://external/'+mainItem.filePath : ''
|
||||
autoTransform: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
visible: status == Image.Ready
|
||||
}
|
||||
asynchronous: true
|
||||
active: !thumbnailImageSource.isVideo && thumbnailProvider.state == 'hovered'
|
||||
}
|
||||
ActionButton{
|
||||
id: thumbnailVideoButton
|
||||
anchors.centerIn: parent
|
||||
visible: thumbnailImageSource.isVideo
|
||||
isCustom: true
|
||||
backgroundRadius: width
|
||||
colorSet: FileViewStyle.thumbnailVideoIcon
|
||||
onClicked:{
|
||||
window.attachVirtualWindow(Utils.buildCommonDialogUri('FileViewDialog'), {
|
||||
contentModel: mainItem.contentModel,
|
||||
}, function (status) {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: animatedImage
|
||||
|
||||
AnimatedImage {
|
||||
id: animatedImageSource
|
||||
property real scaleAnimatorTo : FileViewStyle.animation.to
|
||||
mipmap: SettingsModel.mipmapEnabled
|
||||
source: 'file:/'+mainItem.filePath
|
||||
autoTransform: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------
|
||||
// Extension
|
||||
// ---------------------------------------------------------------------
|
||||
Component {
|
||||
id: extension
|
||||
|
||||
Rectangle {
|
||||
color: ChatStyle.entry.message.file.extension.background.colorModel.color
|
||||
|
||||
Text {
|
||||
property real scaleAnimatorTo : FileViewStyle.animation.to
|
||||
anchors.fill: parent
|
||||
color: mainItem.backgroundColor
|
||||
radius: mainItem.backgroundRadius
|
||||
border.width: mainItem.borderWidth
|
||||
border.color: FileViewStyle.extension.background.borderColorModel.color
|
||||
|
||||
ColumnLayout{
|
||||
anchors.fill: parent
|
||||
|
||||
color: ChatStyle.entry.message.file.extension.text.colorModel.color
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
text: Utils.getExtension(mainItem.name).toUpperCase()
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
anchors.topMargin: FileViewStyle.margins
|
||||
anchors.bottomMargin: FileViewStyle.margins
|
||||
spacing: FileViewStyle.spacing
|
||||
Icon{
|
||||
id: fileIcon
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
icon: extensionText.text != '' ? FileViewStyle.extension.icon : FileViewStyle.extension.unknownIcon
|
||||
iconSize: FileViewStyle.extension.iconSize
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: iconSize
|
||||
Layout.preferredWidth: iconSize
|
||||
Text {
|
||||
id: extensionText
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottomMargin: FileViewStyle.spacing
|
||||
width: FileViewStyle.extension.internalSize
|
||||
onWidthChanged: extensionMetrics.font.pointSize = FileViewStyle.extension.text.pointSize // reset metrics
|
||||
color: FileViewStyle.extension.text.colorModel.color
|
||||
font.bold: true
|
||||
font.pointSize: extensionMetrics.font.pointSize
|
||||
clip: true
|
||||
text: (mainItem.contentModel?Utils.getExtension(mainItem.name).toUpperCase():'')
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
TextMetrics{
|
||||
id: extensionMetrics
|
||||
text: extensionText.text
|
||||
font.pointSize: FileViewStyle.extension.text.pointSize
|
||||
onWidthChanged: if(width > extensionText.width) --font.pointSize
|
||||
Component.onCompleted: if(width > extensionText.width) --font.pointSize
|
||||
}
|
||||
}
|
||||
RoundProgressBar {
|
||||
id: progressBar
|
||||
anchors.centerIn: parent
|
||||
property int fileSize: mainItem.contentModel ? mainItem.contentModel.fileSize : 0
|
||||
to: 100
|
||||
value: mainItem.contentModel ? (fileSize>0 ? Math.floor(100 * mainItem.contentModel.fileOffset / fileSize) : 0) : to
|
||||
visible: mainItem.isTransferring && value != 0
|
||||
/* Change format? Current is %
|
||||
text: if(mainRow.contentModel){
|
||||
var fileSize = Utils.formatSize(mainRow.contentModel.fileSize)
|
||||
return progressBar.visible
|
||||
? Utils.formatSize(mainRow.contentModel.fileOffset) + '/' + fileSize
|
||||
: fileSize
|
||||
}else
|
||||
return ''
|
||||
*/
|
||||
}
|
||||
}
|
||||
Text {
|
||||
id: fileName
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: mainItem.contentModel && !mainItem.isAnimatedImage
|
||||
|
||||
color: FileViewStyle.extension.text.colorModel.color
|
||||
font.pointSize: FileViewStyle.name.pointSize
|
||||
wrapMode: Text.WrapAnywhere
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
maximumLineCount: 2
|
||||
|
||||
text: mainItem.name
|
||||
}
|
||||
Text{
|
||||
id: downloadText
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredHeight: visible ? contentHeight : 0
|
||||
//: 'Cancel' : Message link to cancel a transfer (upload/download)
|
||||
text: mainItem.contentModel ? mainItem.isTransferring ? qsTr('fileTransferCancel')
|
||||
//: 'Download' : Message link to download a file
|
||||
: qsTr('fileTransferDownload') +' ('+Utils.formatSize(mainItem.contentModel.fileSize)+')'
|
||||
: ''
|
||||
font.underline: true
|
||||
font.pointSize: FileViewStyle.download.pointSize
|
||||
color:FileViewStyle.extension.text.colorModel.color
|
||||
visible: (mainItem.contentModel? (!mainItem.isOutgoing && !mainItem.contentModel.wasDownloaded) || mainItem.isTransferring : false)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: thumbnailProvider
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
sourceComponent: (mainItem.active ? (mainItem.thumbnail ? thumbnailImage : extension ): undefined)
|
||||
|
||||
ScaleAnimator {
|
||||
id: thumbnailProviderAnimator
|
||||
|
||||
target: mainItem
|
||||
|
||||
duration: ChatStyle.entry.message.file.animation.duration
|
||||
easing.type: Easing.InOutQuad
|
||||
from: 1.0
|
||||
}
|
||||
|
||||
sourceComponent: (mainItem.contentModel ?
|
||||
(mainItem.isAnimatedImage ? animatedImage
|
||||
: (mainItem.haveThumbnail ? thumbnailImage : extension )
|
||||
) : undefined)
|
||||
|
||||
states: State {
|
||||
name: 'hovered'
|
||||
}
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: ''
|
||||
to: 'hovered'
|
||||
|
||||
ScriptAction {
|
||||
script: {
|
||||
if (thumbnailProviderAnimator.running) {
|
||||
thumbnailProviderAnimator.running = false
|
||||
}
|
||||
|
||||
mainItem.z = Constants.zPopup
|
||||
thumbnailProviderAnimator.to = mainItem.animationScale
|
||||
thumbnailProviderAnimator.running = true
|
||||
}
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
from: 'hovered'
|
||||
to: ''
|
||||
|
||||
ScriptAction {
|
||||
script: {
|
||||
if (thumbnailProviderAnimator.running) {
|
||||
thumbnailProviderAnimator.running = false
|
||||
}
|
||||
|
||||
thumbnailProviderAnimator.to = 1.0
|
||||
thumbnailProviderAnimator.running = true
|
||||
mainItem.z = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
MouseArea {
|
||||
function handleMouseMove (mouse) {
|
||||
thumbnailProvider.state = Utils.pointIsInItem(this, thumbnailProvider, mouse)
|
||||
? 'hovered'
|
||||
: ''
|
||||
}
|
||||
Loader {
|
||||
id: waitingProvider
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: {
|
||||
clickOnFile()
|
||||
thumbnailProvider.state = ''
|
||||
sourceComponent: thumbnailProvider.sourceComponent == thumbnailImage && thumbnailProvider.item.status != Image.Ready
|
||||
? extension
|
||||
: undefined
|
||||
states: State {
|
||||
name: 'hovered'
|
||||
}
|
||||
onExited: thumbnailProvider.state = ''
|
||||
onMouseXChanged: handleMouseMove.call(this, mouse)
|
||||
onMouseYChanged: handleMouseMove.call(this, mouse)
|
||||
}
|
||||
}
|
||||
|
|
@ -201,7 +201,17 @@ QtObject {
|
|||
property var outgoingColor: ColorsList.addImageColor(sectionName+'_download_out', icon, 'g')
|
||||
property var incomingColor: ColorsList.addImageColor(sectionName+'_download_in', icon, 'q')
|
||||
}
|
||||
|
||||
property QtObject thumbnailVideoIcon: QtObject {
|
||||
property int iconSize: 40
|
||||
property string name : 'play'
|
||||
property string icon : 'thumbnail_video_custom'
|
||||
property var backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'wr_n_b_bg')
|
||||
property var backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'wr_h_b_bg')
|
||||
property var backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'wr_p_b_bg')
|
||||
property var foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'wr_n_b_fg')
|
||||
property var foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'wr_h_b_fg')
|
||||
property var foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'wr_p_b_fg')
|
||||
}
|
||||
property QtObject animation: QtObject {
|
||||
property int duration: 300
|
||||
property real to: 1.7
|
||||
|
|
@ -212,6 +222,7 @@ QtObject {
|
|||
property string icon: 'file_extension_custom'
|
||||
property string unknownIcon: 'file_unknown_custom'
|
||||
property int iconSize: 60
|
||||
property int internalSize: 37
|
||||
property int radius: 0
|
||||
|
||||
property QtObject background: QtObject {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
pragma Singleton
|
||||
import QtQml 2.2
|
||||
|
||||
import Units 1.0
|
||||
import ColorsList 1.0
|
||||
|
||||
// =============================================================================
|
||||
|
||||
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: Units.dp * 7
|
||||
}
|
||||
|
||||
property QtObject download: QtObject{
|
||||
property string icon: 'download_custom'
|
||||
property int height: 20
|
||||
property int pointSize: Units.dp * 8
|
||||
property int iconSize: 30
|
||||
property var outgoingColor: ColorsList.addImageColor(sectionName+'_download_out', icon, 'g')
|
||||
property var incomingColor: ColorsList.addImageColor(sectionName+'_download_in', icon, 'q')
|
||||
}
|
||||
property QtObject thumbnailVideoIcon: QtObject {
|
||||
property int iconSize: 40
|
||||
property string name : 'play'
|
||||
property string icon : 'thumbnail_video_custom'
|
||||
property var backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'wr_n_b_bg')
|
||||
property var backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'wr_h_b_bg')
|
||||
property var backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'wr_p_b_bg')
|
||||
property var foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'wr_n_b_fg')
|
||||
property var foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'wr_h_b_fg')
|
||||
property var foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'wr_p_b_fg')
|
||||
}
|
||||
property QtObject animation: QtObject {
|
||||
property int duration: 300
|
||||
property real to: 1.7
|
||||
property real thumbnailTo: 2
|
||||
}
|
||||
|
||||
property QtObject extension: QtObject {
|
||||
property string icon: 'file_extension_custom'
|
||||
property string unknownIcon: 'file_unknown_custom'
|
||||
property int iconSize: 60
|
||||
property int internalSize: 37
|
||||
property int radius: 0
|
||||
|
||||
property QtObject background: QtObject {
|
||||
property var colorModel: ColorsList.add(sectionName+'_file_extension_bg', 'q')
|
||||
property var borderColorModel: ColorsList.add(sectionName+'_file_extension_border', 'extension_file_border')
|
||||
}
|
||||
|
||||
property QtObject text: QtObject {
|
||||
property var colorModel: ColorsList.add(sectionName+'_file_extension_text', 'd')
|
||||
property int pointSize: Units.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 colorModel: ColorsList.add(sectionName+'_file_statusbar_bg', 'f')
|
||||
}
|
||||
|
||||
property QtObject contentItem: QtObject {
|
||||
property var colorModel: ColorsList.add(sectionName+'_file_statusbar_content', 'p')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ singleton OnlineInstallerDialogStyle 1.0 Dialog/OnlineInstallerDialogS
|
|||
singleton SipAddressDialogStyle 1.0 Dialog/SipAddressDialogStyle.qml
|
||||
singleton ZrtpTokenAuthenticationDialogStyle 1.0 Dialog/ZrtpTokenAuthenticationDialogStyle.qml
|
||||
|
||||
singleton FileViewStyle 1.0 File/FileViewStyle.qml
|
||||
|
||||
singleton HistoryStyle 1.0 History/HistoryStyle.qml
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue