package de.duehl.swing.ui;

/*
 * 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.AWTKeyStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.dnd.DropTargetListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TooManyListenersException;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.TitledBorder;
import javax.swing.filechooser.FileFilter;
import javax.swing.plaf.nimbus.AbstractRegionPainter;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.text.JTextComponent;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.io.FileHelper;
import de.duehl.basics.logic.ErrorHandler;
import de.duehl.basics.system.SystemTools;
import de.duehl.basics.text.Text;
import de.duehl.basics.text.html.HtmlTool;
import de.duehl.basics.version.Version;
import de.duehl.swing.ui.colors.ColorTool;
import de.duehl.swing.ui.dialogs.PasswordDialog;
import de.duehl.swing.ui.dialogs.logging.LogfileDialog;
import de.duehl.swing.ui.dialogs.values.ComboBoxDialog;
import de.duehl.swing.ui.dialogs.values.EnterLongTextDialog;
import de.duehl.swing.ui.dragndrop.TextFieldDropTargetListener;
import de.duehl.swing.ui.highlightingeditor.textcomponent.NotEditableScrollingSuppressingTextPane;
import de.duehl.swing.ui.key.KeyDefinition;

/**
 * Diese Klasse stellt einige Hilfsmethoden rund um die Gui zur Verfügung.
 *
 * @version 1.01     2025-12-09
 * @author Christian Dühl
 */

public class GuiTools {

    private static final int DEFAULT_TITLED_BORDER_FONT_SIZE = 12;

    /** Umgehen des Bugs mit den verschwindenden Anfassern von Scrollbars. */
    private static final boolean LIMIT_MINIMUM_THUMB_SIZE = true;

    private GuiTools() {} // don't call me

