/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Nicolaj Hoess <nicohoess@gmail.com> - Editor templates pref page: Allow to sort by column - https://bugs.eclipse.org/203722
 *******************************************************************************/
package org.eclipse.ui.texteditor.templates;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.ibm.icu.text.Collator;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;

import org.eclipse.core.expressions.Expression;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import org.eclipse.text.templates.TemplatePersistenceData;
import org.eclipse.text.templates.TemplateReaderWriter;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.commands.ActionHandler;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.StatusDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.BidiUtils;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.window.Window;

import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.text.templates.ContextTypeRegistry;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateException;
import org.eclipse.jface.text.templates.persistence.TemplateStore;

import org.eclipse.ui.ActiveShellExpression;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.internal.texteditor.NLSUtility;
import org.eclipse.ui.internal.texteditor.SWTUtil;
import org.eclipse.ui.internal.texteditor.TextEditorPlugin;

import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.IUpdate;


/**
 * A template preference page allows configuration of the templates for an
 * editor. It provides controls for adding, removing and changing templates as
 * well as enablement, default management and an optional formatter preference.
 * <p>
 * Subclasses need to provide a {@link TemplateStore} and a
 * {@link ContextTypeRegistry} and should set the preference store. They may
 * optionally override {@link #isShowFormatterSetting()}.
 * </p>
 *
 * @since 3.0
 */
public abstract class TemplatePreferencePage extends PreferencePage implements IWorkbenchPreferencePage {


	/**
	 * Dialog to edit a template. Clients will usually instantiate, but
	 * may also extend.
	 *
	 * @since 3.3
	 */
	protected static class EditTemplateDialog extends StatusDialog {

		private class TextViewerAction extends Action implements IUpdate {

			private int fOperationCode= -1;
			private ITextOperationTarget fOperationTarget;

			/**
			 * Creates a new action.
			 *
			 * @param viewer the viewer
			 * @param operationCode the opcode
			 */
			public TextViewerAction(ITextViewer viewer, int operationCode) {
				fOperationCode= operationCode;
				fOperationTarget= viewer.getTextOperationTarget();
				update();
			}

			/**
			 * Updates the enabled state of the action.
			 * Fires a property change if the enabled state changes.
			 *
			 * @see Action#firePropertyChange(String, Object, Object)
			 */
			@Override
			public void update() {
				// XXX: workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=206111
				if (fOperationCode == ITextOperationTarget.REDO || fOperationCode == ITextOperationTarget.REDO)
					return;

				boolean wasEnabled= isEnabled();
				boolean isEnabled= (fOperationTarget != null && fOperationTarget.canDoOperation(fOperationCode));
				setEnabled(isEnabled);

				if (wasEnabled != isEnabled) {
					firePropertyChange(ENABLED, wasEnabled ? Boolean.TRUE : Boolean.FALSE, isEnabled ? Boolean.TRUE : Boolean.FALSE);
				}
			}

			/**
			 * @see Action#run()
			 */
			@Override
			public void run() {
				if (fOperationCode != -1 && fOperationTarget != null) {
					fOperationTarget.doOperation(fOperationCode);
				}
			}
		}

		private final Template fOriginalTemplate;

		private Text fNameText;
		private Text fDescriptionText;
		private Combo fContextCombo;
		private SourceViewer fPatternEditor;
		private Button fInsertVariableButton;
		private Button fAutoInsertCheckbox;
		private boolean fIsNameModifiable;

		private StatusInfo fValidationStatus;
		private boolean fSuppressError= true; // #4354
		private Map<String, TextViewerAction> fGlobalActions= new HashMap<>(10);
		private List<String> fSelectionActions = new ArrayList<>(3);
		private String[][] fContextTypes;

		private ContextTypeRegistry fContextTypeRegistry;

		private final TemplateVariableProcessor fTemplateProcessor= new TemplateVariableProcessor();

		private Template fNewTemplate;


		/**
		 * Creates a new dialog.
		 *
		 * @param parent the shell parent of the dialog
		 * @param template the template to edit
		 * @param edit whether this is a new template or an existing being edited
		 * @param isNameModifiable whether the name of the template may be modified
		 * @param registry the context type registry to use
		 */
		public EditTemplateDialog(Shell parent, Template template, boolean edit, boolean isNameModifiable, ContextTypeRegistry registry) {
			super(parent);

			String title= edit
				? TemplatesMessages.EditTemplateDialog_title_edit
				: TemplatesMessages.EditTemplateDialog_title_new;
			setTitle(title);

			fOriginalTemplate= template;
			fIsNameModifiable= isNameModifiable;

			List<String[]> contexts= new ArrayList<>();
			for (Iterator<TemplateContextType> it= registry.contextTypes(); it.hasNext();) {
				TemplateContextType type= it.next();
				contexts.add(new String[] { type.getId(), type.getName() });
			}
			Collections.sort(contexts, new Comparator<String[]>() {
				Collator fCollator= Collator.getInstance();
				@Override
				public int compare(String[] o1, String[] o2) {
					return fCollator.compare(o1[1], o2[1]);
				}
			});
			fContextTypes= contexts.toArray(new String[contexts.size()][]);

			fValidationStatus= new StatusInfo();

			fContextTypeRegistry= registry;

			TemplateContextType type= fContextTypeRegistry.getContextType(template.getContextTypeId());
			fTemplateProcessor.setContextType(type);
		}

		@Override
		protected boolean isResizable() {
			return true;
		}

		@Override
		public void create() {
			super.create();
			// update initial OK button to be disabled for new templates
			boolean valid= fNameText == null || fNameText.getText().trim().length() != 0;
			if (!valid) {
				StatusInfo status = new StatusInfo();
				status.setError(TemplatesMessages.EditTemplateDialog_error_noname);
				updateButtonsEnableState(status);
	 		}
		}

