1

I have a JSpinner with a SpinnerDateModel of "HH:mm" format. I want the user to be (for example) able to copy a date in "yyyy-MM-dd HH:mm:ss.SSS" from a table (or any other source) and paste it into the JSpinner - the HH:mm part only. Such full date string is normally invalid for the component but I still want to try the pasted string and get the desired info from from it (if it's there)... I thought that my validation method should look something like below but I don't know how to change the paste() behaviour so that I can add the validation and change of the pasted text...

        private String validateAndReturnCorrected(String pastedText) {

            DateFormat hoursMinutesFormat = new SimpleDateFormat("HH:mm");
            try {
                // trying to paste a full date string?
                DateFormat fullDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                Date date = fullDateFormat.parse(pastedText);
                return hoursMinutesFormat.format(date);
            } catch (ParseException ex) {
            }
            // trying to paste hour and minutes?
            try {
                Date date = hoursMinutesFormat.parse(pastedText);
                return hoursMinutesFormat.format(date);
            } catch (ParseException ex1) {
            }
            // trying to paste date in HH:mm:ss format?
            try {
                DateFormat hoursMinutesSecondsFormat = new SimpleDateFormat("HH:mm:ss");
                Date date = hoursMinutesSecondsFormat.parse(pastedText);
                return hoursMinutesSecondsFormat.format(date);
            } catch (ParseException ex2) {
            }
            // trying to paste date in HH:mm:ss.SSS format?
            try {
                DateFormat hoursMinutesSecondsMilisecondsFormat = new SimpleDateFormat("HH:mm:ss.SSS");
                Date date = hoursMinutesSecondsMilisecondsFormat.parse(pastedText);
                return hoursMinutesFormat.format(date);
            } catch (ParseException ex3) {
            }

            // unable to correct the string...
            return "";

        }

UPDATE

Changing the googled question I found the following two sites which led me to get the problem solved:

So the solution looks something like this:

class ProxyAction extends TextAction implements ClipboardOwner {

    private TextAction action;

    public ProxyAction(TextAction action) {
        super(action.toString());
        this.action = action;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        String cbc=getClipboardContents();
        setClipboardContents(validateAndReturnCorrected(cbc));
        action.actionPerformed(e);
        setClipboardContents(cbc);
        System.out.println("Paste Occured...............................................................");
    }

// here goes the validateAndReturnCorrected method

    public String getClipboardContents() {
        String result = "";
        try {
            result = (String) Toolkit.getDefaultToolkit().getSystemClipboard().getData(DataFlavor.stringFlavor);
        } catch (UnsupportedFlavorException | IOException ex) {
            ex.printStackTrace();
        }
        return result;
    }

    public void setClipboardContents(String aString) {
        StringSelection stringSelection = new StringSelection(aString);
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(stringSelection, this);
    }

    @Override
    public void lostOwnership(Clipboard clipboard, Transferable contents) {
    }
}
Community
  • 1
  • 1
ssr
  • 93
  • 6
  • Here's a question that might help you: http://stackoverflow.com/questions/3707485/how-to-convert-string-to-date-without-knowing-the-format Is it an option to let the user paste a date in a textfield and let the user modify the input if needed? – Bart Burg Dec 08 '15 at 08:45
  • No, it should try to correct the pasted string automagically... I think I need a way to override my jSpinner editor's paste() method or something similar... The question you linked deals with the problem of getting a Date from String but I think I'm doing that ok in the posted validation method. – ssr Dec 08 '15 at 09:14

2 Answers2

2

I want the user to be (for example) able to copy a date in "yyyy-MM-dd HH:mm:ss.SSS" from a table (or any other source) and paste it into the JSpinner - the HH:mm part only.

  • this simple thing is implemented in JSpinners Xxx(Spinner)Model and depends of - if is SimpleDateFormat added to JSpinner

  • validation of the input (SpinnerEditor), is just a JFormattedTextField by default (for more info to read JFormattedTextFields configurations and InputVerifier)

  • for example (the basics and standards, without to override or set something special), date is changed from 08.december to 10. december and hour from 10 a.m. to 7 a.m.

.

enter image description here

.

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import javax.swing.AbstractSpinnerModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerDateModel;
import javax.swing.SpinnerListModel;
import javax.swing.SpinnerNumberModel;

public class JSpinnerTest {

    public static final int DEFAULT_WIDTH = 400;
    public static final int DEFAULT_HEIGHT = 250;
    private JFrame frame = new JFrame();
    private JPanel mainPanel = new JPanel(new GridLayout(0, 3, 10, 10));
    private JButton okButton = new JButton("Ok");
    private JPanel buttonPanel = new JPanel();

    public JSpinnerTest() {
        buttonPanel.add(okButton);
        mainPanel = new JPanel();
        mainPanel.setLayout(new GridLayout(0, 3, 10, 10));

        JSpinner defaultSpinner = new JSpinner();
        addRow("Default", defaultSpinner);
        JSpinner boundedSpinner = new JSpinner(new SpinnerNumberModel(5, 0, 10, 0.5));
        addRow("Bounded", boundedSpinner);
        String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment()
                .getAvailableFontFamilyNames();
        JSpinner listSpinner = new JSpinner(new SpinnerListModel(fonts));
        addRow("List", listSpinner);
        JSpinner reverseListSpinner = new JSpinner(new SpinnerListModel(fonts) {
            private static final long serialVersionUID = 1L;

            @Override
            public Object getNextValue() {
                return super.getPreviousValue();
            }

            @Override
            public Object getPreviousValue() {
                return super.getNextValue();
            }
        });
        addRow("Reverse List", reverseListSpinner);
        JSpinner dateSpinner = new JSpinner(new SpinnerDateModel());
        addRow("Date", dateSpinner);
        JSpinner betterDateSpinner = new JSpinner(new SpinnerDateModel());
        String pattern = ((SimpleDateFormat) DateFormat.getDateInstance()).toPattern();
        betterDateSpinner.setEditor(new JSpinner.DateEditor(betterDateSpinner, pattern));
        addRow("Better Date", betterDateSpinner);
        JSpinner timeSpinner = new JSpinner(new SpinnerDateModel());
        pattern = ((SimpleDateFormat) DateFormat.getTimeInstance(DateFormat.SHORT)).toPattern();
        timeSpinner.setEditor(new JSpinner.DateEditor(timeSpinner, pattern));
        addRow("Time", timeSpinner);
        JSpinner permSpinner = new JSpinner(new PermutationSpinnerModel("meat"));
        addRow("Word permutations", permSpinner);
        frame.setTitle("SpinnerTest");
        frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
        frame.add(buttonPanel, BorderLayout.SOUTH);
        frame.add(mainPanel, BorderLayout.CENTER);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

    private void addRow(String labelText, final JSpinner spinner) {
        mainPanel.add(new JLabel(labelText));
        mainPanel.add(spinner);
        final JLabel valueLabel = new JLabel();
        mainPanel.add(valueLabel);
        okButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                Object value = spinner.getValue();
                valueLabel.setText(value.toString());
            }
        });
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JSpinnerTest frame = new JSpinnerTest();

            }
        });
    }
}