    /** Setzt einen hübschen Layout-Manager. */
    public static void setNiceLayoutManager() {
        try {
            UIManager.setLookAndFeel(
                    //"com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
                    //"java.desktop.javax.swing.plaf.nimbus.NimbusLookAndFeel");
                    "javax.swing.plaf.nimbus.NimbusLookAndFeel");
            // "com.sun.java.swing.plaf.gtk.GTKLookAndFeel" );
            // "com.sun.java.swing.plaf.motif.MotifLookAndFeel" );
            // "com.sun.java.swing.plaf.windows.WindowsLookAndFeel" );

            if (LIMIT_MINIMUM_THUMB_SIZE) {
                LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
                UIDefaults defaults = lookAndFeel.getDefaults();
                defaults.put("ScrollBar.minimumThumbSize", new Dimension(30, 30));
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Ermöglicht es für eine JTextPane im Nimbus Look & Feel die Hintergrundfarbe zu setzen.
     *
     * Im Anschluss an den Aufruf dieser Methode kann man mit
     *     textPane.setBackground(color);
     * Die Hintergrundfarbe ganz normal setzen.
     *
     * @param textPane
     *            Die zu verändernde JTextPane.
     */
    public static void respectTextPaneBackgroundInNimbusLookAndFeel(JTextPane textPane) {
        if (UIManager.getLookAndFeel().getName().equals("Nimbus")) {
            textPane.repaint();
            textPane.validate();
            textPane.revalidate();
            SwingUtilities.invokeLater(() -> {
                Color selFG = textPane.getSelectedTextColor();
                Color selBG = textPane.getSelectionColor();
                javax.swing.plaf.basic.BasicEditorPaneUI ui =
                        new javax.swing.plaf.basic.BasicEditorPaneUI();
                textPane.setUI(ui);
                textPane.setSelectedTextColor(selFG);
                textPane.setSelectionColor(selBG);
            }); // um komische, rote, nicht auffangbare Swing-Ausgaben zu vermeiden ...
        }
    }

    /**
     * Legt für den hübschen Layout-Manager die angegebene Hintergrundfarbe für den
     * HighlightingViewer fest.
     */
    public static void setHighlightingViewerBackgroundForNimbusLookAndFeel(Color background) {
        UIDefaults defaults = UIManager.getLookAndFeelDefaults();
        defaults.put("TextPane[Enabled].backgroundPainter",
            new javax.swing.plaf.nimbus.AbstractRegionPainter() {

                @Override
                protected AbstractRegionPainter.PaintContext getPaintContext() {
                    return new AbstractRegionPainter.PaintContext(null, null, false);
                }

                @Override
                protected void doPaint(Graphics2D g, JComponent c,
                        int width, int height, Object[] extendedCacheKeys) {
                    Color colorToUse;
                    if (null != c && c instanceof NotEditableScrollingSuppressingTextPane) {
                        colorToUse = background;
                    }
                    else {
                        colorToUse = Color.WHITE;
                        // new Color(214, 217, 223);
                        // vgl. https://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/
                        //              _nimbusDefaults.html
                        // Die Farbe sieht aber seltsam aus. Also Weiß.
                    }
                    g.setColor(colorToUse);
                    g.fillRect(0, 0, width, height);
                }
            });
        /*
         * Siehe https://stackoverflow.com/questions/15228336/
         *               changing-the-look-and-feel-changes-the-color-of-jtextpane
         *
         * Nicht gebraucht wird offenbar:
         *     jtxtPane.putClientProperty("Nimbus.Overrides", defaults);
         *     jtxtPane.putClientProperty("Nimbus.Overrides.InheritDefaults", false);
         */
    }

    /** Vergrößert die Schriftart. */
    public static void biggerFont(Component component, int addSize) {
        Font font = component.getFont();
        Font biggerFont = new Font(font.getName(), font.getStyle(), font.getSize() + addSize);
        component.setFont(biggerFont);
    }

    /** Verkleinert die Schriftart. */
    public static void smalerFont(Component component, int minusSize) {
        biggerFont(component, -minusSize);
    }

    /** Macht die Schriftart fett. */
    public static void boldFont(Component component) {
        Font font = component.getFont();
        Font boldFont = new Font(font.getFontName(), Font.BOLD, font.getSize());
        component.setFont(boldFont);
    }

    /** Macht die Schriftart kursiv. */
    public static void italicFont(Component component) {
        Font font = component.getFont();
        Font boldFont = new Font(font.getFontName(), Font.ITALIC, font.getSize());
        component.setFont(boldFont);
    }

    /** Macht die Schriftart normal. */
    public static void normalFont(Component component) {
        Font font = component.getFont();
        Font normalFont = new Font(font.getFontName(), Font.PLAIN, font.getSize());
        component.setFont(normalFont);
    }

    /**
     * Setzt um ein Panel eine Umrandung ohne Titel.
     *
     * @param panel
     *            Panel, der mit einem Titel und Umrandung versehen werden soll.
     */
    public static void createTitle(JComponent panel) {
        //TitledBorder border = BorderFactory.createTitledBorder("");
        //panel.setBorder(border);
        createTitle("", panel);
     }

    /**
     * Setzt um ein Panel eine Umrandung mit Titel.
     *
     * @param title
     *            Angezeigter Titel ("" spart den Titel ganz aus und erzeugt nur eine Umrandung).
     * @param panel
     *            Panel, der mit einem Titel und Umrandung versehen werden soll.
     */
    public static void createTitle(String title, JComponent panel) {
        TitledBorder border = BorderFactory.createTitledBorder(title);
        panel.setBorder(border);
     }

    /**
     * Setzt um ein Panel eine Umrandung mit Titel.
     *
     * @param title
     *            Angezeigter Titel ("" spart den Titel ganz aus und erzeugt nur eine Umrandung).
     * @param titlePanel
     *            Panel, der mit einem Titel und Umrandung versehen werden soll.
     */
    public static void createTitledBorder(String title, JPanel titlePanel) {
        createTitledBorder(
                title,
                titlePanel,
                //Color.BLUE
                new JLabel().getForeground()
                );
    }

    /**
     * Setzt um ein Panel eine Umrandung mit Titel.
     *
     * @param title
     *            Angezeigter Titel ("" spart den Titel ganz aus und erzeugt nur eine Umrandung).
     * @param panel
     *            Panel, der mit einem Titel und Umrandung versehen werden soll.
     * @param color
     *            Farbe des Rahmens und der Schrift.
     */
    public static void createTitledBorder(String title, JPanel panel,
            Color color) {
        panel.setBorder(BorderFactory.createTitledBorder(
                BorderFactory.createLineBorder(color),
                title,
                TitledBorder.LEFT,
                TitledBorder.DEFAULT_POSITION,
                new JTextField().getFont(),
                color));
        //panel.setBorder(BorderFactory.createTitledBorder(title));
    }

    /**
     * Setzt um ein Panel eine blaue Umrandung mit Titel.
     *
     * @param title
     *            Angezeigter Titel ("" spart den Titel ganz aus und erzeugt nur eine Umrandung).
     * @param panel
     *            Panel, der mit einem Titel und Umrandung versehen werden soll.
     */
    public static void createBlueTitle(String title, JPanel panel) {
        createTitle(title, panel, Color.BLUE);
    }

    /**
     * Setzt um ein Panel eine Umrandung mit Titel.
     *
     * @param title
     *            Angezeigter Titel ("" spart den Titel ganz aus und erzeugt nur eine Umrandung).
     * @param panel
     *            Panel, der mit einem Titel und Umrandung versehen werden soll.
     * @param color
     *            Farbe des Rahmen und des Textes (je nach Layoutmanager).
     */
    public static void createTitle(String title, JPanel panel, Color color) {
        TitledBorder border = BorderFactory.createTitledBorder(title);
        border.setTitleColor(color);
        panel.setBorder(border);
    }

    /** Zeigt den Waitcursor an. */
    public static void waitcursorOn(Component component) {
        SwingUtilities.invokeLater(
                () -> component.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)));
    }

    /** Zeigt den normalen Cursor an. */
    public static void waitcursorOff(Component component) {
        SwingUtilities.invokeLater(
                () -> component.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)));
    }

    /** Zeigt den Waitcursor sofort an. */
    public static void waitcursorImmediatelyOn(Component component) {
        component.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    }

    /**
     * Macht die Titelüberschriften linksbündig (statt zentriert).
     * @param table Zu verändernde Tabelle.
     */
    public static void makeTableHeadersLeftAligned(JTable table) {
        DefaultTableCellRenderer headerRenderer = (DefaultTableCellRenderer)
                table.getTableHeader().getDefaultRenderer();
        headerRenderer.setHorizontalAlignment(JLabel.LEFT);
    }

    /**
     * Oft definiert man in einem Menü Tastaturbefehle, die dann aber nicht
     * funktionieren, wenn der Focus auf z.B. einer JEditorPane liegt.          <br><br>
     *
     * Mit dieser Methode kann diese (und ihre JScrollPane) angeweisen werden,
     * die Tastaturbefehle nicht selbst auszuwerten.                            <br><br>
     *
     * Der Aufruf erfolgt dann z.B. so:                                         <br><br><tt>
     *
     *     disableKeys(keys,
     *             editorPane.getInputMap(),
     *             scroll.getInputMap(
     *                     JScrollPane.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT));    </tt>
     *
     * @param keys
     *            Liste mit den Tastaturbefehlen, die nicht selbst ausgewertet
     *            werden sollen.
     * @param inputMaps
     *            Tastaturbelegungen von bestimmten Gui-Elementen.
     */
    public static void disableKeys(List<KeyDefinition> keys, InputMap... inputMaps) {
        for (KeyDefinition key : keys) {
            KeyStroke keyStroke = KeyStroke.getKeyStroke(key.getKeyCode(), key.getModifiers());
            for (InputMap inputMap : inputMaps) {
                inputMap.put(keyStroke, "none");
            }
        }
    }

    /**
     * Ruft den Dialog zum Öffnen einer Datei auf.
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String openFile(String startDirectory, FileFilter fileFilter) {
        return openFile(null, "", startDirectory, fileFilter);
    }

    /**
     * Ruft den Dialog zum Öffnen einer Datei auf.
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String openFile(File startDirectory, FileFilter fileFilter) {
        return openFile(null, "", startDirectory, fileFilter);
    }

    /**
     * Ruft den Dialog zum Öffnen einer Datei auf.
     *
     * @param title
     *            Titel des Dialogs.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String openFile(String title, String startDirectory, FileFilter fileFilter) {
        return openFile(null, title, new File(startDirectory), fileFilter);
    }

    /**
     * Ruft den Dialog zum Öffnen einer Datei auf.
     *
     * @param title
     *            Titel des Dialogs.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String openFile(String title, File startDirectory, FileFilter fileFilter) {
        return openFile(null, title, startDirectory, fileFilter);
    }

    /**
     * Ruft den Dialog zum Öffnen einer Datei auf.
     *
     * @param parent
     *            Übergeordnete Komponente.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String openFile(Component parent, String startDirectory, FileFilter fileFilter) {
        return openFile(parent, "", startDirectory, fileFilter);
    }

    /**
     * Ruft den Dialog zum Öffnen einer Datei auf.
     *
     * @param parent
     *            Übergeordnete Komponente.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String openFile(Component parent, File startDirectory, FileFilter fileFilter) {
        return openFile(parent, "", startDirectory, fileFilter);
    }

    /**
     * Ruft den Dialog zum Öffnen einer Datei auf.
     *
     * @param parent
     *            Übergeordnete Komponente.
     * @param title
     *            Titel des Dialogs.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String openFile(Component parent, String title, String startDirectory,
            FileFilter fileFilter) {
        return openFile(parent, title, new File(startDirectory), fileFilter);
    }

    /**
     * Ruft den Dialog zum Öffnen einer Datei auf.
     *
     * @param parent
     *            Übergeordnete Komponente.
     * @param title
     *            Titel des Dialogs.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String openFile(Component parent, String title, File startDirectory,
            FileFilter fileFilter) {
        JFileChooser fileChooser = createFileChooser(startDirectory, fileFilter);
        if (title != null && !title.isEmpty()) {
            fileChooser.setDialogTitle(title);
        }
        int state = fileChooser.showOpenDialog(parent);
        if (state == JFileChooser.APPROVE_OPTION) {
            File file = fileChooser.getSelectedFile();
            return file.getPath();
        }
        else {
            return "";
        }
    }

    /**
     * Lässt den Benutzer eine Datei im gewünschten Format auswählen.
     *
     * @param parent
     *            Übergeordnete Gui-Komponente.
     * @param extension
     *            Endung der Datei, wie z.B. ".txt".
     * @return Ausgewählte Datei oder der leere String, wenn der Benutzer den Dialog abgebrochen
     *         hat.
     */
    public static String openFileWithExtension(Component parent, String extension) {
        String startDir = SystemTools.getHomeDirectory();
        return openFileWithExtension(parent, extension, startDir);
    }

    /**
     * Lässt den Benutzer eine Datei im gewünschten Format auswählen.
     *
     * @param extension
     *            Endung der Datei, wie z.B. ".txt".
     * @param startDir
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @return Ausgewählte Datei oder der leere String, wenn der Benutzer den Dialog abgebrochen
     *         hat.
     */
    public static String openFileWithExtension(String extension, String startDir) {
        return openFileWithExtension(extension, new File(startDir));
    }

    /**
     * Lässt den Benutzer eine Datei im gewünschten Format auswählen.
     *
     * @param extension
     *            Endung der Datei, wie z.B. ".txt".
     * @param startDir
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @return Ausgewählte Datei oder der leere String, wenn der Benutzer den Dialog abgebrochen
     *         hat.
     */
    public static String openFileWithExtension(String extension, File startDir) {
        return openFileWithExtension(null, extension, startDir);
    }

    /**
     * Lässt den Benutzer eine Datei im gewünschten Format auswählen.
     *
     * @param parent
     *            Übergeordnete Gui-Komponente.
     * @param extension
     *            Endung der Datei, wie z.B. ".txt".
     * @param startDir
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @return Ausgewählte Datei oder der leere String, wenn der Benutzer den Dialog abgebrochen
     *         hat.
     */
    public static String openFileWithExtension(Component parent, String extension, String startDir) {
        return openFileWithExtension(parent, extension, new File(startDir));
    }

    /**
     * Lässt den Benutzer eine Datei im gewünschten Format auswählen.
     *
     * @param title
     *            Titel des Dialogs.
     * @param extension
     *            Endung der Datei, wie z.B. ".txt".
     * @param startDir
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @return Ausgewählte Datei oder der leere String, wenn der Benutzer den Dialog abgebrochen
     *         hat.
     */
    public static String openFileWithExtension(String title, String extension,
            String startDir) {
        return openFileWithExtension(null, title, extension, new File(startDir));
    }

    /**
     * Lässt den Benutzer eine Datei im gewünschten Format auswählen.
     *
     * @param parent
     *            Übergeordnete Gui-Komponente.
     * @param title
     *            Titel des Dialogs.
     * @param extension
     *            Endung der Datei, wie z.B. ".txt".
     * @param startDir
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @return Ausgewählte Datei oder der leere String, wenn der Benutzer den Dialog abgebrochen
     *         hat.
     */
    public static String openFileWithExtension(Component parent, String title, String extension,
            String startDir) {
        return openFileWithExtension(parent, title, extension, new File(startDir));
    }

    /**
     * Lässt den Benutzer eine Datei im gewünschten Format auswählen.
     *
     * @param parent
     *            Übergeordnete Gui-Komponente.
     * @param extension
     *            Endung der Datei, wie z.B. ".txt".
     * @param startDir
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @return Ausgewählte Datei oder der leere String, wenn der Benutzer den Dialog abgebrochen
     *         hat.
     */
    public static String openFileWithExtension(Component parent, String extension, File startDir) {
        FileFilter fileFilter = createExtensionFileFilter(extension);
        return openFile(parent, startDir, fileFilter);
    }

    /**
     * Lässt den Benutzer eine Datei im gewünschten Format auswählen.
     *
     * @param parent
     *            Übergeordnete Gui-Komponente.
     * @param title
     *            Titel des Dialogs.
     * @param extension
     *            Endung der Datei, wie z.B. ".txt".
     * @param startDir
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @return Ausgewählte Datei oder der leere String, wenn der Benutzer den Dialog abgebrochen
     *         hat.
     */
    public static String openFileWithExtension(Component parent, String title, String extension,
            File startDir) {
        FileFilter fileFilter = createExtensionFileFilter(extension);
        return openFile(parent, title, startDir, fileFilter);
    }

    /**
     * Ruft den Dialog zum Speichern einer Datei auf.
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String saveFileAs(String startDirectory, FileFilter fileFilter) {
        return saveFileAsWithTitle(null, startDirectory, fileFilter);
    }

    /**
     * Ruft den Dialog zum Speichern einer Datei auf.
     *
     * @param parent
     *            Übergeordnete Komponente.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String saveFileAs(Component parent, String startDirectory, FileFilter fileFilter) {
        return saveFileAs(parent, new File(startDirectory), fileFilter);
    }

    /**
     * Ruft den Dialog zum Speichern einer Datei auf.
     *
     * @param parent
     *            Übergeordnete Komponente.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String saveFileAs(Component parent, File startDirectory, FileFilter fileFilter) {
        JFileChooser fileChooser = createFileChooser(startDirectory, fileFilter);
        return saveAsWithFileChooser(parent, fileChooser);
    }

    /**
     * Ruft den Dialog zum Datei speichern einer Datei auf, hier kann ein Titel angegeben werden.
     *
     * @param title
     *            Titel des Dialogs.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String saveFileAsWithTitle(String title, String startDirectory,
            FileFilter fileFilter) {
        return saveFileAsWithTitle(title, null, startDirectory, fileFilter);
    }

    /**
     * Ruft den Dialog zum Datei speichern einer Datei auf, hier kann ein Titel angegeben werden.
     *
     * @param title
     *            Titel des Dialogs.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String saveFileAsWithTitle(String title, File startDirectory,
            FileFilter fileFilter) {
        return saveFileAsWithTitle(title, null, startDirectory, fileFilter);
    }

    /**
     * Ruft den Dialog zum Datei speichern einer Datei auf, hier kann ein Titel angegeben werden.
     *
     * @param title
     *            Titel des Dialogs.
     * @param parent
     *            Übergeordnete Komponente.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String saveFileAsWithTitle(String title, Component parent, String startDirectory,
            FileFilter fileFilter) {
        return saveFileAsWithTitle(title, parent, new File(startDirectory), fileFilter);
    }

    /**
     * Ruft den Dialog zum Datei speichern einer Datei auf, hier kann ein Titel angegeben werden.
     *
     * @param title
     *            Titel des Dialogs.
     * @param parent
     *            Übergeordnete Komponente.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String saveFileAsWithTitle(String title, Component parent, File startDirectory,
            FileFilter fileFilter) {
        JFileChooser fileChooser = createFileChooser(startDirectory, fileFilter);
        fileChooser.setDialogTitle(title);
        return saveAsWithFileChooser(parent, fileChooser);
    }

    /**
     * Ruft den Dialog zum Datei speichern einer Datei auf, hier kann ein Titel angegeben werden.
     *
     * @param title
     *            Titel des Dialogs.
     * @param parent
     *            Übergeordnete Komponente.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @param selectedFile
     *            Name der zu speichernden Datei ohne Pfad.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String saveFileAsWithTitle(String title, Component parent, String startDirectory,
            FileFilter fileFilter, String selectedFile) {
        return saveFileAsWithTitle(title, parent, new File(startDirectory), fileFilter,
                new File(FileHelper.concatPathes(startDirectory, selectedFile)));
    }

    /**
     * Ruft den Dialog zum Datei speichern einer Datei auf, hier kann ein Titel angegeben werden.
     *
     * @param title
     *            Titel des Dialogs.
     * @param parent
     *            Übergeordnete Komponente.
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @param selectedFile
     *            Name der zu speichernden Datei mit Pfad.
     * @return Ausgewählter Dateiname oder der leere String, wenn der Benutzer abbricht.
     */
    public static String saveFileAsWithTitle(String title, Component parent, File startDirectory,
            FileFilter fileFilter, File selectedFile) {
        JFileChooser fileChooser = createFileChooser(startDirectory, fileFilter);
        fileChooser.setDialogTitle(title);
        fileChooser.setSelectedFile(selectedFile);
        return saveAsWithFileChooser(parent, fileChooser);
    }

    /**
     * Hilfsmethode zum Speichern.
     *
     * @param parent
     *            Übergeordnete Komponente.
     * @param fileChooser
     *            Der File-Chooser, mit dem der Benutzer nach der Datei gefragt wird.
     * @return Auswahl des Benutzers oder der leere String, wenn der Benutzer abbricht.
     */
    private static String saveAsWithFileChooser(Component parent, JFileChooser fileChooser) {
        int state = fileChooser.showSaveDialog(parent);
        if (state == JFileChooser.APPROVE_OPTION) {
            File file = fileChooser.getSelectedFile();
            return file.getPath();
        }
        else {
            return "";
        }
    }

    /**
     * Erzeugt einen passenden JFileChooser zum Öffnen oder Speichern einer Datei.
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param fileFilter
     *            Filter, der die angezeigten Dateien filtert.
     * @return File-Chooser
     */
    private static JFileChooser createFileChooser(File startDirectory, FileFilter fileFilter) {
        JFileChooser fileChooser = new JFileChooser(startDirectory);

        fileChooser.setAcceptAllFileFilterUsed(false);
        fileChooser.setFileFilter(fileFilter);
        fileChooser.addChoosableFileFilter(fileFilter);

        fileChooser.addHierarchyListener(e -> requestFocusForTextField(fileChooser));
        // Letzteres, da es beim Nimbus-Look&Feel den Bug gibt, dass das Eingabefeld nicht den
        // Fokus hat.

        return fileChooser;
    }

    /**
     * Sucht in den Komponenten des übergebenen Objektes nach dem ersten JTextField und versucht
     * diesem den Fokus zuzuweisen.
     */
    public static void requestFocusForTextField(JComponent component) {
        for (Component innerComponent : component.getComponents()) {
            if (innerComponent instanceof JTextField textField) {
                textField.requestFocusInWindow();
                break;
            }
            else if (innerComponent instanceof JPanel jPanel) {
                requestFocusForTextField(jPanel);
            }
        }
    }

    /**
     * Lässt den Benutzer ein Verzeichnis auswählen.
     *
     * @return Ausgewähltes Verzeichnis der der leere String bei Abbruch.
     */
    public static String openDirectory() {
        return openDirectory(null, "", null);
    }

    /**
     * Lässt den Benutzer ein Verzeichnis zum Öffnen auswählen.
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @return Ausgewähltes Verzeichnis der der leere String bei Abbruch.
     */
    public static String openDirectory(String startDirectory) {
        return openDirectory(startDirectory, "", null);
    }

    /**
     * Lässt den Benutzer ein Verzeichnis zum Öffnen auswählen.
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param parent
     *             the parent component of the dialog, can be null.
     * @return Ausgewähltes Verzeichnis der der leere String bei Abbruch.
     */
    public static String openDirectory(String startDirectory, Component parent) {
        return openDirectory(startDirectory, "", parent);
    }

    /**
     * Lässt den Benutzer ein Verzeichnis zum Öffnen auswählen.
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param title
     *            Titel des Dialogs.
     * @return Ausgewähltes Verzeichnis der der leere String bei Abbruch.
     */
    public static String openDirectory(String startDirectory, String title) {
        return openDirectory(startDirectory, title, null);
    }

    /**
     * Lässt den Benutzer ein Verzeichnis zum Öffnen auswählen.
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param title
     *            Titel des Dialogs.
     * @param parent
     *             the parent component of the dialog, can be null.
     * @return Ausgewähltes Verzeichnis der der leere String bei Abbruch.
     */
    public static String openDirectory(String startDirectory, String title, Component parent) {
        JFileChooser chooser = createDirectoryChooser(startDirectory, title);
        return openDirectoryInternal(chooser, parent);
    }

    /**
     * Lässt den Benutzer ein Verzeichnis zum Öffnen auswählen.
     *
     * @param chooser
     *            FileChooser, der verwendet wird.
     * @param parent
     *             the parent component of the dialog, can be null.
     * @return Ausgewähltes Verzeichnis der der leere String bei Abbruch.
     */
    private static String openDirectoryInternal(JFileChooser chooser, Component parent) {
        int state = chooser.showOpenDialog(parent);
        if (state == JFileChooser.APPROVE_OPTION) {
            File selected = chooser.getSelectedFile();
            return selected.getAbsolutePath();
        }
        else {
            return "";
        }
    }

    /**
     * Lässt den Benutzer ein Verzeichnis zum Speichern auswählen.
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param parent
     *            the parent component of the dialog, can be null.
     * @return Ausgewähltes Verzeichnis oder der leere String bei Abbruch.
     */
    public static String saveDirectory(String startDirectory, Component parent) {
        return saveDirectory(startDirectory, "", parent);
    }

    /**
     * Lässt den Benutzer ein Verzeichnis zum Speichern auswählen.
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @param title
     *            Titel des Dialogs.
     * @param parent
     *            the parent component of the dialog, can be null.
     * @return Ausgewähltes Verzeichnis oder der leere String bei Abbruch.
     */
    public static String saveDirectory(String startDirectory, String title, Component parent) {
        JFileChooser chooser = createDirectoryChooser(startDirectory, title);
        return saveDirectoryInternal(chooser, parent);
    }

    /**
     * Lässt den Benutzer ein Verzeichnis zum Speichern auswählen.
     *
     * @param chooser
     *            FileChooser, der verwendet wird.
     * @param parent
     *            the parent component of the dialog, can be null.
     * @return Ausgewähltes Verzeichnis oder der leere String bei Abbruch.
     */
    private static String saveDirectoryInternal(JFileChooser chooser, Component parent) {
        int state = chooser.showSaveDialog(parent);
        if (state == JFileChooser.APPROVE_OPTION) {
            File selected = chooser.getSelectedFile();
            return selected.getAbsolutePath();
        }
        else {
            return "";
        }
    }

    /**
     * Lässt den Benutzer ein Verzeichnis zum Speichern auswählen.
     * Das Fenster wird unabhängig vom Parent platziert.
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird.
     * @return Ausgewähltes Verzeichnis oder der leere String bei Abbruch.
     */
    public static String saveDirectory(String startDirectory) {
        Component parent = null;
        return saveDirectory(startDirectory, parent);
    }

    /**
     * Erzeugt einen JFileChooser, der nur die Auswahl von Verzeichnissen
     * zulässt.                                                                 <br><br><i><b>
     *
     * Wenn man nicht wirklich den FileChooser braucht, um ihn etwa in einer
     * Karte auf der Gui einzubauen, sollte man nicht diese Methode verwenden,
     * sondern                                                                  <br>&nbsp;&nbsp;&nbsp;&nbsp;<tt>
     *     openDirectory(...)                                                   </tt><br>
     * oder                                                                     <br>&nbsp;&nbsp;&nbsp;&nbsp;<tt>
     *     saveDirectory(...)                                                   </tt><br>
     * !                                                                        </b></i>
     *
     * @see openDirectory
     * @see saveDirectory
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird. Kann null sein.
     * @return JFileChooser für Verzeichnisse
     */
    public static JFileChooser createDirectoryChooser(String startDirectory) {
        return createDirectoryChooser(startDirectory, "");
    }

    /**
     * Erzeugt einen JFileChooser, der nur die Auswahl von Verzeichnissen
     * zulässt.                                                                 <br><br><i><b>
     *
     * Wenn man nicht wirklich den FileChooser braucht, um ihn etwa in einer
     * Karte auf der Gui einzubauen, sollte man nicht diese Methode verwenden,
     * sondern                                                                  <br>&nbsp;&nbsp;&nbsp;&nbsp;<tt>
     *     openDirectory(...)                                                   </tt><br>
     * oder                                                                     <br>&nbsp;&nbsp;&nbsp;&nbsp;<tt>
     *     saveDirectory(...)                                                   </tt><br>
     * !                                                                        </b></i>
     *
     * @see openDirectory
     * @see saveDirectory
     *
     * @param startDirectory
     *            Verzeichnis, das dem Benutzer angezeigt wird. Wird nicht verwendet, falls
     *            startDirectory null oder leer ist.
     * @param title
     *            Titel des Dialogs, wird nicht gesetzt, falls der Titel null oder leer ist.
     * @return JFileChooser für Verzeichnisse
     */
    public static JFileChooser createDirectoryChooser(String startDirectory, String title) {
        JFileChooser fileChooser = (null == startDirectory || startDirectory.isEmpty())
                ? new JFileChooser()
                : new JFileChooser(startDirectory);
        fileChooser.setAcceptAllFileFilterUsed(false);
        FileFilter dirFilter = createDirectoryFilter();
        fileChooser.setFileFilter(dirFilter);
        fileChooser.addChoosableFileFilter(dirFilter);
        fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        if (title != null && !title.isEmpty()) {
            fileChooser.setDialogTitle(title);
        }
        return fileChooser;
    }

    /**
     * Erzeugt einen Filer für einen JFileChooser zur Auswahl von
     * Verzeichnissen.
     * Die Anwendung erfolgt etwa so:
     *                                                                      <pre>&nbsp;&nbsp;&nbsp;&nbsp;
     *     JFileChooser fileChooser = new JFileChooser();                   &nbsp;&nbsp;&nbsp;&nbsp;
     *     FileFilter dirFilter = createDirectoryFilterForFileChooser();    &nbsp;&nbsp;&nbsp;&nbsp;
     *     fileChooser.setFileFilter(dirFilter);                            &nbsp;&nbsp;&nbsp;&nbsp;
     *     fileChooser.addChoosableFileFilter(dirFilter);                   &nbsp;&nbsp;&nbsp;&nbsp;
     *     fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); </pre>
     *
     * @return Erzeugter Filter.
     */
    public static FileFilter createDirectoryFilter() {
        return new FileFilter() {
            @Override
            public String getDescription() {
                return "Verzeichnisse";
            }
            @Override
            public boolean accept(File file) {
                return file.isDirectory();
            }
        };
    }

    /**
     * Erzeugt einen Filer für einen JFileChooser zur Auswahl von
     * Textdateien (und Verzeichnissen).
     * Die Anwendung erfolgt etwa so:
     *                                                                      <pre>&nbsp;&nbsp;&nbsp;&nbsp;
     *     JFileChooser fileChooser = new JFileChooser();                   &nbsp;&nbsp;&nbsp;&nbsp;
     *     FileFilter txtFilter = createTextFileFilter();                   &nbsp;&nbsp;&nbsp;&nbsp;
     *     fileChooser.setFileFilter(txtFilter);                            &nbsp;&nbsp;&nbsp;&nbsp;
     *     fileChooser.addChoosableFileFilter(txtFilter);                   </pre>
     *
     * @return Erzeugter Filter.
     */
    public static FileFilter createTextFileFilter() {
        FileFilter textFilter = new FileFilter() {
            @Override
            public String getDescription() {
                return "Textdateien";
            }
            @Override
            public boolean accept(File file) {
                return file.isDirectory()
                        || (file.isFile() && file.getName().toLowerCase().endsWith(".txt"));
            }
        };
        return textFilter;
    }

    /**
     * Erzeugt einen Filer für einen JFileChooser zur Auswahl von
     * HTML-Dateien (und Verzeichnissen).
     * Die Anwendung erfolgt etwa so:
     *                                                                      <pre>&nbsp;&nbsp;&nbsp;&nbsp;
     *     JFileChooser fileChooser = new JFileChooser();                   &nbsp;&nbsp;&nbsp;&nbsp;
     *     FileFilter htmlFilter = createHtmlFileFilter();                  &nbsp;&nbsp;&nbsp;&nbsp;
     *     fileChooser.setFileFilter(htmlFilter);                           &nbsp;&nbsp;&nbsp;&nbsp;
     *     fileChooser.addChoosableFileFilter(htmlFilter);                  </pre>
     *
     * @return Erzeugter Filter.
     */
    public static FileFilter createHtmlFileFilter() {
        FileFilter htmlFilter = new FileFilter() {
            @Override
            public String getDescription() {
                return "HTML-Dateien";
            }
            @Override
            public boolean accept(File file) {
                if (file.isDirectory()) {
                    return true;
                }
                if (!file.isFile()) {
                    return false;
                }
                String name = file.getName().toLowerCase();
                return name.endsWith(".html") || name.endsWith(".htm") ;
            }
        };
        return htmlFilter;
    }

    /**
     * Erzeugt einen Filer für einen JFileChooser zur Auswahl von
     * Lockdateien (und Verzeichnissen).
     * Die Anwendung erfolgt etwa so:
     *                                                                      <pre>&nbsp;&nbsp;&nbsp;&nbsp;
     *     JFileChooser fileChooser = new JFileChooser();                   &nbsp;&nbsp;&nbsp;&nbsp;
     *     FileFilter lockFilter = createLockFileFilterForFileChooser();    &nbsp;&nbsp;&nbsp;&nbsp;
     *     fileChooser.setFileFilter(lockFilter);                           &nbsp;&nbsp;&nbsp;&nbsp;
     *     fileChooser.addChoosableFileFilter(lockFilter);                  </pre>
     *
     * @return Erzeugter Filter.
     */
    public static FileFilter createLockFileFilter() {
        return new FileFilter() {
            @Override
            public String getDescription() {
                return "Lock-Dateien";
            }
            @Override
            public boolean accept(File file) {
                return file.isDirectory()
                        || (file.isFile() && file.getName().toLowerCase().endsWith(".lock"));
            }
        };
    }

    /** Erzeugt einen Filter für .txt und .csv Dateien. */
    public static FileFilter createTextAndCsvFileFilter() {
        return new FileFilter() {
            @Override
            public boolean accept(File file) {
                return file.isDirectory()
                        || (file.isFile() && file.getName().toLowerCase().endsWith(".txt"))
                        || (file.isFile() && file.getName().toLowerCase().endsWith(".csv"));
            }

            @Override
            public String getDescription() {
                return "txt- und csv-Dateien";
            }
        };
    }

    /** Erzeugt einen Filter für ausführbare Dateien. */
    public static FileFilter createExecutableFileFilter() {
        return new FileFilter() {
            @Override
            public boolean accept(File file) {
                return file.isDirectory()
                        || file.toString().endsWith(".exe")
                        || file.toString().endsWith(".com")
                        || file.toString().endsWith(".bat")
                        || file.toString().endsWith(".cmd");
            }

            @Override
            public String getDescription() {
                return "Ausführbare Dateien";
            }
        };
    }

    /**
     * Erzeugt einen Filter für Dateien mit einer bestimmten Endung.
     *
     * @param extension
     *            Endung, z.B. ".txt"
     * @return FileFilter.
     */
    public static FileFilter createExtensionFileFilter(String extension) {
        return new FileFilter() {
            @Override
            public boolean accept(File file) {
                if (null == file) {
                    return false;
                }
                else {
                    return file.isDirectory()
                            || file.toString().endsWith(extension);
                }
            }

            @Override
            public String getDescription() {
                return "Dateien im " + extension + "-Format";
            }
        };
    }

    /** Erzeugt einen Filter für alle Dateien und Verzeichnisse. */
    public static FileFilter createAllFileFilter() {
        return new FileFilter() {
            @Override
            public boolean accept(File file) {
                return true;
            }

            @Override
            public String getDescription() {
                return "Alle Dateien";
            }
        };
    }

    /**
     * Fragt den Benutzer, ob das Programm beendet werden soll.
     *
     * @param parent
     *            Grafische Komponente, zu der dieser Dialog eröffnet wird.
     * @return Wahrheitswert: true steht für Programm beenden.
     */
    public static boolean askUserToQuit(Component parent) {
        String message = "Möchten Sie das Programm wirklich beenden?";
        return askUserToQuit(parent, message);
    }

    /**
     * Fragt den Benutzer, ob das Programm beendet werden soll.
     *
     * @param parent
     *            Grafische Komponente, zu der dieser Dialog eröffnet wird.
     * @param message
     *            Nachricht, die dem Benutzer angezeigt wird.
     * @return Wahrheitswert: true steht für Programm beenden.
     */
    public static boolean askUserToQuit(Component parent, String message) {
        String[] yesNoOptions = {
                "Programm beenden",
                "Weiter machen"
        };

        int answer = JOptionPane.showOptionDialog(
                parent,
                message,
                "Programm beenden?",
                JOptionPane.YES_NO_OPTION,
                JOptionPane.QUESTION_MESSAGE,
                null,
                yesNoOptions,
                yesNoOptions[0]);
        boolean userAnswer = false;
        if (answer == JOptionPane.YES_OPTION) {
            userAnswer = true;
        }
        else if (answer == JOptionPane.NO_OPTION) {
            userAnswer = false;
        }

        return userAnswer;
    }

    /**
     * Fragt den Benutzer nach etwas.
     *
     * @param title
     *            Titel des Dialogs
     * @param message
     *            anzuzeigende Nachricht
     * @return Wahrheitswert: true steht für "ja" gewählt.
     */
    public static boolean askUser(String title, String message) {
        return askUser(null, title, message);
    }

    /**
     * Fragt den Benutzer nach etwas, di Default-Antwort ist 'ja'.
     *
     * @param parent
     *            Grafische Komponente, zu der dieser Dialog eröffnet wird, darf null sein.
     * @param title
     *            Titel des Dialogs
     * @param message
     *            anzuzeigende Nachricht
     * @return Wahrheitswert: true steht für "ja" gewählt.
     */
    public static boolean askUser(Component parent, String title, String message) {
        String[] yesNoOptions = {
                "Ja",
                "Nein"
        };

        int answer = JOptionPane.showOptionDialog(
                parent,
                message,
                title,
                JOptionPane.YES_NO_OPTION,
                JOptionPane.QUESTION_MESSAGE,
                null,
                yesNoOptions,
                yesNoOptions[0]);

        if (answer == JOptionPane.YES_OPTION) {
            return true;
        }
        else if (answer == JOptionPane.NO_OPTION) {
            return false;
        }
        else {
            return false;
        }
    }

    /**
     * Fragt den Benutzer nach etwas, di Default-Antwort ist 'nein'.
     *
     * @param parent
     *            Grafische Komponente, zu der dieser Dialog eröffnet wird, darf null sein.
     * @param title
     *            Titel des Dialogs
     * @param message
     *            anzuzeigende Nachricht
     * @return Wahrheitswert: true steht für "ja" gewählt.
     */
    public static boolean askUserDefaultNo(Component parent, String title, String message) {
        String[] yesNoOptions = {
                "Ja",
                "Nein"
        };

        int answer = JOptionPane.showOptionDialog(
                parent,
                message,
                title,
                JOptionPane.YES_NO_OPTION,
                JOptionPane.QUESTION_MESSAGE,
                null,
                yesNoOptions,
                yesNoOptions[1]);

        if (answer == JOptionPane.YES_OPTION) {
            return true;
        }
        else if (answer == JOptionPane.NO_OPTION) {
            return false;
        }
        else {
            return false;
        }
    }

    /**
     * Fragt den Benutzer nach etwas.
     *
     * @param parent
     *            Grafische Komponente, zu der dieser Dialog eröffnet wird, darf null sein.
     * @param title
     *            Titel des Dialogs
     * @param message
     *            anzuzeigende Nachricht
     * @param options
     *            Liste mit den Optionen
     * @param defaultOptionIndex
     *            Defaultmäßig ausgewählte Antwort als Index der Liste mit den Optionen
     * @return Vom Benutzer gewählte Antwort als Index der Liste mit den Optionen oder -1, falls
     *         der Benutzer den Dialog abgebrochen hat.
     */
    public static int askUserWithMultipleAnswers(Component parent, String title, String message,
            List<String> options, int defaultOptionIndex) {
        String[] optionsArray = CollectionsHelper.stringListToArray(options);

        int answer = JOptionPane.showOptionDialog(
                parent,
                message,
                title,
                JOptionPane.DEFAULT_OPTION,
                JOptionPane.QUESTION_MESSAGE,
                null,
                optionsArray,
                optionsArray[defaultOptionIndex]);

        return answer;
    }

    /**
     * Fragt den Benutzer nach etwas.
     *
     * @param parent
     *            Grafische Komponente, zu der dieser Dialog eröffnet wird, darf null sein.
     * @param title
     *            Titel des Dialogs
     * @param message
     *            anzuzeigende Nachricht
     * @param options
     *            Liste mit den Optionen
     * @return Vom Benutzer gewählte Antwort als Index der Liste mit den Optionen oder -1, falls
     *         der Benutzer den Dialog abgebrochen hat.
     */
    public static int askUserWithMultipleAnswers(Component parent, String title, String message,
            List<String> options) {
        String[] optionsArray = CollectionsHelper.stringListToArray(options);

        int answer = JOptionPane.showOptionDialog(
                parent,
                message,
                title,
                JOptionPane.DEFAULT_OPTION,
                JOptionPane.QUESTION_MESSAGE,
                null,
                optionsArray,
                null);

        return answer;
    }

    /**
     * Anzeige einer einfachen Benachrichtigung im EDT.
     *
     * @param title
     *            Titel des Dialogs
     * @param message
     *            anzuzeigende Nachricht
     */
    public static void informUserInEdt(String title, String message) {
        informUserInEdt(null, title, message);
    }

    /**
     * Anzeige einer einfachen Benachrichtigung.
     *
     * @param title
     *            Titel des Dialogs
     * @param message
     *            anzuzeigende Nachricht
     */
    public static void informUser(String title, String message) {
        informUser(null, title, message);
    }

    /**
     * Anzeige einer einfachen Benachrichtigung im EDT.
     *
     * @param parent
     *            Grafische Komponente, zu der dieser Dialog eröffnet wird. Darf null sein.
     * @param title
     *            Titel des Dialogs
     * @param message
     *            anzuzeigende Nachricht
     */
    public static void informUserInEdt(Component parent, String title, String message) {
        SwingUtilities.invokeLater(() -> informUser(parent, title, message));
    }

    /**
     * Anzeige einer einfachen Benachrichtigung.
     *
     * @param parent
     *            Grafische Komponente, zu der dieser Dialog eröffnet wird. Darf null sein.
     * @param title
     *            Titel des Dialogs
     * @param message
     *            anzuzeigende Nachricht
     */
    public static void informUser(Component parent, String title, String message) {
        JOptionPane.showMessageDialog(parent,
            message,
            title,
            JOptionPane.INFORMATION_MESSAGE);
    }

    /**
     * Anzeige einer einfachen Benachrichtigung mit Bestätigung.
     *
     * @param title
     *            Titel des Dialogs
     * @param message
     *            anzuzeigende Nachricht
     * @return Wahrheitswert, true genau dann, wenn der Benutzer die dargestellte Information
     *         bestätigt.
     */
    public static boolean getConfirmationByUser(String title, String message) {
        return getConfirmationByUser(null, title, message);
    }

    /**
     * Anzeige einer einfachen Benachrichtigung mit Bestätigung.
     *
     * @param parent
     *            Grafische Komponente, zu der dieser Dialog eröffnet wird. Darf null sein.
     * @param title
     *            Titel des Dialogs
     * @param message
     *            anzuzeigende Nachricht
     * @return Wahrheitswert, true genau dann, wenn der Benutzer die dargestellte Information
     *         bestätigt.
     */
    public static boolean getConfirmationByUser(Component parent, String title, String message) {
        int userSelection = JOptionPane.showConfirmDialog(parent,
                message,
                title,
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.INFORMATION_MESSAGE);
        return userSelection == JOptionPane.YES_OPTION;
    }

    /**
     * Lässt den Benutzer einen Wert eingeben.
     *
     * @param parentComponent
     *            Element, vor dem der Dialog angezeigt wird.
     * @param title
     *            Titel des Dialogs.
     * @param message
     *            Angezeigte Nachricht.
     * @return Eingegebener Wert oder der leere String bei Abbruch durch den Benutzer.
     */
    public static String askUserToEnterAStringValue(Component parentComponent, String title,
            String message) {
        int type = JOptionPane.QUESTION_MESSAGE;
        String answer = JOptionPane.showInputDialog(parentComponent, message, title, type);
        if (null == answer) {
            answer = "";
        }
        return answer.strip();
    }

    /**
     * Lässt den Benutzer einen längeren Text eingeben.
     *
     * @param parentComponent
     *            Element, vor dem der Dialog angezeigt wird.
     * @param title
     *            Titel des Dialogs.
     * @param message
     *            Angezeigte Nachricht.
     * @return Eingegebener Wert oder der leere String bei Abbruch durch den Benutzer.
     */
    public static String askUserToEnterALongStringValue(Component parentComponent, String title,
            String message) {
        Image programImage = null;
        String initialValue = "";
        return askUserToEnterALongStringValue(parentComponent, programImage, title, message,
                initialValue);
    }

    /**
     * Lässt den Benutzer einen längeren Text eingeben.
     *
     * @param parentComponent
     *            Element, vor dem der Dialog angezeigt wird.
     * @param programImage
     *            Das Icon des Programms.
     * @param title
     *            Titel des Dialogs.
     * @param message
     *            Angezeigte Nachricht.
     * @return Eingegebener Wert oder der leere String bei Abbruch durch den Benutzer.
     */
    public static String askUserToEnterALongStringValue(Component parentComponent,
            Image programImage, String title, String message) {
        String initialValue = "";
        return askUserToEnterALongStringValue(parentComponent, programImage, title, message,
                initialValue);
    }

    /**
     * Lässt den Benutzer einen längeren Text eingeben.
     *
     * @param parentComponent
     *            Element, vor dem der Dialog angezeigt wird.
     * @param programImage
     *            Das Icon des Programms.
     * @param title
     *            Titel des Dialogs.
     * @param message
     *            Angezeigte Nachricht.
     * @param initialValue
     *            Angezeigte initiale Eingabe.
     * @return Eingegebener Wert oder der leere String bei Abbruch durch den Benutzer.
     */
    public static String askUserToEnterALongStringValue(Component parentComponent,
            Image programImage, String title, String message, String initialValue) {
        EnterLongTextDialog dialog = new EnterLongTextDialog(
                parentComponent.getLocation(), programImage, title,
                message, initialValue);
        dialog.setVisible(true);
        return dialog.getEnteredValue();
    }

    /**
     * Lässt den Benutzer einen Wert eingeben.
     *
     * @param title
     *            Titel des Dialogs.
     * @param message
     *            Angezeigte Nachricht.
     * @return Eingegebener Wert oder der leere String bei Abbruch durch den Benutzer.
     */
    public static String askUserToEnterAStringValue(String title, String message) {
        int type = JOptionPane.QUESTION_MESSAGE;
        String answer = JOptionPane.showInputDialog(null, message, title, type);
        if (null == answer) {
            answer = "";
        }
        return answer;
    }

    /**
     * Lässt den Benutzer einen Wert eingeben, wobei eine Vorbelegung angezeigt wird.
     *
     * @param parentComponent
     *            Element, vor dem der Dialog angezeigt wird.
     * @param title
     *            Titel des Dialogs.
     * @param message
     *            Angezeigte Nachricht.
     * @param initialValue
     *            Angezeigte initiale Eingabe.
     * @return Eingegebener Wert oder der leere String bei Abbruch durch den Benutzer.
     */
    public static String askUserToEnterAStringValue(Component parentComponent, String title,
            String message, String initialValue) {
        int type = JOptionPane.QUESTION_MESSAGE;
        String answer = (String) JOptionPane.showInputDialog(parentComponent, message, title, type,
                null, null, initialValue);
        if (null == answer) {
            answer = "";
        }
        return answer;
    }

    /**
     * Lässt den Benutzer einen Wert eingeben, wobei eine Vorbelegung angezeigt wird.
     *
     * @param title
     *            Titel des Dialogs.
     * @param message
     *            Angezeigte Nachricht.
     * @param initialValue
     *            Angezeigte initiale Eingabe.
     * @return Eingegebener Wert oder der leere String bei Abbruch durch den Benutzer.
     */
    public static String askUserToEnterAStringValue(String title, String message,
            String initialValue) {
        int type = JOptionPane.QUESTION_MESSAGE;
        String answer = (String) JOptionPane.showInputDialog(null, message, title, type, null, null,
                initialValue);
        if (null == answer) {
            answer = "";
        }
        return answer;
    }

    /**
     * Lässt den Benutzer einen Wert aus einer DropDown-Box auswählen oder einen freien Text
     * eingeben.
     *
     * @param parentLocation
     *            Position des Rahmens der Oberfläche, vor der dieser Dialog erzeugt wird.
     * @param programImage
     *            Icon für den Dialog.
     * @param title
     *            Titel des Dialogs.
     * @param message
     *            Angezeigte Nachricht.
     * @param initialValue
     *            Angezeigte initiale Eingabe.
     * @param values
     *            Werte in der DropDown-Box
     * @return Eingegebener Wert oder der leere String bei Abbruch durch den Benutzer.
     */
    public static String askUserToPickStringFromDropDownOrEnterAValue(Point parentLocation,
            Image programImage, String title, String message, String initialValue,
            List<String> values) {
        ComboBoxDialog dialog = new ComboBoxDialog(parentLocation, programImage, title, message,
                initialValue, values);
        dialog.allowEnteringOwnValues();
        dialog.setVisible(true);
        return dialog.getEnteredValue();
    }

    /**
     * Lässt den Benutzer einen Wert aus einer DropDown-Box auswählen. Es kann kein anderer Wert
     * eingeben werden.
     *
     * @param parentLocation
     *            Position des Rahmens der Oberfläche, vor der dieser Dialog erzeugt wird.
     * @param programImage
     *            Icon für den Dialog.
     * @param title
     *            Titel des Dialogs.
     * @param message
     *            Angezeigte Nachricht.
     * @param initialValue
     *            Angezeigte initiale Eingabe.
     * @param values
     *            Werte in der DropDown-Box
     * @return Ausgewählter Wert oder der leere String bei Abbruch durch den Benutzer.
     */
    public static String askUserToPickStringFromDropDown(Point parentLocation, Image programImage,
            String title, String message, String initialValue, List<String> values) {
        ComboBoxDialog dialog = new ComboBoxDialog(parentLocation, programImage, title, message,
                initialValue, values);
        dialog.setVisible(true);
        return dialog.getEnteredValue();
    }

    /**
     * Scrollt eine ScrollPane nach oben bzw. links.
     *
     * Wird später im EDT ausgeführt.
     *
     * @param scroll
     *            Zu scrollende Scrollpane.
     */
    public static void scrollScrollbarToMinimumLater(JScrollPane scroll) {
        SwingUtilities.invokeLater(() -> scrollScrollbarToMinimum(scroll));
    }

    /**
     * Scrollt eine ScrollPane nach unten bzw. rechts.
     *
     * Wird später im EDT ausgeführt.
     *
     * @param scroll
     *            Zu scrollende Scrollpane.
     */
    public static void scrollScrollbarToMaximumLater(JScrollPane scroll) {
        SwingUtilities.invokeLater(() -> scrollScrollbarToMaximum(scroll));
    }

    /**
     * Setzt eine Scrollleiste auf den Minimalwert. Passiert im gleichen Thread.
     *
     * @param scroll
     *            Zu bearbeitende Scrollleiste.
     */
    public static void scrollScrollbarToMinimum(JScrollPane scroll) {
        int minimum = scroll.getVerticalScrollBar().getMinimum();
        scroll.getVerticalScrollBar().setValue(minimum);
    }

    /**
     * Setzt eine Scrollleiste auf den Maximalwert. Passiert im gleichen Thread.
     *
     * @param scroll
     *            Zu bearbeitende Scrollleiste.
     */
    public static void scrollScrollbarToMaximum(JScrollPane scroll) {
        int maximum = scroll.getVerticalScrollBar().getMaximum();
        scroll.getVerticalScrollBar().setValue(maximum);
    }

    /**
     * Zeigt in einer Scrollleiste den vorherigen Abschnitt an (etwa für PAGE-UP).
     *
     * Passiert im EDT.
     *
     * @param scroll
     *            Zu bearbeitende Scrollleiste.
     */
    public static void scrollScrollbarToPreviousSectionLater(JScrollPane scroll) {
        SwingUtilities.invokeLater(() -> scrollScrollbarToPreviousSection(scroll));
    }

    /**
     * Zeigt in einer Scrollleiste den nächsten Abschnitt an (etwa für PAGE-DOWN).
     *
     * Passiert im EDT.
     *
     * @param scroll
     *            Zu bearbeitende Scrollleiste.
     */
    public static void scrollScrollbarToNextSectionLater(JScrollPane scroll) {
        SwingUtilities.invokeLater(() -> scrollScrollbarToNextSection(scroll));
    }

    /**
     * Zeigt in einer Scrollleiste den vorherigen Abschnitt an (etwa für PAGE-UP).
     *
     * Passiert im gleichen Thread.
     *
     * @param scroll
     *            Zu bearbeitende Scrollleiste.
     */
    public static void scrollScrollbarToPreviousSection(JScrollPane scroll) {
        int minimum = scroll.getVerticalScrollBar().getMinimum();
        int actualPos = scroll.getVerticalScrollBar().getValue();
        int height = scroll.getVisibleRect().height;
        actualPos -= height;
        if (actualPos < minimum) {
            actualPos = minimum;
        }
        scroll.getVerticalScrollBar().setValue(actualPos);
    }

    /**
     * Zeigt in einer Scrollleiste den nächsten Abschnitt an (etwa für PAGE-DOWN).
     *
     * Passiert im gleichen Thread.
     *
     * @param scroll
     *            Zu bearbeitende Scrollleiste.
     */
    public static void scrollScrollbarToNextSection(JScrollPane scroll) {
        int actualPos = scroll.getVerticalScrollBar().getValue();
        int maximum = scroll.getVerticalScrollBar().getMaximum();
        int height = scroll.getVisibleRect().height;
        actualPos += height;
        if (actualPos > maximum) {
            actualPos = maximum;
        }
        scroll.getVerticalScrollBar().setValue(actualPos);
    }

    /**
     * Fügt dem übergebenen JTextField einen Listener hinzu der den alten Text löscht.
     *
     * @param field
     *            JTextField, dem der entsprechende Listener hinzugefügt werden soll.
     * @param error
     *            Objekt zur Fehlerbehandlung.
     */
    public static void addDragNDropTextFieldCorrector(JTextField field, ErrorHandler error) {
        addDropTargetListenerToTextField(field, error, new TextFieldDropTargetListener(field));
    }

    /**
     * Fügt dem übergebenen JTextField einen Listener hinzu der den alten Text löscht.
     *
     * @param field
     *            JTextField, dem der entsprechende Listener hinzugefügt werden soll.
     * @param error
     *            Objekt zur Fehlerbehandlung.
     * @param listener
     *            Hinzuzufügender Listener.
     */
    public static void addDropTargetListenerToTextField(JTextField field, ErrorHandler error,
            DropTargetListener listener) {
        try {
            field.getDropTarget().addDropTargetListener(listener);
        }
        catch (TooManyListenersException e1) {
            error.error("Fehler beim Registrieren eines DropTargetListeners", e1);
        }
    }

    /**
     * Fügt der übergebenen JTextArea einen Listener hinzu der den alten Text löscht.
     *
     * @param area
     *            JTextArea, dem der entsprechende Listener hinzugefügt werden soll.
     * @param error
     *            Objekt zur Fehlerbehandlung.
     * @param listener
     *            Hinzuzufügender Listener.
     */
    public static void addDropTargetListenerToTextArea(JTextArea area, ErrorHandler error,
            DropTargetListener listener) {
        try {
            area.getDropTarget().addDropTargetListener(listener);
        }
        catch (TooManyListenersException e1) {
            error.error("Fehler beim Registrieren eines DropTargetListeners", e1);
        }
    }

    /** Erzeugt einen Panel mit BorderLayout. */
    public static JPanel createBorderLayoutPanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        return panel;
    }

    /** Erzeugt einen Panel mit BorderLayout und leerem Titel. */
    public static JPanel createBorderLayoutPanelWithTitle() {
        JPanel panel = createBorderLayoutPanel();
        createTitle(panel);
        return panel;
    }

    /** Erzeugt einen Panel mit BorderLayout und Titel. */
    public static JPanel createBorderLayoutPanelWithTitle(String title) {
        JPanel panel = createBorderLayoutPanel();
        createTitle(title, panel);
        return panel;
    }

    /**
     * Erzeugt einen Panel mit BorderLayout und leerem Titel, in dessen Center-Element die
     * übergebene Komponente eingebettet wird.
     */
    public static JPanel createBorderLayoutPanelWithTitleAndCenterElement(Component component) {
        JPanel panel = createBorderLayoutPanelWithTitle();
        panel.add(component, BorderLayout.CENTER);
        return panel;
    }

    /**
     * Erzeugt einen Panel mit BorderLayout und Titel, in dessen Center-Element die
     * übergebene Komponente eingebettet wird.
     */
    public static JPanel createBorderLayoutPanelWithTitleAndCenterElement(String title,
            Component component) {
        JPanel panel = createBorderLayoutPanelWithTitle(title);
        panel.add(component, BorderLayout.CENTER);
        return panel;
    }

    /** Erzeugt einen Panel mit FlowLayout und dem Align FlowLayout.CENTER. */
    public static JPanel createFloatingCenteredPanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new FlowLayout(FlowLayout.CENTER));
        return panel;
    }

    /** Erzeugt einen Panel mit FlowLayout und dem Align FlowLayout.LEFT. */
    public static JPanel createFloatingLeftPanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new FlowLayout(FlowLayout.LEFT));
        return panel;
    }

    /** Erzeugt ein vertikal zentriertes Label. */
    public static JLabel createVerticalCenteredLabel(String text) {
        JLabel label = new JLabel(text);
        centerLabelVertically(label);
        return label;
    }

    /** Zentriert ein JLable vertikal. */
    public static void centerLabelVertically(JLabel label) {
        label.setVerticalAlignment(JLabel.CENTER);
    }

    /** Erzeugt ein Label mit fetter Schrift. */
    public static JLabel createBigLabel(String text) {
        JLabel label = new JLabel(text);
        boldFont(label);
        return label;
    }

    /** Erzeugt ein vertikal zentriertes Label mit fetter Schrift. */
    public static JLabel createVerticalCenteredBigLabel(String text) {
        JLabel label = createBigLabel(text);
        centerLabelVertically(label);
        return label;
    }

    /**
     * Setzt für ein Textfeld die Farben und die Schriftart auf Monospaced und 14 Punkt.
     *
     * @param fieldOrArea
     *            Textfeld oder TextArea.
     */
    public static void setEditFieldColors(JTextComponent fieldOrArea) {
        fieldOrArea.setBackground(Color.WHITE);
        fieldOrArea.setForeground(Color.BLACK);
        fieldOrArea.setCaretColor(Color.RED);
        setMonospacedFont(fieldOrArea);
    }

    /**
     * Setzt für eine Textarea die Farben und die Schriftart auf Monospaced und 14 Punkt.
     *
     * @param area
     *            Textarea.
     */
    public static void setEditAreaColors(JTextArea area) {
        area.setBackground(Color.WHITE);
        area.setForeground(Color.BLACK);
        area.setCaretColor(Color.RED);
        setMonospacedFont(area);
    }

    /**
     * Setzt die Schriftart der übergebenen Komponente auf Monospaced und 14 Punkt.
     *
     * @param component
     *            Komponente
     */
    public static void setMonospacedFont(Component component) {
        //Font font = field.getFont();
        //int size = font.getSize();

        //Font newFont = new Font("Monospaced", Font.PLAIN, 14);
        //component.setFont(newFont);

        setMonospacedFont(component, 14);
    }

    /**
     * Setzt die Schriftart der übergebenen Komponente auf Monospaced und die angegebene
     * Schriftgröße.
     *
     * @param component
     *            Komponente
     * @param fontSize
     *            Größe des Fonts in Punkt.
     */
    public static void setMonospacedFont(Component component, int fontSize) {
        Font newFont = new Font("Monospaced", Font.PLAIN, fontSize);
        component.setFont(newFont);
    }

    /**
     * Setzt die Schriftgröße der übergebenen Komponente auf die angegebene Schriftgröße.
     *
     * @param component
     *            Komponente
     * @param fontSize
     *            Größe des Fonts in Punkt.
     */
    public static void setFontSize(Component component, int fontSize) {
        Font font = component.getFont();
        Font biggerFont = new Font(font.getName(), font.getStyle(), fontSize);
        component.setFont(biggerFont);
    }

    /**
     * Umgibt die übergebenen Komponente mit einem Rahmen, den diese komplett ausfüllt. Der
     * umgebende Panel bekommt einen Titel mit dem übergebenen Text.
     *
     * @param component
     *            Einzuhüllende Komponente.
     * @param title
     *            Titel des erzeugten Rahmens.
     * @return Einhüllender Rahmen.
     */
    public static Component surroundWithTitledPanel(Component component, String title) {
        JPanel panel = new JPanel();
        GuiTools.createTitle(title, panel);
        panel.setLayout(new BorderLayout());

        panel.add(component, BorderLayout.CENTER);

        return panel;
    }

    /**
     * Hüllt eine Komponente so ein, dass ihr Inhalt in der Mitte horizontal zentriert angezeigt
     * wird.
     */
    public static JPanel centerHorizontal(Component component) {
        JPanel panel = new JPanel();
        panel.setLayout(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets     = new Insets(0, 0, 0, 0);
        gbc.anchor     = GridBagConstraints.CENTER; // NORTHWEST
        gbc.fill       = GridBagConstraints.BOTH;
        gbc.weightx    = 0.0;
        gbc.weighty    = 0.0;

        gbc.gridx      = 0;
        gbc.gridy      = 0;
        gbc.weightx    = 1.0;
        panel.add(new JLabel(""), gbc);

        gbc.gridx      = 1;
        gbc.gridy      = 0;
        gbc.weightx    = 0.0;
        panel.add(component, gbc);

        gbc.gridx      = 2;
        gbc.gridy      = 0;
        gbc.weightx    = 1.0;
        panel.add(new JLabel(""), gbc);

        return panel;
    }

    /**
     * Hüllt eine Komponente so ein, dass ihr Inhalt in der Mitte vertical zentriert angezeigt
     * wird.
     */
    public static JPanel centerVertical(Component component) {
        JPanel panel = new JPanel();
        panel.setLayout(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets     = new Insets(0, 0, 0, 0);
        gbc.anchor     = GridBagConstraints.CENTER; // NORTHWEST
        gbc.fill       = GridBagConstraints.BOTH;
        gbc.weightx    = 0.0;
        gbc.weighty    = 0.0;

        gbc.gridx      = 0;
        gbc.gridy      = 0;
        gbc.weightx    = 1.0;
        panel.add(new JLabel(""), gbc);

        gbc.gridx      = 0;
        gbc.gridy      = 1;
        gbc.weightx    = 0.0;
        panel.add(component, gbc);

        gbc.gridx      = 0;
        gbc.gridy      = 2;
        gbc.weightx    = 1.0;
        panel.add(new JLabel(""), gbc);

        return panel;
    }

    /**
     * Hüllt eine Komponente so ein, dass ihr Inhalt in der Mitte horizontal und vertical zentriert
     * angezeigt wird.
     */
    public static JPanel center(Component component) {
        return centerVertical(centerHorizontal(component));
    }

    /** Fragte den Benutzer, welches Logfile er sehen möchte und zeigt es an. */
    public static void showLogFiles(String startdir) {
        showLogFiles(null, startdir, null);
    }

    /** Fragte den Benutzer, welches Logfile er sehen möchte und zeigt es an. */
    public static void showLogFiles(Component parentComponent, String startdir,
            Image programImage) {
        String filename = GuiTools.openFileWithExtension(parentComponent, ".log", startdir);
        if (!filename.isEmpty()) {
            Point location = (null == parentComponent ? new Point() : parentComponent.getLocation());
            LogfileDialog dialog = createLogFileDialog(filename, location, programImage);
            dialog.setVisible(true);
        }
    }

    /** Erzeugt einen Logfile_Dialog. */
    public static LogfileDialog createLogFileDialog(String logfile, Point parentLocation,
            Image programImage) {
        LogfileDialog dialog = new LogfileDialog(logfile, parentLocation, programImage);
        return dialog;
    }

    /**
     * Umhüllt die Komponente mit einem Panel im BorderLayout, in dem diese im Norden dargestellt
     * wird.
     */
    public static Component toNorth(Component component) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(component, BorderLayout.NORTH);

        return panel;
    }

    public static Component toEast(Component component) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(component, BorderLayout.EAST);

        return panel;
    }

    public static Component toSouth(Component component) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(component, BorderLayout.SOUTH);

        return panel;
    }

    public static Component toWest(Component component) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(component, BorderLayout.WEST);

        return panel;
    }

    /** Ermittelt die Größe des Bildschirms in Pixeln. */
    public static Dimension getScreenSize() {
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Dimension screenSize = toolkit.getScreenSize();
        return screenSize;
    }

    /** Ermittelt die Höhe des Bildschirms in Pixeln. */
    public static int getScreenHeight() {
        Dimension screenSize = getScreenSize();
        return screenSize.height;
    }

    /** Ermittelt die Breite des Bildschirms in Pixeln. */
    public static int getScreenWidth() {
        Dimension screenSize = getScreenSize();
        return screenSize.width;
    }

    /** Lädt das Icon unter dem angegebenen relativen Pfad zur Klasse. */
    public static Icon loadIcon(String filename, Class<?> callingClass) {
        try {
            Image image = ImageIO.read(callingClass.getResource(filename));
            return new ImageIcon(image);
        }
        catch (Exception exeption) {
            throw new RuntimeException(exeption);
        }
    }

    /** Entfernt die Default-Bindung an F6 von einer JSplitPane ("Move between panes"). */
    public static void removeF6FromJSplitPane(JSplitPane splitPane) {
        removeKeyFormJSplitPane(splitPane, KeyEvent.VK_F6);
    }

    /** Entfernt die Default-Bindung an F8 von einer JSplitPane ("Move to splitter bar"). */
    public static void removeF8FromJSplitPane(JSplitPane splitPane) {
        removeKeyFormJSplitPane(splitPane, KeyEvent.VK_F8);
    }

    private static void removeKeyFormJSplitPane(JSplitPane splitPane, int functionKey) {
        splitPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
                .put(KeyStroke.getKeyStroke(functionKey, 0), "none");
    }

    /** Piept. */
    public static void beep() {
        java.awt.Toolkit.getDefaultToolkit().beep();
    }

    /** Fügt einen KeyListener zur Komponente hinzu, der bei Return die übergebene Aktion ausführt. */
    public static void addReturnListener(Component component, Runnable runnable) {
        component.addKeyListener(createReturnKeyListener(runnable));
    }

    /** Erzeugt einen KeyListener, der bei Return die übergebene Aktion ausführt. */
    public static KeyListener createReturnKeyListener(Runnable runnable) {
        return new KeyListener() {
            @Override
            public void keyTyped(KeyEvent event) {
            }
            @Override
            public void keyReleased(KeyEvent event) {
            }
            @Override
            public void keyPressed(KeyEvent event) {
                if (event.getKeyCode() == KeyEvent.VK_ENTER && event.getModifiersEx() == 0) {
                    runnable.run();
                }
            }
        };
    }

    /**
     * Setzt die vertikale Scrollgeschwindigkeit bei der Benutzung des Mausrades einer JScrollPane.
     *
     * Der Defaultwert ist 1.
     *
     * @param scroll
     *            Die JScrollPane deren vertikale Scrollgeschwindigkeit bei der Benutzung des
     *            Mausrades eingestellt werden soll.
     * @param unitIncrement
     *            Gibt an wie weit nun gescrollt werden soll
     */
    public static void setVerticalScrollBarUnitIncrement(JScrollPane scroll, int unitIncrement) {
        scroll.getVerticalScrollBar().setUnitIncrement(unitIncrement);
    }

    /**
     * Setzt die horizontale Scrollgeschwindigkeit bei der Benutzung des Mausrades einer
     * JScrollPane.
     *
     * Der Defaultwert ist 1.
     *
     * @param scroll
     *            Die JScrollPane deren horizontale Scrollgeschwindigkeit bei der Benutzung des
     *            Mausrades eingestellt werden soll.
     * @param unitIncrement
     *            Gibt an wie weit nun gescrollt werden soll
     */
    public static void setHorizontalScrollBarUnitIncrement(JScrollPane scroll, int unitIncrement) {
        scroll.getHorizontalScrollBar().setUnitIncrement(unitIncrement);
    }

    /**
     * Erzeugt eine Scrollpane um die übergebene Komponente mit einem
     * VerticalScrollBarUnitIncrement von 30.
     */
    public static JScrollPane createScrollPane(Component component) {
        JScrollPane scrollPane = new JScrollPane(component);
        GuiTools.setVerticalScrollBarUnitIncrement(scrollPane, 30);
        return scrollPane;
    }

    /** Erzeugt aus "abc" die vertikale Version "<html>a<br>b<br>c</html>". */
    public static String toVerticalButtonText(String text) {
        return "<html>" + Text.join("<br>", Text.eachCharFromString(text)) + "</html>";
    }

    /**
     * Lässt die Tastenkombinationen Page-Up und Page-Down an die übergeordnete Komponente
     * weiterreichen.
     */
    public static void ignorePageUpAndPageDownInComponent(JComponent component) {
        for (InputMap inputmap : createIgnoreKeysInputMaps(component)) {
            inputmap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), "none");
            inputmap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "none");
        }
    }

    private static List<InputMap> createIgnoreKeysInputMaps(JComponent component) {
        InputMap inputMap1 = component.getInputMap();
        InputMap inputMap2 = component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        InputMap inputMap3 = component.getInputMap(JComponent.WHEN_FOCUSED);
        InputMap inputMap4 = component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        List<InputMap> inputMaps = CollectionsHelper.buildListFrom(inputMap1, inputMap2, inputMap3,
                inputMap4);
        return inputMaps;
        /*
         * Ohne JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT funktioniert es nicht bei einer
         * JComboBox.
         *
         * Für die JScrollPane um eine JEditorPane brauchen wir allerdings die Variante ohne, da
         * es nur mit dieser nicht funktioniert.
         *
         * Die beiden mittleren habe ich ergänzt, da in einer Diskussion gefunden, beim Versuch,
         * auch in einer JTable die Tasten weiterzuleiten, bislang leider ohne Erfolg.
         */
    }

    /**
     * Lässt die Tastenkombinationen Pfeiltaste nach oben und Pfeiltaste nach unten an die
     * übergeordnete Komponente weiterreichen.
     */
    public static void ignoreUpAndDownInComponent(JComponent component) {
        for (InputMap inputmap : createIgnoreKeysInputMaps(component)) {
            inputmap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "none");
            inputmap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "none");
        }
    }

    /**
     * Lässt die Tastenkombinationen Ctrl-Pos1 und Ctrl-End an die übergeordnete Komponente
     * weiterreichen.
     */
    public static void ignoreCtrlPos1AndCtrlEndInComponent(JComponent component) {
        for (InputMap inputmap : createIgnoreKeysInputMaps(component)) {
            inputmap.put(
                    KeyStroke.getKeyStroke(KeyEvent.VK_HOME, InputEvent.CTRL_DOWN_MASK), "none");
            inputmap.put(
                    KeyStroke.getKeyStroke(KeyEvent.VK_END, InputEvent.CTRL_DOWN_MASK), "none");
        }
    }

