]> ruin.nu Git - moosique.git/blob - MooTrackView.java
*** empty log message ***
[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, 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;
33
34         /**
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.
38          */
39         public MooTrackView (Track track, MooTrackTitle title) {
40                 super(true);
41                 this.track = track;
42                 this.title = title;
43
44                 // Creates instance variables
45                 insets = getInsets();
46                 coords = new ArrayList(track.size() / 2);
47                 selection = new TreeSet();
48                 copyBuffer = new ArrayList();
49
50                 // Creates temporary variables
51                 MidiEvent note;
52                 MooNoteElement elem;
53                 extraHeight = Toolkit.getDefaultToolkit().getScreenSize().height - 150;
54
55                 // Configures panel
56                 setBackground(Color.white);
57                 setBorder(BorderFactory.createLineBorder(Color.black));
58                 setLayout(null);
59                 setPreferredSize(new Dimension(VIEW_WIDTH, 140 * NOTE_HEIGHT));
60
61                 // Places note elements
62                 for (int i = 0; i < track.size(); i++) {
63                         note = track.get(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);
68                                 add(elem);
69                                 layoutElement(elem, false);
70                         }
71                         setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
72
73                 }
74
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);
80                 popup.add(popupAdd);
81                 popupPaste = new JMenuItem("Paste");
82                 popupPaste.addActionListener(pList);
83                 popup.add(popupPaste);
84
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);
106
107                 // Adds listeners for popup menu and keyboard synthesizer.
108                 addMouseListener(new MAdapter());
109                 addKeyListener(new MooKeyboard());
110         }
111
112         /**
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.
116          */
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();
120                 if (old){
121                         r = elem.getBounds(r);
122                         for (Iterator i = coords.iterator(); i.hasNext();){
123                                 Object ob = i.next();
124                                 if (r.equals(ob)){
125                                         coords.remove(ob);
126                                         break;
127                                 }
128                         }
129                 }
130
131                 // Creates temporary variables.
132                 ticksPerSixteenth = Moosique.getSequence().getResolution() / 4;
133                 MooNote mn = elem.getNote();
134                 int x, y, height;
135
136                 // Calculates coordinates.
137                 x = insets.left;
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);
142
143                 // Places the element in the appropriate place.
144                 while(isOccupied(r)) r.translate(NOTE_WIDTH, 0);
145                 elem.setBounds(r);
146                 coords.add(r);
147                 if (viewLength < (y + height)){
148                         viewLength = y + height;
149                         if(old)setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
150                 }
151                 if (old) {
152                         validate();
153                         repaint();
154                 }
155         }
156
157         /** 
158          * Returns the track of this view.
159          * @return the track of this view
160          */
161         public Track getTrack() {
162                 return track;
163         }
164
165         /** 
166          * Returns the title of this view.
167          * @return the title of this view
168          */
169         public MooTrackTitle getTitle() {
170                 return title;
171         }
172
173         /**
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.
176          */
177         private boolean isOccupied(Rectangle r) {
178                 Iterator it = coords.iterator();
179                 while (it.hasNext()) {
180                         if(r.intersects((Rectangle)it.next())) return true;
181                 }
182                 return false;
183         }
184         
185         /** 
186          * Adds the given note to the current track, and visualises it.
187          * @param mn    the note to add
188          */
189         public void addNote(MooNote mn) {
190                 mn.addTo(track);
191                 MooNoteElement elem = new MooNoteElement(this, mn);
192                 add(elem);
193                 layoutElement(elem, false);
194                 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
195                 Moosique.setEdited();
196                 validate();
197                 repaint();
198         }
199
200         /**
201          * Adds a standard note to this track.
202          */
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));
207         }
208
209         /** 
210          * Removes the given note element from the view and its note from the current track.
211          * @param elem  the note element to remove
212          */
213         public void removeNote(MooNoteElement elem) {
214                 elem.getNote().removeFrom(track);
215                 remove(elem);
216                 Rectangle r = new Rectangle();
217                 r = elem.getBounds(r);
218                 coords.remove(r);
219                 Moosique.setEdited();
220                 validate();
221                 repaint();
222         }
223
224         /**
225          * Selects the given note
226          * @param the note to select
227          */
228         public void selectNote(MooNoteElement elem) {
229                 selection.add(elem);
230         }
231
232         /**
233          * Deselects the given note
234          * @param the note to deselect
235          */
236         public void deselectNote(MooNoteElement elem) {
237                 selection.remove(elem);
238         }
239
240         /**
241          * Deselects all notes.
242          */
243         public void deselectAllNotes() {
244                 Iterator it = selection.iterator();
245                 while(it.hasNext()) {
246                         ((MooNoteElement)it.next()).deselect();
247                 }
248                 selection.clear();
249         }
250
251         /**
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
254          */
255         public boolean isTheOnlySelected(MooNoteElement elem) {
256                 Iterator it = selection.iterator();
257                 while(it.hasNext()) {
258                         if (!it.next().equals(elem)) return false;
259                 }
260                 return true;
261         }
262
263         /**
264          * Copies the current selection.
265          */
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());
271                 }
272                 Collections.sort(copyBuffer);
273         }
274
275         /**
276          * Cuts the current selection.
277          */
278         public void cutSelectedNotes() {
279                 copySelectedNotes();
280                 removeSelectedNotes();
281         }
282
283         /**
284          * Pastes the current copy buffer at the given timestamp.
285          */
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);
295                                 addNote(mn);
296                         }
297                 }
298         }
299
300         /**
301          * Removes the current selection.
302          */
303         public void removeSelectedNotes() {
304                 Iterator it = selection.iterator();
305                 while(it.hasNext()) {
306                         removeNote((MooNoteElement)it.next());
307                 }
308                 selection.clear();
309         }
310
311         /**
312          * Transposes all selected notes the given number of halftones.
313          */
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);
319                 }
320         }
321
322         /**
323          * Moves the current selection the given number of ticks.
324          * @param ticks         the number of ticks to move the selection.
325          */
326         public void moveSelectedNotes(int ticks) {
327                 Iterator it = selection.iterator();
328                 while(it.hasNext()) {
329                         MooNoteElement elem = (MooNoteElement)it.next();
330                         elem.getNote().setTick(elem.getNote().getTick() + ticks);
331                         layoutElement(elem, true);
332                 }
333         }
334
335         /**
336          * Moves the current selection, depending on the given delta y.
337          * @param y     the number of pixels the selection is moved.
338          */
339         public void maybeMoveSelectedNotes(int y) {
340                 moveSelectedNotes(ticksPerSixteenth * (y / NOTE_HEIGHT));
341         }
342
343         /**
344          * Shows a popup-menu with options for the current selection of note elements.
345          * @param c     the component over which to display the menu
346          * @param x     the x-coordinate in which to display the menu
347          * @param y     the y-coordinate in which to display the menu
348          */
349         public void showSelectionPopup(Component c, int x, int y) {
350                 selPopup.show(c, x, y);
351         }
352
353         /**
354          * Draws the grid that is on the background.
355          * @param g The Graphics object used to draw the grid.
356          */
357         public void paintComponent(Graphics g) {
358                 super.paintComponent(g);
359                 Graphics2D g2 = (Graphics2D)g;
360                 for (int c = 0; c < viewLength || c < getHeight(); c += NOTE_HEIGHT) {
361                         for (int r = 0; r < (10 * NOTE_WIDTH); r += NOTE_WIDTH) {
362                                 box = new Rectangle(r, c, NOTE_WIDTH, NOTE_HEIGHT);
363                                 g2.setColor(Color.gray);
364                                 g2.draw(box);
365                         }
366                 }
367         }
368         
369         /**
370          * Returns whether the left mouse button is currently pressed or not.
371          * @return true if the left mosue button is currently pressed
372          */
373         public boolean isLeftMouseButtonPressed() {
374                 return leftMouseButtonPressed;
375         }
376
377         /**
378          * The adapter used to listen on mouse actions
379          */
380         class MAdapter extends MouseAdapter {
381
382                 /**
383                  * Deselects all note on click, adds a standard note on double click.
384                  */
385                 public void mouseClicked(MouseEvent e) {
386                         if (SwingUtilities.isLeftMouseButton(e)) {
387                                 deselectAllNotes();
388                                 if (e.getClickCount() == 2) {
389                                         popupY = e.getY();
390                                         addStandardNote();
391                                 }
392                         }
393                 }
394         
395                 public void mousePressed(MouseEvent e) {
396                         if (SwingUtilities.isLeftMouseButton(e)) leftMouseButtonPressed = true;
397                         maybeShowPopup(e);
398                 }
399
400                 public void mouseReleased(MouseEvent e) {
401                         if (SwingUtilities.isLeftMouseButton(e)) leftMouseButtonPressed = false;
402                         maybeShowPopup(e);
403                 }
404
405                 /**
406                  * Shows the menu if an OS-specific popup-trigger was activated.
407                  */
408                 private void maybeShowPopup(MouseEvent e) {
409                         if (e.isPopupTrigger()) {
410                                 popupY = e.getY();
411                                 popup.show(e.getComponent(), e.getX(), e.getY());
412                         }
413                 }
414
415                 /**
416                  * Grabs the focus when the mouse has entered.
417                  */
418                 public void mouseEntered(MouseEvent e) {
419                         Moosique.setActiveChannel(title.getChannel());
420                         grabFocus();
421                 }
422         }
423
424         /**
425          * Listens on actions on the popup menu and executes the appropriate action.
426          */
427         class PopupListener implements ActionListener {
428                 public void actionPerformed(ActionEvent e) {
429                         Object source = e.getSource();
430                         // Handling panel popup actions.
431                         if (source == popupAdd) {
432                                 addStandardNote();
433                         } else if (source == popupPaste) {
434                                 pasteCopiedNotes();
435                         // Handling selection popup actions.
436                         } else if (source == selPopupCopy) {
437                                 copySelectedNotes();
438                         } else if (source == selPopupCut) {
439                                 cutSelectedNotes();
440                         } else if (source == selPopupRemove) {
441                                 removeSelectedNotes();
442                         } else if (source == selPopupTranspUpOct) {
443                                 transposeSelectedNotes(12);
444                         } else if (source == selPopupTranspDownOct) {
445                                 transposeSelectedNotes(-12);
446                         }
447                 }
448         }
449 }