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

Quick Search    Search Deep

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


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