package de.renew.navigator.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.dnd.DropTarget;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.SwingWorker;
import javax.swing.ToolTipManager;
import javax.swing.border.EmptyBorder;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import bibliothek.gui.dock.common.DefaultSingleCDockable;

import CH.ifa.draw.DrawPlugin;
import de.renew.draw.ui.ontology.DrawingEditor;
import de.renew.navigator.FilesystemController;
import de.renew.navigator.NavigatorAction;
import de.renew.navigator.NavigatorConfigurator;
import de.renew.navigator.NavigatorExtension;
import de.renew.navigator.NavigatorGui;
import de.renew.navigator.NavigatorPlugin;
import de.renew.navigator.events.DirectoryOpeningEvent;
import de.renew.navigator.gui.actions.AddFilesAction;
import de.renew.navigator.gui.actions.CollapseAllAction;
import de.renew.navigator.gui.actions.ExpandAction;
import de.renew.navigator.gui.actions.OpenHomeAction;
import de.renew.navigator.gui.actions.OpenNetPathAction;
import de.renew.navigator.gui.actions.RefreshAllAction;
import de.renew.navigator.gui.actions.RemoveAllAction;
import de.renew.navigator.gui.actions.RemoveOneAction;
import de.renew.navigator.gui.filters.JavaSearchFilter;
import de.renew.navigator.gui.filters.RNWSearchFilter;
import de.renew.navigator.models.BackgroundTask;
import de.renew.navigator.models.Directory;
import de.renew.navigator.models.Leaf;
import de.renew.navigator.models.Model;
import de.renew.navigator.models.NavigatorFileTree;
import de.renew.navigator.models.SearchFilter;
import de.renew.navigator.models.TreeElement;
import de.renew.plugin.PluginProperties;
import de.renew.util.StringUtil;
import de.renew.windowmanagement.Workbench;
import de.renew.windowmanagement.WorkbenchImpl;


/**
 * The NavigatorGUI class contains all GUI related implementation for the
 * navigator. It is based upon a JFrame and setting up the interface, including
 * all viewable parts of the Navigator. The main component is the JTree,
 * containing all the opened FileTreeNodes.
 * <p/>
 * This class offers methods to access and manipulate the tree as well as
 * opening files in Renew. For this purpose, multiple node selection is
 * supported. These methods are mainly in use by the registered listeners of
 * this GUI.
 *
 * @author Hannes Ahrens (4ahrens)
 * @version March 2009
 */
class NavigatorGuiImpl implements NavigatorGui, Observer {
    public static final long serialVersionUID = 97936353687387679L;
    private static final org.apache.log4j.Logger LOGGER =
        org.apache.log4j.Logger.getLogger(NavigatorGuiImpl.class);
    private final FilesystemController _filesystem;
    private final NavigatorFileTree _model;
    private final List<NavigatorAction> _actions;
    private final JPanel _menuPanel = new JPanel();
    private final JTextField _searchField = new JTextField();
    private final DefaultMutableTreeNode _rootNode = new DefaultMutableTreeNode("rootNode");
    private final DefaultTreeModel _treeModel = new DefaultTreeModel(_rootNode);
    private final JTree _tree = new JTree(_treeModel);
    private final JPanel _filterPanel = new JPanel();
    private final JPanel _tasksPanel = new JPanel();
    private final HashMap<NavigatorAction, Component> _actionMapper =
        new HashMap<NavigatorAction, Component>();
    private final DefaultFileTreeCellRenderer _treeCellRenderer = new DefaultFileTreeCellRenderer();
    private final List<NavigatorExtension> _extensions;
    public boolean _initialized = false;
    private boolean _updating = false;
    private final JPanel _pane;
    private final DefaultSingleCDockable _dockable;
    private final Workbench _workbench;
    private final PluginProperties _plugincfg;

