Added drag'n'drop area to EditCaptionBox.

This commit is contained in:
23rd 2020-06-13 16:08:55 +03:00
parent 6ac9ef34eb
commit 4eaba39a7c
9 changed files with 139 additions and 58 deletions

View File

@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo_media.h"
#include "data/data_document_media.h"
#include "history/history.h"
#include "history/history_drag_area.h"
#include "history/history_item.h"
#include "platform/platform_specific.h"
#include "lang/lang_keys.h"
@ -38,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_player.h"
#include "media/streaming/media_streaming_document.h"
#include "media/streaming/media_streaming_loader_local.h"
#include "platform/platform_file_utilities.h"
#include "storage/localimageloader.h"
#include "storage/storage_media_prepare.h"
#include "mtproto/mtproto_config.h"
@ -64,6 +66,50 @@ namespace {
using namespace ::Media::Streaming;
using Data::PhotoSize;
auto ListFromMimeData(not_null<const QMimeData*> data) {
using Error = Storage::PreparedList::Error;
auto result = data->hasUrls()
? Storage::PrepareMediaList(
// When we edit media, we need only 1 file.
data->urls().mid(0, 1),
st::sendMediaPreviewSize)
: Storage::PreparedList(Error::EmptyFile, QString());
if (result.error == Error::None) {
return result;
} else if (data->hasImage()) {
auto image = Platform::GetImageFromClipboard();
if (image.isNull()) {
image = qvariant_cast<QImage>(data->imageData());
}
if (!image.isNull()) {
return Storage::PrepareMediaFromImage(
std::move(image),
QByteArray(),
st::sendMediaPreviewSize);
}
}
return result;
}
auto CheckMimeData(not_null<const QMimeData*> data, bool isAlbum) {
if (data->urls().size() > 1) {
return false;
} else if (data->hasImage()) {
return true;
}
if (isAlbum && data->hasUrls()) {
const auto url = data->urls().front();
if (url.isLocalFile()) {
using namespace Core;
const auto info = QFileInfo(Platform::File::UrlToLocal(url));
return IsMimeAcceptedForAlbum(MimeTypeForFile(info).name());
}
}
return true;
}
} // namespace
EditCaptionBox::EditCaptionBox(
@ -617,12 +663,8 @@ void EditCaptionBox::prepare() {
if (action == Ui::InputField::MimeAction::Check) {
if (!data->hasText() && !_isAllowedEditMedia) {
return false;
} else if (data->hasImage()) {
} else if (CheckMimeData(data, _isAlbum)) {
return true;
} else if (const auto urls = data->urls(); !urls.empty()) {
if (ranges::all_of(urls, &QUrl::isLocalFile)) {
return true;
}
}
return data->hasText();
} else if (action == Ui::InputField::MimeAction::Insert) {
@ -640,6 +682,8 @@ void EditCaptionBox::prepare() {
auto cursor = _field->textCursor();
cursor.movePosition(QTextCursor::End);
_field->setTextCursor(cursor);
setupDragArea();
}
bool EditCaptionBox::fileFromClipboard(not_null<const QMimeData*> data) {
@ -647,50 +691,34 @@ bool EditCaptionBox::fileFromClipboard(not_null<const QMimeData*> data) {
return false;
}
using Error = Storage::PreparedList::Error;
using AlbumType = Storage::PreparedFile::AlbumType;
auto list = ListFromMimeData(data);
auto list = [&] {
auto url = QList<QUrl>();
auto canAddUrl = false;
// When we edit media, we need only 1 file.
if (data->hasUrls()) {
const auto first = data->urls().front();
url.push_front(first);
canAddUrl = first.isLocalFile();
}
auto result = canAddUrl
? Storage::PrepareMediaList(url, st::sendMediaPreviewSize)
: Storage::PreparedList(
Error::EmptyFile,
QString());
if (result.error == Error::None) {
return result;
} else if (data->hasImage()) {
auto image = Platform::GetImageFromClipboard();
if (image.isNull()) {
image = qvariant_cast<QImage>(data->imageData());
}
if (!image.isNull()) {
_isImage = true;
_photo = true;
return Storage::PrepareMediaFromImage(
std::move(image),
QByteArray(),
st::sendMediaPreviewSize);
}
}
return result;
}();
if (list.error != Error::None || list.files.empty()) {
return false;
}
if (list.files.front().type == Storage::PreparedFile::AlbumType::None
&& _isAlbum) {
Ui::show(
Box<InformBox>(tr::lng_edit_media_album_error(tr::now)),
Ui::LayerOption::KeepOther);
const auto file = &list.files.front();
if (_isAlbum && (file->type == AlbumType::None)) {
const auto imageAsDoc = [&] {
using Info = FileMediaInformation;
const auto fileMedia = &file->information->media;
if (const auto image = base::get_if<Info::Image>(fileMedia)) {
return !Storage::ValidateThumbDimensions(
image->data.width(),
image->data.height());
}
return false;
}();
if (!data->hasText() || imageAsDoc) {
Ui::show(
Box<InformBox>(tr::lng_edit_media_album_error(tr::now)),
Ui::LayerOption::KeepOther);
}
return false;
}
_photo = _isImage = (file->type == AlbumType::Photo);
_preparedList = std::move(list);
updateEditPreview();
return true;
@ -735,6 +763,35 @@ void EditCaptionBox::setupEmojiPanel() {
});
}
void EditCaptionBox::setupDragArea() {
auto enterFilter = [=](not_null<const QMimeData*> data) {
return !_isAllowedEditMedia ? false : CheckMimeData(data, _isAlbum);
};
// Avoid both drag areas appearing at one time.
auto computeState = [=](const QMimeData *data) {
const auto state = Storage::ComputeMimeDataState(data);
return (state == Storage::MimeDataState::PhotoFiles)
? Storage::MimeDataState::Image
: state;
};
const auto areas = DragArea::SetupDragAreaToContainer(
this,
std::move(enterFilter),
[=](bool f) { _field->setAcceptDrops(f); },
nullptr,
std::move(computeState));
const auto droppedCallback = [=](bool compress) {
return [=](const QMimeData *data) {
fileFromClipboard(data);
Window::ActivateWindow(_controller);
};
};
areas.document->setDroppedCallback(droppedCallback(false));
areas.photo->setDroppedCallback(droppedCallback(true));
}
void EditCaptionBox::updateBoxSize() {
auto newHeight = st::boxPhotoPadding.top() + st::boxPhotoCaptionSkip + _field->height() + errorTopSkip() + st::normalFont->height;
if (_photo) {

View File

@ -79,6 +79,8 @@ private:
void updateEmojiPanelGeometry();
void emojiFilterForGeometry(not_null<QEvent*> event);
void setupDragArea();
void save();
void captionResized();

View File

@ -111,4 +111,11 @@ bool IsMimeSticker(const QString &mime) {
|| IsMimeStickerAnimated(mime);
}
bool IsMimeAcceptedForAlbum(const QString &mime) {
return (mime == u"image/jpeg"_q)
|| (mime == u"image/png"_q)
|| (mime == u"video/mp4"_q)
|| (mime == u"video/quicktime"_q);
}
} // namespace Core

View File

@ -41,5 +41,6 @@ MimeType MimeTypeForData(const QByteArray &data);
bool IsMimeStickerAnimated(const QString &mime);
bool IsMimeSticker(const QString &mime);
bool IsMimeAcceptedForAlbum(const QString &mime);
} // namespace Core

View File

@ -40,9 +40,10 @@ constexpr auto kDragAreaEvents = {
DragArea::Areas DragArea::SetupDragAreaToContainer(
not_null<Ui::RpWidget*> container,
Fn<bool()> &&dragEnterFilter,
Fn<bool(not_null<const QMimeData*>)> &&dragEnterFilter,
Fn<void(bool)> &&setAcceptDropsField,
Fn<void()> &&updateControlsGeometry) {
Fn<void()> &&updateControlsGeometry,
DragArea::CallbackComputeState &&computeState) {
using DragState = Storage::MimeDataState;
@ -161,11 +162,13 @@ DragArea::Areas DragArea::SetupDragAreaToContainer(
};
const auto dragEnterEvent = [=](QDragEnterEvent *e) {
if (dragEnterFilter && dragEnterFilter()) {
if (dragEnterFilter && !dragEnterFilter(e->mimeData())) {
return;
}
*attachDragState = Storage::ComputeMimeDataState(e->mimeData());
*attachDragState = computeState
? computeState(e->mimeData())
: Storage::ComputeMimeDataState(e->mimeData());
updateDragAreas();
if (*attachDragState != DragState::None) {
@ -179,6 +182,10 @@ DragArea::Areas DragArea::SetupDragAreaToContainer(
};
const auto dropEvent = [=](QDropEvent *e) {
// Hide fast to avoid visual bugs in resizable boxes.
attachDragDocument->hideFast();
attachDragPhoto->hideFast();
*attachDragState = DragState::None;
updateDragAreas();
e->acceptProposedAction();

View File

@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h"
#include "ui/effects/animations.h"
namespace Storage {
enum class MimeDataState;
} // namespace Storage
class DragArea : public Ui::RpWidget {
Q_OBJECT
@ -20,11 +24,16 @@ public:
DragArea *document;
DragArea *photo;
};
using CallbackComputeState =
Fn<Storage::MimeDataState(const QMimeData *data)>;
static Areas SetupDragAreaToContainer(
not_null<Ui::RpWidget*> container,
Fn<bool()> &&dragEnterFilter = nullptr,
Fn<bool(not_null<const QMimeData*>)> &&dragEnterFilter = nullptr,
Fn<void(bool)> &&setAcceptDropsField = nullptr,
Fn<void()> &&updateControlsGeometry = nullptr);
Fn<void()> &&updateControlsGeometry = nullptr,
CallbackComputeState &&computeState = nullptr);
void setText(const QString &text, const QString &subtext);

View File

@ -449,7 +449,9 @@ HistoryWidget::HistoryWidget(
_attachDragAreas = DragArea::SetupDragAreaToContainer(
this,
crl::guard(this, [=] { return (!_history || !_canSendMessages); }),
crl::guard(this, [=](not_null<const QMimeData*> d) {
return _history && _canSendMessages;
}),
crl::guard(this, [=](bool f) { _field->setAcceptDrops(f); }),
crl::guard(this, [=] { updateControlsGeometry(); }));
_attachDragAreas.document->setDroppedCallback([=](const QMimeData *data) {

View File

@ -1181,7 +1181,7 @@ void ScheduledWidget::clearSelected() {
void ScheduledWidget::setupDragArea() {
const auto areas = DragArea::SetupDragAreaToContainer(
this,
[=] { return !_history; },
[=](not_null<const QMimeData*> d) { return _history; },
nullptr,
[=] { updateControlsGeometry(); });

View File

@ -291,19 +291,15 @@ std::optional<PreparedList> PreparedList::PreparedFileFromFilesDialog(
std::move(result.remoteContent),
previewWidth);
if (Core::IsMimeSticker(list.files.front().mime)) {
const auto mimeFile = list.files.front().mime;
if (Core::IsMimeSticker(mimeFile)) {
errorCallback(tr::lng_edit_media_invalid_file);
return std::nullopt;
}
if (isAlbum) {
const auto albumMimes = {
"image/jpeg",
"image/png",
"video/mp4",
};
const auto file = &list.files.front();
if (!ranges::contains(albumMimes, file->mime)
if (!Core::IsMimeAcceptedForAlbum(mimeFile)
|| file->type == Storage::PreparedFile::AlbumType::None) {
errorCallback(tr::lng_edit_media_album_error);
return std::nullopt;