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, selection, copyBuffer;
26 private Insets insets;
27 private int ticksPerSixteenth, popupY = 0;
28 protected static int viewLength = 0;
29 protected static int extraHeight = 0;
30 public static final int NOTE_HEIGHT = 10, NOTE_WIDTH = 40, VIEW_WIDTH = 200;
33 * Creates the trackview.
34 * @param track The track it represents graphically and operates on.
35 * @param title The object that is used to manipulate instrument, channel, solo, mute.
37 public MooTrackView (Track track, MooTrackTitle title) {
42 // Creates instance variables
44 coords = new ArrayList(track.size() / 2);
45 selection = new ArrayList();
46 copyBuffer = new ArrayList();
48 // Creates temporary variables
51 extraHeight = Toolkit.getDefaultToolkit().getScreenSize().height - 150;
54 setBackground(Color.white);
55 setBorder(BorderFactory.createLineBorder(Color.black));
57 setPreferredSize(new Dimension(VIEW_WIDTH, 140 * NOTE_HEIGHT));
59 // Places note elements
60 for (int i = 0; i < track.size(); i++) {
62 if (note instanceof MooNote) {
63 // Adds the note element to the note area and moves it to the appropriate place.
64 MooNote mn = (MooNote)note;
65 elem = new MooNoteElement(this, mn);
67 layoutElement(elem, false);
69 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
73 // Creates panel pop-up menu.
74 popup = new JPopupMenu();
75 PopupListener pList = new PopupListener();
76 popupAdd = new JMenuItem("Add note...");
77 popupAdd.addActionListener(pList);
79 popupPaste = new JMenuItem("Paste");
80 popupPaste.addActionListener(pList);
81 popup.add(popupPaste);
83 // Creates selection pop-up menu.
84 selPopup = new JPopupMenu();
85 selPopupCopy = new JMenuItem("Copy selection");
86 selPopupCopy.addActionListener(pList);
87 selPopup.add(selPopupCopy);
88 selPopupCut = new JMenuItem("Cut selection");
89 selPopupCut.addActionListener(pList);
90 selPopup.add(selPopupCut);
91 selPopupRemove = new JMenuItem("Remove selection");
92 selPopupRemove.addActionListener(pList);
93 selPopup.add(selPopupRemove);
94 selPopupTranspUp = new JMenu("Transpose selection up");
95 selPopup.add(selPopupTranspUp);
96 selPopupTranspUpOct = new JMenuItem("One octave");
97 selPopupTranspUpOct.addActionListener(pList);
98 selPopupTranspUp.add(selPopupTranspUpOct);
99 selPopupTranspDown = new JMenu("Transpose selection down");
100 selPopup.add(selPopupTranspDown);
101 selPopupTranspDownOct = new JMenuItem("One octave");
102 selPopupTranspDownOct.addActionListener(pList);
103 selPopupTranspDown.add(selPopupTranspDownOct);
105 // Adds listeners for popup menu and keyboard synthesizer.
106 addMouseListener(new MAdapter());
107 addKeyListener(new MooKeyboard());
111 * Layouts the element to the right place.
112 * @param elem the element that will be layouted.
113 * @param old If true, this method will remove the old layout and set the new preferredSize for the trackview.
115 public void layoutElement(MooNoteElement elem, boolean old){
116 // If the element is currently in the view, removes its coordinates from the list.
117 Rectangle r = new Rectangle();
119 r = elem.getBounds(r);
120 for (Iterator i = coords.iterator(); i.hasNext();){
121 Object ob = i.next();
129 // Creates temporary variables.
130 ticksPerSixteenth = Moosique.getSequence().getResolution() / 4;
131 MooNote mn = elem.getNote();
134 // Calculates coordinates.
136 y = insets.top + (int)(mn.getTick() / ticksPerSixteenth) * NOTE_HEIGHT;
137 height = (mn.getDuration() / ticksPerSixteenth) * NOTE_HEIGHT;
138 if (height == 0) height = NOTE_HEIGHT;
139 r = new Rectangle(x, y, NOTE_WIDTH, height);
141 // Places the element in the appropriate place.
142 while(isOccupied(r)) r.translate(NOTE_WIDTH, 0);
145 if (viewLength < (y + height)){
146 viewLength = y + height;
147 if(old)setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
156 * Returns the track of this view.
157 * @return the track of this view
159 public Track getTrack() {
164 * Returns the title of this view.
165 * @return the title of this view
167 public MooTrackTitle getTitle() {
172 * Checks if the element can be fully drawn as this position without inteferring with other elements.
173 * @return true if the position is occupied.
175 private boolean isOccupied(Rectangle r) {
176 Iterator it = coords.iterator();
177 while (it.hasNext()) {
178 if(r.intersects((Rectangle)it.next())) return true;
184 * Adds the given note to the current track, and visualises it.
185 * @param mn the note to add
187 public void addNote(MooNote mn) {
189 MooNoteElement elem = new MooNoteElement(this, mn);
191 layoutElement(elem, false);
192 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
193 Moosique.setEdited();
199 * Adds a standard note to this track.
201 private void addStandardNote() {
202 int row = (popupY - insets.top) / NOTE_HEIGHT;
203 long timestamp = (long)(ticksPerSixteenth * row);
204 addNote(new MooNote(title.getChannel(), 60, 100, timestamp, Moosique.getSequence().getResolution() / 4));
208 * Removes the given note element from the view and its note from the current track.
209 * @param elem the note element to remove
211 public void removeNote(MooNoteElement elem) {
212 elem.getNote().removeFrom(track);
214 Rectangle r = new Rectangle();
215 r = elem.getBounds(r);
217 Moosique.setEdited();
223 * Selects the given note
224 * @param the note to select
226 public void selectNote(MooNoteElement elem) {
231 * Deselects the given note
232 * @param the note to deselect
234 public void deselectNote(MooNoteElement elem) {
235 selection.remove(selection.indexOf(elem));
239 * Deselects all notes.
241 public void deselectAllNotes() {
242 Iterator it = selection.iterator();
243 while(it.hasNext()) {
244 ((MooNoteElement)it.next()).deselect();
250 * Determines if the given MooNoteElement is the only one in the track view that is selected.
251 * @return if the given element is the only selected one
253 public boolean isTheOnlySelected(MooNoteElement elem) {
254 Iterator it = selection.iterator();
255 while(it.hasNext()) {
256 if (!it.next().equals(elem)) return false;
262 * Copies the current selection.
264 public void copySelectedNotes() {
265 copyBuffer = new ArrayList(selection.size());
266 Iterator it = selection.iterator();
267 while(it.hasNext()) {
268 copyBuffer.add(((MooNoteElement)it.next()).getNote().clone());
270 Collections.sort(copyBuffer, new Moosique.NoteComparator());
274 * Cuts the current selection.
276 public void cutSelectedNotes() {
278 removeSelectedNotes();
282 * Pastes the current copy buffer at the given timestamp.
284 public void pasteCopiedNotes() {
285 int row = (popupY - insets.top) / NOTE_HEIGHT;
286 long timestamp = (long)(ticksPerSixteenth * row);
287 if (copyBuffer.size() > 0) {
288 long startTime = ((MooNote)copyBuffer.get(0)).getTick();
289 Iterator it = copyBuffer.iterator();
290 while(it.hasNext()) {
291 MooNote mn = (MooNote)((MooNote)it.next()).clone();
292 mn.setTick(mn.getTick() - startTime + timestamp);
299 * Removes the current selection.
301 public void removeSelectedNotes() {
302 Iterator it = selection.iterator();
303 while(it.hasNext()) {
304 removeNote((MooNoteElement)it.next());
310 * Transposes all selected notes the given number of halftones.
312 private void transposeSelectedNotes(int halftones) {
313 Iterator it = selection.iterator();
314 while(it.hasNext()) {
315 MooNoteElement elem = (MooNoteElement)it.next();
316 elem.transpose(halftones);
321 * Shows a popup-menu with options for the current selection of note elements.
322 * @param c the component over which to display the menu
323 * @param x the x-coordinate in which to display the menu
324 * @param y the y-coordinate in which to display the menu
326 public void showSelectionPopup(Component c, int x, int y) {
327 selPopup.show(c, x, y);
331 * Draws the grid that is on the background.
332 * @param g The Graphics object used to draw the grid.
334 public void paintComponent(Graphics g) {
335 super.paintComponent(g);
336 Graphics2D g2 = (Graphics2D)g;
337 for (int c = 0; c < viewLength || c < getHeight(); c += NOTE_HEIGHT) {
338 for (int r = 0; r < (10 * NOTE_WIDTH); r += NOTE_WIDTH) {
339 box = new Rectangle(r, c, NOTE_WIDTH, NOTE_HEIGHT);
340 g2.setColor(Color.gray);
347 * The adapter used to listen on mouse actions
349 class MAdapter extends MouseAdapter {
352 * Deselects all note on click, adds a standard note on double click.
354 public void mouseClicked(MouseEvent e) {
355 if (SwingUtilities.isLeftMouseButton(e)) {
357 if (e.getClickCount() == 2) {
364 public void mousePressed(MouseEvent e) {
368 public void mouseReleased(MouseEvent e) {
373 * Selects the notes within the area that was selected.
375 public void mouseDragged(MouseEvent e) {
380 * Shows the menu if an OS-specific popup-trigger was activated.
382 private void maybeShowPopup(MouseEvent e) {
383 if (e.isPopupTrigger()) {
385 popup.show(e.getComponent(), e.getX(), e.getY());
390 * Grabs the focus when the mouse has entered.
392 public void mouseEntered(MouseEvent e) {
393 // Moosique.setActiveChannel(track.getChannel());
399 * Listens on actions on the popup menu and executes the appropriate action.
401 class PopupListener implements ActionListener {
402 public void actionPerformed(ActionEvent e) {
403 Object source = e.getSource();
404 // Handling panel popup actions.
405 if (source == popupAdd) {
407 } else if (source == popupPaste) {
409 // Handling selection popup actions.
410 } else if (source == selPopupCopy) {
412 } else if (source == selPopupCut) {
414 } else if (source == selPopupRemove) {
415 removeSelectedNotes();
416 } else if (source == selPopupTranspUpOct) {
417 transposeSelectedNotes(12);
418 } else if (source == selPopupTranspDownOct) {
419 transposeSelectedNotes(-12);