]> ruin.nu Git - moosique.git/blob - MooTrackView.java
f2afcaae2be4e18373b97a3250d4cfe5ce7b29e0
[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;
23         private JMenuItem selPopupRemove, selPopupTranspUpOct, selPopupTranspDownOct;
24
25         private ArrayList rects;
26         private ArrayList selected;
27         private Insets insets;
28         private int ticksPerSixteenth, popupY = 0;
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                 insets = getInsets();
43                 selected = new ArrayList();
44
45                 // Configures panel
46                 setBackground(Color.white);
47                 setBorder(BorderFactory.createLineBorder(Color.black));
48                 setLayout(null);
49                 setPreferredSize(new Dimension(VIEW_WIDTH, 140 * NOTE_HEIGHT));
50
51                 // Creates temporary variables
52                 MidiEvent note;
53                 MooNoteElement elem;
54                 rects = new ArrayList(track.size() / 2);
55                 extraHeight = Toolkit.getDefaultToolkit().getScreenSize().height - 150;
56
57                 // Places note elements
58                 for (int i = 0; i < track.size(); i++) {
59                         note = track.get(i);
60                         if (note instanceof MooNote) {
61                                 // Adds the note element to the note area and moves it to the appropriate place.
62                                 MooNote mn = (MooNote)note;
63                                 elem = new MooNoteElement(this, mn);
64                                 add(elem);
65                                 layoutElement(elem, false);
66                         }
67                         setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
68
69                 }
70
71                 // Creates panel pop-up menu.
72                 popup = new JPopupMenu();
73                 PopupListener pList = new PopupListener();
74                 popupAdd = new JMenuItem("Add note...");
75                 popupAdd.addActionListener(pList);
76                 popup.add(popupAdd);
77
78                 // Creates selection pop-up menu.
79                 selPopup = new JPopupMenu();
80                 selPopupRemove = new JMenuItem("Remove selection");
81                 selPopupRemove.addActionListener(pList);
82                 selPopup.add(selPopupRemove);
83                 selPopupTranspUp = new JMenu("Transpose selection up");
84                 selPopup.add(selPopupTranspUp);
85                 selPopupTranspUpOct = new JMenuItem("One octave");
86                 selPopupTranspUpOct.addActionListener(pList);
87                 selPopupTranspUp.add(selPopupTranspUpOct);
88                 selPopupTranspDown = new JMenu("Transpose selection down");
89                 selPopup.add(selPopupTranspDown);
90                 selPopupTranspDownOct = new JMenuItem("One octave");
91                 selPopupTranspDownOct.addActionListener(pList);
92                 selPopupTranspDown.add(selPopupTranspDownOct);
93
94                 // Adds listeners for popup menu and keyboard synthesizer.
95                 addMouseListener(new MAdapter());
96                 addKeyListener(new MooKeyboard());
97         }
98
99         /**
100          * Layouts the element to the right place.
101          * @param elem  the element that will be layouted.
102          * @param old   If true, this method will remove the old layout and set the new preferredSize for the trackview.
103          */
104         public void layoutElement(MooNoteElement elem, boolean old){
105                 // If the element is currently in the view, removes its coordinates from the list.
106                 Rectangle r = new Rectangle();
107                 if (old){
108                         r = elem.getBounds(r);
109                         for (Iterator i = rects.iterator(); i.hasNext();){
110                                 Object ob = i.next();
111                                 if (r.equals(ob)){
112                                         rects.remove(ob);
113                                         break;
114                                 }
115                         }
116                 }
117
118                 // Creates temporary variables.
119                 ticksPerSixteenth = Moosique.getSequence().getResolution() / 4;
120                 MooNote mn = elem.getNote();
121                 int x, y, height;
122
123                 // Calculates coordinates.
124                 x = insets.left;
125                 y = insets.top + (int)(mn.getTick() / ticksPerSixteenth) * NOTE_HEIGHT;
126                 height = (mn.getDuration() / ticksPerSixteenth) * NOTE_HEIGHT;
127                 if (height == 0) height = NOTE_HEIGHT;
128                 r = new Rectangle(x, y, NOTE_WIDTH, height);
129
130                 // Places the element in the appropriate place.
131                 while(isOccupied(r)) r.translate(NOTE_WIDTH, 0);
132                 elem.setBounds(r);
133                 rects.add(r);
134                 if (viewLength < (y + height)){
135                         viewLength = y + height;
136                         if(old)setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
137                 }
138                 if(old)repaint();
139         }
140
141         /** 
142          * Returns the track of this view.
143          * @return the track of this view
144          */
145         public Track getTrack() {
146                 return track;
147         }
148
149         /** 
150          * Returns the title of this view.
151          * @return the title of this view
152          */
153         public MooTrackTitle getTitle() {
154                 return title;
155         }
156
157         /**
158          * Checks if the element can be fully drawn as this position without inteferring with other elements.
159          * @return true if the position is occupied.
160          */
161         private boolean isOccupied(Rectangle r) {
162                 Iterator it = rects.iterator();
163                 while (it.hasNext()) {
164                         if(r.intersects((Rectangle)it.next())) return true;
165                 }
166                 return false;
167         }
168         
169         /** 
170          * Adds the given note to the current track, and visualises it.
171          * @param mn    the note to add
172          */
173         public void addNote(MooNote mn) {
174                 mn.addTo(track);
175                 MooNoteElement elem = new MooNoteElement(this, mn);
176                 add(elem);
177                 layoutElement(elem, false);
178                 setPreferredSize(new Dimension(VIEW_WIDTH, viewLength + extraHeight));
179                 Moosique.setEdited();
180                 repaint();
181         }
182
183         /**
184          * Adds a standard note to this track.
185          */
186         private void addStandardNote() {
187                 int row =  (popupY - insets.top) / NOTE_HEIGHT;
188                 long timestamp = (long)(ticksPerSixteenth * row);
189                 addNote(new MooNote(title.getChannel(), 60, 100, timestamp, Moosique.getSequence().getResolution() / 4));
190         }
191
192         /** 
193          * Removes the given note element from the view and its note from the current track.
194          * @param elem  the note element to remove
195          */
196         public void removeNote(MooNoteElement elem) {
197                 elem.getNote().removeFrom(track);
198                 remove(elem);
199                 Rectangle r = new Rectangle();
200                 r = elem.getBounds(r);
201                 rects.remove(r);
202                 Moosique.setEdited();
203                 repaint();
204         }
205
206         /**
207          * Deselects all notes.
208          */
209         public void selectNote(MooNoteElement elem) {
210                 selected.add(elem);
211         }
212
213         /**
214          * Deselects all notes.
215          */
216         public void deselectNote(MooNoteElement elem) {
217                 selected.remove(selected.indexOf(elem));
218         }
219
220         /**
221          * Deselects all notes.
222          */
223         public void deselectAllNotes() {
224                 Iterator it = selected.iterator();
225                 while(it.hasNext()) {
226                         ((MooNoteElement)it.next()).deselect();
227                 }
228                 selected.clear();
229         }
230
231         /**
232          * Determines if the given MooNoteElement is the only one in the track view that is selected.
233          * @return if the given element is the only selected one
234          */
235         public boolean isTheOnlySelected(MooNoteElement elem) {
236                 Iterator it = selected.iterator();
237                 while(it.hasNext()) {
238                         if (!it.next().equals(elem)) return false;
239                 }
240                 return true;
241         }
242
243         /**
244          * Shows a popup-menu with options for the current selection of note elements.
245          * @param c     the component over which to display the menu
246          * @param x     the x-coordinate in which to display the menu
247          * @param y     the y-coordinate in which to display the menu
248          */
249         public void showSelectionPopup(Component c, int x, int y) {
250                 selPopup.show(c, x, y);
251         }
252
253         /**
254          * Transposes all selected notes the given number of halftones.
255          */
256         private void transposeSelectedNotes(int halftones) {
257                 Iterator it = selected.iterator();
258                 while(it.hasNext()) {
259                         MooNoteElement elem = (MooNoteElement)it.next();
260                         elem.getNote().transpose(halftones);
261                         elem.update();
262                 }
263         }
264
265         /**
266          * Draws the grid that is on the background.
267          * @param g The Graphics object used to draw the grid.
268          */
269         public void paintComponent(Graphics g) {
270                 super.paintComponent(g);
271                 Graphics2D g2 = (Graphics2D)g;
272                 for (int c = 0; c < viewLength || c < getHeight(); c += NOTE_HEIGHT) {
273                         for (int r = 0; r < (10 * NOTE_WIDTH); r += NOTE_WIDTH) {
274                                 box = new Rectangle(r, c, NOTE_WIDTH, NOTE_HEIGHT);
275                                 g2.setColor(Color.gray);
276                                 g2.draw(box);
277                         }
278                 }
279         }
280         
281         /**
282          * The adapter used to listen on mouse actions
283          */
284         class MAdapter extends MouseAdapter {
285
286                 /**
287                  * Deselects all note on click, adds a standard note on double click.
288                  */
289                 public void mouseClicked(MouseEvent e) {
290                         if (SwingUtilities.isLeftMouseButton(e)) {
291                                 deselectAllNotes();
292                                 if (e.getClickCount() == 2) {
293                                         popupY = e.getY();
294                                         addStandardNote();
295                                 }
296                         }
297                 }
298         
299                 public void mousePressed(MouseEvent e) {
300                         maybeShowPopup(e);
301                 }
302
303                 public void mouseReleased(MouseEvent e) {
304                         maybeShowPopup(e);
305                 }
306
307                 /**
308                  * Selects the notes within the area that was selected.
309                  */
310                 public void mouseDragged(MouseEvent e) {
311                         
312                 }
313
314                 /**
315                  * Shows the menu if an OS-specific popup-trigger was activated.
316                  */
317                 private void maybeShowPopup(MouseEvent e) {
318                         if (e.isPopupTrigger()) {
319                                 popupY = e.getY();
320                                 popup.show(e.getComponent(), e.getX(), e.getY());
321                         }
322                 }
323
324                 /**
325                  * Grabs the focus when the mouse has entered.
326                  */
327                 public void mouseEntered(MouseEvent e) {
328                         // Moosique.setActiveChannel(track.getChannel());
329                         grabFocus();
330                 }
331         }
332
333         /**
334          * Listens on actions on the popup menu and executes the appropriate action.
335          */
336         class PopupListener implements ActionListener {
337                 public void actionPerformed(ActionEvent e) {
338                         Object source = e.getSource();
339                         if (source == popupAdd) {
340                                 addStandardNote();
341                         } else if (source == selPopupRemove) {
342                                 Iterator it = selected.iterator();
343                                 while(it.hasNext()) {
344                                         removeNote((MooNoteElement)it.next());
345                                 }
346                                 selected.clear();
347                         } else if (source == selPopupTranspUpOct) {
348                                 transposeSelectedNotes(12);
349                         } else if (source == selPopupTranspDownOct) {
350                                 transposeSelectedNotes(-12);
351                         }
352                 }
353         }
354 }