/[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 3364 - (show annotations) (download)
Tue Nov 14 18:07:25 2017 UTC (6 years, 4 months ago) by schoenebeck
File size: 19673 byte(s)
* Added experimental support for upcoming GTK(MM)4
  (for now up to GTKMM 3.91.2 while still preserving backward compatibility
  down to GTKMM 2).
* Re-merged r2845 to compile now with and without Gtk "Stock ID" API
  (see also r3158).

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

  ViewVC Help
Powered by ViewVC