new Apply Patch UI
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / patch / ApplyPatchDialog.java
blob905e45c537c8940a159f3d40aa3e473d2e03f15e
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.openapi.vcs.changes.patch;
19 import com.intellij.openapi.actionSystem.AnAction;
20 import com.intellij.openapi.actionSystem.AnActionEvent;
21 import com.intellij.openapi.actionSystem.CommonShortcuts;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.command.CommandProcessor;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.diff.impl.patch.*;
26 import com.intellij.openapi.diff.impl.patch.formove.PathMerger;
27 import com.intellij.openapi.editor.Document;
28 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
29 import com.intellij.openapi.fileEditor.FileDocumentManager;
30 import com.intellij.openapi.fileTypes.FileType;
31 import com.intellij.openapi.fileTypes.FileTypeManager;
32 import com.intellij.openapi.fileTypes.FileTypes;
33 import com.intellij.openapi.fileTypes.StdFileTypes;
34 import com.intellij.openapi.help.HelpManager;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.ui.DialogWrapper;
37 import com.intellij.openapi.ui.Messages;
38 import com.intellij.openapi.ui.TextFieldWithBrowseButton;
39 import com.intellij.openapi.util.Comparing;
40 import com.intellij.openapi.util.Computable;
41 import com.intellij.openapi.util.Pair;
42 import com.intellij.openapi.util.io.FileUtil;
43 import com.intellij.openapi.vcs.FilePath;
44 import com.intellij.openapi.vcs.FilePathImpl;
45 import com.intellij.openapi.vcs.FileStatus;
46 import com.intellij.openapi.vcs.VcsBundle;
47 import com.intellij.openapi.vcs.changes.*;
48 import com.intellij.openapi.vcs.changes.actions.ShowDiffAction;
49 import com.intellij.openapi.vcs.changes.ui.ChangeListChooserPanel;
50 import com.intellij.openapi.vfs.LocalFileSystem;
51 import com.intellij.openapi.vfs.VfsUtil;
52 import com.intellij.openapi.vfs.VirtualFile;
53 import com.intellij.ui.CollectionListModel;
54 import com.intellij.ui.ColoredListCellRenderer;
55 import com.intellij.ui.DocumentAdapter;
56 import com.intellij.ui.SimpleTextAttributes;
57 import com.intellij.util.Alarm;
58 import com.intellij.util.ArrayUtil;
59 import com.intellij.util.Consumer;
60 import com.intellij.util.ui.UIUtil;
61 import org.jetbrains.annotations.NonNls;
62 import org.jetbrains.annotations.Nullable;
63 import org.jetbrains.annotations.PropertyKey;
65 import javax.swing.*;
66 import javax.swing.event.ChangeEvent;
67 import javax.swing.event.ChangeListener;
68 import javax.swing.event.DocumentEvent;
69 import java.awt.*;
70 import java.awt.event.ActionEvent;
71 import java.awt.event.ActionListener;
72 import java.awt.event.MouseAdapter;
73 import java.awt.event.MouseEvent;
74 import java.io.File;
75 import java.io.IOException;
76 import java.util.*;
77 import java.util.List;
79 /**
80 * @author yole
82 public class ApplyPatchDialog extends DialogWrapper {
83 private final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.patch.ApplyPatchDialog");
85 private JPanel myRootPanel;
86 private TextFieldWithBrowseButton myFileNameField;
87 private JLabel myStatusLabel;
88 private TextFieldWithBrowseButton myBaseDirectoryField;
89 private JSpinner myStripLeadingDirectoriesSpinner;
90 private JList myPatchContentsList;
91 private ChangeListChooserPanel myChangeListChooser;
92 private JButton myShowDiffButton;
93 private List<FilePatch> myPatches;
94 private Collection<FilePatch> myPatchesFailedToLoad;
95 private final Alarm myLoadPatchAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
96 private final Alarm myVerifyPatchAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
97 private String myLoadPatchError = null;
98 private String myDetectedBaseDirectory = null;
99 private int myDetectedStripLeadingDirs = -1;
100 private final Project myProject;
101 private boolean myInnerChange;
102 private LocalChangeList mySelectedChangeList;
104 private final Map<Pair<String, String>, String> myMoveRenameInfo;
106 public ApplyPatchDialog(Project project) {
107 super(project, true);
108 myProject = project;
109 setTitle(VcsBundle.message("patch.apply.dialog.title"));
110 final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false) {
111 @Override
112 public boolean isFileSelectable(VirtualFile file) {
113 return file.getFileType() == StdFileTypes.PATCH || file.getFileType() == FileTypes.PLAIN_TEXT;
116 myMoveRenameInfo = new HashMap<Pair<String, String>, String>();
117 myFileNameField.addBrowseFolderListener(VcsBundle.message("patch.apply.select.title"), "", project, descriptor);
118 myFileNameField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
119 protected void textChanged(DocumentEvent e) {
120 updateOKAction();
121 myStatusLabel.setForeground(UIUtil.getLabelForeground());
122 myStatusLabel.setText(VcsBundle.message("patch.load.progress"));
123 myPatches = null;
124 myMoveRenameInfo.clear();
125 myLoadPatchAlarm.cancelAllRequests();
126 myLoadPatchAlarm.addRequest(new Runnable() {
127 public void run() {
128 checkLoadPatches(true);
130 }, 400);
134 myBaseDirectoryField.setText(project.getBaseDir().getPresentableUrl());
135 myBaseDirectoryField.addBrowseFolderListener(VcsBundle.message("patch.apply.select.base.directory.title"), "", project,
136 new FileChooserDescriptor(false, true, false, false, false, false));
137 myBaseDirectoryField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
138 protected void textChanged(final DocumentEvent e) {
139 if (!myInnerChange) {
140 queueVerifyPatchPaths();
145 myStripLeadingDirectoriesSpinner.setModel(new SpinnerNumberModel(0, 0, 256, 1));
146 myStripLeadingDirectoriesSpinner.addChangeListener(new ChangeListener() {
147 public void stateChanged(final ChangeEvent e) {
148 if (!myInnerChange) {
149 queueVerifyPatchPaths();
154 myPatchContentsList.setCellRenderer(new PatchCellRendererPanel());
156 ChangeListManager changeListManager = ChangeListManager.getInstance(project);
157 myChangeListChooser.setChangeLists(changeListManager.getChangeListsCopy());
158 myChangeListChooser.setDefaultSelection(changeListManager.getDefaultChangeList());
159 myChangeListChooser.init(project);
160 init();
161 updateOKAction();
162 myShowDiffButton.addActionListener(new ActionListener() {
163 public void actionPerformed(final ActionEvent e) {
164 showDiff();
167 myPatchContentsList.addMouseListener(new MouseAdapter() {
168 public void mouseClicked(final MouseEvent e) {
169 if (e.getButton() == 1 && e.getClickCount() == 2) {
170 showDiff();
175 new AnAction() {
176 @Override
177 public void actionPerformed(AnActionEvent e) {
178 showDiff();
180 }.registerCustomShortcutSet(CommonShortcuts.getDiff(), myRootPanel, myDisposable);
183 private void showDiff() {
184 List<Change> changes = new ArrayList<Change>();
185 ApplyPatchContext context = getApplyPatchContext().getPrepareContext();
186 Object[] selection = myPatchContentsList.getSelectedValues();
187 if (selection.length == 0) {
188 if (myPatches == null) return;
189 selection = ArrayUtil.toObjectArray(myPatches);
191 for(Object o: selection) {
192 final TextFilePatch patch = (TextFilePatch) o;
193 try {
194 if (patch.isNewFile()) {
195 final FilePath newFilePath = FilePathImpl.createNonLocal(patch.getAfterName(), false);
196 final String content = patch.getNewFileText();
197 ContentRevision revision = new SimpleContentRevision(content, newFilePath, patch.getAfterVersionId());
198 changes.add(new Change(null, revision));
199 } else if ((! patch.isDeletedFile()) && (patch.getBeforeName() != null) && (patch.getAfterName() != null) &&
200 (! patch.getBeforeName().equals(patch.getAfterName()))) {
202 final VirtualFile baseDirectory = getBaseDirectory();
203 final VirtualFile beforeFile = PathMerger.getFile(baseDirectory, patch.getBeforeName());
205 if (beforeFile != null) {
206 final List<String> tail = new ArrayList<String>();
207 final VirtualFile partFile = PathMerger.getFile(baseDirectory, patch.getAfterName(), tail);
208 final StringBuilder sb = new StringBuilder(partFile.getPath());
209 for (String s : tail) {
210 if (sb.charAt(sb.length() - 1) != '/') {
211 sb.append('/');
213 sb.append(s);
216 final Change change =
217 changeForPath(beforeFile, patch, FilePathImpl.createNonLocal(FileUtil.toSystemIndependentName(sb.toString()), false));
218 if (change != null) {
219 changes.add(change);
221 } else {
222 Messages.showErrorDialog(myProject, "Cannot show difference: cannot find file " + patch.getBeforeName(),
223 VcsBundle.message("patch.apply.dialog.title"));
226 else {
227 final VirtualFile fileToPatch = patch.findFileToPatch(context);
228 if (fileToPatch != null) {
229 final FilePathImpl filePath = new FilePathImpl(fileToPatch);
230 final CurrentContentRevision currentRevision = new CurrentContentRevision(filePath);
231 if (patch.isDeletedFile()) {
232 changes.add(new Change(currentRevision, null));
234 else {
235 final Change change = changeForPath(fileToPatch, patch, null);
236 if (change != null) {
237 changes.add(change);
243 catch (Exception e) {
244 Messages.showErrorDialog(myProject, "Error loading changes for " + patch.getAfterFileName() + ": " + e.getMessage(),
245 VcsBundle.message("patch.apply.dialog.title"));
246 return;
249 ShowDiffAction.showDiffForChange(changes.toArray(new Change[changes.size()]), 0, myProject,
250 ShowDiffAction.DiffExtendUIFactory.NONE, false);
253 @Nullable
254 private Change changeForPath(final VirtualFile fileToPatch, final TextFilePatch patch, final FilePath newFilePath) {
255 try {
256 final FilePathImpl filePath = new FilePathImpl(fileToPatch);
257 final CurrentContentRevision currentRevision = new CurrentContentRevision(filePath);
258 final Document doc = FileDocumentManager.getInstance().getDocument(fileToPatch);
259 String baseContent = doc.getText();
260 StringBuilder newText = new StringBuilder();
261 patch.applyModifications(baseContent, newText);
262 ContentRevision revision = new SimpleContentRevision(newText.toString(), (newFilePath == null) ? filePath : newFilePath, patch.getAfterVersionId());
263 return new Change(currentRevision, revision);
264 } catch (ApplyPatchException e) {
265 ApplyPatchContext context = new ApplyPatchContext(getBaseDirectory(), 0, false, false);
266 // just show diff here. maybe refactor further..
267 ApplyPatchAction.mergeAgainstBaseVersion(myProject, fileToPatch, context, patch, ApplyPatchAction.ApplyPatchMergeRequestFactory.INSTANCE_READ_ONLY);
268 return null;
272 @Override
273 @NonNls
274 protected String getDimensionServiceKey() {
275 return "vcs.ApplyPatchDialog";
278 private void queueVerifyPatchPaths() {
279 myStatusLabel.setForeground(UIUtil.getLabelForeground());
280 myStatusLabel.setText(VcsBundle.message("apply.patch.progress.verifying"));
281 myVerifyPatchAlarm.cancelAllRequests();
282 myVerifyPatchAlarm.addRequest(new Runnable() {
283 public void run() {
284 try {
285 if (myPatches != null) {
286 verifyPatchPaths();
289 catch(Exception ex) {
290 LOG.error(ex);
293 }, 400);
296 public void setFileName(String fileName) {
297 myFileNameField.setText(fileName);
298 checkLoadPatches(false);
301 private void checkLoadPatches(final boolean async) {
302 final String fileName = myFileNameField.getText().replace(File.separatorChar, '/');
303 final VirtualFile patchFile = ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() {
304 public VirtualFile compute() {
305 final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileName);
306 if (file != null) {
307 file.refresh(false, false);
308 if (file.isDirectory()) {
309 // we are looking for file not directory
310 return null;
313 return file;
316 if (patchFile == null) {
317 queueUpdateStatus("Cannot find patch file");
318 return;
320 myChangeListChooser.setDefaultName(patchFile.getNameWithoutExtension().replace('_', ' ').trim());
321 if (async) {
322 ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
323 public void run() {
324 loadPatchesFromFile(patchFile);
328 else {
329 loadPatchesFromFile(patchFile);
333 private void loadPatchesFromFile(final VirtualFile patchFile) {
334 myPatches = new ArrayList<FilePatch>();
335 myPatchesFailedToLoad = new HashSet<FilePatch>();
336 ApplicationManager.getApplication().runReadAction(new Runnable() {
337 public void run() {
338 if (!patchFile.isValid()) {
339 queueUpdateStatus("Cannot find patch file");
340 return;
342 PatchReader reader;
343 try {
344 reader = new PatchReader(patchFile);
346 catch (IOException e) {
347 queueUpdateStatus(VcsBundle.message("patch.apply.open.error", e.getMessage()));
348 return;
350 while(true) {
351 FilePatch patch;
352 try {
353 patch = reader.readNextPatch();
355 catch (PatchSyntaxException e) {
356 if (e.getLine() >= 0) {
357 queueUpdateStatus(VcsBundle.message("patch.apply.load.error.line", e.getMessage(), e.getLine()));
359 else {
360 queueUpdateStatus(VcsBundle.message("patch.apply.load.error", e.getMessage()));
362 return;
364 if (patch == null) {
365 break;
368 final String beforeName = patch.getBeforeName();
369 final String afterName = patch.getAfterName();
370 final String movedMessage = RelativePathCalculator.getMovedString(beforeName, afterName);
371 if (movedMessage != null) {
372 myMoveRenameInfo.put(new Pair<String, String>(beforeName, afterName), movedMessage);
374 myPatches.add(patch);
376 if (myPatches.isEmpty()) {
377 queueUpdateStatus(VcsBundle.message("patch.apply.no.patches.found"));
378 return;
381 autoDetectBaseDirectory();
382 queueUpdateStatus(null);
387 private void autoDetectBaseDirectory() {
388 boolean autodetectFailed = false;
389 for(FilePatch patch: myPatches) {
390 VirtualFile baseDir = myDetectedBaseDirectory == null
391 ? getBaseDirectory()
392 : LocalFileSystem.getInstance().findFileByPath(myDetectedBaseDirectory.replace(File.separatorChar, '/'));
393 int skipTopDirs = myDetectedStripLeadingDirs >= 0 ? myDetectedStripLeadingDirs : 0;
394 VirtualFile fileToPatch;
395 try {
396 fileToPatch = patch.findFileToPatch(new ApplyPatchContext(baseDir, skipTopDirs, false, false));
398 catch (IOException e) {
399 myPatchesFailedToLoad.add(patch);
400 continue;
402 if (fileToPatch == null) {
403 boolean success = false;
404 if (!autodetectFailed) {
405 String oldDetectedBaseDirectory = myDetectedBaseDirectory;
406 int oldDetectedStripLeadingDirs = myDetectedStripLeadingDirs;
407 success = detectDirectory(patch);
408 if (success) {
409 if ((oldDetectedBaseDirectory != null && !Comparing.equal(oldDetectedBaseDirectory, myDetectedBaseDirectory)) ||
410 (oldDetectedStripLeadingDirs >= 0 && oldDetectedStripLeadingDirs != myDetectedStripLeadingDirs)) {
411 myDetectedBaseDirectory = null;
412 myDetectedStripLeadingDirs = -1;
413 autodetectFailed = true;
417 if (!success) {
418 myPatchesFailedToLoad.add(patch);
424 private boolean detectDirectory(final FilePatch patch) {
425 if (patch.getBeforeName().equals(patch.getAfterName()) && patch.isNewFile()) {
426 return false;
427 } else {
428 boolean success = detectDirectoryByName(patch.getBeforeName());
429 if (! success) {
430 success = detectDirectoryByName(patch.getAfterName());
432 return success;
436 private Collection<String> verifyPatchPaths() {
437 final ApplyPatchContext context = getApplyPatchContext();
438 myPatchesFailedToLoad.clear();
439 for(FilePatch patch: myPatches) {
440 try {
441 if (context.getBaseDir() == null || patch.findFileToPatch(context) == null) {
442 myPatchesFailedToLoad.add(patch);
445 catch (IOException e) {
446 myPatchesFailedToLoad.add(patch);
449 SwingUtilities.invokeLater(new Runnable() {
450 public void run() {
451 myPatchContentsList.repaint();
452 myStatusLabel.setText("");
455 return context.getMissingDirectories();
458 private boolean detectDirectoryByName(final String patchFileName) {
459 PatchBaseDirectoryDetector detector = PatchBaseDirectoryDetector.getInstance(myProject);
460 if (detector == null) return false;
461 final PatchBaseDirectoryDetector.Result result = detector.detectBaseDirectory(patchFileName);
462 if (result == null) return false;
463 myDetectedBaseDirectory = result.baseDir;
464 myDetectedStripLeadingDirs = result.stripDirs;
465 return true;
468 private void queueUpdateStatus(final String s) {
469 if (!SwingUtilities.isEventDispatchThread()) {
470 SwingUtilities.invokeLater(new Runnable() {
471 public void run() {
472 queueUpdateStatus(s);
475 return;
477 updateStatus(s);
480 private void updateStatus(String s) {
481 myInnerChange = true;
482 try {
483 if (myDetectedBaseDirectory != null) {
484 myBaseDirectoryField.setText(myDetectedBaseDirectory);
485 myDetectedBaseDirectory = null;
487 if (myDetectedStripLeadingDirs != -1) {
488 myStripLeadingDirectoriesSpinner.setValue(myDetectedStripLeadingDirs);
489 myDetectedStripLeadingDirs = -1;
492 finally {
493 myInnerChange = false;
495 myLoadPatchError = s;
496 if (s == null) {
497 myStatusLabel.setForeground(UIUtil.getLabelForeground());
498 myStatusLabel.setText(buildPatchSummary());
500 else {
501 myStatusLabel.setText(s);
502 myStatusLabel.setForeground(Color.red);
504 updatePatchTableModel();
505 updateOKAction();
508 private void updatePatchTableModel() {
509 if (myPatches != null) {
510 myPatchContentsList.setModel(new CollectionListModel(myPatches));
512 else {
513 myPatchContentsList.setModel(new DefaultListModel());
515 myShowDiffButton.setEnabled(myPatches != null && myPatches.size() > 0);
518 private String buildPatchSummary() {
519 int newFiles = 0;
520 int changedFiles = 0;
521 int deletedFiles = 0;
522 for(FilePatch patch: myPatches) {
523 if (patch.isNewFile()) {
524 newFiles++;
526 else if (patch.isDeletedFile()) {
527 deletedFiles++;
529 else {
530 changedFiles++;
533 StringBuilder summaryBuilder = new StringBuilder("<html><body><b>").append(VcsBundle.message("apply.patch.summary.title")).append("</b> ");
534 appendSummary(changedFiles, 0, summaryBuilder, "patch.summary.changed.files");
535 appendSummary(newFiles, changedFiles, summaryBuilder, "patch.summary.new.files");
536 appendSummary(deletedFiles, changedFiles + newFiles, summaryBuilder, "patch.summary.deleted.files");
537 summaryBuilder.append("</body></html>");
538 return summaryBuilder.toString();
541 private static void appendSummary(final int count, final int prevCount, final StringBuilder summaryBuilder,
542 @PropertyKey(resourceBundle = "messages.VcsBundle") final String key) {
543 if (count > 0) {
544 if (prevCount > 0) {
545 summaryBuilder.append(", ");
547 summaryBuilder.append(VcsBundle.message(key, count));
551 @Override
552 protected void dispose() {
553 myLoadPatchAlarm.dispose();
554 myVerifyPatchAlarm.dispose();
555 super.dispose();
558 private void updateOKAction() {
559 setOKActionEnabled(myFileNameField.getText().length() > 0 && myLoadPatchError == null);
562 @Override
563 protected void doOKAction() {
564 if (myPatches == null) {
565 myLoadPatchAlarm.cancelAllRequests();
566 checkLoadPatches(false);
568 if (myLoadPatchError == null) {
569 mySelectedChangeList = myChangeListChooser.getSelectedList(myProject);
570 if (mySelectedChangeList == null) return;
571 final Collection<String> missingDirs = verifyPatchPaths();
572 if (missingDirs.size() > 0 && !checkCreateMissingDirs(missingDirs)) return;
573 if (getBaseDirectory() == null) {
574 Messages.showErrorDialog(getContentPane(), "Could not find patch base directory " + myBaseDirectoryField.getText());
575 return;
577 super.doOKAction();
581 private boolean checkCreateMissingDirs(final Collection<String> missingDirs) {
582 StringBuilder messageBuilder = new StringBuilder(VcsBundle.message("apply.patch.create.dirs.prompt.header"));
583 for(String missingDir: missingDirs) {
584 messageBuilder.append(missingDir).append("\r\n");
586 messageBuilder.append(VcsBundle.message("apply.patch.create.dirs.prompt.footer"));
587 int rc = Messages.showYesNoCancelDialog(myProject, messageBuilder.toString(), VcsBundle.message("patch.apply.dialog.title"),
588 Messages.getQuestionIcon());
589 if (rc == 0) {
590 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
591 public void run() {
592 for(String dir: missingDirs) {
593 try {
594 VfsUtil.createDirectories(dir);
596 catch (IOException e) {
597 Messages.showErrorDialog(myProject, "Error creating directories: " + e.getMessage(),
598 VcsBundle.message("patch.apply.dialog.title"));
602 }, "Creating directories for new files in patch", null);
604 else if (rc != 1) {
605 return false;
607 return true;
610 @Nullable
611 protected JComponent createCenterPanel() {
612 return myRootPanel;
615 public List<FilePatch> getPatches() {
616 return myPatches;
619 private VirtualFile getBaseDirectory() {
620 if (ApplicationManager.getApplication().isDispatchThread()) {
621 return LocalFileSystem.getInstance().refreshAndFindFileByPath(FileUtil.toSystemIndependentName(myBaseDirectoryField.getText()));
623 return LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(myBaseDirectoryField.getText()));
626 private int getStripLeadingDirectories() {
627 return ((Integer) myStripLeadingDirectoriesSpinner.getValue()).intValue();
630 public ApplyPatchContext getApplyPatchContext() {
631 return new ApplyPatchContext(getBaseDirectory(), getStripLeadingDirectories(), false, false);
634 public LocalChangeList getSelectedChangeList() {
635 return mySelectedChangeList;
638 private static String getChangeType(final FilePatch filePatch) {
639 if (filePatch.isNewFile()) return VcsBundle.message("change.type.new");
640 if (filePatch.isDeletedFile()) return VcsBundle.message("change.type.deleted");
641 return VcsBundle.message("change.type.modified");
644 protected void doHelpAction() {
645 HelpManager.getInstance().invokeHelp("reference.dialogs.vcs.patch.apply");
648 protected Action[] createActions() {
649 return new Action[]{ getOKAction(), getCancelAction(), getHelpAction() };
652 private void createUIComponents() {
653 myChangeListChooser = new ChangeListChooserPanel(null, new Consumer<Boolean>() {
654 public void consume(final Boolean aBoolean) {
655 setOKActionEnabled(aBoolean);
660 private class PatchCellRendererPanel extends JPanel implements ListCellRenderer {
661 private final PatchCellRenderer myRenderer;
662 private final JLabel myFileTypeLabel;
664 public PatchCellRendererPanel() {
665 super(new BorderLayout());
666 setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 2));
667 myRenderer = new PatchCellRenderer();
668 add(myRenderer, BorderLayout.CENTER);
669 myFileTypeLabel = new JLabel();
670 myFileTypeLabel.setHorizontalAlignment(JLabel.RIGHT);
671 add(myFileTypeLabel, BorderLayout.EAST);
674 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
675 FilePatch patch = (FilePatch) value;
676 myRenderer.getListCellRendererComponent(list, value, index, isSelected, false);
677 myFileTypeLabel.setText("(" + getChangeType(patch) + ")");
678 if (isSelected) {
679 setBackground(UIUtil.getListSelectionBackground());
680 setForeground(UIUtil.getListSelectionForeground());
681 myFileTypeLabel.setForeground(UIUtil.getListSelectionForeground());
683 else {
684 setBackground(UIUtil.getListBackground());
685 setForeground(UIUtil.getListForeground());
686 myFileTypeLabel.setForeground(Color.gray);
688 return this;
692 private class PatchCellRenderer extends ColoredListCellRenderer {
693 private final SimpleTextAttributes myNewAttributes = new SimpleTextAttributes(0, FileStatus.ADDED.getColor());
694 private final SimpleTextAttributes myDeletedAttributes = new SimpleTextAttributes(0, FileStatus.DELETED.getColor());
695 private final SimpleTextAttributes myModifiedAttributes = new SimpleTextAttributes(0, FileStatus.MODIFIED.getColor());
697 private boolean assumeProblemWillBeFixed(final FilePatch filePatch) {
698 // if some of the files are valid, assume that "red" new files will be fixed by creating directories
699 if (myPatches == null || myPatchesFailedToLoad == null) return false;
700 return (filePatch.isNewFile() && myPatchesFailedToLoad.size() != myPatches.size());
703 protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) {
704 FilePatch filePatch = (FilePatch) value;
705 String name = filePatch.getAfterNameRelative(getStripLeadingDirectories());
707 final FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(name);
708 setIcon(fileType.getIcon());
710 if (myPatchesFailedToLoad.contains(filePatch) && !assumeProblemWillBeFixed(filePatch)) {
711 append(name, SimpleTextAttributes.ERROR_ATTRIBUTES);
713 else if (filePatch.isNewFile()) {
714 append(name, myNewAttributes);
716 else if (filePatch.isDeletedFile()) {
717 append(name, myDeletedAttributes);
719 else {
720 append(name, myModifiedAttributes);
723 final String afterPath = filePatch.getAfterName();
724 final String beforePath = filePatch.getBeforeName();
726 if ((beforePath != null) && (afterPath != null) && (! beforePath.equals(afterPath))) {
727 final String message = myMoveRenameInfo.get(new Pair<String, String>(beforePath, afterPath));
728 if (message != null) {
729 append(message, SimpleTextAttributes.REGULAR_ATTRIBUTES);