Coverage Report

Created: 2026-01-09 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wt/src/Wt/WCalendar.C
Line
Count
Source
1
/*
2
 * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3
 *
4
 * See the LICENSE file for terms of use.
5
 */
6
7
#include "Wt/WApplication.h"
8
#include "Wt/WEnvironment.h"
9
#include "Wt/WCalendar.h"
10
11
#include "Wt/WComboBox.h"
12
#include "Wt/WInPlaceEdit.h"
13
#include "Wt/WLineEdit.h"
14
#include "Wt/WLogger.h"
15
#include "Wt/WStringStream.h"
16
#include "Wt/WTemplate.h"
17
#include "Wt/WText.h"
18
19
#include "WebUtils.h"
20
21
namespace Wt {
22
23
LOGGER("WCalendar");
24
25
// Because WDate returns days and weeks as WT_USTRING, we need this:
26
#ifndef WT_TARGET_JAVA
27
#define DATE_NAME_STR(e) e
28
#else
29
#define DATE_NAME_STR(e) WString::fromUTF8(e)
30
#endif
31
32
WCalendar::WCalendar()
33
0
{
34
0
  create();
35
0
  impl_->addStyleClass("Wt-calendar");
36
0
}
37
38
void WCalendar::setSelectionMode(SelectionMode mode)
39
0
{
40
0
  if (selectionMode_ != mode) {
41
0
    if (mode != SelectionMode::Extended && selection_.size() > 1) {
42
0
      selection_.clear();
43
0
      renderMonth();
44
0
    }
45
0
    selectionMode_ = mode;
46
0
  }
47
0
}
48
49
void WCalendar::setSingleClickSelect(bool single)
50
0
{
51
0
  singleClickSelect_ = single;
52
0
}
53
54
void WCalendar::create()
55
0
{
56
0
  selectionMode_ = SelectionMode::Single;
57
0
  singleClickSelect_ = false;
58
0
  horizontalHeaderFormat_ = CalendarHeaderFormat::ShortDayNames;
59
0
  firstDayOfWeek_ = 1;
60
61
0
  WDate currentDay = WDate::currentDate();
62
63
0
  currentYear_ = currentDay.year();
64
0
  currentMonth_ = currentDay.month();
65
66
0
  WStringStream text;
67
68
0
  text <<
69
0
    "<table class=\"days ${table-class}\" cellspacing=\"0\" cellpadding=\"0\">"
70
0
    """<tr>"
71
0
    ""  "<th class=\"caption\">${nav-prev}</th>"
72
0
    ""  "<th class=\"caption\"colspan=\"5\">${month} ${year}</th>"
73
0
    ""  "<th class=\"caption\">${nav-next}</th>"
74
0
    """</tr>"
75
0
    """<tr>";
76
77
0
  for (int j = 0; j < 7; ++j)
78
0
    text <<
79
0
      "<th title=\"${t" << j << "}\" scope=\"col\">${d" << j << "}</th>";
80
81
0
  text << "</tr>";
82
83
0
  for (int i = 0; i < 6; ++i) {
84
0
    text << "<tr>";
85
0
    for (int j = 0; j < 7; ++j)
86
0
      text << "<td>${c" << (i * 7 + j) << "}</td>";
87
0
    text << "</tr>";
88
0
  }
89
90
0
  text << "</table>";
91
92
0
  std::unique_ptr<WTemplate> t(new WTemplate());
93
0
  impl_ = t.get();
94
0
  setImplementation(std::move(t));
95
0
  impl_->setTemplateText(WString::fromUTF8(text.str()),
96
0
                         TextFormat::UnsafeXHTML);
97
0
  impl_->setStyleClass("Wt-cal");
98
99
0
  setSelectable(false);
100
101
0
  std::unique_ptr<WText> prevMonth(new WText(tr("Wt.WCalendar.PrevMonth")));
102
0
  prevMonth->setStyleClass("Wt-cal-navbutton");
103
0
  prevMonth->clicked().connect(this, &WCalendar::browseToPreviousMonth);
104
105
0
  std::unique_ptr<WText> nextMonth(new WText(tr("Wt.WCalendar.NextMonth")));
106
0
  nextMonth->setStyleClass("Wt-cal-navbutton");
107
0
  nextMonth->clicked().connect(this, &WCalendar::browseToNextMonth);
108
109
0
  std::unique_ptr<WComboBox> monthEdit(new WComboBox());
110
0
  monthEdit_ = monthEdit.get();
111
112
0
  monthEdit->setInline(true);
113
0
  for (unsigned i = 0; i < 12; ++i)
114
0
    monthEdit->addItem(WDate::longMonthName(i+1));
115
0
  monthEdit->activated().connect(this, &WCalendar::monthChanged);
116
0
  monthEdit->setDisabled(!WApplication::instance()->environment().ajax());
117
118
0
  std::unique_ptr<WInPlaceEdit> yearEdit(new WInPlaceEdit(""));
119
0
  yearEdit_ = yearEdit.get();
120
121
0
  yearEdit->setButtonsEnabled(false);
122
0
  yearEdit->lineEdit()->setTextSize(4);
123
0
  yearEdit->setStyleClass("Wt-cal-year");
124
0
  yearEdit->valueChanged().connect(this, &WCalendar::yearChanged);
125
126
0
  impl_->bindWidget("nav-prev", std::move(prevMonth));
127
0
  impl_->bindWidget("nav-next", std::move(nextMonth));
128
0
  impl_->bindWidget("month", std::move(monthEdit));
129
0
  impl_->bindWidget("year", std::move(yearEdit));
130
131
0
  setHorizontalHeaderFormat(horizontalHeaderFormat_);
132
0
  setFirstDayOfWeek(firstDayOfWeek_);
133
0
}
134
135
void WCalendar::enableAjax()
136
0
{
137
0
  WCompositeWidget::enableAjax();
138
0
  monthEdit_->enable();
139
140
0
}
141
142
void WCalendar::load()
143
0
{
144
0
  WCompositeWidget::load();
145
0
  if(WApplication::instance()->environment().ajax()){
146
0
    monthEdit_->enable();
147
0
  }
148
0
}
149
150
void WCalendar::setFirstDayOfWeek(int dayOfWeek)
151
0
{
152
0
  firstDayOfWeek_ = dayOfWeek;
153
154
0
  for (unsigned i = 0; i < 7; ++i) {
155
0
    int day = (i + firstDayOfWeek_ - 1) % 7 + 1;
156
157
0
    WString title = WDate::longDayName(day);
158
0
    impl_->bindString("t" + std::to_string(i),
159
0
                      title,
160
0
                      TextFormat::UnsafeXHTML);
161
162
0
    WString abbr;
163
0
    switch (horizontalHeaderFormat_) {
164
0
    case CalendarHeaderFormat::SingleLetterDayNames:
165
0
      abbr = WString::fromUTF8(WDate::shortDayName(day).toUTF8().substr(0, 1));
166
0
      break;
167
0
    case CalendarHeaderFormat::ShortDayNames:
168
0
      abbr = WDate::shortDayName(day);
169
0
      break;
170
0
    case CalendarHeaderFormat::LongDayNames:
171
0
      abbr = WDate::longDayName(day);
172
0
      break;
173
0
    }
174
175
0
    impl_->bindString("d" + std::to_string(i), abbr,
176
0
                      TextFormat::UnsafeXHTML);
177
0
  }
178
179
0
  renderMonth();
180
0
}
181
182
void WCalendar::setHorizontalHeaderFormat(CalendarHeaderFormat format)
183
0
{
184
0
  std::string d;
185
0
  switch (format) {
186
0
  case CalendarHeaderFormat::SingleLetterDayNames:
187
0
    d = "d1"; break;
188
0
  case CalendarHeaderFormat::ShortDayNames:
189
0
    d = "d3"; break;
190
0
  case CalendarHeaderFormat::LongDayNames:
191
0
    d = "dlong"; break;
192
0
  default:
193
0
    LOG_ERROR("setHorizontalHeaderFormat(): "
194
0
              "improper horizontal header format.");
195
0
    format = CalendarHeaderFormat::SingleLetterDayNames;
196
0
    d = "d1";
197
0
  }
198
199
0
  horizontalHeaderFormat_ = format;
200
201
0
  impl_->bindString("table-class", d, TextFormat::UnsafeXHTML);
202
203
0
  setFirstDayOfWeek(firstDayOfWeek_);
204
0
}
205
206
void WCalendar::renderMonth()
207
0
{
208
0
  needRenderMonth_ = true;
209
210
0
  if (isRendered())
211
0
    scheduleRender();
212
0
}
213
214
void WCalendar::render(WFlags<RenderFlag> flags)
215
0
{
216
0
  if (needRenderMonth_) {
217
0
#ifndef WT_TARGET_JAVA
218
0
    char buf[30];
219
#else
220
    char *buf;
221
#endif // WT_TARGET_JAVA
222
223
0
    int m = currentMonth_ - 1;
224
0
    if (monthEdit_->currentIndex() != m)
225
0
      monthEdit_->setCurrentIndex(m);
226
227
0
    int y = currentYear_;
228
0
    Utils::itoa(y, buf);
229
0
    if (yearEdit_->text().toUTF8() != buf)
230
0
      yearEdit_->setText(WString::fromUTF8(buf));
231
232
    // The first line contains the last day of the previous month.
233
0
    WDate d(currentYear_, currentMonth_, 1);
234
0
    d = d.addDays(-1);
235
236
0
    d = WDate::previousWeekday(d, firstDayOfWeek_);
237
238
0
    for (unsigned i = 0; i < 6; ++i) {
239
0
      for (unsigned j = 0; j < 7; ++j) {
240
0
        Utils::itoa(i * 7 + j, buf);
241
0
        std::string cell = std::string("c") + buf;
242
243
0
        WDate date(d.year(), d.month(), d.day());
244
245
0
        WWidget *w = impl_->resolveWidget(cell);
246
0
        WWidget *rw = renderCell(w, date);
247
0
        WInteractWidget* iw = dynamic_cast<WInteractWidget*>(rw->webWidget());
248
249
0
        if (rw != w)
250
0
          impl_->bindWidget(cell, std::unique_ptr<WWidget>(rw));
251
252
0
        if (iw && iw != w) {
253
0
          if (clicked().isConnected()
254
0
              || (selectionMode_ == SelectionMode::Extended)
255
0
              || (selectionMode_ != SelectionMode::Extended &&
256
0
                  singleClickSelect_ && activated().isConnected())) {
257
0
            const Coordinate c(i, j);
258
0
            iw->clicked().connect
259
0
              (this,
260
0
               std::bind(&WCalendar::cellClicked, this, c));
261
0
          }
262
263
0
          if ((selectionMode_ != SelectionMode::Extended &&
264
0
               !singleClickSelect_ && (activated().isConnected() ||
265
0
                   selectionChanged().isConnected()))) {
266
0
            const Coordinate c(i, j);
267
0
            iw->doubleClicked().connect
268
0
              (this,
269
0
               std::bind(&WCalendar::cellDblClicked, this, c));
270
0
          }
271
0
        }
272
273
0
    d = d.addDays(1);
274
0
      }
275
0
    }
276
277
0
    needRenderMonth_ = false;
278
0
  }
279
280
0
  WCompositeWidget::render(flags);
281
0
}
282
283
WWidget *WCalendar::renderCell(WWidget* widget, const WDate& date)
284
0
{
285
0
  WText* t = dynamic_cast<WText*>(widget);
286
287
0
  if (!t) {
288
0
    t = new WText();
289
0
    t->setInline(false);
290
0
    t->setTextFormat(TextFormat::Plain);
291
0
  }
292
293
0
#ifndef WT_TARGET_JAVA
294
0
    char buf[30];
295
#else
296
    char *buf;
297
#endif // WT_TARGET_JAVA
298
0
  Utils::itoa(date.day(), buf);
299
0
  t->setText(WString::fromUTF8(buf));
300
301
0
  std::string styleClass;
302
303
0
  if (isInvalid(date))
304
0
    styleClass += " Wt-cal-oor";
305
0
  else if (date.month() != currentMonth())
306
0
    styleClass += " Wt-cal-oom";
307
308
0
  if (isSelected(date))
309
0
    styleClass += " Wt-cal-sel";
310
311
0
  WDate currentDate = WDate::currentDate();
312
0
  if (date.day() == currentDate.day() && date.month() == currentDate.month() &&
313
0
      date.year() == currentDate.year()) {
314
0
    if (!isSelected(date))
315
0
      styleClass += " Wt-cal-now";
316
0
    t->setToolTip(WString::tr("Wt.WCalendar.today"));
317
0
  } else
318
0
    t->setToolTip("");
319
320
0
  t->setStyleClass(styleClass.c_str());
321
322
0
  return t;
323
0
}
324
325
bool WCalendar::isSelected(const WDate& d) const
326
0
{
327
0
  return selection_.find(d) != selection_.end();
328
0
}
329
330
void WCalendar::clearSelection()
331
0
{
332
0
  selection_.clear();
333
334
0
  renderMonth();
335
0
}
336
337
void WCalendar::select(const WDate& date)
338
0
{
339
0
  selection_.clear();
340
341
0
  selection_.insert(date);
342
0
  renderMonth();
343
0
}
344
345
void WCalendar::browseTo(const WDate& date)
346
0
{
347
0
  bool rerender = false;
348
349
0
  if (currentYear_ != date.year()) {
350
0
    currentYear_ = date.year();
351
0
    rerender = true;
352
0
  }
353
354
0
  if (currentMonth_ != date.month()) {
355
0
    currentMonth_ = date.month();
356
0
    rerender = true;
357
0
  }
358
359
0
  if (rerender) {
360
0
    emitCurrentPageChanged();
361
0
    renderMonth();
362
0
  }
363
0
}
364
365
void WCalendar::select(const std::set<WDate>& dates)
366
0
{
367
0
  if (selectionMode_ == SelectionMode::Extended) {
368
0
    selection_ = dates;
369
0
    renderMonth();
370
0
  } else if (selectionMode_ == SelectionMode::Single) {
371
0
    if (dates.empty())
372
0
      clearSelection();
373
0
    else
374
0
      select(*dates.begin());
375
0
  }
376
0
}
377
378
void WCalendar::selectInCurrentMonth(const WDate& d)
379
0
{
380
0
  if (d.month() == currentMonth_ &&
381
0
      selectionMode_ != SelectionMode::None) {
382
0
    if (selectionMode_ == SelectionMode::Extended) {
383
0
      if (isSelected(d))
384
0
        selection_.erase(d);
385
0
      else
386
0
        selection_.insert(d);
387
0
    } else {
388
0
      selection_.clear();
389
0
      selection_.insert(d);
390
0
    }
391
392
0
    renderMonth();
393
0
    selectionChanged().emit();
394
0
  }
395
0
}
396
397
bool WCalendar::isInvalid(const WDate& dt)
398
0
{
399
0
  return ((!bottom_.isNull() && dt < bottom_) ||
400
0
          (!top_.isNull() && dt > top_));
401
0
}
402
403
void WCalendar::cellClicked(Coordinate weekday)
404
0
{
405
0
  WDate dt = dateForCell(weekday.i, weekday.j);
406
0
  if (isInvalid(dt))
407
0
    return;
408
409
0
  selectInCurrentMonth(dt);
410
0
  clicked().emit(dt);
411
412
0
  if (selectionMode_ != SelectionMode::Extended &&
413
0
      singleClickSelect_)
414
0
    activated().emit(dt);
415
0
}
416
417
void WCalendar::cellDblClicked(Coordinate weekday)
418
0
{
419
0
  WDate dt = dateForCell(weekday.i, weekday.j);
420
0
  if (isInvalid(dt))
421
0
    return;
422
423
0
  selectInCurrentMonth(dt);
424
425
0
  if (selectionMode_ != SelectionMode::Extended &&
426
0
      !singleClickSelect_)
427
0
    activated().emit(dt);
428
0
}
429
430
WDate WCalendar::dateForCell(int week, int dayOfWeek)
431
0
{
432
0
  WDate d(currentYear_, currentMonth_, 1);
433
0
  d = d.addDays(-1);
434
0
  d = WDate::previousWeekday(d, firstDayOfWeek_);
435
0
  d = d.addDays(week * 7 + dayOfWeek);
436
0
  return d;
437
0
}
438
439
void WCalendar::emitCurrentPageChanged()
440
0
{
441
0
  currentPageChanged().emit(currentYear_, currentMonth_);
442
0
}
443
444
void WCalendar::browseToPreviousYear()
445
0
{
446
0
  --currentYear_;
447
448
0
  emitCurrentPageChanged();
449
0
  renderMonth();
450
0
}
451
452
void WCalendar::browseToPreviousMonth()
453
0
{
454
0
  if (--currentMonth_ == 0) {
455
0
    currentMonth_ = 12;
456
0
    --currentYear_;
457
0
  }
458
459
0
  emitCurrentPageChanged();
460
0
  renderMonth();
461
0
}
462
463
void WCalendar::browseToNextYear()
464
0
{
465
0
  ++currentYear_;
466
467
0
  emitCurrentPageChanged();
468
0
  renderMonth();
469
0
}
470
471
void WCalendar::browseToNextMonth()
472
0
{
473
0
  if (++currentMonth_ == 13) {
474
0
    currentMonth_ = 1;
475
0
    ++currentYear_;
476
0
  }
477
478
0
  emitCurrentPageChanged();
479
0
  renderMonth();
480
0
}
481
482
void WCalendar::monthChanged(int newMonth)
483
0
{
484
0
  ++newMonth;
485
486
0
  if (currentMonth_ != newMonth
487
0
      && (newMonth >= 1 && newMonth <= 12)) {
488
489
0
    currentMonth_ = newMonth;
490
491
0
    emitCurrentPageChanged();
492
0
    renderMonth();
493
0
  }
494
0
}
495
496
void WCalendar::yearChanged(WString yearStr)
497
0
{
498
0
  try {
499
0
    int year = Utils::stoi(yearStr.toUTF8());
500
501
0
    if (currentYear_ != year &&
502
0
        (year >= 1900 && year <= 2200)) { // ??
503
0
      currentYear_ = year;
504
505
0
      emitCurrentPageChanged();
506
0
      renderMonth();
507
0
    }
508
0
  } catch (std::exception& e) {
509
0
  }
510
0
}
511
512
void WCalendar::setBottom(const WDate& bottom)
513
0
{
514
0
  if (bottom_ != bottom) {
515
0
    bottom_ = bottom;
516
0
    renderMonth();
517
0
  }
518
0
}
519
520
void WCalendar::setTop(const WDate& top)
521
0
{
522
0
  if (top_ != top) {
523
0
    top_ = top;
524
0
    renderMonth();
525
0
  }
526
0
}
527
}