Allow delete / forward selected in shared media.

Also use PeerListBox with a chats list with global search controller
instead of HistoryHider for forward / share contact.
This commit is contained in:
John Preston 2017-10-20 19:19:42 +03:00
parent 7b69282c7e
commit be5f4c9a71
24 changed files with 508 additions and 47 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

View File

@ -100,6 +100,7 @@ void PeerListBox::prepare() {
setDimensions(st::boxWideWidth, st::boxMaxListHeight); setDimensions(st::boxWideWidth, st::boxMaxListHeight);
if (_select) { if (_select) {
_select->finishAnimating(); _select->finishAnimating();
myEnsureResized(_select);
_scrollBottomFixed = true; _scrollBottomFixed = true;
onScrollToY(0); onScrollToY(0);
} }

View File

@ -80,12 +80,31 @@ infoTopBarTitle: FlatLabel(defaultFlatLabel) {
linkFontOver: font(14px semibold); linkFontOver: font(14px semibold);
} }
} }
infoTopBarClose: IconButton(infoTopBarBack) {
icon: icon {{ "info_close", boxTitleCloseFg }};
iconOver: icon {{ "info_close", boxTitleCloseFgOver }};
}
infoTopBarForward: IconButton(infoTopBarBack) {
width: 46px;
icon: icon {{ "info_media_forward", boxTitleCloseFg }};
iconOver: icon {{ "info_media_forward", boxTitleCloseFgOver }};
iconPosition: point(6px, -1px);
rippleAreaPosition: point(1px, 6px);
}
infoTopBarDelete: IconButton(infoTopBarForward) {
icon: icon {{ "info_media_delete", boxTitleCloseFg }};
iconOver: icon {{ "info_media_delete", boxTitleCloseFgOver }};
}
infoTopBar: InfoTopBar { infoTopBar: InfoTopBar {
height: infoTopBarHeight; height: infoTopBarHeight;
back: infoTopBarBack; back: infoTopBarBack;
title: infoTopBarTitle; title: infoTopBarTitle;
titlePosition: point(23px, 18px); titlePosition: point(23px, 18px);
bg: windowBg; bg: windowBg;
mediaCancel: infoTopBarClose;
mediaActionsSkip: 4px;
mediaForward: infoTopBarForward;
mediaDelete: infoTopBarDelete;
} }
infoLayerTopBarHeight: boxLayerTitleHeight; infoLayerTopBarHeight: boxLayerTitleHeight;
@ -107,12 +126,27 @@ infoLayerTopBarClose: IconButton(infoLayerTopBarBack) {
icon: infoLayerTopBarCloseIcon; icon: infoLayerTopBarCloseIcon;
iconOver: infoLayerTopBarCloseIconOver; iconOver: infoLayerTopBarCloseIconOver;
} }
infoLayerTopBarForward: IconButton(infoLayerTopBarBack) {
width: 45px;
icon: icon {{ "info_media_forward", boxTitleCloseFg }};
iconOver: icon {{ "info_media_forward", boxTitleCloseFgOver }};
iconPosition: point(6px, -1px);
rippleAreaPosition: point(1px, 6px);
}
infoLayerTopBarDelete: IconButton(infoLayerTopBarForward) {
icon: icon {{ "info_media_delete", boxTitleCloseFg }};
iconOver: icon {{ "info_media_delete", boxTitleCloseFgOver }};
}
infoLayerTopBar: InfoTopBar { infoLayerTopBar: InfoTopBar {
height: infoLayerTopBarHeight; height: infoLayerTopBarHeight;
back: infoLayerTopBarBack; back: infoLayerTopBarBack;
title: boxTitle; title: boxTitle;
titlePosition: boxLayerTitlePosition; titlePosition: boxLayerTitlePosition;
bg: boxBg; bg: boxBg;
mediaCancel: infoLayerTopBarClose;
mediaActionsSkip: 6px;
mediaForward: infoLayerTopBarForward;
mediaDelete: infoLayerTopBarDelete;
} }
infoMinimalWidth: 324px; infoMinimalWidth: 324px;

View File

@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include <rpl/never.h> #include <rpl/never.h>
#include <rpl/combine.h> #include <rpl/combine.h>
#include <rpl/range.h>
#include "window/window_controller.h" #include "window/window_controller.h"
#include "ui/widgets/scroll_area.h" #include "ui/widgets/scroll_area.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -156,4 +157,8 @@ QRect ContentWidget::rectForFloatPlayer() const {
return mapToGlobal(_scroll->geometry()); return mapToGlobal(_scroll->geometry());
} }
rpl::producer<SelectedItems> ContentWidget::selectedListValue() const {
return rpl::single(SelectedItems(Storage::SharedMediaType::Photo));
}
} // namespace Info } // namespace Info

