Logo Search packages:      
Sourcecode: jackbeat version File versions

gui.c

/*
 *   Jackbeat - JACK sequencer
 *    
 *   Copyright (c) 2004-2006 Olivier Guilyardi <olivier {at} samalyse {dot} com>
 *    
 *   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.
 *
 *   SVN:$Id: gui.c 71 2007-03-01 13:11:20Z olivier $
 */


#include <stdio.h>
#include <stdlib.h>
#include <gtk/gtk.h>
#include <string.h>
#include <libgen.h>
#include <config.h>
#include <assert.h>
#include <unistd.h>
#include <math.h>
#include <sys/types.h>
#include <dirent.h>

#include "song.h"
#include "sequence.h"
#include "sample.h"
#include "gui.h"
#include "jab.h"
#include "error.h"
#include "grid.h"

#ifdef MEMDEBUG
#include "memdebug.h"
#endif

#ifdef DMALLOC
#include "dmalloc.h"
#endif

#define GUI_ANIMATION_INTERVAL 30 // miliseconds

#define DEBUG(M, ...) { printf("GUI %.2d  %s(): ",gui->instance_index, __func__); printf(M, ## __VA_ARGS__); printf("\n"); }

typedef struct gui_track_button_t
{
  GtkWidget * widget;
  GtkWidget * mask_button;
  int         track;
  int         beat;
  gui_t *     gui;
} gui_track_button_t;

typedef struct gui_track_sample_t
{
  gui_t * gui;
  int     track;
  char *  sample_fname;
} gui_track_sample_t;

/* GUI object type */
struct gui_t
{
  /* nested objects */
  song_t *              song;
  sequence_t *          sequence;
  rc_t *                rc;
  arg_t *               arg;
  /* flags & info */
  char                  filename[256];
  int                   sequence_is_modified;
  int                   refreshing;
  int                   filename_is_set;
  int                   transpose_volumes_round;
  int                   instance_index;
  char                  name_prefix[256];
  /* widgets */
  GtkWidget *           window;
  GtkWidget *           tracks_box;
  GtkWidget *           main_vbox;
  GtkWidget *           tracks_num;
  GtkWidget *           beats_num;
  GtkWidget *           measure_len;
  GtkWidget *           bpm;
  GtkWidget *           loop;
  GtkWidget *           rewind;
  GtkWidget *           progress_window;
  GtkWidget *           progress_bar;
  GtkTooltips *         tooltips;
  GtkWidget **          volume_spinners;
  GtkWidget *           file_selection;
  grid_t *              grid;
  /* callback helpers */
  gui_track_button_t *  tracks_buttons;
  gui_track_sample_t *  track_samples;
  gint                  timeout_tag;
};

static int gui_instance_counter = 0;
static int gui_instance_num = 0;

static void gui_clear_sequence (gui_t * gui, guint action, GtkWidget * w);
static void gui_load_sequence (gui_t * gui, guint action, GtkWidget * w);
static void gui_save_sequence (gui_t * gui, guint action, GtkWidget * w);
static void gui_save_as_sequence (gui_t * gui, guint action, GtkWidget * w);
static void gui_auto_connect (gui_t *gui,
                              guint callback_action, GtkWidget * menu_item);
static void gui_set_transport (gui_t *gui,
                               guint action, GtkWidget * menu_item);
static void gui_new_instance (gui_t * gui, guint action, GtkWidget * w);
static void gui_close_from_menu (gui_t * gui, guint action, GtkWidget * w);
static void gui_exit (gui_t * gui, guint action, GtkWidget * w);
static void gui_new_child (rc_t *rc,  arg_t *arg, gui_t *parent, 
                           song_t *song, sequence_t *sequence, char *filename);
static void gui_duplicate_sequence (gui_t * gui, guint action, GtkWidget * w);
static void gui_refresh (gui_t * gui);
static void gui_set_resampler_type (gui_t *gui,
                                    guint type, GtkWidget *menu_item);
static void gui_transpose_volumes_dialog (gui_t *gui, guint action, GtkWidget *widget);
static void gui_draw_tracks (gui_t * gui);
static void gui_export_sequence (gui_t * gui, guint action, GtkWidget * w);

static GtkItemFactoryEntry gui_menu_items[] = {
  /* File menu */
  {"/_File", NULL, NULL, 0, "<Branch>"},
  {"/File/New", "<control>N", gui_new_instance, 1, "<StockItem>",
   GTK_STOCK_NEW},
  {"/File/Open", "<control>O", gui_load_sequence, 1, "<StockItem>",
   GTK_STOCK_OPEN},
  {"/File/Save", "<control>S", gui_save_sequence, 1, "<StockItem>",
   GTK_STOCK_SAVE},
  {"/File/Save as", NULL, gui_save_as_sequence, 1, "<StockItem>",
   GTK_STOCK_SAVE_AS},
  {"/File/Export waveform", NULL, gui_export_sequence, 1, "<Item>"},
  {"/File/Close", "<Control><Shift>W", gui_close_from_menu, 1, "<StockItem>",
   GTK_STOCK_CLOSE},
  {"/File/separator", NULL, NULL, 0, "<Separator>"},
  {"/File/Quit", "<control>Q", gui_exit, 1, "<StockItem>",
   GTK_STOCK_QUIT},

  /* Edit menu */ 
  {"/_Edit", NULL, NULL, 0, "<Branch>"},
  {"/Edit/Clear", NULL, gui_clear_sequence, 1, "<StockItem>", GTK_STOCK_DELETE},
  {"/Edit/Double", NULL, gui_duplicate_sequence, 1, "<Item>"},
  {"/Edit/Transpose volumes", NULL, gui_transpose_volumes_dialog, 1, "<Item>"},

  /* Options menu */
  {"/_Options", NULL, NULL, 0, "<Branch>"},
  {"/Options/Auto-connect", NULL, gui_auto_connect, 1, "<CheckItem>"},
  {"/Options/Transport control", NULL, NULL, 0, "<Branch>"},
  {"/Options/Transport control/Respond", NULL, gui_set_transport, 2,
   "<RadioItem>"},
  {"/Options/Transport control/Respond and query", NULL, gui_set_transport, 3,
   "/Options/Transport control/Respond"},
  {"/Options/Resampling quality", NULL, NULL, 0, "<Branch>"},
  {"/Options/Resampling quality/Hi-fi : sinc algorithm - more CPU", NULL, gui_set_resampler_type, 
   SEQUENCE_SINC, "<RadioItem>"},
  {"/Options/Resampling quality/Lo-fi : linear - lightweight", NULL, gui_set_resampler_type, 
   SEQUENCE_LINEAR, "/Options/Resampling quality/Hi-fi : sinc algorithm - more CPU"},
};

static gint gui_menu_nitems =
  sizeof (gui_menu_items) / sizeof (gui_menu_items[0]);

static void
gui_update_window_title (gui_t * gui)
{
  char s[128];
  sprintf (s, "%s%s - %s_%d", (gui->sequence_is_modified) ? "*" : "",
           basename (gui->filename), gui->name_prefix, gui->instance_index+1);
  gtk_window_set_title (GTK_WINDOW (gui->window), s);
}

static void
gui_set_modified (gui_t * gui, int status)
{
  gui->sequence_is_modified = status;
  gui_update_window_title (gui);
}

static void
gui_auto_connect (gui_t *gui,
                  guint callback_action, GtkWidget * menu_item)
{
  sequence_set_auto_connect (gui->sequence, (GTK_CHECK_MENU_ITEM (menu_item))->active);
  gui->rc->auto_connect = (GTK_CHECK_MENU_ITEM (menu_item))->active ? 1 : 0;
}

