/[svn]/gigedit/trunk/src/gigedit/gigedit.cpp
ViewVC logotype

Contents of /gigedit/trunk/src/gigedit/gigedit.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2689 - (show annotations) (download)
Sun Jan 4 17:19:19 2015 UTC (4 years, 10 months ago) by schoenebeck
File size: 16737 byte(s)
* Automatically switch the sampler's (audible) instrument (in live-mode)
  whenever another instrument was selected in gigedit (this default
  behavior can be switched off in the settings menu of gigedit).

1 /*
2 * Copyright (C) 2007-2015 Andreas Persson
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2, or (at
7 * your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with program; see the file COPYING. If not, write to the Free
16 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17 * 02110-1301 USA.
18 */
19
20 #include "gigedit.h"
21
22 #include <glibmm/dispatcher.h>
23 #include <glibmm/main.h>
24 #include <gtkmm/main.h>
25
26 #if defined(__APPLE__)
27 # include <CoreFoundation/CoreFoundation.h>
28 # include "MacHelper.h"
29 #endif
30
31 #include "mainwindow.h"
32
33 #include "global.h"
34
35 #ifdef __APPLE__
36 #include <dlfcn.h>
37 #include <glibmm/fileutils.h>
38 #include <glibmm/miscutils.h>
39 #endif
40
41 //TODO: (hopefully) just a temporary nasty hack for launching gigedit on the main thread on Mac (see comments below in this file for details)
42 #if defined(__APPLE__) // the following global external variables are defined in LinuxSampler's global_private.cpp ...
43 extern bool g_mainThreadCallbackSupported;
44 extern void (*g_mainThreadCallback)(void* info);
45 extern void* g_mainThreadCallbackInfo;
46 extern bool g_fireMainThreadCallback;
47 #endif
48
49 namespace {
50
51 // State for a gigedit thread.
52 //
53 // This class is only used when gigedit is run as a plugin and makes
54 // sure that there's only one Gtk::Main event loop. The event loop is
55 // started in a separate thread. The plugin thread just dispatches an
56 // event to the main loop to open a window and then goes to sleep
57 // until the window is closed.
58 //
59 class GigEditState : public sigc::trackable {
60 public:
61 GigEditState(GigEdit* parent) : parent(parent), instrument(NULL) { }
62 void run(gig::Instrument* pInstrument);
63
64 MainWindow* window;
65
66 private:
67
68 // simple condition variable abstraction
69 class Cond {
70 private:
71 bool pred;
72 Glib::Threads::Mutex mutex;
73 Glib::Threads::Cond cond;
74 public:
75 Cond() : pred(false) { }
76 void signal() {
77 Glib::Threads::Mutex::Lock lock(mutex);
78 pred = true;
79 cond.signal();
80 }
81 void wait() {
82 Glib::Threads::Mutex::Lock lock(mutex);
83 while (!pred) cond.wait(mutex);
84 }
85 };
86
87 #ifdef OLD_THREADS
88 static Glib::StaticMutex mutex;
89 #else
90 static Glib::Threads::Mutex mutex;
91 #endif
92 static Glib::Dispatcher* dispatcher;
93 static GigEditState* current;
94
95 static void main_loop_run(Cond* intialized);
96 static void open_window_static();
97
98 GigEdit* parent;
99 Cond open;
100 Cond close;
101 Cond initialized;
102 gig::Instrument* instrument;
103
104 void open_window();
105 void close_window();
106 #if defined(__APPLE__)
107 static void runInCFMainLoop(void* info);
108 #endif
109 };
110
111 #ifdef WIN32
112 HINSTANCE gigedit_dll_handle = 0;
113 #endif
114
115 #ifdef __APPLE__
116 std::string gigedit_localedir;
117 #endif
118
119 void init_app() {
120 static bool process_initialized = false;
121 if (!process_initialized) {
122 std::cout << "Initializing 3rd party services needed by gigedit.\n"
123 << std::flush;
124 setlocale(LC_ALL, "");
125
126 #ifdef __APPLE__
127 // Look for pango.modules, gdk-pixbuf.loaders and locale files
128 // under the same dir as the gigedit dylib is installed in.
129 Dl_info info;
130 if (dladdr((void*)&init_app, &info)) {
131 std::string libdir = Glib::path_get_dirname(info.dli_fname);
132
133 if (Glib::getenv("PANGO_SYSCONFDIR") == "" &&
134 Glib::file_test(Glib::build_filename(libdir,
135 "pango/pango.modules"),
136 Glib::FILE_TEST_EXISTS)) {
137 Glib::setenv("PANGO_SYSCONFDIR", libdir, true);
138 }
139 if (Glib::getenv("GDK_PIXBUF_MODULE_FILE") == "") {
140 std::string module_file =
141 Glib::build_filename(libdir,
142 "gtk-2.0/gdk-pixbuf.loaders");
143 if (Glib::file_test(module_file, Glib::FILE_TEST_EXISTS)) {
144 Glib::setenv("GDK_PIXBUF_MODULE_FILE", module_file, true);
145 }
146 }
147 //FIXME: for some reason AC GETTEXT check fails on the Mac cross compiler?
148 //#if HAVE_GETTEXT
149 std::string localedir = Glib::build_filename(libdir, "locale");
150 if (Glib::file_test(localedir, Glib::FILE_TEST_EXISTS)) {
151 gigedit_localedir = localedir;
152 bindtextdomain(GETTEXT_PACKAGE, gigedit_localedir.c_str());
153 } else {
154 bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
155 }
156 //#endif
157 }
158
159 // The gtk file dialog stores its recent files state in
160 // ~/.local/share
161 g_mkdir_with_parents(
162 Glib::build_filename(Glib::get_home_dir(),
163 ".local/share").c_str(), 0777);
164 #endif // __APPLE__
165
166 //FIXME: for some reason AC GETTEXT check fails on the Mac cross compiler?
167 #if (HAVE_GETTEXT || defined(__APPLE__))
168
169 #ifdef WIN32
170 #if GLIB_CHECK_VERSION(2, 16, 0)
171 gchar* root =
172 g_win32_get_package_installation_directory_of_module(gigedit_dll_handle);
173 #else
174 gchar* root =
175 g_win32_get_package_installation_directory(NULL, NULL);
176 #endif
177 gchar* temp = g_build_filename(root, "/share/locale", NULL);
178 g_free(root);
179 gchar* localedir = g_win32_locale_filename_from_utf8(temp);
180 g_free(temp);
181 bindtextdomain(GETTEXT_PACKAGE, localedir);
182 g_free(localedir);
183 #elif !defined(__APPLE__)
184 bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
185 #endif
186 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
187 textdomain(GETTEXT_PACKAGE);
188 #endif // HAVE_GETTEXT
189
190 #ifdef OLD_THREADS
191 // make sure thread_init() is called once and ONLY once per process
192 if (!Glib::thread_supported()) Glib::thread_init();
193 #endif
194 process_initialized = true;
195 }
196 }
197
198 void connect_signals(GigEdit* gigedit, MainWindow* mainwindow) {
199 // the signals of the "GigEdit" class are actually just proxies, that
200 // is they simply forward the signals of the internal classes to the
201 // outer world
202 mainwindow->signal_file_structure_to_be_changed().connect(
203 gigedit->signal_file_structure_to_be_changed().make_slot()
204 );
205 mainwindow->signal_file_structure_changed().connect(
206 gigedit->signal_file_structure_changed().make_slot()
207 );
208 mainwindow->signal_samples_to_be_removed().connect(
209 gigedit->signal_samples_to_be_removed().make_slot()
210 );
211 mainwindow->signal_samples_removed().connect(
212 gigedit->signal_samples_removed().make_slot()
213 );
214 mainwindow->signal_region_to_be_changed().connect(
215 gigedit->signal_region_to_be_changed().make_slot()
216 );
217 mainwindow->signal_region_changed().connect(
218 gigedit->signal_region_changed().make_slot()
219 );
220 mainwindow->signal_dimreg_to_be_changed().connect(
221 gigedit->signal_dimreg_to_be_changed().make_slot()
222 );
223 mainwindow->signal_dimreg_changed().connect(
224 gigedit->signal_dimreg_changed().make_slot()
225 );
226 mainwindow->signal_sample_changed().connect(
227 gigedit->signal_sample_changed().make_slot()
228 );
229 mainwindow->signal_sample_ref_changed().connect(
230 gigedit->signal_sample_ref_changed().make_slot()
231 );
232 mainwindow->signal_keyboard_key_hit().connect(
233 gigedit->signal_keyboard_key_hit().make_slot()
234 );
235 mainwindow->signal_keyboard_key_released().connect(
236 gigedit->signal_keyboard_key_released().make_slot()
237 );
238 mainwindow->signal_switch_sampler_instrument().connect(
239 gigedit->signal_switch_sampler_instrument().make_slot()
240 );
241 }
242
243 } // namespace
244
245 GigEdit::GigEdit() {
246 state = NULL;
247 }
248
249 int GigEdit::run(int argc, char* argv[]) {
250 init_app();
251
252 Gtk::Main kit(argc, argv);
253
254 //FIXME: for some reason AC GETTEXT check fails on the Mac cross compiler?
255 #if (/*HAVE_GETTEXT &&*/ defined(__APPLE__))
256 // Gtk::Main binds the gtk locale to a possible non-existent
257 // directory. If we have bundled gtk locale files, we rebind here,
258 // after the Gtk::Main constructor.
259 if (!gigedit_localedir.empty()) {
260 bindtextdomain("gtk20", gigedit_localedir.c_str());
261 }
262 #endif
263
264 MainWindow window;
265 connect_signals(this, &window);
266 if (argc >= 2) window.load_file(argv[1]);
267 kit.run(window);
268 return 0;
269 }
270
271 int GigEdit::run(gig::Instrument* pInstrument) {
272 init_app();
273
274 GigEditState state(this);
275 this->state = &state;
276 state.run(pInstrument);
277 this->state = NULL;
278 return 0;
279 }
280
281 void GigEdit::on_note_on_event(int key, int velocity) {
282 if (!this->state) return;
283 GigEditState* state = (GigEditState*) this->state;
284 state->window->signal_note_on().emit(key, velocity);
285 }
286
287 void GigEdit::on_note_off_event(int key, int velocity) {
288 if (!this->state) return;
289 GigEditState* state = (GigEditState*) this->state;
290 state->window->signal_note_off().emit(key, velocity);
291 }
292
293 sigc::signal<void, gig::File*>& GigEdit::signal_file_structure_to_be_changed() {
294 return file_structure_to_be_changed_signal;
295 }
296
297 sigc::signal<void, gig::File*>& GigEdit::signal_file_structure_changed() {
298 return file_structure_changed_signal;
299 }
300
301 sigc::signal<void, std::list<gig::Sample*> >& GigEdit::signal_samples_to_be_removed() {
302 return samples_to_be_removed_signal;
303 }
304
305 sigc::signal<void>& GigEdit::signal_samples_removed() {
306 return samples_removed_signal;
307 }
308
309 sigc::signal<void, gig::Region*>& GigEdit::signal_region_to_be_changed() {
310 return region_to_be_changed_signal;
311 }
312
313 sigc::signal<void, gig::Region*>& GigEdit::signal_region_changed() {
314 return region_changed_signal;
315 }
316
317 sigc::signal<void, gig::DimensionRegion*>& GigEdit::signal_dimreg_to_be_changed() {
318 return dimreg_to_be_changed_signal;
319 }
320
321 sigc::signal<void, gig::DimensionRegion*>& GigEdit::signal_dimreg_changed() {
322 return dimreg_changed_signal;
323 }
324
325 sigc::signal<void, gig::Sample*>& GigEdit::signal_sample_changed() {
326 return sample_changed_signal;
327 }
328
329 sigc::signal<void, gig::Sample*/*old*/, gig::Sample*/*new*/>& GigEdit::signal_sample_ref_changed() {
330 return sample_ref_changed_signal;
331 }
332
333 sigc::signal<void, int/*key*/, int/*velocity*/>& GigEdit::signal_keyboard_key_hit() {
334 return keyboard_key_hit_signal;
335 }
336
337 sigc::signal<void, int/*key*/, int/*velocity*/>& GigEdit::signal_keyboard_key_released() {
338 return keyboard_key_released_signal;
339 }
340
341 sigc::signal<void, gig::Instrument*>& GigEdit::signal_switch_sampler_instrument() {
342 return switch_sampler_instrument_signal;
343 }
344
345 #ifdef OLD_THREADS
346 Glib::StaticMutex GigEditState::mutex = GLIBMM_STATIC_MUTEX_INIT;
347 #else
348 Glib::Threads::Mutex GigEditState::mutex;
349 #endif
350 Glib::Dispatcher* GigEditState::dispatcher = 0;
351 GigEditState* GigEditState::current = 0;
352
353 void GigEditState::open_window_static() {
354 GigEditState* c = GigEditState::current;
355 c->open.signal();
356 c->open_window();
357 }
358
359 void GigEditState::open_window() {
360 window = new MainWindow();
361
362 connect_signals(parent, window);
363 if (instrument) window->load_instrument(instrument);
364
365 window->signal_hide().connect(sigc::mem_fun(this,
366 &GigEditState::close_window));
367 window->present();
368 }
369
370 void GigEditState::close_window() {
371 delete window;
372 close.signal();
373 }
374
375 void GigEditState::main_loop_run(Cond* initialized) {
376 int argc = 1;
377 const char* argv_c[] = { "gigedit" };
378 char** argv = const_cast<char**>(argv_c);
379 Gtk::Main main_loop(argc, argv);
380 //FIXME: for some reason AC GETTEXT check fails on the Mac cross compiler?
381 #if (/*HAVE_GETTEXT &&*/ defined(__APPLE__))
382 if (!gigedit_localedir.empty()) {
383 bindtextdomain("gtk20", gigedit_localedir.c_str());
384 }
385 #endif
386
387 dispatcher = new Glib::Dispatcher();
388 dispatcher->connect(sigc::ptr_fun(&GigEditState::open_window_static));
389 initialized->signal();
390
391 main_loop.run();
392 }
393
394 #if defined(__APPLE__)
395
396 void GigEditState::runInCFMainLoop(void* info) {
397 printf("runInCFMainLoop() entered\n"); fflush(stdout);
398 GigEditState* state = static_cast<GigEditState*>(info);
399 state->main_loop_run(
400 &state->initialized
401 );
402 printf("runInCFMainLoop() left\n"); fflush(stdout);
403 }
404
405 #endif // __APPLE__
406
407 void GigEditState::run(gig::Instrument* pInstrument) {
408 mutex.lock(); // lock access to static variables
409
410 static bool main_loop_started = false;
411 instrument = pInstrument;
412 if (!main_loop_started) {
413 #if defined(__APPLE__)
414 // spawn GUI on main thread :
415 // On OS X the Gtk GUI can only be launched on a process's "main"
416 // thread. When trying to launch the Gtk GUI on any other thread,
417 // there will only be a white box, because the GUI would not receive
418 // any events, since it would listen to the wrong system event loop.
419 // So far we haven't investigated whether there is any kind of
420 // circumvention to allow doing that also on other OS X threads.
421 {
422 // In case the sampler was launched as standalone sampler (not as
423 // plugin), use the following global callback variable hack ...
424 if (g_mainThreadCallbackSupported) {
425 printf("Setting callback ...\n"); fflush(stdout);
426 g_mainThreadCallback = runInCFMainLoop;
427 g_mainThreadCallbackInfo = this;
428 g_fireMainThreadCallback = true;
429 printf("Callback variables set.\n"); fflush(stdout);
430 } else { // Sampler was launched as (i.e. AU / VST) plugin ...
431 // When the sampler was launched as plugin, we have no idea
432 // whether any sampler thread is the process's "main" thread.
433 // So that's why we are trying to use Apple's API for trying to
434 // launch our callback function on the process's main thread.
435 // However this will only work, if the plugin host application
436 // established a CF event loop, that is if the application is
437 // using Cocoa for its GUI. For other host applications the
438 // callback will never be executed and thus gigedit would not
439 // popup.
440
441 // should be pretty much the same as the Objective-C solution below with macHelperRunCFuncOnMainThread()
442 /*CFRunLoopSourceContext sourceContext = CFRunLoopSourceContext();
443 sourceContext.info = this;
444 sourceContext.perform = runInCFMainLoop;
445 printf("CFRunLoopSourceCreate\n"); fflush(stdout);
446 CFRunLoopSourceRef source = CFRunLoopSourceCreate(
447 kCFAllocatorDefault, // allocator
448 1, // priority
449 &sourceContext
450 );
451 printf("CFRunLoopAddSource\n"); fflush(stdout);
452 CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
453 CFRelease(source);*/
454
455 // use Apple's Objective-C API to call our callback function
456 // 'runInCFMainLoop()' on the process's "main" thread
457 macHelperRunCFuncOnMainThread(runInCFMainLoop, this);
458 }
459 }
460 #else
461 #ifdef OLD_THREADS
462 Glib::Thread::create(
463 sigc::bind(sigc::ptr_fun(&GigEditState::main_loop_run),
464 &initialized),
465 false);
466 #else
467 Glib::Threads::Thread::create(
468 sigc::bind(sigc::ptr_fun(&GigEditState::main_loop_run),
469 &initialized));
470 #endif
471 #endif
472 printf("Waiting for GUI being initialized (on main thread) ....\n"); fflush(stdout);
473 initialized.wait();
474 printf("GUI is now initialized. Everything done.\n"); fflush(stdout);
475 main_loop_started = true;
476 }
477 current = this;
478 dispatcher->emit();
479 open.wait(); // wait until the GUI thread has read current
480 mutex.unlock();
481 close.wait(); // sleep until window is closed
482 }
483
484 #if defined(WIN32)
485 extern "C" {
486 BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
487 {
488 switch (reason) {
489 case DLL_PROCESS_ATTACH:
490 gigedit_dll_handle = instance;
491 break;
492 }
493 return TRUE;
494 }
495 }
496 #endif

  ViewVC Help
Powered by ViewVC