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 Rectangle box;
20 private JPopupMenu popup, selPopup;
21 private JMenu selPopupTranspUp, selPopupTranspDown;
22 private JMenuItem popupAdd, popupPaste;
23 private JMenuItem selPopupCopy, selPopupCut, selPopupRemove, selPopupTranspUpOct, selPopupTranspDownOct;
25 private ArrayList coords, copyBuffer;
26 private TreeSet selection;
27 private Insets insets;
28 private int ticksPerSixteenth, popupY = 0;
29 private boolean leftMouseButtonPressed = false;
30 protected static int viewLength = 0;
31 protected static int extraHeight = 0;
32 public static final int NOTE_HEIGHT = 10, NOTE_WIDTH = 40, VIEW_WIDTH = 200;
35 * Creates the trackview.
36 * @param track The track it represents graphically and operates on.
37 * @param title The object that is used to manipulate instrument, channel, solo, mute.
39 public MooTrackView (Track track, MooTrackTitle title) {
44 // Creates instance variables
46 coords = new ArrayList(track.size() / 2);
47 selection = new TreeSet();
48 copyBuffer = new ArrayList();
50 // Creates temporary variables
53 extraHeight = Toolkit.getDefaultToolkit().getScreenSize().height - 150;
56 setBackground(Color.white);
57 setBorder(BorderFactory.createLineBorder(Color.black));
59 setPreferredSize(new Dimension(VIEW_WIDTH, 140 * NOTE_HEIGHT));
61 // Places note elements
62 for (int i = 0; i < track.size(); i++) {
64 if (note instanceof MooNote) {
65 // Adds the note element to the note area and moves it to the appropriate place.
66 MooNote mn = (MooNote)note;
67 elem = new MooNoteElement(this, mn);
69 layoutElement(elem, false);
71 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
75 // Creates panel pop-up menu.
76 popup = new JPopupMenu();
77 PopupListener pList = new PopupListener();
78 popupAdd = new JMenuItem("Add note...");
79 popupAdd.addActionListener(pList);
81 popupPaste = new JMenuItem("Paste");
82 popupPaste.addActionListener(pList);
83 popup.add(popupPaste);
85 // Creates selection pop-up menu.
86 selPopup = new JPopupMenu();
87 selPopupCopy = new JMenuItem("Copy selection");
88 selPopupCopy.addActionListener(pList);
89 selPopup.add(selPopupCopy);
90 selPopupCut = new JMenuItem("Cut selection");
91 selPopupCut.addActionListener(pList);
92 selPopup.add(selPopupCut);
93 selPopupRemove = new JMenuItem("Remove selection");
94 selPopupRemove.addActionListener(pList);
95 selPopup.add(selPopupRemove);
96 selPopupTranspUp = new JMenu("Transpose selection up");
97 selPopup.add(selPopupTranspUp);
98 selPopupTranspUpOct = new JMenuItem("One octave");
99 selPopupTranspUpOct.addActionListener(pList);
100 selPopupTranspUp.add(selPopupTranspUpOct);
101 selPopupTranspDown = new JMenu("Transpose selection down");
102 selPopup.add(selPopupTranspDown);
103 selPopupTranspDownOct = new JMenuItem("One octave");
104 selPopupTranspDownOct.addActionListener(pList);
105 selPopupTranspDown.add(selPopupTranspDownOct);
107 // Adds listeners for popup menu and keyboard synthesizer.
108 addMouseListener(new MAdapter());
109 addKeyListener(new MooKeyboard());
113 * Layouts the element to the right place.
114 * @param elem the element that will be layouted.
115 * @param old If true, this method will remove the old layout and set the new preferredSize for the trackview.
117 public void layoutElement(MooNoteElement elem, boolean old){
118 // If the element is currently in the view, removes its coordinates from the list.
119 Rectangle r = new Rectangle();
121 r = elem.getBounds(r);
122 for (Iterator i = coords.iterator(); i.hasNext();){
123 Object ob = i.next();
131 // Creates temporary variables.
132 ticksPerSixteenth = Moosique.getSequence().getResolution() / 4;
133 MooNote mn = elem.getNote();
136 // Calculates coordinates.
138 y = insets.top + Math.round(mn.getTick() / ticksPerSixteenth) * NOTE_HEIGHT;
139 height = (mn.getDuration() / ticksPerSixteenth) * NOTE_HEIGHT;
140 if (height == 0) height = NOTE_HEIGHT;
141 r = new Rectangle(x, y, NOTE_WIDTH, height);
143 // Places the element in the appropriate place.
144 while(isOccupied(r)) r.translate(NOTE_WIDTH, 0);
147 if (viewLength < (y + height)){
148 viewLength = y + height;
149 if(old)setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
158 * Returns the track of this view.
159 * @return the track of this view
161 public Track getTrack() {
166 * Returns the title of this view.
167 * @return the title of this view
169 public MooTrackTitle getTitle() {
174 * Checks if the element can be fully drawn as this position without inteferring with other elements.
175 * @return true if the position is occupied.
177 private boolean isOccupied(Rectangle r) {
178 Iterator it = coords.iterator();
179 while (it.hasNext()) {
180 if(r.intersects((Rectangle)it.next())) return true;
186 * Adds the given note to the current track, and visualises it.
187 * @param mn the note to add
189 public void addNote(MooNote mn) {
191 MooNoteElement elem = new MooNoteElement(this, mn);
193 layoutElement(elem, false);
194 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
195 Moosique.setEdited();
201 * Adds a standard note to this track.
203 private void addStandardNote() {
204 int row = (popupY - insets.top) / NOTE_HEIGHT;
205 long timestamp = (long)(ticksPerSixteenth * row);
206 addNote(new MooNote(title.getChannel(), 60, 100, timestamp, Moosique.getSequence().getResolution() / 4));
210 * Removes the given note element from the view and its note from the current track.
211 * @param elem the note element to remove
213 public void removeNote(MooNoteElement elem) {
214 elem.getNote().removeFrom(track);
216 Rectangle r = new Rectangle();
217 r = elem.getBounds(r);
219 Moosique.setEdited();
225 * Selects the given note
226 * @param the note to select
228 public void selectNote(MooNoteElement elem) {
233 * Deselects the given note
234 * @param the note to deselect
236 public void deselectNote(MooNoteElement elem) {
237 selection.remove(elem);
241 * Deselects all notes.
243 public void deselectAllNotes() {
244 Iterator it = selection.iterator();
245 while(it.hasNext()) {
246 ((MooNoteElement)it.next()).deselect();
252 * Determines if the given MooNoteElement is the only one in the track view that is selected.
253 * @return if the given element is the only selected one
255 public boolean isTheOnlySelected(MooNoteElement elem) {
256 Iterator it = selection.iterator();
257 while(it.hasNext()) {
258 if (!it.next().equals(elem)) return false;
264 * Copies the current selection.
266 public void copySelectedNotes() {
267 copyBuffer = new ArrayList(selection.size());
268 Iterator it = selection.iterator();
269 while(it.hasNext()) {
270 copyBuffer.add(((MooNoteElement)it.next()).getNote().clone());
272 Collections.sort(copyBuffer);
276 * Cuts the current selection.
278 public void cutSelectedNotes() {
280 removeSelectedNotes();
284 * Pastes the current copy buffer at the given timestamp.
286 public void pasteCopiedNotes() {
287 int row = (popupY - insets.top) / NOTE_HEIGHT;
288 long timestamp = (long)(ticksPerSixteenth * row);
289 if (copyBuffer.size() > 0) {
290 long startTime = ((MooNote)copyBuffer.get(0)).getTick();
291 Iterator it = copyBuffer.iterator();
292 while(it.hasNext()) {
293 MooNote mn = (MooNote)((MooNote)it.next()).clone();
294 mn.setTick(mn.getTick() - startTime + timestamp);
301 * Removes the current selection.
303 public void removeSelectedNotes() {
304 Iterator it = selection.iterator();
305 while(it.hasNext()) {
306 removeNote((MooNoteElement)it.next());
312 * Transposes all selected notes the given number of halftones.
314 private void transposeSelectedNotes(int halftones) {
315 Iterator it = selection.iterator();
316 while(it.hasNext()) {
317 MooNoteElement elem = (MooNoteElement)it.next();
318 elem.transpose(halftones);
323 * Moves the current selection the given number of ticks.
324 * @param ticks the number of ticks to move the selection.
326 public void moveSelectedNotes(int ticks) {
328 // If the selection should be moved downwards, traverses the list in the natural order.
329 Iterator it = selection.iterator();
330 while(it.hasNext()) {
331 MooNoteElement elem = (MooNoteElement)it.next();
332 elem.getNote().setTick(elem.getNote().getTick() + ticks);
333 layoutElement(elem, true);
336 // If the selection should be moved upwards, traverses the list in the opposite order.
337 ArrayList selectedList = new ArrayList(selection);
338 ListIterator it = selectedList.listIterator(selectedList.size());
339 while(it.hasPrevious()) {
340 MooNoteElement elem = (MooNoteElement)it.previous();
341 elem.getNote().setTick(elem.getNote().getTick() + ticks);
342 layoutElement(elem, true);
348 * Moves the current selection, depending on the given delta y.
349 * @param y the number of pixels the selection is moved.
351 public void maybeMoveSelectedNotes(int y) {
352 moveSelectedNotes(ticksPerSixteenth * (y / NOTE_HEIGHT));
356 * Shows a popup-menu with options for the current selection of note elements.
357 * @param c the component over which to display the menu
358 * @param x the x-coordinate in which to display the menu
359 * @param y the y-coordinate in which to display the menu
361 public void showSelectionPopup(Component c, int x, int y) {
362 selPopup.show(c, x, y);
366 * Draws the grid that is on the background.
367 * @param g The Graphics object used to draw the grid.
369 public void paintComponent(Graphics g) {
370 super.paintComponent(g);
371 Graphics2D g2 = (Graphics2D)g;
372 for (int c = 0; c < viewLength || c < getHeight(); c += NOTE_HEIGHT) {
373 for (int r = 0; r < (10 * NOTE_WIDTH); r += NOTE_WIDTH) {
374 box = new Rectangle(r, c, NOTE_WIDTH, NOTE_HEIGHT);
375 g2.setColor(Color.gray);
382 * Returns whether the left mouse button is currently pressed or not.
383 * @return true if the left mosue button is currently pressed
385 public boolean isLeftMouseButtonPressed() {
386 return leftMouseButtonPressed;
390 * The adapter used to listen on mouse actions
392 class MAdapter extends MouseAdapter {
395 * Deselects all note on click, adds a standard note on double click.
397 public void mouseClicked(MouseEvent e) {
398 if (SwingUtilities.isLeftMouseButton(e)) {
400 if (e.getClickCount() == 2) {
407 public void mousePressed(MouseEvent e) {
408 if (SwingUtilities.isLeftMouseButton(e)) leftMouseButtonPressed = true;
412 public void mouseReleased(MouseEvent e) {
413 if (SwingUtilities.isLeftMouseButton(e)) leftMouseButtonPressed = false;
418 * Shows the menu if an OS-specific popup-trigger was activated.
420 private void maybeShowPopup(MouseEvent e) {
421 if (e.isPopupTrigger()) {
423 popup.show(e.getComponent(), e.getX(), e.getY());
428 * Grabs the focus when the mouse has entered.
430 public void mouseEntered(MouseEvent e) {
431 Moosique.setActiveChannel(title.getChannel());
437 * Listens on actions on the popup menu and executes the appropriate action.
439 class PopupListener implements ActionListener {
440 public void actionPerformed(ActionEvent e) {
441 Object source = e.getSource();
442 // Handling panel popup actions.
443 if (source == popupAdd) {
445 } else if (source == popupPaste) {
447 // Handling selection popup actions.
448 } else if (source == selPopupCopy) {
450 } else if (source == selPopupCut) {
452 } else if (source == selPopupRemove) {
453 removeSelectedNotes();
454 } else if (source == selPopupTranspUpOct) {
455 transposeSelectedNotes(12);
456 } else if (source == selPopupTranspDownOct) {
457 transposeSelectedNotes(-12);