static void
gui_set_transport (gui_t *gui,
                   guint action, GtkWidget * menu_item)
{
  switch (action)
    {
    case 1:
      sequence_set_transport (gui->sequence, 0, 0);
      break;
    case 2:
      if (gui->rewind) gtk_widget_set_sensitive (gui->rewind, FALSE);
      sequence_set_transport (gui->sequence, 1, 0);
      gui->rc->transport_aware = 1;
      gui->rc->transport_query = 0;
      break;
    case 3:
      if (gui->rewind) gtk_widget_set_sensitive (gui->rewind, TRUE);
      sequence_set_transport (gui->sequence, 1, 1);
      gui->rc->transport_aware = 1;
      gui->rc->transport_query = 1;
      break;
    }
}

static void gui_set_resampler_type (gui_t *gui, guint type, 
                                    GtkWidget *menu_item)
{
  if (type != sequence_get_resampler_type(gui->sequence))
   {
    sequence_set_resampler_type (gui->sequence, type);
    gui->rc->default_resampler_type = type;
   }
}


static void
gui_display_error (gui_t * gui, char *text)
{
  GtkWidget *dialog;

  dialog =
    gtk_message_dialog_new (gui->window ? GTK_WINDOW (gui->window) : NULL,
                            GTK_DIALOG_DESTROY_WITH_PARENT |
                            GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
                            GTK_BUTTONS_OK, "%s", text);

  gtk_window_set_title (GTK_WINDOW (dialog), "Jackbeat");
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);
}

static int
gui_ask_confirmation (gui_t * gui, char *text)
{
  GtkWidget *dialog;
  gint response;

  dialog =
    gtk_message_dialog_new (gui->window ? GTK_WINDOW (gui->window) : NULL,
                            GTK_DIALOG_DESTROY_WITH_PARENT |
                            GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
                            GTK_BUTTONS_OK_CANCEL, "%s", text);
  gtk_window_set_title (GTK_WINDOW (dialog), "Jackbeat");

  response = gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);

  if (response == GTK_RESPONSE_OK)
    return 1;

  return 0;
}

static gboolean 
gui_no_delete (GtkWidget *widget, GdkEvent  *event, gpointer data)
{
  return TRUE;
}

static void
gui_transpose_volumes (GtkWidget *adj, gui_t *gui)
{
  GtkWidget *master = g_object_get_data (G_OBJECT (adj), "user-data");
  double *orig_volumes = g_object_get_data (G_OBJECT (master), "user-data");
  double mvol = (double) gtk_spin_button_get_value (GTK_SPIN_BUTTON (master));
  int ntracks = sequence_get_tracks_num (gui->sequence);
  int i;
  for (i=0; i < ntracks; i++) 
   {
    double tvol = orig_volumes[i] * mvol / 100;
    if (gui->transpose_volumes_round) tvol =  floor (tvol);
    gtk_spin_button_set_value (GTK_SPIN_BUTTON (gui->volume_spinners[i]),
                               tvol);
   }
}
    
static void
gui_transpose_volumes_toggle_round (GtkWidget * widget, gui_t * gui)
{
  gui->transpose_volumes_round = 
    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)) ? 1 : 0;
  if (gui->transpose_volumes_round) 
   {
    GtkWidget *adj = g_object_get_data (G_OBJECT (widget), "user-data");
    gui_transpose_volumes (adj, gui);
   }
}

static void
gui_transpose_volumes_dialog (gui_t *gui, guint action, GtkWidget *widget)
{ 
  GtkWidget * dialog;

  GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
  GtkWidget *vbox = gtk_vbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 15);

  int ntracks = sequence_get_tracks_num (gui->sequence);
  double *orig_volumes = calloc (ntracks, sizeof (double));
  int i;
  for (i = 0; i < ntracks; i++)
    orig_volumes[i] = gtk_spin_button_get_value (GTK_SPIN_BUTTON (gui->volume_spinners[i]));
  
  char s[128];
  sprintf (s, "Transpose volumes - %s_%.2d", gui->name_prefix, gui->instance_index+1);

  dialog = gtk_dialog_new_with_buttons (
              s,
              GTK_WINDOW (gui->window),
              GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
              GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT, NULL);

  sprintf (s, "[%s:%.2d] Transpose all volumes by (%%) :",  gui->name_prefix, 
           gui->instance_index+1);
  GtkWidget *label = gtk_label_new (s);
  gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 8);
 

  GtkAdjustment *adj = (GtkAdjustment *) 
                        gtk_adjustment_new (100, 0, 999, 1, 0.01, 0);
  GtkWidget *master = gtk_spin_button_new (adj, 0.5, 2);
  g_object_set_data (G_OBJECT (adj), "user-data", (gpointer) master);
  g_object_set_data (G_OBJECT (master), "user-data", (gpointer) orig_volumes);
  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (master),
                                     GTK_UPDATE_IF_VALID);
  g_signal_connect (G_OBJECT (adj), "value_changed",
                    G_CALLBACK (gui_transpose_volumes), (gpointer) gui);

  gtk_box_pack_start (GTK_BOX (vbox), master, TRUE, TRUE, 8);

  GtkWidget *button = gtk_check_button_new_with_label ("Round values");
  g_object_set_data (G_OBJECT (button), "user-data", (gpointer) adj);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
                                gui->transpose_volumes_round ? TRUE : FALSE);
  g_signal_connect (G_OBJECT (button), "toggled",
                    G_CALLBACK (gui_transpose_volumes_toggle_round), (gpointer) gui);
  
  gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 8);
  
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, TRUE, TRUE,
                      0);
  gtk_widget_show_all (dialog);

  gtk_dialog_run (GTK_DIALOG (dialog)); 
  gtk_widget_destroy (dialog);
  free (orig_volumes);
}

char *
gui_ask_track_name (gui_t *gui, char *current_name, int is_name_doublon, int allow_cancel)
{  
  GtkWidget * dialog;
  char str[128];
  char *new_name = NULL;

  GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
  GtkWidget *vbox = gtk_vbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 15);

  if (allow_cancel) 
   {
    dialog = gtk_dialog_new_with_buttons (
                "Rename track",
                GTK_WINDOW (gui->window),
                GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
                GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
                GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
                NULL);
   }
  else
    dialog = gtk_dialog_new_with_buttons (
                "Rename track",
                GTK_WINDOW (gui->window),
                GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
                GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
                NULL);
    g_signal_connect (G_OBJECT (dialog), "delete_event", 
                      G_CALLBACK (gui_no_delete), NULL);
  
  if (is_name_doublon)
   {
    sprintf (str, "A track named \"%s\" already exists, please provide another name :",
             current_name);
             
    GtkWidget *label = gtk_label_new (str);
    gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 15);
   }
  
  GtkWidget *entry = gtk_entry_new();
  gtk_entry_set_max_length (GTK_ENTRY (entry), 127);
  gtk_entry_set_text (GTK_ENTRY (entry), current_name);
  gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
  gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 15);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, TRUE, TRUE,
                      0);
  gtk_widget_show_all (dialog);
  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) 
   {
    char *buf = (char *)gtk_entry_get_text (GTK_ENTRY (entry));
    if (strlen (buf)) {
      new_name = malloc (128);
      strcpy (new_name, buf);
      g_strstrip (new_name);
    }
   }
  gtk_widget_destroy (dialog);
  return new_name;
}


