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, quantizeRecording = false;
32 protected static int viewLength = 0;
33 protected static int extraHeight = 0;
34 public static final int NOTE_HEIGHT = 10, NOTE_WIDTH = 40, VIEW_WIDTH = 200;
37 * Creates the trackview.
38 * @param track The track it represents graphically and operates on.
39 * @param title The object that is used to manipulate instrument, channel, solo, mute.
41 public MooTrackView (Track track, MooTrackTitle title) {
44 // Defines instance variables
50 setBackground(Color.white);
51 setBorder(BorderFactory.createLineBorder(Color.black));
53 setPreferredSize(new Dimension(VIEW_WIDTH, 140 * NOTE_HEIGHT));
55 placeNoteElements(false);
57 // Creates panel pop-up menu.
58 popup = new JPopupMenu();
59 popupAdd = addMenuItem(popup, "Add note...");
60 popupPaste = addMenuItem(popup, "Paste");
62 // Creates selection pop-up menu.
63 selPopup = new JPopupMenu();
64 selPopupCopy = addMenuItem(selPopup, "Copy selection");
65 selPopupCut = addMenuItem(selPopup, "Cut selection");
66 selPopupRemove = addMenuItem(selPopup, "Remove selection");
67 selPopupTranspUpItems = new JMenuItem[12];
68 selPopupTranspDownItems = new JMenuItem[12];
69 selPopupTranspUp = createTransposeMenu(selPopup, selPopupTranspUpItems, "selection up");
70 selPopupTranspDown = createTransposeMenu(selPopup, selPopupTranspDownItems, "selection down");
72 // Adds listeners for popup menu and keyboard synthesizer.
73 addMouseListener(new MAdapter());
74 keyboard = new MooKeyboard(title);
75 addKeyListener(keyboard);
79 * Creates note elements for all MooNotes in the track, and places them in the appropriate place.
81 public void placeNoteElements(boolean quantize) {
82 // Converts the track.
83 Moosique.convertTrack(track);
85 // Empties the container
87 coords = new ArrayList(track.size() / 2);
88 selection = new TreeSet();
90 // Creates temporary variables
93 extraHeight = Toolkit.getDefaultToolkit().getScreenSize().height - 150;
95 // Places note elements
96 for (int i = 0; i < track.size(); i++) {
98 if (note instanceof MooNote) {
99 // Adds the note element to the note area and moves it to the appropriate place.
100 MooNote mn = (MooNote)note;
101 elem = new MooNoteElement(this, mn);
103 layoutElement(elem, false);
105 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
110 * Layouts the element to the right place.
111 * @param elem the element that will be layouted.
112 * @param old If true, this method will remove the old layout and set the new preferredSize for the trackview.
114 public void layoutElement(MooNoteElement elem, boolean old){
115 // If the element is currently in the view, removes its coordinates from the list.
116 Rectangle r = new Rectangle();
118 r = elem.getBounds(r);
119 for (Iterator i = coords.iterator(); i.hasNext();){
120 Object ob = i.next();
128 // Creates temporary variables.
129 ticksPerSixteenth = Moosique.getSequence().getResolution() / 4;
130 MooNote mn = elem.getNote();
133 // Calculates coordinates.
135 if (quantizeRecording) {
136 // Snap to nearest sixteenth
137 y = insets.top + Math.round(mn.getTick() / ticksPerSixteenth) * NOTE_HEIGHT;
138 height = (mn.getDuration() / ticksPerSixteenth) * NOTE_HEIGHT;
140 y = insets.top + (int)((mn.getTick() * NOTE_HEIGHT) / ticksPerSixteenth);
141 height = (mn.getDuration() * NOTE_HEIGHT) / ticksPerSixteenth;
143 if (height == 0) height = NOTE_HEIGHT;
144 r = new Rectangle(x, y, NOTE_WIDTH, height);
146 // Places the element in the appropriate place.
147 while(isOccupied(r)) r.translate(NOTE_WIDTH, 0);
150 if (viewLength < (y + height)){
151 viewLength = y + height;
152 if(old)setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
161 * Returns the track of this view.
162 * @return the track of this view
164 public Track getTrack() {
169 * Returns the title of this view.
170 * @return the title of this view
172 public MooTrackTitle getTitle() {
177 * Checks if the element can be fully drawn as this position without inteferring with other elements.
178 * @return true if the position is occupied.
180 private boolean isOccupied(Rectangle r) {
181 Iterator it = coords.iterator();
182 while (it.hasNext()) {
183 if(r.intersects((Rectangle)it.next())) return true;
189 * Adds the given note to the current track, and visualises it.
190 * @param mn the note to add
192 public void addNote(MooNote mn) {
194 MooNoteElement elem = new MooNoteElement(this, mn);
196 layoutElement(elem, false);
197 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
198 Moosique.setEdited();
204 * Adds a standard note to this track.
206 private void addStandardNote() {
207 int row = (popupY - insets.top) / NOTE_HEIGHT;
208 long timestamp = (long)(ticksPerSixteenth * row);
209 addNote(new MooNote(title.getChannel(), 60, 100, timestamp, Moosique.getSequence().getResolution() / 4));
213 * Removes the given note element from the view and its note from the current track.
214 * @param elem the note element to remove
216 public void removeNote(MooNoteElement elem) {
217 elem.getNote().removeFrom(track);
219 Rectangle r = new Rectangle();
220 r = elem.getBounds(r);
222 Moosique.setEdited();
228 * Selects the given note
229 * @param the note to select
231 public void selectNote(MooNoteElement elem) {
236 * Deselects the given note
237 * @param the note to deselect
239 public void deselectNote(MooNoteElement elem) {
240 selection.remove(elem);
244 * Deselects all notes.
246 public void deselectAllNotes() {
247 Iterator it = selection.iterator();
248 while(it.hasNext()) {
249 ((MooNoteElement)it.next()).deselect();
255 * Determines if the given MooNoteElement is the only one in the track view that is selected.
256 * @return if the given element is the only selected one
258 public boolean isTheOnlySelected(MooNoteElement elem) {
259 Iterator it = selection.iterator();
260 while(it.hasNext()) {
261 if (!it.next().equals(elem)) return false;
267 * Copies the current selection.
269 public void copySelectedNotes() {
270 ArrayList copyBuffer = new ArrayList(selection.size());
271 Iterator it = selection.iterator();
272 while(it.hasNext()) {
273 copyBuffer.add(((MooNoteElement)it.next()).getNote().clone());
275 Collections.sort(copyBuffer);
276 Moosique.setCopyBuffer(copyBuffer);
280 * Cuts the current selection.
282 public void cutSelectedNotes() {
284 removeSelectedNotes();
288 * Pastes the current copy buffer at the given timestamp.
290 public void pasteCopiedNotes() {
291 int row = (popupY - insets.top) / NOTE_HEIGHT;
292 long timestamp = (long)(ticksPerSixteenth * row);
293 ArrayList copyBuffer = Moosique.getCopyBuffer();
294 if (copyBuffer.size() > 0) {
295 long startTime = ((MooNote)copyBuffer.get(0)).getTick();
296 Iterator it = copyBuffer.iterator();
297 while(it.hasNext()) {
298 MooNote mn = (MooNote)((MooNote)it.next()).clone();
299 mn.setTick(mn.getTick() - startTime + timestamp);
300 mn.setChannel(title.getChannel());
307 * Removes the current selection.
309 public void removeSelectedNotes() {
310 Iterator it = selection.iterator();
311 while(it.hasNext()) {
312 removeNote((MooNoteElement)it.next());
318 * Transposes all selected notes the given number of halftones.
320 private void transposeSelectedNotes(int halftones) {
321 Iterator it = selection.iterator();
322 while(it.hasNext()) {
323 MooNoteElement elem = (MooNoteElement)it.next();
324 elem.transpose(halftones);
329 * Moves the current selection the given number of ticks.
330 * @param ticks the number of ticks to move the selection.
332 public void moveSelectedNotes(int ticks) {
334 // If the selection should be moved upwards, traverses the list in the natural order.
335 Iterator it = selection.iterator();
336 while(it.hasNext()) {
337 MooNoteElement elem = (MooNoteElement)it.next();
338 elem.getNote().setTick(elem.getNote().getTick() + ticks);
339 layoutElement(elem, true);
342 // If the selection should be moved downwards, traverses the list in the opposite order.
343 ArrayList selectedList = new ArrayList(selection);
344 ListIterator it = selectedList.listIterator(selectedList.size());
345 while(it.hasPrevious()) {
346 MooNoteElement elem = (MooNoteElement)it.previous();
347 elem.getNote().setTick(elem.getNote().getTick() + ticks);
348 layoutElement(elem, true);
354 * Moves the current selection, depending on the given delta y.
355 * @param y the number of pixels the selection is moved.
357 public void maybeMoveSelectedNotes(int y) {
358 moveSelectedNotes(ticksPerSixteenth * (y / NOTE_HEIGHT));
362 * Enables keyboard recording.
364 public void enableKeyboardRecording() {
365 keyboard.recordEnable();
369 * Disables keyboard recording.
371 public void disableKeyboardRecording() {
372 keyboard.recordDisable();
376 * Adds a menu item with the given command to the given popup menu.
378 private JMenuItem addMenuItem(JPopupMenu menu, String command) {
379 JMenuItem item = new JMenuItem(command);
380 item.addActionListener(new PopupListener());
386 * Adds a menu item with the given command to the given menu.
388 private JMenuItem addMenuItem(JMenu menu, String command) {
389 JMenuItem item = new JMenuItem(command);
390 item.addActionListener(new PopupListener());
396 * Creates a transpose sub menu with the given title in the given popup menu,
397 * inserting the items into the given array.
399 private JMenu createTransposeMenu(JPopupMenu menu, JMenuItem[] items, String title) {
400 JMenu trans = new JMenu("Transpose " + title);
402 items[0] = addMenuItem(trans, "One octave");
403 for (int i = 1; i < 12; i++) {
404 items[i] = addMenuItem(trans, (i) + " halftones");
410 * Shows a popup-menu with options for the current selection of note elements.
411 * @param c the component over which to display the menu
412 * @param x the x-coordinate in which to display the menu
413 * @param y the y-coordinate in which to display the menu
415 public void showSelectionPopup(Component c, int x, int y) {
416 selPopup.show(c, x, y);
420 * Draws the grid that is on the background.
421 * @param g The Graphics object used to draw the grid.
423 public void paintComponent(Graphics g) {
424 super.paintComponent(g);
425 Graphics2D g2 = (Graphics2D)g;
426 for (int c = 0; c < viewLength || c < getHeight(); c += NOTE_HEIGHT) {
427 for (int r = 0; r < (10 * NOTE_WIDTH); r += NOTE_WIDTH) {
428 box = new Rectangle(r, c, NOTE_WIDTH, NOTE_HEIGHT);
429 g2.setColor(Color.gray);
436 * Returns whether the left mouse button is currently pressed or not.
437 * @return true if the left mosue button is currently pressed
439 public boolean isLeftMouseButtonPressed() {
440 return leftMouseButtonPressed;
444 * The adapter used to listen on mouse actions
446 class MAdapter extends MouseAdapter {
449 * Deselects all note on click, adds a standard note on double click.
451 public void mouseClicked(MouseEvent e) {
452 if (SwingUtilities.isLeftMouseButton(e)) {
454 if (e.getClickCount() == 2) {
461 public void mousePressed(MouseEvent e) {
462 if (SwingUtilities.isLeftMouseButton(e)) leftMouseButtonPressed = true;
466 public void mouseReleased(MouseEvent e) {
467 if (SwingUtilities.isLeftMouseButton(e)) leftMouseButtonPressed = false;
472 * Shows the menu if an OS-specific popup-trigger was activated.
474 private void maybeShowPopup(MouseEvent e) {
475 if (e.isPopupTrigger()) {
477 popup.show(e.getComponent(), e.getX(), e.getY());
482 * Grabs the focus when the mouse has entered.
484 public void mouseEntered(MouseEvent e) {
485 Moosique.setActiveChannel(title.getChannel());
491 * Takes the appropriate action when a user selects an item on the popup menu.
493 class PopupListener implements ActionListener {
494 public void actionPerformed(ActionEvent e) {
495 Object source = e.getSource();
496 // Handling panel popup actions.
497 if (source == popupAdd) {
499 } else if (source == popupPaste) {
501 // Handling selection popup actions.
502 } else if (source == selPopupCopy) {
504 } else if (source == selPopupCut) {
506 } else if (source == selPopupRemove) {
507 removeSelectedNotes();
508 } else if (source == selPopupTranspUpItems[0]) {
509 transposeSelectedNotes(12);
510 } else if (source == selPopupTranspDownItems[0]) {
511 transposeSelectedNotes(-12);
513 for (int i = 1; i < 12; i++) {
514 if (source == selPopupTranspUpItems[i]) transposeSelectedNotes(i);
515 else if (source == selPopupTranspDownItems[i]) transposeSelectedNotes(-i);