class PermutationSpinnerModel extends AbstractSpinnerModel {

    private static final long serialVersionUID = 1L;

    /**
     * Constructs the model.
     *
     * @param w the word to permute
     */
    public PermutationSpinnerModel(String w) {
        word = w;
    }

    @Override
    public Object getValue() {
        return word;
    }

    @Override
    public void setValue(Object value) {
        if (!(value instanceof String)) {
            throw new IllegalArgumentException();
        }
        word = (String) value;
        fireStateChanged();
    }

    @Override
    public Object getNextValue() {
        int[] codePoints = toCodePointArray(word);
        for (int i = codePoints.length - 1; i > 0; i--) {
            if (codePoints[i - 1] < codePoints[i]) {
                int j = codePoints.length - 1;
                while (codePoints[i - 1] > codePoints[j]) {
                    j--;
                }
                swap(codePoints, i - 1, j);
                reverse(codePoints, i, codePoints.length - 1);
                return new String(codePoints, 0, codePoints.length);
            }
        }
        reverse(codePoints, 0, codePoints.length - 1);
        return new String(codePoints, 0, codePoints.length);
    }

    @Override
    public Object getPreviousValue() {
        int[] codePoints = toCodePointArray(word);
        for (int i = codePoints.length - 1; i > 0; i--) {
            if (codePoints[i - 1] > codePoints[i]) {
                int j = codePoints.length - 1;
                while (codePoints[i - 1] < codePoints[j]) {
                    j--;
                }
                swap(codePoints, i - 1, j);
                reverse(codePoints, i, codePoints.length - 1);
                return new String(codePoints, 0, codePoints.length);
            }
        }
        reverse(codePoints, 0, codePoints.length - 1);
        return new String(codePoints, 0, codePoints.length);
    }

