1 /*
2     SPDX-FileCopyrightText: 2008 Joris Guisson <joris.guisson@gmail.com>
3     SPDX-FileCopyrightText: 2008 Ivan Vasic <ivasic@gmail.com>
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "schedulegraphicsitem.h"
8 
9 #include <cmath>
10 #include <cstdlib>
11 
12 #include <QBrush>
13 #include <QCursor>
14 #include <QFontMetricsF>
15 #include <QGraphicsSceneMouseEvent>
16 #include <QGraphicsTextItem>
17 #include <QPen>
18 #include <QRectF>
19 
20 #include <KCursor>
21 #include <KLocalizedString>
22 
23 #include "bwschedulerpluginsettings.h"
24 #include "schedule.h"
25 #include "weekscene.h"
26 #include <util/functions.h>
27 #include <util/log.h>
28 
29 using namespace bt;
30 
31 namespace kt
32 {
33 const Uint32 Top = 1;
34 const Uint32 Bottom = 2;
35 const Uint32 Left = 4;
36 const Uint32 Right = 8;
37 
38 const Uint32 TopRight = Top | Right;
39 const Uint32 TopLeft = Top | Left;
40 const Uint32 BottomRight = Bottom | Right;
41 const Uint32 BottomLeft = Bottom | Left;
42 
ScheduleGraphicsItem(ScheduleItem * item,const QRectF & r,const QRectF & constraints,WeekScene * ws)43 ScheduleGraphicsItem::ScheduleGraphicsItem(ScheduleItem *item, const QRectF &r, const QRectF &constraints, WeekScene *ws)
44     : QGraphicsRectItem(r)
45     , item(item)
46     , constraints(constraints)
47     , ws(ws)
48     , text_item(nullptr)
49     , resize_edge(0)
50     , ready_to_resize(false)
51     , resizing(false)
52 {
53     setAcceptHoverEvents(true);
54     setPen(QPen(Qt::black));
55     setZValue(3);
56     setHandlesChildEvents(true);
57 
58     setBrush(QBrush(item->suspended ? SchedulerPluginSettings::suspendedColor() : SchedulerPluginSettings::itemColor()));
59     setFlag(QGraphicsItem::ItemIsSelectable, true);
60     setFlag(QGraphicsItem::ItemIsMovable, true);
61 }
62 
~ScheduleGraphicsItem()63 ScheduleGraphicsItem::~ScheduleGraphicsItem()
64 {
65 }
66 
update(const QRectF & r)67 void ScheduleGraphicsItem::update(const QRectF &r)
68 {
69     setRect(r);
70     setPos(QPointF(0, 0));
71     QString text;
72     if (item->suspended) {
73         setBrush(QBrush(SchedulerPluginSettings::suspendedColor()));
74         text = i18n("Suspended");
75     } else {
76         setBrush(QBrush(SchedulerPluginSettings::itemColor()));
77         QString ds = item->download_limit == 0 ? i18n("Unlimited") : BytesPerSecToString(item->download_limit * 1024);
78         QString us = item->upload_limit == 0 ? i18n("Unlimited") : BytesPerSecToString(item->upload_limit * 1024);
79         text = i18n("%1 Down\n%2 Up", ds, us);
80     }
81 
82     if (text_item == nullptr)
83         text_item = scene()->addText(text);
84     else
85         text_item->setPlainText(text);
86 
87     QFontMetricsF fm(text_item->font());
88     text_item->setPos(QPointF(r.x(), r.y()));
89     text_item->setZValue(4);
90     text_item->setTextWidth(r.width());
91     text_item->setParentItem(this);
92     setToolTip(text);
93 
94     if (text_item->boundingRect().height() > r.height()) {
95         // Text is to big for rect
96         delete text_item;
97         text_item = nullptr;
98     }
99 }
100 
itemChange(GraphicsItemChange change,const QVariant & value)101 QVariant ScheduleGraphicsItem::itemChange(GraphicsItemChange change, const QVariant &value)
102 {
103     if (change == ItemPositionChange && scene()) {
104         QPointF new_pos = value.toPointF();
105         if (!constraints.contains(new_pos)) {
106             qreal x = constraints.x() - boundingRect().x();
107             if (new_pos.x() < x)
108                 new_pos.setX(x);
109             else if (new_pos.x() + rect().width() > x + constraints.width())
110                 new_pos.setX(x + constraints.width() - rect().width());
111 
112             qreal y = constraints.y() - boundingRect().y();
113             if (new_pos.y() < y)
114                 new_pos.setY(y);
115             else if (new_pos.y() + rect().height() > y + constraints.height())
116                 new_pos.setY(y + constraints.height() - rect().height());
117 
118             return new_pos;
119         }
120     }
121 
122     return QGraphicsItem::itemChange(change, value);
123 }
124 
resize(QPointF scene_pos)125 QRectF ScheduleGraphicsItem::resize(QPointF scene_pos)
126 {
127     qreal x = scene_pos.x();
128     qreal y = scene_pos.y();
129 
130     QRectF cur = rect();
131     if (resize_edge & Top) {
132         if (y >= cur.y() + cur.height()) { // rect becomes flipped
133             qreal yn = cur.y() + cur.height();
134             if (yn < constraints.y())
135                 yn = constraints.y();
136 
137             qreal h = y - yn;
138             cur.setY(yn);
139             cur.setHeight(h);
140             resize_edge |= kt::Bottom;
141             resize_edge &= ~kt::Top;
142         } else {
143             qreal yn = y < constraints.y() ? constraints.y() : y;
144             qreal h = cur.height() + (cur.y() - yn);
145             cur.setY(yn);
146             cur.setHeight(h);
147         }
148     } else if (resize_edge & Bottom) {
149         if (y < cur.y()) { // rect becomes flipped
150             qreal yn = y;
151             if (yn < constraints.y())
152                 yn = constraints.y();
153 
154             qreal h = cur.y() - yn;
155             cur.setY(yn);
156             cur.setHeight(h);
157             resize_edge |= kt::Top;
158             resize_edge &= ~kt::Bottom;
159         } else {
160             cur.setHeight(y - cur.y());
161             if (cur.y() + cur.height() >= constraints.y() + constraints.height())
162                 cur.setHeight(constraints.y() + constraints.height() - cur.y());
163         }
164     }
165 
166     if (resize_edge & Left) {
167         if (x >= cur.x() + cur.width()) { // rect becomes flipped
168             qreal xn = cur.x() + cur.x();
169             if (xn < constraints.x())
170                 xn = constraints.x();
171 
172             qreal w = x - xn;
173             cur.setX(xn);
174             cur.setWidth(w);
175             resize_edge |= kt::Right;
176             resize_edge &= ~kt::Left;
177         } else {
178             qreal xn = x < constraints.x() ? constraints.x() : x;
179             qreal w = cur.width() + (cur.x() - xn);
180             cur.setX(xn);
181             cur.setWidth(w);
182         }
183     } else if (resize_edge & Right) {
184         if (x < cur.x()) { // rect becomes flipped
185             qreal xn = x;
186             if (xn < constraints.x())
187                 xn = constraints.x();
188 
189             qreal w = cur.x() - xn;
190             cur.setX(xn);
191             cur.setWidth(w);
192             resize_edge |= kt::Left;
193             resize_edge &= ~kt::Right;
194         } else {
195             cur.setWidth(x - cur.x());
196             if (cur.x() + cur.width() >= constraints.x() + constraints.width())
197                 cur.setWidth(constraints.x() + constraints.width() - cur.x());
198         }
199     }
200 
201     return cur;
202 }
203 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)204 void ScheduleGraphicsItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
205 {
206     if (!resizing) {
207         QGraphicsItem::mouseMoveEvent(event);
208         ws->setShowGuidanceLines(true);
209         QPointF sp = pos() + rect().topLeft();
210         ws->updateGuidanceLines(sp.y(), sp.y() + rect().height());
211 
212         setCursor(ws->validMove(item, sp) ? Qt::DragMoveCursor : Qt::ForbiddenCursor);
213     } else {
214         QRectF cur = resize(event->scenePos());
215         setRect(cur);
216         if (text_item)
217             text_item->setPos(cur.x(), cur.y());
218 
219         ws->updateGuidanceLines(cur.y(), cur.y() + cur.height());
220     }
221 }
222 
mousePressEvent(QGraphicsSceneMouseEvent * event)223 void ScheduleGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
224 {
225     if (!ready_to_resize || !(event->button() & Qt::LeftButton)) {
226         QGraphicsRectItem::mousePressEvent(event);
227         // keep track of original position before the item is dragged
228         original_pos = pos();
229     } else {
230         resizing = true;
231         ws->setShowGuidanceLines(true);
232         ws->updateGuidanceLines(rect().y(), rect().y() + rect().height());
233     }
234 
235     setZValue(4);
236 }
237 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)238 void ScheduleGraphicsItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
239 {
240     if (resizing) {
241         resizing = false;
242         ws->setShowGuidanceLines(false);
243         ws->itemResized(item, rect());
244     } else {
245         QGraphicsRectItem::mouseReleaseEvent(event);
246 
247         if (event->button() & Qt::LeftButton) {
248             if (original_pos != pos()) {
249                 QPointF sp = pos() + rect().topLeft();
250                 ws->itemMoved(item, sp);
251             }
252         }
253         ws->setShowGuidanceLines(false);
254     }
255 
256     setZValue(3);
257     setCursor(Qt::ArrowCursor);
258 }
259 
hoverEnterEvent(QGraphicsSceneHoverEvent * event)260 void ScheduleGraphicsItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
261 {
262     ready_to_resize = true;
263     resize_edge = nearEdge(event->scenePos());
264     updateCursor();
265 }
266 
hoverLeaveEvent(QGraphicsSceneHoverEvent * event)267 void ScheduleGraphicsItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
268 {
269     Q_UNUSED(event);
270     setCursor(Qt::ArrowCursor);
271     ready_to_resize = false;
272 }
273 
hoverMoveEvent(QGraphicsSceneHoverEvent * event)274 void ScheduleGraphicsItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
275 {
276     resize_edge = nearEdge(event->scenePos());
277     ready_to_resize = resize_edge != 0;
278     updateCursor();
279 }
280 
updateCursor()281 void ScheduleGraphicsItem::updateCursor()
282 {
283     Qt::CursorShape shape = Qt::ArrowCursor;
284     if (resize_edge != 0) {
285         if (resize_edge == kt::TopRight || resize_edge == kt::BottomLeft)
286             shape = Qt::SizeBDiagCursor;
287         else if (resize_edge == kt::BottomRight || resize_edge == kt::TopLeft)
288             shape = Qt::SizeFDiagCursor;
289         else if (resize_edge == kt::Top || resize_edge == kt::Bottom)
290             shape = Qt::SizeVerCursor;
291         else
292             shape = Qt::SizeHorCursor;
293     }
294     setCursor(shape);
295 }
296 
nearEdge(QPointF p)297 Uint32 ScheduleGraphicsItem::nearEdge(QPointF p)
298 {
299     qreal y = rect().y();
300     qreal ye = y + rect().height();
301     qreal x = rect().x();
302     qreal xe = x + rect().width();
303     Uint32 ret = 0;
304     if (std::fabs(p.y() - y) < 4)
305         ret |= Top;
306     else if (std::fabs(p.y() - ye) < 4)
307         ret |= Bottom;
308 
309     if (std::fabs(p.x() - x) < 4)
310         ret |= Left;
311     else if (std::fabs(p.x() - xe) < 4)
312         ret |= Right;
313 
314     return ret;
315 }
316 
317 }
318