//    /**
//     * Lässt die Tastenkombinationen Tabulator und Ctrl-Tabulator, die normalerweise zum Wechsel
//     * zwischen den Eingabeelementen genutzt werden, an die übergeordnete Komponente weiterreichen.
//     */
//    public static void ignoreTabulatorInComponent(JComponent component) {
//        for (InputMap inputmap : createIgnoreKeysInputMaps(component)) {
//            inputmap.put(
//                    KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "none");
//            inputmap.put(
//                    KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.CTRL_DOWN_MASK), "none");
//        }
//    }
// das funktioniert so nicht ...

    /** Vergrößert die Schriftart einer Überschrift eines Panels mit TitledBorder. */
    public static void biggerTitledBorderFontOfPanel(JPanel panel, int addSize) {
        Border border = panel.getBorder();
        if (null != border) {
            if (border instanceof TitledBorder titledBorder) {
                Font font = titledBorder.getTitleFont();
                Font biggerFont = new Font(font.getName(), font.getStyle(),
                        font.getSize() + addSize);
                titledBorder.setTitleFont(biggerFont);
            }
        }
    }

    /**
     * Setzt die Schriftart einer Überschrift eines Panels mit TitledBorder auf die Standardgröße
     * von 12.
     */
    public static void defaultTitledBorderFontSize(JPanel panel) {
        setTitledBorderFontSize(panel, DEFAULT_TITLED_BORDER_FONT_SIZE);
    }

    /** Setzt die Schriftart einer Überschrift eines Panels mit TitledBorder. */
    public static void setTitledBorderFontSize(JPanel panel, int fontSize) {
        Border border = panel.getBorder();
        if (null != border) {
            if (border instanceof TitledBorder titledBorder) {
                Font font = titledBorder.getTitleFont();
                Font biggerFont = new Font(font.getName(), font.getStyle(), fontSize);
                titledBorder.setTitleFont(biggerFont);
            }
        }
    }

    /**
     * Ermittelt die Schriftart einer Überschrift eines Panels mit TitledBorder.
     *
     * Gibt -1 zurück, falls kein TitledBorder am Panel hinterlegt ist.
     */
    public static int getTitledBorderFontSizeOfPanel(JPanel panel) {
        Border border = panel.getBorder();
        if (null == border) {
            return -1;
        }
        else {
            if (border instanceof TitledBorder titledBorder) {
                Font font = titledBorder.getTitleFont();
                return font.getSize();
            }
            else {
                return -1;
            }
        }
    }

    /**
     * Setzt Hintergrund- und Vordergrundfarbe eines Buttons. Die Übergebene Farbe wird der
     * Hintergrund und als Vordergrund wird die entgegengesetzte Farbe verwendet.
     */
    public static void setForegroundAndBackground(JButton button, Color color) {
        button.setBackground(color);
        button.setForeground(ColorTool.anticolor(color));
    }

    /**
     * Zeigt Informationen über das Programm an.
     *
     * @param name
     *            Name des Programms.
     * @param artikel
     *            Bestimmter Artikel zum Namen ("den", "das") der nach 'Über' steht.
     * @param version
     *            Version des Programms.
     * @param imageIcon
     *            Icon des Programms.
     * @param frame
     *            Rahmen des Programms vor dem der Dialog angezeigt werden soll.
     */
    public static void showAbout(String name, String artikel, Version version, ImageIcon imageIcon,
            JFrame frame) {
        showAbout(name, artikel, "", version, imageIcon, frame);
    }

    /**
     * Zeigt Informationen über das Programm mit Zusatzinformationen an.
     *
     * @param name
     *            Name des Programms.
     * @param artikel
     *            Bestimmter Artikel zum Namen ("den", "das") der nach 'Über' steht.
     * @param additionalText
     *            Zusätzlich anzuzeigender Text.
     * @param version
     *            Version des Programms.
     * @param imageIcon
     *            Icon des Programms.
     * @param frame
     *            Rahmen des Programms vor dem der Dialog angezeigt werden soll.
     */
    public static void showAbout(String name, String artikel, String additionalText,
            Version version, ImageIcon imageIcon, JFrame frame) {
        StringBuilder builder = new StringBuilder();
        builder.append(name + "\n\n");
        builder.append("Autor: Christian Dühl\n");
        if (!additionalText.isBlank()) {
            builder.append(additionalText).append("\n");
        }
        builder.append("Version: " + version.getVersionAndDate() + "\n\n");
        String message = builder.toString();

        JOptionPane.showMessageDialog(frame, message,
                "Über " + artikel + " " + name,
                JOptionPane.INFORMATION_MESSAGE,
                imageIcon);
    }

    /** Erzeugt ein Panel, das die übergebene Komponente eingerückt darstellt. */
    public static Component indentComponent(
            Component component) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        JLabel spaceLabel = new JLabel();
        spaceLabel.setPreferredSize(new Dimension(50, 0));
        panel.add(spaceLabel, BorderLayout.WEST);

        panel.add(component, BorderLayout.CENTER);

        return panel;
    }

    /**
     * Fertigt einen Screenshot mit Fensterrahmen von der übergebenen Komponente an.
     *
     * @param component
     *            Abzubildende Komponente (z.B. der JFrame).
     * @param filename
     *            Name der Datei, unter dem der Screenshot gespeichert werden soll.
     */
    public static void takeScreenshot(Component component, String filename) {
        BufferedImage image = getScreenShot(component);
        writeImageToPngFile(image, filename);
    }

    private static BufferedImage getScreenShot(Component component) {
        BufferedImage image = new BufferedImage(component.getWidth(), component.getHeight(),
                BufferedImage.TYPE_INT_RGB);
        // call the Component's paint method, using
        // the Graphics object of the image.
        //component.paint(image.getGraphics()); // alternately use .printAll(..)
        component.printAll(image.getGraphics()); // alternately use .printAll(..)
        return image;
    }

    /**
     * Speichert das als BufferedImage vorliegende Bild als png-Datei.
     *
     * @param image
     *            Das Bild als BufferedImage.
     * @param filename
     *            Der Dateiname unter dem Das Bild abgespeichert wird.
     */
    public static void writeImageToPngFile(BufferedImage image, String filename) {
        try {
            ImageIO.write(image, "png", new File(filename)); // write the image as a PNG
        }
        catch (Exception exception) {
            throw new RuntimeException(
                    "Konnte das Bild nicht unter '" + filename + "' abspeichern.", exception);
        }
    }

    /**
     * Lädt das in einer Datei vorliegende Bild als Image.
     *
     * @param filename
     *            Der Dateiname unter dem Das Bild abgespeichert ist.
     *            @return Das eingelesen Image.
     */
    public static BufferedImage loadImage(String filename) {
        try {
            BufferedImage image = ImageIO.read(new File(filename));
            return image;
        }
        catch (Exception exeption) {
            throw new RuntimeException(exeption);
        }
    }

    /**
     * Fertigt einen Screenshot ohne Fensterrahmen vom übergebenen JFrame an.
     *
     * @param frame
     *            Abzubildender JFrame.
     * @param filename
     *            Name der Datei, unter dem der Screenshot gespeichert werden soll.
     */
    public static void takeScreenshotWithoutFrame(JFrame frame, String filename) {
        takeScreenshot(frame.getContentPane(), filename);
    }

    /**
     * Erzeugt einen MouseAdaoter (wie man ihn bei addMouseListener() hinterlegen kann), der auf
     * einen einzelnen Klick reagiert.
     */
    public static MouseAdapter createSingleClickMouseAdapter(Runnable runnable) {
        return new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                int count = e.getClickCount();
                if (count == 1) {
                    runnable.run();
                }
            }
        };
    }

