/*
*
* * Copyright (c) 2016. David Sowerby
* *
* * 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 uk.q3c.krail.core.option;
import com.google.inject.Inject;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.DefaultConverterFactory;
import com.vaadin.server.Sizeable;
import com.vaadin.ui.*;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.q3c.krail.core.i18n.I18NKey;
import uk.q3c.krail.core.i18n.LabelKey;
import uk.q3c.krail.core.i18n.Translate;
import uk.q3c.krail.core.ui.DataTypeToUI;
import uk.q3c.util.ID;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.Optional;
/**
* Default implementation for {@link OptionPopup}
* <p>
* Created by David Sowerby on 29/05/15.
*/
@SuppressFBWarnings("URV_CHANGE_RETURN_TYPE") // not clear what this is trying to indicate
public class DefaultOptionPopup implements OptionPopup {
private static Logger log = LoggerFactory.getLogger(DefaultOptionPopup.class);
private OptionContext activeContext;
private Map<OptionKey, Class<?>> contextKeys;
private DataTypeToUI dataTypeToUI;
private Translate translate;
private OptionKeyLocator optionKeyLocator;
private Window window;
private DefaultConverterFactory converterFactory;
@Inject
public DefaultOptionPopup(DataTypeToUI dataTypeToUI, Translate translate, OptionKeyLocator optionKeyLocator) {
this.dataTypeToUI = dataTypeToUI;
this.translate = translate;
this.optionKeyLocator = optionKeyLocator;
converterFactory = new DefaultConverterFactory();
}
/**
* The context is scanned for {@link OptionKey} instances. If none are found a message is displayed saying there are no options. A context is loaded
* only once - the {@link OptionKey} instances are cached. {@link #dataTypeToUI} is used to identify the user interface component to use for the option
* data type. The {@link #converterFactory} provides converters to enable conversion to and from the type needed for presentation (usually String). <p>
* Options are displayed in a grid of 2 columns, the first column containing a Vaadin component to display the option value and the second a button to
* reset the value to default. The component and button are each wrapped in a FormLayout to position the caption to the left of the value<p>
* A value change listener is attached to the Vaadin component to change the option value in response to the user changing the value ion the component.
*
* @param context the context to take the options from
* @param windowCaption the I18NKey to provide a window caption
*/
@Override
public void popup(@Nonnull OptionContext context, I18NKey windowCaption) {
// changing context, so we need to re-read the context fields
if (context != activeContext) {
contextKeys = contextKeys(context);
}
Option option = context.getOption();
if (window != null) {
window.close();
}
window = new Window();
window.setCaption(windowCaption(windowCaption));
int rows = contextKeys.size() > 0 ? contextKeys.size() : 1;
GridLayout baseLayout = new GridLayout(2, rows);
baseLayout.setSizeUndefined();
if (contextKeys.isEmpty()) {
Label label = new Label(translate.from(LabelKey.No_Options_to_Show));
baseLayout.addComponent(label, 0, 0);
} else {
calculateWindowSize(window);
int row = 0;
for (OptionKey key : contextKeys.keySet()) {
Object value = option.get(key);
AbstractField uiField = dataTypeToUI.componentFor(value);
uiField.setCaption(translate.from(key.getKey()));
uiField.setDescription(translate.from(key.getDescriptionKey()));
Optional<String> optionKeyName = Optional.of(((Enum) key.getKey()).name());
uiField.setId(ID.getId(optionKeyName, this, uiField));
log.debug("Component id for '{}' set to: '{}'", uiField.getCaption(), uiField.getId());
setFieldValue(uiField, value);
uiField.addValueChangeListener(event -> {
option.set(key, uiField.getValue());
context.optionValueChanged(event);
});
Button defaultsButton = new Button(translate.from(LabelKey.Reset_to_Default));
defaultsButton.setId(ID.getId(optionKeyName, this, defaultsButton));
defaultsButton.addClickListener((event -> {
option.delete(key, 0);
//we create an event to represent the field which whose value will be affected by this change
AbstractField.ValueChangeEvent changeEvent = new AbstractField.ValueChangeEvent(uiField);
context.optionValueChanged(changeEvent);
//update the value of the field - it may have changed
setFieldValue(uiField, option.get(key));
}));
baseLayout.addComponent(new FormLayout(uiField), 0, row);
baseLayout.addComponent(new FormLayout(defaultsButton), 1, row);
row++;
}
}
window.setId(ID.getId(Optional.empty(), context, this, window));
window.setClosable(true);
//use panel to scroll
window.setContent(new Panel(baseLayout));
window.center();
UI.getCurrent()
.addWindow(window);
this.activeContext = context;
}
/**
* Checks to see whether data type conversion is needed - if so, converter is created and assigned to the field. If not, the value is assigned directly
* to the uiField
*
* @param uiField the field to display the value
* @param value the value
*/
@SuppressWarnings("unchecked")
protected void setFieldValue(AbstractField uiField, Object value) {
if (uiField.getType()
.isAssignableFrom(value.getClass())) {
uiField.setValue(value);
} else {
//needs conversion
Converter<String, ?> converter = converterFactory.createConverter(String.class, value.getClass());
//noinspection unchecked
uiField.setConverter(converter);
uiField.setConvertedValue(value);
}
}
private void calculateWindowSize(Sizeable window) {
window.setSizeUndefined();
window.setHeight("600px");
}
protected String windowCaption(I18NKey i18NKey) {
return translate.from(i18NKey);
}
/**
* {@inheritDoc}
*/
@Nonnull
@Override
public Map<OptionKey, Class<?>> contextKeys(OptionContext context) {
return optionKeyLocator.contextKeyMap(context);
}
public Window getWindow() {
return window;
}
}