1 /*******************************************************************************
2  * Copyright (c) 2000, 2018 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Nicolaj Hoess <nicohoess@gmail.com> - Editor templates pref page: Allow to sort by column - https://bugs.eclipse.org/203722
14  *******************************************************************************/
15 package org.eclipse.ui.texteditor.templates;
16 
17 import java.io.BufferedInputStream;
18 import java.io.BufferedOutputStream;
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.text.Collator;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 
36 import org.eclipse.swt.SWT;
37 import org.eclipse.swt.custom.StyledText;
38 import org.eclipse.swt.events.FocusEvent;
39 import org.eclipse.swt.events.FocusListener;
40 import org.eclipse.swt.events.ModifyListener;
41 import org.eclipse.swt.events.SelectionAdapter;
42 import org.eclipse.swt.events.SelectionEvent;
43 import org.eclipse.swt.events.SelectionListener;
44 import org.eclipse.swt.graphics.Cursor;
45 import org.eclipse.swt.graphics.GC;
46 import org.eclipse.swt.graphics.Image;
47 import org.eclipse.swt.layout.GridData;
48 import org.eclipse.swt.layout.GridLayout;
49 import org.eclipse.swt.widgets.Button;
50 import org.eclipse.swt.widgets.Combo;
51 import org.eclipse.swt.widgets.Composite;
52 import org.eclipse.swt.widgets.Control;
53 import org.eclipse.swt.widgets.FileDialog;
54 import org.eclipse.swt.widgets.Label;
55 import org.eclipse.swt.widgets.Menu;
56 import org.eclipse.swt.widgets.Shell;
57 import org.eclipse.swt.widgets.Table;
58 import org.eclipse.swt.widgets.TableColumn;
59 import org.eclipse.swt.widgets.Text;
60 import org.eclipse.swt.widgets.Widget;
61 
62 import org.eclipse.core.expressions.Expression;
63 
64 import org.eclipse.core.runtime.IStatus;
65 import org.eclipse.core.runtime.Status;
66 
67 import org.eclipse.text.templates.TemplatePersistenceData;
68 import org.eclipse.text.templates.TemplateReaderWriter;
69 
70 import org.eclipse.jface.action.Action;
71 import org.eclipse.jface.action.GroupMarker;
72 import org.eclipse.jface.action.IAction;
73 import org.eclipse.jface.action.IMenuManager;
74 import org.eclipse.jface.action.MenuManager;
75 import org.eclipse.jface.action.Separator;
76 import org.eclipse.jface.commands.ActionHandler;
77 import org.eclipse.jface.dialogs.Dialog;
78 import org.eclipse.jface.dialogs.IDialogConstants;
79 import org.eclipse.jface.dialogs.IDialogSettings;
80 import org.eclipse.jface.dialogs.MessageDialog;
81 import org.eclipse.jface.dialogs.StatusDialog;
82 import org.eclipse.jface.preference.IPreferenceStore;
83 import org.eclipse.jface.preference.PreferencePage;
84 import org.eclipse.jface.resource.JFaceResources;
85 import org.eclipse.jface.util.BidiUtils;
86 import org.eclipse.jface.viewers.CheckboxTableViewer;
87 import org.eclipse.jface.viewers.ColumnPixelData;
88 import org.eclipse.jface.viewers.ColumnWeightData;
89 import org.eclipse.jface.viewers.IBaseLabelProvider;
90 import org.eclipse.jface.viewers.IStructuredSelection;
91 import org.eclipse.jface.viewers.ITableLabelProvider;
92 import org.eclipse.jface.viewers.LabelProvider;
93 import org.eclipse.jface.viewers.StructuredSelection;
94 import org.eclipse.jface.viewers.TableViewer;
95 import org.eclipse.jface.viewers.Viewer;
96 import org.eclipse.jface.viewers.ViewerComparator;
97 import org.eclipse.jface.window.Window;
98 
99 import org.eclipse.jface.text.Document;
100 import org.eclipse.jface.text.IDocument;
101 import org.eclipse.jface.text.ITextOperationTarget;
102 import org.eclipse.jface.text.ITextViewer;
103 import org.eclipse.jface.text.contentassist.ContentAssistant;
104 import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
105 import org.eclipse.jface.text.contentassist.IContentAssistant;
106 import org.eclipse.jface.text.source.ISourceViewer;
107 import org.eclipse.jface.text.source.SourceViewer;
108 import org.eclipse.jface.text.source.SourceViewerConfiguration;
109 import org.eclipse.jface.text.templates.ContextTypeRegistry;
110 import org.eclipse.jface.text.templates.Template;
111 import org.eclipse.jface.text.templates.TemplateContextType;
112 import org.eclipse.jface.text.templates.TemplateException;
113 import org.eclipse.jface.text.templates.persistence.TemplateStore;
114 
115 import org.eclipse.ui.ActiveShellExpression;
116 import org.eclipse.ui.IWorkbench;
117 import org.eclipse.ui.IWorkbenchCommandConstants;
118 import org.eclipse.ui.IWorkbenchPreferencePage;
119 import org.eclipse.ui.PlatformUI;
120 import org.eclipse.ui.handlers.IHandlerActivation;
121 import org.eclipse.ui.handlers.IHandlerService;
122 import org.eclipse.ui.internal.texteditor.NLSUtility;
123 import org.eclipse.ui.internal.texteditor.SWTUtil;
124 import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
125 
126 import org.eclipse.ui.texteditor.ITextEditorActionConstants;
127 import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
128 import org.eclipse.ui.texteditor.IUpdate;
129 
130 
131 /**
132  * A template preference page allows configuration of the templates for an
133  * editor. It provides controls for adding, removing and changing templates as
134  * well as enablement, default management and an optional formatter preference.
135  * <p>
136  * Subclasses need to provide a {@link TemplateStore} and a
137  * {@link ContextTypeRegistry} and should set the preference store. They may
138  * optionally override {@link #isShowFormatterSetting()}.
139  * </p>
140  *
141  * @since 3.0
142  */
143 public abstract class TemplatePreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
144 
145 
146 	/**
147 	 * Dialog to edit a template. Clients will usually instantiate, but
148 	 * may also extend.
149 	 *
150 	 * @since 3.3
151 	 */
152 	protected static class EditTemplateDialog extends StatusDialog {
153 
154 		private class TextViewerAction extends Action implements IUpdate {
155 
156 			private int fOperationCode= -1;
157 			private ITextOperationTarget fOperationTarget;
158 
159 			/**
160 			 * Creates a new action.
161 			 *
162 			 * @param viewer the viewer
163 			 * @param operationCode the opcode
164 			 */
TextViewerAction(ITextViewer viewer, int operationCode)165 			public TextViewerAction(ITextViewer viewer, int operationCode) {
166 				fOperationCode= operationCode;
167 				fOperationTarget= viewer.getTextOperationTarget();
168 				update();
169 			}
170 
171 			/**
172 			 * Updates the enabled state of the action.
173 			 * Fires a property change if the enabled state changes.
174 			 *
175 			 * @see Action#firePropertyChange(String, Object, Object)
176 			 */
177 			@Override
update()178 			public void update() {
179 				// XXX: workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=206111
180 				if (fOperationCode == ITextOperationTarget.REDO || fOperationCode == ITextOperationTarget.REDO)
181 					return;
182 
183 				boolean wasEnabled= isEnabled();
184 				boolean isEnabled= (fOperationTarget != null && fOperationTarget.canDoOperation(fOperationCode));
185 				setEnabled(isEnabled);
186 
187 				if (wasEnabled != isEnabled) {
188 					firePropertyChange(ENABLED, wasEnabled ? Boolean.TRUE : Boolean.FALSE, isEnabled ? Boolean.TRUE : Boolean.FALSE);
189 				}
190 			}
191 
192 			/**
193 			 * @see Action#run()
194 			 */
195 			@Override
run()196 			public void run() {
197 				if (fOperationCode != -1 && fOperationTarget != null) {
198 					fOperationTarget.doOperation(fOperationCode);
199 				}
200 			}
201 		}
202 
203 		private final Template fOriginalTemplate;
204 
205 		private Text fNameText;
206 		private Text fDescriptionText;
207 		private Combo fContextCombo;
208 		private SourceViewer fPatternEditor;
209 		private Button fInsertVariableButton;
210 		private Button fAutoInsertCheckbox;
211 		private boolean fIsNameModifiable;
212 
213 		private StatusInfo fValidationStatus;
214 		private boolean fSuppressError= true; // #4354
215 		private Map<String, TextViewerAction> fGlobalActions= new HashMap<>(10);
216 		private List<String> fSelectionActions = new ArrayList<>(3);
217 		private String[][] fContextTypes;
218 
219 		private ContextTypeRegistry fContextTypeRegistry;
220 
221 		private final TemplateVariableProcessor fTemplateProcessor= new TemplateVariableProcessor();
222 
223 		private Template fNewTemplate;
224 
225 
226 		/**
227 		 * Creates a new dialog.
228 		 *
229 		 * @param parent the shell parent of the dialog
230 		 * @param template the template to edit
231 		 * @param edit whether this is a new template or an existing being edited
232 		 * @param isNameModifiable whether the name of the template may be modified
233 		 * @param registry the context type registry to use
234 		 */
EditTemplateDialog(Shell parent, Template template, boolean edit, boolean isNameModifiable, ContextTypeRegistry registry)235 		public EditTemplateDialog(Shell parent, Template template, boolean edit, boolean isNameModifiable, ContextTypeRegistry registry) {
236 			super(parent);
237 
238 			String title= edit
239 				? TemplatesMessages.EditTemplateDialog_title_edit
240 				: TemplatesMessages.EditTemplateDialog_title_new;
241 			setTitle(title);
242 
243 			fOriginalTemplate= template;
244 			fIsNameModifiable= isNameModifiable;
245 
246 			List<String[]> contexts= new ArrayList<>();
247 			for (Iterator<TemplateContextType> it= registry.contextTypes(); it.hasNext();) {
248 				TemplateContextType type= it.next();
249 				contexts.add(new String[] { type.getId(), type.getName() });
250 			}
251 			Collections.sort(contexts, new Comparator<String[]>() {
252 				Collator fCollator= Collator.getInstance();
253 				@Override
254 				public int compare(String[] o1, String[] o2) {
255 					return fCollator.compare(o1[1], o2[1]);
256 				}
257 			});
258 			fContextTypes= contexts.toArray(new String[contexts.size()][]);
259 
260 			fValidationStatus= new StatusInfo();
261 
262 			fContextTypeRegistry= registry;
263 
264 			TemplateContextType type= fContextTypeRegistry.getContextType(template.getContextTypeId());
265 			fTemplateProcessor.setContextType(type);
266 		}
267 
268 		@Override
isResizable()269 		protected boolean isResizable() {
270 			return true;
271 		}
272 
273 		@Override
create()274 		public void create() {
275 			super.create();
276 			// update initial OK button to be disabled for new templates
277 			boolean valid= fNameText == null || !fNameText.getText().trim().isEmpty();
278 			if (!valid) {
279 				StatusInfo status = new StatusInfo();
280 				status.setError(TemplatesMessages.EditTemplateDialog_error_noname);
281 				updateButtonsEnableState(status);
282 	 		}
283 		}
284 
285 		@Override
createDialogArea(Composite ancestor)286 		protected Control createDialogArea(Composite ancestor) {
287 			Composite parent= new Composite(ancestor, SWT.NONE);
288 			GridLayout layout= new GridLayout();
289 			layout.numColumns= 2;
290 			layout.marginHeight= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
291 			layout.marginWidth= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
292 			layout.verticalSpacing= convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
293 			layout.horizontalSpacing= convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
294 			parent.setLayout(layout);
295 			parent.setLayoutData(new GridData(GridData.FILL_BOTH));
296 
297 			ModifyListener listener = e -> doTextWidgetChanged(e.widget);
298 
299 			if (fIsNameModifiable) {
300 				createLabel(parent, TemplatesMessages.EditTemplateDialog_name);
301 
302 				Composite composite= new Composite(parent, SWT.NONE);
303 				composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
304 				layout= new GridLayout();
305 				layout.numColumns= 4;
306 				layout.marginWidth= 0;
307 				layout.marginHeight= 0;
308 				composite.setLayout(layout);
309 
310 				fNameText= createText(composite);
311 				fNameText.addModifyListener(listener);
312 				fNameText.addFocusListener(new FocusListener() {
313 
314 					@Override
315 					public void focusGained(FocusEvent e) {
316 					}
317 
318 					@Override
319 					public void focusLost(FocusEvent e) {
320 						if (fSuppressError) {
321 							fSuppressError= false;
322 							updateButtons();
323 						}
324 					}
325 				});
326 				BidiUtils.applyBidiProcessing(fNameText, BidiUtils.BTD_DEFAULT);
327 
328 				createLabel(composite, TemplatesMessages.EditTemplateDialog_context);
329 				fContextCombo= new Combo(composite, SWT.READ_ONLY);
330 
331 				for (String[] fContextType : fContextTypes) {
332 					fContextCombo.add(fContextType[1]);
333 				}
334 
335 				fContextCombo.addModifyListener(listener);
336 				SWTUtil.setDefaultVisibleItemCount(fContextCombo);
337 
338 				fAutoInsertCheckbox= createCheckbox(composite, TemplatesMessages.EditTemplateDialog_autoinsert);
339 				fAutoInsertCheckbox.setSelection(fOriginalTemplate.isAutoInsertable());
340 			}
341 
342 			createLabel(parent, TemplatesMessages.EditTemplateDialog_description);
343 
344 			int descFlags= fIsNameModifiable ? SWT.BORDER : SWT.BORDER | SWT.READ_ONLY;
345 			fDescriptionText= new Text(parent, descFlags );
346 			fDescriptionText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
347 
348 			fDescriptionText.addModifyListener(listener);
349 			BidiUtils.applyBidiProcessing(fDescriptionText, BidiUtils.BTD_DEFAULT);
350 
351 			Label patternLabel= createLabel(parent, TemplatesMessages.EditTemplateDialog_pattern);
352 			patternLabel.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
353 			fPatternEditor= createEditor(parent, fOriginalTemplate.getPattern());
354 
355 			Label filler= new Label(parent, SWT.NONE);
356 			filler.setLayoutData(new GridData());
357 
358 			Composite composite= new Composite(parent, SWT.NONE);
359 			layout= new GridLayout();
360 			layout.marginWidth= 0;
361 			layout.marginHeight= 0;
362 			composite.setLayout(layout);
363 			composite.setLayoutData(new GridData());
364 
365 			fInsertVariableButton= new Button(composite, SWT.NONE);
366 			fInsertVariableButton.setLayoutData(getButtonGridData(fInsertVariableButton));
367 			fInsertVariableButton.setText(TemplatesMessages.EditTemplateDialog_insert_variable);
368 			fInsertVariableButton.addSelectionListener(new SelectionListener() {
369 				@Override
370 				public void widgetSelected(SelectionEvent e) {
371 					fPatternEditor.getTextWidget().setFocus();
372 					fPatternEditor.doOperation(ISourceViewer.CONTENTASSIST_PROPOSALS);
373 				}
374 
375 				@Override
376 				public void widgetDefaultSelected(SelectionEvent e) {}
377 			});
378 
379 			fDescriptionText.setText(fOriginalTemplate.getDescription());
380 			if (fIsNameModifiable) {
381 				fNameText.setText(fOriginalTemplate.getName());
382 				fNameText.addModifyListener(listener);
383 				fContextCombo.select(getIndex(fOriginalTemplate.getContextTypeId()));
384 			} else {
385 				fPatternEditor.getControl().setFocus();
386 			}
387 			initializeActions();
388 
389 			applyDialogFont(parent);
390 			return composite;
391 		}
392 
doTextWidgetChanged(Widget w)393 		private void doTextWidgetChanged(Widget w) {
394 			if (w == fNameText) {
395 				fSuppressError= false;
396 				updateButtons();
397 			} else if (w == fContextCombo) {
398 				String contextId= getContextId();
399 				fTemplateProcessor.setContextType(fContextTypeRegistry.getContextType(contextId));
400 			} else if (w == fDescriptionText) {
401 				// oh, nothing
402 			}
403 		}
404 
getContextId()405 		private String getContextId() {
406 			if (fContextCombo != null && !fContextCombo.isDisposed()) {
407 				String name= fContextCombo.getText();
408 				for (String[] fContextType : fContextTypes) {
409 					if (name.equals(fContextType[1])) {
410 						return fContextType[0];
411 					}
412 				}
413 			}
414 
415 			return fOriginalTemplate.getContextTypeId();
416 		}
417 
doSourceChanged(IDocument document)418 		private void doSourceChanged(IDocument document) {
419 			String text= document.get();
420 			fValidationStatus.setOK();
421 			TemplateContextType contextType= fContextTypeRegistry.getContextType(getContextId());
422 			if (contextType != null) {
423 				try {
424 					contextType.validate(text);
425 				} catch (TemplateException e) {
426 					fValidationStatus.setError(e.getLocalizedMessage());
427 				}
428 			}
429 
430 			updateAction(ITextEditorActionConstants.UNDO);
431 			updateButtons();
432 		}
433 
434 		/**
435 		 * Return the grid data for the button.
436 		 *
437 		 * @param button the button
438 		 * @return the grid data
439 		 */
getButtonGridData(Button button)440 		private static GridData getButtonGridData(Button button) {
441 			GridData data= new GridData(GridData.FILL_HORIZONTAL);
442 			// TODO get some button hints.
443 //			data.heightHint= SWTUtil.getButtonHeightHint(button);
444 
445 			return data;
446 		}
447 
createLabel(Composite parent, String name)448 		private static Label createLabel(Composite parent, String name) {
449 			Label label= new Label(parent, SWT.NULL);
450 			label.setText(name);
451 			label.setLayoutData(new GridData());
452 
453 			return label;
454 		}
455 
createText(Composite parent)456 		private static Text createText(Composite parent) {
457 			Text text= new Text(parent, SWT.BORDER);
458 			text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
459 
460 			return text;
461 		}
462 
createCheckbox(Composite parent, String name)463 		private static Button createCheckbox(Composite parent, String name) {
464 			Button button= new Button(parent, SWT.CHECK);
465 			button.setText(name);
466 			button.setLayoutData(new GridData());
467 
468 			return button;
469 		}
470 
createEditor(Composite parent, String pattern)471 		private SourceViewer createEditor(Composite parent, String pattern) {
472 			SourceViewer viewer= createViewer(parent);
473 			viewer.setEditable(true);
474 
475 			IDocument document= viewer.getDocument();
476 			if (document != null)
477 				document.set(pattern);
478 			else {
479 				document= new Document(pattern);
480 				viewer.setDocument(document);
481 			}
482 
483 			int nLines= document.getNumberOfLines();
484 			if (nLines < 5) {
485 				nLines= 5;
486 			} else if (nLines > 12) {
487 				nLines= 12;
488 			}
489 
490 			Control control= viewer.getControl();
491 			GridData data= new GridData(GridData.FILL_BOTH);
492 			data.widthHint= convertWidthInCharsToPixels(80);
493 			data.heightHint= convertHeightInCharsToPixels(nLines);
494 			control.setLayoutData(data);
495 
496 			viewer.addTextListener(event -> {
497 				if (event.getDocumentEvent() != null)
498 					doSourceChanged(event.getDocumentEvent().getDocument());
499 			});
500 
501 			viewer.addSelectionChangedListener(event -> updateSelectionDependentActions());
502 
503 			return viewer;
504 		}
505 
506 		/**
507 		 * Creates the viewer to be used to display the pattern. Subclasses may override.
508 		 *
509 		 * @param parent the parent composite of the viewer
510 		 * @return a configured <code>SourceViewer</code>
511 		 */
createViewer(Composite parent)512 		protected SourceViewer createViewer(Composite parent) {
513 			SourceViewer viewer= new SourceViewer(parent, null, null, false, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
514 			SourceViewerConfiguration configuration= new SourceViewerConfiguration() {
515 				@Override
516 				public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
517 
518 					ContentAssistant assistant= new ContentAssistant();
519 					assistant.enableAutoActivation(true);
520 					assistant.enableAutoInsert(true);
521 					assistant.setContentAssistProcessor(fTemplateProcessor, IDocument.DEFAULT_CONTENT_TYPE);
522 					return assistant;
523 				}
524 			};
525 			viewer.configure(configuration);
526 			return viewer;
527 		}
528 
initializeActions()529 		private void initializeActions() {
530 			final ArrayList<IHandlerActivation> handlerActivations= new ArrayList<>(3);
531 			final IHandlerService handlerService= PlatformUI.getWorkbench().getAdapter(IHandlerService.class);
532 			final Expression expression= new ActiveShellExpression(fPatternEditor.getControl().getShell());
533 
534 			getShell().addDisposeListener(e -> handlerService.deactivateHandlers(handlerActivations));
535 
536 			fPatternEditor.getTextWidget().addFocusListener(new FocusListener() {
537 				@Override
538 				public void focusLost(FocusEvent e) {
539 					handlerService.deactivateHandlers(handlerActivations);
540 				}
541 
542 				@Override
543 				public void focusGained(FocusEvent e) {
544 					IAction action= fGlobalActions.get(ITextEditorActionConstants.REDO);
545 					handlerActivations.add(handlerService.activateHandler(IWorkbenchCommandConstants.EDIT_REDO, new ActionHandler(action), expression));
546 					action= fGlobalActions.get(ITextEditorActionConstants.UNDO);
547 					handlerActivations.add(handlerService.activateHandler(IWorkbenchCommandConstants.EDIT_UNDO, new ActionHandler(action), expression));
548 					action= fGlobalActions.get(ITextEditorActionConstants.CONTENT_ASSIST);
549 					handlerActivations.add(handlerService.activateHandler(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS, new ActionHandler(action), expression));
550 				}
551 			});
552 
553 			TextViewerAction action= new TextViewerAction(fPatternEditor, ITextOperationTarget.UNDO);
554 			action.setText(TemplatesMessages.EditTemplateDialog_undo);
555 			fGlobalActions.put(ITextEditorActionConstants.UNDO, action);
556 
557 			action= new TextViewerAction(fPatternEditor, ITextOperationTarget.REDO);
558 			action.setText(TemplatesMessages.EditTemplateDialog_redo);
559 			fGlobalActions.put(ITextEditorActionConstants.REDO, action);
560 
561 			action= new TextViewerAction(fPatternEditor, ITextOperationTarget.CUT);
562 			action.setText(TemplatesMessages.EditTemplateDialog_cut);
563 			fGlobalActions.put(ITextEditorActionConstants.CUT, action);
564 
565 			action= new TextViewerAction(fPatternEditor, ITextOperationTarget.COPY);
566 			action.setText(TemplatesMessages.EditTemplateDialog_copy);
567 			fGlobalActions.put(ITextEditorActionConstants.COPY, action);
568 
569 			action= new TextViewerAction(fPatternEditor, ITextOperationTarget.PASTE);
570 			action.setText(TemplatesMessages.EditTemplateDialog_paste);
571 			fGlobalActions.put(ITextEditorActionConstants.PASTE, action);
572 
573 			action= new TextViewerAction(fPatternEditor, ITextOperationTarget.SELECT_ALL);
574 			action.setText(TemplatesMessages.EditTemplateDialog_select_all);
575 			fGlobalActions.put(ITextEditorActionConstants.SELECT_ALL, action);
576 
577 			action= new TextViewerAction(fPatternEditor, ISourceViewer.CONTENTASSIST_PROPOSALS);
578 			action.setText(TemplatesMessages.EditTemplateDialog_content_assist);
579 			fGlobalActions.put(ITextEditorActionConstants.CONTENT_ASSIST, action);
580 
581 			fSelectionActions.add(ITextEditorActionConstants.CUT);
582 			fSelectionActions.add(ITextEditorActionConstants.COPY);
583 			fSelectionActions.add(ITextEditorActionConstants.PASTE);
584 
585 			// create context menu
586 			MenuManager manager= new MenuManager(null, null);
587 			manager.setRemoveAllWhenShown(true);
588 			manager.addMenuListener(this::fillContextMenu);
589 
590 			StyledText text= fPatternEditor.getTextWidget();
591 			Menu menu= manager.createContextMenu(text);
592 			text.setMenu(menu);
593 		}
594 
fillContextMenu(IMenuManager menu)595 		private void fillContextMenu(IMenuManager menu) {
596 			menu.add(new GroupMarker(ITextEditorActionConstants.GROUP_UNDO));
597 			menu.appendToGroup(ITextEditorActionConstants.GROUP_UNDO, fGlobalActions.get(ITextEditorActionConstants.UNDO));
598 			menu.appendToGroup(ITextEditorActionConstants.GROUP_UNDO, fGlobalActions.get(ITextEditorActionConstants.REDO));
599 
600 			menu.add(new Separator(ITextEditorActionConstants.GROUP_EDIT));
601 			menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.CUT));
602 			menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.COPY));
603 			menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.PASTE));
604 			menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, fGlobalActions.get(ITextEditorActionConstants.SELECT_ALL));
605 
606 			menu.add(new Separator("templates")); //$NON-NLS-1$
607 			menu.appendToGroup("templates", fGlobalActions.get("ContentAssistProposal")); //$NON-NLS-1$ //$NON-NLS-2$
608 		}
609 
updateSelectionDependentActions()610 		private void updateSelectionDependentActions() {
611 			Iterator<String> iterator= fSelectionActions.iterator();
612 			while (iterator.hasNext())
613 				updateAction(iterator.next());
614 		}
615 
updateAction(String actionId)616 		private void updateAction(String actionId) {
617 			IAction action= fGlobalActions.get(actionId);
618 			if (action instanceof IUpdate)
619 				((IUpdate) action).update();
620 		}
621 
getIndex(String contextid)622 		private int getIndex(String contextid) {
623 
624 			if (contextid == null)
625 				return -1;
626 
627 			for (int i= 0; i < fContextTypes.length; i++) {
628 				if (contextid.equals(fContextTypes[i][0])) {
629 					return i;
630 				}
631 			}
632 			return -1;
633 		}
634 
updateButtons()635 		private void updateButtons() {
636 			StatusInfo status;
637 
638 			boolean valid= fNameText == null || !fNameText.getText().trim().isEmpty();
639 			if (!valid) {
640 				status = new StatusInfo();
641 				if (!fSuppressError)
642 					status.setError(TemplatesMessages.EditTemplateDialog_error_noname);
643 			} else if (!isValidPattern(fPatternEditor.getDocument().get())) {
644 				status = new StatusInfo();
645 				if (!fSuppressError)
646 					status.setError(TemplatesMessages.EditTemplateDialog_error_invalidPattern);
647 	 		} else {
648 	 			status= fValidationStatus;
649 	 		}
650 			updateStatus(status);
651 		}
652 
653 		/**
654 		 * Validates the pattern.
655 		 * <p>
656 		 * The default implementation rejects invalid XML characters.
657 		 * </p>
658 		 *
659 		 * @param pattern the pattern to verify
660 		 * @return <code>true</code> if the pattern is valid
661 		 * @since 3.7 protected, before it was private
662 		 */
isValidPattern(String pattern)663 		protected boolean isValidPattern(String pattern) {
664 			for (int i= 0; i < pattern.length(); i++) {
665 				char ch= pattern.charAt(i);
666 				if (!(ch == 9 || ch == 10 || ch == 13 || ch >= 32))
667 					return false;
668 			}
669 			return true;
670 		}
671 
672 		/*
673 		 * @since 3.1
674 		 */
675 		@Override
okPressed()676 		protected void okPressed() {
677 			String name= fNameText == null ? fOriginalTemplate.getName() : fNameText.getText();
678 			boolean isAutoInsertable= fAutoInsertCheckbox != null && fAutoInsertCheckbox.getSelection();
679 			fNewTemplate= new Template(name, fDescriptionText.getText(), getContextId(), fPatternEditor.getDocument().get(), isAutoInsertable);
680 			super.okPressed();
681 		}
682 
683 		/**
684 		 * Returns the created template.
685 		 *
686 		 * @return the created template
687 		 * @since 3.1
688 		 */
getTemplate()689 		public Template getTemplate() {
690 			return fNewTemplate;
691 		}
692 
693 		/**
694 		 * Returns the content assist processor that
695 		 * suggests template variables.
696 		 *
697 		 * @return the processor to suggest variables
698 		 * @since 3.3
699 		 */
getTemplateProcessor()700 		protected IContentAssistProcessor getTemplateProcessor() {
701 			return fTemplateProcessor;
702 		}
703 
704 		@Override
getDialogBoundsSettings()705 		protected IDialogSettings getDialogBoundsSettings() {
706 			String sectionName= getClass().getName() + "_dialogBounds"; //$NON-NLS-1$
707 			IDialogSettings settings= TextEditorPlugin.getDefault().getDialogSettings();
708 			IDialogSettings section= settings.getSection(sectionName);
709 			if (section == null)
710 				section= settings.addNewSection(sectionName);
711 			return section;
712 		}
713 
714 	}
715 
716 
717 	/**
718 	 * Label provider for templates.
719 	 */
720 	private class TemplateLabelProvider extends LabelProvider implements ITableLabelProvider {
721 
722 		@Override
getColumnImage(Object element, int columnIndex)723 		public Image getColumnImage(Object element, int columnIndex) {
724 			return null;
725 		}
726 
727 		@Override
getColumnText(Object element, int columnIndex)728 		public String getColumnText(Object element, int columnIndex) {
729 			TemplatePersistenceData data = (TemplatePersistenceData) element;
730 			Template template= data.getTemplate();
731 
732 			switch (columnIndex) {
733 				case 0:
734 					return template.getName();
735 				case 1:
736 					TemplateContextType type= fContextTypeRegistry.getContextType(template.getContextTypeId());
737 					if (type != null)
738 						return type.getName();
739 					return template.getContextTypeId();
740 				case 2:
741 					return template.getDescription();
742 				case 3:
743 					return template.isAutoInsertable() ? TemplatesMessages.TemplatePreferencePage_on : "";  //$NON-NLS-1$
744 				default:
745 					return ""; //$NON-NLS-1$
746 			}
747 		}
748 	}
749 
750 
751 	/** Qualified key for formatter preference. */
752 	private static final String DEFAULT_FORMATTER_PREFERENCE_KEY= "org.eclipse.ui.texteditor.templates.preferences.format_templates"; //$NON-NLS-1$
753 
754 	/** The table presenting the templates. */
755 	private CheckboxTableViewer fTableViewer;
756 
757 	/* buttons */
758 	private Button fAddButton;
759 	private Button fEditButton;
760 	private Button fImportButton;
761 	private Button fExportButton;
762 	private Button fRemoveButton;
763 	private Button fRestoreButton;
764 	private Button fRevertButton;
765 
766 	/** The viewer displays the pattern of selected template. */
767 	private SourceViewer fPatternViewer;
768 	/** Format checkbox. This gets conditionally added. */
769 	private Button fFormatButton;
770 	/** The store for our templates. */
771 	private TemplateStore fTemplateStore;
772 	/** The context type registry. */
773 	private ContextTypeRegistry fContextTypeRegistry;
774 
775 
776 	/**
777 	 * Creates a new template preference page.
778 	 */
TemplatePreferencePage()779 	protected TemplatePreferencePage() {
780 		super();
781 
782 		setDescription(TemplatesMessages.TemplatePreferencePage_message);
783 	}
784 
785 	/**
786 	 * Returns the template store.
787 	 *
788 	 * @return the template store
789 	 */
getTemplateStore()790 	public TemplateStore getTemplateStore() {
791 		return fTemplateStore;
792 	}
793 
794 	/**
795 	 * Returns the context type registry.
796 	 *
797 	 * @return the context type registry
798 	 */
getContextTypeRegistry()799 	public ContextTypeRegistry getContextTypeRegistry() {
800 		return fContextTypeRegistry;
801 	}
802 
803 	/**
804 	 * Sets the template store.
805 	 *
806 	 * @param store the new template store
807 	 */
setTemplateStore(TemplateStore store)808 	public void setTemplateStore(TemplateStore store) {
809 		fTemplateStore= store;
810 	}
811 
812 	/**
813 	 * Sets the context type registry.
814 	 *
815 	 * @param registry the new context type registry
816 	 */
setContextTypeRegistry(ContextTypeRegistry registry)817 	public void setContextTypeRegistry(ContextTypeRegistry registry) {
818 		fContextTypeRegistry= registry;
819 	}
820 
821 	@Override
init(IWorkbench workbench)822 	public void init(IWorkbench workbench) {
823 	}
824 
825 	@Override
createContents(Composite ancestor)826 	protected Control createContents(Composite ancestor) {
827 		Composite parent= new Composite(ancestor, SWT.NONE);
828 		GridLayout layout= new GridLayout();
829 		layout.numColumns= 2;
830 		layout.marginHeight= 0;
831 		layout.marginWidth= 0;
832 		parent.setLayout(layout);
833 
834 		Composite innerParent= new Composite(parent, SWT.NONE);
835 		GridLayout innerLayout= new GridLayout();
836 		innerLayout.numColumns= 2;
837 		innerLayout.marginHeight= 0;
838 		innerLayout.marginWidth= 0;
839 		innerParent.setLayout(innerLayout);
840 		GridData gd= new GridData(GridData.FILL_BOTH);
841 		gd.horizontalSpan= 2;
842 		innerParent.setLayoutData(gd);
843 
844 		Composite tableComposite= new Composite(innerParent, SWT.NONE);
845 		GridData data= new GridData(GridData.FILL_BOTH);
846 		data.widthHint= 360;
847 		data.heightHint= convertHeightInCharsToPixels(10);
848 		tableComposite.setLayoutData(data);
849 
850 		ColumnLayout columnLayout= new ColumnLayout();
851 		tableComposite.setLayout(columnLayout);
852 		Table table= new Table(tableComposite, SWT.CHECK | SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL);
853 
854 		table.setHeaderVisible(true);
855 		table.setLinesVisible(true);
856 
857 		GC gc= new GC(getShell());
858 		gc.setFont(JFaceResources.getDialogFont());
859 
860 		TemplateViewerComparator viewerComparator= new TemplateViewerComparator();
861 
862 		TableColumn column1= new TableColumn(table, SWT.NONE);
863 		column1.setText(TemplatesMessages.TemplatePreferencePage_column_name);
864 		int minWidth= computeMinimumColumnWidth(gc, TemplatesMessages.TemplatePreferencePage_column_name);
865 		columnLayout.addColumnData(new ColumnWeightData(2, minWidth, true));
866 		column1.addSelectionListener(new TemplateColumnSelectionAdapter(column1, 0, viewerComparator));
867 
868 		TableColumn column2= new TableColumn(table, SWT.NONE);
869 		column2.setText(TemplatesMessages.TemplatePreferencePage_column_context);
870 		minWidth= computeMinimumContextColumnWidth(gc);
871 		columnLayout.addColumnData(new ColumnWeightData(1, minWidth, true));
872 		column2.addSelectionListener(new TemplateColumnSelectionAdapter(column2, 1, viewerComparator));
873 
874 		TableColumn column3= new TableColumn(table, SWT.NONE);
875 		column3.setText(TemplatesMessages.TemplatePreferencePage_column_description);
876 		minWidth= computeMinimumColumnWidth(gc, TemplatesMessages.TemplatePreferencePage_column_description);
877 		columnLayout.addColumnData(new ColumnWeightData(3, minWidth, true));
878 		column3.addSelectionListener(new TemplateColumnSelectionAdapter(column3, 2, viewerComparator));
879 
880 		TableColumn column4= new TableColumn(table, SWT.NONE);
881 		column4.setAlignment(SWT.CENTER);
882 		column4.setText(TemplatesMessages.TemplatePreferencePage_column_autoinsert);
883 		minWidth= computeMinimumColumnWidth(gc, TemplatesMessages.TemplatePreferencePage_column_autoinsert);
884 		minWidth= Math.max(minWidth, computeMinimumColumnWidth(gc, TemplatesMessages.TemplatePreferencePage_on));
885 		columnLayout.addColumnData(new ColumnPixelData(minWidth, false, false));
886 		column4.addSelectionListener(new TemplateColumnSelectionAdapter(column4, 3, viewerComparator));
887 
888 		gc.dispose();
889 
890 		fTableViewer= new CheckboxTableViewer(table);
891 		fTableViewer.setLabelProvider(new TemplateLabelProvider());
892 		fTableViewer.setContentProvider(new TemplateContentProvider());
893 		fTableViewer.setComparator(viewerComparator);
894 
895 		// Specify default sorting
896 		table.setSortColumn(column1);
897 		table.setSortDirection(viewerComparator.getDirection());
898 
899 		fTableViewer.addDoubleClickListener(e -> edit());
900 
901 		fTableViewer.addSelectionChangedListener(e -> selectionChanged1());
902 
903 		fTableViewer.addCheckStateListener(event -> {
904 			TemplatePersistenceData d = (TemplatePersistenceData) event.getElement();
905 			d.setEnabled(event.getChecked());
906 		});
907 
908 		BidiUtils.applyTextDirection(fTableViewer.getControl(), BidiUtils.BTD_DEFAULT);
909 
910 		Composite buttons= new Composite(innerParent, SWT.NONE);
911 		buttons.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
912 		layout= new GridLayout();
913 		layout.marginHeight= 0;
914 		layout.marginWidth= 0;
915 		buttons.setLayout(layout);
916 
917 		fAddButton= new Button(buttons, SWT.PUSH);
918 		fAddButton.setText(TemplatesMessages.TemplatePreferencePage_new);
919 		fAddButton.setLayoutData(getButtonGridData(fAddButton));
920 		fAddButton.addListener(SWT.Selection, e -> add());
921 
922 		fEditButton= new Button(buttons, SWT.PUSH);
923 		fEditButton.setText(TemplatesMessages.TemplatePreferencePage_edit);
924 		fEditButton.setLayoutData(getButtonGridData(fEditButton));
925 		fEditButton.addListener(SWT.Selection, e -> edit());
926 
927 		fRemoveButton= new Button(buttons, SWT.PUSH);
928 		fRemoveButton.setText(TemplatesMessages.TemplatePreferencePage_remove);
929 		fRemoveButton.setLayoutData(getButtonGridData(fRemoveButton));
930 		fRemoveButton.addListener(SWT.Selection, e -> remove());
931 
932 		createSeparator(buttons);
933 
934 		fRestoreButton= new Button(buttons, SWT.PUSH);
935 		fRestoreButton.setText(TemplatesMessages.TemplatePreferencePage_restore);
936 		fRestoreButton.setLayoutData(getButtonGridData(fRestoreButton));
937 		fRestoreButton.addListener(SWT.Selection, e -> restoreDeleted());
938 
939 		fRevertButton= new Button(buttons, SWT.PUSH);
940 		fRevertButton.setText(TemplatesMessages.TemplatePreferencePage_revert);
941 		fRevertButton.setLayoutData(getButtonGridData(fRevertButton));
942 		fRevertButton.addListener(SWT.Selection, e -> revert());
943 
944 		createSeparator(buttons);
945 
946 		fImportButton= new Button(buttons, SWT.PUSH);
947 		fImportButton.setText(TemplatesMessages.TemplatePreferencePage_import);
948 		fImportButton.setLayoutData(getButtonGridData(fImportButton));
949 		fImportButton.addListener(SWT.Selection, e -> import_());
950 
951 		fExportButton= new Button(buttons, SWT.PUSH);
952 		fExportButton.setText(TemplatesMessages.TemplatePreferencePage_export);
953 		fExportButton.setLayoutData(getButtonGridData(fExportButton));
954 		fExportButton.addListener(SWT.Selection, e -> export());
955 
956 		fPatternViewer= doCreateViewer(parent);
957 
958 		if (isShowFormatterSetting()) {
959 			fFormatButton= new Button(parent, SWT.CHECK);
960 			fFormatButton.setText(TemplatesMessages.TemplatePreferencePage_use_code_formatter);
961 			GridData gd1= new GridData();
962 			gd1.horizontalSpan= 2;
963 			fFormatButton.setLayoutData(gd1);
964 			fFormatButton.setSelection(getPreferenceStore().getBoolean(getFormatterPreferenceKey()));
965 		}
966 
967 		fTableViewer.setInput(fTemplateStore);
968 		fTableViewer.setAllChecked(false);
969 		fTableViewer.setCheckedElements(getEnabledTemplates());
970 
971 		updateButtons();
972 		Dialog.applyDialogFont(parent);
973 		innerParent.layout();
974 
975 		return parent;
976 	}
977 
978 	/*
979 	 * @since 3.2
980 	 */
computeMinimumColumnWidth(GC gc, String string)981 	private int computeMinimumColumnWidth(GC gc, String string) {
982 		return gc.stringExtent(string).x + 10; // pad 10 to accommodate table header trimmings
983 	}
984 
985 	/*
986 	 * @since 3.4
987 	 */
computeMinimumContextColumnWidth(GC gc)988 	private int computeMinimumContextColumnWidth(GC gc) {
989 		int width= gc.stringExtent(TemplatesMessages.TemplatePreferencePage_column_context).x;
990 		Iterator<TemplateContextType> iter= getContextTypeRegistry().contextTypes();
991 		while (iter.hasNext()) {
992 			TemplateContextType contextType= iter.next();
993 			width= Math.max(width, gc.stringExtent(contextType.getName()).x);
994 		}
995 		return width;
996 	}
997 
998 	/**
999 	 * Creates a separator between buttons.
1000 	 *
1001 	 * @param parent the parent composite
1002 	 * @return a separator
1003 	 */
createSeparator(Composite parent)1004 	private Label createSeparator(Composite parent) {
1005 		Label separator= new Label(parent, SWT.NONE);
1006 		separator.setVisible(false);
1007 		GridData gd= new GridData();
1008 		gd.horizontalAlignment= GridData.FILL;
1009 		gd.verticalAlignment= GridData.BEGINNING;
1010 		gd.heightHint= 4;
1011 		separator.setLayoutData(gd);
1012 		return separator;
1013 	}
1014 
1015 
1016 	/**
1017 	 * Returns whether the formatter preference checkbox should be shown.
1018 	 *
1019 	 * @return <code>true</code> if the formatter preference checkbox should
1020 	 *         be shown, <code>false</code> otherwise
1021 	 */
isShowFormatterSetting()1022 	protected boolean isShowFormatterSetting() {
1023 		return true;
1024 	}
1025 
getEnabledTemplates()1026 	private TemplatePersistenceData[] getEnabledTemplates() {
1027 		List<TemplatePersistenceData> enabled= new ArrayList<>();
1028 		TemplatePersistenceData[] datas= fTemplateStore.getTemplateData(false);
1029 		for (TemplatePersistenceData data : datas) {
1030 			if (data.isEnabled()) {
1031 				enabled.add(data);
1032 			}
1033 		}
1034 		return enabled.toArray(new TemplatePersistenceData[enabled.size()]);
1035 	}
1036 
doCreateViewer(Composite parent)1037 	private SourceViewer doCreateViewer(Composite parent) {
1038 		Label label= new Label(parent, SWT.NONE);
1039 		label.setText(TemplatesMessages.TemplatePreferencePage_preview);
1040 		GridData data= new GridData();
1041 		data.horizontalSpan= 2;
1042 		label.setLayoutData(data);
1043 
1044 		SourceViewer viewer= createViewer(parent);
1045 
1046 		viewer.setEditable(false);
1047 		Cursor arrowCursor= viewer.getTextWidget().getDisplay().getSystemCursor(SWT.CURSOR_ARROW);
1048 		viewer.getTextWidget().setCursor(arrowCursor);
1049 
1050 		// Don't set caret to 'null' as this causes https://bugs.eclipse.org/293263
1051 //		viewer.getTextWidget().setCaret(null);
1052 
1053 		Control control= viewer.getControl();
1054 		data= new GridData(GridData.FILL_BOTH);
1055 		data.horizontalSpan= 2;
1056 		data.heightHint= convertHeightInCharsToPixels(5);
1057 		control.setLayoutData(data);
1058 
1059 		return viewer;
1060 	}
1061 
1062 	/**
1063 	 * Creates, configures and returns a source viewer to present the template
1064 	 * pattern on the preference page. Clients may override to provide a custom
1065 	 * source viewer featuring e.g. syntax coloring.
1066 	 *
1067 	 * @param parent the parent control
1068 	 * @return a configured source viewer
1069 	 */
createViewer(Composite parent)1070 	protected SourceViewer createViewer(Composite parent) {
1071 		SourceViewer viewer= new SourceViewer(parent, null, null, false, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
1072 		SourceViewerConfiguration configuration= new SourceViewerConfiguration();
1073 		viewer.configure(configuration);
1074 		IDocument document= new Document();
1075 		viewer.setDocument(document);
1076 		return viewer;
1077 	}
1078 
1079 	/**
1080 	 * Return the grid data for the button.
1081 	 *
1082 	 * @param button the button
1083 	 * @return the grid data
1084 	 */
getButtonGridData(Button button)1085 	private static GridData getButtonGridData(Button button) {
1086 		GridData data= new GridData(GridData.FILL_HORIZONTAL);
1087 		// TODO replace SWTUtil
1088 //		data.widthHint= SWTUtil.getButtonWidthHint(button);
1089 //		data.heightHint= SWTUtil.getButtonHeightHint(button);
1090 
1091 		return data;
1092 	}
1093 
selectionChanged1()1094 	private void selectionChanged1() {
1095 		updateViewerInput();
1096 		updateButtons();
1097 	}
1098 
1099 	/**
1100 	 * Updates the pattern viewer.
1101 	 */
updateViewerInput()1102 	protected void updateViewerInput() {
1103 		IStructuredSelection selection = fTableViewer.getStructuredSelection();
1104 
1105 		if (selection.size() == 1) {
1106 			TemplatePersistenceData data= (TemplatePersistenceData) selection.getFirstElement();
1107 			Template template= data.getTemplate();
1108 			fPatternViewer.getDocument().set(template.getPattern());
1109 		} else {
1110 			fPatternViewer.getDocument().set(""); //$NON-NLS-1$
1111 		}
1112 	}
1113 
1114 	/**
1115 	 * Updates the buttons.
1116 	 */
updateButtons()1117 	protected void updateButtons() {
1118 		IStructuredSelection selection = fTableViewer.getStructuredSelection();
1119 		int selectionCount= selection.size();
1120 		int itemCount= fTableViewer.getTable().getItemCount();
1121 		boolean canRestore= fTemplateStore.getTemplateData(true).length != fTemplateStore.getTemplateData(false).length;
1122 		boolean canRevert= false;
1123 		for (Iterator<?> it= selection.iterator(); it.hasNext();) {
1124 			TemplatePersistenceData data= (TemplatePersistenceData) it.next();
1125 			if (data.isModified()) {
1126 				canRevert= true;
1127 				break;
1128 			}
1129 		}
1130 
1131 		fEditButton.setEnabled(selectionCount == 1);
1132 		fExportButton.setEnabled(selectionCount > 0);
1133 		fRemoveButton.setEnabled(selectionCount > 0 && selectionCount <= itemCount);
1134 		fRestoreButton.setEnabled(canRestore);
1135 		fRevertButton.setEnabled(canRevert);
1136 	}
1137 
add()1138 	private void add() {
1139 
1140 		Iterator<TemplateContextType> it= fContextTypeRegistry.contextTypes();
1141 		if (it.hasNext()) {
1142 			Template template= new Template("", "", it.next().getId(), "", true);   //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
1143 
1144 			Template newTemplate= editTemplate(template, false, true);
1145 			if (newTemplate != null) {
1146 				TemplatePersistenceData data= new TemplatePersistenceData(newTemplate, true);
1147 				fTemplateStore.add(data);
1148 				fTableViewer.refresh();
1149 				fTableViewer.setChecked(data, true);
1150 				fTableViewer.setSelection(new StructuredSelection(data));
1151 			}
1152 		}
1153 	}
1154 
1155 	/**
1156 	 * Creates the edit dialog. Subclasses may override this method to provide a
1157 	 * custom dialog.
1158 	 *
1159 	 * @param template the template being edited
1160 	 * @param edit whether the dialog should be editable
1161 	 * @param isNameModifiable whether the template name may be modified
1162 	 * @return an <code>EditTemplateDialog</code> which will be opened.
1163 	 * @deprecated not called any longer as of 3.1 - use {@link #editTemplate(Template, boolean, boolean)}
1164 	 */
1165 	@Deprecated
createTemplateEditDialog(Template template, boolean edit, boolean isNameModifiable)1166 	protected Dialog createTemplateEditDialog(Template template, boolean edit, boolean isNameModifiable) {
1167 		return new EditTemplateDialog(getShell(), template, edit, isNameModifiable, fContextTypeRegistry);
1168 	}
1169 
1170 	/**
1171 	 * Creates the edit dialog. Subclasses may override this method to provide a
1172 	 * custom dialog.
1173 	 *
1174 	 * @param template the template being edited
1175 	 * @param edit whether the dialog should be editable
1176 	 * @param isNameModifiable whether the template name may be modified
1177 	 * @return the created or modified template, or <code>null</code> if the edition failed
1178 	 * @since 3.1
1179 	 */
editTemplate(Template template, boolean edit, boolean isNameModifiable)1180 	protected Template editTemplate(Template template, boolean edit, boolean isNameModifiable) {
1181 		EditTemplateDialog dialog= new EditTemplateDialog(getShell(), template, edit, isNameModifiable, fContextTypeRegistry);
1182 		if (dialog.open() == Window.OK) {
1183 			return dialog.getTemplate();
1184 		}
1185 		return null;
1186 	}
1187 
edit()1188 	private void edit() {
1189 		IStructuredSelection selection = fTableViewer.getStructuredSelection();
1190 
1191 		Object[] objects= selection.toArray();
1192 		if ((objects == null) || (objects.length != 1))
1193 			return;
1194 
1195 		TemplatePersistenceData data= (TemplatePersistenceData) selection.getFirstElement();
1196 		edit(data);
1197 	}
1198 
edit(TemplatePersistenceData data)1199 	private void edit(TemplatePersistenceData data) {
1200 		Template oldTemplate= data.getTemplate();
1201 		Template newTemplate= editTemplate(new Template(oldTemplate), true, true);
1202 		if (newTemplate != null) {
1203 
1204 			if (!newTemplate.getName().equals(oldTemplate.getName()) &&
1205 					openCreateNewOrRenameDialog())
1206 			{
1207 				data= new TemplatePersistenceData(newTemplate, true);
1208 				fTemplateStore.add(data);
1209 				fTableViewer.refresh();
1210 			} else {
1211 				data.setTemplate(newTemplate);
1212 				fTableViewer.refresh(data);
1213 			}
1214 			selectionChanged1();
1215 			fTableViewer.setChecked(data, data.isEnabled());
1216 			fTableViewer.setSelection(new StructuredSelection(data));
1217 		}
1218 	}
1219 
import_()1220 	private void import_() {
1221 		FileDialog dialog= new FileDialog(getShell());
1222 		dialog.setText(TemplatesMessages.TemplatePreferencePage_import_title);
1223 		dialog.setFilterExtensions(new String[] {TemplatesMessages.TemplatePreferencePage_import_extension});
1224 		String path= dialog.open();
1225 
1226 		if (path == null)
1227 			return;
1228 
1229 		try {
1230 			ArrayList<TemplatePersistenceData> selection= new ArrayList<>();
1231 			TemplateReaderWriter reader= new TemplateReaderWriter();
1232 			File file= new File(path);
1233 			if (file.exists()) {
1234 				try (InputStream input = new BufferedInputStream(new FileInputStream(file))) {
1235 					TemplatePersistenceData[] datas= reader.read(input, null);
1236 					for (TemplatePersistenceData data : datas) {
1237 						fTemplateStore.add(data);
1238 						String id= data.getId();
1239 						if (id == null) {
1240 							selection.add(data);
1241 						} else {
1242 							data= fTemplateStore.getTemplateData(id);
1243 							if (data != null) {
1244 								selection.add(data);
1245 							}
1246 						}
1247 					}
1248 				}
1249 			}
1250 
1251 			fTableViewer.refresh();
1252 			fTableViewer.setAllChecked(false);
1253 			fTableViewer.setCheckedElements(getEnabledTemplates());
1254 			fTableViewer.setSelection(new StructuredSelection(selection), true);
1255 			selectionChanged1();
1256 
1257 		} catch (FileNotFoundException e) {
1258 			openReadErrorDialog(e);
1259 		} catch (IOException e) {
1260 			openReadErrorDialog(e);
1261 		}
1262 	}
1263 
export()1264 	private void export() {
1265 		IStructuredSelection selection = fTableViewer.getStructuredSelection();
1266 		Object[] templates= selection.toArray();
1267 
1268 		TemplatePersistenceData[] datas= new TemplatePersistenceData[templates.length];
1269 		for (int i= 0; i != templates.length; i++)
1270 			datas[i]= (TemplatePersistenceData) templates[i];
1271 
1272 		export(datas);
1273 	}
1274 
export(TemplatePersistenceData[] templates)1275 	private void export(TemplatePersistenceData[] templates) {
1276 		FileDialog dialog= new FileDialog(getShell(), SWT.SAVE);
1277 		dialog.setText(TemplatesMessages.TemplatePreferencePage_export_title);
1278 		dialog.setFilterExtensions(new String[] {TemplatesMessages.TemplatePreferencePage_export_extension});
1279 		dialog.setFileName(TemplatesMessages.TemplatePreferencePage_export_filename);
1280 		String path= dialog.open();
1281 
1282 		if (path == null)
1283 			return;
1284 
1285 		File file= new File(path);
1286 
1287 		if (file.isHidden()) {
1288 			String title= TemplatesMessages.TemplatePreferencePage_export_error_title;
1289 			String message= NLSUtility.format(TemplatesMessages.TemplatePreferencePage_export_error_hidden, file.getAbsolutePath());
1290 			MessageDialog.openError(getShell(), title, message);
1291 			return;
1292 		}
1293 
1294 		if (file.exists() && !file.canWrite()) {
1295 			String title= TemplatesMessages.TemplatePreferencePage_export_error_title;
1296 			String message= NLSUtility.format(TemplatesMessages.TemplatePreferencePage_export_error_canNotWrite, file.getAbsolutePath());
1297 			MessageDialog.openError(getShell(), title, message);
1298 			return;
1299 		}
1300 
1301 		if (!file.exists() || confirmOverwrite(file)) {
1302 			try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file))) {
1303 				TemplateReaderWriter writer= new TemplateReaderWriter();
1304 				writer.save(templates, output);
1305 			} catch (IOException e) {
1306 				openWriteErrorDialog(e);
1307 			}
1308 		}
1309 	}
1310 
confirmOverwrite(File file)1311 	private boolean confirmOverwrite(File file) {
1312 		return MessageDialog.openQuestion(getShell(),
1313 			TemplatesMessages.TemplatePreferencePage_export_exists_title,
1314 			NLSUtility.format(TemplatesMessages.TemplatePreferencePage_export_exists_message, file.getAbsolutePath()));
1315 	}
1316 
remove()1317 	private void remove() {
1318 		IStructuredSelection selection = fTableViewer.getStructuredSelection();
1319 
1320 		Iterator<?> elements= selection.iterator();
1321 		while (elements.hasNext()) {
1322 			TemplatePersistenceData data= (TemplatePersistenceData) elements.next();
1323 			fTemplateStore.delete(data);
1324 		}
1325 
1326 		fTableViewer.refresh();
1327 	}
1328 
restoreDeleted()1329 	private void restoreDeleted() {
1330 		TemplatePersistenceData[] oldTemplates= fTemplateStore.getTemplateData(false);
1331 		fTemplateStore.restoreDeleted();
1332 		TemplatePersistenceData[] newTemplates= fTemplateStore.getTemplateData(false);
1333 		fTableViewer.refresh();
1334 		fTableViewer.setCheckedElements(getEnabledTemplates());
1335 		ArrayList<TemplatePersistenceData> selection= new ArrayList<>();
1336 		selection.addAll(Arrays.asList(newTemplates));
1337 		selection.removeAll(Arrays.asList(oldTemplates));
1338 		fTableViewer.setSelection(new StructuredSelection(selection), true);
1339 		selectionChanged1();
1340 	}
1341 
revert()1342 	private void revert() {
1343 		IStructuredSelection selection = fTableViewer.getStructuredSelection();
1344 
1345 		Iterator<?> elements= selection.iterator();
1346 		while (elements.hasNext()) {
1347 			TemplatePersistenceData data= (TemplatePersistenceData) elements.next();
1348 			data.revert();
1349 			fTableViewer.setChecked(data, data.isEnabled());
1350 		}
1351 
1352 		selectionChanged1();
1353 		fTableViewer.refresh();
1354 	}
1355 
1356 	@Override
setVisible(boolean visible)1357 	public void setVisible(boolean visible) {
1358 		super.setVisible(visible);
1359 		if (visible)
1360 			setTitle(TemplatesMessages.TemplatePreferencePage_title);
1361 	}
1362 
1363 	@Override
performDefaults()1364 	protected void performDefaults() {
1365 		if (isShowFormatterSetting()) {
1366 			IPreferenceStore prefs= getPreferenceStore();
1367 			fFormatButton.setSelection(prefs.getDefaultBoolean(getFormatterPreferenceKey()));
1368 		}
1369 
1370 		fTemplateStore.restoreDefaults(false);
1371 
1372 		// refresh
1373 		fTableViewer.refresh();
1374 		fTableViewer.setAllChecked(false);
1375 		fTableViewer.setCheckedElements(getEnabledTemplates());
1376 	}
1377 
1378 	@Override
performOk()1379 	public boolean performOk() {
1380 		if (isShowFormatterSetting()) {
1381 			IPreferenceStore prefs= getPreferenceStore();
1382 			prefs.setValue(getFormatterPreferenceKey(), fFormatButton.getSelection());
1383 		}
1384 
1385 		try {
1386 			fTemplateStore.save();
1387 		} catch (IOException e) {
1388 			openWriteErrorDialog(e);
1389 		}
1390 
1391 		return super.performOk();
1392 	}
1393 
1394 	/**
1395 	 * Returns the key to use for the formatter preference.
1396 	 *
1397 	 * @return the formatter preference key
1398 	 */
getFormatterPreferenceKey()1399 	protected String getFormatterPreferenceKey() {
1400 		return DEFAULT_FORMATTER_PREFERENCE_KEY;
1401 	}
1402 
1403 	@Override
performCancel()1404 	public boolean performCancel() {
1405 		try {
1406 			fTemplateStore.load();
1407 		} catch (IOException e) {
1408 			openReadErrorDialog(e);
1409 			return false;
1410 		}
1411 		return super.performCancel();
1412 	}
1413 
1414 	/*
1415 	 * @since 3.2
1416 	 */
openReadErrorDialog(IOException ex)1417 	private void openReadErrorDialog(IOException ex) {
1418 		IStatus status= new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK, "Failed to read templates.", ex); //$NON-NLS-1$
1419 		TextEditorPlugin.getDefault().getLog().log(status);
1420 		String title= TemplatesMessages.TemplatePreferencePage_error_read_title;
1421 		String message= TemplatesMessages.TemplatePreferencePage_error_read_message;
1422 		MessageDialog.openError(getShell(), title, message);
1423 	}
1424 
1425 	/*
1426 	 * @since 3.2
1427 	 */
openWriteErrorDialog(IOException ex)1428 	private void openWriteErrorDialog(IOException ex) {
1429 		IStatus status= new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, IStatus.OK, "Failed to write templates.", ex); //$NON-NLS-1$
1430 		TextEditorPlugin.getDefault().getLog().log(status);
1431 		String title= TemplatesMessages.TemplatePreferencePage_error_write_title;
1432 		String message= TemplatesMessages.TemplatePreferencePage_error_write_message;
1433 		MessageDialog.openError(getShell(), title, message);
1434 	}
1435 
openCreateNewOrRenameDialog()1436 	private boolean openCreateNewOrRenameDialog() {
1437 		MessageDialog dialog = new MessageDialog(getShell(),
1438 				TemplatesMessages.TemplatePreferencePage_question_create_new_title, null,
1439 				TemplatesMessages.TemplatePreferencePage_question_create_new_message, MessageDialog.QUESTION, 0,
1440 				TemplatesMessages.TemplatePreferencePage_question_create_new_button_create,
1441 				TemplatesMessages.TemplatePreferencePage_question_create_new_button_rename);
1442 		return dialog.open() == 0;
1443 	}
1444 
getViewer()1445 	protected SourceViewer getViewer() {
1446 		return fPatternViewer;
1447 	}
1448 
getTableViewer()1449 	protected TableViewer getTableViewer() {
1450 		return fTableViewer;
1451 	}
1452 
1453 	private final class TemplateViewerComparator extends ViewerComparator {
1454 
1455 		private int fSortColumn;
1456 
1457 		private int fSortOrder; // 1 = asc, -1 = desc
1458 
TemplateViewerComparator()1459 		public TemplateViewerComparator() {
1460 			fSortColumn= 0;
1461 			fSortOrder= 1;
1462 		}
1463 
1464 		/**
1465 		 * Returns the {@linkplain SWT} style constant for the sort direction.
1466 		 *
1467 		 * @return {@link SWT#DOWN} for asc sorting, {@link SWT#UP} otherwise
1468 		 */
getDirection()1469 		public int getDirection() {
1470 			return fSortOrder == 1 ? SWT.DOWN : SWT.UP;
1471 		}
1472 
1473 		/**
1474 		 * Sets the sort column. If the newly set sort column equals the previous set sort column,
1475 		 * the sort direction changes.
1476 		 *
1477 		 * @param column New sort column
1478 		 */
setColumn(int column)1479 		public void setColumn(int column) {
1480 			if (column == fSortColumn) {
1481 				fSortOrder*= -1;
1482 			} else {
1483 				fSortColumn= column;
1484 				fSortOrder= 1;
1485 			}
1486 		}
1487 
1488 		@Override
compare(Viewer viewer, Object e1, Object e2)1489 		public int compare(Viewer viewer, Object e1, Object e2) {
1490 
1491 			if (viewer instanceof TableViewer) {
1492 				IBaseLabelProvider baseLabel= ((TableViewer)viewer).getLabelProvider();
1493 
1494 				String left= ((TemplateLabelProvider)baseLabel).getColumnText(e1, fSortColumn);
1495 				String right= ((TemplateLabelProvider)baseLabel).getColumnText(e2, fSortColumn);
1496 				int sortResult= getComparator().compare(left, right);
1497 				return sortResult * fSortOrder;
1498 			}
1499 
1500 			return super.compare(viewer, e1, e2);
1501 		}
1502 	}
1503 
1504 	private final class TemplateColumnSelectionAdapter extends SelectionAdapter {
1505 
1506 		private final TableColumn fTableColumn;
1507 
1508 		private final int fColumnIndex;
1509 
1510 		private final TemplateViewerComparator fViewerComparator;
1511 
TemplateColumnSelectionAdapter(TableColumn column, int index, TemplateViewerComparator vc)1512 		public TemplateColumnSelectionAdapter(TableColumn column, int index, TemplateViewerComparator vc) {
1513 			fTableColumn= column;
1514 			fColumnIndex= index;
1515 			fViewerComparator= vc;
1516 		}
1517 
1518 		@Override
widgetSelected(SelectionEvent e)1519 		public void widgetSelected(SelectionEvent e) {
1520 			fViewerComparator.setColumn(fColumnIndex);
1521 			int dir= fViewerComparator.getDirection();
1522 			fTableViewer.getTable().setSortDirection(dir);
1523 			fTableViewer.getTable().setSortColumn(fTableColumn);
1524 			fTableViewer.refresh();
1525 		}
1526 	}
1527 }
1528