2530e2fa808e2c4bdc97357b78d4e7ac036dad40
[ardour.git] / gtk2_ardour / ardour_ui_dialogs.cc
1 /*
2     Copyright (C) 2000 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 /* This file contains any ARDOUR_UI methods that require knowledge of
21    the various dialog boxes, and exists so that no compilation dependency
22    exists between the main ARDOUR_UI modules and their respective classes.
23    This is to cut down on the compile times.  It also helps with my sanity.
24 */
25
26 #include "ardour/audioengine.h"
27 #include "ardour/automation_watch.h"
28 #include "ardour/control_protocol_manager.h"
29 #include "ardour/profile.h"
30 #include "ardour/session.h"
31 #include "control_protocol/control_protocol.h"
32
33 #include "actions.h"
34 #include "add_route_dialog.h"
35 #include "add_video_dialog.h"
36 #include "ardour_ui.h"
37 #include "big_clock_window.h"
38 #include "bundle_manager.h"
39 #include "global_port_matrix.h"
40 #include "gui_object.h"
41 #include "gui_thread.h"
42 #include "keyeditor.h"
43 #include "location_ui.h"
44 #include "main_clock.h"
45 #include "meter_patterns.h"
46 #include "midi_tracer.h"
47 #include "mixer_ui.h"
48 #include "public_editor.h"
49 #include "rc_option_editor.h"
50 #include "route_params_ui.h"
51 #include "shuttle_control.h"
52 #include "session_option_editor.h"
53 #include "speaker_dialog.h"
54 #include "splash.h"
55 #include "sfdb_ui.h"
56 #include "theme_manager.h"
57 #include "time_info_box.h"
58 #include "timers.h"
59
60 #include <gtkmm2ext/keyboard.h>
61
62 #include "i18n.h"
63
64 using namespace ARDOUR;
65 using namespace PBD;
66 using namespace Glib;
67 using namespace Gtk;
68 using namespace Gtkmm2ext;
69
70 void
71 ARDOUR_UI::set_session (Session *s)
72 {
73         SessionHandlePtr::set_session (s);
74
75         if (!_session) {
76                 WM::Manager::instance().set_session (s);
77                 /* Session option editor cannot exist across change-of-session */
78                 session_option_editor.drop_window ();
79                 /* Ditto for AddVideoDialog */
80                 add_video_dialog.drop_window ();
81                 return;
82         }
83
84         const XMLNode* node = _session->extra_xml (X_("UI"));
85
86         if (node) {
87                 const XMLNodeList& children = node->children();
88                 for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
89                         if ((*i)->name() == GUIObjectState::xml_node_name) {
90                                 gui_object_state->load (**i);
91                                 break;
92                         }
93                 }
94         }
95
96         WM::Manager::instance().set_session (s);
97
98         AutomationWatch::instance().set_session (s);
99
100         if (shuttle_box) {
101                 shuttle_box->set_session (s);
102         }
103
104         primary_clock->set_session (s);
105         secondary_clock->set_session (s);
106         big_clock->set_session (s);
107         time_info_box->set_session (s);
108         video_timeline->set_session (s);
109
110         /* sensitize menu bar options that are now valid */
111
112         ActionManager::set_sensitive (ActionManager::session_sensitive_actions, true);
113         ActionManager::set_sensitive (ActionManager::write_sensitive_actions, _session->writable());
114
115         if (_session->locations()->num_range_markers()) {
116                 ActionManager::set_sensitive (ActionManager::range_sensitive_actions, true);
117         } else {
118                 ActionManager::set_sensitive (ActionManager::range_sensitive_actions, false);
119         }
120
121         if (!_session->monitor_out()) {
122                 Glib::RefPtr<Action> act = ActionManager::get_action (X_("options"), X_("SoloViaBus"));
123                 if (act) {
124                         act->set_sensitive (false);
125                 }
126         }
127
128         /* allow wastebasket flush again */
129
130         Glib::RefPtr<Action> act = ActionManager::get_action (X_("Main"), X_("FlushWastebasket"));
131         if (act) {
132                 act->set_sensitive (true);
133         }
134
135         /* there are never any selections on startup */
136
137         ActionManager::set_sensitive (ActionManager::time_selection_sensitive_actions, false);
138         ActionManager::set_sensitive (ActionManager::track_selection_sensitive_actions, false);
139         ActionManager::set_sensitive (ActionManager::line_selection_sensitive_actions, false);
140         ActionManager::set_sensitive (ActionManager::point_selection_sensitive_actions, false);
141         ActionManager::set_sensitive (ActionManager::playlist_selection_sensitive_actions, false);
142
143         rec_button.set_sensitive (true);
144
145         solo_alert_button.set_active (_session->soloing());
146
147         setup_session_options ();
148
149         blink_connection = Timers::blink_connect (sigc::mem_fun(*this, &ARDOUR_UI::blink_handler));
150
151         _session->SaveSessionRequested.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::save_session_at_its_request, this, _1), gui_context());
152         _session->RecordStateChanged.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::record_state_changed, this), gui_context());
153         _session->StepEditStatusChange.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::step_edit_status_change, this, _1), gui_context());
154         _session->TransportStateChange.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::map_transport_state, this), gui_context());
155         _session->DirtyChanged.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::update_autosave, this), gui_context());
156
157         _session->Xrun.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::xrun_handler, this, _1), gui_context());
158         _session->SoloActive.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::soloing_changed, this, _1), gui_context());
159         _session->AuditionActive.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::auditioning_changed, this, _1), gui_context());
160         _session->locations()->added.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::handle_locations_change, this, _1), gui_context());
161         _session->locations()->removed.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::handle_locations_change, this, _1), gui_context());
162         _session->config.ParameterChanged.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::session_parameter_changed, this, _1), gui_context ());
163
164         /* Clocks are on by default after we are connected to a session, so show that here.
165         */
166
167         connect_dependents_to_session (s);
168
169         /* listen to clock mode changes. don't do this earlier because otherwise as the clocks
170            restore their modes or are explicitly set, we will cause the "new" mode to be saved
171            back to the session XML ("Extra") state.
172          */
173
174         AudioClock::ModeChanged.connect (sigc::mem_fun (*this, &ARDOUR_UI::store_clock_modes));
175
176         Glib::signal_idle().connect (sigc::mem_fun (*this, &ARDOUR_UI::first_idle));
177
178         start_clocking ();
179
180         map_transport_state ();
181
182         second_connection = Timers::second_connect (sigc::mem_fun(*this, &ARDOUR_UI::every_second));
183         point_one_second_connection = Timers::rapid_connect (sigc::mem_fun(*this, &ARDOUR_UI::every_point_one_seconds));
184         point_zero_something_second_connection = Timers::super_rapid_connect (sigc::mem_fun(*this, &ARDOUR_UI::every_point_zero_something_seconds));
185         set_fps_timeout_connection();
186
187         update_format ();
188
189         if (meter_box.get_parent()) {
190                 transport_tearoff_hbox.remove (meter_box);
191                 transport_tearoff_hbox.remove (editor_meter_peak_display);
192         }
193
194         if (editor_meter) {
195                 meter_box.remove(*editor_meter);
196                 delete editor_meter;
197                 editor_meter = 0;
198                 editor_meter_peak_display.hide();
199         }
200
201         if (meter_box.get_parent()) {
202                 transport_tearoff_hbox.remove (meter_box);
203                 transport_tearoff_hbox.remove (editor_meter_peak_display);
204         }
205
206         if (_session && 
207             _session->master_out() && 
208             _session->master_out()->n_outputs().n(DataType::AUDIO) > 0) {
209
210                 if (!ARDOUR::Profile->get_trx()) {
211                         editor_meter = new LevelMeterHBox(_session);
212                         editor_meter->set_meter (_session->master_out()->shared_peak_meter().get());
213                         editor_meter->clear_meters();
214                         editor_meter->set_type (_session->master_out()->meter_type());
215                         editor_meter->setup_meters (30, 12, 6);
216                         editor_meter->show();
217                         meter_box.pack_start(*editor_meter);
218                 }
219
220                 ArdourMeter::ResetAllPeakDisplays.connect (sigc::mem_fun(*this, &ARDOUR_UI::reset_peak_display));
221                 ArdourMeter::ResetRoutePeakDisplays.connect (sigc::mem_fun(*this, &ARDOUR_UI::reset_route_peak_display));
222                 ArdourMeter::ResetGroupPeakDisplays.connect (sigc::mem_fun(*this, &ARDOUR_UI::reset_group_peak_display));
223
224                 editor_meter_peak_display.set_name ("meterbridge peakindicator");
225                 editor_meter_peak_display.unset_flags (Gtk::CAN_FOCUS);
226                 editor_meter_peak_display.set_size_request (std::max(9.f, rintf(8.f * ui_scale)), -1);
227                 editor_meter_peak_display.set_corner_radius (3.0);
228
229                 editor_meter_max_peak = -INFINITY;
230                 editor_meter_peak_display.signal_button_release_event().connect (sigc::mem_fun(*this, &ARDOUR_UI::editor_meter_peak_button_release), false);
231
232                 if (ARDOUR_UI::config()->get_show_editor_meter() && !ARDOUR::Profile->get_trx()) {
233                         transport_tearoff_hbox.pack_start (meter_box, false, false);
234                         transport_tearoff_hbox.pack_start (editor_meter_peak_display, false, false);
235                         meter_box.show();
236                         editor_meter_peak_display.show();
237                 }
238         }
239 }
240
241 int
242 ARDOUR_UI::unload_session (bool hide_stuff)
243 {
244         if (_session) {
245                 ARDOUR_UI::instance()->video_timeline->sync_session_state();
246         }
247
248         if (_session && _session->dirty()) {
249                 std::vector<std::string> actions;
250                 actions.push_back (_("Don't close"));
251                 actions.push_back (_("Just close"));
252                 actions.push_back (_("Save and close"));
253                 switch (ask_about_saving_session (actions)) {
254                 case -1:
255                         // cancel
256                         return 1;
257
258                 case 1:
259                         _session->save_state ("");
260                         break;
261                 }
262         }
263
264         {
265                 // tear down session specific CPI (owned by rc_config_editor which can remain)
266                 ControlProtocolManager& m = ControlProtocolManager::instance ();
267                 for (std::list<ControlProtocolInfo*>::iterator i = m.control_protocol_info.begin(); i != m.control_protocol_info.end(); ++i) {
268                         if (*i && (*i)->protocol && (*i)->protocol->has_editor ()) {
269                                 (*i)->protocol->tear_down_gui ();
270                         }
271                 }
272         }
273
274         if (hide_stuff) {
275                 editor->hide ();
276                 mixer->hide ();
277                 meterbridge->hide ();
278                 audio_port_matrix->hide();
279                 midi_port_matrix->hide();
280                 route_params->hide();
281         }
282
283         second_connection.disconnect ();
284         point_one_second_connection.disconnect ();
285         point_zero_something_second_connection.disconnect();
286         fps_connection.disconnect();
287
288         if (editor_meter) {
289                 meter_box.remove(*editor_meter);
290                 delete editor_meter;
291                 editor_meter = 0;
292                 editor_meter_peak_display.hide();
293         }
294
295         ActionManager::set_sensitive (ActionManager::session_sensitive_actions, false);
296
297         rec_button.set_sensitive (false);
298
299         WM::Manager::instance().set_session ((ARDOUR::Session*) 0);
300
301         if (ARDOUR_UI::instance()->video_timeline) {
302                 ARDOUR_UI::instance()->video_timeline->close_session();
303         }
304
305         stop_clocking ();
306
307         /* drop everything attached to the blink signal */
308
309         blink_connection.disconnect ();
310
311         delete _session;
312         _session = 0;
313
314         session_loaded = false;
315
316         update_buffer_load ();
317
318         return 0;
319 }
320
321 static bool
322 _hide_splash (gpointer arg)
323 {
324         ((ARDOUR_UI*)arg)->hide_splash();
325         return false;
326 }
327
328 void
329 ARDOUR_UI::goto_editor_window ()
330 {
331         if (splash && splash->is_visible()) {
332                 // in 2 seconds, hide the splash screen
333                 Glib::signal_timeout().connect (sigc::bind (sigc::ptr_fun (_hide_splash), this), 2000);
334         }
335
336         editor->show_window ();
337         editor->present ();
338         /* mixer should now be on top */
339         if (ARDOUR_UI::config()->get_transients_follow_front()) {
340                 WM::Manager::instance().set_transient_for (editor);
341         }
342         _mixer_on_top = false;
343 }
344
345 void
346 ARDOUR_UI::goto_mixer_window ()
347 {
348         Glib::RefPtr<Gdk::Window> win;
349         Glib::RefPtr<Gdk::Screen> screen;
350         
351         if (editor) {
352                 win = editor->get_window ();
353         }
354
355         if (win) {
356                 screen = win->get_screen();
357         } else {
358                 screen = Gdk::Screen::get_default();
359         }
360         
361         if (g_getenv ("ARDOUR_LOVES_STUPID_TINY_SCREENS") == 0 && screen && screen->get_height() < 700) {
362                 Gtk::MessageDialog msg (_("This screen is not tall enough to display the mixer window"));
363                 msg.run ();
364                 return;
365         }
366
367         mixer->show_window ();
368         mixer->present ();
369         /* mixer should now be on top */
370         if (ARDOUR_UI::config()->get_transients_follow_front()) {
371                 WM::Manager::instance().set_transient_for (mixer);
372         }
373         _mixer_on_top = true;
374 }
375
376 void
377 ARDOUR_UI::toggle_mixer_window ()
378 {
379         if (!editor || !mixer) {
380                 /* can this really happen?
381                  * keyboard shortcut during session close, maybe?
382                  */
383 #ifndef NDEBUG
384                  /* one way to find out: */
385                 printf("ARDOUR_UI::toggle_mixer_window: Editor: %p Mixer: %p\n", editor, mixer);
386                 PBD::stacktrace (std::cerr, 20);
387                 assert (0);
388 #endif
389                 return;
390         }
391
392         bool show = false;
393         bool obscuring = false;
394
395         if (mixer->not_visible ()) {
396                 show = true;
397         }
398         else if (editor->get_screen() == mixer->get_screen()) {
399                 gint ex, ey, ew, eh;
400                 gint mx, my, mw, mh;
401
402                 editor->get_position (ex, ey);
403                 editor->get_size (ew, eh);
404                 mixer->get_position (mx, my);
405                 mixer->get_size (mw, mh);
406
407                 GdkRectangle e;
408                 GdkRectangle m;
409                 GdkRectangle r;
410
411                 e.x = ex;
412                 e.y = ey;
413                 e.width = ew;
414                 e.height = eh;
415
416                 m.x = mx;
417                 m.y = my;
418                 m.width = mw;
419                 m.height = mh;
420
421                 if (gdk_rectangle_intersect (&e, &m, &r)) {
422                         obscuring = true;
423                 }
424         }
425
426         if (obscuring && editor->property_has_toplevel_focus()) {
427                 show = true;
428         }
429
430         if (show) {
431                 goto_mixer_window ();
432         } else {
433                 mixer->hide ();
434         }
435 }
436
437 void
438 ARDOUR_UI::toggle_meterbridge ()
439 {
440         if (!editor || !meterbridge) {
441                 /* can this really happen?
442                  * keyboard shortcut during session close, maybe?
443                  */
444 #ifndef NDEBUG
445                  /* one way to find out: */
446                 printf("ARDOUR_UI::toggle_meterbridge: Editor: %p MB: %p\n", editor, meterbridge);
447                 PBD::stacktrace (std::cerr, 20);
448                 assert (0);
449 #endif
450                 return;
451         }
452
453         bool show = false;
454         bool obscuring = false;
455
456         if (meterbridge->not_visible ()) {
457                 show = true;
458         }
459         else if (editor->get_screen() == meterbridge->get_screen()) {
460                 gint ex, ey, ew, eh;
461                 gint mx, my, mw, mh;
462
463                 editor->get_position (ex, ey);
464                 editor->get_size (ew, eh);
465                 meterbridge->get_position (mx, my);
466                 meterbridge->get_size (mw, mh);
467
468                 GdkRectangle e;
469                 GdkRectangle m;
470                 GdkRectangle r;
471
472                 e.x = ex;
473                 e.y = ey;
474                 e.width = ew;
475                 e.height = eh;
476
477                 m.x = mx;
478                 m.y = my;
479                 m.width = mw;
480                 m.height = mh;
481
482                 if (gdk_rectangle_intersect (&e, &m, &r)) {
483                         obscuring = true;
484                 }
485         }
486
487         if (obscuring && editor->property_has_toplevel_focus()) {
488                 show = true;
489         }
490
491         if (show) {
492                 meterbridge->show_window ();
493                 meterbridge->present ();
494                 meterbridge->raise ();
495         } else {
496                 meterbridge->hide_window (NULL);
497         }
498 }
499
500 void
501 ARDOUR_UI::toggle_editor_mixer ()
502 {
503         bool obscuring = false;
504         /* currently, if windows are on different
505            screens then we do nothing; but in the
506            future we may want to bring the window 
507            to the front or something, so I'm leaving this 
508            variable for future use
509         */
510         bool same_screen = true; 
511         
512         if (editor && mixer) {
513
514                 /* remeber: Screen != Monitor (Screen is a separately rendered
515                  * continuous geometry that make include 1 or more monitors.
516                  */
517                 
518                 if (editor->get_screen() != mixer->get_screen() && (mixer->get_screen() != 0) && (editor->get_screen() != 0)) {
519                         // different screens, so don't do anything
520                         same_screen = false;
521                 } else {
522                         // they are on the same screen, see if they are obscuring each other
523
524                         gint ex, ey, ew, eh;
525                         gint mx, my, mw, mh;
526
527                         editor->get_position (ex, ey);
528                         editor->get_size (ew, eh);
529
530                         mixer->get_position (mx, my);
531                         mixer->get_size (mw, mh);
532
533                         GdkRectangle e;
534                         GdkRectangle m;
535                         GdkRectangle r;
536
537                         e.x = ex;
538                         e.y = ey;
539                         e.width = ew;
540                         e.height = eh;
541
542                         m.x = mx;
543                         m.y = my;
544                         m.width = mw;
545                         m.height = mh;
546
547                         if (gdk_rectangle_intersect (&e, &m, &r)) {
548                                 obscuring = true;
549                         }
550                 }
551         }
552
553         if (mixer && !mixer->not_visible() && mixer->property_has_toplevel_focus()) {
554                 if (obscuring && same_screen) {
555                         goto_editor_window();
556                 }
557         } else if (editor && !editor->not_visible() && editor->property_has_toplevel_focus()) {
558                 if (obscuring && same_screen) {
559                         goto_mixer_window();
560                 }
561         } else if (mixer && mixer->not_visible()) {
562                 if (obscuring && same_screen) {
563                         goto_mixer_window ();
564                 }
565         } else if (editor && editor->not_visible()) {
566                 if (obscuring && same_screen) {
567                         goto_editor_window ();
568                 }
569         } else if (obscuring && same_screen) {
570                 //it's unclear what to do here, so just do the opposite of what we did last time  (old behavior)
571                 if (_mixer_on_top) {
572                         goto_editor_window ();
573                 } else {
574                         goto_mixer_window ();
575                 }
576         }
577 }
578
579 void
580 ARDOUR_UI::new_midi_tracer_window ()
581 {
582         RefPtr<Action> act = ActionManager::get_action (X_("Common"), X_("NewMIDITracer"));
583         if (!act) {
584                 return;
585         }
586
587         std::list<MidiTracer*>::iterator i = _midi_tracer_windows.begin ();
588         while (i != _midi_tracer_windows.end() && (*i)->get_visible() == true) {
589                 ++i;
590         }
591
592         if (i == _midi_tracer_windows.end()) {
593                 /* all our MIDITracer windows are visible; make a new one */
594                 MidiTracer* t = new MidiTracer ();
595                 t->show_all ();
596                 _midi_tracer_windows.push_back (t);
597         } else {
598                 /* re-use the hidden one */
599                 (*i)->show_all ();
600         }
601 }
602
603 BundleManager*
604 ARDOUR_UI::create_bundle_manager ()
605 {
606         return new BundleManager (_session);
607 }
608
609 AddVideoDialog*
610 ARDOUR_UI::create_add_video_dialog ()
611 {
612         return new AddVideoDialog (_session);
613 }
614
615 SessionOptionEditor*
616 ARDOUR_UI::create_session_option_editor ()
617 {
618         return new SessionOptionEditor (_session);
619 }
620
621 BigClockWindow*
622 ARDOUR_UI::create_big_clock_window ()
623 {
624         return new BigClockWindow (*big_clock);
625 }
626
627 void
628 ARDOUR_UI::handle_locations_change (Location *)
629 {
630         if (_session) {
631                 if (_session->locations()->num_range_markers()) {
632                         ActionManager::set_sensitive (ActionManager::range_sensitive_actions, true);
633                 } else {
634                         ActionManager::set_sensitive (ActionManager::range_sensitive_actions, false);
635                 }
636         }
637 }
638
639 bool
640 ARDOUR_UI::main_window_state_event_handler (GdkEventWindowState* ev, bool window_was_editor)
641 {
642         if (window_was_editor) {
643
644                 if ((ev->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) &&
645                     (ev->new_window_state & GDK_WINDOW_STATE_FULLSCREEN)) {
646                         if (big_clock_window) {
647                                 big_clock_window->set_transient_for (*editor);
648                         }
649                 }
650
651         } else {
652
653                 if ((ev->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) &&
654                     (ev->new_window_state & GDK_WINDOW_STATE_FULLSCREEN)) {
655                         if (big_clock_window) {
656                                 big_clock_window->set_transient_for (*mixer);
657                         }
658                 }
659         }
660
661         return false;
662 }
663
664 bool
665 ARDOUR_UI::editor_meter_peak_button_release (GdkEventButton* ev)
666 {
667         if (ev->button == 1 && Gtkmm2ext::Keyboard::modifier_state_equals (ev->state, Gtkmm2ext::Keyboard::PrimaryModifier|Gtkmm2ext::Keyboard::TertiaryModifier)) {
668                 ArdourMeter::ResetAllPeakDisplays ();
669         } else if (ev->button == 1 && Gtkmm2ext::Keyboard::modifier_state_equals (ev->state, Gtkmm2ext::Keyboard::PrimaryModifier)) {
670                 if (_session->master_out()) {
671                         ArdourMeter::ResetGroupPeakDisplays (_session->master_out()->route_group());
672                 }
673         } else if (_session->master_out()) {
674                 ArdourMeter::ResetRoutePeakDisplays (_session->master_out().get());
675         }
676         return false;
677 }
678
679 void
680 ARDOUR_UI::toggle_mixer_space()
681 {
682         Glib::RefPtr<Action> act = ActionManager::get_action ("Common", "ToggleMaximalMixer");
683
684         if (act) {
685                 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
686                 if (tact->get_active()) {
687                         mixer->maximise_mixer_space ();
688                 } else {
689                         mixer->restore_mixer_space ();
690                 }
691         }
692 }