hu.pagavcs.client.gui.commit.CommitGui.java Source code

Java tutorial

Introduction

Here is the source code for hu.pagavcs.client.gui.commit.CommitGui.java

Source

package hu.pagavcs.client.gui.commit;

import hu.pagavcs.client.bl.Manager;
import hu.pagavcs.client.bl.OnSwing;
import hu.pagavcs.client.bl.OnSwingWait;
import hu.pagavcs.client.bl.PagaException;
import hu.pagavcs.client.bl.SettingsStore;
import hu.pagavcs.client.bl.ThreadAction;
import hu.pagavcs.client.gui.CommitListItem;
import hu.pagavcs.client.gui.Refreshable;
import hu.pagavcs.client.gui.StatusCellRendererForCommitListItem;
import hu.pagavcs.client.gui.Working;
import hu.pagavcs.client.gui.platform.DotTextCellRenderer;
import hu.pagavcs.client.gui.platform.Frame;
import hu.pagavcs.client.gui.platform.GuiHelper;
import hu.pagavcs.client.gui.platform.Label;
import hu.pagavcs.client.gui.platform.MessagePane;
import hu.pagavcs.client.gui.platform.MessagePane.OPTIONS;
import hu.pagavcs.client.gui.platform.ProgressBar;
import hu.pagavcs.client.gui.platform.Table;
import hu.pagavcs.client.gui.platform.TableModel;
import hu.pagavcs.client.gui.platform.TextArea;
import hu.pagavcs.client.operation.Commit;
import hu.pagavcs.client.operation.Commit.CommitStatus;
import hu.pagavcs.client.operation.Commit.CommittedItemStatus;
import hu.pagavcs.client.operation.ContentStatus;
import hu.pagavcs.client.operation.Log;
import hu.pagavcs.client.operation.MergeOperation;
import hu.pagavcs.client.operation.ResolveConflict;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.prefs.BackingStoreException;

import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableRowSorter;

import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.wc.SVNInfo;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

/**
 * PagaVCS 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.<br>
 * <br>
 * PagaVCS 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.<br>
 * <br>
 * You should have received a copy of the GNU General Public License along with
 * PagaVCS; If not, see http://www.gnu.org/licenses/.
 */
public class CommitGui implements Working, Refreshable {

    private HashMap<File, List<CommitListItem>> mapDeletedHiddenFiles;

    private Frame frame;
    private String path;
    private Table<CommitListItem> tblCommit;
    private TableModel<CommitListItem> tmdlCommit;
    private Commit commit;
    private JButton btnStop;
    private TextArea taMessage;
    private JButton btnCommit;
    private Label lblUrl;
    private ProgressBar prgWorkinProgress;
    private Label lblInfo;
    private int noCommit;
    private boolean preRealCommitProcess;
    private JButton btnRefresh;
    private JComboBox cboMessage;
    private int logMinSize;
    private Label lblWorkingCopy;
    private JCheckBox btnSelectAllNone;
    private Label lblSelectedInfo;
    private JButton btnCreatePatch;
    private JCheckBox cbHelpMerge;
    private String url;
    private Semaphore refreshFinished;

    private DotTextCellRenderer pathCellRenderer;

    public CommitGui(Commit commit) {
        this.commit = commit;
        refreshFinished = new Semaphore(1, true);
    }