		@Override
		protected Control createDialogArea(Composite ancestor) {
			Composite parent= new Composite(ancestor, SWT.NONE);
			GridLayout layout= new GridLayout();
			layout.numColumns= 2;
			layout.marginHeight= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
			layout.marginWidth= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
			layout.verticalSpacing= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
			layout.horizontalSpacing= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
			parent.setLayout(layout);
			parent.setLayoutData(new GridData(GridData.FILL_BOTH));

			ModifyListener listener = e -> doTextWidgetChanged(e.widget);

			if (fIsNameModifiable) {
				createLabel(parent, TemplatesMessages.EditTemplateDialog_name);

				Composite composite= new Composite(parent, SWT.NONE);
				composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
				layout= new GridLayout();
				layout.numColumns= 4;
				layout.marginWidth= 0;
				layout.marginHeight= 0;
				composite.setLayout(layout);

				fNameText= createText(composite);
				fNameText.addModifyListener(listener);
				fNameText.addFocusListener(new FocusListener() {

					@Override
					public void focusGained(FocusEvent e) {
					}

					@Override
					public void focusLost(FocusEvent e) {
						if (fSuppressError) {
							fSuppressError= false;
							updateButtons();
						}
					}
				});
				BidiUtils.applyBidiProcessing(fNameText, BidiUtils.BTD_DEFAULT);

				createLabel(composite, TemplatesMessages.EditTemplateDialog_context);
				fContextCombo= new Combo(composite, SWT.READ_ONLY);

				for (int i= 0; i < fContextTypes.length; i++) {
					fContextCombo.add(fContextTypes[i][1]);
				}

				fContextCombo.addModifyListener(listener);
				SWTUtil.setDefaultVisibleItemCount(fContextCombo);

				fAutoInsertCheckbox= createCheckbox(composite, TemplatesMessages.EditTemplateDialog_autoinsert);
				fAutoInsertCheckbox.setSelection(fOriginalTemplate.isAutoInsertable());
			}

			createLabel(parent, TemplatesMessages.EditTemplateDialog_description);

			int descFlags= fIsNameModifiable ? SWT.BORDER : SWT.BORDER | SWT.READ_ONLY;
			fDescriptionText= new Text(parent, descFlags );
			fDescriptionText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

			fDescriptionText.addModifyListener(listener);
			BidiUtils.applyBidiProcessing(fDescriptionText, BidiUtils.BTD_DEFAULT);

			Label patternLabel= createLabel(parent, TemplatesMessages.EditTemplateDialog_pattern);
			patternLabel.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
			fPatternEditor= createEditor(parent, fOriginalTemplate.getPattern());

			Label filler= new Label(parent, SWT.NONE);
			filler.setLayoutData(new GridData());

			Composite composite= new Composite(parent, SWT.NONE);
			layout= new GridLayout();
			layout.marginWidth= 0;
			layout.marginHeight= 0;
			composite.setLayout(layout);
			composite.setLayoutData(new GridData());

			fInsertVariableButton= new Button(composite, SWT.NONE);
			fInsertVariableButton.setLayoutData(getButtonGridData(fInsertVariableButton));
			fInsertVariableButton.setText(TemplatesMessages.EditTemplateDialog_insert_variable);
			fInsertVariableButton.addSelectionListener(new SelectionListener() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					fPatternEditor.getTextWidget().setFocus();
					fPatternEditor.doOperation(ISourceViewer.CONTENTASSIST_PROPOSALS);
				}

				@Override
				public void widgetDefaultSelected(SelectionEvent e) {}
			});

			fDescriptionText.setText(fOriginalTemplate.getDescription());
			if (fIsNameModifiable) {
				fNameText.setText(fOriginalTemplate.getName());
				fNameText.addModifyListener(listener);
				fContextCombo.select(getIndex(fOriginalTemplate.getContextTypeId()));
			} else {
				fPatternEditor.getControl().setFocus();
			}
			initializeActions();

