IDEA-27603 (Indicate which branch is being worked on)
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / patch / ApplyPatchDifferentiatedDialog.java
blob9b68b9efec02298cecb82e8517f1dd9a7db9ed0c
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.
16 package com.intellij.openapi.vcs.changes.patch;
18 import com.intellij.openapi.actionSystem.*;
19 import com.intellij.openapi.diff.impl.patch.FilePatch;
20 import com.intellij.openapi.diff.impl.patch.PatchReader;
21 import com.intellij.openapi.diff.impl.patch.PatchSyntaxException;
22 import com.intellij.openapi.diff.impl.patch.TextFilePatch;
23 import com.intellij.openapi.fileChooser.FileChooser;
24 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
25 import com.intellij.openapi.fileChooser.FileChooserDialog;
26 import com.intellij.openapi.fileChooser.FileChooserFactory;
27 import com.intellij.openapi.fileTypes.FileTypes;
28 import com.intellij.openapi.fileTypes.StdFileTypes;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.ui.DialogWrapper;
31 import com.intellij.openapi.ui.TextFieldWithBrowseButton;
32 import com.intellij.openapi.ui.popup.JBPopupFactory;
33 import com.intellij.openapi.ui.popup.PopupStep;
34 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
35 import com.intellij.openapi.util.IconLoader;
36 import com.intellij.openapi.util.Pair;
37 import com.intellij.openapi.util.text.StringUtil;
38 import com.intellij.openapi.vcs.ObjectsConvertor;
39 import com.intellij.openapi.vcs.VcsBundle;
40 import com.intellij.openapi.vcs.ZipperUpdater;
41 import com.intellij.openapi.vcs.changes.Change;
42 import com.intellij.openapi.vcs.changes.ChangeListManager;
43 import com.intellij.openapi.vcs.changes.LocalChangeList;
44 import com.intellij.openapi.vcs.changes.actions.DiffRequestPresentable;
45 import com.intellij.openapi.vcs.changes.actions.ShowDiffAction;
46 import com.intellij.openapi.vcs.changes.ui.*;
47 import com.intellij.openapi.vfs.LocalFileSystem;
48 import com.intellij.openapi.vfs.VirtualFile;
49 import com.intellij.ui.DocumentAdapter;
50 import com.intellij.ui.SimpleColoredComponent;
51 import com.intellij.ui.SimpleTextAttributes;
52 import com.intellij.util.Consumer;
53 import com.intellij.util.containers.Convertor;
54 import org.jetbrains.annotations.NonNls;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
58 import javax.swing.*;
59 import javax.swing.event.DocumentEvent;
60 import javax.swing.tree.DefaultTreeModel;
61 import java.awt.*;
62 import java.io.File;
63 import java.io.IOException;
64 import java.util.*;
65 import java.util.List;
66 import java.util.concurrent.atomic.AtomicReference;
68 public class ApplyPatchDifferentiatedDialog extends DialogWrapper {
69 private final ZipperUpdater myLoadQueue;
70 private TextFieldWithBrowseButton myPatchFile;
72 private final List<FilePatchInProgress> myPatches;
73 private final MyChangeTreeList myChangesTreeList;
75 private JComponent myCenterPanel;
76 private JComponent mySouthPanel;
77 private final Project myProject;
79 private AtomicReference<FilePresentation> myRecentPathFileChange;
80 private ApplyPatchDifferentiatedDialog.MyUpdater myUpdater;
81 private Runnable myReset;
82 private ChangeListChooserPanel myChangeListChooser;
83 private ChangesLegendCalculator myInfoCalculator;
84 private CommitLegendPanel myCommitLegendPanel;
85 private final Consumer<ApplyPatchDifferentiatedDialog> myCallback;
87 private boolean myContainBasedChanges;
89 public ApplyPatchDifferentiatedDialog(final Project project, final Consumer<ApplyPatchDifferentiatedDialog> callback) {
90 super(project, true);
91 myCallback = callback;
92 setModal(false);
93 setTitle(VcsBundle.message("patch.apply.dialog.title"));
95 final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false) {
96 @Override
97 public boolean isFileSelectable(VirtualFile file) {
98 return file.getFileType() == StdFileTypes.PATCH || file.getFileType() == FileTypes.PLAIN_TEXT;
101 descriptor.setTitle(VcsBundle.message("patch.apply.select.title"));
102 myUpdater = new MyUpdater();
103 myPatchFile = new TextFieldWithBrowseButton();
104 myPatchFile.addBrowseFolderListener(VcsBundle.message("patch.apply.select.title"), "", project, descriptor);
105 myPatchFile.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
106 protected void textChanged(DocumentEvent e) {
107 setPathFileChangeDefault();
108 myLoadQueue.queue(myUpdater);
112 myProject = project;
113 myLoadQueue = new ZipperUpdater(500);
114 myPatches = new LinkedList<FilePatchInProgress>();
115 myRecentPathFileChange = new AtomicReference<FilePresentation>();
116 myChangesTreeList = new MyChangeTreeList(project, Collections.<FilePatchInProgress.PatchChange>emptyList(),
117 new Runnable() {
118 public void run() {
119 final NamedTrinity includedTrinity = new NamedTrinity();
120 final Collection<FilePatchInProgress.PatchChange> includedChanges = myChangesTreeList.getIncludedChanges();
121 final Set<Pair<String, String>> set = new HashSet<Pair<String, String>>();
122 for (FilePatchInProgress.PatchChange change : includedChanges) {
123 final TextFilePatch patch = change.getPatchInProgress().getPatch();
124 final Pair<String, String> pair = new Pair<String, String>(patch.getBeforeName(), patch.getAfterName());
125 if (set.contains(pair)) continue;
126 set.add(pair);
127 acceptChange(includedTrinity, change);
129 myInfoCalculator.setIncluded(includedTrinity);
130 myCommitLegendPanel.update();
132 }, new MyChangeNodeDecorator());
133 myReset = new Runnable() {
134 public void run() {
135 reset();
139 myChangeListChooser = new ChangeListChooserPanel(null, new Consumer<String>() {
140 public void consume(final String errorMessage) {
141 setOKActionEnabled(errorMessage == null);
142 setErrorText(errorMessage);
145 ChangeListManager changeListManager = ChangeListManager.getInstance(project);
146 myChangeListChooser.setChangeLists(changeListManager.getChangeListsCopy());
147 myChangeListChooser.setDefaultSelection(changeListManager.getDefaultChangeList());
148 myChangeListChooser.init(project);
150 myInfoCalculator = new ChangesLegendCalculator();
151 myCommitLegendPanel = new CommitLegendPanel(myInfoCalculator);
153 init();
154 final FileChooserDialog fileChooserDialog = FileChooserFactory.getInstance().createFileChooser(descriptor, project);
155 final VirtualFile[] files = fileChooserDialog.choose(null, project);
156 if (files != null && files.length > 0) {
157 init(files[0]);
161 @Override
162 @NonNls
163 protected String getDimensionServiceKey() {
164 return "vcs.ApplyPatchDifferentiatedDialog";
167 @Override
168 protected String getHelpId() {
169 return "reference.dialogs.vcs.patch.apply";
172 private void setPathFileChangeDefault() {
173 myRecentPathFileChange.set(new FilePresentation(myPatchFile.getText()));
176 public void init(final VirtualFile patchFile) {
177 myPatchFile.setText(patchFile.getPath());
178 myRecentPathFileChange.set(new FilePresentation(patchFile));
179 myLoadQueue.queue(myUpdater);
182 private class MyUpdater implements Runnable {
183 public void run() {
184 final FilePresentation filePresentation = myRecentPathFileChange.get();
185 if ((filePresentation == null) || (filePresentation.getVf() == null)) {
186 SwingUtilities.invokeLater(myReset);
187 return;
189 final VirtualFile file = filePresentation.getVf();
191 final List<TextFilePatch> patches = loadPatches(file);
192 final AutoMatchIterator autoMatchIterator = new AutoMatchIterator(myProject);
193 final List<FilePatchInProgress> matchedPathes = autoMatchIterator.execute(patches);
195 SwingUtilities.invokeLater(new Runnable() {
196 public void run() {
197 myChangeListChooser.setDefaultName(file.getNameWithoutExtension().replace('_', ' ').trim());
198 myPatches.clear();
199 myPatches.addAll(matchedPathes);
201 updateTree(true);
207 private List<TextFilePatch> loadPatches(final VirtualFile patchFile) {
208 if (! patchFile.isValid()) {
209 //todo
210 //queueUpdateStatus("Cannot find patch file");
211 return Collections.emptyList();
213 PatchReader reader;
214 try {
215 reader = new PatchReader(patchFile);
217 catch (IOException e) {
218 //todo
219 //queueUpdateStatus(VcsBundle.message("patch.apply.open.error", e.getMessage()));
220 return Collections.emptyList();
222 final List<TextFilePatch> result = new LinkedList<TextFilePatch>();
223 while(true) {
224 FilePatch patch;
225 try {
226 patch = reader.readNextPatch();
228 catch (PatchSyntaxException e) {
229 // todo
230 if (e.getLine() >= 0) {
231 //queueUpdateStatus(VcsBundle.message("patch.apply.load.error.line", e.getMessage(), e.getLine()));
233 else {
234 //queueUpdateStatus(VcsBundle.message("patch.apply.load.error", e.getMessage()));
236 return Collections.emptyList();
238 if (patch == null) {
239 break;
242 result.add((TextFilePatch) patch);
244 if (myPatches.isEmpty()) {
245 // todo
246 //queueUpdateStatus(VcsBundle.message("patch.apply.no.patches.found"));
248 return result;
251 private static class FilePresentation {
252 private final VirtualFile myVf;
253 private final String myPath;
255 private FilePresentation(VirtualFile vf) {
256 myVf = vf;
257 myPath = null;
260 private FilePresentation(String path) {
261 myPath = path;
262 myVf = null;
265 @Nullable
266 public VirtualFile getVf() {
267 if (myVf != null) {
268 return myVf;
270 final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(myPath);
271 return (file != null) && (! file.isDirectory()) ? file : null;
275 public void reset() {
276 myPatches.clear();
277 myChangesTreeList.setChangesToDisplay(Collections.<FilePatchInProgress.PatchChange>emptyList());
278 myChangesTreeList.repaint();
279 myContainBasedChanges = false;
282 @Override
283 protected JComponent createCenterPanel() {
284 if (myCenterPanel == null) {
285 myCenterPanel = new JPanel(new GridBagLayout());
286 final GridBagConstraints gb =
287 new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(1, 1, 1, 1), 0, 0);
289 final JLabel label = new JLabel(VcsBundle.message("create.patch.file.name.field"));
290 label.setLabelFor(myPatchFile);
291 myCenterPanel.add(label, gb);
293 ++ gb.gridx;
294 gb.fill = GridBagConstraints.HORIZONTAL;
295 gb.weightx = 1;
296 myCenterPanel.add(myPatchFile, gb);
298 gb.gridx = 0;
299 ++ gb.gridy;
300 gb.weightx = 1;
301 gb.weighty = 0;
302 gb.fill = GridBagConstraints.HORIZONTAL;
303 gb.gridwidth = 2;
305 final DefaultActionGroup group = new DefaultActionGroup();
306 final AnAction[] treeActions = myChangesTreeList.getTreeActions();
307 group.addAll(treeActions);
308 group.add(new MapDirectory());
310 final MyShowDiff diffAction = new MyShowDiff();
311 diffAction.registerCustomShortcutSet(CommonShortcuts.getDiff(), myCenterPanel);
312 group.add(diffAction);
314 group.add(new StripUp());
315 group.add(new StripDown());
316 group.add(new ResetStrip());
317 group.add(new ZeroStrip());
319 final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar("APPLY_PATCH", group, true);
320 myCenterPanel.add(toolbar.getComponent(), gb);
322 gb.gridx = 0;
323 ++ gb.gridy;
324 gb.weighty = 1;
325 gb.gridwidth = 2;
326 gb.fill = GridBagConstraints.BOTH;
327 myCenterPanel.add(myChangesTreeList, gb);
329 final JPanel wrapper = new JPanel(new GridBagLayout());
330 final GridBagConstraints gb1 =
331 new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(1, 1, 1, 1), 0, 0);
332 wrapper.add(myChangeListChooser, gb1);
333 ++ gb1.gridx;
334 gb1.fill = GridBagConstraints.NONE;
335 gb1.weightx = 0;
336 gb1.insets.left = 10;
337 wrapper.add(myCommitLegendPanel.getComponent(), gb1);
339 gb.gridx = 0;
340 ++ gb.gridy;
341 gb.weightx = 1;
342 gb.weighty = 0;
343 gb.fill = GridBagConstraints.HORIZONTAL;
344 myCenterPanel.add(wrapper, gb);
346 return myCenterPanel;
349 private static class MyChangeTreeList extends ChangesTreeList<FilePatchInProgress.PatchChange> {
350 private MyChangeTreeList(Project project,
351 Collection<FilePatchInProgress.PatchChange> initiallyIncluded,
352 @Nullable Runnable inclusionListener,
353 @Nullable ChangeNodeDecorator decorator) {
354 super(project, initiallyIncluded, true, false, inclusionListener, decorator);
357 @Override
358 protected DefaultTreeModel buildTreeModel(List<FilePatchInProgress.PatchChange> changes, ChangeNodeDecorator changeNodeDecorator) {
359 TreeModelBuilder builder = new TreeModelBuilder(myProject, false);
360 return builder.buildModel(ObjectsConvertor.convert(changes,
361 new Convertor<FilePatchInProgress.PatchChange, Change>() {
362 public Change convert(FilePatchInProgress.PatchChange o) {
363 return o;
365 }), changeNodeDecorator);
368 @Override
369 protected List<FilePatchInProgress.PatchChange> getSelectedObjects(ChangesBrowserNode<FilePatchInProgress.PatchChange> node) {
370 final List<Change> under = node.getAllChangesUnder();
371 return ObjectsConvertor.convert(under, new Convertor<Change, FilePatchInProgress.PatchChange>() {
372 public FilePatchInProgress.PatchChange convert(Change o) {
373 return (FilePatchInProgress.PatchChange) o;
378 @Override
379 protected FilePatchInProgress.PatchChange getLeadSelectedObject(ChangesBrowserNode node) {
380 final Object o = node.getUserObject();
381 if (o instanceof FilePatchInProgress.PatchChange) {
382 return (FilePatchInProgress.PatchChange) o;
384 return null;
388 private class MapDirectory extends AnAction {
389 private final NewBaseSelector myNewBaseSelector;
391 private MapDirectory() {
392 super("Map base directory", "Map base directory", IconLoader.getIcon("/vcs/mapBase.png"));
393 myNewBaseSelector = new NewBaseSelector();
396 @Override
397 public void actionPerformed(AnActionEvent e) {
398 final List<FilePatchInProgress.PatchChange> selectedChanges = myChangesTreeList.getSelectedChanges();
399 if ((selectedChanges.size() >= 1) && (sameBase(selectedChanges))) {
400 final FilePatchInProgress.PatchChange patchChange = selectedChanges.get(0);
401 final FilePatchInProgress patch = patchChange.getPatchInProgress();
402 final List<VirtualFile> autoBases = patch.getAutoBasesCopy();
403 if (autoBases.isEmpty() || (autoBases.size() == 1 && autoBases.get(0).equals(patch.getBase()))) {
404 myNewBaseSelector.run();
405 } else {
406 autoBases.add(null);
407 final MapPopup step = new MapPopup(autoBases, myNewBaseSelector);
408 JBPopupFactory.getInstance().createListPopup(step).showCenteredInCurrentWindow(myProject);
413 @Override
414 public void update(AnActionEvent e) {
415 final List<FilePatchInProgress.PatchChange> selectedChanges = myChangesTreeList.getSelectedChanges();
416 e.getPresentation().setEnabled((selectedChanges.size() >= 1) && (sameBase(selectedChanges)));
420 private boolean sameBase(final List<FilePatchInProgress.PatchChange> selectedChanges) {
421 VirtualFile base = null;
422 for (FilePatchInProgress.PatchChange change : selectedChanges) {
423 final VirtualFile changeBase = change.getPatchInProgress().getBase();
424 if (base == null) {
425 base = changeBase;
426 } else if (! base.equals(changeBase)) {
427 return false;
430 return true;
433 private void updateTree(boolean doInitCheck) {
434 final List<FilePatchInProgress> patchesToSelect = changes2patches(myChangesTreeList.getSelectedChanges());
435 final List<FilePatchInProgress.PatchChange> changes = getAllChanges();
436 final Collection<FilePatchInProgress.PatchChange> included = getIncluded(doInitCheck, changes);
438 myChangesTreeList.setChangesToDisplay(changes);
439 myChangesTreeList.setIncludedChanges(included);
440 myChangesTreeList.repaint();
441 if ((! doInitCheck) && patchesToSelect != null) {
442 final List<FilePatchInProgress.PatchChange> toSelect = new ArrayList<FilePatchInProgress.PatchChange>(patchesToSelect.size());
443 for (FilePatchInProgress.PatchChange change : changes) {
444 if (patchesToSelect.contains(change.getPatchInProgress())) {
445 toSelect.add(change);
448 myChangesTreeList.select(toSelect);
451 myContainBasedChanges = false;
452 for (FilePatchInProgress patch : myPatches) {
453 if (patch.baseExistsOrAdded()) {
454 myContainBasedChanges = true;
455 break;
460 private List<FilePatchInProgress.PatchChange> getAllChanges() {
461 return ObjectsConvertor.convert(myPatches,
462 new Convertor<FilePatchInProgress, FilePatchInProgress.PatchChange>() {
463 public FilePatchInProgress.PatchChange convert(FilePatchInProgress o) {
464 return o.getChange();
469 private void acceptChange(final NamedTrinity trinity, final FilePatchInProgress.PatchChange change) {
470 final FilePatchInProgress patchInProgress = change.getPatchInProgress();
471 if (FilePatchStatus.ADDED.equals(patchInProgress.getStatus())) {
472 trinity.plusAdded();
473 } else if (FilePatchStatus.DELETED.equals(patchInProgress.getStatus())) {
474 trinity.plusDeleted();
475 } else {
476 trinity.plusModified();
480 private Collection<FilePatchInProgress.PatchChange> getIncluded(boolean doInitCheck, List<FilePatchInProgress.PatchChange> changes) {
481 final NamedTrinity totalTrinity = new NamedTrinity();
482 final NamedTrinity includedTrinity = new NamedTrinity();
484 final Collection<FilePatchInProgress.PatchChange> included = new LinkedList<FilePatchInProgress.PatchChange>();
485 if (doInitCheck) {
486 for (FilePatchInProgress.PatchChange change : changes) {
487 acceptChange(totalTrinity, change);
488 final FilePatchInProgress filePatchInProgress = change.getPatchInProgress();
489 if (filePatchInProgress.baseExistsOrAdded()) {
490 acceptChange(includedTrinity, change);
491 included.add(change);
494 } else {
495 // todo maybe written pretty
496 final Collection<FilePatchInProgress.PatchChange> includedNow = myChangesTreeList.getIncludedChanges();
497 final Set<FilePatchInProgress> toBeIncluded = new HashSet<FilePatchInProgress>();
498 for (FilePatchInProgress.PatchChange change : includedNow) {
499 final FilePatchInProgress patch = change.getPatchInProgress();
500 toBeIncluded.add(patch);
502 for (FilePatchInProgress.PatchChange change : changes) {
503 final FilePatchInProgress patch = change.getPatchInProgress();
504 acceptChange(totalTrinity, change);
505 if (toBeIncluded.contains(patch) && patch.baseExistsOrAdded()) {
506 acceptChange(includedTrinity, change);
507 included.add(change);
511 myInfoCalculator.setTotal(totalTrinity);
512 myInfoCalculator.setIncluded(includedTrinity);
513 myCommitLegendPanel.update();
514 return included;
517 private class NewBaseSelector implements Runnable {
518 public void run() {
519 final FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, false, false, false, false);
520 VirtualFile[] selectedFiles = FileChooser.chooseFiles(myProject, descriptor);
521 if (selectedFiles.length != 1 || selectedFiles[0] == null) {
522 return;
524 final VirtualFile selectedValue = selectedFiles[0];
526 final List<FilePatchInProgress.PatchChange> selectedChanges = myChangesTreeList.getSelectedChanges();
527 if (selectedChanges.size() >= 1) {
528 for (FilePatchInProgress.PatchChange patchChange : selectedChanges) {
529 final FilePatchInProgress patch = patchChange.getPatchInProgress();
530 patch.setNewBase(selectedValue);
532 updateTree(false);
537 private List<FilePatchInProgress> changes2patches(final List<FilePatchInProgress.PatchChange> selectedChanges) {
538 return ObjectsConvertor.convert(selectedChanges, new Convertor<FilePatchInProgress.PatchChange, FilePatchInProgress>() {
539 public FilePatchInProgress convert(FilePatchInProgress.PatchChange o) {
540 return o.getPatchInProgress();
545 private class MapPopup extends BaseListPopupStep<VirtualFile> {
546 private final Runnable myNewBaseSelector;
548 private MapPopup(final @NotNull List<? extends VirtualFile> aValues, Runnable newBaseSelector) {
549 super("Select base directory for a path", aValues);
550 myNewBaseSelector = newBaseSelector;
553 @Override
554 public boolean isSpeedSearchEnabled() {
555 return true;
558 @Override
559 public PopupStep onChosen(final VirtualFile selectedValue, boolean finalChoice) {
560 if (selectedValue == null) {
561 myNewBaseSelector.run();
562 return null;
564 final List<FilePatchInProgress.PatchChange> selectedChanges = myChangesTreeList.getSelectedChanges();
565 if (selectedChanges.size() >= 1) {
566 for (FilePatchInProgress.PatchChange patchChange : selectedChanges) {
567 final FilePatchInProgress patch = patchChange.getPatchInProgress();
568 patch.setNewBase(selectedValue);
570 updateTree(false);
572 return null;
575 @NotNull
576 @Override
577 public String getTextFor(VirtualFile value) {
578 return value == null ? "Select base for a path" : value.getPath();
582 private static class NamedTrinity {
583 private int myAdded;
584 private int myModified;
585 private int myDeleted;
587 public NamedTrinity() {
588 myAdded = 0;
589 myModified = 0;
590 myDeleted = 0;
593 public NamedTrinity(int added, int modified, int deleted) {
594 myAdded = added;
595 myModified = modified;
596 myDeleted = deleted;
599 public void plusAdded() {
600 ++ myAdded;
603 public void plusModified() {
604 ++ myModified;
607 public void plusDeleted() {
608 ++ myDeleted;
611 public int getAdded() {
612 return myAdded;
615 public int getModified() {
616 return myModified;
619 public int getDeleted() {
620 return myDeleted;
624 private static class ChangesLegendCalculator implements CommitLegendPanel.InfoCalculator {
625 private NamedTrinity myTotal;
626 private NamedTrinity myIncluded;
628 private ChangesLegendCalculator() {
629 myTotal = new NamedTrinity();
630 myIncluded = new NamedTrinity();
633 public void setTotal(final NamedTrinity trinity) {
634 myTotal = trinity;
637 public void setIncluded(final NamedTrinity trinity) {
638 myIncluded = trinity;
641 public int getNew() {
642 return myTotal.getAdded();
645 public int getModified() {
646 return myTotal.getModified();
649 public int getDeleted() {
650 return myTotal.getDeleted();
653 public int getIncludedNew() {
654 return myIncluded.getAdded();
657 public int getIncludedModified() {
658 return myIncluded.getModified();
661 public int getIncludedDeleted() {
662 return myIncluded.getDeleted();
666 private static class MyChangeNodeDecorator implements ChangeNodeDecorator {
667 public void decorate(Change change, SimpleColoredComponent component, boolean isShowFlatten) {
668 if (change instanceof FilePatchInProgress.PatchChange) {
669 final FilePatchInProgress.PatchChange patchChange = (FilePatchInProgress.PatchChange) change;
670 if (! isShowFlatten) {
671 // add change subpath
672 final TextFilePatch filePatch = patchChange.getPatchInProgress().getPatch();
673 final String patchPath = filePatch.getAfterName() == null ? filePatch.getBeforeName() : filePatch.getAfterName();
674 component.append(" ");
675 component.append("["+ patchPath + "]", SimpleTextAttributes.GRAY_ATTRIBUTES);
677 if (patchChange.getPatchInProgress().getCurrentStrip() > 0) {
678 component.append(" stripped " + patchChange.getPatchInProgress().getCurrentStrip(), SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES);
680 final String text;
681 if (FilePatchStatus.ADDED.equals(patchChange.getPatchInProgress().getStatus())) {
682 text = "(Added)";
683 } else if (FilePatchStatus.DELETED.equals(patchChange.getPatchInProgress().getStatus())) {
684 text = "(Deleted)";
685 } else {
686 text = "(Modified)";
688 component.append(" ");
689 component.append(text, SimpleTextAttributes.GRAY_ATTRIBUTES);
693 public List<Pair<String, Stress>> stressPartsOfFileName(final Change change, final String parentPath) {
694 if (change instanceof FilePatchInProgress.PatchChange) {
695 final FilePatchInProgress.PatchChange patchChange = (FilePatchInProgress.PatchChange) change;
696 final String basePath = patchChange.getPatchInProgress().getBase().getPath();
697 final String basePathCorrected = basePath.trim().replace('/', File.separatorChar);
698 if (parentPath.startsWith(basePathCorrected)) {
699 return Arrays.asList(new Pair<String, Stress>(basePathCorrected, Stress.BOLD),
700 new Pair<String, Stress>(StringUtil.tail(parentPath, basePathCorrected.length()), Stress.PLAIN));
703 return null;
706 public void preDecorate(Change change, ChangesBrowserNodeRenderer renderer, boolean showFlatten) {
710 public Collection<FilePatchInProgress> getIncluded() {
711 return ObjectsConvertor.convert(myChangesTreeList.getIncludedChanges(),
712 new Convertor<FilePatchInProgress.PatchChange, FilePatchInProgress>() {
713 public FilePatchInProgress convert(FilePatchInProgress.PatchChange o) {
714 return o.getPatchInProgress();
719 public LocalChangeList getSelectedChangeList() {
720 return myChangeListChooser.getSelectedList(myProject);
723 @Override
724 protected void doOKAction() {
725 super.doOKAction();
726 myCallback.consume(this);
729 private class ZeroStrip extends AnAction {
730 private ZeroStrip() {
731 super("Remove Directories", "Remove Directories", IconLoader.getIcon("/vcs/stripNull.png"));
734 @Override
735 public void actionPerformed(AnActionEvent e) {
736 final List<FilePatchInProgress.PatchChange> selectedChanges = myChangesTreeList.getSelectedChanges();
737 for (FilePatchInProgress.PatchChange change : selectedChanges) {
738 change.getPatchInProgress().setZero();
740 updateTree(false);
744 private class StripDown extends AnAction {
745 private StripDown() {
746 super("Restore Directory", "Restore Directory", IconLoader.getIcon("/vcs/stripDown.png"));
749 @Override
750 public void update(AnActionEvent e) {
751 e.getPresentation().setEnabled(isEnabled());
754 @Override
755 public void actionPerformed(AnActionEvent e) {
756 if (! isEnabled()) return;
757 final List<FilePatchInProgress.PatchChange> selectedChanges = myChangesTreeList.getSelectedChanges();
758 for (FilePatchInProgress.PatchChange change : selectedChanges) {
759 change.getPatchInProgress().down();
761 updateTree(false);
764 private boolean isEnabled() {
765 final List<FilePatchInProgress.PatchChange> selectedChanges = myChangesTreeList.getSelectedChanges();
766 if (selectedChanges.isEmpty()) return false;
767 for (FilePatchInProgress.PatchChange change : selectedChanges) {
768 if (! change.getPatchInProgress().canDown()) return false;
770 return true;
774 private class StripUp extends AnAction {
775 private StripUp() {
776 super("Strip Directory", "Strip Directory", IconLoader.getIcon("/vcs/stripUp.png"));
779 @Override
780 public void update(AnActionEvent e) {
781 e.getPresentation().setEnabled(isEnabled());
784 @Override
785 public void actionPerformed(AnActionEvent e) {
786 if (! isEnabled()) return;
787 final List<FilePatchInProgress.PatchChange> selectedChanges = myChangesTreeList.getSelectedChanges();
788 for (FilePatchInProgress.PatchChange change : selectedChanges) {
789 change.getPatchInProgress().up();
791 updateTree(false);
794 private boolean isEnabled() {
795 final List<FilePatchInProgress.PatchChange> selectedChanges = myChangesTreeList.getSelectedChanges();
796 if (selectedChanges.isEmpty()) return false;
797 for (FilePatchInProgress.PatchChange change : selectedChanges) {
798 if (! change.getPatchInProgress().canUp()) return false;
800 return true;
804 private class ResetStrip extends AnAction {
805 private ResetStrip() {
806 super("Reset Directories", "Reset Directories", IconLoader.getIcon("/vcs/resetStrip.png"));
809 @Override
810 public void actionPerformed(AnActionEvent e) {
811 final List<FilePatchInProgress.PatchChange> selectedChanges = myChangesTreeList.getSelectedChanges();
812 for (FilePatchInProgress.PatchChange change : selectedChanges) {
813 change.getPatchInProgress().reset();
815 updateTree(false);
819 private class MyShowDiff extends AnAction {
820 private MyShowDiff() {
821 super("Show Diff", "Show Diff", IconLoader.getIcon("/actions/diff.png"));
824 public void update(AnActionEvent e) {
825 e.getPresentation().setEnabled((! myPatches.isEmpty()) && myContainBasedChanges);
828 public void actionPerformed(AnActionEvent e) {
829 if (myPatches.isEmpty() || (! myContainBasedChanges)) return;
830 final List<FilePatchInProgress.PatchChange> changes = getAllChanges();
831 Collections.sort(changes, MyChangeComparator.getInstance());
832 final List<FilePatchInProgress.PatchChange> selectedChanges = myChangesTreeList.getSelectedChanges();
834 int selectedIdx = 0;
835 final ArrayList<DiffRequestPresentable> diffRequestPresentables = new ArrayList<DiffRequestPresentable>(changes.size());
836 if (! selectedChanges.isEmpty()) {
837 final FilePatchInProgress.PatchChange c = selectedChanges.get(0);
838 for (FilePatchInProgress.PatchChange change : changes) {
839 final FilePatchInProgress patchInProgress = change.getPatchInProgress();
840 if (! patchInProgress.baseExistsOrAdded()) continue;
841 final DiffRequestPresentable diffRequestPresentable = change.createDiffRequestPresentable(myProject);
842 if (diffRequestPresentable != null) {
843 diffRequestPresentables.add(diffRequestPresentable);
845 if (change.equals(c)) {
846 selectedIdx = diffRequestPresentables.size() - 1;
850 if (diffRequestPresentables.isEmpty()) return;
851 ShowDiffAction.showDiffImpl(myProject, diffRequestPresentables, selectedIdx, ShowDiffAction.DiffExtendUIFactory.NONE, false);
855 private static class MyChangeComparator implements Comparator<FilePatchInProgress.PatchChange> {
856 private static final MyChangeComparator ourInstance = new MyChangeComparator();
858 public static MyChangeComparator getInstance() {
859 return ourInstance;
862 public int compare(FilePatchInProgress.PatchChange o1, FilePatchInProgress.PatchChange o2) {
863 return o1.getPatchInProgress().getIoCurrentBase().compareTo(o2.getPatchInProgress().getIoCurrentBase());