]> ruin.nu Git - moosique.git/blob - MooTrackView.java
Fixed the move function (still buggy tho)
[moosique.git] / MooTrackView.java
1 import javax.swing.*;
2 import java.awt.*;
3 import java.awt.event.*;
4 import javax.sound.midi.*;
5 import java.util.*;
6
7 /**
8  * Graphical representation of a MIDI track.
9  * 
10  * @author  Andersson , Andreen, Lanneskog, Pehrson
11  * @version 1
12  */
13
14 public class MooTrackView extends JPanel {
15
16         private Track track;
17         private MooTrackTitle title;
18         private Rectangle box;
19
20         private JPopupMenu popup, selPopup;
21         private JMenu selPopupTranspUp, selPopupTranspDown;
22         private JMenuItem popupAdd, popupPaste;
23         private JMenuItem selPopupCopy, selPopupCut, selPopupRemove, selPopupTranspUpOct, selPopupTranspDownOct;
24
25         private ArrayList coords, selection, copyBuffer; 
26         private Insets insets;
27         private int ticksPerSixteenth, popupY = 0;
28         private boolean leftMouseButtonPressed = false;
29         protected static int viewLength = 0;
30         protected static int extraHeight = 0;
31         public static final int NOTE_HEIGHT = 10, NOTE_WIDTH = 40, VIEW_WIDTH = 200;
32
33         /**
34          * Creates the trackview.
35          * @param track The track it represents graphically and operates on.
36          * @param title The object that is used to manipulate instrument, channel, solo, mute.
37          */
38         public MooTrackView (Track track, MooTrackTitle title) {
39                 super(true);
40                 this.track = track;
41                 this.title = title;
42
43                 // Creates instance variables
44                 insets = getInsets();
45                 coords = new ArrayList(track.size() / 2);
46                 selection = new ArrayList();
47                 copyBuffer = new ArrayList();
48
49                 // Creates temporary variables
50                 MidiEvent note;
51                 MooNoteElement elem;
52                 extraHeight = Toolkit.getDefaultToolkit().getScreenSize().height - 150;
53
54                 // Configures panel
55                 setBackground(Color.white);
56                 setBorder(BorderFactory.createLineBorder(Color.black));
57                 setLayout(null);
58                 setPreferredSize(new Dimension(VIEW_WIDTH, 140 * NOTE_HEIGHT));
59
60                 // Places note elements
61                 for (int i = 0; i < track.size(); i++) {
62                         note = track.get(i);
63                         if (note instanceof MooNote) {
64                                 // Adds the note element to the note area and moves it to the appropriate place.
65                                 MooNote mn = (MooNote)note;
66                                 elem = new MooNoteElement(this, mn);
67                                 add(elem);
68                                 layoutElement(elem, false);
69                         }
70                         setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
71
72                 }
73
74                 // Creates panel pop-up menu.
75                 popup = new JPopupMenu();
76                 PopupListener pList = new PopupListener();
77                 popupAdd = new JMenuItem("Add note...");
78                 popupAdd.addActionListener(pList);
79                 popup.add(popupAdd);
80                 popupPaste = new JMenuItem("Paste");
81                 popupPaste.addActionListener(pList);
82                 popup.add(popupPaste);
83
84                 // Creates selection pop-up menu.
85                 selPopup = new JPopupMenu();
86                 selPopupCopy = new JMenuItem("Copy selection");
87                 selPopupCopy.addActionListener(pList);
88                 selPopup.add(selPopupCopy);
89                 selPopupCut = new JMenuItem("Cut selection");
90                 selPopupCut.addActionListener(pList);
91                 selPopup.add(selPopupCut);
92                 selPopupRemove = new JMenuItem("Remove selection");
93                 selPopupRemove.addActionListener(pList);
94                 selPopup.add(selPopupRemove);
95                 selPopupTranspUp = new JMenu("Transpose selection up");
96                 selPopup.add(selPopupTranspUp);
97                 selPopupTranspUpOct = new JMenuItem("One octave");
98                 selPopupTranspUpOct.addActionListener(pList);
99                 selPopupTranspUp.add(selPopupTranspUpOct);
100                 selPopupTranspDown = new JMenu("Transpose selection down");
101                 selPopup.add(selPopupTranspDown);
102                 selPopupTranspDownOct = new JMenuItem("One octave");
103                 selPopupTranspDownOct.addActionListener(pList);
104                 selPopupTranspDown.add(selPopupTranspDownOct);
105
106                 // Adds listeners for popup menu and keyboard synthesizer.
107                 addMouseListener(new MAdapter());
108                 addKeyListener(new MooKeyboard());
109         }
110
111         /**
112          * Layouts the element to the right place.
113          * @param elem  the element that will be layouted.
114          * @param old   If true, this method will remove the old layout and set the new preferredSize for the trackview.
115          */
116         public void layoutElement(MooNoteElement elem, boolean old){
117                 // If the element is currently in the view, removes its coordinates from the list.
118                 Rectangle r = new Rectangle();
119                 if (old){
120                         r = elem.getBounds(r);
121                         for (Iterator i = coords.iterator(); i.hasNext();){
122                                 Object ob = i.next();
123                                 if (r.equals(ob)){
124                                         coords.remove(ob);
125                                         break;
126                                 }
127                         }
128                 }
129
130                 // Creates temporary variables.
131                 ticksPerSixteenth = Moosique.getSequence().getResolution() / 4;
132                 MooNote mn = elem.getNote();
133                 int x, y, height;
134
135                 // Calculates coordinates.
136                 x = insets.left;
137                 y = insets.top + Math.round(mn.getTick() / ticksPerSixteenth) * NOTE_HEIGHT;
138                 height = (mn.getDuration() / ticksPerSixteenth) * NOTE_HEIGHT;
139                 if (height == 0) height = NOTE_HEIGHT;
140                 r = new Rectangle(x, y, NOTE_WIDTH, height);
141
142                 // Places the element in the appropriate place.
143                 while(isOccupied(r)) r.translate(NOTE_WIDTH, 0);
144                 elem.setBounds(r);
145                 coords.add(r);
146                 if (viewLength < (y + height)){
147                         viewLength = y + height;
148                         if(old)setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
149                 }
150                 if (old) {
151                         validate();
152                         repaint();
153                 }
154         }
155
156         /** 
157          * Returns the track of this view.
158          * @return the track of this view
159          */
160         public Track getTrack() {
161                 return track;
162         }
163
164         /** 
165          * Returns the title of this view.
166          * @return the title of this view
167          */
168         public MooTrackTitle getTitle() {
169                 return title;
170         }
171
172         /**
173          * Checks if the element can be fully drawn as this position without inteferring with other elements.
174          * @return true if the position is occupied.
175          */
176         private boolean isOccupied(Rectangle r) {
177                 Iterator it = coords.iterator();
178                 while (it.hasNext()) {
179                         if(r.intersects((Rectangle)it.next())) return true;
180                 }
181                 return false;
182         }
183         
184         /** 
185          * Adds the given note to the current track, and visualises it.
186          * @param mn    the note to add
187          */
188         public void addNote(MooNote mn) {
189                 mn.addTo(track);
190                 MooNoteElement elem = new MooNoteElement(this, mn);
191                 add(elem);
192                 layoutElement(elem, false);
193                 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
194                 Moosique.setEdited();
195                 validate();
196                 repaint();
197         }
198
199         /**
200          * Adds a standard note to this track.
201          */
202         private void addStandardNote() {
203                 int row = (popupY - insets.top) / NOTE_HEIGHT;
204                 long timestamp = (long)(ticksPerSixteenth * row);
205                 addNote(new MooNote(title.getChannel(), 60, 100, timestamp, Moosique.getSequence().getResolution() / 4));
206         }
207
208         /** 
209          * Removes the given note element from the view and its note from the current track.
210          * @param elem  the note element to remove
211          */
212         public void removeNote(MooNoteElement elem) {
213                 elem.getNote().removeFrom(track);
214                 remove(elem);
215                 Rectangle r = new Rectangle();
216                 r = elem.getBounds(r);
217                 coords.remove(r);
218                 Moosique.setEdited();
219                 validate();
220                 repaint();
221         }
222
223         /**
224          * Selects the given note
225          * @param the note to select
226          */
227         public void selectNote(MooNoteElement elem) {
228                 selection.add(elem);
229         }
230
231         /**
232          * Deselects the given note
233          * @param the note to deselect
234          */
235         public void deselectNote(MooNoteElement elem) {
236                 selection.remove(selection.indexOf(elem));
237         }
238
239         /**
240          * Deselects all notes.
241          */
242         public void deselectAllNotes() {
243                 Iterator it = selection.iterator();
244                 while(it.hasNext()) {
245                         ((MooNoteElement)it.next()).deselect();
246                 }
247                 selection.clear();
248         }
249
250         /**
251          * Determines if the given MooNoteElement is the only one in the track view that is selected.
252          * @return if the given element is the only selected one
253          */
254         public boolean isTheOnlySelected(MooNoteElement elem) {
255                 Iterator it = selection.iterator();
256                 while(it.hasNext()) {
257                         if (!it.next().equals(elem)) return false;
258                 }
259                 return true;
260         }
261
262         /**
263          * Copies the current selection.
264          */
265         public void copySelectedNotes() {
266                 copyBuffer = new ArrayList(selection.size());
267                 Iterator it = selection.iterator();
268                 while(it.hasNext()) {
269                         copyBuffer.add(((MooNoteElement)it.next()).getNote().clone());
270                 }
271                 Collections.sort(copyBuffer, new Moosique.NoteComparator());
272         }
273
274         /**
275          * Cuts the current selection.
276          */
277         public void cutSelectedNotes() {
278                 copySelectedNotes();
279                 removeSelectedNotes();
280         }
281
282         /**
283          * Pastes the current copy buffer at the given timestamp.
284          */
285         public void pasteCopiedNotes() {
286                 int row = (popupY - insets.top) / NOTE_HEIGHT;
287                 long timestamp = (long)(ticksPerSixteenth * row);
288                 if (copyBuffer.size() > 0) {
289                         long startTime = ((MooNote)copyBuffer.get(0)).getTick();
290                         Iterator it = copyBuffer.iterator();
291                         while(it.hasNext()) {
292                                 MooNote mn = (MooNote)((MooNote)it.next()).clone();
293                                 mn.setTick(mn.getTick() - startTime + timestamp);
294                                 addNote(mn);
295                         }
296                 }
297         }
298
299         /**
300          * Removes the current selection.
301          */
302         public void removeSelectedNotes() {
303                 Iterator it = selection.iterator();
304                 while(it.hasNext()) {
305                         removeNote((MooNoteElement)it.next());
306                 }
307                 selection.clear();
308         }
309
310         /**
311          * Transposes all selected notes the given number of halftones.
312          */
313         private void transposeSelectedNotes(int halftones) {
314                 Iterator it = selection.iterator();
315                 while(it.hasNext()) {
316                         MooNoteElement elem = (MooNoteElement)it.next();
317                         elem.transpose(halftones);
318                 }
319         }
320
321         /**
322          * Moves the current selection, if the mouse was pressed on a note element
323          * and then released in another row.
324          * @param srcY  the y-coordinate of the point in which the mouse button was pressed
325          * @param destY the y-coordinate of the point in which the mouse button was released
326          */
327         public void maybeMoveSelectedNotes(int srcY, int destY) {
328                 int srcRow = (srcY - insets.top) / NOTE_HEIGHT;
329                 int destRow = (destY - insets.top) / NOTE_HEIGHT;
330                 long timeDiff = (long)(ticksPerSixteenth * (destRow - srcRow));
331                 Iterator it = selection.iterator();
332                 while(it.hasNext()) {
333                         MooNoteElement elem = (MooNoteElement)it.next();
334                         elem.getNote().setTick(elem.getNote().getTick() + timeDiff);
335                         layoutElement(elem, true);
336                 }
337         }
338
339         /**
340          * Shows a popup-menu with options for the current selection of note elements.
341          * @param c     the component over which to display the menu
342          * @param x     the x-coordinate in which to display the menu
343          * @param y     the y-coordinate in which to display the menu
344          */
345         public void showSelectionPopup(Component c, int x, int y) {
346                 selPopup.show(c, x, y);
347         }
348
349         /**
350          * Draws the grid that is on the background.
351          * @param g The Graphics object used to draw the grid.
352          */
353         public void paintComponent(Graphics g) {
354                 super.paintComponent(g);
355                 Graphics2D g2 = (Graphics2D)g;
356                 for (int c = 0; c < viewLength || c < getHeight(); c += NOTE_HEIGHT) {
357                         for (int r = 0; r < (10 * NOTE_WIDTH); r += NOTE_WIDTH) {
358                                 box = new Rectangle(r, c, NOTE_WIDTH, NOTE_HEIGHT);
359                                 g2.setColor(Color.gray);
360                                 g2.draw(box);
361                         }
362                 }
363         }
364         
365         /**
366          * Returns whether the left mouse button is currently pressed or not.
367          * @return true if the left mosue button is currently pressed
368          */
369         public boolean isLeftMouseButtonPressed() {
370                 return leftMouseButtonPressed;
371         }
372
373         /**
374          * The adapter used to listen on mouse actions
375          */
376         class MAdapter extends MouseAdapter {
377
378                 /**
379                  * Deselects all note on click, adds a standard note on double click.
380                  */
381                 public void mouseClicked(MouseEvent e) {
382                         if (SwingUtilities.isLeftMouseButton(e)) {
383                                 deselectAllNotes();
384                                 if (e.getClickCount() == 2) {
385                                         popupY = e.getY();
386                                         addStandardNote();
387                                 }
388                         }
389                 }
390         
391                 public void mousePressed(MouseEvent e) {
392                         if (SwingUtilities.isLeftMouseButton(e)) leftMouseButtonPressed = true;
393                         maybeShowPopup(e);
394                 }
395
396                 public void mouseReleased(MouseEvent e) {
397                         if (SwingUtilities.isLeftMouseButton(e)) leftMouseButtonPressed = false;
398                         maybeShowPopup(e);
399                 }
400
401                 /**
402                  * Shows the menu if an OS-specific popup-trigger was activated.
403                  */
404                 private void maybeShowPopup(MouseEvent e) {
405                         if (e.isPopupTrigger()) {
406                                 popupY = e.getY();
407                                 popup.show(e.getComponent(), e.getX(), e.getY());
408                         }
409                 }
410
411                 /**
412                  * Grabs the focus when the mouse has entered.
413                  */
414                 public void mouseEntered(MouseEvent e) {
415                         Moosique.setActiveChannel(title.getChannel());
416                         grabFocus();
417                 }
418         }
419
420         /**
421          * Listens on actions on the popup menu and executes the appropriate action.
422          */
423         class PopupListener implements ActionListener {
424                 public void actionPerformed(ActionEvent e) {
425                         Object source = e.getSource();
426                         // Handling panel popup actions.
427                         if (source == popupAdd) {
428                                 addStandardNote();
429                         } else if (source == popupPaste) {
430                                 pasteCopiedNotes();
431                         // Handling selection popup actions.
432                         } else if (source == selPopupCopy) {
433                                 copySelectedNotes();
434                         } else if (source == selPopupCut) {
435                                 cutSelectedNotes();
436                         } else if (source == selPopupRemove) {
437                                 removeSelectedNotes();
438                         } else if (source == selPopupTranspUpOct) {
439                                 transposeSelectedNotes(12);
440                         } else if (source == selPopupTranspDownOct) {
441                                 transposeSelectedNotes(-12);
442                         }
443                 }
444         }
445 }