cbd80ba197eb5d037459f96c2e30a619a61f9c24
[phpeclipse.git] /
1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.ui.preferences;
12
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Map;
18
19 import net.sourceforge.phpdt.internal.ui.IJavaHelpContextIds;
20 import net.sourceforge.phpdt.internal.ui.dialogs.StatusDialog;
21 import net.sourceforge.phpdt.internal.ui.dialogs.StatusInfo;
22 import net.sourceforge.phpdt.internal.ui.text.IPHPPartitions;
23 import net.sourceforge.phpdt.internal.ui.text.template.preferences.TemplateVariableProcessor;
24 import net.sourceforge.phpdt.internal.ui.util.SWTUtil;
25 import net.sourceforge.phpdt.ui.IContextMenuConstants;
26 import net.sourceforge.phpdt.ui.PreferenceConstants;
27 import net.sourceforge.phpdt.ui.text.JavaTextTools;
28 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
29 import net.sourceforge.phpeclipse.phpeditor.JavaSourceViewer;
30
31 import org.eclipse.jface.action.Action;
32 import org.eclipse.jface.action.GroupMarker;
33 import org.eclipse.jface.action.IAction;
34 import org.eclipse.jface.action.IMenuListener;
35 import org.eclipse.jface.action.IMenuManager;
36 import org.eclipse.jface.action.MenuManager;
37 import org.eclipse.jface.action.Separator;
38 import org.eclipse.jface.preference.IPreferenceStore;
39 import org.eclipse.jface.resource.JFaceResources;
40 import org.eclipse.jface.text.Document;
41 import org.eclipse.jface.text.IDocument;
42 import org.eclipse.jface.text.ITextListener;
43 import org.eclipse.jface.text.ITextOperationTarget;
44 import org.eclipse.jface.text.ITextViewer;
45 import org.eclipse.jface.text.TextEvent;
46 import org.eclipse.jface.text.source.ISourceViewer;
47 import org.eclipse.jface.text.source.SourceViewer;
48 import org.eclipse.jface.text.templates.ContextTypeRegistry;
49 import org.eclipse.jface.text.templates.Template;
50 import org.eclipse.jface.text.templates.TemplateContextType;
51 import org.eclipse.jface.text.templates.TemplateException;
52 import org.eclipse.jface.viewers.ISelectionChangedListener;
53 import org.eclipse.jface.viewers.SelectionChangedEvent;
54 import org.eclipse.swt.SWT;
55 import org.eclipse.swt.custom.StyledText;
56 import org.eclipse.swt.custom.VerifyKeyListener;
57 import org.eclipse.swt.events.FocusEvent;
58 import org.eclipse.swt.events.FocusListener;
59 import org.eclipse.swt.events.ModifyEvent;
60 import org.eclipse.swt.events.ModifyListener;
61 import org.eclipse.swt.events.SelectionEvent;
62 import org.eclipse.swt.events.SelectionListener;
63 import org.eclipse.swt.events.VerifyEvent;
64 import org.eclipse.swt.graphics.Font;
65 import org.eclipse.swt.layout.GridData;
66 import org.eclipse.swt.layout.GridLayout;
67 import org.eclipse.swt.widgets.Button;
68 import org.eclipse.swt.widgets.Combo;
69 import org.eclipse.swt.widgets.Composite;
70 import org.eclipse.swt.widgets.Control;
71 import org.eclipse.swt.widgets.Label;
72 import org.eclipse.swt.widgets.Menu;
73 import org.eclipse.swt.widgets.Shell;
74 import org.eclipse.swt.widgets.Text;
75 import org.eclipse.swt.widgets.Widget;
76 import org.eclipse.ui.help.WorkbenchHelp;
77 import org.eclipse.ui.texteditor.ITextEditorActionConstants;
78 import org.eclipse.ui.texteditor.IUpdate;
79
80 /**
81  * Dialog to edit a template.
82  */
83 public class EditTemplateDialog extends StatusDialog {
84
85         private static class TextViewerAction extends Action implements IUpdate {
86         
87                 private int fOperationCode= -1;
88                 private ITextOperationTarget fOperationTarget;
89         
90                 /** 
91                  * Creates a new action.
92                  * 
93                  * @param viewer the viewer
94                  * @param operationCode the opcode
95                  */
96                 public TextViewerAction(ITextViewer viewer, int operationCode) {
97                         fOperationCode= operationCode;
98                         fOperationTarget= viewer.getTextOperationTarget();
99                         update();
100                 }
101         
102                 /**
103                  * Updates the enabled state of the action.
104                  * Fires a property change if the enabled state changes.
105                  * 
106                  * @see Action#firePropertyChange(String, Object, Object)
107                  */
108                 public void update() {
109         
110                         boolean wasEnabled= isEnabled();
111                         boolean isEnabled= (fOperationTarget != null && fOperationTarget.canDoOperation(fOperationCode));
112                         setEnabled(isEnabled);
113         
114                         if (wasEnabled != isEnabled) {
115                                 firePropertyChange(ENABLED, wasEnabled ? Boolean.TRUE : Boolean.FALSE, isEnabled ? Boolean.TRUE : Boolean.FALSE);
116                         }
117                 }
118                 
119                 /**
120                  * @see Action#run()
121                  */
122                 public void run() {
123                         if (fOperationCode != -1 && fOperationTarget != null) {
124                                 fOperationTarget.doOperation(fOperationCode);
125                         }
126                 }
127         }       
128
129         private final Template fTemplate;
130         
131         private Text fNameText;
132         private Text fDescriptionText;
133         private Combo fContextCombo;
134         private SourceViewer fPatternEditor;    
135         private Button fInsertVariableButton;
136         private boolean fIsNameModifiable;
137
138         private StatusInfo fValidationStatus;
139         private boolean fSuppressError= true; // #4354  
140         private Map fGlobalActions= new HashMap(10);
141         private List fSelectionActions = new ArrayList(3);      
142         private String[][] fContextTypes;
143         
144         private ContextTypeRegistry fContextTypeRegistry; 
145         
146         private final TemplateVariableProcessor fTemplateProcessor= new TemplateVariableProcessor();
147                 
148         /**
149          * Creates a new dialog.
150          * 
151          * @param parent the shell parent of the dialog
152          * @param template the template to edit
153          * @param edit whether this is a new template or an existing being edited
154          * @param isNameModifiable whether the name of the template may be modified
155          * @param registry the context type registry to use
156          */
157         public EditTemplateDialog(Shell parent, Template template, boolean edit, boolean isNameModifiable, ContextTypeRegistry registry) {
158                 super(parent);
159                 
160                 setShellStyle(getShellStyle() | SWT.MAX | SWT.RESIZE);
161                 
162                 String title= edit
163                         ? PreferencesMessages.getString("EditTemplateDialog.title.edit") //$NON-NLS-1$
164                         : PreferencesMessages.getString("EditTemplateDialog.title.new"); //$NON-NLS-1$
165                 setTitle(title);
166
167                 fTemplate= template;
168                 fIsNameModifiable= isNameModifiable;
169                 
170                 // XXX workaround for bug 63313 - disabling prefix until fixed.
171 //              String delim= new Document().getLegalLineDelimiters()[0];
172                 
173                 List contexts= new ArrayList();
174                 for (Iterator it= registry.contextTypes(); it.hasNext();) {
175                         TemplateContextType type= (TemplateContextType) it.next();
176 //                      if (type.getId().equals("javadoc")) //$NON-NLS-1$
177 //                              contexts.add(new String[] { type.getId(), type.getName(), "/**" + delim }); //$NON-NLS-1$
178 //                      else
179                                 contexts.add(new String[] { type.getId(), type.getName(), "" }); //$NON-NLS-1$
180                 }
181                 fContextTypes= (String[][]) contexts.toArray(new String[contexts.size()][]);
182                                 
183                 fValidationStatus= new StatusInfo();
184                 
185                 fContextTypeRegistry= registry;
186                 
187                 TemplateContextType type= fContextTypeRegistry.getContextType(template.getContextTypeId());
188                 fTemplateProcessor.setContextType(type);
189         }
190         
191         /*
192          * @see net.sourceforge.phpdt.internal.ui.dialogs.StatusDialog#create()
193          */
194         public void create() {
195                 super.create();
196                 // update initial ok button to be disabled for new templates 
197                 boolean valid= fNameText == null || fNameText.getText().trim().length() != 0;
198                 if (!valid) {
199                         StatusInfo status = new StatusInfo();
200                         status.setError(PreferencesMessages.getString("EditTemplateDialog.error.noname")); //$NON-NLS-1$
201                         updateButtonsEnableState(status);
202                 }
203         }
204         
205         /*
206          * @see Dialog#createDialogArea(Composite)
207          */
208         protected Control createDialogArea(Composite ancestor) {
209                 Composite parent= new Composite(ancestor, SWT.NONE);
210                 GridLayout layout= new GridLayout();
211                 layout.numColumns= 2;
212                 parent.setLayout(layout);
213                 parent.setLayoutData(new GridData(GridData.FILL_BOTH));
214                 
215                 ModifyListener listener= new ModifyListener() {
216                         public void modifyText(ModifyEvent e) {
217                                 doTextWidgetChanged(e.widget);
218                         }
219                 };
220                 
221                 if (fIsNameModifiable) {
222                         createLabel(parent, PreferencesMessages.getString("EditTemplateDialog.name")); //$NON-NLS-1$    
223                         
224                         Composite composite= new Composite(parent, SWT.NONE);
225                         composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
226                         layout= new GridLayout();               
227                         layout.numColumns= 3;
228                         layout.marginWidth= 0;
229                         layout.marginHeight= 0;
230                         composite.setLayout(layout);
231                         
232                         fNameText= createText(composite);
233                         fNameText.addFocusListener(new FocusListener() {
234                                 
235                                 public void focusGained(FocusEvent e) {
236                                 }
237                                 
238                                 public void focusLost(FocusEvent e) {
239                                         if (fSuppressError) {
240                                                 fSuppressError= false;
241                                                 updateButtons();
242                                         }
243                                 }
244                         });
245                         
246                         createLabel(composite, PreferencesMessages.getString("EditTemplateDialog.context")); //$NON-NLS-1$              
247                         fContextCombo= new Combo(composite, SWT.READ_ONLY);
248         
249                         for (int i= 0; i < fContextTypes.length; i++) {
250                                 fContextCombo.add(fContextTypes[i][1]);
251                         }
252         
253                         fContextCombo.addModifyListener(listener);
254                 }
255                 
256                 createLabel(parent, PreferencesMessages.getString("EditTemplateDialog.description")); //$NON-NLS-1$             
257                 
258                 int descFlags= fIsNameModifiable ? SWT.BORDER : SWT.BORDER | SWT.READ_ONLY;
259                 fDescriptionText= new Text(parent, descFlags );
260                 fDescriptionText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 
261                 
262                 fDescriptionText.addModifyListener(listener);
263
264                 Label patternLabel= createLabel(parent, PreferencesMessages.getString("EditTemplateDialog.pattern")); //$NON-NLS-1$
265                 patternLabel.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
266                 fPatternEditor= createEditor(parent);
267                 
268                 Label filler= new Label(parent, SWT.NONE);              
269                 filler.setLayoutData(new GridData());
270                 
271                 Composite composite= new Composite(parent, SWT.NONE);
272                 layout= new GridLayout();               
273                 layout.marginWidth= 0;
274                 layout.marginHeight= 0;
275                 composite.setLayout(layout);            
276                 composite.setLayoutData(new GridData());
277                 
278                 fInsertVariableButton= new Button(composite, SWT.NONE);
279                 fInsertVariableButton.setLayoutData(getButtonGridData(fInsertVariableButton));
280                 fInsertVariableButton.setText(PreferencesMessages.getString("EditTemplateDialog.insert.variable")); //$NON-NLS-1$
281                 fInsertVariableButton.addSelectionListener(new SelectionListener() {
282                         public void widgetSelected(SelectionEvent e) {
283                                 fPatternEditor.getTextWidget().setFocus();
284                                 fPatternEditor.doOperation(ISourceViewer.CONTENTASSIST_PROPOSALS);                      
285                         }
286
287                         public void widgetDefaultSelected(SelectionEvent e) {}
288                 });
289
290                 fDescriptionText.setText(fTemplate.getDescription());
291                 if (fIsNameModifiable) {
292                         fNameText.setText(fTemplate.getName());
293                         fNameText.addModifyListener(listener);
294                         fContextCombo.select(getIndex(fTemplate.getContextTypeId()));
295                 } else {
296                         fPatternEditor.getControl().setFocus();
297                 }
298                 initializeActions();
299
300                 applyDialogFont(parent);
301                 return composite;
302         }
303         
304         protected void doTextWidgetChanged(Widget w) {
305                 if (w == fNameText) {
306                         fSuppressError= false;
307                         String name= fNameText.getText();
308                         fTemplate.setName(name);
309                         updateButtons();                        
310                 } else if (w == fContextCombo) {
311                         String name= fContextCombo.getText();
312                         String contextId= getContextId(name);
313                         fTemplate.setContextTypeId(contextId);
314                         fTemplateProcessor.setContextType(fContextTypeRegistry.getContextType(contextId));
315                 } else if (w == fDescriptionText) {
316                         String desc= fDescriptionText.getText();
317                         fTemplate.setDescription(desc);
318                 }       
319         }
320         
321         private String getContextId(String name) {
322                 if (name == null)
323                         return name;
324                 
325                 for (int i= 0; i < fContextTypes.length; i++) {
326                         if (name.equals(fContextTypes[i][1])) {
327                                 return fContextTypes[i][0];     
328                         }
329                 }
330                 return name;
331         }
332
333         protected void doSourceChanged(IDocument document) {
334                 String text= document.get();
335                 String prefix= getPrefix();
336                 fTemplate.setPattern(text.substring(prefix.length(), text.length()));
337                 fValidationStatus.setOK();
338                 TemplateContextType contextType= fContextTypeRegistry.getContextType(fTemplate.getContextTypeId());
339                 if (contextType != null) {
340                         try {
341                                 contextType.validate(text);
342                         } catch (TemplateException e) {
343                                 fValidationStatus.setError(e.getLocalizedMessage());
344                         }
345                 }
346
347                 updateUndoAction();
348                 updateButtons();
349         }       
350
351         private static GridData getButtonGridData(Button button) {
352                 GridData data= new GridData(GridData.FILL_HORIZONTAL);
353                 data.heightHint= SWTUtil.getButtonHeightHint(button);
354         
355                 return data;
356         }
357
358         private static Label createLabel(Composite parent, String name) {
359                 Label label= new Label(parent, SWT.NULL);
360                 label.setText(name);
361                 label.setLayoutData(new GridData());
362
363                 return label;
364         }
365
366         private static Text createText(Composite parent) {
367                 Text text= new Text(parent, SWT.BORDER);
368                 text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));             
369                 
370                 return text;
371         }
372
373         private SourceViewer createEditor(Composite parent) {
374                 String prefix= getPrefix();
375                 IDocument document= new Document(prefix + fTemplate.getPattern());
376                 JavaTextTools tools= PHPeclipsePlugin.getDefault().getJavaTextTools();
377                 tools.setupJavaDocumentPartitioner(document, IPHPPartitions.PHP_PARTITIONING);
378                 IPreferenceStore store= PHPeclipsePlugin.getDefault().getCombinedPreferenceStore();
379                 SourceViewer viewer= new JavaSourceViewer(parent, null, null, false, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL, store);
380                 TemplateEditorSourceViewerConfiguration configuration= new TemplateEditorSourceViewerConfiguration(tools.getColorManager(), store, null, fTemplateProcessor);
381                 viewer.configure(configuration);
382                 viewer.setEditable(true);
383                 // XXX workaround for bug 63313 - disabling prefix until fixed.
384 //              viewer.setDocument(document, prefix.length(), document.getLength() - prefix.length());
385                 viewer.setDocument(document);
386                 
387                 Font font= JFaceResources.getFont(PreferenceConstants.EDITOR_TEXT_FONT);
388                 viewer.getTextWidget().setFont(font);
389                 new JavaSourcePreviewerUpdater(viewer, configuration, store);
390                 
391                 int nLines= document.getNumberOfLines();
392                 if (nLines < 5) {
393                         nLines= 5;
394                 } else if (nLines > 12) {
395                         nLines= 12;     
396                 }
397                                 
398                 Control control= viewer.getControl();
399                 GridData data= new GridData(GridData.FILL_BOTH);
400                 data.widthHint= convertWidthInCharsToPixels(80);
401                 data.heightHint= convertHeightInCharsToPixels(nLines);
402                 control.setLayoutData(data);
403                 
404                 viewer.addTextListener(new ITextListener() {
405                         public void textChanged(TextEvent event) {
406                                 if (event .getDocumentEvent() != null)
407                                         doSourceChanged(event.getDocumentEvent().getDocument());
408                         }
409                 });
410
411                 viewer.addSelectionChangedListener(new ISelectionChangedListener() {                    
412                         public void selectionChanged(SelectionChangedEvent event) {
413                                 updateSelectionDependentActions();
414                         }
415                 });
416
417                 viewer.prependVerifyKeyListener(new VerifyKeyListener() {
418                         public void verifyKey(VerifyEvent event) {
419                                 handleVerifyKeyPressed(event);
420                         }
421                 });
422                 
423                 return viewer;
424         }
425         
426         private String getPrefix() {
427                 String prefix;
428                 int idx= getIndex(fTemplate.getContextTypeId());
429                 if (idx != -1)
430                         prefix= fContextTypes[idx][2];
431                 else
432                         prefix= ""; //$NON-NLS-1$
433
434                 return prefix;
435         }
436
437         private void handleVerifyKeyPressed(VerifyEvent event) {
438                 if (!event.doit)
439                         return;
440
441                 if (event.stateMask != SWT.MOD1)
442                         return;
443                         
444                 switch (event.character) {
445                         case ' ':
446                                 fPatternEditor.doOperation(ISourceViewer.CONTENTASSIST_PROPOSALS);
447                                 event.doit= false;
448                                 break;
449
450                         // CTRL-Z
451                         case 'z' - 'a' + 1:
452                                 fPatternEditor.doOperation(ITextOperationTarget.UNDO);
453                                 event.doit= false;
454                                 break;                          
455                 }
456         }
457
458         private void initializeActions() {
459                 TextViewerAction action= new TextViewerAction(fPatternEditor, SourceViewer.UNDO);
460                 action.setText(PreferencesMessages.getString("EditTemplateDialog.undo")); //$NON-NLS-1$
461                 fGlobalActions.put(ITextEditorActionConstants.UNDO, action);
462
463                 action= new TextViewerAction(fPatternEditor, SourceViewer.CUT);
464                 action.setText(PreferencesMessages.getString("EditTemplateDialog.cut")); //$NON-NLS-1$
465                 fGlobalActions.put(ITextEditorActionConstants.CUT, action);
466
467                 action= new TextViewerAction(fPatternEditor, SourceViewer.COPY);
468                 action.setText(PreferencesMessages.getString("EditTemplateDialog.copy")); //$NON-NLS-1$
469                 fGlobalActions.put(ITextEditorActionConstants.COPY, action);
470
471                 action= new TextViewerAction(fPatternEditor, SourceViewer.PASTE);
472                 action.setText(PreferencesMessages.getString("EditTemplateDialog.paste")); //$NON-NLS-1$
473                 fGlobalActions.put(ITextEditorActionConstants.PASTE, action);
474
475                 action= new TextViewerAction(fPatternEditor, SourceViewer.SELECT_ALL);
476                 action.setText(PreferencesMessages.getString("EditTemplateDialog.select.all")); //$NON-NLS-1$
477                 fGlobalActions.put(ITextEditorActionConstants.SELECT_ALL, action);
478
479                 action= new TextViewerAction(fPatternEditor, SourceViewer.CONTENTASSIST_PROPOSALS);
480                 action.setText(PreferencesMessages.getString("EditTemplateDialog.content.assist")); //$NON-NLS-1$
481                 fGlobalActions.put("ContentAssistProposal", action); //$NON-NLS-1$
482
483                 fSelectionActions.add(ITextEditorActionConstants.CUT);
484                 fSelectionActions.add(ITextEditorActionConstants.COPY);
485                 fSelectionActions.add(ITextEditorActionConstants.PASTE);
486                 
487                 // create context menu
488                 MenuManager manager= new MenuManager(null, null);
489                 manager.setRemoveAllWhenShown(true);
490                 manager.addMenuListener(new IMenuListener() {
491                         public void menuAboutToShow(IMenuManager mgr) {
492                                 fillContextMenu(mgr);
493                         }
494                 });
495
496                 StyledText text= fPatternEditor.getTextWidget();                
497                 Menu menu= manager.createContextMenu(text);
498                 text.setMenu(menu);
499         }
500
501         private void fillContextMenu(IMenuManager menu) {
502                 menu.add(new GroupMarker(ITextEditorActionConstants.GROUP_UNDO));
503                 menu.appendToGroup(ITextEditorActionConstants.GROUP_UNDO, (IAction) fGlobalActions.get(ITextEditorActionConstants.UNDO));
504                 
505                 menu.add(new Separator(ITextEditorActionConstants.GROUP_EDIT));         
506                 menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, (IAction) fGlobalActions.get(ITextEditorActionConstants.CUT));
507                 menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, (IAction) fGlobalActions.get(ITextEditorActionConstants.COPY));
508                 menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, (IAction) fGlobalActions.get(ITextEditorActionConstants.PASTE));
509                 menu.appendToGroup(ITextEditorActionConstants.GROUP_EDIT, (IAction) fGlobalActions.get(ITextEditorActionConstants.SELECT_ALL));
510
511                 menu.add(new Separator(IContextMenuConstants.GROUP_GENERATE));
512                 menu.appendToGroup(IContextMenuConstants.GROUP_GENERATE, (IAction) fGlobalActions.get("ContentAssistProposal")); //$NON-NLS-1$
513         }
514
515         protected void updateSelectionDependentActions() {
516                 Iterator iterator= fSelectionActions.iterator();
517                 while (iterator.hasNext())
518                         updateAction((String)iterator.next());          
519         }
520
521         protected void updateUndoAction() {
522                 IAction action= (IAction) fGlobalActions.get(ITextEditorActionConstants.UNDO);
523                 if (action instanceof IUpdate)
524                         ((IUpdate) action).update();
525         }
526
527         protected void updateAction(String actionId) {
528                 IAction action= (IAction) fGlobalActions.get(actionId);
529                 if (action instanceof IUpdate)
530                         ((IUpdate) action).update();
531         }
532
533         private int getIndex(String contextid) {
534                 
535                 if (contextid == null)
536                         return -1;
537                 
538                 for (int i= 0; i < fContextTypes.length; i++) {
539                         if (contextid.equals(fContextTypes[i][0])) {
540                                 return i;       
541                         }
542                 }
543                 return -1;
544         }
545         
546         protected void okPressed() {
547                 super.okPressed();
548         }
549         
550         private void updateButtons() {          
551                 StatusInfo status;
552
553                 boolean valid= fNameText == null || fNameText.getText().trim().length() != 0;
554                 if (!valid) {
555                         status = new StatusInfo();
556                         if (!fSuppressError) {
557                                 status.setError(PreferencesMessages.getString("EditTemplateDialog.error.noname")); //$NON-NLS-1$
558                         }
559                 } else {
560                         status= fValidationStatus; 
561                 }
562                 updateStatus(status);
563         }
564
565         /*
566          * @see org.eclipse.jface.window.Window#configureShell(Shell)
567          */
568         protected void configureShell(Shell newShell) {
569                 super.configureShell(newShell);
570                 WorkbenchHelp.setHelp(newShell, IJavaHelpContextIds.EDIT_TEMPLATE_DIALOG);
571         }
572
573
574 }