static void
gui_rename_track (GtkWidget * menu_item, gui_track_button_t * tb)
{
  gui_t *gui = tb->gui;
  char *new_name;
  char *old_name = sequence_get_track_name (gui->sequence, tb->track);
  new_name = gui_ask_track_name (gui, old_name, 0, 1); 
  while (new_name 
         && sequence_track_name_exists (gui->sequence, new_name) 
         && strcmp (new_name, old_name))
    new_name = gui_ask_track_name (gui, new_name, 1, 1); 
  if (new_name)
   {
    sequence_set_track_name (gui->sequence, tb->track, new_name);
    free (new_name);
    gui_refresh (gui);
    gui_set_modified (gui, 1);
   }
}

static void
gui_remove_track (GtkWidget * menu_item, gui_track_button_t * tb)
{
  gui_t *gui = tb->gui;
  sequence_remove_track (gui->sequence, tb->track);
  song_cleanup_unused_samples (gui->song);
  gui_refresh (gui);
  gui_set_modified (gui, 1);
}

static void
gui_show_progress (gui_t *gui, char *title, char *text)
{
  gui->progress_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  if (gui->window != NULL)
   {
    gtk_window_set_modal (GTK_WINDOW (gui->progress_window), TRUE);
    gtk_window_set_transient_for (GTK_WINDOW (gui->progress_window), 
                                  GTK_WINDOW (gui->window));
    gtk_window_set_destroy_with_parent (GTK_WINDOW (gui->progress_window), TRUE);
   }
  gtk_window_set_default_size (GTK_WINDOW (gui->progress_window), 350,50);
  gtk_window_set_position (GTK_WINDOW (gui->progress_window), GTK_WIN_POS_CENTER_ON_PARENT);
  gtk_window_set_title (GTK_WINDOW (gui->progress_window), title);
  g_signal_connect (G_OBJECT (gui->progress_window), "delete_event", 
                    G_CALLBACK (gui_no_delete), NULL);

  
  GtkWidget *vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_add (GTK_CONTAINER (gui->progress_window), vbox);
  
  GtkWidget *hbox_top = gtk_hbox_new (FALSE, 0);
  
  GtkWidget *spacer = gtk_label_new (" ");
  gtk_box_pack_start (GTK_BOX (vbox), spacer, TRUE, TRUE, 0);
  
  gtk_box_pack_start (GTK_BOX (vbox), hbox_top, TRUE, TRUE, 0);

  GtkWidget *icon = gtk_image_new_from_stock (GTK_STOCK_DIALOG_INFO, 
                                              GTK_ICON_SIZE_DIALOG);
  gtk_box_pack_start (GTK_BOX (hbox_top), icon, FALSE, FALSE, 15);
  
  GtkWidget *label = gtk_label_new (text);
  gtk_misc_set_alignment (GTK_MISC (label), 0, 0.2);
  gtk_box_pack_start (GTK_BOX (hbox_top), label, FALSE, FALSE, 0);

  spacer = gtk_label_new (" ");
  gtk_box_pack_start (GTK_BOX (vbox), spacer, TRUE, TRUE, 0);

  GtkWidget *hbox_bot = gtk_hbox_new (FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), hbox_bot, TRUE, TRUE, 0);
  
  gui->progress_bar = gtk_progress_bar_new();
  gtk_box_pack_start (GTK_BOX (hbox_bot), gui->progress_bar, TRUE, TRUE, 15);

  spacer = gtk_label_new (" ");
  gtk_box_pack_start (GTK_BOX (vbox), spacer, TRUE, TRUE, 0);

  gtk_widget_show_all (vbox);

}

void 
gui_progress_callback (char * status, double fraction, void * data)
{
  gui_t *gui = (gui_t *) data;
  if (!GTK_WIDGET_VISIBLE(gui->progress_window))
    gtk_widget_show (gui->progress_window);
  gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gui->progress_bar), status);
  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (gui->progress_bar), fraction);
  while (g_main_context_iteration (NULL, FALSE));
}

static void
gui_hide_progress (gui_t * gui)
{
  gtk_widget_destroy (gui->progress_window);
}

static void
gui_clear_sequence (gui_t * gui, guint action, GtkWidget * w)
{
  if (!gui->sequence_is_modified || gui_ask_confirmation
      (gui, "This will clear the whole pattern and associated samples. Are your sure ?"))
    {
      int error;

      int rtype = sequence_get_resampler_type (gui->sequence);
      char *name = strdup (sequence_get_name (gui->sequence));

      song_unregister_sequence (gui->song, gui->sequence);
      song_cleanup_unused_samples (gui->song);
      sequence_destroy (gui->sequence);
      gui->sequence = sequence_new (name, &error);
      free (name);
      sequence_set_resampler_type (gui->sequence, rtype);
      sequence_set_transport (gui->sequence, gui->rc->transport_aware,
                              gui->rc->transport_query);
      song_register_sequence (gui->song, gui->sequence);
      gui_refresh (gui);
      gui_set_modified (gui, 0);
    }
}

sequence_t * 
gui_do_load_sequence (gui_t *gui, char *filename)
{
  jab_t *jab;
  char *y = strdup (filename);
  sprintf (gui->rc->sequence_wdir, "%s/", dirname (y));
  free (y);
  gui_show_progress (gui, "Loading JAB file", "Hold on...");
  if (!(jab = jab_open (filename, JAB_READ, gui_progress_callback, (void *) gui))) {
      gui_hide_progress (gui);
      gui_display_error (gui, "Unable to load the specified file.");
      return NULL;
  }
  sequence_t *sequence;
  char sn[256];
  sprintf (sn, "%s_%d", gui->name_prefix, gui_instance_counter + 1);
  int error;
  if (jab && (sequence = jab_retrieve_sequence (jab, sn, &error)))
    {
      rc_add_sequence (gui->rc, filename);
      song_register_sequence (gui->song, sequence);
      song_register_sequence_samples (gui->song, sequence);
      sequence_set_transport (sequence, gui->rc->transport_aware, gui->rc->transport_query);
      sequence_set_resampler_type (sequence, gui->rc->default_resampler_type);
      gui_hide_progress (gui);
      jab_close (jab);
      return sequence;
    }
  else
    {
      char s[256];
      sprintf (s, "Unable to load the specified file : %s", 
               error_to_string (error));
      gui_display_error (gui, s);
      gui_hide_progress (gui);
      if (jab) jab_close (jab);
      return 0;
    }
}

static void
gui_load_sequence_selected (GtkWidget * w, gui_t * gui)
{
  char *filename = strdup (gtk_file_selection_get_filename 
                              (GTK_FILE_SELECTION (gui->file_selection)));
  gtk_widget_destroy (gui->file_selection);
  sequence_t *sequence;
  if ((sequence = gui_do_load_sequence (gui, filename)))
    gui_new_child (gui->rc, gui->arg, gui, gui->song, sequence, filename);
  free (filename);
}

static void
gui_show_file_selection (gui_t *gui, char *title, char *path, GCallback callback, gpointer callback_data)
{
  gui->file_selection = gtk_file_selection_new (title);
  gtk_window_set_modal (GTK_WINDOW (gui->file_selection), TRUE);
  gtk_window_set_transient_for (GTK_WINDOW (gui->file_selection), GTK_WINDOW (gui->window));
  gtk_file_selection_set_filename (GTK_FILE_SELECTION (gui->file_selection), path);
  g_signal_connect (G_OBJECT
                    (GTK_FILE_SELECTION (gui->file_selection)->ok_button),
                    "clicked", callback, callback_data);
  g_signal_connect_swapped (G_OBJECT
                            (GTK_FILE_SELECTION (gui->file_selection)->cancel_button), "clicked",
                            G_CALLBACK (gtk_widget_destroy),
                            G_OBJECT (gui->file_selection));
  gtk_widget_show (gui->file_selection);
}

