/**
*
* Copyright
* 2009-2015 Jayway Products AB
* 2016-2017 Föreningen Sambruk
*
* Licensed under AGPL, Version 3.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.gnu.org/licenses/agpl.txt
*
* 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 se.streamsource.streamflow.client.ui.workspace.cases.general;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.swing.EventComboBoxModel;
import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import com.jgoodies.forms.layout.Sizes;
import org.jdesktop.application.Action;
import org.jdesktop.application.ApplicationContext;
import org.jdesktop.application.Task;
import org.jdesktop.swingx.JXDatePicker;
import org.jdesktop.swingx.calendar.DatePickerFormatter;
import org.jdesktop.swingx.color.ColorUtil;
import org.qi4j.api.constraint.ConstraintViolationException;
import org.qi4j.api.injection.scope.Service;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.injection.scope.Uses;
import org.qi4j.api.object.ObjectBuilderFactory;
import org.qi4j.api.property.Property;
import org.qi4j.api.structure.Module;
import org.qi4j.library.constraints.annotation.MaxLength;
import se.streamsource.dci.value.link.LinkValue;
import se.streamsource.streamflow.api.administration.priority.PriorityValue;
import se.streamsource.streamflow.client.MacOsUIWrapper;
import se.streamsource.streamflow.client.StreamflowResources;
import se.streamsource.streamflow.client.ui.workspace.WorkspaceResources;
import se.streamsource.streamflow.client.ui.workspace.cases.caselog.CaseLogView;
import se.streamsource.streamflow.client.ui.workspace.cases.general.forms.PossibleFormsView;
import se.streamsource.streamflow.client.ui.workspace.cases.note.CaseNoteView;
import se.streamsource.streamflow.client.util.ActionBinder;
import se.streamsource.streamflow.client.util.CommandTask;
import se.streamsource.streamflow.client.util.RefreshComponents;
import se.streamsource.streamflow.client.util.RefreshWhenShowing;
import se.streamsource.streamflow.client.util.Refreshable;
import se.streamsource.streamflow.client.util.StreamflowButton;
import se.streamsource.streamflow.client.util.UncaughtExceptionHandler;
import se.streamsource.streamflow.client.util.ValueBinder;
import se.streamsource.streamflow.client.util.dialog.DialogService;
import se.streamsource.streamflow.client.util.dialog.SelectLinkDialog;
import se.streamsource.streamflow.client.util.i18n;
import se.streamsource.streamflow.infrastructure.event.domain.TransactionDomainEvents;
import se.streamsource.streamflow.infrastructure.event.domain.source.TransactionListener;
import se.streamsource.streamflow.util.Strings;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.SwingConstants;
import javax.swing.text.DefaultFormatterFactory;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.geom.Ellipse2D;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import static java.awt.RenderingHints.*;
import static java.lang.Integer.*;
import static se.streamsource.streamflow.client.util.BindingFormBuilder.Fields.*;
import static se.streamsource.streamflow.client.util.i18n.*;
import static se.streamsource.streamflow.infrastructure.event.domain.source.helper.Events.*;
/**
* JAVADOC
*/
public class CaseGeneralView extends JScrollPane implements TransactionListener, Refreshable
{
@Service
private DialogService dialogs;
@Service
private UncaughtExceptionHandler exception;
@Structure
Module module;
private ActionBinder actionBinder;
private ValueBinder valueBinder;
private CaseGeneralModel model;
private JTextField descriptionField;
private JXDatePicker dueOnField;
private JPanel leftPane;
private CaseLabelsView labels;
private PossibleFormsView forms;
private RemovableLabel selectedCaseType = new RemovableLabel();
private StreamflowButton caseTypeButton;
private StreamflowButton labelButton;
private final ApplicationContext appContext;
private CaseNoteView caseNotes;
private JComboBox casePriority;
public CaseGeneralView(@Service ApplicationContext appContext, @Uses CaseGeneralModel generalModel,
@Uses CaseLogView caseLogView, @Structure Module module)
{
this.appContext = appContext;
this.model = generalModel;
RefreshComponents refreshComponents = new RefreshComponents();
model.addObserver( refreshComponents );
ObjectBuilderFactory obf = module.objectBuilderFactory();
this.labels = obf.newObjectBuilder( CaseLabelsView.class ).use( generalModel.newLabelsModel() ).newInstance();
this.caseNotes = obf.newObjectBuilder( CaseNoteView.class ).use( generalModel.newCaseNoteModel() ).newInstance();
RefreshComponents refreshLabelComponents = new RefreshComponents();
labels.getModel().addObserver( refreshLabelComponents );
this.forms = obf.newObjectBuilder( PossibleFormsView.class ).use( generalModel.newPossibleFormsModel() )
.newInstance();
refreshComponents.visibleOn( "changedescription", forms );
this.setBorder( BorderFactory.createEmptyBorder() );
getVerticalScrollBar().setUnitIncrement( 30 );
setActionMap( appContext.getActionMap( this ) );
ActionMap am = getActionMap();
MacOsUIWrapper.convertAccelerators( appContext.getActionMap( CaseGeneralView.class, this ) );
actionBinder = obf.newObjectBuilder( ActionBinder.class ).use( am ).newInstance();
valueBinder = obf.newObject( ValueBinder.class );
actionBinder.setResourceMap( appContext.getResourceMap( getClass() ) );
// Layout and form for the right panel
FormLayout leftLayout = new FormLayout( "70dlu, 2dlu, 200:grow, 70dlu",
"pref, pref, pref, pref, 20dlu, pref, pref, pref, pref" );
leftPane = new JPanel( leftLayout );
leftPane.setFocusable( false );
DefaultFormBuilder leftBuilder = new DefaultFormBuilder( leftLayout, leftPane );
leftBuilder.setBorder( Borders.createEmptyBorder( Sizes.DLUY2, Sizes.DLUX2, Sizes.DLUY2, Sizes.DLUX11 ) );
selectedCaseType.getLabel().setFont( selectedCaseType.getLabel().getFont().deriveFont( Font.BOLD ) );
selectedCaseType.getButton().addActionListener( am.get( "removeCaseType" ) );
valueBinder.bind( "caseType", selectedCaseType );
// Description & DueDate
leftBuilder.setExtent( 3, 1 );
JLabel descriptionLabel = leftBuilder.getComponentFactory().createLabel(
i18n.text( WorkspaceResources.description_label ) );
leftBuilder.add( descriptionLabel );
descriptionLabel.setBorder( BorderFactory.createEmptyBorder( 0, 2, 0, 0 ) );
leftBuilder.nextColumn(3);
JLabel dueOnLabel = leftBuilder.append( i18n.text( WorkspaceResources.due_on_label ) );
dueOnLabel.setBorder( BorderFactory.createEmptyBorder( 0, 2, 0, 0 ) );
leftBuilder.nextLine();
leftBuilder.setExtent( 3, 1 );
JPanel descPanel = new JPanel( new BorderLayout() );
descPanel.add(
valueBinder.bind( "description",
actionBinder.bind( "changeDescription", descriptionField = (JTextField) TEXTFIELD.newField() ) ),
BorderLayout.WEST );
leftBuilder.add( descPanel );
descriptionField.setName( "txtCaseDescription" );
leftBuilder.add(
valueBinder.bind( "dueOn",
actionBinder.bind( "changeDueOn", dueOnField = (JXDatePicker) DATEPICKER.newField() ) ),
new CellConstraints( 4, 2, 1, 1, CellConstraints.LEFT, CellConstraints.BOTTOM, new Insets( 4, 0, 0, 0 ) ) );
leftBuilder.nextLine();
descriptionLabel.setLabelFor( descriptionField );
dueOnLabel.setLabelFor( dueOnField );
refreshComponents.enabledOn( "changedescription", descriptionField );
refreshComponents.enabledOn( "changedueon", dueOnField );
// Select case type
javax.swing.Action caseTypeAction = am.get( "changeCaseType" );
caseTypeButton = new StreamflowButton( caseTypeAction );
caseTypeButton.registerKeyboardAction( caseTypeAction,
(KeyStroke) caseTypeAction.getValue( javax.swing.Action.ACCELERATOR_KEY ),
JComponent.WHEN_IN_FOCUSED_WINDOW );
caseTypeButton.setHorizontalAlignment( SwingConstants.LEFT );
refreshComponents.enabledOn( "casetype", caseTypeButton, selectedCaseType );
leftBuilder.add( caseTypeButton, new CellConstraints( 1, 3, 1, 1, CellConstraints.FILL, CellConstraints.TOP,
new Insets( 2, 0, 5, 0 ) ) );
leftBuilder.add( selectedCaseType, new CellConstraints(3 , 3, 1, 1, CellConstraints.LEFT, CellConstraints.BOTTOM,
new Insets( 5, 5, 0, 0 ) ) );
JLabel priorityLabel = leftBuilder.getComponentFactory().createLabel(
i18n.text( WorkspaceResources.priority_label ) );
priorityLabel.setBorder( BorderFactory.createEmptyBorder( 0, 2, 0, 0 ) );
leftBuilder.add( priorityLabel ,
new CellConstraints( 4, 3, 1, 1, CellConstraints.FILL, CellConstraints.BOTTOM,
new Insets(4, 0, 0, 0 ) ) );
leftBuilder.nextLine();
// Select labels
javax.swing.Action labelAction = labels.getActionMap().get( "addLabel" );
labelButton = new StreamflowButton( labelAction );
// NotificationGlassPane.registerButton(labelButton);
labelButton.registerKeyboardAction( labelAction,
(KeyStroke) labelAction.getValue( javax.swing.Action.ACCELERATOR_KEY ), JComponent.WHEN_IN_FOCUSED_WINDOW );
labelButton.setHorizontalAlignment( SwingConstants.LEFT );
labels.setButtonRelation( labelButton );
refreshLabelComponents.enabledOn( "addlabel", labelButton, labels );
leftBuilder.add( labelButton, new CellConstraints( 1, 4, 1, 1, CellConstraints.FILL, CellConstraints.TOP,
new Insets( 5, 0, 0, 0 ) ) );
labels.setPreferredSize( new Dimension( 500, 50 ) );
leftBuilder.add( labels, new CellConstraints( 3, 4, 1, 1, CellConstraints.LEFT, CellConstraints.TOP, new Insets(
5, 0, 0, 0 ) ) );
JPanel prioPanel = new JPanel(new BorderLayout());
prioPanel.add( valueBinder.bind( "priority", actionBinder.bind(
"changePriority", casePriority = (JComboBox)COMBOBOX.newField() ) ) );
leftBuilder.add(prioPanel ,
new CellConstraints( 4, 4, 1, 1, CellConstraints.FILL, CellConstraints.TOP,
new Insets(2, 0, 0, 0 ) ) );
priorityLabel.setLabelFor( casePriority );
refreshComponents.visibleOn( "changepriority", casePriority, priorityLabel );
refreshComponents.enabledOn( "changepriority", casePriority, priorityLabel );
casePriority.setRenderer( new DefaultListCellRenderer(){
public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus )
{
final PriorityValue itemValue = (PriorityValue) value;
String val = itemValue == null ? "" : itemValue.text().get();
JPanel panel = new JPanel( );
panel.setOpaque( false );
FormLayout layout = new FormLayout( "10dlu, 60dlu:grow", "pref" );
DefaultFormBuilder formBuilder = new DefaultFormBuilder( layout, panel );
panel.setBorder( BorderFactory.createEmptyBorder( 2,0,1,0 ) );
JLabel label = new JLabel( ){
@Override
protected void paintComponent(Graphics g) {
Color color = getBackground();
if( itemValue != null )
{
if( !Strings.empty( itemValue.color().get() ) )
color = new Color( parseInt( itemValue.color().get() ) );
else
color = Color.BLACK;
}
final Color FILL_COLOR = ColorUtil.removeAlpha( color );
Graphics2D g2 = (Graphics2D) g.create();
try {
g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
g2.setColor(Color.LIGHT_GRAY);
final int DIAM = Math.min(getWidth(), getHeight());
final int inset = 3;
g2.fill(new Ellipse2D.Float(inset, inset, DIAM-2*inset, DIAM-2*inset));
g2.setColor(FILL_COLOR);
final int border = 1;
g2.fill(new Ellipse2D.Float(inset+border, inset+border, DIAM-2*inset-2*border, DIAM-2*inset-2*border));
} finally {
g2.dispose();
}
}
};
label.setPreferredSize( new Dimension( 10, 10 ) );
formBuilder.add( ( Strings.empty(val) || "-".equals( val ) ) ? new JLabel( ) : label,
new CellConstraints(1 , 1, 1, 1, CellConstraints.FILL, CellConstraints.FILL,
new Insets( 0, 0, 0, 0 ) ) );
JLabel text = new JLabel( val );
formBuilder.add( text, new CellConstraints(2 , 1, 1, 1, CellConstraints.LEFT, CellConstraints.FILL,
new Insets( 0, 0, 0, 0 ) ) );
return panel;
}
});
leftBuilder.nextLine();
leftBuilder.add( caseNotes, new CellConstraints( 1, 6, 4, 1, CellConstraints.FILL, CellConstraints.TOP,
new Insets( 0, 2, 5, 0 ) ) );
// Forms
JLabel formsLabel = new JLabel( i18n.text( WorkspaceResources.forms_label ) );
refreshComponents.visibleOn( "changedescription", formsLabel );
leftBuilder.add( formsLabel, new CellConstraints( 1, 7, 1, 1, CellConstraints.LEFT, CellConstraints.TOP,
new Insets( 5, 0, 0, 0 ) ) );
leftBuilder.nextLine();
JPanel formsPanel = new JPanel( new BorderLayout() );
formsPanel.add( forms, BorderLayout.WEST );
leftBuilder.add( formsPanel, new CellConstraints( 1, 8, 3, 1, CellConstraints.FILL, CellConstraints.FILL,
new Insets( 5, 0, 0, 0 ) ) );
// Limit pickable dates to future
Calendar calendar = Calendar.getInstance();
calendar.setTime( new Date() );
calendar.add( Calendar.DAY_OF_MONTH, 1 );
dueOnField.getMonthView().setLowerBound( calendar.getTime() );
final DateFormat dateFormat = DateFormat.getDateInstance( DateFormat.SHORT );
dateFormat.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
dueOnField.getEditor().setFormatterFactory(
new DefaultFormatterFactory( new DatePickerFormatter( new DateFormat[]
{ dateFormat } )
{
@Override
public Object stringToValue(String text) throws ParseException
{
Object result;
try
{
result = super.stringToValue( text );
} catch (ParseException pe)
{
dialogs.showMessageDialog( dueOnField, text( WorkspaceResources.wrong_format_msg ) + " "
+ ((SimpleDateFormat) dateFormat).toPattern(), text( WorkspaceResources.wrong_format_title ) );
throw pe;
}
return result;
}
} ) );
// Main panel that contains both left and right pane
JPanel formsContainer = new JPanel();
formsContainer.setLayout( new GridLayout( 1, 2 ) );
formsContainer.setBorder( Borders.createEmptyBorder( "2dlu, 2dlu, 2dlu, 2dlu" ) );
formsContainer.add( leftPane );
formsContainer.add( caseLogView );
setViewportView( formsContainer );
setFocusTraversalPolicy( new LayoutFocusTraversalPolicy() );
setFocusCycleRoot( true );
setFocusable( true );
new RefreshWhenShowing( this, this );
}
public void refresh()
{
model.refresh();
valueBinder.update( model.getGeneral() );
selectedCaseType.setClickLink( model.getGeneral().caseType().get() );
if( model.getCommandEnabled( "changepriority" ) )
{
EventComboBoxModel comboBoxModel = model.getCasePriorities();
casePriority.setModel( comboBoxModel );
LinkValue selectPriority = model.getGeneral().priority().get();
if( selectPriority != null )
{
// omit first element since priority is always null in the first element
for(int i = 0; i < comboBoxModel.getSize(); i++)
{
PriorityValue priorityValue = ((PriorityValue)comboBoxModel.getElementAt( i ));
if( priorityValue == null ) continue;
if( priorityValue.id().get().equals( selectPriority.id().get() ))
{
casePriority.setSelectedItem( comboBoxModel.getElementAt( i ) );
casePriority.setToolTipText( selectPriority.text().get() );
}
}
}
}
}
@Action(block = Task.BlockingScope.COMPONENT)
public Task changeDescription(final ActionEvent event)
{
Property<String> description = model.getGeneral().description();
String oldValue = description.get();
try
{
description.set( descriptionField.getText() );
// set back old value to not mess up model execution
description.set( oldValue );
} catch (ConstraintViolationException cve)
{
int maxLength = description.metaInfo( MaxLength.class ).value();
descriptionField.setText( descriptionField.getText().substring( 0, maxLength ) );
throw new RuntimeException( new MessageFormat( i18n.text( StreamflowResources.max_length ) ).format(
new Object[]
{ maxLength } ).toString() );
}
return new CommandTask()
{
@Override
public void command() throws Exception
{
model.changeDescription( descriptionField.getText() );
}
};
}
@Action(block = Task.BlockingScope.COMPONENT)
public Task changeDueOn( final ActionEvent event )
{
return new CommandTask()
{
@Override
public void command() throws Exception
{
model.changeDueOn( dueOnField.getDate() );
}
};
}
@Action
public Task changeCaseType()
{
final SelectLinkDialog dialog = module.objectBuilderFactory().newObjectBuilder( SelectLinkDialog.class )
.use( model.getPossibleCaseTypes(), new Integer(1) ).newInstance();
dialog.setPreferredSize( new Dimension( 400, 200 ) );
dialogs.showOkCancelHelpDialog( caseTypeButton, dialog, i18n.text( WorkspaceResources.choose_casetype ) );
caseTypeButton.requestFocusInWindow();
if (dialog.getSelectedLink() != null)
{
return new CommandTask()
{
@Override
protected void command() throws Exception
{
LinkValue selected = dialog.getSelectedLink();
model.changeCaseType( selected );
// selectedCaseType.setRemoveLink(selected);
String labelQuery = dialog.getFilterField().getText();
// if the query string has any match inside label descriptions
// we do a search for that labels and add them to the case
// automatically
if (!"".equals( labelQuery )
&& selected.classes().get().toLowerCase().indexOf( labelQuery.toLowerCase() ) != -1)
{
EventList<LinkValue> possibleLabels = labels.getModel().getPossibleLabels();
for (LinkValue link : possibleLabels)
{
if (link.text().get().toLowerCase().contains( labelQuery.toLowerCase() ))
{
labels.getModel().addLabel( link );
}
}
}
}
};
} else
return null;
}
@Action
public Task removeCaseType()
{
return new CommandTask()
{
@Override
public void command() throws Exception
{
model.removeCaseType();
}
};
}
@Action
public Task changePriority()
{
final PriorityValue selected = (PriorityValue)casePriority.getSelectedItem();
String oldPriority = model.getGeneral().priority().get() != null ? model.getGeneral().priority().get().id().get() : "-1";
String newPriority = selected != null ? selected.id().get() : "-1";
if( !newPriority.equals( oldPriority ) )
{
return new CommandTask()
{
@Override
protected void command() throws Exception
{
model.changePriority( selected.id().get() );
}
};
} else
return null;
}
public void notifyTransactions(Iterable<TransactionDomainEvents> transactions)
{
if (matches( withNames( "addedLabel", "removedLabel", "changedOwner", "changedCaseType", "changedStatus", "changedPriority", "setUnread" ),
transactions ))
{
refresh();
}
}
}