/[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 2474 by schoenebeck, Sun Sep 15 18:16:21 2013 UTC
# Line 1  Line 1 
1  /*  /*
2   * Copyright (C) 2007 Andreas Persson   * Copyright (C) 2007-2013 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 19  Line 19 
19    
20  #include "gigedit.h"  #include "gigedit.h"
21    
22    #include <glibmm/dispatcher.h>
23    #include <glibmm/main.h>
24  #include <gtkmm/main.h>  #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"  #include "mainwindow.h"
32    
33  #include <libintl.h>  #include "global.h"
 #include <config.h>  
34    
35  // the app has to work from a DLL as well, so we hard code argv  #ifdef __APPLE__
36  int argc = 1;  #include <dlfcn.h>
37  const char* argv_c[] = { "gigedit" };  #include <glibmm/fileutils.h>
38  char** argv = const_cast<char**>(argv_c);  #include <glibmm/miscutils.h>
39  //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
40  Gtk::Main kit(argc, argv);  
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) { }
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  static void __init_app() {  void init_app() {
120      static bool process_initialized = false;      static bool process_initialized = false;
121      if (!process_initialized) {      if (!process_initialized) {
122          std::cout << "Initializing 3rd party services needed by gigedit.\n"          std::cout << "Initializing 3rd party services needed by gigedit.\n"
123                    << std::flush;                    << std::flush;
124          setlocale(LC_ALL, "");          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);          bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
185      #endif
186          bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");          bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
187          textdomain(GETTEXT_PACKAGE);          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          // make sure thread_init() is called once and ONLY once per process
192          if (!Glib::thread_supported()) Glib::thread_init();          if (!Glib::thread_supported()) Glib::thread_init();
193    #endif
194          process_initialized = true;          process_initialized = true;
195      }      }
196  }  }
197    
198  static void __connect_signals(GigEdit* gigedit, MainWindow* mainwindow) {  void connect_signals(GigEdit* gigedit, MainWindow* mainwindow) {
199      // the signals of the "GigEdit" class are actually just proxies, that      // the signals of the "GigEdit" class are actually just proxies, that
200      // is they simply forward the signals of the internal classes to the      // is they simply forward the signals of the internal classes to the
201      // outer world      // outer world
# Line 76  static void __connect_signals(GigEdit* g Line 223  static void __connect_signals(GigEdit* g
223      mainwindow->signal_dimreg_changed().connect(      mainwindow->signal_dimreg_changed().connect(
224          gigedit->signal_dimreg_changed().make_slot()          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(      mainwindow->signal_sample_ref_changed().connect(
230          gigedit->signal_sample_ref_changed().make_slot()          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  }  }
239    
240  int GigEdit::run() {  } // namespace
241      __init_app();  
242      MainWindow window;  GigEdit::GigEdit() {
243      __connect_signals(this, &window);      state = NULL;
     kit.run(window);  
     return 0;  
244  }  }
245    
246  int GigEdit::run(const char* pFileName) {  int GigEdit::run(int argc, char* argv[]) {
247      __init_app();      init_app();
248    
249        Gtk::Main kit(argc, argv);
250    
251    //FIXME: for some reason AC GETTEXT check fails on the Mac cross compiler?
252    #if (/*HAVE_GETTEXT &&*/ defined(__APPLE__))
253        // Gtk::Main binds the gtk locale to a possible non-existent
254        // directory. If we have bundled gtk locale files, we rebind here,
255        // after the Gtk::Main constructor.
256        if (!gigedit_localedir.empty()) {
257            bindtextdomain("gtk20", gigedit_localedir.c_str());
258        }
259    #endif
260    
261      MainWindow window;      MainWindow window;
262      __connect_signals(this, &window);      connect_signals(this, &window);
263      if (pFileName) window.load_file(pFileName);      if (argc >= 2) window.load_file(argv[1]);
264      kit.run(window);      kit.run(window);
265      return 0;      return 0;
266  }  }
267    
268  int GigEdit::run(gig::Instrument* pInstrument) {  int GigEdit::run(gig::Instrument* pInstrument) {
269      __init_app();      init_app();
270      MainWindow window;  
271      __connect_signals(this, &window);      GigEditState state(this);
272      if (pInstrument) window.load_instrument(pInstrument);      this->state = &state;
273      kit.run(window);      state.run(pInstrument);
274        this->state = NULL;
275      return 0;      return 0;
276  }  }
277    
278  sigc::signal<void, gig::File*> GigEdit::signal_file_structure_to_be_changed() {  void GigEdit::on_note_on_event(int key, int velocity) {
279        if (!this->state) return;
280        GigEditState* state = (GigEditState*) this->state;
281        state->window->signal_note_on().emit(key, velocity);
282    }
283    
284    void GigEdit::on_note_off_event(int key, int velocity) {
285        if (!this->state) return;
286        GigEditState* state = (GigEditState*) this->state;
287        state->window->signal_note_off().emit(key, velocity);
288    }
289    
290    sigc::signal<void, gig::File*>& GigEdit::signal_file_structure_to_be_changed() {
291      return file_structure_to_be_changed_signal;      return file_structure_to_be_changed_signal;
292  }  }
293    
294  sigc::signal<void, gig::File*> GigEdit::signal_file_structure_changed() {  sigc::signal<void, gig::File*>& GigEdit::signal_file_structure_changed() {
295      return file_structure_changed_signal;      return file_structure_changed_signal;
296  }  }
297    
298  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() {
299      return samples_to_be_removed_signal;      return samples_to_be_removed_signal;
300  }  }
301    
302  sigc::signal<void> GigEdit::signal_samples_removed() {  sigc::signal<void>& GigEdit::signal_samples_removed() {
303      return samples_removed_signal;      return samples_removed_signal;
304  }  }
305    
306  sigc::signal<void, gig::Region*> GigEdit::signal_region_to_be_changed() {  sigc::signal<void, gig::Region*>& GigEdit::signal_region_to_be_changed() {
307      return region_to_be_changed_signal;      return region_to_be_changed_signal;
308  }  }
309    
310  sigc::signal<void, gig::Region*> GigEdit::signal_region_changed() {  sigc::signal<void, gig::Region*>& GigEdit::signal_region_changed() {
311      return region_changed_signal;      return region_changed_signal;
312  }  }
313    
314  sigc::signal<void, gig::DimensionRegion*> GigEdit::signal_dimreg_to_be_changed() {  sigc::signal<void, gig::DimensionRegion*>& GigEdit::signal_dimreg_to_be_changed() {
315      return dimreg_to_be_changed_signal;      return dimreg_to_be_changed_signal;
316  }  }
317    
318  sigc::signal<void, gig::DimensionRegion*> GigEdit::signal_dimreg_changed() {  sigc::signal<void, gig::DimensionRegion*>& GigEdit::signal_dimreg_changed() {
319      return dimreg_changed_signal;      return dimreg_changed_signal;
320  }  }
321    
322  sigc::signal<void, gig::Sample*/*old*/, gig::Sample*/*new*/> GigEdit::signal_sample_ref_changed() {  sigc::signal<void, gig::Sample*>& GigEdit::signal_sample_changed() {
323        return sample_changed_signal;
324    }
325    
326    sigc::signal<void, gig::Sample*/*old*/, gig::Sample*/*new*/>& GigEdit::signal_sample_ref_changed() {
327      return sample_ref_changed_signal;      return sample_ref_changed_signal;
328  }  }
329    
330    sigc::signal<void, int/*key*/, int/*velocity*/>& GigEdit::signal_keyboard_key_hit() {
331        return keyboard_key_hit_signal;
332    }
333    
334    sigc::signal<void, int/*key*/, int/*velocity*/>& GigEdit::signal_keyboard_key_released() {
335        return keyboard_key_released_signal;
336    }
337    
338    #ifdef OLD_THREADS
339    Glib::StaticMutex GigEditState::mutex = GLIBMM_STATIC_MUTEX_INIT;
340    #else
341    Glib::Threads::Mutex GigEditState::mutex;
342    #endif
343    Glib::Dispatcher* GigEditState::dispatcher = 0;
344    GigEditState* GigEditState::current = 0;
345    
346    void GigEditState::open_window_static() {
347        GigEditState* c = GigEditState::current;
348        c->open.signal();
349        c->open_window();
350    }
351    
352    void GigEditState::open_window() {
353        window = new MainWindow();
354    
355        connect_signals(parent, window);
356        if (instrument) window->load_instrument(instrument);
357    
358        window->signal_hide().connect(sigc::mem_fun(this,
359                                                    &GigEditState::close_window));
360        window->present();
361    }
362    
363    void GigEditState::close_window() {
364        delete window;
365        close.signal();
366    }
367    
368    void GigEditState::main_loop_run(Cond* initialized) {
369        int argc = 1;
370        const char* argv_c[] = { "gigedit" };
371        char** argv = const_cast<char**>(argv_c);
372        Gtk::Main main_loop(argc, argv);
373    //FIXME: for some reason AC GETTEXT check fails on the Mac cross compiler?
374    #if (/*HAVE_GETTEXT &&*/ defined(__APPLE__))
375        if (!gigedit_localedir.empty()) {
376            bindtextdomain("gtk20", gigedit_localedir.c_str());
377        }
378    #endif
379    
380        dispatcher = new Glib::Dispatcher();
381        dispatcher->connect(sigc::ptr_fun(&GigEditState::open_window_static));
382        initialized->signal();
383    
384        main_loop.run();
385    }
386    
387    #if defined(__APPLE__)
388    
389    void GigEditState::runInCFMainLoop(void* info) {
390        printf("runInCFMainLoop() entered\n"); fflush(stdout);
391        GigEditState* state = static_cast<GigEditState*>(info);
392        state->main_loop_run(
393            &state->initialized
394        );
395        printf("runInCFMainLoop() left\n"); fflush(stdout);
396    }
397    
398    #endif // __APPLE__
399    
400    void GigEditState::run(gig::Instrument* pInstrument) {
401        mutex.lock(); // lock access to static variables
402    
403        static bool main_loop_started = false;
404        if (!main_loop_started) {
405    #if defined(__APPLE__)
406            // spawn GUI on main thread :
407            //     On OS X the Gtk GUI can only be launched on a process's "main"
408            //     thread. When trying to launch the Gtk GUI on any other thread,
409            //     there will only be a white box, because the GUI would not receive
410            //     any events, since it would listen to the wrong system event loop.
411            //     So far we haven't investigated whether there is any kind of
412            //     circumvention to allow doing that also on other OS X threads.
413            {
414                // In case the sampler was launched as standalone sampler (not as
415                // plugin), use the following global callback variable hack ...
416                if (g_mainThreadCallbackSupported) {
417                    printf("Setting callback ...\n"); fflush(stdout);
418                    g_mainThreadCallback = runInCFMainLoop;
419                    g_mainThreadCallbackInfo = this;
420                    g_fireMainThreadCallback = true;
421                    printf("Callback variables set.\n"); fflush(stdout);
422                } else { // Sampler was launched as (i.e. AU / VST) plugin ...
423                    // When the sampler was launched as plugin, we have no idea
424                    // whether any sampler thread is the process's "main" thread.
425                    // So that's why we are trying to use Apple's API for trying to
426                    // launch our callback function on the process's main thread.
427                    // However this will only work, if the plugin host application
428                    // established a CF event loop, that is if the application is
429                    // using Cocoa for its GUI. For other host applications the
430                    // callback will never be executed and thus gigedit would not
431                    // popup.
432                    
433                    // should be pretty much the same as the Objective-C solution below with macHelperRunCFuncOnMainThread()
434                    /*CFRunLoopSourceContext sourceContext = CFRunLoopSourceContext();
435                    sourceContext.info = this;
436                    sourceContext.perform = runInCFMainLoop;
437                    printf("CFRunLoopSourceCreate\n"); fflush(stdout);
438                    CFRunLoopSourceRef source = CFRunLoopSourceCreate(
439                        kCFAllocatorDefault, // allocator
440                        1, // priority
441                        &sourceContext
442                    );
443                    printf("CFRunLoopAddSource\n"); fflush(stdout);
444                    CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
445                    CFRelease(source);*/
446                    
447                    // use Apple's Objective-C API to call our callback function
448                    // 'runInCFMainLoop()' on the process's "main" thread
449                    macHelperRunCFuncOnMainThread(runInCFMainLoop, this);
450                }
451            }
452    #else
453      #ifdef OLD_THREADS
454            Glib::Thread::create(
455                sigc::bind(sigc::ptr_fun(&GigEditState::main_loop_run),
456                           &initialized),
457                false);
458      #else
459            Glib::Threads::Thread::create(
460                sigc::bind(sigc::ptr_fun(&GigEditState::main_loop_run),
461                           &initialized));
462      #endif
463    #endif
464            printf("Waiting for GUI being initialized (on main thread) ....\n"); fflush(stdout);
465            initialized.wait();
466            printf("GUI is now initialized. Everything done.\n"); fflush(stdout);
467            main_loop_started = true;
468        }
469        instrument = pInstrument;
470        current = this;
471        dispatcher->emit();
472        open.wait(); // wait until the GUI thread has read current
473        mutex.unlock();
474        close.wait(); // sleep until window is closed
475    }
476    
477    #if defined(WIN32)
478    extern "C" {
479        BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
480        {
481            switch (reason) {
482            case DLL_PROCESS_ATTACH:
483                gigedit_dll_handle = instance;
484                break;
485            }
486            return TRUE;
487        }
488    }
489    #endif

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

  ViewVC Help
Powered by ViewVC