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

KDE's Doxygen guidelines are available online.