Subversion Repositories svn.mios

Rev

Rev 628 | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 628 Rev 641
1
/*
1
/*
2
 * @(#)MidiDeviceRoutingGUI.java    beta7   2006/04/23
2
 * @(#)MidiDeviceRoutingGUI.java    beta7   2006/04/23
3
 *
3
 *
4
 * Copyright (C) 2006    Adam King (adamjking@optusnet.com.au)
4
 * Copyright (C) 2006    Adam King (adamjking@optusnet.com.au)
5
 *
5
 *
6
 * This application is free software; you can redistribute it and/or modify
6
 * This application is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 2 of the License, or
8
 * the Free Software Foundation; either version 2 of the License, or
9
 * (at your option) any later version.
9
 * (at your option) any later version.
10
 *
10
 *
11
 * This application is distributed in the hope that it will be useful,
11
 * This application is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
14
 * GNU General Public License for more details.
15
 *
15
 *
16
 * You should have received a copy of the GNU General Public License
16
 * You should have received a copy of the GNU General Public License
17
 * along with this application; if not, write to the Free Software
17
 * along with this application; if not, write to the Free Software
18
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
 */
19
 */
20
20
21
package org.midibox.apps.SIDV2librarian.gui;
21
package org.midibox.apps.SIDV2librarian.gui;
22
22
23
import java.awt.BorderLayout;
23
import java.awt.BorderLayout;
24
import java.awt.Color;
24
import java.awt.Color;
25
import java.awt.Component;
25
import java.awt.Component;
26
import java.awt.Dimension;
26
import java.awt.Dimension;
27
import java.awt.GridBagConstraints;
27
import java.awt.GridBagConstraints;
28
import java.awt.GridBagLayout;
28
import java.awt.GridBagLayout;
29
import java.awt.Insets;
29
import java.awt.Insets;
30
import java.awt.event.ActionEvent;
30
import java.awt.event.ActionEvent;
31
import java.awt.event.ActionListener;
31
import java.awt.event.ActionListener;
32
import java.util.Hashtable;
32
import java.util.Hashtable;
33
import java.util.Iterator;
33
import java.util.Iterator;
34
import java.util.Observable;
34
import java.util.Observable;
35
import java.util.Observer;
35
import java.util.Observer;
36
36
37
import javax.sound.midi.MidiDevice;
37
import javax.sound.midi.MidiDevice;
38
import javax.swing.BorderFactory;
38
import javax.swing.BorderFactory;
39
import javax.swing.DefaultListCellRenderer;
39
import javax.swing.DefaultListCellRenderer;
40
import javax.swing.DefaultListModel;
40
import javax.swing.DefaultListModel;
41
import javax.swing.Icon;
41
import javax.swing.Icon;
42
import javax.swing.JButton;
42
import javax.swing.JButton;
43
import javax.swing.JLabel;
43
import javax.swing.JLabel;
44
import javax.swing.JList;
44
import javax.swing.JList;
45
import javax.swing.JPanel;
45
import javax.swing.JPanel;
46
import javax.swing.JScrollPane;
46
import javax.swing.JScrollPane;
47
import javax.swing.ListSelectionModel;
47
import javax.swing.ListSelectionModel;
48
import javax.swing.SwingConstants;
48
import javax.swing.SwingConstants;
49
import javax.swing.event.ChangeEvent;
49
import javax.swing.event.ChangeEvent;
50
import javax.swing.event.ChangeListener;
50
import javax.swing.event.ChangeListener;
51
import javax.swing.event.ListSelectionEvent;
51
import javax.swing.event.ListSelectionEvent;
52
import javax.swing.event.ListSelectionListener;
52
import javax.swing.event.ListSelectionListener;
53
53
54
import org.midibox.apps.SIDV2librarian.SIDV2librarian;
54
import org.midibox.apps.SIDV2librarian.SIDV2librarian;
55
import org.midibox.midi.MidiDeviceRouting;
55
import org.midibox.midi.MidiDeviceRouting;
56
import org.midibox.utils.gui.GuiUtils;
56
import org.midibox.utils.gui.GuiUtils;
57
import org.midibox.utils.gui.ImageLoader;
57
import org.midibox.utils.gui.ImageLoader;
58
58
59
public class SIDV2librarianMidiDeviceRoutingGUI extends JPanel implements
59
public class SIDV2librarianMidiDeviceRoutingGUI extends JPanel implements
60
        ChangeListener, ListSelectionListener, ActionListener, Observer {
60
        ChangeListener, ListSelectionListener, ActionListener, Observer {
61
61
62
    private SIDV2librarian sidv2librarian;
62
    private SIDV2librarian sidv2librarian;
63
63
64
    private static Icon openIcon = ImageLoader.getImageIcon("midiPortOpen.png");
64
    private static Icon openIcon = ImageLoader.getImageIcon("midiPortOpen.png");
65
65
66
    private static Icon closedIcon = ImageLoader
66
    private static Icon closedIcon = ImageLoader
67
            .getImageIcon("midiPortClosed.png");
67
            .getImageIcon("midiPortClosed.png");
68
68
69
    private Hashtable icons = new Hashtable();
69
    private Hashtable icons = new Hashtable();
70
70
71
    private DefaultListModel midiReadDevicesListModel;
71
    private DefaultListModel midiReadDevicesListModel;
72
72
73
    private JList midiReadDevicesList;
73
    private JList midiReadDevicesList;
74
74
75
    private JScrollPane midiReadDevicesScroller;
75
    private JScrollPane midiReadDevicesScroller;
76
76
77
    private DefaultListModel midiWriteDevicesListModel;
77
    private DefaultListModel midiWriteDevicesListModel;
78
78
79
    private JList midiWriteDevicesList;
79
    private JList midiWriteDevicesList;
80
80
81
    private JScrollPane midiWriteDevicesScroller;
81
    private JScrollPane midiWriteDevicesScroller;
82
82
83
    private JButton rescan;
83
    private JButton rescan;
84
84
85
    public SIDV2librarianMidiDeviceRoutingGUI(SIDV2librarian sidv2librarian) {
85
    public SIDV2librarianMidiDeviceRoutingGUI(SIDV2librarian sidv2librarian) {
86
        super(new BorderLayout());
86
        super(new BorderLayout());
87
87
88
        this.sidv2librarian = sidv2librarian;
88
        this.sidv2librarian = sidv2librarian;
89
        sidv2librarian.getMidiDeviceRouting().addObserver(this);
89
        sidv2librarian.getMidiDeviceRouting().addObserver(this);
90
90
91
        midiReadDevicesListModel = new DefaultListModel();
91
        midiReadDevicesListModel = new DefaultListModel();
92
        midiWriteDevicesListModel = new DefaultListModel();
92
        midiWriteDevicesListModel = new DefaultListModel();
93
93
94
        midiReadDevicesList = new JList(midiReadDevicesListModel);
94
        midiReadDevicesList = new JList(midiReadDevicesListModel);
95
        midiReadDevicesList
95
        midiReadDevicesList
96
                .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
96
                .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
97
        midiReadDevicesList.setLayoutOrientation(JList.VERTICAL);
97
        midiReadDevicesList.setLayoutOrientation(JList.VERTICAL);
98
        midiReadDevicesList.setVisibleRowCount(10);
98
        midiReadDevicesList.setVisibleRowCount(10);
99
        midiReadDevicesList.setCellRenderer(new MyListCellRenderer());
99
        midiReadDevicesList.setCellRenderer(new MyListCellRenderer());
100
        midiReadDevicesList.setBackground(Color.WHITE);
100
        midiReadDevicesList.setBackground(Color.WHITE);
101
        midiWriteDevicesList = new JList(midiWriteDevicesListModel);
101
        midiWriteDevicesList = new JList(midiWriteDevicesListModel);
102
        midiWriteDevicesList
102
        midiWriteDevicesList
103
                .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
103
                .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
104
        midiWriteDevicesList.setLayoutOrientation(JList.VERTICAL);
104
        midiWriteDevicesList.setLayoutOrientation(JList.VERTICAL);
105
        midiWriteDevicesList.setVisibleRowCount(10);
105
        midiWriteDevicesList.setVisibleRowCount(10);
106
        midiWriteDevicesList.setCellRenderer(new MyListCellRenderer());
106
        midiWriteDevicesList.setCellRenderer(new MyListCellRenderer());
107
        midiWriteDevicesList.setBackground(Color.WHITE);
107
        midiWriteDevicesList.setBackground(Color.WHITE);
108
108
109
        JPanel listPane = new JPanel(new GridBagLayout());
109
        JPanel listPane = new JPanel(new GridBagLayout());
110
110
111
        GridBagConstraints gbc = new GridBagConstraints();
111
        GridBagConstraints gbc = new GridBagConstraints();
112
        gbc.gridx = 1;
112
        gbc.gridx = 1;
113
        gbc.gridy = 1;
113
        gbc.gridy = 1;
114
        gbc.gridwidth = 1;
114
        gbc.gridwidth = 1;
115
        gbc.gridheight = 1;
115
        gbc.gridheight = 1;
116
        gbc.insets = new Insets(2, 0, 2, 0);
116
        gbc.insets = new Insets(2, 0, 2, 0);
117
        gbc.fill = GridBagConstraints.HORIZONTAL;
117
        gbc.fill = GridBagConstraints.HORIZONTAL;
118
        gbc.weightx = 1.0;
118
        gbc.weightx = 1.0;
119
        gbc.weighty = 0.0;
119
        gbc.weighty = 0.0;
120
120
121
        listPane.setOpaque(false);
121
        listPane.setOpaque(false);
122
122
123
        listPane.add(new JLabel("MIDI Input devices", SwingConstants.CENTER),
123
        listPane.add(new JLabel("MIDI Input devices", SwingConstants.CENTER),
124
                gbc);
124
                gbc);
125
        gbc.gridy++;
125
        gbc.gridy++;
126
126
127
        midiReadDevicesScroller = new JScrollPane(midiReadDevicesList);
127
        midiReadDevicesScroller = new JScrollPane(midiReadDevicesList);
128
        midiReadDevicesScroller.getViewport().addChangeListener(this);
128
        midiReadDevicesScroller.getViewport().addChangeListener(this);
129
        midiReadDevicesScroller
129
        midiReadDevicesScroller
130
                .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
130
                .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
131
        midiReadDevicesScroller
131
        midiReadDevicesScroller
132
                .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
132
                .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
133
133
134
        gbc.fill = GridBagConstraints.BOTH;
134
        gbc.fill = GridBagConstraints.BOTH;
135
        gbc.weighty = 1.0;
135
        gbc.weighty = 1.0;
136
        gbc.weighty = 1.0;
136
        gbc.weighty = 1.0;
137
137
138
        listPane.add(midiReadDevicesScroller, gbc);
138
        listPane.add(midiReadDevicesScroller, gbc);
139
        gbc.gridx++;
139
        gbc.gridx++;
140
140
141
        gbc.gridx++;
141
        gbc.gridx++;
142
        gbc.gridy--;
142
        gbc.gridy--;
143
143
144
        gbc.fill = GridBagConstraints.HORIZONTAL;
144
        gbc.fill = GridBagConstraints.HORIZONTAL;
145
        gbc.weightx = 1.0;
145
        gbc.weightx = 1.0;
146
        gbc.weighty = 0.0;
146
        gbc.weighty = 0.0;
147
147
148
        listPane.add(new JLabel("MIDI Output devices", SwingConstants.CENTER),
148
        listPane.add(new JLabel("MIDI Output devices", SwingConstants.CENTER),
149
                gbc);
149
                gbc);
150
        gbc.gridy++;
150
        gbc.gridy++;
151
151
152
        midiWriteDevicesScroller = new JScrollPane(midiWriteDevicesList);
152
        midiWriteDevicesScroller = new JScrollPane(midiWriteDevicesList);
153
        midiWriteDevicesScroller
153
        midiWriteDevicesScroller
154
                .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
154
                .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
155
        midiWriteDevicesScroller
155
        midiWriteDevicesScroller
156
                .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
156
                .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
157
        midiWriteDevicesScroller.getViewport().addChangeListener(this);
157
        midiWriteDevicesScroller.getViewport().addChangeListener(this);
158
158
159
        gbc.fill = GridBagConstraints.BOTH;
159
        gbc.fill = GridBagConstraints.BOTH;
160
        gbc.weightx = 1.0;
160
        gbc.weightx = 1.0;
161
        gbc.weighty = 1.0;
161
        gbc.weighty = 1.0;
162
162
163
        listPane.add(midiWriteDevicesScroller, gbc);
163
        listPane.add(midiWriteDevicesScroller, gbc);
164
164
165
        int maxPreferredWidth = Math.max(midiReadDevicesScroller
165
        int maxPreferredWidth = Math.max(midiReadDevicesScroller
166
                .getPreferredSize().width, midiWriteDevicesScroller
166
                .getPreferredSize().width, midiWriteDevicesScroller
167
                .getPreferredSize().width);
167
                .getPreferredSize().width);
168
        int maxPreferredHeight = Math.max(midiReadDevicesScroller
168
        int maxPreferredHeight = Math.max(midiReadDevicesScroller
169
                .getPreferredSize().height, midiWriteDevicesScroller
169
                .getPreferredSize().height, midiWriteDevicesScroller
170
                .getPreferredSize().height);
170
                .getPreferredSize().height);
171
171
172
        GuiUtils.setComponentSize(midiReadDevicesScroller, maxPreferredWidth,
172
        GuiUtils.setComponentSize(midiReadDevicesScroller, maxPreferredWidth,
173
                maxPreferredHeight);
173
                maxPreferredHeight);
174
        GuiUtils.setComponentSize(midiWriteDevicesScroller, maxPreferredWidth,
174
        GuiUtils.setComponentSize(midiWriteDevicesScroller, maxPreferredWidth,
175
                maxPreferredHeight);
175
                maxPreferredHeight);
176
176
177
        midiReadDevicesList.addListSelectionListener(this);
177
        midiReadDevicesList.addListSelectionListener(this);
178
        midiWriteDevicesList.addListSelectionListener(this);
178
        midiWriteDevicesList.addListSelectionListener(this);
179
179
180
        listPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
180
        listPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
181
        listPane.setOpaque(false);
181
        listPane.setOpaque(false);
182
        add(listPane, BorderLayout.CENTER);
182
        add(listPane, BorderLayout.CENTER);
183
        rescan = new JButton("Rescan");
183
        rescan = new JButton("Rescan");
184
        rescan.addActionListener(this);
184
        rescan.addActionListener(this);
185
        add(rescan, BorderLayout.SOUTH);
185
        add(rescan, BorderLayout.SOUTH);
186
        populateTrees();
186
        populateTrees();
187
    }
187
    }
188
188
189
    public MidiDeviceRouting getMidiDeviceRouting() {
189
    public MidiDeviceRouting getMidiDeviceRouting() {
190
        return sidv2librarian.getMidiDeviceRouting();
190
        return sidv2librarian.getMidiDeviceRouting();
191
    }
191
    }
192
192
193
    public Icon getMidiDeviceIcon(MidiDevice md) {
193
    public Icon getMidiDeviceIcon(MidiDevice md) {
194
        if (icons.containsKey(md)) {
194
        if (icons.containsKey(md)) {
195
            return (Icon) icons.get(md);
195
            return (Icon) icons.get(md);
196
        }
196
        }
197
197
198
        if (icons.containsKey(md.getClass())) {
198
        if (icons.containsKey(md.getClass())) {
199
            return (Icon) icons.get(md.getClass());
199
            return (Icon) icons.get(md.getClass());
200
        }
200
        }
201
201
202
        try {
202
        try {
203
            if (md.isOpen()) {
203
            if (md.isOpen()) {
204
                return openIcon;
204
                return openIcon;
205
            }
205
            }
206
206
207
            return closedIcon;
207
            return closedIcon;
208
208
209
        } catch (Exception e) {
209
        } catch (Exception e) {
210
            return closedIcon;
210
            return closedIcon;
211
        }
211
        }
212
    }
212
    }
213
213
214
    public void redrawAll() {
214
    public void redrawAll() {
215
        midiReadDevicesList.repaint();
215
        midiReadDevicesList.repaint();
216
        midiWriteDevicesList.repaint();
216
        midiWriteDevicesList.repaint();
217
    }
217
    }
218
218
219
    private void populateTrees() {
219
    private void populateTrees() {
220
        midiReadDevicesListModel.removeAllElements();
220
        midiReadDevicesListModel.removeAllElements();
221
        Iterator it = sidv2librarian.getMidiDeviceRouting()
221
        Iterator it = sidv2librarian.getMidiDeviceRouting()
222
                .getMidiReadDevices().iterator();
222
                .getMidiReadDevices().iterator();
223
223
224
        while (it.hasNext()) {
224
        while (it.hasNext()) {
225
            midiReadDevicesListModel.addElement(((MidiDevice) it.next())
225
            midiReadDevicesListModel.addElement(((MidiDevice) it.next())
226
                    .getDeviceInfo().getName());
226
                    .getDeviceInfo().getName());
227
        }
227
        }
228
        midiReadDevicesList.setSelectedIndex(0);
228
        midiReadDevicesList.setSelectedIndex(0);
229
        midiWriteDevicesListModel.removeAllElements();
229
        midiWriteDevicesListModel.removeAllElements();
230
        it = sidv2librarian.getMidiDeviceRouting().getMidiWriteDevices()
230
        it = sidv2librarian.getMidiDeviceRouting().getMidiWriteDevices()
231
                .iterator();
231
                .iterator();
232
        while (it.hasNext()) {
232
        while (it.hasNext()) {
233
            midiWriteDevicesListModel.addElement(((MidiDevice) it.next())
233
            midiWriteDevicesListModel.addElement(((MidiDevice) it.next())
234
                    .getDeviceInfo().getName());
234
                    .getDeviceInfo().getName());
235
        }
235
        }
236
        midiWriteDevicesList.setSelectedIndex(0);
236
        midiWriteDevicesList.setSelectedIndex(0);
237
        updateUI();
237
        updateUI();
238
    }
238
    }
239
239
240
    public void valueChanged(ListSelectionEvent lse) {
240
    public void valueChanged(ListSelectionEvent lse) {
241
        if (lse.getSource() == midiReadDevicesList) {
241
        if (lse.getSource() == midiReadDevicesList) {
242
            if (midiReadDevicesList.getSelectedIndex() > 0) {
242
            if (midiReadDevicesList.getSelectedIndex() > 0) {
243
                sidv2librarian.setInputDeviceIndex(midiReadDevicesList
243
                sidv2librarian.setInputDeviceIndex(midiReadDevicesList
244
                        .getSelectedIndex());
244
                        .getSelectedIndex());
245
            }
245
            }
246
        } else if (lse.getSource() == midiWriteDevicesList) {
246
        } else if (lse.getSource() == midiWriteDevicesList) {
247
            if (midiWriteDevicesList.getSelectedIndex() > 0) {
247
            if (midiWriteDevicesList.getSelectedIndex() > 0) {
248
                sidv2librarian.setOutputDeviceIndex(midiWriteDevicesList
248
                sidv2librarian.setOutputDeviceIndex(midiWriteDevicesList
249
                        .getSelectedIndex());
249
                        .getSelectedIndex());
250
            }
250
            }
251
        }
251
        }
252
    }
252
    }
253
253
254
    public void stateChanged(ChangeEvent ce) {
254
    public void stateChanged(ChangeEvent ce) {
255
    }
255
    }
256
256
257
    public Dimension getMinimumSize() {
257
    public Dimension getMinimumSize() {
258
        return getPreferredSize();
258
        return getPreferredSize();
259
    }
259
    }
260
260
261
    public void update(Observable observable, Object object) {
261
    public void update(Observable observable, Object object) {
262
        if (observable == sidv2librarian.getMidiDeviceRouting()) {
262
        if (observable == sidv2librarian.getMidiDeviceRouting()) {
263
            if (object == sidv2librarian.getMidiDeviceRouting()
263
            if (object == sidv2librarian.getMidiDeviceRouting()
264
                    .getMidiReadDevices()
264
                    .getMidiReadDevices()
265
                    || object == sidv2librarian.getMidiDeviceRouting()
265
                    || object == sidv2librarian.getMidiDeviceRouting()
266
                            .getMidiWriteDevices()) {
266
                            .getMidiWriteDevices()) {
267
                populateTrees();
267
                populateTrees();
268
            }
268
            }
269
            midiReadDevicesList.setSelectedIndex(sidv2librarian
269
            midiReadDevicesList.setSelectedIndex(sidv2librarian
270
                    .getInputDeviceIndex());
270
                    .getInputDeviceIndex());
271
            midiWriteDevicesList.setSelectedIndex(sidv2librarian
271
            midiWriteDevicesList.setSelectedIndex(sidv2librarian
272
                    .getOutputDeviceIndex());
272
                    .getOutputDeviceIndex());
273
            redrawAll();
273
            redrawAll();
274
        }
274
        }
275
275
276
    }
276
    }
277
277
278
    public void actionPerformed(ActionEvent ae) {
278
    public void actionPerformed(ActionEvent ae) {
279
        if (ae.getSource() == rescan) {
279
        if (ae.getSource() == rescan) {
280
            sidv2librarian.getMidiDeviceRouting().getMidiDeviceManager()
280
            sidv2librarian.getMidiDeviceManager()
281
                    .rescanDevices();
281
                    .rescanDevices();
282
        }
282
        }
283
    }
283
    }
284
284
285
    public class MyListCellRenderer extends DefaultListCellRenderer {
285
    public class MyListCellRenderer extends DefaultListCellRenderer {
286
        public Component getListCellRendererComponent(JList list, Object value,
286
        public Component getListCellRendererComponent(JList list, Object value,
287
                int index, boolean isSelected, boolean hasFocus) {
287
                int index, boolean isSelected, boolean hasFocus) {
288
288
289
            super.getListCellRendererComponent(list, value, index, isSelected,
289
            super.getListCellRendererComponent(list, value, index, isSelected,
290
                    hasFocus);
290
                    hasFocus);
291
291
292
            setIcon(getMidiDeviceIcon(((MidiDevice) ((list == midiWriteDevicesList) ? sidv2librarian
292
            setIcon(getMidiDeviceIcon(((MidiDevice) ((list == midiWriteDevicesList) ? sidv2librarian
293
                    .getMidiDeviceRouting().getMidiWriteDevices()
293
                    .getMidiDeviceRouting().getMidiWriteDevices()
294
                    : sidv2librarian.getMidiDeviceRouting()
294
                    : sidv2librarian.getMidiDeviceRouting()
295
                            .getMidiReadDevices()).elementAt(index))));
295
                            .getMidiReadDevices()).elementAt(index))));
296
296
297
            return this;
297
            return this;
298
        }
298
        }
299
    }
299
    }
300
}
300
}
301
 
301