OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
18 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
18 ilm 5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.erp.core.sales.pos.io;
15
 
16
import org.openconcerto.erp.core.sales.pos.ui.BarcodeListener;
182 ilm 17
import org.openconcerto.erp.gs1.GS1Util;
83 ilm 18
import org.openconcerto.ui.DefaultGridBagConstraints;
19
import org.openconcerto.ui.component.ITextArea;
180 ilm 20
import org.openconcerto.utils.StringUtils;
18 ilm 21
 
83 ilm 22
import java.awt.Dimension;
23
import java.awt.GridBagConstraints;
24
import java.awt.GridBagLayout;
18 ilm 25
import java.awt.KeyEventDispatcher;
26
import java.awt.KeyboardFocusManager;
27
import java.awt.event.KeyEvent;
28
import java.util.ArrayList;
156 ilm 29
import java.util.HashMap;
18 ilm 30
import java.util.List;
156 ilm 31
import java.util.Map;
32
import java.util.Map.Entry;
18 ilm 33
import java.util.Timer;
34
import java.util.TimerTask;
35
 
83 ilm 36
import javax.swing.JFrame;
37
import javax.swing.JLabel;
38
import javax.swing.JPanel;
39
import javax.swing.JScrollPane;
18 ilm 40
import javax.swing.SwingUtilities;
83 ilm 41
import javax.swing.UIManager;
18 ilm 42
 
43
/**
44
 * Lecteur code barres, intercepte les événements clavier pour détecter un scan de code. Le code
182 ilm 45
 * barre doit terminer par un retour à la ligne. Pour les GS1 : Aim Code ID Character AIM ID doit
46
 * être activé, ]D2 pour databar / ]C1 pour GS1
142 ilm 47
 */
18 ilm 48
public class BarcodeReader implements KeyEventDispatcher {
49
 
83 ilm 50
    public int maxInterKeyDelay = 80;
18 ilm 51
    private static final int MIN_BARCODE_LENGTH = 2;
52
    private final List<BarcodeListener> listeners = new ArrayList<BarcodeListener>(1);
53
    private String value = "";
54
    private final List<KeyEvent> eve = new ArrayList<KeyEvent>();
55
    private long firstTime = -1;
56
    private Timer timer;
57
    // non final car un TimerTask n'est pas reutilisable
58
    private TimerTask task;
142 ilm 59
    private boolean enable = true;
156 ilm 60
    private boolean debug = false;
182 ilm 61
    private Map<Integer, String> mapCharacterFR = new HashMap<>();
18 ilm 62
 
83 ilm 63
    public BarcodeReader(int maxInterKeyDelay) {
18 ilm 64
        this.timer = null;
65
        this.task = null;
83 ilm 66
        this.maxInterKeyDelay = maxInterKeyDelay;
180 ilm 67
        this.mapCharacterFR.put((int) '&', "1");
68
        this.mapCharacterFR.put((int) 'é', "2");
69
        this.mapCharacterFR.put((int) '"', "3");
70
        this.mapCharacterFR.put((int) '\'', "4");
71
        this.mapCharacterFR.put((int) '(', "5");
72
        this.mapCharacterFR.put((int) '-', "6");
73
        this.mapCharacterFR.put((int) 'è', "7");
74
        this.mapCharacterFR.put((int) '_', "8");
75
        this.mapCharacterFR.put((int) 'ç', "9");
76
        this.mapCharacterFR.put((int) 'à', "0");
18 ilm 77
    }
78
 
79
    public synchronized void removeBarcodeListener(BarcodeListener l) {
80
        this.listeners.remove(l);
144 ilm 81
        if (this.listeners.isEmpty()) {
18 ilm 82
            stop();
83
        }
84
    }
85
 
86
    public synchronized void addBarcodeListener(final BarcodeListener l) {
144 ilm 87
        if (this.timer == null) {
88
            start();
89
        }
18 ilm 90
        this.listeners.add(l);
91
    }
92
 
93
    private void fire(String code) {
94
        for (int i = 0; i < this.listeners.size(); i++) {
95
            this.listeners.get(i).barcodeRead(code);
96
        }
97
    }
98
 
99
    /**
100
     * Commence à ecouter les évenements clavier pour intercepter les codes barres
142 ilm 101
     */
18 ilm 102
 
142 ilm 103
    public synchronized void start() {
104
        if (this.timer == null) {
105
            // init avant que les listeners s'en servent
106
            this.timer = new Timer(getClass().getName(), true);
107
            KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this);
180 ilm 108
            System.err.println("BarcodeReader start : scan delay " + this.maxInterKeyDelay + " ms");
142 ilm 109
        }
18 ilm 110
    }
111
 