			applyDialogFont(parent);
			return composite;
		}

		private void doTextWidgetChanged(Widget w) {
			if (w == fNameText) {
				fSuppressError= false;
				updateButtons();
			} else if (w == fContextCombo) {
				String contextId= getContextId();
				fTemplateProcessor.setContextType(fContextTypeRegistry.getContextType(contextId));
			} else if (w == fDescriptionText) {
				// oh, nothing
			}
		}

		private String getContextId() {
			if (fContextCombo != null && !fContextCombo.isDisposed()) {
				String name= fContextCombo.getText();
				for (int i= 0; i < fContextTypes.length; i++) {
					if (name.equals(fContextTypes[i][1])) {
						return fContextTypes[i][0];
					}
				}
			}

			return fOriginalTemplate.getContextTypeId();
		}

		private void doSourceChanged(IDocument document) {
			String text= document.get();
			fValidationStatus.setOK();
			TemplateContextType contextType= fContextTypeRegistry.getContextType(getContextId());
			if (contextType != null) {
				try {
					contextType.validate(text);
				} catch (TemplateException e) {
					fValidationStatus.setError(e.getLocalizedMessage());
				}
			}

			updateAction(ITextEditorActionConstants.UNDO);
			updateButtons();
		}

		/**
		 * Return the grid data for the button.
		 *
		 * @param button the button
		 * @return the grid data
		 */
		private static GridData getButtonGridData(Button button) {
			GridData data= new GridData(GridData.FILL_HORIZONTAL);
			// TODO get some button hints.
//			data.heightHint= SWTUtil.getButtonHeightHint(button);

			return data;
		}

		private static Label createLabel(Composite parent, String name) {
			Label label= new Label(parent, SWT.NULL);
			label.setText(name);
			label.setLayoutData(new GridData());

			return label;
		}

		private static Text createText(Composite parent) {
			Text text= new Text(parent, SWT.BORDER);
			text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

			return text;
		}

		private static Button createCheckbox(Composite parent, String name) {
			Button button= new Button(parent, SWT.CHECK);
			button.setText(name);
			button.setLayoutData(new GridData());

			return button;
		}

		private SourceViewer createEditor(Composite parent, String pattern) {
			SourceViewer viewer= createViewer(parent);
			viewer.setEditable(true);

			IDocument document= viewer.getDocument();
			if (document != null)
				document.set(pattern);
			else {
				document= new Document(pattern);
				viewer.setDocument(document);
			}

			int nLines= document.getNumberOfLines();
			if (nLines < 5) {
				nLines= 5;
			} else if (nLines > 12) {
				nLines= 12;
			}

			Control control= viewer.getControl();
			GridData data= new GridData(GridData.FILL_BOTH);
			data.widthHint= convertWidthInCharsToPixels(80);
			data.heightHint= convertHeightInCharsToPixels(nLines);
			control.setLayoutData(data);

			viewer.addTextListener(event -> {
				if (event.getDocumentEvent() != null)
					doSourceChanged(event.getDocumentEvent().getDocument());
			});

			viewer.addSelectionChangedListener(event -> updateSelectionDependentActions());

			return viewer;
		}

		/**
		 * Creates the viewer to be used to display the pattern. Subclasses may override.
		 *
		 * @param parent the parent composite of the viewer
		 * @return a configured <code>SourceViewer</code>
		 */
		protected SourceViewer createViewer(Composite parent) {
			SourceViewer viewer= new SourceViewer(parent, null, null, false, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
			SourceViewerConfiguration configuration= new SourceViewerConfiguration() {
				@Override
				public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {

					ContentAssistant assistant= new ContentAssistant();
					assistant.enableAutoActivation(true);
					assistant.enableAutoInsert(true);
					assistant.setContentAssistProcessor(fTemplateProcessor, IDocument.DEFAULT_CONTENT_TYPE);
					return assistant;
				}
			};
			viewer.configure(configuration);
			return viewer;
		}

		private void initializeActions() {
			final ArrayList<IHandlerActivation> handlerActivations= new ArrayList<>(3);
			final IHandlerService handlerService= PlatformUI.getWorkbench().getAdapter(IHandlerService.class);
			final Expression expression= new ActiveShellExpression(fPatternEditor.getControl().getShell());

			getShell().addDisposeListener(e -> handlerService.deactivateHandlers(handlerActivations));

			fPatternEditor.getTextWidget().addFocusListener(new FocusListener() {
				@Override
				public void focusLost(FocusEvent e) {
					handlerService.deactivateHandlers(handlerActivations);
				}

				@Override
				public void focusGained(FocusEvent e) {
					IAction action= fGlobalActions.get(ITextEditorActionConstants.REDO);
					handlerActivations.add(handlerService.activateHandler(IWorkbenchCommandConstants.EDIT_REDO, new ActionHandler(action), expression));
					action= fGlobalActions.get(ITextEditorActionConstants.UNDO);
					handlerActivations.add(handlerService.activateHandler(IWorkbenchCommandConstants.EDIT_UNDO, new ActionHandler(action), expression));
					action= fGlobalActions.get(ITextEditorActionConstants.CONTENT_ASSIST);
					handlerActivations.add(handlerService.activateHandler(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS, new ActionHandler(action), expression));
				}
			});

			TextViewerAction action= new TextViewerAction(fPatternEditor, ITextOperationTarget.UNDO);
			action.setText(TemplatesMessages.EditTemplateDialog_undo);
			fGlobalActions.put(ITextEditorActionConstants.UNDO, action);

			action= new TextViewerAction(fPatternEditor, ITextOperationTarget.REDO);
			action.setText(TemplatesMessages.EditTemplateDialog_redo);
			fGlobalActions.put(ITextEditorActionConstants.REDO, action);

			action= new TextViewerAction(fPatternEditor, ITextOperationTarget.CUT);
			action.setText(TemplatesMessages.EditTemplateDialog_cut);
			fGlobalActions.put(ITextEditorActionConstants.CUT, action);

			action= new TextViewerAction(fPatternEditor, ITextOperationTarget.COPY);
			action.setText(TemplatesMessages.EditTemplateDialog_copy);
			fGlobalActions.put(ITextEditorActionConstants.COPY, action);

			action= new TextViewerAction(fPatternEditor, ITextOperationTarget.PASTE);
			action.setText(TemplatesMessages.EditTemplateDialog_paste);
			fGlobalActions.put(ITextEditorActionConstants.PASTE, action);

			action= new TextViewerAction(fPatternEditor, ITextOperationTarget.SELECT_ALL);
			action.setText(TemplatesMessages.EditTemplateDialog_select_all);
			fGlobalActions.put(ITextEditorActionConstants.SELECT_ALL, action);

			action= new TextViewerAction(fPatternEditor, ISourceViewer.CONTENTASSIST_PROPOSALS);
			action.setText(TemplatesMessages.EditTemplateDialog_content_assist);
			fGlobalActions.put(ITextEditorActionConstants.CONTENT_ASSIST, action);

			fSelectionActions.add(ITextEditorActionConstants.CUT);
			fSelectionActions.add(ITextEditorActionConstants.COPY);
			fSelectionActions.add(ITextEditorActionConstants.PASTE);

			// create context menu
			MenuManager manager= new MenuManager(null, null);
			manager.setRemoveAllWhenShown(true);
			manager.addMenuListener(mgr -> fillContextMenu(mgr));

			StyledText text= fPatternEditor.getTextWidget();
			Menu menu= manager.createContextMenu(text);
			text.setMenu(menu);
		}

		private void fillContextMenu(IMenuManager menu) {
			menu.add(new GroupMarker(ITextEditorActionConstants.GROUP_UNDO));
			menu.appendToGroup(ITextEditorActionConstants.GROUP_UNDO, fGlobalActions.get(ITextEditorActionConstants.UNDO));
			menu.appendToGroup(ITextEditorActionConstants.GROUP_UNDO, fGlobalActions.get(ITextEditorActionConstants.REDO));

			menu.add(new Separator(ITextEditorActionConstants.GROUP_EDIT));
			menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.CUT));
			menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.COPY));
			menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.PASTE));
			menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.SELECT_ALL));

			menu.add(new Separator("templates")); //$NON-NLS-1$
			menu.appendToGroup("templates", fGlobalActions.get("ContentAssistProposal")); //$NON-NLS-1$ //$NON-NLS-2$
		}

		private void updateSelectionDependentActions() {
			Iterator<String> iterator= fSelectionActions.iterator();
			while (iterator.hasNext())
				updateAction(iterator.next());
		}

		private void updateAction(String actionId) {
			IAction action= fGlobalActions.get(actionId);
			if (action instanceof IUpdate)
				((IUpdate) action).update();
		}

		private int getIndex(String contextid) {

			if (contextid == null)
				return -1;

			for (int i= 0; i < fContextTypes.length; i++) {
				if (contextid.equals(fContextTypes[i][0])) {
					return i;
				}
			}
			return -1;
		}

		private void updateButtons() {
			StatusInfo status;

			boolean valid= fNameText == null || fNameText.getText().trim().length() != 0;
			if (!valid) {
				status = new StatusInfo();
				if (!fSuppressError)
					status.setError(TemplatesMessages.EditTemplateDialog_error_noname);
			} else if (!isValidPattern(fPatternEditor.getDocument().get())) {
				status = new StatusInfo();
				if (!fSuppressError)
					status.setError(TemplatesMessages.EditTemplateDialog_error_invalidPattern);
	 		} else {
	 			status= fValidationStatus;
	 		}
			updateStatus(status);
		}

		/**
		 * Validates the pattern.
		 * <p>
		 * The default implementation rejects invalid XML characters.
		 * </p>
		 *
		 * @param pattern the pattern to verify
		 * @return <code>true</code> if the pattern is valid
		 * @since 3.7 protected, before it was private
		 */
		protected boolean isValidPattern(String pattern) {
			for (int i= 0; i < pattern.length(); i++) {
				char ch= pattern.charAt(i);
				if (!(ch == 9 || ch == 10 || ch == 13 || ch >= 32))
					return false;
			}
			return true;
		}

		/*
		 * @since 3.1
		 */
		@Override
		protected void okPressed() {
			String name= fNameText == null ? fOriginalTemplate.getName() : fNameText.getText();
			boolean isAutoInsertable= fAutoInsertCheckbox != null && fAutoInsertCheckbox.getSelection();
			fNewTemplate= new Template(name, fDescriptionText.getText(), getContextId(), fPatternEditor.getDocument().get(), isAutoInsertable);
			super.okPressed();
		}

		/**
		 * Returns the created template.
		 *
		 * @return the created template
		 * @since 3.1
		 */
		public Template getTemplate() {
			return fNewTemplate;
		}

		/**
		 * Returns the content assist processor that
		 * suggests template variables.
		 *
		 * @return the processor to suggest variables
		 * @since 3.3
		 */
		protected IContentAssistProcessor getTemplateProcessor() {
			return fTemplateProcessor;
		}

		@Override
		protected IDialogSettings getDialogBoundsSettings() {
			String sectionName= getClass().getName() + "_dialogBounds"; //$NON-NLS-1$
			IDialogSettings settings= TextEditorPlugin.getDefault().getDialogSettings();
			IDialogSettings section= settings.getSection(sectionName);
			if (section == null)
				section= settings.addNewSection(sectionName);
			return section;
		}

	}


	/**
	 * Label provider for templates.
	 */
	private class TemplateLabelProvider extends LabelProvider implements ITableLabelProvider {

		@Override
		public Image getColumnImage(Object element, int columnIndex) {
			return null;
		}

		@Override
		public String getColumnText(Object element, int columnIndex) {
			TemplatePersistenceData data = (TemplatePersistenceData) element;
			Template template= data.getTemplate();

			switch (columnIndex) {
				case 0:
					return template.getName();
				case 1:
					TemplateContextType type= fContextTypeRegistry.getContextType(template.getContextTypeId());
					if (type != null)
						return type.getName();
					return template.getContextTypeId();
				case 2:
					return template.getDescription();
				case 3:
					return template.isAutoInsertable() ? TemplatesMessages.TemplatePreferencePage_on : "";  //$NON-NLS-1$
				default:
					return ""; //$NON-NLS-1$
			}
		}
	}


	/** Qualified key for formatter preference. */
	private static final String DEFAULT_FORMATTER_PREFERENCE_KEY= "org.eclipse.ui.texteditor.templates.preferences.format_templates"; //$NON-NLS-1$

	/** The table presenting the templates. */
	private CheckboxTableViewer fTableViewer;

	/* buttons */
	private Button fAddButton;
	private Button fEditButton;
	private Button fImportButton;
	private Button fExportButton;
	private Button fRemoveButton;
	private Button fRestoreButton;
	private Button fRevertButton;

	/** The viewer displays the pattern of selected template. */
	private SourceViewer fPatternViewer;
	/** Format checkbox. This gets conditionally added. */
	private Button fFormatButton;
	/** The store for our templates. */
	private TemplateStore fTemplateStore;
	/** The context type registry. */
	private ContextTypeRegistry fContextTypeRegistry;


	/**
	 * Creates a new template preference page.
	 */
	protected TemplatePreferencePage() {
		super();

		setDescription(TemplatesMessages.TemplatePreferencePage_message);
	}

	/**
	 * Returns the template store.
	 *
	 * @return the template store
	 */
	public TemplateStore getTemplateStore() {
		return fTemplateStore;
	}

	/**
	 * Returns the context type registry.
	 *
	 * @return the context type registry
	 */
	public ContextTypeRegistry getContextTypeRegistry() {
		return fContextTypeRegistry;
	}

	/**
	 * Sets the template store.
	 *
	 * @param store the new template store
	 */
	public void setTemplateStore(TemplateStore store) {
		fTemplateStore= store;
	}

	/**
	 * Sets the context type registry.
	 *
	 * @param registry the new context type registry
	 */
	public void setContextTypeRegistry(ContextTypeRegistry registry) {
		fContextTypeRegistry= registry;
	}

	@Override
	public void init(IWorkbench workbench) {
	}

	@Override
	protected Control createContents(Composite ancestor) {
		Composite parent= new Composite(ancestor, SWT.NONE);
		GridLayout layout= new GridLayout();
		layout.numColumns= 2;
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		parent.setLayout(layout);

		Composite innerParent= new Composite(parent, SWT.NONE);
		GridLayout innerLayout= new GridLayout();
		innerLayout.numColumns= 2;
		innerLayout.marginHeight= 0;
		innerLayout.marginWidth= 0;
		innerParent.setLayout(innerLayout);
		GridData gd= new GridData(GridData.FILL_BOTH);
		gd.horizontalSpan= 2;
		innerParent.setLayoutData(gd);

		Composite tableComposite= new Composite(innerParent, SWT.NONE);
		GridData data= new GridData(GridData.FILL_BOTH);
		data.widthHint= 360;
		data.heightHint= convertHeightInCharsToPixels(10);
		tableComposite.setLayoutData(data);

		ColumnLayout columnLayout= new ColumnLayout();
		tableComposite.setLayout(columnLayout);
		Table table= new Table(tableComposite, SWT.CHECK | SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL);

		table.setHeaderVisible(true);
		table.setLinesVisible(true);

		GC gc= new GC(getShell());
		gc.setFont(JFaceResources.getDialogFont());

		TemplateViewerComparator viewerComparator= new TemplateViewerComparator();

		TableColumn column1= new TableColumn(table, SWT.NONE);
		column1.setText(TemplatesMessages.TemplatePreferencePage_column_name);
		int minWidth= computeMinimumColumnWidth(gc, TemplatesMessages.TemplatePreferencePage_column_name);
		columnLayout.addColumnData(new ColumnWeightData(2, minWidth, true));
		column1.addSelectionListener(new TemplateColumnSelectionAdapter(column1, 0, viewerComparator));

		TableColumn column2= new TableColumn(table, SWT.NONE);
		column2.setText(TemplatesMessages.TemplatePreferencePage_column_context);
		minWidth= computeMinimumContextColumnWidth(gc);
		columnLayout.addColumnData(new ColumnWeightData(1, minWidth, true));
		column2.addSelectionListener(new TemplateColumnSelectionAdapter(column2, 1, viewerComparator));

		TableColumn column3= new TableColumn(table, SWT.NONE);
		column3.setText(TemplatesMessages.TemplatePreferencePage_column_description);
		minWidth= computeMinimumColumnWidth(gc, TemplatesMessages.TemplatePreferencePage_column_description);
		columnLayout.addColumnData(new ColumnWeightData(3, minWidth, true));
		column3.addSelectionListener(new TemplateColumnSelectionAdapter(column3, 2, viewerComparator));

		TableColumn column4= new TableColumn(table, SWT.NONE);
		column4.setAlignment(SWT.CENTER);
		column4.setText(TemplatesMessages.TemplatePreferencePage_column_autoinsert);
		minWidth= computeMinimumColumnWidth(gc, TemplatesMessages.TemplatePreferencePage_column_autoinsert);
		minWidth= Math.max(minWidth, computeMinimumColumnWidth(gc, TemplatesMessages.TemplatePreferencePage_on));
		columnLayout.addColumnData(new ColumnPixelData(minWidth, false, false));
		column4.addSelectionListener(new TemplateColumnSelectionAdapter(column4, 3, viewerComparator));

		gc.dispose();

		fTableViewer= new CheckboxTableViewer(table);
		fTableViewer.setLabelProvider(new TemplateLabelProvider());
		fTableViewer.setContentProvider(new TemplateContentProvider());
		fTableViewer.setComparator(viewerComparator);

		// Specify default sorting
		table.setSortColumn(column1);
		table.setSortDirection(viewerComparator.getDirection());

		fTableViewer.addDoubleClickListener(e -> edit());

		fTableViewer.addSelectionChangedListener(e -> selectionChanged1());

		fTableViewer.addCheckStateListener(event -> {
			TemplatePersistenceData d = (TemplatePersistenceData) event.getElement();
			d.setEnabled(event.getChecked());
		});

		BidiUtils.applyTextDirection(fTableViewer.getControl(), BidiUtils.BTD_DEFAULT);

		Composite buttons= new Composite(innerParent, SWT.NONE);
		buttons.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
		layout= new GridLayout();
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		buttons.setLayout(layout);

		fAddButton= new Button(buttons, SWT.PUSH);
		fAddButton.setText(TemplatesMessages.TemplatePreferencePage_new);
		fAddButton.setLayoutData(getButtonGridData(fAddButton));
		fAddButton.addListener(SWT.Selection, e -> add());

		fEditButton= new Button(buttons, SWT.PUSH);
		fEditButton.setText(TemplatesMessages.TemplatePreferencePage_edit);
		fEditButton.setLayoutData(getButtonGridData(fEditButton));
		fEditButton.addListener(SWT.Selection, e -> edit());

		fRemoveButton= new Button(buttons, SWT.PUSH);
		fRemoveButton.setText(TemplatesMessages.TemplatePreferencePage_remove);
		fRemoveButton.setLayoutData(getButtonGridData(fRemoveButton));
		fRemoveButton.addListener(SWT.Selection, e -> remove());

		createSeparator(buttons);

		fRestoreButton= new Button(buttons, SWT.PUSH);
		fRestoreButton.setText(TemplatesMessages.TemplatePreferencePage_restore);
		fRestoreButton.setLayoutData(getButtonGridData(fRestoreButton));
		fRestoreButton.addListener(SWT.Selection, e -> restoreDeleted());

		fRevertButton= new Button(buttons, SWT.PUSH);
		fRevertButton.setText(TemplatesMessages.TemplatePreferencePage_revert);
		fRevertButton.setLayoutData(getButtonGridData(fRevertButton));
		fRevertButton.addListener(SWT.Selection, e -> revert());

		createSeparator(buttons);

		fImportButton= new Button(buttons, SWT.PUSH);
		fImportButton.setText(TemplatesMessages.TemplatePreferencePage_import);
		fImportButton.setLayoutData(getButtonGridData(fImportButton));
		fImportButton.addListener(SWT.Selection, e -> import_());

		fExportButton= new Button(buttons, SWT.PUSH);
		fExportButton.setText(TemplatesMessages.TemplatePreferencePage_export);
		fExportButton.setLayoutData(getButtonGridData(fExportButton));
		fExportButton.addListener(SWT.Selection, e -> export());

		fPatternViewer= doCreateViewer(parent);

		if (isShowFormatterSetting()) {
			fFormatButton= new Button(parent, SWT.CHECK);
			fFormatButton.setText(TemplatesMessages.TemplatePreferencePage_use_code_formatter);
			GridData gd1= new GridData();
			gd1.horizontalSpan= 2;
			fFormatButton.setLayoutData(gd1);
			fFormatButton.setSelection(getPreferenceStore().getBoolean(getFormatterPreferenceKey()));
		}

		fTableViewer.setInput(fTemplateStore);
		fTableViewer.setAllChecked(false);
		fTableViewer.setCheckedElements(getEnabledTemplates());

		updateButtons();
		Dialog.applyDialogFont(parent);
		innerParent.layout();

		return parent;
	}

	/*
	 * @since 3.2
	 */
	private int computeMinimumColumnWidth(GC gc, String string) {
		return gc.stringExtent(string).x + 10; // pad 10 to accommodate table header trimmings
	}

	/*
	 * @since 3.4
	 */
	private int computeMinimumContextColumnWidth(GC gc) {
		int width= gc.stringExtent(TemplatesMessages.TemplatePreferencePage_column_context).x;
		Iterator<TemplateContextType> iter= getContextTypeRegistry().contextTypes();
		while (iter.hasNext()) {
			TemplateContextType contextType= iter.next();
			width= Math.max(width, gc.stringExtent(contextType.getName()).x);
		}
		return width;
	}

	/**
	 * Creates a separator between buttons.
	 *
	 * @param parent the parent composite
	 * @return a separator
	 */
	private Label createSeparator(Composite parent) {
		Label separator= new Label(parent, SWT.NONE);
		separator.setVisible(false);
		GridData gd= new GridData();
		gd.horizontalAlignment= GridData.FILL;
		gd.verticalAlignment= GridData.BEGINNING;
		gd.heightHint= 4;
		separator.setLayoutData(gd);
		return separator;
	}


	/**
	 * Returns whether the formatter preference checkbox should be shown.
	 *
	 * @return <code>true</code> if the formatter preference checkbox should
	 *         be shown, <code>false</code> otherwise
	 */
	protected boolean isShowFormatterSetting() {
		return true;
	}

	private TemplatePersistenceData[] getEnabledTemplates() {
		List<TemplatePersistenceData> enabled= new ArrayList<>();
		TemplatePersistenceData[] datas= fTemplateStore.getTemplateData(false);
		for (int i= 0; i < datas.length; i++) {
			if (datas[i].isEnabled())
				enabled.add(datas[i]);
		}
		return enabled.toArray(new TemplatePersistenceData[enabled.size()]);
	}

	private SourceViewer doCreateViewer(Composite parent) {
		Label label= new Label(parent, SWT.NONE);
		label.setText(TemplatesMessages.TemplatePreferencePage_preview);
		GridData data= new GridData();
		data.horizontalSpan= 2;
		label.setLayoutData(data);

		SourceViewer viewer= createViewer(parent);

		viewer.setEditable(false);
		Cursor arrowCursor= viewer.getTextWidget().getDisplay().getSystemCursor(SWT.CURSOR_ARROW);
		viewer.getTextWidget().setCursor(arrowCursor);

		// Don't set caret to 'null' as this causes https://bugs.eclipse.org/293263
//		viewer.getTextWidget().setCaret(null);

		Control control= viewer.getControl();
		data= new GridData(GridData.FILL_BOTH);
		data.horizontalSpan= 2;
		data.heightHint= convertHeightInCharsToPixels(5);
		control.setLayoutData(data);

		return viewer;
	}

	/**
	 * Creates, configures and returns a source viewer to present the template
	 * pattern on the preference page. Clients may override to provide a custom
	 * source viewer featuring e.g. syntax coloring.
	 *
	 * @param parent the parent control
	 * @return a configured source viewer
	 */
	protected SourceViewer createViewer(Composite parent) {
		SourceViewer viewer= new SourceViewer(parent, null, null, false, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
		SourceViewerConfiguration configuration= new SourceViewerConfiguration();
		viewer.configure(configuration);
		IDocument document= new Document();
		viewer.setDocument(document);
		return viewer;
	}

	/**
	 * Return the grid data for the button.
	 *
	 * @param button the button
	 * @return the grid data
	 */
	private static GridData getButtonGridData(Button button) {
		GridData data= new GridData(GridData.FILL_HORIZONTAL);
		// TODO replace SWTUtil
//		data.widthHint= SWTUtil.getButtonWidthHint(button);
//		data.heightHint= SWTUtil.getButtonHeightHint(button);

		return data;
	}

	private void selectionChanged1() {
		updateViewerInput();
		updateButtons();
	}

	/**
	 * Updates the pattern viewer.
	 */
	protected void updateViewerInput() {
		IStructuredSelection selection = fTableViewer.getStructuredSelection();

		if (selection.size() == 1) {
			TemplatePersistenceData data= (TemplatePersistenceData) selection.getFirstElement();
			Template template= data.getTemplate();
			fPatternViewer.getDocument().set(template.getPattern());
		} else {
			fPatternViewer.getDocument().set(""); //$NON-NLS-1$
		}
	}

	/**
	 * Updates the buttons.
	 */
	protected void updateButtons() {
		IStructuredSelection selection = fTableViewer.getStructuredSelection();
		int selectionCount= selection.size();
		int itemCount= fTableViewer.getTable().getItemCount();
		boolean canRestore= fTemplateStore.getTemplateData(true).length != fTemplateStore.getTemplateData(false).length;
		boolean canRevert= false;
		for (Iterator<?> it= selection.iterator(); it.hasNext();) {
			TemplatePersistenceData data= (TemplatePersistenceData) it.next();
			if (data.isModified()) {
				canRevert= true;
				break;
			}
		}

		fEditButton.setEnabled(selectionCount == 1);
		fExportButton.setEnabled(selectionCount > 0);
		fRemoveButton.setEnabled(selectionCount > 0 && selectionCount <= itemCount);
		fRestoreButton.setEnabled(canRestore);
		fRevertButton.setEnabled(canRevert);
	}

	private void add() {

		Iterator<TemplateContextType> it= fContextTypeRegistry.contextTypes();
		if (it.hasNext()) {
			Template template= new Template("", "", it.next().getId(), "", true);   //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

			Template newTemplate= editTemplate(template, false, true);
			if (newTemplate != null) {
				TemplatePersistenceData data= new TemplatePersistenceData(newTemplate, true);
				fTemplateStore.add(data);
				fTableViewer.refresh();
				fTableViewer.setChecked(data, true);
				fTableViewer.setSelection(new StructuredSelection(data));
			}
		}
	}

	/**
	 * Creates the edit dialog. Subclasses may override this method to provide a
	 * custom dialog.
	 *
	 * @param template the template being edited
	 * @param edit whether the dialog should be editable
	 * @param isNameModifiable whether the template name may be modified
	 * @return an <code>EditTemplateDialog</code> which will be opened.
	 * @deprecated not called any longer as of 3.1 - use {@link #editTemplate(Template, boolean, boolean)}
	 */
	@Deprecated
	protected Dialog createTemplateEditDialog(Template template, boolean edit, boolean isNameModifiable) {
		return new EditTemplateDialog(getShell(), template, edit, isNameModifiable, fContextTypeRegistry);
	}

	/**
	 * Creates the edit dialog. Subclasses may override this method to provide a
	 * custom dialog.
	 *
	 * @param template the template being edited
	 * @param edit whether the dialog should be editable
	 * @param isNameModifiable whether the template name may be modified
	 * @return the created or modified template, or <code>null</code> if the edition failed
	 * @since 3.1
	 */
	protected Template editTemplate(Template template, boolean edit, boolean isNameModifiable) {
		EditTemplateDialog dialog= new EditTemplateDialog(getShell(), template, edit, isNameModifiable, fContextTypeRegistry);
		if (dialog.open() == Window.OK) {
			return dialog.getTemplate();
		}
		return null;
	}

	private void edit() {
		IStructuredSelection selection = fTableViewer.getStructuredSelection();

		Object[] objects= selection.toArray();
		if ((objects == null) || (objects.length != 1))
			return;

		TemplatePersistenceData data= (TemplatePersistenceData) selection.getFirstElement();
		edit(data);
	}

	private void edit(TemplatePersistenceData data) {
		Template oldTemplate= data.getTemplate();
		Template newTemplate= editTemplate(new Template(oldTemplate), true, true);
		if (newTemplate != null) {

			if (!newTemplate.getName().equals(oldTemplate.getName()) &&
				MessageDialog.openQuestion(getShell(),
				TemplatesMessages.TemplatePreferencePage_question_create_new_title,
				TemplatesMessages.TemplatePreferencePage_question_create_new_message))
			{
				data= new TemplatePersistenceData(newTemplate, true);
				fTemplateStore.add(data);
				fTableViewer.refresh();
			} else {
				data.setTemplate(newTemplate);
				fTableViewer.refresh(data);
			}
			selectionChanged1();
			fTableViewer.setChecked(data, data.isEnabled());
			fTableViewer.setSelection(new StructuredSelection(data));
		}
	}

	private void import_() {
		FileDialog dialog= new FileDialog(getShell());
		dialog.setText(TemplatesMessages.TemplatePreferencePage_import_title);
		dialog.setFilterExtensions(new String[] {TemplatesMessages.TemplatePreferencePage_import_extension});
		String path= dialog.open();

		if (path == null)
			return;

		try {
			ArrayList<TemplatePersistenceData> selection= new ArrayList<>();
			TemplateReaderWriter reader= new TemplateReaderWriter();
			File file= new File(path);
			if (file.exists()) {
				try (InputStream input = new BufferedInputStream(new FileInputStream(file))) {
					TemplatePersistenceData[] datas= reader.read(input, null);
					for (int i= 0; i < datas.length; i++) {
						TemplatePersistenceData data= datas[i];
						fTemplateStore.add(data);
						String id= data.getId();
						if (id == null) {
							selection.add(data);
						} else {
							data= fTemplateStore.getTemplateData(id);
							if (data != null) {
								selection.add(data);
							}
						}
					}
				}
			}

			fTableViewer.refresh();
			fTableViewer.setAllChecked(false);
			fTableViewer.setCheckedElements(getEnabledTemplates());
			fTableViewer.setSelection(new StructuredSelection(selection), true);
			selectionChanged1();

		} catch (FileNotFoundException e) {
			openReadErrorDialog(e);
		} catch (IOException e) {
			openReadErrorDialog(e);
		}
	}

	private void export() {
		IStructuredSelection selection = fTableViewer.getStructuredSelection();
		Object[] templates= selection.toArray();

		TemplatePersistenceData[] datas= new TemplatePersistenceData[templates.length];
		for (int i= 0; i != templates.length; i++)
			datas[i]= (TemplatePersistenceData) templates[i];

		export(datas);
	}

	private void export(TemplatePersistenceData[] templates) {
		FileDialog dialog= new FileDialog(getShell(), SWT.SAVE);
		dialog.setText(TemplatesMessages.TemplatePreferencePage_export_title);
		dialog.setFilterExtensions(new String[] {TemplatesMessages.TemplatePreferencePage_export_extension});
		dialog.setFileName(TemplatesMessages.TemplatePreferencePage_export_filename);
		String path= dialog.open();

		if (path == null)
			return;

		File file= new File(path);

		if (file.isHidden()) {
			String title= TemplatesMessages.TemplatePreferencePage_export_error_title;
			String message= NLSUtility.format(TemplatesMessages.TemplatePreferencePage_export_error_hidden, file.getAbsolutePath());
			MessageDialog.openError(getShell(), title, message);
			return;
		}

		if (file.exists() && !file.canWrite()) {
			String title= TemplatesMessages.TemplatePreferencePage_export_error_title;
			String message= NLSUtility.format(TemplatesMessages.TemplatePreferencePage_export_error_canNotWrite, file.getAbsolutePath());
			MessageDialog.openError(getShell(), title, message);
			return;
		}

		if (!file.exists() || confirmOverwrite(file)) {
			try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) {
				TemplateReaderWriter writer= new TemplateReaderWriter();
				writer.save(templates, output);
			} catch (IOException e) {
				openWriteErrorDialog(e);
			}
		}
	}

	private boolean confirmOverwrite(File file) {
		return MessageDialog.openQuestion(getShell(),
			TemplatesMessages.TemplatePreferencePage_export_exists_title,
			NLSUtility.format(TemplatesMessages.TemplatePreferencePage_export_exists_message, file.getAbsolutePath()));
	}

	private void remove() {
		IStructuredSelection selection = fTableViewer.getStructuredSelection();

		Iterator<?> elements= selection.iterator();
		while (elements.hasNext()) {
			TemplatePersistenceData data= (TemplatePersistenceData) elements.next();
			fTemplateStore.delete(data);
		}

		fTableViewer.refresh();
	}

	private void restoreDeleted() {
		TemplatePersistenceData[] oldTemplates= fTemplateStore.getTemplateData(false);
		fTemplateStore.restoreDeleted();
		TemplatePersistenceData[] newTemplates= fTemplateStore.getTemplateData(false);
		fTableViewer.refresh();
		fTableViewer.setCheckedElements(getEnabledTemplates());
		ArrayList<TemplatePersistenceData> selection= new ArrayList<>();
		selection.addAll(Arrays.asList(newTemplates));
		selection.removeAll(Arrays.asList(oldTemplates));
		fTableViewer.setSelection(new StructuredSelection(selection), true);
		selectionChanged1();
	}

	private void revert() {
		IStructuredSelection selection = fTableViewer.getStructuredSelection();

		Iterator<?> elements= selection.iterator();
		while (elements.hasNext()) {
			TemplatePersistenceData data= (TemplatePersistenceData) elements.next();
			data.revert();
			fTableViewer.setChecked(data, data.isEnabled());
		}

		selectionChanged1();
		fTableViewer.refresh();
	}

	@Override
	public void setVisible(boolean visible) {
		super.setVisible(visible);
		if (visible)
			setTitle(TemplatesMessages.TemplatePreferencePage_title);
	}

	@Override
	protected void performDefaults() {
		if (isShowFormatterSetting()) {
			IPreferenceStore prefs= getPreferenceStore();
			fFormatButton.setSelection(prefs.getDefaultBoolean(getFormatterPreferenceKey()));
		}

		fTemplateStore.restoreDefaults(false);

		// refresh
		fTableViewer.refresh();
		fTableViewer.setAllChecked(false);
		fTableViewer.setCheckedElements(getEnabledTemplates());
	}

	@Override
	public boolean performOk() {
		if (isShowFormatterSetting()) {
			IPreferenceStore prefs= getPreferenceStore();
			prefs.setValue(getFormatterPreferenceKey(), fFormatButton.getSelection());
		}

		try {
			fTemplateStore.save();
		} catch (IOException e) {
			openWriteErrorDialog(e);
		}

		return super.performOk();
	}

	/**
	 * Returns the key to use for the formatter preference.
	 *
	 * @return the formatter preference key
	 */
	protected String getFormatterPreferenceKey() {
		return DEFAULT_FORMATTER_PREFERENCE_KEY;
	}

	@Override
	public boolean performCancel() {
		try {
			fTemplateStore.load();
		} catch (IOException e) {
			openReadErrorDialog(e);
			return false;
		}
		return super.performCancel();
	}

	/*
	 * @since 3.2
	 */
	private void openReadErrorDialog(IOException ex) {
		IStatus status= new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK, "Failed to read templates.", ex); //$NON-NLS-1$
		TextEditorPlugin.getDefault().getLog().log(status);
		String title= TemplatesMessages.TemplatePreferencePage_error_read_title;
		String message= TemplatesMessages.TemplatePreferencePage_error_read_message;
		MessageDialog.openError(getShell(), title, message);
	}

	/*
	 * @since 3.2
	 */
	private void openWriteErrorDialog(IOException ex) {
		IStatus status= new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK, "Failed to write templates.", ex); //$NON-NLS-1$
		TextEditorPlugin.getDefault().getLog().log(status);
		String title= TemplatesMessages.TemplatePreferencePage_error_write_title;
		String message= TemplatesMessages.TemplatePreferencePage_error_write_message;
		MessageDialog.openError(getShell(), title, message);
	}

	protected SourceViewer getViewer() {
		return fPatternViewer;
	}

	protected TableViewer getTableViewer() {
		return fTableViewer;
	}

	private final class TemplateViewerComparator extends ViewerComparator {

		private int fSortColumn;

		private int fSortOrder; // 1 = asc, -1 = desc

		public TemplateViewerComparator() {
			fSortColumn= 0;
			fSortOrder= 1;
		}

		/**
		 * Returns the {@linkplain SWT} style constant for the sort direction.
		 *
		 * @return {@link SWT#DOWN} for asc sorting, {@link SWT#UP} otherwise
		 */
		public int getDirection() {
			return fSortOrder == 1 ? SWT.DOWN : SWT.UP;
		}

		/**
		 * Sets the sort column. If the newly set sort column equals the previous set sort column,
		 * the sort direction changes.
		 *
		 * @param column New sort column
		 */
		public void setColumn(int column) {
			if (column == fSortColumn) {
				fSortOrder*= -1;
			} else {
				fSortColumn= column;
				fSortOrder= 1;
			}
		}

		@Override
		public int compare(Viewer viewer, Object e1, Object e2) {

			if (viewer instanceof TableViewer) {
				IBaseLabelProvider baseLabel= ((TableViewer)viewer).getLabelProvider();

				String left= ((TemplateLabelProvider)baseLabel).getColumnText(e1, fSortColumn);
				String right= ((TemplateLabelProvider)baseLabel).getColumnText(e2, fSortColumn);
				int sortResult= getComparator().compare(left, right);
				return sortResult * fSortOrder;
			}

			return super.compare(viewer, e1, e2);
		}
	}

	private final class TemplateColumnSelectionAdapter extends SelectionAdapter {

		private final TableColumn fTableColumn;

		private final int fColumnIndex;

		private final TemplateViewerComparator fViewerComparator;

		public TemplateColumnSelectionAdapter(TableColumn column, int index, TemplateViewerComparator vc) {
			fTableColumn= column;
			fColumnIndex= index;
			fViewerComparator= vc;
		}

		@Override
		public void widgetSelected(SelectionEvent e) {
			fViewerComparator.setColumn(fColumnIndex);
			int dir= fViewerComparator.getDirection();
			fTableViewer.getTable().setSortDirection(dir);
			fTableViewer.getTable().setSortColumn(fTableColumn);
			fTableViewer.refresh();
		}
	}
}
