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

Diff of /gigedit/trunk/src/gigedit/gigedit.cpp

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1333 by schoenebeck, Sun Sep 9 16:53:29 2007 UTC revision 2841 by persson, Sun Aug 30 10:00:49 2015 UTC
# Line 1  Line 1 
1  /*  /*
2   * Copyright (C) 2007 Andreas Persson   * Copyright (C) 2007-2015 Andreas Persson
3   *   *
4   * This program is free software; you can redistribute it and/or   * This program is free software; you can redistribute it and/or
5   * modify it under the terms of the GNU General Public License as   * modify it under the terms of the GNU General Public License as
# Line 17  Line 17 
17   * 02110-1301 USA.   * 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"  #include "gigedit.h"
29    
30    #include <glibmm/dispatcher.h>
31    #include <glibmm/main.h>
32  #include <gtkmm/main.h>  #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"  #include "mainwindow.h"
40    
41  #include <libintl.h>  #include "global.h"
 #include <config.h>  
42    
43  // the app has to work from a DLL as well, so we hard code argv  #ifdef __APPLE__
44  int argc = 1;  #include <dlfcn.h>
45  const char* argv_c[] = { "gigedit" };  #include <glibmm/fileutils.h>
46  char** argv = const_cast<char**>(argv_c);  #include <glibmm/miscutils.h>
47  //FIXME: Gtk only allows to instantiate one Gtk::Main object per process, so this might crash other Gtk applications, i.e. launched as plugins by LinuxSampler  #endif
48  Gtk::Main kit(argc, argv);  
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  static void __init_app() {  void init_app() {
129      static bool process_initialized = false;      static bool process_initialized = false;
130      if (!process_initialized) {      if (!process_initialized) {
131          std::cout << "Initializing 3rd party services needed by gigedit.\n"          std::cout << "Initializing 3rd party services needed by gigedit.\n"
132                    << std::flush;                    << std::flush;
133          setlocale(LC_ALL, "");          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);          bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
194      #endif
195          bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");          bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
196          textdomain(GETTEXT_PACKAGE);          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          // make sure thread_init() is called once and ONLY once per process
201          if (!Glib::thread_supported()) Glib::thread_init();          if (!Glib::thread_supported()) Glib::thread_init();
202    #endif
203          process_initialized = true;          process_initialized = true;
204      }      }
205  }  }
206    
207  static void __connect_signals(GigEdit* gigedit, MainWindow* mainwindow) {  void connect_signals(GigEdit* gigedit, MainWindow* mainwindow) {
208      // the signals of the "GigEdit" class are actually just proxies, that      // the signals of the "GigEdit" class are actually just proxies, that
209      // is they simply forward the signals of the internal classes to the      // is they simply forward the signals of the internal classes to the
210      // outer world      // outer world
# Line 76  static void __connect_signals(GigEdit* g Line 232  static void __connect_signals(GigEdit* g
232      mainwindow->signal_dimreg_changed().connect(      mainwindow->signal_dimreg_changed().connect(
233          gigedit->signal_dimreg_changed().make_slot()          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(      mainwindow->signal_sample_ref_changed().connect(
239          gigedit->signal_sample_ref_changed().make_slot()          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  int GigEdit::run() {  } // namespace
253      __init_app();  
254      MainWindow window;  GigEdit::GigEdit() {
255      __connect_signals(this, &window);      state = NULL;
     kit.run(window);  
     return 0;  
256  }  }
257    
258  int GigEdit::run(const char* pFileName) {  int GigEdit::run(int argc, char* argv[]) {
259      __init_app();      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;      MainWindow window;
274      __connect_signals(this, &window);      connect_signals(this, &window);
275      if (pFileName) window.load_file(pFileName);      if (argc >= 2) window.load_file(argv[1]);
276      kit.run(window);      kit.run(window);
277      return 0;      return 0;
278  }  }
279    
280  int GigEdit::run(gig::Instrument* pInstrument) {  int GigEdit::run(gig::Instrument* pInstrument) {
281      __init_app();      init_app();
282      MainWindow window;  
283      __connect_signals(this, &window);      GigEditState state(this);
284      if (pInstrument) window.load_instrument(pInstrument);      this->state = &state;
285      kit.run(window);      state.run(pInstrument);
286        this->state = NULL;
287      return 0;      return 0;
288  }  }
289    
290  sigc::signal<void, gig::File*> GigEdit::signal_file_structure_to_be_changed() {  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;      return file_structure_to_be_changed_signal;
304  }  }
305    
306  sigc::signal<void, gig::File*> GigEdit::signal_file_structure_changed() {  sigc::signal<void, gig::File*>& GigEdit::signal_file_structure_changed() {
307      return file_structure_changed_signal;      return file_structure_changed_signal;
308  }  }
309    
310  sigc::signal<void, std::list<gig::Sample*> > GigEdit::signal_samples_to_be_removed() {  sigc::signal<void, std::list<gig::Sample*> >& GigEdit::signal_samples_to_be_removed() {
311      return samples_to_be_removed_signal;      return samples_to_be_removed_signal;
312  }  }
313    
314  sigc::signal<void> GigEdit::signal_samples_removed() {  sigc::signal<void>& GigEdit::signal_samples_removed() {
315      return samples_removed_signal;      return samples_removed_signal;
316  }  }
317    
318  sigc::signal<void, gig::Region*> GigEdit::signal_region_to_be_changed() {  sigc::signal<void, gig::Region*>& GigEdit::signal_region_to_be_changed() {
319      return region_to_be_changed_signal;      return region_to_be_changed_signal;
320  }  }
321    
322  sigc::signal<void, gig::Region*> GigEdit::signal_region_changed() {  sigc::signal<void, gig::Region*>& GigEdit::signal_region_changed() {
323      return region_changed_signal;      return region_changed_signal;
324  }  }
325    
326  sigc::signal<void, gig::DimensionRegion*> GigEdit::signal_dimreg_to_be_changed() {  sigc::signal<void, gig::DimensionRegion*>& GigEdit::signal_dimreg_to_be_changed() {
327      return dimreg_to_be_changed_signal;      return dimreg_to_be_changed_signal;
328  }  }
329    
330  sigc::signal<void, gig::DimensionRegion*> GigEdit::signal_dimreg_changed() {  sigc::signal<void, gig::DimensionRegion*>& GigEdit::signal_dimreg_changed() {
331      return dimreg_changed_signal;      return dimreg_changed_signal;
332  }  }
333    
334  sigc::signal<void, gig::Sample*/*old*/, gig::Sample*/*new*/> GigEdit::signal_sample_ref_changed() {  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;      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

Legend:
Removed from v.1333  
changed lines
  Added in v.2841

  ViewVC Help
Powered by ViewVC