static void
gui_load_sequence (gui_t * gui, guint action, GtkWidget * w)
{
  gui_show_file_selection (gui, "Load Sequence", gui->rc->sequence_wdir, G_CALLBACK (gui_load_sequence_selected), (gpointer) gui);
}

static void
gui_export_sequence_selected (GtkWidget * w, gui_t * gui)
{
  char filename[512];
  const char *s =
    gtk_file_selection_get_filename (GTK_FILE_SELECTION
                                     (gui->file_selection));
  strcpy (filename, s);
  int len = strlen (filename);
  if ((len < 4) || (strncasecmp(filename + len - 4, ".wav", 4) != 0))
    strcat(filename, ".wav");
  gtk_widget_destroy (gui->file_selection);
  if ((access (filename, F_OK) == -1) || gui_ask_confirmation
      (gui, "Are you sure you want to overwrite the file ?"))
    {
      gui_show_progress (gui, "Exporting sequence", "Hold on...");
      sequence_export (gui->sequence, filename, gui_progress_callback, (void *) gui);
      gui_hide_progress (gui);
    }
}

static void
gui_export_sequence (gui_t * gui, guint action, GtkWidget * w)
{
  gui_show_file_selection (gui, "Export Sequence as", "untitled.wav", G_CALLBACK (gui_export_sequence_selected), (gpointer) gui);
}

static void
gui_save_as_sequence_selected (GtkWidget * w, gui_t * gui)
{
  char filename[512];
  const char *str =
    gtk_file_selection_get_filename (GTK_FILE_SELECTION
                                     (gui->file_selection));
  strcpy (filename, str);
  int len = strlen (filename);
  if ((len < 4) || (strncasecmp (filename + len - 4, ".jab", 4) != 0))
    strcat(filename, ".jab");
  gtk_widget_destroy (gui->file_selection);
  if ((access (filename, F_OK) == -1) || gui_ask_confirmation
      (gui, "Are you sure you want to overwrite the file ?"))
    {
      int success = 0;
      jab_t *jab;
      gui_show_progress (gui, "Saving sequence", "Hold on...");
      if ((jab = jab_open (filename, JAB_WRITE, gui_progress_callback, (void *) gui)))
       {
        jab_add_sequence (jab, gui->sequence);
        if (jab_close (jab)) success = 1;
       }
      gui_hide_progress (gui);
      if (success)
        {
          strcpy (gui->filename, filename);
          rc_add_sequence (gui->rc, filename);
          gui->filename_is_set = 1;
          sprintf (gui->rc->sequence_wdir, "%s/", dirname (filename));
          gui_set_modified (gui, 0);
        }
      else
        {
          gui_display_error (gui, "Unable to save the specified file.");
        }
    }
}

static void
gui_save_as_sequence (gui_t * gui, guint action, GtkWidget * w)
{
  char filename[512];
  if (gui->filename_is_set) strcpy (filename, gui->filename);
  else sprintf (filename, "%s/%s", gui->rc->sequence_wdir, basename (gui->filename));

  gui_show_file_selection (gui, "Save Sequence as", filename, G_CALLBACK (gui_save_as_sequence_selected), (gpointer) gui);
}

static void
gui_save_sequence (gui_t * gui, guint action, GtkWidget * w)
{
  if (gui->filename_is_set)
    {
      DEBUG("Ok, we already have a filename. Let's try and save..."); 
      int success = 0;
      jab_t *jab;
      gui_show_progress (gui, "Saving sequence", "Hold on...");
      if ((jab = jab_open (gui->filename,JAB_WRITE, gui_progress_callback,
                           (void *) gui)))
       {
        jab_add_sequence (jab, gui->sequence);
        if (jab_close (jab)) success = 1;
       }
      gui_hide_progress (gui);
      if (success) 
        gui_set_modified (gui, 0);
      else
        gui_display_error (gui, "Unable to save the current sequence.");
    }
  else
    gui_save_as_sequence (gui, 0, w);
}

static GtkWidget *
gui_make_menubar (gui_t * gui, GtkItemFactoryEntry * items, gint nitems)
{
  GtkItemFactory *item_factory;
  GtkAccelGroup *accel_group;

  accel_group = gtk_accel_group_new ();
  item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>",
                                       accel_group);
  gtk_item_factory_create_items (item_factory, nitems, items, (gpointer) gui);
  gtk_window_add_accel_group (GTK_WINDOW (gui->window), accel_group);

  if (gui->rc->transport_query)
    gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM
                                    (gtk_item_factory_get_item
                                     (item_factory, "/Options/Transport control/Respond and query")),
                                    TRUE);
  else
    gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM
                                    (gtk_item_factory_get_item
                                     (item_factory, "/Options/Transport control/Respond")),
                                    TRUE);

  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM
                                  (gtk_item_factory_get_item
                                   (item_factory, "/Options/Auto-connect")),
                                  (sequence_is_auto_connecting(gui->sequence)) ? TRUE : FALSE);

  GtkWidget *item = NULL;
  switch (sequence_get_resampler_type (gui->sequence)) {
    case SEQUENCE_SINC: 
     item =  gtk_item_factory_get_item (item_factory, 
              "/Options/Resampling quality/Hi-fi : sinc algorithm - more CPU");
     break;
    case SEQUENCE_LINEAR:
     item =  gtk_item_factory_get_item (item_factory, 
              "/Options/Resampling quality/Lo-fi : linear - lightweight");
     break;
  }
  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);

  return gtk_item_factory_get_widget (item_factory, "<main>");
}

static void
gui_do_load_sample (gui_t *gui, int track, char *filename)
{
  /* Trying to reuse memory if this sample is already loaded*/
  sample_t *sample = song_try_reuse_sample (gui->song, filename);
  
  if (sample == NULL)
   {
    /* New or modified sample : loading ... */
    gui_show_progress (gui, "Loading sample", "Hold on...");
    if ((sample = sample_new (filename, gui_progress_callback, (void *) gui)) != NULL)
     {
      song_register_sample (gui->song, sample);
     }
    gui_hide_progress (gui);
   }

  char *track_name = NULL;
  if (sample != NULL)
   {
    /* Handling track names conflict */ 
    if (sequence_track_name_exists (gui->sequence, sample->name))
     {
      while ((track_name = gui_ask_track_name (gui, sample->name, 1, 1)) 
             && (strcmp (track_name, sample->name) == 0)) 
       {
        free (track_name);
        track_name = NULL;
       }
     } else {
      track_name = strdup (sample->name);
     }

    if (track_name) {
      /* We got a sample */
      sequence_set_sample (gui->sequence, track, sample);
      rc_add_sample (gui->rc, sample->filename);

      sequence_set_track_name (gui->sequence, track, track_name);
      free (track_name);

      gui_set_modified (gui, 1);
      gui_refresh (gui);
    }


    /* Trying to free some memory */
    song_cleanup_unused_samples (gui->song);
   }
  else
   {
    gui_display_error (gui,
                       "Unable to load the requested sample file.");
   }
}

static void
gui_sample_file_selected (GtkWidget * widget, gui_track_button_t * tb)
{
  gui_t *gui = tb->gui;
  const char *s =
    gtk_file_selection_get_filename (GTK_FILE_SELECTION
                                     (gui->file_selection));
  gtk_widget_destroy (gui->file_selection);
  char *filename = strdup (s);
  
  gui_do_load_sample (gui, tb->track, filename);

  char dir[256];
  sprintf (dir, "%s/", dirname (filename));
  DIR *dir_stream;
  if ((dir_stream = opendir(dirname(filename)))) {
    closedir(dir_stream);
    DEBUG ("Setting sample_wdir to : %s", dir);
    strcpy (gui->rc->sample_wdir, dir);
  } else {
    DEBUG ("Not setting sample_wdir to : %s", dir);
  }
  free (filename);
}

