//------------------------------------------------------------------------- // Filename: gui.cc // Version: $Id: gui.cc,v 1.2.4.4 1999/06/09 10:14:04 erik Exp $ // Copyright: Copyright (C) 1999, Erik Mouw (J.A.K.Mouw@its.tudelft.nl) // Author: Erik Mouw // Description: GUI for Musica // Created at: Sat Mar 27 21:42:40 1999 // Modified by: Erik Mouw // Modified at: Wed May 26 16:57:03 1999 //------------------------------------------------------------------------- // // 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // #ident "$Id: gui.cc,v 1.2.4.4 1999/06/09 10:14:04 erik Exp $" #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include using namespace std; #include #include #if STDC_HEADERS # include # include #else # ifdef HAVE_MALLOC_H # include # else # ifndef NULL # define NULL ((void *)0) # endif # endif #endif #include #include #include "dnd.hh" #include "events.hh" #include "file.hh" #include "gui.hh" #include "help.hh" #include "main.hh" #include "options.hh" #include "pixmaps.hh" #include "player.hh" #include "readmidi.hh" #ifndef HAVE_STRDUP extern "C" char *strdup(const char *s); #endif static void getMainMenu(GtkWidget *window, GtkWidget **menubar); static void getToolBar(GtkWidget *window, GtkWidget **toolbar); static void tempoAdjustCallback(GtkWidget *widget, gpointer data); static void rewindCallback(GtkWidget *widget, gpointer data); static void playCallback(GtkWidget *widget, gpointer data); static void fastForwardPressedCallback(GtkWidget *widget, gpointer data); static void fastForwardReleasedCallback(GtkWidget *widget, gpointer data); static void pauseCallback(GtkWidget *widget, gpointer data); static void stopCallback(GtkWidget *widget, gpointer data); static void setStatusbarText(const char *text); static void pushStatusbarText(const char *text); static void popStatusbarText(void); static void startPlaying(void); static void startTimer(void); static void stopTimer(void); static gint timeoutFunction(gpointer data); static gint timeoutId = -1; static guint32 timeoutTime = 2; // timeout in milliseconds, // don't use shorter than 2 milliseconds! static GTimer *midiTimer; static gulong currentTime = 0; static gchar fileEntry[] = "/_File"; static gchar fileOpenEntry[] = "/File/_Open..."; static gchar fileSep1Entry[] = "/File/sep1"; static gchar fileQuitEntry[] = "/File/_Quit"; static gchar editEntry[] = "/_Edit"; static gchar editPlayEntry[] = "/Edit/_Play options..."; static gchar editDevEntry[] = "/Edit/_Device options..."; static gchar midiEntry[] = "/_Midi"; static gchar midiNotesOffEntry[] = "/Midi/All notes off"; static gchar midiResetEntry[] = "/Midi/Reset"; static gchar helpEntry[] = "/_Help"; static gchar helpAboutEntry[] = "/Help/_About ..."; static gchar helpSep1Entry[] = "/Help/sep1"; static gchar helpCopyEntry[] = "/Help/_Copying ..."; static gchar branchEntry[] = ""; static gchar lastBranchEntry[] = ""; static gchar controlDEntry[] = "D"; static gchar controlOEntry[] = "O"; static gchar controlPEntry[] = "P"; static gchar controlQEntry[] = "Q"; static gchar separatorEntry[] = ""; static GtkItemFactoryEntry menu_items[] = { {fileEntry, NULL, NULL, NULL, branchEntry}, {fileOpenEntry, controlOEntry, GTK_SIGNAL_FUNC(fileOpenCallback), 0, NULL}, {fileSep1Entry, NULL, NULL, 0, separatorEntry}, {fileQuitEntry, controlQEntry, GTK_SIGNAL_FUNC(gtk_main_quit), 0, NULL}, {editEntry, NULL, NULL, 0, branchEntry}, {editPlayEntry, controlPEntry, GTK_SIGNAL_FUNC(optionsPlayCallback), 0, NULL}, {editDevEntry, controlDEntry, GTK_SIGNAL_FUNC(optionsDeviceCallback), 0, NULL}, {midiEntry, NULL, NULL, NULL, branchEntry}, {midiNotesOffEntry, NULL, NULL, 0, NULL}, {midiResetEntry, NULL, NULL, 0, NULL}, {helpEntry, NULL, NULL, 0, lastBranchEntry}, {helpAboutEntry, NULL, GTK_SIGNAL_FUNC(helpAboutCallback), 0, NULL}, {helpSep1Entry, NULL, NULL, 0, separatorEntry}, {helpCopyEntry, NULL, GTK_SIGNAL_FUNC(helpCopyingCallback), 0, NULL} }; static gchar stringEntry[] = "STRING"; static gchar textplainEntry[] = "text/plain"; static GtkTargetEntry target_table[] = { { stringEntry, 0, 0 }, { textplainEntry, 0, 0 } }; typedef struct { const char **pixmap; char *text; char *tooltip_text; GtkSignalFunc callback; gpointer user_data; GtkWidget *widget; } toolbarEntry; static char rewindEntry[] = "Rewind"; static char rewindEntryDescr[] = "Rewind MIDI file"; static char stopEntry[] = "Stop"; static char stopEntryDescr[] = "Stop playing and shut off all MIDI controllers"; static char playEntry[] = "Play"; static char playEntryDescr[] = "Play MIDI file"; static char pauseEntry[] = "Pause"; static char pauseEntryDescr[] = "Pause playing"; static char ffwdEntry[] = "Ffwd"; static char ffwdEntryDescr[] = "Fast forward"; static toolbarEntry toolbar_items[] = { { rewind_xpm, rewindEntry, rewindEntryDescr, GTK_SIGNAL_FUNC(rewindCallback), NULL, NULL}, { stop_xpm, stopEntry, stopEntryDescr, GTK_SIGNAL_FUNC(stopCallback), NULL, NULL}, { play_xpm, playEntry, playEntryDescr, GTK_SIGNAL_FUNC(playCallback), NULL, NULL}, { pause_xpm, pauseEntry, pauseEntryDescr, GTK_SIGNAL_FUNC(pauseCallback), NULL, NULL}, { fastforward_xpm, ffwdEntry, ffwdEntryDescr, NULL, NULL, NULL} }; static GtkWidget *toplevel; static gint statusbar_context_id = -1; static GtkWidget *statusbar = 0; static GtkObject *tempoAdjustment = 0; static GtkWidget *tempoRange = 0; bool autoPlay = false; bool resetTempo = false; char *windowName = 0; GtkWidget *createMainWindow(char *filename) { GtkWidget *mainVbox; GtkWidget *hbox; GtkWidget *menubar; GtkWidget *toolbar; GtkWidget *frame; GtkWidget *separator; GtkWidget *vbox; GtkWidget *label; char buf[128]; gtk_rc_parse("/home/erik/.musicagtkrc"); // make window name windowName = strdup(PACKAGE); windowName[0] = toupper(windowName[0]); // toplevel window toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(toplevel), windowName); gtk_window_set_wmclass(GTK_WINDOW(toplevel), windowName, PACKAGE); // realize it for pixmaps gtk_widget_realize(toplevel); // vbox mainVbox = gtk_vbox_new(FALSE, 0); gtk_container_border_width(GTK_CONTAINER(mainVbox), 1); gtk_container_add(GTK_CONTAINER(toplevel), mainVbox); gtk_widget_show(mainVbox); // menubar getMainMenu(toplevel, &menubar); gtk_box_pack_start(GTK_BOX(mainVbox), menubar, FALSE, TRUE, 1); gtk_widget_show(menubar); // hbox hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(mainVbox), hbox, FALSE, TRUE, 1); gtk_widget_show(hbox); // frame for file drop frame = gtk_frame_new(" File "); gtk_widget_set_usize(frame, 50, 30); gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, TRUE, 1); gtk_widget_show(frame); dropEventbox = gtk_event_box_new(); gtk_container_add(GTK_CONTAINER(frame), dropEventbox); gtk_container_border_width(GTK_CONTAINER(dropEventbox), 2); gtk_widget_show(dropEventbox); dropFrame = gtk_frame_new(0); gtk_frame_set_shadow_type (GTK_FRAME(dropFrame), GTK_SHADOW_IN); gtk_container_add(GTK_CONTAINER(dropEventbox), dropFrame); gtk_widget_show(dropFrame); // setup the event box for drop actions gtk_drag_dest_set(dropEventbox, GTK_DEST_DEFAULT_ALL, target_table, 2, (GdkDragAction)(GDK_ACTION_COPY | GDK_ACTION_MOVE)); gtk_signal_connect(GTK_OBJECT(dropEventbox), "drag_motion", GTK_SIGNAL_FUNC(dndMotionCallback), NULL); gtk_signal_connect(GTK_OBJECT(dropEventbox), "drag_leave", GTK_SIGNAL_FUNC(dndLeaveCallback), NULL); gtk_signal_connect(GTK_OBJECT(dropEventbox), "drag_drop", GTK_SIGNAL_FUNC(dndDropCallback), NULL); gtk_signal_connect(GTK_OBJECT(dropEventbox), "drag_data_received", GTK_SIGNAL_FUNC(dndDataReceivedCallback), NULL); // frame for slider frame = gtk_frame_new(" Tempo "); gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 1); gtk_widget_show(frame); // vbox for slider and text vbox = gtk_vbox_new(FALSE, 1); gtk_container_add(GTK_CONTAINER(frame), vbox); gtk_widget_show(vbox); // slider in the frame tempoAdjustment = gtk_adjustment_new(0.0, // value -4.0, // lower 4.1, // upper 0.1, // step increment 0.1, // page increment 0.1); // page size tempoRange = gtk_hscale_new(GTK_ADJUSTMENT(tempoAdjustment)); gtk_scale_set_digits(GTK_SCALE(tempoRange), 1); gtk_scale_set_value_pos(GTK_SCALE(tempoRange), GTK_POS_TOP); gtk_range_set_update_policy(GTK_RANGE(tempoRange), GTK_UPDATE_DELAYED); gtk_signal_connect(GTK_OBJECT(tempoAdjustment), "value_changed", GTK_SIGNAL_FUNC(tempoAdjustCallback), NULL); gtk_box_pack_start(GTK_BOX(vbox), tempoRange, FALSE, TRUE, 1); gtk_widget_show(tempoRange); // hbox for the two labels hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 1); gtk_widget_show(hbox); // labels label = gtk_label_new("Slower"); gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 1); gtk_widget_show(label); label = gtk_label_new("Faster"); gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_RIGHT); gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 1); gtk_widget_show(label); // toolbar getToolBar(toplevel, &toolbar); // set callback functions for the fastforward button gtk_signal_connect(GTK_OBJECT(toolbar_items[4].widget), "pressed", GTK_SIGNAL_FUNC(fastForwardPressedCallback), 0); gtk_signal_connect(GTK_OBJECT(toolbar_items[4].widget), "released", GTK_SIGNAL_FUNC(fastForwardReleasedCallback), 0); // show toolbar gtk_box_pack_start(GTK_BOX(mainVbox), toolbar, FALSE, FALSE, 1); gtk_widget_show(toolbar); // now that we've made the toolbar, we can also set a tooltip on // the tempo slider using the tooltips set of the toolbar gtk_tooltips_set_tip(GTK_TOOLBAR(toolbar)->tooltips, tempoRange, "Relative tempo", "private"); // and of course on the file drop pocket gtk_tooltips_set_tip(GTK_TOOLBAR(toolbar)->tooltips, dropEventbox, "Drop a MIDI file to open it", "private"); // separator separator = gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(mainVbox), separator, FALSE, FALSE, 1); gtk_widget_show(separator); // statusbar statusbar = gtk_statusbar_new(); gtk_box_pack_start(GTK_BOX(mainVbox), statusbar, FALSE, FALSE, 1); gtk_widget_show(statusbar); statusbar_context_id = gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar), "Default statusbar"); g_snprintf(buf, 127, "%s %s", windowName, VERSION); pushStatusbarText(buf); // add timer // FIXME: this is not the right place midiTimer = g_timer_new(); // open file, if neccessary if(filename != 0) openFile(filename); return(toplevel); } gint dialogDeleteCallback(GtkWidget *widget, gpointer data) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif assert(widget != 0); gtk_widget_hide(widget); return(TRUE); } void showOrRaiseWindow(GtkWidget *widget) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif assert(widget != 0); if((widget->window == 0) || (!gdk_window_is_visible(widget->window))) gtk_widget_show(widget); else gdk_window_raise(widget->window); } static void getMainMenu(GtkWidget *window, GtkWidget **menubar) { GtkItemFactory *item_factory; GtkAccelGroup *accel_group; gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]); assert(window != 0); accel_group = gtk_accel_group_new (); /* This function initializes the item factory. Param 1: The type of menu - can be GTK_TYPE_MENU_BAR, GTK_TYPE_MENU, or GTK_TYPE_OPTION_MENU. Param 2: The path of the menu. Param 3: A pointer to a gtk_accel_group. The item factory sets up the accelerator table while generating menus. */ item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "
", accel_group); /* This function generates the menu items. Pass the item factory, the number of items in the array, the array itself, and any callback data for the the menu items. */ gtk_item_factory_create_items (item_factory, nmenu_items, menu_items, NULL); /* Attach the new accelerator group to the window. */ gtk_accel_group_attach (accel_group, GTK_OBJECT (window)); if (menubar) /* Finally, return the actual menu bar created by the item factory. */ *menubar = gtk_item_factory_get_widget (item_factory, "
"); } static void getToolBar(GtkWidget *window, GtkWidget **toolbar) { GtkStyle *style; GtkWidget *pixmap; GdkPixmap *pmap; GdkBitmap *mask; gint ntoolbar_items = sizeof (toolbar_items) / sizeof (toolbar_items[0]); int i; assert(window != 0); // make toolbar *toolbar = gtk_toolbar_new(GTK_ORIENTATION_HORIZONTAL, GTK_TOOLBAR_BOTH); // get parent style for pixmaps in buttons style = gtk_widget_get_style(window); // now the entries for(i = 0; i < ntoolbar_items; i++) { pmap = gdk_pixmap_create_from_xpm_d(window->window, &mask, &style->bg[GTK_STATE_NORMAL], (gchar **)toolbar_items[i].pixmap); pixmap = gtk_pixmap_new(pmap, mask); // FIXME: is this enough to destroy pmap and mask? gdk_pixmap_unref(pmap); gdk_bitmap_unref(mask); toolbar_items[i].widget = gtk_toolbar_append_item(GTK_TOOLBAR(*toolbar), toolbar_items[i].text, toolbar_items[i].tooltip_text, "private", pixmap, toolbar_items[i].callback, toolbar_items[i].user_data); } } static void tempoAdjustCallback(GtkWidget *widget, gpointer data) { int value; #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif value = (int)(10.0 * GTK_ADJUSTMENT(tempoAdjustment)->value); thePlayer->setRelativeTempo(value); } static void rewindCallback(GtkWidget *widget, gpointer data) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif thePlayer->rewind(); stopTimer(); setStatusbarText("Rewinded"); } static void playCallback(GtkWidget *widget, gpointer data) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif startPlaying(); } static void fastForwardPressedCallback(GtkWidget *widget, gpointer data) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif thePlayer->fastForward(true); } static void fastForwardReleasedCallback(GtkWidget *widget, gpointer data) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif thePlayer->fastForward(false); } static void pauseCallback(GtkWidget *widget, gpointer data) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif thePlayer->stop(); stopTimer(); setStatusbarText("Paused"); } static void stopCallback(GtkWidget *widget, gpointer data) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif thePlayer->stop(); thePlayer->rewind(); stopTimer(); setStatusbarText("Paused"); } static void setStatusbarText(const char *text) { assert(text != 0); popStatusbarText(); pushStatusbarText(text); } static void pushStatusbarText(const char *text) { assert(text != 0); gtk_statusbar_push(GTK_STATUSBAR(statusbar), statusbar_context_id, text); } static void popStatusbarText(void) { gtk_statusbar_pop(GTK_STATUSBAR(statusbar), statusbar_context_id); } void openFile(const char *filename) { const char *dummy; char buf[128]; midiSequence *oldsequence = 0; midiSequence *sequence = 0; assert(filename != 0); setStatusbarText("Reading MIDI file ..."); while (gtk_events_pending()) gtk_main_iteration(); thePlayer->stop(); stopTimer(); // delete current events oldsequence = thePlayer->getSequence(); // read midi file sequence = new midiSequence; if(readMidi(filename, sequence)) { if(oldsequence != 0) delete oldsequence; thePlayer->setSequence(sequence); dummy = strrchr(filename, '/'); if(dummy == NULL) dummy = filename; else dummy++; // set title g_snprintf(buf, 127, "%s: %s", windowName, dummy); gtk_window_set_title(GTK_WINDOW(toplevel), buf); // set statusbar text g_snprintf(buf, 127, "Read %s", dummy); setStatusbarText(buf); if(resetTempo == true) gtk_adjustment_set_value(GTK_ADJUSTMENT(tempoAdjustment), 0.0); if(autoPlay == true) startPlaying(); } else { delete sequence; setStatusbarText("Read MIDI file failed"); } } static void startPlaying(void) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif thePlayer->play(); setStatusbarText("Playing"); startTimer(); } static void startTimer(void) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif if(timeoutId == -1) { currentTime = 0; g_timer_reset(midiTimer); g_timer_start(midiTimer); timeoutId = gtk_timeout_add(timeoutTime, timeoutFunction, NULL); } } static void stopTimer(void) { #ifdef MUSICA_DEBUG cerr<< __PRETTY_FUNCTION__<< endl; #endif if(timeoutId != -1) { gtk_timeout_remove(timeoutId); timeoutId = -1; } } static gint timeoutFunction(gpointer data) { gulong microseconds; gulong seconds; char buf[128]; static int min = -1; static int sec = -1; int newmin, newsec; long int deltaTime; // play as long as the next note occurs within our timer interval do { seconds = (gulong)floor(g_timer_elapsed(midiTimer, µseconds)); currentTime = (seconds * 1000000L) + microseconds ; deltaTime = thePlayer->doPlay(currentTime); if(deltaTime < 0L) { return(FALSE); // end of song, stop playing } } while(deltaTime <= (long int)(timeoutTime * 1000)); newmin = (int)(seconds / 60L); newsec = (int)(seconds % 60L); if((newsec != sec) || (newmin != min)) { sec = newsec; min = newmin; g_snprintf(buf, 127, "Playing - %d:%02d", min, sec); setStatusbarText(buf); } return(TRUE); // we want to be called again and again }