Fix length of player output so it can be either the film's length or playlist's lengt...
[dcpomatic.git] / src / wx / timing_panel.cc
1 /*
2     Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "timing_panel.h"
22 #include "wx_util.h"
23 #include "film_viewer.h"
24 #include "timecode.h"
25 #include "content_panel.h"
26 #include "move_to_dialog.h"
27 #include "static_text.h"
28 #include "dcpomatic_button.h"
29 #include "lib/content.h"
30 #include "lib/image_content.h"
31 #include "lib/text_content.h"
32 #include "lib/dcp_subtitle_content.h"
33 #include "lib/audio_content.h"
34 #include "lib/string_text_file_content.h"
35 #include "lib/video_content.h"
36 #include "lib/dcp_content.h"
37 #include "lib/ffmpeg_content.h"
38 #include <dcp/locale_convert.h>
39 #include <boost/foreach.hpp>
40 #include <set>
41 #include <iostream>
42
43 using std::cout;
44 using std::string;
45 using std::set;
46 using boost::shared_ptr;
47 using boost::weak_ptr;
48 using boost::dynamic_pointer_cast;
49 using boost::optional;
50 using dcp::locale_convert;
51 using namespace dcpomatic;
52
53 TimingPanel::TimingPanel (ContentPanel* p, weak_ptr<FilmViewer> viewer)
54         /* horrid hack for apparent lack of context support with wxWidgets i18n code */
55         /// TRANSLATORS: translate the word "Timing" here; do not include the "Timing|" prefix
56         : ContentSubPanel (p, S_("Timing|Timing"))
57         , _viewer (viewer)
58         , _film_content_changed_suspender (boost::bind(&TimingPanel::film_content_changed, this, _1))
59 {
60         wxSize size = TimecodeBase::size (this);
61
62         for (int i = 0; i < 3; ++i) {
63                 _colon[i] = create_label (this, wxT(":"), false);
64         }
65
66         //// TRANSLATORS: this is an abbreviation for "hours"
67         _h_label = new StaticText (this, _("h"), wxDefaultPosition, size, wxALIGN_CENTRE_HORIZONTAL);
68 #ifdef DCPOMATIC_LINUX
69         /* Hack to work around failure to centre text on GTK */
70         gtk_label_set_line_wrap (GTK_LABEL(_h_label->GetHandle()), FALSE);
71 #endif
72         //// TRANSLATORS: this is an abbreviation for "minutes"
73         _m_label = new StaticText (this, _("m"), wxDefaultPosition, size, wxALIGN_CENTRE_HORIZONTAL);
74 #ifdef DCPOMATIC_LINUX
75         gtk_label_set_line_wrap (GTK_LABEL (_m_label->GetHandle()), FALSE);
76 #endif
77         //// TRANSLATORS: this is an abbreviation for "seconds"
78         _s_label = new StaticText (this, _("s"), wxDefaultPosition, size, wxALIGN_CENTRE_HORIZONTAL);
79 #ifdef DCPOMATIC_LINUX
80         gtk_label_set_line_wrap (GTK_LABEL(_s_label->GetHandle()), FALSE);
81 #endif
82         //// TRANSLATORS: this is an abbreviation for "frames"
83         _f_label = new StaticText (this, _("f"), wxDefaultPosition, size, wxALIGN_CENTRE_HORIZONTAL);
84 #ifdef DCPOMATIC_LINUX
85         gtk_label_set_line_wrap (GTK_LABEL(_f_label->GetHandle()), FALSE);
86 #endif
87
88         _position_label = create_label (this, _("Position"), true);
89         _position = new Timecode<DCPTime> (this);
90         _move_to_start_of_reel = new Button (this, _("Move to start of reel"));
91         _full_length_label = create_label (this, _("Full length"), true);
92         _full_length = new Timecode<DCPTime> (this);
93         _trim_start_label = create_label (this, _("Trim from start"), true);
94         _trim_start = new Timecode<ContentTime> (this);
95         _trim_start_to_playhead = new Button (this, _("Trim up to current position"));
96         _trim_end_label = create_label (this, _("Trim from end"), true);
97         _trim_end = new Timecode<ContentTime> (this);
98         _trim_end_to_playhead = new Button (this, _("Trim after current position"));
99         _play_length_label = create_label (this, _("Play length"), true);
100         _play_length = new Timecode<DCPTime> (this);
101
102         _video_frame_rate_label = create_label (this, _("Video frame rate"), true);
103         _video_frame_rate = new wxTextCtrl (this, wxID_ANY);
104         _set_video_frame_rate = new Button (this, _("Set"));
105         _set_video_frame_rate->Enable (false);
106
107         /* We can't use Wrap() here as it doesn't work with markup:
108          * http://trac.wxwidgets.org/ticket/13389
109          */
110
111         wxString in = _("<i>Only change this if the content's frame rate has been read incorrectly.</i>");
112         wxString out;
113         int const width = 20;
114         int current = 0;
115         for (size_t i = 0; i < in.Length(); ++i) {
116                 if (in[i] == ' ' && current >= width) {
117                         out += '\n';
118                         current = 0;
119                 } else {
120                         out += in[i];
121                         ++current;
122                 }
123         }
124
125         _tip = new StaticText (this, wxT (""));
126         _tip->SetLabelMarkup (out);
127 #ifdef DCPOMATIC_OSX
128         /* Hack to stop hidden text on some versions of OS X */
129         _tip->SetMinSize (wxSize (-1, 256));
130 #endif
131
132         _position->Changed.connect    (boost::bind (&TimingPanel::position_changed, this));
133         _move_to_start_of_reel->Bind  (wxEVT_BUTTON, boost::bind (&TimingPanel::move_to_start_of_reel_clicked, this));
134         _full_length->Changed.connect (boost::bind (&TimingPanel::full_length_changed, this));
135         _trim_start->Changed.connect  (boost::bind (&TimingPanel::trim_start_changed, this));
136         _trim_start_to_playhead->Bind (wxEVT_BUTTON, boost::bind (&TimingPanel::trim_start_to_playhead_clicked, this));
137         _trim_end->Changed.connect    (boost::bind (&TimingPanel::trim_end_changed, this));
138         _trim_end_to_playhead->Bind   (wxEVT_BUTTON, boost::bind (&TimingPanel::trim_end_to_playhead_clicked, this));
139         _play_length->Changed.connect (boost::bind (&TimingPanel::play_length_changed, this));
140         _video_frame_rate->Bind       (wxEVT_TEXT, boost::bind (&TimingPanel::video_frame_rate_changed, this));
141         _set_video_frame_rate->Bind   (wxEVT_BUTTON, boost::bind (&TimingPanel::set_video_frame_rate, this));
142
143         shared_ptr<FilmViewer> fv = _viewer.lock ();
144         DCPOMATIC_ASSERT (fv);
145         fv->ImageChanged.connect (boost::bind (&TimingPanel::setup_sensitivity, this));
146
147         setup_sensitivity ();
148         add_to_grid ();
149 }
150
151 void
152 TimingPanel::add_to_grid ()
153 {
154         bool const full = Config::instance()->interface_complexity() == Config::INTERFACE_FULL;
155
156         int r = 0;
157
158         wxSizer* labels = new wxBoxSizer (wxHORIZONTAL);
159         labels->Add (_h_label, 1, wxEXPAND);
160         add_label_to_sizer (labels, _colon[0], false);
161         labels->Add (_m_label, 1, wxEXPAND);
162         add_label_to_sizer (labels, _colon[1], false);
163         labels->Add (_s_label, 1, wxEXPAND);
164         add_label_to_sizer (labels, _colon[2], false);
165         labels->Add (_f_label, 1, wxEXPAND);
166         _grid->Add (labels, wxGBPosition(r, 1));
167         ++r;
168
169         add_label_to_sizer (_grid, _position_label, true, wxGBPosition(r, 0));
170         _grid->Add (_position, wxGBPosition(r, 1));
171         ++r;
172
173         _move_to_start_of_reel->Show (full);
174         _full_length_label->Show (full);
175         _full_length->Show (full);
176         _play_length_label->Show (full);
177         _play_length->Show (full);
178         _video_frame_rate_label->Show (full);
179         _video_frame_rate->Show (full);
180         _set_video_frame_rate->Show (full);
181         _tip->Show (full);
182
183         if (full) {
184                 _grid->Add (_move_to_start_of_reel, wxGBPosition(r, 1));
185                 ++r;
186
187                 add_label_to_sizer (_grid, _full_length_label, true, wxGBPosition(r, 0));
188                 _grid->Add (_full_length, wxGBPosition(r, 1));
189                 ++r;
190         }
191
192         add_label_to_sizer (_grid, _trim_start_label, true, wxGBPosition(r, 0));
193         _grid->Add (_trim_start, wxGBPosition(r, 1));
194         ++r;
195
196         _grid->Add (_trim_start_to_playhead, wxGBPosition(r, 1));
197         ++r;
198
199         add_label_to_sizer (_grid, _trim_end_label, true, wxGBPosition(r, 0));
200         _grid->Add (_trim_end, wxGBPosition(r, 1));
201         ++r;
202
203         _grid->Add (_trim_end_to_playhead, wxGBPosition(r, 1));
204         ++r;
205
206         if (full) {
207                 add_label_to_sizer (_grid, _play_length_label, true, wxGBPosition(r, 0));
208                 _grid->Add (_play_length, wxGBPosition(r, 1));
209                 ++r;
210
211                 {
212                         add_label_to_sizer (_grid, _video_frame_rate_label, true, wxGBPosition(r, 0));
213                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
214                         s->Add (_video_frame_rate, 1, wxEXPAND);
215                         s->Add (_set_video_frame_rate, 0, wxLEFT | wxRIGHT, 8);
216                         _grid->Add (s, wxGBPosition(r, 1), wxGBSpan(1, 2));
217                 }
218                 ++r;
219
220                 _grid->Add (_tip, wxGBPosition(r, 1), wxGBSpan(1, 2));
221         }
222
223         /* Completely speculative fix for #891 */
224         _grid->Layout ();
225 }
226
227 void
228 TimingPanel::update_full_length ()
229 {
230         set<DCPTime> check;
231         BOOST_FOREACH (shared_ptr<const Content> i, _parent->selected ()) {
232                 check.insert (i->full_length(_parent->film()));
233         }
234
235         if (check.size() == 1) {
236                 _full_length->set (_parent->selected().front()->full_length(_parent->film()), _parent->film()->video_frame_rate());
237         } else {
238                 _full_length->clear ();
239         }
240 }
241
242 void
243 TimingPanel::update_play_length ()
244 {
245         set<DCPTime> check;
246         BOOST_FOREACH (shared_ptr<const Content> i, _parent->selected ()) {
247                 check.insert (i->length_after_trim(_parent->film()));
248         }
249
250         if (check.size() == 1) {
251                 _play_length->set (_parent->selected().front()->length_after_trim(_parent->film()), _parent->film()->video_frame_rate());
252         } else {
253                 _play_length->clear ();
254         }
255 }
256
257 void
258 TimingPanel::film_content_changed (int property)
259 {
260         if (_film_content_changed_suspender.check(property)) {
261                 return;
262         }
263
264         int const film_video_frame_rate = _parent->film()->video_frame_rate ();
265
266         /* Here we check to see if we have exactly one different value of various
267            properties, and fill the controls with that value if so.
268         */
269
270         if (property == ContentProperty::POSITION) {
271
272                 set<DCPTime> check;
273                 BOOST_FOREACH (shared_ptr<const Content> i, _parent->selected ()) {
274                         check.insert (i->position ());
275                 }
276
277                 if (check.size() == 1) {
278                         _position->set (_parent->selected().front()->position(), film_video_frame_rate);
279                 } else {
280                         _position->clear ();
281                 }
282
283         } else if (
284                 property == ContentProperty::LENGTH ||
285                 property == ContentProperty::VIDEO_FRAME_RATE ||
286                 property == VideoContentProperty::FRAME_TYPE
287                 ) {
288
289                 update_full_length ();
290
291         } else if (property == ContentProperty::TRIM_START) {
292
293                 set<ContentTime> check;
294                 BOOST_FOREACH (shared_ptr<const Content> i, _parent->selected ()) {
295                         check.insert (i->trim_start ());
296                 }
297
298                 if (check.size() == 1) {
299                         _trim_start->set (_parent->selected().front()->trim_start (), film_video_frame_rate);
300                 } else {
301                         _trim_start->clear ();
302                 }
303
304         } else if (property == ContentProperty::TRIM_END) {
305
306                 set<ContentTime> check;
307                 BOOST_FOREACH (shared_ptr<const Content> i, _parent->selected ()) {
308                         check.insert (i->trim_end ());
309                 }
310
311                 if (check.size() == 1) {
312                         _trim_end->set (_parent->selected().front()->trim_end (), film_video_frame_rate);
313                 } else {
314                         _trim_end->clear ();
315                 }
316         }
317
318         if (
319                 property == ContentProperty::LENGTH ||
320                 property == ContentProperty::TRIM_START ||
321                 property == ContentProperty::TRIM_END ||
322                 property == ContentProperty::VIDEO_FRAME_RATE ||
323                 property == VideoContentProperty::FRAME_TYPE
324                 ) {
325
326                 update_play_length ();
327         }
328
329         if (property == ContentProperty::VIDEO_FRAME_RATE) {
330                 set<double> check_vc;
331                 shared_ptr<const Content> content;
332                 int count_ac = 0;
333                 int count_sc = 0;
334                 BOOST_FOREACH (shared_ptr<const Content> i, _parent->selected ()) {
335                         if (i->video && i->video_frame_rate()) {
336                                 check_vc.insert (i->video_frame_rate().get());
337                                 content = i;
338                         }
339                         if (i->audio && i->video_frame_rate()) {
340                                 ++count_ac;
341                                 content = i;
342                         }
343                         if (!i->text.empty() && i->video_frame_rate()) {
344                                 ++count_sc;
345                                 content = i;
346                         }
347
348                 }
349
350                 bool const single_frame_image_content = content && dynamic_pointer_cast<const ImageContent> (content) && content->number_of_paths() == 1;
351
352                 if ((check_vc.size() == 1 || count_ac == 1 || count_sc == 1) && !single_frame_image_content) {
353                         checked_set (_video_frame_rate, locale_convert<string> (content->video_frame_rate().get(), 5));
354                         _video_frame_rate->Enable (true);
355                 } else {
356                         checked_set (_video_frame_rate, wxT (""));
357                         _video_frame_rate->Enable (false);
358                 }
359         }
360
361         bool have_still = false;
362         BOOST_FOREACH (shared_ptr<const Content> i, _parent->selected ()) {
363                 shared_ptr<const ImageContent> ic = dynamic_pointer_cast<const ImageContent> (i);
364                 if (ic && ic->still ()) {
365                         have_still = true;
366                 }
367         }
368
369         _full_length->set_editable (have_still);
370         _play_length->set_editable (!have_still);
371         _set_video_frame_rate->Enable (false);
372         setup_sensitivity ();
373 }
374
375 void
376 TimingPanel::position_changed ()
377 {
378         DCPTime const pos = _position->get (_parent->film()->video_frame_rate ());
379         BOOST_FOREACH (shared_ptr<Content> i, _parent->selected ()) {
380                 i->set_position (_parent->film(), pos);
381         }
382 }
383
384 void
385 TimingPanel::full_length_changed ()
386 {
387         int const vfr = _parent->film()->video_frame_rate ();
388         Frame const len = _full_length->get (vfr).frames_round (vfr);
389         BOOST_FOREACH (shared_ptr<Content> i, _parent->selected ()) {
390                 shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (i);
391                 if (ic && ic->still ()) {
392                         ic->video->set_length (len);
393                 }
394         }
395 }
396
397 void
398 TimingPanel::trim_start_changed ()
399 {
400         shared_ptr<FilmViewer> fv = _viewer.lock ();
401         if (!fv) {
402                 return;
403         }
404
405         DCPTime const ph = fv->position ();
406
407         fv->set_coalesce_player_changes (true);
408
409         shared_ptr<Content> ref;
410         optional<FrameRateChange> ref_frc;
411         optional<DCPTime> ref_ph;
412
413         Suspender::Block bl = _film_content_changed_suspender.block ();
414         BOOST_FOREACH (shared_ptr<Content> i, _parent->selected ()) {
415                 if (i->position() <= ph && ph < i->end(_parent->film())) {
416                         /* The playhead is in i.  Use it as a reference to work out
417                            where to put the playhead post-trim; we're trying to keep the playhead
418                            at the same frame of content that we're looking at pre-trim.
419                         */
420                         ref = i;
421                         ref_frc = _parent->film()->active_frame_rate_change (i->position ());
422                         ref_ph = ph - i->position() + DCPTime (i->trim_start(), ref_frc.get());
423                 }
424
425                 ContentTime const trim = _trim_start->get (i->video_frame_rate().get_value_or(_parent->film()->video_frame_rate()));
426                 i->set_trim_start (trim);
427         }
428
429         if (ref) {
430                 fv->seek (max(DCPTime(), ref_ph.get() + ref->position() - DCPTime(ref->trim_start(), ref_frc.get())), true);
431         }
432
433         fv->set_coalesce_player_changes (false);
434 }
435
436 void
437 TimingPanel::trim_end_changed ()
438 {
439         shared_ptr<FilmViewer> fv = _viewer.lock ();
440         if (!fv) {
441                 return;
442         }
443
444         fv->set_coalesce_player_changes (true);
445
446         Suspender::Block bl = _film_content_changed_suspender.block ();
447         BOOST_FOREACH (shared_ptr<Content> i, _parent->selected ()) {
448                 ContentTime const trim = _trim_end->get (i->video_frame_rate().get_value_or(_parent->film()->video_frame_rate()));
449                 i->set_trim_end (trim);
450         }
451
452         /* XXX: maybe playhead-off-the-end-of-the-film should be handled elsewhere */
453         if (fv->position() >= _parent->film()->length()) {
454                 fv->seek (_parent->film()->length() - DCPTime::from_frames(1, _parent->film()->video_frame_rate()), true);
455         }
456
457         fv->set_coalesce_player_changes (false);
458 }
459
460 void
461 TimingPanel::play_length_changed ()
462 {
463         DCPTime const play_length = _play_length->get (_parent->film()->video_frame_rate());
464         Suspender::Block bl = _film_content_changed_suspender.block ();
465         BOOST_FOREACH (shared_ptr<Content> i, _parent->selected ()) {
466                 FrameRateChange const frc = _parent->film()->active_frame_rate_change (i->position ());
467                 i->set_trim_end (
468                         ContentTime (max(DCPTime(), i->full_length(_parent->film()) - play_length), frc) - i->trim_start()
469                         );
470         }
471 }
472
473 void
474 TimingPanel::video_frame_rate_changed ()
475 {
476         bool enable = true;
477         if (_video_frame_rate->GetValue() == wxT("")) {
478                 /* No frame rate has been entered; if the user clicks "set" now it would unset the video
479                    frame rate in the selected content.  This can't be allowed for some content types.
480                 */
481                 BOOST_FOREACH (shared_ptr<Content> i, _parent->selected()) {
482                         if (
483                                 dynamic_pointer_cast<DCPContent>(i) ||
484                                 dynamic_pointer_cast<FFmpegContent>(i)
485                                 ) {
486                                 enable = false;
487                         }
488                 }
489         }
490
491         _set_video_frame_rate->Enable (enable);
492 }
493
494 void
495 TimingPanel::set_video_frame_rate ()
496 {
497         optional<double> fr;
498         if (_video_frame_rate->GetValue() != wxT("")) {
499                 fr = locale_convert<double> (wx_to_std (_video_frame_rate->GetValue ()));
500         }
501         Suspender::Block bl = _film_content_changed_suspender.block ();
502         BOOST_FOREACH (shared_ptr<Content> i, _parent->selected ()) {
503                 if (fr) {
504                         i->set_video_frame_rate (*fr);
505                 } else {
506                         i->unset_video_frame_rate ();
507                 }
508         }
509
510         _set_video_frame_rate->Enable (false);
511 }
512
513 void
514 TimingPanel::content_selection_changed ()
515 {
516         setup_sensitivity ();
517
518         film_content_changed (ContentProperty::POSITION);
519         film_content_changed (ContentProperty::LENGTH);
520         film_content_changed (ContentProperty::TRIM_START);
521         film_content_changed (ContentProperty::TRIM_END);
522         film_content_changed (ContentProperty::VIDEO_FRAME_RATE);
523 }
524
525 void
526 TimingPanel::film_changed (Film::Property p)
527 {
528         if (p == Film::VIDEO_FRAME_RATE) {
529                 update_full_length ();
530                 update_play_length ();
531         }
532 }
533
534 void
535 TimingPanel::trim_start_to_playhead_clicked ()
536 {
537         shared_ptr<FilmViewer> fv = _viewer.lock ();
538         if (!fv) {
539                 return;
540         }
541
542         shared_ptr<const Film> film = _parent->film ();
543         DCPTime const ph = fv->position().floor (film->video_frame_rate ());
544         optional<DCPTime> new_ph;
545
546         fv->set_coalesce_player_changes (true);
547
548         BOOST_FOREACH (shared_ptr<Content> i, _parent->selected ()) {
549                 if (i->position() < ph && ph < i->end(film)) {
550                         FrameRateChange const frc = film->active_frame_rate_change (i->position());
551                         i->set_trim_start (i->trim_start() + ContentTime (ph - i->position(), frc));
552                         new_ph = i->position ();
553                 }
554         }
555
556         if (new_ph) {
557                 fv->seek (new_ph.get(), true);
558         }
559
560         fv->set_coalesce_player_changes (false);
561 }
562
563 void
564 TimingPanel::trim_end_to_playhead_clicked ()
565 {
566         shared_ptr<FilmViewer> fv = _viewer.lock ();
567         if (!fv) {
568                 return;
569         }
570
571         shared_ptr<const Film> film = _parent->film ();
572         DCPTime const ph = fv->position().floor (film->video_frame_rate ());
573         BOOST_FOREACH (shared_ptr<Content> i, _parent->selected ()) {
574                 if (i->position() < ph && ph < i->end(film)) {
575                         FrameRateChange const frc = film->active_frame_rate_change (i->position ());
576                         i->set_trim_end (ContentTime(i->position() + i->full_length(film) - ph - DCPTime::from_frames(1, frc.dcp), frc) - i->trim_start());
577                 }
578         }
579 }
580
581 void
582 TimingPanel::setup_sensitivity ()
583 {
584         bool const e = !_parent->selected().empty ();
585
586         _position->Enable (e);
587         _move_to_start_of_reel->Enable (e);
588         _full_length->Enable (e);
589         _trim_start->Enable (e);
590         _trim_end->Enable (e);
591         _play_length->Enable (e);
592         _video_frame_rate->Enable (e);
593
594         shared_ptr<FilmViewer> fv = _viewer.lock ();
595         DCPOMATIC_ASSERT (fv);
596         DCPTime const ph = fv->position ();
597         bool any_over_ph = false;
598         BOOST_FOREACH (shared_ptr<const Content> i, _parent->selected ()) {
599                 if (i->position() <= ph && ph < i->end(_parent->film())) {
600                         any_over_ph = true;
601                 }
602         }
603
604         _trim_start_to_playhead->Enable (any_over_ph);
605         _trim_end_to_playhead->Enable (any_over_ph);
606 }
607
608 void
609 TimingPanel::move_to_start_of_reel_clicked ()
610 {
611         /* Find common position of all selected content, if it exists */
612
613         optional<DCPTime> position;
614         BOOST_FOREACH (shared_ptr<Content> i, _parent->selected ()) {
615                 if (!position) {
616                         position = i->position();
617                 } else {
618                         if (position.get() != i->position()) {
619                                 position.reset ();
620                                 break;
621                         }
622                 }
623         }
624
625         MoveToDialog* d = new MoveToDialog (this, position, _parent->film());
626
627         if (d->ShowModal() == wxID_OK) {
628                 BOOST_FOREACH (shared_ptr<Content> i, _parent->selected()) {
629                         i->set_position (_parent->film(), d->position());
630                 }
631         }
632         d->Destroy ();
633 }