static void
gui_load_sample_dialog (GtkWidget * widget, gui_track_button_t * tb)
{
  gui_t *gui = tb->gui;  
  DEBUG ("Using sample_wdir : %s", gui->rc->sample_wdir);
  gui_show_file_selection (gui, "Load sample", gui->rc->sample_wdir, G_CALLBACK (gui_sample_file_selected), (gpointer) tb);
}

static void
gui_sequence_modified (void *data, int col, int row, int value)
{
  gui_t *gui = (gui_t *)data;
  sequence_set_beat (gui->sequence, row, col, value);
  gui_set_modified (gui, 1);
}

static void
gui_mask_modified (void *data, int col, int row, int mask)
{
  gui_t *gui = (gui_t *)data;
  sequence_set_mask_beat (gui->sequence, row, col, mask);
  gui_set_modified (gui, 1);
}

static void
gui_mute_track (GtkWidget *toggle, gui_track_button_t * tb)
{
  gui_t *gui = tb->gui;
 
  if (!gui->refreshing) 
   {
    int active = 
      (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle)) == TRUE) 
        ? 1 : 0;

    sequence_mute_track (gui->sequence, active, tb->track);
    gui_set_modified (gui, 1);
  }
}

static void
gui_track_size_allocate (GtkWidget * menu_item, GtkAllocation * allocation ,gui_t * gui)
{
  int height = allocation->height - 2;
  grid_set_cell_size (gui->grid, height * 3 / 4, height * 3 / 4, height, height);
}
  
static void
gui_header_size_allocate (GtkWidget * menu_item, GtkAllocation * allocation ,gui_t * gui)
{
  printf ("header height/y : %d/%d\n", allocation->height, allocation->y);
  grid_set_header_label_ypos (gui->grid, allocation->y - 1);
}
  
static void
gui_toggle_smoothing (GtkWidget * menu_item, gui_track_button_t * tb)
{
  gui_t *gui = tb->gui;
  if (!gui->refreshing)
   {
    int status = (GTK_CHECK_MENU_ITEM (menu_item)->active) ? 1 : 0;
    sequence_set_smoothing (gui->sequence, tb->track, status);
    gui_set_modified (gui, 1);
   }
}

static void
gui_load_sample_from_history (GtkWidget * menu_item, gui_track_sample_t * ts)
{
  gui_t *gui = ts->gui;
  char *filename = strdup (ts->sample_fname);
  gui_do_load_sample (gui, ts->track, filename);
  free (filename);
}

static void
gui_pitch_changed (GtkWidget * adj, gui_track_button_t * tb)
{
  gui_t *gui = tb->gui;  
  if (!gui->refreshing) 
   {
    GtkWidget *spinner = g_object_get_data (G_OBJECT (adj), "user-data");
    sequence_set_pitch (gui->sequence, 
                        tb->track,
                        gtk_spin_button_get_value (GTK_SPIN_BUTTON (spinner)));

    gui_set_modified (gui, 1);
   }
}

static void
gui_volume_changed (GtkWidget * adj, gui_track_button_t * tb)
{
  gui_t *gui = tb->gui;  
  GtkWidget *spinner = g_object_get_data (G_OBJECT (adj), "user-data");
  if (!gui->refreshing) 
    sequence_set_volume (gui->sequence, 
                         tb->track,
                         gtk_spin_button_get_value (GTK_SPIN_BUTTON (spinner)) / 100); 

  gui_set_modified (gui, 1);
}

static GtkWidget  *gui_add_menu_item (GtkWidget *menu, char *type, char *label, GCallback callback, gpointer data)
{
  GtkWidget *item ;
  if (!strcmp(type,"#separator")) item = gtk_separator_menu_item_new();  
  else if (!strcmp (type, "#toggle")) {
    item = gtk_check_menu_item_new_with_label (label);
    if (callback) g_signal_connect (G_OBJECT (item), "toggled", callback, data);
  } else if (!strcmp (type, "#stock")) {
    item = gtk_image_menu_item_new_from_stock (label, NULL);
    if (callback) g_signal_connect (G_OBJECT (item), "activate", callback, data);
  } else {
    item = gtk_menu_item_new_with_label (label);
    if (callback) g_signal_connect (G_OBJECT (item), "activate", callback, data);
    if (!strcmp (type, "#ghost"))
      gtk_widget_set_sensitive (item, FALSE);
  }
  gtk_menu_shell_append (GTK_MENU_SHELL (menu),item);
  gtk_widget_show (item);
  return item;
}

static GtkWidget *gui_make_track_menu (gui_t *gui, char *name, int track)
{
  GtkWidget *menu, *submenu;
  GtkWidget *item;
  gui_track_button_t *tb;
  int i;
  gui_track_sample_t *ts;
  
  tb = gui->tracks_buttons + track * sequence_get_beats_num (gui->sequence);

  menu = gtk_menu_new ();

  gui_add_menu_item (menu, "#item", "Load sample...", G_CALLBACK (gui_load_sample_dialog), (gpointer) tb);
  submenu = gtk_menu_new ();
  if (gui->rc->sample_history_num == 0) {
    item = gui_add_menu_item (submenu, "#ghost", "<No sample>", NULL, NULL);
  } else {
    for (i=0; i < gui->rc->sample_history_num; i++) 
     {
      ts = gui->track_samples + track * gui->rc->sample_history_num + i;
      ts->gui = gui;
      ts->track = track;
      ts->sample_fname = gui->rc->sample_history[i];
      char *hist = strdup (gui->rc->sample_history[i]);
      char *dirname1 = strdup (dirname (hist));
      free (hist);
      char *_dirname1 = strdup (dirname1);
      char *dirname2 = strdup (dirname (_dirname1));
      free (_dirname1);
      _dirname1 = strdup (dirname1);
      char *dir_basename = strdup (basename (_dirname1));
      free (_dirname1);
      hist = strdup (gui->rc->sample_history[i]);
      char *basename1 = strdup (basename (hist));
      free (hist);
      hist = strdup (gui->rc->sample_history[i]);
     
      char display[256];
      
      if ((strcmp (dirname1, "/") == 0) || (strcmp (dirname2, "/") == 0)) strcpy (display, hist);
      else sprintf (display, "/../%s/%s", dir_basename, basename1);

      free (dirname1);
      free (dirname2);
      free (dir_basename);
      free (basename1);
      free (hist);
      
      gui_add_menu_item (submenu, "#item", display, G_CALLBACK (gui_load_sample_from_history), (gpointer) ts);
     }
  }

  item = gui_add_menu_item (menu, "#item", "Recent",NULL,NULL);
  gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);

  gui_add_menu_item (menu, "#separator", NULL, NULL, NULL);

  item = gui_add_menu_item (menu, "#toggle", "Smoothing", NULL, NULL);
  if (sequence_get_smoothing (gui->sequence, track))
      gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
  g_signal_connect (G_OBJECT (item), "toggled", G_CALLBACK (gui_toggle_smoothing), (gpointer) tb);
  
  gui_add_menu_item (menu, "#separator", NULL, NULL, NULL);

  item = gui_add_menu_item (menu, "#item", "Rename...", G_CALLBACK
                            (gui_rename_track), (gpointer) tb);

  item = gui_add_menu_item (menu, "#stock", GTK_STOCK_REMOVE, G_CALLBACK
                            (gui_remove_track), (gpointer) tb);

  if (sequence_get_tracks_num (gui->sequence) == 1)
    gtk_widget_set_sensitive (item, FALSE);

  item = gtk_menu_item_new_with_label (name);
  gtk_widget_show(item);
  gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
    
  return item;
}