112
    /**
113
     * Stoppe l'écoute sur les évenements clavier
142 ilm 114
     */
115
    public synchronized void stop() {
18 ilm 116
        if (this.timer != null) {
144 ilm 117
            System.err.println("BarcodeReader stop");
18 ilm 118
            this.timer.cancel();
93 ilm 119
            this.timer = null;
142 ilm 120
            KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this);
18 ilm 121
        }
122
    }
123
 
124
    @Override
125
    public boolean dispatchKeyEvent(KeyEvent e) {
180 ilm 126
        if (!this.enable) {
142 ilm 127
            return false;
180 ilm 128
        }
18 ilm 129
        if (this.task != null)
130
            this.task.cancel();
131
 
132
        final long t = e.getWhen();
133
        if (this.firstTime < 0) {
134
            this.firstTime = t;
135
        }
156 ilm 136
        int keyCode = e.getKeyCode();
18 ilm 137
 
142 ilm 138
        final long delay = t - this.firstTime;
180 ilm 139
        if (keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_DELETE || (delay > this.maxInterKeyDelay && keyCode != KeyEvent.VK_SHIFT)) {
18 ilm 140
            // touche normale
156 ilm 141
            if (this.debug) {
180 ilm 142
                System.err.println("Touche normale " + keyCode);
156 ilm 143
            }
142 ilm 144
            this.eve.add(e);
18 ilm 145
            redispatch();
142 ilm 146
            return true;
18 ilm 147
        }
148
 
156 ilm 149
        final char keyChar = e.getKeyChar();
18 ilm 150
        this.eve.add(e);
142 ilm 151
        if (e.getID() == KeyEvent.KEY_RELEASED) {
156 ilm 152
            if (keyCode == KeyEvent.VK_SHIFT) {
18 ilm 153
                // rien
156 ilm 154
                if (this.debug) {
155
                    System.err.println("SHIFT " + keyCode);
156
                }
180 ilm 157
            } else if (keyChar == ']') {
158
                this.value += keyChar;
159
                if (this.debug) {
160
                    System.err.println("]");
161
                }
156 ilm 162
            } else if (keyChar == '*' || keyChar == '$' || keyChar == '+' || keyChar == '/' || keyChar == '%' || keyChar == ' ') {
163
                this.value += keyChar;
164
                if (this.debug) {
165
                    System.err.println("KEY " + keyCode + " - " + keyChar);
166
                }
167
            } else if (keyCode >= KeyEvent.VK_0 && keyCode <= KeyEvent.VK_9 || keyCode >= KeyEvent.VK_A && keyCode <= KeyEvent.VK_Z) {
18 ilm 168
                // from KeyEvent : same as ASCII
156 ilm 169
                if (this.debug) {
180 ilm 170
                    System.err.println("[0-9] [A-Z] " + keyCode + " : " + keyChar);
156 ilm 171
                }
172
                this.value += (char) keyCode;
173
            } else if (keyCode == KeyEvent.VK_ENTER && this.value.length() >= MIN_BARCODE_LENGTH) {
18 ilm 174
                // fin de code barre
156 ilm 175
                if (this.debug) {
176
                    System.err.println("BARCODE OK ENTER OR LENGHT " + keyCode + " length = " + this.value.length() + " min length =" + MIN_BARCODE_LENGTH);
177
                }
18 ilm 178
                this.value = this.value.trim();
179
                fire(this.value);
180
                reset();
180 ilm 181
            } else if (this.mapCharacterFR.containsKey((int) keyChar)) {
156 ilm 182
                if (this.debug) {
180 ilm 183
                    System.err.println("MAP DEFAULT FR CHAR " + keyChar + " WITH " + this.mapCharacterFR.get((int) keyChar));
156 ilm 184
                }
180 ilm 185
                this.value += this.mapCharacterFR.get((int) keyChar);
156 ilm 186
            } else if (Character.isLetter(keyChar) || Character.isDigit(keyChar)) {
187
                this.value += keyChar;
188
                if (this.debug) {
189
                    System.err.println("LETTER OR DIGIT " + keyChar);
190
                }
180 ilm 191
            } else if (keyChar == 29) {
192
                this.value += '\u001D';
193
                if (this.debug) {
194
                    System.err.println("<GS>");
195
                }
196
            } else if (keyChar == KeyEvent.CHAR_UNDEFINED) {
197
                System.err.println("CHAR_UNDEFINED");
182 ilm 198
                this.value += '\u001D';
18 ilm 199
            } else {
200
                // Caractere non code barre
156 ilm 201
                if (this.debug) {
180 ilm 202
                    System.err.println("CHAR NON CODE BARRE keyCode:" + keyCode + " keyChar:" + keyChar);
156 ilm 203
                }
18 ilm 204
                redispatch();
205
            }
206
            // lance un timer s'il reste des evenements non dispatchés
144 ilm 207
            if (!this.eve.isEmpty()) {
18 ilm 208
                this.firstTime = t;
209
                this.task = new TimerTask() {
210
                    @Override
211
                    public void run() {
182 ilm 212
                        SwingUtilities.invokeLater(new Runnable() {
213
                            @Override
214
                            public void run() {
215
                                redispatch();
216
                            }
217
                        });
18 ilm 218
                    }
219
                };
182 ilm 220
                try {
221
                    this.timer.schedule(this.task, this.maxInterKeyDelay);
222
                } catch (Exception ex) {
223
                    ex.printStackTrace();
224
                }
18 ilm 225
            }
226
            // si pas d'evenement, pas de temps associé
144 ilm 227
            assert !this.eve.isEmpty() || this.firstTime == -1;
18 ilm 228
        }
229
        return true;
180 ilm 230
 
18 ilm 231
    }
