1 /*
2 * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert
3 * Copyright (C) 2011 Pete Shorthose
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * ---------------------------------------------------------------------------
19 *
20 *
21 * ----------------------------------------------------------------------------
22 */
23
24 #include <guitarix.h>
25
26 /****************************************************************
27 * class PresetWindow
28 *
29 * model / view scheme
30 *
31 * There might be several views (clients), and the model (represented
32 * by a GxMachineBase/GxSettingsBase instance) might be located in a
33 * remote headless Guitarix instance.
34 *
35 * The model state consists of
36 * - list of banks
37 * - current bank
38 * - list of presets (for current bank)
39 * - current preset
40 *
41 * Model state changes trigger one of two signals:
42 * - presetlist_changed: need full state reload
43 * - selection_changed: current bank / preset changed
44 *
45 * Modifications of the model state will be reflected by a
46 * signal. Changes loaded in response of a signal must not be set
47 * (again) in the model.
48 */
49
PresetStore()50 PresetStore::PresetStore(): Gtk::ListStore(), col() {
51 set_column_types(col);
52 }
53
row_draggable_vfunc(const TreeModel::Path & path) const54 bool PresetStore::row_draggable_vfunc(const TreeModel::Path& path) const {
55 Gtk::TreeModel::const_iterator i = const_cast<PresetStore*>(this)->get_iter(path); // Bug in Gtkmm: no get_iter() const
56 Glib::ustring s(i->get_value(col.name));
57 if (s.empty()) {
58 return false;
59 } else {
60 return true;
61 }
62 }
63
PresetWindow(Glib::RefPtr<gx_gui::GxBuilder> bld,gx_engine::GxMachineBase & machine_,const gx_system::CmdlineOptions & options_,GxActions & actions_,UIManager & uimanager)64 PresetWindow::PresetWindow(Glib::RefPtr<gx_gui::GxBuilder> bld, gx_engine::GxMachineBase& machine_,
65 const gx_system::CmdlineOptions& options_, GxActions& actions_,
66 UIManager& uimanager)
67 : sigc::trackable(),
68 machine(machine_),
69 actions(actions_),
70 accelgroup(uimanager.get_accel_group()),
71 in_edit(false),
72 edit_iter(),
73 pb_edit(),
74 pb_del(),
75 pb_scratch(),
76 pb_versiondiff(),
77 pb_readonly(),
78 pb_factory(),
79 pstore(new PresetStore),
80 target_col(),
81 bank_col(),
82 vpaned_pos(),
83 vpaned_step(),
84 vpaned_target(),
85 options(options_),
86 in_current_preset(false),
87 load_in_progress(false),
88 on_map_conn()
89 /* widget pointers not initialized */ {
90 load_widget_pointers(bld);
91 gx_gui::GxBuilder::connect_gx_tooltip_handler(GTK_WIDGET(bank_treeview->gobj()));
92
93 // create actions
94 actions.new_bank = uimanager.add_action(
95 "NewBank", sigc::mem_fun(*this, &PresetWindow::on_new_bank));
96 UIManager::set_widget_action(new_preset_bank, actions.new_bank);
97 actions.save_changes = uimanager.add_action(
98 "Save", sigc::mem_fun(*this, &PresetWindow::on_preset_save));
99 UIManager::set_widget_action(save_preset, actions.save_changes);
100 actions.organize = uimanager.add_toggle_action("Organize");
101 actions.organize->signal_toggled().connect(
102 sigc::mem_fun(*this, &PresetWindow::on_organize));
103 UIManager::set_widget_action(organize_presets, actions.organize);
104 actions.online_preset_bank = uimanager.add_action(
105 "OnlineBank", sigc::mem_fun(*this, &PresetWindow::on_online_preset));
106 UIManager::set_widget_action(online_preset, actions.online_preset_bank);
107
108 // bank treeview
109 bank_treeview->set_model(Gtk::ListStore::create(bank_col));
110 bank_treeview->get_selection()->set_select_function(
111 sigc::mem_fun(*this, &PresetWindow::select_func));
112 bank_treeview->set_has_tooltip(true);
113 bank_treeview->signal_query_tooltip().connect(
114 sigc::mem_fun(*this, &PresetWindow::on_bank_query_tooltip));
115 pb_edit = bank_treeview->render_icon_pixbuf(Gtk::Stock::EDIT, Gtk::ICON_SIZE_MENU);
116 pb_del = bank_treeview->render_icon_pixbuf(Gtk::Stock::DELETE, Gtk::ICON_SIZE_MENU);
117 pb_scratch = Gdk::Pixbuf::create_from_file(options.get_style_filepath("scratch.png"));
118 pb_versiondiff = Gdk::Pixbuf::create_from_file(options.get_style_filepath("versiondiff.png"));
119 pb_readonly = Gdk::Pixbuf::create_from_file(options.get_style_filepath("readonly.png"));
120 pb_factory = Gdk::Pixbuf::create_from_file(options.get_style_filepath("factory.png"));
121 bank_treeview->set_row_separator_func(sigc::mem_fun(*this, &PresetWindow::is_row_separator));
122 bank_cellrenderer->signal_edited().connect(
123 sigc::bind(sigc::mem_fun(*this, &PresetWindow::on_bank_edited), bank_treeview));
124 bank_cellrenderer->signal_editing_canceled().connect(
125 sigc::bind(sigc::mem_fun(*this, &PresetWindow::on_edit_canceled), bank_column_bank));
126 bank_cellrenderer->signal_editing_started().connect(
127 sigc::bind(sigc::mem_fun(*this, &PresetWindow::on_editing_started), bank_treeview->get_model()));
128 bank_column_bank->set_cell_data_func(*bank_cellrenderer, sigc::mem_fun(*this, &PresetWindow::highlight_current_bank));
129
130 std::vector<Gtk::TargetEntry> listTargets;
131 listTargets.push_back(Gtk::TargetEntry("GTK_TREE_MODEL_ROW", Gtk::TARGET_SAME_WIDGET, MODELROW_TARGET));
132 listTargets.push_back(Gtk::TargetEntry("text/uri-list", Gtk::TARGET_OTHER_APP, URILIST_TARGET));
133 bank_treeview->enable_model_drag_source(listTargets, Gdk::BUTTON1_MASK, Gdk::ACTION_COPY);
134 bank_treeview->drag_source_add_text_targets(); // sets info == 0 (TEXT_TARGETS)
135 bank_treeview->signal_drag_motion().connect(sigc::mem_fun(*this, &PresetWindow::on_bank_drag_motion), false);
136 bank_treeview->enable_model_drag_dest(listTargets, Gdk::ACTION_COPY);
137 bank_treeview->signal_drag_data_received().connect(
138 sigc::mem_fun(*this, &PresetWindow::on_bank_drag_data_received));
139 bank_treeview->signal_drag_data_get().connect(
140 sigc::mem_fun(*this, &PresetWindow::on_bank_drag_data_get));
141 bank_treeview->signal_drag_begin().connect(
142 sigc::hide(sigc::mem_fun(machine, &gx_engine::GxMachineBase::bank_drag_begin)));
143 Glib::RefPtr<Gtk::TreeSelection> sel = bank_treeview->get_selection();
144 sel->signal_changed().connect(sigc::mem_fun(*this, &PresetWindow::on_bank_changed));
145 bank_treeview->signal_button_release_event().connect(sigc::mem_fun(*this, &PresetWindow::on_bank_button_release), true);
146 Glib::RefPtr<Gtk::TreeModel> ls = bank_treeview->get_model();
147 ls->signal_row_deleted().connect(sigc::mem_fun(*this, &PresetWindow::on_bank_reordered));
148
149 // preset treeview
150 preset_treeview->set_model(pstore);
151 preset_treeview->signal_drag_motion().connect(sigc::mem_fun(*this, &PresetWindow::on_preset_drag_motion), false);
152 preset_treeview->signal_drag_data_get().connect(sigc::mem_fun(*this, &PresetWindow::on_preset_drag_data_get));
153 preset_treeview->signal_row_activated().connect(sigc::mem_fun(*this, &PresetWindow::on_preset_row_activated));
154 preset_treeview->signal_button_release_event().connect(sigc::mem_fun(*this, &PresetWindow::on_preset_button_release), true);
155 pstore->signal_row_deleted().connect(sigc::mem_fun(*this, &PresetWindow::on_preset_reordered));
156 preset_treeview->get_selection()->set_select_function(
157 sigc::mem_fun(*this, &PresetWindow::select_func));
158 preset_treeview->signal_cursor_changed ().connect(sigc::mem_fun(*this, &PresetWindow::on_preset_changed));
159 preset_cellrenderer->signal_edited().connect(sigc::mem_fun(*this, &PresetWindow::on_preset_edited));
160 preset_cellrenderer->signal_editing_canceled().connect(
161 sigc::bind(sigc::mem_fun(*this, &PresetWindow::on_edit_canceled), preset_column_preset));
162 preset_cellrenderer->signal_editing_started().connect(
163 sigc::bind(
164 sigc::mem_fun(*this, &PresetWindow::on_editing_started),
165 Glib::RefPtr<Gtk::TreeModel>::cast_static(pstore)));
166 preset_column_preset->set_cell_data_func(
167 *preset_cellrenderer, sigc::mem_fun(*this, &PresetWindow::text_func));
168
169 // third colum
170 banks_combobox->signal_changed().connect(
171 sigc::mem_fun(*this, &PresetWindow::on_preset_combo_changed));
172 std::vector<Gtk::TargetEntry> listTargets3;
173 listTargets3.push_back(
174 Gtk::TargetEntry("application/x-guitarix-preset", Gtk::TARGET_SAME_APP, 0));
175 presets_target_treeview->enable_model_drag_dest(listTargets3, Gdk::ACTION_COPY);
176 presets_target_treeview->signal_drag_motion().connect(
177 sigc::mem_fun(*this, &PresetWindow::on_target_drag_motion), false);
178 presets_target_treeview->signal_drag_data_received().connect_notify(
179 sigc::mem_fun(*this, &PresetWindow::target_drag_data_received));
180 machine.signal_selection_changed().connect(
181 sigc::mem_fun(*this, &PresetWindow::on_selection_changed));
182 machine.signal_presetlist_changed().connect(
183 sigc::mem_fun(*this, &PresetWindow::on_presetlist_changed));
184
185 curl_global_init(CURL_GLOBAL_DEFAULT);
186 curl = curl_easy_init();
187 }
188
~PresetWindow()189 PresetWindow::~PresetWindow() {
190 curl_easy_cleanup(curl);
191 curl_global_cleanup();
192 }
193
194 /*
195 * bank / preset in model has changed
196 *
197 * redraw the banklist and presetlist to make sure the current
198 * selection is displayed.
199 * The save button state depends on the current bank.
200 */
on_selection_changed()201 void PresetWindow::on_selection_changed() {
202 Glib::ustring bank = get_current_bank();
203 in_current_preset = (!bank.empty() && bank == machine.get_current_bank());
204 Glib::RefPtr<Gtk::TreeModel> ls = bank_treeview->get_model();
205 // show active bank
206 Gtk::TreeNodeChildren ch = ls->children();
207 Glib::ustring active_bank = machine.get_current_bank();
208 for (Gtk::TreeIter it = ch.begin(); it != ch.end(); ++it) {
209 if (it->get_value(bank_col.name) == active_bank) {
210 if (!in_current_preset) {
211 bank_treeview->get_selection()->select(it);
212 // now in_current_preset == 1
213 }
214 // first time after program start sometimes does not work
215 // when using direct call (Gtk 3.24)
216 Glib::signal_idle().connect_once(
217 sigc::bind(
218 sigc::mem_fun1(bank_treeview, &MyTreeView::scroll_to_row),
219 ls->get_path(it)));
220 break;
221 }
222 }
223 if (in_current_preset) {
224 Glib::ustring active_preset = machine.get_current_name();
225 ch = pstore->children();
226 for (Gtk::TreeIter it = ch.begin(); it != ch.end(); ++it) {
227 if (it->get_value(pstore->col.name) == active_preset) {
228 preset_treeview->scroll_to_row(pstore->get_path(it));
229 break;
230 }
231 }
232 }
233 bank_treeview->queue_draw();
234 preset_treeview->queue_draw();
235 bool savable = false;
236 if (!organize_presets->get_active() && machine.setting_is_preset()) {
237 gx_system::PresetFileGui *pf = machine.get_current_bank_file();
238 if (pf && pf->is_mutable()) {
239 savable = true;
240 }
241 }
242 actions.save_changes->set_enabled(savable);
243 }
244
245 /**
246 * state of model has changed
247 *
248 * reload the bank list and try to preserve the state of the displayed
249 * lists
250 */
on_presetlist_changed()251 void PresetWindow::on_presetlist_changed() {
252 load_in_progress = true;
253 Glib::ustring current_bank = get_current_bank();
254 Glib::RefPtr<Gtk::ListStore> ls = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(bank_treeview->get_model());
255 ls->clear();
256 Gtk::TreeIter it, ifound, mfound;
257 Glib::ustring mbank = machine.get_current_bank();
258 int in_factory = false;
259 for (gx_engine::bank_iterator v = machine.bank_begin(); v != machine.bank_end(); ++v) {
260 if (!in_factory && v->get_type() == gx_system::PresetFile::PRESET_FACTORY) {
261 it = ls->append();
262 it->set_value(bank_col.tp, static_cast<int>(gx_system::PresetFile::PRESET_SEP));
263 in_factory = true;
264 }
265 it = ls->append();
266 set_row_for_presetfile(it, *v);
267 if (v->get_name() == current_bank) {
268 ifound = it;
269 }
270 if (!ifound && v->get_name() == mbank) {
271 mfound = it;
272 }
273 }
274 if (!ifound) {
275 ifound = mfound;
276 }
277 if (!ifound) {
278 pstore->clear();
279 preset_title->set_text("");
280 }
281 load_in_progress = false;
282 if (ifound) {
283 bank_treeview->get_selection()->select(ifound);
284 }
285 if (organize_presets->get_active()) {
286 reload_combo();
287 }
288 }
289
on_button_press_event(GdkEventButton * button_event)290 bool MyTreeView::on_button_press_event(GdkEventButton* button_event) {
291 bool r = Gtk::TreeView::on_button_press_event(button_event);
292 m_signal_row_clicked();
293 return r;
294 }
295
load_widget_pointers(Glib::RefPtr<gx_gui::GxBuilder> bld)296 void PresetWindow::load_widget_pointers(Glib::RefPtr<gx_gui::GxBuilder> bld) {
297 bld->find_widget("save_preset", save_preset);
298 bld->find_widget("new_preset_bank", new_preset_bank);
299 bld->find_widget("organize_presets", organize_presets);
300 bld->find_widget("online_preset", online_preset);
301 bld->find_widget_derived("bank_treeview", bank_treeview, sigc::ptr_fun(MyTreeView::create_from_builder));
302 bld->find_object("bank_cellrenderer", bank_cellrenderer);
303 bld->find_widget_derived("preset_treeview", preset_treeview, sigc::ptr_fun(MyTreeView::create_from_builder));
304 bld->find_object("preset_cellrenderer", preset_cellrenderer);
305 bld->find_widget("banks_combobox", banks_combobox);
306 bld->find_widget_derived("presets_target_treeview", presets_target_treeview, sigc::ptr_fun(MyTreeView::create_from_builder));
307 bld->find_widget("preset_title", preset_title);
308 bld->find_widget("presets_target_scrolledbox", presets_target_scrolledbox);
309 bld->find_object("bank_column_flags", bank_column_flags);
310 bld->find_object("bank_column_bank", bank_column_bank);
311 bld->find_object("bank_column_edit", bank_column_edit);
312 bld->find_object("bank_column_delete", bank_column_delete);
313 bld->find_object("bank_column_spacer", bank_column_spacer);
314 bld->find_object("preset_column_preset", preset_column_preset);
315 bld->find_object("preset_column_edit", preset_column_edit);
316 bld->find_object("preset_column_delete", preset_column_delete);
317 bld->find_object("preset_column_spacer", preset_column_spacer);
318 bld->find_widget("main_vpaned", main_vpaned);
319 bld->find_widget("preset_scrolledbox", preset_scrolledbox);
320 }
321
on_bank_query_tooltip(int x,int y,bool kb_tooltip,Glib::RefPtr<Gtk::Tooltip> tooltip)322 bool PresetWindow::on_bank_query_tooltip(int x, int y, bool kb_tooltip, Glib::RefPtr<Gtk::Tooltip> tooltip) {
323 Gtk::TreeIter it;
324 if (!bank_treeview->get_tooltip_context_iter(x, y, kb_tooltip, it)) {
325 return false;
326 }
327 int tp = it->get_value(bank_col.tp);
328 if (tp == gx_system::PresetFile::PRESET_SEP) {
329 return false;
330 }
331 Gtk::TreeModel::Path pt;
332 Gtk::TreeViewColumn *col;
333 int dx, dy;
334 if (!bank_treeview->get_path_at_pos(x, y, pt, col, dx, dy)) {
335 col = 0;
336 }
337 Glib::ustring nm = it->get_value(bank_col.name);
338 if (nm.empty()) {
339 return false;
340 }
341 gx_system::PresetFileGui *f = machine.get_bank_file(nm);
342 if (col == bank_column_flags || col == bank_column_bank) {
343 if (tp == gx_system::PresetFile::PRESET_FILE) {
344 if (f->get_flags() & gx_system::PRESET_FLAG_INVALID) {
345 tooltip->set_text(_("damaged bank file; click to delete"));
346 } else if (f->get_flags() & gx_system::PRESET_FLAG_VERSIONDIFF) {
347 tooltip->set_text(
348 Glib::ustring::compose(_("wrong format version (is %1, need %2)\nclick to convert"),
349 f->get_header().version_string(),
350 f->get_header().current_version_string()));
351 } else if (f->get_flags() & gx_system::PRESET_FLAG_READONLY) {
352 tooltip->set_text(_("readonly bank, click to change to read-write"));
353 } else if (col == bank_column_flags){
354 tooltip->set_text(_("click to set to readonly"));
355 } else {
356 return false;
357 }
358 } else if (tp == gx_system::PresetFile::PRESET_FACTORY) {
359 tooltip->set_text(_("predefined factory preset bank"));
360 } else if (tp == gx_system::PresetFile::PRESET_SCRATCH) {
361 tooltip->set_text(_("scratch preset bank: changes will be persistent (without explicit saving)"));
362 } else {
363 return false;
364 }
365 } else if (col == bank_column_edit) {
366 if (f->get_flags()) {
367 return false;
368 }
369 tooltip->set_text(_("click to edit the bank name"));
370 } else if (col == bank_column_delete) {
371 if (f->get_flags()) {
372 return false;
373 }
374 tooltip->set_text(_("click to delete the bank"));
375 } else {
376 return false;
377 }
378 bank_treeview->set_tooltip_cell(tooltip, &pt, col, 0);
379 return true;
380 }
381
on_preset_row_activated(const Gtk::TreePath & path,Gtk::TreeViewColumn * column)382 void PresetWindow::on_preset_row_activated(const Gtk::TreePath& path, Gtk::TreeViewColumn* column) {
383 bool in_organize = actions.organize->get_active();
384 actions.presets->set_active(false);
385 if (in_organize) {
386 preset_treeview->get_selection()->select(path);
387 }
388 }
389
on_preset_drag_data_get(const Glib::RefPtr<Gdk::DragContext> & context,Gtk::SelectionData & selection,int info,int timestamp)390 void PresetWindow::on_preset_drag_data_get(const Glib::RefPtr<Gdk::DragContext>& context, Gtk::SelectionData& selection, int info, int timestamp) {
391 if (selection.get_target() == "application/x-guitarix-preset") {
392 Gtk::TreeModel::Path path;
393 Gtk::TreeViewColumn *focus_column;
394 preset_treeview->get_cursor(path, focus_column);
395 Glib::ustring data = pstore->get_iter(path)->get_value(pstore->col.name);
396 selection.set("application/x-guitarix-preset", data);
397 }
398 }
399
on_bank_drag_data_get(const Glib::RefPtr<Gdk::DragContext> & context,Gtk::SelectionData & selection,int info,int timestamp)400 void PresetWindow::on_bank_drag_data_get(const Glib::RefPtr<Gdk::DragContext>& context, Gtk::SelectionData& selection, int info, int timestamp) {
401 if (info != URILIST_TARGET && info != TEXT_TARGETS) {
402 return;
403 }
404 Gtk::TreeModel::Path path;
405 Gtk::TreeViewColumn *focus_column;
406 bank_treeview->get_cursor(path, focus_column);
407 Glib::RefPtr<Gio::File> f =
408 Gio::File::create_for_path(
409 machine.bank_get_filename(
410 bank_treeview->get_model()->get_iter(path)->get_value(bank_col.name)));
411 if (info == TEXT_TARGETS) {
412 selection.set_text(f->get_path());
413 } else {
414 std::vector<Glib::ustring> uris;
415 uris.push_back(f->get_uri());
416 selection.set_uris(uris);
417 }
418 }
419
on_bank_drag_data_received(const Glib::RefPtr<Gdk::DragContext> & context,int x,int y,const Gtk::SelectionData & data,guint info,guint timestamp)420 void PresetWindow::on_bank_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, const Gtk::SelectionData& data, guint info, guint timestamp) {
421 if (info != URILIST_TARGET) {
422 return;
423 }
424 bool is_move = context->get_selected_action() == Gdk::ACTION_MOVE;
425 bool success = false;
426 std::vector<Glib::ustring> uris = data.get_uris();
427 Gtk::TreePath pt;
428 Gtk::TreeViewDropPosition dst;
429 int position = 0;
430 if (PresetWindow::bank_find_drop_position(x, y, pt, dst)) {
431 position = pt[0];
432 if (dst == Gtk::TREE_VIEW_DROP_AFTER) {
433 position += 1;
434 }
435 }
436 for (std::vector<Glib::ustring>::iterator i = uris.begin(); i != uris.end(); ++i) {
437 machine.bank_insert_uri(*i, is_move, position);
438 }
439 context->drag_finish(success, false, timestamp);
440 }
441
get_combo_selection()442 Glib::ustring PresetWindow::get_combo_selection() {
443 Gtk::TreeIter idx = banks_combobox->get_active();
444 if (!idx) {
445 return "";
446 }
447 return idx->get_value(target_col.name);
448 }
449
450 /*
451 ** dnd target
452 */
453
target_drag_data_received(const Glib::RefPtr<Gdk::DragContext> & context,int x,int y,const Gtk::SelectionData & data,guint info,guint timestamp)454 void PresetWindow::target_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, const Gtk::SelectionData& data, guint info, guint timestamp) {
455 Glib::ustring bank = get_combo_selection();
456 if (bank.empty()) {
457 presets_target_treeview->signal_drag_data_received().emission_stop();
458 return;
459 }
460 gx_system::PresetFileGui& fl = *machine.get_bank_file(bank);
461 Glib::ustring srcnm = data.get_data_as_string();
462 Glib::ustring dstnm = srcnm;
463 int n = 1;
464 while (fl.has_entry(dstnm)) {
465 dstnm = srcnm + "-" + gx_system::to_string(n);
466 n += 1;
467 }
468 Glib::ustring src_bank = get_current_bank();
469 gx_system::PresetFileGui& pf = *machine.bank_get_file(src_bank);
470 if (src_bank == bank) {
471 gx_print_error("preset", "can't copy inside the same bank");
472 return;
473 }
474 Gtk::TreeModel::Path pt;
475 Gtk::TreeViewDropPosition dst;
476 if (!presets_target_treeview->get_dest_row_at_pos(x, y, pt, dst)) {
477 machine.pf_append(pf, srcnm, fl, dstnm);
478 } else {
479 Gtk::TreeIter it = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(presets_target_treeview->get_model())->get_iter(pt);
480 if (dst == Gtk::TREE_VIEW_DROP_BEFORE || dst == Gtk::TREE_VIEW_DROP_INTO_OR_BEFORE) {
481 machine.pf_insert_before(pf, srcnm, fl, it->get_value(target_col.name), dstnm);
482 } else { // gtk.TREE_VIEW_DROP_INTO_OR_AFTER, gtk.TREE_VIEW_DROP_AFTER
483 machine.pf_insert_after(pf, srcnm, fl, it->get_value(target_col.name), dstnm);
484 }
485 }
486 if (context->get_actions() == Gdk::ACTION_MOVE) {
487 machine.erase_preset(pf, srcnm);
488 }
489 if (src_bank == bank) {
490 on_bank_changed();
491 }
492 }
493
change_drag_dst_before_or_after(Gtk::TreeViewDropPosition & dst)494 inline void change_drag_dst_before_or_after(Gtk::TreeViewDropPosition& dst) {
495 if (dst == Gtk::TREE_VIEW_DROP_INTO_OR_BEFORE) {
496 dst = Gtk::TREE_VIEW_DROP_BEFORE;
497 } else if (dst == Gtk::TREE_VIEW_DROP_INTO_OR_AFTER) {
498 dst = Gtk::TREE_VIEW_DROP_AFTER;
499 }
500 }
501
on_target_drag_motion(const Glib::RefPtr<Gdk::DragContext> & context,int x,int y,guint timestamp)502 bool PresetWindow::on_target_drag_motion(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, guint timestamp) {
503 Gtk::Widget *source_widget = Gtk::Widget::drag_get_source_widget(context);
504 if (source_widget != preset_treeview || get_combo_selection().empty()) {
505 context->drag_status((Gdk::DragAction)0, timestamp);
506 return true;
507 }
508 presets_target_treeview->on_drag_motion(context, x, y, timestamp);
509 Gtk::TreeIter it = get_current_bank_iter();
510 int tp = it->get_value(bank_col.tp);
511 Glib::ustring nm = it->get_value(bank_col.name);
512 if ((tp != gx_system::PresetFile::PRESET_SCRATCH && tp != gx_system::PresetFile::PRESET_FILE) || machine.get_bank_file(nm)->get_flags() ||
513 get_combo_selection() == nm) {
514 context->drag_status(Gdk::ACTION_COPY, timestamp);
515 }
516 Gtk::TreeModel::Path pt;
517 Gtk::TreeViewDropPosition dst;
518 if (presets_target_treeview->get_dest_row_at_pos(x, y, pt, dst)) {
519 change_drag_dst_before_or_after(dst);
520 } else {
521 Gtk::TreeModel::Path start;
522 presets_target_treeview->get_visible_range(start, pt);
523 dst = Gtk::TREE_VIEW_DROP_AFTER;
524 }
525 presets_target_treeview->set_drag_dest_row(pt, dst);
526 return true;
527 }
528
reload_combo()529 void PresetWindow::reload_combo() {
530 Glib::ustring old = get_combo_selection();
531 Glib::RefPtr<Gtk::ListStore> ls = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(banks_combobox->get_model());
532 ls->clear();
533 int n = 0;
534 int nn = -1;
535 for (gx_engine::bank_iterator i = machine.bank_begin(); i != machine.bank_end(); ++i) {
536 int tp = i->get_type();
537 if (tp != gx_system::PresetFile::PRESET_FILE && tp != gx_system::PresetFile::PRESET_SCRATCH) {
538 continue;
539 }
540 if (i->get_flags()) {
541 continue;
542 }
543 Glib::ustring s = i->get_name();
544 ls->append()->set_value(bank_col.name, s);
545 if (s == old) {
546 nn = n;
547 }
548 n += 1;
549 }
550 banks_combobox->set_active(nn);
551 }
552
on_preset_combo_changed()553 void PresetWindow::on_preset_combo_changed() {
554 Glib::RefPtr<Gtk::ListStore> ls = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(presets_target_treeview->get_model());
555 ls->clear();
556 Glib::ustring nm = get_combo_selection();
557 if (nm.empty()) {
558 return;
559 }
560 gx_system::PresetFileGui& f = *machine.get_bank_file(nm);
561 for (gx_system::PresetFile::iterator i = f.begin(); i != f.end(); ++i) {
562 ls->append()->set_value(bank_col.name, i->name);
563 }
564 }
565
566 /*
567 ** name edit
568 */
569
select_func(const Glib::RefPtr<Gtk::TreeModel> & model,const Gtk::TreePath & path,bool path_currently_selected)570 bool PresetWindow::select_func(const Glib::RefPtr<Gtk::TreeModel>& model, const Gtk::TreePath& path, bool path_currently_selected) {
571 Glib::ustring s = model->get_iter(path)->get_value(bank_col.name);
572 if (s.empty()) {
573 return false;
574 }
575 return true;
576 }
577
on_editing_started(const Gtk::CellEditable * edit,const Glib::ustring & path,Glib::RefPtr<Gtk::TreeModel> & model)578 void PresetWindow::on_editing_started(const Gtk::CellEditable* edit, const Glib::ustring& path, Glib::RefPtr<Gtk::TreeModel>& model) {
579 Glib::ustring s = model->get_iter(path)->get_value(bank_col.name);
580 if (s.empty()) {
581 dynamic_cast<Gtk::Entry*>(const_cast<Gtk::CellEditable*>(edit))->set_text("");
582 } else {
583 dynamic_cast<Gtk::Entry*>(const_cast<Gtk::CellEditable*>(edit))->set_text(s);
584 }
585 dynamic_cast<Gtk::Window*>(main_vpaned->get_toplevel())->remove_accel_group(accelgroup);
586 }
587
reset_edit(Gtk::TreeViewColumn & col)588 void PresetWindow::reset_edit(Gtk::TreeViewColumn& col) {
589 if (edit_iter) {
590 Glib::RefPtr<Gtk::ListStore> ls = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(col.get_tree_view()->get_model());
591 ls->erase(edit_iter);
592 edit_iter = ls->children().end();
593 }
594 Gtk::CellRendererText& cell = *dynamic_cast<Gtk::CellRendererText*>(col.get_first_cell());
595 cell.property_editable().set_value(false);
596 col.set_min_width(0);
597 col.queue_resize();
598 in_edit = false;
599 if (!organize_presets->get_active()) {
600 actions.save_changes->set_enabled(true);
601 }
602 dynamic_cast<Gtk::Window*>(main_vpaned->get_toplevel())->add_accel_group(accelgroup);
603 }
604
on_edit_canceled(Gtk::TreeViewColumn * col)605 void PresetWindow::on_edit_canceled(Gtk::TreeViewColumn *col) {
606 reset_edit(*col);
607 }
608
start_edit(const Gtk::TreeModel::Path & pt,Gtk::TreeViewColumn & col,Gtk::CellRenderer & cell)609 void PresetWindow::start_edit(const Gtk::TreeModel::Path& pt, Gtk::TreeViewColumn& col, Gtk::CellRenderer& cell) {
610 col.set_min_width(100);
611 dynamic_cast<Gtk::CellRendererText*>(&cell)->property_editable().set_value(true);
612 col.get_tree_view()->set_cursor(pt, col, cell, true);
613 }
614
615 /*
616 ** list of banks
617 */
618
set_cell_text(Gtk::Widget * widget,Gtk::CellRenderer * cell,const Glib::ustring & text,bool selected)619 void PresetWindow::set_cell_text(Gtk::Widget *widget, Gtk::CellRenderer *cell, const Glib::ustring& text, bool selected) {
620 cell->set_property("text", text);
621 Gtk::CellRendererText *tc = dynamic_cast<Gtk::CellRendererText*>(cell);
622 if (selected) {
623 Glib::RefPtr<Gtk::StyleContext> context = widget->get_style_context();
624 tc->property_foreground_rgba().set_value(
625 context->get_color(Gtk::STATE_FLAG_CHECKED));
626 Pango::FontDescription font = context->get_font(Gtk::STATE_FLAG_CHECKED);
627 tc->property_weight().set_value(font.get_weight());
628 } else{
629 tc->property_foreground_set().set_value(false);
630 tc->property_weight().set_value(false);
631 }
632 }
633
highlight_current_bank(Gtk::CellRenderer * cell,const Gtk::TreeModel::iterator & iter)634 void PresetWindow::highlight_current_bank(Gtk::CellRenderer *cell, const Gtk::TreeModel::iterator& iter) {
635 Glib::ustring t = iter->get_value(bank_col.name);
636 if (t.empty()) {
637 return;
638 }
639 bool selected = (machine.setting_is_preset() && t == machine.get_current_bank());
640 if (!organize_presets->get_active()) {
641 int idx = *bank_treeview->get_model()->get_path(iter).begin();
642 if (machine.get_bank_file(t)->get_type() & gx_system::PresetFile::PRESET_FACTORY) {
643 // correction for separator before factory rows
644 idx -= 1;
645 }
646 char c = KeySwitcher::bank_idx_to_char(idx, machine.bank_size());
647 if (c) {
648 t = Glib::ustring::compose("%1: %2", c, t);
649 } else {
650 t = " " + t;
651 }
652 }
653 set_cell_text(bank_treeview, cell, t, selected);
654 }
655
get_current_bank()656 Glib::ustring PresetWindow::get_current_bank() {
657 Gtk::TreeIter it = get_current_bank_iter();
658 if (!it) {
659 return "";
660 }
661 return it->get_value(bank_col.name);
662 }
663
run_message_dialog(Gtk::Widget & w,const Glib::ustring & msg)664 bool PresetWindow::run_message_dialog(Gtk::Widget& w, const Glib::ustring& msg) {
665 Gtk::MessageDialog d(*dynamic_cast<Gtk::Window*>(w.get_toplevel()), msg, false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL, true);
666 d.set_position(Gtk::WIN_POS_MOUSE);
667 return d.run() == Gtk::RESPONSE_OK;
668 }
669
on_bank_button_release(GdkEventButton * ev)670 bool PresetWindow::on_bank_button_release(GdkEventButton *ev) {
671 // edit bank name / delete bank
672 Gtk::TreeModel::Path pt;
673 Gtk::TreeViewColumn *col;
674 int dx, dy;
675 if (!bank_treeview->get_path_at_pos(ev->x, ev->y, pt, col, dx, dy)) {
676 return false;
677 }
678 Gtk::TreeModel::Path path;
679 Gtk::TreeViewColumn *focus_column;
680 bank_treeview->get_cursor(path, focus_column);
681 if (col != focus_column || pt != path) {
682 return false;
683 }
684 Glib::RefPtr<Gtk::ListStore> ls = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(bank_treeview->get_model());
685 Gtk::TreeModel::iterator it = ls->get_iter(pt);
686 Glib::ustring nm = it->get_value(bank_col.name);
687 int tp = it->get_value(bank_col.tp);
688 if (col == bank_column_flags) {
689 if (tp == gx_system::PresetFile::PRESET_SCRATCH || tp == gx_system::PresetFile::PRESET_FILE) {
690 int flags = machine.get_bank_file(nm)->get_flags();
691 gx_system::PresetFileGui *f = machine.get_bank_file(nm);
692 if (flags == 0 && tp == gx_system::PresetFile::PRESET_FILE) {
693 /*if (run_message_dialog(*bank_treeview, "set bank " + nm + " to readonly?")) {*/
694 machine.bank_set_flag(f, gx_system::PRESET_FLAG_READONLY, true);
695 } else if (flags == gx_system::PRESET_FLAG_VERSIONDIFF) {
696 if (run_message_dialog(*bank_treeview, "convert bank " + nm + " to new version?")) {
697 machine.convert_preset(*f);
698 }
699 } else if (flags == gx_system::PRESET_FLAG_READONLY) {
700 /*if (run_message_dialog(*bank_treeview, "set bank " + nm + " to read/write?")) {*/
701 machine.bank_set_flag(f, gx_system::PRESET_FLAG_READONLY, false);
702 } else if (flags == (gx_system::PRESET_FLAG_READONLY | gx_system::PRESET_FLAG_VERSIONDIFF)) {
703 if (run_message_dialog(*bank_treeview, "convert readonly bank " + nm + " to new version?")) {
704 machine.convert_preset(*f);
705 }
706 } else if (flags & gx_system::PRESET_FLAG_INVALID) {
707 if (run_message_dialog(
708 *bank_treeview, "delete damaged bank " + nm + "?"
709 " Export it before deleting and ask in the"
710 " guitarix online forum if you want to try to repair it!")) {
711 machine.bank_remove(nm);
712 }
713 }
714 }
715 return false;
716 }
717 if (tp != gx_system::PresetFile::PRESET_FILE || machine.get_bank_file(nm)->get_flags()) {
718 return false;
719 }
720 if (col == bank_column_edit) {
721 start_edit(pt, *bank_column_bank, *bank_cellrenderer);
722 } else if (col == bank_column_delete) {
723 Gtk::MessageDialog d(*dynamic_cast<Gtk::Window*>(bank_treeview->get_toplevel()), "delete bank " + nm + "?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL, true);
724 d.set_position(Gtk::WIN_POS_MOUSE);
725 if (d.run() == Gtk::RESPONSE_OK) {
726 machine.bank_remove(nm);
727 }
728 }
729 return false;
730 }
731
on_bank_edited(const Glib::ustring & path,const Glib::ustring & newtext,Gtk::TreeView * w)732 void PresetWindow::on_bank_edited(const Glib::ustring& path, const Glib::ustring& newtext, Gtk::TreeView* w) {
733 Gtk::TreeIter sel = w->get_model()->get_iter(path);
734 Glib::ustring oldname = sel->get_value(bank_col.name);
735 Glib::ustring newname = newtext;
736 gx_system::strip(newname);
737 if (newname.empty() || newname == oldname) {
738 reset_edit(*bank_column_bank);
739 return;
740 }
741 Glib::RefPtr<Gtk::ListStore> ls = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(banks_combobox->get_model());
742 if (edit_iter) {
743 edit_iter = Gtk::TreeIter();
744 machine.bank_insert_new(newname);
745 } else {
746 machine.rename_bank(oldname, newname);
747 }
748 reset_edit(*bank_column_bank);
749 }
750
is_row_separator(const Glib::RefPtr<Gtk::TreeModel> & model,const Gtk::TreeModel::iterator & iter)751 bool PresetWindow::is_row_separator(const Glib::RefPtr<Gtk::TreeModel>& model, const Gtk::TreeModel::iterator& iter) {
752 return iter->get_value(bank_col.tp) == gx_system::PresetFile::PRESET_SEP;
753 }
754
on_new_bank()755 void PresetWindow::on_new_bank() {
756 Glib::RefPtr<Gtk::ListStore> m = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(bank_treeview->get_model());
757 edit_iter = m->prepend();
758 edit_iter->set_value(bank_col.tp, static_cast<int>(gx_system::PresetFile::PRESET_FILE));
759 in_edit = true;
760 start_edit(m->get_path(edit_iter), *bank_column_bank, *bank_cellrenderer);
761 }
762
763 // Online Preset Downloader
764
resolve_hostname()765 Glib::ustring PresetWindow::resolve_hostname() {
766 static Glib::ustring hostname = "localhost";
767 if (! machine.get_jack()) {
768 hostname = Gio::Resolver::get_default()->lookup_by_address
769 (Gio::InetAddress::create( machine.get_options().get_rpcaddress()));
770 }
771 return hostname;
772 }
773
download_file(Glib::ustring from_uri,Glib::ustring to_path)774 bool PresetWindow::download_file(Glib::ustring from_uri, Glib::ustring to_path) {
775
776 CURLcode res;
777 FILE *out;
778 out = fopen(to_path.c_str(), "wb");
779
780 curl_easy_setopt(curl, CURLOPT_WRITEDATA, out);
781 curl_easy_setopt(curl, CURLOPT_URL, from_uri.c_str());
782 res = curl_easy_perform(curl);
783 if(CURLE_OK == res) {
784 char *ct = NULL;
785 res = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
786 if (strstr(ct, "application/json")!= NULL ) {
787 gx_print_info( "download_file", from_uri);
788 } else if (strstr(ct, "application/octet-stream")!= NULL) {
789 gx_print_info( "download_preset", from_uri);
790 } else {
791 res = CURLE_CONV_FAILED;
792 }
793 }
794 curl_easy_reset(curl);
795 fclose(out);
796 if(res != CURLE_OK) {
797 remove(to_path.c_str());
798 gx_print_error( "download_file", Glib::ustring::compose("curl_easy_perform() failed: %1", curl_easy_strerror(res)));
799 return false;
800 }
801 return true;
802 }
803
downloadPreset(Gtk::Menu * presetMenu,std::string uri)804 void PresetWindow::downloadPreset(Gtk::Menu *presetMenu,std::string uri) {
805
806 std::string::size_type n = uri.find_last_of('/');
807 if (n != std::string::npos) {
808 std::string fn = uri.substr(n);
809 std::string ff = "/tmp"+fn;
810
811 if (download_file(uri, ff)) {
812 Glib::RefPtr<Gtk::ListStore> ls = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(bank_treeview->get_model());
813 machine.bank_insert_uri(Glib::filename_to_uri(ff, resolve_hostname()), false, 0);
814 }
815 } else {
816 gx_print_error("downloadPreset", _("can't download preset from https://musical-artifacts.com/"));
817 }
818 }
819
read_preset_menu()820 void PresetWindow::read_preset_menu() {
821 ifstream is(options.get_online_config_filename());
822 gx_system::JsonParser jp(&is);
823 try {
824 jp.next(gx_system::JsonParser::begin_array);
825 do {
826 std::string NAME_;
827 std::string FILE_;
828 std::string INFO_;
829 std::string AUTHOR_;
830 jp.next(gx_system::JsonParser::begin_object);
831 do {
832 jp.next(gx_system::JsonParser::value_key);
833 if (jp.current_value() == "name") {
834 jp.read_kv("name", NAME_);
835 } else if (jp.current_value() == "description") {
836 jp.read_kv("description", INFO_);
837 } else if (jp.current_value() == "author") {
838 jp.read_kv("author", AUTHOR_);
839 } else if (jp.current_value() == "file") {
840 jp.read_kv("file", FILE_);
841 } else {
842 jp.skip_object();
843 }
844 } while (jp.peek() == gx_system::JsonParser::value_key);
845 jp.next(gx_system::JsonParser::end_object);
846 INFO_ += "Author : " + AUTHOR_;
847 olp.push_back(std::tuple<std::string,std::string,std::string>(NAME_,FILE_,INFO_));
848 } while (jp.peek() == gx_system::JsonParser::begin_object);
849 } catch (gx_system::JsonException& e) {
850 cerr << "JsonException: " << e.what() << ": '" << jp.current_value() << "'" << endl;
851 assert(false);
852 }
853 }
854
popup_pos(int & x,int & y,bool & push_in)855 void PresetWindow::popup_pos( int& x, int& y, bool& push_in ){
856 online_preset->get_window()->get_origin( x, y );
857 x +=150;
858 y -= 450;
859 push_in = false;
860 }
861
create_preset_menu()862 void PresetWindow::create_preset_menu() {
863
864 static bool read_new = true;
865 if (read_new) {
866 read_preset_menu();
867 read_new = false;
868 }
869
870 Gtk::MenuItem* item;
871 Gtk::Menu *presetMenu = Gtk::manage(new Gtk::Menu());
872 presetMenu->set_size_request (-1, 600);
873 for(std::vector<std::tuple<std::string,std::string,std::string> >::iterator it = olp.begin(); it != olp.end(); it++) {
874 item = Gtk::manage(new Gtk::MenuItem(get<0>(*it), true));
875 item->set_tooltip_text(get<2>(*it));
876 std::string f = get<1>(*it);
877 item->signal_activate().connect(
878 sigc::bind(sigc::mem_fun(*this, &PresetWindow::downloadPreset),
879 presetMenu, f));
880 presetMenu->append(*item);
881 }
882 presetMenu->show_all();
883 presetMenu->popup(Gtk::Menu::SlotPositionCalc(sigc::mem_fun(
884 *this, &PresetWindow::popup_pos ) ),0,gtk_get_current_event_time());
885 }
886
show_online_preset()887 void PresetWindow::show_online_preset() {
888 static bool load_new = true;
889 Glib::RefPtr<Gio::File> dest = Gio::File::create_for_path(options.get_online_config_filename());
890 bool exists = dest->query_exists();
891 if (load_new || !exists) {
892 load_new = false;
893 bool reload = false;
894 gx_gui::WaitCursor(dynamic_cast<Gtk::Window*>(main_vpaned->get_toplevel()));
895 if (exists) {
896 Gtk::MessageDialog d(*dynamic_cast<Gtk::Window*>(online_preset->get_toplevel()),
897 "Do you want to check for new presets from\n https://musical-artifacts.com ?\n"
898 "Note, that may take a while",
899 false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, true);
900 d.set_position(Gtk::WIN_POS_MOUSE);
901 if (d.run() == Gtk::RESPONSE_YES) {
902 reload = true;
903 }
904 }
905 if (!exists || reload) {
906 if (!download_file("https://musical-artifacts.com/artifacts.json?apps=guitarix", options.get_online_config_filename())) {
907 return;
908 }
909 }
910 }
911 create_preset_menu();
912 }
913
on_online_preset()914 void PresetWindow::on_online_preset() {
915 Glib::signal_idle().connect_once(sigc::mem_fun(*this, &PresetWindow::show_online_preset));
916 }
917
bank_drag_moveable(Gtk::TreePath pt)918 bool PresetWindow::bank_drag_moveable(Gtk::TreePath pt) {
919 if (!pt) {
920 return false;
921 }
922 Gtk::TreeIter it = bank_treeview->get_model()->get_iter(pt);
923 if (!it) {
924 return false;
925 }
926 string nm = it->get_value(bank_col.name);
927 if (nm.empty()) {
928 return false;
929 }
930 return machine.get_bank_file(nm)->is_moveable();
931 }
932
bank_find_drop_position(int x,int y,Gtk::TreeModel::Path & pt,Gtk::TreeViewDropPosition & dst)933 bool PresetWindow::bank_find_drop_position(int x, int y, Gtk::TreeModel::Path& pt, Gtk::TreeViewDropPosition& dst) {
934 if (bank_treeview->get_dest_row_at_pos(x, y, pt, dst)) {
935 change_drag_dst_before_or_after(dst);
936 } else {
937 Gtk::TreeModel::Path start;
938 preset_treeview->get_visible_range(start, pt);
939 dst = Gtk::TREE_VIEW_DROP_AFTER;
940 }
941 if (!bank_drag_moveable(pt)) {
942 if (dst == Gtk::TREE_VIEW_DROP_AFTER) {
943 pt.next();
944 if (bank_drag_moveable(pt)) {
945 dst = Gtk::TREE_VIEW_DROP_BEFORE;
946 } else {
947 return false;
948 }
949 } else {
950 if (pt.prev()) {
951 if (bank_drag_moveable(pt)) {
952 dst = Gtk::TREE_VIEW_DROP_AFTER;
953 } else {
954 return false;
955 }
956 }
957 }
958 }
959 return true;
960 }
961
on_bank_drag_motion(const Glib::RefPtr<Gdk::DragContext> & context,int x,int y,guint timestamp)962 bool PresetWindow::on_bank_drag_motion(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, guint timestamp) {
963 Gtk::Widget *source_widget = Gtk::Widget::drag_get_source_widget(context);
964 if (source_widget && source_widget != bank_treeview) {
965 // other window
966 context->drag_status((Gdk::DragAction)0, timestamp);
967 return true;
968 }
969 Gdk::DragAction action;
970 if (!source_widget) {
971 // URI from other application
972 if (context->get_suggested_action() & Gdk::ACTION_MOVE) {
973 action = Gdk::ACTION_MOVE;
974 } else {
975 action = Gdk::ACTION_COPY;
976 }
977 } else {
978 // reorder
979 Gtk::TreeIter it = get_current_bank_iter();
980 if (!it) {
981 // unknown source drag bank
982 context->drag_status((Gdk::DragAction)0, timestamp);
983 return true;
984 }
985 if (!machine.get_bank_file(it->get_value(bank_col.name))->is_moveable()) {
986 context->drag_status((Gdk::DragAction)0, timestamp);
987 return true;
988 }
989 action = Gdk::ACTION_MOVE;
990 }
991 Gtk::TreeModel::Path pt;
992 Gtk::TreeViewDropPosition dst;
993 if (PresetWindow::bank_find_drop_position(x, y, pt, dst)) {
994 bank_treeview->set_drag_dest_row(pt, dst);
995 } else {
996 action = (Gdk::DragAction)0;
997 bank_treeview->unset_drag_dest_row();
998 }
999 context->drag_status(action, timestamp);
1000 return true;
1001 }
1002
on_bank_changed()1003 void PresetWindow::on_bank_changed() {
1004 if (load_in_progress) {
1005 return;
1006 }
1007 load_in_progress = true;
1008 pstore->clear();
1009 Gtk::TreeIter it = get_current_bank_iter();
1010 if (!it) {
1011 preset_title->set_text("");
1012 in_current_preset = false;
1013 load_in_progress = false;
1014 return;
1015 }
1016 Glib::ustring nm = it->get_value(bank_col.name);
1017 preset_title->set_text(nm);
1018 in_current_preset = (nm == machine.get_current_bank());
1019 Gtk::TreeIter i;
1020 gx_system::PresetFileGui& ll = *machine.get_bank_file(nm);
1021 if ((ll.get_flags() & gx_system::PRESET_FLAG_VERSIONDIFF) ||
1022 ((ll.get_flags() & gx_system::PRESET_FLAG_READONLY) && !actions.organize->get_active())) {
1023 preset_treeview->unset_rows_drag_source();
1024 } else {
1025 preset_treeview->unset_rows_drag_source(); //FIXME: needed?
1026 preset_treeview->set_reorderable(true);
1027 std::vector<Gtk::TargetEntry> listTargets2;
1028 listTargets2.push_back(Gtk::TargetEntry("GTK_TREE_MODEL_ROW", Gtk::TARGET_SAME_WIDGET, 0));
1029 listTargets2.push_back(Gtk::TargetEntry("application/x-guitarix-preset", Gtk::TARGET_SAME_APP, 1));
1030 preset_treeview->enable_model_drag_source(listTargets2, Gdk::BUTTON1_MASK, Gdk::ACTION_COPY|Gdk::ACTION_MOVE);
1031 }
1032 bool modifiable = ll.is_mutable();
1033 Gtk::TreeIter first_row;
1034 Gtk::TreePath sel_path;
1035 for (gx_system::PresetFile::iterator s = ll.begin(); s != ll.end(); ++s) {
1036 i = pstore->append();
1037 if (!first_row) {
1038 first_row = i;
1039 }
1040 i->set_value(pstore->col.name, s->name);
1041 if (modifiable) {
1042 i->set_value(pstore->col.edit_pb, pb_edit);
1043 i->set_value(pstore->col.del_pb, pb_del);
1044 }
1045 if (in_current_preset && s->name == machine.get_current_name()) {
1046 if (preset_treeview->get_mapped()) {
1047 sel_path = pstore->get_path(i);
1048 }
1049 }
1050 }
1051 if (modifiable) {
1052 i = pstore->append();
1053 if (!first_row) {
1054 first_row = i;
1055 }
1056 }
1057 if (first_row) {
1058 // when the treeview widget gets the focus with no cursor set,
1059 // it will automatically be set on the first row and trigger a
1060 // selection of the first list entry. So the side effect will
1061 // be that unexpectedly the first preset will be set if one
1062 // clicks on some inactive part of the widget. So make sure
1063 // the cursor is set:
1064 preset_treeview->set_cursor(pstore->get_path(first_row));
1065 preset_treeview->get_selection()->unselect(first_row);
1066 }
1067 if (sel_path) {
1068 preset_treeview->scroll_to_row(sel_path);
1069 }
1070 load_in_progress = false;
1071 }
1072
set_row_for_presetfile(Gtk::TreeIter i,gx_system::PresetFileGui * f)1073 void PresetWindow::set_row_for_presetfile(Gtk::TreeIter i, gx_system::PresetFileGui *f) {
1074 i->set_value(bank_col.name, f->get_name());
1075 if (f->get_flags() & gx_system::PRESET_FLAG_INVALID) {
1076 i->set_value(bank_col.type_pb, pb_del);
1077 } else if (f->get_flags() & gx_system::PRESET_FLAG_VERSIONDIFF) {
1078 i->set_value(bank_col.type_pb, pb_versiondiff);
1079 } else if (f->get_flags() & gx_system::PRESET_FLAG_READONLY) {
1080 i->set_value(bank_col.type_pb, pb_readonly);
1081 }
1082 int tp = f->get_type();
1083 i->set_value(bank_col.tp, tp);
1084 if (tp == gx_system::PresetFile::PRESET_SCRATCH) {
1085 i->set_value(bank_col.type_pb, pb_scratch);
1086 } else if (tp == gx_system::PresetFile::PRESET_FACTORY) {
1087 i->set_value(bank_col.type_pb, pb_factory);
1088 } else if (f->is_mutable() || f->get_flags() & gx_system::PRESET_FLAG_VERSIONDIFF) {
1089 i->set_value(bank_col.edit_pb, pb_edit);
1090 i->set_value(bank_col.del_pb, pb_del);
1091 }
1092 }
1093
1094 // row deleted in model of bank_treeview
on_bank_reordered(const Gtk::TreeModel::Path & path)1095 void PresetWindow::on_bank_reordered(const Gtk::TreeModel::Path& path) {
1096 if (load_in_progress) {
1097 return;
1098 }
1099 Glib::RefPtr<Gtk::TreeModel> ls = bank_treeview->get_model();
1100 Gtk::TreeModel::Children ch = ls->children();
1101 std::vector<Glib::ustring> l;
1102 for (Gtk::TreeIter i = ch.begin(); i != ch.end(); ++i) {
1103 int tp = i->get_value(bank_col.tp);
1104 Glib::ustring nm = i->get_value(bank_col.name);
1105 if (!nm.empty() && (tp == gx_system::PresetFile::PRESET_SCRATCH || tp == gx_system::PresetFile::PRESET_FILE)) {
1106 l.push_back(nm);
1107 }
1108 }
1109 machine.bank_reorder(l);
1110 }
1111
1112 /*
1113 ** list of presets
1114 */
1115
text_func(Gtk::CellRenderer * cell,const Gtk::TreeModel::iterator & iter)1116 void PresetWindow::text_func(Gtk::CellRenderer *cell, const Gtk::TreeModel::iterator& iter) {
1117 Glib::ustring val = iter->get_value(pstore->col.name);
1118 Glib::ustring t = val;
1119 if (t.empty() && !cell->property_editing().get_value()) {
1120 t = "<new>";
1121 } else if (in_current_preset && !organize_presets->get_active()) {
1122 char c = KeySwitcher::idx_to_char(*pstore->get_path(iter).begin());
1123 if (c) {
1124 t = Glib::ustring::compose("%1: %2", c, t);
1125 } else {
1126 t = " " + t;
1127 }
1128 }
1129 bool selected = (in_current_preset && machine.setting_is_preset() && val == machine.get_current_name());
1130 set_cell_text(preset_treeview, cell, t, selected);
1131 }
1132
1133 /*
1134 * button-release event handler for preset_treeview
1135 * edit and delete actions
1136 * maybe better done by setting the CellRenderer mode property
1137 */
on_preset_button_release(GdkEventButton * ev)1138 bool PresetWindow::on_preset_button_release(GdkEventButton *ev) {
1139 Gtk::TreeModel::Path pt;
1140 Gtk::TreeViewColumn *col;
1141 int dx, dy;
1142 if (!preset_treeview->get_path_at_pos(ev->x, ev->y, pt, col, dx, dy)) {
1143 return false;
1144 }
1145 Gtk::TreeModel::Path path;
1146 Gtk::TreeViewColumn *focus_column;
1147 preset_treeview->get_cursor(path, focus_column);
1148 if (col != focus_column || !path || pt != path) {
1149 return false;
1150 }
1151 Gtk::TreeIter bank_iter = get_current_bank_iter();
1152 int tp = bank_iter->get_value(bank_col.tp);
1153 if ((tp != gx_system::PresetFile::PRESET_SCRATCH && tp != gx_system::PresetFile::PRESET_FILE)
1154 || machine.get_bank_file(bank_iter->get_value(bank_col.name))->get_flags()) {
1155 return false;
1156 }
1157 Glib::ustring nm = pstore->get_iter(pt)->get_value(pstore->col.name);
1158 if (nm.empty()) { // "<new>"
1159 if (col == preset_column_preset) {
1160 if (!organize_presets->get_active()) {
1161 Glib::RefPtr<Gtk::TreeSelection> sel = preset_treeview->get_selection();
1162 sel->set_mode(Gtk::SELECTION_SINGLE);
1163 actions.save_changes->set_enabled(false);
1164 }
1165 start_edit(pt, *preset_column_preset, *preset_cellrenderer);
1166 }
1167 return false;
1168 }
1169 if (col == preset_column_edit) {
1170 start_edit(pt, *preset_column_preset, *preset_cellrenderer);
1171 } else if (col == preset_column_delete) {
1172 Gtk::MessageDialog d(*dynamic_cast<Gtk::Window*>(preset_treeview->get_toplevel()),
1173 Glib::ustring::compose("delete preset %1?", nm), false,
1174 Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL, true);
1175 d.set_position(Gtk::WIN_POS_MOUSE);
1176 if (d.run() == Gtk::RESPONSE_OK) {
1177 machine.erase_preset(*machine.get_bank_file(bank_iter->get_value(bank_col.name)), nm);
1178 }
1179 }
1180 return false;
1181 }
1182
on_preset_edited(const Glib::ustring & path,const Glib::ustring & newtext)1183 void PresetWindow::on_preset_edited(const Glib::ustring& path, const Glib::ustring& newtext) {
1184 Gtk::TreeIter it = pstore->get_iter(path);
1185 Glib::ustring oldname = it->get_value(pstore->col.name);
1186 Glib::ustring newname = newtext;
1187 gx_system::strip(newname);
1188 if (newname.empty() || newname == oldname) {
1189 reset_edit(*preset_column_preset);
1190 return;
1191 }
1192 gx_system::PresetFileGui& fl = *machine.get_bank_file(get_current_bank());
1193 Glib::ustring t = newname;
1194 int n = 1;
1195 while (fl.has_entry(newname)) {
1196 newname = Glib::ustring::compose("%1-%2", t, n);
1197 n += 1;
1198 }
1199 it->set_value(pstore->col.name, newname);
1200 it->set_value(pstore->col.edit_pb, pb_edit);
1201 it->set_value(pstore->col.del_pb, pb_del);
1202 if (oldname.empty()) {
1203 // check if current preset is scratch and needs to be saved
1204 if (!machine.get_current_bank().empty()) {
1205 gx_system::PresetFileGui *cpf = machine.get_bank_file(machine.get_current_bank());
1206 if (cpf && cpf->has_entry(machine.get_current_name())) {
1207 if (cpf->get_type() == gx_system::PresetFile::PRESET_SCRATCH && cpf->is_mutable()) {
1208 machine.pf_save(*cpf, machine.get_current_name());
1209 }
1210 }
1211 }
1212 pstore->append();
1213 machine.pf_save(fl, newname);
1214 } else {
1215 machine.rename_preset(fl, oldname, newname);
1216 }
1217 reset_edit(*preset_column_preset);
1218 }
1219
on_preset_drag_motion(const Glib::RefPtr<Gdk::DragContext> & context,int x,int y,guint timestamp)1220 bool PresetWindow::on_preset_drag_motion(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, guint timestamp) {
1221 if (Gtk::Widget::drag_get_source_widget(context) == preset_treeview) {
1222 Gtk::TreeIter it = get_current_bank_iter();
1223 if (it && (machine.get_bank_file(it->get_value(bank_col.name))->is_mutable())) {
1224 preset_treeview->on_drag_motion(context, x, y, timestamp);
1225 Gtk::TreeModel::Path pt;
1226 Gtk::TreeViewDropPosition dst;
1227 if (preset_treeview->get_dest_row_at_pos(x, y, pt, dst)) {
1228 if (pstore->get_iter(pt)->get_value(pstore->col.name).empty()) {
1229 dst = Gtk::TREE_VIEW_DROP_BEFORE;
1230 }
1231 change_drag_dst_before_or_after(dst);
1232 } else {
1233 Gtk::TreeModel::Path start;
1234 preset_treeview->get_visible_range(start, pt);
1235 dst = Gtk::TREE_VIEW_DROP_BEFORE;
1236 }
1237 preset_treeview->set_drag_dest_row(pt, dst);
1238 context->drag_status(Gdk::ACTION_MOVE, timestamp);
1239 return true;
1240 }
1241 }
1242 context->drag_status((Gdk::DragAction)0, timestamp);
1243 return true;
1244 }
1245
on_preset_reordered(const Gtk::TreeModel::Path & path)1246 void PresetWindow::on_preset_reordered(const Gtk::TreeModel::Path& path) {
1247 // save changed order to file
1248 if (load_in_progress) {
1249 return;
1250 }
1251 Gtk::TreeModel::Children ch = pstore->children();
1252 std::vector<Glib::ustring> l;
1253 for (Gtk::TreeIter i = ch.begin(); i != ch.end(); ++i) {
1254 Glib::ustring s = i->get_value(pstore->col.name);
1255 if (!s.empty()) {
1256 l.push_back(s);
1257 }
1258 }
1259 machine.reorder_preset(*machine.get_bank_file(get_current_bank()), l);
1260 }
1261
autosize()1262 void PresetWindow::autosize() {
1263 if (bank_treeview->get_mapped()) {
1264 bank_treeview->columns_autosize();
1265 preset_treeview->columns_autosize();
1266 }
1267 }
1268
on_organize()1269 void PresetWindow::on_organize() {
1270 bool v = organize_presets->get_active();
1271 bank_column_edit->set_visible(v);
1272 bank_column_delete->set_visible(v);
1273 bank_column_spacer->set_visible(v);
1274 preset_column_edit->set_visible(v);
1275 preset_column_delete->set_visible(v);
1276 preset_column_spacer->set_visible(v);
1277 Glib::RefPtr<Gtk::TreeSelection> sel = preset_treeview->get_selection();
1278 if (v) {
1279 actions.presets->set_active(true);
1280 reload_combo();
1281 sel->set_mode(Gtk::SELECTION_NONE);
1282 banks_combobox->set_active(-1);
1283 banks_combobox->show();
1284 presets_target_scrolledbox->show();
1285 actions.save_changes->set_enabled(false);
1286 } else {
1287 sel->set_mode(Gtk::SELECTION_SINGLE);
1288 banks_combobox->hide();
1289 presets_target_scrolledbox->hide();
1290 if (machine.setting_is_preset()) {
1291 if (machine.get_bank_file(machine.get_current_bank())->is_mutable()) {
1292 actions.save_changes->set_enabled(true);
1293 }
1294 }
1295 }
1296 on_bank_changed(); // reload for DnD adjustment of readonly banks
1297 autosize();
1298 // strange bug in Gtk (V3.24): preset treeview with apparently
1299 // wrong internal sizes on first switch after program start
1300 // a delayed resize operation seems to fix it
1301 Gtk::Widget *toplevel = main_vpaned->get_toplevel();
1302 Glib::signal_idle().connect_once(
1303 sigc::mem_fun(toplevel, &Gtk::Widget::queue_resize),
1304 Glib::PRIORITY_LOW);
1305 }
1306
1307 /*
1308 ** preset window
1309 */
1310
animate_preset_show()1311 bool PresetWindow::animate_preset_show() {
1312 vpaned_pos -= vpaned_step;
1313 if (vpaned_pos <= vpaned_target) {
1314 main_vpaned->set_position(vpaned_target);
1315 gx_gui::child_set_property(*main_vpaned, *preset_scrolledbox, "shrink", false);
1316 Gtk::TreeIter it = get_current_bank_iter();
1317 if (it) {
1318 bank_treeview->scroll_to_row(bank_treeview->get_model()->get_path(it));
1319 }
1320 return false;
1321 }
1322 main_vpaned->set_position(vpaned_pos);
1323 return true;
1324 }
1325
animate_preset_hide()1326 bool PresetWindow::animate_preset_hide() {
1327 vpaned_pos += vpaned_step;
1328 if (vpaned_pos >= vpaned_target) {
1329 preset_scrolledbox->hide();
1330 return false;
1331 }
1332 main_vpaned->set_position(vpaned_pos);
1333 return true;
1334 }
1335
on_preset_changed()1336 void PresetWindow::on_preset_changed() {
1337 if (load_in_progress) {
1338 return;
1339 }
1340 if (actions.organize->get_active()) {
1341 return;
1342 }
1343 Glib::ustring bank = get_current_bank();
1344 if (bank.empty()) {
1345 return;
1346 }
1347 Gtk::TreeIter it = preset_treeview->get_selection()->get_selected();
1348 if (!it) {
1349 return;
1350 }
1351 Glib::ustring name = it->get_value(pstore->col.name);
1352 bool is_scratch = false;
1353 gx_system::PresetFileGui *cpf = 0;
1354 if (!machine.get_current_bank().empty()) {
1355 cpf = machine.get_bank_file(machine.get_current_bank());
1356 if (cpf && cpf->has_entry(machine.get_current_name())) {
1357 is_scratch = (cpf->get_type() == gx_system::PresetFile::PRESET_SCRATCH);
1358 }
1359 }
1360 if (is_scratch) {
1361 if (bank == machine.get_current_bank() && name == machine.get_current_name()) {
1362 machine.pf_save(*cpf, machine.get_current_name());
1363 // no reload necessary
1364 return;
1365 }
1366 }
1367 in_current_preset = true;
1368 cpf = machine.get_bank_file(bank);
1369 machine.load_preset(cpf, name);
1370 }
1371
on_preset_save()1372 void PresetWindow::on_preset_save() {
1373 if (!machine.setting_is_preset()) {
1374 return;
1375 }
1376 gx_system::PresetFileGui *pf = machine.get_bank_file(machine.get_current_bank());
1377 if (!pf->is_mutable()) {
1378 return;
1379 }
1380 machine.pf_save(*pf, machine.get_current_name());
1381 }
1382
display_paned(bool show_preset,int paned_child_height)1383 void PresetWindow::display_paned(bool show_preset, int paned_child_height) {
1384 on_map_conn.disconnect();
1385 if (preset_scrolledbox->get_parent() == main_vpaned) {
1386 vpaned_pos = main_vpaned->get_allocation().get_height();
1387 int h;
1388 h = main_vpaned->get_handle_window()->get_height();
1389 vpaned_target = vpaned_pos - paned_child_height - h;
1390 main_vpaned->set_position(vpaned_target);
1391 gx_gui::child_set_property(*main_vpaned, *preset_scrolledbox, "shrink", false);
1392 }
1393 preset_scrolledbox->show();
1394 if (!show_preset || !in_current_preset) {
1395 return;
1396 }
1397 // make the current entry in the preset list window
1398 // visible (in case it's outside the displayed range).
1399 // apparently only works after the window is mapped
1400 // and some size calculations are done, so put it into
1401 // an idle handler.
1402 Gtk::TreeIter it = get_current_bank_iter();
1403 if (it) {
1404 Glib::signal_idle().connect_once(
1405 sigc::bind(
1406 sigc::mem_fun1(bank_treeview, &MyTreeView::scroll_to_row),
1407 bank_treeview->get_model()->get_path(it)));
1408 }
1409 Gtk::TreeNodeChildren ch = pstore->children();
1410 for (it = ch.begin(); it != ch.end(); ++it) {
1411 if (it->get_value(pstore->col.name) == machine.get_current_name()) {
1412 Glib::signal_idle().connect_once(
1413 sigc::bind(
1414 sigc::mem_fun1(*preset_treeview, &MyTreeView::scroll_to_row),
1415 pstore->get_path(it)));
1416 break;
1417 }
1418 }
1419 }
1420
1421 // open the preset selection pane
on_preset_select(bool v,bool animated,int paned_child_height)1422 void PresetWindow::on_preset_select(bool v, bool animated, int paned_child_height) {
1423 static bool first_time = true;
1424 if (first_time) {
1425 //FIXME needed to fix first time display height, not clear why
1426 paned_child_height += 4;
1427 first_time = false;
1428 }
1429 // (margin.top + margin.bottom) of the GtkPaned css node "separator"
1430 // there doesn't seem to be a way to get this value with Gtk3
1431 // see also the guitarix scss style file
1432 const int paned_sep_margin_top_bottom = -8;
1433 paned_child_height += paned_sep_margin_top_bottom;
1434
1435 on_map_conn.disconnect();
1436 bool is_mapped = main_vpaned->get_toplevel()->get_mapped();
1437 bool rack_visible = actions.show_rack->get_active();
1438 if (v) {
1439 if (machine.bank_check_reparse()) {
1440 on_presetlist_changed();
1441 } else if (!get_current_bank_iter()) {
1442 on_presetlist_changed();
1443 }
1444 autosize();
1445 Gtk::TreeIter it = get_current_bank_iter();
1446 if (it && animated && is_mapped) {
1447 bank_treeview->scroll_to_row(bank_treeview->get_model()->get_path(it));
1448 }
1449 if (!is_mapped) {
1450 // don't have widget height to calculate paned separator
1451 // position before window is mapped
1452 on_map_conn = main_vpaned->get_toplevel()->signal_map().connect(
1453 sigc::bind(sigc::mem_fun(*this, &PresetWindow::display_paned), true, paned_child_height));
1454 } else if (animated && rack_visible) {
1455 gx_gui::child_set_property(*main_vpaned, *preset_scrolledbox, "shrink", true);
1456 vpaned_pos = main_vpaned->get_allocation().get_height();
1457 int h;
1458 h = main_vpaned->get_handle_window()->get_height();
1459 vpaned_target = vpaned_pos - paned_child_height - h;
1460 main_vpaned->set_position(vpaned_pos);
1461 vpaned_step = paned_child_height / 5;
1462 preset_scrolledbox->show();
1463 animate_preset_show();
1464 Glib::signal_timeout().connect(sigc::mem_fun(*this, &PresetWindow::animate_preset_show), 20);
1465 } else {
1466 display_paned(false, paned_child_height);
1467 }
1468 } else {
1469 vpaned_target = main_vpaned->get_allocation().get_height();
1470 vpaned_pos = main_vpaned->get_position();
1471 if (animated && is_mapped && rack_visible) {
1472 vpaned_step = paned_child_height / 5;
1473 gx_gui::child_set_property(*main_vpaned, *preset_scrolledbox, "shrink", true);
1474 Glib::signal_timeout().connect(sigc::mem_fun(*this, &PresetWindow::animate_preset_hide), 20);
1475 } else {
1476 preset_scrolledbox->hide();
1477 }
1478 actions.organize->set_active(false);
1479 }
1480 }
1481