Vkeybd: use ArdourWidgets for all GUI elements
[ardour.git] / gtk2_ardour / plugin_scan_dialog.cc
1 /*
2  * Copyright (C) 2005-2019 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
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 along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #include "gtk2ardour-version.h"
23 #endif
24
25 #include <gtkmm/progressbar.h>
26
27 #include "ardour/plugin_manager.h"
28
29 #include "ardour_ui.h"
30 #include "debug.h"
31 #include "gui_thread.h"
32 #include "plugin_scan_dialog.h"
33 #include "ui_config.h"
34
35 #include "pbd/i18n.h"
36
37 using namespace ARDOUR;
38 using namespace PBD;
39 using namespace Gtk;
40 using namespace std;
41
42 PluginScanDialog::PluginScanDialog (bool just_cached, bool v)
43         : ArdourDialog (_("Scanning for plugins"))
44         , timeout_button (_("Stop Timeout"))
45         , cancel_button (_("Cancel Plugin Scan"))
46         , cache_only (just_cached)
47         , verbose (v)
48 {
49         VBox* vbox = get_vbox();
50         vbox->set_size_request(400,-1);
51
52         message.set_padding (12, 12);
53         vbox->pack_start (message);
54
55         cancel_button.set_name ("EditorGTKButton");
56         cancel_button.signal_clicked().connect (sigc::mem_fun (*this, &PluginScanDialog::cancel_plugin_scan));
57         cancel_button.show();
58
59         vbox->pack_start (cancel_button, PACK_SHRINK);
60
61         timeout_button.set_name ("EditorGTKButton");
62         timeout_button.signal_clicked().connect (sigc::mem_fun (*this, &PluginScanDialog::cancel_plugin_timeout));
63         timeout_button.show();
64
65         pbar.set_orientation(Gtk::PROGRESS_RIGHT_TO_LEFT);
66         pbar.set_text(_("Scan Timeout"));
67         pbar.show();
68
69         tbox.pack_start (pbar, PACK_EXPAND_WIDGET, 4);
70         tbox.pack_start (timeout_button, PACK_SHRINK, 4);
71
72         vbox->pack_start (tbox, PACK_SHRINK, 4);
73
74         ARDOUR::PluginScanMessage.connect (connections, MISSING_INVALIDATOR, boost::bind(&PluginScanDialog::message_handler, this, _1, _2, _3), gui_context());
75         ARDOUR::PluginScanTimeout.connect (connections, MISSING_INVALIDATOR, boost::bind(&PluginScanDialog::plugin_scan_timeout, this, _1), gui_context());
76
77         vbox->show_all ();
78 }
79
80 void
81 PluginScanDialog::start ()
82 {
83         /* OK, this is extremely hard to understand on first reading, so please
84          * read this and think about it carefully if you are confused.
85          *
86          * Plugin discovery must take place in the main thread of the
87          * process. This is not true for all plugin APIs but it is true for
88          * VST.  For AU, although plugins themselves do not care, Apple decided
89          * that Cocoa must be "invoked" from the main thread. Since the plugin
90          * might show a "registration" GUI, discovery must be done
91          * in the main thread.
92          *
93          * This means that the PluginManager::refresh() call MUST be made from
94          * the main thread (typically the GUI thread, but certainly the thread
95          * running main()). Failure to do this will cause crashes, undefined
96          * behavior and other undesirable stuff (because plugin APIs failed to
97          * specify this aspect of the host behavior).
98          *
99          * The ::refresh call is likely to be slow, particularly in the case of
100          * VST(2) plugins where we are forced to load the shared object do
101          * discovery (there is no separate metadata as with LV2 for
102          * example). This means that it will block the GUI event loop where we
103          * are calling it from. This is a problem.
104          *
105          * Normally we would solve this by running it in a separate thread, but
106          * we cannot do this for reasons described above regarding plugin
107          * discovery.
108          *
109          * We "solve" this by making the PluginManager emit a signal as it
110          * examines every new plugin. Our handler for this signal checks the
111          * message, and then runs ARDOUR_UI::gui_idle_handler() which flushes
112          * the GUI event loop of pending events. This effectively handles
113          * redraws and event input and all the usual stuff, meaning that the
114          * GUI event loop appears to continue running during the ::refresh()
115          * call. In reality, it only runs at the start of each plugin
116          * discovery, so if the discovery process for a particular plugin takes
117          * a long time (e.g. because it displays a licensing window and sits
118          * waiting for input from the user), there's nothing we can do -
119          * control will not be returned to our GUI event loop until that is
120          * finished.
121          *
122          * This is a horrible design. Truly, really horrible. But it is caused
123          * by plugin APIs failing to mandate that discovery can happen from any
124          * thread and that plugins should NOT display a GUI or interact with
125          * the user during discovery/instantiation. Fundamentally, all plugin
126          * APIs should allow discovery without instantiation, like LV2 does
127          * (and to a very limited extent like AU does, if you play some games
128          * with the lower level APIs).
129          *
130          * For now (October 2019) it is the best we can come up with that does
131          * not break when some VST plugin decides to behave stupidly.
132          */
133
134
135         DEBUG_TRACE (DEBUG::GuiStartup, "plugin refresh starting\n");
136         PluginManager::instance().refresh (cache_only);
137         DEBUG_TRACE (DEBUG::GuiStartup, "plugin refresh complete\n");
138
139         /* scan is done at this point, return full control to main event loop */
140 }
141
142 void
143 PluginScanDialog::cancel_plugin_scan ()
144 {
145         PluginManager::instance().cancel_plugin_scan();
146 }
147
148 void
149 PluginScanDialog::cancel_plugin_timeout ()
150 {
151         PluginManager::instance().cancel_plugin_timeout();
152         timeout_button.set_sensitive (false);
153 }
154
155 void
156 PluginScanDialog::plugin_scan_timeout (int timeout)
157 {
158         if (!is_mapped()) {
159                 return;
160         }
161
162         if (timeout > 0) {
163                 pbar.set_sensitive (false);
164                 timeout_button.set_sensitive (true);
165                 pbar.set_fraction ((float) timeout / (float) Config->get_vst_scan_timeout());
166                 tbox.show();
167         } else {
168                 pbar.set_sensitive (false);
169                 timeout_button.set_sensitive (false);
170         }
171
172         ARDOUR_UI::instance()->gui_idle_handler ();
173 }
174
175 void
176 PluginScanDialog::message_handler (std::string type, std::string plugin, bool can_cancel)
177 {
178         DEBUG_TRACE (DEBUG::GuiStartup, string_compose (X_("plugin scan message: %1 cancel? %2\n"), type, can_cancel));
179
180         if (type == X_("closeme") && !is_mapped()) {
181                 return;
182         }
183
184         const bool cancelled = PluginManager::instance().cancelled();
185
186         if (type != X_("closeme") && (!UIConfiguration::instance().get_show_plugin_scan_window()) && !verbose) {
187
188                 if (cancelled && is_mapped()) {
189                         hide();
190                         connections.drop_connections();
191                         ARDOUR_UI::instance()->gui_idle_handler ();
192                         return;
193                 }
194                 if (cancelled || !can_cancel) {
195                         return;
196                 }
197         }
198
199         if (type == X_("closeme")) {
200                 tbox.hide();
201                 hide();
202                 connections.drop_connections ();
203         } else {
204                 message.set_text (type + ": " + Glib::path_get_basename(plugin));
205                 show();
206         }
207
208         if (!can_cancel || !cancelled) {
209                 timeout_button.set_sensitive(false);
210         }
211
212         cancel_button.set_sensitive(can_cancel && !cancelled);
213
214         ARDOUR_UI::instance()->gui_idle_handler ();
215 }