/*
    Copyright (C) 1999 Paul Barton-Davis 

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Id: gtk_ui.h,v 1.15 2005/04/05 13:05:29 pauld Exp $
*/

#ifndef __pbd_gtk_ui_h__
#define __pbd_gtk_ui_h__

#include <string>
#include <queue>
#include <map>

#include <pthread.h>
#include <gtk--.h>
#include <pbd/abstract_ui.h>
#include <pbd/ringbufferNPT.h>
#include <pbd/atomic.h>
#include <pbd/pool.h>
#include <pbd/error.h>
#include <pbd/lockmonitor.h>

using std::string;
using std::queue;

class Touchable;

namespace Gtkmmext {

class TextViewer;

class UI : public AbstractUI

{
  public:
	UI (string name, int *argc, char **argv[], string rcfile);
	virtual ~UI ();

	static UI *instance() { return theGtkUI; }

	/* Abstract UI interfaces */

	bool running ();
	void quit    ();
	void kill    ();
	int  load_rcfile (string);
	void request (RequestType); 
	void run (Receiver &old_receiver);
	void call_slot (SigC::Slot0<void>);
	void call_slot_locked (SigC::Slot0<void>);
	void touch_display (Touchable *);
	void receive (Transmitter::Channel, const char *);
	void register_thread (pthread_t, string);

	bool caller_is_gui_thread () { 
		return pthread_equal (gui_thread, pthread_self());
	}

	/* Gtk-UI specific interfaces */

	void set_tip (Gtk::Widget *, const gchar *txt, const gchar *hlp = 0);
	void set_state (Gtk::Widget *w, GtkStateType state);
	void idle_add (int (*)(void *), void *);
	void timeout_add (unsigned int, int (*)(void *), void *);
	void popup_error (const char *text);
	void flush_pending ();
	void toggle_errors ();

	template<class T> static gint idle_delete (T *obj) { delete obj; return false; }
	template<class T> static void delete_when_idle (T *obj) {
		Gtk::Main::idle.connect (bind (slot (&UI::idle_delete<T>), obj));
	}

	GdkColor get_color (const string& prompt, bool& picked, gdouble *initial = 0);

	/* starting is sent just before we enter the main loop,
	   stopping just after we return from it (at the top level)
	*/

	SigC::Signal0<void> starting;
	SigC::Signal0<void> stopping;

	static gint just_hide_it (GdkEventAny *, Gtk::Window *);

  protected:
	virtual void handle_fatal (const char *);
	virtual void display_message (const char *prefix, gint prefix_len, 
				      GtkStyle *style, const char *msg);

	/* stuff to invoke member functions in another
	   thread so that we can keep the GUI running.
	*/

	template<class UI_CLASS> struct thread_arg {
	    UI_CLASS *ui;
	    void (UI_CLASS::*func)(void *);
	    void *arg;
	};

	template<class UI_CLASS> static void *start_other_thread (void *arg);
	template<class UI_CLASS> void other_thread (void (UI_CLASS::*func)(void *), void *arg = 0);

  private:
	struct Request {

	    /* this once used anonymous unions to merge elements
	       that are never part of the same request. that makes
	       the creation of a legal copy constructor difficult
	       because of the semantics of the slot member.
	    */

	    RequestType type;
	    Touchable *display;
	    const char *msg;
	    GtkStateType new_state;
	    int (*function)(void *);
	    Gtk::Widget *widget;
	    Transmitter::Channel chn;
	    void *arg;
	    const char *msg2;
	    unsigned int timeout;
	    SigC::Slot0<void> slot;

	    /* this is for CallSlotLocked requests */

	    pthread_mutex_t slot_lock;
	    pthread_cond_t  slot_cond;

	    Request ();
	    ~Request () { 
		    if (type == ErrorMessage && msg) {
			    /* msg was strdup()'ed */
			    free ((char *)msg);
		    }
	    }
	};

	static UI *theGtkUI;
	static pthread_t gui_thread;
	bool _active;
	string _ui_name;
	Gtk::Main *theMain;
	Gtk::Tooltips *tips;
	TextViewer *errors;

	/* error message display styles */

	GtkStyle *error_message_style;
	GtkStyle *info_message_style;
	GtkStyle *warning_message_style;
	GtkStyle *fatal_message_style;

	int signal_pipe[2];
	PBD::Lock request_buffer_map_lock;
	typedef std::map<pthread_t,RingBufferNPT<Request>* > RequestBufferMap;
	RequestBufferMap request_buffers;
	Request* get_request(RequestType);
	pthread_key_t thread_request_buffer_key;

	int setup_signal_pipe ();

	void handle_ui_requests ();
	void do_request (Request *);
	void send_request (Request *);
	static void signal_pipe_callback (void *, gint, GdkInputCondition);
	void process_error_message (Transmitter::Channel, const char *);
	void do_quit ();

	void color_selection_done (bool status);
	gint color_selection_deleted (GdkEventAny *);
	bool color_picked;
};

template<class UI_CLASS> void *
UI::start_other_thread (void *arg)

{
	thread_arg<UI_CLASS> *ta = (thread_arg<UI_CLASS> *) arg;
	(ta->ui->*ta->func)(ta->arg);
	delete ta;
	return 0;
}

template<class UI_CLASS> void
UI::other_thread (void (UI_CLASS::*func)(void *), void *arg)

{
	pthread_t thread_id;
	thread_arg<UI_CLASS> *ta = new thread_arg<UI_CLASS>;

	ta->ui = dynamic_cast<UI_CLASS *> (this);
	if (ta->ui == 0) {
		error << "UI::other thread called illegally"
		      << endmsg;
		return;
	}
	ta->func = func;
	ta->arg = arg;
	pthread_create (&thread_id, 0, start_other_thread, ta);
}


} /* namespace */

#endif /* __pbd_gtk_ui_h__ */