#define gtk_table_attach(c,w,l,r,t,b,px,py,sx,sy) printf("Table attach: (%d,%d),(%d,%d) \n",l,t,r,b); \
        gtk_table_attach (c,w,l,r,t,b,px,py,sx,sy)

static void
gui_draw_tracks (gui_t * gui)
{
  GtkWidget *scrolled_win;
  GtkWidget *table;
  GtkWidget *hbox;
  GtkWidget *scrolled_vbox;
  int i, j;
  GtkWidget *button;
  GtkWidget *menu;
  gui_track_button_t *tb = NULL;

  DEBUG ("Drawing sequence");

  /* Fetching dimensions */
  int ntracks = sequence_get_tracks_num(gui->sequence);
  int nbeats = sequence_get_beats_num(gui->sequence);
  int measure_len = sequence_get_measure_len(gui->sequence);
  int nmeasures = (nbeats - 1) / measure_len + 1;

  /* (Re)allocating main widgets and callback data */
  if (gui->tracks_box != NULL) gtk_widget_destroy (gui->tracks_box);

  if (gui->tracks_buttons) free (gui->tracks_buttons);
  gui->tracks_buttons = calloc (nbeats * ntracks, sizeof (gui_track_button_t));

  if (gui->track_samples) free (gui->track_samples);
  
  if ((i = gui->rc->sample_history_num))
    gui->track_samples = calloc (i * ntracks, sizeof (gui_track_sample_t));

  gui->volume_spinners = realloc (gui->volume_spinners, ntracks * sizeof (GtkWidget *));
  
  hbox = gtk_hbox_new (FALSE, 0);

  scrolled_vbox = gtk_vbox_new (FALSE, 0);
  
  /* Creating scrolled window */
  DEBUG("Creating scrolled window");
  scrolled_win = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

  /* Creating table */
  
  /* Row headers : label row */
  int nrow_headers = 1;
  
  /* Two rows for each track + headers */
  int nrows = ntracks * 2 + nrow_headers; 

  /* Column headers : menus + mute buttons + pitch control + level control */
  int ncol_headers = 4;
  
  /* headers + nbeats + separators cols (nmeasures - 1) */
  int ncols = ncol_headers + nbeats + nmeasures - 1;
  
  table = gtk_table_new (nrows, ncols, FALSE);
  DEBUG ("Created table of size : %d x %d", nrows, ncols);
  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
  gtk_table_set_col_spacings (GTK_TABLE (table), 2);

  gtk_box_pack_start (GTK_BOX(scrolled_vbox), table, FALSE, FALSE, 0);
  
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_win),
                                         scrolled_vbox);
  gtk_box_pack_start (GTK_BOX (hbox), scrolled_win, TRUE, TRUE, 0);

  /* Drawing labels */
  DEBUG ("Drawing labels");

  /* Top-left empty label */
  button = gtk_label_new (" ");
  g_signal_connect (G_OBJECT (button), "size-allocate", 
                    G_CALLBACK (gui_header_size_allocate), (gpointer) gui);
  gtk_table_attach (GTK_TABLE (table), button, 0, 2, 0, 1,
                    GTK_EXPAND | GTK_FILL, 0, 1, 1);
  
  /* Pitch label */
  button = gtk_label_new ("Pitch");
  gtk_table_attach (GTK_TABLE (table), button, 2, 3, 0, 1,
                    GTK_EXPAND | GTK_FILL, 0, 1, 1);
  
  /* Volume label */
  button = gtk_label_new ("Volume");
  gtk_table_attach (GTK_TABLE (table), button, 3, 4, 0, 1,
                    GTK_EXPAND | GTK_FILL, 0, 1, 1);
  
  /* Drawing menus */
  DEBUG ("Drawing menus");
  for (i=0; i < ntracks; i++)
   {
    menu = gui_make_track_menu (gui, sequence_get_track_name (gui->sequence, i), i);
    button = gtk_menu_bar_new();
    gtk_menu_bar_append (GTK_MENU_BAR (button), menu);
    gtk_table_attach (GTK_TABLE (table), button, 0, 1, i * 2 + 1, i * 2 + 3,
                      GTK_EXPAND | GTK_FILL, 0, 1, 1);
   }
 
  /* Drawing Mute buttons */
  DEBUG ("Drawing mute buttons");
  for (i=0; i < ntracks; i++)
   {
    tb = gui->tracks_buttons + i * nbeats;

    button = gtk_toggle_button_new_with_label ("M");
    if (sequence_track_is_muted (gui->sequence, i))
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
    gtk_tooltips_set_tip (gui->tooltips, button, "Mute/unmute this track", "");
    g_signal_connect (G_OBJECT (button), "toggled",
                      G_CALLBACK (gui_mute_track), (gpointer) tb);
    if (i == 0) 
      g_signal_connect (G_OBJECT (button), "size-allocate", 
                        G_CALLBACK (gui_track_size_allocate), (gpointer) gui);
  
    gtk_table_attach (GTK_TABLE (table), button, 1, 2, i * 2 + 1, i * 2 + 3,
                      0, 0, 1, 1);
    
   }

  DEBUG ("Drawing controls");
  /* Drawing pitch spin buttons */
  GtkAdjustment *adj;
  for (i=0; i < ntracks; i++)
   {
    adj = (GtkAdjustment *) gtk_adjustment_new (sequence_get_pitch (gui->sequence, i),
                                                -96, 96, 0.01, 0.0001, 0);
    button = gtk_spin_button_new (adj, 0.5, 4);
    g_object_set_data (G_OBJECT (adj), "user-data", (gpointer) button);
    gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (button),
                                       GTK_UPDATE_IF_VALID);
    tb = gui->tracks_buttons + i * nbeats;
    g_signal_connect (G_OBJECT (adj), "value_changed",
                      G_CALLBACK (gui_pitch_changed), (gpointer) tb);
    gtk_table_attach (GTK_TABLE (table), button, 2, 3, i * 2 + 1, i * 2 + 3,
                      GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
   }
  
  /* Drawing volume spin buttons */
  for (i=0; i < ntracks; i++)
   {
    adj = (GtkAdjustment *) gtk_adjustment_new (sequence_get_volume (gui->sequence, i) * 100,
                                                0, 999, 1, 0.01, 0);
    button = gtk_spin_button_new (adj, 0.5, 2);
    g_object_set_data (G_OBJECT (adj), "user-data", (gpointer) button);
    gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (button),
                                       GTK_UPDATE_IF_VALID);
    tb = gui->tracks_buttons + i * nbeats;
    g_signal_connect (G_OBJECT (adj), "value_changed",
                      G_CALLBACK (gui_volume_changed), (gpointer) tb);
    gtk_table_attach (GTK_TABLE (table), button, 3, 4, i * 2 + 1, i * 2 + 3,
                      GTK_EXPAND | GTK_FILL, 0, 1, 1);
    gui->volume_spinners[i] = button;
   }
  
  /* Setting up grid */
  DEBUG ("Setting up grid");
  grid_resize (gui->grid, nbeats, ntracks);
  grid_set_column_group_size (gui->grid, measure_len);
  for (j=0; j < ntracks; j++)
   {
    int handle_mask = sequence_is_enabled_mask (gui->sequence, j);
    for (i=0; i < nbeats; i++)
     {
      tb = gui->tracks_buttons + j * nbeats + i;
      tb->gui = gui;
      tb->track = j;
      tb->beat = i;
      
      grid_set_value (gui->grid, i, j, sequence_get_beat(gui->sequence, j, i));
      if (handle_mask)
        grid_set_mask (gui->grid, i, j, sequence_get_mask_beat(gui->sequence, j, i));
     }
   }

  gtk_table_attach (GTK_TABLE (table), grid_get_widget (gui->grid), 
                    ncol_headers, ncols, 0, nrows, 
                    GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
  
  /* Finalizing */
  gui->tracks_box = hbox;
  gtk_box_pack_start (GTK_BOX (gui->main_vbox), gui->tracks_box, TRUE, TRUE, 0);
  gtk_widget_show_all (gui->tracks_box);
  DEBUG ("Finished drawing sequence");
}

