Java tutorial
/******************************************************************************* * Copyright (c) 2006-2008 VecTrace (Zingo Andersen) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Jerome Negre - implementation * Andrei Loskutov - bug fixes *******************************************************************************/ package com.vectrace.MercurialEclipse.views; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Observable; import java.util.Observer; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.IJobChangeListener; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.viewers.ColumnLayoutData; import org.eclipse.jface.viewers.ColumnPixelData; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Shell; import org.eclipse.team.ui.TeamUI; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import com.aragost.javahg.merge.ConflictResolvingContext; import com.aragost.javahg.merge.KeepDeleteConflict; import com.vectrace.MercurialEclipse.MercurialEclipsePlugin; import com.vectrace.MercurialEclipse.commands.HgResolveClient; import com.vectrace.MercurialEclipse.commands.extensions.HgRebaseClient; import com.vectrace.MercurialEclipse.exception.HgException; import com.vectrace.MercurialEclipse.menu.AbortRebaseHandler; import com.vectrace.MercurialEclipse.menu.CommitMergeHandler; import com.vectrace.MercurialEclipse.menu.ContinueRebaseHandler; import com.vectrace.MercurialEclipse.menu.MergeHandler; import com.vectrace.MercurialEclipse.menu.RunnableHandler; import com.vectrace.MercurialEclipse.menu.UpdateHandler; import com.vectrace.MercurialEclipse.model.HgRoot; import com.vectrace.MercurialEclipse.model.ResolveStatus; import com.vectrace.MercurialEclipse.preferences.MercurialPreferenceConstants; import com.vectrace.MercurialEclipse.team.CompareAction; import com.vectrace.MercurialEclipse.team.MercurialTeamProvider; import com.vectrace.MercurialEclipse.team.MercurialUtilities; import com.vectrace.MercurialEclipse.team.cache.MercurialStatusCache; import com.vectrace.MercurialEclipse.ui.AbstractHighlightableTable; import com.vectrace.MercurialEclipse.ui.AbstractHighlightableTable.HighlightingLabelProvider; import com.vectrace.MercurialEclipse.utils.ResourceUtils; /** * TODO: Make use of JavaHg MergeContext */ public class MergeView extends AbstractRootView implements Observer { public static final String ID = MergeView.class.getName(); private MergeTable table; private Action abortAction; private Action completeAction; private Action markResolvedAction; private Action markUnresolvedAction; private Action deleteFileAction; protected boolean merging = true; protected HashMap<IResource, KeepDeleteConflict> keepDeleteConflicts = new HashMap<IResource, KeepDeleteConflict>(); @Override public void createPartControl(final Composite parent) { super.createPartControl(parent); MercurialStatusCache.getInstance().addObserver(this); } /** * @see com.vectrace.MercurialEclipse.views.AbstractRootView#createTable(org.eclipse.swt.widgets.Composite) */ @Override protected void createTable(Composite parent) { table = new MergeTable(parent); table.getTableViewer().addDoubleClickListener(new IDoubleClickListener() { public void doubleClick(DoubleClickEvent event) { openMergeEditor((ResolveStatus) ((IStructuredSelection) event.getSelection()).getFirstElement()); } }); } /** * @see com.vectrace.MercurialEclipse.views.AbstractRootView#createActions() */ @Override protected void createActions() { completeAction = new Action(Messages.getString("MergeView.merge.complete")) { //$NON-NLS-1$ @Override public void run() { if (areAllResolved()) { attemptToCommit(); refresh(hgRoot, conflictResolvingContext); } } }; completeAction.setEnabled(false); completeAction.setImageDescriptor(MercurialEclipsePlugin.getImageDescriptor("actions/commit.gif")); abortAction = new Action(Messages.getString("MergeView.merge.abort")) { //$NON-NLS-1$ @Override public void run() { try { RunnableHandler runnable; if (!merging) { runnable = new AbortRebaseHandler(); } else { UpdateHandler update = new UpdateHandler(); update.setCleanEnabled(true); update.setRevision("."); runnable = update; } runnable.setShell(table.getShell()); runnable.run(hgRoot); refresh(hgRoot, conflictResolvingContext); } catch (CoreException e) { handleError(e); } MercurialUtilities.setOfferAutoCommitMerge(true); } }; abortAction.setEnabled(false); abortAction.setImageDescriptor( PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_ELCL_STOP)); markResolvedAction = new Action(Messages.getString("MergeView.markResolved")) { //$NON-NLS-1$ @Override public void run() { try { List<IFile> files = getSelections(); if (files != null) { for (IFile file : files) { KeepDeleteConflict c = keepDeleteConflicts.get(file); if (c != null) { // just remove it from the list.. conflictResolvingContext.getKeepDeleteConflicts().remove(c); } else { HgResolveClient.markResolved(hgRoot, file); } } populateView(true); } } catch (HgException e) { handleError(e); } } }; markResolvedAction.setEnabled(false); markUnresolvedAction = new Action(Messages.getString("MergeView.markUnresolved")) { //$NON-NLS-1$ @Override public void run() { try { List<IFile> files = getSelections(); if (files != null) { for (IFile file : files) { KeepDeleteConflict c = keepDeleteConflicts.get(file); // do nothing for keep-delete conflicts if (c == null) { HgResolveClient.markUnresolved(hgRoot, file); } } populateView(true); } } catch (HgException e) { handleError(e); } } }; markUnresolvedAction.setEnabled(false); deleteFileAction = new Action(Messages.getString("MergeView.deleteFile")) { @Override public void run() { super.run(); try { List<IFile> files = getSelections(); if (files != null) { for (IFile file : files) { KeepDeleteConflict c = keepDeleteConflicts.get(file); if (c != null) { try { HgResolveClient.deleteKeepDeleteConflict(hgRoot, conflictResolvingContext, c, file); } catch (Exception ex) { // just in case, I've seen some random failures when the workspace isn't in sync with the file system MessageDialog dialog = new MessageDialog(null, "Error Deleting File", null, ex.getMessage(), MessageDialog.ERROR, new String[] { "Ok" }, 0); } } else { // even if it's not a keep-delete conflict, we are providing a delete file option, // so delete the file either way. try { file.delete(true, null); } catch (Exception ex) { MessageDialog dialog = new MessageDialog(null, "Error Deleting File", null, ex.getMessage(), MessageDialog.ERROR, new String[] { "Ok" }, 0); } } // refresh status MercurialStatusCache.getInstance().refreshStatus(file, null); ResourceUtils.touch(file); } populateView(true); } } catch (HgException e) { handleError(e); } } }; deleteFileAction.setEnabled(false); } /** * @see com.vectrace.MercurialEclipse.views.AbstractRootView#createToolBar(org.eclipse.jface.action.IToolBarManager) */ @Override protected void createToolBar(IToolBarManager mgr) { mgr.add(makeActionContribution(completeAction)); mgr.add(makeActionContribution(abortAction)); } /** * @see com.vectrace.MercurialEclipse.views.AbstractRootView#createMenus(org.eclipse.jface.action.IMenuManager) */ @Override protected void createMenus(IMenuManager mgr) { final Action openMergeEditorAction = new Action("Open in Merge Editor") { @Override public void run() { ResolveStatus selection = table.getSelection(); if (selection != null) { openMergeEditor(selection); } } }; final Action openEditorAction = new Action("Open in Default Editor") { @Override public void run() { IFile file = getSelection(); if (file == null) { return; } ResourceUtils.openEditor(getSite().getPage(), file); } }; final Action actionShowHistory = new Action("Show History") { @Override public void run() { IFile file = getSelection(); if (file == null) { return; } TeamUI.getHistoryView().showHistoryFor(file); } }; actionShowHistory.setImageDescriptor(MercurialEclipsePlugin.getImageDescriptor("history.gif")); // Contribute actions to popup menu final MenuManager menuMgr = new MenuManager(); Menu menu = menuMgr.createContextMenu(table); menuMgr.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager menuMgr1) { menuMgr1.add(openMergeEditorAction); menuMgr1.add(openEditorAction); menuMgr1.add(new Separator()); menuMgr1.add(actionShowHistory); menuMgr1.add(new Separator()); menuMgr1.add(markResolvedAction); menuMgr1.add(markUnresolvedAction); menuMgr1.add(new Separator()); menuMgr1.add(deleteFileAction); menuMgr1.add(new Separator()); menuMgr1.add(completeAction); menuMgr1.add(abortAction); } }); menuMgr.setRemoveAllWhenShown(true); table.getTableViewer().getControl().setMenu(menu); } private void populateView(boolean attemptToCommit) throws HgException { boolean bAllResolved = true; List<ResolveStatus> status = HgResolveClient.list(hgRoot); keepDeleteConflicts.clear(); // add keep-delete conflicts for (KeepDeleteConflict c : conflictResolvingContext.getKeepDeleteConflicts()) { IResource iFile = ResourceUtils.getFileHandle(hgRoot.toAbsolute(new Path(c.getFilename()))); keepDeleteConflicts.put(iFile, c); status.add(new ResolveStatus(iFile, c)); } table.setItems(status); for (ResolveStatus flagged : status) { if (flagged.isUnresolved()) { bAllResolved = false; } } completeAction.setEnabled(bAllResolved); /* TODO: remove this block? Commit button enablement provides sufficient feedback if (bAllResolved) { String label; if (merging) { label = Messages.getString("MergeView.PleaseCommitMerge"); } else { label = Messages.getString("MergeView.PleaseCommitRebase"); } showInfo(label); } else { hideStatus(); }*/ // Show commit dialog if (attemptToCommit && MercurialUtilities.isOfferAutoCommitMerge() && areAllResolved()) { /* * Offer commit of merge or rebase exactly once if no conflicts are found. Uses {@link * ResourceProperties#MERGE_COMMIT_OFFERED} to avoid showing the user the commit dialog * repeatedly. This flag should be cleared when any of the following operations occur: * commit, rebase, revert. */ attemptToCommit(); } } /** * @see com.vectrace.MercurialEclipse.views.AbstractRootView#onRootChanged() */ @Override protected void onRootChanged() { if (hgRoot == null || !MercurialStatusCache.getInstance().isMergeInProgress(hgRoot)) { table.setItems(null); abortAction.setEnabled(false); completeAction.setEnabled(false); markResolvedAction.setEnabled(false); markUnresolvedAction.setEnabled(false); deleteFileAction.setEnabled(false); table.setEnabled(false); return; } abortAction.setEnabled(true); completeAction.setEnabled(true); markResolvedAction.setEnabled(true); markUnresolvedAction.setEnabled(true); deleteFileAction.setEnabled(true); table.setEnabled(true); try { merging = !HgRebaseClient.isRebasing(hgRoot); if (merging) { abortAction.setText(Messages.getString("MergeView.merge.abort")); completeAction.setText(Messages.getString("MergeView.merge.complete")); } else { abortAction.setText(Messages.getString("MergeView.rebase.abort")); completeAction.setText(Messages.getString("MergeView.rebase.complete")); } getViewSite().getActionBars().getToolBarManager().update(true); populateView(false); } catch (HgException e) { handleError(e); } } private void attemptToCommit() { try { MercurialUtilities.setOfferAutoCommitMerge(false); RunnableHandler handler = merging ? new CommitMergeHandler() : new ContinueRebaseHandler(); handler.setShell(getSite().getShell()); handler.run(hgRoot); } catch (CoreException e) { MercurialEclipsePlugin.logError(e); } } /** * @see com.vectrace.MercurialEclipse.views.AbstractRootView#getDescription() */ @Override protected String getDescription() { if (hgRoot == null || !MercurialStatusCache.getInstance().isMergeInProgress(hgRoot)) { return "No merge in progress. Select a merging or rebasing repository"; } if (merging) { String mergeNodeId = MercurialStatusCache.getInstance().getMergeChangesetId(hgRoot); if (mergeNodeId != null) { return hgRoot.getName() + ": Merging with " + mergeNodeId; } return hgRoot.getName() + ": Merging"; } return hgRoot.getName() + ": Rebasing"; } /** * @see com.vectrace.MercurialEclipse.views.AbstractRootView#canChangeRoot(com.vectrace.MercurialEclipse.model.HgRoot, boolean) */ @Override protected boolean canChangeRoot(HgRoot newRoot, boolean fromSelection) { boolean ok = super.canChangeRoot(newRoot, fromSelection); if (fromSelection) { ok &= MercurialStatusCache.getInstance().isMergeInProgress(newRoot); } return ok; } private boolean areAllResolved() { boolean allResolved = true; for (ResolveStatus fa : table.getItems()) { allResolved &= fa.isResolved(); } return allResolved; } /** * @see com.vectrace.MercurialEclipse.views.AbstractRootView#selectionChanged(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection) */ public void selectionChanged(IWorkbenchPart part, ISelection selection) { if (selection instanceof IStructuredSelection && !selection.isEmpty()) { IStructuredSelection structured = (IStructuredSelection) selection; IResource resource = MercurialEclipsePlugin.getAdapter(structured.getFirstElement(), IResource.class); if (resource != null) { rootSelected(MercurialTeamProvider.hasHgRoot(resource)); } } } @Override public void setFocus() { table.setFocus(); } @Override public void dispose() { super.dispose(); MercurialStatusCache.getInstance().deleteObserver(this); } private IFile getSelection() { return getFile(table.getSelection()); } private List<IFile> getSelections() { List<ResolveStatus> selections = table.getSelections(); if (selections != null) { List<IFile> result = new ArrayList<IFile>(); for (ResolveStatus flaggedAdaptable : selections) { IFile file = getFile(flaggedAdaptable); if (file != null) { result.add(file); } } return result; } return null; } private static IFile getFile(ResolveStatus adaptable) { if (adaptable != null) { return (IFile) adaptable.getAdapter(IFile.class); } return null; } public void update(Observable o, Object arg) { if (hgRoot == null || !(arg instanceof Set<?>)) { return; } Set<?> set = (Set<?>) arg; Set<IProject> projects = ResourceUtils.getProjects(hgRoot); // create intersection of the root projects with the updated set projects.retainAll(set); // if the intersection contains common projects, we need update the view if (!projects.isEmpty()) { Display.getDefault().asyncExec(new Runnable() { public void run() { refresh(hgRoot, conflictResolvingContext); } }); } } private static void openMergeEditor(ResolveStatus flagged) { IFile file = (IFile) flagged.getAdapter(IFile.class); CompareAction compareAction = new CompareAction(file); compareAction.setEnableMerge(true); compareAction.run(null); } /** * Must be called from the UI thread */ public static void showMergeConflict(HgRoot hgRoot, final ConflictResolvingContext ctx, Shell shell) throws PartInitException { MergeView view = (MergeView) MercurialEclipsePlugin.getActivePage().showView(MergeView.ID); view.refresh(hgRoot, ctx); MercurialEclipsePlugin.showDontShowAgainConfirmDialog("A merge conflict occurred", "A merge conflict occurred. Use the merge view to resolve and commit the merge", MessageDialog.INFORMATION, MercurialPreferenceConstants.PREF_SHOW_MERGE_CONFICT_NOTIFICATION_DIALOG, shell); } /** * Must be called from the UI thread */ public static void showRebaseConflict(HgRoot hgRoot, final ConflictResolvingContext ctx, Shell shell) throws PartInitException { MergeView view = (MergeView) MercurialEclipsePlugin.getActivePage().showView(MergeView.ID); view.refresh(hgRoot, ctx); MercurialEclipsePlugin.showDontShowAgainConfirmDialog("A rebase conflict occurred", "A rebase conflict occurred. Use the merge view to resolve and complete the rebase", MessageDialog.INFORMATION, MercurialPreferenceConstants.PREF_SHOW_REBASE_CONFICT_NOTIFICATION_DIALOG, shell); } /** * Make a job change listener so that when the job is done the merge view will be opened an a * message shown saying a merge or rebase conflict occurred. * * @param hgRoot The root * @param shell The shell, may be null * @param merge True if this is a merge, false if it's a rebase. * @return A new job change listener */ public static IJobChangeListener makeConflictJobChangeListener(final HgRoot hgRoot, final ConflictResolvingContext ctx, final Shell shell, final boolean merge) { return makeUIJobChangeAdapter(new Runnable() { public void run() { try { Shell sh = (shell == null) ? MercurialEclipsePlugin.getActiveShell() : shell; if (merge) { showMergeConflict(hgRoot, ctx, sh); } else { showRebaseConflict(hgRoot, ctx, sh); } } catch (PartInitException e1) { MercurialEclipsePlugin.logError(e1); } } }); } /** * Make a job change listener so that when the job is done the current merge will auto-committed * * @param hgRoot * The root to use * @param shell * The shell to use. May be null. * @return Newly created job change listener */ public static IJobChangeListener makeCommitMergeJobChangeListener(final HgRoot hgRoot, final Shell shell, final String mergeNode) { return makeUIJobChangeAdapter(new Runnable() { public void run() { MergeHandler.commitMerge(new NullProgressMonitor(), hgRoot, mergeNode, shell, true); } }); } private static IJobChangeListener makeUIJobChangeAdapter(final Runnable run) { return new JobChangeAdapter() { @Override public void done(IJobChangeEvent event) { Display.getDefault().asyncExec(run); } }; } private static class MergeTable extends AbstractHighlightableTable<ResolveStatus> { public MergeTable(Composite parent) { super(parent, new MergeTableLabelProvider()); } /** * @see com.vectrace.MercurialEclipse.ui.AbstractHighlightableTable#createColumns(org.eclipse.jface.viewers.TableViewer, org.eclipse.jface.layout.TableColumnLayout) */ @Override protected List<TableViewerColumn> createColumns(TableViewer viewer, TableColumnLayout tableColumnLayout) { List<TableViewerColumn> l = new ArrayList<TableViewerColumn>(2); String[] titles = { Messages.getString("MergeView.column.status"), //$NON-NLS-1$ Messages.getString("MergeView.column.file") }; //$NON-NLS-1$ ColumnLayoutData[] widths = { new ColumnPixelData(100, false, true), new ColumnWeightData(100, 200, true) }; for (int i = 0; i < titles.length; i++) { TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE); column.getColumn().setText(titles[i]); tableColumnLayout.setColumnData(column.getColumn(), widths[i]); l.add(column); } return l; } } private static class MergeTableLabelProvider extends HighlightingLabelProvider<ResolveStatus> { /** * @see com.vectrace.MercurialEclipse.ui.AbstractHighlightableTable.HighlightingLabelProvider#isHighlighted(java.lang.Object) */ @Override public boolean isHighlighted(ResolveStatus flagged) { return flagged.isUnresolved(); } /** * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, int) */ public Image getColumnImage(Object element, int columnIndex) { return null; } /** * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, int) */ public String getColumnText(Object element, int columnIndex) { ResolveStatus flagged = (ResolveStatus) element; switch (columnIndex) { case 0: return flagged.getStatus(); case 1: // TODO: this is wrong when hgroot not at project root return ((IFile) flagged.getAdapter(IFile.class)).getProjectRelativePath().toString(); } throw new IllegalStateException(); } } }