choose_date_time.cpp 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "ui/boxes/choose_date_time.h"
  8. #include "base/unixtime.h"
  9. #include "base/event_filter.h"
  10. #include "ui/boxes/calendar_box.h"
  11. #include "ui/widgets/buttons.h"
  12. #include "ui/widgets/fields/input_field.h"
  13. #include "ui/widgets/time_input.h"
  14. #include "ui/ui_utility.h"
  15. #include "lang/lang_keys.h"
  16. #include "styles/style_layers.h"
  17. #include "styles/style_boxes.h"
  18. #include <QtWidgets/QTextEdit>
  19. namespace Ui {
  20. namespace {
  21. constexpr auto kMinimalSchedule = TimeId(10);
  22. QString DayString(const QDate &date) {
  23. return tr::lng_month_day(
  24. tr::now,
  25. lt_month,
  26. Lang::MonthDay(date.month())(tr::now),
  27. lt_day,
  28. QString::number(date.day()));
  29. }
  30. QString TimeString(QTime time) {
  31. return QString("%1:%2"
  32. ).arg(time.hour()
  33. ).arg(time.minute(), 2, 10, QLatin1Char('0'));
  34. }
  35. } // namespace
  36. ChooseDateTimeStyleArgs::ChooseDateTimeStyleArgs()
  37. : labelStyle(&st::boxLabel)
  38. , dateFieldStyle(&st::scheduleDateField)
  39. , timeFieldStyle(&st::scheduleTimeField)
  40. , separatorStyle(&st::scheduleTimeSeparator)
  41. , atStyle(&st::scheduleAtLabel)
  42. , calendarStyle(&st::defaultCalendarColors) {
  43. }
  44. ChooseDateTimeBoxDescriptor ChooseDateTimeBox(
  45. not_null<GenericBox*> box,
  46. ChooseDateTimeBoxArgs &&args) {
  47. struct State {
  48. rpl::variable<QDate> date;
  49. not_null<InputField*> day;
  50. not_null<TimeInput*> time;
  51. not_null<FlatLabel*> at;
  52. };
  53. box->setTitle(std::move(args.title));
  54. box->setWidth(st::boxWideWidth);
  55. const auto content = box->addRow(
  56. object_ptr<FixedHeightWidget>(box, st::scheduleHeight));
  57. if (args.description) {
  58. box->addRow(object_ptr<FlatLabel>(
  59. box,
  60. std::move(args.description),
  61. *args.style.labelStyle));
  62. }
  63. const auto parsed = base::unixtime::parse(args.time);
  64. const auto state = box->lifetime().make_state<State>(State{
  65. .date = parsed.date(),
  66. .day = CreateChild<InputField>(
  67. content,
  68. *args.style.dateFieldStyle),
  69. .time = CreateChild<TimeInput>(
  70. content,
  71. TimeString(parsed.time()),
  72. *args.style.timeFieldStyle,
  73. *args.style.dateFieldStyle,
  74. *args.style.separatorStyle,
  75. st::scheduleTimeSeparatorPadding),
  76. .at = CreateChild<FlatLabel>(
  77. content,
  78. tr::lng_schedule_at(),
  79. *args.style.atStyle),
  80. });
  81. state->date.value(
  82. ) | rpl::start_with_next([=](QDate date) {
  83. state->day->setText(DayString(date));
  84. state->time->setFocusFast();
  85. }, state->day->lifetime());
  86. const auto min = args.min ? args.min : [] {
  87. return base::unixtime::now() + kMinimalSchedule;
  88. };
  89. const auto max = args.max ? args.max : [] {
  90. return base::unixtime::serialize(
  91. QDateTime::currentDateTime().addYears(1)) - 1;
  92. };
  93. const auto minDate = [=] {
  94. return base::unixtime::parse(min()).date();
  95. };
  96. const auto maxDate = [=] {
  97. return base::unixtime::parse(max()).date();
  98. };
  99. const auto &dayViewport = state->day->rawTextEdit()->viewport();
  100. base::install_event_filter(dayViewport, [=](not_null<QEvent*> event) {
  101. if (event->type() == QEvent::Wheel) {
  102. const auto e = static_cast<QWheelEvent*>(event.get());
  103. const auto direction = Ui::WheelDirection(e);
  104. if (!direction) {
  105. return base::EventFilterResult::Continue;
  106. }
  107. const auto d = state->date.current().addDays(direction);
  108. state->date = std::clamp(d, minDate(), maxDate());
  109. return base::EventFilterResult::Cancel;
  110. }
  111. return base::EventFilterResult::Continue;
  112. });
  113. content->widthValue(
  114. ) | rpl::start_with_next([=](int width) {
  115. const auto paddings = width
  116. - state->at->width()
  117. - 2 * st::scheduleAtSkip
  118. - st::scheduleDateWidth
  119. - st::scheduleTimeWidth;
  120. const auto left = paddings / 2;
  121. state->day->resizeToWidth(st::scheduleDateWidth);
  122. state->day->moveToLeft(left, st::scheduleDateTop, width);
  123. state->at->moveToLeft(
  124. left + st::scheduleDateWidth + st::scheduleAtSkip,
  125. st::scheduleAtTop,
  126. width);
  127. state->time->resizeToWidth(st::scheduleTimeWidth);
  128. state->time->moveToLeft(
  129. width - left - st::scheduleTimeWidth,
  130. st::scheduleDateTop,
  131. width);
  132. }, content->lifetime());
  133. const auto calendar
  134. = content->lifetime().make_state<QPointer<CalendarBox>>();
  135. const auto calendarStyle = args.style.calendarStyle;
  136. state->day->focusedChanges(
  137. ) | rpl::start_with_next([=](bool focused) {
  138. if (*calendar || !focused) {
  139. return;
  140. }
  141. *calendar = box->getDelegate()->show(
  142. Box<CalendarBox>(Ui::CalendarBoxArgs{
  143. .month = state->date.current(),
  144. .highlighted = state->date.current(),
  145. .callback = crl::guard(box, [=](QDate chosen) {
  146. state->date = chosen;
  147. (*calendar)->closeBox();
  148. }),
  149. .minDate = minDate(),
  150. .maxDate = maxDate(),
  151. .stColors = *calendarStyle,
  152. }));
  153. (*calendar)->boxClosing(
  154. ) | rpl::start_with_next(crl::guard(state->time, [=] {
  155. state->time->setFocusFast();
  156. }), (*calendar)->lifetime());
  157. }, state->day->lifetime());
  158. const auto collect = [=] {
  159. const auto timeValue = state->time->valueCurrent().split(':');
  160. if (timeValue.size() != 2) {
  161. return 0;
  162. }
  163. const auto time = QTime(timeValue[0].toInt(), timeValue[1].toInt());
  164. if (!time.isValid()) {
  165. return 0;
  166. }
  167. const auto result = base::unixtime::serialize(
  168. QDateTime(state->date.current(), time));
  169. if (result < min() || result > max()) {
  170. return 0;
  171. }
  172. return result;
  173. };
  174. const auto save = [=, done = args.done] {
  175. if (const auto result = collect()) {
  176. done(result);
  177. } else {
  178. state->time->showError();
  179. }
  180. };
  181. state->time->submitRequests(
  182. ) | rpl::start_with_next(save, state->time->lifetime());
  183. auto result = ChooseDateTimeBoxDescriptor();
  184. box->setFocusCallback([=] { state->time->setFocusFast(); });
  185. result.submit = box->addButton(std::move(args.submit), save);
  186. result.collect = [=] {
  187. if (const auto result = collect()) {
  188. return result;
  189. }
  190. state->time->showError();
  191. return 0;
  192. };
  193. result.values = rpl::combine(
  194. state->date.value(),
  195. state->time->value()
  196. ) | rpl::map(collect);
  197. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  198. return result;
  199. }
  200. } // namespace Ui