    /**
     * The constructor only sets the name of the main frame of this navigator.
     */
    public NavigatorGuiImpl(
        final FilesystemController filesystem, Workbench workbench, final NavigatorPlugin plugin)
    {
        // super("Navigator");
        this._model = plugin.getModel();
        _model.addObserver(this);

        _plugincfg = plugin.getProperties();

        this._actions = new ArrayList<NavigatorAction>();
        this._filesystem = filesystem;
        _extensions = new LinkedList<NavigatorExtension>();

        _pane = new JPanel();

        _pane.setPreferredSize(new Dimension(320, 640));
        // this.setLocation(0, 150);

        // Set Navigator icon image.
        // this.setIconImage(Iconkit.instance()
        // .getImage(NavigatorIcons.ICON_NAVIGATOR));

        // Set Window layout.
        final Dimension barDimension = new Dimension(-1, 20);
        _pane.setLayout(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.BOTH;
        c.gridx = 0;
        c.ipadx = 0;
        c.weightx = 1;
        c.weighty = 0;

        // Create the menu panel.
        _menuPanel.setLayout(new BoxLayout(_menuPanel, BoxLayout.LINE_AXIS));
        _menuPanel.setPreferredSize(barDimension);
        _menuPanel.setMaximumSize(barDimension);

        // Align menu panel.
        c.gridy = 0;
        _pane.add(_menuPanel, c);
        initMenuBar();

        // Create the filter panel.
        _filterPanel.setLayout(new BoxLayout(_filterPanel, BoxLayout.LINE_AXIS));
        _filterPanel.setPreferredSize(barDimension);
        _filterPanel.setMaximumSize(barDimension);

        // Align filter panel.
        c.gridy = 1;
        _pane.add(_filterPanel, c);
        initFilterBar();

        // Create listeners.
        TreeMouseListener treeMouseListener = new TreeMouseListener(this);
        TreeKeyListener treeKeyListener = new TreeKeyListener(this);

        // Initialize the navigator tree
        ToolTipManager.sharedInstance().registerComponent(_tree);
        _tree.setRootVisible(false);
        _tree.setFocusable(true);
        _tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
        _tree.addMouseListener(treeMouseListener);
        _tree.addKeyListener(treeKeyListener);
        _tree.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 0));
        _tree.setCellRenderer(_treeCellRenderer);
        _tree.setLargeModel(true);

        JPanel panel = new JPanel(new BorderLayout());
        panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
        panel.setBackground(Color.WHITE);
        panel.add(_tree, BorderLayout.LINE_START);
        JScrollPane scrollPane = new JScrollPane(panel);
        scrollPane.getVerticalScrollBar().setUnitIncrement(10);
        scrollPane.getHorizontalScrollBar().setUnitIncrement(10);

        c.gridy = 2;
        c.weighty = 1;
        _pane.add(scrollPane, c);

        c.weighty = 0;
        c.gridy = 3;
        c.anchor = GridBagConstraints.PAGE_END;

        _tasksPanel.setLayout(new BoxLayout(_tasksPanel, BoxLayout.PAGE_AXIS));
        _pane.add(_tasksPanel, c);

        _tree.addTreeExpansionListener(new TreeExpansionListener(_model));
        new DropTarget(_pane, new DragDropListener(filesystem));
        _initialized = true;


