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