//    /**
//     * Erzeugt einen MouseAdaoter (wie man ihn bei addMouseListener() hinterlegen kann), der auf
//     * einen einzelnen Klick beim loslassen reagiert.
//     */
//    // Das funktioniert so nicht... er zählt doch da, wo der Klick war...
//    public static MouseAdapter createSingleReleasedMouseAdapter(Runnable runnable) {
//        return new MouseAdapter() {
//            @Override
//            public void mouseReleased(MouseEvent e) {
//                int count = e.getClickCount();
//                if (count == 1) {
//                    runnable.run();
//                }
//            }
//        };
//    }

    /**
     * Erzeugt einen MouseAdaoter (wie man ihn bei addMouseListener() hinterlegen kann), der auf
     * einen Doppelklick reagiert.
     */
    public static MouseAdapter createDoubleClickMouseAdapter(Runnable runnable) {
        return new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                int count = e.getClickCount();
                if (count == 2) {
                    runnable.run();
                }
            }
        };
    }

    /**
     * Erzeugt einen MouseAdaoter (wie man ihn bei addMouseListener() hinterlegen kann), der auf
     * einen Doppelklick reagiert und das Scrollen an die Eltern-Komponente weiterreicht.
     *
     * @param component
     *            Komponente, der der Mouse/Wheel-Listener hinzugefügt werden soll.
     * @param runnable
     *            Wird bei Doppelklick ausgeführt.
     */
    public static void createDoubleClickAndDispatchMouseScrollEventMouseAdapter(
            Component component, Runnable runnable) {
        component.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                int count = e.getClickCount();
                if (count == 2) {
                    runnable.run();
                }
            }

            @Override
            public void mouseWheelMoved(MouseWheelEvent event) {
                component.getParent().dispatchEvent(event);
            }
        });
    }

    /** Reicht das Scrollen an die Eltern-Komponente weiter. */
    public static void dispatchMouseScrollEvent(Component component) {
        component.addMouseWheelListener(new MouseWheelListener() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent event) {
                Container parent = component.getParent();
                while (parent != null) {
                    parent.dispatchEvent(event);
                    parent = parent.getParent();
                }
                /*
                 * Ohne die Schleife funktioniert es nicht für die JTextArea.
                 * (Personen-Strukturen-Editor HR-Bereitstellung)
                 */
            }
        });
    }

    /** Reicht das Scrollen an die Eltern-Komponente bis zur angegebenen Stufe weiter. */
    public static void dispatchMouseScrollEvent(Component component, int parentCount) {
        component.addMouseWheelListener(new MouseWheelListener() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent event) {
                int count = 0;
                Container parent = component.getParent();
                while (parent != null && count < parentCount) {
                    parent.dispatchEvent(event);
                    parent = parent.getParent();
                    ++count;
                }
            }
        });
    }

    /** Reicht das Scrollen an genau eine Eltern-Komponente weiter. */
    public static void dispatchMouseScrollEventOnlyOnce(Component component) {
        component.addMouseWheelListener(new MouseWheelListener() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent event) {
                Container parent = component.getParent();
                if (parent != null) {
                    parent.dispatchEvent(event);
                }
            }
        });
    }

    /** Gibt zu einer Combobox mit Texteingabe das JTextField zurück. */
    public static  JTextField getTextFieldInComboBox(JComboBox<String> comboBox) {
        return (JTextField) comboBox.getEditor().getEditorComponent();
    }

    /**
     * Diese Methode erzeugt einen ggf. umgebrochenen Text, den man für einen JButton, ein JLabel
     * oder dergleichen verwenden kann.
     *
     * Hierbei werden Unterstriche in Leerzeichen gewandelt.
     *
     * @param text
     *            Der originale Text.
     * @param maxTextLenght
     *            Die Länge bei der Umgebrochen wird.
     * @return Der erzeugte Text in HTML-Form.
     */
    public static String createHtmlTextForButtonsWithUnderscoreReplacement(String text,
            int maxTextLenght) {
        String htmlText = text.replace("_", " ");
        return createHtmlTextForButtons(htmlText, maxTextLenght);
    }

    /**
     * Diese Methode erzeugt einen ggf. umgebrochenen Text, den man für einen JButton, ein JLabel
     * oder dergleichen verwenden kann.
     *
     * Hierbei werden Unterstriche in Leerzeichen gewandelt.
     *
     * @param text
     *            Der originale Text.
     * @param maxTextLenght
     *            Die Länge bei der Umgebrochen wird.
     * @return Der erzeugte Text in HTML-Form.
     */
    public static String createHtmlTextForButtons(String text, int maxTextLenght) {
        String htmlText = text;
        if (htmlText.length() > maxTextLenght) {
            htmlText = Text.addLineBreaks(htmlText, maxTextLenght);
            htmlText = HtmlTool.htmlifyWithoutFrame(htmlText);
        }
        htmlText = "<html>" + htmlText + "</html>";
        return htmlText;
    }

    /**
     * Normalerweise dehnen sich Panel mit GridLayout in alle Richtungen aus. Hier wird nun dafür
     * gesorgt, dass er sich in Y-Richtung nicht nach unten ausdehnt.
     */
    public static Component makeGridLayoutComponentNotYGreedy(Component component) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(component, BorderLayout.NORTH);
        panel.add(new JLabel(""), BorderLayout.CENTER);

        return panel;
    }

    /**
     * Umgibt die übergebene Komponente mit einer SCrollPane, welche nicht in horizontale Richtung
     * scrollt.
     *
     * Setzt die vertikale Scrollgeschwindigkeit bei der Benutzung des Mausrades der JScrollPane
     * auf 30.
     */
    public static JScrollPane addNotHorizontalScrollingScrollPane(Component component) {
        JScrollPane scroll = new JScrollPane(component,
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); // HORIZONTAL_SCROLLBAR_AS_NEEDED
        GuiTools.setVerticalScrollBarUnitIncrement(scroll, 30);
        return scroll;
    }

    /**
     * Fragt den Benutzer nach einem Passwort.
     *
     * @param title
     *            Der Titel des Dialogs.
     * @param message
     *            Der Text des Dialogs.
     * @return Das Passwort oder einen leeren String, wenn der Benutzer den Dialog abgebrochen hat.
     */
    public static String askUserToEnterAPassword(String title, String message) {
        PasswordDialog dialog = new PasswordDialog(title, message);
        dialog.setVisible(true);
        String password = dialog.getPassword();
        return password;
    }

    /**
     * Löscht in der übergebenen Komponente die normalen Tastenbelegungen (Tabulator und
     * Shift-Tabulator) zum Wechseln durch die Gui-Komponenten, die den Focus bekommen können.
     *
     * Dies wird etwa benötigt, wenn man die Tabulator-Taste in einer Anwendung zu eigenen Zwecken
     * nutzen möchte.
     */
    public static void setFocusTraversalKeysToEmptySet(Component component) {
        Set<AWTKeyStroke> forwardKeys = Collections.emptySet();
        Set<AWTKeyStroke> backwardKeys = Collections.emptySet();
        component.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
        component.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backwardKeys);
    }

    /**
     * Umgibt die übergebene Komponente mit einem leeren Rahmen, der auf der linken Seite die
     * übergebene Breite an Platz lässt.
     *
     * Damit das auch mit Komponenten funktioniert, die schon einen eigenen Rahmen haben, wird sie
     * hier in einen Panel gehüllt.
     *
     * @param component
     *            Die zu umhüllende Komponente.
     * @param numberOfPixel
     *            Anzahl Pixel für den Abstand auf der linken Seite.
     * @return Erzeugte Komponente.
     */
    public static Component addLeftSpace(Component component, int numberOfPixel) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(component, BorderLayout.CENTER);

        panel.setBorder(BorderFactory.createEmptyBorder(0, numberOfPixel, 0, 0));

        return panel;
    }

    /** Hüllt die Komponente in einen Panel mti BorderLayout ein, wo sie rechts dargestellt wird. */
    public static Component createEasternComponent(Component component) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(component, BorderLayout.EAST);

        return panel;
    }

    /** Erstellt ein leeres Label mit dem gewünschten horizontalen Abstand. */
    public static Component createEmptyLabelWithWith(int width) {
        JLabel label = new JLabel();
        label.setPreferredSize(new Dimension(width, 0));
        return label;
    }

}