static void
gui_refresh (gui_t * gui)
{
  if (!gui->refreshing) 
   {
    DEBUG ("Refreshing GUI");
    gui->refreshing = 1;
    gui_update_window_title (gui);
    gtk_spin_button_set_value (GTK_SPIN_BUTTON (gui->tracks_num),
                               sequence_get_tracks_num(gui->sequence));
    gtk_spin_button_set_value (GTK_SPIN_BUTTON (gui->beats_num),
                               sequence_get_beats_num(gui->sequence));
    gtk_spin_button_set_value (GTK_SPIN_BUTTON (gui->measure_len),
                               sequence_get_measure_len(gui->sequence));
    gtk_spin_button_set_value (GTK_SPIN_BUTTON (gui->bpm), sequence_get_bpm(gui->sequence));
    if (sequence_is_looping(gui->sequence))
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gui->loop), TRUE);
    else
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gui->loop), FALSE);
    gui_draw_tracks (gui);
    gui->refreshing = 0;
   }
}

void
gui_do_exit (gui_t *gui)
{
  DEBUG("Writing rc settings");  
  rc_write (gui->rc);
  DEBUG ("Exiting GTK");
  gtk_main_quit ();
  DEBUG ("Shutting down JACK client");
  DEBUG ("Bye");
}

static gboolean
gui_close (GtkWidget * widget, GdkEvent  *event, gui_t * gui)
{
  if (!gui->sequence_is_modified || gui_ask_confirmation
      (gui, "Current changes will be lost if you continue. Are your sure ?"))
    {
      song_unregister_sequence (gui->song, gui->sequence);
      song_cleanup_unused_samples (gui->song);
      sequence_destroy (gui->sequence);
      g_source_remove (gui->timeout_tag);
      gtk_object_destroy (GTK_OBJECT (gui->tooltips));
      gtk_widget_destroy (gui->window);
      if (--gui_instance_num == 0) 
       {  
        gui_do_exit (gui);
       } 
      free (gui);
      return FALSE;
    }
  return TRUE;
}

gboolean
gui_timeout (gpointer data)
{
  gui_t *gui = (gui_t *) data;
  int i;
  int tracks_num = sequence_get_tracks_num (gui->sequence);
  if (sequence_is_playing (gui->sequence))
    for (i=0; i < tracks_num; i++) 
     {
      grid_highlight_cell (gui->grid, 
                           sequence_get_active_beat (gui->sequence, i), 
                           i, 
                           sequence_get_level (gui->sequence, i));

     }
  else
    for (i=0; i < tracks_num; i++) 
       {
        grid_highlight_cell (gui->grid, -1, i, 0);
       }
  return TRUE;
}

static void
gui_duplicate_sequence (gui_t * gui, guint action, GtkWidget * w)
{
  if (!gui->refreshing)
    {
      DEBUG ("Pattern resized");
      gint tracks_num = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (gui->tracks_num));
      gint beats_num = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (gui->beats_num));
      gint measure_len = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (gui->measure_len));

      g_source_remove (gui->timeout_tag);

      sequence_resize (gui->sequence, tracks_num, beats_num * 2, measure_len, 1);
      song_cleanup_unused_samples (gui->song);
      
      gui_refresh (gui);
      gui->timeout_tag = g_timeout_add (GUI_ANIMATION_INTERVAL, gui_timeout, (gpointer) gui);
      gui_set_modified (gui, 1);
    }
}

static gboolean
gui_sequence_do_resize (gpointer data)
{
    gui_t *gui = (gui_t *) data;
    gint tracks_num = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (gui->tracks_num));
    gint beats_num = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (gui->beats_num));
    gint measure_len = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (gui->measure_len));

    g_source_remove (gui->timeout_tag);

    sequence_resize (gui->sequence, tracks_num, beats_num, measure_len, 0);
    song_cleanup_unused_samples (gui->song);
    
    gui_draw_tracks (gui);
    gui->timeout_tag = g_timeout_add (GUI_ANIMATION_INTERVAL, gui_timeout, (gpointer) gui);
    gui_set_modified (gui, 1);
    return FALSE;
}


static void
gui_sequence_resized (GtkWidget * widget, gui_t * gui)
{
  if (!gui->refreshing)
    {
      DEBUG ("Pattern resized");
      g_idle_add (gui_sequence_do_resize, (gpointer) gui);
    }
}

static void
gui_bpm_changed (GtkWidget * widget, gui_t * gui)
{
  sequence_set_bpm (gui->sequence, gtk_spin_button_get_value (GTK_SPIN_BUTTON (gui->bpm)));
  gui_set_modified (gui, 1);
}

static void
gui_play_clicked (GtkWidget * widget, gui_t * gui)
{
  sequence_start (gui->sequence);
}

static void
gui_pause_clicked (GtkWidget * widget, gui_t * gui)
{
  sequence_stop (gui->sequence);
}

static gboolean
gui_playback_toggle_pressed (GtkWidget * widget, GdkEventKey *event, gui_t * gui)
{
  if (event->keyval == 32) {
    if (sequence_is_playing (gui->sequence)) sequence_stop (gui->sequence);
    else sequence_start (gui->sequence);

    return TRUE;
  }
  return FALSE;
}

static void
gui_rewind_clicked (GtkWidget * widget, gui_t * gui)
{
  sequence_rewind (gui->sequence);
}

static void
gui_loop_toggled (GtkWidget * widget, gui_t * gui)
{
  if (!gui->refreshing)
    {
      if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
          sequence_set_looping (gui->sequence);
      else
          sequence_unset_looping (gui->sequence);
    }
}

static GtkWidget *
gui_make_toolbar_spin_button (gui_t *gui, GtkWidget *box, const char *label, int digits, GtkAdjustment *adj, GCallback callback) 
{
  GtkWidget *label_wid = gtk_label_new (label);
  gtk_box_pack_start (GTK_BOX (box), label_wid, TRUE, FALSE, 0);
  GtkWidget *spin_button = gtk_spin_button_new (adj, 0.5, digits);
  gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (spin_button), GTK_UPDATE_IF_VALID);
  g_signal_connect (G_OBJECT (adj), "value_changed",
                    G_CALLBACK (callback), (gpointer) gui);
  gtk_box_pack_start (GTK_BOX (box), spin_button, TRUE, FALSE, 0);

  return spin_button;
}

static GtkWidget *
gui_make_toolbar_button (gui_t *gui, GtkWidget *box, char *stock_id, GCallback callback)
{
  GtkWidget *button = gtk_button_new ();
  GtkWidget *icon = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_BUTTON);
  gtk_container_add (GTK_CONTAINER (button), icon);
  g_signal_connect (G_OBJECT (button), "clicked", callback, (gpointer) gui);
  gtk_box_pack_start (GTK_BOX (box), button, TRUE, FALSE, 0);

  return button;
}

