/**
*
* 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.table;
import ca.odell.glazedlists.SeparatorList;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swing.EventJXTableModel;
import ca.odell.glazedlists.swing.EventTableModel;
import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import org.jdesktop.application.ApplicationContext;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.color.ColorUtil;
import org.jdesktop.swingx.decorator.AbstractHighlighter;
import org.jdesktop.swingx.decorator.HighlightPredicate;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingx.renderer.DefaultTableRenderer;
import org.jdesktop.swingx.renderer.StringValue;
import org.jdesktop.swingx.table.TableColumnExt;
import org.jdesktop.swingx.table.TableColumnModelExt;
import org.qi4j.api.common.Optional;
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.structure.Module;
import org.qi4j.api.util.Iterables;
import se.streamsource.dci.value.link.LinkValue;
import se.streamsource.streamflow.api.administration.priority.PriorityValue;
import se.streamsource.streamflow.api.workspace.cases.CaseStates;
import se.streamsource.streamflow.client.Icons;
import se.streamsource.streamflow.client.MacOsUIWrapper;
import se.streamsource.streamflow.client.ui.DateFormats;
import se.streamsource.streamflow.client.ui.workspace.WorkspaceResources;
import se.streamsource.streamflow.client.ui.workspace.cases.CaseResources;
import se.streamsource.streamflow.client.ui.workspace.cases.CaseTableValue;
import se.streamsource.streamflow.client.util.CommandTask;
import se.streamsource.streamflow.client.util.RefreshWhenShowing;
import se.streamsource.streamflow.client.util.i18n;
import se.streamsource.streamflow.client.util.table.SeparatorTable;
import se.streamsource.streamflow.infrastructure.event.domain.DomainEvent;
import se.streamsource.streamflow.infrastructure.event.domain.TransactionDomainEvents;
import se.streamsource.streamflow.infrastructure.event.domain.source.TransactionListener;
import se.streamsource.streamflow.infrastructure.event.domain.source.helper.EventParameters;
import se.streamsource.streamflow.infrastructure.event.domain.source.helper.Events;
import se.streamsource.streamflow.util.Strings;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.font.TextAttribute;
import java.awt.geom.Ellipse2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.TimeZone;
import static java.awt.RenderingHints.*;
import static java.lang.Integer.*;
import static se.streamsource.streamflow.client.util.i18n.*;
import static se.streamsource.streamflow.infrastructure.event.domain.source.helper.Events.*;
/**
* Base class for all views of case lists.
*/
public class CasesTableView
extends JPanel
implements TransactionListener
{
@Structure
Module module;
public static final int MILLIS_IN_DAY = (1000 * 60 * 60 * 24);
public static final WorkspaceResources[] dueGroups = {WorkspaceResources.overdue, WorkspaceResources.duetoday, WorkspaceResources.duetomorrow, WorkspaceResources.duenextweek, WorkspaceResources.duenextmonth, WorkspaceResources.later, WorkspaceResources.noduedate};
private Comparator<CaseTableValue> groupingComparator = new Comparator<CaseTableValue>()
{
public int compare( CaseTableValue o1, CaseTableValue o2 )
{
GroupBy groupBy = model.getGroupBy();
switch (groupBy)
{
case caseType:
return o1.caseType().get().compareTo( o2.caseType().get() );
case dueOn:
return dueOnGroup( o1.dueOn().get() ).compareTo( dueOnGroup( o2.dueOn().get() ) );
case assignee:
return o1.assignedTo().get().compareTo( o2.assignedTo().get() );
case project:
return o1.owner().get().compareTo( o2.owner().get() );
case priority:
Integer prio1 = o1.priority().get() != null ? o1.priority().get().priority().get() : new Integer( 9999 );
Integer prio2 = o2.priority().get() != null ? o2.priority().get().priority().get() : new Integer( 9999 );
return prio1.compareTo( prio2 );
default:
return 0;
}
}
};
protected JXTable caseTable;
protected CasesTableModel model;
private TableFormat tableFormat;
private ApplicationContext context;
private PerspectiveView filter;
public void init( final @Service ApplicationContext context,
@Uses CasesTableModel casesTableModel,
final @Uses TableFormat tableFormat,
@Optional @Uses JTextField searchField )
{
setLayout( new BorderLayout() );
this.context = context;
this.model = casesTableModel;
this.tableFormat = tableFormat;
ActionMap am = context.getActionMap( CasesTableView.class, this );
setActionMap( am );
MacOsUIWrapper.convertAccelerators( context.getActionMap(
CasesTableView.class, this ) );
// Filter
filter = module.objectBuilderFactory().newObjectBuilder(PerspectiveView.class).use( model, searchField ).newInstance();
add( filter, BorderLayout.NORTH );
// Table
// Trigger creation of filters and table model
caseTable = new SeparatorTable( null )
{
public Component prepareRenderer(
TableCellRenderer renderer, int row, int column)
{
Component c = super.prepareRenderer(renderer, row, column);
// add custom rendering here
EventTableModel model = (EventTableModel) getModel();
if( model.getElementAt( row ) instanceof CaseTableValue )
{
Map attributes = c.getFont().getAttributes();
if( ((CaseTableValue) model.getElementAt( row )).removed().get() )
{
attributes.put( TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
} else if ( ((CaseTableValue) model.getElementAt( row )).unread().get() )
{
attributes.put( TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD );
}
c.setFont( new Font(attributes) );
}
return c;
}
};
caseTable.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
caseTable.getActionMap().getParent().setParent( am );
caseTable.setFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.getDefaultFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS ) );
caseTable.setFocusTraversalKeys( KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.getDefaultFocusTraversalKeys( KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS ) );
caseTable.getActionMap().remove( "column.horizontalScroll" );
caseTable.getActionMap().remove( "column.packAll" );
caseTable.getActionMap().remove( "column.packSelected" );
caseTable.setColumnControlVisible( true );
caseTable.setModel( new EventJXTableModel<CaseTableValue>( model.getEventList(), tableFormat ) );
model.addObserver( new Observer()
{
public void update( Observable o, Object arg )
{
if (model.getGroupBy() == GroupBy.none)
{
caseTable.setModel( new EventJXTableModel<CaseTableValue>( model.getEventList(), tableFormat ) );
}
else
{
SeparatorList<CaseTableValue> groupingList = new SeparatorList<CaseTableValue>( model.getEventList(),
groupingComparator, 1, 10000 );
caseTable.setModel( new EventJXTableModel<CaseTableValue>( groupingList, tableFormat ) );
}
if( !model.containsCaseWithPriority()) {
model.addInvisibleColumn( 8 );
}
for (Integer invisibleCol : model.getInvisibleColumns())
{
TableColumnModelExt tm = (TableColumnModelExt) caseTable.getColumnModel();
if (tm.getColumnExt( invisibleCol ).isVisible())
caseTable.getColumnExt( invisibleCol ).setVisible( false );
}
}
} );
caseTable.getColumn( 0 ).setPreferredWidth( 500 );
caseTable.getColumn( 1 ).setPreferredWidth( 70 );
caseTable.getColumn( 1 ).setMaxWidth( 70 );
caseTable.getColumn( 1 ).setResizable( false );
caseTable.getColumn( 2 ).setPreferredWidth( 300 );
caseTable.getColumn( 2 ).setMaxWidth( 300 );
caseTable.getColumn( 3 ).setPreferredWidth( 150 );
caseTable.getColumn( 3 ).setMaxWidth( 150 );
caseTable.getColumn( 4 ).setPreferredWidth( 90 );
caseTable.getColumn( 4 ).setMaxWidth( 90 );
caseTable.getColumn( 5 ).setPreferredWidth( 150 );
caseTable.getColumn( 5 ).setMaxWidth( 150 );
caseTable.getColumn( 6 ).setPreferredWidth( 90 );
caseTable.getColumn( 6 ).setMaxWidth( 90 );
caseTable.getColumn( 7 ).setPreferredWidth( 150 );
caseTable.getColumn( 7 ).setMaxWidth( 150 );
caseTable.getColumn( 7 ).setResizable( false );
caseTable.getColumn( 8 ).setPreferredWidth( 100 );
caseTable.getColumn( 8 ).setMaxWidth( 100 );
caseTable.getColumn( 9 ).setMaxWidth( 50 );
caseTable.getColumn( 9 ).setResizable( false );
caseTable.setAutoCreateColumnsFromModel( false );
int count = 0;
for (TableColumn c : caseTable.getColumns())
{
c.setIdentifier( (Integer)count );
count++;
c.addPropertyChangeListener( new PropertyChangeListener()
{
public void propertyChange( PropertyChangeEvent evt )
{
if ("visible".equals( evt.getPropertyName() ))
{
TableColumnExt columnExt = (TableColumnExt) evt.getSource();
if (columnExt.isVisible())
{
model.removeInvisibleColumn( columnExt.getModelIndex() );
} else
{
model.addInvisibleColumn( columnExt.getModelIndex() );
}
}
}
} );
}
JScrollPane caseScrollPane = new JScrollPane( caseTable, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED );
add( caseScrollPane, BorderLayout.CENTER );
caseTable.setDefaultRenderer( Date.class, new DefaultTableRenderer( new StringValue()
{
private static final long serialVersionUID = 4782416330896582518L;
public String getString( Object value )
{
return value != null ? DateFormats.getProgressiveDateTimeValue( (Date) value, Locale.getDefault() ) : "";
}
} ) );
caseTable.setDefaultRenderer( ArrayList.class, new DefaultTableCellRenderer()
{
@Override
public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column )
{
if (value == null) return this;
if (value instanceof SeparatorList.Separator)
return caseTable.getDefaultRenderer( SeparatorList.Separator.class )
.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
JPanel renderer = new JPanel( new FlowLayout( FlowLayout.LEFT ) );
ArrayList<String> icons = (ArrayList<String>) value;
for (String icon : icons)
{
ImageIcon image = i18n.icon( Icons.valueOf( icon ), 11 );
JLabel iconLabel = image != null ? new JLabel( image, SwingConstants.LEADING ) : new JLabel( " " );
renderer.add( iconLabel );
}
if (isSelected)
renderer.setBackground( table.getSelectionBackground() );
return renderer;
}
} );
caseTable.setDefaultRenderer( CaseStates.class, new DefaultTableCellRenderer()
{
@Override
public Component getTableCellRendererComponent( JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column )
{
if( value == null ) return this;
EventTableModel model = (EventTableModel) table.getModel();
boolean hasResolution = !Strings.empty( ((CaseTableValue) model.getElementAt( row )).resolution().get() );
boolean removed = ((CaseTableValue)model.getElementAt( row )).removed().get();
String iconName = hasResolution ? "case_status_withresolution_" + value.toString().toLowerCase() + "_icon"
: "case_status_" + value.toString().toLowerCase() + "_icon";
iconName = removed ? "case_status_draft_icon" : iconName;
JLabel renderedComponent = (JLabel) super.getTableCellRendererComponent( table, value, isSelected, hasFocus,
row, column );
renderedComponent.setHorizontalAlignment( SwingConstants.CENTER );
setText( null );
setIcon( i18n.icon( CaseResources.valueOf( iconName ),
i18n.ICON_16 ) );
setName( i18n.text( CaseResources.valueOf( "case_status_" + value.toString().toLowerCase() + "_text" ) ) );
setToolTipText( i18n.text( CaseResources.valueOf( "case_status_" + value.toString().toLowerCase() + "_text" ) ) );
return this;
}
} );
caseTable.setDefaultRenderer( PriorityValue.class, new DefaultTableCellRenderer()
{
@Override
public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column )
{
final PriorityValue priority = (PriorityValue) value;
String val = priority == null ? "" : priority.text().get();
JPanel panel = new JPanel( );
FormLayout layout = new FormLayout( "10dlu, 50dlu:grow", "pref" );
DefaultFormBuilder formBuilder = new DefaultFormBuilder( layout, panel );
panel.setBorder( BorderFactory.createEmptyBorder( 0, 0, 0, 0 ) );
JLabel label = new JLabel( ){
@Override
protected void paintComponent(Graphics g) {
Color color = getBackground();
if( priority != null )
{
if( !Strings.empty( priority.color().get() ) )
color = new Color( parseInt( priority.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 ) );
//label.setBorder( BorderFactory.createLineBorder( Color.RED ) );
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 );
//text.setBorder( BorderFactory.createLineBorder( Color.RED ) );
formBuilder.add( text, new CellConstraints(2, 1, 1, 1, CellConstraints.LEFT, CellConstraints.FILL,
new Insets( 0, 0, 0, 0 ) ) );
if (isSelected)
{
panel.setBackground( table.getSelectionBackground() );
text.setForeground( table.getSelectionForeground() );
}
return panel;
}
});
caseTable.setDefaultRenderer( SeparatorList.Separator.class, new DefaultTableCellRenderer()
{
@Override
public Component getTableCellRendererComponent( JTable table, Object separator, boolean isSelected, boolean hasFocus, int row, int column )
{
String value = "";
boolean emptyDescription = false;
switch (model.getGroupBy())
{
case caseType:
emptyDescription = Strings.empty( ((CaseTableValue) ((SeparatorList.Separator) separator).first()).caseType().get() );
value = !emptyDescription ? ((CaseTableValue) ((SeparatorList.Separator) separator).first()).caseType().get() : text( WorkspaceResources.no_casetype );
break;
case assignee:
emptyDescription = Strings.empty( ((CaseTableValue) ((SeparatorList.Separator) separator).first()).assignedTo().get() );
value = !emptyDescription ? ((CaseTableValue) ((SeparatorList.Separator) separator).first()).assignedTo().get() : text( WorkspaceResources.no_assignee );
break;
case project:
emptyDescription = Strings.empty( ((CaseTableValue) ((SeparatorList.Separator) separator).first()).owner().get() );
value = !emptyDescription ? ((CaseTableValue) ((SeparatorList.Separator) separator).first()).owner().get() : text( WorkspaceResources.no_project );
break;
case dueOn:
value = text( dueGroups[dueOnGroup( ((CaseTableValue) ((SeparatorList.Separator) separator).first()).dueOn().get() )] );
break;
case priority:
emptyDescription = ((CaseTableValue) ((SeparatorList.Separator) separator).first()).priority().get() == null
|| Strings.empty( ((CaseTableValue) ((SeparatorList.Separator) separator).first()).priority().get().color().get() );
value = !emptyDescription ? ((CaseTableValue) ((SeparatorList.Separator) separator).first()).priority().get().text().get() : text( WorkspaceResources.no_priority);
break;
}
Component component = super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
component.setFont( component.getFont().deriveFont( Font.BOLD + Font.ITALIC ) );
component.setBackground( Color.lightGray );
return component;
}
} );
AbstractHighlighter separatorHighlighter = (AbstractHighlighter) HighlighterFactory.createSimpleStriping(HighlighterFactory.QUICKSILVER);
separatorHighlighter.setHighlightPredicate(new HighlightPredicate.TypeHighlightPredicate(SeparatorList.Separator.class));
caseTable.addHighlighter(HighlighterFactory.createAlternateStriping());
caseTable.addHighlighter(separatorHighlighter);
addFocusListener( new FocusAdapter()
{
public void focusGained( FocusEvent e )
{
caseTable.requestFocusInWindow();
}
} );
model.getEventList().addListEventListener( new ListEventListener<CaseTableValue>()
{
public void listChanged( ListEvent<CaseTableValue> listChanges )
{
// Synchronize lists
Set<String> labels = new HashSet<String>();
Set<String> assignees = new HashSet<String>();
Set<String> projects = new HashSet<String>();
for (CaseTableValue caseTableValue : listChanges.getSourceList())
{
for (LinkValue linkValue : caseTableValue.labels().get().links().get())
{
labels.add( linkValue.text().get() );
}
assignees.add( caseTableValue.assignedTo().get() );
projects.add( caseTableValue.owner().get() );
}
List<String> sortedLabels = new ArrayList<String>( labels );
List<String> sortedAssignees = new ArrayList<String>( assignees );
List<String> sortedProjects = new ArrayList<String>( projects );
Collections.sort( sortedLabels );
Collections.sort( sortedAssignees );
Collections.sort( sortedProjects );
sortedLabels.add( 0, text( WorkspaceResources.all ) );
sortedAssignees.add( 0, text( WorkspaceResources.all ) );
sortedProjects.add( 0, text( WorkspaceResources.all ) );
}
} );
addHierarchyListener(new HierarchyListener()
{
public void hierarchyChanged(HierarchyEvent e)
{
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) > 0)
{
if (CasesTableView.this.isShowing())
{
context.getActionMap().get("savePerspective").setEnabled(true);
}
}
}
});
new RefreshWhenShowing( this, model );
}
public JXTable getCaseTable()
{
return caseTable;
}
public CasesTableModel getModel()
{
return model;
}
private Integer dueOnGroup( Date date )
{
/**
* 0 = Overdue
* 1 = Today
* 2 = Tomorrow
* 3 = Within next week
* 4 = Within next month
* 5 = Later
* 6 = No due date
*/
long currentTime = System.currentTimeMillis();
currentTime = currentTime / MILLIS_IN_DAY;
currentTime *= MILLIS_IN_DAY;
Date today = new Date( currentTime );
Date lateToday = new Date( currentTime + MILLIS_IN_DAY - 1 );
Calendar month = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
month.setTime( today );
month.add( Calendar.MONTH, 1 );
Calendar week = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
week.setTime( today );
week.add( Calendar.WEEK_OF_YEAR, 1 );
Calendar tomorrow = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
tomorrow.setTime( lateToday );
tomorrow.add( Calendar.DATE, 1 );
int group;
if (date == null)
group = 6;
else if (date.after( month.getTime() ))
group = 5; // Later
else if (date.after( week.getTime() ))
group = 4; // Within next month
else if (date.after( tomorrow.getTime() ))
group = 3; // Within next week
else if (date.after( lateToday ))
group = 2; // Tomorrow
else if (date.after( today ))
group = 1;
else
group = 0;
return group;
}
public void notifyTransactions( final Iterable<TransactionDomainEvents> transactions )
{
if (Events.matches( withNames( "createdCase" ), transactions ))
{
final DomainEvent event = Iterables.first( Iterables.filter( withNames( "createdCase" ), Events.events( transactions ) ) );;
context.getTaskService().execute( new CommandTask()
{
@Override
protected void command() throws Exception
{
model.refresh();
}
@Override
protected void succeeded( Iterable<TransactionDomainEvents> transactionEventsIterable )
{
super.succeeded( transactionEventsIterable );
TableModel model = caseTable.getModel();
boolean rowFound = false;
for( int i=0, n=model.getRowCount(); i < n; i++ )
{
if( model.getValueAt( i, model.getColumnCount() ).toString().endsWith( EventParameters.getParameter( event, "param1" ) + "/") )
{
caseTable.getSelectionModel().setSelectionInterval( caseTable.convertRowIndexToView( i ), caseTable.convertRowIndexToView( i ) );
caseTable.scrollRectToVisible( caseTable.getCellRect( i, 0, true ) );
rowFound = true;
break;
}
}
}
} );
} else if (Events.matches( withNames( "addedLabel", "removedLabel",
"changedDescription", "changedCaseType", "changedStatus",
"changedOwner", "assignedTo", "unassigned", "changedRemoved","deletedEntity",
"updatedContact", "addedContact", "deletedContact",
"createdConversation", "changedDueOn", "submittedForm", "createdAttachment",
"removedAttachment", "changedPriority", "setUnread", "createdMessageFromDraft" ), transactions ))
{
context.getTaskService().execute( new CommandTask()
{
@Override
protected void command() throws Exception
{
model.refresh();
if (Events.matches( withNames( "changedStatus",
"changedOwner", "assignedTo", "unassigned", "deletedEntity" ), transactions ))
{
caseTable.getSelectionModel().clearSelection();
}
}
} );
}
}
}