View File

@ -79,6 +79,10 @@ public:
bool wheelEventFromFloatPlayer(QEvent *e); bool wheelEventFromFloatPlayer(QEvent *e);
QRect rectForFloatPlayer() const; QRect rectForFloatPlayer() const;
virtual rpl::producer<SelectedItems> selectedListValue() const;
virtual void cancelSelection() {
}
protected: protected:
template <typename Widget> template <typename Widget>
Widget *setInnerWidget( Widget *setInnerWidget(

View File

@ -0,0 +1,176 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "info/info_top_bar_override.h"
#include <rpl/merge.h>
#include "styles/style_info.h"
#include "lang/lang_keys.h"
#include "info/info_wrap_widget.h"
#include "storage/storage_shared_media.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/widgets/shadow.h"
#include "mainwidget.h"
#include "boxes/confirm_box.h"
#include "boxes/peer_list_controllers.h"
namespace Info {
ChooseRecipientBoxController::ChooseRecipientBoxController(
base::lambda<void(not_null<PeerData*>)> callback)
: _callback(std::move(callback)) {
}
void ChooseRecipientBoxController::prepareViewHook() {
delegate()->peerListSetTitle(langFactory(lng_forward_choose));
}
void ChooseRecipientBoxController::rowClicked(not_null<PeerListRow*> row) {
_callback(row->peer());
}
auto ChooseRecipientBoxController::createRow(
not_null<History*> history) -> std::unique_ptr<Row> {
return std::make_unique<Row>(history);
}
TopBarOverride::TopBarOverride(
QWidget *parent,
const style::InfoTopBar &st,
SelectedItems &&items)
: RpWidget(parent)
, _st(st)
, _items(std::move(items))
, _canDelete(computeCanDelete())
, _cancel(this, _st.mediaCancel)
, _text(this, generateText(), Ui::FlatLabel::InitType::Simple, _st.title)
, _forward(this, _st.mediaForward)
, _delete(this, _st.mediaDelete) {
setAttribute(Qt::WA_OpaquePaintEvent);
updateControlsVisibility();
_forward->addClickHandler([this] { performForward(); });
_delete->addClickHandler([this] { performDelete(); });
}
QString TopBarOverride::generateText() const {
using Type = Storage::SharedMediaType;
auto phrase = [&] {
switch (_items.type) {
case Type::Photo: return lng_profile_photos;
case Type::Video: return lng_profile_videos;
case Type::File: return lng_profile_files;
case Type::MusicFile: return lng_profile_songs;
case Type::Link: return lng_profile_shared_links;
case Type::VoiceFile: return lng_profile_audios;
case Type::RoundFile: return lng_profile_rounds;
}
Unexpected("Type in TopBarOverride::generateText()");
}();
return phrase(lt_count, _items.list.size());
}
bool TopBarOverride::computeCanDelete() const {
return base::find_if(_items.list, [](const SelectedItem &item) {
return !item.canDelete;
}) == _items.list.end();
}
void TopBarOverride::setItems(SelectedItems &&items) {
_items = std::move(items);
_canDelete = computeCanDelete();
_text->setText(generateText());
updateControlsVisibility();
updateControlsGeometry(width());
}
rpl::producer<> TopBarOverride::cancelRequests() const {
return rpl::merge(
_cancel->clicks(),
_correctionCancelRequests.events());
}
int TopBarOverride::resizeGetHeight(int newWidth) {
updateControlsGeometry(newWidth);
return _st.height;
}
void TopBarOverride::updateControlsGeometry(int newWidth) {
auto right = _st.mediaActionsSkip;
if (_canDelete) {
_delete->moveToRight(right, 0, newWidth);
right += _delete->width();
}
_forward->moveToRight(right, 0, newWidth);
_cancel->moveToLeft(0, 0);
_text->moveToLeft(_cancel->width(), _st.titlePosition.y());
}
void TopBarOverride::updateControlsVisibility() {
_delete->setVisible(_canDelete);
}
void TopBarOverride::paintEvent(QPaintEvent *e) {
Painter p(this);
p.fillRect(e->rect(), _st.bg);
}
SelectedItemSet TopBarOverride::collectItems() const {
auto result = SelectedItemSet();
for (auto value : _items.list) {
if (auto item = App::histItemById(value.msgId)) {
result.insert(result.size(), item);
}
}
return result;
}
void TopBarOverride::performForward() {
auto items = collectItems();
if (items.empty()) {
_correctionCancelRequests.fire({});
return;
}
auto callback = [items = std::move(items)](not_null<PeerData*> peer) {
App::main()->setForwardDraft(peer->id, items);
};
Ui::show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(std::move(callback)),
[](not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_cancel), [box] {
box->closeBox();
});
}));
}
void TopBarOverride::performDelete() {
auto items = collectItems();
if (items.empty()) {
_correctionCancelRequests.fire({});
} else {
Ui::show(Box<DeleteMessagesBox>(items));
}
}
} // namespace Info

