mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/telegramdesktop/tdesktop
synced 2026-09-20 08:03:45 +08:00
Added ability to unpack archives from send files box. (Combined commit.)
This commit is contained in:
@@ -8820,5 +8820,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_folder_archive_failed" = "Failed to create archive.";
|
||||
"lng_folder_archive_status" = "ZIP archive";
|
||||
"lng_folder_archive_unpack" = "Unpack archive";
|
||||
"lng_folder_archive_pack" = "Pack into ZIP archive";
|
||||
"lng_folder_archive_pack_selected" = "Pack selected into ZIP archive";
|
||||
"lng_folder_archive_select" = "Select";
|
||||
"lng_folder_archive_deselect" = "Deselect";
|
||||
|
||||
// Keys finished
|
||||
|
||||
@@ -545,6 +545,21 @@ rpl::producer<int> SendFilesBox::Block::itemRenameRequest() const {
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<int> SendFilesBox::Block::itemSelectRequest() const {
|
||||
using namespace rpl::mappers;
|
||||
|
||||
const auto preview = _preview.get();
|
||||
const auto from = _from;
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(preview);
|
||||
return album->thumbSelected() | rpl::map(_1 + from);
|
||||
} else if (_isSingleMedia) {
|
||||
return rpl::never<int>();
|
||||
}
|
||||
const auto single = static_cast<Ui::SingleFilePreview*>(preview);
|
||||
return single->selectRequests() | rpl::map_to(from);
|
||||
}
|
||||
|
||||
rpl::producer<> SendFilesBox::Block::orderUpdated() const {
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
|
||||
@@ -582,6 +597,28 @@ void SendFilesBox::Block::toggleSpoilers(bool enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::Block::setSelectionMode(bool enabled) {
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
|
||||
album->setSelectionMode(enabled);
|
||||
} else if (!_isSingleMedia) {
|
||||
const auto single = static_cast<Ui::SingleFilePreview*>(
|
||||
_preview.get());
|
||||
single->setSelectionMode(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::Block::setSelected(int index, bool selected) {
|
||||
if (_isAlbum) {
|
||||
const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
|
||||
album->setSelected(index - _from, selected);
|
||||
} else if (!_isSingleMedia) {
|
||||
const auto single = static_cast<Ui::SingleFilePreview*>(
|
||||
_preview.get());
|
||||
single->setSelected(selected);
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::Block::applyChanges() {
|
||||
if (!_isAlbum) {
|
||||
if (_isSingleMedia) {
|
||||
@@ -959,6 +996,171 @@ void SendFilesBox::refreshAllAfterChanges(int fromItem, Fn<void()> perform) {
|
||||
_inner->resizeToWidth(st::boxWideWidth);
|
||||
refreshControls();
|
||||
captionResized();
|
||||
updateSelectionMode();
|
||||
}
|
||||
|
||||
void SendFilesBox::unpackArchive(int index) {
|
||||
if (_preparing || index < 0 || index >= _list.files.size()) {
|
||||
return;
|
||||
}
|
||||
const auto archive = _list.files[index].archive;
|
||||
if (!archive) {
|
||||
return;
|
||||
}
|
||||
const auto paths = archive->folder.isEmpty()
|
||||
? archive->paths
|
||||
: Storage::FolderFilesForSending(archive->folder);
|
||||
auto list = Storage::PrepareMediaList(
|
||||
paths,
|
||||
st::sendMediaPreviewSize,
|
||||
_show->session().premium());
|
||||
if (list.error != Ui::PreparedList::Error::None
|
||||
|| list.files.empty()) {
|
||||
showToast(tr::lng_send_media_invalid_files(tr::now));
|
||||
return;
|
||||
} else if (!checkWith(list, _sendWay.current())) {
|
||||
return;
|
||||
} else if (_limits & SendFilesAllow::OnlyOne) {
|
||||
auto removing = std::move(_list.files[index]);
|
||||
std::swap(_list.files[index], _list.files.back());
|
||||
_list.files.pop_back();
|
||||
const auto ok = _list.canBeSentInSlowmodeWith(list);
|
||||
_list.files.push_back(std::move(removing));
|
||||
std::swap(_list.files[index], _list.files.back());
|
||||
if (!ok) {
|
||||
showToast(tr::lng_slowmode_no_many(tr::now));
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (auto i = 0; i != archive->names.size(); ++i) {
|
||||
if (archive->names[i].isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (auto &file : list.files) {
|
||||
if (file.path == archive->paths[i]) {
|
||||
file.displayName = archive->names[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
refreshAllAfterChanges(index, [&] {
|
||||
_list.files.erase(_list.files.begin() + index);
|
||||
_list.files.insert(
|
||||
_list.files.begin() + index,
|
||||
std::make_move_iterator(list.files.begin()),
|
||||
std::make_move_iterator(list.files.end()));
|
||||
});
|
||||
_list.filesToProcess.insert(
|
||||
_list.filesToProcess.end(),
|
||||
std::make_move_iterator(list.filesToProcess.begin()),
|
||||
std::make_move_iterator(list.filesToProcess.end()));
|
||||
enqueueNextPrepare();
|
||||
}
|
||||
|
||||
std::vector<int> SendFilesBox::archivableIndices(bool selectedOnly) const {
|
||||
auto result = std::vector<int>();
|
||||
for (auto i = 0, count = int(_list.files.size()); i != count; ++i) {
|
||||
const auto &file = _list.files[i];
|
||||
if (!file.archive
|
||||
&& !file.path.isEmpty()
|
||||
&& (!selectedOnly || file.selected)) {
|
||||
result.push_back(i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SendFilesBox::canArchiveAll() const {
|
||||
const auto count = int(_list.files.size());
|
||||
return !_preparing
|
||||
&& (count > 0)
|
||||
&& (archivableIndices(false).size() == count);
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasArchives() const {
|
||||
return !_preparing && ranges::any_of(_list.files, [](const auto &file) {
|
||||
return file.archive != nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
void SendFilesBox::unpackArchives() {
|
||||
for (auto i = int(_list.files.size()); i != 0;) {
|
||||
--i;
|
||||
if (_list.files[i].archive) {
|
||||
unpackArchive(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SendFilesBox::hasSelection() const {
|
||||
return ranges::any_of(_list.files, &Ui::PreparedFile::selected);
|
||||
}
|
||||
|
||||
void SendFilesBox::toggleSelection(int index) {
|
||||
applyBlockChanges();
|
||||
if (index < 0 || index >= _list.files.size()) {
|
||||
return;
|
||||
}
|
||||
auto &file = _list.files[index];
|
||||
file.selected = !file.selected;
|
||||
for (auto &block : _blocks) {
|
||||
if (index >= block.fromIndex() && index < block.tillIndex()) {
|
||||
block.setSelected(index, file.selected);
|
||||
}
|
||||
}
|
||||
updateSelectionMode();
|
||||
}
|
||||
|
||||
bool SendFilesBox::clearSelection() {
|
||||
if (!hasSelection()) {
|
||||
return false;
|
||||
}
|
||||
for (auto i = 0, count = int(_list.files.size()); i != count; ++i) {
|
||||
auto &file = _list.files[i];
|
||||
if (!file.selected) {
|
||||
continue;
|
||||
}
|
||||
file.selected = false;
|
||||
for (auto &block : _blocks) {
|
||||
if (i >= block.fromIndex() && i < block.tillIndex()) {
|
||||
block.setSelected(i, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateSelectionMode();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendFilesBox::updateSelectionMode() {
|
||||
const auto enabled = hasSelection();
|
||||
for (auto &block : _blocks) {
|
||||
block.setSelectionMode(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::archiveFiles(const std::vector<int> &indices) {
|
||||
if (_preparing || indices.empty()) {
|
||||
return;
|
||||
}
|
||||
auto paths = QStringList();
|
||||
auto names = QStringList();
|
||||
for (const auto index : indices) {
|
||||
const auto &file = _list.files[index];
|
||||
paths.push_back(file.path);
|
||||
names.push_back(file.displayName);
|
||||
}
|
||||
auto list = Ui::PreparedList();
|
||||
list.files.push_back(Storage::PrepareFilesArchive(paths, names));
|
||||
if (!checkWith(list, _sendWay.current())) {
|
||||
return;
|
||||
}
|
||||
refreshAllAfterChanges(indices.front(), [&] {
|
||||
for (const auto index : ranges::views::reverse(indices)) {
|
||||
_list.files.erase(_list.files.begin() + index);
|
||||
}
|
||||
_list.files.insert(
|
||||
_list.files.begin() + indices.front(),
|
||||
std::move(list.files.front()));
|
||||
});
|
||||
}
|
||||
|
||||
bool SendFilesBox::setDisplayNameInSingleFilePreview(
|
||||
@@ -1244,7 +1446,7 @@ QImage SendFilesBox::preparePriceTagBg(QSize size) const {
|
||||
|
||||
void SendFilesBox::addMenuButton() {
|
||||
const auto details = _sendMenuDetails();
|
||||
if (!hasSendMenu(details)) {
|
||||
if (!hasSendMenu(details) && !canArchiveAll() && !hasArchives()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1254,6 +1456,18 @@ void SendFilesBox::addMenuButton() {
|
||||
_menu = base::make_unique_q<Ui::PopupMenu>(top, tabbed.menu);
|
||||
_menu->setForcedOrigin(Ui::PanelAnimation::Origin::TopRight);
|
||||
const auto position = QCursor::pos();
|
||||
if (canArchiveAll()) {
|
||||
_menu->addAction(
|
||||
tr::lng_folder_archive_pack(tr::now),
|
||||
[=] { archiveFiles(archivableIndices(false)); },
|
||||
&st::menuIconArchive);
|
||||
}
|
||||
if (hasArchives()) {
|
||||
_menu->addAction(
|
||||
tr::lng_folder_archive_unpack(tr::now),
|
||||
[=] { unpackArchives(); },
|
||||
&st::menuIconUnarchive);
|
||||
}
|
||||
const auto result = SendMenu::FillSendMenu(
|
||||
_menu.get(),
|
||||
_show,
|
||||
@@ -1261,11 +1475,11 @@ void SendFilesBox::addMenuButton() {
|
||||
_sendMenuCallback,
|
||||
&_st.tabbed.icons,
|
||||
position);
|
||||
if (result != SendMenu::FillMenuResult::Prepared) {
|
||||
if (result == SendMenu::FillMenuResult::Failed || _menu->empty()) {
|
||||
_menu = nullptr;
|
||||
return true;
|
||||
}
|
||||
_menu->popupPrepared();
|
||||
_menu->popup(position);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -1396,6 +1610,7 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
gifPaused,
|
||||
_sendWay.current());
|
||||
auto &block = _blocks.back();
|
||||
block.setSelectionMode(hasSelection());
|
||||
const auto widget = _inner->add(
|
||||
block.takeWidget(),
|
||||
QMargins(0, _inner->count() ? st::sendMediaRowSkip : 0, 0, 0));
|
||||
@@ -1598,7 +1813,7 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
if (!canEditFileData) {
|
||||
return;
|
||||
}
|
||||
const auto allowExtensionEdit = file.path.isEmpty();
|
||||
const auto allowExtensionEdit = file.path.isEmpty() && !file.archive;
|
||||
_show->show(Box(
|
||||
RenameFileBox,
|
||||
file.displayName,
|
||||
@@ -1808,6 +2023,23 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
&st::menuIconCancel);
|
||||
}
|
||||
}
|
||||
if (!compressed && !_preparing && !file.archive) {
|
||||
const auto selected = archivableIndices(true);
|
||||
if (!selected.empty()) {
|
||||
state->menu->addAction(
|
||||
tr::lng_folder_archive_pack_selected(tr::now),
|
||||
[=] { archiveFiles(selected); },
|
||||
&st::menuIconArchive);
|
||||
}
|
||||
if (!file.path.isEmpty()) {
|
||||
state->menu->addAction(
|
||||
(file.selected
|
||||
? tr::lng_folder_archive_deselect
|
||||
: tr::lng_folder_archive_select)(tr::now),
|
||||
[=] { toggleSelection(fileIndex); },
|
||||
&st::menuIconSelect);
|
||||
}
|
||||
}
|
||||
if (state->menu->empty()) {
|
||||
state->menu = nullptr;
|
||||
return false;
|
||||
@@ -1862,6 +2094,11 @@ void SendFilesBox::pushBlock(int from, int till) {
|
||||
renameFile(index);
|
||||
}, widget->lifetime());
|
||||
|
||||
block.itemSelectRequest(
|
||||
) | rpl::on_next([=](int index) {
|
||||
toggleSelection(index);
|
||||
}, widget->lifetime());
|
||||
|
||||
block.orderUpdated() | rpl::on_next([=]{
|
||||
if (_priceTag) {
|
||||
_priceTagBg = QImage();
|
||||
@@ -2069,6 +2306,9 @@ void SendFilesBox::setupCaption() {
|
||||
}, _caption->lifetime());
|
||||
_caption->cancelled(
|
||||
) | rpl::on_next([=] {
|
||||
if (hasSelection()) {
|
||||
return;
|
||||
}
|
||||
requestToTakeTextWithTags();
|
||||
closeBox();
|
||||
}, _caption->lifetime());
|
||||
@@ -2436,6 +2676,8 @@ void SendFilesBox::keyPressEvent(QKeyEvent *e) {
|
||||
|| modifiers.testFlag(Qt::MetaModifier);
|
||||
const auto shift = modifiers.testFlag(Qt::ShiftModifier);
|
||||
send({}, ctrl && shift);
|
||||
} else if (e->key() == Qt::Key_Escape && clearSelection()) {
|
||||
e->accept();
|
||||
} else {
|
||||
BoxContent::keyPressEvent(e);
|
||||
}
|
||||
|
||||
@@ -184,10 +184,13 @@ private:
|
||||
[[nodiscard]] rpl::producer<int> itemReplaceRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemModifyRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemRenameRequest() const;
|
||||
[[nodiscard]] rpl::producer<int> itemSelectRequest() const;
|
||||
[[nodiscard]] rpl::producer<> orderUpdated() const;
|
||||
|
||||
void setSendWay(Ui::SendFilesWay way);
|
||||
void toggleSpoilers(bool enabled);
|
||||
void setSelectionMode(bool enabled);
|
||||
void setSelected(int index, bool selected);
|
||||
void applyChanges();
|
||||
|
||||
[[nodiscard]] QImage generatePriceTagBackground() const;
|
||||
@@ -267,6 +270,16 @@ private:
|
||||
|
||||
void openDialogToAddFileToAlbum();
|
||||
void refreshAllAfterChanges(int fromItem, Fn<void()> perform = nullptr);
|
||||
void unpackArchive(int index);
|
||||
[[nodiscard]] std::vector<int> archivableIndices(bool selectedOnly) const;
|
||||
[[nodiscard]] bool hasSelection() const;
|
||||
void toggleSelection(int index);
|
||||
void updateSelectionMode();
|
||||
[[nodiscard]] bool clearSelection();
|
||||
void archiveFiles(const std::vector<int> &indices);
|
||||
[[nodiscard]] bool canArchiveAll() const;
|
||||
[[nodiscard]] bool hasArchives() const;
|
||||
void unpackArchives();
|
||||
[[nodiscard]] bool setDisplayNameInSingleFilePreview(
|
||||
int fileIndex,
|
||||
const QString &displayName);
|
||||
|
||||
@@ -227,6 +227,7 @@ ComposeFiles {
|
||||
buttonFile: IconButton;
|
||||
buttonFileEdit: icon;
|
||||
buttonFileDelete: icon;
|
||||
selectCheck: RoundCheckbox;
|
||||
iconBg: color;
|
||||
iconPlay: icon;
|
||||
iconImage: icon;
|
||||
@@ -705,6 +706,9 @@ sendBoxAlbumGroupButtonFile: IconButton(editMediaButton) {
|
||||
}
|
||||
sendBoxAlbumGroupEditButtonIconFile: editMediaButtonIconFile;
|
||||
sendBoxAlbumGroupDeleteButtonIconFile: icon {{ "send_media/send_media_cross", menuIconFg }};
|
||||
sendBoxFileSelectCheck: RoundCheckbox(defaultPeerListCheck) {
|
||||
bgActive: boxTextFgGood;
|
||||
}
|
||||
|
||||
sendBoxAlbumButtonMediaMore: icon {{ "send_media/send_media_more", roundedFg }};
|
||||
sendBoxAlbumGroupButtonMediaMore: icon {{ "send_media/send_media_more", roundedFg, point(4px, 1px) }};
|
||||
@@ -1578,6 +1582,7 @@ defaultComposeFiles: ComposeFiles {
|
||||
buttonFile: sendBoxAlbumGroupButtonFile;
|
||||
buttonFileEdit: sendBoxAlbumGroupEditButtonIconFile;
|
||||
buttonFileDelete: sendBoxAlbumGroupDeleteButtonIconFile;
|
||||
selectCheck: sendBoxFileSelectCheck;
|
||||
iconBg: msgFileInBg;
|
||||
iconPlay: icon {{ "history_file_play", historyFileInIconFg }};
|
||||
iconImage: icon {{ "history_file_image", historyFileInIconFg }};
|
||||
|
||||
@@ -57,7 +57,9 @@ constexpr auto kDragAreaEvents = {
|
||||
}
|
||||
}
|
||||
if (!modifiers) {
|
||||
return state;
|
||||
return (state == DragState::FilesArchive)
|
||||
? DragState::Files
|
||||
: state;
|
||||
}
|
||||
return (state == DragState::PhotoFiles)
|
||||
? DragState::PhotoFilesArchive
|
||||
@@ -68,7 +70,9 @@ constexpr auto kDragAreaEvents = {
|
||||
|
||||
[[nodiscard]] bool IsPromotedToArchive(Storage::MimeDataState state) {
|
||||
using DragState = Storage::MimeDataState;
|
||||
return (state == DragState::PhotoFilesArchive)
|
||||
return (state == DragState::FilesArchive)
|
||||
|| (state == DragState::FilesArchiveOnly)
|
||||
|| (state == DragState::PhotoFilesArchive)
|
||||
|| (state == DragState::MediaFilesArchive);
|
||||
}
|
||||
|
||||
|
||||
@@ -7970,14 +7970,6 @@ bool HistoryWidget::confirmSendingFiles(
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (overrideSendImagesAsPhotos == true
|
||||
&& (Storage::ComputeMimeDataState(data)
|
||||
== Storage::MimeDataState::FilesArchive)) {
|
||||
auto list = Ui::PreparedList();
|
||||
list.files.push_back(Storage::PrepareFilesArchive(urls));
|
||||
confirmSendingFiles(std::move(list), QString());
|
||||
return true;
|
||||
}
|
||||
auto list = Storage::PrepareMediaList(
|
||||
urls,
|
||||
st::sendMediaPreviewSize,
|
||||
|
||||
@@ -2022,14 +2022,6 @@ bool ChatWidget::confirmSendingFiles(
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (overrideSendImagesAsPhotos == true
|
||||
&& (Storage::ComputeMimeDataState(data)
|
||||
== Storage::MimeDataState::FilesArchive)) {
|
||||
auto list = Ui::PreparedList();
|
||||
list.files.push_back(Storage::PrepareFilesArchive(urls));
|
||||
confirmSendingFiles(std::move(list), QString());
|
||||
return true;
|
||||
}
|
||||
auto list = Storage::PrepareMediaList(
|
||||
urls,
|
||||
st::sendMediaPreviewSize,
|
||||
|
||||
@@ -573,14 +573,6 @@ bool ScheduledWidget::confirmSendingFiles(
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (overrideSendImagesAsPhotos == true
|
||||
&& (Storage::ComputeMimeDataState(data)
|
||||
== Storage::MimeDataState::FilesArchive)) {
|
||||
auto list = Ui::PreparedList();
|
||||
list.files.push_back(Storage::PrepareFilesArchive(urls));
|
||||
confirmSendingFiles(std::move(list), QString());
|
||||
return true;
|
||||
}
|
||||
auto list = Storage::PrepareMediaList(
|
||||
urls,
|
||||
st::sendMediaPreviewSize,
|
||||
|
||||
@@ -900,6 +900,14 @@ storiesComposeControls: ComposeControls(defaultComposeControls) {
|
||||
}
|
||||
buttonFileEdit: icon {{ "send_media/send_media_replace", storiesComposeGrayIcon }};
|
||||
buttonFileDelete: icon {{ "send_media/send_media_delete", storiesComposeGrayIcon }};
|
||||
selectCheck: RoundCheckbox(sendBoxFileSelectCheck) {
|
||||
border: storiesComposeBg;
|
||||
check: icon {{
|
||||
"default_checkbox_check",
|
||||
storiesComposeWhiteText,
|
||||
point(3px, 6px)
|
||||
}};
|
||||
}
|
||||
iconBg: storiesComposeBlue;
|
||||
iconPlay: icon {{ "history_file_play", storiesComposeWhiteText }};
|
||||
iconImage: icon {{ "history_file_image", storiesComposeWhiteText }};
|
||||
|
||||
@@ -273,10 +273,12 @@ void FillEntryDate(const QDateTime &modified, zip_fileinfo *result) {
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<ArchiveEntries> GatherFiles(
|
||||
const QStringList &paths) {
|
||||
const QStringList &paths,
|
||||
const QStringList &names) {
|
||||
auto result = ArchiveEntries();
|
||||
auto counts = base::flat_map<QString, int>();
|
||||
for (const auto &path : paths) {
|
||||
for (auto i = 0; i != paths.size(); ++i) {
|
||||
const auto &path = paths[i];
|
||||
const auto info = QFileInfo(path);
|
||||
if (info.isDir()) {
|
||||
continue;
|
||||
@@ -286,10 +288,13 @@ void FillEntryDate(const QDateTime &modified, zip_fileinfo *result) {
|
||||
continue;
|
||||
}
|
||||
const auto size = info.size();
|
||||
const auto name = (i < names.size() && !names[i].isEmpty())
|
||||
? names[i]
|
||||
: info.fileName();
|
||||
result.total += size;
|
||||
result.list.push_back({
|
||||
info.absoluteFilePath(),
|
||||
DedupedName(counts, info.fileName()),
|
||||
DedupedName(counts, name),
|
||||
info.lastModified(),
|
||||
size,
|
||||
});
|
||||
@@ -469,17 +474,24 @@ Ui::PreparedFile PrepareFilesArchive(const QList<QUrl> &urls) {
|
||||
paths.push_back(Platform::File::UrlToLocal(url));
|
||||
}
|
||||
}
|
||||
return PrepareFilesArchive(paths);
|
||||
}
|
||||
|
||||
Ui::PreparedFile PrepareFilesArchive(
|
||||
const QStringList &paths,
|
||||
const QStringList &names) {
|
||||
const auto parent = CommonParentName(paths);
|
||||
const auto name = parent.isEmpty() ? u"Archive"_q : parent;
|
||||
return ArchiveFile(name + u".zip"_q, {
|
||||
.paths = paths,
|
||||
.names = names,
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<ArchiveEntries> GatherArchiveEntries(
|
||||
const Ui::PreparedFileArchive &job) {
|
||||
return job.folder.isEmpty()
|
||||
? GatherFiles(job.paths)
|
||||
? GatherFiles(job.paths, job.names)
|
||||
: WalkFolder(job.folder, job.root);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ struct ArchiveWriteResult {
|
||||
|
||||
[[nodiscard]] Ui::PreparedFile PrepareFolderArchive(const QString &folder);
|
||||
[[nodiscard]] Ui::PreparedFile PrepareFilesArchive(const QList<QUrl> &urls);
|
||||
[[nodiscard]] Ui::PreparedFile PrepareFilesArchive(
|
||||
const QStringList &paths,
|
||||
const QStringList &names = {});
|
||||
|
||||
[[nodiscard]] std::optional<ArchiveEntries> GatherArchiveEntries(
|
||||
const Ui::PreparedFileArchive &job);
|
||||
|
||||
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "ui/chat/attach/attach_abstract_single_file_preview.h"
|
||||
|
||||
#include "base/timer_rpl.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/text/text_options.h"
|
||||
@@ -30,7 +29,8 @@ AbstractSingleFilePreview::AbstractSingleFilePreview(
|
||||
, _type(type)
|
||||
, _captionContext(captionContext)
|
||||
, _editMedia(this, _st.files.buttonFile)
|
||||
, _deleteMedia(this, _st.files.buttonFile) {
|
||||
, _deleteMedia(this, _st.files.buttonFile)
|
||||
, _selectCheck(_st.files.selectCheck, [=] { update(); }) {
|
||||
const auto repaint = _captionContext.repaint;
|
||||
_captionContext.repaint = [=] {
|
||||
if (repaint) {
|
||||
@@ -63,15 +63,17 @@ AbstractSingleFilePreview::AbstractSingleFilePreview(
|
||||
AbstractSingleFilePreview::~AbstractSingleFilePreview() = default;
|
||||
|
||||
rpl::producer<> AbstractSingleFilePreview::editRequests() const {
|
||||
return _editMedia->clicks() | rpl::map([] {
|
||||
return base::timer_once(st::historyAttach.ripple.hideDuration);
|
||||
}) | rpl::flatten_latest();
|
||||
return _editMedia->clicks() | rpl::to_empty;
|
||||
}
|
||||
|
||||
rpl::producer<> AbstractSingleFilePreview::renameRequests() const {
|
||||
return _renameRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<> AbstractSingleFilePreview::selectRequests() const {
|
||||
return _selectRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<> AbstractSingleFilePreview::deleteRequests() const {
|
||||
return _deleteMedia->clicks() | rpl::to_empty;
|
||||
}
|
||||
@@ -91,6 +93,24 @@ void AbstractSingleFilePreview::setRenameEnabled(bool enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractSingleFilePreview::setSelectable(bool selectable) {
|
||||
_selectable = selectable && (_type == AttachControls::Type::Full);
|
||||
}
|
||||
|
||||
void AbstractSingleFilePreview::setSelectionMode(bool enabled) {
|
||||
_selectionMode = enabled;
|
||||
}
|
||||
|
||||
void AbstractSingleFilePreview::setSelected(
|
||||
bool selected,
|
||||
anim::type animated) {
|
||||
_selectCheck.setChecked(selected, animated);
|
||||
}
|
||||
|
||||
bool AbstractSingleFilePreview::selected() const {
|
||||
return _selectCheck.checked();
|
||||
}
|
||||
|
||||
void AbstractSingleFilePreview::setDisplayName(const QString &displayName) {
|
||||
_data.name = displayName;
|
||||
updateTextWidthFor(_data);
|
||||
@@ -174,6 +194,14 @@ void AbstractSingleFilePreview::paintEvent(QPaintEvent *e) {
|
||||
style::rtlrect(x, y, st.thumbSize, st.thumbSize, width()));
|
||||
p.drawPixmap(rthumb.topLeft(), _data.fileThumb);
|
||||
}
|
||||
const auto checkSize = _st.files.selectCheck.size;
|
||||
const auto check = style::rtlrect(
|
||||
x + st.thumbSize - checkSize,
|
||||
y + st.thumbSize - checkSize,
|
||||
checkSize,
|
||||
checkSize,
|
||||
width());
|
||||
_selectCheck.paint(p, check.x(), check.y(), width());
|
||||
p.setFont(st::semiboldFont);
|
||||
p.setPen(_st.files.nameFg);
|
||||
p.drawTextLeft(
|
||||
@@ -297,7 +325,11 @@ void AbstractSingleFilePreview::setData(Data data) {
|
||||
}
|
||||
|
||||
void AbstractSingleFilePreview::mousePressEvent(QMouseEvent *e) {
|
||||
if (isOverName(e->pos())) {
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
} else if (selectingByClick(e->modifiers())) {
|
||||
_selectPressed = true;
|
||||
} else if (isOverName(e->pos())) {
|
||||
_namePressed = true;
|
||||
}
|
||||
}
|
||||
@@ -309,9 +341,13 @@ void AbstractSingleFilePreview::mouseMoveEvent(QMouseEvent *e) {
|
||||
}
|
||||
|
||||
void AbstractSingleFilePreview::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (base::take(_namePressed)
|
||||
&& (e->button() == Qt::LeftButton)
|
||||
&& isOverName(e->pos())) {
|
||||
const auto selectPressed = base::take(_selectPressed);
|
||||
const auto namePressed = base::take(_namePressed);
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
} else if (selectPressed && rect().contains(e->pos())) {
|
||||
_selectRequests.fire({});
|
||||
} else if (namePressed && isOverName(e->pos())) {
|
||||
_renameRequests.fire({});
|
||||
}
|
||||
}
|
||||
@@ -335,7 +371,13 @@ QRect AbstractSingleFilePreview::nameRect() const {
|
||||
}
|
||||
|
||||
bool AbstractSingleFilePreview::isOverName(QPoint point) const {
|
||||
return _renameEnabled && nameRect().contains(point);
|
||||
return _renameEnabled && !_selectionMode && nameRect().contains(point);
|
||||
}
|
||||
|
||||
bool AbstractSingleFilePreview::selectingByClick(
|
||||
Qt::KeyboardModifiers modifiers) const {
|
||||
return _selectable
|
||||
&& (_selectionMode || modifiers.testFlag(Qt::ControlModifier));
|
||||
}
|
||||
|
||||
void AbstractSingleFilePreview::applyCursor(style::cursor cursor) {
|
||||
|
||||
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "ui/chat/attach/attach_abstract_single_preview.h"
|
||||
#include "ui/chat/attach/attach_controls.h"
|
||||
#include "ui/effects/round_checkbox.h"
|
||||
#include "base/object_ptr.h"
|
||||
|
||||
namespace style {
|
||||
@@ -31,8 +32,14 @@ public:
|
||||
[[nodiscard]] rpl::producer<> deleteRequests() const override;
|
||||
[[nodiscard]] rpl::producer<> editRequests() const override;
|
||||
[[nodiscard]] rpl::producer<> renameRequests() const;
|
||||
[[nodiscard]] rpl::producer<> selectRequests() const;
|
||||
[[nodiscard]] rpl::producer<> modifyRequests() const override;
|
||||
void setRenameEnabled(bool enabled);
|
||||
void setSelectionMode(bool enabled);
|
||||
void setSelected(
|
||||
bool selected,
|
||||
anim::type animated = anim::type::normal);
|
||||
[[nodiscard]] bool selected() const;
|
||||
virtual void setDisplayName(const QString &displayName);
|
||||
virtual void setCaption(const TextWithTags &caption);
|
||||
|
||||
@@ -56,6 +63,7 @@ protected:
|
||||
}
|
||||
|
||||
void setData(Data data);
|
||||
void setSelectable(bool selectable);
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
@@ -69,6 +77,8 @@ private:
|
||||
[[nodiscard]] QRect captionRect() const;
|
||||
[[nodiscard]] QRect nameRect() const;
|
||||
[[nodiscard]] bool isOverName(QPoint point) const;
|
||||
[[nodiscard]] bool selectingByClick(
|
||||
Qt::KeyboardModifiers modifiers) const;
|
||||
void applyCursor(style::cursor cursor);
|
||||
|
||||
const style::ComposeControls &_st;
|
||||
@@ -80,10 +90,15 @@ private:
|
||||
object_ptr<IconButton> _editMedia = { nullptr };
|
||||
object_ptr<IconButton> _deleteMedia = { nullptr };
|
||||
rpl::event_stream<> _renameRequests;
|
||||
rpl::event_stream<> _selectRequests;
|
||||
RoundCheckbox _selectCheck;
|
||||
|
||||
style::cursor _cursor = style::cur_default;
|
||||
bool _namePressed = false;
|
||||
bool _renameEnabled = false;
|
||||
bool _selectable = false;
|
||||
bool _selectionMode = false;
|
||||
bool _selectPressed = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -128,6 +128,16 @@ void AlbumPreview::toggleSpoilers(bool enabled) {
|
||||
}
|
||||
}
|
||||
|
||||
void AlbumPreview::setSelectionMode(bool enabled) {
|
||||
_selectionMode = enabled;
|
||||
}
|
||||
|
||||
void AlbumPreview::setSelected(int index, bool selected) {
|
||||
if (index >= 0 && index < _order.size()) {
|
||||
_thumbs[_order[index]]->setSelected(selected);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> AlbumPreview::takeOrder() {
|
||||
//Expects(_thumbs.size() == _order.size());
|
||||
//Expects(_itemsShownDimensions.size() == _order.size());
|
||||
@@ -184,12 +194,6 @@ void AlbumPreview::prepareThumbs(gsl::span<Ui::PreparedFile> items) {
|
||||
this,
|
||||
[=] { update(); },
|
||||
[=](QRect rect) { update(rect); },
|
||||
// Bound to the thumb that owns the button, not to whatever is
|
||||
// under the cursor when the callback runs: editing is delayed by
|
||||
// the ripple hide duration, and keyboard activation has no
|
||||
// cursor over the thumb at all. Bound by pointer and not by
|
||||
// index, because takeOrder() permutes _thumbs in place - so the
|
||||
// slot this one was built in can hold another thumb later.
|
||||
[=] { changeThumbByIndex(orderIndex(*self)); },
|
||||
[=] { deleteThumbByIndex(orderIndex(*self)); }));
|
||||
*self = _thumbs.back().get();
|
||||
@@ -515,6 +519,10 @@ void AlbumPreview::mousePressEvent(QMouseEvent *e) {
|
||||
const auto position = e->pos();
|
||||
cancelDrag();
|
||||
if (const auto thumb = findThumb(position)) {
|
||||
if (selectingByClick(e, thumb)) {
|
||||
_selectPressedThumb = thumb;
|
||||
return;
|
||||
}
|
||||
_draggedStartPosition = position;
|
||||
_pressedThumb = thumb;
|
||||
_pressedButtonType = thumb->buttonTypeFromPoint(position);
|
||||
@@ -615,6 +623,12 @@ void AlbumPreview::updateSuggestedDrag(QPoint position) {
|
||||
}
|
||||
|
||||
void AlbumPreview::mouseReleaseEvent(QMouseEvent *e) {
|
||||
if (const auto thumb = base::take(_selectPressedThumb)) {
|
||||
if (e->button() == Qt::LeftButton && findThumb(e->pos()) == thumb) {
|
||||
_thumbSelected.fire(orderIndex(thumb));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_draggedThumb) {
|
||||
finishDrag();
|
||||
_shrinkAnimation.start(
|
||||
@@ -635,6 +649,16 @@ void AlbumPreview::mouseReleaseEvent(QMouseEvent *e) {
|
||||
_pressedButtonType = AttachButtonType::None;
|
||||
}
|
||||
|
||||
bool AlbumPreview::selectingByClick(
|
||||
QMouseEvent *e,
|
||||
not_null<AlbumThumbnail*> thumb) const {
|
||||
return !_sendWay.sendImagesAsPhotos()
|
||||
&& thumb->selectable()
|
||||
&& (e->button() == Qt::LeftButton)
|
||||
&& (_selectionMode
|
||||
|| e->modifiers().testFlag(Qt::ControlModifier));
|
||||
}
|
||||
|
||||
void AlbumPreview::switchToDrag() {
|
||||
_paintedAbove
|
||||
= _suggestedThumb
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
[[nodiscard]] base::flat_set<int> collectSpoileredIndices();
|
||||
[[nodiscard]] bool canHaveSpoiler(int index) const;
|
||||
void toggleSpoilers(bool enabled);
|
||||
void setSelectionMode(bool enabled);
|
||||
void setSelected(int index, bool selected);
|
||||
[[nodiscard]] std::vector<int> takeOrder();
|
||||
|
||||
[[nodiscard]] rpl::producer<int> thumbDeleted() const {
|
||||
@@ -50,6 +52,9 @@ public:
|
||||
[[nodiscard]] rpl::producer<int> thumbModified() const {
|
||||
return _thumbModified.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<int> thumbSelected() const {
|
||||
return _thumbSelected.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<> orderUpdated() const {
|
||||
return _orderUpdated.events();
|
||||
}
|
||||
@@ -81,6 +86,9 @@ private:
|
||||
AttachButtonType type);
|
||||
|
||||
void switchToDrag();
|
||||
[[nodiscard]] bool selectingByClick(
|
||||
QMouseEvent *e,
|
||||
not_null<AlbumThumbnail*> thumb) const;
|
||||
|
||||
void paintAlbum(Painter &p) const;
|
||||
void paintPhotos(Painter &p, QRect clip) const;
|
||||
@@ -113,6 +121,8 @@ private:
|
||||
AlbumThumbnail *_suggestedThumb = nullptr;
|
||||
AlbumThumbnail *_paintedAbove = nullptr;
|
||||
AlbumThumbnail *_pressedThumb = nullptr;
|
||||
AlbumThumbnail *_selectPressedThumb = nullptr;
|
||||
bool _selectionMode = false;
|
||||
QPoint _draggedStartPosition;
|
||||
|
||||
base::Timer _dragTimer;
|
||||
@@ -121,6 +131,7 @@ private:
|
||||
rpl::event_stream<int> _thumbDeleted;
|
||||
rpl::event_stream<int> _thumbChanged;
|
||||
rpl::event_stream<int> _thumbModified;
|
||||
rpl::event_stream<int> _thumbSelected;
|
||||
rpl::event_stream<> _orderUpdated;
|
||||
|
||||
mutable Animations::Simple _thumbsHeightAnimation;
|
||||
|
||||
@@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/ui_utility.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_chat_helpers.h"
|
||||
#include "styles/style_boxes.h"
|
||||
@@ -53,7 +52,11 @@ AlbumThumbnail::AlbumThumbnail(
|
||||
, _ttlSeconds(file.ttlSeconds)
|
||||
, _isCompressedSticker(Core::IsMimeSticker(file.information->filemime))
|
||||
, _repaint(std::move(repaint))
|
||||
, _repaintRect(std::move(repaintRect)) {
|
||||
, _repaintRect(std::move(repaintRect))
|
||||
, _selectCheck(_st.files.selectCheck, _repaint)
|
||||
, _selectable(!_isCompressedSticker
|
||||
&& !file.archive
|
||||
&& !file.path.isEmpty()) {
|
||||
Expects(!_fullPreview.isNull());
|
||||
|
||||
moveToLayout(layout);
|
||||
@@ -152,18 +155,14 @@ AlbumThumbnail::AlbumThumbnail(
|
||||
_editMedia.create(parent, _st.files.buttonFile);
|
||||
_deleteMedia.create(parent, _st.files.buttonFile);
|
||||
|
||||
const auto duration = st::historyAttach.ripple.hideDuration;
|
||||
_editMedia->setClickedCallback([=] {
|
||||
// Guarded by the button, which dies with this thumbnail, so the
|
||||
// delayed edit is dropped instead of firing for a thumb that is gone.
|
||||
base::call_delayed(duration, _editMedia.data(), editCallback);
|
||||
});
|
||||
_editMedia->setClickedCallback(editCallback);
|
||||
_deleteMedia->setClickedCallback(deleteCallback);
|
||||
|
||||
_editMedia->setIconOverride(&_st.files.buttonFileEdit);
|
||||
_deleteMedia->setIconOverride(&_st.files.buttonFileDelete);
|
||||
|
||||
setSpoiler(file.spoiler);
|
||||
setSelected(file.selected, anim::type::instant);
|
||||
setButtonVisible(false);
|
||||
}
|
||||
|
||||
@@ -190,6 +189,18 @@ void AlbumThumbnail::setCaption(const TextWithTags &caption) {
|
||||
_repaint();
|
||||
}
|
||||
|
||||
void AlbumThumbnail::setSelected(bool selected, anim::type animated) {
|
||||
_selectCheck.setChecked(selected, animated);
|
||||
}
|
||||
|
||||
bool AlbumThumbnail::selected() const {
|
||||
return _selectCheck.checked();
|
||||
}
|
||||
|
||||
bool AlbumThumbnail::selectable() const {
|
||||
return _selectable;
|
||||
}
|
||||
|
||||
bool AlbumThumbnail::hasSpoiler() const {
|
||||
return _spoiler != nullptr;
|
||||
}
|
||||
@@ -586,6 +597,12 @@ void AlbumThumbnail::paintFile(
|
||||
const auto textLeft = left + st.thumbSize + st.thumbSkip;
|
||||
|
||||
p.drawPixmap(left, top, _fileThumb);
|
||||
const auto checkSize = _st.files.selectCheck.size;
|
||||
_selectCheck.paint(
|
||||
p,
|
||||
left + st.thumbSize - checkSize,
|
||||
top + st.thumbSize - checkSize,
|
||||
outerWidth);
|
||||
p.setFont(st::semiboldFont);
|
||||
p.setPen(_st.files.nameFg);
|
||||
p.drawTextLeft(
|
||||
|
||||
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "ui/chat/attach/attach_controls.h"
|
||||
#include "ui/chat/attach/attach_send_files_way.h"
|
||||
#include "ui/effects/animations.h"
|
||||
#include "ui/effects/round_checkbox.h"
|
||||
#include "ui/grouped_layout.h"
|
||||
#include "ui/round_rect.h"
|
||||
#include "base/object_ptr.h"
|
||||
@@ -42,6 +43,11 @@ public:
|
||||
void resetLayoutAnimation();
|
||||
|
||||
void setSpoiler(bool spoiler);
|
||||
void setSelected(
|
||||
bool selected,
|
||||
anim::type animated = anim::type::normal);
|
||||
[[nodiscard]] bool selected() const;
|
||||
[[nodiscard]] bool selectable() const;
|
||||
void setCaption(const TextWithTags &caption);
|
||||
[[nodiscard]] bool hasSpoiler() const;
|
||||
|
||||
@@ -134,6 +140,8 @@ private:
|
||||
QImage _cornerCache;
|
||||
Fn<void()> _repaint;
|
||||
Fn<void(QRect)> _repaintRect;
|
||||
RoundCheckbox _selectCheck;
|
||||
const bool _selectable = false;
|
||||
|
||||
QRect _lastRectOfModify;
|
||||
QRect _lastRectOfButtons;
|
||||
|
||||
@@ -66,6 +66,7 @@ struct PreparedFileArchive {
|
||||
QString folder;
|
||||
QString root;
|
||||
QStringList paths;
|
||||
QStringList names;
|
||||
};
|
||||
|
||||
struct PreparedFile {
|
||||
@@ -117,6 +118,7 @@ struct PreparedFile {
|
||||
Type type = Type::File;
|
||||
crl::time ttlSeconds = 0;
|
||||
bool spoiler = false;
|
||||
bool selected = false;
|
||||
bool sendLargePhotos = false;
|
||||
std::shared_ptr<Media::Encode::Job> animationJob;
|
||||
std::shared_ptr<PreparedFileArchive> archive;
|
||||
|
||||
@@ -118,6 +118,8 @@ void SingleFilePreview::preparePreview(const PreparedFile &file) {
|
||||
captionContext());
|
||||
|
||||
setData(std::move(data));
|
||||
setSelectable(!file.archive && !file.path.isEmpty());
|
||||
setSelected(file.selected, anim::type::instant);
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
|
||||
Reference in New Issue
Block a user