/* * Sweep, a sound wave editor. * * Copyright (C) 2000 Conrad Parker * Copyright (C) 2002 Commonwealth Scientific and Industrial Research * Organisation (CSIRO), Australia * * 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. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include "sweep_app.h" #include "edit.h" #include "undo_dialog.h" #include "head.h" #include "play.h" #include "file_dialogs.h" #include "question_dialogs.h" #ifdef LIMITED_UNDO /* Nr. of undo operations remembered */ #define UNDO_LEVELS 99 #endif /* Within each sample s, maintain: * s->current_undo == s->current_redo->prev * s->current_redo == s->current_undo->next */ /*#define DEBUG*/ static void op_main (sw_sample * sample) { GList * gl; sw_op_instance * inst; #ifdef DEBUG g_print ("%d: Hello from op_main %d!\n", getpid(), getpid()); #endif while (sample->edit_state != SWEEP_EDIT_STATE_CANCEL && (gl = sample->pending_ops) != NULL) { g_mutex_lock (sample->edit_mutex); while (sample->edit_state != SWEEP_EDIT_STATE_PENDING && sample->edit_state != SWEEP_EDIT_STATE_CANCEL) { g_cond_wait (sample->pending_cond, sample->edit_mutex); } if (sample->edit_state == SWEEP_EDIT_STATE_CANCEL) { #ifdef DEBUG g_print ("Caught an early cancelmoose; pending is %p\n", sample->pending_ops); fflush (stdout); #endif } else { gboolean was_going = FALSE; inst = (sw_op_instance *)gl->data; g_assert (sample->edit_state == SWEEP_EDIT_STATE_PENDING); sample->edit_state = SWEEP_EDIT_STATE_BUSY; sample->pending_ops = g_list_remove_link (sample->pending_ops, gl); g_mutex_unlock (sample->edit_mutex); if (inst->op->edit_mode == SWEEP_EDIT_MODE_ALLOC) { g_mutex_lock (sample->play_mutex); if ((was_going = sample->play_head->going)) { head_set_stop_offset (sample->play_head, sample->user_offset); head_set_going (sample->play_head, FALSE); } g_mutex_unlock (sample->play_mutex); } /* XXX: this is fubar -- change to SweepFunction ?? or change all to * have sample as first arg ... */ inst->op->_do_ ((sw_sample *)inst, (void *)inst); g_mutex_lock (sample->edit_mutex); #ifdef DEBUG if (sample->edit_state == SWEEP_EDIT_STATE_CANCEL) { g_print ("Caught a late cancelmoose; pending is %p\n", sample->pending_ops); fflush (stdout); } else { g_print ("%d: post-op edit state is %d\n", getpid(), sample->edit_state); } #endif } sample->edit_state = SWEEP_EDIT_STATE_DONE; g_mutex_unlock (sample->edit_mutex); } g_mutex_lock (sample->edit_mutex); #ifdef DEBUG if (sample->edit_state == SWEEP_EDIT_STATE_CANCEL) { g_print ("%d: Let me save you from scrubby!.\n", getpid()); sample->edit_state = SWEEP_EDIT_STATE_DONE; } else { g_print ("%d: exit edit state is %d\n", getpid(), sample->edit_state); } g_print ("%d: yada yada that's all!\n", getpid()); #endif sample->ops_thread = (pthread_t) -1; g_mutex_unlock (sample->edit_mutex); } static void prepare_op (sw_op_instance * inst) { sw_sample * sample = inst->sample; gchar buf[128]; g_snprintf (buf, sizeof (buf), "%s (%%p%%%%)", inst->description); sample_set_progress_text (sample, buf); sample_set_edit_mode (sample, inst->op->edit_mode); sample_set_progress_percent (sample, 0); if (sample->ops_thread == (pthread_t) -1) { pthread_create (&sample->ops_thread, NULL, (void *) (*op_main), sample); } } gint update_edit_progress (gpointer data) { sw_sample * sample = (sw_sample *)data; sw_op_instance * inst; if (sample->edit_state == SWEEP_EDIT_STATE_BUSY) { sample_refresh_progress_percent (sample); if (sample->edit_mode == SWEEP_EDIT_MODE_META || sample->edit_mode == SWEEP_EDIT_MODE_FILTER) sample_refresh_views (sample); return TRUE; } if (sample->edit_state == SWEEP_EDIT_STATE_DONE) { undo_dialog_refresh_history (sample); if (sample->sounddata->sels == NULL) { sample_stop_marching_ants (sample); } sample_refresh_views (sample); if (sample->pending_ops == NULL) sample_set_edit_state (sample, SWEEP_EDIT_STATE_IDLE); } if (sample->pending_ops != NULL) { inst = (sw_op_instance *)sample->pending_ops->data; prepare_op (inst); sample_set_edit_state (sample, SWEEP_EDIT_STATE_PENDING); } if (sample->edit_state == SWEEP_EDIT_STATE_IDLE) { sample_refresh_views (sample); sample->op_progress_tag = -1; return FALSE; } if (sample->edit_state == SWEEP_EDIT_STATE_CANCEL) { sample_set_progress_text (sample, "Scrubby has you."); } return TRUE; } static void sw_op_instance_clear (sw_op_instance * inst) { if (!inst) return; if (inst->do_data) { if (inst->op->purge_do) inst->op->purge_do (inst->do_data); } if (inst->undo_data) { if (inst->op->purge_undo) inst->op->purge_undo (inst->undo_data); else g_free (inst->undo_data); } if (inst->redo_data && inst->redo_data != inst->undo_data) { if (inst->op->purge_redo) inst->op->purge_redo (inst->redo_data); else g_free (inst->redo_data); } inst->do_data = NULL; inst->undo_data = NULL; inst->redo_data = NULL; } sw_op_instance * sw_op_instance_new (sw_sample * sample, char * desc, sw_operation * op) { sw_op_instance * inst; inst = g_malloc (sizeof(sw_op_instance)); inst->sample = sample; inst->description = strdup (desc); inst->op = op; inst->do_data = NULL; inst->undo_data = NULL; inst->redo_data = NULL; return inst; } void trim_registered_ops (sw_sample * s, int length) { GList * gl; while (g_list_length (s->registered_ops) > length) { gl = g_list_first (s->registered_ops); s->registered_ops = g_list_remove_link (s->registered_ops, gl); sw_op_instance_clear (gl->data); g_list_free (gl); } if (s->registered_ops == NULL) s->current_undo = NULL; } static void schedule_operation_do (sw_op_instance * inst) { sw_sample * sample = inst->sample; g_mutex_lock (sample->edit_mutex); sample->pending_ops = g_list_append (sample->pending_ops, inst); g_mutex_unlock (sample->edit_mutex); if (sample->op_progress_tag == -1) { sample->op_progress_tag = g_timeout_add (30, (GtkFunction)update_edit_progress, (gpointer)sample); } } static void schedule_operation_ok_cb (GtkWidget * widget, gpointer data) { sw_op_instance * inst = (sw_op_instance *)data; sw_sample * sample = inst->sample; sample->edit_ignore_mtime = TRUE; schedule_operation_do (inst); } void schedule_operation (sw_sample * sample, char * description, sw_operation * operation, void * do_data) { sw_op_instance * inst; char buf[512]; inst = sw_op_instance_new (sample, description, operation); inst->do_data = do_data; if (operation->edit_mode != SWEEP_EDIT_MODE_META && !sample->edit_ignore_mtime && sample_mtime_changed (sample)) { snprintf (buf, sizeof (buf), _("%s\n has changed on disk.\n\n" "Do you want to continue editing this buffer?"), sample->pathname); question_dialog_new (sample, _("File modified"), buf, _("Continue editing"), _("Reread from disk"), G_CALLBACK (schedule_operation_ok_cb), inst, G_CALLBACK (sample_revert_ok_cb), sample, SWEEP_EDIT_MODE_ALLOC); } else { schedule_operation_do (inst); } } void register_operation (sw_sample * s, sw_op_instance * inst) { GList * trash, * gl; sw_op_instance * inst2; #ifdef DEBUG printf("Registering %s\n", inst->description); #endif g_mutex_lock (s->ops_mutex); if (s->registered_ops && s->current_redo) { /* Split the list here -- keep everything up to and * including current_undo, and trash the rest. */ s->current_redo->prev = NULL; if (s->current_undo) s->current_undo->next = NULL; if (s->registered_ops == s->current_redo) s->registered_ops = NULL; trash = s->current_redo; s->current_redo = NULL; /* Free up the rest of the list */ for (gl = trash; gl; gl = gl->next) { inst2 = (sw_op_instance *)gl->data; sw_op_instance_clear (inst2); } /* Trash it */ g_list_free (trash); } s->registered_ops = g_list_append (s->registered_ops, inst); #ifdef LIMITED_UNDO trim_registered_ops (s, UNDO_LEVELS); #endif s->current_undo = g_list_find (s->registered_ops, inst); s->active_op = NULL; if (inst->op->edit_mode != SWEEP_EDIT_MODE_META) { s->modified = TRUE; } g_mutex_unlock (s->ops_mutex); } static void undo_operation (sw_sample * s, sw_op_instance * inst) { if (inst && inst->op->undo) { inst->op->undo(s, inst->undo_data); } } static void redo_operation (sw_sample * s, sw_op_instance * inst) { if (inst && inst->op->redo) { inst->op->redo(s, inst->redo_data); } } static void do_undo_current_thread (sw_op_instance * inst) { sw_sample * s = inst->sample; if (s == NULL || s->current_undo == NULL) goto noop; undo_operation (s, inst->do_data); g_mutex_lock (s->ops_mutex); if (s->edit_state == SWEEP_EDIT_STATE_BUSY) { s->current_redo = s->current_undo; s->current_undo = s->current_undo->prev; } g_mutex_unlock (s->ops_mutex); return; noop: sample_set_tmp_message (s, _("Nothing to undo")); } static sw_operation undo_filter_op = { SWEEP_EDIT_MODE_FILTER, (SweepCallback)do_undo_current_thread, (SweepFunction)NULL, (SweepCallback)NULL, (SweepFunction)NULL, (SweepCallback)NULL, (SweepFunction)NULL, }; static sw_operation undo_alloc_op = { SWEEP_EDIT_MODE_ALLOC, (SweepCallback)do_undo_current_thread, (SweepFunction)NULL, (SweepCallback)NULL, (SweepFunction)NULL, (SweepCallback)NULL, (SweepFunction)NULL, }; static void schedule_undo_inst (sw_sample * sample, sw_op_instance * inst) { sw_operation * op; gchar buf[128]; g_snprintf (buf, sizeof (buf), "Undo %s", inst->description); if (inst->op->edit_mode == SWEEP_EDIT_MODE_FILTER) op = &undo_filter_op; else op = &undo_alloc_op; schedule_operation (sample, buf, op, inst); } void undo_current (sw_sample * sample) { sw_op_instance * inst; if (sample == NULL) return; if (sample->edit_state != SWEEP_EDIT_STATE_IDLE) return; if (sample->current_undo == NULL) goto noop; inst = sample->current_undo->data; if (inst->op->undo != NULL) schedule_undo_inst (sample, inst); return; noop: sample_set_tmp_message (sample, _("Nothing to undo")); sample_set_progress_ready (sample); } static void do_redo_current_thread (sw_op_instance * inst) { sw_sample * s = inst->sample; if (s == NULL || s->current_redo == NULL) goto noop; redo_operation (s, inst->do_data); g_mutex_lock (s->ops_mutex); if (s->edit_state == SWEEP_EDIT_STATE_BUSY) { s->current_undo = s->current_redo; s->current_redo = s->current_redo->next; } g_mutex_unlock (s->ops_mutex); return; noop: sample_set_tmp_message (s, _("Nothing to redo")); } static sw_operation redo_filter_op = { SWEEP_EDIT_MODE_FILTER, (SweepCallback)do_redo_current_thread, (SweepFunction)NULL, (SweepCallback)NULL, (SweepFunction)NULL, (SweepCallback)NULL, (SweepFunction)NULL, }; static sw_operation redo_alloc_op = { SWEEP_EDIT_MODE_ALLOC, (SweepCallback)do_redo_current_thread, (SweepFunction)NULL, (SweepCallback)NULL, (SweepFunction)NULL, (SweepCallback)NULL, (SweepFunction)NULL, }; static void schedule_redo_inst (sw_sample * sample, sw_op_instance * inst) { sw_operation * op; gchar buf[128]; g_snprintf (buf, sizeof (buf), "Redo %s", inst->description); if (inst->op->edit_mode == SWEEP_EDIT_MODE_FILTER) op = &redo_filter_op; else op = &redo_alloc_op; schedule_operation (sample, buf, op, inst); } void redo_current (sw_sample * sample) { sw_op_instance * inst; if (sample == NULL) return; if (sample->edit_state != SWEEP_EDIT_STATE_IDLE) return; if (sample->current_redo == NULL) goto noop; inst = sample->current_redo->data; if (inst->op->redo != NULL) schedule_redo_inst (sample, inst); noop: sample_set_tmp_message (sample, _("Nothing to redo")); sample_set_progress_ready (sample); } void revert_op (sw_sample * sample, GList * op_gl) { GList * gl = NULL; sw_op_instance * inst; gboolean need_undo = FALSE; if (sample == NULL) return; for (gl = g_list_last (sample->registered_ops); gl; gl = gl->prev) { inst = (sw_op_instance *)gl->data; if (gl == sample->current_undo) { need_undo = TRUE; } if (gl == op_gl) break; } if (need_undo) { for (gl = sample->current_undo; gl != op_gl; gl = gl->prev) { inst = (sw_op_instance *)gl->data; schedule_undo_inst (sample, inst); } } else { for (gl = sample->current_redo; gl != op_gl; gl = gl->next) { inst = (sw_op_instance *)gl->data; schedule_redo_inst (sample, inst); } if (gl != NULL) { inst = (sw_op_instance *)gl->data; schedule_redo_inst (sample, inst); } } } void set_active_op (sw_sample * s, sw_op_instance * inst) { g_mutex_lock (s->ops_mutex); g_assert (s->active_op == NULL); s->active_op = inst; g_mutex_unlock (s->ops_mutex); } #ifdef TRYCANCEL static gint try_cancel_active_op (gpointer data) { sw_sample * s = (sw_sample *)data; if (g_mutex_trylock (s->ops_mutex)) { if (s->active_op) { undo_operation (s, s->active_op); } s->active_op = NULL; sample_set_edit_state (s, SWEEP_EDIT_STATE_CANCEL); g_mutex_unlock (s->ops_mutex); return FALSE; } else { return TRUE; } } #endif void cancel_active_op (sw_sample * s) { #ifdef TRYCANCEL g_idle_add (GSourceFunc(try_cancel_active_op), (gpointer)s); #else g_mutex_lock (s->ops_mutex); if (s->active_op) { undo_operation (s, s->active_op); sample_set_tmp_message (s, "%s CANCELLED", s->active_op->description); /* XXX: does this leak s->active_op here? */ } s->active_op = NULL; g_mutex_lock (s->edit_mutex); /* * Signal to the ops thread to cancel or not perform the current operation. * It is possible that the ops thread has already exited, in which case * s->ops_thread will have been set to -1 on its departure, also within * s->edit_mutex, in which case the signalled CANCEL would never be cleared. */ if (s->ops_thread != (pthread_t) -1) { s->edit_state = SWEEP_EDIT_STATE_CANCEL; } if (s->pending_ops) { GList * gltmp = s->pending_ops; s->pending_ops = NULL; g_print ("XXX: cancel: need to remove pending ops\n"); g_list_free (gltmp); } g_mutex_unlock (s->edit_mutex); /* sample_set_edit_state (s, SWEEP_EDIT_STATE_CANCEL);*/ g_mutex_unlock (s->ops_mutex); #endif } /* Standard undo/redo functions */ sounddata_replace_data * sounddata_replace_data_new (sw_sample * sample, sw_sounddata * old_sounddata, sw_sounddata * new_sounddata) { sounddata_replace_data * sr; sr = g_malloc (sizeof(sounddata_replace_data)); sr->sample = sample; sr->old_sounddata = old_sounddata; sr->new_sounddata = new_sounddata; old_sounddata->refcount++; new_sounddata->refcount++; return sr; } void sounddata_replace_data_destroy (sounddata_replace_data * sr) { sounddata_destroy (sr->old_sounddata); sounddata_destroy (sr->new_sounddata); g_free (sr); } void undo_by_sounddata_replace (sw_sample * s, sounddata_replace_data * sr) { g_mutex_lock (s->ops_mutex); s->sounddata = sr->old_sounddata; g_mutex_unlock (s->ops_mutex); } void redo_by_sounddata_replace (sw_sample * s, sounddata_replace_data * sr) { g_mutex_lock (s->ops_mutex); s->sounddata = sr->new_sounddata; g_mutex_unlock (s->ops_mutex); } paste_over_data * paste_over_data_new (sw_edit_buffer * old_eb, sw_edit_buffer * new_eb) { paste_over_data * p; p = g_malloc (sizeof(paste_over_data)); p->old_eb = old_eb; p->new_eb = new_eb; return p; }; void paste_over_data_destroy (paste_over_data * p) { edit_buffer_destroy (p->old_eb); if (p->new_eb != p->old_eb) edit_buffer_destroy (p->new_eb); g_free (p); } void undo_by_paste_over (sw_sample * s, paste_over_data * p) { paste_over (s, p->old_eb); } void redo_by_paste_over (sw_sample * s, paste_over_data * p) { paste_over (s, p->new_eb); } splice_data * splice_data_new (sw_edit_buffer * eb, GList * sels) { splice_data * s; s = g_malloc (sizeof(splice_data)); s->eb = eb; s->sels = sels_copy (sels); return s; } void splice_data_destroy (splice_data * s) { edit_buffer_destroy (s->eb); g_list_free (s->sels); g_free (s); } void undo_by_splice_in (sw_sample * s, splice_data * sp) { splice_in_eb (s, sp->eb); } void redo_by_splice_out (sw_sample * s, splice_data * sp) { splice_out_sel (s); } void undo_by_splice_out (sw_sample * s, splice_data * sp) { splice_out_sel (s); s->sounddata->sels = sels_copy (sp->sels); } void redo_by_splice_in (sw_sample * s, splice_data * sp) { splice_in_eb (s, sp->eb); } void undo_by_splice_over (sw_sample * s, splice_data * sp) { s->sounddata->sels = sels_copy (sp->sels); paste_over (s, sp->eb); } void redo_by_splice_over (sw_sample * s, splice_data * sp) { s->sounddata->sels = sels_copy (sp->sels); paste_over (s, sp->eb); } void undo_by_crop_in (sw_sample * s, splice_data * sp) { crop_in (s, sp->eb); } void redo_by_crop_out (sw_sample * s, splice_data * sp) { crop_out (s); }