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