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/scene/scene_item_base.h"
9
10 #include "editor/scene/scene.h"
11 #include "lang/lang_keys.h"
12 #include "ui/widgets/popup_menu.h"
13 #include "styles/style_editor.h"
14
15 #include <QGraphicsScene>
16 #include <QGraphicsSceneHoverEvent>
17 #include <QGraphicsSceneMouseEvent>
18 #include <QStyleOptionGraphicsItem>
19
20 namespace Editor {
21 namespace {
22
23 constexpr auto kSnapAngle = 45.;
24
25 const auto kDuplicateSequence = QKeySequence("ctrl+d");
26 const auto kFlipSequence = QKeySequence("ctrl+s");
27 const auto kDeleteSequence = QKeySequence("delete");
28
29 constexpr auto kMinSizeRatio = 0.05;
30 constexpr auto kMaxSizeRatio = 1.00;
31
Normalized(float64 angle)32 auto Normalized(float64 angle) {
33 return angle
34 + ((std::abs(angle) < 360) ? 0 : (-360 * (angle < 0 ? -1 : 1)));
35 }
36
37 } // namespace
38
type() const39 int NumberedItem::type() const {
40 return NumberedItem::Type;
41 }
42
number() const43 int NumberedItem::number() const {
44 return _number;
45 }
46
setNumber(int number)47 void NumberedItem::setNumber(int number) {
48 _number = number;
49 }
50
status() const51 NumberedItem::Status NumberedItem::status() const {
52 return _status;
53 }
54
isNormalStatus() const55 bool NumberedItem::isNormalStatus() const {
56 return _status == Status::Normal;
57 }
58
isUndidStatus() const59 bool NumberedItem::isUndidStatus() const {
60 return _status == Status::Undid;
61 }
62
isRemovedStatus() const63 bool NumberedItem::isRemovedStatus() const {
64 return _status == Status::Removed;
65 }
66
save(SaveState state)67 void NumberedItem::save(SaveState state) {
68 }
69
restore(SaveState state)70 void NumberedItem::restore(SaveState state) {
71 }
72
hasState(SaveState state) const73 bool NumberedItem::hasState(SaveState state) const {
74 return false;
75 }
76
setStatus(Status status)77 void NumberedItem::setStatus(Status status) {
78 if (status != _status) {
79 _status = status;
80 setVisible(status == Status::Normal);
81 }
82 }
83
ItemBase(Data data)84 ItemBase::ItemBase(Data data)
85 : _lastZ(data.zPtr)
86 , _imageSize(data.imageSize)
87 , _horizontalSize(data.size) {
88 setFlags(QGraphicsItem::ItemIsMovable
89 | QGraphicsItem::ItemIsSelectable
90 | QGraphicsItem::ItemIsFocusable);
91 setAcceptHoverEvents(true);
92 applyData(data);
93 }
94
boundingRect() const95 QRectF ItemBase::boundingRect() const {
96 return innerRect() + _scaledInnerMargins;
97 }
98
contentRect() const99 QRectF ItemBase::contentRect() const {
100 return innerRect() - _scaledInnerMargins;
101 }
102
innerRect() const103 QRectF ItemBase::innerRect() const {
104 const auto &hSize = _horizontalSize;
105 const auto &vSize = _verticalSize;
106 return QRectF(-hSize / 2, -vSize / 2, hSize, vSize);
107 }
108
paint(QPainter * p,const QStyleOptionGraphicsItem * option,QWidget *)109 void ItemBase::paint(
110 QPainter *p,
111 const QStyleOptionGraphicsItem *option,
112 QWidget *) {
113 if (!(option->state & QStyle::State_Selected)) {
114 return;
115 }
116 PainterHighQualityEnabler hq(*p);
117 const auto hasFocus = (option->state & QStyle::State_HasFocus);
118 p->setPen(hasFocus ? _pens.select : _pens.selectInactive);
119 p->drawRect(innerRect());
120
121 p->setPen(hasFocus ? _pens.handle : _pens.handleInactive);
122 p->setBrush(st::photoEditorItemBaseHandleFg);
123 p->drawEllipse(rightHandleRect());
124 p->drawEllipse(leftHandleRect());
125 }
126
mouseMoveEvent(QGraphicsSceneMouseEvent * event)127 void ItemBase::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
128 if (isHandling()) {
129 const auto mousePos = event->pos();
130 const auto shift = event->modifiers().testFlag(Qt::ShiftModifier);
131 const auto isLeft = (_handle == HandleType::Left);
132 if (!shift) {
133 // Resize.
134 const auto p = isLeft ? (mousePos * -1) : mousePos;
135 const auto dx = int(2.0 * p.x());
136 const auto dy = int(2.0 * p.y());
137 prepareGeometryChange();
138 _horizontalSize = std::clamp(
139 (dx > dy ? dx : dy),
140 _sizeLimits.min,
141 _sizeLimits.max);
142 updateVerticalSize();
143 }
144
145 // Rotate.
146 const auto origin = mapToScene(boundingRect().center());
147 const auto pos = mapToScene(mousePos);
148
149 const auto diff = pos - origin;
150 const auto angle = Normalized((isLeft ? 180 : 0)
151 + (std::atan2(diff.y(), diff.x()) * 180 / M_PI));
152 setRotation(shift
153 ? (base::SafeRound(angle / kSnapAngle) * kSnapAngle)
154 : angle);
155 } else {
156 QGraphicsItem::mouseMoveEvent(event);
157 }
158 }
159
hoverMoveEvent(QGraphicsSceneHoverEvent * event)160 void ItemBase::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
161 setCursor(isHandling()
162 ? Qt::ClosedHandCursor
163 : (handleType(event->pos()) != HandleType::None) && isSelected()
164 ? Qt::OpenHandCursor
165 : Qt::ArrowCursor);
166 QGraphicsItem::hoverMoveEvent(event);
167 }
168
mousePressEvent(QGraphicsSceneMouseEvent * event)169 void ItemBase::mousePressEvent(QGraphicsSceneMouseEvent *event) {
170 setZValue((*_lastZ)++);
171 if (event->button() == Qt::LeftButton) {
172 _handle = handleType(event->pos());
173 }
174 if (isHandling()) {
175 setCursor(Qt::ClosedHandCursor);
176 } else {
177 QGraphicsItem::mousePressEvent(event);
178 }
179 }
180
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)181 void ItemBase::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
182 if ((event->button() == Qt::LeftButton) && isHandling()) {
183 _handle = HandleType::None;
184 } else {
185 QGraphicsItem::mouseReleaseEvent(event);
186 }
187 }
188
contextMenuEvent(QGraphicsSceneContextMenuEvent * event)189 void ItemBase::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) {
190 if (scene()) {
191 scene()->clearSelection();
192 setSelected(true);
193 }
194
195 const auto add = [&](
196 auto base,
197 const QKeySequence &sequence,
198 Fn<void()> callback) {
199 // TODO: refactor.
200 const auto sequenceText = QChar('\t')
201 + sequence.toString(QKeySequence::NativeText);
202 _menu->addAction(base(tr::now) + sequenceText, std::move(callback));
203 };
204
205 _menu = base::make_unique_q<Ui::PopupMenu>(nullptr);
206 add(
207 tr::lng_photo_editor_menu_delete,
208 kDeleteSequence,
209 [=] { actionDelete(); });
210 add(
211 tr::lng_photo_editor_menu_flip,
212 kFlipSequence,
213 [=] { actionFlip(); });
214 add(
215 tr::lng_photo_editor_menu_duplicate,
216 kDuplicateSequence,
217 [=] { actionDuplicate(); });
218
219 _menu->popup(event->screenPos());
220 }
221
performForSelectedItems(Action action)222 void ItemBase::performForSelectedItems(Action action) {
223 if (const auto s = scene()) {
224 for (const auto item : s->selectedItems()) {
225 if (const auto base = static_cast<ItemBase*>(item)) {
226 (base->*action)();
227 }
228 }
229 }
230 }
231
actionFlip()232 void ItemBase::actionFlip() {
233 setFlip(!flipped());
234 }
235
actionDelete()236 void ItemBase::actionDelete() {
237 if (const auto s = static_cast<Scene*>(scene())) {
238 s->removeItem(this);
239 }
240 }
241
actionDuplicate()242 void ItemBase::actionDuplicate() {
243 if (const auto s = static_cast<Scene*>(scene())) {
244 auto data = generateData();
245 data.x += int(_horizontalSize / 3);
246 data.y += int(_verticalSize / 3);
247 const auto newItem = duplicate(std::move(data));
248 if (hasFocus()) {
249 newItem->setFocus();
250 }
251 const auto selected = isSelected();
252 newItem->setSelected(selected);
253 setSelected(false);
254 s->addItem(newItem);
255 }
256 }
257
keyPressEvent(QKeyEvent * e)258 void ItemBase::keyPressEvent(QKeyEvent *e) {
259 if (e->key() == Qt::Key_Escape) {
260 if (const auto s = scene()) {
261 s->clearSelection();
262 s->clearFocus();
263 return;
264 }
265 }
266 handleActionKey(e);
267 }
268
handleActionKey(not_null<QKeyEvent * > e)269 void ItemBase::handleActionKey(not_null<QKeyEvent*> e) {
270 const auto matches = [&](const QKeySequence &sequence) {
271 const auto searchKey = (e->modifiers() | e->key())
272 & ~(Qt::KeypadModifier | Qt::GroupSwitchModifier);
273 const auto events = QKeySequence(searchKey);
274 return sequence.matches(events) == QKeySequence::ExactMatch;
275 };
276 if (matches(kDuplicateSequence)) {
277 performForSelectedItems(&ItemBase::actionDuplicate);
278 } else if (matches(kDeleteSequence)) {
279 performForSelectedItems(&ItemBase::actionDelete);
280 } else if (matches(kFlipSequence)) {
281 performForSelectedItems(&ItemBase::actionFlip);
282 }
283 }
284
rightHandleRect() const285 QRectF ItemBase::rightHandleRect() const {
286 return QRectF(
287 (_horizontalSize / 2) - (_scaledHandleSize / 2),
288 0 - (_scaledHandleSize / 2),
289 _scaledHandleSize,
290 _scaledHandleSize);
291 }
292
leftHandleRect() const293 QRectF ItemBase::leftHandleRect() const {
294 return QRectF(
295 (-_horizontalSize / 2) - (_scaledHandleSize / 2),
296 0 - (_scaledHandleSize / 2),
297 _scaledHandleSize,
298 _scaledHandleSize);
299 }
300
isHandling() const301 bool ItemBase::isHandling() const {
302 return _handle != HandleType::None;
303 }
304
size() const305 float64 ItemBase::size() const {
306 return _horizontalSize;
307 }
308
updateVerticalSize()309 void ItemBase::updateVerticalSize() {
310 const auto verticalSize = _horizontalSize * _aspectRatio;
311 _verticalSize = std::max(
312 verticalSize,
313 float64(_sizeLimits.min));
314 if (verticalSize < _sizeLimits.min) {
315 _horizontalSize = _verticalSize / _aspectRatio;
316 }
317 }
318
setAspectRatio(float64 aspectRatio)319 void ItemBase::setAspectRatio(float64 aspectRatio) {
320 _aspectRatio = aspectRatio;
321 updateVerticalSize();
322 }
323
handleType(const QPointF & pos) const324 ItemBase::HandleType ItemBase::handleType(const QPointF &pos) const {
325 return rightHandleRect().contains(pos)
326 ? HandleType::Right
327 : leftHandleRect().contains(pos)
328 ? HandleType::Left
329 : HandleType::None;
330 }
331
flipped() const332 bool ItemBase::flipped() const {
333 return _flipped;
334 }
335
setFlip(bool value)336 void ItemBase::setFlip(bool value) {
337 if (_flipped != value) {
338 performFlip();
339 _flipped = value;
340 }
341 }
342
type() const343 int ItemBase::type() const {
344 return ItemBase::Type;
345 }
346
updateZoom(float64 zoom)347 void ItemBase::updateZoom(float64 zoom) {
348 _scaledHandleSize = st::photoEditorItemHandleSize / zoom;
349 _scaledInnerMargins = QMarginsF(
350 _scaledHandleSize,
351 _scaledHandleSize,
352 _scaledHandleSize,
353 _scaledHandleSize) * 0.5;
354
355 const auto maxSide = std::max(
356 _imageSize.width(),
357 _imageSize.height());
358 _sizeLimits = {
359 .min = int(maxSide * kMinSizeRatio),
360 .max = int(maxSide * kMaxSizeRatio),
361 };
362 _horizontalSize = std::clamp(
363 _horizontalSize,
364 float64(_sizeLimits.min),
365 float64(_sizeLimits.max));
366 updateVerticalSize();
367
368 updatePens(QPen(
369 QBrush(),
370 1 / zoom,
371 Qt::DashLine,
372 Qt::SquareCap,
373 Qt::RoundJoin));
374 }
375
performFlip()376 void ItemBase::performFlip() {
377 }
378
updatePens(QPen pen)379 void ItemBase::updatePens(QPen pen) {
380 _pens = {
381 .select = pen,
382 .selectInactive = pen,
383 .handle = pen,
384 .handleInactive = pen,
385 };
386 _pens.select.setColor(Qt::white);
387 _pens.selectInactive.setColor(Qt::gray);
388 _pens.handle.setColor(Qt::white);
389 _pens.handleInactive.setColor(Qt::gray);
390 _pens.handle.setStyle(Qt::SolidLine);
391 _pens.handleInactive.setStyle(Qt::SolidLine);
392 }
393
generateData() const394 ItemBase::Data ItemBase::generateData() const {
395 return {
396 .initialZoom = (st::photoEditorItemHandleSize / _scaledHandleSize),
397 .zPtr = _lastZ,
398 .size = int(_horizontalSize),
399 .x = int(scenePos().x()),
400 .y = int(scenePos().y()),
401 .flipped = flipped(),
402 .rotation = int(rotation()),
403 .imageSize = _imageSize,
404 };
405 }
406
applyData(const Data & data)407 void ItemBase::applyData(const Data &data) {
408 // _lastZ is const.
409 // _imageSize is const.
410 _horizontalSize = data.size;
411 setPos(data.x, data.y);
412 setZValue((*_lastZ)++);
413 setFlip(data.flipped);
414 setRotation(data.rotation);
415 updateZoom(data.initialZoom);
416 update();
417 }
418
save(SaveState state)419 void ItemBase::save(SaveState state) {
420 const auto z = zValue();
421 auto &saved = (state == SaveState::Keep) ? _keeped : _saved;
422 saved = {
423 .data = generateData(),
424 .zValue = z,
425 .status = status(),
426 };
427 }
428
restore(SaveState state)429 void ItemBase::restore(SaveState state) {
430 if (!hasState(state)) {
431 return;
432 }
433 const auto &saved = (state == SaveState::Keep) ? _keeped : _saved;
434 applyData(saved.data);
435 setZValue(saved.zValue);
436 setStatus(saved.status);
437 }
438
hasState(SaveState state) const439 bool ItemBase::hasState(SaveState state) const {
440 const auto &saved = (state == SaveState::Keep) ? _keeped : _saved;
441 return saved.zValue;
442 }
443
444 } // namespace Editor
445