Source code: com/virtuosotechnologies/asaph/standardmodel/StdSongLine.java
1 /*
2 ================================================================================
3
4 FILE: StdSongLine.java
5
6 PROJECT:
7
8 Asaph
9
10 CONTENTS:
11
12 Standard implementation of SongLine
13
14 PROGRAMMERS:
15
16 Daniel Azuma (DA) <dazuma@kagi.com>
17
18 COPYRIGHT:
19
20 Copyright (C) 2003 Daniel Azuma (dazuma@kagi.com)
21
22 This program is free software; you can redistribute it and/or
23 modify it under the terms of the GNU General Public License as
24 published by the Free Software Foundation; either version 2
25 of the License, or (at your option) any later version.
26
27 This program is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public
33 License along with this program; if not, write to
34 Free Software Foundation, Inc.
35 59 Temple Place, Suite 330
36 Boston, MA 02111-1307 USA
37
38 ================================================================================
39 */
40
41
42 package com.virtuosotechnologies.asaph.standardmodel;
43
44
45 import java.util.List;
46 import java.util.ArrayList;
47 import java.util.Iterator;
48 import java.io.IOException;
49 import javax.swing.undo.AbstractUndoableEdit;
50 import javax.swing.undo.CannotUndoException;
51 import javax.swing.undo.CannotRedoException;
52 import javax.swing.event.UndoableEditListener;
53 import org.xml.sax.SAXException;
54 import org.xml.sax.Attributes;
55 import org.xml.sax.ErrorHandler;
56 import org.xml.sax.Locator;
57
58 import com.virtuosotechnologies.lib.xml.XMLUnparser;
59
60 import com.virtuosotechnologies.asaph.model.SongLine;
61 import com.virtuosotechnologies.asaph.model.SongLineMember;
62 import com.virtuosotechnologies.asaph.model.SongBlock;
63 import com.virtuosotechnologies.asaph.model.TextString;
64 import com.virtuosotechnologies.asaph.model.CommentString;
65 import com.virtuosotechnologies.asaph.model.ChordAnnotation;
66 import com.virtuosotechnologies.asaph.model.ChordSet;
67 import com.virtuosotechnologies.asaph.model.notation.Chord;
68 import com.virtuosotechnologies.asaph.model.notation.NotationFactory;
69
70
71 /**
72 * Standard implementation of SongLine
73 */
74 /*package*/ class StdSongLine
75 extends BaseSongMember
76 implements
77 SongLine
78 {
79 private static final String STR_xml_AnnotationNoDefaultChordSet =
80 ResourceAccess.Strings.buildString("xml_AnnotationNoDefaultChordSet");
81
82 private int indentLevel_;
83 private ListHelper memberList_;
84
85
86 /*package*/ StdSongLine(
87 BaseSongBlock parent)
88 {
89 this(parent, 0);
90 }
91
92
93 /*package*/ StdSongLine(
94 BaseSongBlock parent,
95 int indentLevel)
96 {
97 super(parent);
98 indentLevel_ = indentLevel;
99 memberList_ = new ListHelper(this);
100 }
101
102
103 /*package*/ void handleChordSetDeleted(
104 StdChordSet chordSet,
105 UndoableEditListener undoListener)
106 {
107 List toRemove = new ArrayList();
108 for (SongLineMember member = (SongLineMember)memberList_.getNextObject(null);
109 member != null; member = (SongLineMember)memberList_.getNextObject(member))
110 {
111 if (member instanceof StdChordAnnotation &&
112 ((StdChordAnnotation)member).getChordSet() == chordSet)
113 {
114 toRemove.add(member);
115 }
116 }
117 for (Iterator iter = toRemove.iterator(); iter.hasNext(); )
118 {
119 memberList_.removeObject(iter.next(), undoListener);
120 }
121 }
122
123
124 /*package*/ void unparse(
125 XMLUnparser unparser)
126 throws
127 IOException
128 {
129 unparser.startSingleLineElement(XMLConstants.LINE_ELEMENT);
130 if (indentLevel_ != 0)
131 {
132 unparser.addAttribute(XMLConstants.LINE_INDENT_ATTRIBUTE,
133 Integer.toString(indentLevel_));
134 }
135 for (Object member = memberList_.getNextObject(null);
136 member != null; member = memberList_.getNextObject(member))
137 {
138 if (member instanceof BaseStringSongLineMember)
139 {
140 ((BaseStringSongLineMember)member).unparse(unparser);
141 }
142 else if (member instanceof StdChordAnnotation)
143 {
144 ((StdChordAnnotation)member).unparse(unparser);
145 }
146 }
147 unparser.endElement(XMLConstants.LINE_ELEMENT);
148 }
149
150
151 /*package*/ class ParseHandler
152 extends ParseHandlerBase
153 {
154 private StdSong song_;
155 private StdChordSet defaultChordSet_;
156 private NotationFactory notationFactory_;
157
158 /*package*/ ParseHandler(
159 ErrorHandler errorHandler,
160 Locator locator,
161 StdSong song,
162 NotationFactory notationFactory)
163 {
164 super(errorHandler, locator, XMLConstants.LINE_ELEMENT);
165 song_ = song;
166 defaultChordSet_ = (StdChordSet)song_.getDefaultChordSet();
167 notationFactory_ = notationFactory;
168 }
169
170 /*package*/ ParseHandlerBase localStartElement(
171 String uri,
172 String localName,
173 String qName,
174 Attributes attributes)
175 throws
176 SAXException
177 {
178 if (localName.equals(XMLConstants.COMMENT_ELEMENT))
179 {
180 StdCommentString nstring = new StdCommentString(StdSongLine.this, "");
181 memberList_.appendObject(nstring, null);
182 return nstring.new ParseHandler(getErrorHandler(), getDocumentLocator());
183 }
184 else if (localName.equals(XMLConstants.TEXT_ELEMENT))
185 {
186 StdTextString nstring = new StdTextString(StdSongLine.this, "");
187 memberList_.appendObject(nstring, null);
188 return nstring.new ParseHandler(getErrorHandler(), getDocumentLocator());
189 }
190 else if (localName.equals(XMLConstants.ANNOTATION_ELEMENT))
191 {
192 String chordSetStr = attributes.getValue(XMLConstants.ANNOTATION_CHORDSET_ATTRIBUTE);
193 if (chordSetStr == null)
194 {
195 chordSetStr = attributes.getValue(XMLConstants.ANNOTATION_CHORDSET_ATTRIBUTE_ALT);
196 }
197 StdChordSet chordSet = defaultChordSet_;
198 if (chordSetStr != null)
199 {
200 chordSet = (StdChordSet)song_.getChordSetForSerializableID(chordSetStr);
201 if (chordSet == null)
202 {
203 reportFatalError(ResourceAccess.Strings.buildString(
204 "xml_AnnotationChordSetNotFound", chordSetStr));
205 }
206 }
207 if (chordSet == null)
208 {
209 reportFatalError(STR_xml_AnnotationNoDefaultChordSet);
210 }
211 String mainStr = attributes.getValue(XMLConstants.ANNOTATION_PRIMARY_ATTRIBUTE);
212 Chord main;
213 if (mainStr != null)
214 {
215 main = notationFactory_.parseChord(mainStr);
216 }
217 else
218 {
219 main = notationFactory_.getEmptyChord();
220 }
221 String preStr = attributes.getValue(XMLConstants.ANNOTATION_PRECEDING_ATTRIBUTE);
222 Chord[] pre;
223 if (preStr != null)
224 {
225 pre = notationFactory_.parseChordArray(preStr);
226 }
227 else
228 {
229 pre = new Chord[0];
230 }
231 String postStr = attributes.getValue(XMLConstants.ANNOTATION_FOLLOWING_ATTRIBUTE);
232 Chord[] post;
233 if (postStr != null)
234 {
235 post = notationFactory_.parseChordArray(postStr);
236 }
237 else
238 {
239 post = new Chord[0];
240 }
241 StdChordAnnotation annot = new StdChordAnnotation(StdSongLine.this, chordSet,
242 main, pre, post);
243 memberList_.appendObject(annot, null);
244 return annot.new ParseHandler(getErrorHandler(), getDocumentLocator());
245 }
246 else
247 {
248 return super.localStartElement(uri, localName, qName, attributes);
249 }
250 }
251
252 /*package*/ void localCharacters(
253 char[] chs,
254 int start,
255 int length)
256 throws
257 SAXException
258 {
259 Object member = memberList_.getPreviousObject(null);
260 StdTextString textStr;
261 if (member instanceof StdTextString)
262 {
263 textStr = (StdTextString)member;
264 }
265 else
266 {
267 textStr = new StdTextString(StdSongLine.this, "");
268 memberList_.appendObject(textStr, null);
269 }
270 textStr.setString(textStr.getString()+new String(chs, start, length), null);
271 }
272 }
273
274
275 //-------------------------------------------------------------------------
276 // Methods of SongLine
277 //-------------------------------------------------------------------------
278
279 /**
280 * Get the containing song block
281 *
282 * @return SongBlock containing this line
283 */
284 public SongBlock getSongBlock()
285 {
286 return (BaseSongBlock)internalGetParent();
287 }
288
289
290 /**
291 * Get the indent level
292 *
293 * @return indent level
294 */
295 public int getIndentLevel()
296 {
297 return indentLevel_;
298 }
299
300
301 /**
302 * Get the number of members
303 *
304 * @return number of members
305 */
306 public int getMemberCount()
307 {
308 return memberList_.getCount();
309 }
310
311
312 /**
313 * Get the nth member
314 *
315 * @param n index
316 * @return member
317 */
318 public SongLineMember getNthMember(
319 int n)
320 {
321 return (SongLineMember)memberList_.getNthObject(n);
322 }
323
324
325 /**
326 * Get the next member following reference.
327 * If reference is null, returns the first member.
328 * If reference is the last member, returns null;
329 *
330 * @param reference reference SongLineMember
331 * @return next member
332 * @exception IllegalArgumentException reference is not a member
333 */
334 public SongLineMember getNextMember(
335 SongLineMember reference)
336 {
337 return (SongLineMember)memberList_.getNextObject(reference);
338 }
339
340
341 /**
342 * Get the previous member preceding reference.
343 * If reference is null, returns the last member.
344 * If reference is the first member, returns null;
345 *
346 * @param reference reference SongLineMember
347 * @return previous member
348 * @exception IllegalArgumentException reference is not a member
349 */
350 public SongLineMember getPreviousMember(
351 SongLineMember reference)
352 {
353 return (SongLineMember)memberList_.getPreviousObject(reference);
354 }
355
356
357 private class ChordSetFilter
358 extends ListHelper.ObjectFilter
359 {
360 private StdChordSet set_;
361
362 /*package*/ ChordSetFilter(
363 ChordSet set)
364 {
365 if (set != null && !((StdSong)getSong()).containsChordSet(set))
366 {
367 throw new IllegalArgumentException("Unknown chord set");
368 }
369 set_ = (StdChordSet)set;
370 }
371
372 protected boolean isRelevant(
373 BaseSongMember member)
374 {
375 if (member instanceof ChordAnnotation)
376 {
377 return ((ChordAnnotation)member).isInChordSet(set_);
378 }
379 else
380 {
381 return true;
382 }
383 }
384 }
385
386
387 /**
388 * Get the number of members in the given chord set
389 *
390 * @param set ChordSet to filter by, or null for no chords
391 * @return number of members
392 */
393 public int getMemberCount(
394 ChordSet set)
395 {
396 return memberList_.getCount(new ChordSetFilter(set));
397 }
398
399
400 /**
401 * Get the nth member
402 *
403 * @param n index
404 * @param set ChordSet to filter by, or null for no chords
405 * @return member
406 */
407 public SongLineMember getNthMember(
408 int n,
409 ChordSet set)
410 {
411 return (SongLineMember)memberList_.getNthObject(n, new ChordSetFilter(set));
412 }
413
414
415 /**
416 * Get the next member following reference.
417 * If reference is null, returns the first member.
418 * If reference is the last member, returns null;
419 *
420 * @param reference reference SongLineMember
421 * @param set ChordSet to filter by, or null for no chords
422 * @return next member
423 * @exception IllegalArgumentException reference is not a member
424 */
425 public SongLineMember getNextMember(
426 SongLineMember reference,
427 ChordSet set)
428 {
429 return (SongLineMember)memberList_.getNextObject(reference, new ChordSetFilter(set));
430 }
431
432
433 /**
434 * Get the previous member preceding reference.
435 * If reference is null, returns the last member.
436 * If reference is the first member, returns null;
437 *
438 * @param reference reference SongLineMember
439 * @param set ChordSet to filter by, or null for no chords
440 * @return previous member
441 * @exception IllegalArgumentException reference is not a member
442 */
443 public SongLineMember getPreviousMember(
444 SongLineMember reference,
445 ChordSet set)
446 {
447 return (SongLineMember)memberList_.getPreviousObject(reference, new ChordSetFilter(set));
448 }
449
450
451 /**
452 * Set the indent level
453 *
454 * @param indent new indent level
455 * @param undoListener listener to notify if an undoable edit is generated,
456 * or null to suppress generation of undoable edits
457 */
458 public void setIndentLevel(
459 final int indent,
460 UndoableEditListener undoListener)
461 {
462 final int oldIndent = indentLevel_;
463 indentLevel_ = indent;
464 if (undoListener != null)
465 {
466 internalReportUndoableEdit(undoListener,
467 new AbstractUndoableEdit()
468 {
469 public void undo()
470 throws CannotUndoException
471 {
472 super.undo();
473 indentLevel_ = oldIndent;
474 }
475
476 public void redo()
477 throws CannotRedoException
478 {
479 super.redo();
480 indentLevel_ = indent;
481 }
482 });
483 }
484 }
485
486
487 /**
488 * Add a text string at the given position.
489 *
490 * @param before insert before this member, or at the end if null
491 * @param str string value
492 * @param undoListener listener to notify if an undoable edit is generated,
493 * or null to suppress generation of undoable edits
494 * @return added TextString
495 * @exception IllegalArgumentException before is not a member
496 * @exception NullPointerException str was null
497 */
498 public TextString insertTextStringBefore(
499 SongLineMember before,
500 String str,
501 UndoableEditListener undoListener)
502 {
503 StdTextString nstring = new StdTextString(this, str);
504 memberList_.insertObjectBefore(nstring, before, undoListener);
505 return nstring;
506 }
507
508
509 /**
510 * Add a text string at the given position.
511 *
512 * @param after insert after this member, or at the beginning if null
513 * @param str string value
514 * @param undoListener listener to notify if an undoable edit is generated,
515 * or null to suppress generation of undoable edits
516 * @return added TextString
517 * @exception IllegalArgumentException after is not a member
518 * @exception NullPointerException str was null
519 */
520 public TextString insertTextStringAfter(
521 SongLineMember after,
522 String str,
523 UndoableEditListener undoListener)
524 {
525 StdTextString nstring = new StdTextString(this, str);
526 memberList_.insertObjectAfter(nstring, after, undoListener);
527 return nstring;
528 }
529
530
531 /**
532 * Add a comment string at the given position.
533 *
534 * @param before insert before this member, or at the end if null
535 * @param str string value
536 * @param undoListener listener to notify if an undoable edit is generated,
537 * or null to suppress generation of undoable edits
538 * @return added CommentString
539 * @exception IllegalArgumentException before is not a member
540 * @exception NullPointerException str was null
541 */
542 public CommentString insertCommentStringBefore(
543 SongLineMember before,
544 String str,
545 UndoableEditListener undoListener)
546 {
547 StdCommentString nstring = new StdCommentString(this, str);
548 memberList_.insertObjectBefore(nstring, before, undoListener);
549 return nstring;
550 }
551
552
553 /**
554 * Add a comment string at the given position.
555 *
556 * @param after insert after this member, or at the beginning if null
557 * @param str string value
558 * @param undoListener listener to notify if an undoable edit is generated,
559 * or null to suppress generation of undoable edits
560 * @return added CommentString
561 * @exception IllegalArgumentException after is not a member
562 * @exception NullPointerException str was null
563 */
564 public CommentString insertCommentStringAfter(
565 SongLineMember after,
566 String str,
567 UndoableEditListener undoListener)
568 {
569 StdCommentString nstring = new StdCommentString(this, str);
570 memberList_.insertObjectAfter(nstring, after, undoListener);
571 return nstring;
572 }
573
574
575 /**
576 * Add a chord annotation at the given position.
577 *
578 * @param before insert before this member, or at the end if null
579 * @param cs ChordSet for the annotation. May not be null.
580 * @param primary primary chord for the annotation. May not be null.
581 * @param preceding array of preceding chords. May be null.
582 * @param following array of following chords. May be null.
583 * @param undoListener listener to notify if an undoable edit is generated,
584 * or null to suppress generation of undoable edits
585 * @return added ChordAnnotation
586 * @exception IllegalArgumentException before or cs is not a member
587 * @exception NullPointerException cs was null
588 */
589 public ChordAnnotation insertChordAnnotationBefore(
590 SongLineMember before,
591 ChordSet cs,
592 Chord primary,
593 Chord[] preceding,
594 Chord[] following,
595 UndoableEditListener undoListener)
596 {
597 if (!((StdSong)getSong()).containsChordSet(cs))
598 {
599 throw new IllegalArgumentException();
600 }
601 StdChordAnnotation ann = new StdChordAnnotation(this, (StdChordSet)cs, primary, preceding, following);
602 memberList_.insertObjectBefore(ann, before, undoListener);
603 return ann;
604 }
605
606
607 /**
608 * Add a chord annotation at the given position.
609 *
610 * @param after insert after this member, or at the beginning if null
611 * @param cs ChordSet for the annotation. May not be null.
612 * @param primary primary chord for the annotation. May not be null.
613 * @param preceding array of preceding chords. May be null.
614 * @param following array of following chords. May be null.
615 * @param undoListener listener to notify if an undoable edit is generated,
616 * or null to suppress generation of undoable edits
617 * @return added ChordAnnotation
618 * @exception IllegalArgumentException after or cs is not a member
619 * @exception NullPointerException cs was null
620 */
621 public ChordAnnotation insertChordAnnotationAfter(
622 SongLineMember after,
623 ChordSet cs,
624 Chord primary,
625 Chord[] preceding,
626 Chord[] following,
627 UndoableEditListener undoListener)
628 {
629 if (!((StdSong)getSong()).containsChordSet(cs))
630 {
631 throw new IllegalArgumentException();
632 }
633 StdChordAnnotation ann = new StdChordAnnotation(this, (StdChordSet)cs, primary, preceding, following);
634 memberList_.insertObjectAfter(ann, after, undoListener);
635 return ann;
636 }
637
638
639 /**
640 * Remove the given member.
641 *
642 * @param member member to remove
643 * @param undoListener listener to notify if an undoable edit is generated,
644 * or null to suppress generation of undoable edits
645 * @exception IllegalArgumentException member is not a member
646 * @exception NullPointerException member was null
647 */
648 public void removeMember(
649 SongLineMember member,
650 UndoableEditListener undoListener)
651 {
652 memberList_.removeObject(member, undoListener);
653 }
654 }