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