    public void display() throws SVNException {
        logMinSize = 0;
        CellConstraints cc = new CellConstraints();
        tmdlCommit = new TableModel<CommitListItem>(new CommitListItem());
        tblCommit = new Table<CommitListItem>(tmdlCommit);
        tblCommit.addMouseListener(new PopupupMouseListener());
        tblCommit.setRowSorter(new TableRowSorter<TableModel<CommitListItem>>(tmdlCommit));
        tblCommit.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        tblCommit.addKeyListener(new SelectDeselectSelectedKeyListener());
        tblCommit.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
        pathCellRenderer = new DotTextCellRenderer();
        tblCommit.getColumnModel().getColumn(3)
                .setCellRenderer(new StatusCellRendererForCommitListItem(tblCommit, pathCellRenderer));

        SelectDeselectListener selectDeselectListener = new SelectDeselectListener();
        tmdlCommit.addTableModelListener(selectDeselectListener);

        tblCommit.getSelectionModel().addListSelectionListener(selectDeselectListener);

        new StatusCellRendererForCommitListItem(tblCommit);
        final JScrollPane spCommitList = new JScrollPane(tblCommit);
        spCommitList.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        taMessage = new TextArea();
        taMessage.setLineWrap(true);
        taMessage.setWrapStyleWord(true);
        JScrollPane spMessage = new JScrollPane(taMessage);

        JSplitPane splMain = new JSplitPane(JSplitPane.VERTICAL_SPLIT, spMessage, spCommitList);
        splMain.setPreferredSize(new Dimension(300, 300));
        lblUrl = new Label();
        lblWorkingCopy = new Label();
        cboMessage = new JComboBox();
        cboMessage.setPreferredSize(new Dimension(100, (int) cboMessage.getPreferredSize().getHeight()));
        cboMessage.addItemListener(new ItemListener() {

            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    if (e.getItem() != null) {
                        taMessage.setText(((RecentMessageSlot) e.getItem()).message);
                    }
                }
            }
        });

        JPanel pnlTop = new JPanel(new FormLayout("r:p,2dlu,p:g", "p,2dlu,p,2dlu,p,2dlu"));
        pnlTop.add(new Label("Commit to:"), cc.xy(1, 1));
        pnlTop.add(lblUrl, cc.xy(3, 1));
        pnlTop.add(new Label("Working copy:"), cc.xy(1, 3));
        pnlTop.add(lblWorkingCopy, cc.xy(3, 3));
        pnlTop.add(new Label("Recent messages:"), cc.xy(1, 5));
        pnlTop.add(cboMessage, cc.xy(3, 5));

        btnStop = new JButton(new StopAction());
        prgWorkinProgress = new ProgressBar(this);

        lblInfo = new Label();
        btnCreatePatch = new JButton(new CreatePatchAction());
        btnCreatePatch.setEnabled(false);
        btnCommit = new JButton(new CommitAction());
        btnCommit.setEnabled(false);
        btnRefresh = new JButton(new RefreshAction());
        btnRefresh.setEnabled(false);

        btnSelectAllNone = new JCheckBox(new SelectAllNoneAction());
        lblSelectedInfo = new Label();
        cbHelpMerge = new JCheckBox("Merge too");

        JPanel pnlCheck = new JPanel(new FormLayout("p,2dlu:g,p,2dlu,p", "p"));
        pnlCheck.add(btnSelectAllNone, cc.xy(1, 1));
        pnlCheck.add(lblSelectedInfo, cc.xy(3, 1));
        pnlCheck.add(cbHelpMerge, cc.xy(5, 1));

        JPanel pnlBottom = new JPanel(new FormLayout("p,2dlu, 50dlu:g, 2dlu,p, 2dlu,p, 2dlu,p", "p,2dlu,p"));

        pnlBottom.add(pnlCheck, cc.xywh(1, 1, 9, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
        pnlBottom.add(btnRefresh, cc.xy(1, 3));
        pnlBottom.add(prgWorkinProgress, cc.xy(3, 3));
        pnlBottom.add(btnCreatePatch, cc.xy(5, 3));
        pnlBottom.add(btnStop, cc.xy(7, 3));
        pnlBottom.add(btnCommit, cc.xy(9, 3));

        FormLayout lyMain = new FormLayout("p:g", "p,2dlu,f:max(80dlu;p):g,2dlu,p");
        JPanel pnlMain = new JPanel(lyMain);
        pnlMain.add(pnlTop, cc.xy(1, 1));
        pnlMain.add(splMain, cc.xy(1, 3));
        pnlMain.add(pnlBottom, cc.xy(1, 5));

        frame = GuiHelper.createAndShowFrame(pnlMain, "Commit", "commit-app-icon.png", false);
        frame.addWindowListener(new WindowAdapter() {

            public void windowClosing(WindowEvent e) {
                commit.setCancel(true);
            }
        });
    }

    public void setLogTemplate(String strLogTemplate) {
        taMessage.setText(strLogTemplate);
    }

    public void setLogMinSize(int logMinSize) {
        this.logMinSize = logMinSize;
    }

    public synchronized void workStarted() throws Exception {
        new OnSwing() {

            protected void process() throws Exception {
                prgWorkinProgress.startProgress();
            }
        }.run();
    }

    public synchronized void workEnded() throws Exception {
        new OnSwing() {

            protected void process() throws Exception {
                prgWorkinProgress.stopProgress();
            }
        }.run();
    }

    public void setUrlLabel(String url) {
        this.url = url;
        lblUrl.setText(url);
    }

    public void setPath(String path) {
        this.path = path;

        pathCellRenderer.setTruncatePrefix(path);

        lblWorkingCopy.setText(path);
        frame.setTitlePrefix(path);
        // it's executed last, so it's enough to call the pack only here.
        frame.packLater();
    }

    public void setRecentMessages(String[] recentMessages) {
        List<RecentMessageSlot> lstRecentMessageSlot = new ArrayList<RecentMessageSlot>();
        for (String str : recentMessages) {
            RecentMessageSlot li = new RecentMessageSlot(str);
            lstRecentMessageSlot.add(li);
        }
        ComboBoxModel modelUrl = new DefaultComboBoxModel(lstRecentMessageSlot.toArray());
        cboMessage.setModel(modelUrl);
        cboMessage.setPreferredSize(new Dimension(10, (int) cboMessage.getPreferredSize().getHeight()));
    }

    public void refreshSelectedInfo() {
        int selectedCount = 0;
        List<CommitListItem> lstAllData = tblCommit.getModel().getAllData();
        for (CommitListItem li : lstAllData) {
            if (li.isSelected()) {
                selectedCount++;
            }
        }
        lblSelectedInfo.setText(selectedCount + "/" + lstAllData.size() + " selected");
    }

    public void setStatus(final CommitStatus status, final String message) throws Exception {
        new OnSwing() {

            protected void process() throws Exception {

                if (CommitStatus.FILE_LIST_GATHERING_COMPLETED.equals(status)) {

                    // remove unversioned files of conflicted items
                    HashSet<File> setRemoveUnversioned = new HashSet<File>();
                    List<CommitListItem> lstData = tmdlCommit.getAllData();
                    for (CommitListItem li : lstData) {
                        if (li.getStatus().equals(ContentStatus.CONFLICTED)) {
                            SVNInfo info = Manager.getInfo(li.getPath());
                            setRemoveUnversioned.add(info.getConflictNewFile());
                            setRemoveUnversioned.add(info.getConflictOldFile());
                            setRemoveUnversioned.add(info.getConflictWrkFile());
                        }
                    }

                    if (!setRemoveUnversioned.isEmpty()) {
                        for (CommitListItem li : new ArrayList<CommitListItem>(lstData)) {
                            if (li.getStatus().equals(ContentStatus.UNVERSIONED)
                                    && setRemoveUnversioned.contains(li.getPath())) {
                                tmdlCommit.removeLine(li);
                            }
                        }
                    }

                    refreshSelectedInfo();

                    workEnded();

                    btnSelectAllNone.setEnabled(true);

                    prgWorkinProgress.setIndeterminate(false);
                    btnStop.setEnabled(false);
                    btnCommit.setEnabled(true);
                    btnCreatePatch.setEnabled(true);
                    btnCreatePatch.setEnabled(true);
                    btnRefresh.setEnabled(true);
                    if (tmdlCommit.getAllData().isEmpty()) {
                        tblCommit.showMessage("There is nothing to commit", Manager.getIconWarning());
                    }
                }
                if (CommitStatus.INIT.equals(status)) {
                    workStarted();
                    tblCommit.showMessage("Working...", Manager.getIconInformation());

                    btnSelectAllNone.setEnabled(false);

                    mapDeletedHiddenFiles = new HashMap<File, List<CommitListItem>>();

                } else if (CommitStatus.COMMIT_COMPLETED.equals(status)) {
                    commitCompleted(message);
                } else if (CommitStatus.FAILED.equals(status)) {
                    // MessagePaune.showError(frame, "Failed",
                    // "Commit failed!");

                    tblCommit.setEnabled(true);
                    btnCommit.setEnabled(true);
                    btnCreatePatch.setEnabled(true);
                    prgWorkinProgress.setValue(0);
                    prgWorkinProgress.setIndeterminate(false);
                    prgWorkinProgress.setString("");
                    preRealCommitProcess = false;
                } else if (CommitStatus.CANCEL.equals(status)) {
                    MessagePane.showInfo(frame, "Cancelled", "Commit cancelled!");

                    tblCommit.setEnabled(true);
                    btnCommit.setEnabled(true);
                    btnCreatePatch.setEnabled(true);
                    prgWorkinProgress.setValue(0);
                    prgWorkinProgress.setIndeterminate(false);
                    prgWorkinProgress.setString("");
                    preRealCommitProcess = false;
                }
            }

        }.run();
    }

    private void commitCompleted(String message) throws BackingStoreException, SVNException, PagaException {
        CommitCompletedMessagePane.showInfo(frame, "Completed", getCommitNotifyMessage(message));

        if (cbHelpMerge.isSelected()) {
            String mergeToDir = SettingsStore.getInstance().getLastHelpMergeToDir();
            JFileChooser jc = new JFileChooser();
            if (mergeToDir != null && new File(mergeToDir).isDirectory()) {
                jc.setCurrentDirectory(new File(mergeToDir));
            } else {
                File defaultDir = new File(path);
                if (!defaultDir.isDirectory()) {
                    defaultDir = defaultDir.getParentFile();
                }
                jc.setCurrentDirectory(defaultDir);
            }
            jc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            jc.setAcceptAllFileFilterUsed(false);
            int option = jc.showOpenDialog(frame);
            GuiHelper.closeWindow(frame);

            if (option == JFileChooser.APPROVE_OPTION) {
                String selectedDir = jc.getSelectedFile().getAbsolutePath();
                SettingsStore.getInstance().setLastHelpMergeToDir(selectedDir);
                MergeOperation mergeOperation = new MergeOperation(selectedDir);
                mergeOperation.setPrefillCommitNumber(message);
                mergeOperation.setPrefillMergeFromUrl(url);
                mergeOperation.setPrefillCommitToo(true);
                mergeOperation.execute();
            }
        } else {
            GuiHelper.closeWindow(frame);
        }
    }

    private String getCommitNotifyMessage(String revision) {
        String messageTemplates = Manager.getSettings().getCommitCompletedMessageTemplates();
        if (messageTemplates != null) {
            String sep = Manager.COMMIT_COMPLETED_TEMPLATE_SEPARATOR;
            String path = commit.getPath();
            for (String tmpl : messageTemplates.split("\n")) {
                int sepIndex = tmpl.indexOf(sep);
                if (sepIndex != -1) {
                    String pattern = tmpl.substring(0, sepIndex);
                    String template = tmpl.substring(sepIndex + sep.length());
                    if (path.contains(pattern)) {
                        return MessageFormat.format(template, revision);
                    }
                }
            }
        }
        return "Revision: " + revision;
    }

    public void refresh() throws Exception {
        refreshFinished.acquire();
        new OnSwing() {

            protected void process() throws Exception {
                workStarted();
                btnStop.setEnabled(true);
                btnCommit.setEnabled(false);
                btnCreatePatch.setEnabled(false);
                btnRefresh.setEnabled(false);

                final HashMap<File, Boolean> mapOldSelectionState = new HashMap<File, Boolean>();
                for (CommitListItem li : tmdlCommit.getAllData()) {
                    mapOldSelectionState.put(li.getPath(), li.isSelected());
                }

                tmdlCommit.clear();

                new Thread(new Runnable() {

                    public void run() {
                        try {
                            commit.refresh();
                            workEnded();

                            new OnSwingWait<Object, Object>() {

                                protected Object process() throws Exception {
                                    for (CommitListItem li : tmdlCommit.getAllData()) {
                                        Boolean oldSelectionState = mapOldSelectionState.get(li.getPath());
                                        if (oldSelectionState != null) {
                                            li.setSelected(oldSelectionState);
                                        }
                                    }
                                    tblCommit.repaint();
                                    return null;
                                }
                            }.run();
                        } catch (Exception e) {
                            Manager.handle(e);
                        } finally {
                            refreshFinished.release();
                        }
                    }
                }).start();
            }

        }.run();

    }

    private boolean isParentDeleted(CommitListItem li) {
        File parent = li.getPath().getParentFile();
        while (parent != null && !mapDeletedHiddenFiles.containsKey(parent)) {
            parent = parent.getParentFile();
        }
        if (mapDeletedHiddenFiles.containsKey(parent)) {
            mapDeletedHiddenFiles.get(parent).add(li);
            return true;
        }

        return false;
    }

    public void addItem(final File file, final ContentStatus contentStatus, final ContentStatus propertyStatus,
            final String contentStatusRemark, final SVNNodeKind nodeKind) throws Exception {
        if ((contentStatus.equals(ContentStatus.NORMAL) || contentStatus.equals(ContentStatus.NONE))
                && (propertyStatus.equals(ContentStatus.NORMAL) || propertyStatus.equals(ContentStatus.NONE))) {
            return;
        }
        new OnSwing() {

            protected void process() throws Exception {
                tblCommit.hideMessage();

                CommitListItem li = new CommitListItem();

                if (contentStatus.equals(ContentStatus.MODIFIED) || contentStatus.equals(ContentStatus.ADDED)
                        || contentStatus.equals(ContentStatus.DELETED)
                        || propertyStatus.equals(ContentStatus.MODIFIED)
                        || propertyStatus.equals(ContentStatus.ADDED)) {
                    li.setSelected(true);
                } else {
                    li.setSelected(false);
                }

                li.setPath(file);
                li.setStatus(contentStatus);
                li.setPropertyStatus(propertyStatus);
                li.setStatusRemark(contentStatusRemark);
                li.setNodeKind(nodeKind);
                if (!isParentDeleted(li)) {
                    if (contentStatus.equals(ContentStatus.DELETED) && file.isDirectory()) {
                        mapDeletedHiddenFiles.put(file, new ArrayList<CommitListItem>());
                    }
                    tmdlCommit.addLine(li);
                    tblCommit.followScrollToNewItems();
                }
            }

        }.run();
    }

    public void addCommittedItem(String fileName, CommittedItemStatus itemStatus) {
        if (preRealCommitProcess) {
            prgWorkinProgress.setIndeterminate(false);
            prgWorkinProgress.getModel().setMaximum(noCommit + 1);
            prgWorkinProgress.setValue(1);
            preRealCommitProcess = false;
        }

        if (itemStatus.equals(CommittedItemStatus.ADDED) || itemStatus.equals(CommittedItemStatus.DELETED)
                || itemStatus.equals(CommittedItemStatus.MODIFIED)
                || itemStatus.equals(CommittedItemStatus.REPLACED)) {
            prgWorkinProgress.setStringPainted(true);
            prgWorkinProgress.setString(fileName);
        }

        if (itemStatus.equals(CommittedItemStatus.DELTA_SENT)) {
            prgWorkinProgress.setValue(prgWorkinProgress.getValue() + 1);
            prgWorkinProgress.setString(fileName);
        }

        if (itemStatus.equals(CommittedItemStatus.COMPLETED)) {
            prgWorkinProgress.setStringPainted(false);
            lblInfo.setText(fileName);
        }
    }

    public void resolveConflict(CommitListItem li) throws Exception {

        File file = li.getPath();
        if (file.isDirectory()) {
            MessagePane.showError(frame, "Cannot resolve conflict", "Cannot resolve conflict on directory");
            return;
        }
        new ResolveConflict(this, file.getPath(), false).execute();
    }

    /**
     * @return Creates a new list of select list items
     */
    private List<CommitListItem> getSelectedItems() {
        int[] selectedRows = tblCommit.getSelectedRows();
        ArrayList<CommitListItem> lstResult = new ArrayList<CommitListItem>(selectedRows.length);
        for (int row : selectedRows) {
            lstResult.add(tmdlCommit.getRow(tblCommit.convertRowIndexToModel(row)));
        }
        return lstResult;
    }

    private List<Integer> getSelectedItemIndeces() {
        int[] selectedRows = tblCommit.getSelectedRows();
        ArrayList<Integer> lstResult = new ArrayList<Integer>(selectedRows.length);
        for (int row : selectedRows) {
            lstResult.add(tblCommit.convertRowIndexToModel(row));
        }
        return lstResult;
    }

    private boolean addRecoursively(List<CommitListItem> lstItems) throws SVNException {
        boolean hasDirectory = false;
        for (CommitListItem li : lstItems) {
            if (li != null && li.getPath() != null && li.getPath().isDirectory()) {
                if (li.getPath().listFiles().length != 0) {
                    hasDirectory = true;
                    break;
                }
            }
        }

        boolean addRecursively = false;
        if (hasDirectory) {
            OPTIONS answer = MessagePane.executeConfirmDialog(frame, "Do you want to add recursively?");
            if (answer == OPTIONS.CANCEL) {
                return false;
            } else {
                if (answer == OPTIONS.OK) {
                    addRecursively = true;
                }
            }
        }

        for (CommitListItem li : lstItems) {
            li.setSelected(true);
            commit.add(li.getPath(), addRecursively);
        }
        return true;
    }

    private class CreatePatchAction extends ThreadAction {

        public CreatePatchAction() {
            super("Create patch");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            noCommit = 0;
            ArrayList<File> lstCommit = new ArrayList<File>();
            for (CommitListItem li : tmdlCommit.getAllData()) {
                if (li.isSelected()) {
                    lstCommit.add(li.getPath());
                    noCommit++;

                    if (li.getStatus().equals(ContentStatus.UNVERSIONED)) {
                        // auto-add unversioned items
                        commit.add(li.getPath(), false);
                    } else if (li.getStatus().equals(ContentStatus.CONFLICTED)) {
                        int modelRowIndex = tmdlCommit.getAllData().indexOf(li);
                        tblCommit.scrollRectToVisible(
                                tblCommit.getCellRect(tblCommit.convertRowIndexToView(modelRowIndex), 0, true));
                        MessagePane.showError(frame, "Cannot create patch",
                                "Cannot create patch from conflicted file! Please resolve the conflict first.");
                        return;
                    }
                }
            }
            if (noCommit == 0) {
                MessagePane.showError(frame, "Cannot create patch", "Nothing is selected for creating patch");
                return;
            }

            JFileChooser fc = new JFileChooser(new File(commit.getPath()));
            fc.setFileSelectionMode(JFileChooser.FILES_ONLY);

            int choosed = fc.showSaveDialog(frame);

            if (choosed == JFileChooser.APPROVE_OPTION) {
                File file = fc.getSelectedFile();
                if (file.getName().indexOf('.') == -1) {
                    file = new File(file.getAbsolutePath() + ".patch");
                }

                OutputStream out = new FileOutputStream(file);
                commit.createPatch(lstCommit.toArray(new File[0]), out);
            }
            MessagePane.showInfo(frame, "Patch created", "Patch created");
        }
    }

    private class CommitAction extends ThreadAction {

        private boolean needRefreshAndRecount;
        private boolean firstPass;

        public CommitAction() {
            super("Commit");
        }

        private List<File> commitHelper() throws SVNException {
            ArrayList<File> lstCommit = new ArrayList<File>();
            for (CommitListItem li : tmdlCommit.getAllData()) {
                if (li.isSelected()) {

                    if (firstPass && li.getStatus().equals(ContentStatus.UNVERSIONED)) {
                        // auto-add unversioned items
                        boolean success = addRecoursively(Arrays.asList(li));
                        if (!success) {
                            MessagePane.showError(frame, "Cancelled", "Cancelled.");
                            return null;
                        } else {
                            needRefreshAndRecount = true;
                        }

                    } else if (li.getStatus().equals(ContentStatus.CONFLICTED)) {
                        int modelRowIndex = tmdlCommit.getAllData().indexOf(li);
                        tblCommit.scrollRectToVisible(
                                tblCommit.getCellRect(tblCommit.convertRowIndexToView(modelRowIndex), 0, true));
                        MessagePane.showError(frame, "Cannot commit",
                                "Cannot commit conflicted file! Please resolve the conflict first.");
                        return null;
                    }

                    lstCommit.add(li.getPath());
                    noCommit++;
                    if (ContentStatus.DELETED.equals(li.getStatus())) {
                        List<CommitListItem> lstHiddenDeletedFiles = mapDeletedHiddenFiles.get(li);
                        if (lstHiddenDeletedFiles != null) {
                            for (CommitListItem liHidden : lstHiddenDeletedFiles) {
                                lstCommit.add(liHidden.getPath());
                                noCommit++;
                            }
                        }
                    }
                }
            }
            return lstCommit;
        }

        public void actionProcess(ActionEvent e) throws Exception {
            workStarted();
            List<File> lstCommit;
            try {
                if (taMessage.getText().trim().length() < logMinSize) {
                    MessagePane.showError(frame, "Cannot commit",
                            "Message length must be at least " + logMinSize + "!");
                    return;
                }

                Manager.getSettings().addCommitMessageForHistory(taMessage.getText().trim());

                noCommit = 0;
                needRefreshAndRecount = false;
                firstPass = true;
                lstCommit = commitHelper();
                if (lstCommit != null && needRefreshAndRecount) {
                    firstPass = false;
                    refresh();
                    refreshFinished.acquire();
                    refreshFinished.release();
                    lstCommit = commitHelper();
                }

                if (lstCommit == null) {
                    return;
                }

                if (noCommit == 0) {
                    MessagePane.showError(frame, "Cannot commit", "Nothing is selected to commit");
                    return;
                }

                tblCommit.setEnabled(false);
                btnCommit.setEnabled(false);
                btnCreatePatch.setEnabled(false);
                prgWorkinProgress.setValue(0);
                prgWorkinProgress.setIndeterminate(true);
                preRealCommitProcess = true;
            } finally {
                workEnded();
            }
            commit.doCommit(lstCommit, taMessage.getText());
        }
    }

    private class RefreshAction extends ThreadAction {

        public RefreshAction() {
            super("Refresh");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            refresh();
        }
    }

    private class StopAction extends ThreadAction {

        public StopAction() {
            super("Stop");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            new OnSwing() {

                protected void process() throws Exception {
                    btnStop.setEnabled(false);
                }

            }.run();
            prgWorkinProgress.setString("");
            commit.setCancel(true);
        }
    }

    private class AddAction extends ThreadAction {

        public AddAction() {
            super("Add");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            workStarted();
            try {
                boolean success = addRecoursively(getSelectedItems());
                if (success) {
                    refresh();
                }
            } finally {
                workEnded();
            }
        }
    }

    private class IgnoreAction extends ThreadAction {

        public IgnoreAction() {
            super("Ignore");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            workStarted();
            for (CommitListItem li : getSelectedItems()) {
                commit.ignore(li.getPath());
                // the callback from Commit doesn't work, because it knows only
                // the
                // parent dir
                // changeToIgnore(li.getPath());
            }
            refresh();
            workEnded();
        }
    }

    private class DeleteAction extends ThreadAction {

        public DeleteAction() {
            super("Delete");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            workStarted();
            for (CommitListItem li : getSelectedItems()) {
                li.setSelected(true);
                commit.delete(li.getPath());
                // changeToDeleted(li.getPath());
            }
            refresh();
            workEnded();
        }
    }

    private class ShowChangesAction extends ThreadAction {

        public ShowChangesAction(PopupupMouseListener popupupMouseListener) {
            super("Show changes");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            for (CommitListItem li : getSelectedItems()) {
                commit.showChangesFromBase(li.getPath());
            }
        }
    }

    private class RevertChangesAction extends ThreadAction {

        public RevertChangesAction() {
            super("Revert changes");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            workStarted();
            List<CommitListItem> lstFiles = getSelectedItems();

            Collections.sort(lstFiles, new Comparator<CommitListItem>() {

                public int compare(CommitListItem o1, CommitListItem o2) {
                    return o2.getPath().getAbsolutePath().compareTo(o1.getPath().getAbsolutePath());
                }
            });

            for (CommitListItem li : lstFiles) {
                li.setSelected(false);
                commit.revertChanges(li.getPath());
            }
            refresh();
            workEnded();
        }
    }

    private class ShowPropertyChangesAction extends ThreadAction {

        public ShowPropertyChangesAction() {
            super("Show property changes");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            for (CommitListItem li : getSelectedItems()) {
                commit.showPropertyChangesFromBase(li.getPath());
            }
        }
    }

    private class RevertPropertyChangesAction extends ThreadAction {

        public RevertPropertyChangesAction() {
            super("Revert property changes");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            workStarted();
            for (CommitListItem li : getSelectedItems()) {
                commit.revertPropertyChanges(li.getPath());
            }
            workEnded();
            refresh();
        }
    }

    private class ShowLog extends ThreadAction {

        public ShowLog() {
            super("Show log");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            workStarted();
            try {
                for (CommitListItem li : getSelectedItems()) {
                    new Log(li.getPath().getPath()).execute();
                }
            } catch (Exception ex) {
                Manager.handle(ex);
            }
            workEnded();
        }
    }

    private class ResolveTreeConflictUsingMergedAction extends ThreadAction {

        public ResolveTreeConflictUsingMergedAction() {
            super("Resolve tree conflict");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            for (CommitListItem li : getSelectedItems()) {
                Manager.resolveTreeConflictUsingMerged(li.getPath().getPath());
            }
            refresh();
        }
    }

    private class ResolveConflictUsingTheirsAction extends ThreadAction {

        public ResolveConflictUsingTheirsAction() {
            super("Resolve conflict using theirs");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            for (CommitListItem li : getSelectedItems()) {
                Manager.resolveConflictUsingTheirs(li.getPath().getPath());
            }
            refresh();
        }
    }

    private class ResolveConflictUsingMineAction extends ThreadAction {

        public ResolveConflictUsingMineAction() {
            super("Resolve conflict using mine");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            for (CommitListItem li : getSelectedItems()) {
                Manager.resolveConflictUsingMine(li.getPath().getPath());
            }
            refresh();
        }
    }

    private class ResolveConflictAction extends ThreadAction {

        public ResolveConflictAction() {
            super("Resolve conflict");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            for (CommitListItem li : getSelectedItems()) {
                resolveConflict(li);
            }
        }
    }

    private class CreateMissingDirAction extends ThreadAction {

        public CreateMissingDirAction() {
            super("Create missing directory");
        }

        public void actionProcess(ActionEvent e) throws Exception {
            workStarted();
            List<CommitListItem> lstFiles = getSelectedItems();

            for (CommitListItem li : lstFiles) {
                if (ContentStatus.MISSING.equals(li.getStatus())) {
                    li.getPath().mkdirs();
                }
            }
            refresh();
            workEnded();
        }
    }

    private class PopupupMouseListener extends MouseAdapter {

        private JPopupMenu ppVisible;

        public PopupupMouseListener() {
        }

        private void showPopup(MouseEvent e) {
            Point p = new Point(e.getX(), e.getY());
            int row = tblCommit.rowAtPoint(p);
            if (row == -1) {
                return;
            }

            boolean isSelected = false;
            for (int rowLi : tblCommit.getSelectedRows()) {
                if (rowLi == row) {
                    isSelected = true;
                    break;
                }
            }
            if (!isSelected) {
                tblCommit.getSelectionModel().setSelectionInterval(row, row);
            }

            HashSet<ContentStatus> setUsedStatus = new HashSet<ContentStatus>();
            HashSet<ContentStatus> setUsedPropertyStatus = new HashSet<ContentStatus>();
            int[] selectedRows = tblCommit.getSelectedRows();
            // boolean hasFiles = false;
            // boolean hasDirs = false;
            for (int rowLi : selectedRows) {
                CommitListItem li = tmdlCommit.getRow(tblCommit.convertRowIndexToModel(rowLi));
                setUsedStatus.add(li.getStatus());
                setUsedPropertyStatus.add(li.getPropertyStatus());
                // if (SVNNodeKind.DIR.equals(li.getNodeKind())) {
                // hasDirs = true;
                // } else if (SVNNodeKind.FILE.equals(li.getNodeKind())) {
                // hasFiles = true;
                // }
            }

            JPopupMenu ppMixed = new JPopupMenu();
            boolean onlyOneKind = setUsedStatus.size() == 1;

            if (setUsedStatus.contains(ContentStatus.MODIFIED)) {
                ppMixed.add(new ShowChangesAction(this));
            }

            if (setUsedStatus.contains(ContentStatus.MODIFIED) && !setUsedStatus.contains(ContentStatus.UNVERSIONED)
                    && !setUsedStatus.contains(ContentStatus.ADDED)) {
                ppMixed.add(new ShowLog());
            }

            if (setUsedStatus.contains(ContentStatus.MISSING)) {
                ppMixed.add(new CreateMissingDirAction());
            }

            if ((setUsedStatus.contains(ContentStatus.MODIFIED) || setUsedStatus.contains(ContentStatus.ADDED)
                    || setUsedStatus.contains(ContentStatus.MISSING)
                    || setUsedStatus.contains(ContentStatus.DELETED))
                    && !setUsedStatus.contains(ContentStatus.UNVERSIONED)) {
                ppMixed.add(new RevertChangesAction());
            }

            ppMixed.add(new IgnoreAction());
            if (onlyOneKind && setUsedStatus.contains(ContentStatus.UNVERSIONED)) {
                ppMixed.add(new AddAction());
            }
            ppMixed.add(new DeleteAction());

            if (setUsedPropertyStatus.contains(ContentStatus.MODIFIED)) {
                ppMixed.add(new ShowPropertyChangesAction());
                ppMixed.add(new RevertPropertyChangesAction());
            }

            if (onlyOneKind && setUsedStatus.contains(ContentStatus.CONFLICTED)) {
                ppMixed.add(new ResolveConflictUsingTheirsAction());
                ppMixed.add(new ResolveConflictUsingMineAction());
                ppMixed.add(new ResolveConflictAction());
            }
            if (onlyOneKind && setUsedStatus.contains(ContentStatus.REPLACED)) {
                ppMixed.add(new ResolveTreeConflictUsingMergedAction());
            }

            ppVisible = ppMixed;
            ppVisible.setInvoker(tblCommit);
            ppVisible.setLocation(e.getXOnScreen(), e.getYOnScreen());
            ppVisible.setVisible(true);
            e.consume();
        }

        public void mousePressed(MouseEvent e) {
            if (e.isPopupTrigger()) {
                showPopup(e);
            }
        }

        public void mouseReleased(MouseEvent e) {
            if (e.isPopupTrigger()) {
                showPopup(e);
            }
        }

        public void mouseClicked(MouseEvent e) {
            if (e.getClickCount() == 2) {
                Point p = new Point(e.getX(), e.getY());
                int row = tblCommit.rowAtPoint(p);
                if (row == -1) {
                    return;
                }
                CommitListItem selected = tmdlCommit.getRow(row);
                ContentStatus status = selected.getStatus();
                // ContentStatus propertyStatus = selected.getPropertyStatus();
                if (status.equals(ContentStatus.MODIFIED)) {
                    new ShowChangesAction(this).actionPerformed(null);
                } else if (selected.getPropertyStatus().equals(ContentStatus.MODIFIED)) {
                    new ShowPropertyChangesAction().actionPerformed(null);
                }
            }
        }
    }

    private class SelectAllNoneAction extends ThreadAction {

        public SelectAllNoneAction() {
            super("Select / deselect all");
        }

        public void actionProcess(ActionEvent e) throws Exception {

            boolean selected = btnSelectAllNone.isSelected();

            for (CommitListItem li : tmdlCommit.getAllData()) {
                if (selected) {
                    li.setSelected(true);
                } else {
                    li.setSelected(false);
                }
            }
            new OnSwing() {

                protected void process() throws Exception {
                    tblCommit.repaint();
                }

            }.run();
        }

    }

    private class SelectDeselectSelectedKeyListener extends KeyAdapter {

        public void keyTyped(KeyEvent e) {
            if (e.getKeyChar() == ' ') {
                boolean hasSelected = false;
                for (CommitListItem li : getSelectedItems()) {
                    if (li.isSelected()) {
                        hasSelected = true;
                        break;
                    }
                }
                boolean selectThem;
                if (hasSelected) {
                    selectThem = false;
                } else {
                    selectThem = true;
                }
                for (CommitListItem li : getSelectedItems()) {
                    li.setSelected(selectThem);
                }

                tblCommit.repaint();
            }
        }

    }

    private static class RecentMessageSlot {

        private static final int SHORT_MESSAGE_LENGTH = 64;
        private final String message;
        private String shortMessage;

        public RecentMessageSlot(String message) {
            this.message = message;
            shortMessage = message;
            if (shortMessage != null && shortMessage.length() > SHORT_MESSAGE_LENGTH) {
                shortMessage = shortMessage.substring(0, SHORT_MESSAGE_LENGTH) + "...";
            }
            if (shortMessage == null) {
                shortMessage = "";
            }
        }

        public String toString() {
            return shortMessage;
        }
    }

    private class SelectDeselectListener implements TableModelListener, ListSelectionListener {

        private List<Integer> lstLastSelectedItems;
        private boolean suppressListSelectionListener;

        public void valueChanged(ListSelectionEvent e) {
            if (!e.getValueIsAdjusting() && !suppressListSelectionListener) {
                lstLastSelectedItems = getSelectedItemIndeces();
            }
        }

        private boolean checkAddedSelected(CommitListItem li) {
            File parent = li.getPath().getParentFile();
            boolean changed = false;
            for (CommitListItem li2 : tblCommit.getModel().getAllData()) {
                if (parent.equals(li2.getPath())) {
                    if (ContentStatus.DELETED.equals(li2.getStatus())) {
                        li.setSelected(false);
                        changed = true;
                        break;
                    } else if (ContentStatus.IGNORED.equals(li2.getStatus())) {
                        li.setSelected(false);
                        changed = true;
                        break;
                    } else if (ContentStatus.ADDED.equals(li2.getStatus()) && !li2.isSelected()) {
                        li2.setSelected(true);
                        checkStatusSelected(li2);
                        changed = true;
                        break;
                    }
                }
            }

            return changed;
        }

        private boolean checkAddedDeSelected(CommitListItem li) {
            boolean changed = false;
            for (CommitListItem li2 : tblCommit.getModel().getAllData()) {
                if (li.getPath().equals(li2.getPath().getParentFile())) {
                    if (li2.isSelected()) {
                        li2.setSelected(false);
                        checkAddedDeSelected(li2);
                        changed = true;
                    }
                }
            }

            return changed;
        }

        private boolean checkStatusSelected(CommitListItem li) {
            boolean changed = false;
            if (ContentStatus.MISSING.equals(li.getStatus())) {
                li.setSelected(false);
                changed = true;
            } else if (ContentStatus.ADDED.equals(li.getStatus())) {
                changed = checkAddedSelected(li);
            }
            return changed;
        }

        private boolean checkStatusDeselected(CommitListItem li) {
            boolean changed = false;
            if (ContentStatus.ADDED.equals(li.getStatus())) {
                changed = checkAddedDeSelected(li);
            }
            return changed;
        }

        public void tableChanged(TableModelEvent e) {

            if (e.getColumn() == 0) {
                suppressListSelectionListener = true;

                if (lstLastSelectedItems != null) {

                    int selectedRowIndex = tblCommit.convertRowIndexToModel(tblCommit.getSelectedRow());
                    if (lstLastSelectedItems.contains(selectedRowIndex)) {
                        boolean newSelectionState = tmdlCommit.getRow(selectedRowIndex).isSelected();

                        for (Integer index : lstLastSelectedItems) {
                            CommitListItem li = tmdlCommit.getRow(index);
                            li.setSelected(newSelectionState);
                            int indexView = tblCommit.convertRowIndexToView(index);
                            tblCommit.getSelectionModel().addSelectionInterval(indexView, indexView);
                        }
                        tblCommit.repaint();
                    }
                }

                boolean changed = false;
                for (CommitListItem li : getSelectedItems()) {
                    if (li.isSelected()) {
                        changed |= checkStatusSelected(li);
                    } else {
                        changed |= checkStatusDeselected(li);
                    }
                }

                if (changed) {
                    tblCommit.repaint();
                }
            }

            refreshSelectedInfo();

            suppressListSelectionListener = false;
        }
    }

    public Window getFrame() {
        return frame;
    }

}