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

Quick Search    Search Deep

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


1   /*
2   ================================================================================
3   
4     FILE:  SongBlockEditor.java
5     
6     PROJECT:
7     
8       Asaph
9     
10    CONTENTS:
11    
12      The song block editor: line list
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.Toolkit;
48  import java.awt.datatransfer.Transferable;
49  import java.awt.datatransfer.DataFlavor;
50  import java.awt.geom.Rectangle2D;
51  import java.awt.event.MouseEvent;
52  import java.awt.event.KeyEvent;
53  import java.util.List;
54  import java.util.ArrayList;
55  
56  import com.virtuosotechnologies.lib.platform.PlatformUtils;
57  
58  import com.virtuosotechnologies.asaph.model.Song;
59  import com.virtuosotechnologies.asaph.model.SongBlock;
60  import com.virtuosotechnologies.asaph.model.SongLine;
61  import com.virtuosotechnologies.asaph.model.AddedSongBlock;
62  import com.virtuosotechnologies.asaph.model.ChordSet;
63  import com.virtuosotechnologies.asaph.model.Variation;
64  import com.virtuosotechnologies.asaph.modelutils.DataTransferUtils;
65  import com.virtuosotechnologies.asaph.modelutils.SongUtils;
66  import com.virtuosotechnologies.asaph.modelutils.CannotPasteException;
67  
68  
69  /**
70   * The song block editor
71   */
72  /*package*/ class SongBlockEditor
73  {
74    private SongBlock block_;
75    private SongBodyEditorPane pane_;
76    private SongBodyEditor bodyEditor_;
77    
78    private List lineEditors_;
79    private List lineEditorSizes_;
80    private List lineEditorYs_;
81    
82    private BodyEditorSize curSize_;
83    
84    
85    /**
86     * Constructor
87     */
88    /*package*/ SongBlockEditor(
89      SongBodyEditorPane pane,
90      SongBodyEditor bodyEditor,
91      SongBlock block)
92    {
93      pane_ = pane;
94      bodyEditor_ = bodyEditor;
95      block_ = block;
96      lineEditors_ = new ArrayList();
97      lineEditorSizes_ = new ArrayList();
98      lineEditorYs_ = new ArrayList();
99    }
100   
101   
102   /**
103    * Get the size of the editor
104    */
105   /*package*/ BodyEditorSize getSize()
106   {
107     return curSize_;
108   }
109   
110   
111   /**
112    * Rebuild according to the current model and settings
113    */
114   /*package*/ void rebuild(
115     boolean refresh)
116   {
117     lineEditors_.clear();
118     lineEditorSizes_.clear();
119     lineEditorYs_.clear();
120     
121     curSize_ = new BodyEditorSize(
122       -EditorConstants.LEFT_BLOCK_HANDLE_WIDTH-EditorConstants.BLOCK_HANDLE_LEFT_FULLSPACING+
123         block_.getIndentLevel()*EditorConstants.INDENT_WIDTH,
124       EditorConstants.RIGHT_BLOCK_HANDLE_WIDTH+EditorConstants.BLOCK_HANDLE_SPACING+
125         EditorConstants.EMPTY_WIDTH+block_.getIndentLevel()*EditorConstants.INDENT_WIDTH,
126       EditorConstants.LINE_SPACING);
127     
128     for (SongLine line = block_.getNextLine(null); line != null;
129       line = block_.getNextLine(line))
130     {
131       SongLineEditor lineEditor = new SongLineEditor(pane_, this, block_, line);
132       lineEditor.rebuild(false);
133       lineEditors_.add(lineEditor);
134       lineEditorYs_.add(new Float(curSize_.getBottom()));
135       BodyEditorSize lineSize = lineEditor.getSize();
136       lineEditorSizes_.add(lineSize);
137       curSize_ = curSize_.addSize(
138         lineSize.getLeft(),
139         lineSize.getRight()+EditorConstants.RIGHT_BLOCK_HANDLE_WIDTH+
140           EditorConstants.BLOCK_HANDLE_SPACING,
141         lineSize.getBottom()+EditorConstants.LINE_SPACING);
142     }
143     
144     bodyEditor_.setBlockSize(this, curSize_, refresh);
145   }
146   
147   
148   /*package*/ SongBodyEditor getBodyEditor()
149   {
150     return bodyEditor_;
151   }
152   
153   
154   /**
155    * Get the index of this line
156    */
157   /*package*/ int getLineIndex(
158     SongLineEditor lineEditor)
159   {
160     return lineEditors_.indexOf(lineEditor);
161   }
162   
163   
164   /**
165    * Get the nth line
166    */
167   /*package*/ SongLineEditor getNthLine(
168     int n)
169   {
170     return (SongLineEditor)lineEditors_.get(n);
171   }
172   
173   
174   /**
175    * Resize for a block
176    */
177   /*package*/ void setLineSize(
178     SongLineEditor lineEditor,
179     BodyEditorSize size,
180     boolean refresh)
181   {
182     int index = lineEditors_.indexOf(lineEditor);
183     if (index == -1)
184     {
185       return;
186     }
187     BodyEditorSize oldSize = (BodyEditorSize)lineEditorSizes_.get(index);
188     lineEditorSizes_.set(index, size);
189     float nLeft = curSize_.getLeft();
190     float nRight = curSize_.getRight();
191     float nBottom = curSize_.getBottom();
192     boolean changed = false;
193     if (oldSize.getBottom() != size.getBottom())
194     {
195       float deltaHeight = size.getBottom() - oldSize.getBottom();
196       for (int i=index+1; i<lineEditors_.size(); ++i)
197       {
198         float y = ((Float)lineEditorYs_.get(i)).floatValue();
199         lineEditorYs_.set(i, new Float(y+deltaHeight));
200       }
201       nBottom += deltaHeight;
202       changed = true;
203     }
204     if (oldSize.getLeft() != size.getLeft())
205     {
206       if (size.getLeft() < oldSize.getLeft())
207       {
208         nLeft = Math.min(nLeft, size.getLeft());
209       }
210       else
211       {
212         nLeft = -EditorConstants.LEFT_BLOCK_HANDLE_WIDTH-EditorConstants.BLOCK_HANDLE_LEFT_FULLSPACING+
213           block_.getIndentLevel()*EditorConstants.INDENT_WIDTH;
214         for (int i=0; i<lineEditors_.size(); ++i)
215         {
216           BodyEditorSize s = (BodyEditorSize)lineEditorSizes_.get(i);
217           nLeft = Math.min(nLeft, s.getLeft());
218         }
219       }
220       if (nLeft != curSize_.getLeft())
221       {
222         changed = true;
223       }
224     }
225     if (oldSize.getRight() != size.getRight())
226     {
227       if (size.getRight() > oldSize.getRight())
228       {
229         nRight = Math.max(nRight, EditorConstants.RIGHT_BLOCK_HANDLE_WIDTH+
230           EditorConstants.BLOCK_HANDLE_SPACING+size.getRight());
231       }
232       else
233       {
234         nRight = EditorConstants.RIGHT_BLOCK_HANDLE_WIDTH+EditorConstants.BLOCK_HANDLE_SPACING+
235           EditorConstants.EMPTY_WIDTH;
236         for (int i=0; i<lineEditors_.size(); ++i)
237         {
238           BodyEditorSize s = (BodyEditorSize)lineEditorSizes_.get(i);
239           nRight = Math.max(nRight, EditorConstants.RIGHT_BLOCK_HANDLE_WIDTH+
240             EditorConstants.BLOCK_HANDLE_SPACING+s.getRight());
241         }
242       }
243       if (nRight != curSize_.getRight())
244       {
245         changed = true;
246       }
247     }
248     
249     if (changed)
250     {
251       curSize_ = new BodyEditorSize(nLeft, nRight, nBottom);
252       bodyEditor_.setBlockSize(this, curSize_, refresh);
253     }
254     else if (refresh)
255     {
256       EditorSelection toRefresh = new EditorSelection(null);
257       toRefresh.prepareSelectionInsideBlock(bodyEditor_.getBlockIndex(this));
258       toRefresh.setLineSelection(index, index+1, false);
259       pane_.repaintGivenSelection(toRefresh);
260     }
261   }
262   
263   
264   /**
265    * Draw
266    */
267   /*package*/ void paint(
268     Graphics2D g2d,
269     float x,
270     float y,
271     Rectangle clipBounds,
272     boolean select,
273     boolean containsSelect)
274   {
275     EditorSelection selection = pane_.getSelection();
276     boolean focused = pane_.isFocused();
277     
278     // Draw structure
279     float left = -EditorConstants.LEFT_BLOCK_HANDLE_WIDTH-EditorConstants.BLOCK_HANDLE_LEFT_FULLSPACING+
280       block_.getIndentLevel()*EditorConstants.INDENT_WIDTH;
281     //g2d.setColor(EditorConstants.BACKGROUND_COLOR);
282     //g2d.fill(new Rectangle2D.Float(x+left, y, curSize_.getRight()-left, curSize_.getBottom()));
283     
284     if (select && focused)
285     {
286       g2d.setColor(EditorConstants.SELECTED_BLOCK_HANDLE_COLOR);
287     }
288     else if (block_ instanceof AddedSongBlock)
289     {
290       g2d.setColor(EditorConstants.ADDED_BLOCK_HANDLE_COLOR);
291     }
292     else
293     {
294       g2d.setColor(EditorConstants.BLOCK_HANDLE_COLOR);
295     }
296     g2d.fill(new Rectangle2D.Float(
297       x+left, y, EditorConstants.LEFT_BLOCK_HANDLE_WIDTH, curSize_.getBottom()));
298     g2d.fill(new Rectangle2D.Float(
299       x+curSize_.getRight()-EditorConstants.RIGHT_BLOCK_HANDLE_WIDTH,
300       y, EditorConstants.RIGHT_BLOCK_HANDLE_WIDTH, curSize_.getBottom()));
301     g2d.fill(new Rectangle2D.Float(
302       x+left, y, curSize_.getRight()-left, EditorConstants.BLOCK_BORDER_WIDTH));
303     g2d.fill(new Rectangle2D.Float(
304       x+left, y+curSize_.getBottom()-EditorConstants.BLOCK_BORDER_WIDTH,
305       curSize_.getRight()-left, EditorConstants.BLOCK_BORDER_WIDTH));
306     
307     // Draw lines
308     int num = lineEditors_.size();
309     int selectionTop = -1;
310     int selectionBottom = -1;
311     int containingLine = -1;
312     if (containsSelect)
313     {
314       selectionTop = selection.getTopLinePos();
315       selectionBottom = selection.getBottomLinePos();
316       containingLine = selection.getContainingLine();
317     }
318     for (int i=0; i<num; ++i)
319     {
320       SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(i);
321       float y2 = y+((Float)lineEditorYs_.get(i)).floatValue();
322       float bottom = y2+((BodyEditorSize)lineEditorSizes_.get(i)).getBottom();
323       if (y2 < clipBounds.y+clipBounds.height && bottom > clipBounds.y)
324       {
325         lineEditor.paint(g2d, x, y2, clipBounds,
326           selectionTop <= i && selectionBottom > i, containingLine == i);
327       }
328     }
329     
330     // Draw caret
331     if (containsSelect && focused)
332     {
333       int sel = selection.getEndLinePos();
334       if (sel != -1 && pane_.isCaretVisible())
335       {
336         float y2 = y;
337         if (sel == 0)
338         {
339           y += EditorConstants.LINE_SPACING*0.5f;
340         }
341         else if (sel == lineEditorYs_.size())
342         {
343           y += ((Float)lineEditorYs_.get(sel-1)).floatValue()+
344             ((BodyEditorSize)lineEditorSizes_.get(sel-1)).getBottom()+
345             EditorConstants.LINE_SPACING*0.5f;
346         }
347         else
348         {
349           y += ((Float)lineEditorYs_.get(sel)).floatValue()-
350             EditorConstants.LINE_SPACING*0.5f;
351         }
352         g2d.setColor(EditorConstants.LINE_CARET_COLOR);
353         float lft = x-EditorConstants.BLOCK_HANDLE_LEFT_FULLSPACING+
354           block_.getIndentLevel()*EditorConstants.INDENT_WIDTH;
355         float rght = x+curSize_.getRight()-EditorConstants.RIGHT_BLOCK_HANDLE_WIDTH;
356         g2d.draw(new Rectangle2D.Float(lft, y-0.5f, rght-lft, 1f));
357       }
358     }
359   }
360   
361   
362   /**
363    * Get the update rectangle for the current selection.
364    * Returns a Rectangle2D, or null for no selection.
365    */
366   /*package*/ Rectangle2D getSelectionBounds(
367     EditorSelection selection,
368     boolean blink,
369     float x,
370     float y)
371   {
372     if (selection.isLineSelection())
373     {
374       int selectionEnd = selection.getEndLinePos();
375       if (blink || selection.isSinglePointSelection())
376       {
377         float y2;
378         if (selectionEnd == 0)
379         {
380           y2 = 0f;
381         }
382         else if (selectionEnd == lineEditorYs_.size())
383         {
384           y2 = ((Float)lineEditorYs_.get(selectionEnd-1)).floatValue()+
385             ((BodyEditorSize)lineEditorSizes_.get(selectionEnd-1)).getBottom();
386         }
387         else
388         {
389           y2 = ((Float)lineEditorYs_.get(selectionEnd)).floatValue()-
390             EditorConstants.LINE_SPACING;
391         }
392         return new Rectangle2D.Float(x+curSize_.getLeft(), y+y2,
393           curSize_.getRight()-curSize_.getLeft(), EditorConstants.LINE_SPACING);
394       }
395       else
396       {
397         int selectionTop = selection.getTopLinePos();
398         int selectionBottom = selection.getBottomLinePos();
399         float top = ((Float)lineEditorYs_.get(selectionTop)).floatValue();
400         float bottom = ((Float)lineEditorYs_.get(selectionBottom-1)).floatValue()+
401           ((BodyEditorSize)lineEditorSizes_.get(selectionBottom-1)).getBottom();
402         if (selectionTop == selectionEnd)
403         {
404           top -= EditorConstants.LINE_SPACING;
405         }
406         else if (selectionBottom == selectionEnd)
407         {
408           bottom += EditorConstants.LINE_SPACING;
409         }
410         return new Rectangle2D.Float(x+curSize_.getLeft(), y+top,
411           curSize_.getRight()-curSize_.getLeft(), bottom-top);
412       }
413     }
414     else
415     {
416       int selectionContainer = selection.getContainingLine();
417       float y2 = y+((Float)lineEditorYs_.get(selectionContainer)).floatValue();
418       return ((SongLineEditor)lineEditors_.get(selectionContainer)).
419         getSelectionBounds(selection, blink, x, y2);
420     }
421   }
422   
423   
424   /**
425    * Returns true if this block contains at least one line
426    */
427   /*package*/ boolean hasAtLeastOneLine()
428   {
429     return !lineEditors_.isEmpty();
430   }
431   
432   
433   /**
434    * Select default. Should be called only when the user drills in from
435    * the body level using the keyboard.
436    */
437   /*package*/ void selectDefault()
438   {
439     pane_.getSelection().setLineSelection(0, 0, true);
440   }
441   
442   
443   /**
444    * Select the top of the block. Should be called from the body only when the
445    * user navigates from the previous block.
446    */
447   /*package*/ void selectTop()
448   {
449     pane_.getSelection().setLineSelection(0, 0, true);
450   }
451   
452   
453   /**
454    * Select the bottom of the block. Should be called from the body only when the
455    * user navigates from the next block.
456    */
457   /*package*/ void selectBottom()
458   {
459     int sel = lineEditors_.size();
460     pane_.getSelection().setLineSelection(sel, sel, true);
461   }
462   
463   
464   /**
465    * Move the caret to the previous line.
466    * We expect this to be called from the current line because the user is
467    * navigating up using the keyboard.
468    */
469   /*package*/ void selectInPreviousLine(
470     float x)
471   {
472     EditorSelection selection = pane_.getSelection();
473     int line = selection.getContainingLine()-1;
474     if (line < 0)
475     {
476       bodyEditor_.selectInPreviousLine(x);
477     }
478     else
479     {
480       selection.prepareSelectionInsideLine(line);
481       ((SongLineEditor)lineEditors_.get(line)).selectInLineForX(x);
482     }
483   }
484   
485   
486   /**
487    * Move the caret to the next line.
488    * We expect this to be called from the current line because the user is
489    * navigating down using the keyboard.
490    */
491   /*package*/ void selectInNextLine(
492     float x)
493   {
494     EditorSelection selection = pane_.getSelection();
495     int line = selection.getContainingLine()+1;
496     if (line == lineEditors_.size())
497     {
498       bodyEditor_.selectInNextLine(x);
499     }
500     else
501     {
502       selection.prepareSelectionInsideLine(line);
503       ((SongLineEditor)lineEditors_.get(line)).selectInLineForX(x);
504     }
505   }
506   
507   
508   /**
509    * Move the caret to the top line.
510    * We expect this to be called from the body, after the selection has been cleared.
511    */
512   /*package*/ void selectInTopLine(
513     float x)
514   {
515     pane_.getSelection().prepareSelectionInsideLine(0);
516     ((SongLineEditor)lineEditors_.get(0)).selectInLineForX(x);
517   }
518   
519   
520   /**
521    * Move the caret to the bottom line.
522    * We expect this to be called from the body, after the selection has been cleared.
523    */
524   /*package*/ void selectInBottomLine(
525     float x)
526   {
527     int line = lineEditors_.size()-1;
528     pane_.getSelection().prepareSelectionInsideLine(line);
529     ((SongLineEditor)lineEditors_.get(line)).selectInLineForX(x);
530   }
531   
532   
533   /**
534    * Handle mouse-down events
535    */
536   /*package*/ boolean handleMousePressed(
537     MouseEvent ev,
538     float x,
539     float y,
540     int containerBlock)
541   {
542     EditorSelection selection = pane_.getSelection();
543     
544     float px = ev.getPoint().x-x;
545     float left = -EditorConstants.LEFT_BLOCK_HANDLE_WIDTH-EditorConstants.BLOCK_HANDLE_LEFT_FULLSPACING+
546       block_.getIndentLevel()*EditorConstants.INDENT_WIDTH;
547     float right = curSize_.getRight();
548     
549     int lineIndex = findLineForPoint(ev.getPoint().y-y, false);
550     if (lineIndex < 0)
551     {
552       int container = -1-lineIndex;
553       SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(container);
554       float y2 = y+((Float)lineEditorYs_.get(container)).floatValue();
555       if (lineEditor.handleMousePressed(ev, x, y2, containerBlock, container))
556       {
557         return true;
558       }
559       if ((px > left+EditorConstants.LEFT_BLOCK_HANDLE_WIDTH) &&
560         (px < right-EditorConstants.RIGHT_BLOCK_HANDLE_WIDTH))
561       {
562         float height = ((BodyEditorSize)lineEditorSizes_.get(container)).getBottom();
563         if (ev.getPoint().y > y2+height/2)
564         {
565           ++container;
566         }
567         lineIndex = container;
568       }
569     }
570     if ((px > left+EditorConstants.LEFT_BLOCK_HANDLE_WIDTH) &&
571       (px < right-EditorConstants.RIGHT_BLOCK_HANDLE_WIDTH))
572     {
573       if (ev.isShiftDown() && selection.isLineSelection() &&
574         selection.getContainingBlock() == containerBlock)
575       {
576         selection.extendLineSelection(lineIndex, false);
577       }
578       else
579       {
580         selection.prepareSelectionInsideBlock(containerBlock);
581         selection.setLineSelection(lineIndex, lineIndex, false);
582       }
583       return true;
584     }
585     
586     if ((px >= left && px <= left+EditorConstants.LEFT_BLOCK_HANDLE_WIDTH) ||
587       (px <= right && px >= right-EditorConstants.RIGHT_BLOCK_HANDLE_WIDTH))
588     {
589       if (ev.isShiftDown() && selection.isBlockSelection())
590       {
591         if (containerBlock >= selection.getStartBlockPos())
592         {
593           ++containerBlock;
594         }
595         selection.extendBlockSelection(containerBlock, false);
596       }
597       else
598       {
599         selection.startBlockSelection(containerBlock, containerBlock+1, false);
600       }
601       return true;
602     }
603     
604     return false;
605   }
606   
607   
608   /**
609    * Handle mouse-drag events
610    */
611   /*package*/ void handleMouseDragged(
612     MouseEvent ev,
613     float x,
614     float y)
615   {
616     EditorSelection selection = pane_.getSelection();
617     
618     if (selection.isLineSelection())
619     {
620       int lineIndex = findLineForPoint(ev.getPoint().y-y, true);
621       selection.extendLineSelection(lineIndex, false);
622     }
623     else
624     {
625       int container = selection.getContainingLine();
626       SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(container);
627       float y2 = y+((Float)lineEditorYs_.get(container)).floatValue();
628       lineEditor.handleMouseDragged(ev, x, y2);
629     }
630   }
631   
632   
633   /**
634    * Handle mouse-up events
635    */
636   /*package*/ void handleMouseReleased(
637     MouseEvent ev,
638     float x,
639     float y)
640   {
641     EditorSelection selection = pane_.getSelection();
642     
643     if (!selection.isLineSelection())
644     {
645       int container = selection.getContainingLine();
646       SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(container);
647       float y2 = y+((Float)lineEditorYs_.get(container)).floatValue();
648       lineEditor.handleMouseReleased(ev, x, y2);
649     }
650   }
651   
652   
653   /**
654    * Handle key typed events
655    */
656   /*package*/ void handleKey(
657     KeyEvent ev,
658     float x)
659   {
660     EditorSelection selection = pane_.getSelection();
661     int metaMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
662     boolean metaDown = ((ev.getModifiers() & metaMask) == metaMask);
663     
664     if (selection.isLineSelection())
665     {
666       if (ev.getID() == KeyEvent.KEY_PRESSED)
667       {
668         switch (ev.getKeyCode())
669         {
670           case KeyEvent.VK_UP:
671           case KeyEvent.VK_KP_UP:
672           {
673             int curEnd = selection.getEndLinePos();
674             if (ev.isShiftDown())
675             {
676               if (curEnd > 0)
677               {
678                 selection.extendLineSelection(curEnd-1, true);
679               }
680             }
681             else if (selection.isSinglePointSelection())
682             {
683               if (curEnd > 0)
684               {
685                 selection.setLineSelection(curEnd-1, curEnd-1, true);
686               }
687               else if (metaDown)
688               {
689                 int block = selection.getContainingBlock();
690                 selection.setBlockSelection(block, block, true);
691               }
692               else
693               {
694                 bodyEditor_.selectBottomOfPreviousBlock();
695               }
696             }
697             else
698             {
699               int pos = selection.getTopLinePos();
700               selection.setLineSelection(pos, pos, true);
701             }
702             ev.consume();
703             break;
704           }
705           
706           case KeyEvent.VK_DOWN:
707           case KeyEvent.VK_KP_DOWN:
708           {
709             int curEnd = selection.getEndLinePos();
710             if (ev.isShiftDown())
711             {
712               if (curEnd < lineEditors_.size())
713               {
714                 selection.extendLineSelection(curEnd+1, true);
715               }
716             }
717             else if (selection.isSinglePointSelection())
718             {
719               if (curEnd < lineEditors_.size())
720               {
721                 selection.setLineSelection(curEnd+1, curEnd+1, true);
722               }
723               else if (metaDown)
724               {
725                 int block = selection.getContainingBlock()+1;
726                 selection.setBlockSelection(block, block, true);
727               }
728               else
729               {
730                 bodyEditor_.selectTopOfNextBlock();
731               }
732             }
733             else
734             {
735               int pos = selection.getBottomLinePos();
736               selection.setLineSelection(pos, pos, true);
737             }
738             ev.consume();
739             break;
740           }
741           
742           case KeyEvent.VK_LEFT:
743           case KeyEvent.VK_KP_LEFT:
744           {
745             int block = selection.getContainingBlock();
746             selection.setBlockSelection(block, block+1, true);
747             ev.consume();
748             break;
749           }
750           
751           case KeyEvent.VK_RIGHT:
752           case KeyEvent.VK_KP_RIGHT:
753           {
754             int line = selection.getEndLinePos();
755             if (line == lineEditors_.size() ||
756               (line == selection.getBottomLinePos() && !selection.isSinglePointSelection()))
757             {
758               --line;
759             }
760             if (line >= 0)
761             {
762               selection.prepareSelectionInsideLine(line);
763               ((SongLineEditor)lineEditors_.get(line)).selectDefault();
764             }
765             ev.consume();
766             break;
767           }
768           
769           case KeyEvent.VK_BACK_SPACE:
770           {
771             doDeleteSelection(true);
772             ev.consume();
773             break;
774           }
775           
776           case KeyEvent.VK_ENTER:
777           {
778             doEnter();
779             ev.consume();
780             break;
781           }
782           
783           case KeyEvent.VK_HOME:
784           case KeyEvent.VK_PAGE_UP:
785           {
786             if (ev.isShiftDown())
787             {
788               selection.extendLineSelection(0, true);
789             }
790             else
791             {
792               selection.setLineSelection(0, 0, true);
793             }
794             ev.consume();
795             break;
796           }
797           
798           case KeyEvent.VK_END:
799           case KeyEvent.VK_PAGE_DOWN:
800           {
801             if (ev.isShiftDown())
802             {
803               selection.extendLineSelection(lineEditors_.size(), true);
804             }
805             else
806             {
807               selection.setLineSelection(lineEditors_.size(),
808                 lineEditors_.size(), true);
809             }
810             ev.consume();
811             break;
812           }
813         }
814       }
815     }
816     else
817     {
818       SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(selection.getContainingLine());
819       lineEditor.handleKey(ev, x);
820     }
821   }
822   
823   
824   /*package*/ class BlockUndoHelper
825   extends UndoHelper
826   {
827     private int blockNum_;
828     
829     /*package*/ BlockUndoHelper()
830     {
831       super(pane_);
832       blockNum_ = bodyEditor_.getBlockIndex(SongBlockEditor.this);
833     }
834     
835     protected void rebuildView()
836     {
837       bodyEditor_.getNthBlock(blockNum_).rebuild(true);
838     }
839   }
840   
841   
842   /*package*/ void doCopy(
843     boolean remove)
844   {
845     EditorSelection selection = pane_.getSelection();
846     
847     if (selection.isLineSelection())
848     {
849       DataTransferUtils dataTransferUtils = pane_.getDataTransferUtils();
850       List lines = new ArrayList();
851       int top = selection.getTopLinePos();
852       int bottom = selection.getBottomLinePos();
853       for (int i=top; i<bottom; ++i)
854       {
855         lines.add(block_.getNthLine(i));
856       }
857       
858       Transferable transfer;
859       if (remove)
860       {
861         EditorSelection newSelection = selection.createCopy();
862         newSelection.setLineSelection(top, top, false);
863         UndoHelper helper = new BlockUndoHelper();
864         transfer = dataTransferUtils.createLineListTransferable(
865           block_.getSong(), lines, pane_.getCurChordSet(), true, helper);
866         helper.finish(newSelection);
867       }
868       else
869       {
870         transfer = dataTransferUtils.createLineListTransferable(
871           block_.getSong(), lines, pane_.getCurChordSet(), false, null);
872       }
873       dataTransferUtils.setClipboardContents(transfer);
874     }
875     else
876     {
877       SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(
878         selection.getContainingLine());
879       lineEditor.doCopy(remove);
880     }
881   }
882   
883   
884   /*package*/ void doPaste()
885   {
886     EditorSelection selection = pane_.getSelection();
887     
888     if (selection.isLineSelection())
889     {
890       DataTransferUtils dataTransferUtils = pane_.getDataTransferUtils();
891       EditorSelection newSelection = selection.createCopy();
892       int pos = selection.getTopLinePos();
893       int len = selection.getBottomLinePos()-pos;
894       newSelection.setLineSelection(pos, pos, false);
895       
896       Transferable transfer = dataTransferUtils.getClipboardContents();
897       Variation var = pane_.getCurVariation();
898       DataFlavor pasteType;
899       try
900       {
901         pasteType = dataTransferUtils.getPasteType(
902           transfer, DataTransferUtils.LINELIST_FLAVOR);
903       }
904       catch (CannotPasteException ex)
905       {
906         return;
907       }
908       
909       UndoHelper helper;
910       if (pasteType.equals(DataTransferUtils.BLOCKLIST_FLAVOR))
911       {
912         helper = bodyEditor_.new BodyUndoHelper();
913       }
914       else
915       {
916         assert pasteType.equals(DataTransferUtils.LINELIST_FLAVOR);
917         helper = new BlockUndoHelper();
918       }
919       
920       SongLine prevLine;
921       if (pos == block_.getLineCount())
922       {
923         prevLine = block_.getPreviousLine(null);
924       }
925       else
926       {
927         prevLine = block_.getPreviousLine(block_.getNthLine(pos));
928       }
929       for (int i=0; i<len; ++i)
930       {
931         SongLine line = block_.getNextLine(prevLine);
932         block_.removeLine(line, helper);
933       }
934       
935       int ocount;
936       if (pasteType.equals(DataTransferUtils.BLOCKLIST_FLAVOR))
937       {
938         ocount = block_.getSong().getBlockCount(var);
939       }
940       else
941       {
942         ocount = block_.getLineCount();
943       }
944       
945       try
946       {
947         dataTransferUtils.pasteTransferableAfter(transfer, block_, prevLine,
948           pane_.getCurChordSet(), var, helper);
949         if (pasteType.equals(DataTransferUtils.BLOCKLIST_FLAVOR))
950         {
951           newSelection.prepareSelectionInsideBlock(newSelection.getContainingBlock()+
952             block_.getSong().getBlockCount(var)-ocount);
953           newSelection.setLineSelection(0, 0, false);
954         }
955         else
956         {
957           pos += (block_.getLineCount()-ocount);
958           newSelection.setLineSelection(pos, pos, false);
959         }
960       }
961       catch (CannotPasteException ex)
962       {
963       }
964       
965       helper.finish(newSelection);
966     }
967     else
968     {
969       SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(
970         selection.getContainingLine());
971       lineEditor.doPaste();
972     }
973   }
974   
975   
976   /*package*/ void doInsert()
977   {
978     EditorSelection selection = pane_.getSelection();
979     
980     if (selection.isLineSelection())
981     {
982       EditorSelection newSelection = selection.createCopy();
983       int pos = selection.getEndLinePos();
984       newSelection.setLineSelection(pos, pos+1, false);
985       
986       UndoHelper helper = new BlockUndoHelper();
987       
988       SongLine line;
989       if (pos == 0)
990       {
991         line = block_.insertLineAfter(null, helper);
992       }
993       else
994       {
995         SongLine prev = block_.getNthLine(pos-1);
996         line = block_.insertLineAfter(prev, helper);
997       }
998       line.insertTextStringAfter(null, "", helper);
999       
1000      helper.finish(newSelection);
1001    }
1002    else
1003    {
1004      SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(
1005        selection.getContainingLine());
1006      lineEditor.doInsert();
1007    }
1008  }
1009  
1010  
1011  /*package*/ void doComment(
1012    boolean comment)
1013  {
1014    SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(
1015      pane_.getSelection().getContainingLine());
1016    lineEditor.doComment(comment);
1017  }
1018  
1019  
1020  /*package*/ void doDeleteSelection(
1021    boolean backspace)
1022  {
1023    EditorSelection selection = pane_.getSelection();
1024    
1025    if (selection.isLineSelection())
1026    {
1027      int pos;
1028      int len;
1029      if (selection.isSinglePointSelection())
1030      {
1031        if (!backspace)
1032        {
1033          return;
1034        }
1035        pos = selection.getStartLinePos()-1;
1036        if (pos < 0)
1037        {
1038          doMergeWithPreviousBlock();
1039          return;
1040        }
1041        len = 1;
1042      }
1043      else
1044      {
1045        pos = selection.getTopLinePos();
1046        len = selection.getBottomLinePos()-pos;
1047      }
1048      
1049      EditorSelection newSelection = selection.createCopy();
1050      newSelection.setLineSelection(pos, pos, false);
1051      
1052      UndoHelper helper = new BlockUndoHelper();
1053      
1054      for (int i=0; i<len; ++i)
1055      {
1056        SongLine line = block_.getNthLine(pos);
1057        block_.removeLine(line, helper);
1058      }
1059      
1060      helper.finish(newSelection);
1061    }
1062    else
1063    {
1064      SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(
1065        selection.getContainingLine());
1066      lineEditor.doDeleteSelection();
1067    }
1068  }
1069  
1070  
1071  /*package*/ void doShift(
1072    int shift)
1073  {
1074    EditorSelection selection = pane_.getSelection();
1075    
1076    if (selection.isChordSelection())
1077    {
1078      SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(
1079        selection.getContainingLine());
1080      lineEditor.doShiftChords(shift);
1081    }
1082    else
1083    {
1084      int start;
1085      int end;
1086      if (selection.isLineSelection())
1087      {
1088        start = selection.getTopLinePos();
1089        end = selection.getBottomLinePos();
1090      }
1091      else
1092      {
1093        start = selection.getContainingLine();
1094        end = start+1;
1095      }
1096      
1097      UndoHelper helper = new BlockUndoHelper();
1098      for (int i=start; i<end; ++i)
1099      {
1100        SongLine line = block_.getNthLine(i);
1101        line.setIndentLevel(line.getIndentLevel()+shift, helper);
1102      }
1103      helper.finish(null);
1104    }
1105  }
1106  
1107  
1108  /*package*/ boolean canInsertChordMarkingHere()
1109  {
1110    SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(
1111      pane_.getSelection().getContainingLine());
1112    return lineEditor.canInsertChordMarkingHere();
1113  }
1114  
1115  
1116  /*package*/ void doSelectAll()
1117  {
1118    EditorSelection selection = pane_.getSelection();
1119    
1120    if (selection.isLineSelection())
1121    {
1122      selection.setLineSelection(0, lineEditors_.size(), false);
1123    }
1124    else
1125    {
1126      SongLineEditor lineEditor = (SongLineEditor)lineEditors_.get(
1127        selection.getContainingLine());
1128      lineEditor.doSelectAll();
1129    }
1130  }
1131  
1132  
1133  private void doEnter()
1134  {
1135    EditorSelection selection = pane_.getSelection();
1136    int pos = selection.getTopLinePos();
1137    int len = selection.getBottomLinePos()-pos;
1138    
1139    SongUtils songUtils = pane_.getSongUtils();
1140    
1141    EditorSelection newSelection = selection.createCopy();
1142    newSelection.prepareSelectionInsideBlock(newSelection.getContainingBlock()+1);
1143    newSelection.setLineSelection(0, 0, false);
1144    
1145    UndoHelper helper = bodyEditor_.new BodyUndoHelper();
1146    
1147    SongLine line = null;
1148    if (block_.getLineCount() > pos)
1149    {
1150      line = block_.getNthLine(pos);
1151    }
1152    for (int i=0; i<len; ++i)
1153    {
1154      SongLine next = block_.getNextLine(line);
1155      block_.removeLine(line, helper);
1156      line = next;
1157    }
1158    line = block_.getPreviousLine(line);
1159    songUtils.splitBlock(block_, line, helper);
1160    
1161    helper.finish(newSelection);
1162  }
1163  
1164  
1165  private void doMergeWithPreviousBlock()
1166  {
1167    EditorSelection selection = pane_.getSelection();
1168    
1169    if (selection.getContainingBlock() > 0)
1170    {
1171      DataTransferUtils dataTransferUtils = pane_.getDataTransferUtils();
1172      ChordSet curChordSet = pane_.getCurChordSet();
1173      Variation curVariation = pane_.getCurVariation();
1174      EditorSelection newSelection = selection.createCopy();
1175      
1176      newSelection.prepareSelectionInsideBlock(newSelection.getContainingBlock()-1);
1177      Song song = block_.getSong();
1178      SongBlock prevBlock = song.getPreviousBlock(block_, curVariation);
1179      int lineCount = prevBlock.getLineCount();
1180      newSelection.setLineSelection(lineCount, lineCount, false);
1181      
1182      UndoHelper helper = bodyEditor_.new BodyUndoHelper();
1183      
1184      List lineList = new ArrayList();
1185      for (SongLine line = block_.getNextLine(null); line != null;
1186        line = block_.getNextLine(line))
1187      {
1188        lineList.add(line);
1189      }
1190      Transferable transfer = dataTransferUtils.createLineListTransferable(song,
1191        lineList, curChordSet, false, helper);
1192      try
1193      {
1194        dataTransferUtils.pasteTransferableAfter(transfer, prevBlock,
1195          prevBlock.getPreviousLine(null), curChordSet, curVariation, helper);
1196        song.removeBlock(block_, helper);
1197        helper.finish(newSelection);
1198      }
1199      catch (CannotPasteException ex)
1200      {
1201        // Unexpected
1202        throw new RuntimeException(ex);
1203      }
1204    }
1205  }
1206  
1207  
1208  /**
1209   * Utility determining where a mouse press happened.
1210   * If the hit is between lines, the nonnegative position is returned.
1211   * If the hit is on a line, -1-index is returned.
1212   */
1213  private int findLineForPoint(
1214    float y,
1215    boolean forceBetween)
1216  {
1217    int numLines =lineEditorYs_.size();
1218    for (int i=0; i<numLines; ++i)
1219    {
1220      float by = ((Float)lineEditorYs_.get(i)).floatValue();
1221      if (y < by)
1222      {
1223        return i;
1224      }
1225      float height = ((BodyEditorSize)lineEditorSizes_.get(i)).getBottom();
1226      if (y < by+height)
1227      {
1228        if (forceBetween)
1229        {
1230          if (y < by+height/2)
1231          {
1232            return i;
1233          }
1234          else
1235          {
1236            return i+1;
1237          }
1238        }
1239        return -1-i;
1240      }
1241    }
1242    return numLines;
1243  }
1244}