46ebcec4005694d8cfb4cb467e64f59019fdd85f
[ardour.git] / libs / ardour / export_multiplication.cc
1 /*
2     Copyright (C) 2008-2012 Paul Davis 
3     Author: Sakari Bergen
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 /* This file is not used at the moment. It includes code related to export a
22  * multiplication graph system that can be used together with the ExportMultiplicator
23  * class in the gtk2_ardour folder.
24  * - Sakari Bergen 6.8.2008 -
25  */
26
27 void
28 ExportProfileManager::register_all_configs ()
29 {
30         list<TimespanNodePtr>::iterator tsl_it; // timespan list node iterator
31         for (tsl_it = graph.timespans.begin(); tsl_it != graph.timespans.end(); ++tsl_it) {
32                 list<GraphNode *>::const_iterator cc_it; // channel config node iterator
33                 for (cc_it = (*tsl_it)->get_children().begin(); cc_it != (*tsl_it)->get_children().end(); ++cc_it) {
34                         list<GraphNode *>::const_iterator f_it; // format node iterator
35                         for (f_it = (*cc_it)->get_children().begin(); f_it != (*cc_it)->get_children().end(); ++f_it) {
36                                 list<GraphNode *>::const_iterator fn_it; // filename node iterator
37                                 for (fn_it = (*f_it)->get_children().begin(); fn_it != (*f_it)->get_children().end(); ++fn_it) {
38                                         /* Finally loop through each timespan in the timespan list */
39
40                                         TimespanNodePtr ts_node;
41                                         if (!(ts_node = boost::dynamic_pointer_cast<TimespanNode> (*tsl_it))) {
42                                                 throw ExportFailed (X_("Programming error, Invalid pointer cast in ExportProfileManager"));
43                                         }
44
45                                         TimespanListPtr ts_list = ts_node->data()->timespans;
46                                         TimespanList::iterator ts_it;
47                                         for (ts_it = ts_list->begin(); ts_it != ts_list->end(); ++ts_it) {
48
49                                                 TimespanPtr timespan = *ts_it;
50
51                                                 ChannelConfigNode * cc_node;
52                                                 if (!(cc_node = dynamic_cast<ChannelConfigNode *> (*cc_it))) {
53                                                         throw ExportFailed (X_("Programming error, Invalid pointer cast in ExportProfileManager"));
54                                                 }
55                                                 ChannelConfigPtr channel_config = cc_node->data()->config;
56
57                                                 FormatNode * f_node;
58                                                 if (!(f_node = dynamic_cast<FormatNode *> (*f_it))) {
59                                                         throw ExportFailed (X_("Programming error, Invalid pointer cast in ExportProfileManager"));
60                                                 }
61                                                 FormatPtr format = f_node->data()->format;
62
63                                                 FilenameNode * fn_node;
64                                                 if (!(fn_node = dynamic_cast<FilenameNode *> (*fn_it))) {
65                                                         throw ExportFailed (X_("Programming error, Invalid pointer cast in ExportProfileManager"));
66                                                 }
67                                                 FilenamePtr filename = fn_node->data()->filename;
68
69                                                 handler->add_export_config (timespan, channel_config, format, filename);
70                                         }
71                                 }
72                         }
73                 }
74         }
75 }
76
77 void
78 ExportProfileManager::create_empty_config ()
79 {
80         TimespanNodePtr timespan = TimespanNode::create (new TimespanState ());
81         timespan->data()->timespans->push_back (handler->add_timespan());
82
83         ChannelConfigNodePtr channel_config = ChannelConfigNode::create (new ChannelConfigState(handler->add_channel_config()));
84
85         FormatNodePtr format;
86         load_formats ();
87         if (!format_list.empty()) {
88                 format = FormatNode::create (new FormatState (*format_list.begin ()));
89         } else {
90                 format = FormatNode::create (new FormatState (handler->add_format ()));
91         }
92
93         FilenameNodePtr filename = FilenameNode::create (new FilenameState (handler->add_filename()));
94
95         /* Bring everything together */
96
97         timespan->add_child (channel_config.get(), 0);
98         channel_config->add_child (format.get(), 0);
99         format->add_child (filename.get(), 0);
100
101         graph.timespans.push_back (timespan);
102         graph.channel_configs.push_back (channel_config);
103         graph.formats.push_back (format);
104         graph.filenames.push_back (filename);
105 }
106
107 /*** GraphNode ***/
108
109 uint32_t ExportProfileManager::GraphNode::id_counter = 0;
110
111 ExportProfileManager::GraphNode::GraphNode ()
112 {
113         _id = ++id_counter;
114 }
115
116 ExportProfileManager::GraphNode::~GraphNode ()
117 {
118         while (!children.empty()) {
119                 remove_child (children.front());
120         }
121
122         while (!parents.empty()) {
123                 parents.front()->remove_child (this);
124         }
125 }
126
127 void
128 ExportProfileManager::GraphNode::add_parent (GraphNode * parent)
129 {
130         for (list<GraphNode *>::iterator it = parents.begin(); it != parents.end(); ++it) {
131                 if (*it == parent) {
132                         return;
133                 }
134         }
135
136         parents.push_back (parent);
137 }
138
139 void
140 ExportProfileManager::GraphNode::add_child (GraphNode * child, GraphNode * left_sibling)
141 {
142         for (list<GraphNode *>::iterator it = children.begin(); it != children.end(); ++it) {
143                 if (*it == child) {
144                         return;
145                 }
146         }
147
148         if (left_sibling) {
149                 insert_after (children, left_sibling, child);
150         } else {
151                 children.push_back (child);
152         }
153
154         child->add_parent (this);
155 }
156
157 bool
158 ExportProfileManager::GraphNode::is_ancestor_of (GraphNode const * node) const
159 {
160         for (list<GraphNode *>::const_iterator it = children.begin(); it != children.end(); ++it) {
161                 if (*it == node || (*it)->is_ancestor_of (node)) {
162                         return true;
163                 }
164         }
165
166         return false;
167 }
168
169 bool
170 ExportProfileManager::GraphNode::is_descendant_of (GraphNode const * node) const
171 {
172         for (list<GraphNode *>::const_iterator it = parents.begin(); it != parents.end(); ++it) {
173                 if (*it == node || (*it)->is_descendant_of (node)) {
174                         return true;
175                 }
176         }
177
178         return false;
179 }
180
181 void
182 ExportProfileManager::GraphNode::select (bool value)
183 {
184         if (_selected == value) { return; }
185
186         _selected = value;
187         SelectChanged (value);
188 }
189
190 void
191 ExportProfileManager::GraphNode::remove_parent (GraphNode * parent)
192 {
193         for (list<GraphNode *>::iterator it = parents.begin(); it != parents.end(); ++it) {
194                 if (*it == parent) {
195                         parents.erase (it);
196                         break;
197                 }
198         }
199 }
200
201 void
202 ExportProfileManager::GraphNode::remove_child (GraphNode * child)
203 {
204         for (list<GraphNode *>::iterator it = children.begin(); it != children.end(); ++it) {
205                 if (*it == child) {
206                         children.erase (it);
207                         break;
208                 }
209         }
210
211         child->remove_parent (this);
212 }
213
214 void
215 ExportProfileManager::split_node (GraphNode * node, float position)
216 {
217         TimespanNode * ts_node;
218         if ((ts_node = dynamic_cast<TimespanNode *> (node))) {
219                 split_timespan (ts_node->self_ptr(), position);
220                 return;
221         }
222
223         ChannelConfigNode * cc_node;
224         if ((cc_node = dynamic_cast<ChannelConfigNode *> (node))) {
225                 split_channel_config (cc_node->self_ptr(), position);
226                 return;
227         }
228
229         FormatNode * f_node;
230         if ((f_node = dynamic_cast<FormatNode *> (node))) {
231                 split_format (f_node->self_ptr(), position);
232                 return;
233         }
234
235         FilenameNode * fn_node;
236         if ((fn_node = dynamic_cast<FilenameNode *> (node))) {
237                 split_filename (fn_node->self_ptr(), position);
238                 return;
239         }
240 }
241
242 void
243 ExportProfileManager::remove_node (GraphNode * node)
244 {
245         TimespanNode * ts_node;
246         if ((ts_node = dynamic_cast<TimespanNode *> (node))) {
247                 remove_timespan (ts_node->self_ptr());
248                 return;
249         }
250
251         ChannelConfigNode * cc_node;
252         if ((cc_node = dynamic_cast<ChannelConfigNode *> (node))) {
253                 remove_channel_config (cc_node->self_ptr());
254                 return;
255         }
256
257         FormatNode * f_node;
258         if ((f_node = dynamic_cast<FormatNode *> (node))) {
259                 remove_format (f_node->self_ptr());
260                 return;
261         }
262
263         FilenameNode * fn_node;
264         if ((fn_node = dynamic_cast<FilenameNode *> (node))) {
265                 remove_filename (fn_node->self_ptr());
266                 return;
267         }
268 }
269
270 void
271 ExportProfileManager::purge_graph ()
272 {
273         for (list<TimespanNodePtr>::iterator it = graph.timespans.begin(); it != graph.timespans.end(); ) {
274                 list<TimespanNodePtr>::iterator tmp = it;
275                 ++it;
276
277                 if ((*tmp)->get_children().empty()) {
278                         graph.timespans.erase (tmp);
279                 }
280         }
281
282         for (list<ChannelConfigNodePtr>::iterator it = graph.channel_configs.begin(); it != graph.channel_configs.end(); ) {
283                 list<ChannelConfigNodePtr>::iterator tmp = it;
284                 ++it;
285
286                 if ((*tmp)->get_parents().empty()) {
287                         graph.channel_configs.erase (tmp);
288                 }
289         }
290
291         for (list<FormatNodePtr>::iterator it = graph.formats.begin(); it != graph.formats.end(); ) {
292                 list<FormatNodePtr>::iterator tmp = it;
293                 ++it;
294
295                 if ((*tmp)->get_parents().empty()) {
296                         graph.formats.erase (tmp);
297                 }
298         }
299
300         for (list<FilenameNodePtr>::iterator it = graph.filenames.begin(); it != graph.filenames.end(); ) {
301                 list<FilenameNodePtr>::iterator tmp = it;
302                 ++it;
303
304                 if ((*tmp)->get_parents().empty()) {
305                         graph.filenames.erase (tmp);
306                 }
307         }
308
309         GraphChanged();
310 }
311
312 template<typename T>
313 void
314 ExportProfileManager::insert_after (list<T> & the_list, T const & position, T const & element)
315 {
316         typename list<T>::iterator it;
317         for (it = the_list.begin(); it != the_list.end(); ++it) {
318                 if (*it == position) {
319                         the_list.insert (++it, element);
320                         return;
321                 }
322         }
323
324         std::cerr << "invalid position given to ExportProfileManager::insert_after (aborting)" << std::endl;
325
326         abort();
327 }
328
329 template<typename T>
330 void
331 ExportProfileManager::remove_by_element (list<T> & the_list, T const & element)
332 {
333         typename list<T>::iterator it;
334         for (it = the_list.begin(); it != the_list.end(); ++it) {
335                 if (*it == element) {
336                         the_list.erase (it);
337                         return;
338                 }
339         }
340 }
341
342 bool
343 ExportProfileManager::nodes_have_one_common_child (list<GraphNode *> const & the_list)
344 {
345         return end_of_common_child_range (the_list, the_list.begin()) == --the_list.end();
346 }
347
348 list<ExportProfileManager::GraphNode *>::const_iterator
349 ExportProfileManager::end_of_common_child_range (list<GraphNode *> const & the_list, list<GraphNode *>::const_iterator beginning)
350 {
351         if ((*beginning)->get_children().size() != 1) { return beginning; }
352         GraphNode * child = (*beginning)->get_children().front();
353
354         list<GraphNode *>::const_iterator it = beginning;
355         while (it != the_list.end() && (*it)->get_children().size() == 1 && (*it)->get_children().front() == child) {
356                 ++it;
357         }
358
359         return --it;
360 }
361
362 void
363 ExportProfileManager::split_node_at_position (GraphNode * old_node, GraphNode * new_node, float position)
364 {
365         list<GraphNode *> const & node_parents = old_node->get_parents();
366         uint32_t split_index = (int) (node_parents.size() * position + 0.5);
367         split_index = std::max ((uint32_t) 1, std::min (split_index, node_parents.size() - 1));
368
369         list<GraphNode *>::const_iterator it = node_parents.begin();
370         for (uint32_t index = 1; it != node_parents.end(); ++index) {
371                 if (index > split_index) {
372                         list<GraphNode *>::const_iterator tmp = it++;
373                         (*tmp)->add_child (new_node, old_node);
374                         (*tmp)->remove_child (old_node);
375                 } else {
376                         ++it;
377                 }
378         }
379 }
380
381 void
382 ExportProfileManager::split_timespan (TimespanNodePtr node, float)
383 {
384         TimespanNodePtr new_timespan = duplicate_timespan_node (node);
385         insert_after (graph.timespans, node, new_timespan);
386
387         /* Note: Since a timespan selector allows all combinations of ranges
388          * there is no reason for a channel configuration to have two parents
389          */
390
391         duplicate_timespan_children (node->self_ptr(), new_timespan);
392
393         GraphChanged();
394 }
395
396 void
397 ExportProfileManager::split_channel_config (ChannelConfigNodePtr node, float)
398 {
399         ChannelConfigNodePtr new_config = duplicate_channel_config_node (node);
400         insert_after (graph.channel_configs, node, new_config);
401
402         /* Channel configs have only one parent, see above! */
403         node->get_parents().front()->add_child (new_config.get(), node.get());
404
405         if (node->get_children().size() == 1) {
406                 new_config->add_child (node->first_child(), 0);
407         } else {
408                 duplicate_channel_config_children (node, new_config);
409         }
410
411         GraphChanged();
412 }
413
414 void
415 ExportProfileManager::split_format (FormatNodePtr node, float position)
416 {
417         FormatNodePtr new_format = duplicate_format_node (node);
418         insert_after (graph.formats, node, new_format);
419
420         list<GraphNode *> const & node_parents = node->get_parents();
421         if (node_parents.size() == 1) {
422                 node_parents.front()->add_child (new_format.get(), 0);
423         } else {
424                 node->sort_parents (graph.channel_configs);
425                 split_node_at_position (node.get(), new_format.get(), position);
426         }
427
428         if (node->get_children().size() == 1) {
429                 new_format->add_child (node->first_child(), 0);
430         } else {
431                 duplicate_format_children (node, new_format);
432         }
433
434         GraphChanged();
435 }
436
437 void
438 ExportProfileManager::split_filename (FilenameNodePtr node, float position)
439 {
440         FilenameNodePtr new_filename = duplicate_filename_node (node);
441         insert_after (graph.filenames, node, new_filename);
442
443         list<GraphNode *> const & node_parents = node->get_parents();
444
445         if (node_parents.size() == 1) {
446                 node_parents.front()->add_child (new_filename.get(), 0);
447         } else {
448                 node->sort_parents (graph.formats);
449                 split_node_at_position (node.get(), new_filename.get(), position);
450         }
451
452         GraphChanged();
453 }
454
455 void
456 ExportProfileManager::duplicate_timespan_children (TimespanNodePtr source, TimespanNodePtr target, GraphNode * insertion_point)
457 {
458         list<GraphNode *> const & source_children = source->get_children();
459         bool one_grandchild = nodes_have_one_common_child (source_children);
460         GraphNode * child_insertion_point = 0;
461
462         ChannelConfigNodePtr node_insertion_point;
463         ChannelConfigNode * node_insertion_ptr;
464         if (!insertion_point) { insertion_point = source->last_child(); }
465         if (!(node_insertion_ptr = dynamic_cast<ChannelConfigNode *> (insertion_point))) {
466                 throw ExportFailed (X_("Programming error, Invalid pointer cast in ExportProfileManager"));
467         }
468         node_insertion_point = node_insertion_ptr->self_ptr();
469
470         /* Keep track of common children */
471
472         list<GraphNode *>::const_iterator common_children_begin = source_children.begin();
473         list<GraphNode *>::const_iterator common_children_end = end_of_common_child_range (source_children, source_children.begin());
474         GraphNode * common_child = 0;
475
476         for (list<GraphNode *>::const_iterator it = source_children.begin(); it != source_children.end(); ++it) {
477                 /* Duplicate node */
478
479                 ChannelConfigNode *  node;
480                 ChannelConfigNodePtr new_node;
481
482                 if (!(node = dynamic_cast<ChannelConfigNode *> (*it))) {
483                         throw ExportFailed (X_("Programming error, Invalid pointer cast in ExportProfileManager"));
484                 }
485
486                 new_node = duplicate_channel_config_node (node->self_ptr());
487
488                 /* Insert in gaph's list and update insertion position */
489
490                 insert_after (graph.channel_configs, node_insertion_point, new_node);
491                 node_insertion_point = new_node;
492
493                 /* Handle children */
494
495                 target->add_child (new_node.get(), child_insertion_point);
496                 child_insertion_point = new_node.get();
497
498                 if (one_grandchild) {
499                         new_node->add_child (node->first_child(), 0);
500                 } else {
501                         list<GraphNode *>::const_iterator past_end = common_children_end;
502                         if (it == ++past_end) { // At end => start new range
503                                 common_children_begin = it;
504                                 common_children_end = end_of_common_child_range (source_children, it);
505                         }
506
507                         if (it == common_children_begin) { // At beginning => do duplication
508                                 GraphNode * grand_child_ins_pt = common_child;
509                                 if (!grand_child_ins_pt) {
510                                         grand_child_ins_pt = source->last_child()->last_child();
511                                 }
512                                 duplicate_channel_config_children (node->self_ptr(), new_node, grand_child_ins_pt);
513                                 common_child = new_node->first_child();
514                         } else { // Somewhere in between end and beginning => share child
515                                 new_node->add_child (common_child, 0);
516                         }
517                 }
518         }
519 }
520
521 void
522 ExportProfileManager::duplicate_channel_config_children (ChannelConfigNodePtr source, ChannelConfigNodePtr target, GraphNode * insertion_point)
523 {
524         list<GraphNode *> const & source_children = source->get_children();
525         bool one_grandchild = nodes_have_one_common_child (source_children);
526         GraphNode * child_insertion_point = 0;
527
528         FormatNodePtr node_insertion_point;
529         FormatNode * node_insertion_ptr;
530         if (!insertion_point) { insertion_point = source->last_child(); }
531         if (!(node_insertion_ptr = dynamic_cast<FormatNode *> (insertion_point))) {
532                 throw ExportFailed (X_("Programming error, Invalid pointer cast in ExportProfileManager"));
533         }
534         node_insertion_point = node_insertion_ptr->self_ptr();
535
536         /* Keep track of common children */
537
538         list<GraphNode *>::const_iterator common_children_begin = source_children.begin();
539         list<GraphNode *>::const_iterator common_children_end = end_of_common_child_range (source_children, source_children.begin());
540         GraphNode * common_child = 0;
541
542         for (list<GraphNode *>::const_iterator it = source_children.begin(); it != source_children.end(); ++it) {
543                 /* Duplicate node */
544
545                 FormatNode *  node;
546                 FormatNodePtr new_node;
547
548                 if (!(node = dynamic_cast<FormatNode *> (*it))) {
549                         throw ExportFailed (X_("Programming error, Invalid pointer cast in ExportProfileManager"));
550                 }
551
552                 new_node = duplicate_format_node (node->self_ptr());
553
554                 /* Insert in gaph's list and update insertion position */
555
556                 insert_after (graph.formats, node_insertion_point, new_node);
557                 node_insertion_point = new_node;
558
559                 /* Handle children */
560
561                 target->add_child (new_node.get(), child_insertion_point);
562                 child_insertion_point = new_node.get();
563
564                 if (one_grandchild) {
565                         new_node->add_child (node->first_child(), 0);
566                 } else {
567                         list<GraphNode *>::const_iterator past_end = common_children_end;
568                         if (it == ++past_end) { // At end => start new range
569                                 common_children_begin = it;
570                                 common_children_end = end_of_common_child_range (source_children, it);
571                         }
572
573                         if (it == common_children_begin) { // At beginning => do duplication
574                                 GraphNode * grand_child_ins_pt = common_child;
575                                 if (!grand_child_ins_pt) {
576                                         grand_child_ins_pt = source->get_parents().back()->last_child()->last_child()->last_child();
577                                 }
578                                 duplicate_format_children (node->self_ptr(), new_node, grand_child_ins_pt);
579                                 common_child = new_node->first_child();
580                         } else { // Somewhere in between end and beginning => share child
581                                 new_node->add_child (common_child, 0);
582                         }
583                 }
584         }
585 }
586
587 void
588 ExportProfileManager::duplicate_format_children (FormatNodePtr source, FormatNodePtr target, GraphNode * insertion_point)
589 {
590         GraphNode * child_insertion_point = 0;
591
592         FilenameNodePtr node_insertion_point;
593         FilenameNode * node_insertion_ptr;
594         if (!insertion_point) { insertion_point = source->last_child(); }
595         if (!(node_insertion_ptr = dynamic_cast<FilenameNode *> (insertion_point))) {
596                 throw ExportFailed (X_("Programming error, Invalid pointer cast in ExportProfileManager"));
597         }
598         node_insertion_point = node_insertion_ptr->self_ptr();
599
600         for (list<GraphNode *>::const_iterator it = source->get_children().begin(); it != source->get_children().end(); ++it) {
601                 /* Duplicate node */
602
603                 FilenameNode *  node;
604                 FilenameNodePtr new_node;
605
606                 if (!(node = dynamic_cast<FilenameNode *> (*it))) {
607                         throw ExportFailed (X_("Programming error, Invalid pointer cast in ExportProfileManager"));
608                 }
609
610                 new_node = duplicate_filename_node (node->self_ptr());
611
612                 /* Insert in gaph's list and update insertion position */
613
614                 insert_after (graph.filenames, node_insertion_point, new_node);
615                 node_insertion_point = new_node;
616
617                 /* Handle children */
618
619                 target->add_child (new_node.get(), child_insertion_point);
620                 child_insertion_point = new_node.get();
621         }
622 }
623
624 ExportProfileManager::TimespanNodePtr
625 ExportProfileManager::duplicate_timespan_node (TimespanNodePtr node)
626 {
627         TimespanStatePtr state = node->data();
628         TimespanStatePtr new_state (new TimespanState ());
629         TimespanNodePtr  new_node = TimespanNode::create (new_state);
630
631         for (TimespanList::iterator it = state->timespans->begin(); it != state->timespans->end(); ++it) {
632                 new_state->timespans->push_back (handler->add_timespan_copy (*it));
633         }
634
635         new_state->time_format = state->time_format;
636         new_state->marker_format = state->marker_format;
637
638         return new_node;
639 }
640
641 ExportProfileManager::ChannelConfigNodePtr
642 ExportProfileManager::duplicate_channel_config_node (ChannelConfigNodePtr node)
643 {
644         ChannelConfigStatePtr state = node->data();
645         ChannelConfigStatePtr new_state (new ChannelConfigState (handler->add_channel_config_copy (state->config)));
646         ChannelConfigNodePtr  new_node = ChannelConfigNode::create (new_state);
647
648         return new_node;
649 }
650
651 ExportProfileManager::FormatNodePtr
652 ExportProfileManager::duplicate_format_node (FormatNodePtr node)
653 {
654         FormatStatePtr state = node->data();
655         FormatStatePtr new_state (new FormatState (handler->add_format_copy (state->format)));
656         FormatNodePtr  new_node = FormatNode::create (new_state);
657
658         return new_node;
659 }
660
661 ExportProfileManager::FilenameNodePtr
662 ExportProfileManager::duplicate_filename_node (FilenameNodePtr node)
663 {
664         FilenameStatePtr state = node->data();
665         FilenameStatePtr new_state (new FilenameState (handler->add_filename_copy (state->filename)));
666         FilenameNodePtr  new_node = FilenameNode::create (new_state);
667
668         return new_node;
669 }
670
671 void
672 ExportProfileManager::remove_timespan (TimespanNodePtr node)
673 {
674         remove_by_element (graph.timespans, node);
675         purge_graph ();
676 }
677
678 void
679 ExportProfileManager::remove_channel_config (ChannelConfigNodePtr node)
680 {
681         remove_by_element (graph.channel_configs, node);
682         purge_graph ();
683 }
684
685 void
686 ExportProfileManager::remove_format (FormatNodePtr node)
687 {
688         remove_by_element (graph.formats, node);
689         purge_graph ();
690 }
691
692 void
693 ExportProfileManager::remove_filename (FilenameNodePtr node)
694 {
695         remove_by_element (graph.filenames, node);
696         purge_graph ();
697 }