/* * Freeplane - mind map editor * Copyright (C) 2008 Dimitry Polivaev * * This file author is Dimitry Polivaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.freeplane.features.filter; import java.awt.Component; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.security.AccessControlException; import java.util.Vector; import javax.swing.ButtonModel; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.SwingConstants; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import org.freeplane.core.extension.IExtension; import org.freeplane.core.resources.ResourceController; import org.freeplane.core.ui.AFreeplaneAction; import org.freeplane.core.ui.IMenuContributor; import org.freeplane.core.ui.MenuBuilder; import org.freeplane.core.ui.SelectableAction; import org.freeplane.core.ui.components.FreeplaneToolBar; import org.freeplane.core.ui.components.JAutoToggleButton; import org.freeplane.core.ui.components.UITools; import org.freeplane.core.util.LogUtils; import org.freeplane.core.util.TextUtils; import org.freeplane.features.filter.condition.ASelectableCondition; import org.freeplane.features.filter.condition.ConditionFactory; import org.freeplane.features.filter.condition.DefaultConditionRenderer; import org.freeplane.features.filter.condition.ICondition; import org.freeplane.features.filter.condition.NoFilteringCondition; import org.freeplane.features.filter.condition.SelectedViewCondition; import org.freeplane.features.filter.condition.SelectedViewSnapshotCondition; import org.freeplane.features.map.IMapSelectionListener; import org.freeplane.features.map.MapController.Direction; import org.freeplane.features.map.MapModel; import org.freeplane.features.map.MapNavigationUtils; import org.freeplane.features.map.NodeModel; import org.freeplane.features.mode.Controller; import org.freeplane.features.mode.ModeController; import org.freeplane.features.ui.ToggleToolbarAction; import org.freeplane.features.ui.ViewController; import org.freeplane.n3.nanoxml.IXMLParser; import org.freeplane.n3.nanoxml.IXMLReader; import org.freeplane.n3.nanoxml.StdXMLReader; import org.freeplane.n3.nanoxml.XMLElement; import org.freeplane.n3.nanoxml.XMLParserFactory; import org.freeplane.n3.nanoxml.XMLWriter; /** * @author Dimitry Polivaev */ public class FilterController implements IMapSelectionListener, IExtension { @SuppressWarnings("serial") @SelectableAction(checkOnPopup = true) private class ToggleFilterToolbarAction extends ToggleToolbarAction { private ToggleFilterToolbarAction(String actionName, String toolbarName) { super(actionName, toolbarName); } @Override public void actionPerformed(ActionEvent event) { if(isVisible() && ! quickEditor.isInputFieldFocused() && (EventQueue.getCurrentEvent() instanceof KeyEvent)) quickEditor.focusInputField(true); else super.actionPerformed(event); } @Override protected void setVisible(final JComponent toolBar, final boolean visible) { quickEditor.addAncestorListener(new AncestorListener() { public void ancestorAdded(final AncestorEvent event) { quickEditor.focusInputField(true); quickEditor.removeAncestorListener(this); } public void ancestorMoved(final AncestorEvent event) { } public void ancestorRemoved(final AncestorEvent event) { final Component selectedComponent = Controller.getCurrentController().getMapViewManager().getSelectedComponent(); if(selectedComponent != null) selectedComponent.requestFocusInWindow(); quickEditor.removeAncestorListener(this); } }); super.setVisible(toolBar, visible); } } private class FilterChangeListener implements ListDataListener, ChangeListener { /* * (non-Javadoc) * @see * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent * ) */ public FilterChangeListener() { } public void contentsChanged(final ListDataEvent e) { if (e.getIndex0() == -1) { applyFilter(false); } } public void intervalAdded(final ListDataEvent e) { } public void intervalRemoved(final ListDataEvent e) { } public void stateChanged(final ChangeEvent e) { applyFilter(false); } } static final String FREEPLANE_FILTER_EXTENSION_WITHOUT_DOT = "mmfilter"; private static final ASelectableCondition NO_FILTERING = NoFilteringCondition.createCondition(); public static FilterController getController(Controller controller) { return (FilterController) controller.getExtension(FilterController.class); } public static FilterController getCurrentFilterController() { return getController(Controller.getCurrentController()); } public static void install() { Controller.getCurrentController().addExtension(FilterController.class, new FilterController()); } private final ButtonModel applyToVisibleNodeOnly; private ConditionFactory conditionFactory; private DefaultConditionRenderer conditionRenderer = null; // // private final Controller controller; final private FilterChangeListener filterChangeListener; private DefaultComboBoxModel filterConditions; private final FilterMenuBuilder filterMenuBuilder; private JToolBar filterToolbar; private final FilterHistory history; private Filter inactiveFilter; final private String pathToFilterFile; private ASelectableCondition selectedViewCondition; private final ButtonModel showAncestors; private final ButtonModel approximateMatchingButtonModel; private final ButtonModel caseSensitiveButtonModel; private final ButtonModel showDescendants; private final ButtonModel highlightNodes; private ASelectableCondition highlightCondition; private JComboBox activeFilterConditionComboBox; private FilterConditionEditor quickEditor; public FilterController() { Controller controller = Controller.getCurrentController(); filterMenuBuilder = new FilterMenuBuilder(this); history = new FilterHistory(); filterChangeListener = new FilterChangeListener(); showAncestors = new JToggleButton.ToggleButtonModel(); showAncestors.setSelected(true); showAncestors.addChangeListener(filterChangeListener); showDescendants = new JToggleButton.ToggleButtonModel(); showDescendants.setSelected(false); showDescendants.addChangeListener(filterChangeListener); highlightNodes = new JToggleButton.ToggleButtonModel(); highlightNodes.setSelected(false); applyToVisibleNodeOnly = new JToggleButton.ToggleButtonModel(); applyToVisibleNodeOnly.setSelected(false); approximateMatchingButtonModel = new JToggleButton.ToggleButtonModel(); approximateMatchingButtonModel.setSelected(false); caseSensitiveButtonModel = new JToggleButton.ToggleButtonModel(); caseSensitiveButtonModel.setSelected(false); controller.getMapViewManager().addMapSelectionListener(this); final AFreeplaneAction showFilterToolbar = new ToggleFilterToolbarAction("ShowFilterToolbarAction", "/filter_toolbar"); quickEditor = new FilterConditionEditor(this, 0, true); quickEditor.setEnterKeyActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { ((QuickFindAction)Controller.getCurrentController().getAction("QuickFindAction.FORWARD")).executeAction(true); if(getHighlightNodes().isSelected()){ setHighlightCondition( quickEditor.getCondition()); } } } ); controller.addAction(showFilterToolbar); controller.addAction(new ApplyNoFilteringAction(this)); controller.addAction(new ApplySelectedViewConditionAction(this)); controller.addAction(new EditFilterAction(this)); controller.addAction(new UndoFilterAction(this)); controller.addAction(new RedoFilterAction(this)); controller.addAction(new ReapplyFilterAction(this)); controller.addAction(new ShowAncestorsAction(this)); controller.addAction(new ShowDescendantsAction(this)); controller.addAction(new ApplyToVisibleAction(this)); controller.addAction(new QuickFilterAction(this, quickEditor)); controller.addAction(new QuickFindAction(this, quickEditor, Direction.BACK)); controller.addAction(new QuickFindAction(this, quickEditor, Direction.FORWARD)); controller.addAction(new QuickFindAllAction(this, quickEditor)); controller.addAction(new QuickHighlightAction(this, quickEditor)); final FindAction find = new FindAction(); controller.addAction(find); pathToFilterFile = ResourceController.getResourceController().getFreeplaneUserDirectory() + File.separator + "auto." + FilterController.FREEPLANE_FILTER_EXTENSION_WITHOUT_DOT; } private void addStandardConditions() { final ASelectableCondition noFiltering = NO_FILTERING; filterConditions.insertElementAt(noFiltering, 0); if (selectedViewCondition == null) { selectedViewCondition = SelectedViewCondition.CreateCondition(); } filterConditions.insertElementAt(selectedViewCondition, 1); if (filterConditions.getSelectedItem() == null) { filterConditions.setSelectedItem(noFiltering); } } /** */ public void afterMapChange(final MapModel oldMap, final MapModel newMap) { if(filterToolbar == null){ return; } history.clear(); if (newMap != null) { filterToolbar.setEnabled(true); activeFilterConditionComboBox.setEnabled(true); quickEditor.setEnabled(true); quickEditor.mapChanged(newMap); final Filter filter = newMap.getFilter(); updateSettingsFromFilter(filter); } else { filterConditions.setSelectedItem(filterConditions.getElementAt(0)); filterToolbar.setEnabled(false); quickEditor.setEnabled(false); activeFilterConditionComboBox.setEnabled(false); } } void applyFilter(final boolean force) { final ASelectableCondition selectedCondition = getSelectedCondition(); final Filter filter = createFilter(selectedCondition); final ICondition condition = filter.getCondition(); if(condition != selectedCondition && condition instanceof ASelectableCondition) getFilterConditions().setSelectedItem(condition); else applyFilter(filter, Controller.getCurrentController().getMap(), force); } public void applyFilter(final Filter filter, MapModel map, final boolean force) { filter.applyFilter(this, map, force); history.add(filter); } public void applyNoFiltering() { getFilterConditions().setSelectedItem(NO_FILTERING); } void applySelectedViewCondition() { if (getFilterConditions().getSelectedItem() != selectedViewCondition) { getFilterConditions().setSelectedItem(selectedViewCondition); } else { applyFilter(true); } } public void beforeMapChange(final MapModel oldMap, final MapModel newMap) { } private Filter createFilter(final ASelectableCondition selectedCondition) { final ASelectableCondition filterCondition; if (selectedCondition == null || selectedCondition.equals(NO_FILTERING)) { filterCondition = null; } else if (selectedCondition.equals(selectedViewCondition)) { filterCondition = new SelectedViewSnapshotCondition(); } else { filterCondition = selectedCondition; } final Filter filter = new Filter(filterCondition, showAncestors.isSelected(), showDescendants .isSelected(), applyToVisibleNodeOnly.isSelected()); return filter; } private JToolBar createFilterToolbar() { final JToolBar filterToolbar = new FreeplaneToolBar("filter_toolbar", SwingConstants.HORIZONTAL); filterToolbar.setVisible(ResourceController.getResourceController() .getBooleanProperty("filter_toolbar_visible")); filterToolbar.putClientProperty(ViewController.VISIBLE_PROPERTY_KEY, "filter_toolbar_visible"); Controller controller = Controller.getCurrentController(); final JButton undoBtn = new JButton(controller.getAction("UndoFilterAction")); final JButton redoBtn = new JButton(controller.getAction("RedoFilterAction")); final JToggleButton showAncestorsBox = new JAutoToggleButton(controller.getAction("ShowAncestorsAction"), showAncestors); showAncestorsBox.setSelected(showAncestors.isSelected()); final JToggleButton showDescendantsBox = new JAutoToggleButton(controller.getAction("ShowDescendantsAction"), showDescendants); final JToggleButton applyToVisibleBox = new JAutoToggleButton(controller.getAction("ApplyToVisibleAction"), applyToVisibleNodeOnly); final JButton btnEdit = new JButton(controller.getAction("EditFilterAction")); activeFilterConditionComboBox = new JComboBox(getFilterConditions()); final JButton applyBtn = new JButton(controller.getAction("ReapplyFilterAction")); final JButton filterSelectedBtn = new JButton(controller.getAction("ApplySelectedViewConditionAction")); final JButton noFilteringBtn = new JButton(controller.getAction("ApplyNoFilteringAction")); final JButton applyFindPreviousBtn = new JButton(controller.getAction("QuickFindAction.BACK")); final JButton applyFindNextBtn = new JButton(controller.getAction("QuickFindAction.FORWARD")); final JButton applyQuickFilterBtn = new JButton(controller.getAction("QuickFilterAction")); final JButton applyQuickSelectBtn = new JButton(controller.getAction("QuickFindAllAction")); final JToggleButton applyQuickHighlightBtn = new JAutoToggleButton(controller.getAction("QuickHighlightAction")); filterToolbar.addSeparator(); filterToolbar.add(undoBtn); filterToolbar.add(redoBtn); filterToolbar.add(showAncestorsBox); filterToolbar.add(showDescendantsBox); filterToolbar.add(applyToVisibleBox); filterToolbar.add(activeFilterConditionComboBox); filterToolbar.add(applyBtn); filterToolbar.add(filterSelectedBtn); filterToolbar.add(noFilteringBtn); filterToolbar.add(btnEdit); filterToolbar.addSeparator(); filterToolbar.add(quickEditor); filterToolbar.add(applyFindPreviousBtn); filterToolbar.add(applyFindNextBtn); filterToolbar.add(applyQuickSelectBtn); filterToolbar.add(applyQuickFilterBtn); filterToolbar.add(applyQuickHighlightBtn); final DefaultConditionRenderer toolbarConditionRenderer = new DefaultConditionRenderer(TextUtils.getText("filter_no_filtering"), false); activeFilterConditionComboBox.setRenderer(toolbarConditionRenderer); return filterToolbar; } public Filter createTransparentFilter() { if (inactiveFilter == null) { inactiveFilter = Filter.createTransparentFilter(); } return inactiveFilter; } protected ButtonModel getApplyToVisibleNodeOnly() { return applyToVisibleNodeOnly; } public ConditionFactory getConditionFactory() { if (conditionFactory == null) { conditionFactory = new ConditionFactory(); } return conditionFactory; } DefaultConditionRenderer getConditionRenderer() { if (conditionRenderer == null) { conditionRenderer = new DefaultConditionRenderer(TextUtils.getText("filter_no_filtering"), true); } return conditionRenderer; } public DefaultComboBoxModel getFilterConditions() { if (filterConditions == null) { initConditions(); } return filterConditions; } /** */ public JToolBar getFilterToolbar() { if (filterToolbar == null) { filterToolbar = createFilterToolbar(); } return filterToolbar; } public FilterHistory getHistory() { return history; } ASelectableCondition getSelectedCondition() { return (ASelectableCondition) getFilterConditions().getSelectedItem(); } public ButtonModel getShowAncestors() { return showAncestors; } public ButtonModel getShowDescendants() { return showDescendants; } public ButtonModel getHighlightNodes() { return highlightNodes; } public ASelectableCondition getHighlightCondition() { return highlightCondition; } void setHighlightCondition(final ASelectableCondition condition) { if(condition != null){ this.highlightCondition = condition; getHighlightNodes().setSelected(true); } else{ this.highlightCondition = null; } final Component mapViewComponent = Controller.getCurrentController().getMapViewManager().getMapViewComponent(); if(mapViewComponent != null) mapViewComponent.repaint(); } private void initConditions() { filterConditions = new DefaultComboBoxModel(); addStandardConditions(); filterConditions.setSelectedItem(filterConditions.getElementAt(0)); filterConditions.addListDataListener(filterChangeListener); } public void loadDefaultConditions() { try { loadConditions(getFilterConditions(), pathToFilterFile); } catch (final Exception e) { LogUtils.severe(e); } } void loadConditions(final DefaultComboBoxModel filterConditionModel, final String pathToFilterFile) throws IOException { try { final IXMLParser parser = XMLParserFactory.createDefaultXMLParser(); final IXMLReader reader = new StdXMLReader(new BufferedInputStream(new FileInputStream(pathToFilterFile))); parser.setReader(reader); final XMLElement loader = (XMLElement) parser.parse(); final Vector<XMLElement> conditions = loader.getChildren(); for (int i = 0; i < conditions.size(); i++) { final ASelectableCondition condition = getConditionFactory().loadCondition(conditions.get(i)); if(condition != null){ filterConditionModel.addElement(condition); } } } catch (final FileNotFoundException e) { } catch (final AccessControlException e) { } catch (final Exception e) { LogUtils.warn(e); UITools.errorMessage(TextUtils.getText("filters_not_loaded")); } } public void saveConditions() { try { saveConditions(getFilterConditions(), pathToFilterFile); } catch (final Exception e) { LogUtils.warn(e); } } void saveConditions(final DefaultComboBoxModel filterConditionModel, final String pathToFilterFile) throws IOException { final XMLElement saver = new XMLElement(); saver.setName("filter_conditions"); final Writer writer = new FileWriter(pathToFilterFile); for (int i = 0; i < filterConditionModel.getSize(); i++) { final ASelectableCondition cond = (ASelectableCondition) filterConditionModel.getElementAt(i); if (cond != null && !(cond instanceof NoFilteringCondition)) { cond.toXml(saver); } } final XMLWriter xmlWriter = new XMLWriter(writer); xmlWriter.write(saver, true); writer.close(); } void setFilterConditions(final DefaultComboBoxModel newConditionModel) { filterConditions.removeListDataListener(filterChangeListener); filterConditions.removeAllElements(); for (int i = 0; i < newConditionModel.getSize(); i++) { filterConditions.addElement(newConditionModel.getElementAt(i)); } filterConditions.setSelectedItem(newConditionModel.getSelectedItem()); addStandardConditions(); filterConditions.addListDataListener(filterChangeListener); applyFilter(false); Controller controller = Controller.getCurrentController(); final ModeController modeController = controller.getModeController(); final MenuBuilder menuBuilder = modeController.getUserInputListenerFactory().getMenuBuilder(); filterMenuBuilder.updateMenus(modeController, menuBuilder); } private void updateSettingsFromFilter(final Filter filter) { getFilterConditions().removeListDataListener(filterChangeListener); showAncestors.removeChangeListener(filterChangeListener); showDescendants.removeChangeListener(filterChangeListener); filterConditions.setSelectedItem(filter.getCondition()); showAncestors.setSelected(filter.areAncestorsShown()); showDescendants.setSelected(filter.areDescendantsShown()); applyToVisibleNodeOnly.setSelected(filter.appliesToVisibleNodesOnly()); filterConditions.addListDataListener(filterChangeListener); showAncestors.addChangeListener(filterChangeListener); showDescendants.addChangeListener(filterChangeListener); } void updateSettingsFromHistory() { final Filter filter = history.getCurrentFilter(); updateSettingsFromFilter(filter); } NodeModel findNext(final NodeModel from, final NodeModel end, final Direction direction, final ASelectableCondition condition) { NodeModel next = from; for (;;) { do { switch (direction) { case FORWARD: case FORWARD_N_FOLD: next = MapNavigationUtils.findNext(direction, next, end); break; case BACK: case BACK_N_FOLD: next = MapNavigationUtils.findPrevious(direction, next, end); break; } if (next == null) { return null; } } while (!next.isVisible()); if (next == from) { break; } if (condition == null || condition.checkNode(next)) { return next; } } return null; } public void redo() { history.redo(); updateSettingsFromHistory(); } public void undo() { history.undo(); updateSettingsFromHistory(); } public boolean isNodeHighlighted(NodeModel node) { return highlightCondition != null && highlightCondition.checkNode(node); } public ButtonModel getApproximateMatchingButtonModel() { return approximateMatchingButtonModel; } public ButtonModel getCaseSensitiveButtonModel() { return caseSensitiveButtonModel; } public void apply(ASelectableCondition condition) { final DefaultComboBoxModel filterConditions = getFilterConditions(); if(condition.equals(filterConditions.getSelectedItem())) applyFilter(true); else filterConditions.setSelectedItem(condition); } public IMenuContributor getMenuContributor() { return filterMenuBuilder; } }