package de.duehl.swing.ui.dialogs.base;

/*
 * Copyright 2025 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.Window;
import java.net.URL;

import javax.swing.JRootPane;
import javax.swing.SwingUtilities;

import de.duehl.basics.logging.Logger;
import de.duehl.basics.logic.ErrorHandler;
import de.duehl.swing.logic.LongTimeProcessInformer;
import de.duehl.swing.logic.Quitter;
import de.duehl.swing.ui.GuiTools;
import de.duehl.swing.ui.colors.Colorizer;
import de.duehl.swing.ui.dialogs.DialogHelper;
import de.duehl.swing.ui.dialogs.base.additional.DialogCloser;
import de.duehl.swing.ui.dialogs.base.additional.LocationGetter;
import de.duehl.swing.ui.dialogs.base.additional.WindowRefresher;
import de.duehl.swing.ui.elements.DisabledGlassPane;
import de.duehl.swing.ui.handler.error.GlassPaneDeactivatingErrorHandler;
import de.duehl.swing.ui.key.BindKeysOnRootPane;
import de.duehl.swing.ui.listener.ClosingWindowListener;
import de.duehl.swing.ui.text.html.HtmlDialog;
import de.duehl.swing.ui.text.html.HtmlFrame;

/**
 * Diese Klasse ist die gemeinsame abstrakte Basis der Dialoge mit JFrame oder JDialog.
 *
 * @version 1.01     2025-07-18
 * @author Christian Dühl
 */

