/** * * 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.SortedList; import org.jdesktop.application.Action; import org.jdesktop.application.ApplicationAction; import org.jdesktop.application.ApplicationContext; import org.jdesktop.swingx.util.WindowUtils; 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.dci.value.link.Links; import se.streamsource.streamflow.client.Icons; import se.streamsource.streamflow.client.ui.workspace.WorkspaceResources; import se.streamsource.streamflow.client.ui.workspace.search.SearchView; import se.streamsource.streamflow.client.util.BottomBorder; import se.streamsource.streamflow.client.util.StreamflowButton; import se.streamsource.streamflow.client.util.StreamflowToggleButton; import se.streamsource.streamflow.client.util.dialog.DialogService; import javax.swing.BorderFactory; import javax.swing.DefaultListCellRenderer; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Observable; import java.util.Observer; import static se.streamsource.streamflow.client.ui.workspace.WorkspaceResources.*; import static se.streamsource.streamflow.client.util.i18n.*; public class PerspectiveView extends JPanel implements Observer { private static final long serialVersionUID = -149885124005347187L; @Service DialogService dialogs; @Structure Module module; private CasesTableModel model; private JDialog popup; private JTextField searchField; private JPanel optionsPanel; private ApplicationContext context; private JPanel filterPanel; private JPanel viewPanel; private JList groupByList; private JList sortByList; private JList statusList; private enum FilterActions { filterClear, filterStatus, filterCaseType, filterLabel, filterAssignee, filterProject, filterCreatedBy, filterCreatedOn, filterDueOn, viewSorting, viewGrouping } public void initView(final @Service ApplicationContext context, final @Uses CasesTableModel model, @Optional @Uses JTextField searchField) { this.context = context; this.model = model; model.addObserver( this ); this.searchField = searchField; setActionMap( context.getActionMap( this ) ); setFocusable(true); setLayout(new BorderLayout()); filterPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); javax.swing.Action filterClearAction = getActionMap().get( FilterActions.filterClear.name() ); StreamflowButton filterClearButton = new StreamflowButton( filterClearAction ); filterPanel.add(filterClearButton); addPopupButton( filterPanel, FilterActions.filterCreatedOn.name() ); List<LinkValue> linkValues = model.possibleFilterLinks(); if( Iterables.matchesAny( Links.withRel( "possibleprojects" ), linkValues ) ) addPopupButton(filterPanel, FilterActions.filterProject.name() ); if( Iterables.matchesAny( Links.withRel( "possibleassignees" ), linkValues ) ) addPopupButton(filterPanel, FilterActions.filterAssignee.name() ); addPopupButton(filterPanel, FilterActions.filterCaseType.name() ); addPopupButton(filterPanel, FilterActions.filterLabel.name() ); if( Iterables.matchesAny( Links.withRel( "possiblecreatedby" ), linkValues ) ) addPopupButton(filterPanel, FilterActions.filterCreatedBy.name() ); addPopupButton(filterPanel, FilterActions.filterDueOn.name() ); if( Iterables.matchesAny( Links.withRel( "possiblestatus" ) , linkValues ) ) addPopupButton(filterPanel, FilterActions.filterStatus.name() ); add( filterPanel, BorderLayout.WEST ); statusList = new StatusList(); viewPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); addPopupButton(viewPanel, FilterActions.viewSorting.name() ); addPopupButton(viewPanel, FilterActions.viewGrouping.name() ); add(viewPanel, BorderLayout.EAST); sortByList = new SortByList(); groupByList = new GroupByList(); addHierarchyListener( new HierarchyListener() { public void hierarchyChanged( HierarchyEvent e ) { if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) > 0) { if (!PerspectiveView.this.isShowing()) { for (Component component : Iterables.flatten( Iterables.iterable( filterPanel.getComponents() ), Iterables.iterable( viewPanel.getComponents() ) )) { if( !(component instanceof StreamflowToggleButton) ) continue; ((StreamflowToggleButton) component).setSelected( false ); } } } } } ); } private void addPopupButton(JPanel panel, String action) { javax.swing.Action filterAction = getActionMap().get(action); StreamflowToggleButton button = new StreamflowToggleButton(filterAction); button.addItemListener( new ItemListener() { public void itemStateChanged(ItemEvent itemEvent) { int state = itemEvent.getStateChange(); if (state == ItemEvent.SELECTED) { for (Component component : Iterables.flatten(Iterables.iterable(filterPanel.getComponents()), Iterables.iterable(viewPanel.getComponents()))) { if( !(component instanceof StreamflowToggleButton) ) continue; if (component != itemEvent.getSource() ) { ((StreamflowToggleButton)component).setSelected(false); } } optionsPanel = new JPanel(); StreamflowToggleButton button = (StreamflowToggleButton) itemEvent.getSource(); showPopup(button); } else if (state == ItemEvent.DESELECTED) { killPopup(); } } }); panel.add(button); } @Action public void filterClear() { model.clearFilter(); if( searchField != null ) searchField.setText( "" ); killPopup(); } @Action public void filterStatus() { JPanel statusPanel = new JPanel( new BorderLayout() ); statusPanel.setPreferredSize( new Dimension( 100, 60 ) ); statusPanel.setMaximumSize( new Dimension( 100, 60 ) ); statusPanel.setMaximumSize( new Dimension( 100, 60 ) ); statusPanel.add( statusList, BorderLayout.CENTER ); optionsPanel.add( statusPanel ); } @Action public void filterCaseType() { SortedList<LinkValue> sortedCaseTypes = new SortedList<LinkValue>( model.getPossibleCaseTypes(), new SelectedLinkValueComparator(model.getSelectedCaseTypes())); PerspectiveOptions panel = new PerspectiveOptions(context, sortedCaseTypes, model.getSelectedCaseTypeIds(), true, text( WorkspaceResources.selected_case_types )); optionsPanel.add( panel ); } @Action public void filterLabel() { SortedList<LinkValue> sortedLabels = new SortedList<LinkValue>( model.getPossibleLabels(), new SelectedLinkValueComparator( model.getSelectedLabels() ) ); PerspectiveOptions panel = new PerspectiveOptions( context, sortedLabels, model.getSelectedLabelIds(), true, text( WorkspaceResources.selected_labels )); optionsPanel.add(panel); } @Action public void filterAssignee() { SortedList<LinkValue> sortedAssignees = new SortedList<LinkValue>( model.getPossibleAssignees(), new SelectedLinkValueComparator( model.getSelectedAssignees() ) ); PerspectiveOptions panel = new PerspectiveOptions( context, sortedAssignees, model.getSelectedAssigneeIds(), false, text( WorkspaceResources.selected_users ) ); optionsPanel.add(panel); } @Action public void filterProject() { SortedList<LinkValue> sortedProjects = new SortedList<LinkValue>( model.getPossibleProjects(), new SelectedLinkValueComparator( model.getSelectedProjects() ) ); PerspectiveOptions panel = new PerspectiveOptions( context, sortedProjects, model.getSelectedProjectIds(), true, text( WorkspaceResources.selected_projects )); optionsPanel.add( panel ); } @Action public void filterCreatedOn(ActionEvent event) { PerspectivePeriodView period = module.objectBuilderFactory().newObjectBuilder( PerspectivePeriodView.class ).use( model.getCreatedOnModel() ).newInstance(); optionsPanel.add(period); } @Action public void filterDueOn(ActionEvent event) { PerspectivePeriodView period = module.objectBuilderFactory().newObjectBuilder( PerspectivePeriodView.class ).use( model.getDueOnModel() ).newInstance(); optionsPanel.add(period); } @Action public void filterCreatedBy() { SortedList<LinkValue> sortedCreatedBy = new SortedList<LinkValue>( model.getPossibleCreatedBy(), new SelectedLinkValueComparator( model.getSelectedCreatedBy() ) ); PerspectiveOptions panel = new PerspectiveOptions( context, sortedCreatedBy, model.getSelectedCreatedByIds(), false, text( WorkspaceResources.selected_users )); optionsPanel.add(panel); } @Action public void viewSorting() { optionsPanel.add(sortByList); } @Action public void viewGrouping() { optionsPanel.add(groupByList); } private void showPopup(final Component button) { SwingUtilities.invokeLater( new Runnable() { public void run() { // Make it impossible to have several popups open at the same time if( popup != null ) { popup.dispose(); popup = null; } final JFrame frame = (JFrame) SwingUtilities.getAncestorOfClass( JFrame.class, PerspectiveView.this ); popup = new JDialog( frame ); popup.getRootPane().registerKeyboardAction( new ActionListener() { public void actionPerformed( ActionEvent e ) { killPopup(); cleanToggleButtonSelection(); } }, KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ), JComponent.WHEN_IN_FOCUSED_WINDOW); popup.setUndecorated( true ); popup.setModal( false ); popup.setLayout( new BorderLayout() ); popup.add( optionsPanel, BorderLayout.CENTER ); Point location = button.getLocationOnScreen(); popup.setBounds( (int) location.getX(), (int) location.getY() + button.getHeight(), optionsPanel.getWidth(), optionsPanel.getHeight() ); popup.pack(); popup.setVisible( true ); frame.addComponentListener( new ComponentAdapter() { @Override public void componentMoved( ComponentEvent e ) { if (popup != null) { killPopup(); frame.removeComponentListener( this ); } } } ); } } ); } public void killPopup() { if (popup != null) { popup.setVisible(false); popup.dispose(); popup = null; } // only do active update if we are not connected to SearchView if (!windowContainsSearchView(WindowUtils.findWindow( this ))) { model.refresh(); } else { this.update( null, null ); } } private boolean windowContainsSearchView( Container container ) { boolean result = false; for (Component c : container.getComponents()) { if (c instanceof Container) { if (c instanceof SearchView) { result = c.isShowing(); } else { result |= windowContainsSearchView((Container) c); } } } return result; } public JDialog getCurrentPopup() { return popup; } public void setCurrentPopup( JDialog dialog ) { popup = dialog; } public void cleanToggleButtonSelection() { for (Component component : Iterables.flatten( Iterables.iterable( filterPanel.getComponents() ), Iterables.iterable( viewPanel.getComponents() ) )) { if (!(component instanceof StreamflowToggleButton)) continue; if (((StreamflowToggleButton) component).isSelected()) { ((StreamflowToggleButton) component).setSelected( false ); } } } public void update( Observable o, Object arg ) { for( Component comp : Iterables.flatten( Iterables.iterable(filterPanel.getComponents()), Iterables.iterable(viewPanel.getComponents()) ) ) { if( comp instanceof StreamflowToggleButton ) { StreamflowToggleButton button = (StreamflowToggleButton)comp; boolean selectedIsEmpty = true; switch( FilterActions.valueOf( ((ApplicationAction)button.getAction()).getName())) { case filterStatus: selectedIsEmpty = model.getSelectedStatuses().isEmpty(); break; case filterAssignee: selectedIsEmpty = model.getSelectedAssigneeIds().isEmpty(); break; case filterLabel: selectedIsEmpty = model.getSelectedLabelIds().isEmpty(); break; case filterProject: selectedIsEmpty = model.getSelectedProjectIds().isEmpty(); break; case filterCaseType: selectedIsEmpty = model.getSelectedCaseTypeIds().isEmpty(); break; case filterCreatedBy: selectedIsEmpty = model.getSelectedCreatedByIds().isEmpty(); break; case filterCreatedOn: selectedIsEmpty = Period.none.equals( model.getCreatedOnModel().getPeriod() ); break; case filterDueOn: selectedIsEmpty = Period.none.equals( model.getDueOnModel().getPeriod() ); break; case viewSorting: selectedIsEmpty = SortBy.none.equals( model.getSortBy() ); break; case viewGrouping: selectedIsEmpty = GroupBy.none.equals( model.getGroupBy() ); break; default: } button.setIcon( selectedIsEmpty ? icon( Icons.down_no_selection, ICON_16 ) : icon( Icons.down_with_selection, ICON_16 ) ); } } SwingUtilities.invokeLater( new Runnable(){ public void run() { PerspectiveView.this.invalidate(); } }); } class SelectedLinkValueComparator implements Comparator<LinkValue> { private List<String> selected; public SelectedLinkValueComparator(List<String> selectedValues ) { selected = selectedValues; } public int compare(LinkValue o1, LinkValue o2) { int val1 = selected.contains( o1.text().get() ) ? 1:0; int val2 = selected.contains( o2.text().get() ) ? 1:0; int selectedCompare = val2 - val1; if (selectedCompare == 0) return o1.text().get().compareToIgnoreCase(o2.text().get()); else return selectedCompare; } } class StatusList extends JList { public StatusList() { super(new Object[] { OPEN.name(), ON_HOLD.name(), CLOSED.name() }); setCellRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { setFont(list.getFont()); setBackground(list.getBackground()); setForeground(list.getForeground()); if (model.getSelectedStatuses().contains(value)) { setIcon( icon( Icons.check, 12 )); setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0)); } else { setIcon(null); setBorder(BorderFactory.createEmptyBorder(4, 16, 0, 0)); } setText(text( valueOf( value.toString() ))); return this; } }); addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { if (!event.getValueIsAdjusting()) { String selectedValue = (String) statusList.getSelectedValue(); if (selectedValue != null) { if (model.getSelectedStatuses().contains(selectedValue)) { model.getSelectedStatuses().remove(selectedValue); } else { model.getSelectedStatuses().add(selectedValue); } statusList.clearSelection(); } } } }); } } class SortByList extends JList { public SortByList() { List<Enum> allValues = new ArrayList<Enum>(); allValues.addAll( Arrays.asList( SortBy.values() )); allValues.addAll(Arrays.asList(SortOrder.values())); setListData(allValues.toArray()); setSelectedIndex(0); setCellRenderer(new DefaultListCellRenderer(){ @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { setFont(list.getFont()); setBackground(list.getBackground()); setForeground(list.getForeground()); if (value.equals(model.getSortBy()) || value.equals(model.getSortOrder())) { setIcon( icon( Icons.check, 12 )); setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0 )); } else { setIcon(null); setBorder(BorderFactory.createEmptyBorder(4, 16, 0, 0 )); } setText(text((Enum) value)); if (index == SortBy.values().length-1) setBorder(BorderFactory.createCompoundBorder(new BottomBorder(Color.LIGHT_GRAY, 1, 3), getBorder())); return this; }}); addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { if (!event.getValueIsAdjusting()) { Enum selectedValue = (Enum) getSelectedValue(); if (selectedValue != null) { if (selectedValue instanceof SortBy) { model.setSortBy((SortBy)selectedValue); } else { model.setSortOrder((SortOrder) selectedValue); } clearSelection(); repaint(); } } } }); } } class GroupByList extends JList { public GroupByList() { super(GroupBy.values()); setSelectedIndex(0); setCellRenderer(new DefaultListCellRenderer() { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { setFont(list.getFont()); setBackground(list.getBackground()); setForeground(list.getForeground()); if (value.equals(model.getGroupBy())) { setIcon( icon( Icons.check, 12 )); setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0 )); } else { setIcon(null); setBorder(BorderFactory.createEmptyBorder(4, 16, 0, 0 )); } setText(text((GroupBy)value)); return this; }}); addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { if (!event.getValueIsAdjusting()) { model.setGroupBy( (GroupBy) getSelectedValue()); } } }); } } }