static void
gui_init (gui_t * gui)
{
  GtkWidget *button;
  GtkWidget *hbox;

  DEBUG ("Initializing GUI");
  
  gui->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  g_signal_connect (G_OBJECT (gui->window), "delete_event",
                    G_CALLBACK (gui_close), (gpointer) gui);
  gui_update_window_title (gui);
  gtk_container_set_border_width (GTK_CONTAINER (gui->window), 0);
  gtk_window_set_default_size (GTK_WINDOW (gui->window), 400, 200);

  gtk_container_set_border_width (GTK_CONTAINER (gui->window), 1);
  gui->main_vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_add (GTK_CONTAINER (gui->window), gui->main_vbox);

  GtkWidget *menubar = gui_make_menubar (gui, gui_menu_items,
                                         gui_menu_nitems);

  gtk_box_pack_start (GTK_BOX (gui->main_vbox), menubar, FALSE, TRUE, 0);

  hbox = gtk_hbox_new (FALSE, 0);

  GtkAdjustment *adj;

  adj = (GtkAdjustment *) gtk_adjustment_new (sequence_get_tracks_num (gui->sequence), 1, 128, 1, 1, 0);
  gui->tracks_num = gui_make_toolbar_spin_button (gui, hbox, " Tracks : ", 0, adj,  G_CALLBACK (gui_sequence_resized));

  adj = (GtkAdjustment *) gtk_adjustment_new (sequence_get_beats_num(gui->sequence), 1, 4096, 1, 1, 0);
  gui->beats_num = gui_make_toolbar_spin_button (gui, hbox, " Beats : ", 0, adj,  G_CALLBACK (gui_sequence_resized));

  adj = (GtkAdjustment *) gtk_adjustment_new (sequence_get_measure_len(gui->sequence), 1, 64, 1, 1, 0);
  gui->measure_len = gui_make_toolbar_spin_button (gui, hbox, " Measure : ", 0, adj,  G_CALLBACK (gui_sequence_resized));

  adj = (GtkAdjustment *) gtk_adjustment_new (sequence_get_bpm (gui->sequence), 0.1, 1000, 0.5, 0.5, 0);
  gui->bpm = gui_make_toolbar_spin_button (gui, hbox, " Bpm : ", 2, adj,  G_CALLBACK (gui_bpm_changed));

  button = gtk_vseparator_new ();
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, FALSE, 0);

  gui->loop = gtk_check_button_new_with_label ("Loop");
  if (sequence_is_looping(gui->sequence))
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gui->loop), TRUE);
  g_signal_connect (G_OBJECT (gui->loop), "toggled",
                    G_CALLBACK (gui_loop_toggled), (gpointer) gui);
  gtk_box_pack_start (GTK_BOX (hbox), gui->loop, TRUE, FALSE, 0);

  gui->rewind = gui_make_toolbar_button (gui, hbox, GTK_STOCK_MEDIA_PREVIOUS, G_CALLBACK (gui_rewind_clicked));
  gui_make_toolbar_button (gui, hbox, GTK_STOCK_MEDIA_PAUSE, G_CALLBACK (gui_pause_clicked));
  gui_make_toolbar_button (gui, hbox, GTK_STOCK_MEDIA_PLAY, G_CALLBACK (gui_play_clicked));

  g_signal_connect (G_OBJECT (gui->window), "key_press_event",
                    G_CALLBACK (gui_playback_toggle_pressed), (gpointer) gui);

  GtkWidget *handle_box = gtk_handle_box_new ();
  gtk_container_add (GTK_CONTAINER (handle_box), hbox);
  gtk_box_pack_start (GTK_BOX (gui->main_vbox), handle_box, FALSE, TRUE, 0);

  button = gtk_hseparator_new ();
  gtk_box_pack_start (GTK_BOX (gui->main_vbox), button, FALSE, TRUE, 0);

  gui->grid = grid_new ();
  grid_set_value_changed_callback (gui->grid, gui_sequence_modified, gui);
  grid_set_mask_changed_callback (gui->grid, gui_mask_modified, gui);
  
  gui_draw_tracks (gui);

  gtk_widget_show_all (gui->main_vbox);
}

void
gui_new (rc_t *rc,  arg_t *arg, song_t *song)
{
  gui_new_child (rc, arg, NULL, song, NULL, NULL);
}

static void
gui_new_child (rc_t *rc, arg_t *arg, gui_t *parent, song_t *song, sequence_t *sequence, char *filename)
{
  gui_t *gui;
  gui = calloc (1, sizeof (gui_t));
  DEBUG("Creating new GUI (PID: %d)", getpid());  
  gui->tracks_box = NULL;
  gui->sequence_is_modified = 0;
  gui->rewind = NULL;
  gui->refreshing = 0;
  gui->tracks_buttons = NULL;
  gui->track_samples = NULL;
  gui->song = song;
  gui->progress_window = NULL;
  gui->window = NULL;
  gui->volume_spinners = NULL;
  gui->transpose_volumes_round = 0;
  gui->rc = rc;
  gui->arg = arg;
 
  if ((parent == NULL))
    {
      gtk_rc_parse ("jackbeat.gtk.rc");
      gtk_init (&(arg->argc), &(arg->argv));
      strcpy (gui->name_prefix, arg->port_prefix);
      if (arg->filename != NULL)
       {
        if (!(sequence = gui_do_load_sequence (gui, arg->filename))) exit(1);
        filename = arg->filename;
       }
    }
  else
   {
    strcpy (gui->name_prefix, parent->name_prefix);
   }

  gui->instance_index = gui_instance_counter++;

  char s[128];
  if (sequence) gui->sequence = sequence;
  else 
   {
    sprintf(s, "%s_%d", gui->name_prefix, gui_instance_counter);
    int error;
    gui->sequence = sequence_new(s, &error); 
    if (gui->sequence == NULL)
      {
        if (error == ERR_SEQUENCE_JACK_CONNECT) 
         {
          sprintf (gui->name_prefix, "%s_%d", gui->arg->port_prefix, getpid());
          sprintf(s, "%s_%d", gui->name_prefix, gui_instance_counter);
          gui->sequence = sequence_new(s, &error); 
          if (gui->sequence == NULL)
            {
              char *s = error_to_string (error);
              gui_display_error (gui, s);
              free (s);
              free (gui);
              return;
            }
         }
      }

    sequence_set_transport (gui->sequence, rc->transport_aware, rc->transport_query);
    sequence_set_auto_connect (gui->sequence, rc->auto_connect);

    if (gui->sequence && (rc->default_resampler_type != -1))
      sequence_set_resampler_type (gui->sequence, rc->default_resampler_type);

    song_register_sequence (gui->song, gui->sequence);
   }
 
  if (filename) 
   {
    strcpy (gui->filename, filename);
    gui->filename_is_set = 1;
   }
  else 
   {
    strcpy (gui->filename, "untitled.jab");
    gui->filename_is_set = 0;
   }

  gui->tooltips = gtk_tooltips_new();
  gui_init (gui);  
  
  DEBUG ("Starting GUI"); 
  gui->timeout_tag = g_timeout_add (GUI_ANIMATION_INTERVAL, gui_timeout, (gpointer) gui);
  gtk_widget_show (gui->window);
  gui_instance_num++;
  if (!parent) {
      gtk_main ();
  }
}

static void
gui_new_instance (gui_t * gui, guint action, GtkWidget * w)
{
  gui_new_child (gui->rc, gui->arg, gui, gui->song, NULL, NULL);
  gui_refresh (gui);
}
    
static void
gui_close_from_menu (gui_t * gui, guint action, GtkWidget * w)
{
  gui_close (NULL, NULL, gui);
}
    
static void
gui_exit (gui_t * gui, guint action, GtkWidget * w)
{
  // FIXME: not checking if there's any unsaved sequences
  if (gui_ask_confirmation (gui, "Are you sure that you want to close all "
                                 "windows and quit Jackbeat ?"))
    gui_do_exit (gui);
}
    

Generated by  Doxygen 1.6.0   Back to index