1 /*
2  * Copyright (C) 2005 2006 2007 2010 2011 2012, Magnus Hjorth
3  *
4  * This file is part of mhWaveEdit.
5  *
6  * mhWaveEdit is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * mhWaveEdit is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with mhWaveEdit; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include <config.h>
22 
23 
24 #include "um.h"
25 #include "document.h"
26 #include "filetypes.h"
27 #include "player.h"
28 #include "gettext.h"
29 
30 ListObject *document_objects = NULL;
31 
32 Document *playing_document = NULL;
33 static guint untitled_count = 0;
34 
35 gboolean view_follow_strict_flag;
36 
37 enum { VIEW_CHANGED_SIGNAL, SELECTION_CHANGED_SIGNAL, CURSOR_CHANGED_SIGNAL,
38        STATE_CHANGED_SIGNAL, LAST_SIGNAL };
39 static guint document_signals[LAST_SIGNAL] = { 0 };
40 
41 static GtkObjectClass *parent_class;
42 
43 static void document_set_cursor_main(Document *d, off_t cursorpos,
44 				     gboolean playslave, gboolean running,
45 				     off_t bufpos);
46 
clear_marklist(struct MarkList * m)47 static void clear_marklist(struct MarkList *m)
48 {
49      int i;
50      for (i=0; i<m->length; i++) g_free(m->names[i]);
51      g_free(m->names);
52      g_free(m->places);
53      m->names = NULL;
54      m->places = NULL;
55      m->length = m->alloced = 0;
56 }
57 
copy_marklist(struct MarkList * dest,struct MarkList * src)58 static void copy_marklist(struct MarkList *dest, struct MarkList *src)
59 {
60      int i;
61 
62      for (i=0; i<dest->length; i++) g_free(dest->names[i]);
63      dest->length = 0;
64 
65      if ( dest->alloced < src->length ) {
66 	  dest->names = g_realloc(dest->names,
67 				  src->length * sizeof(dest->names[0]));
68 	  dest->places = g_realloc(dest->places,
69 				   src->length * sizeof(dest->places[0]));
70 	  dest->alloced = src->length;
71      }
72 
73      for (i=0; i<src->length; i++) {
74 	  dest->names[i] = g_strdup(src->names[i]);
75 	  dest->places[i] = src->places[i];
76      }
77      dest->length = src->length;
78 }
79 
marklist_equal(struct MarkList * l1,struct MarkList * l2)80 static gboolean marklist_equal(struct MarkList *l1, struct MarkList *l2)
81 {
82      int i,j;
83      if (l1->length != l2->length) return FALSE;
84      for (i=0; i<l1->length; i++) {
85 	  for (j=0; j<l2->length; j++)
86 	       if (!strcmp(l1->names[i],l2->names[j])) break;
87 	  if (j == l2->length) return FALSE;
88 	  if (l1->places[i] != l2->places[i]) return FALSE;
89      }
90      return TRUE;
91 }
92 
document_init(GtkObject * obj)93 static void document_init(GtkObject *obj)
94 {
95      Document *d = DOCUMENT(obj);
96      d->filename = NULL;
97      d->lossy = FALSE;
98      d->titlename = NULL;
99      d->title_serial = 0;
100      d->history_pos = NULL;
101      d->chunk = NULL;
102      d->viewstart = d->viewend = 0;
103      d->selstart = d->selend = 0;
104      d->cursorpos = 0;
105      d->marks.length = d->marks.alloced = 0;
106      /* Most initialization is delayed until we have a chunk */
107 }
108 
clear_history(struct HistoryEntry * h)109 static void clear_history(struct HistoryEntry *h)
110 {
111      struct HistoryEntry *ph;
112      if (h != NULL) {
113 	  while (h->next != NULL) h=h->next;
114 	  while (h != NULL) {
115 	       if (h->chunk != NULL) {
116 		    gtk_object_unref(GTK_OBJECT(h->chunk));
117 		    h->chunk = NULL;
118 	       }
119 	       clear_marklist(&(h->marks));
120 
121 	       ph = h->prev;
122 	       g_free(h);
123 	       h = ph;
124 	  }
125      }
126 }
127 
document_destroy(GtkObject * object)128 static void document_destroy(GtkObject *object)
129 {
130      Document *d = DOCUMENT(object);
131      if (d->filename != NULL) {
132 	  g_free(d->filename);
133 	  d->filename = NULL;
134      }
135      if (d->titlename != NULL) {
136 	  g_free(d->titlename);
137 	  d->titlename = NULL;
138      }
139      if (d->chunk != NULL) {
140 	  gtk_object_unref(GTK_OBJECT(d->chunk));
141 	  d->chunk = NULL;
142      }
143      clear_history(d->history_pos);
144      d->history_pos = NULL;
145      list_object_remove(document_objects,object);
146      parent_class->destroy(object);
147 }
148 
document_state_changed(Document * d)149 static void document_state_changed(Document *d)
150 {
151      list_object_notify(document_objects,d);
152 }
153 
document_class_init(GtkObjectClass * klass)154 static void document_class_init(GtkObjectClass *klass)
155 {
156      DocumentClass *dc = DOCUMENT_CLASS(klass);
157      parent_class = gtk_type_class(gtk_object_get_type());
158 
159      klass->destroy = document_destroy;
160      dc->view_changed = NULL;
161      dc->selection_changed = NULL;
162      dc->cursor_changed = NULL;
163      dc->state_changed = document_state_changed;
164 
165      document_signals[VIEW_CHANGED_SIGNAL] =
166 	  gtk_signal_new("view-changed", GTK_RUN_FIRST, GTK_CLASS_TYPE(klass),
167 			 GTK_SIGNAL_OFFSET(DocumentClass,view_changed),
168 			 gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);
169      document_signals[SELECTION_CHANGED_SIGNAL] =
170 	  gtk_signal_new("selection-changed", GTK_RUN_FIRST,
171 			 GTK_CLASS_TYPE(klass),
172 			 GTK_SIGNAL_OFFSET(DocumentClass,selection_changed),
173 			 gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);
174      document_signals[CURSOR_CHANGED_SIGNAL] =
175 	  gtk_signal_new("cursor-changed", GTK_RUN_FIRST, GTK_CLASS_TYPE(klass),
176 			 GTK_SIGNAL_OFFSET(DocumentClass,cursor_changed),
177 			 gtk_marshal_NONE__BOOL, GTK_TYPE_NONE, 1,
178 			 GTK_TYPE_BOOL);
179      document_signals[STATE_CHANGED_SIGNAL] =
180 	  gtk_signal_new("state-changed", GTK_RUN_FIRST, GTK_CLASS_TYPE(klass),
181 			 GTK_SIGNAL_OFFSET(DocumentClass,state_changed),
182 			 gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0);
183 
184      gtk_object_class_add_signals(klass,document_signals,LAST_SIGNAL);
185 
186 }
187 
document_get_type(void)188 GtkType document_get_type(void)
189 {
190      static GtkType id = 0;
191      if (!id) {
192 	  GtkTypeInfo info = {
193 	       "Document",
194 	       sizeof(Document),
195 	       sizeof(DocumentClass),
196 	       (GtkClassInitFunc) document_class_init,
197 	       (GtkObjectInitFunc) document_init
198 	  };
199 	  id = gtk_type_unique(gtk_object_get_type(),&info);
200      }
201      return id;
202 }
203 
document_new_with_file(gchar * filename,StatusBar * bar)204 Document *document_new_with_file(gchar *filename, StatusBar *bar)
205 {
206      Chunk *c;
207      gboolean b;
208      Document *d;
209      c = chunk_load_x(filename,dither_editing,bar,&b);
210      if (c == NULL) return NULL;
211      d = document_new_with_chunk(c,filename,bar);
212      d->lossy = b;
213      return d;
214 }
215 
document_set_filename(Document * d,gchar * filename,gboolean do_notify)216 static void document_set_filename(Document *d, gchar *filename,
217 				  gboolean do_notify)
218 {
219      guint i=0;
220      GList *l;
221      Document *x;
222      if ((filename == d->filename && filename != NULL) ||
223 	 (d->filename != NULL && filename != NULL &&
224 	  !strcmp(d->filename,filename)))
225 	  return;
226      g_free(d->titlename);
227      g_free(d->filename);
228      d->filename = g_strdup(filename);
229      if (filename == NULL) {
230 	  d->titlename = g_strdup_printf(_("untitled #%d"),++untitled_count);
231      } else {
232 	  for (l=document_objects->list;l!=NULL;l=l->next) {
233 	       x = DOCUMENT(l->data);
234 	       if (x == d) continue;
235 	       if (d->filename == NULL || x->filename == NULL ||
236 		   strcmp(namepart(d->filename),namepart(x->filename)))
237 		    continue;
238 	       if (x->title_serial > i) i=x->title_serial;
239 	  }
240 	  d->title_serial = i+1;
241 	  if (d->title_serial > 1)
242 	       d->titlename = g_strdup_printf("%s #%d",namepart(d->filename),
243 					      d->title_serial);
244 	  else
245 	       d->titlename = g_strdup(namepart(d->filename));
246      }
247      if (do_notify)
248 	  list_object_notify(document_objects,d);
249 }
250 
document_forget_filename(Document * d)251 void document_forget_filename(Document *d)
252 {
253      document_set_filename(d,NULL,FALSE);
254      gtk_signal_emit(GTK_OBJECT(d),document_signals[STATE_CHANGED_SIGNAL]);
255 }
256 
document_new_with_chunk(Chunk * chunk,gchar * sourcename,StatusBar * bar)257 Document *document_new_with_chunk(Chunk *chunk, gchar *sourcename,
258 				  StatusBar *bar)
259 {
260      Document *d = (Document *)gtk_type_new(document_get_type());
261      /* Don't send the notify signal from document_objects here, since we
262       * haven't added the document yet. */
263      document_set_filename(d,sourcename,FALSE);
264      d->chunk = chunk;
265      gtk_object_ref(GTK_OBJECT(chunk));
266      gtk_object_sink(GTK_OBJECT(chunk));
267      d->viewstart = 0;
268      d->viewend = chunk->length;
269      d->bar = bar;
270      /* The add signal will be sent here */
271      list_object_add(document_objects,d);
272      return d;
273 }
274 
document_save(Document * d,gchar * filename,gint type_id,gboolean use_defs)275 gboolean document_save(Document *d, gchar *filename, gint type_id,
276 		       gboolean use_defs)
277 {
278      gboolean b;
279      int i;
280      if (d->filename != NULL && !strcmp(filename,d->filename) &&
281 	 d->lossy) {
282 	  i = user_message(_("Loading and then saving files with 'lossy' "
283 			     "formats (like mp3 and ogg) leads to a quality "
284 			     "loss, also for the unmodified parts of the "
285 			     "file."), UM_OKCANCEL);
286 	  if (i == MR_CANCEL) return TRUE;
287      }
288      b = chunk_save(d->chunk,filename,type_id,use_defs,dither_editing,d->bar);
289      if (!b) {
290 	  clear_history(d->history_pos);
291 	  d->history_pos = NULL;
292 	  document_set_filename(d,filename,TRUE);
293 	  gtk_signal_emit(GTK_OBJECT(d),
294 			  document_signals[STATE_CHANGED_SIGNAL]);
295      }
296      return b;
297 }
298 
cursor_cb(off_t pos,off_t bufpos,gboolean is_running)299 static void cursor_cb(off_t pos, off_t bufpos, gboolean is_running)
300 {
301      Document *d = playing_document;
302      if (playing_document != NULL) {
303 	  if (!is_running) playing_document=NULL;
304 	  document_set_cursor_main(d,pos,TRUE,is_running,bufpos);
305 	  if (!is_running) gtk_object_unref(GTK_OBJECT(d));
306      }
307 }
308 
document_play(Document * d,off_t start,off_t end,gboolean loop,gfloat speed)309 void document_play(Document *d, off_t start, off_t end, gboolean loop,
310 		   gfloat speed)
311 {
312      off_t ps,pe,pc;
313 
314      g_assert(d != NULL);
315      /* This special if case was introduced to avoid a skipping sound
316       * caused by the cursor not matching the actual playing positon when
317       * pressing the "play" button and you're already playing. This
318       * becomes a "wobbling" sound when holding down the ',' key due
319       * to the keyboard auto-repeating. */
320      if (player_playing() && playing_document == d &&
321 	 BOOLEQ(player_looping(),loop) && start == d->cursorpos
322 	 && d->cursorpos < player_get_buffer_pos()) {
323 	  /* Keep everything intact except end */
324 	  player_get_range(&ps,&pe);
325 	  pc = player_get_buffer_pos();
326 	  player_change_range(pc,end);
327      } else {
328 
329 	  /* General case */
330 	  /* Stop the player if running, this should lead to cursor_cb
331 	   * being called and clearing playing_document */
332 	  player_stop();
333 	  g_assert(playing_document == NULL);
334 	  /* playing_document must be setup before calling player_play,
335 	   * since for very short files the cursor_cb might be called
336 	   * immediately with is_running==FALSE */
337 	  playing_document = d;
338 	  gtk_object_ref(GTK_OBJECT(d));
339 	  if (player_play(d->chunk,start,end,loop,cursor_cb)) {
340 	       playing_document = NULL;
341 	       gtk_object_unref(GTK_OBJECT(d));
342 	  }
343      }
344 
345      /* This sets the speed to the proper value */
346      player_set_speed(speed);
347 }
348 
document_play_from_cursor(Document * d,gboolean loopmode,gfloat speed)349 void document_play_from_cursor(Document *d, gboolean loopmode, gfloat speed)
350 {
351      g_assert(d != NULL);
352      document_play(d,d->cursorpos,d->chunk->length,loopmode,speed);
353 }
354 
document_play_selection(Document * d,gboolean loopmode,gfloat speed)355 void document_play_selection(Document *d, gboolean loopmode, gfloat speed)
356 {
357      g_assert(d != NULL);
358      if (d->selstart != d->selend)
359 	  document_play(d,d->selstart,d->selend,loopmode,speed);
360      else
361 	  document_play(d,0,d->chunk->length,loopmode,speed);
362 }
363 
document_stop(Document * d,gboolean do_return)364 void document_stop(Document *d, gboolean do_return)
365 {
366      off_t s,e;
367      g_assert(d != NULL);
368      if (d != playing_document) return;
369      player_get_range(&s,&e);
370      player_stop();
371      g_assert(playing_document == NULL);
372      if (do_return)
373 	  document_set_cursor(d,s);
374 }
375 
376 /* Make sure the current state is stored properly in history */
fix_history(Document * d)377 static void fix_history(Document *d)
378 {
379      struct HistoryEntry *h;
380 
381      if (d->history_pos == NULL || d->chunk != d->history_pos->chunk ||
382 	 d->selstart != d->history_pos->selstart ||
383 	 d->selend != d->history_pos->selend ||
384 	 !marklist_equal(&(d->marks),&(d->history_pos->marks))) {
385 
386 	  /* Create a new history entry and add it to the history */
387 	  h = g_malloc0(sizeof(*h));
388 	  h->chunk = d->chunk;
389 	  gtk_object_ref(GTK_OBJECT(d->chunk));
390 	  h->selstart = d->selstart;
391 	  h->selend = d->selend;
392 	  h->viewstart = d->viewstart;
393 	  h->viewend = d->viewend;
394 	  h->cursor = d->cursorpos;
395 	  copy_marklist(&(h->marks), &(d->marks));
396 
397 	  if (d->history_pos == NULL) {
398 	       h->prev = h->next = NULL;
399 	       d->history_pos = h;
400 	  } else {
401 	       h->prev = d->history_pos;
402 	       h->next = d->history_pos->next;
403 	       if (h->next != NULL) h->next->prev = h;
404 	       d->history_pos->next = h;
405 	       d->history_pos = h;
406 	  }
407      }
408 
409 }
410 
document_update(Document * d,Chunk * new_chunk,off_t movestart,off_t movedist)411 void document_update(Document *d, Chunk *new_chunk,
412 		     off_t movestart, off_t movedist)
413 {
414      int i,j;
415 
416      if (new_chunk == NULL) return;
417 
418      /* Make sure the current state is stored in history */
419      fix_history(d);
420 
421      /* Remove current redo data, if any. */
422      if (d->history_pos->next != NULL) {
423 	  d->history_pos->next->prev = NULL;
424 	  clear_history(d->history_pos->next);
425 	  d->history_pos->next = NULL;
426      }
427 
428      /* If we've converted sample rate or channel, we must stop playback */
429      if ((d->chunk->format.channels != new_chunk->format.channels ||
430 	  d->chunk->format.samplerate != new_chunk->format.samplerate) &&
431 	 playing_document == d)
432 	  player_stop();
433 
434      /* Set the new chunk */
435      gtk_object_unref(GTK_OBJECT(d->chunk));
436      d->chunk = new_chunk;
437      gtk_object_ref(GTK_OBJECT(new_chunk));
438      gtk_object_sink(GTK_OBJECT(new_chunk));
439 
440      /* Update the view. We have three cases: */
441      if ((d->viewend - d->viewstart) >= new_chunk->length) {
442 	  /* 1. The resulting chunk is smaller than the current view */
443 	  d->viewstart = 0;
444 	  d->viewend = new_chunk->length;
445      } else if (d->viewend > new_chunk->length) {
446 	  /* 2. The resulting chunk is large enough for the current view
447 	   *    but the view must be panned to the left */
448 	  d->viewstart -= (d->viewend - new_chunk->length);
449 	  d->viewend = new_chunk->length;
450      } else {
451 	  /* 3. The view still works - do nothing */
452      }
453 
454      /* Update selection endpoints */
455      if (d->selstart >= movestart) {
456 	  d->selstart += movedist;
457 	  if (d->selstart < movestart) d->selstart = movestart;
458 	  d->selend += movedist;
459 	  if (d->selend < movestart) d->selend = movestart;
460      }
461      if (d->selstart >= new_chunk->length)
462 	  d->selstart = d->selend = 0;
463      else if (d->selend >= new_chunk->length)
464 	  d->selend = new_chunk->length;
465 
466      /* Update marks */
467      for (i=d->marks.length-1; i>=0; i--) {
468 	  if (d->marks.places[i] >= movestart) {
469 
470 	       d->marks.places[i] += movedist;
471 
472 	       if (d->marks.places[i] < movestart ||
473 		   d->marks.places[i] > new_chunk->length) {
474 		    g_free(d->marks.names[i]);
475 		    for (j=i; j<d->marks.length-1; j++) {
476 			 d->marks.places[j] = d->marks.places[j+1];
477 			 d->marks.names[j] = d->marks.names[j+1];
478 		    }
479 		    d->marks.length --;
480 	       }
481 	  }
482      }
483 
484      /* If we're playing this document, update the playback range. If not,
485       * update the cursor */
486      if (playing_document == d && player_playing())
487 	  player_switch(new_chunk, movestart, movedist);
488      else {
489 	  if (d->cursorpos >= movestart) {
490 	       d->cursorpos += movedist;
491 	       if (d->cursorpos < movestart) d->cursorpos = movestart;
492 	  }
493 	  if (d->cursorpos > new_chunk->length)
494 	       d->cursorpos = new_chunk->length;
495      }
496 
497      /* Make sure the current state is stored in history */
498      fix_history(d);
499 
500      /* Emit signal */
501      gtk_signal_emit(GTK_OBJECT(d),document_signals[STATE_CHANGED_SIGNAL]);
502 }
503 
document_apply(Document * d,chunk_filter_proc proc,chunk_filter_proc eof_proc,gint amount,gboolean convert,gchar * title)504 gboolean document_apply(Document *d, chunk_filter_proc proc,
505 			chunk_filter_proc eof_proc, gint amount,
506 			gboolean convert, gchar *title)
507 {
508      Chunk *c,*p,*r;
509      off_t u,plen,rlen;
510      /* Decide if we should filter selection or the whole file */
511      if ((d->selstart == d->selend) ||
512 	 (d->selstart==0 && d->selend >= d->chunk->length)) {
513 	  /* procstart(w); */
514 	  r = chunk_filter( d->chunk, proc, eof_proc, amount,
515 			    convert, dither_editing, d->bar, title);
516 	  /* procend(w); */
517 	  if (r) {
518 	       document_update(d, r, 0, 0);
519 	       return FALSE;
520 	  } else
521 	       return TRUE;
522      } else {
523 	  u = d->selstart;
524 	  p = chunk_get_part(d->chunk, u, d->selend - u);
525 	  plen = p->length;
526 	  /* procstart(w); */
527 	  r = chunk_filter( p, proc, eof_proc, amount,
528 			    convert, dither_editing, d->bar, title);
529 	  /* procend(w); */
530 	  gtk_object_sink(GTK_OBJECT(p));
531 	  if (r) {
532 	       rlen = r->length;
533 	       c = chunk_replace_part(d->chunk,u,plen,r);
534 	       gtk_object_sink(GTK_OBJECT(r));
535 	       /* If the result is longer, report the difference as an
536 		* insertion at the old selection's right endpoint
537 		* If the result is shorter, report the difference as a
538 		* removal at the new selection's right endpoint */
539 	       if (rlen > plen)
540 		    document_update(d, c, d->selstart+plen, rlen-plen);
541 	       else
542 		    document_update(d, c, d->selstart+rlen, rlen-plen);
543 	       return FALSE;
544 	  } else
545 	       return TRUE;
546      }
547 }
548 
549 
550 
document_parse(Document * d,chunk_parse_proc proc,gboolean allchannels,gboolean convert,gchar * title)551 void document_parse(Document *d, chunk_parse_proc proc,
552 		    gboolean allchannels, gboolean convert, gchar *title)
553 {
554      Chunk *c;
555      if ((d->selstart == d->selend) ||
556 	 (d->selstart==0 && d->selend >= d->chunk->length)) {
557 	  /* procstart(w); */
558 	  chunk_parse(d->chunk,proc,allchannels,convert,dither_editing,
559 		      d->bar,title,0,FALSE);
560 	  /* procend(w); */
561      } else {
562 	  c = chunk_get_part(d->chunk,d->selstart,
563 			     d->selend - d->selstart);
564 	  /* procstart(w); */
565 	  chunk_parse(c,proc,allchannels,convert,dither_editing,
566 		      d->bar,title,0,FALSE);
567 	  /* procend(w); */
568 	  gtk_object_sink(GTK_OBJECT(c));
569      }
570 }
571 
572 
document_apply_cb(Document * d,document_apply_proc proc,gboolean selection_only,gpointer user_data)573 gboolean document_apply_cb(Document *d, document_apply_proc proc,
574 			   gboolean selection_only, gpointer user_data)
575 {
576      Chunk *c,*p,*r;
577      off_t u,plen,rlen;
578      /* Decide if we should filter selection or the whole file */
579      if ((!selection_only) ||
580 	 (d->selstart == d->selend) ||
581 	 (d->selstart==0 && d->selend >= d->chunk->length)) {
582 	  r = proc( d->chunk, d->bar, user_data );
583 	  if (r) {
584 	       document_update(d, r, 0, 0);
585 	       return FALSE;
586 	  } else
587 	       return TRUE;
588      } else {
589 	  u = d->selstart;
590 	  p = chunk_get_part(d->chunk, u, d->selend - u);
591 	  plen = p->length;
592 	  r = proc( p, d->bar, user_data );
593 	  gtk_object_sink(GTK_OBJECT(p));
594 	  if (r) {
595 	       rlen = r->length;
596 	       c = chunk_replace_part(d->chunk,u,plen,r);
597 	       gtk_object_sink(GTK_OBJECT(r));
598 	       /* If the result is longer, report the difference as an
599 		* insertion at the old selection's right endpoint
600 		* If the result is shorter, report the difference as a
601 		* removal at the new selection's right endpoint */
602 	       if (rlen > plen)
603 		    document_update(d, c, d->selstart+plen, rlen-plen);
604 	       else
605 		    document_update(d, c, d->selstart+rlen, rlen-plen);
606 	       return FALSE;
607 	  } else
608 	       return TRUE;
609      }
610 }
611 
612 
document_set_cursor_main(Document * d,off_t cursorpos,gboolean playslave,gboolean running,off_t bufpos)613 static void document_set_cursor_main(Document *d, off_t cursorpos,
614 				     gboolean playslave, gboolean running,
615 				     off_t bufpos)
616 {
617      off_t vs,ve,dist;
618 
619      g_assert(cursorpos >= 0 && cursorpos <= d->chunk->length);
620 
621      if (d->cursorpos == cursorpos) return;
622 
623      if (!playslave && playing_document == d && player_playing()) {
624 	  player_set_buffer_pos(cursorpos);
625 	  return;
626      }
627 
628 
629      if (d->followmode && playslave) {
630 	  dist = d->viewend - d->viewstart;
631 	  if (view_follow_strict_flag) {
632 	       vs = cursorpos - dist/2;
633 	       ve = vs + dist;
634 	  } else {
635 	       if (d->cursorpos < d->viewend &&
636 		   cursorpos > d->viewend && cursorpos < d->viewend + dist) {
637 		    /* Scroll one page forward */
638 		    vs = d->viewend;
639 		    ve = d->viewend + dist;
640 	       }  else if (cursorpos >= d->viewstart &&
641 			   cursorpos < d->viewend) {
642 		    /* Do nothing */
643 		    vs = d->viewstart;
644 		    ve = d->viewend;
645 	       } else {
646 		    vs = cursorpos - dist/2;
647 		    ve = vs + dist;
648 	       }
649 	  }
650 	  if (vs < 0) {
651 	       ve -= vs;
652 	       vs = 0;
653 	  } else if (ve > d->chunk->length) {
654 	       ve = d->chunk->length;
655 	       vs = ve - dist;
656 	  }
657 	  document_set_view(d,vs,ve);
658      }
659 
660      d->old_cursorpos = d->cursorpos;
661      d->cursorpos = cursorpos;
662      d->old_playbufpos = d->playbufpos;
663      d->playbufpos = running ? bufpos : -1;
664 
665      gtk_signal_emit(GTK_OBJECT(d),
666 		     document_signals[CURSOR_CHANGED_SIGNAL],
667 		     running);
668 }
669 
document_set_cursor(Document * d,off_t cursorpos)670 void document_set_cursor(Document *d, off_t cursorpos)
671 {
672      document_set_cursor_main(d,cursorpos,FALSE,(playing_document == d && player_playing()),cursorpos);
673 }
674 
document_nudge_cursor(Document * d,off_t delta)675 off_t document_nudge_cursor(Document *d, off_t delta)
676 {
677      off_t newpos;
678 
679      newpos = d->cursorpos + delta;
680      if(newpos > d->chunk->length)
681      {
682           delta = (d->chunk->length) - (d->cursorpos);
683      } else if(newpos < 0) {
684           delta = - (d->cursorpos);
685      }
686 
687      newpos = d->cursorpos + delta;
688      document_set_cursor(d,newpos);
689 
690      return delta;
691 }
692 
document_set_followmode(Document * d,gboolean mode)693 void document_set_followmode(Document *d, gboolean mode)
694 {
695      d->followmode = mode;
696 }
697 
document_set_view(Document * d,off_t viewstart,off_t viewend)698 void document_set_view(Document *d, off_t viewstart, off_t viewend)
699 {
700      off_t o;
701 
702      /* puts("document_set_view"); */
703      if (viewstart > viewend) {
704 	  o = viewstart;
705 	  viewstart = viewend;
706 	  viewend = o;
707      }
708      g_assert(viewstart >= 0 &&
709 	      (viewstart < d->chunk->length || d->chunk->length == 0) &&
710 	      viewend >= 0 && viewend <= d->chunk->length);
711      if (d->viewstart != viewstart || d->viewend != viewend) {
712 	  d->viewstart = viewstart;
713 	  d->viewend = viewend;
714 	  gtk_signal_emit(GTK_OBJECT(d),document_signals[VIEW_CHANGED_SIGNAL]);
715      }
716 }
717 
document_scroll(Document * d,off_t distance)718 void document_scroll(Document *d, off_t distance)
719 {
720      if (d->viewstart + distance < 0)
721 	  distance = -d->viewstart;
722      else if (d->viewend + distance > d->chunk->length)
723 	  distance = d->chunk->length - d->viewend;
724      document_set_view(d,d->viewstart+distance,d->viewend+distance);
725 }
726 
document_set_selection(Document * d,off_t selstart,off_t selend)727 void document_set_selection(Document *d, off_t selstart, off_t selend)
728 {
729      off_t o;
730 
731      /* Adjust the input arguments so they're properly ordered etc. */
732      if (selstart > selend) {
733 	  o = selstart;
734 	  selstart = selend;
735 	  selend = o;
736      }
737      if (selstart == selend)
738 	  selstart = selend = 0;
739 
740      if (d->chunk->length == 0 && d->selstart == 0 && d->selend == 0) return;
741 
742      g_assert(selstart >= 0 && selstart < d->chunk->length &&
743 	      selend >= 0 && selend <= d->chunk->length);
744      if (d->selstart != selstart || d->selend != selend) {
745 	  d->old_selstart = d->selstart;
746 	  d->old_selend = d->selend;
747 	  d->selstart = selstart;
748 	  d->selend = selend;
749 	  gtk_signal_emit(GTK_OBJECT(d),
750 			  document_signals[SELECTION_CHANGED_SIGNAL]);
751      }
752 }
753 
document_zoom(Document * d,gfloat zoom,gboolean followcursor)754 void document_zoom(Document *d, gfloat zoom, gboolean followcursor)
755 {
756      off_t dist,newdist;
757      off_t newstart,newend;
758      /* puts("document_zoom"); */
759      dist = d->viewend - d->viewstart;
760      newdist = (off_t) ((gfloat) dist / zoom);
761      if (newdist >= d->chunk->length) {
762 	  document_set_view(d,0,d->chunk->length);
763 	  return;
764      }
765      if (newdist < 1) newdist = 1;
766      /* printf("dist: %d, newdist: %d\n",(int)dist,(int)newdist);
767 	printf("d->cursorpos: %d, d->viewstart: %d, d->viewend: %d\n",
768 	(int)d->cursorpos,(int)d->viewstart,(int)d->viewend); */
769      if (followcursor && d->cursorpos >= d->viewstart &&
770 	 d->cursorpos <= d->viewend)
771 	  newstart = d->cursorpos - newdist/2;
772      else
773 	  newstart = d->viewstart + (dist - newdist)/2;
774      if (newstart < 0) newstart = 0;
775      newend = newstart + newdist;
776      if (newend > d->chunk->length) {
777 	  newend = d->chunk->length;
778 	  newstart = newend - newdist;
779      }
780      document_set_view(d, newstart, newend);
781 }
782 
document_set_mark(Document * d,gchar * label,off_t position)783 void document_set_mark(Document *d, gchar *label, off_t position)
784 {
785      int i,j;
786      for (i=0; i<d->marks.length; i++)
787 	  if (!strcmp(d->marks.names[i],label)) {
788 	       if (position == DOCUMENT_BAD_MARK) {
789 		    g_free(d->marks.names[i]);
790 		    for (j=i; j<d->marks.length-1; j++) {
791 			 d->marks.names[j] = d->marks.names[j+1];
792 			 d->marks.places[j] = d->marks.places[j+1];
793 		    }
794 		    d->marks.length --;
795 	       } else {
796 		    d->marks.places[i] = position;
797 	       }
798 	       gtk_signal_emit(GTK_OBJECT(d),
799 			       document_signals[STATE_CHANGED_SIGNAL]);
800 	       return;
801 	  }
802      if (d->marks.length == d->marks.alloced) {
803 	  d->marks.alloced += 16;
804 	  d->marks.names = g_realloc(d->marks.names,d->marks.alloced*
805 				     sizeof(d->marks.names[0]));
806 	  d->marks.places = g_realloc(d->marks.places,d->marks.alloced*
807 				      sizeof(d->marks.places[0]));
808      }
809      d->marks.names[d->marks.length] = g_strdup(label);
810      d->marks.places[d->marks.length] = position;
811      d->marks.length ++;
812      gtk_signal_emit(GTK_OBJECT(d),
813 		     document_signals[STATE_CHANGED_SIGNAL]);
814 }
815 
document_get_mark(Document * d,const gchar * label)816 off_t document_get_mark(Document *d, const gchar *label)
817 {
818      int i;
819      for (i=0; i<d->marks.length; i++) {
820 	  if (!strcmp(d->marks.names[i],label))
821 	       return d->marks.places[i];
822      }
823      return DOCUMENT_BAD_MARK;
824 }
825 
document_clear_marks(Document * d)826 void document_clear_marks(Document *d)
827 {
828      if (d->marks.length > 0) {
829 	  clear_marklist(&(d->marks));
830 	  gtk_signal_emit(GTK_OBJECT(d),
831 			  document_signals[STATE_CHANGED_SIGNAL]);
832      }
833 }
834 
document_foreach_mark(Document * d,void (* func)(gchar * label,off_t position,gpointer user_data),gpointer user_data)835 void document_foreach_mark(Document *d,
836 			   void (*func)(gchar *label, off_t position,
837 					gpointer user_data),
838 			   gpointer user_data)
839 {
840      int i;
841      for (i=0; i<d->marks.length; i++)
842 	  func(d->marks.names[i],d->marks.places[i],user_data);
843 }
844 
document_can_undo(Document * d)845 gboolean document_can_undo(Document *d)
846 {
847      return (d->history_pos != NULL && d->history_pos->prev != NULL);
848 }
849 
document_can_redo(Document * d)850 gboolean document_can_redo(Document *d)
851 {
852      return (d->history_pos != NULL && d->history_pos->next != NULL);
853 }
854 
get_state_from_history(Document * d)855 static void get_state_from_history(Document *d)
856 {
857      d->viewstart = d->history_pos->viewstart;
858      d->viewend = d->history_pos->viewend;
859      d->selstart = d->history_pos->selstart;
860      d->selend = d->history_pos->selend;
861      gtk_object_unref(GTK_OBJECT(d->chunk));
862      d->chunk = d->history_pos->chunk;
863      gtk_object_ref(GTK_OBJECT(d->chunk));
864      copy_marklist(&(d->marks),&(d->history_pos->marks));
865      gtk_signal_emit(GTK_OBJECT(d),document_signals[STATE_CHANGED_SIGNAL]);
866 }
867 
document_undo(Document * d)868 void document_undo(Document *d)
869 {
870      g_assert(document_can_undo(d));
871      player_stop();
872      fix_history(d);
873      d->history_pos = d->history_pos->prev;
874      get_state_from_history(d);
875 }
876 
document_redo(Document * d)877 void document_redo(Document *d)
878 {
879      g_assert(document_can_redo(d));
880      player_stop();
881      fix_history(d);
882      d->history_pos = d->history_pos->next;
883      get_state_from_history(d);
884 }
885 
document_set_status_bar(Document * d,StatusBar * bar)886 void document_set_status_bar(Document *d, StatusBar *bar)
887 {
888      d->bar = bar;
889 }
890