        _dockable = new DefaultSingleCDockable("Navigator", _pane);
        _dockable.setCloseable(true);
        this._workbench = workbench;
        workbench.addViewWindow(_dockable, WorkbenchImpl.LOWER_LEFT_GROUP);
        updateGui(true);
        // pack();
    }

    @Override
    public void openWindow() {
        if (_dockable.isVisible()) {
            _dockable.toFront();
            return;
        }

        _dockable.setVisible(true);
    }

    @Override
    public void closeWindow() {
        _workbench.close(_dockable);
    }

    /**
     * @param doUpdateSearchFilter
     *            if <code>true</code>, the search filter will be updated.
     */
    public void updateGui(boolean doUpdateSearchFilter) {
        if (_updating) {
            return;
        }

        _updating = true;
        closeAll();
        for (TreeElement treeRoot : _model.getTreeRoots()) {
            final FileTreeNode node = renderNode(treeRoot);
            if (node != null) {
                _rootNode.add(node);
            }
        }

        updateTree();
        expandFromModel();

        // Set search term in gui.
        if (doUpdateSearchFilter) {
            final SearchFilter searchFilter = _model.getTextSearch();
            if (searchFilter != null && searchFilter.getTerms().size() > 0) {
                String term = searchFilter.getTerms().get(0);
                _searchField.setText(term);
            }
        }

        updateBackgroundTasks();
        _pane.repaint();
        _updating = false;
    }

    /**
     * Adds an extension to the GUI.
     *
     * @param extension
     *            The extension registered by the plugin.
     */
    @Override
    public void addExtension(NavigatorExtension extension) {
        _extensions.add(extension);
        extension.configure(new NavigatorConfigurator() {
            @Override
            public void addMenuAction(NavigatorAction action) {
                if (_actionMapper.containsKey(action)) {
                    return;
                }

                _actionMapper.put(action, createButton(_menuPanel, action));
            }

            @Override
            public void addFilterAction(NavigatorAction action) {
                if (_actionMapper.containsKey(action)) {
                    return;
                }

                _actionMapper.put(action, createButton(_filterPanel, action));
            }

            @Override
            public void addFileTreeCellRenderer(FileTreeCellRenderer renderer) {
                _treeCellRenderer.addAdditionalRenderer(renderer);
            }
        });
    }

    /**
     * Removes an extension from the GUI.
     *
     * @param extension
     *            The extension registered by the plugin.
     */
    @Override
    public boolean removeExtension(NavigatorExtension extension) {
        if (!_extensions.remove(extension)) {
            return false;
        }

        extension.configure(new NavigatorConfigurator() {
            @Override
            public void addMenuAction(NavigatorAction action) {
                _menuPanel.remove(_actionMapper.remove(action));
            }

            @Override
            public void addFilterAction(NavigatorAction action) {
                _filterPanel.remove(_actionMapper.remove(action));
            }

            @Override
            public void addFileTreeCellRenderer(FileTreeCellRenderer renderer) {
                _treeCellRenderer.removeAdditionalRenderer(renderer);
            }
        });
        _pane.repaint();
        return true;
    }

    @Override
    public DefaultFileTreeCellRenderer getTreeCellRenderer() {
        return _treeCellRenderer;
    }

    public void initFilterBar() {
        _searchField.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent keyEvent) {
                final String param = _searchField.getText();
                if (param.isEmpty()) {
                    _model.setTextSearch(null);
                } else {
                    _model.setTextSearch(
                        new SearchFilter("search", SearchFilter.Type.CONTAINS, false, param));
                }
                _model.notifyObservers(_searchField);
            }
        });

        Dimension searchFieldDimension = new Dimension(198, 20);
        _searchField.setMaximumSize(searchFieldDimension);
        _searchField.setPreferredSize(searchFieldDimension);
        _searchField.setBorder(new EmptyBorder(0, 0, 0, 0));
        _filterPanel.add(_searchField);

        // Create a filter button.
        createButton(_filterPanel, new NavigatorAction("Clear Filter", NavigatorIcons.CLEAR, null) {
            @Override
            public void actionPerformed(ActionEvent e) {
                _model.setTextSearch(null);
                _searchField.setText("");
                _model.notifyObservers(_searchField);
            }
        });

        createButton(_filterPanel, new RNWSearchFilter(_model));
        createButton(_filterPanel, new JavaSearchFilter(_model));
    }

    /**
     * Creates a new menu button.
     *
     * @param action
     *            Action of the button
     * @return the created menu button instance
     * @deprecated Use {@link #createButton(JPanel, NavigatorAction)}
     */
    @Deprecated
    public JButton createButton(String context, NavigatorAction action) {
        return createButton(getPanelForContext(context), action);
    }

    /**
     * Creates a new menu button.
     *
     * @param panel
     *            The panel to add the action to.
     * @param action
     *            Action of the button
     * @return the created menu button instance
     */
    private JButton createButton(JPanel panel, NavigatorAction action) {
        Dimension buttonDimension = new Dimension(33, 20);
        final JButton menuButton = new JButton(action);
        menuButton.setMinimumSize(buttonDimension);
        menuButton.setPreferredSize(buttonDimension);
        menuButton.setAlignmentX(Component.LEFT_ALIGNMENT);
        action.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
                // Do nothing for other properties.
                if (!propertyChangeEvent.getPropertyName().equals("active")) {
                    return;
                }

                final boolean newValue = (Boolean) propertyChangeEvent.getNewValue();
                menuButton.setSelected(newValue);
            }
        });

        panel.add(menuButton);
        panel.validate();
        panel.repaint();

        return menuButton;
    }

    /**
     * @return all selected file tree nodes.
     */
    public List<FileTreeNode> getSelectedNodes() {
        final List<FileTreeNode> result = new ArrayList<FileTreeNode>();
        final TreePath[] selectionPaths = getSelectedTreePaths();
        for (TreePath path : selectionPaths) {
            final Object component = path.getLastPathComponent();

            if (!(component instanceof FileTreeNode)) {
                continue;
            }

            result.add((FileTreeNode) component);
        }

        return result;
    }

    /**
     * @return all selected tree paths.
     */
    public TreePath[] getSelectedTreePaths() {
        final TreePath[] selectionPaths = _tree.getSelectionPaths();
        return selectionPaths == null ? new TreePath[0] : selectionPaths;
    }

    /**
     * Opens all selected files in Renew.
     * <p/>
         *
     * For opening the files, it is using the CH.ifa.draw.IOHelper.
     */
    public void openSelected() {
        final TreePath[] paths = getSelectedTreePaths();
        for (TreePath path : paths) {
            Object o = path.getLastPathComponent();
            if (o == null || !(o instanceof FileTreeNode)) {
                continue;
            }

            FileTreeNode ftn = ((FileTreeNode) o);
            final File file = ftn.getFile();

            if (file.isDirectory()) {
                continue;
            }

            SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
                @Override
                protected Void doInBackground() throws Exception {
                    if (_filesystem.isExternallyOpenedFile(file)) {
                        if (Desktop.isDesktopSupported()) {
                            try {
                                Desktop platform = Desktop.getDesktop();
                                platform.open(file);
                            } catch (IOException | UnsupportedOperationException exception) {
                                String fileExtension = StringUtil.getExtension(file.getName());

                                DrawingEditor gui = DrawPlugin.getCurrent().getDrawingEditor();
                                gui.showStatus(
                                    "Error: Files of type ." + fileExtension
                                        + " cannot be opened by renew on this platform.");
                            }
                        } else {
                            String fileExtension = StringUtil.getExtension(file.getName());

                            DrawingEditor gui = DrawPlugin.getCurrent().getDrawingEditor();
                            gui.showStatus(
                                "Error: Files of type ." + fileExtension
                                    + " cannot be opened by renew on this platform.");

                        }
                    } else {
                        //TODO: Replace deprecated getGUI()
                        DrawPlugin.getGui().openOrLoadDrawing(file.getPath()); // the actual opening process.
                    }
                    return null;
                }
            };
            worker.execute();
        }
    }

    /**
     * This method pops up a simple context menu. It currently only contains a
     * selection field to remove the MutableTreeNode of the specified location. The
     * pop-up will only show if the node is a direct child to the internal root
     * node.
     *
     * @param x
     *            tree relative x position to show the pop-up
     * @param y
     *            tree relative y position to show the pop-up
     */
    public void showContextMenu(int x, int y) {
        final TreePath path = _tree.getPathForLocation(x, y);
        if (null == path) {
            return;
        }

        final Object o = path.getLastPathComponent();
        if (o == null || !(o instanceof FileTreeNode)) {
            return;
        }

        final FileTreeNode mtn = ((FileTreeNode) o);
        JPopupMenu popupMenu = new JPopupMenu();

        // Show Remove tree action.
        if (mtn.getParent() == _rootNode) {
            final RemoveOneAction action = new RemoveOneAction(this, _model, _plugincfg);
            JMenuItem menuItem = new JMenuItem(action);
            menuItem.setText(action.getActionName());
            popupMenu.add(menuItem);
        }

        // Reveal in finder.
        final String os = System.getProperty("os.name");
        String explorer = os.equals("Mac OS X") ? "Finder" : "Explorer";
        JMenuItem reveal = new JMenuItem("Reveal in " + explorer);
        reveal.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Desktop desktop = Desktop.getDesktop();
                try {
                    File file = mtn.getFile();
                    if (file.isFile()) {
                        file = file.getParentFile();
                    }
                    desktop.open(file);
                } catch (IOException e1) {
                    JOptionPane.showMessageDialog(null, "Could not reveal.");
                }
            }
        });
        popupMenu.add(reveal);

        // Add "Create Class File" action if the selected Java source appears
        final File selectedFile = mtn.getFile();
        if (selectedFile.isFile() && selectedFile.getName().endsWith(".java")) {
            if (CreateClassFileAction.extendsNetInstanceImpl(selectedFile)) {
                JMenuItem createClass = new JMenuItem(new CreateClassFileAction(selectedFile));
                popupMenu.add(createClass);
            }
        }

        for (NavigatorExtension extension : _extensions) {
            JMenuItem menuItem2 = extension.getMenuItem(_tree, x, y, o, mtn);
            if (menuItem2 != null) {
                popupMenu.add(menuItem2);
                popupMenu.repaint();
            }
        }
        popupMenu.show(_tree, x, y);
    }

    /**
     * closeAll() removes all opened nodes from tree and triggeres autosave.
     */
    public void closeAll() {
        _rootNode.removeAllChildren();
        _treeModel.nodeStructureChanged(_rootNode);
    }

    /**
     * collapseAll() collapses all visible nodes and invalidates the rest.
     */
    @Override
    public void collapseAll() {
        LOGGER.debug("------------> COLLAPSE ALL");
        for (TreeElement rootDirectory : _model.getTreeRoots()) {
            if (rootDirectory instanceof Directory) {
                ((Directory) rootDirectory).setOpened(false);
            }
        }
        _model.notifyObservers();
    }

    /**
     * Expands recursively all child nodes to the selected node
     */
    @Override
    public void expand() {
        LOGGER.debug("------------> EXPAND RECURSIVE");
        try {
            TreePath path = _tree.getSelectionPath();
            TreeExpansionListener.setActive(false);

            if (path != null) {
                // Expand selected nodes.
                FileTreeNode tn = (FileTreeNode) path.getLastPathComponent();
                final TreeElement treeElement = tn.getModel();

                if (treeElement instanceof Directory) {
                    treeElement.expandAll();
                } else {
                    final Model parent = treeElement.getParent();
                    if (parent instanceof Directory) {
                        ((Directory) parent).expandAll();
                    }
                }
            } else {
                // Expand all.
                _model.expandAll();
            }
            _model.notifyObservers();

            TreeExpansionListener.setActive(true);
            _tree.fireTreeExpanded(path);
        } catch (Exception e) {
            // Nothing to do
        }
    }

    @Override
    public void removeSelectedNodes() {
        final List<TreeElement> selectedElements = getSelectedElements();
        for (TreeElement treeElement : selectedElements) {
            if (treeElement.getParent() instanceof NavigatorFileTree) {
                _model.remove(treeElement);
                continue;
            }

            treeElement.setExcluded(true);
        }
        _model.notifyObservers();
    }

    @Override
    public List<TreeElement> getSelectedElements() {
        final List<TreeElement> elements = new LinkedList<TreeElement>();
        final List<FileTreeNode> selectedNodes = getSelectedNodes();

        for (FileTreeNode node : selectedNodes) {
            elements.add(node.getModel());
        }

        return elements;
    }

    @Override
    public JTree getTree() {
        return this._tree;
    }

    @Override
    public DefaultMutableTreeNode getRootNode() {
        return _rootNode;
    }

    /**
     * Removes a button from the menu.
     *
     * @param button
     *            the button to remove
     */
    public void removeMenuButton(JButton button) {
        _menuPanel.remove(button);
        _menuPanel.validate();
        _menuPanel.repaint();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(
                NavigatorGuiImpl.class.getSimpleName() + ": " + "removing button: " + button);
        }
    }

    public List<NavigatorAction> getActions() {
        return _actions;
    }

    public void updateBackgroundTasks() {
        _tasksPanel.removeAll();
        for (BackgroundTask task : _model.getBackgroundTasks()) {
            renderTask(task);
        }
        _tasksPanel.revalidate();
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o == _model) {
            if (arg instanceof DirectoryOpeningEvent) {
                final DirectoryOpeningEvent event = (DirectoryOpeningEvent) arg;
                toggleNode(event.getNode(), event.isOpening());
                return;
            }

            updateGui(arg != _searchField);
        }
    }

    @Override
    public List<NavigatorExtension> getExtensions() {
        return _extensions;
    }

    /**
     * Opens or clothes a file tree node.
     */
    private void toggleNode(FileTreeNode node, boolean opening) {
        if (opening) {
            _tree.expandPath(node.getPath());
            return;
        }

        _tree.collapsePath(node.getPath());
    }

    /**
     * Repaints the tree
     */
    private void updateTree() {
        _treeModel.reload();
        _pane.validate();
        _pane.repaint();
    }

    /**
     * Expands all nodes which are expanded in the model.
     */
    private void expandFromModel() {
        for (int i = 0; i < _rootNode.getChildCount(); ++i) {
            FileTreeNode treeNode = (FileTreeNode) _rootNode.getChildAt(i);

            // tree.scrollPathToVisible(treeNode.getPath());
            for (FileTreeNode openedDir : treeNode.getOpenedDirectories()) {
                final TreePath path = openedDir.getPath();
                _tree.expandPath(path);
            }
        }
    }

    /**
     * Render a background task.
     *
     * @param task
     *            the task to render.
     */
    private void renderTask(final BackgroundTask task) {
        final JPanel panel = new JPanel();
        final JPanel leftPanel = new JPanel();
        final JLabel label = new JLabel(task.getName());
        final JButton cancel = new JButton();
        final JProgressBar progressBar = new JProgressBar();

        final Observer observer = new Observer() {
            @Override
            public void update(Observable o, Object arg) {
                if (o instanceof BackgroundTask) {
                    final BackgroundTask bg = (BackgroundTask) o;
                    label.setText(bg.getName() + " " + Math.round(bg.getCurrent() * 100) + " %");
                    cancel.setEnabled(bg.isCancelable());
                    progressBar.setMinimum(0);
                    progressBar.setMaximum(Integer.MAX_VALUE);
                    progressBar.setValue(Math.round(bg.getCurrent() * Integer.MAX_VALUE));
                    progressBar.setIndeterminate(bg.isIndeterminate());
                }
            }
        };
        task.addObserver(observer);
        observer.update(task, null);

        panel.setMinimumSize(new Dimension(-1, 50));
        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));

        leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.PAGE_AXIS));

        // Configure task label.
        label.setLabelFor(progressBar);
        label.setBackground(Color.RED);
        leftPanel.add(label);

        // Configure task progress bar.
        progressBar.setBackground(Color.BLUE);
        leftPanel.add(progressBar);

        panel.add(leftPanel);
        panel.add(Box.createRigidArea(new Dimension(10, 0)));

        cancel.setAction(new AbstractAction("Cancel") {
            @Override
            public void actionPerformed(ActionEvent e) {
                task.cancel();
            }
        });
        panel.add(cancel);
        panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

        _tasksPanel.add(panel);
    }

    /**
     * Adds the default menu buttons to the menu.
     */
    private void initMenuBar() {
        createButton(_menuPanel, new OpenHomeAction(_filesystem));
        createButton(_menuPanel, new OpenNetPathAction(_filesystem));
        createButton(_menuPanel, new AddFilesAction(_filesystem, _plugincfg));
        createButton(_menuPanel, new ExpandAction(this));
        createButton(_menuPanel, new CollapseAllAction(this, _plugincfg));
        createButton(_menuPanel, new RefreshAllAction(_filesystem, _plugincfg));
        createButton(_menuPanel, new RemoveOneAction(this, _model, _plugincfg));
        createButton(_menuPanel, new RemoveAllAction(_model, _plugincfg));
    }

    /**
     * Expands recursively all child nodes to a node
     */
    private void expandRecursively(TreePath tp, TreeNode tn) {
        Enumeration<?> children = tn.children();
        while (children.hasMoreElements()) {
            Object o = children.nextElement();

            if (o instanceof TreeNode) {
                TreeNode child = (TreeNode) o;

                if (!child.isLeaf()) {
                    expandRecursively(tp.pathByAddingChild(child), child);
                }
            }
        }
        _tree.expandPath(tp);
    }

    /**
     * @param context
     *            the context provides a specific menu.
     * @return panel belonging to the given context
     * @deprecated Use {@link #createButton(JPanel, NavigatorAction)}
     */
    @Deprecated
    private JPanel getPanelForContext(String context) {
        if (context.equals("menu")) {
            return _menuPanel;
        }

        return _filterPanel;
    }

    /**
     * Renders a model as a file tree node.
     *
     * @param model
     *            The model to render.
     * @return The node being rendered.
     */
    private FileTreeNode renderNode(TreeElement model) {
        // Do not render excluded models.
        if (model.isExcluded()) {
            return null;
        }

        if (model instanceof Directory) {
            return renderDirectoryModel((Directory) model);
        }

        if (model instanceof Leaf) {
            return renderFileNode((Leaf) model);
        }

        throw new RuntimeException("model must be either File or Directory.");
    }

    /**
     * @param model
     *            The directory model to render.
     * @return The node being rendered.
     */
    private FileTreeNode renderDirectoryModel(Directory model) {
        final FileTreeNode treeNode = new FileTreeNode(model);

        // Load children of directory.
        int numChildren = 0;
        for (TreeElement child : model.getChildren()) {
            final FileTreeNode node = renderNode(child);

            // Just add non-null tree nodes.
            if (node != null) {
                treeNode.add(node);
                numChildren++;
            }
        }

        // If a directory has no children, do not render it.
        if (numChildren == 0) {
            return null;
        }

        return treeNode;
    }

    /**
     * @param model
     *            The file model to render.
     * @return The node being rendered.
     */
    private FileTreeNode renderFileNode(Leaf model) {
        return shouldRenderFileNode(model) ? new FileTreeNode(model) : null;
    }

    /**
     * Returns true, if the given model is not filtered.
     *
     * @param model
     *            The file model to check.
     * @return true, if not filtered.
     */
    private boolean shouldRenderFileNode(Leaf model) {
        int numFilters = 0;
        final String toMatch = model.getName();

        // Match the text search filter.
        if (this._model.getTextSearch() != null && !this._model.getTextSearch().match(toMatch)) {
            return false;
        }

        // Check file filters with a logical OR.
        for (SearchFilter filter : this._model.getActiveFileFilters()) {
            if (!filter.isValid()) {
                continue;
            }

            numFilters++;
            if (filter.match(toMatch)) {
                return true;
            }
        }

        return numFilters == 0;
    }
}