Java tutorial
/********************************************************************** * * Copyright (c) 2004 Olaf Willuhn * All rights reserved. * * This software is copyrighted work licensed under the terms of the * Jameica License. Please consult the file "LICENSE" for details. * **********************************************************************/ package de.willuhn.jameica.hbci.passports.pintan; import java.rmi.RemoteException; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.lang.StringUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.ProgressBar; import org.eclipse.swt.widgets.Spinner; import org.eclipse.swt.widgets.Text; import org.kapott.hbci.manager.FlickerRenderer; import org.kapott.hbci.smartcardio.ChipTanCardService; import org.kapott.hbci.smartcardio.SmartCardService; import de.willuhn.jameica.gui.GUI; import de.willuhn.jameica.gui.Part; import de.willuhn.jameica.gui.util.Container; import de.willuhn.jameica.gui.util.SWTUtil; import de.willuhn.jameica.gui.util.SimpleContainer; import de.willuhn.jameica.hbci.JameicaCompat; import de.willuhn.jameica.hbci.passports.pintan.rmi.PinTanConfig; import de.willuhn.jameica.messaging.StatusBarMessage; import de.willuhn.jameica.system.Application; import de.willuhn.jameica.system.Customizing; import de.willuhn.jameica.system.Settings; import de.willuhn.logging.Level; import de.willuhn.logging.Logger; import de.willuhn.util.ApplicationException; /** * Dialog fr die ChipTAN (OPTIC und USB). */ public class ChipTANDialog extends TANDialog { private final static Settings settings = new Settings(ChipTANDialog.class); private String code = null; private boolean smallDisplay = false; private boolean usb = false; private ChipTanCardService service = null; /** * ct. * @param config die PINTAN-Config. * @param code der Flicker-Code. * @throws RemoteException * @throws ApplicationException wenn der Flicker-Code nicht geparst werden konnte. */ public ChipTANDialog(PinTanConfig config, String code) throws RemoteException, ApplicationException { super(config); this.code = code; this.checkUSB(); Logger.info("using chiptan " + (this.usb ? "USB" : "OPTIC")); if (this.usb) this.setSideImage(SWTUtil.getImage("cardreader.png")); else this.setSideImage(null); // den Platz haben wir hier nicht. this.smallDisplay = Customizing.SETTINGS.getBoolean("application.scrollview", false); } /** * @see de.willuhn.jameica.hbci.passports.pintan.TANDialog#setText(java.lang.String) */ @Override public void setText(String text) { // Ueberschrieben, um den im Fliesstext am Anfang enthaltenen Flicker-Code rauszuschneiden. text = StringUtils.trimToNull(text); if (text != null) { final String token2 = "CHLGTEXT"; // Jetzt checken, ob die beiden Tokens enthalten sind int t1Start = text.indexOf("CHLGUC"); int t2Start = text.indexOf(token2); if (t1Start == -1 || t2Start == -1 || t2Start <= t1Start) { // Ne, nicht enthalten super.setText(text); return; } // Alles bis zum Ende des zweiten Tocken abschneiden text = text.substring(t2Start + token2.length()); // Jetzt noch diese 4-stellige Ziffer abschneiden, die auch noch hinten dran steht. // Aber nur, wenn auch noch relevant Text uebrig ist if (text.length() > 4) { String nums = text.substring(0, 4); if (nums.matches("[0-9]{4}")) text = text.substring(4); } } super.setText(text); return; } /** * Findet heraus, ob alle Vorbedingungen fuer die Nutzung von ChipTAN USB erfuellt sind. * @throws RemoteException */ private void checkUSB() throws RemoteException { // Checken, ob wir chipTAN USB ausgewaehlt haben PtSecMech mech = config != null ? config.getCurrentSecMech() : null; if (mech == null || !mech.useUSB()) return; // brauchen wir gar nicht weiter ueberlegen // Checken, ob der User schon entschieden hatte, dass er chipTAN USB NICHT nutzen moechte Boolean use = config != null ? config.isChipTANUSB() : null; if (use != null && !use.booleanValue()) return; // User hat explizit entschieden, chipTAN USB NICHT zu nutzen // Versuchen, die Verbindung zum Kartenleser herzustellen try { Logger.info("searching for smartcards, please wait..."); Application.getMessagingFactory().sendMessage(new StatusBarMessage( i18n.tr("Legen Sie die bitte Chipkarte ein."), StatusBarMessage.TYPE_INFO)); this.service = SmartCardService.createInstance(ChipTanCardService.class, this.config != null ? StringUtils.trimToNull(this.config.getCardReader()) : null); // Wir haben grundsaetzlich einen Kartenleser. if (this.service != null && this.config != null) { Logger.info("found smartcard, to be used: " + (use != null ? use : "<asking user>")); // User hat explizit entschieden, den Kartenleser per USB zu nutzen. if (use != null && use.booleanValue()) { this.usb = this.config.isChipTANUSB(); return; } // User fragen, ob er ihn auch nutzen moechte, wenn wir das noch nicht getan haben // Das Speichern der Antwort koennen wir nicht Jameica selbst ueberlassen, weil // die Entscheidung ja pro PIN/TAN-Config gelten soll und nicht global. this.usb = Application.getCallback().askUser(i18n.tr( "Es wurde ein USB-Kartenleser gefunden.\nMchten Sie diesen zur Erzeugung der TAN verwenden?"), false); this.config.setChipTANUSB(this.usb); } } catch (Throwable t) { Logger.info("no chipcard reader found, chipTAN USB not available: " + t.getMessage()); Logger.write(Level.DEBUG, "stacktrace for debugging purpose", t); } } /** * @see de.willuhn.jameica.gui.dialogs.PasswordDialog#paint(org.eclipse.swt.widgets.Composite) */ protected void paint(final Composite parent) throws Exception { Container container = new SimpleContainer(parent); if (this.usb) { Application.getMessagingFactory().sendMessage(new StatusBarMessage( i18n.tr("Legen Sie die bitte Chipkarte ein."), StatusBarMessage.TYPE_INFO)); this.setShowPassword(true); // Bei ChipTAN USB immer die TAN anzeigen, damit der User vergleichen kann. container.addHeadline(i18n.tr("ChipTAN USB")); container.addText(i18n.tr( "Legen Sie die Chipkarte ein und folgen Sie den Anweisungen des Kartenlesers. Klicken Sie auf \"OK\", wenn die TAN korrekt bertragen wurde."), true); ProgressBar progress = new ProgressBar(container.getComposite(), SWT.INDETERMINATE); final GridData gd = new GridData(GridData.FILL_HORIZONTAL); gd.horizontalSpan = 2; progress.setLayoutData(gd); final AtomicBoolean cancelled = new AtomicBoolean(false); final Thread usbThread = new Thread() { public void run() { try { Logger.info("trying to get TAN using USB cardreader"); String s = StringUtils.trimToNull(service.getTan(code)); if (s != null && !cancelled.get()) { setPassword(s, parent); } else { throw new Exception(i18n.tr("Keine TAN bertragen")); } } catch (Exception e) { if (!cancelled.get()) { Logger.error("unable to get tan from chipcard", e); setErrorText(i18n.tr("Fehler bei TAN-Ermittlung: {0}", e.getMessage())); } } } }; usbThread.start(); parent.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { cancelled.set(true); // Nur fuer den Fall, dass der Thread noch laeuft. try { if (usbThread != null) usbThread.interrupt(); } catch (Throwable th) { Logger.error("unable to interrupt USB thread", th); } } }); } else { container.addHeadline(i18n.tr(this.usb ? "ChipTAN USB" : "Flicker-Grafik")); container.addText(i18n.tr("Klicken Sie \"-\" bzw. \"+\", um die Breite anzupassen."), true); FlickerPart flicker = new FlickerPart(this.code); flicker.paint(parent); } // Hier stehen dann noch die Anweisungen von der Bank drin super.paint(parent); if (!this.usb) { // Wir muessen das Fenster wenigstens gross genug machen, damit der Flickercode reinpasst, falls // der User den recht verbreitert hat int width = settings.getInt("width", -1); if (width > 0) width += 10; // Wenigstens noch 10 Pixel Rand hinzufuegen. // Wir nehmen den groesseren von beiden Werten getShell().setMinimumSize(getShell().computeSize(width > WINDOW_WIDTH ? width : WINDOW_WIDTH, smallDisplay ? 520 : SWT.DEFAULT)); } } /** * Ueberschrieben, um Abwaertskompatibilitaet zu Jameica 2.8.0 herzustellen. * Dort war es noch nicht moeglich, das Passwort per Code zu setzen. * @param password das zu setzende Passwort. * @param parent das Composite. */ private void setPassword(final String password, final Composite parent) { GUI.getDisplay().asyncExec(new Runnable() { @Override public void run() { setPassword(password); // Checken, ob es das Passwort-Eingabefeld schon als Member in der Klasse gibt try { Object input = JameicaCompat.get(this, null, "passwordInput"); // OK, das Feld gibts. Dann ist die Version aktuell. if (input != null) return; // Feld gibts nicht. Dann muessen wir das Eingabefeld suchen applyPassword(password, parent.getChildren()); } catch (Exception e) { Logger.error("unable to update password field", e); } } }); } /** * Uebernimmt das Passwort. * @param password das Passwort. * @param controls die Controls. * @return true, wenn es uebernommen wurde. */ private boolean applyPassword(String password, Control[] controls) { if (controls == null || controls.length == 0) return false; for (Control c : controls) { if (c instanceof Text) { Text t = (Text) c; if (!t.isDisposed()) { // Das kann nur das Passwort-Feld sein. String value = StringUtils.trimToNull(t.getText()); if (value == null) { t.setText(password); return true; } } } // Rekursion if (c instanceof Composite) { Composite comp = (Composite) c; boolean b = applyPassword(password, comp.getChildren()); if (b) return true; // gefunden } } return false; } /** * Implementiert die Flicker-Grafik. */ private class FlickerPart extends FlickerRenderer implements Part { private Composite comp = null; private Canvas canvas = null; private boolean[] bits = new boolean[5]; private Color black = GUI.getDisplay().getSystemColor(SWT.COLOR_BLACK); private Color white = GUI.getDisplay().getSystemColor(SWT.COLOR_WHITE); /** * ct. * @param code der anzuzeigende Flicker-Code. * @throws ApplicationException */ private FlickerPart(String code) throws ApplicationException { super(code); setFrequency(settings.getInt("freq", FlickerRenderer.FREQUENCY_DEFAULT)); } /** * @see org.kapott.hbci.manager.FlickerRenderer#paint(boolean, boolean, boolean, boolean, boolean) */ public void paint(boolean b1, boolean b2, boolean b3, boolean b4, boolean b5) { this.bits[0] = b1; this.bits[1] = b2; this.bits[2] = b3; this.bits[3] = b4; this.bits[4] = b5; if (canvas == null || canvas.isDisposed()) return; // Redraw ausloesen // Wir sind hier nicht im Event-Dispatcher-Thread von SWT. Daher uebergeben wir das an diesen canvas.getDisplay().syncExec(new Runnable() { public void run() { // Wir muessen hier nochmal checken, weil das inzwischen disposed sein kann. if (canvas.isDisposed()) return; // Neu zeichnen canvas.redraw(); } }); } /** * @see de.willuhn.jameica.gui.Part#paint(org.eclipse.swt.widgets.Composite) */ public void paint(final Composite parent) throws RemoteException { if (this.canvas != null) return; //////////////////////////////////////////////////////////////////////// // die beiden Buttons zum Vergroessern und Verkleinern { Composite buttonComp = new Composite(parent, SWT.NONE); GridData buttonGd = new GridData(); buttonGd.horizontalAlignment = SWT.CENTER; buttonComp.setLayoutData(buttonGd); buttonComp.setLayout(new GridLayout(5, false)); final Label label1 = new Label(buttonComp, SWT.NONE); label1.setLayoutData(new GridData()); label1.setText(i18n.tr("Breite")); Button smaller = new Button(buttonComp, SWT.PUSH); smaller.setToolTipText(i18n.tr("Flicker-Code verkleinern")); smaller.setLayoutData(new GridData()); smaller.setText(" - "); smaller.addSelectionListener(new SelectionAdapter() { /** * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent) */ public void widgetSelected(SelectionEvent e) { GridData gd = (GridData) comp.getLayoutData(); if (gd.widthHint < SWTUtil.mm2px(20)) // unplausibel return; gd.widthHint -= 8; Point newSize = new Point(gd.widthHint, comp.getSize().y); comp.setSize(newSize); parent.layout(); // zentriert den Flicker-Code wieder } }); Button larger = new Button(buttonComp, SWT.PUSH); larger.setToolTipText(i18n.tr("Flicker-Code vergrern")); larger.setLayoutData(new GridData()); larger.setText(" + "); larger.addSelectionListener(new SelectionAdapter() { /** * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent) */ public void widgetSelected(SelectionEvent e) { GridData gd = (GridData) comp.getLayoutData(); // Eigentlich sollten 120mm als Grenze reichen. Wenn aber ein HighDPI-System falsche // DPI-Zahlen zurueckliefert, koennte das zu wenig sein. Siehe BUGZILLA 1565 if (gd.widthHint > SWTUtil.mm2px(400)) return; gd.widthHint += 8; Point newSize = new Point(gd.widthHint, comp.getSize().y); comp.setSize(newSize); parent.layout(); // zentriert den Flicker-Code wieder } }); final Label label2 = new Label(buttonComp, SWT.NONE); label2.setLayoutData(new GridData()); label2.setText(i18n.tr("Geschwindigkeit")); final Spinner spinner = new Spinner(buttonComp, SWT.BORDER); spinner.setToolTipText(i18n.tr("Geschwindigkeit in Hz (1/Sekunde)")); spinner.setLayoutData(new GridData()); spinner.setSelection(settings.getInt("freq", 14)); spinner.setMinimum(FlickerRenderer.FREQUENCY_MIN); spinner.setMaximum(FlickerRenderer.FREQUENCY_MAX); spinner.setIncrement(1); spinner.setTextLimit(2); spinner.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { int freq = spinner.getSelection(); settings.setAttribute("freq", freq); setFrequency(freq); } }); } // //////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // Das Composite fuer den Flicker-Code { this.comp = new Composite(parent, SWT.BORDER); this.comp.setBackground(GUI.getDisplay().getSystemColor(SWT.COLOR_BLACK)); final GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_CENTER); int width = SWTUtil.mm2px(60); // das muesste ca. die Breite von ReinerSCT-Geraeten sein if (width == -1) width = 206; // falls die Umrechnung nicht klappte gd.widthHint = settings.getInt("width", width); int height = SWTUtil.mm2px(smallDisplay ? 40 : 50); if (height == -1) width = smallDisplay ? 100 : 140; gd.heightHint = height; this.comp.setLayoutData(gd); // Wir lassen etwas Abstand zum Rand GridLayout gl = new GridLayout(); gl.marginHeight = 20; gl.marginWidth = 20; this.comp.setLayout(gl); // Beim Disposen stoppen wir den Flicker-Thread. this.comp.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { // Wir merken uns die Groesse des Canvas. Logger.info("saving width of flickercode: " + gd.widthHint + " px"); settings.setAttribute("width", gd.widthHint); } }); } // ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // Das eigentliche Canvas mit dem Flicker-Code. this.canvas = new Canvas(this.comp, SWT.NONE); this.canvas.setBackground(GUI.getDisplay().getSystemColor(SWT.COLOR_BLACK)); this.canvas.setLayoutData(new GridData(GridData.FILL_BOTH)); // Bei jedem Paint-Event aktualisieren wir die Balken this.canvas.addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { update(e.gc); } }); this.canvas.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { // Update-Thread stoppen stop(); } }); // ////////////////////////////////////////////////////////////////////////// // Und los gehts this.start(); } /** * Aktualisiert die Grafik basierend auf dem aktuellen Code. * @ctx der Graphics-Context. */ private void update(GC ctx) { int barwidth = canvas.getSize().x / 5; int margin = barwidth / 4; barwidth -= margin; // Rand noch abziehen barwidth += margin / 4; // und dann nochmal ein Viertel des Randes dran, damit wir am Ende keinen schwarzen Rand haben Point size = canvas.getSize(); for (int i = 0; i < this.bits.length; ++i) { ctx.setBackground(this.bits[i] ? white : black); ctx.fillRectangle(i * (barwidth + margin), 12, barwidth, size.y - 12); // die 12 sind der Abstand von oben (wegen den Dreiecken) } // Die Kalibrierungs-Dreiecke noch reinmalen ctx.setBackground(white); // Breite und Hoehe der Dreiecke int width = 12; int height = 6; // Abstand vom Rand -> halbe Balkenbreite minus halbe Dreieck-Breite int gap = barwidth / 2 - (width / 2); int[] left = new int[] { gap, 0, gap + width, 0, gap + (width / 2), height }; int[] right = new int[] { size.x - gap - width, 0, size.x - gap, 0, size.x - gap - (width / 2), height }; ctx.fillPolygon(left); ctx.fillPolygon(right); } } }