View File

@ -0,0 +1,91 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "ui/rp_widget.h"
#include "info/info_wrap_widget.h"
#include "boxes/peer_list_controllers.h"
namespace style {
struct InfoTopBar;
} // namespace style
namespace Ui {
class IconButton;
class FlatLabel;
} // namespace Ui
namespace Info {
class ChooseRecipientBoxController : public ChatsListBoxController {
public:
ChooseRecipientBoxController(
base::lambda<void(not_null<PeerData*>)> callback);
void rowClicked(not_null<PeerListRow*> row) override;
protected:
void prepareViewHook() override;
std::unique_ptr<Row> createRow(
not_null<History*> history) override;
base::lambda<void(not_null<PeerData*>)> _callback;
};
class TopBarOverride : public Ui::RpWidget {
public:
TopBarOverride(
QWidget *parent,
const style::InfoTopBar &st,
SelectedItems &&items);
void setItems(SelectedItems &&items);
rpl::producer<> cancelRequests() const;
protected:
int resizeGetHeight(int newWidth) override;
void paintEvent(QPaintEvent *e) override;
private:
void updateControlsVisibility();
void updateControlsGeometry(int newWidth);
QString generateText() const;
[[nodiscard]] bool computeCanDelete() const;
[[nodiscard]] SelectedItemSet collectItems() const;
void performForward();
void performDelete();
const style::InfoTopBar &_st;
SelectedItems _items;
bool _canDelete = false;
object_ptr<Ui::IconButton> _cancel;
object_ptr<Ui::FlatLabel> _text;
object_ptr<Ui::IconButton> _forward;
object_ptr<Ui::IconButton> _delete;
rpl::event_stream<> _correctionCancelRequests;
};
} // namespace Info

View File

