1 | /* |
2 | * This file is part of KQuickCharts |
3 | * SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl> |
4 | * |
5 | * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
6 | */ |
7 | |
8 | #include "AxisLabels.h" |
9 | |
10 | #include <QDebug> |
11 | #include <QQmlContext> |
12 | |
13 | #include "ItemBuilder.h" |
14 | #include "datasource/ChartDataSource.h" |
15 | |
16 | AxisLabelsAttached::AxisLabelsAttached(QObject *parent) |
17 | : QObject(parent) |
18 | { |
19 | } |
20 | |
21 | int AxisLabelsAttached::index() const |
22 | { |
23 | return m_index; |
24 | } |
25 | |
26 | void AxisLabelsAttached::setIndex(int newIndex) |
27 | { |
28 | if (newIndex == m_index) { |
29 | return; |
30 | } |
31 | |
32 | m_index = newIndex; |
33 | Q_EMIT indexChanged(); |
34 | } |
35 | |
36 | QString AxisLabelsAttached::label() const |
37 | { |
38 | return m_label; |
39 | } |
40 | |
41 | void AxisLabelsAttached::setLabel(const QString &newLabel) |
42 | { |
43 | if (newLabel == m_label) { |
44 | return; |
45 | } |
46 | |
47 | m_label = newLabel; |
48 | Q_EMIT labelChanged(); |
49 | } |
50 | |
51 | AxisLabels::AxisLabels(QQuickItem *parent) |
52 | : QQuickItem(parent) |
53 | { |
54 | m_itemBuilder = std::make_unique<ItemBuilder>(); |
55 | connect(sender: m_itemBuilder.get(), signal: &ItemBuilder::finished, context: this, slot: &AxisLabels::scheduleLayout); |
56 | connect(sender: m_itemBuilder.get(), signal: &ItemBuilder::beginCreate, context: this, slot: &AxisLabels::onBeginCreate); |
57 | } |
58 | |
59 | AxisLabels::~AxisLabels() = default; |
60 | |
61 | AxisLabels::Direction AxisLabels::direction() const |
62 | { |
63 | return m_direction; |
64 | } |
65 | |
66 | void AxisLabels::setDirection(AxisLabels::Direction newDirection) |
67 | { |
68 | if (newDirection == m_direction) { |
69 | return; |
70 | } |
71 | |
72 | m_direction = newDirection; |
73 | scheduleLayout(); |
74 | Q_EMIT directionChanged(); |
75 | } |
76 | |
77 | QQmlComponent *AxisLabels::delegate() const |
78 | { |
79 | return m_itemBuilder->component(); |
80 | } |
81 | |
82 | void AxisLabels::setDelegate(QQmlComponent *newDelegate) |
83 | { |
84 | if (newDelegate == m_itemBuilder->component()) { |
85 | return; |
86 | } |
87 | |
88 | m_itemBuilder->setComponent(newDelegate); |
89 | updateLabels(); |
90 | Q_EMIT delegateChanged(); |
91 | } |
92 | |
93 | ChartDataSource *AxisLabels::source() const |
94 | { |
95 | return m_source; |
96 | } |
97 | |
98 | void AxisLabels::setSource(ChartDataSource *newSource) |
99 | { |
100 | if (newSource == m_source) { |
101 | return; |
102 | } |
103 | |
104 | if (m_source) { |
105 | m_source->disconnect(receiver: this); |
106 | } |
107 | |
108 | m_source = newSource; |
109 | |
110 | if (m_source) { |
111 | connect(sender: m_source, signal: &ChartDataSource::dataChanged, context: this, slot: [this]() { |
112 | updateLabels(); |
113 | }); |
114 | } |
115 | |
116 | updateLabels(); |
117 | Q_EMIT sourceChanged(); |
118 | } |
119 | |
120 | Qt::Alignment AxisLabels::alignment() const |
121 | { |
122 | return m_alignment; |
123 | } |
124 | |
125 | void AxisLabels::setAlignment(Qt::Alignment newAlignment) |
126 | { |
127 | if (newAlignment == m_alignment) { |
128 | return; |
129 | } |
130 | |
131 | m_alignment = newAlignment; |
132 | scheduleLayout(); |
133 | Q_EMIT alignmentChanged(); |
134 | } |
135 | |
136 | bool AxisLabels::constrainToBounds() const |
137 | { |
138 | return m_constrainToBounds; |
139 | } |
140 | |
141 | void AxisLabels::setConstrainToBounds(bool newConstrainToBounds) |
142 | { |
143 | if (newConstrainToBounds == m_constrainToBounds) { |
144 | return; |
145 | } |
146 | |
147 | m_constrainToBounds = newConstrainToBounds; |
148 | scheduleLayout(); |
149 | Q_EMIT constrainToBoundsChanged(); |
150 | } |
151 | void AxisLabels::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) |
152 | { |
153 | QQuickItem::geometryChange(newGeometry, oldGeometry); |
154 | |
155 | if (newGeometry != oldGeometry) { |
156 | scheduleLayout(); |
157 | } |
158 | } |
159 | |
160 | void AxisLabels::scheduleLayout() |
161 | { |
162 | if (!m_layoutScheduled) { |
163 | auto scheduleLayoutLambda = [this]() { |
164 | layout(); |
165 | m_layoutScheduled = false; |
166 | }; |
167 | QMetaObject::invokeMethod(object: this, function&: scheduleLayoutLambda, type: Qt::QueuedConnection); |
168 | m_layoutScheduled = true; |
169 | } |
170 | } |
171 | |
172 | bool AxisLabels::isHorizontal() |
173 | { |
174 | return m_direction == Direction::HorizontalLeftRight || m_direction == Direction::HorizontalRightLeft; |
175 | } |
176 | |
177 | void AxisLabels::updateLabels() |
178 | { |
179 | m_itemBuilder->clear(); |
180 | |
181 | if (!m_itemBuilder->component() || !m_source) { |
182 | return; |
183 | } |
184 | |
185 | m_itemBuilder->setCount(m_source->itemCount()); |
186 | m_itemBuilder->build(parent: this); |
187 | } |
188 | |
189 | void AxisLabels::layout() |
190 | { |
191 | if (!m_itemBuilder->isFinished()) { |
192 | scheduleLayout(); |
193 | return; |
194 | } |
195 | |
196 | auto maxWidth = 0.0; |
197 | auto totalWidth = 0.0; |
198 | auto maxHeight = 0.0; |
199 | auto totalHeight = 0.0; |
200 | |
201 | auto labels = m_itemBuilder->items(); |
202 | for (auto label : labels) { |
203 | maxWidth = std::max(a: maxWidth, b: label->width()); |
204 | maxHeight = std::max(a: maxHeight, b: label->height()); |
205 | totalWidth += label->width(); |
206 | totalHeight += label->height(); |
207 | } |
208 | |
209 | auto impWidth = isHorizontal() ? totalWidth : maxWidth; |
210 | auto impHeight = isHorizontal() ? maxHeight : totalHeight; |
211 | |
212 | if (qFuzzyCompare(p1: impWidth, p2: width()) && qFuzzyCompare(p1: impHeight, p2: height())) { |
213 | return; |
214 | } |
215 | |
216 | setImplicitWidth(impWidth); |
217 | setImplicitHeight(impHeight); |
218 | |
219 | auto spacing = (isHorizontal() ? width() : height()) / (labels.size() - 1); |
220 | auto i = 0; |
221 | auto layoutWidth = isHorizontal() ? 0.0 : width(); |
222 | auto layoutHeight = isHorizontal() ? height() : 0.0; |
223 | |
224 | for (auto label : labels) { |
225 | auto x = 0.0; |
226 | auto y = 0.0; |
227 | |
228 | switch (m_direction) { |
229 | case Direction::HorizontalLeftRight: |
230 | x = i * spacing; |
231 | break; |
232 | case Direction::HorizontalRightLeft: |
233 | x = width() - i * spacing; |
234 | break; |
235 | case Direction::VerticalTopBottom: |
236 | y = i * spacing; |
237 | break; |
238 | case Direction::VerticalBottomTop: |
239 | y = height() - i * spacing; |
240 | break; |
241 | } |
242 | |
243 | if (m_alignment & Qt::AlignHCenter) { |
244 | x += (layoutWidth - label->width()) / 2; |
245 | } else if (m_alignment & Qt::AlignRight) { |
246 | x += layoutWidth - label->width(); |
247 | } |
248 | |
249 | if (m_alignment & Qt::AlignVCenter) { |
250 | y += (layoutHeight - label->height()) / 2; |
251 | } else if (m_alignment & Qt::AlignBottom) { |
252 | y += layoutHeight - label->height(); |
253 | } |
254 | |
255 | if (m_constrainToBounds) { |
256 | x = std::max(a: x, b: 0.0); |
257 | x = x + label->width() > width() ? width() - label->width() : x; |
258 | y = std::max(a: y, b: 0.0); |
259 | y = y + label->height() > height() ? height() - label->height() : y; |
260 | } |
261 | |
262 | label->setX(x); |
263 | label->setY(y); |
264 | i++; |
265 | } |
266 | } |
267 | |
268 | void AxisLabels::onBeginCreate(int index, QQuickItem *item) |
269 | { |
270 | QObject::connect(sender: item, signal: &QQuickItem::xChanged, context: this, slot: [this]() { |
271 | scheduleLayout(); |
272 | }); |
273 | QObject::connect(sender: item, signal: &QQuickItem::yChanged, context: this, slot: [this]() { |
274 | scheduleLayout(); |
275 | }); |
276 | QObject::connect(sender: item, signal: &QQuickItem::widthChanged, context: this, slot: [this]() { |
277 | scheduleLayout(); |
278 | }); |
279 | QObject::connect(sender: item, signal: &QQuickItem::heightChanged, context: this, slot: [this]() { |
280 | scheduleLayout(); |
281 | }); |
282 | |
283 | auto attached = static_cast<AxisLabelsAttached *>(qmlAttachedPropertiesObject<AxisLabels>(obj: item, create: true)); |
284 | attached->setIndex(index); |
285 | attached->setLabel(m_source->item(index).toString()); |
286 | } |
287 | |
288 | #include "moc_AxisLabels.cpp" |
289 | |