Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/virtuosotechnologies/asaph/standardgui/SongLineEditor.java


1   /*
2   ================================================================================
3   
4     FILE:  SongLineEditor.java
5     
6     PROJECT:
7     
8       Asaph
9     
10    CONTENTS:
11    
12      The song line editor
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.standardgui;
43  
44  
45  import java.awt.Graphics2D;
46  import java.awt.Rectangle;
47  import java.awt.Point;
48  import java.awt.Toolkit;
49  import java.awt.datatransfer.Transferable;
50  import java.awt.datatransfer.DataFlavor;
51  import java.awt.datatransfer.StringSelection;
52  import java.awt.datatransfer.UnsupportedFlavorException;
53  import java.awt.font.FontRenderContext;
54  import java.awt.geom.Rectangle2D;
55  import java.awt.geom.Line2D;
56  import java.awt.geom.GeneralPath;
57  import java.awt.event.MouseEvent;
58  import java.awt.event.KeyEvent;
59  import java.io.IOException;
60  import java.util.List;
61  import java.util.ArrayList;
62  import java.util.Map;
63  import java.util.HashMap;
64  import java.util.Iterator;
65  import java.util.logging.Logger;
66  import java.util.logging.Level;
67  
68  import com.virtuosotechnologies.lib.platform.PlatformUtils;
69  
70  import com.virtuosotechnologies.asaph.model.Song;
71  import com.virtuosotechnologies.asaph.model.Variation;
72  import com.virtuosotechnologies.asaph.model.ChordSet;
73  import com.virtuosotechnologies.asaph.model.SongBlock;
74  import com.virtuosotechnologies.asaph.model.SongLine;
75  import com.virtuosotechnologies.asaph.model.SongLineMember;
76  import com.virtuosotechnologies.asaph.model.StringSongLineMember;
77  import com.virtuosotechnologies.asaph.model.ChordAnnotation;
78  import com.virtuosotechnologies.asaph.model.TextString;
79  import com.virtuosotechnologies.asaph.model.CommentString;
80  import com.virtuosotechnologies.asaph.model.notation.Chord;
81  import com.virtuosotechnologies.asaph.model.notation.NotationFactory;
82  import virtuoso.asaph.util.render.LineLayout;
83  import virtuoso.asaph.util.render.TextFragment;
84  import virtuoso.asaph.util.render.ChordFragment;
85  import virtuoso.asaph.util.render.ChordFragmentGroup;
86  import com.virtuosotechnologies.asaph.modelutils.DataTransferUtils;
87  import com.virtuosotechnologies.asaph.modelutils.SongUtils;
88  import com.virtuosotechnologies.asaph.modelutils.CannotPasteException;
89  
90  
91  /**
92   * The song line editor
93   */
94  /*package*/ class SongLineEditor
95  {
96    private static final char SMARTQUOTE_OPENSINGLE = (char)Integer.parseInt(
97      ResourceAccess.Strings.buildString("SmartQuote_OpenSingle_Hex"), 16);
98    private static final char SMARTQUOTE_CLOSESINGLE = (char)Integer.parseInt(
99      ResourceAccess.Strings.buildString("SmartQuote_CloseSingle_Hex"), 16);
100   private static final char SMARTQUOTE_OPENDOUBLE = (char)Integer.parseInt(
101     ResourceAccess.Strings.buildString("SmartQuote_OpenDouble_Hex"), 16);
102   private static final char SMARTQUOTE_CLOSEDOUBLE = (char)Integer.parseInt(
103     ResourceAccess.Strings.buildString("SmartQuote_CloseDouble_Hex"), 16);
104   
105   
106   private SongBlock block_;
107   private SongLine line_;
108   private SongBodyEditorPane pane_;
109   private SongBlockEditor blockEditor_;
110   
111   private BodyEditorSize curSize_;
112   private List lineViews_;
113   private List chordGroupViews_;
114   
115   private float chordsTop_;
116   private float chordsBottom_;
117   private float textTop_;
118   private float textBottom_;
119   
120   private int draggingChordNum_;
121   private int chordDragIndex_;
122   private int chordDragOffset_;
123   
124   
125   /**
126    * Constructor
127    */
128   /*package*/ SongLineEditor(
129     SongBodyEditorPane pane,
130     SongBlockEditor blockEditor,
131     SongBlock block,
132     SongLine line)
133   {
134     pane_ = pane;
135     blockEditor_ = blockEditor;
136     block_ = block;
137     line_ = line;
138     lineViews_ = new ArrayList();
139     chordGroupViews_ = new ArrayList();
140     draggingChordNum_ = 0;
141     chordDragIndex_ = -1;
142     chordDragOffset_ = -1;
143   }
144   
145   
146   /**
147    * Get the size of the editor
148    */
149   /*package*/ BodyEditorSize getSize()
150   {
151     return curSize_;
152   }
153   
154   
155   /**
156    * Rebuild according to the current model and settings
157    */
158   /*package*/ void rebuild(
159     boolean refresh)
160   {
161     lineViews_.clear();
162     chordGroupViews_.clear();
163     
164     FontRenderContext frc = pane_.getFontRenderContext();
165     LineLayout layout = new EditorLineLayout(line_, pane_.getCurChordSet(),
166       block_.getIndentLevel(), frc);
167     chordsTop_ = layout.getLineTop();
168     chordsBottom_ = layout.getChordBottom()+2;
169     
170     float handleLeft = (line_.getSongBlock().getIndentLevel()+line_.getIndentLevel())*EditorConstants.INDENT_WIDTH+
171       (-EditorConstants.LEFT_LINE_HANDLE_WIDTH-EditorConstants.LINE_HANDLE_SPACING);
172     curSize_ = new BodyEditorSize(
173       Math.min(handleLeft, layout.getLineLeft()-EditorConstants.LINE_HANDLE_SPACING),
174       EditorConstants.RIGHT_LINE_HANDLE_WIDTH+EditorConstants.LINE_HANDLE_SPACING+layout.getLineRight(),
175       layout.getLineBottom()+EditorConstants.LINE_BOTTOM_MARGIN);
176     
177     // Create views of chord groups
178     Map annotationToGroupView = new HashMap();
179     int numChordGroups = layout.getChordFragmentGroupCount();
180     for (int i=0; i<numChordGroups; ++i)
181     {
182       ChordFragmentGroup group = layout.getNthChordFragmentGroup(i);
183       ChordAnnotation ann = group.getChordAnnotation();
184       ChordGroupView groupView = new ChordGroupView(ann, group.getPosition(), group.getWidth(),
185         chordsTop_, chordsBottom_);
186       chordGroupViews_.add(groupView);
187       annotationToGroupView.put(ann, groupView);
188       
189       // Views of chord fragments
190       ChordFragmentView lastFragmentView = null;
191       int numChordFragments = group.getChordFragmentCount();
192       for (int j=0; j<numChordFragments; ++j)
193       {
194         ChordFragment frag = group.getNthChordFragment(j);
195         ChordFragmentView view = new ChordFragmentView(ann, j-ann.getPrecedingChords().length,
196           frag.getString(), frag.getPosition(), frag.getWidth(), layout.getChordBaseline(), frc);
197         groupView.addChordFragment(view);
198         if (lastFragmentView != null)
199         {
200           float div = (lastFragmentView.getXPos()+lastFragmentView.getWidth()+view.getXPos())/2;
201           lastFragmentView.setRightLimit(div);
202           view.setLeftLimit(div);
203         }
204         lastFragmentView = view;
205       }
206     }
207     for (int i=1; i<numChordGroups; ++i)
208     {
209       ChordGroupView group1 = (ChordGroupView)chordGroupViews_.get(i-1);
210       ChordGroupView group2 = (ChordGroupView)chordGroupViews_.get(i);
211       float limit = (group1.getRightLimit()+group2.getLeftLimit())*0.5f;
212       group1.setRightLimit(limit);
213       group2.setLeftLimit(limit);
214     }
215     if (numChordGroups > 0)
216     {
217       ((ChordGroupView)chordGroupViews_.get(0)).setLeftLimit(Float.NEGATIVE_INFINITY);
218       ((ChordGroupView)chordGroupViews_.get(numChordGroups-1)).setRightLimit(Float.POSITIVE_INFINITY);
219     }
220     
221     // Create views of text fragments
222     textTop_ = Float.POSITIVE_INFINITY;
223     textBottom_ = Float.NEGATIVE_INFINITY;
224     LineTextView lastTextView = null;
225     int numTextFragments = layout.getTextFragmentCount();
226     for (int i=0; i<numTextFragments; ++i)
227     {
228       TextFragment frag = layout.getNthTextFragment(i);
229       TextFragment.Type type = frag.getType();
230       LineMemberView view = null;
231       if (type == TextFragment.TEXT_TYPE)
232       {
233         view = new LineTextView((TextString)frag.getSongLineMember(), frag.getString(),
234           frag.getPosition(), frag.getWidth(), layout.getTextBaseline(), frc);
235       }
236       else if (type == TextFragment.COMMENT_TYPE)
237       {
238         view = new LineTextView((CommentString)frag.getSongLineMember(), frag.getString(),
239           frag.getPosition(), frag.getWidth(), layout.getTextBaseline(), frc);
240       }
241       else if (type == TextFragment.CHORD_PLACEHOLDER_TYPE)
242       {
243         ChordAnnotation ann = (ChordAnnotation)frag.getSongLineMember();
244         ChordGroupView groupView = (ChordGroupView)annotationToGroupView.get(ann);
245         view = new LineChordPlaceholderView(ann, frag.getPosition(), frag.getWidth(),
246           layout.getChordBottom()+1, layout.getLineBottom(), groupView);
247         groupView.setPlaceholderView((LineChordPlaceholderView)view);
248       }
249       if (view != null)
250       {
251         if (view instanceof LineTextView)
252         {
253           LineTextView textView = (LineTextView)view;
254           textTop_ = Math.min(textTop_, textView.getTop());
255           textBottom_ = Math.max(textBottom_, textView.getTop()+textView.getHeight());
256           if (lastTextView != null)
257           {
258             float div = (lastTextView.getXPos()+lastTextView.getWidth()+textView.getXPos())/2;
259             lastTextView.setRightLimit(div);
260             textView.setLeftLimit(div);
261           }
262           lastTextView = textView;
263         }
264         lineViews_.add(view);
265       }
266     }
267     
268     blockEditor_.setLineSize(this, curSize_, refresh);
269   }
270   
271   
272   /**
273    * Draw
274    */
275   /*package*/ void paint(
276     Graphics2D g2d,
277     float x,
278     float y,
279     Rectangle clipBounds,
280     boolean select,
281     boolean containsSelect)
282   {
283     EditorSelection selection = pane_.getSelection();
284     boolean focused = pane_.isFocused();
285     
286     // Draw structure
287     float handleLeft = (line_.getSongBlock().getIndentLevel()+line_.getIndentLevel())*EditorConstants.INDENT_WIDTH+
288       (-EditorConstants.LEFT_LINE_HANDLE_WIDTH-EditorConstants.LINE_HANDLE_SPACING);
289     g2d.setColor(EditorConstants.BACKGROUND_COLOR);
290     g2d.fill(new Rectangle2D.Float(x+handleLeft, y,
291       curSize_.getRight()-handleLeft, curSize_.getBottom()));
292     
293     if (select && focused)
294     {
295       g2d.setColor(EditorConstants.SELECTED_LINE_HANDLE_COLOR);
296     }
297     else
298     {
299       g2d.setColor(EditorConstants.LINE_HANDLE_COLOR);
300     }
301     g2d.fill(new Rectangle2D.Float(
302       x+handleLeft, y,
303       EditorConstants.LEFT_LINE_HANDLE_WIDTH, curSize_.getBottom()));
304     g2d.fill(new Rectangle2D.Float(
305       x+curSize_.getRight()-EditorConstants.RIGHT_LINE_HANDLE_WIDTH,
306       y, EditorConstants.RIGHT_LINE_HANDLE_WIDTH, curSize_.getBottom()));
307     g2d.fill(new Rectangle2D.Float(
308       x+handleLeft, y,
309       curSize_.getRight()-handleLeft, EditorConstants.LINE_BORDER_WIDTH));
310     g2d.fill(new Rectangle2D.Float(
311       x+handleLeft, y+curSize_.getBottom()-EditorConstants.LINE_BORDER_WIDTH,
312       curSize_.getRight()-handleLeft, EditorConstants.LINE_BORDER_WIDTH));
313     
314     // Draw backgrounds behind chord groups
315     for (Iterator iter = chordGroupViews_.iterator(); iter.hasNext(); )
316     {
317       ChordGroupView groupView = (ChordGroupView)iter.next();
318       groupView.paintBackground(g2d, x, y);
319     }
320     
321     // Draw hilite
322     if (containsSelect && focused)
323     {
324       Line2D leftSeg;
325       Line2D rightSeg;
326       if (selection.isChordSelection())
327       {
328         ChordGroupView groupView = (ChordGroupView)chordGroupViews_.get(selection.getChordGroup());
329         ChordFragmentView leftView = groupView.getNthChordFragment(selection.getLeftChordView());
330         ChordFragmentView rightView = groupView.getNthChordFragment(selection.getRightChordView());
331         leftSeg = leftView.getCaretSegment(selection.getLeftChordPos(), x, y);
332         rightSeg = rightView.getCaretSegment(selection.getRightChordPos(), x, y);
333       }
334       else
335       {
336         assert selection.isTextSelection();
337         LineTextView leftView = (LineTextView)lineViews_.get(selection.getLeftTextView());
338         LineTextView rightView = (LineTextView)lineViews_.get(selection.getRightTextView());
339         leftSeg = leftView.getCaretSegment(selection.getLeftTextPos(), x, y);
340         rightSeg = rightView.getCaretSegment(selection.getRightTextPos(), x, y);
341       }
342       if (!selection.isSinglePointSelection())
343       {
344         GeneralPath path = new GeneralPath();
345         path.moveTo((float)leftSeg.getX1(), (float)leftSeg.getY1());
346         path.lineTo((float)rightSeg.getX1(), (float)rightSeg.getY1());
347         path.lineTo((float)rightSeg.getX2(), (float)rightSeg.getY2());
348         path.lineTo((float)leftSeg.getX2(), (float)leftSeg.getY2());
349         path.closePath();
350         if (draggingChordNum_ != 0)
351         {
352           g2d.setColor(EditorConstants.DRAGGING_TEXT_HILITE_COLOR);
353         }
354         else
355         {
356           g2d.setColor(EditorConstants.TEXT_HILITE_COLOR);
357         }
358         g2d.fill(path);
359       }
360     }
361     
362     // Draw line views
363     for (Iterator iter = lineViews_.iterator(); iter.hasNext(); )
364     {
365       LineMemberView view = (LineMemberView)iter.next();
366       view.paint(g2d, x, y);
367     }
368     for (Iterator iter = chordGroupViews_.iterator(); iter.hasNext(); )
369     {
370       ChordGroupView groupView = (ChordGroupView)iter.next();
371       groupView.paint(g2d, x, y);
372     }
373     
374     // Draw caret
375     if (pane_.isCaretVisible() && containsSelect && focused && draggingChordNum_ == 0)
376     {
377       Line2D endSeg;
378       if (selection.isChordSelection())
379       {
380         ChordGroupView groupView = (ChordGroupView)chordGroupViews_.get(selection.getChordGroup());
381         ChordFragmentView endView = groupView.getNthChordFragment(selection.getEndChordView());
382         endSeg = endView.getCaretSegment(selection.getEndChordPos(), x, y);
383       }
384       else
385       {
386         assert selection.isTextSelection();
387         LineTextView endView = (LineTextView)lineViews_.get(selection.getEndTextView());
388         endSeg = endView.getCaretSegment(selection.getEndTextPos(), x, y);
389       }
390       g2d.setColor(EditorConstants.TEXT_CARET_COLOR);
391       g2d.draw(endSeg);
392     }
393     
394     // Drag chord drag position
395     if (draggingChordNum_ != 0 && chordDragIndex_ != -1)
396     {
397       LineTextView dragView = (LineTextView)lineViews_.get(chordDragIndex_);
398       Line2D dragSeg = dragView.getCaretSegment(chordDragOffset_, x, y);
399       g2d.setColor(EditorConstants.CHORD_DRAG_COLOR);
400       g2d.draw(dragSeg);
401     }
402   }
403   
404   
405   /**
406    * Get the update rectangle for the current selection.
407    * Returns a Rectangle2D, or null for no selection.
408    */
409   /*package*/ Rectangle2D getSelectionBounds(
410     EditorSelection selection,
411     boolean blink,
412     float x,
413     float y)
414   {
415     if (selection.isChordSelection())
416     {
417       ChordGroupView groupView = (ChordGroupView)chordGroupViews_.get(selection.getChordGroup());
418       if (blink || selection.isSinglePointSelection())
419       {
420         ChordFragmentView view = groupView.getNthChordFragment(selection.getEndChordView());
421         Line2D seg = view.getCaretSegment(selection.getEndChordPos(), x, y);
422         return new Rectangle2D.Float(((float)seg.getX2())-1, ((float)seg.getY1())-1,
423           (float)(seg.getX1()-seg.getX2())+2, (float)(seg.getY2()-seg.getY1())+2);
424       }
425       else
426       {
427         ChordFragmentView leftView = groupView.getNthChordFragment(selection.getLeftChordView());
428         ChordFragmentView rightView = groupView.getNthChordFragment(selection.getRightChordView());
429         Line2D leftSeg = leftView.getCaretSegment(selection.getLeftChordPos(), x, y);
430         Line2D rightSeg = rightView.getCaretSegment(selection.getRightChordPos(), x, y);
431         float top = Math.min((float)leftSeg.getY1(), (float)rightSeg.getY1());
432         float bottom = Math.max((float)leftSeg.getY2(), (float)rightSeg.getY2());
433         return new Rectangle2D.Float(((float)leftSeg.getX2())-1, top-1,
434           (float)(rightSeg.getX1()-leftSeg.getX2())+2, bottom-top+2);
435       }
436     }
437     else if (selection.isTextSelection())
438     {
439       if (blink || selection.isSinglePointSelection())
440       {
441         LineTextView view = (LineTextView)lineViews_.get(selection.getEndTextView());
442         Line2D seg = view.getCaretSegment(selection.getEndTextPos(), x, y);
443         return new Rectangle2D.Float(((float)seg.getX2())-1, ((float)seg.getY1())-1,
444           (float)(seg.getX1()-seg.getX2())+2, (float)(seg.getY2()-seg.getY1())+2);
445       }
446       else
447       {
448         LineTextView leftView = (LineTextView)lineViews_.get(selection.getLeftTextView());
449         LineTextView rightView = (LineTextView)lineViews_.get(selection.getRightTextView());
450         Line2D leftSeg = leftView.getCaretSegment(selection.getLeftTextPos(), x, y);
451         Line2D rightSeg = rightView.getCaretSegment(selection.getRightTextPos(), x, y);
452         float top = Math.min((float)leftSeg.getY1(), (float)rightSeg.getY1());
453         float bottom = Math.max((float)leftSeg.getY2(), (float)rightSeg.getY2());
454         return new Rectangle2D.Float(((float)leftSeg.getX2())-1, top-1,
455           (float)(rightSeg.getX1()-leftSeg.getX2())+2, bottom-top+2);
456       }
457     }
458     return null;
459   }
460   
461   
462   /**
463    * Select default. Should be called only when the user drills in from
464    * the block level using the keyboard.
465    */
466   /*package*/ void selectDefault()
467   {
468     pane_.getSelection().setTextSelection(0, 0, 0, 0, true);
469   }
470   
471   
472   /**
473    * Select in the line, given an x in line-relative coordinates.
474    */
475   /*package*/ void selectInLineForX(
476     float x)
477   {
478     int numLineViews = lineViews_.size();
479     for (int i=0; i<numLineViews; ++i)
480     {
481       LineMemberView view = (LineMemberView)lineViews_.get(i);
482       if (view instanceof LineTextView)
483       {
484         LineTextView textView = (LineTextView)view;
485         int offset = textView.getOffsetForPosition(x, textTop_);
486         if (offset != -1)
487         {
488           pane_.getSelection().setTextSelection(i, offset, i, offset, true);
489           return;
490         }
491       }
492     }
493     assert false;
494   }
495   
496   
497   /**
498    * Select in the chords, given an x in line-relative coordinates.
499    */
500   /*package*/ boolean selectInChordsForX(
501     float x)
502   {
503     int numChordGroups = chordGroupViews_.size();
504     for (int groupi=0; groupi<numChordGroups; ++groupi)
505     {
506       ChordGroupView groupView = (ChordGroupView)chordGroupViews_.get(groupi);
507       if (groupView.isInXLimits(x))
508       {
509         int numChordViews = groupView.getFragmentCount();
510         for (int viewi=0; viewi<numChordViews; ++viewi)
511         {
512           ChordFragmentView chordView = groupView.getNthChordFragment(viewi);
513           int offset = chordView.getOffsetForPosition(x, textTop_);
514           if (offset != -1)
515           {
516             pane_.getSelection().setChordSelection(groupi, viewi, offset, viewi, offset, true);
517             return true;
518           }
519         }
520       }
521     }
522     return false;
523   }
524   
525   
526   /**
527    * Handle mouse-down events
528    */
529   /*package*/ boolean handleMousePressed(
530     MouseEvent ev,
531     float x,
532     float y,
533     int containerBlock,
534     int containerLine)
535   {
536     EditorSelection selection = pane_.getSelection();
537     float xorigin = (line_.getSongBlock().getIndentLevel()+line_.getIndentLevel())*EditorConstants.INDENT_WIDTH;
538     float handleLeft = xorigin-EditorConstants.LEFT_LINE_HANDLE_WIDTH-EditorConstants.LINE_HANDLE_SPACING;
539     
540     Point p = ev.getPoint();
541     float px = p.x-x;
542     float py = p.y-y;
543     
544     if (px > xorigin-EditorConstants.LINE_HANDLE_SPACING &&
545       px < curSize_.getRight()-EditorConstants.RIGHT_LINE_HANDLE_WIDTH &&
546       py >= textTop_ && py <= textBottom_)
547     {
548       int numLineViews = lineViews_.size();
549       
550       // Check draggable chords
551       for (int i=0; i<numLineViews; ++i)
552       {
553         LineMemberView view = (LineMemberView)lineViews_.get(i);
554         if (view instanceof LineChordPlaceholderView)
555         {
556           LineChordPlaceholderView chordView = (LineChordPlaceholderView)view;
557           if (chordView.isInSelectionArea(px))
558           {
559             draggingChordNum_ = i;
560             LineTextView prevView = (LineTextView)lineViews_.get(i-1);
561             chordDragIndex_ = -1;
562             chordDragOffset_ = -1;
563             selection.prepareSelectionInsideBlock(containerBlock);
564             selection.prepareSelectionInsideLine(containerLine);
565             selection.setTextSelection(i-1, prevView.getCharacterCount(), i+1, 0, false);
566             return true;
567           }
568         }
569       }
570       
571       // Check text fragments
572       for (int i=0; i<numLineViews; ++i)
573       {
574         LineMemberView view = (LineMemberView)lineViews_.get(i);
575         if (view instanceof LineTextView)
576         {
577           LineTextView textView = (LineTextView)view;
578           int offset = textView.getOffsetForPosition(px, py);
579           if (offset != -1)
580           {
581             if (ev.isShiftDown() && selection.isTextSelection() &&
582               selection.getContainingBlock() == containerBlock &&
583               selection.getContainingLine() == containerLine)
584             {
585               selection.extendTextSelection(i, offset, false);
586             }
587             else
588             {
589               selection.prepareSelectionInsideBlock(containerBlock);
590               selection.prepareSelectionInsideLine(containerLine);
591               if (ev.getClickCount() == 1)
592               {
593                 selection.setTextSelection(i, offset, i, offset, false);
594               }
595               else if (ev.getClickCount() == 2)
596               {
597                 int[] offsets = textView.getDoubleClickOffsetsForPosition(px, py);
598                 selection.startTextSelection(i, offsets[0], i, offsets[1], false);
599               }
600               else if (ev.getClickCount() == 3)
601               {
602                 selection.startTextSelection(i, 0, i, textView.getCharacterCount(), false);
603               }
604               else
605               {
606                 selection.startTextSelection(0, 0, numLineViews-1,
607                   ((LineTextView)lineViews_.get(numLineViews-1)).getCharacterCount(), false);
608               }
609             }
610             return true;
611           }
612         }
613       }
614     }
615     
616     // Check chord fragments
617     float leftChordPos = 0;
618     if (!chordGroupViews_.isEmpty())
619     {
620       leftChordPos = Math.min(0, ((ChordGroupView)chordGroupViews_.get(0)).getXPos());
621     }
622     if (px > xorigin+leftChordPos-EditorConstants.LINE_HANDLE_SPACING &&
623       px < curSize_.getRight()-EditorConstants.RIGHT_LINE_HANDLE_WIDTH &&
624       py >= chordsTop_ && py <= chordsBottom_)
625     {
626       int numChordGroups = chordGroupViews_.size();
627       for (int groupi=0; groupi<numChordGroups; ++groupi)
628       {
629         ChordGroupView groupView = (ChordGroupView)chordGroupViews_.get(groupi);
630         if (groupView.isInXLimits(px))
631         {
632           int numChordViews = groupView.getFragmentCount();
633           for (int viewi=0; viewi<numChordViews; ++viewi)
634           {
635             ChordFragmentView chordView = groupView.getNthChordFragment(viewi);
636             int offset = chordView.getOffsetForPosition(px, py);
637             if (offset != -1)
638             {
639               if (ev.isShiftDown() && selection.isChordSelection() &&
640                 selection.getContainingBlock() == containerBlock &&
641                 selection.getContainingLine() == containerLine &&
642                 selection.getChordGroup() == groupi)
643               {
644                 selection.extendChordSelection(viewi, offset, false);
645               }
646               else
647               {
648                 selection.prepareSelectionInsideBlock(containerBlock);
649                 selection.prepareSelectionInsideLine(containerLine);
650                 if (ev.getClickCount() == 1)
651                 {
652                   selection.setChordSelection(groupi, viewi, offset, viewi, offset, false);
653                 }
654                 else if (ev.getClickCount() == 2)
655                 {
656                   selection.startChordSelection(groupi, viewi, 0, viewi,
657                     chordView.getCharacterCount(), false);
658                 }
659                 else
660                 {
661                   selection.startChordSelection(groupi, 0, 0, numChordViews-1,
662                     groupView.getNthChordFragment(numChordViews-1).getCharacterCount(), false);
663                 }
664               }
665               return true;
666             }
667           }
668         }
669       }
670     }
671     
672     if (px < handleLeft || px > curSize_.getRight())
673     {
674       return false;
675     }
676     
677     if (ev.isShiftDown() && selection.isLineSelection() &&
678       selection.getContainingBlock() == containerBlock)
679     {
680       if (containerLine >= selection.getStartLinePos())
681       {
682         ++containerLine;
683       }
684       selection.extendLineSelection(containerLine, false);
685     }
686     else
687     {
688       selection.prepareSelectionInsideBlock(containerBlock);
689       selection.startLineSelection(containerLine, containerLine+1, false);
690     }
691     return true;
692   }
693   
694   
695   /**
696    * Handle mouse-drag events
697    */
698   /*package*/ void handleMouseDragged(
699     MouseEvent ev,
700     float x,
701     float y)
702   {
703     EditorSelection selection = pane_.getSelection();
704     
705     Point p = ev.getPoint();
706     float px = p.x-x;
707     float py = p.y-y;
708     
709     if (draggingChordNum_ != 0)
710     {
711       chordDragIndex_ = -1;
712       chordDragOffset_ = -1;
713       int numLineViews = lineViews_.size();
714       for (int i=0; i<numLineViews; ++i)
715       {
716         LineMemberView view = (LineMemberView)lineViews_.get(i);
717         if (view instanceof LineTextView)
718         {
719           LineTextView textView = (LineTextView)view;
720           int offset = textView.getOffsetForPosition(px, py);
721           if (offset != -1)
722           {
723             chordDragIndex_ = i;
724             chordDragOffset_ = offset;
725             break;
726           }
727         }
728       }
729       pane_.repaintFilledWidthSelection();
730     }
731     else if (selection.isChordSelection())
732     {
733       ChordGroupView groupView = (ChordGroupView)chordGroupViews_.get(selection.getChordGroup());
734       int numChordViews = groupView.getFragmentCount();
735       for (int i=0; i<numChordViews; ++i)
736       {
737         ChordFragmentView chordView = groupView.getNthChordFragment(i);
738         int offset = chordView.getOffsetForPosition(px, py);
739         if (offset != -1)
740         {
741           selection.extendChordSelection(i, offset, false);
742           break;
743         }
744       }
745     }
746     else if (selection.isTextSelection())
747     {
748       int numLineViews = lineViews_.size();
749       for (int i=0; i<numLineViews; ++i)
750       {
751         LineMemberView view = (LineMemberView)lineViews_.get(i);
752         if (view instanceof LineTextView)
753         {
754           LineTextView textView = (LineTextView)view;
755           int offset = textView.getOffsetForPosition(px, py);
756           if (offset != -1)
757           {
758             selection.extendTextSelection(i, offset, false);
759             break;
760           }
761         }
762       }
763     }
764   }
765   
766   
767   /**
768    * Handle mouse-up events
769    */
770   /*package*/ void handleMouseReleased(
771     MouseEvent ev,
772     float x,
773     float y)
774   {
775     EditorSelection selection = pane_.getSelection();
776     
777     Point p = ev.getPoint();
778     float px = p.x-x;
779     float py = p.y-y;
780     
781     if (draggingChordNum_ != 0)
782     {
783       if (chordDragIndex_ == -1)
784       {
785         draggingChordNum_ = 0;
786         pane_.repaintFilledWidthSelection();
787       }
788       else
789       {
790         SongUtils songUtils = pane_.getSongUtils();
791         LineChordPlaceholderView fromView = (LineChordPlaceholderView)lineViews_.get(draggingChordNum_);
792         ChordAnnotation fromChord = (ChordAnnotation)fromView.getModel();
793         LineTextView toView = (LineTextView)lineViews_.get(chordDragIndex_);
794         StringSongLineMember toModel = (StringSongLineMember)toView.getModel();
795         
796         UndoHelper helper = new LineUndoHelper();
797         ChordAnnotation nChord = songUtils.moveChordAnnotation(
798           fromChord, toModel, chordDragOffset_, null, helper);
799         
800         draggingChordNum_ = 0;
801         chordDragIndex_ = -1;
802         chordDragOffset_ = -1;
803         
804         if (nChord != fromChord)
805         {
806           ChordSet chordSet = pane_.getCurChordSet();
807           EditorSelection newSelection = selection.createCopy();
808           int index = songUtils.getMemberPosition(nChord, chordSet);
809           StringSongLineMember prevMember = (StringSongLineMember)line_.getPreviousMember(nChord, chordSet);
810           newSelection.setTextSelection(index-1, prevMember.getString().length(), index+1, 0, true);
811           helper.finish(newSelection);
812         }
813         else
814         {
815           pane_.repaintFilledWidthSelection();
816         }
817       }
818     }
819   }
820   
821   
822   /**
823    * Handle key typed events
824    */
825   /*package*/ void handleKey(
826     KeyEvent ev,
827     float x)
828   {
829     EditorSelection selection = pane_.getSelection();
830     int metaMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
831     boolean metaDown = ((ev.getModifiers() & metaMask) == metaMask);
832     
833     if (ev.getID() == KeyEvent.KEY_TYPED)
834     {
835       if (!metaDown)
836       {
837         char ch = ev.getKeyChar();
838         if (!Character.isISOControl(ch))
839         {
840           if (pane_.isEducatingQuotes() && selection.isTextSelection())
841           {
842             ch = educateQuote(selection, ch);
843           }
844           doReplaceSelection(new String(new char[]{ch}));
845           ev.consume();
846         }
847       }
848     }
849     else if (ev.getID() == KeyEvent.KEY_PRESSED)
850     {
851       if (selection.isChordSelection())
852       {
853         // Selection is in the chords
854         switch (ev.getKeyCode())
855         {
856           case KeyEvent.VK_ENTER:
857           case KeyEvent.VK_TAB:
858           {
859             ChordGroupView groupView = (ChordGroupView)chordGroupViews_.get(selection.getChordGroup());
860             LineChordPlaceholderView placeholderView = groupView.getPlaceholderView();
861             boolean foundPlaceholder = false;
862             for (int i=0; i<lineViews_.size(); ++i)
863             {
864               LineMemberView view = (LineMemberView)lineViews_.get(i);
865               if (view == placeholderView)
866               {
867                 foundPlaceholder = true;
868               }
869               else if (foundPlaceholder && (view instanceof LineTextView))
870               {
871                 selection.setTextSelection(i, 0, i, 0, true);
872                 break;
873               }
874             }
875             ev.consume();
876             break;
877           }
878           
879           case KeyEvent.VK_LEFT:
880           case KeyEvent.VK_KP_LEFT:
881           {
882             if (metaDown)
883             {
884               int line = selection.getContainingLine();
885               selection.setLineSelection(line, line+1, true); 
886             }
887             else
888             {
889               moveChordSelectionLeft(selection, ev.isShiftDown());
890             }
891             ev.consume();
892             break;
893           }
894           
895           case KeyEvent.VK_RIGHT:
896           case KeyEvent.VK_KP_RIGHT:
897           {
898             moveChordSelectionRight(selection, ev.isShiftDown());
899             ev.consume();
900             break;
901           }
902           
903           case KeyEvent.VK_UP:
904           case KeyEvent.VK_KP_UP:
905           {
906             float localx;
907             Float lastx = selection.getLastX();
908             if (lastx == null)
909             {
910               ChordGroupView groupView = (ChordGroupView)chordGroupViews_.get(selection.getChordGroup());
911               ChordFragmentView chordView = groupView.getNthChordFragment(selection.getEndChordView());
912               localx = chordView.getXForOffset(selection.getEndChordPos());
913             }
914             else
915             {
916               localx = lastx.floatValue();
917             }
918             if (metaDown)
919             {
920               int line = selection.getContainingLine();
921               selection.setLineSelection(line, line, true);
922             }
923             else
924             {
925               blockEditor_.selectInPreviousLine(localx);
926               selection.setLastX(localx);
927             }
928             ev.consume();
929             break;
930           }
931           
932           case KeyEvent.VK_DOWN:
933           case KeyEvent.VK_KP_DOWN:
934           {
935             float localx;
936             Float lastx = selection.getLastX();
937             if (lastx == null)
938             {
939               ChordGroupView groupView = (ChordGroupView)chordGroupViews_.get(selection.getChordGroup());
940               ChordFragmentView chordView = groupView.getNthChordFragment(selection.getEndChordView());
941               localx = chordView.getXForOffset(selection.getEndChordPos());
942             }
943             else
944             {
945               localx = lastx.floatValue();
946             }
947             selectInLineForX(localx);
948             selection.setLastX(localx);
949             ev.consume();
950             break;
951           }
952           
953           case KeyEvent.VK_BACK_SPACE:
954           {
955             doBackspace();
956             ev.consume();
957             break;
958           }
959         }
960       }
961       else if (selection.isTextSelection())
962       {
963         // Selection is in the line text
964         switch (ev.getKeyCode())
965         {
966           case KeyEvent.VK_LEFT:
967           case KeyEvent.VK_KP_LEFT:
968           {
969             if (metaDown)
970             {
971               int line = selection.getContainingLine();
972               selection.setLineSelection(line, line+1, true); 
973             }
974             else
975             {
976               moveLineSelectionLeft(selection, ev.isShiftDown());
977             }
978             ev.consume();
979             break;
980           }
981           
982           case KeyEvent.VK_RIGHT:
983           case KeyEvent.VK_KP_RIGHT:
984           {
985             moveLineSelectionRight(selection, ev.isShiftDown());
986             ev.consume();
987             break;
988           }
989           
990           case KeyEvent.VK_UP:
991           case KeyEvent.VK_KP_UP:
992           {
993             float localx;
994             Float lastx = selection.getLastX();
995             if (lastx == null)
996             {
997               LineTextView lineView = (LineTextView)lineViews_.get(selection.getEndTextView());
998               localx = lineView.getXForOffset(selection.getEndTextPos());
999             }
1000            else
1001            {
1002              localx = lastx.floatValue();
1003            }
1004            if (metaDown)
1005            {
1006              if (selectInChordsForX(localx))
1007              {
1008                selection.setLastX(localx);
1009              }
1010              else
1011              {
1012                int line = selection.getContainingLine();
1013                selection.setLineSelection(line, line, true);
1014              }
1015            }
1016            else
1017            {
1018              blockEditor_.selectInPreviousLine(localx);
1019              selection.setLastX(localx);
1020            }
1021            ev.consume();
1022            break;
1023          }
1024          
1025          case KeyEvent.VK_DOWN:
1026          case KeyEvent.VK_KP_DOWN:
1027          {
1028            float localx;
1029            Float lastx = selection.getLastX();
1030            if (lastx == null)
1031            {
1032              LineTextView lineView = (LineTextView)lineViews_.get(selection.getEndTextView());
1033              localx = lineView.getXForOffset(selection.getEndTextPos());
1034            }
1035            else
1036            {
1037              localx = lastx.floatValue();
1038            }
1039            if (metaDown)
1040            {
1041              int line = selection.getContainingLine()+1;
1042              selection.setLineSelection(line, line, true);
1043            }
1044            else
1045            {
1046              blockEditor_.selectInNextLine(localx);
1047              selection.setLastX(localx);
1048            }
1049            ev.consume();
1050            break;
1051          }
1052          
1053          case KeyEvent.VK_BACK_SPACE:
1054          {
1055            doBackspace();
1056            ev.consume();
1057            break;
1058          }
1059          
1060          case KeyEvent.VK_ENTER:
1061          {
1062            doEnter();
1063            ev.consume();
1064            break;
1065          }
1066          
1067          case KeyEvent.VK_HOME:
1068          case KeyEvent.VK_PAGE_UP:
1069          {
1070            if (ev.isShiftDown())
1071            {
1072              selection.extendTextSelection(0, 0, true);
1073            }
1074            else
1075            {
1076              selection.setTextSelection(0, 0, 0, 0, true);
1077            }
1078            ev.consume();
1079            break;
1080          }
1081          
1082          case KeyEvent.VK_END:
1083          case KeyEvent.VK_PAGE_DOWN:
1084          {
1085            int viewNum = lineViews_.size()-1;
1086            LineTextView lineView = (LineTextView)lineViews_.get(viewNum);
1087            int pos = lineView.getCharacterCount();
1088            if (ev.isShiftDown())
1089            {
1090              selection.extendTextSelection(viewNum, pos, true);
1091            }
1092            else
1093            {
1094              selection.setTextSelection(viewNum, pos, viewNum, pos, true);
1095            }
1096            ev.consume();
1097            break;
1098          }
1099        }
1100      }
1101    }
1102  }
1103  
1104  
1105  private char educateQuote(
1106    EditorSelection selection,
1107    char ch)
1108  {
1109    if (ch == '\"' || ch == '\'')
1110    {
1111      char prev = ' ';
1112      int view = selection.getLeftTextView();
1113      int pos = selection.getLeftTextPos()-1;
1114      if (pos < 0)
1115      {
1116        --view;
1117      }
1118      while (view >= 0)
1119      {
1120        Object obj = lineViews_.get(view);
1121        if (obj instanceof LineTextView)
1122        {
1123          LineTextView ltv = (LineTextView)obj;
1124          if (pos >= 0)
1125          {
1126            prev = ltv.getText().charAt(pos);
1127            break;
1128          }
1129          String text = ltv.getText();
1130          if (text.length() > 0)
1131          {
1132            prev = text.charAt(text.length()-1);
1133            break;
1134          }
1135        }
1136        --view;
1137      }
1138      if (Character.isWhitespace(prev))
1139      {
1140        if (ch == '\'')
1141        {
1142          return SMARTQUOTE_OPENSINGLE;
1143        }
1144        else
1145        {
1146          return SMARTQUOTE_OPENDOUBLE;
1147        }
1148      }
1149      else
1150      {
1151        if (ch == '\'')
1152        {
1153          return SMARTQUOTE_CLOSESINGLE;
1154        }
1155        else
1156        {
1157          return SMARTQUOTE_CLOSEDOUBLE;
1158        }
1159      }
1160    }
1161    return ch;
1162  }
1163  
1164  
1165  /*package*/ class LineUndoHelper
1166  extends UndoHelper
1167  {
1168    private int blockNum_;
1169    private int lineNum_;
1170    
1171    /*package*/ LineUndoHelper()
1172    {
1173      super(pane_);
1174      lineNum_ = blockEditor_.getLineIndex(SongLineEditor.this);
1175      blockNum_ = pane_.getSongBodyEditor().getBlockIndex(blockEditor_);
1176    }
1177    
1178    protected void rebuildView()
1179    {
1180      pane_.getSongBodyEditor().getNthBlock(blockNum_).getNthLine(lineNum_).rebuild(true);
1181    }
1182  }
1183  
1184  
1185  /*package*/ void doCopy(
1186    boolean remove)
1187  {
1188    EditorSelection selection = pane_.getSelection();
1189    DataTransferUtils dataTransferUtils = pane_.getDataTransferUtils();
1190    
1191    if (selection.isChordSelection())
1192    {
1193      int chordGroup = selection.getChordGroup();
1194      ChordGroupView groupView = (ChordGroupView)chordGroupViews_.get(chordGroup);
1195      int leftView = selection.getLeftChordView();
1196      int leftPos = selection.getLeftChordPos();
1197      int rightView = selection.getRightChordView();
1198      int rightPos = selection.getRightChordPos();
1199      
1200      String str = groupView.getRangeAsString(leftView, leftPos, rightView, rightPos);
1201      dataTransferUtils.setClipboardContents(new StringSelection(str));
1202      if (remove)
1203      {
1204        doDeleteSelection();
1205      }
1206    }
1207    else
1208    {
1209      assert selection.isTextSelection();
1210      
1211      int leftView = selection.getLeftTextView();
1212      int leftPos = selection.getLeftTextPos();
1213      int rightView = selection.getRightTextView();
1214      int rightPos = selection.getRightTextPos();
1215      
1216      EditorSelection newSelection = selection.createCopy();
1217      newSelection.setTextSelection(leftView, leftPos, leftView, leftPos, false);
1218      
1219      StringSongLineMember leftModel = (StringSongLineMember)
1220        ((LineMemberView)lineViews_.get(leftView)).getModel();
1221      StringSongLineMember rightModel = (StringSongLineMember)
1222        ((LineMemberView)lineViews_.get(rightView)).getModel();
1223      
1224      UndoHelper helper = null;
1225      if (remove)
1226      {
1227        helper = new LineUndoHelper();
1228      }
1229      Transferable transfer = dataTransferUtils.createLineFragmentTransferable(
1230        line_, leftModel, leftPos, rightModel, rightPos,
1231        pane_.getCurChordSet(), remove, helper);
1232      dataTransferUtils.setClipboardContents(transfer);
1233      if (helper != null)
1234      {
1235        helper.finish(newSelection);
1236      }
1237    }
1238  }
1239  
1240  
1241  /*package*/ void doPaste()
1242  {
1243    EditorSelection selection = pane_.getSelection();
1244    DataTransferUtils dataTransferUtils = pane_.getDataTransferUtils();
1245    
1246    if (selection.isChordSelection())
1247    {
1248      Transferable transfer = dataTransferUtils.getClipboardContents();
1249      if (transfer.isDataFlavorSupported(DataFlavor.stringFlavor))
1250      {
1251        try
1252        {
1253          String str = (String)transfer.getTransferData(DataFlavor.stringFlavor);
1254          char[] chars = str.toCharArray();
1255          StringBuffer buf = new StringBuffer();
1256          for (int i=0; i<chars.length; )
1257          {
1258            char ch = chars[i++];
1259            if (ch >= 32 && ch != 127)
1260            {
1261              buf.append(ch);
1262            }
1263          }
1264          doReplaceSelection(new String(buf));
1265        }
1266        catch (UnsupportedFlavorException ex)
1267        {
1268          // Unexpected
1269          Logger.getLogger("com.virtuosotechnologies.asaph.standardgui").log(Level.WARNING,
1270            "Unexpected exception", ex);
1271        }
1272        catch (IOException ex)
1273        {
1274          Logger.getLogger("com.virtuosotechnologies.asaph.standardgui").log(Level.WARNING,
1275            "Unable to paste", ex);
1276        }
1277      }
1278    }
1279    else
1280    {
1281      assert selection.isTextSelection();
1282      int leftView = selection.getLeftTextView();
1283      int leftPos = selection.getLeftTextPos();
1284      int rightView = selection.getRightTextView();
1285      int rightPos = selection.getRightTextPos();
1286      
1287      EditorSelection newSelection = selection.createCopy();
1288      newSelection.setTextSelection(leftView, leftPos, leftView, leftPos, false);
1289      
1290      StringSongLineMember leftModel = (StringSongLineMember)
1291        ((LineMemberView)lineViews_.get(leftView)).getModel();
1292      StringSongLineMember rightModel = (StringSongLineMember)
1293        ((LineMemberView)lineViews_.get(rightView)).getModel();
1294      
1295      try
1296      {
1297        Transferable transfer = dataTransferUtils.getClipboardContents();
1298        DataFlavor pasteType = dataTransferUtils.getPasteType(
1299          transfer, DataTransferUtils.LINEFRAGMENT_FLAVOR);
1300        Variation var = pane_.getCurVariation();
1301        ChordSet chordSet = pane_.getCurChordSet();
1302        
1303        UndoHelper helper;
1304        int ocount;
1305        if (pasteType.equals(DataTransferUtils.BLOCKLIST_FLAVOR))
1306        {
1307          helper = blockEditor_.getBodyEditor().new BodyUndoHelper();
1308          ocount = line_.getSongBlock().getSo