1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4 
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "editor/editor_paint.h"
9 
10 #include "ui/boxes/confirm_box.h"
11 #include "editor/controllers/controllers.h"
12 #include "editor/scene/scene.h"
13 #include "editor/scene/scene_item_canvas.h"
14 #include "editor/scene/scene_item_image.h"
15 #include "editor/scene/scene_item_sticker.h"
16 #include "lottie/lottie_single_player.h"
17 #include "storage/storage_media_prepare.h"
18 #include "ui/chat/attach/attach_prepare.h"
19 #include "ui/ui_utility.h"
20 
21 #include <QGraphicsView>
22 #include <QtCore/QMimeData>
23 
24 namespace Editor {
25 namespace {
26 
27 constexpr auto kMaxBrush = 25.;
28 constexpr auto kMinBrush = 1.;
29 
30 constexpr auto kViewStyle = "QGraphicsView {\
31 		background-color: transparent;\
32 		border: 0px\
33 	}"_cs;
34 
EnsureScene(PhotoModifications & mods,const QSize & size)35 std::shared_ptr<Scene> EnsureScene(
36 		PhotoModifications &mods,
37 		const QSize &size) {
38 	if (!mods.paint) {
39 		mods.paint = std::make_shared<Scene>(QRectF(QPointF(), size));
40 	}
41 	return mods.paint;
42 }
43 
44 } // namespace
45 
46 using ItemPtr = std::shared_ptr<QGraphicsItem>;
47 
Paint(not_null<Ui::RpWidget * > parent,PhotoModifications & modifications,const QSize & imageSize,std::shared_ptr<Controllers> controllers)48 Paint::Paint(
49 	not_null<Ui::RpWidget*> parent,
50 	PhotoModifications &modifications,
51 	const QSize &imageSize,
52 	std::shared_ptr<Controllers> controllers)
53 : RpWidget(parent)
54 , _controllers(controllers)
55 , _scene(EnsureScene(modifications, imageSize))
56 , _view(base::make_unique_q<QGraphicsView>(_scene.get(), this))
57 , _imageSize(imageSize) {
58 	Expects(modifications.paint != nullptr);
59 
60 	keepResult();
61 
62 	_view->show();
63 	_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
64 	_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
65 	_view->setStyleSheet(kViewStyle.utf8());
66 
67 	// Undo / Redo.
68 	controllers->undoController->performRequestChanges(
69 	) | rpl::start_with_next([=](const Undo &command) {
70 		if (command == Undo::Undo) {
71 			_scene->performUndo();
72 		} else {
73 			_scene->performRedo();
74 		}
75 
76 		_hasUndo = _scene->hasUndo();
77 		_hasRedo = _scene->hasRedo();
78 	}, lifetime());
79 
80 	controllers->undoController->setCanPerformChanges(rpl::merge(
81 		_hasUndo.value() | rpl::map([](bool enable) {
82 			return UndoController::EnableRequest{
83 				.command = Undo::Undo,
84 				.enable = enable,
85 			};
86 		}),
87 		_hasRedo.value() | rpl::map([](bool enable) {
88 			return UndoController::EnableRequest{
89 				.command = Undo::Redo,
90 				.enable = enable,
91 			};
92 		})));
93 
94 	if (controllers->stickersPanelController) {
95 		using ShowRequest = StickersPanelController::ShowRequest;
96 
97 		controllers->stickersPanelController->setShowRequestChanges(
98 			controllers->stickersPanelController->stickerChosen(
99 			) | rpl::map_to(ShowRequest::HideAnimated));
100 
101 		controllers->stickersPanelController->stickerChosen(
102 		) | rpl::start_with_next([=](not_null<DocumentData*> document) {
103 			const auto item = std::make_shared<ItemSticker>(
104 				document,
105 				itemBaseData());
106 			_scene->addItem(item);
107 			_scene->clearSelection();
108 		}, lifetime());
109 	}
110 
111 	rpl::merge(
112 		controllers->stickersPanelController
113 			? controllers->stickersPanelController->stickerChosen(
114 				) | rpl::to_empty
115 			: rpl::never<>() | rpl::type_erased(),
116 		_scene->addsItem()
117 	) | rpl::start_with_next([=] {
118 		clearRedoList();
119 		updateUndoState();
120 	}, lifetime());
121 
122 	_scene->removesItem(
123 	) | rpl::start_with_next([=] {
124 		updateUndoState();
125 	}, lifetime());
126 
127 }
128 
applyTransform(QRect geometry,int angle,bool flipped)129 void Paint::applyTransform(QRect geometry, int angle, bool flipped) {
130 	if (geometry.isEmpty()) {
131 		return;
132 	}
133 	setGeometry(geometry);
134 	const auto size = geometry.size();
135 
136 	const auto rotatedImageSize = QTransform()
137 		.rotate(angle)
138 		.mapRect(QRect(QPoint(), _imageSize));
139 
140 	const auto ratioW = size.width() / float64(rotatedImageSize.width())
141 		* (flipped ? -1 : 1);
142 	const auto ratioH = size.height() / float64(rotatedImageSize.height());
143 
144 	_view->setTransform(QTransform().scale(ratioW, ratioH).rotate(angle));
145 	_view->setGeometry(QRect(QPoint(), size));
146 
147 	_transform = {
148 		.angle = angle,
149 		.flipped = flipped,
150 		.zoom = size.width() / float64(_scene->sceneRect().width()),
151 	};
152 	_scene->updateZoom(_transform.zoom);
153 }
154 
saveScene() const155 std::shared_ptr<Scene> Paint::saveScene() const {
156 	_scene->save(SaveState::Save);
157 	return _scene->items().empty()
158 		? nullptr
159 		: _scene;
160 }
161 
restoreScene()162 void Paint::restoreScene() {
163 	_scene->restore(SaveState::Save);
164 }
165 
cancel()166 void Paint::cancel() {
167 	_scene->restore(SaveState::Keep);
168 }
169 
keepResult()170 void Paint::keepResult() {
171 	_scene->save(SaveState::Keep);
172 }
173 
clearRedoList()174 void Paint::clearRedoList() {
175 	_scene->clearRedoList();
176 
177 	_hasRedo = false;
178 }
179 
updateUndoState()180 void Paint::updateUndoState() {
181 	_hasUndo = _scene->hasUndo();
182 	_hasRedo = _scene->hasRedo();
183 }
184 
applyBrush(const Brush & brush)185 void Paint::applyBrush(const Brush &brush) {
186 	_scene->applyBrush(
187 		brush.color,
188 		(kMinBrush + float64(kMaxBrush - kMinBrush) * brush.sizeRatio));
189 }
190 
handleMimeData(const QMimeData * data)191 void Paint::handleMimeData(const QMimeData *data) {
192 	const auto add = [&](QImage image) {
193 		if (image.isNull()) {
194 			return;
195 		}
196 		if (!Ui::ValidateThumbDimensions(image.width(), image.height())) {
197 			_controllers->showBox(
198 				Box<Ui::InformBox>(tr::lng_edit_media_invalid_file(tr::now)));
199 			return;
200 		}
201 
202 		const auto item = std::make_shared<ItemImage>(
203 			Ui::PixmapFromImage(std::move(image)),
204 			itemBaseData());
205 		_scene->addItem(item);
206 		_scene->clearSelection();
207 	};
208 
209 	using Error = Ui::PreparedList::Error;
210 	auto result = data->hasUrls()
211 		? Storage::PrepareMediaList(
212 			data->urls().mid(0, 1),
213 			_imageSize.width() / 2)
214 		: Ui::PreparedList(Error::EmptyFile, QString());
215 	if (result.error == Error::None) {
216 		add(base::take(result.files.front().preview));
217 	} else if (data->hasImage()) {
218 		add(qvariant_cast<QImage>(data->imageData()));
219 	}
220 }
221 
itemBaseData() const222 ItemBase::Data Paint::itemBaseData() const {
223 	const auto s = _scene->sceneRect().toRect().size();
224 	const auto size = std::min(s.width(), s.height()) / 2;
225 	const auto x = s.width() / 2;
226 	const auto y = s.height() / 2;
227 	return ItemBase::Data{
228 		.initialZoom = _transform.zoom,
229 		.zPtr = _scene->lastZ(),
230 		.size = size,
231 		.x = x,
232 		.y = y,
233 		.flipped = _transform.flipped,
234 		.rotation = -_transform.angle,
235 		.imageSize = _imageSize,
236 	};
237 }
238 
239 } // namespace Editor
240