@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "info/info_content_widget.h" #include "info/info_content_widget.h"
#include "info/info_memento.h" #include "info/info_memento.h"
#include "info/info_top_bar.h" #include "info/info_top_bar.h"
#include "info/info_top_bar_override.h"
#include "ui/widgets/discrete_sliders.h" #include "ui/widgets/discrete_sliders.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
@ -39,6 +40,15 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_profile.h" #include "styles/style_profile.h"
namespace Info { namespace Info {
namespace {
const style::InfoTopBar &TopBarStyle(Wrap wrap) {
return (wrap == Wrap::Layer)
? st::infoLayerTopBar
: st::infoTopBar;
}
} // namespace
struct WrapWidget::StackItem { struct WrapWidget::StackItem {
std::unique_ptr<ContentMemento> section; std::unique_ptr<ContentMemento> section;
@ -54,6 +64,12 @@ WrapWidget::WrapWidget(
, _wrap(wrap) , _wrap(wrap)
, _topShadow(this) { , _topShadow(this) {
_topShadow->toggleOn(topShadowToggledValue()); _topShadow->toggleOn(topShadowToggledValue());
selectedListValue()
| rpl::start_with_next([this](SelectedItems &&items) {
InvokeQueued(this, [this, items = std::move(items)]() mutable {
refreshTopBarOverride(std::move(items));
});
}, lifetime());
showNewContent(memento->content()); showNewContent(memento->content());
} }
@ -182,11 +198,7 @@ void WrapWidget::setupTop(
void WrapWidget::createTopBar( void WrapWidget::createTopBar(
const Section &section, const Section &section,
PeerId peerId) { PeerId peerId) {
_topBar.create( _topBar.create(this, TopBarStyle(wrap()));
this,
(wrap() == Wrap::Layer)
? st::infoLayerTopBar
: st::infoTopBar);
_topBar->setTitle(TitleValue( _topBar->setTitle(TitleValue(
section, section,
@ -212,6 +224,52 @@ void WrapWidget::createTopBar(
_topBar->show(); _topBar->show();
} }
void WrapWidget::refreshTopBarOverride(SelectedItems &&items) {
if (items.list.empty()) {
destroyTopBarOverride();
} else if (_topBarOverride) {
_topBarOverride->setItems(std::move(items));
} else {
createTopBarOverride(std::move(items));
}
}
void WrapWidget::destroyTopBarOverride() {
if (!_topBarOverride) {
return;
}
auto widget = std::exchange(_topBarOverride, nullptr);
auto handle = weak(widget.data());
_topBarOverrideAnimation.start([this, handle] {
}, 1., 0., st::slideWrapDuration);
widget.destroy();
if (_topTabs) {
_topTabs->show();
} else if (_topBar) {
_topBar->show();
}
}
void WrapWidget::createTopBarOverride(SelectedItems &&items) {
Expects(_topBarOverride == nullptr);
_topBarOverride.create(
this,
TopBarStyle(wrap()),
std::move(items));
if (_topTabs) {
_topTabs->hide();
} else if (_topBar) {
_topBar->hide();
}
_topBarOverride->cancelRequests()
| rpl::start_with_next([this](auto) {
_content->cancelSelection();
}, _topBarOverride->lifetime());
_topBarOverride->moveToLeft(0, 0);
_topBarOverride->resizeToWidth(width());
_topBarOverride->show();
}
void WrapWidget::showBackFromStack() { void WrapWidget::showBackFromStack() {
auto params = Window::SectionShow( auto params = Window::SectionShow(
Window::SectionShow::Way::Backward); Window::SectionShow::Way::Backward);
@ -245,6 +303,7 @@ void WrapWidget::finishShowContent() {
updateContentGeometry(); updateContentGeometry();
_desiredHeights.fire(desiredHeightForContent()); _desiredHeights.fire(desiredHeightForContent());
_desiredShadowVisibilities.fire(_content->desiredShadowVisibility()); _desiredShadowVisibilities.fire(_content->desiredShadowVisibility());
_selectedLists.fire(_content->selectedListValue());
_topShadow->raise(); _topShadow->raise();
_topShadow->finishAnimating(); _topShadow->finishAnimating();
if (_topTabs) { if (_topTabs) {
@ -268,6 +327,10 @@ rpl::producer<int> WrapWidget::desiredHeightForContent() const {
$1 + $2); $1 + $2);
} }
rpl::producer<SelectedItems> WrapWidget::selectedListValue() const {
return _selectedLists.events() | rpl::flatten_latest();
}
object_ptr<ContentWidget> WrapWidget::createContent(Tab tab) { object_ptr<ContentWidget> WrapWidget::createContent(Tab tab) {
switch (tab) { switch (tab) {
case Tab::Profile: return createProfileWidget(); case Tab::Profile: return createProfileWidget();
@ -450,6 +513,9 @@ void WrapWidget::resizeEvent(QResizeEvent *e) {
} else if (_topBar) { } else if (_topBar) {
_topBar->resizeToWidth(width()); _topBar->resizeToWidth(width());
} }
if (_topBarOverride) {
_topBarOverride->resizeToWidth(width());
}
updateContentGeometry(); updateContentGeometry();
} }

View File

@ -49,6 +49,7 @@ class MoveMemento;
class ContentMemento; class ContentMemento;
class ContentWidget; class ContentWidget;
class TopBar; class TopBar;
class TopBarOverride;
enum class Wrap { enum class Wrap {
Layer, Layer,
@ -87,6 +88,25 @@ private:
}; };
struct SelectedItem {
explicit SelectedItem(FullMsgId msgId) : msgId(msgId) {
}
FullMsgId msgId;
bool canDelete = false;
bool canForward = false;
};
struct SelectedItems {
explicit SelectedItems(Storage::SharedMediaType type)
: type(type) {
}
Storage::SharedMediaType type;
std::vector<SelectedItem> list;
};
class WrapWidget final : public Window::SectionWidget { class WrapWidget final : public Window::SectionWidget {
public: public:
WrapWidget( WrapWidget(
@ -171,11 +191,18 @@ private:
object_ptr<ContentWidget> createContent( object_ptr<ContentWidget> createContent(
not_null<ContentMemento*> memento); not_null<ContentMemento*> memento);
rpl::producer<SelectedItems> selectedListValue() const;
void refreshTopBarOverride(SelectedItems &&items);
void createTopBarOverride(SelectedItems &&items);
void destroyTopBarOverride();
rpl::variable<Wrap> _wrap; rpl::variable<Wrap> _wrap;
object_ptr<ContentWidget> _content = { nullptr }; object_ptr<ContentWidget> _content = { nullptr };
object_ptr<Ui::PlainShadow> _topTabsBackground = { nullptr }; object_ptr<Ui::PlainShadow> _topTabsBackground = { nullptr };
object_ptr<Ui::SettingsSlider> _topTabs = { nullptr }; object_ptr<Ui::SettingsSlider> _topTabs = { nullptr };
object_ptr<TopBar> _topBar = { nullptr }; object_ptr<TopBar> _topBar = { nullptr };
object_ptr<TopBarOverride> _topBarOverride = { nullptr };
Animation _topBarOverrideAnimation;
object_ptr<Ui::FadeShadow> _topShadow; object_ptr<Ui::FadeShadow> _topShadow;
Tab _tab = Tab::Profile; Tab _tab = Tab::Profile;
std::unique_ptr<ContentMemento> _anotherTabMemento; std::unique_ptr<ContentMemento> _anotherTabMemento;
@ -183,6 +210,7 @@ private:
rpl::event_stream<rpl::producer<int>> _desiredHeights; rpl::event_stream<rpl::producer<int>> _desiredHeights;
rpl::event_stream<rpl::producer<bool>> _desiredShadowVisibilities; rpl::event_stream<rpl::producer<bool>> _desiredShadowVisibilities;
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists;
}; };

View File

@ -47,7 +47,7 @@ inline auto MediaTextPhrase(Type type) {
case Type::VoiceFile: return lng_profile_audios; case Type::VoiceFile: return lng_profile_audios;
case Type::RoundFile: return lng_profile_rounds; case Type::RoundFile: return lng_profile_rounds;
} }
Unexpected("Type in setupSharedMedia()"); Unexpected("Type in MediaTextPhrase()");
}; };
inline auto MediaText(Type type) { inline auto MediaText(Type type) {

View File

@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/ */
#include "info/media/info_media_inner_widget.h" #include "info/media/info_media_inner_widget.h"
#include <rpl/flatten_latest.h>
#include "boxes/abstract_box.h" #include "boxes/abstract_box.h"
#include "info/media/info_media_list_widget.h" #include "info/media/info_media_list_widget.h"
#include "info/media/info_media_buttons.h" #include "info/media/info_media_buttons.h"
@ -246,6 +247,7 @@ object_ptr<ListWidget> InnerWidget::setupList(
| rpl::start_to_stream( | rpl::start_to_stream(
_scrollToRequests, _scrollToRequests,
result->lifetime()); result->lifetime());
_selectedLists.fire(result->selectedListValue());
return result; return result;
} }
@ -255,6 +257,15 @@ void InnerWidget::saveState(not_null<Memento*> memento) {
void InnerWidget::restoreState(not_null<Memento*> memento) { void InnerWidget::restoreState(not_null<Memento*> memento) {
} }
rpl::producer<SelectedItems> InnerWidget::selectedListValue() const {
return _selectedLists.events_starting_with(_list->selectedListValue())
| rpl::flatten_latest();
}
void InnerWidget::cancelSelection() {
_list->cancelSelection();
}
int InnerWidget::resizeGetHeight(int newWidth) { int InnerWidget::resizeGetHeight(int newWidth) {
_inResize = true; _inResize = true;
auto guard = gsl::finally([this] { _inResize = false; }); auto guard = gsl::finally([this] { _inResize = false; });

View File

@ -55,6 +55,8 @@ public:
rpl::producer<int> scrollToRequests() const { rpl::producer<int> scrollToRequests() const {
return _scrollToRequests.events(); return _scrollToRequests.events();
} }
rpl::producer<SelectedItems> selectedListValue() const;
void cancelSelection();
protected: protected:
int resizeGetHeight(int newWidth) override; int resizeGetHeight(int newWidth) override;
@ -86,6 +88,7 @@ private:
object_ptr<ListWidget> _list = { nullptr }; object_ptr<ListWidget> _list = { nullptr };
rpl::event_stream<int> _scrollToRequests; rpl::event_stream<int> _scrollToRequests;
rpl::event_stream<rpl::producer<SelectedItems>> _selectedLists;
}; };

View File

@ -159,21 +159,14 @@ bool ListWidget::IsAfter(
bool ListWidget::SkipSelectFromItem(const CursorState &state) { bool ListWidget::SkipSelectFromItem(const CursorState &state) {
if (state.cursor.y() >= state.size.height() if (state.cursor.y() >= state.size.height()
&& state.cursor.x() >= 0) { || state.cursor.x() >= state.size.width()) {
return true;
} else if (state.cursor.x() >= state.size.width()
&& state.cursor.y() >= 0) {
return true; return true;
} }
return false; return false;
} }
bool ListWidget::SkipSelectTillItem(const CursorState &state) { bool ListWidget::SkipSelectTillItem(const CursorState &state) {
if (state.cursor.y() < state.size.height() if (state.cursor.x() < 0 || state.cursor.y() < 0) {
&& state.cursor.x() < 0) {
return true;
} else if (state.cursor.x() < state.size.width()
&& state.cursor.y() < 0) {
return true; return true;
} }
return false; return false;
@ -600,7 +593,6 @@ void ListWidget::itemRemoved(not_null<const HistoryItem*> item) {
auto i = _selected.find(universalId); auto i = _selected.find(universalId);
if (i != _selected.cend()) { if (i != _selected.cend()) {
removeItemSelection(i); removeItemSelection(i);
pushSelectedItems();
} }
mouseActionUpdate(_mousePosition); mouseActionUpdate(_mousePosition);
@ -630,18 +622,20 @@ auto ListWidget::collectSelectedItems() const -> SelectedItems {
auto transformation = [&](const auto &item) { auto transformation = [&](const auto &item) {
return convert(item.first, item.second); return convert(item.first, item.second);
}; };
auto items = SelectedItems(); auto items = SelectedItems(_type);
items.reserve(_selected.size()); if (hasSelectedItems()) {
std::transform( items.list.reserve(_selected.size());
_selected.begin(), std::transform(
_selected.end(), _selected.begin(),
std::back_inserter(items), _selected.end(),
transformation); std::back_inserter(items.list),
transformation);
}
return items; return items;
} }
void ListWidget::pushSelectedItems() { void ListWidget::pushSelectedItems() {
_selectedItemsStream.fire(collectSelectedItems()); _selectedListStream.fire(collectSelectedItems());
} }
bool ListWidget::hasSelected() const { bool ListWidget::hasSelected() const {
@ -661,6 +655,7 @@ void ListWidget::removeItemSelection(
if (_selected.empty()) { if (_selected.empty()) {
update(); update();
} }
pushSelectedItems();
} }
bool ListWidget::hasSelectedText() const { bool ListWidget::hasSelectedText() const {
@ -854,8 +849,8 @@ void ListWidget::refreshRows() {
clearStaleLayouts(); clearStaleLayouts();
resizeToWidth(width()); resizeToWidth(width());
restoreScrollState(); restoreScrollState();
mouseActionUpdate();
} }
void ListWidget::markLayoutsStale() { void ListWidget::markLayoutsStale() {
@ -1025,7 +1020,7 @@ void ListWidget::paintEvent(QPaintEvent *e) {
fromSectionIt, fromSectionIt,
clip.y() + clip.height()); clip.y() + clip.height());
auto context = Context { auto context = Context {
Layout::PaintContext(ms, !_selected.empty()), Layout::PaintContext(ms, hasSelectedItems()),
&_selected, &_selected,
&_dragSelected, &_dragSelected,
_dragSelectAction _dragSelectAction
@ -1113,6 +1108,7 @@ void ListWidget::applyItemSelection(
universalId, universalId,
selection)) { selection)) {
repaintItem(universalId); repaintItem(universalId);
pushSelectedItems();
} }
} }
@ -1203,10 +1199,12 @@ void ListWidget::clearSelected() {
} }
if (hasSelectedText()) { if (hasSelectedText()) {
repaintItem(_selected.begin()->first); repaintItem(_selected.begin()->first);
_selected.clear();
} else { } else {
_selected.clear();
pushSelectedItems();
update(); update();
} }
_selected.clear();
} }
void ListWidget::validateTrippleClickStartTime() { void ListWidget::validateTrippleClickStartTime() {
@ -1246,7 +1244,7 @@ QPoint ListWidget::clampMousePosition(QPoint position) const {
} }
void ListWidget::mouseActionUpdate(const QPoint &screenPos) { void ListWidget::mouseActionUpdate(const QPoint &screenPos) {
if (_sections.empty()) { if (_sections.empty() || _visibleBottom <= _visibleTop) {
return; return;
} }
@ -1657,6 +1655,7 @@ void ListWidget::mouseActionFinish(const QPoint &screenPos, Qt::MouseButton butt
void ListWidget::applyDragSelection() { void ListWidget::applyDragSelection() {
applyDragSelection(_selected); applyDragSelection(_selected);
clearDragSelection(); clearDragSelection();
pushSelectedItems();
} }
void ListWidget::applyDragSelection(SelectedMap &applyTo) const { void ListWidget::applyDragSelection(SelectedMap &applyTo) const {
@ -1693,6 +1692,9 @@ void ListWidget::mouseActionUpdate() {
void ListWidget::clearStaleLayouts() { void ListWidget::clearStaleLayouts() {
for (auto i = _layouts.begin(); i != _layouts.end();) { for (auto i = _layouts.begin(); i != _layouts.end();) {
if (i->second.stale) { if (i->second.stale) {
if (i->second.item.get() == _overLayout) {
_overLayout = nullptr;
}
i = _layouts.erase(i); i = _layouts.erase(i);
} else { } else {
++i; ++i;

View File

@ -66,17 +66,12 @@ public:
rpl::producer<int> scrollToRequests() const { rpl::producer<int> scrollToRequests() const {
return _scrollToRequests.events(); return _scrollToRequests.events();
} }
struct SelectedItem { rpl::producer<SelectedItems> selectedListValue() const {
explicit SelectedItem(FullMsgId msgId) : msgId(msgId) { return _selectedListStream.events_starting_with(
} collectSelectedItems());
}
FullMsgId msgId; void cancelSelection() {
bool canDelete = false; clearSelected();
bool canForward = false;
};
using SelectedItems = std::vector<SelectedItem>;
rpl::producer<SelectedItems> selectedItemsValue() const {
return _selectedItemsStream.events();
} }
~ListWidget(); ~ListWidget();
@ -246,10 +241,6 @@ private:
void updateDragSelection(); void updateDragSelection();
void clearDragSelection(); void clearDragSelection();
void setDragSelection(
BaseLayout *dragSelectFrom,
BaseLayout *dragSelectTill,
DragSelectAction action);
void trySwitchToWordSelection(); void trySwitchToWordSelection();
void switchToWordSelection(); void switchToWordSelection();
@ -285,7 +276,7 @@ private:
bool _pressWasInactive = false; bool _pressWasInactive = false;
SelectedMap _selected; SelectedMap _selected;
SelectedMap _dragSelected; SelectedMap _dragSelected;
rpl::event_stream<SelectedItems> _selectedItemsStream; rpl::event_stream<SelectedItems> _selectedListStream;
style::cursor _cursor = style::cur_default; style::cursor _cursor = style::cur_default;
DragSelectAction _dragSelectAction = DragSelectAction::None; DragSelectAction _dragSelectAction = DragSelectAction::None;
bool _wasSelectedText = false; // was some text selected in current drag action bool _wasSelectedText = false; // was some text selected in current drag action

View File

@ -60,6 +60,14 @@ Widget::Widget(
}, _inner->lifetime()); }, _inner->lifetime());
} }
rpl::producer<SelectedItems> Widget::selectedListValue() const {
return _inner->selectedListValue();
}
void Widget::cancelSelection() {
_inner->cancelSelection();
}
Section Widget::section() const { Section Widget::section() const {
return Section(type()); return Section(type());
} }

View File

@ -79,10 +79,13 @@ public:
const QRect &geometry, const QRect &geometry,
not_null<Memento*> memento); not_null<Memento*> memento);
rpl::producer<SelectedItems> selectedListValue() const override;
void cancelSelection() override;
private: private:
void saveState(not_null<Memento*> memento); void saveState(not_null<Memento*> memento);
void restoreState(not_null<Memento*> memento); void restoreState(not_null<Memento*> memento);
InnerWidget *_inner = nullptr; InnerWidget *_inner = nullptr;
}; };

View File

@ -31,6 +31,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "info/profile/info_profile_icon.h" #include "info/profile/info_profile_icon.h"
#include "info/profile/info_profile_members.h" #include "info/profile/info_profile_members.h"
#include "info/media/info_media_buttons.h" #include "info/media/info_media_buttons.h"
#include "info/info_top_bar_override.h"
#include "boxes/abstract_box.h" #include "boxes/abstract_box.h"
#include "boxes/add_contact_box.h" #include "boxes/add_contact_box.h"
#include "boxes/confirm_box.h" #include "boxes/confirm_box.h"
@ -351,7 +352,7 @@ object_ptr<Ui::RpWidget> InnerWidget::setupUserActions(
addButton( addButton(
Lang::Viewer(lng_profile_share_contact), Lang::Viewer(lng_profile_share_contact),
CanShareContactValue(user), CanShareContactValue(user),
[user] { App::main()->shareContactLayer(user); }); [this, user] { shareContact(user); });
addButton( addButton(
Lang::Viewer(lng_info_edit_contact), Lang::Viewer(lng_info_edit_contact),
IsContactValue(user), IsContactValue(user),
@ -422,6 +423,36 @@ object_ptr<Ui::RpWidget> InnerWidget::setupUserActions(
return std::move(result); return std::move(result);
} }
void InnerWidget::shareContact(not_null<UserData*> user) const {
auto callback = [user](not_null<PeerData*> peer) {
if (!peer->canWrite()) {
Ui::show(Box<InformBox>(
lang(lng_forward_share_cant)),
LayerOption::KeepOther);
return;
}
auto recipient = peer->isUser()
? peer->name
: '\xAB' + peer->name + '\xBB';
Ui::show(Box<ConfirmBox>(
lng_forward_share_contact(lt_recipient, recipient),
lang(lng_forward_send),
[peer, user] {
App::main()->onShareContact(
peer->id,
user);
Ui::hideLayer();
}), LayerOption::KeepOther);
};
Ui::show(Box<PeerListBox>(
std::make_unique<ChooseRecipientBoxController>(std::move(callback)),
[](not_null<PeerListBox*> box) {
box->addButton(langFactory(lng_cancel), [box] {
box->closeBox();
});
}));
}
object_ptr<Ui::RpWidget> InnerWidget::createSkipWidget( object_ptr<Ui::RpWidget> InnerWidget::createSkipWidget(
RpWidget *parent) const { RpWidget *parent) const {
return Ui::CreateSkipWidget(parent, st::infoProfileSkip); return Ui::CreateSkipWidget(parent, st::infoProfileSkip);

View File

@ -88,6 +88,7 @@ private:
object_ptr<RpWidget> setupUserActions( object_ptr<RpWidget> setupUserActions(
RpWidget *parent, RpWidget *parent,
not_null<UserData*> user) const; not_null<UserData*> user) const;
void shareContact(not_null<UserData*> user) const;
object_ptr<RpWidget> createSkipWidget(RpWidget *parent) const; object_ptr<RpWidget> createSkipWidget(RpWidget *parent) const;
object_ptr<Ui::SlideWrap<RpWidget>> createSlideSkipWidget( object_ptr<Ui::SlideWrap<RpWidget>> createSlideSkipWidget(

View File

@ -677,7 +677,7 @@ bool MainWidget::setForwardDraft(PeerId peerId, const SelectedItemSet &items) {
auto peer = App::peer(peerId); auto peer = App::peer(peerId);
auto error = GetErrorTextForForward(peer, items); auto error = GetErrorTextForForward(peer, items);
if (!error.isEmpty()) { if (!error.isEmpty()) {
Ui::show(Box<InformBox>(error)); Ui::show(Box<InformBox>(error), LayerOption::KeepOther);
return false; return false;
} }

View File

@ -1117,4 +1117,8 @@ InfoTopBar {
title: FlatLabel; title: FlatLabel;
titlePosition: point; titlePosition: point;
bg: color; bg: color;
mediaCancel: IconButton;
mediaActionsSkip: pixels;
mediaForward: IconButton;
mediaDelete: IconButton;
} }

View File

@ -219,6 +219,8 @@
<(src_loc)/info/info_section_widget.h <(src_loc)/info/info_section_widget.h
<(src_loc)/info/info_top_bar.cpp <(src_loc)/info/info_top_bar.cpp
<(src_loc)/info/info_top_bar.h <(src_loc)/info/info_top_bar.h
<(src_loc)/info/info_top_bar_override.cpp
<(src_loc)/info/info_top_bar_override.h
<(src_loc)/info/info_wrap_widget.cpp <(src_loc)/info/info_wrap_widget.cpp
<(src_loc)/info/info_wrap_widget.h <(src_loc)/info/info_wrap_widget.h
<(src_loc)/info/media/info_media_buttons.h <(src_loc)/info/media/info_media_buttons.h