+#include "tilda-window-dbus-glue.h"
+#include "tomboykeybinder.h"
+
+/**
+ * Find the TildaTerminal corresponding to the currently selected
+ * tab in self->notebook. This could go away if TildaTerminal were
+ * a proper subclass of GtkWidget.
+ */
+static TildaTerminal *
+tilda_window_find_current_terminal (TildaWindow *self)
+{
+ debug_enter();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ gint i;
+ TildaTerminal *ret;
+ gint current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK(self->notebook));
+ GtkWidget *box = gtk_notebook_get_nth_page (GTK_NOTEBOOK(self->notebook), current_page);
+
+ for (i=0; i<self->terms->len; ++i)
+ {
+ ret = g_ptr_array_index (self->terms, i);
+
+ if (ret->hbox == box)
+ return ret;
+ }
+
+ debug_printf ("ERROR: unable to find current terminal!\n");
+ return NULL;
+}
+
+static gint
+tilda_window_find_next_free_terminal_number (TildaWindow *self)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ gint i, j;
+ gboolean found;
+
+ for (i=0; i<INT_MAX; ++i)
+ {
+ found = FALSE;
+
+ for (j=0; j<self->terms->len; ++j)
+ {
+ TildaTerminal *tt = g_ptr_array_index (self->terms, j);
+
+ if (tt->number == i)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found)
+ return i;
+ }
+
+ return 0;
+}
+
+static void
+tilda_window_show_hide_tabs_if_appropriate (TildaWindow *self)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ /* If we only have one tab, we have a choice to make, otherwise, always show tabs */
+ if (gtk_notebook_get_n_pages (GTK_NOTEBOOK(self->notebook)) <= 1)
+ {
+ if (self->always_show_tabs)
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK(self->notebook), TRUE);
+ else
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK(self->notebook), FALSE);
+ }
+ else
+ {
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK(self->notebook), TRUE);
+ }
+}
+
+/**
+ * Clean up and remove self completely from the program
+ *
+ * Should only be used by DBus...
+ */
+gboolean
+tilda_window_close (TildaWindow *self)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ tilda_controller_remove_window (TILDA_CONTROLLER(self->controller), self->number);
+
+ return TRUE;
+}
+
+gboolean
+tilda_window_add_terminal (TildaWindow *self)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ gint number;
+ TildaTerminal *tt;
+ GtkWidget *label;
+ gint notebook_index;
+
+ number = tilda_window_find_next_free_terminal_number (self);
+ tt = g_object_new (TILDA_TYPE_TERMINAL,
+ "number", number,
+ "parent-window", self,
+ NULL);
+ g_ptr_array_add (self->terms, tt);
+
+ label = gtk_label_new ("Tilda");
+ notebook_index = gtk_notebook_append_page (GTK_NOTEBOOK(self->notebook), tt->hbox, label);
+ gtk_notebook_set_tab_label_packing (GTK_NOTEBOOK(self->notebook), tt->hbox,
+ self->full_width_tabs, TRUE, GTK_PACK_START);
+ gtk_notebook_set_current_page (GTK_NOTEBOOK(self->notebook), notebook_index);
+
+ /* Always show tabs if we have > 1 tab open */
+ tilda_window_show_hide_tabs_if_appropriate (self);
+
+ /* Focus the VTE Terminal */
+ gtk_widget_grab_focus (tt->vte_term);
+
+ return TRUE;
+}
+
+/**
+ * Remove the TildaTerminal with the given number from the given
+ * TildaWindow.
+ *
+ * Return: TRUE on success, FALSE otherwise.
+ */
+gboolean
+tilda_window_remove_terminal (TildaWindow *self, gint terminal_number)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+ debug_assert (terminal_number >= 0);
+
+ gint i;
+
+ for (i=0; i<self->terms->len; ++i)
+ {
+ TildaTerminal *tt = g_ptr_array_index (self->terms, i);
+
+ if (tt->number == terminal_number)
+ {
+ gint notebook_index = gtk_notebook_page_num (GTK_NOTEBOOK(self->notebook), tt->hbox);
+
+ /* Make sure the index was valid */
+ if (notebook_index == -1)
+ {
+ debug_printf ("ERROR: Bad Notebook Tab\n");
+ return FALSE;
+ }
+
+ /* Actually remove the terminal */
+ gtk_notebook_remove_page (GTK_NOTEBOOK (self->notebook), notebook_index);
+
+ /* We should hide the tabs if there is only one tab left */
+ tilda_window_show_hide_tabs_if_appropriate (self);
+
+ /* Remove the term from our lists, then free it */
+ g_ptr_array_remove_fast (self->terms, tt);
+ g_object_unref (G_OBJECT(tt));
+
+ /* With no pages left, it's time to remove this window */
+ if (gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->notebook)) < 1)
+ {
+ debug_printf ("no terminals left, closing window %d\n", self->number);
+ tilda_controller_remove_window (TILDA_CONTROLLER(self->controller), self->number);
+ }
+
+ /* Leave the loop, we're done */
+ break;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * This sets up the given TildaWindow for the capability of real
+ * transparency, if the X server is capable of it. */
+static void
+tilda_window_setup_real_transparency (TildaWindow *self)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ GdkScreen *screen;
+ GdkColormap *colormap;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET(self->window));
+ colormap = gdk_screen_get_rgba_colormap (screen);
+
+ /* If possible, set the RGBA colormap so VTE can use real alpha
+ * channels for transparency. */
+ if (colormap != NULL && gdk_screen_is_composited (screen))
+ {
+ gtk_widget_set_colormap (GTK_WIDGET(self->window), colormap);
+ self->have_real_transparency = TRUE;
+ return;
+ }
+
+ self->have_real_transparency = FALSE;
+}
+
+/* Center the given TildaWindow in the horizontal axis */
+static void
+tilda_window_center_horizontally (TildaWindow *self)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ const gint screen_center = gdk_screen_width() / 2;
+ const gint tilda_center = self->width / 2;
+ const gint center_coord = screen_center - tilda_center;
+
+ g_object_set (G_OBJECT(self), "x-position", center_coord, NULL);
+}
+
+/* Center the given TildaWindow in the vertical axis */
+static void
+tilda_window_center_vertically (TildaWindow *self)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ const gint screen_center = gdk_screen_height() / 2;
+ const gint tilda_center = self->height / 2;
+ const gint center_coord = screen_center - tilda_center;
+
+ g_object_set (G_OBJECT(self), "y-position", center_coord, NULL);
+}
+
+/* Shamelessly adapted (read: ripped off) from gdk_window_focus() and
+ * http://code.google.com/p/ttm/ trunk/src/window.c set_active()
+ *
+ * Also, more thanks to halfline and marnanel from irc.gnome.org #gnome
+ * for their help in figuring this out.
+ *
+ * Thank you.
+ */
+
+/* This function will make sure that tilda window becomes active (gains
+ * the focus) when it is called.
+ *
+ * This has to be the worst possible way of making this work, but it was the
+ * only way to get metacity to play nicely. All the other WM's are so nice,
+ * why oh why does metacity hate us so?
+ */
+static void
+tilda_window_set_active (TildaWindow *self)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ Display *x11_display = GDK_WINDOW_XDISPLAY( self->window->window );
+ Window *x11_window = GDK_WINDOW_XWINDOW( self->window->window );
+ Window *x11_root_window = GDK_WINDOW_XWINDOW( gtk_widget_get_root_window (self->window) );
+ GdkScreen *screen = gtk_widget_get_screen (self->window);
+
+ XEvent event;
+ long mask = SubstructureRedirectMask | SubstructureNotifyMask;
+
+ if (gdk_x11_screen_supports_net_wm_hint (screen,
+ gdk_atom_intern_static_string ("_NET_ACTIVE_WINDOW")))
+ {
+ event.xclient.type = ClientMessage;
+ event.xclient.serial = 0;
+ event.xclient.send_event = True;
+ event.xclient.display = x11_display;
+ event.xclient.window = x11_window;
+ event.xclient.message_type = gdk_x11_get_xatom_by_name ("_NET_ACTIVE_WINDOW");
+
+ event.xclient.format = 32;
+ event.xclient.data.l[0] = 2; /* pager */
+ event.xclient.data.l[1] = tomboy_keybinder_get_current_event_time(); /* timestamp */
+ event.xclient.data.l[2] = 0;
+ event.xclient.data.l[3] = 0;
+ event.xclient.data.l[4] = 0;
+
+ XSendEvent (x11_display, x11_root_window, False, mask, &event);
+ }
+ else
+ {
+ /* The WM doesn't support the EWMH standards. We'll print a warning and
+ * try this, though it probably won't work... */
+ g_printerr (_("WARNING: Window manager (%s) does not support EWMH hints\n"),
+ gdk_x11_screen_get_window_manager_name (screen));
+ XRaiseWindow (x11_display, x11_window);
+ }
+}
+
+static void
+tilda_window_keybinding_cb (const gchar *keystr, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(data));
+
+ TildaWindow *self = TILDA_WINDOW(data);
+ TildaTerminal *tt;
+
+ /* This call sets the X11 window property _NET_WM_USER_TIME, which GTK+ normally
+ * sets for us. However, because this callback is activated via a global keybinding,
+ * we see the event before GDK / GTK+ does. Therefore, to get the focus, we must
+ * set the property ourselves. */
+ gdk_x11_window_set_user_time (GTK_WIDGET(self->window)->window,
+ tomboy_keybinder_get_current_event_time());
+
+ switch (self->state)
+ {
+ case WINDOW_UP: /* Pull the window up */
+
+ /* Bugfix: having this here keeps the tilda window from being
+ * hidden if you turn off "stick", pull it down on workspace 1,
+ * switch to workspace 2, then pull it up and back down. Without
+ * this, something in metacity (at least) hides the window. Stupid. */
+ gtk_window_deiconify (GTK_WINDOW(self->window));
+
+ /* Re-set the window properties that do not linger after hiding the
+ * window. I know this looks stupid, but it keeps all of the state-
+ * changing code in the place it belongs: the property-setting code. */
+ g_object_set (G_OBJECT(self),
+ "keep-above", self->keep_above,
+ "stick", self->stick,
+ NULL);
+ gtk_widget_show (GTK_WIDGET(self->window));
+
+ /* Bugfix: this code fixes metacity-2.22 */
+ tilda_window_set_active (self);
+
+ /* Focusing the term here works perfectly, near as I can tell */
+ tt = tilda_window_find_current_terminal (self);
+ gtk_widget_grab_focus (GTK_WIDGET(tt->vte_term));
+
+ self->state = WINDOW_DOWN;
+ break;
+
+ case WINDOW_DOWN: /* Pull the window up */
+
+ gtk_widget_hide (GTK_WIDGET(self->window));
+
+ self->state = WINDOW_UP;
+ break;
+
+ default:
+ debug_printf ("ERROR: Window is in a bad state!\n");
+
+ /* Pretend we're down, for good measure.... */
+ self->state = WINDOW_DOWN;
+ break;
+ }
+}
+
+/**
+ * Attempt to bind the new_key to show this window.
+ *
+ * Return: TRUE if successful, FALSE otherwise.
+ */
+static gboolean
+tilda_window_try_to_bind_key (TildaWindow *self, const gchar *new_key)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ gboolean ret = FALSE;
+
+ /* Make sure the new key is not null in any way */
+ if (new_key == NULL || g_ascii_strcasecmp("", new_key) == 0)
+ return FALSE;
+
+ /* Check that no other windows are using the key */
+ // FIXME: there should be a hidden option to disable this. Maybe some people want
+ // to have logs in two Tildas, and just show them with one key. Crazy...
+ if (tilda_controller_global_key_in_use(TILDA_CONTROLLER(self->controller), new_key))
+ return FALSE;
+
+ /* Unbind if we were set */
+ if (self->key)
+ tomboy_keybinder_unbind (self->key, tilda_window_keybinding_cb);
+
+ ret = tomboy_keybinder_bind (new_key, tilda_window_keybinding_cb, self);
+
+ /* If it was successful, update the self->key variable and be done with it */
+ if (ret)
+ {
+ g_free (self->key);
+ self->key = g_strdup (new_key);
+ return TRUE;
+ }
+
+ g_printerr (_("Bind key '%s' failed. Reverting to original keybinding\n"), self->key);
+
+ /* Not successful, so rebind the old key, and return FALSE */
+ if (self->key != NULL && g_ascii_strcasecmp("",self->key) != 0)
+ {
+ ret = tomboy_keybinder_bind (self->key, tilda_window_keybinding_cb, self);
+
+ /* Check that it went ok */
+ if (!ret)
+ g_printerr (_("Unable to re-bind original key '%s'. Oh shit...\n"), self->key);
+ }
+ else
+ g_printerr (_("No original key to revert to!\n"));
+
+ return FALSE;
+}
+
+static void
+tilda_window_dbus_register_object (TildaWindow *self)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ gchar *object_path;
+
+ /* If DBus is not running, leave */
+ if (!dbus_connection)
+ return;
+
+ /* Register this object with DBus */
+ object_path = g_strdup_printf ("/net/sourceforge/Tilda/Window%d", self->number);
+ dbus_g_connection_register_g_object (dbus_connection, object_path, G_OBJECT(self));
+ g_free (object_path);
+}
+
+/*******************************************************************************
+ * All accelerator-related stuff below
+ ******************************************************************************/
+
+typedef gboolean (*TildaWindowAccelCallback) (TildaWindow *self, gpointer data);
+
+/**
+ * This function updates the accelerator used to call the given function for this
+ * TildaWindow. If accel is NULL, then func will be removed (no accelerator will
+ * cause func to be called).
+ *
+ * Returns: TRUE on success, FALSE on failure
+ */
+static gboolean
+tilda_window_update_accelerator (TildaWindow *self, /* object */
+ gchar **accel_to_update, /* self->??? */
+ const gchar *accel, /* new accel */
+ const TildaWindowAccelCallback func)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+ debug_assert (accel_to_update != NULL);
+ debug_assert (func != NULL);
+
+ gboolean ret;
+ guint key;
+ GdkModifierType mod;
+ GClosure *closure;
+
+ /* Remove the old accelerator if there was a previous one set */
+ if (*accel_to_update != NULL)
+ {
+ /* This should always parse, we've done it before! */
+ gtk_accelerator_parse (*accel_to_update, &key, &mod);
+ ret = gtk_accel_group_disconnect_key (self->accel_group, key, mod);
+ }
+
+ /* If we are just removing the old accelerator, we're already done */
+ if (accel == NULL)
+ {
+ g_free (*accel_to_update);
+ *accel_to_update = NULL;
+ return TRUE;
+ }
+
+ /* Create the closure for this function */
+ closure = g_cclosure_new_swap (G_CALLBACK(func), self, NULL);
+
+ /* Try to parse the new accelerator */
+ gtk_accelerator_parse (accel, &key, &mod);
+
+ if (!gtk_accelerator_valid (key, mod))
+ {
+ g_warning (_("Failed to parse accelerator: %s\n"), accel);
+
+ /* Re-install the old accelerator */
+ if (*accel_to_update != NULL)
+ {
+ gtk_accelerator_parse (*accel_to_update, &key, &mod);
+ gtk_accel_group_connect (self->accel_group, key, mod, GTK_ACCEL_VISIBLE, closure);
+ }
+ return FALSE;
+ }
+
+ /* All good, g_free() the old accelerator, g_strdup() the new one */
+ g_free (*accel_to_update);
+ *accel_to_update = g_strdup(accel);
+
+ /* Add the new accelerator */
+ gtk_accel_group_connect (self->accel_group, key, mod, GTK_ACCEL_VISIBLE, closure);
+
+ return TRUE;
+}
+
+static gboolean
+tilda_window_accel_quit_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ tilda_window_close (self);
+
+ /* Do not keep propagating */
+ return TRUE;
+}
+
+static gboolean
+tilda_window_accel_next_tab_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ gint num_pages;
+ gint current_page;
+
+ num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK(self->notebook));
+ current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK(self->notebook));
+
+ /* Go to next page (with wrapping) */
+ if (num_pages != (current_page + num_pages))
+ gtk_notebook_next_page (GTK_NOTEBOOK(self->notebook));
+ else
+ gtk_notebook_set_current_page (GTK_NOTEBOOK(self->notebook), num_pages-1);
+
+ /* Do not keep propagating */
+ return TRUE;
+}
+
+static gboolean
+tilda_window_accel_prev_tab_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ gint num_pages;
+ gint current_page;
+
+ num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK(self->notebook));
+ current_page = gtk_notebook_get_current_page (GTK_NOTEBOOK(self->notebook));
+
+ if ((num_pages-1) != current_page)
+ gtk_notebook_prev_page (GTK_NOTEBOOK(self->notebook));
+ else
+ gtk_notebook_set_current_page (GTK_NOTEBOOK(self->notebook), 0);
+
+ /* Do not keep propagating */
+ return TRUE;
+}
+
+static gboolean
+tilda_window_accel_add_term_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ tilda_window_add_terminal (self);
+
+ /* Do not keep propagating */
+ return TRUE;
+}
+
+static gboolean
+tilda_window_accel_remove_term_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ TildaTerminal *tt = tilda_window_find_current_terminal (self);
+
+ tilda_window_remove_terminal (self, tt->number);
+
+ /* Do not keep propagating */
+ return TRUE;
+}
+
+static gboolean
+tilda_window_accel_copy_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ TildaTerminal *tt = tilda_window_find_current_terminal (self);
+
+ vte_terminal_copy_clipboard (VTE_TERMINAL(tt->vte_term));
+
+ /* Do not keep propagating */
+ return TRUE;
+}
+
+static gboolean
+tilda_window_accel_paste_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ TildaTerminal *tt = tilda_window_find_current_terminal (self);
+
+ vte_terminal_paste_clipboard (VTE_TERMINAL(tt->vte_term));
+
+ /* Do not keep propagating */
+ return TRUE;
+}
+
+static gboolean
+tilda_window_accel_goto_generic (TildaWindow *self, guint number)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK(self->notebook), number-1);
+
+ /* Do not keep propagating */
+ return TRUE;
+}
+
+static gboolean
+tilda_window_accel_goto_1_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ return tilda_window_accel_goto_generic (self, 1);
+}
+
+static gboolean
+tilda_window_accel_goto_2_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ return tilda_window_accel_goto_generic (self, 2);
+}
+
+static gboolean
+tilda_window_accel_goto_3_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ return tilda_window_accel_goto_generic (self, 3);
+}
+
+static gboolean
+tilda_window_accel_goto_4_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ return tilda_window_accel_goto_generic (self, 4);
+}
+
+static gboolean
+tilda_window_accel_goto_5_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ return tilda_window_accel_goto_generic (self, 5);
+}
+
+static gboolean
+tilda_window_accel_goto_6_cb (TildaWindow *self, gpointer data)
+{
+ debug_enter ();
+ debug_assert (TILDA_IS_WINDOW(self));
+
+ return tilda_window_accel_goto_generic (self, 6);
+}