r4fterman/pdf.forms

View on GitHub
src/main/java/org/pdf/forms/gui/properties/border/BorderPropertiesPanel.java

Summary

Maintainability
D
2 days
Test Coverage
F
45%
package org.pdf.forms.gui.properties.border;

import static java.util.stream.Collectors.toUnmodifiableList;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import javax.swing.*;

import org.jdesktop.layout.GroupLayout;
import org.jdesktop.layout.LayoutStyle;
import org.pdf.forms.gui.designer.IDesigner;
import org.pdf.forms.gui.designer.gui.Rule;
import org.pdf.forms.model.des.BorderProperties;
import org.pdf.forms.model.des.Borders;
import org.pdf.forms.widgets.IWidget;

import com.google.common.primitives.Doubles;

public class BorderPropertiesPanel extends JPanel {

    private static final String[] BORDER_STYLES = {
            "None",
            "Solid",
            "Beveled"
    };
    private static final String[] BACKGROUND_FILL_STYLES = {
            "None",
            "Solid"
    };

    private static final int UNITS = (int) (Rule.INCH / 2.54);

    private IDesigner designerPanel;
    private Set<IWidget> widgets;

    private JComboBox<String> backgroundFillBox;
    private JButton backgroundFillColorButton;
    private JButton borderColorButton;
    private JComboBox<String> borderStyleBox;
    private JTextField borderWidthBox;

    BorderPropertiesPanel() {
        initComponents();
    }

    public void setDesignerPanel(final IDesigner designerPanel) {
        this.designerPanel = designerPanel;
    }

