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