3 import java.awt.event.*;
4 import javax.sound.midi.*;
8 * Graphical representation of a MIDI track.
10 * @author Andersson , Andreen, Lanneskog, Pehrson
14 public class MooTrackView extends JPanel {
17 private MooTrackTitle title;
18 private MooKeyboard keyboard;
20 private JPopupMenu popup, selPopup;
21 private JMenu selPopupTranspUp, selPopupTranspDown;
22 private JMenuItem popupAdd, popupPaste;
23 private JMenuItem selPopupCopy, selPopupCut, selPopupRemove;
24 private JMenuItem[] selPopupTranspUpItems, selPopupTranspDownItems;
26 private ArrayList coords;
27 private TreeSet selection;
28 private Insets insets;
29 private Rectangle box;
30 private int ticksPerSixteenth, popupY = 0;
31 private boolean leftMouseButtonPressed = false;
32 private static boolean snapToSixteenths = true;
33 protected static int viewLength = 0;
34 protected static int extraHeight = 0;
35 public static final int NOTE_HEIGHT = 10, NOTE_WIDTH = 40, VIEW_WIDTH = 200;
38 * Creates the trackview.
39 * @param track The track it represents graphically and operates on.
40 * @param title The object that is used to manipulate instrument, channel, solo, mute.
42 public MooTrackView (Track track, MooTrackTitle title) {
45 // Defines instance variables
48 ticksPerSixteenth = Moosique.getSequence().getResolution() / 4;
50 coords = new ArrayList(track.size() / 2);
51 selection = new TreeSet();
54 setBackground(Color.white);
55 setBorder(BorderFactory.createLineBorder(Color.black));
57 setPreferredSize(new Dimension(VIEW_WIDTH, 140 * NOTE_HEIGHT));
59 // Creates temporary variables
62 extraHeight = Toolkit.getDefaultToolkit().getScreenSize().height - 150;
64 // Places note elements
65 for (int i = 0; i < track.size(); i++) {
67 if (note instanceof MooNote) {
68 // Adds the note element to the note area and moves it to the appropriate place.
69 MooNote mn = (MooNote)note;
70 elem = new MooNoteElement(this, mn);
72 layoutElement(elem, false);
75 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
77 // Creates panel pop-up menu.
78 popup = new JPopupMenu();
79 popupAdd = addMenuItem(popup, "Add note...");
80 popupPaste = addMenuItem(popup, "Paste");
82 // Creates selection pop-up menu.
83 selPopup = new JPopupMenu();
84 selPopupCopy = addMenuItem(selPopup, "Copy selection");
85 selPopupCut = addMenuItem(selPopup, "Cut selection");
86 selPopupRemove = addMenuItem(selPopup, "Remove selection");
87 selPopupTranspUpItems = new JMenuItem[12];
88 selPopupTranspDownItems = new JMenuItem[12];
89 selPopupTranspUp = createTransposeMenu(selPopup, selPopupTranspUpItems, "selection up");
90 selPopupTranspDown = createTransposeMenu(selPopup, selPopupTranspDownItems, "selection down");
92 // Adds listeners for popup menu and keyboard synthesizer.
93 addMouseListener(new MAdapter());
94 keyboard = new MooKeyboard(title);
95 addKeyListener(keyboard);
99 * Creates note elements for all MooNotes in the given list, and places them in the appropriate place.
101 public void placeNewNotes(java.util.List notes) {
102 // Creates temporary variables
105 extraHeight = Toolkit.getDefaultToolkit().getScreenSize().height - 150;
107 // Places note elements
108 for (int i = 0; i < notes.size(); i++) {
109 note = (MidiEvent)notes.get(i);
110 if (note instanceof MooNote) {
111 // Adds the note element to the note area and moves it to the appropriate place.
112 MooNote mn = (MooNote)note;
113 elem = new MooNoteElement(this, mn);
115 layoutElement(elem, false);
118 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
122 * Layouts the element to the right place.
123 * @param elem the element that will be layouted.
124 * @param old If true, this method will remove the old layout and set the new preferredSize for the trackview.
126 public void layoutElement(MooNoteElement elem, boolean old){
127 // If the element is currently in the view, removes its coordinates from the list.
128 Rectangle r = new Rectangle();
130 r = elem.getBounds(r);
131 for (Iterator i = coords.iterator(); i.hasNext();){
132 Object ob = i.next();
140 // Creates temporary variables.
141 MooNote mn = elem.getNote();
144 // Calculates coordinates.
146 y = insets.top + (int)((mn.getTick() * NOTE_HEIGHT) / ticksPerSixteenth);
147 height = (mn.getDuration() * NOTE_HEIGHT) / ticksPerSixteenth;
148 if (height == 0) height = NOTE_HEIGHT;
149 if (snapToSixteenths && height < NOTE_HEIGHT) height = NOTE_HEIGHT;
150 r = new Rectangle(x, y, NOTE_WIDTH, height);
152 // Places the element in the appropriate place.
153 while(isOccupied(r)) r.translate(NOTE_WIDTH, 0);
156 if (viewLength < (y + height)){
157 viewLength = y + height;
158 if(old)setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
167 * Returns the track of this view.
168 * @return the track of this view
170 public Track getTrack() {
175 * Returns the title of this view.
176 * @return the title of this view
178 public MooTrackTitle getTitle() {
183 * Checks if the element can be fully drawn as this position without inteferring with other elements.
184 * @return true if the position is occupied.
186 private boolean isOccupied(Rectangle r) {
187 Iterator it = coords.iterator();
188 while (it.hasNext()) {
189 if(r.intersects((Rectangle)it.next())) return true;
195 * Adds the given note to the current track, and visualises it.
196 * @param mn the note to add
198 public void addNote(MooNote mn) {
200 MooNoteElement elem = new MooNoteElement(this, mn);
202 layoutElement(elem, false);
203 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
204 Moosique.setEdited();
210 * Adds a standard note to this track.
212 private void addStandardNote() {
213 int row = (popupY - insets.top) / NOTE_HEIGHT;
214 long timestamp = (long)(ticksPerSixteenth * row);
215 addNote(new MooNote(title.getChannel(), 60, 100, timestamp, Moosique.getSequence().getResolution() / 4));
219 * Removes the given note element from the view and its note from the current track.
220 * @param elem the note element to remove
222 public void removeNote(MooNoteElement elem) {
223 elem.getNote().removeFrom(track);
225 Rectangle r = new Rectangle();
226 r = elem.getBounds(r);
228 Moosique.setEdited();
234 * Selects the given note
235 * @param the note to select
237 public void selectNote(MooNoteElement elem) {
242 * Deselects the given note
243 * @param the note to deselect
245 public void deselectNote(MooNoteElement elem) {
246 selection.remove(elem);
250 * Deselects all notes.
252 public void deselectAllNotes() {
253 Iterator it = selection.iterator();
254 while(it.hasNext()) {
255 ((MooNoteElement)it.next()).deselect();
261 * Determines if the given MooNoteElement is the only one in the track view that is selected.
262 * @return if the given element is the only selected one
264 public boolean isTheOnlySelected(MooNoteElement elem) {
265 Iterator it = selection.iterator();
266 while(it.hasNext()) {
267 if (!it.next().equals(elem)) return false;
273 * Copies the current selection.
275 public void copySelectedNotes() {
276 ArrayList copyBuffer = new ArrayList(selection.size());
277 Iterator it = selection.iterator();
278 while(it.hasNext()) {
279 copyBuffer.add(((MooNoteElement)it.next()).getNote().clone());
281 Collections.sort(copyBuffer);
282 Moosique.setCopyBuffer(copyBuffer);
286 * Cuts the current selection.
288 public void cutSelectedNotes() {
290 removeSelectedNotes();
294 * Pastes the current copy buffer at the given timestamp.
296 public void pasteCopiedNotes() {
297 int row = (popupY - insets.top) / NOTE_HEIGHT;
298 long timestamp = (long)(ticksPerSixteenth * row);
299 ArrayList copyBuffer = Moosique.getCopyBuffer();
300 if (copyBuffer.size() > 0) {
301 long startTime = ((MooNote)copyBuffer.get(0)).getTick();
302 Iterator it = copyBuffer.iterator();
303 while(it.hasNext()) {
304 MooNote mn = (MooNote)((MooNote)it.next()).clone();
305 mn.setTick(mn.getTick() - startTime + timestamp);
306 mn.setChannel(title.getChannel());
313 * Removes the current selection.
315 public void removeSelectedNotes() {
316 Iterator it = selection.iterator();
317 while(it.hasNext()) {
318 removeNote((MooNoteElement)it.next());
324 * Transposes all selected notes the given number of halftones.
326 private void transposeSelectedNotes(int halftones) {
327 Iterator it = selection.iterator();
328 while(it.hasNext()) {
329 MooNoteElement elem = (MooNoteElement)it.next();
330 elem.transpose(halftones);
335 * Moves the current selection the given number of ticks.
336 * @param ticks the number of ticks to move the selection.
338 public void moveSelectedNotes(int ticks) {
340 // If the selection should be moved upwards, traverses the list in the natural order.
341 Iterator it = selection.iterator();
342 while(it.hasNext()) {
343 MooNoteElement elem = (MooNoteElement)it.next();
344 elem.getNote().setTick(elem.getNote().getTick() + ticks);
345 layoutElement(elem, true);
348 // If the selection should be moved downwards, traverses the list in the opposite order.
349 ArrayList selectedList = new ArrayList(selection);
350 ListIterator it = selectedList.listIterator(selectedList.size());
351 while(it.hasPrevious()) {
352 MooNoteElement elem = (MooNoteElement)it.previous();
353 elem.getNote().setTick(elem.getNote().getTick() + ticks);
354 layoutElement(elem, true);
360 * Moves the current selection, depending on the given delta y.
361 * @param y the number of pixels the selection is moved.
363 public void maybeMoveSelectedNotes(int y) {
364 moveSelectedNotes(ticksPerSixteenth * (y / NOTE_HEIGHT));
368 * Enables keyboard recording.
370 public void enableKeyboardRecording() {
371 keyboard.recordEnable();
375 * Disables keyboard recording.
377 public void disableKeyboardRecording() {
378 keyboard.recordDisable();
382 * Adds a menu item with the given command to the given popup menu.
384 private JMenuItem addMenuItem(JPopupMenu menu, String command) {
385 JMenuItem item = new JMenuItem(command);
386 item.addActionListener(new PopupListener());
392 * Adds a menu item with the given command to the given menu.
394 private JMenuItem addMenuItem(JMenu menu, String command) {
395 JMenuItem item = new JMenuItem(command);
396 item.addActionListener(new PopupListener());
402 * Creates a transpose sub menu with the given title in the given popup menu,
403 * inserting the items into the given array.
405 private JMenu createTransposeMenu(JPopupMenu menu, JMenuItem[] items, String title) {
406 JMenu trans = new JMenu("Transpose " + title);
408 items[0] = addMenuItem(trans, "One octave");
409 for (int i = 1; i < 12; i++) {
410 items[i] = addMenuItem(trans, (i) + " halftones");
416 * Shows a popup-menu with options for the current selection of note elements.
417 * @param c the component over which to display the menu
418 * @param x the x-coordinate in which to display the menu
419 * @param y the y-coordinate in which to display the menu
421 public void showSelectionPopup(Component c, int x, int y) {
422 selPopup.show(c, x, y);
426 * Draws the grid that is on the background.
427 * @param g The Graphics object used to draw the grid.
429 public void paintComponent(Graphics g) {
430 super.paintComponent(g);
431 Graphics2D g2 = (Graphics2D)g;
432 for (int c = 0; c < viewLength || c < getHeight(); c += NOTE_HEIGHT) {
433 for (int r = 0; r < (10 * NOTE_WIDTH); r += NOTE_WIDTH) {
434 box = new Rectangle(r, c, NOTE_WIDTH, NOTE_HEIGHT);
435 g2.setColor(Color.gray);
442 * Returns whether the left mouse button is currently pressed or not.
443 * @return true if the left mosue button is currently pressed
445 public boolean isLeftMouseButtonPressed() {
446 return leftMouseButtonPressed;
450 * The adapter used to listen on mouse actions
452 class MAdapter extends MouseAdapter {
455 * Deselects all note on click, adds a standard note on double click.
457 public void mouseClicked(MouseEvent e) {
458 if (SwingUtilities.isLeftMouseButton(e)) {
460 if (e.getClickCount() == 2) {
467 public void mousePressed(MouseEvent e) {
468 if (SwingUtilities.isLeftMouseButton(e)) leftMouseButtonPressed = true;
472 public void mouseReleased(MouseEvent e) {
473 if (SwingUtilities.isLeftMouseButton(e)) leftMouseButtonPressed = false;
478 * Shows the menu if an OS-specific popup-trigger was activated.
480 private void maybeShowPopup(MouseEvent e) {
481 if (e.isPopupTrigger()) {
483 popup.show(e.getComponent(), e.getX(), e.getY());
488 * Grabs the focus when the mouse has entered.
490 public void mouseEntered(MouseEvent e) {
491 Moosique.setActiveChannel(title.getChannel());
497 * Takes the appropriate action when a user selects an item on the popup menu.
499 class PopupListener implements ActionListener {
500 public void actionPerformed(ActionEvent e) {
501 Object source = e.getSource();
502 // Handling panel popup actions.
503 if (source == popupAdd) {
505 } else if (source == popupPaste) {
507 // Handling selection popup actions.
508 } else if (source == selPopupCopy) {
510 } else if (source == selPopupCut) {
512 } else if (source == selPopupRemove) {
513 removeSelectedNotes();
514 } else if (source == selPopupTranspUpItems[0]) {
515 transposeSelectedNotes(12);
516 } else if (source == selPopupTranspDownItems[0]) {
517 transposeSelectedNotes(-12);
519 for (int i = 1; i < 12; i++) {
520 if (source == selPopupTranspUpItems[i]) transposeSelectedNotes(i);
521 else if (source == selPopupTranspDownItems[i]) transposeSelectedNotes(-i);