Eventviews

timelabels.cpp
1/*
2 SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
3 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
4 SPDX-FileCopyrightText: 2007 Bruno Virlet <bruno@virlet.org>
5
6 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
7*/
8#include "timelabels.h"
9using namespace Qt::Literals::StringLiterals;
10
11#include "agenda.h"
12#include "prefs.h"
13#include "timelabelszone.h"
14#include "timescaleconfigdialog.h"
15
16#include <KCalUtils/Stringify>
17
18#include <KLocalizedString>
19
20#include <QHelpEvent>
21#include <QIcon>
22#include <QMenu>
23#include <QPainter>
24#include <QPointer>
25#include <QToolTip>
26
27using namespace EventViews;
28
29TimeLabels::TimeLabels(const QTimeZone &zone, int rows, TimeLabelsZone *parent, Qt::WindowFlags f)
30 : QWidget(parent, f)
31 , mTimezone(zone)
32{
33 mTimeLabelsZone = parent;
34 mRows = rows;
35 mMiniWidth = 0;
36
37 mCellHeight = mTimeLabelsZone->preferences()->hourSize() * 4;
38
39 setBackgroundRole(QPalette::Window);
40
41 mMousePos = new QFrame(this);
42 mMousePos->setLineWidth(1);
43 mMousePos->setFrameStyle(QFrame::HLine | QFrame::Plain);
44 mMousePos->setFixedSize(width(), 1);
45 colorMousePos();
46 mAgenda = nullptr;
47
49
50 updateConfig();
51}
52
53void TimeLabels::mousePosChanged(QPoint pos)
54{
55 colorMousePos();
56 mMousePos->move(0, pos.y());
57
58 // The repaint somehow prevents that the red line leaves a black artifact when
59 // moved down. It's not a full solution, though.
60 repaint();
61}
62
63void TimeLabels::showMousePos()
64{
65 // touch screen have no mouse position
66 mMousePos->show();
67}
68
69void TimeLabels::hideMousePos()
70{
71 mMousePos->hide();
72}
73
74void TimeLabels::colorMousePos()
75{
76 QPalette pal;
77 pal.setColor(QPalette::Window, // for Oxygen
78 mTimeLabelsZone->preferences()->agendaMarcusBainsLineLineColor());
79 pal.setColor(QPalette::WindowText, // for Plastique
80 mTimeLabelsZone->preferences()->agendaMarcusBainsLineLineColor());
81 mMousePos->setPalette(pal);
82}
83
84void TimeLabels::setCellHeight(double height)
85{
86 if (mCellHeight != height) {
87 mCellHeight = height;
89 }
90}
91
92QSize TimeLabels::minimumSizeHint() const
93{
95 sh.setWidth(mMiniWidth);
96 return sh;
97}
98
99static bool use12Clock()
100{
101 const QString str = QLocale().timeFormat();
102 // 'A' or 'a' means am/pm is shown (and then 'h' uses 12-hour format)
103 // but 'H' forces a 24-hour format anyway, even with am/pm shown.
104 return str.contains(QLatin1Char('a'), Qt::CaseInsensitive) && !str.contains(QLatin1Char('H'));
105}
106
107/** updates widget's internal state */
108void TimeLabels::updateConfig()
109{
110 setFont(mTimeLabelsZone->preferences()->agendaTimeLabelsFont());
111
112 QString test = QStringLiteral("20");
113 if (use12Clock()) {
114 test = QStringLiteral("12");
115 }
116 mMiniWidth = fontMetrics().boundingRect(test).width();
117 if (use12Clock()) {
118 test = QStringLiteral("pm");
119 } else {
120 test = QStringLiteral("00");
121 }
122 QFont sFont = font();
123 sFont.setPointSize(sFont.pointSize() / 2);
124 QFontMetrics fmS(sFont);
125 mMiniWidth += fmS.boundingRect(test).width() + 4;
126
127 /** Can happen if all resources are disabled */
128 if (!mAgenda) {
129 return;
130 }
131
132 // update HourSize
133 mCellHeight = mTimeLabelsZone->preferences()->hourSize() * 4;
134 // If the agenda is zoomed out so that more than 24 would be shown,
135 // the agenda only shows 24 hours, so we need to take the cell height
136 // from the agenda, which is larger than the configured one!
137 if (mCellHeight < 4 * mAgenda->gridSpacingY()) {
138 mCellHeight = 4 * mAgenda->gridSpacingY();
139 }
140
142
143 repaint();
144}
145
146/** */
147void TimeLabels::setAgenda(Agenda *agenda)
148{
149 mAgenda = agenda;
150
151 if (mAgenda) {
152 connect(mAgenda, &Agenda::mousePosSignal, this, &TimeLabels::mousePosChanged);
153 connect(mAgenda, &Agenda::enterAgenda, this, &TimeLabels::showMousePos);
154 connect(mAgenda, &Agenda::leaveAgenda, this, &TimeLabels::hideMousePos);
155 connect(mAgenda, &Agenda::gridSpacingYChanged, this, &TimeLabels::setCellHeight);
156 }
157}
158
159int TimeLabels::yposToCell(const int ypos) const
160{
161 const KCalendarCore::DateList datelist = mAgenda->dateList();
162 if (datelist.isEmpty()) {
163 return 0;
164 }
165
166 const auto firstDay = QDateTime(datelist.first(), QTime(0, 0, 0), QTimeZone::LocalTime).toUTC();
167 const int beginning // the hour we start drawing with
168 = !mTimezone.isValid() ? 0 : (mTimezone.offsetFromUtc(firstDay) - mTimeLabelsZone->preferences()->timeZone().offsetFromUtc(firstDay)) / 3600;
169
170 return static_cast<int>(ypos / mCellHeight) + beginning;
171}
172
173int TimeLabels::cellToHour(const int cell) const
174{
175 int tCell = cell % 24;
176 // handle different timezones
177 if (tCell < 0) {
178 tCell += 24;
179 }
180 // handle 24h and am/pm time formats
181 if (use12Clock()) {
182 if (tCell == 0) {
183 tCell = 12;
184 }
185 if (tCell < 0) {
186 tCell += 24;
187 }
188 if (tCell > 12) {
189 tCell %= 12;
190 if (tCell == 0) {
191 tCell = 12;
192 }
193 }
194 }
195 return tCell;
196}
197
198QString TimeLabels::cellToSuffix(const int cell) const
199{
200 // TODO: rewrite this using QTime's time formats. "am/pm" doesn't make sense
201 // in some locale's
202 QString suffix;
203 if (use12Clock()) {
204 if ((cell / 12) % 2 != 0) {
205 suffix = QStringLiteral("pm");
206 } else {
207 suffix = QStringLiteral("am");
208 }
209 } else {
210 suffix = QStringLiteral("00");
211 }
212 return suffix;
213}
214
215/** This is called in response to repaint() */
216void TimeLabels::paintEvent(QPaintEvent *)
217{
218 if (!mAgenda) {
219 return;
220 }
221 const KCalendarCore::DateList datelist = mAgenda->dateList();
222 if (datelist.isEmpty()) {
223 return;
224 }
225
226 QPainter p(this);
227
228 const int ch = height();
229
230 // We won't paint parts that aren't visible
231 const int cy = -y(); // y() returns a negative value.
232
233 const auto firstDay = QDateTime(datelist.first(), QTime(0, 0, 0), QTimeZone::LocalTime).toUTC();
234 const int beginning =
235 !mTimezone.isValid() ? 0 : (mTimezone.offsetFromUtc(firstDay) - mTimeLabelsZone->preferences()->timeZone().offsetFromUtc(firstDay)) / 3600;
236
237 // bug: the parameters cx and cw are the areas that need to be
238 // redrawn, not the area of the widget. unfortunately, this
239 // code assumes the latter...
240
241 // now, for a workaround...
242 const int cx = 0;
243 const int cw = width();
244 // end of workaround
245
246 int cell = yposToCell(cy);
247 double y = (cell - beginning) * mCellHeight;
249 QString hour;
250 int timeHeight = fm.ascent();
251 QFont hourFont = mTimeLabelsZone->preferences()->agendaTimeLabelsFont();
252 p.setFont(font());
253
254 // TODO: rewrite this using QTime's time formats. "am/pm" doesn't make sense
255 // in some locale's
256 QString suffix;
257 if (!use12Clock()) {
258 suffix = QStringLiteral("00");
259 } else {
260 suffix = QStringLiteral("am");
261 }
262
263 // We adjust the size of the hour font to keep it reasonable
264 if (timeHeight > mCellHeight) {
265 timeHeight = static_cast<int>(mCellHeight - 1);
266 int pointS = hourFont.pointSize();
267 while (pointS > 4) { // TODO: use smallestReadableFont() when added to kdelibs
268 hourFont.setPointSize(pointS);
269 fm = QFontMetrics(hourFont);
270 if (fm.ascent() < mCellHeight) {
271 break;
272 }
273 --pointS;
274 }
275 fm = QFontMetrics(hourFont);
276 timeHeight = fm.ascent();
277 }
278 // timeHeight -= (timeHeight/4-2);
279 QFont suffixFont = hourFont;
280 suffixFont.setPointSize(suffixFont.pointSize() / 2);
281 QFontMetrics fmS(suffixFont);
282 const int startW = cw - 2;
283 const int tw2 = fmS.boundingRect(suffix).width();
284 const int divTimeHeight = (timeHeight - 1) / 2 - 1;
285 // testline
286 // p->drawLine(0,0,0,contentsHeight());
287 while (y < cy + ch + mCellHeight) {
288 QColor lineColor;
289 QColor textColor;
290 textColor = palette().color(QPalette::WindowText);
291 if (cell < 0 || cell >= 24) {
292 textColor.setAlphaF(0.5);
293 }
294 lineColor = textColor;
295 lineColor.setAlphaF(lineColor.alphaF() / 5.);
296 p.setPen(lineColor);
297
298 // hour, full line
299 p.drawLine(cx, int(y), cw + 2, int(y));
300
301 // set the hour and suffix from the cell
302 hour.setNum(cellToHour(cell));
303 suffix = cellToSuffix(cell);
304
305 // draw the time label
306 p.setPen(textColor);
307 const int timeWidth = fm.boundingRect(hour).width();
308 int offset = startW - timeWidth - tw2 - 1;
309 p.setFont(hourFont);
310 p.drawText(offset, static_cast<int>(y + timeHeight), hour);
311 p.setFont(suffixFont);
312 offset = startW - tw2;
313 p.drawText(offset, static_cast<int>(y + timeHeight - divTimeHeight), suffix);
314
315 // increment indices
316 y += mCellHeight;
317 cell++;
318 }
319}
320
321QSize TimeLabels::sizeHint() const
322{
323 return {mMiniWidth, static_cast<int>(mRows * mCellHeight)};
324}
325
326void TimeLabels::contextMenuEvent(QContextMenuEvent *event)
327{
328 Q_UNUSED(event)
329
330 QMenu popup(this);
331 QAction *editTimeZones = popup.addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("&Add Timezones…"));
332 QAction *removeTimeZone = popup.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("&Remove Timezone %1", i18n(mTimezone.id().constData())));
333 if (!mTimezone.isValid() || !mTimeLabelsZone->preferences()->timeScaleTimezones().count() || mTimezone == mTimeLabelsZone->preferences()->timeZone()) {
334 removeTimeZone->setEnabled(false);
335 }
336
337 QAction *activatedAction = popup.exec(QCursor::pos());
338 if (activatedAction == editTimeZones) {
339 QPointer<TimeScaleConfigDialog> dialog = new TimeScaleConfigDialog(mTimeLabelsZone->preferences(), this);
340 if (dialog->exec() == QDialog::Accepted) {
341 mTimeLabelsZone->reset();
342 }
343 delete dialog;
344 } else if (activatedAction == removeTimeZone) {
345 QStringList list = mTimeLabelsZone->preferences()->timeScaleTimezones();
346 list.removeAll(QString::fromUtf8(mTimezone.id()));
347 mTimeLabelsZone->preferences()->setTimeScaleTimezones(list);
348 mTimeLabelsZone->preferences()->writeConfig();
349 mTimeLabelsZone->reset();
350 hide();
351 deleteLater();
352 }
353}
354
355QTimeZone TimeLabels::timeZone() const
356{
357 return mTimezone;
358}
359
360QString TimeLabels::header() const
361{
362 return i18n(mTimezone.id().constData());
363}
364
365QString TimeLabels::headerToolTip() const
366{
369
370 toolTip += "<qt>"_L1;
371 toolTip += i18nc("title for timezone info, the timezone id and utc offset",
372 "<b>%1 (%2)</b>",
373 i18n(mTimezone.id().constData()),
375 toolTip += "<hr>"_L1;
376 toolTip += i18nc("heading for timezone display name", "<i>Name:</i> %1", mTimezone.displayName(now, QTimeZone::LongName));
377 toolTip += "<br/>"_L1;
378
379 if (mTimezone.territory() != QLocale::AnyCountry) {
380 toolTip += i18nc("heading for timezone country", "<i>Country:</i> %1", QLocale::territoryToString(mTimezone.territory()));
381 toolTip += "<br/>"_L1;
382 }
383
384 auto abbreviations = QStringLiteral("&nbsp;");
385 const auto lst = mTimezone.transitions(now, now.addYears(1));
386 for (const auto &transition : lst) {
387 abbreviations += transition.abbreviation;
388 abbreviations += ",&nbsp;"_L1;
389 }
390 abbreviations.chop(7);
391 if (!abbreviations.isEmpty()) {
392 toolTip += i18nc("heading for comma-separated list of timezone abbreviations", "<i>Abbreviations:</i>");
393 toolTip += abbreviations;
394 toolTip += "<br/>"_L1;
395 }
396 const QString timeZoneComment(mTimezone.comment());
397 if (!timeZoneComment.isEmpty()) {
398 toolTip += i18nc("heading for the timezone comment", "<i>Comment:</i> %1", timeZoneComment);
399 }
400 toolTip += "</qt>"_L1;
401
402 return toolTip;
403}
404
405bool TimeLabels::event(QEvent *event)
406{
407 if (event->type() == QEvent::ToolTip) {
408 auto helpEvent = static_cast<QHelpEvent *>(event);
409 const int cell = yposToCell(helpEvent->pos().y());
410
412 toolTip += "<qt>"_L1;
413 toolTip += i18nc("[hour of the day][am/pm/00] [timezone id (timezone-offset)]",
414 "%1%2<br/>%3 (%4)",
415 cellToHour(cell),
416 cellToSuffix(cell),
417 i18n(mTimezone.id().constData()),
419 toolTip += "</qt>"_L1;
420
421 QToolTip::showText(helpEvent->globalPos(), toolTip, this);
422
423 return true;
424 }
425 return QWidget::event(event);
426}
427
428#include "moc_timelabels.cpp"
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
Namespace EventViews provides facilities for displaying incidences, including events,...
Definition agenda.h:33
KCALUTILS_EXPORT QString tzUTCOffsetStr(const QTimeZone &tz)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem test()
void setEnabled(bool)
const char * constData() const const
float alphaF() const const
void setAlphaF(float alpha)
QPoint pos()
QDateTime addYears(int nyears) const const
QDateTime currentDateTime()
QDateTime toUTC() const const
int pointSize() const const
void setPointSize(int pointSize)
int ascent() const const
QRect boundingRect(QChar ch) const const
QIcon fromTheme(const QString &name)
T & first()
bool isEmpty() const const
qsizetype removeAll(const AT &t)
QString territoryToString(Territory territory)
QString timeFormat(FormatType format) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void setColor(ColorGroup group, ColorRole role, const QColor &color)
int width() const const
void setWidth(int width)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
QString & setNum(double n, char format, int precision)
CaseInsensitive
typedef WindowFlags
QString comment() const const
QString displayName(QTimeZone::TimeType timeType, QTimeZone::NameType nameType, const QLocale &locale) const const
QByteArray id() const const
bool isValid() const const
int offsetFromUtc(const QDateTime &atDateTime) const const
QLocale::Territory territory() const const
OffsetDataList transitions(const QDateTime &fromDateTime, const QDateTime &toDateTime) const const
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
virtual bool event(QEvent *event) override
void setFont(const QFont &)
QFontMetrics fontMetrics() const const
void hide()
void setPalette(const QPalette &)
void move(const QPoint &)
void repaint()
void show()
void updateGeometry()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:12:24 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.