    private void initComponents() {
        final JPanel panel = createBordersPanel();
        final JPanel backgroundPanel = createBackgroundPanel();

        final GroupLayout layout = new GroupLayout(this);
        setLayout(layout);
        layout.setHorizontalGroup(
                layout.createParallelGroup(GroupLayout.LEADING)
                        .add(GroupLayout.TRAILING, layout.createSequentialGroup()
                                .add(layout.createParallelGroup(GroupLayout.TRAILING)
                                        .add(backgroundPanel,
                                                GroupLayout.DEFAULT_SIZE,
                                                GroupLayout.DEFAULT_SIZE,
                                                Short.MAX_VALUE)
                                        .add(panel,
                                                GroupLayout.DEFAULT_SIZE,
                                                GroupLayout.DEFAULT_SIZE,
                                                Short.MAX_VALUE))
                                .addContainerGap())
        );
        layout.setVerticalGroup(
                layout.createParallelGroup(GroupLayout.LEADING)
                        .add(layout.createSequentialGroup()
                                .add(panel,
                                        GroupLayout.PREFERRED_SIZE,
                                        GroupLayout.DEFAULT_SIZE,
                                        GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(LayoutStyle.RELATED)
                                .add(backgroundPanel,
                                        GroupLayout.PREFERRED_SIZE,
                                        GroupLayout.DEFAULT_SIZE,
                                        GroupLayout.PREFERRED_SIZE)
                                .addContainerGap(16, Short.MAX_VALUE))
        );
    }

    private JPanel createBordersPanel() {
        final JPanel panel = new JPanel();
        panel.setBorder(BorderFactory.createTitledBorder("Borders"));

        final JLabel leftEdgesIcon = new JLabel(new ImageIcon(getClass()
                .getResource("/org/pdf/forms/res/Border Together.png")));
        leftEdgesIcon.setOpaque(true);

        borderStyleBox = new JComboBox<>(BORDER_STYLES);
        borderStyleBox.addActionListener(this::updateBorderStyle);

        borderWidthBox = new JTextField();
        borderWidthBox.addFocusListener(new FocusAdapter() {
            @Override
            public void focusLost(final FocusEvent event) {
                updateBorderStyle(null);
            }
        });

        borderColorButton = new JButton();
        borderColorButton.setContentAreaFilled(false);
        borderColorButton.setOpaque(true);
        borderColorButton.addActionListener(this::borderColorButtonClicked);

        final GroupLayout groupLayout = new GroupLayout(panel);
        panel.setLayout(groupLayout);
        groupLayout.setHorizontalGroup(
                groupLayout.createParallelGroup(GroupLayout.LEADING)
                        .add(GroupLayout.TRAILING, groupLayout.createSequentialGroup()
                                .add(12, 12, 12)
                                .add(leftEdgesIcon, GroupLayout.PREFERRED_SIZE, 16, GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(LayoutStyle.RELATED)
                                .add(borderStyleBox, 0, 135, Short.MAX_VALUE)
                                .addPreferredGap(LayoutStyle.RELATED)
                                .add(borderWidthBox, GroupLayout.PREFERRED_SIZE, 55, GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(LayoutStyle.RELATED)
                                .add(borderColorButton, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE)
                                .addContainerGap())
        );
        groupLayout.setVerticalGroup(
                groupLayout.createParallelGroup(GroupLayout.LEADING)
                        .add(groupLayout.createSequentialGroup()
                                .add(groupLayout.createParallelGroup(GroupLayout.TRAILING)
                                        .add(groupLayout.createSequentialGroup()
                                                .add(leftEdgesIcon,
                                                        GroupLayout.PREFERRED_SIZE,
                                                        16,
                                                        GroupLayout.PREFERRED_SIZE)
                                                .add(4, 4, 4))
                                        .add(groupLayout.createParallelGroup(GroupLayout.BASELINE)
                                                .add(borderColorButton,
                                                        GroupLayout.PREFERRED_SIZE,
                                                        23,
                                                        GroupLayout.PREFERRED_SIZE)
                                                .add(borderStyleBox,
                                                        GroupLayout.PREFERRED_SIZE,
                                                        GroupLayout.DEFAULT_SIZE,
                                                        GroupLayout.PREFERRED_SIZE)
                                                .add(borderWidthBox,
                                                        GroupLayout.PREFERRED_SIZE,
                                                        GroupLayout.DEFAULT_SIZE,
                                                        GroupLayout.PREFERRED_SIZE)))
                                .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        return panel;
    }

    private JPanel createBackgroundPanel() {
        final JPanel panel = new JPanel();
        panel.setBorder(BorderFactory.createTitledBorder("Background Fill"));

        final JLabel styleLabel = new JLabel("Style:");

        backgroundFillBox = new JComboBox<>(BACKGROUND_FILL_STYLES);
        backgroundFillBox.setEnabled(false);
        backgroundFillBox.addActionListener(this::updateFillStyle);

        backgroundFillColorButton = new JButton();
        backgroundFillColorButton.setEnabled(false);
        backgroundFillColorButton.setContentAreaFilled(false);
        backgroundFillColorButton.setOpaque(true);
        backgroundFillColorButton.addActionListener(this::fillColorClicked);

        final GroupLayout groupLayout = new GroupLayout(panel);
        panel.setLayout(groupLayout);
        groupLayout.setHorizontalGroup(
                groupLayout.createParallelGroup(GroupLayout.LEADING)
                        .add(groupLayout.createSequentialGroup()
                                .add(styleLabel)
                                .addPreferredGap(LayoutStyle.RELATED)
                                .add(backgroundFillBox, 0, 191, Short.MAX_VALUE)
                                .addPreferredGap(LayoutStyle.RELATED)
                                .add(backgroundFillColorButton,
                                        GroupLayout.PREFERRED_SIZE,
                                        23,
                                        GroupLayout.PREFERRED_SIZE)
                                .addContainerGap())
        );
        groupLayout.setVerticalGroup(
                groupLayout.createParallelGroup(GroupLayout.LEADING)
                        .add(groupLayout.createSequentialGroup()
                                .add(groupLayout.createParallelGroup(GroupLayout.BASELINE)
                                        .add(styleLabel)
                                        .add(backgroundFillColorButton,
                                                GroupLayout.PREFERRED_SIZE,
                                                23,
                                                GroupLayout.PREFERRED_SIZE)
                                        .add(backgroundFillBox,
                                                GroupLayout.PREFERRED_SIZE,
                                                GroupLayout.DEFAULT_SIZE,
                                                GroupLayout.PREFERRED_SIZE))
                                .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        return panel;
    }

    private void fillColorClicked(final ActionEvent evt) {
        //todo: add your handling code here:
    }

    private void updateFillStyle(final ActionEvent evt) {
        //todo: add your handling code here:
    }

    private void borderColorButtonClicked(final ActionEvent evt) {
        final Color currentBackground = borderColorButton.getBackground();
        final Color color = JColorChooser.showDialog(null, "Color Chooser", currentBackground);
        if (color != null) {
            borderColorButton.setIcon(null);
            borderColorButton.setContentAreaFilled(false);
            borderColorButton.setOpaque(true);
            borderColorButton.setBackground(color);
        }

        updateBorderStyle(null);
    }

    private void updateBorderStyle(final ActionEvent evt) {
        final String style = (String) borderStyleBox.getSelectedItem();
        final boolean borderEnabled = style != null && !style.equals("None");

        borderWidthBox.setEnabled(borderEnabled);
        borderColorButton.setEnabled(borderEnabled);

        widgets.forEach(widget -> setBorderValues(style, widget));

        designerPanel.repaint();
    }

    private void setBorderValues(
            final String style,
            final IWidget widget) {
        widget.getWidgetModel().getProperties().getBorder()
                .ifPresent(borderProperties -> {
                    final Borders borders = borderProperties.getBorders();

                    Optional.ofNullable(style)
                            .ifPresent(borders::setBorderStyle);

                    Optional.ofNullable(borderColorButton.getBackground())
                            .ifPresent(color -> borders.setBorderColor(String.valueOf(color.getRGB())));

                    getBorderWidthValue()
                            .ifPresent(width -> borders.setBorderWidth(String.valueOf(Math.round(width))));
                });
    }

    private Optional<Double> getBorderWidthValue() {
        if (borderWidthBox.getText().equals("mixed")) {
            return Optional.empty();
        }

        final String widthText = borderWidthBox.getText().replace("cm", "");
        return Optional.ofNullable(Doubles.tryParse(widthText))
                .map(customWidth -> {
                    borderWidthBox.setText(customWidth + " cm");
                    return customWidth * UNITS;
                });
    }

    public void setProperties(final Set<IWidget> widgets) {
        this.widgets = widgets;

        final String borderStyleToUse = getBorderStyle(widgets);
        applyBorderStyle(borderStyleToUse);

        final String borderWidthToUse = getBorderWidth(widgets);
        applyBorderWidth(borderWidthToUse);

        final String borderColorToUse = getBorderColor(widgets);
        setButtonBackgroundColor(borderColorToUse, borderColorButton);

        final String backgroundStyleToUse = getBackgroundStyle(widgets);
        //todo: apply background style

        final String backgroundColorToUse = getBackgroundColor(widgets);
        setComboBoxValue(backgroundColorToUse, backgroundFillBox);
        setButtonBackgroundColor(backgroundColorToUse, backgroundFillColorButton);
    }

    private void applyBorderStyle(final String borderStyle) {
        setComboBoxValue(borderStyle, borderStyleBox);

        final boolean borderEnabled = !"None".equals(borderStyle);
        borderWidthBox.setEnabled(borderEnabled);
        borderColorButton.setEnabled(borderEnabled);
    }

    private void applyBorderWidth(final String borderWidth) {
        final double borderWidthAsDouble = Integer.parseInt(borderWidth);
        final double convertedBorderWidth = round(borderWidthAsDouble / UNITS);
        borderWidthBox.setText(convertedBorderWidth + " cm");
    }

    private String getBorderStyle(final Set<IWidget> widgets) {
        final List<String> borderStyles = widgets.stream()
                .map(widget -> widget.getWidgetModel().getProperties().getBorder())
                .filter(Optional::isPresent)
                .map(Optional::get)
                .map(BorderProperties::getBorders)
                .map(borders -> borders.getBorderStyle().orElse(""))
                .collect(toUnmodifiableList());

        return findCommonOrMixedValue(borderStyles);
    }

    private String getBorderWidth(final Set<IWidget> widgets) {
        final List<String> borderWidth = widgets.stream()
                .map(widget -> widget.getWidgetModel().getProperties().getBorder())
                .filter(Optional::isPresent)
                .map(Optional::get)
                .map(BorderProperties::getBorders)
                .map(borders -> borders.getBorderWidth().orElse(""))
                .collect(toUnmodifiableList());

        return findCommonOrMixedValue(borderWidth);
    }

    private String getBorderColor(final Set<IWidget> widgets) {
        final List<String> borderColors = widgets.stream()
                .map(widget -> widget.getWidgetModel().getProperties().getBorder())
                .filter(Optional::isPresent)
                .map(Optional::get)
                .map(BorderProperties::getBorders)
                .map(borders -> borders.getBorderColor().orElse(""))
                .collect(toUnmodifiableList());

        return findCommonOrMixedValue(borderColors);
    }

    private String getBackgroundStyle(final Set<IWidget> widgets) {
        final List<String> styles = widgets.stream()
                .map(widget -> widget.getWidgetModel().getProperties().getBorder())
                .filter(Optional::isPresent)
                .map(Optional::get)
                .map(BorderProperties::getBackgroundFill)
                .map(backgroundFill -> backgroundFill.getStyle().orElse(""))
                .collect(toUnmodifiableList());

        return findCommonOrMixedValue(styles);
    }

    private String getBackgroundColor(final Set<IWidget> widgets) {
        final List<String> backgroundColors = widgets.stream()
                .map(widget -> widget.getWidgetModel().getProperties().getBorder())
                .filter(Optional::isPresent)
                .map(Optional::get)
                .map(BorderProperties::getBackgroundFill)
                .map(backgroundFill -> backgroundFill.getFillColor().orElse(""))
                .collect(toUnmodifiableList());

        return findCommonOrMixedValue(backgroundColors);
    }

    private void setButtonBackgroundColor(
            final String color,
            final JButton button) {
        if (color.equals("mixed")) {
            final BufferedImage bufferedImage = new BufferedImage(button.getWidth(),
                    button.getHeight(),
                    BufferedImage.TYPE_INT_ARGB);
            final Graphics2D g2 = (Graphics2D) bufferedImage.getGraphics();
            g2.setColor(Color.red);
            g2.drawLine(-1, 0, bufferedImage.getWidth() - 1, bufferedImage.getHeight());
            g2.drawLine(-1, bufferedImage.getHeight() - 1, bufferedImage.getWidth() - 1, -1);

            button.setContentAreaFilled(true);
            button.setBackground(null);
            button.setIcon(new ImageIcon(bufferedImage));
        } else {
            button.setIcon(null);
            button.setContentAreaFilled(false);
            button.setOpaque(true);
            button.setBackground(new Color(Integer.parseInt(color)));
        }
    }

    private void setComboBoxValue(
            final String value,
            final JComboBox<String> comboBox) {
        final ActionListener[] listeners = comboBox.getActionListeners();
        Arrays.stream(listeners).forEach(comboBox::removeActionListener);

        final String comboboxValue = convertMixedValueForComboBoxSelection(value);
        comboBox.setSelectedItem(comboboxValue);

        Arrays.stream(listeners).forEach(comboBox::addActionListener);
    }

    private String convertMixedValueForComboBoxSelection(final String value) {
        if ("mixed".equals(value)) {
            return null;
        }
        return value;
    }

    private String findCommonOrMixedValue(final List<String> values) {
        final int numberOfIdenticalItems = Collections.frequency(values, values.get(0));
        final boolean listContainsOnlyEqualValues = numberOfIdenticalItems == values.size();
        if (listContainsOnlyEqualValues) {
            return values.get(0);
        }
        return "mixed";
    }

    private double round(final double number) {
        final double exponential = Math.pow(10, 3);

        double value = number;
        value *= exponential;
        value = Math.round(value);
        value /= exponential;

        return value;
    }
}