OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 150 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
137 ilm 1
package org.jopencalendar.ui;
2
 
3
import java.awt.Color;
4
import java.awt.Component;
5
import java.awt.Dimension;
6
import java.awt.FlowLayout;
7
import java.awt.GraphicsConfiguration;
8
import java.awt.GraphicsDevice;
9
import java.awt.GraphicsEnvironment;
10
import java.awt.Insets;
11
import java.awt.Point;
12
import java.awt.Rectangle;
13
import java.awt.Toolkit;
14
import java.awt.Window;
15
import java.awt.event.ActionEvent;
16
import java.awt.event.ActionListener;
17
import java.awt.event.WindowEvent;
18
import java.awt.event.WindowFocusListener;
19
import java.beans.PropertyChangeEvent;
20
import java.beans.PropertyChangeListener;
21
import java.text.DateFormat;
22
import java.text.Format;
23
import java.text.ParseException;
24
import java.util.ArrayList;
25
import java.util.Calendar;
26
import java.util.Date;
27
import java.util.List;
28
 
29
import javax.swing.AbstractAction;
30
import javax.swing.Action;
31
import javax.swing.BorderFactory;
32
import javax.swing.ImageIcon;
33
import javax.swing.JButton;
34
import javax.swing.JComponent;
35
import javax.swing.JDialog;
36
import javax.swing.JFormattedTextField;
37
import javax.swing.JPanel;
38
import javax.swing.JTextField;
39
import javax.swing.SwingUtilities;
40
 
