2016-10-26 16:43:13 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2016-10-26 16:43:13 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2016-10-26 16:43:13 +00:00
|
|
|
*/
|
|
|
|
#include "history/history_drag_area.h"
|
|
|
|
|
2020-06-09 23:29:24 +00:00
|
|
|
#include "base/event_filter.h"
|
2021-10-18 21:36:55 +00:00
|
|
|
#include "ui/boxes/confirm_box.h"
|
2017-04-06 14:38:10 +00:00
|
|
|
#include "boxes/sticker_set_box.h"
|
2016-10-26 16:43:13 +00:00
|
|
|
#include "inline_bots/inline_bot_result.h"
|
|
|
|
#include "inline_bots/inline_bot_layout_item.h"
|
2021-10-01 10:48:52 +00:00
|
|
|
#include "dialogs/ui/dialogs_layout.h"
|
2017-06-29 10:27:09 +00:00
|
|
|
#include "history/history_widget.h"
|
2017-03-04 10:23:56 +00:00
|
|
|
#include "storage/localstorage.h"
|
2017-04-13 08:27:10 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2019-09-13 12:22:54 +00:00
|
|
|
#include "ui/widgets/shadow.h"
|
|
|
|
#include "ui/ui_utility.h"
|
2020-10-13 15:11:53 +00:00
|
|
|
#include "ui/cached_round_corners.h"
|
2016-10-26 16:43:13 +00:00
|
|
|
#include "mainwindow.h"
|
|
|
|
#include "apiwrap.h"
|
|
|
|
#include "mainwidget.h"
|
2020-06-09 23:29:24 +00:00
|
|
|
#include "storage/storage_media_prepare.h"
|
2019-09-13 12:22:54 +00:00
|
|
|
#include "styles/style_chat_helpers.h"
|
2019-09-18 11:19:05 +00:00
|
|
|
#include "styles/style_layers.h"
|
2016-10-26 16:43:13 +00:00
|
|
|
|
2020-06-09 23:29:24 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
constexpr auto kDragAreaEvents = {
|
|
|
|
QEvent::DragEnter,
|
|
|
|
QEvent::DragLeave,
|
|
|
|
QEvent::Drop,
|
|
|
|
QEvent::MouseButtonRelease,
|
|
|
|
QEvent::Leave,
|
|
|
|
};
|
|
|
|
|
2020-06-10 13:10:19 +00:00
|
|
|
inline auto InnerRect(not_null<Ui::RpWidget*> widget) {
|
|
|
|
return QRect(
|
|
|
|
st::dragPadding.left(),
|
|
|
|
st::dragPadding.top(),
|
|
|
|
widget->width() - st::dragPadding.left() - st::dragPadding.right(),
|
|
|
|
widget->height() - st::dragPadding.top() - st::dragPadding.bottom());
|
|
|
|
}
|
|
|
|
|
2020-06-09 23:29:24 +00:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
DragArea::Areas DragArea::SetupDragAreaToContainer(
|
|
|
|
not_null<Ui::RpWidget*> container,
|
2020-06-13 13:08:55 +00:00
|
|
|
Fn<bool(not_null<const QMimeData*>)> &&dragEnterFilter,
|
2020-06-09 23:29:24 +00:00
|
|
|
Fn<void(bool)> &&setAcceptDropsField,
|
2020-06-13 13:08:55 +00:00
|
|
|
Fn<void()> &&updateControlsGeometry,
|
2021-05-06 21:47:49 +00:00
|
|
|
DragArea::CallbackComputeState &&computeState,
|
|
|
|
bool hideSubtext) {
|
2020-06-09 23:29:24 +00:00
|
|
|
|
|
|
|
using DragState = Storage::MimeDataState;
|
|
|
|
|
|
|
|
auto &lifetime = container->lifetime();
|
|
|
|
container->setAcceptDrops(true);
|
|
|
|
|
2020-06-10 11:46:27 +00:00
|
|
|
const auto attachDragDocument =
|
|
|
|
Ui::CreateChild<DragArea>(container.get());
|
2020-06-09 23:29:24 +00:00
|
|
|
const auto attachDragPhoto = Ui::CreateChild<DragArea>(container.get());
|
|
|
|
|
|
|
|
attachDragDocument->hide();
|
|
|
|
attachDragPhoto->hide();
|
|
|
|
|
|
|
|
attachDragDocument->raise();
|
|
|
|
attachDragPhoto->raise();
|
|
|
|
|
|
|
|
const auto attachDragState =
|
|
|
|
lifetime.make_state<DragState>(DragState::None);
|
|
|
|
|
|
|
|
const auto width = [=] {
|
|
|
|
return container->width();
|
|
|
|
};
|
|
|
|
const auto height = [=] {
|
|
|
|
return container->height();
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto horizontalMargins = st::dragMargin.left()
|
|
|
|
+ st::dragMargin.right();
|
|
|
|
const auto verticalMargins = st::dragMargin.top()
|
|
|
|
+ st::dragMargin.bottom();
|
|
|
|
const auto resizeToFull = [=](not_null<DragArea*> w) {
|
|
|
|
w->resize(width() - horizontalMargins, height() - verticalMargins);
|
|
|
|
};
|
|
|
|
const auto moveToTop = [=](not_null<DragArea*> w) {
|
|
|
|
w->move(st::dragMargin.left(), st::dragMargin.top());
|
|
|
|
};
|
|
|
|
const auto updateAttachGeometry = crl::guard(container, [=] {
|
|
|
|
if (updateControlsGeometry) {
|
|
|
|
updateControlsGeometry();
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (*attachDragState) {
|
|
|
|
case DragState::Files:
|
|
|
|
resizeToFull(attachDragDocument);
|
|
|
|
moveToTop(attachDragDocument);
|
|
|
|
break;
|
|
|
|
case DragState::PhotoFiles:
|
|
|
|
attachDragDocument->resize(
|
|
|
|
width() - horizontalMargins,
|
|
|
|
(height() - verticalMargins) / 2);
|
|
|
|
moveToTop(attachDragDocument);
|
|
|
|
attachDragPhoto->resize(
|
|
|
|
attachDragDocument->width(),
|
|
|
|
attachDragDocument->height());
|
|
|
|
attachDragPhoto->move(
|
|
|
|
st::dragMargin.left(),
|
|
|
|
height()
|
|
|
|
- attachDragPhoto->height()
|
|
|
|
- st::dragMargin.bottom());
|
|
|
|
break;
|
|
|
|
case DragState::Image:
|
|
|
|
resizeToFull(attachDragPhoto);
|
|
|
|
moveToTop(attachDragPhoto);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const auto updateDragAreas = [=] {
|
|
|
|
if (setAcceptDropsField) {
|
|
|
|
setAcceptDropsField(*attachDragState == DragState::None);
|
|
|
|
}
|
|
|
|
updateAttachGeometry();
|
|
|
|
|
|
|
|
switch (*attachDragState) {
|
|
|
|
case DragState::None:
|
|
|
|
attachDragDocument->otherLeave();
|
|
|
|
attachDragPhoto->otherLeave();
|
|
|
|
break;
|
|
|
|
case DragState::Files:
|
|
|
|
attachDragDocument->setText(
|
|
|
|
tr::lng_drag_files_here(tr::now),
|
2021-05-06 21:47:49 +00:00
|
|
|
hideSubtext
|
|
|
|
? QString()
|
|
|
|
: tr::lng_drag_to_send_files(tr::now));
|
2020-06-09 23:29:24 +00:00
|
|
|
attachDragDocument->otherEnter();
|
|
|
|
attachDragPhoto->hideFast();
|
|
|
|
break;
|
|
|
|
case DragState::PhotoFiles:
|
|
|
|
attachDragDocument->setText(
|
|
|
|
tr::lng_drag_images_here(tr::now),
|
2021-05-06 21:47:49 +00:00
|
|
|
hideSubtext
|
|
|
|
? QString()
|
|
|
|
: tr::lng_drag_to_send_no_compression(tr::now));
|
2020-06-09 23:29:24 +00:00
|
|
|
attachDragPhoto->setText(
|
|
|
|
tr::lng_drag_photos_here(tr::now),
|
2021-05-06 21:47:49 +00:00
|
|
|
hideSubtext
|
|
|
|
? QString()
|
|
|
|
: tr::lng_drag_to_send_quick(tr::now));
|
2020-06-09 23:29:24 +00:00
|
|
|
attachDragDocument->otherEnter();
|
|
|
|
attachDragPhoto->otherEnter();
|
|
|
|
break;
|
|
|
|
case DragState::Image:
|
|
|
|
attachDragPhoto->setText(
|
|
|
|
tr::lng_drag_images_here(tr::now),
|
2021-05-06 21:47:49 +00:00
|
|
|
hideSubtext
|
|
|
|
? QString()
|
|
|
|
: tr::lng_drag_to_send_quick(tr::now));
|
2020-06-09 23:29:24 +00:00
|
|
|
attachDragDocument->hideFast();
|
|
|
|
attachDragPhoto->otherEnter();
|
|
|
|
break;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
container->sizeValue(
|
|
|
|
) | rpl::start_with_next(updateAttachGeometry, lifetime);
|
|
|
|
|
|
|
|
const auto resetDragStateIfNeeded = [=] {
|
|
|
|
if (*attachDragState != DragState::None
|
|
|
|
|| !attachDragPhoto->isHidden()
|
|
|
|
|| !attachDragDocument->isHidden()) {
|
|
|
|
*attachDragState = DragState::None;
|
|
|
|
updateDragAreas();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto dragEnterEvent = [=](QDragEnterEvent *e) {
|
2020-06-13 13:08:55 +00:00
|
|
|
if (dragEnterFilter && !dragEnterFilter(e->mimeData())) {
|
2020-06-09 23:29:24 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-13 13:08:55 +00:00
|
|
|
*attachDragState = computeState
|
|
|
|
? computeState(e->mimeData())
|
|
|
|
: Storage::ComputeMimeDataState(e->mimeData());
|
2020-06-09 23:29:24 +00:00
|
|
|
updateDragAreas();
|
|
|
|
|
|
|
|
if (*attachDragState != DragState::None) {
|
|
|
|
e->setDropAction(Qt::IgnoreAction);
|
|
|
|
e->accept();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto dragLeaveEvent = [=](QDragLeaveEvent *e) {
|
|
|
|
resetDragStateIfNeeded();
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto dropEvent = [=](QDropEvent *e) {
|
2020-06-13 13:08:55 +00:00
|
|
|
// Hide fast to avoid visual bugs in resizable boxes.
|
|
|
|
attachDragDocument->hideFast();
|
|
|
|
attachDragPhoto->hideFast();
|
|
|
|
|
2020-06-09 23:29:24 +00:00
|
|
|
*attachDragState = DragState::None;
|
|
|
|
updateDragAreas();
|
|
|
|
e->acceptProposedAction();
|
|
|
|
};
|
|
|
|
|
2020-06-10 11:46:27 +00:00
|
|
|
const auto processDragEvents = [=](not_null<QEvent*> event) {
|
|
|
|
switch (event->type()) {
|
|
|
|
case QEvent::DragEnter:
|
|
|
|
dragEnterEvent(static_cast<QDragEnterEvent*>(event.get()));
|
|
|
|
return true;
|
|
|
|
case QEvent::DragLeave:
|
|
|
|
dragLeaveEvent(static_cast<QDragLeaveEvent*>(event.get()));
|
|
|
|
return true;
|
|
|
|
case QEvent::Drop:
|
|
|
|
dropEvent(static_cast<QDropEvent*>(event.get()));
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2020-06-09 23:29:24 +00:00
|
|
|
container->events(
|
|
|
|
) | rpl::filter([=](not_null<QEvent*> event) {
|
|
|
|
return ranges::contains(kDragAreaEvents, event->type());
|
|
|
|
}) | rpl::start_with_next([=](not_null<QEvent*> event) {
|
|
|
|
const auto type = event->type();
|
|
|
|
|
2020-06-10 11:46:27 +00:00
|
|
|
if (processDragEvents(event)) {
|
|
|
|
return;
|
|
|
|
} else if (type == QEvent::Leave
|
|
|
|
|| type == QEvent::MouseButtonRelease) {
|
2020-06-09 23:29:24 +00:00
|
|
|
resetDragStateIfNeeded();
|
|
|
|
}
|
|
|
|
}, lifetime);
|
|
|
|
|
|
|
|
const auto eventFilter = [=](not_null<QEvent*> event) {
|
2020-06-10 11:46:27 +00:00
|
|
|
processDragEvents(event);
|
2020-06-09 23:29:24 +00:00
|
|
|
return base::EventFilterResult::Continue;
|
|
|
|
};
|
|
|
|
base::install_event_filter(attachDragDocument, eventFilter);
|
|
|
|
base::install_event_filter(attachDragPhoto, eventFilter);
|
|
|
|
|
|
|
|
updateDragAreas();
|
|
|
|
|
|
|
|
return {
|
2020-07-22 06:25:55 +00:00
|
|
|
.document = attachDragDocument,
|
2020-06-09 23:29:24 +00:00
|
|
|
.photo = attachDragPhoto,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
DragArea::DragArea(QWidget *parent) : Ui::RpWidget(parent) {
|
2016-10-26 16:43:13 +00:00
|
|
|
setMouseTracking(true);
|
|
|
|
setAcceptDrops(true);
|
|
|
|
}
|
|
|
|
|
2016-12-13 17:07:56 +00:00
|
|
|
bool DragArea::overlaps(const QRect &globalRect) {
|
|
|
|
if (isHidden() || _a_opacity.animating()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-06-10 13:10:19 +00:00
|
|
|
const auto inner = InnerRect(this);
|
|
|
|
const auto testRect = QRect(
|
|
|
|
mapFromGlobal(globalRect.topLeft()),
|
|
|
|
globalRect.size());
|
|
|
|
const auto h = QMargins(st::boxRadius, 0, st::boxRadius, 0);
|
|
|
|
const auto v = QMargins(0, st::boxRadius, 0, st::boxRadius);
|
|
|
|
return inner.marginsRemoved(h).contains(testRect)
|
|
|
|
|| inner.marginsRemoved(v).contains(testRect);
|
2016-12-13 17:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-10-26 16:43:13 +00:00
|
|
|
void DragArea::mouseMoveEvent(QMouseEvent *e) {
|
2020-06-10 13:10:19 +00:00
|
|
|
if (_hiding) {
|
|
|
|
return;
|
|
|
|
}
|
2016-10-26 16:43:13 +00:00
|
|
|
|
2020-06-10 13:10:19 +00:00
|
|
|
setIn(InnerRect(this).contains(e->pos()));
|
2016-10-26 16:43:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DragArea::dragMoveEvent(QDragMoveEvent *e) {
|
2020-06-10 13:10:19 +00:00
|
|
|
setIn(InnerRect(this).contains(e->pos()));
|
2016-10-26 16:43:13 +00:00
|
|
|
e->setDropAction(_in ? Qt::CopyAction : Qt::IgnoreAction);
|
|
|
|
e->accept();
|
|
|
|
}
|
|
|
|
|
2016-12-07 13:32:25 +00:00
|
|
|
void DragArea::setIn(bool in) {
|
|
|
|
if (_in != in) {
|
|
|
|
_in = in;
|
2020-06-10 13:10:19 +00:00
|
|
|
_a_in.start(
|
|
|
|
[=] { update(); },
|
|
|
|
_in ? 0. : 1.,
|
|
|
|
_in ? 1. : 0.,
|
|
|
|
st::boxDuration);
|
2016-12-07 13:32:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-26 16:43:13 +00:00
|
|
|
void DragArea::setText(const QString &text, const QString &subtext) {
|
|
|
|
_text = text;
|
|
|
|
_subtext = subtext;
|
|
|
|
update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DragArea::paintEvent(QPaintEvent *e) {
|
|
|
|
Painter p(this);
|
|
|
|
|
2020-06-10 13:10:19 +00:00
|
|
|
const auto opacity = _a_opacity.value(_hiding ? 0. : 1.);
|
2016-12-07 13:32:25 +00:00
|
|
|
if (!_a_opacity.animating() && _hiding) {
|
|
|
|
return;
|
2016-10-26 16:43:13 +00:00
|
|
|
}
|
2016-12-07 13:32:25 +00:00
|
|
|
p.setOpacity(opacity);
|
2020-06-10 13:10:19 +00:00
|
|
|
const auto inner = InnerRect(this);
|
2016-10-26 16:43:13 +00:00
|
|
|
|
2016-12-13 17:07:56 +00:00
|
|
|
if (!_cache.isNull()) {
|
2020-06-10 13:10:19 +00:00
|
|
|
p.drawPixmapLeft(
|
|
|
|
inner.x() - st::boxRoundShadow.extend.left(),
|
|
|
|
inner.y() - st::boxRoundShadow.extend.top(),
|
|
|
|
width(),
|
|
|
|
_cache);
|
2016-12-13 17:07:56 +00:00
|
|
|
return;
|
|
|
|
}
|
2016-10-26 16:43:13 +00:00
|
|
|
|
2016-12-13 17:07:56 +00:00
|
|
|
Ui::Shadow::paint(p, inner, width(), st::boxRoundShadow);
|
2020-10-13 15:11:53 +00:00
|
|
|
Ui::FillRoundRect(p, inner, st::boxBg, Ui::BoxCorners);
|
2016-10-26 16:43:13 +00:00
|
|
|
|
2020-06-10 13:10:19 +00:00
|
|
|
p.setPen(anim::pen(
|
|
|
|
st::dragColor,
|
|
|
|
st::dragDropColor,
|
|
|
|
_a_in.value(_in ? 1. : 0.)));
|
2016-10-26 16:43:13 +00:00
|
|
|
|
2016-10-31 12:29:26 +00:00
|
|
|
p.setFont(st::dragFont);
|
2020-06-10 13:10:19 +00:00
|
|
|
const auto rText = QRect(
|
|
|
|
0,
|
|
|
|
(height() - st::dragHeight) / 2,
|
|
|
|
width(),
|
|
|
|
st::dragFont->height);
|
|
|
|
p.drawText(rText, _text, QTextOption(style::al_top));
|
2016-10-26 16:43:13 +00:00
|
|
|
|
2016-10-31 12:29:26 +00:00
|
|
|
p.setFont(st::dragSubfont);
|
2020-06-10 13:10:19 +00:00
|
|
|
const auto rSubtext = QRect(
|
|
|
|
0,
|
|
|
|
(height() + st::dragHeight) / 2 - st::dragSubfont->height,
|
|
|
|
width(),
|
|
|
|
st::dragSubfont->height * 2);
|
|
|
|
p.drawText(rSubtext, _subtext, QTextOption(style::al_top));
|
2016-10-26 16:43:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DragArea::dragEnterEvent(QDragEnterEvent *e) {
|
|
|
|
e->setDropAction(Qt::IgnoreAction);
|
|
|
|
e->accept();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DragArea::dragLeaveEvent(QDragLeaveEvent *e) {
|
2016-12-07 13:32:25 +00:00
|
|
|
setIn(false);
|
2016-10-26 16:43:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DragArea::dropEvent(QDropEvent *e) {
|
2016-11-28 15:45:07 +00:00
|
|
|
if (e->isAccepted() && _droppedCallback) {
|
|
|
|
_droppedCallback(e->mimeData());
|
2016-10-26 16:43:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DragArea::otherEnter() {
|
|
|
|
showStart();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DragArea::otherLeave() {
|
|
|
|
hideStart();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DragArea::hideFast() {
|
2019-04-02 09:13:30 +00:00
|
|
|
_a_opacity.stop();
|
2016-10-26 16:43:13 +00:00
|
|
|
hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DragArea::hideStart() {
|
2016-12-13 17:07:56 +00:00
|
|
|
if (_hiding || isHidden()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (_cache.isNull()) {
|
2017-12-26 12:41:48 +00:00
|
|
|
_cache = Ui::GrabWidget(
|
|
|
|
this,
|
2020-06-10 13:10:19 +00:00
|
|
|
InnerRect(this).marginsAdded(st::boxRoundShadow.extend));
|
2016-12-13 17:07:56 +00:00
|
|
|
}
|
2016-10-26 16:43:13 +00:00
|
|
|
_hiding = true;
|
2016-12-07 13:32:25 +00:00
|
|
|
setIn(false);
|
2020-06-10 13:10:19 +00:00
|
|
|
_a_opacity.start(
|
|
|
|
[=] { opacityAnimationCallback(); },
|
|
|
|
1.,
|
|
|
|
0.,
|
|
|
|
st::boxDuration);
|
2016-10-26 16:43:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DragArea::hideFinish() {
|
|
|
|
hide();
|
|
|
|
_in = false;
|
2019-04-02 09:13:30 +00:00
|
|
|
_a_in.stop();
|
2016-10-26 16:43:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void DragArea::showStart() {
|
2016-12-13 17:07:56 +00:00
|
|
|
if (!_hiding && !isHidden()) {
|
|
|
|
return;
|
|
|
|
}
|
2016-10-26 16:43:13 +00:00
|
|
|
_hiding = false;
|
2016-12-13 17:07:56 +00:00
|
|
|
if (_cache.isNull()) {
|
2017-12-26 12:41:48 +00:00
|
|
|
_cache = Ui::GrabWidget(
|
|
|
|
this,
|
2020-06-10 13:10:19 +00:00
|
|
|
InnerRect(this).marginsAdded(st::boxRoundShadow.extend));
|
2016-12-13 17:07:56 +00:00
|
|
|
}
|
2016-10-26 16:43:13 +00:00
|
|
|
show();
|
2020-06-10 13:10:19 +00:00
|
|
|
_a_opacity.start(
|
|
|
|
[=] { opacityAnimationCallback(); },
|
|
|
|
0.,
|
|
|
|
1.,
|
|
|
|
st::boxDuration);
|
2016-10-26 16:43:13 +00:00
|
|
|
}
|
|
|
|
|
2016-12-07 13:32:25 +00:00
|
|
|
void DragArea::opacityAnimationCallback() {
|
|
|
|
update();
|
|
|
|
if (!_a_opacity.animating()) {
|
2016-12-13 17:07:56 +00:00
|
|
|
_cache = QPixmap();
|
2016-10-26 16:43:13 +00:00
|
|
|
if (_hiding) {
|
|
|
|
hideFinish();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|