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

Legend:
Removed from v.1328  
changed lines
  Added in v.2844

  ViewVC Help
Powered by ViewVC