41
public class DatePicker extends JPanel {
42
 
43
    private JFormattedTextField text;
44
    private JDialog dialog;
45
    private long dialogLostFocusTime;
46
    private DatePickerPanel pickerPanel;
47
    private Date date;
48
    private List<ActionListener> actionListeners = new ArrayList<ActionListener>();
49
    private static final long RELEASE_TIME_MS = 500;
50
    private JButton button;
51
 
52
    public DatePicker() {
53
        this(true);
54
    }
55
 
56
    public DatePicker(boolean useEditor) {
57
        this(useEditor, false);
58
    }
59
 
60
    public DatePicker(boolean useEditor, boolean fillWithCurrentDate) {
61
        this(DateFormat.getDateInstance(), useEditor, fillWithCurrentDate);
62
    }
63
 
64
    public DatePicker(Format dateFormat, boolean useEditor, boolean fillWithCurrentDate) {
65
        this.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
66
        this.setOpaque(false);
67
        if (useEditor) {
68
            text = new JFormattedTextField(dateFormat);
69
            text.setColumns(12);
70
            text.addPropertyChangeListener("value", new PropertyChangeListener() {
71
                @Override
72
                public void propertyChange(PropertyChangeEvent evt) {
73
                    setDate((Date) evt.getNewValue());
74
                }
75
            });
76
            this.add(text);
77
            JPanel spacer = new JPanel();
78
            spacer.setOpaque(false);
79
            spacer.setMinimumSize(new Dimension(3, 5));
80
            spacer.setPreferredSize(new Dimension(3, 5));
81
            this.add(spacer);
82
        }
83
 
153 ilm 84
        String iconFileName = "calendar.png";
85
        if (getFont().getSize() > 16) {
86
            iconFileName = "calendar_2x.png";
87
        }
88
        final ImageIcon icon = new ImageIcon(this.getClass().getResource(iconFileName));
137 ilm 89
        button = new JButton(new AbstractAction(null, icon) {
90
 
91
            @Override
92
            public void actionPerformed(ActionEvent e) {
153 ilm 93
                calendarButtonPressed(e);
137 ilm 94
            }
95
 
96
        });
97
        button.setContentAreaFilled(false);
98
        button.setOpaque(false);
99
        button.setFocusable(false);
100
        button.setBorder(BorderFactory.createEmptyBorder());
101
        this.add(button);
102
 
103
        // init & synchronize text field
104
        setDate(fillWithCurrentDate ? new Date() : null, true);
105
    }
106
 
107
    public void setDate(Date date) {
108
        this.setDate(date, false);
109
    }
110
 
111
    public void setButtonVisible(boolean b) {
112
        this.button.setVisible(b);
113
    }
114
 
115
    private void setDate(Date date, boolean force) {
116
        Date oldDate = this.date;
117
        if (force || !equals(oldDate, date)) {
118
            this.date = date;
119
            if (text != null) {
120
                text.setValue(date);
121
            }
122
            firePropertyChange("value", oldDate, date);
123
        }
124
    }
125
 
126
    public Date getDate() {
127
        return date;
128
    }
129
 
130
    public JFormattedTextField getEditor() {
131
        return text;
132
    }
133
 
134
    public void setEditable(boolean b) {
135
        if (this.text != null) {
136
            this.text.setEditable(b);
137
        }
138
    }
139
 
140
    public boolean isEditable() {
141
        if (this.text != null) {
142
            return this.text.isEditable();
143
        }
144
        return true;
145
    }
146
 
147
    @Override
148
    public void setEnabled(boolean enabled) {
149
        super.setEnabled(enabled);
150
        for (final Component c : this.getComponents())
151
            c.setEnabled(enabled);
152
    }
153
 
154
    public void addActionListener(ActionListener listener) {
155
        this.actionListeners.add(listener);
156
 
157
    }
158
 
159
    public void removeActionListener(ActionListener listener) {
160
        this.actionListeners.remove(listener);
161
 
162
    }
163
 
164
    public void fireActionPerformed() {
165
        for (ActionListener l : actionListeners) {
166
            l.actionPerformed(new ActionEvent(this, 0, "dateChanged"));
167
        }
168
    }
169
 
170
    /**
171
     * Tries to find GraphicsConfiguration that contains the mouse cursor position. Can return null.
172
     */
173
    private GraphicsConfiguration getCurrentGraphicsConfiguration(Point popupLocation) {
174
        GraphicsConfiguration gc = null;
175
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
176
        GraphicsDevice[] gd = ge.getScreenDevices();
177
        for (int i = 0; i < gd.length; i++) {
178
            if (gd[i].getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
179
                GraphicsConfiguration dgc = gd[i].getDefaultConfiguration();
180
                if (dgc.getBounds().contains(popupLocation)) {
181
                    gc = dgc;
182
                    break;
183
                }
184
            }
185
        }
186
 
187
        return gc;
188
    }
189
 
190
    /**
191
     * Returns an point which has been adjusted to take into account of the desktop bounds, taskbar
192
     * and multi-monitor configuration.
193
     * <p>
194
     * This adustment may be cancelled by invoking the application with
195
     * -Djavax.swing.adjustPopupLocationToFit=false
196
     *
197
     * @param popupSize
198
     */
199
    Point adjustPopupLocationToFitScreen(int xPosition, int yPosition, Dimension popupSize, Dimension buttonSize) {
200
        Point popupLocation = new Point(xPosition, yPosition);
201
 
202
        if (GraphicsEnvironment.isHeadless()) {
203
            return popupLocation;
204
        }
205
 
206
        // Get screen bounds
207
        Rectangle scrBounds;
208
        GraphicsConfiguration gc = getCurrentGraphicsConfiguration(popupLocation);
209
        Toolkit toolkit = Toolkit.getDefaultToolkit();
210
        if (gc != null) {
211
            // If we have GraphicsConfiguration use it to get screen bounds
212
            scrBounds = gc.getBounds();
213
        } else {
214
            // If we don't have GraphicsConfiguration use primary screen
215
            scrBounds = new Rectangle(toolkit.getScreenSize());
216
        }
217
 
218
        // Calculate the screen size that popup should fit
219
 
220
        long popupRightX = (long) popupLocation.x + (long) popupSize.width;
221
        long popupBottomY = (long) popupLocation.y + (long) popupSize.height;
222
        int scrWidth = scrBounds.width;
223
        int scrHeight = scrBounds.height;
224
 
225
        // Insets include the task bar. Take them into account.
226
        Insets scrInsets = toolkit.getScreenInsets(gc);
227
        scrBounds.x += scrInsets.left;
228
        scrBounds.y += scrInsets.top;
229
        scrWidth -= scrInsets.left + scrInsets.right;
230
        scrHeight -= scrInsets.top + scrInsets.bottom;
231
 
232
        int scrRightX = scrBounds.x + scrWidth;
233
        int scrBottomY = scrBounds.y + scrHeight;
234
 
235
        // Ensure that popup menu fits the screen
236
        if (popupRightX > (long) scrRightX) {
237
            popupLocation.x = scrRightX - popupSize.width;
238
            popupLocation.y = popupLocation.y - buttonSize.height - popupSize.height - 2;
239
        }
240
 
241
        if (popupBottomY > (long) scrBottomY) {
242
            popupLocation.y = scrBottomY - popupSize.height;
243
            if (popupRightX + buttonSize.width < (long) scrRightX) {
244
                popupLocation.x += buttonSize.width;
245
            }
246
        }
247
 
248
        if (popupLocation.x < scrBounds.x) {
249
            popupLocation.x = scrBounds.x;
250
        }
251
 
252
        if (popupLocation.y < scrBounds.y) {
253
            popupLocation.y = scrBounds.y;
254
        }
255
 
256
        return popupLocation;
257
    }
258
 
259
    public void commitEdit() throws ParseException {
260
        if (text != null) {
261
            this.text.commitEdit();
262
        }
263
    }
264
 
153 ilm 265
    private void calendarButtonPressed(ActionEvent e) {
266
        final JComponent source = (JComponent) e.getSource();
267
 
268
        if (text != null) {
269
            // if the button isn't focusable, no FOCUS_LOST will occur and the text won't
270
            // get
271
            // committed (or reverted). So act as if the user requested a commit, so that
272
            // invalidEdit() can be called (usually causing a beep).
273
            if (!source.isFocusable()) {
274
                for (final Action a : text.getActions()) {
275
                    final String name = (String) a.getValue(Action.NAME);
276
                    if (JTextField.notifyAction.equals(name))
277
                        a.actionPerformed(new ActionEvent(text, e.getID(), null));
278
                }
279
            }
280
 
281
            // if after trying to commit, the value is invalid then don't show the popup
282
            if (!text.isEditValid())
283
                return;
284
        }
285
        if (dialog == null) {
286
            final JDialog d = new JDialog(SwingUtilities.getWindowAncestor(DatePicker.this));
287
            pickerPanel = new DatePickerPanel();
288
 
289
            d.setContentPane(pickerPanel);
290
            d.setUndecorated(true);
291
            pickerPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY));
292
            d.pack();
293
 
294
            pickerPanel.addPropertyChangeListener(DatePickerPanel.TIME_IN_MILLIS, new PropertyChangeListener() {
295
 
296
                @Override
297
                public void propertyChange(PropertyChangeEvent evt) {
298
                    if (!DatePicker.this.isEditable())
299
                        return;
300
                    final Long millis = (Long) evt.getNewValue();
301
                    final Calendar c = Calendar.getInstance();
302
                    c.setTimeInMillis(millis);
303
                    setDate(c.getTime());
304
                    if (dialog != null) {
305
                        dialog.dispose();
306
                        dialog = null;
307
                    }
308
                    fireActionPerformed();
309
                }
310
 
311
            });
312
            d.addWindowFocusListener(new WindowFocusListener() {
313
 
314
                public void windowGainedFocus(WindowEvent e) {
315
                    // do nothing
316
                }
317
 
318
                public void windowLostFocus(WindowEvent e) {
319
                    if (dialog != null) {
320
                        final Window oppositeWindow = e.getOppositeWindow();
321
                        if (oppositeWindow != null && SwingUtilities.isDescendingFrom(oppositeWindow, dialog)) {
322
                            return;
323
                        }
324
                        dialog.dispose();
325
                        dialog = null;
326
                    }
327
                    dialogLostFocusTime = System.currentTimeMillis();
328
                }
329
 
330
            });
331
            dialog = d;
332
        }
