1 /*
2  * Copyright (C) 2018-2019 Robin Gareus <robin@gareus.org>
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 along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #include <gtkmm/box.h>
20 #include <gtkmm/frame.h>
21 
22 #include "gtkmm2ext/utils.h"
23 
24 #include "ardour/plugin.h"
25 #include "gui_thread.h"
26 
27 #include "plugin_presets_ui.h"
28 
29 #include "pbd/i18n.h"
30 
31 using namespace ARDOUR;
32 using namespace Gtk;
33 
PluginPresetsUI(boost::shared_ptr<PluginInsert> insert)34 PluginPresetsUI::PluginPresetsUI (boost::shared_ptr<PluginInsert> insert)
35 	: _insert (insert)
36 	, _load_button (_("Load"))
37 {
38 	_filter_banks_model = TreeStore::create (_filter_banks_columns);
39 	_filter_banks_display.set_model (_filter_banks_model);
40 	_filter_banks_display.set_headers_visible (true);
41 	_filter_banks_display.get_selection ()->set_mode (SELECTION_BROWSE);
42 	_filter_banks_display.get_selection ()->signal_changed ().connect (sigc::mem_fun (*this, &PluginPresetsUI::filter_presets));
43 	_filter_banks_display.set_sensitive (true);
44 	_filter_banks_display.append_column (_("Bank/Vendor"), _filter_banks_columns.name);
45 	_banks_scroller.set_policy (POLICY_NEVER, POLICY_AUTOMATIC);
46 	_banks_scroller.add (_filter_banks_display);
47 	_banks_scroller.set_no_show_all (true);
48 
49 	_filter_types_model = TreeStore::create (_filter_types_columns);
50 	_filter_types_display.set_model (_filter_types_model);
51 	_filter_types_display.set_headers_visible (true);
52 	_filter_types_display.get_selection ()->set_mode (SELECTION_BROWSE);
53 	_filter_types_display.get_selection ()->signal_changed ().connect (sigc::mem_fun (*this, &PluginPresetsUI::filter_presets));
54 	_filter_types_display.set_sensitive (true);
55 	_filter_types_display.append_column (_("Type/Category"), _filter_types_columns.name);
56 	_types_scroller.set_policy (POLICY_NEVER, POLICY_AUTOMATIC);
57 	_types_scroller.add (_filter_types_display);
58 	_types_scroller.set_no_show_all (true);
59 
60 	_plugin_preset_model = TreeStore::create (_plugin_preset_columns);
61 	_plugin_preset_display.set_model (_plugin_preset_model);
62 	_plugin_preset_display.set_headers_visible (true);
63 	_plugin_preset_display.get_selection ()->set_mode (SELECTION_BROWSE);
64 	_plugin_preset_display.get_selection ()->signal_changed ().connect (sigc::mem_fun (*this, &PluginPresetsUI::preset_selected));
65 	_plugin_preset_display.signal_row_activated ().connect (sigc::mem_fun (*this, &PluginPresetsUI::preset_row_activated));
66 	_plugin_preset_display.set_sensitive (true);
67 
68 	CellRendererText* label_render = manage (new CellRendererText());
69 	TreeView::Column* label_col = manage (new TreeView::Column (_("Preset"), *label_render));
70 	label_col->add_attribute (label_render->property_markup(), _plugin_preset_columns.name);
71 	_plugin_preset_display.append_column (*label_col);
72 
73 	_preset_desc.set_editable (false);
74 	_preset_desc.set_can_focus (false);
75 	_preset_desc.set_wrap_mode (WRAP_WORD);
76 	_preset_desc.set_size_request (300,200);
77 	_preset_desc.set_name (X_("TextOnBackground"));
78 	_preset_desc.set_border_width (15);
79 
80 	Frame* frame = manage (new Frame);
81 	frame->set_label (_("Description"));
82 	frame->add (_preset_desc);
83 
84 	_preset_scroller.set_policy (POLICY_NEVER, POLICY_AUTOMATIC);
85 	_preset_scroller.add (_plugin_preset_display);
86 
87 	_load_button.set_name ("generic button");
88 	_load_button.signal_clicked.connect (sigc::mem_fun (*this, &PluginPresetsUI::load_preset));
89 	_load_button.set_sensitive (false);
90 
91 	Box* filter_box = manage (new VBox ());
92 	filter_box->pack_start (_banks_scroller);
93 	filter_box->pack_start (_types_scroller);
94 
95 	attach (*filter_box,      0, 1, 0, 2, FILL, EXPAND|FILL, 2, 0);
96 	attach (_preset_scroller, 1, 2, 0, 2, FILL, EXPAND|FILL, 2, 0);
97 	attach (*frame,           2, 3, 0, 1, EXPAND|FILL, EXPAND|FILL, 2, 4);
98 	attach (_load_button,     2, 3, 1, 2, FILL, SHRINK, 2, 0);
99 
100 	boost::shared_ptr<Plugin> plugin (_insert->plugin ());
101 
102 	plugin->PresetAdded.connect (_preset_connections, invalidator (*this), boost::bind (&PluginPresetsUI::update_preset_list, this), gui_context ());
103 	plugin->PresetRemoved.connect (_preset_connections, invalidator (*this), boost::bind (&PluginPresetsUI::update_preset_list, this), gui_context ());
104 
105 	plugin->PresetLoaded.connect (_preset_connections, invalidator (*this), boost::bind (&PluginPresetsUI::filter_presets, this), gui_context ());
106 	plugin->PresetDirty.connect (_preset_connections, invalidator (*this), boost::bind (&PluginPresetsUI::filter_presets, this), gui_context ());
107 
108 	update_preset_list ();
109 }
110 
111 void
update_preset_list()112 PluginPresetsUI::update_preset_list ()
113 {
114 	boost::shared_ptr<Plugin> plugin (_insert->plugin ());
115 	std::vector<Plugin::PresetRecord> presets = plugin->get_presets ();
116 
117 	_pps.clear ();
118 	std::map<std::string, size_t> banks; // or vendors
119 	std::map<std::string, size_t> types; // or categories
120 
121 	for (std::vector<Plugin::PresetRecord>::const_iterator i = presets.begin (); i != presets.end (); ++i) {
122 		++banks[_("-All-")];
123 		++types[_("-All-")];
124 		if (i->user) {
125 			++banks[_("-User-")];
126 			_pps.push_back (PluginPreset(*i, _("-User-")));
127 			continue;
128 		}
129 
130 		std::string l (i->label);
131 		std::vector<std::string> cat;
132 		size_t pos = 0;
133 		while ((pos = l.find(" - ")) != std::string::npos) {
134 			cat.push_back (l.substr (0, pos));
135 			l.erase (0, pos + 3);
136 			if (cat.size() > 1) {
137 				break;
138 			}
139 		}
140 		if (cat.size() > 1) {
141 			++banks[cat.at (0)];
142 			++types[cat.at (1)];
143 			_pps.push_back (PluginPreset(*i, cat.at (0), cat.at (1)));
144 		} else if (cat.size() > 0) {
145 			++banks[cat.at (0)];
146 			_pps.push_back (PluginPreset(*i, cat.at (0)));
147 		} else {
148 			_pps.push_back (PluginPreset(*i));
149 		}
150 	}
151 
152 	if (types.size() > 2) {
153 		std::string selected_type;
154 		if (_filter_types_display.get_selection ()->count_selected_rows () == 1) {
155 			TreeIter iter = _filter_types_display.get_selection ()->get_selected ();
156 			selected_type = (*iter)[_filter_types_columns.name];
157 		} else {
158 			selected_type = Gtkmm2ext::markup_escape_text(_("-All-"));
159 		}
160 		_filter_types_model->clear ();
161 		for (std::map<std::string, size_t>::const_iterator i = types.begin (); i != types.end(); ++i) {
162 			TreeModel::Row row = *(_filter_types_model->append ());
163 			row[_filter_types_columns.name] = Gtkmm2ext::markup_escape_text (i->first);
164 			row[_filter_types_columns.count] = i->second;
165 		}
166 		TreeModel::Children rows = _filter_types_model->children ();
167 		for (TreeModel::Children::iterator i = rows.begin (); i != rows.end (); ++i) {
168 			std::string const& name ((*i)[_filter_types_columns.name]);
169 			if (selected_type == name) {
170 				_filter_types_display.get_selection ()->select (*i);
171 				break;
172 			}
173 		}
174 		_filter_types_display.show_all ();
175 		_types_scroller.show ();
176 	} else {
177 		_filter_types_model->clear ();
178 		_types_scroller.hide ();
179 	}
180 
181 	if (banks.size() > 2) {
182 		std::string selected_bank = Gtkmm2ext::markup_escape_text(_("-All-"));
183 		if (_filter_banks_display.get_selection ()->count_selected_rows () == 1) {
184 			TreeIter iter = _filter_banks_display.get_selection ()->get_selected ();
185 			selected_bank = (*iter)[_filter_banks_columns.name];
186 		}
187 		_filter_banks_model->clear ();
188 		for (std::map<std::string, size_t>::const_iterator i = banks.begin (); i != banks.end(); ++i) {
189 			TreeModel::Row row = *(_filter_banks_model->append ());
190 			row[_filter_banks_columns.name] = Gtkmm2ext::markup_escape_text (i->first);
191 			row[_filter_banks_columns.count] = i->second;
192 		}
193 		TreeModel::Children rows = _filter_banks_model->children ();
194 		for (TreeModel::Children::iterator i = rows.begin (); i != rows.end (); ++i) {
195 			std::string const& name ((*i)[_filter_banks_columns.name]);
196 			if (selected_bank == name) {
197 				_filter_banks_display.get_selection ()->select (*i);
198 				break;
199 			}
200 		}
201 		_filter_banks_display.show_all ();
202 		_banks_scroller.show ();
203 	} else {
204 		_filter_banks_model->clear ();
205 		_banks_scroller.hide ();
206 	}
207 
208 	std::sort (_pps.begin(), _pps.end());
209 
210 	filter_presets ();
211 }
212 
213 void
filter_presets()214 PluginPresetsUI::filter_presets ()
215 {
216 	bool user_only = false;
217 	std::string selected_bank;
218 	if (_filter_banks_display.get_selection ()->count_selected_rows () == 1) {
219 		TreeIter iter = _filter_banks_display.get_selection ()->get_selected ();
220 		selected_bank = (*iter)[_filter_banks_columns.name];
221 		if (_("-All-") == selected_bank) {
222 			selected_bank = "";
223 		}
224 		if (_("-User-") == selected_bank) {
225 			selected_bank = "";
226 			user_only = true;
227 		}
228 	}
229 
230 	std::string selected_type;
231 	if (_filter_types_display.get_selection ()->count_selected_rows () == 1) {
232 		TreeIter iter = _filter_types_display.get_selection ()->get_selected ();
233 		selected_type = (*iter)[_filter_types_columns.name];
234 		if (_("-All-") == selected_type) {
235 			selected_type = "";
236 		}
237 	}
238 
239 	boost::shared_ptr<Plugin> plugin (_insert->plugin ());
240 	Plugin::PresetRecord const& p = plugin->last_preset ();
241 
242 	std::string selected_uri = p.valid ? p.uri : "";
243 	if (_plugin_preset_display.get_selection ()->count_selected_rows () == 1) {
244 		TreeIter iter = _plugin_preset_display.get_selection ()->get_selected ();
245 		ARDOUR::Plugin::PresetRecord const& ppr ((*iter)[_plugin_preset_columns.plugin_preset]);
246 		selected_uri = ppr.uri;
247 	}
248 
249 	_plugin_preset_model->clear ();
250 	bool const modified = plugin->parameter_changed_since_last_preset ();
251 
252 	for (std::vector<PluginPreset>::const_iterator i = _pps.begin (); i != _pps.end (); ++i) {
253 		if (!selected_type.empty() && i->_type != selected_type) {
254 			continue;
255 		}
256 		if (!selected_bank.empty() && i->_bank != selected_bank) {
257 			continue;
258 		}
259 
260 		ARDOUR::Plugin::PresetRecord const& ppr (i->_preset_record);
261 
262 		if (user_only && !ppr.user) {
263 			continue;
264 		}
265 
266 		TreeModel::Row row = *(_plugin_preset_model->append ());
267 		if (p.uri == ppr.uri && !modified) {
268 			row[_plugin_preset_columns.name] = string_compose ("<span weight=\"bold\"  background=\"green\">%1</span>", Gtkmm2ext::markup_escape_text (ppr.label));
269 		} else {
270 			row[_plugin_preset_columns.name] = Gtkmm2ext::markup_escape_text (ppr.label);
271 		}
272 		row[_plugin_preset_columns.description] = ppr.description;
273 		row[_plugin_preset_columns.plugin_preset] = ppr;
274 	}
275 
276 	int path = 0;
277 	TreeModel::Children rows = _plugin_preset_model->children ();
278 	for (TreeModel::Children::iterator i = rows.begin (); i != rows.end (); ++i, ++path) {
279 		ARDOUR::Plugin::PresetRecord const& ppr ((*i)[_plugin_preset_columns.plugin_preset]);
280 		if (ppr.uri == selected_uri) {
281 			_plugin_preset_display.get_selection ()->select (*i);
282 			char row_path[21];
283 			snprintf(row_path, 21, "%d", path);
284 			_plugin_preset_display.scroll_to_row (Gtk::TreePath(row_path));
285 			break;
286 		}
287 	}
288 }
289 
290 void
preset_selected()291 PluginPresetsUI::preset_selected ()
292 {
293 	if (_plugin_preset_display.get_selection ()->count_selected_rows () != 1) {
294 		_preset_desc.get_buffer ()->set_text ("");
295 		_load_button.set_sensitive (false);
296 		return;
297 	}
298 
299 	TreeIter iter = _plugin_preset_display.get_selection ()->get_selected ();
300 	assert (iter);
301 	ARDOUR::Plugin::PresetRecord const& ppr ((*iter)[_plugin_preset_columns.plugin_preset]);
302 
303 	std::string d;
304 	if (!ppr.valid) {
305 		d = "-";
306 	} else if (ppr.user) {
307 		d = _("(user preset)");
308 	} else {
309 		d = (*iter)[_plugin_preset_columns.description];
310 	}
311 	_preset_desc.get_buffer ()->set_text (d);
312 
313 	Plugin::PresetRecord const& p = _insert->plugin ()->last_preset ();
314 	_load_button.set_sensitive (ppr.valid && !(p.valid && p.uri == ppr.uri));
315 }
316 
317 void
preset_row_activated(Gtk::TreeModel::Path,Gtk::TreeViewColumn *)318 PluginPresetsUI::preset_row_activated (Gtk::TreeModel::Path, Gtk::TreeViewColumn*)
319 {
320 	if (_load_button.get_sensitive ()) {
321 		load_preset ();
322 	}
323 }
324 
325 void
load_preset()326 PluginPresetsUI::load_preset ()
327 {
328 	if (_plugin_preset_display.get_selection ()->count_selected_rows () != 1) {
329 		return;
330 	}
331 
332 	TreeIter iter = _plugin_preset_display.get_selection ()->get_selected ();
333 	ARDOUR::Plugin::PresetRecord const& ppr ((*iter)[_plugin_preset_columns.plugin_preset]);
334 	if (ppr.valid) {
335 		_insert->load_preset (ppr);
336 	}
337 }
338