Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.haulmont.cuba.desktop.gui.components; import com.haulmont.chile.core.datatypes.Datatypes; import com.haulmont.chile.core.model.MetaProperty; import com.haulmont.chile.core.model.MetaPropertyPath; import com.haulmont.chile.core.model.utils.InstanceUtils; import com.haulmont.cuba.core.global.AppBeans; import com.haulmont.cuba.core.global.Messages; import com.haulmont.cuba.core.global.UserSessionSource; import com.haulmont.cuba.desktop.gui.executors.impl.DesktopBackgroundWorker; import com.haulmont.cuba.desktop.sys.DesktopToolTipManager; import com.haulmont.cuba.desktop.sys.vcl.Flushable; import com.haulmont.cuba.gui.components.DateField; import com.haulmont.cuba.gui.components.TimeField; import com.haulmont.cuba.gui.data.Datasource; import com.haulmont.cuba.gui.data.impl.WeakItemChangeListener; import com.haulmont.cuba.gui.data.impl.WeakItemPropertyChangeListener; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import javax.swing.text.MaskFormatter; import java.awt.*; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.sql.Time; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Objects; public class DesktopTimeField extends DesktopAbstractField<JFormattedTextField> implements TimeField { private final Logger log = LoggerFactory.getLogger(DesktopTimeField.class); private boolean showSeconds; private String timeFormat; private String mask; private MaskFormatter formatter; private boolean updatingInstance; private Datasource datasource; private Object prevValue; private DateField.Resolution resolution; protected static final int DEFAULT_DIGIT_WIDTH = 23; private int digitWidth; protected Datasource.ItemChangeListener itemChangeListener; protected Datasource.ItemPropertyChangeListener itemPropertyChangeListener; public DesktopTimeField() { UserSessionSource uss = AppBeans.get(UserSessionSource.NAME); digitWidth = getDigitWidth(); timeFormat = Datatypes.getFormatStringsNN(uss.getLocale()).getTimeFormat(); resolution = DateField.Resolution.MIN; formatter = new MaskFormatter(); formatter.setPlaceholderCharacter('_'); impl = new FlushableFormattedTextField(formatter); FieldListener listener = new FieldListener(); impl.addFocusListener(listener); impl.addKeyListener(listener); setShowSeconds(timeFormat.contains("ss")); } @Override public boolean getShowSeconds() { return showSeconds; } private Object validateRawValue(String value) throws NumberFormatException, ParseException { if (value.equals(StringUtils.replaceChars(mask, "#U", "__"))) { return null; } SimpleDateFormat sdf = new SimpleDateFormat(timeFormat); sdf.setLenient(false); try { Date parsedValue = sdf.parse(value); Class targetType = null; if (datasource != null && metaPropertyPath != null) { targetType = metaPropertyPath.getRangeJavaClass(); } if (targetType == java.sql.Time.class) { return new Time(parsedValue.getTime()); } if (targetType == java.sql.Date.class) { log.warn("Do not use java.sql.Date with time field"); return new java.sql.Date(parsedValue.getTime()); } return parsedValue; } catch (ParseException e) { showValidationMessage(); return prevValue; } } private void showValidationMessage() { Messages messages = AppBeans.get(Messages.NAME); DesktopComponentsHelper.getTopLevelFrame(this).showNotification(messages.getMainMessage("validationFail"), com.haulmont.cuba.gui.components.Frame.NotificationType.TRAY); } public void setResolution(DateField.Resolution resolution) { this.resolution = resolution; if (resolution.ordinal() <= DateField.Resolution.SEC.ordinal()) { setShowSeconds(true); } else if (resolution.ordinal() <= DateField.Resolution.MIN.ordinal()) { setShowSeconds(false); } else if (resolution.ordinal() <= DateField.Resolution.HOUR.ordinal()) { StringBuilder builder = new StringBuilder(timeFormat); if (timeFormat.contains("mm")) { int minutesIndex = builder.indexOf("mm"); builder.delete(minutesIndex > 0 ? --minutesIndex : minutesIndex, minutesIndex + 3); timeFormat = builder.toString(); } setShowSeconds(false); } } public DateField.Resolution getResolution() { return resolution; } @Override public void setShowSeconds(boolean showSeconds) { this.showSeconds = showSeconds; if (showSeconds) { if (!timeFormat.contains("ss")) { int minutesIndex = timeFormat.indexOf("mm"); StringBuilder builder = new StringBuilder(timeFormat); builder.insert(minutesIndex + 2, ":ss"); timeFormat = builder.toString(); } } else { if (timeFormat.contains("ss")) { int secondsIndex = timeFormat.indexOf("ss"); StringBuilder builder = new StringBuilder(timeFormat); builder.delete(secondsIndex > 0 ? --secondsIndex : secondsIndex, secondsIndex + 3); timeFormat = builder.toString(); } } updateTimeFormat(); updateWidth(); } private void updateTimeFormat() { mask = StringUtils.replaceChars(timeFormat, "Hhmsa", "####U"); try { formatter.setMask(mask); } catch (ParseException e) { throw new RuntimeException(e); } impl.setValue(impl.getValue()); } @Override public Datasource getDatasource() { return datasource; } @Override public MetaProperty getMetaProperty() { return metaProperty; } @Override public MetaPropertyPath getMetaPropertyPath() { return metaPropertyPath; } @Override public void setDatasource(Datasource datasource, String property) { this.datasource = datasource; if (datasource == null) { setValue(null); return; } resolveMetaPropertyPath(datasource.getMetaClass(), property); itemChangeListener = e -> { if (updatingInstance) { return; } Date value = InstanceUtils.getValueEx(e.getItem(), metaPropertyPath.getPath()); updateComponent(value); fireChangeListeners(value); }; //noinspection unchecked datasource.addItemChangeListener(new WeakItemChangeListener(datasource, itemChangeListener)); itemPropertyChangeListener = e -> { if (updatingInstance) { return; } if (e.getProperty().equals(metaPropertyPath.toString())) { updateComponent(e.getValue()); fireChangeListeners(e.getValue()); } }; //noinspection unchecked; datasource.addItemPropertyChangeListener( new WeakItemPropertyChangeListener(datasource, itemPropertyChangeListener)); if (datasource.getState() == Datasource.State.VALID && datasource.getItem() != null) { if (property.equals(metaPropertyPath.toString())) { Date value = InstanceUtils.getValueEx(datasource.getItem(), metaPropertyPath.getPath()); updateComponent(value); fireChangeListeners(value); } } initRequired(metaPropertyPath); if (metaProperty.isReadOnly()) { setEditable(false); } initBeanValidator(); } @Override public void setFormat(String timeFormat) { this.timeFormat = timeFormat; showSeconds = timeFormat.contains("ss"); updateTimeFormat(); updateWidth(); } @Override public String getFormat() { return timeFormat; } @Override protected void setCaptionToComponent(String caption) { super.setCaptionToComponent(caption); requestContainerUpdate(); } @Override public String getDescription() { return impl.getToolTipText(); } @Override public void setDescription(String description) { if (!Objects.equals(this.getDescription(), description)) { impl.setToolTipText(description); DesktopToolTipManager.getInstance().registerTooltip(impl); requestContainerUpdate(); } } @SuppressWarnings("unchecked") @Override public Date getValue() { try { return new SimpleDateFormat(timeFormat).parse(impl.getText()); } catch (ParseException e) { return null; } } @Override public void setValue(Object value) { DesktopBackgroundWorker.checkSwingUIAccess(); if (!Objects.equals(prevValue, value)) { updateInstance(value); updateComponent(value); fireChangeListeners(value); } } protected void setValueInternal(Object value) { boolean editable = isEditable(); setEditable(true); setValue(value); setEditable(editable); } protected void updateComponent(Object value) { updatingInstance = true; if (value == null) { impl.setValue(""); updatingInstance = false; } else { try { if (value instanceof Date) { SimpleDateFormat sdf = new SimpleDateFormat(timeFormat); impl.setValue(sdf.format(value)); } } finally { updatingInstance = false; } } updateMissingValueState(); } protected void updateInstance(Object value) { if (updatingInstance) return; if (Objects.equals(prevValue, value)) return; updatingInstance = true; try { if (datasource != null && metaProperty != null && datasource.getItem() != null) InstanceUtils.setValueEx(datasource.getItem(), metaPropertyPath.getPath(), value); } finally { updatingInstance = false; } } protected void updateWidth() { int width = isAmPmUsed() ? digitWidth : 0; if (showSeconds) { width = width + digitWidth; } int height = impl.getPreferredSize().height; switch (resolution) { case HOUR: impl.setMinimumSize(new Dimension(digitWidth + width, height)); break; case MIN: case SEC: impl.setMinimumSize(new Dimension(digitWidth * 2 + width, height)); } } protected void fireChangeListeners(Object newValue) { Object oldValue = prevValue; prevValue = newValue; if (!Objects.equals(oldValue, newValue)) { fireValueChanged(oldValue, newValue); } } @Override protected void setEditableToComponent(boolean editable) { impl.setEditable(editable); updateMissingValueState(); } @Override public void updateMissingValueState() { Object implValue = impl.getValue(); boolean value = required && isEditableWithParent() && (implValue == null || implValue instanceof String && StringUtils.isBlank((String) implValue)); decorateMissingValue(impl, value); } public boolean isAmPmUsed() { return timeFormat.contains("a"); } @Override public void commit() { // do nothing } @Override public void discard() { // do nothing } @Override public boolean isBuffered() { // do nothing return false; } @Override public void setBuffered(boolean buffered) { // do nothing } @Override public boolean isModified() { // do nothing return false; } protected class FieldListener implements FocusListener, KeyListener { private static final int ENTER_CODE = 10; @Override public void focusGained(FocusEvent e) { } @Override public void focusLost(FocusEvent e) { fireEvent(); } @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { if (ENTER_CODE == e.getKeyCode()) fireEvent(); } @Override public void keyReleased(KeyEvent e) { } private void fireEvent() { flush(); } } protected void flush() { if (isEditable() && isEnabled()) { Object newValue; try { newValue = validateRawValue(getImpl().getText()); } catch (Exception e) { showValidationMessage(); newValue = prevValue; } if ("".equals(newValue)) newValue = null; if (!Objects.equals(prevValue, newValue)) setValue(newValue); else updateComponent(newValue); } } protected class FlushableFormattedTextField extends JFormattedTextField implements Flushable { public FlushableFormattedTextField(MaskFormatter formatter) { super(formatter); } @Override public void flushValue() { flush(); } } protected int getDigitWidth() { UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults(); if (lafDefaults.getDimension("TimeField.digitWidth") != null) { // take it from desktop theme return (int) lafDefaults.getDimension("TimeField.digitWidth").getWidth(); } return DEFAULT_DIGIT_WIDTH; } }