1use super::date_picker::index_of_ignore_case;
2use super::{
3 DatePicker, DateTimePickerPanel, DateTimePickerPanelType, PickerPresenterBase, PickerPresenterBaseImpl,
4};
5use crate::primitives::{TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt};
6use crate::{Button, ColumnDefinition, Control, ControlImpl, Grid, GridUnitType, Panel, SpinDirection};
7use ferroui_base::input::{
8 FocusManager, InputElementImpl, InputElementImplExt, Key, KeyEventArgs, KeyModifiers, KeyboardNavigation, KeyboardNavigationHandler,
9 KeyboardNavigationMode, NavigationDirection, NavigationMethod,
10};
11use ferroui_base::interactivity::InteractiveImpl;
12use ferroui_base::layout::LayoutableImpl;
13use ferroui_base::utilities::{CultureInfo, DateTimeOffset, GregorianCalendar};
14use ferroui_base::{
15 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, FerroObject, FerroObjectImpl, FerroObjectImplExt,
16 FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElementImpl, StyledProperty, StyledPropertyOptions,
17 VisualImpl,
18};
19use std::cell::{Cell, RefCell};
20
21const PICKER_CONTAINER_NAME: &str = "PART_PickerContainer";
22const ACCEPT_BUTTON_NAME: &str = "PART_AcceptButton";
23const DISMISS_BUTTON_NAME: &str = "PART_DismissButton";
24const FIRST_SPACER_NAME: &str = "PART_FirstSpacer";
25const SECOND_SPACER_NAME: &str = "PART_SecondSpacer";
26const MONTH_HOST_NAME: &str = "PART_MonthHost";
27const YEAR_HOST_NAME: &str = "PART_YearHost";
28const DAY_HOST_NAME: &str = "PART_DayHost";
29const MONTH_SELECTOR_NAME: &str = "PART_MonthSelector";
30const YEAR_SELECTOR_NAME: &str = "PART_YearSelector";
31const DAY_SELECTOR_NAME: &str = "PART_DaySelector";
32const MONTH_UP_BUTTON_NAME: &str = "PART_MonthUpButton";
33const DAY_UP_BUTTON_NAME: &str = "PART_DayUpButton";
34const YEAR_UP_BUTTON_NAME: &str = "PART_YearUpButton";
35const MONTH_DOWN_BUTTON_NAME: &str = "PART_MonthDownButton";
36const DAY_DOWN_BUTTON_NAME: &str = "PART_DayDownButton";
37const YEAR_DOWN_BUTTON_NAME: &str = "PART_YearDownButton";
38
39#[derive(Clone)]
41#[allow(dead_code)]
42struct TemplateItems {
43 picker_container: Ref<Grid>,
44 accept_button: Ref<Button>,
45 dismiss_button: Option<Ref<Button>>,
46 first_spacer: Option<Ref<Control>>,
47 second_spacer: Option<Ref<Control>>,
48 month_host: Ref<Panel>,
49 year_host: Ref<Panel>,
50 day_host: Ref<Panel>,
51 month_selector: Ref<DateTimePickerPanel>,
52 year_selector: Ref<DateTimePickerPanel>,
53 day_selector: Ref<DateTimePickerPanel>,
54 month_up_button: Option<Ref<Button>>,
55 day_up_button: Option<Ref<Button>>,
56 year_up_button: Option<Ref<Button>>,
57 month_down_button: Option<Ref<Button>>,
58 day_down_button: Option<Ref<Button>>,
59 year_down_button: Option<Ref<Button>>,
60}
61
62#[repr(C)]
64pub struct DatePickerPresenter {
65 base: PickerPresenterBase,
66 template_items: RefCell<Option<TemplateItems>>,
67 sync_date: Cell<DateTimeOffset>,
68 calendar: GregorianCalendar,
69 suppress_update_selection: Cell<bool>,
70}
71
72ferro_class!(DatePickerPresenter: PickerPresenterBase);
73
74ferro_class_info!(DatePickerPresenter {
75 new: DatePickerPresenter::new,
76 markup: {
77 attributes: [
78 TemplatePart("PART_AcceptButton", type(Ref<Button>), IsRequired = true),
79 TemplatePart("PART_DayDownButton", type(Ref<Button>)),
80 TemplatePart("PART_DayHost", type(Ref<Panel>), IsRequired = true),
81 TemplatePart("PART_DaySelector", type(Ref<DateTimePickerPanel>), IsRequired = true),
82 TemplatePart("PART_DayUpButton", type(Ref<Button>)),
83 TemplatePart("PART_DismissButton", type(Ref<Button>)),
84 TemplatePart("PART_FirstSpacer", type(Ref<Control>)),
85 TemplatePart("PART_MonthDownButton", type(Ref<Button>)),
86 TemplatePart("PART_MonthHost", type(Ref<Panel>), IsRequired = true),
87 TemplatePart("PART_MonthSelector", type(Ref<DateTimePickerPanel>), IsRequired = true),
88 TemplatePart("PART_MonthUpButton", type(Ref<Button>)),
89 TemplatePart("PART_PickerContainer", type(Ref<Grid>), IsRequired = true),
90 TemplatePart("PART_SecondSpacer", type(Ref<Control>)),
91 TemplatePart("PART_YearDownButton", type(Ref<Button>)),
92 TemplatePart("PART_YearHost", type(Ref<Panel>), IsRequired = true),
93 TemplatePart("PART_YearSelector", type(Ref<DateTimePickerPanel>), IsRequired = true),
94 TemplatePart("PART_YearUpButton", type(Ref<Button>)),
95 ],
96 },
97});
98
99ferro_impl_classes!(
100 DatePickerPresenter: StyledElementImpl,
101 VisualImpl,
102 LayoutableImpl,
103 InteractiveImpl,
104 ControlImpl,
105 PickerPresenterBaseImpl
106);
107
108impl FerroObjectImpl for DatePickerPresenter {
109 fn constructed(this: &Self) {
110 Self::parent_constructed(this);
111
112 let now = DateTimeOffset::now();
113 this.set_current_value(
114 Self::min_year_property(),
115 DateTimeOffset::new(now.year() - 100, 1, 1, 0, 0, 0, now.offset()),
116 );
117 this.set_current_value(
118 Self::max_year_property(),
119 DateTimeOffset::new(now.year() + 100, 12, 31, 0, 0, 0, now.offset()),
120 );
121 this.set_current_value(Self::date_property(), now);
122 }
123
124 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
125 Self::parent_on_property_changed(this, change);
126
127 let property = change.property();
128 if property == Self::date_property().as_property() {
129 this.on_date_changed(change.get_new_value::<DateTimeOffset>());
130 } else if property == Self::max_year_property().as_property()
131 || property == Self::min_year_property().as_property()
132 {
133 this.on_date_range_changed();
134 } else if property == Self::month_format_property().as_property()
135 || property == Self::year_format_property().as_property()
136 || property == Self::day_format_property().as_property()
137 {
138 this.init_picker();
139 }
140 }
141}
142
143impl TemplatedControlImpl for DatePickerPresenter {
144 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
145 Self::parent_on_apply_template(this, e);
146
147 let name_scope = e.name_scope();
148 let weak_this = this.to_ref().downgrade();
149 let selector_button = |name: &str, type_: DateTimePickerPanelType, direction: SpinDirection| {
150 let button = name_scope.find_as::<Button>(name)?;
151 let weak = weak_this.clone();
152 button.click(move |_, _| {
153 if let Some(this) = weak.upgrade() {
154 this.on_selector_button_click(type_, direction);
155 }
156 });
157 Some(button)
158 };
159
160 let items = TemplateItems {
161 picker_container: name_scope.get_as::<Grid>(PICKER_CONTAINER_NAME),
163 month_host: name_scope.get_as::<Panel>(MONTH_HOST_NAME),
164 day_host: name_scope.get_as::<Panel>(DAY_HOST_NAME),
165 year_host: name_scope.get_as::<Panel>(YEAR_HOST_NAME),
166
167 month_selector: name_scope.get_as::<DateTimePickerPanel>(MONTH_SELECTOR_NAME),
168 day_selector: name_scope.get_as::<DateTimePickerPanel>(DAY_SELECTOR_NAME),
169 year_selector: name_scope.get_as::<DateTimePickerPanel>(YEAR_SELECTOR_NAME),
170
171 accept_button: name_scope.get_as::<Button>(ACCEPT_BUTTON_NAME),
172
173 month_up_button: selector_button(MONTH_UP_BUTTON_NAME, DateTimePickerPanelType::Month, SpinDirection::Decrease),
174 month_down_button: selector_button(
175 MONTH_DOWN_BUTTON_NAME,
176 DateTimePickerPanelType::Month,
177 SpinDirection::Increase,
178 ),
179 day_up_button: selector_button(DAY_UP_BUTTON_NAME, DateTimePickerPanelType::Day, SpinDirection::Decrease),
180 day_down_button: selector_button(DAY_DOWN_BUTTON_NAME, DateTimePickerPanelType::Day, SpinDirection::Increase),
181 year_up_button: selector_button(YEAR_UP_BUTTON_NAME, DateTimePickerPanelType::Year, SpinDirection::Decrease),
182 year_down_button: selector_button(
183 YEAR_DOWN_BUTTON_NAME,
184 DateTimePickerPanelType::Year,
185 SpinDirection::Increase,
186 ),
187
188 dismiss_button: name_scope.find_as::<Button>(DISMISS_BUTTON_NAME),
189 first_spacer: name_scope.find_as::<Control>(FIRST_SPACER_NAME),
190 second_spacer: name_scope.find_as::<Control>(SECOND_SPACER_NAME),
191 };
192 *this.template_items.borrow_mut() = Some(items.clone());
193
194 let weak = weak_this.clone();
195 items.accept_button.click(move |_, _| {
196 if let Some(this) = weak.upgrade() {
197 this.on_accept_button_clicked();
198 }
199 });
200 let weak = weak_this.clone();
201 items.month_selector.selection_changed(move || {
202 if let Some(this) = weak.upgrade() {
203 this.on_month_changed();
204 }
205 });
206 let weak = weak_this.clone();
207 items.day_selector.selection_changed(move || {
208 if let Some(this) = weak.upgrade() {
209 this.on_day_changed();
210 }
211 });
212 let weak = weak_this.clone();
213 items.year_selector.selection_changed(move || {
214 if let Some(this) = weak.upgrade() {
215 this.on_year_changed();
216 }
217 });
218
219 if let Some(dismiss_button) = &items.dismiss_button {
220 let weak = weak_this.clone();
221 dismiss_button.click(move |_, _| {
222 if let Some(this) = weak.upgrade() {
223 this.on_dismiss_button_clicked();
224 }
225 });
226 }
227
228 this.init_picker();
229 }
230}
231
232impl InputElementImpl for DatePickerPresenter {
233 fn on_key_down(this: &Self, e: &KeyEventArgs) {
234 match e.key {
235 Key::Escape => {
236 this.on_dismiss();
237 e.set_handled(true);
238 }
239 Key::Tab => {
240 let focus_manager = FocusManager::get_focus_manager(this);
241 if let Some(focus) = focus_manager.and_then(|focus_manager| focus_manager.get_focused_element()) {
242 let next_focus = KeyboardNavigationHandler::get_next(&focus, NavigationDirection::Next);
243 if let Some(next_focus) = next_focus {
244 next_focus.focus_with(NavigationMethod::Tab, KeyModifiers::NONE);
245 }
246 e.set_handled(true);
247 }
248 }
249 Key::Enter => {
250 this.set_current_value(Self::date_property(), this.sync_date.get());
251 this.on_confirmed();
252 e.set_handled(true);
253 }
254 _ => {}
255 }
256 Self::parent_on_key_down(this, e);
257 }
258}
259
260ferro_properties! {
261 impl DatePickerPresenter {
262 pub fn date_property() -> StyledProperty<DateTimeOffset> {
264 FerroProperty::register_with::<DatePickerPresenter, _>(
265 "Date",
266 StyledPropertyOptions::new(DateTimeOffset::default()).coerce(DatePickerPresenter::coerce_date),
267 )
268 }
269
270 pub fn day_format_property() -> StyledProperty<String> {
272 DatePicker::day_format_property().add_owner::<DatePickerPresenter>()
273 }
274
275 pub fn day_visible_property() -> StyledProperty<bool> {
277 DatePicker::day_visible_property().add_owner::<DatePickerPresenter>()
278 }
279
280 pub fn max_year_property() -> StyledProperty<DateTimeOffset> {
282 DatePicker::max_year_property().add_owner::<DatePickerPresenter>()
283 }
284
285 pub fn min_year_property() -> StyledProperty<DateTimeOffset> {
287 DatePicker::min_year_property().add_owner::<DatePickerPresenter>()
288 }
289
290 pub fn month_format_property() -> StyledProperty<String> {
292 DatePicker::month_format_property().add_owner::<DatePickerPresenter>()
293 }
294
295 pub fn month_visible_property() -> StyledProperty<bool> {
297 DatePicker::month_visible_property().add_owner::<DatePickerPresenter>()
298 }
299
300 pub fn year_format_property() -> StyledProperty<String> {
302 DatePicker::year_format_property().add_owner::<DatePickerPresenter>()
303 }
304
305 pub fn year_visible_property() -> StyledProperty<bool> {
307 DatePicker::year_visible_property().add_owner::<DatePickerPresenter>()
308 }
309 }
310}
311
312impl DatePickerPresenter {
313 fn static_constructor() {
314 KeyboardNavigation::tab_navigation_property()
315 .override_default_value::<DatePickerPresenter>(KeyboardNavigationMode::Cycle);
316 }
317
318 pub fn construct() -> Self {
320 Self {
321 base: PickerPresenterBase::construct(),
322 template_items: RefCell::new(None),
323 sync_date: Cell::new(DateTimeOffset::default()),
324 calendar: GregorianCalendar::new(),
325 suppress_update_selection: Cell::new(false),
326 }
327 }
328
329 pub fn new() -> Ref<Self> {
330 instantiate(Self::construct())
331 }
332
333 fn coerce_date(sender: &FerroObject, value: DateTimeOffset) -> DateTimeOffset {
334 let max = sender.get_value(Self::max_year_property());
335 if value > max {
336 return max;
337 }
338 let min = sender.get_value(Self::min_year_property());
339 if value < min {
340 return min;
341 }
342
343 value
344 }
345
346 fn on_date_range_changed(&self) {
347 self.coerce_value(Self::date_property().as_property());
348 }
349
350 pub fn date(&self) -> DateTimeOffset {
352 self.get_value(Self::date_property())
353 }
354
355 pub fn set_date(&self, value: DateTimeOffset) {
356 self.set_value(Self::date_property(), value)
357 }
358
359 fn on_date_changed(&self, new_value: DateTimeOffset) {
360 self.sync_date.set(new_value);
361 self.init_picker();
362 }
363
364 pub fn day_format(&self) -> String {
366 self.get_value(Self::day_format_property())
367 }
368
369 pub fn set_day_format(&self, value: &str) {
370 self.set_value(Self::day_format_property(), value.to_string())
371 }
372
373 pub fn day_visible(&self) -> bool {
375 self.get_value(Self::day_visible_property())
376 }
377
378 pub fn set_day_visible(&self, value: bool) {
379 self.set_value(Self::day_visible_property(), value)
380 }
381
382 pub fn max_year(&self) -> DateTimeOffset {
384 self.get_value(Self::max_year_property())
385 }
386
387 pub fn set_max_year(&self, value: DateTimeOffset) {
388 self.set_value(Self::max_year_property(), value)
389 }
390
391 pub fn min_year(&self) -> DateTimeOffset {
393 self.get_value(Self::min_year_property())
394 }
395
396 pub fn set_min_year(&self, value: DateTimeOffset) {
397 self.set_value(Self::min_year_property(), value)
398 }
399
400 pub fn month_format(&self) -> String {
402 self.get_value(Self::month_format_property())
403 }
404
405 pub fn set_month_format(&self, value: &str) {
406 self.set_value(Self::month_format_property(), value.to_string())
407 }
408
409 pub fn month_visible(&self) -> bool {
411 self.get_value(Self::month_visible_property())
412 }
413
414 pub fn set_month_visible(&self, value: bool) {
415 self.set_value(Self::month_visible_property(), value)
416 }
417
418 pub fn year_format(&self) -> String {
420 self.get_value(Self::year_format_property())
421 }
422
423 pub fn set_year_format(&self, value: &str) {
424 self.set_value(Self::year_format_property(), value.to_string())
425 }
426
427 pub fn year_visible(&self) -> bool {
429 self.get_value(Self::year_visible_property())
430 }
431
432 pub fn set_year_visible(&self, value: bool) {
433 self.set_value(Self::year_visible_property(), value)
434 }
435
436 fn items(&self) -> Option<TemplateItems> {
437 self.template_items.borrow().clone()
438 }
439
440 fn init_picker(&self) {
442 let Some(items) = self.items() else {
444 return;
445 };
446
447 self.suppress_update_selection.set(true);
448
449 items.month_selector.set_maximum_value(12);
450 items.month_selector.set_minimum_value(1);
451 items.month_selector.set_item_format(&self.month_format());
452
453 items.day_selector.set_item_format(&self.day_format());
454
455 items.year_selector.set_maximum_value(self.max_year().year());
456 items.year_selector.set_minimum_value(self.min_year().year());
457 items.year_selector.set_item_format(&self.year_format());
458
459 self.set_grid(&items);
460
461 let dt = self.date();
463 if self.day_visible() {
464 items.day_selector.set_format_date(dt.date());
465 let max_days = self.calendar.get_days_in_month(dt.year(), dt.month());
466 items.day_selector.set_maximum_value(max_days);
467 items.day_selector.set_minimum_value(1);
468 items.day_selector.set_selected_value(dt.day());
469 }
470
471 if self.month_visible() {
472 items.month_selector.set_selected_value(dt.month());
473 items.month_selector.set_format_date(dt.date());
474 }
475
476 if self.year_visible() {
477 items.year_selector.set_selected_value(dt.year());
478 items.year_selector.set_format_date(dt.date());
479 }
480
481 self.suppress_update_selection.set(false);
482
483 self.set_initial_focus(&items);
484 }
485
486 fn set_grid(&self, items: &TemplateItems) {
487 let date_time_format = CultureInfo::current_culture().date_time_format();
488 let fmt = date_time_format.short_date_pattern();
489 let mut columns = [
490 (&items.month_host, if self.month_visible() { index_of_ignore_case(fmt, 'm') } else { -1 }),
491 (&items.year_host, if self.year_visible() { index_of_ignore_case(fmt, 'y') } else { -1 }),
492 (&items.day_host, if self.day_visible() { index_of_ignore_case(fmt, 'd') } else { -1 }),
493 ];
494
495 columns.sort_by_key(|column| column.1);
496 items.picker_container.column_definitions().clear();
497
498 let mut column_index = 0;
499
500 for (host, index) in columns {
501 host.set_is_visible(index != -1);
502
503 if index != -1 {
504 if column_index > 0 {
505 items.picker_container.column_definitions().add(ColumnDefinition::with_value(0.0, GridUnitType::Auto));
506 }
507
508 items.picker_container.column_definitions().add(ColumnDefinition::with_value(
509 if host.ptr_eq(&items.month_host) { 138.0 } else { 78.0 },
510 GridUnitType::Star,
511 ));
512
513 if host.parent().is_none() {
514 items.picker_container.children().add(host.clone());
515 }
516
517 Grid::set_column(host, column_index * 2);
518 column_index += 1;
519 }
520 }
521
522 fn configure_spacer(spacer: &Option<Ref<Control>>, visible: bool, column: i32) {
523 let Some(spacer) = spacer else {
524 return;
525 };
526 Grid::set_column(spacer, if visible { column } else { 0 });
528 spacer.set_is_visible(visible);
529 }
530
531 configure_spacer(&items.first_spacer, column_index > 1, 1);
532 configure_spacer(&items.second_spacer, column_index > 2, 3);
533 }
534
535 fn set_initial_focus(&self, items: &TemplateItems) {
536 let candidates = [
537 (self.month_visible(), &items.month_host, &items.month_selector),
538 (self.day_visible(), &items.day_host, &items.day_selector),
539 (self.year_visible(), &items.year_host, &items.year_selector),
540 ];
541
542 let mut leftmost: Option<&Ref<DateTimePickerPanel>> = None;
543 let mut min_col = i32::MAX;
544
545 for (visible, host, selector) in candidates {
546 if !visible {
547 continue;
548 }
549
550 let col = Grid::get_column(host);
551 if col < min_col {
552 min_col = col;
553 leftmost = Some(selector);
554 }
555 }
556
557 if let Some(leftmost) = leftmost {
558 leftmost.focus_with(NavigationMethod::Pointer, KeyModifiers::NONE);
559 }
560 }
561
562 fn on_dismiss_button_clicked(&self) {
563 self.on_dismiss();
564 }
565
566 fn on_accept_button_clicked(&self) {
567 self.set_current_value(Self::date_property(), self.sync_date.get());
568 self.on_confirmed();
569 }
570
571 fn on_selector_button_click(&self, type_: DateTimePickerPanelType, direction: SpinDirection) {
572 let items = self.items();
573 let target = match type_ {
574 DateTimePickerPanelType::Month => items.map(|items| items.month_selector),
575 DateTimePickerPanelType::Day => items.map(|items| items.day_selector),
576 DateTimePickerPanelType::Year => items.map(|items| items.year_selector),
577 _ => panic!("The method or operation is not implemented."),
578 };
579
580 let Some(target) = target else {
581 return;
582 };
583
584 match direction {
585 SpinDirection::Increase => target.scroll_down(1),
586 SpinDirection::Decrease => target.scroll_up(1),
587 }
588 }
589
590 fn on_year_changed(&self) {
591 if self.suppress_update_selection.get() {
592 return;
593 }
594 let Some(items) = self.items() else {
595 return;
596 };
597
598 let sync_date = self.sync_date.get();
599 let max_days = self.calendar.get_days_in_month(items.year_selector.selected_value(), sync_date.month());
600 let new_date = DateTimeOffset::new(
601 items.year_selector.selected_value(),
602 sync_date.month(),
603 if sync_date.day() > max_days { max_days } else { sync_date.day() },
604 0,
605 0,
606 0,
607 sync_date.offset(),
608 );
609
610 self.sync_date.set(new_date);
611
612 if !self.day_visible() || new_date.month() != 2 {
614 return;
615 }
616
617 self.suppress_update_selection.set(true);
618
619 items.day_selector.set_format_date(new_date.date());
620
621 if items.day_selector.maximum_value() != max_days {
622 items.day_selector.set_maximum_value(max_days);
623 } else {
624 items.day_selector.refresh_items();
625 }
626
627 self.suppress_update_selection.set(false);
628 }
629
630 fn on_day_changed(&self) {
631 if self.suppress_update_selection.get() {
632 return;
633 }
634 let Some(items) = self.items() else {
635 return;
636 };
637 let sync_date = self.sync_date.get();
638 self.sync_date.set(DateTimeOffset::new(
639 sync_date.year(),
640 sync_date.month(),
641 items.day_selector.selected_value(),
642 0,
643 0,
644 0,
645 sync_date.offset(),
646 ));
647 }
648
649 fn on_month_changed(&self) {
650 if self.suppress_update_selection.get() {
651 return;
652 }
653 let Some(items) = self.items() else {
654 return;
655 };
656
657 let sync_date = self.sync_date.get();
658 let max_days = self.calendar.get_days_in_month(sync_date.year(), items.month_selector.selected_value());
659 let new_date = DateTimeOffset::new(
660 sync_date.year(),
661 items.month_selector.selected_value(),
662 if sync_date.day() > max_days { max_days } else { sync_date.day() },
663 0,
664 0,
665 0,
666 sync_date.offset(),
667 );
668
669 if !self.day_visible() {
670 self.sync_date.set(new_date);
671 return;
672 }
673
674 self.suppress_update_selection.set(true);
675
676 items.day_selector.set_format_date(new_date.date());
677 self.sync_date.set(new_date);
678
679 if items.day_selector.maximum_value() != max_days {
680 items.day_selector.set_maximum_value(max_days);
681 } else {
682 items.day_selector.refresh_items();
683 }
684
685 self.suppress_update_selection.set(false);
686 }
687
688 pub(crate) fn get_offset_for_popup(&self) -> f64 {
689 let Some(items) = self.items() else {
690 return 0.0;
691 };
692
693 let accept_dismiss_button_height = items.accept_button.bounds().height;
694 -(self.max_height() - accept_dismiss_button_height) / 2.0 - (items.month_selector.item_height() / 2.0)
695 }
696}