/[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 2841 - (show annotations) (download)
Sun Aug 30 10:00:49 2015 UTC (8 years, 7 months ago) by persson
File size: 17151 byte(s)
* allow building with G_DISABLE_DEPRECATED
* fixed building without liblinuxsampler on Mac
* fixed some compiler and cppcheck warnings

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

  ViewVC Help
Powered by ViewVC