public abstract class AbstractDialogBase implements DialogCloser, LongTimeProcessInformer,
    LocationGetter {

    protected static final Dimension IGNORE_DIMENSION = new Dimension(-1, -1);

    /** Die Position des Rahmens der Oberfläche, vor der dieser Dialog erzeugt wird. */
    private final Point parentLocation;

    /** Das Icon für das Programm. */
    private final Image programImage;

    /** Das Fenster, das dargestellt wird. */
    private Window window; // de fakto final

    /** Die Größe des Fensters. */
    private final Dimension windowDimension;

    /** Die Farbverwaltung für die Gui. */
    private final Colorizer colorizer;

    /** GlassPane, die die Bedienung der Gui-Elemente verhindert, wenn ein Prozess im Gang ist. */
    private final DisabledGlassPane glassPane;

    /** Gibt an, ob absolut statt relativ zur Position des Parents positioniert werden soll. */
    private boolean hasAbsoluteLocation;

    /** Absolute Position, falls hasAbsoluteLocation true ist. */
    private Point absoluteWindowLocation;

    /**
     * Ist diese Option gesetzt, so wird die Größe des Fensters nicht fest gesetzt, sondern
     * mit pack() auf die kleinste passende Größe festgesetzt.
     *
     * Man kann optional mit setPreferredSize() eine minimal gewünschte Größe angeben.
     */
    private boolean ignoreSize;

    private int minWidth = 0;
    private int minHeight = 0;

    /**
     * Zähler, wird bei jedem startLongTimeProcess erhöht und bei jedem endLongTimeProcess
     * verringert.
     */
    private int longTimeProccessCounter = 0;

    /** Gibt an, ob der longTimeProccessCounter berücksichtigt werden soll. */
    private boolean useLongTimeProccessCounter = false;

    /** Gibt an, ob ein ClosingWindowListener benutzt wird. */
    private boolean weUseAClosingWindowListener;

    /**
     * Konstruktor für ein gepacktes Fenster ohne fest vorgegebene Dimension.
     *
     * @param programImage
     *            Das Icon für das Programm.
     * @param colorizer
     *            Die Farbverwaltung für die Gui.
     * @param window
     *            Die Komponente mit dem JDialog oder JFrame.
     */
    public AbstractDialogBase(Image programImage, Colorizer colorizer) {
        this(new Point(0, 0), programImage, IGNORE_DIMENSION, colorizer);
        ignoreSize();
    }

    /**
     * Konstruktor.
     *
     * @param programImage
     *            Das Icon für das Programm.
     * @param windowDimension
     *            Größe des Fensters.
     * @param colorizer
     *            Die Farbverwaltung für die Gui.
     * @param window
     *            Die Komponente mit dem JDialog oder JFrame.
     */
    public AbstractDialogBase(Image programImage, Dimension windowDimension, Colorizer colorizer) {
        this(new Point(0, 0), programImage, windowDimension, colorizer);
    }

    /**
     * Konstruktor für ein gepacktes Fenster ohne fest vorgegebene Dimension.
     *
     * @param parentLocation
     *            Die Position des Rahmens der Oberfläche, vor der dieses Fensters erzeugt wird.
     * @param programImage
     *            Das Icon für das Programm.
     * @param colorizer
     *            Die Farbverwaltung für die Gui.
     * @param window
     *            Die Komponente mit dem JDialog oder JFrame.
     */
    public AbstractDialogBase(Point parentLocation, Image programImage, Colorizer colorizer) {
        this(parentLocation, programImage, IGNORE_DIMENSION, colorizer);
        ignoreSize();
    }

    /**
     * Konstruktor.
     *
     * @param parentLocation
     *            Die Position des Rahmens der Oberfläche, vor der dieses Fensters erzeugt wird.
     * @param programImage
     *            Das Icon für das Programm.
     * @param windowDimension
     *            Die Größe des Fensters.
     * @param colorizer
     *            Die Farbverwaltung für die Gui.
     * @param window
     *            Die Komponente mit dem JDialog oder JFrame.
     */
    public AbstractDialogBase(Point parentLocation, Image programImage, Dimension windowDimension,
            Colorizer colorizer) {
        this.parentLocation = parentLocation;
        this.programImage = programImage;
        this.windowDimension = windowDimension;
        this.colorizer = colorizer;

        hasAbsoluteLocation = false;

        ignoreSize = false;
        minWidth = 0;
        minHeight = 0;

        weUseAClosingWindowListener = false;

        glassPane = new DisabledGlassPane();
    }

    /** Setter für die Komponente mit dem JDialog oder JFrame. */
    protected final void setWindow(Window window) {
        this.window = window;
        glassPane.connectTo(window);
    }

    /** Baut die Gui auf. */
    protected final void fillDialog() {
        setColors(window);
        if (null != programImage) {
            window.setIconImage(programImage);
        }
        window.setLayout(new BorderLayout());

        populateDialog();

        if (ignoreSize || windowDimension.equals(IGNORE_DIMENSION)) { // sollte immer beides sein
            window.setMinimumSize(new Dimension(minWidth, minHeight));
            window.pack();
        }
        else {
            window.setPreferredSize(windowDimension);
        }

        window.pack();
        //window.setResizable(false); // TODO Optional per Methodenaufruf ...
        window.setLocation(calculateMyLocation());
    }

    /** Füllt den Rahmen des Dialoges. */
    protected abstract void populateDialog();

    private Point calculateMyLocation() {
        if (hasAbsoluteLocation) {
            return absoluteWindowLocation;
        }
        else {
            double x = parentLocation.getX() + 150;
            double y = parentLocation.getY() + 90;
            Point point = new Point();
            point.setLocation(x, y);
            return point;
        }
    }

    /** Legt die Vordergrundfarbe fest, wenn ein Colorizer hinterlegt wurde, dann aus diesem. */
    protected final Color determineForeground() {
        if (null != colorizer) {
            return colorizer.getForegroundColor();
        }
        else {
            return new Color(0x00, 0x00, 0x00); // new Color(0x00, 0x00, 0x37);
            //return dialog.getForeground(); // NULL !!!
        }
    }

    /** Legt die Hintergrundfarbe fest, wenn ein Colorizer hinterlegt wurde, dann aus diesem. */
    protected final Color determineBackground() {
        if (null != colorizer) {
            return colorizer.getBackgroundColor();
        }
        else {
            return new Color(0xff, 0xff, 0xff);
            //return dialog.getBackground();
        }
    }

    /**
     * Fügt eine Komponente zum Rahmen des Programms hinzu
     *
     * @param component
     *            Komponente.
     * @param constraints
     *            Gibt die Position an (z.B. BorderLayout.CENTER).
     */
    protected final void add(Component component, Object constraints) {
        window.add(component, constraints);
    }

    /** Beendet das Fenster. */
    @Override
    public final void closeDialog() {
        deactivateGlassPane();
        window.setVisible(false);
        /*
         * Gegen gelegentliche Fehler der Art "Exception while removing
         * reference: java.lang.InterruptedException"
         * vgl. http://stackoverflow.com/questions/2873449/
         * occasional-interruptedexception-when-quitting-a-swing-application
         */
        System.gc();
        window.dispose();
    }

    /**
     * Zeigt das Fenster an oder versteckt ihn.
     *
     * @param visible
     *            Gibt an, ob das Fenster angezeigt (true) oder versteckt (false) werden soll.
     */
    public void setVisible(boolean visible) {
        /* Kein SwingUtilities.invokeLater(), sonst stoppt der aufrufende Prozess nicht! */
        window.setVisible(visible);
    }

    /** Erneuert die Anzeige des Dialogs. */
    protected void refresh() {
        window.revalidate();
        window.repaint();
    }

    /** Gibt ein Objekt zurück, das die Anzeige des Dialogs erneuern kann. */
    protected WindowRefresher createRefresher() {
        return () -> refresh();
    }

    /** Getter für die Komponente des Dialogs, der dargestellt wird. */
    protected Component getDialog() {
        return window;
    }

    /** Färbt die übergebene Komponente ein, falls ein Colorizer übergeben wurde. */
    protected void setColors(Component component) {
        if (colorizer != null) {
            colorizer.setColors(component);
        }
    }

    /** Getter für die Position des Rahmens der Oberfläche, vor der dieser Dialog erzeugt wird. */
    protected final Point getParentLocation() {
        return parentLocation;
    }

    /** Getter für das Icon für das Programm. */
    public final Image getProgramImage() {
        return programImage;
    }

    /** Getter für den Colorizer. */
    protected final Colorizer getColorizer() {
        return colorizer;
    }

    /** Verlangt nach dem Focus. */
    public void requestFocus() {
        window.requestFocus();
    }

    /**
     * Setzt die bevorzugte Größe des Fensters und überschreibt damit die beim Konstruktor
     * mitgegebene.
     */
    public void setPreferredSize(Dimension dimension) {
        window.setPreferredSize(dimension);
    }

    /**
     * Ignoriert bei fillDialog() die Größenangabe und packt das Fenster oder den Dialog.
     *
     * Eine minimale Größe kann mit setMinimumSize(), oder setMinimumWidth() und setMinimumHeight()
     * angegeben werden.
     */
    protected void ignoreSize() {
        ignoreSize = true;
    }

    /**
     * Setzt die minimale Größe des Fensters.
     *
     * Hierfür muss im Konstruktor ignoreSize() aufgerufen werden, um die absolute Fensterdimension
     * zu ignorieren.
     */
    protected void setMinimumSize(Dimension dimension) {
        window.setMinimumSize(dimension);
        minWidth = (int) dimension.getWidth();
        minHeight = (int) dimension.getHeight();
    }

    /**
     * Setzt die minimale Größe des Fensters.
     *
     * Hierfür muss im Konstruktor ignoreSize() aufgerufen werden, um die absolute Fensterdimension
     * zu ignorieren.
     */
    protected void setMinimumSize(int minWidth, int minHeight) {
        this.minWidth = minWidth;
        this.minHeight = minHeight;
        window.setMinimumSize(new Dimension(minWidth, minHeight));
    }

    /**
     * Setzt die minimale Breite.
     *
     * Hierfür muss im Konstruktor ignoreSize() aufgerufen werden, um die absolute Fensterdimension
     * zu ignorieren.
     */
    protected void setMinimumWidth(int minWidth) {
        this.minWidth = minWidth;
    }

    /**
     * Setzt die minimale Höhe für den Fall, dass bei fillDialog() die Größenangabe ignoriert
     * werden soll.
     */
    protected void setMinimumHeight(int minHeight) {
        this.minHeight = minHeight;
    }

    /**
     * Macht die GlassPane sichtbar und setzt den Mauscursor aus den Wait-Cursor.
     *
     * @param text
     *            Diese Nachricht wird in der Mitte der GlassPane angezeigt, darunter läuft eine
     *            Stoppuhr um anzuzeigen, wie lange die GlassPane nun schon aktiv ist.
     */
    private void activateGlassPane(String message) {
        SwingUtilities.invokeLater(() -> glassPane.activate(message + " ..."));
    }

    /** Versteckt die GlassPane wieder und beendet die Stoppuhr. */
    private void deactivateGlassPane() {
        SwingUtilities.invokeLater(() -> glassPane.deactivate());
    }

    /** Zum Merken des alten Cursors. */
    private Cursor saveCursor;

    /** Wird beim Abbruch des Dialogs aufgerufen. */
    private Quitter quitter;

    /**
     * Startet einen Prozess, der eine Weile braucht.
     *
     * @param message
     *            Anzuzeigende Nachricht.
     */
    @Override
    public synchronized void startLongTimeProcess(String message) {
        if (useLongTimeProccessCounter) {
            ++longTimeProccessCounter;
        }

        if (!useLongTimeProccessCounter || longTimeProccessCounter == 1) {
            activateGlassPane(message);
            saveCursor = window.getCursor();
            window.setCursor(new Cursor(Cursor.WAIT_CURSOR));
        }
    }

    /** Beendet einen Prozess, der eine Weile braucht. */
    @Override
    public synchronized void endLongTimeProcess() {
        if (useLongTimeProccessCounter) {
            --longTimeProccessCounter;
        }

        if (!useLongTimeProccessCounter || longTimeProccessCounter == 0) {
            deactivateGlassPane(); // nutzt invokeLater()
            SwingUtilities.invokeLater(() -> window.setCursor(saveCursor)); // 15.03.2021 invokeLater
        }
    }

    /**
     * Damit verschiedene Prozesse (zum Beispiel das Einlesen von unterschiedlichen Listen
     * von Dateien (wie z.B. in der WzPreparationGui), wird mitgezählt, ob auch so oft
     * endLongTimeProzess() aufgerufen wurde, wie vorher startLongTimeProzess(). Erst dann
     * wird die GlassPane wirklich deaktiviert. Die neuen Titel der GlassPane werden
     * allerdings wirklich angezeigt.
     *
     * Standardmäßig schaltet das erste endLongTimeProzess() die GlassPane ab.
     */
    public void useLongTimeProccessCounter() {
        this.useLongTimeProccessCounter = true;
    }

    /** Setzt die Lokation. */
    public void setLocation(int x, int y) {
        setLocation(new Point(x, y));
    }

    protected void setLocation(Point location) {
        hasAbsoluteLocation = true;
        absoluteWindowLocation = location;
        window.setLocation(location);
    }

    /** Gibt die Position des Dialogs an. */
    @Override
    public Point getLocation() {
        return window.getLocation();
    }

    protected void dispose() {
        window.dispose();
    }

    /** Getter für das Fenster, das dargestellt wird, als Komponente. */
    public Component getWindowAsComponent() {
        return window;
    }

    public Window getWindow() {
        return window;
    }

    /** Zeigt den Waitcursor an. */
    public void waitcursorOn() {
        GuiTools.waitcursorOn(window);
    }

    /** Zeigt den normalen Cursor an. */
    public void waitcursorOff() {
        GuiTools.waitcursorOff(window);
    }

    /**
     * Getter für die GlassPane, die die Bedienung der Gui-Elemente verhindert, wenn ein Prozess im
     * Gang ist.
     */
    protected DisabledGlassPane getGlassPane() {
        return glassPane;
    }

    /** Erzeugt einen passenden ErrorHandler, der sich auch um die GlassPane kümmert. */
    public ErrorHandler createErrorHandler() {
        return createGlassPaneDeactivatingErrorHandler();
    }

    /** Erzeugt einen passenden ErrorHandler, der sich auch um die GlassPane kümmert. */
    public GlassPaneDeactivatingErrorHandler createGlassPaneDeactivatingErrorHandler() {
        GlassPaneDeactivatingErrorHandler error = new GlassPaneDeactivatingErrorHandler(
                programImage, getWindowAsComponent(), glassPane);
        return error;
    }

    /** Erzeugt einen passenden ErrorHandler mit Logger, der sich auch um die GlassPane kümmert. */
    public GlassPaneDeactivatingErrorHandler createErrorHandler(Logger logger) {
        GlassPaneDeactivatingErrorHandler error = new GlassPaneDeactivatingErrorHandler(
                programImage, getWindowAsComponent(), glassPane, logger);
        return error;
    }

    /** Passt die Größe an die Gui-Elemente an. */
    protected void pack() {
        window.pack();
    }

    /** Repaints this component. */
    public void repaint() {
        window.repaint();
    }

    /** Validates this container and all of its subcomponents. */
    public void validate() {
        window.validate();
    }

    /** Invalidates the container. . */
    public void invalidate() {
        window.invalidate();
    }

    /** Revalidates the component hierarchy up to the nearest validate root. */
    public void revalidate() {
        window.revalidate();
    }

    /** Gibt die RootPane des Dialogs oder des Frames zurück. */
    public abstract JRootPane getRootPane();

    /** Fügt die Tastaturbindung Escape hinzu. */
    protected void setKeyBindingEscape(Runnable escapeRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingEscape(escapeRunnable);
        /*
         * Man kann leider keine Klassenvariable bindKeysOnRootPane verwenden, da beim
         * Aufruf des Konstruktors der Dialog bzw. Frame noch nicht existiert und
         * die Keybindings in der Regel vor fillDialog gesetzt werden, weil das das
         * letzte Kommando im Konstruktor der verwendenden Klassen sein soll.
         * Daher muss hier leider immer ein BindKeysOnRootPane-Objekt erzeugt werden.
         *
         * Möglich wäre jeweils initialiseBindKeysOnRootPane() aufzurufen, und falls null dann
         * das Objekt zu erzeugen, aber das ist auch irgendwie hässlich, dann lieber so!
         */
    }

    /** Fügt die Tastaturbindung Home (Pos1) hinzu. */
    protected void setKeyBindingHome(Runnable homeRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingHome(homeRunnable);
    }

    /** Fügt die Tastaturbindung End (Ende) hinzu. */
    protected void setKeyBindingEnd(Runnable endRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingEnd(endRunnable);
    }

    /** Fügt die Tastaturbindung Ctrl-Home (Ctrl-Pos1) hinzu. */
    protected void setKeyBindingCtrlHome(Runnable ctrlHomeRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingCtrlHome(ctrlHomeRunnable);
    }

    /** Fügt die Tastaturbindung Ctrl-End (Ctrl-Ende) hinzu. */
    protected void setKeyBindingCtrlEnd(Runnable ctrlEndRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingCtrlEnd(ctrlEndRunnable);
    }

    /** Fügt die Tastaturbindung PageUp (Bild auf) hinzu. */
    protected void setKeyBindingPageUp(Runnable pageUpRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingPageUp(pageUpRunnable);
    }

    /** Fügt die Tastaturbindung PageDown (Bild runter) hinzu. */
    protected void setKeyBindingPageDown(Runnable pageDownRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingPageDown(pageDownRunnable);
    }

    /** Fügt die Tastaturbindung Enter hinzu. */
    protected void setKeyBindingEnter(Runnable enterRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingEnter(enterRunnable);
    }

    /** Fügt die Tastaturbindung Ctrl-Enter hinzu. */
    protected void setKeyBindingCtrlEnter(Runnable ctrlEnterRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingCtrlEnter(ctrlEnterRunnable);
    }

    /** Fügt die Tastaturbindung F1 hinzu. */
    protected void setKeyBindingF1(Runnable f1Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF1(f1Runnable);
    }

    /** Fügt die Tastaturbindung F2 hinzu. */
    protected void setKeyBindingF2(Runnable f2Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF2(f2Runnable);
    }

    /** Fügt die Tastaturbindung F3 hinzu. */
    protected void setKeyBindingF3(Runnable f3Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF3(f3Runnable);
    }

    /** Fügt die Tastaturbindung F4 hinzu. */
    protected void setKeyBindingF4(Runnable f4Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF4(f4Runnable);
    }

    /** Fügt die Tastaturbindung F5 hinzu. */
    protected void setKeyBindingF5(Runnable f5Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF5(f5Runnable);
    }

    /** Fügt die Tastaturbindung F6 hinzu. */
    protected void setKeyBindingF6(Runnable f6Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF6(f6Runnable);
    }

    /** Fügt die Tastaturbindung F7 hinzu. */
    protected void setKeyBindingF7(Runnable f7Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF7(f7Runnable);
    }

    /** Fügt die Tastaturbindung F8 hinzu. */
    protected void setKeyBindingF8(Runnable f8Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF8(f8Runnable);
    }

    /** Fügt die Tastaturbindung F9 hinzu. */
    protected void setKeyBindingF9(Runnable f9Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF9(f9Runnable);
    }

    /** Fügt die Tastaturbindung F10 hinzu. */
    protected void setKeyBindingF10(Runnable f10Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF10(f10Runnable);
    }

    /** Fügt die Tastaturbindung F11 hinzu. */
    protected void setKeyBindingF11(Runnable f11Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF11(f11Runnable);
    }

    /** Fügt die Tastaturbindung F12 hinzu. */
    protected void setKeyBindingF12(Runnable f12Runnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingF12(f12Runnable);
    }

    /** Fügt die Tastaturbindung "links" hinzu. */
    protected void setKeyBindingLeft(Runnable leftRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingLeft(leftRunnable);
    }

    /** Fügt die Tastaturbindung "rechts" hinzu. */
    protected void setKeyBindingRight(Runnable rightRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingRight(rightRunnable);
    }

    /** Fügt die Tastaturbindung "oben" hinzu. */
    protected void setKeyBindingUp(Runnable upRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingUp(upRunnable);
    }

    /** Fügt die Tastaturbindung "unten" hinzu. */
    protected void setKeyBindingDown(Runnable downRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingDown(downRunnable);
    }

    /** Fügt die Tastaturbindung Ctrl-links hinzu. */
    protected void setKeyBindingCtrlLeft(Runnable ctrlLeftRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingCtrlLeft(ctrlLeftRunnable);
    }

    /** Fügt die Tastaturbindung Ctrl-rechts hinzu. */
    protected void setKeyBindingCtrlRight(Runnable ctrlRightRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingCtrlRight(ctrlRightRunnable);
    }

    /** Fügt die Tastaturbindung Ctrl-oben hinzu. */
    protected void setKeyBindingCtrlUp(Runnable ctrlUpRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingCtrlUp(ctrlUpRunnable);
    }

    /** Fügt die Tastaturbindung Ctrl-unten hinzu. */
    protected void setKeyBindingCtrlDown(Runnable ctrlDownRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingCtrlDown(ctrlDownRunnable);
    }

    /** Fügt die Tastaturbindung Alt-links hinzu. */
    protected void setKeyBindingAltLeft(Runnable altLeftRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingAltLeft(altLeftRunnable);
    }

    /** Fügt die Tastaturbindung Alt-rechts hinzu. */
    protected void setKeyBindingAltRight(Runnable altRightRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingAltRight(altRightRunnable);
    }

    /** Fügt die Tastaturbindung Alt-oben hinzu. */
    protected void setKeyBindingAltUp(Runnable altUpRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingAltUp(altUpRunnable);
    }

    /** Fügt die Tastaturbindung Alt-unten hinzu. */
    protected void setKeyBindingAltDown(Runnable altDownRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingAltDown(altDownRunnable);
    }

    /** Fügt die Tastaturbindung Tabulator hinzu. */
    protected void setKeyBindingTabulator(Runnable tabulatorRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingTabulator(tabulatorRunnable);
    }

    /** Fügt die Tastaturbindung Entfernen hinzu. */
    protected void setKeyBindingDelete(Runnable deleteRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingDelete(deleteRunnable);
    }

    /** Fügt die Tastaturbindung Einfügen hinzu. */
    protected void setKeyBindingInsert(Runnable deleteRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingInsert(deleteRunnable);
    }

    /** Fügt die Tastaturbindung Nummmernblock-Plus hinzu. */
    protected void setKeyBindingNumpadPlus(Runnable numpadPlusRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingNumpadPlus(numpadPlusRunnable);
    }

    /** Fügt die Tastaturbindung Nummmernblock-Minus hinzu. */
    protected void setKeyBindingNumpadMinus(Runnable numpadMinusRunnable) {
        BindKeysOnRootPane bind = new BindKeysOnRootPane(getRootPane());
        bind.setKeyBindingNumpadMinus(numpadMinusRunnable);
    }

    /**
     * Fügt dem Frame oder Dialog die Tastenkombination Escape hinzu, woraufhin die übergebene
     * Komponente beendet wird.
     */
    protected final void addEscapeBehaviour() {
        DialogHelper.addEscapeBehaviourForDialogBase(this);
    }

    /** Fügt einen Listener zum Beenden des Dialogs über das [x] oben rechts hinzu. */
    protected void addClosingWindowListener(Quitter quitter) {
        window.addWindowListener(new ClosingWindowListener(quitter));
        this.quitter = quitter;
        weUseAClosingWindowListener = true;
    }

    /**
     * Falls ein Quitter hinterlegt wurde, wird dieser aufgerufen, anderenfalls wird der Dialog
     * geschlossen.
     */
    public void closeOrCallQuitter() {
        if (weUseAClosingWindowListener) {
            quitter.quit();
        }
        else {
            closeDialog();
        }
    }

    /**
     * Zeigt ein Dokument als HTML an über die Url in der Jar.
     *
     * @param title
     *            Titel der Anzuzeigenden HTML-Seite.
     * @param url
     *            Url zum Dokument in der Jar.
     */
    protected final void showHtmlViaUrl(String title, URL url) {
        HtmlFrame dialog = new HtmlFrame(title, getProgramImage(), getLocation());
        dialog.showHtml(url);
        dialog.setVisible(true);
    }

    /** Zeigt das übergebene HTML in einem Fenster mit dem übergebenen Titel an. */
    public final void showHtmlViaHtml(String title, String html) {
        SwingUtilities.invokeLater(() -> showHtmlViaHtmlInEDT(title, html));
    }

    private void showHtmlViaHtmlInEDT(String title, String html) {
        HtmlFrame dialog = new HtmlFrame(title, getProgramImage(), getLocation());
        dialog.setText(html);
        dialog.setVisible(true);
    }

    /** Zeigt das übergebene HTML in einem Fenster mit dem übergebenen Titel an. */
    public final void showHtmlInDialog(String title, String html) {
        HtmlDialog dialog = new HtmlDialog(title, getProgramImage(), getLocation());
        dialog.setText(html);
        dialog.scrollScrollbarToMinimumLater();
        dialog.setVisible(true);
    }

    /** Gibt die Größe des Fensters zurück. */
    public Dimension getSizeOfWindow() {
        return window.getSize();
        //return window.getToolkit().getScreenSize();
    }

}