232
 
233
    private void redispatch() {
234
        for (int i = 0; i < this.eve.size(); i++) {
235
            final KeyEvent ee = this.eve.get(i);
236
            KeyboardFocusManager.getCurrentKeyboardFocusManager().redispatchEvent(ee.getComponent(), ee);
237
            for (int j = 0; j < this.listeners.size(); j++) {
238
                this.listeners.get(j).keyReceived(ee);
239
            }
240
        }
241
        reset();
242
    }
243
 
244
    private void reset() {
245
        this.value = "";
246
        this.eve.clear();
247
        this.firstTime = -1;
248
    }
249
 
156 ilm 250
    public Map<Integer, String> getMapCharacterFR() {
180 ilm 251
        return this.mapCharacterFR;
156 ilm 252
    }
253
 
254
    public void setDebug(boolean debug) {
255
        this.debug = debug;
256
    }
257
 
83 ilm 258
    public static void main(String[] args) {
259
        String delay = "80";
260
        if (args.length > 0) {
261
            delay = args[0];
262
        }
263
        final int d = Integer.parseInt(delay);
264
        SwingUtilities.invokeLater(new Runnable() {
265
 
266
            @Override
267
            public void run() {
268
                try {
269
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
270
                } catch (Exception e) {
271
                    e.printStackTrace();
272
                }
273
                System.out.println("BarCode reader");
274
                System.out.println("Using inter key delay: " + d);
275
                JFrame f = new JFrame();
276
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
277
                JPanel panel = new JPanel();
278
                f.setTitle("Barcode reader test");
279
                f.setContentPane(panel);
280
                panel.setLayout(new GridBagLayout());
281
                GridBagConstraints c = new DefaultGridBagConstraints();
282
                final JLabel l = new JLabel("BarCode reader output :");
283
                panel.add(l, c);
284
                c.gridy++;
285
                c.weighty = 1;
286
                c.weightx = 1;
287
                c.fill = GridBagConstraints.BOTH;
288
                final ITextArea t1 = new ITextArea();
289
                panel.add(new JScrollPane(t1), c);
290
 
291
                BarcodeReader reader = new BarcodeReader(d);
156 ilm 292
                reader.setDebug(true);
293
 
294
                System.err.println("FR MAP");
295
                for (Entry<Integer, String> string : reader.getMapCharacterFR().entrySet()) {
296
                    System.err.println(string.getKey() + " --> " + string.getValue());
297
                }
298
 
83 ilm 299
                reader.addBarcodeListener(new BarcodeListener() {
300
 
301
                    @Override
302
                    public void keyReceived(KeyEvent ee) {
303
                        System.err.println("BarcodeReader keyReceived() : " + ee);
304
                    }
305
 
306
                    @Override
307
                    public void barcodeRead(String code) {
308
                        t1.append("Barcode OK : '" + code + "'\n");
180 ilm 309
                        t1.append("Hex: " + StringUtils.bytesToHexString(code.getBytes()));
182 ilm 310
                        t1.append("\n");
311
                        GS1Util e = new GS1Util();
312
                        try {
313
                            t1.append("GS1: " + e.parseFromScanner(code).formatHumanReadable());
314
                        } catch (Exception ex) {
315
                            t1.append("GS1: " + ex.getMessage());
316
                        }
317
                        t1.append("\n\n");
83 ilm 318
                    }
319
                });
320
 
321
                f.setSize(new Dimension(640, 480));
322
                f.setLocationRelativeTo(null);
323
                f.setVisible(true);
324
 
325
            }
326
        });
327
 
328
    }
142 ilm 329
 
330
    public void setEnabled(boolean b) {
331
        this.enable = b;
332
    }
18 ilm 333
}