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}