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
;
66 import javax
.swing
.event
.ChangeEvent
;
67 import javax
.swing
.event
.ChangeListener
;
68 import javax
.swing
.event
.DocumentEvent
;
70 import java
.awt
.event
.ActionEvent
;
71 import java
.awt
.event
.ActionListener
;
72 import java
.awt
.event
.MouseAdapter
;
73 import java
.awt
.event
.MouseEvent
;
75 import java
.io
.IOException
;
77 import java
.util
.List
;
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);
109 setTitle(VcsBundle
.message("patch.apply.dialog.title"));
110 final FileChooserDescriptor descriptor
= new FileChooserDescriptor(true, false, false, false, false, false) {
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
) {
121 myStatusLabel
.setForeground(UIUtil
.getLabelForeground());
122 myStatusLabel
.setText(VcsBundle
.message("patch.load.progress"));
124 myMoveRenameInfo
.clear();
125 myLoadPatchAlarm
.cancelAllRequests();
126 myLoadPatchAlarm
.addRequest(new Runnable() {
128 checkLoadPatches(true);
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
);
162 myShowDiffButton
.addActionListener(new ActionListener() {
163 public void actionPerformed(final ActionEvent e
) {
167 myPatchContentsList
.addMouseListener(new MouseAdapter() {
168 public void mouseClicked(final MouseEvent e
) {
169 if (e
.getButton() == 1 && e
.getClickCount() == 2) {
177 public void actionPerformed(AnActionEvent e
) {
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
;
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) != '/') {
216 final Change change
=
217 changeForPath(beforeFile
, patch
, FilePathImpl
.createNonLocal(FileUtil
.toSystemIndependentName(sb
.toString()), false));
218 if (change
!= null) {
222 Messages
.showErrorDialog(myProject
, "Cannot show difference: cannot find file " + patch
.getBeforeName(),
223 VcsBundle
.message("patch.apply.dialog.title"));
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));
235 final Change change
= changeForPath(fileToPatch
, patch
, null);
236 if (change
!= null) {
243 catch (Exception e
) {
244 Messages
.showErrorDialog(myProject
, "Error loading changes for " + patch
.getAfterFileName() + ": " + e
.getMessage(),
245 VcsBundle
.message("patch.apply.dialog.title"));
249 ShowDiffAction
.showDiffForChange(changes
.toArray(new Change
[changes
.size()]), 0, myProject
,
250 ShowDiffAction
.DiffExtendUIFactory
.NONE
, false);
254 private Change
changeForPath(final VirtualFile fileToPatch
, final TextFilePatch patch
, final FilePath newFilePath
) {
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
);
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() {
285 if (myPatches
!= null) {
289 catch(Exception ex
) {
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
);
307 file
.refresh(false, false);
308 if (file
.isDirectory()) {
309 // we are looking for file not directory
316 if (patchFile
== null) {
317 queueUpdateStatus("Cannot find patch file");
320 myChangeListChooser
.setDefaultName(patchFile
.getNameWithoutExtension().replace('_', ' ').trim());
322 ApplicationManager
.getApplication().executeOnPooledThread(new Runnable() {
324 loadPatchesFromFile(patchFile
);
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() {
338 if (!patchFile
.isValid()) {
339 queueUpdateStatus("Cannot find patch file");
344 reader
= new PatchReader(patchFile
);
346 catch (IOException e
) {
347 queueUpdateStatus(VcsBundle
.message("patch.apply.open.error", e
.getMessage()));
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()));
360 queueUpdateStatus(VcsBundle
.message("patch.apply.load.error", e
.getMessage()));
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"));
381 autoDetectBaseDirectory();
382 queueUpdateStatus(null);
387 private void autoDetectBaseDirectory() {
388 boolean autodetectFailed
= false;
389 for(FilePatch patch
: myPatches
) {
390 VirtualFile baseDir
= myDetectedBaseDirectory
== null
392 : LocalFileSystem
.getInstance().findFileByPath(myDetectedBaseDirectory
.replace(File
.separatorChar
, '/'));
393 int skipTopDirs
= myDetectedStripLeadingDirs
>= 0 ? myDetectedStripLeadingDirs
: 0;
394 VirtualFile fileToPatch
;
396 fileToPatch
= patch
.findFileToPatch(new ApplyPatchContext(baseDir
, skipTopDirs
, false, false));
398 catch (IOException e
) {
399 myPatchesFailedToLoad
.add(patch
);
402 if (fileToPatch
== null) {
403 boolean success
= false;
404 if (!autodetectFailed
) {
405 String oldDetectedBaseDirectory
= myDetectedBaseDirectory
;
406 int oldDetectedStripLeadingDirs
= myDetectedStripLeadingDirs
;
407 success
= detectDirectory(patch
);
409 if ((oldDetectedBaseDirectory
!= null && !Comparing
.equal(oldDetectedBaseDirectory
, myDetectedBaseDirectory
)) ||
410 (oldDetectedStripLeadingDirs
>= 0 && oldDetectedStripLeadingDirs
!= myDetectedStripLeadingDirs
)) {
411 myDetectedBaseDirectory
= null;
412 myDetectedStripLeadingDirs
= -1;
413 autodetectFailed
= true;
418 myPatchesFailedToLoad
.add(patch
);
424 private boolean detectDirectory(final FilePatch patch
) {
425 if (patch
.getBeforeName().equals(patch
.getAfterName()) && patch
.isNewFile()) {
428 boolean success
= detectDirectoryByName(patch
.getBeforeName());
430 success
= detectDirectoryByName(patch
.getAfterName());
436 private Collection
<String
> verifyPatchPaths() {
437 final ApplyPatchContext context
= getApplyPatchContext();
438 myPatchesFailedToLoad
.clear();
439 for(FilePatch patch
: myPatches
) {
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() {
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
;
468 private void queueUpdateStatus(final String s
) {
469 if (!SwingUtilities
.isEventDispatchThread()) {
470 SwingUtilities
.invokeLater(new Runnable() {
472 queueUpdateStatus(s
);
480 private void updateStatus(String s
) {
481 myInnerChange
= true;
483 if (myDetectedBaseDirectory
!= null) {
484 myBaseDirectoryField
.setText(myDetectedBaseDirectory
);
485 myDetectedBaseDirectory
= null;
487 if (myDetectedStripLeadingDirs
!= -1) {
488 myStripLeadingDirectoriesSpinner
.setValue(myDetectedStripLeadingDirs
);
489 myDetectedStripLeadingDirs
= -1;
493 myInnerChange
= false;
495 myLoadPatchError
= s
;
497 myStatusLabel
.setForeground(UIUtil
.getLabelForeground());
498 myStatusLabel
.setText(buildPatchSummary());
501 myStatusLabel
.setText(s
);
502 myStatusLabel
.setForeground(Color
.red
);
504 updatePatchTableModel();
508 private void updatePatchTableModel() {
509 if (myPatches
!= null) {
510 myPatchContentsList
.setModel(new CollectionListModel(myPatches
));
513 myPatchContentsList
.setModel(new DefaultListModel());
515 myShowDiffButton
.setEnabled(myPatches
!= null && myPatches
.size() > 0);
518 private String
buildPatchSummary() {
520 int changedFiles
= 0;
521 int deletedFiles
= 0;
522 for(FilePatch patch
: myPatches
) {
523 if (patch
.isNewFile()) {
526 else if (patch
.isDeletedFile()) {
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
) {
545 summaryBuilder
.append(", ");
547 summaryBuilder
.append(VcsBundle
.message(key
, count
));
552 protected void dispose() {
553 myLoadPatchAlarm
.dispose();
554 myVerifyPatchAlarm
.dispose();
558 private void updateOKAction() {
559 setOKActionEnabled(myFileNameField
.getText().length() > 0 && myLoadPatchError
== null);
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());
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());
590 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
592 for(String dir
: missingDirs
) {
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);
611 protected JComponent
createCenterPanel() {
615 public List
<FilePatch
> getPatches() {
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
<String
>() {
654 public void consume(final String errorMessage
) {
655 setOKActionEnabled(errorMessage
== null);
656 setErrorText(errorMessage
);
661 private class PatchCellRendererPanel
extends JPanel
implements ListCellRenderer
{
662 private final PatchCellRenderer myRenderer
;
663 private final JLabel myFileTypeLabel
;
665 public PatchCellRendererPanel() {
666 super(new BorderLayout());
667 setBorder(BorderFactory
.createEmptyBorder(0, 0, 0, 2));
668 myRenderer
= new PatchCellRenderer();
669 add(myRenderer
, BorderLayout
.CENTER
);
670 myFileTypeLabel
= new JLabel();
671 myFileTypeLabel
.setHorizontalAlignment(JLabel
.RIGHT
);
672 add(myFileTypeLabel
, BorderLayout
.EAST
);
675 public Component
getListCellRendererComponent(JList list
, Object value
, int index
, boolean isSelected
, boolean cellHasFocus
) {
676 FilePatch patch
= (FilePatch
) value
;
677 myRenderer
.getListCellRendererComponent(list
, value
, index
, isSelected
, false);
678 myFileTypeLabel
.setText("(" + getChangeType(patch
) + ")");
680 setBackground(UIUtil
.getListSelectionBackground());
681 setForeground(UIUtil
.getListSelectionForeground());
682 myFileTypeLabel
.setForeground(UIUtil
.getListSelectionForeground());
685 setBackground(UIUtil
.getListBackground());
686 setForeground(UIUtil
.getListForeground());
687 myFileTypeLabel
.setForeground(Color
.gray
);
693 private class PatchCellRenderer
extends ColoredListCellRenderer
{
694 private final SimpleTextAttributes myNewAttributes
= new SimpleTextAttributes(0, FileStatus
.ADDED
.getColor());
695 private final SimpleTextAttributes myDeletedAttributes
= new SimpleTextAttributes(0, FileStatus
.DELETED
.getColor());
696 private final SimpleTextAttributes myModifiedAttributes
= new SimpleTextAttributes(0, FileStatus
.MODIFIED
.getColor());
698 private boolean assumeProblemWillBeFixed(final FilePatch filePatch
) {
699 // if some of the files are valid, assume that "red" new files will be fixed by creating directories
700 if (myPatches
== null || myPatchesFailedToLoad
== null) return false;
701 return (filePatch
.isNewFile() && myPatchesFailedToLoad
.size() != myPatches
.size());
704 protected void customizeCellRenderer(JList list
, Object value
, int index
, boolean selected
, boolean hasFocus
) {
705 FilePatch filePatch
= (FilePatch
) value
;
706 String name
= filePatch
.getAfterNameRelative(getStripLeadingDirectories());
708 final FileType fileType
= FileTypeManager
.getInstance().getFileTypeByFileName(name
);
709 setIcon(fileType
.getIcon());
711 if (myPatchesFailedToLoad
.contains(filePatch
) && !assumeProblemWillBeFixed(filePatch
)) {
712 append(name
, SimpleTextAttributes
.ERROR_ATTRIBUTES
);
714 else if (filePatch
.isNewFile()) {
715 append(name
, myNewAttributes
);
717 else if (filePatch
.isDeletedFile()) {
718 append(name
, myDeletedAttributes
);
721 append(name
, myModifiedAttributes
);
724 final String afterPath
= filePatch
.getAfterName();
725 final String beforePath
= filePatch
.getBeforeName();
727 if ((beforePath
!= null) && (afterPath
!= null) && (! beforePath
.equals(afterPath
))) {
728 final String message
= myMoveRenameInfo
.get(new Pair
<String
, String
>(beforePath
, afterPath
));
729 if (message
!= null) {
730 append(message
, SimpleTextAttributes
.REGULAR_ATTRIBUTES
);