]> ruin.nu Git - moosique.git/blob - MooTrackView.java
bf3590aedc59e2f25663b367beb3f85aef0763cd
[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         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;
31
32         /**
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.
36          */
37         public MooTrackView (Track track, MooTrackTitle title) {
38                 super(true);
39                 this.track = track;
40                 this.title = title;
41
42                 // Creates instance variables
43                 insets = getInsets();
44                 coords = new ArrayList(track.size() / 2);
45                 selection = new ArrayList();
46                 copyBuffer = new ArrayList();
47
48                 // Creates temporary variables
49                 MidiEvent note;
50                 MooNoteElement elem;
51                 extraHeight = Toolkit.getDefaultToolkit().getScreenSize().height - 150;
52
53                 // Configures panel
54                 setBackground(Color.white);
55                 setBorder(BorderFactory.createLineBorder(Color.black));
56                 setLayout(null);
57                 setPreferredSize(new Dimension(VIEW_WIDTH, 140 * NOTE_HEIGHT));
58
59                 // Places note elements
60                 for (int i = 0; i < track.size(); i++) {
61                         note = track.get(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);
66                                 add(elem);
67                                 layoutElement(elem, false);
68                         }
69                         setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
70
71                 }
72
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);
78                 popup.add(popupAdd);
79                 popupPaste = new JMenuItem("Paste");
80                 popupPaste.addActionListener(pList);
81                 popup.add(popupPaste);
82
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);
104
105                 // Adds listeners for popup menu and keyboard synthesizer.
106                 addMouseListener(new MAdapter());
107                 addKeyListener(new MooKeyboard());
108         }
109
110         /**
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.
114          */
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();
118                 if (old){
119                         r = elem.getBounds(r);
120                         for (Iterator i = coords.iterator(); i.hasNext();){
121                                 Object ob = i.next();
122                                 if (r.equals(ob)){
123                                         coords.remove(ob);
124                                         break;
125                                 }
126                         }
127                 }
128
129                 // Creates temporary variables.
130                 ticksPerSixteenth = Moosique.getSequence().getResolution() / 4;
131                 MooNote mn = elem.getNote();
132                 int x, y, height;
133
134                 // Calculates coordinates.
135                 x = insets.left;
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);
140
141                 // Places the element in the appropriate place.
142                 while(isOccupied(r)) r.translate(NOTE_WIDTH, 0);
143                 elem.setBounds(r);
144                 coords.add(r);
145                 if (viewLength < (y + height)){
146                         viewLength = y + height;
147                         if(old)setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
148                 }
149                 if (old) {
150                         validate();
151                         repaint();
152                 }
153         }
154
155         /** 
156          * Returns the track of this view.
157          * @return the track of this view
158          */
159         public Track getTrack() {
160                 return track;
161         }
162
163         /** 
164          * Returns the title of this view.
165          * @return the title of this view
166          */
167         public MooTrackTitle getTitle() {
168                 return title;
169         }
170
171         /**
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.
174          */
175         private boolean isOccupied(Rectangle r) {
176                 Iterator it = coords.iterator();
177                 while (it.hasNext()) {
178                         if(r.intersects((Rectangle)it.next())) return true;
179                 }
180                 return false;
181         }
182         
183         /** 
184          * Adds the given note to the current track, and visualises it.
185          * @param mn    the note to add
186          */
187         public void addNote(MooNote mn) {
188                 mn.addTo(track);
189                 MooNoteElement elem = new MooNoteElement(this, mn);
190                 add(elem);
191                 layoutElement(elem, false);
192                 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
193                 Moosique.setEdited();
194                 validate();
195                 repaint();
196         }
197
198         /**
199          * Adds a standard note to this track.
200          */
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));
205         }
206
207         /** 
208          * Removes the given note element from the view and its note from the current track.
209          * @param elem  the note element to remove
210          */
211         public void removeNote(MooNoteElement elem) {
212                 elem.getNote().removeFrom(track);
213                 remove(elem);
214                 Rectangle r = new Rectangle();
215                 r = elem.getBounds(r);
216                 coords.remove(r);
217                 Moosique.setEdited();
218                 validate();
219                 repaint();
220         }
221
222         /**
223          * Selects the given note
224          * @param the note to select
225          */
226         public void selectNote(MooNoteElement elem) {
227                 selection.add(elem);
228         }
229
230         /**
231          * Deselects the given note
232          * @param the note to deselect
233          */
234         public void deselectNote(MooNoteElement elem) {
235                 selection.remove(selection.indexOf(elem));
236         }
237
238         /**
239          * Deselects all notes.
240          */
241         public void deselectAllNotes() {
242                 Iterator it = selection.iterator();
243                 while(it.hasNext()) {
244                         ((MooNoteElement)it.next()).deselect();
245                 }
246                 selection.clear();
247         }
248
249         /**
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
252          */
253         public boolean isTheOnlySelected(MooNoteElement elem) {
254                 Iterator it = selection.iterator();
255                 while(it.hasNext()) {
256                         if (!it.next().equals(elem)) return false;
257                 }
258                 return true;
259         }
260
261         /**
262          * Copies the current selection.
263          */
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());
269                 }
270                 Collections.sort(copyBuffer, new Moosique.NoteComparator());
271         }
272
273         /**
274          * Cuts the current selection.
275          */
276         public void cutSelectedNotes() {
277                 copySelectedNotes();
278                 removeSelectedNotes();
279         }
280
281         /**
282          * Pastes the current copy buffer at the given timestamp.
283          */
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);
293                                 addNote(mn);
294                         }
295                 }
296         }
297
298         /**
299          * Removes the current selection.
300          */
301         public void removeSelectedNotes() {
302                 Iterator it = selection.iterator();
303                 while(it.hasNext()) {
304                         removeNote((MooNoteElement)it.next());
305                 }
306                 selection.clear();
307         }
308
309         /**
310          * Transposes all selected notes the given number of halftones.
311          */
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);
317                 }
318         }
319
320         /**
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
325          */
326         public void showSelectionPopup(Component c, int x, int y) {
327                 selPopup.show(c, x, y);
328         }
329
330         /**
331          * Draws the grid that is on the background.
332          * @param g The Graphics object used to draw the grid.
333          */
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);
341                                 g2.draw(box);
342                         }
343                 }
344         }
345         
346         /**
347          * The adapter used to listen on mouse actions
348          */
349         class MAdapter extends MouseAdapter {
350
351                 /**
352                  * Deselects all note on click, adds a standard note on double click.
353                  */
354                 public void mouseClicked(MouseEvent e) {
355                         if (SwingUtilities.isLeftMouseButton(e)) {
356                                 deselectAllNotes();
357                                 if (e.getClickCount() == 2) {
358                                         popupY = e.getY();
359                                         addStandardNote();
360                                 }
361                         }
362                 }
363         
364                 public void mousePressed(MouseEvent e) {
365                         maybeShowPopup(e);
366                 }
367
368                 public void mouseReleased(MouseEvent e) {
369                         maybeShowPopup(e);
370                 }
371
372                 /**
373                  * Selects the notes within the area that was selected.
374                  */
375                 public void mouseDragged(MouseEvent e) {
376                         
377                 }
378
379                 /**
380                  * Shows the menu if an OS-specific popup-trigger was activated.
381                  */
382                 private void maybeShowPopup(MouseEvent e) {
383                         if (e.isPopupTrigger()) {
384                                 popupY = e.getY();
385                                 popup.show(e.getComponent(), e.getX(), e.getY());
386                         }
387                 }
388
389                 /**
390                  * Grabs the focus when the mouse has entered.
391                  */
392                 public void mouseEntered(MouseEvent e) {
393                         // Moosique.setActiveChannel(track.getChannel());
394                         grabFocus();
395                 }
396         }
397
398         /**
399          * Listens on actions on the popup menu and executes the appropriate action.
400          */
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) {
406                                 addStandardNote();
407                         } else if (source == popupPaste) {
408                                 pasteCopiedNotes();
409                         // Handling selection popup actions.
410                         } else if (source == selPopupCopy) {
411                                 copySelectedNotes();
412                         } else if (source == selPopupCut) {
413                                 cutSelectedNotes();
414                         } else if (source == selPopupRemove) {
415                                 removeSelectedNotes();
416                         } else if (source == selPopupTranspUpOct) {
417                                 transposeSelectedNotes(12);
418                         } else if (source == selPopupTranspDownOct) {
419                                 transposeSelectedNotes(-12);
420                         }
421                 }
422         }
423 }