333
        // Set picker panel date
334
        final Calendar calendar = Calendar.getInstance();
335
        if (date != null) {
336
            calendar.setTime(date);
337
            pickerPanel.setSelectedDate(calendar);
338
        } else {
339
            pickerPanel.setSelectedDate(null);
340
        }
341
        // Show dialog
342
        final int x = source.getLocation().x;
343
        final int y = source.getLocation().y + source.getHeight();
344
        Point p = new Point(x, y);
345
        SwingUtilities.convertPointToScreen(p, DatePicker.this);
346
        p = adjustPopupLocationToFitScreen(p.x, p.y, dialog.getSize(), source.getSize());
347
        dialog.setLocation(p.x, p.y);
348
        final long time = System.currentTimeMillis() - dialogLostFocusTime;
349
        if (time > RELEASE_TIME_MS) {
350
            dialog.setVisible(true);
351
        } else {
352
            dialogLostFocusTime = System.currentTimeMillis() - RELEASE_TIME_MS;
353
        }
354
    }
355
 
356
    public static final boolean equals(Object o1, Object o2) {
137 ilm 357
        if (o1 == null && o2 == null)
358
            return true;
359
        if (o1 == null || o2 == null)
360
            return false;
361
        return o1.equals(o2);
362
    }
363
 
364
}