    private static int[] toCodePointArray(String str) {
        int[] codePoints = new int[str.codePointCount(0, str.length())];
        for (int i = 0, j = 0; i < str.length(); i++, j++) {
            int cp = str.codePointAt(i);
            if (Character.isSupplementaryCodePoint(cp)) {
                i++;
            }
            codePoints[j] = cp;
        }
        return codePoints;
    }

    private static void swap(int[] a, int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    private static void reverse(int[] a, int i, int j) {
        while (i < j) {
            swap(a, i, j);
            i++;
            j--;
        }
    }
    private String word;
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • I can't seem to be able to copy the string form "Date" spinner and paste it into "Better Date" or "Time" so that they will "get" the parts they need from the clipboard value... Or am I doing something wrong? Is there a way to change what's being pasted? – ssr Dec 08 '15 at 10:20
  • I just realized that you misunderstood me: I want to be able to copy the entire date and paste the entire date but that the pasted string will be modified at the moment of pasting to only include the valid data (in my case it should strip the pasted full date to only the hours and minutes part - I don't want the user to have to do it manually; an "intelligent" pasting of dates). – ssr Dec 08 '15 at 12:47
  • @ssr 1. for better help sooner post an SSCCE / MCVE short, runnable, compilable, 2. basics workaround (wrong and schrinked value) is described (date is changed from 08.december to 10. december and hour from 10 a.m. to 7 a.m.) in my answer, there is a few ways, but (don't want to guessing if you missing hours, minutes or hour and minutes and whats real value in the model and formatted output in the screen) – mKorbel Dec 08 '15 at 13:41
  • at 1. the only SSCCE that I could post at the moment would be a panel with spinners and no real additional functionality, that's why I just posted the validation method. What I need is a way to call my validation method on the text to be pasted (as a reaction to ctrl+v or choosing "paste" from menu) so that the corrected string will be pasted in the spinner instead of the original clipboard stored value (the clipboard shouldn't change). The only idea i have is to override the paste() method of spinner's editor but can I do it? Unfortunately I don't understand your second point... – ssr Dec 10 '15 at 13:02
  • For example: I copied the full date 2015-12-10 14:12:30.125 into the clipboard and I try to paste it into a "HH:mm" formatted spinner. The value in the spinner should become 14:12, the contents of the clipboard unchanged. The "stripping" is made in the validation method but I don't know how to hook it to the paste operation. – ssr Dec 10 '15 at 13:17
0

I think you should be able to this in 2 steps.

  1. First you need to attach an ChangeListener on the JSpinner.
  2. Then inside the stateChanged method you do your validation and apply the changes to the value.
imps
  • 1,423
  • 16
  • 22
  • I've tried that but I couldn't make the listener distinguish a paste from a normal edit made by typing. – ssr Dec 08 '15 at 09:07
  • Just tried it once more to see what exactly was the problem with it and the stateChanged method fires only after the edit is successfully commited. I could do setCommitsOnValidEdit(true) for the formatter but that also wouldn't make it work how I want - it would just make it fire at every character typed etc.... What I want is to modify the pasted string at the time of pasting it into the textfield. For example my clipboard has a date in a full format but my code would change the string on the fly to "HH:mm" format and paste that into the field or similar. – ssr Dec 08 '15 at 09:54