Fix nasty bug #706. See trac.
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / ui / preferences / OptionsConfigurationBlock.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2003 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.Hashtable;
16 import java.util.Iterator;
17 import java.util.Map;
18 import java.util.StringTokenizer;
19 import java.util.Map.Entry;
20
21 import net.sourceforge.phpdt.core.IJavaProject;
22 import net.sourceforge.phpdt.core.JavaCore;
23 import net.sourceforge.phpdt.internal.ui.wizards.IStatusChangeListener;
24 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
25
26 import org.eclipse.core.resources.IncrementalProjectBuilder;
27 import org.eclipse.core.resources.ResourcesPlugin;
28 import org.eclipse.core.runtime.CoreException;
29 import org.eclipse.core.runtime.IProgressMonitor;
30 import org.eclipse.core.runtime.IStatus;
31 import org.eclipse.core.runtime.OperationCanceledException;
32 import org.eclipse.core.runtime.Status;
33 import org.eclipse.core.runtime.SubProgressMonitor;
34 import org.eclipse.core.runtime.jobs.Job;
35 import org.eclipse.jface.dialogs.IDialogConstants;
36 import org.eclipse.jface.dialogs.MessageDialog;
37 import org.eclipse.swt.SWT;
38 import org.eclipse.swt.events.ModifyEvent;
39 import org.eclipse.swt.events.ModifyListener;
40 import org.eclipse.swt.events.SelectionEvent;
41 import org.eclipse.swt.events.SelectionListener;
42 import org.eclipse.swt.layout.GridData;
43 import org.eclipse.swt.layout.GridLayout;
44 import org.eclipse.swt.widgets.Button;
45 import org.eclipse.swt.widgets.Combo;
46 import org.eclipse.swt.widgets.Composite;
47 import org.eclipse.swt.widgets.Control;
48 import org.eclipse.swt.widgets.Label;
49 import org.eclipse.swt.widgets.Shell;
50 import org.eclipse.swt.widgets.Text;
51 import org.eclipse.swt.widgets.Widget;
52
53 /**
54  * Abstract options configuration block providing a general implementation for
55  * setting up an options configuration page.
56  * 
57  * @since 2.1
58  */
59 public abstract class OptionsConfigurationBlock {
60
61         protected static class ControlData {
62                 private String fKey;
63
64                 private String[] fValues;
65
66                 public ControlData(String key, String[] values) {
67                         fKey = key;
68                         fValues = values;
69                 }
70
71                 public String getKey() {
72                         return fKey;
73                 }
74
75                 public String getValue(boolean selection) {
76                         int index = selection ? 0 : 1;
77                         return fValues[index];
78                 }
79
80                 public String getValue(int index) {
81                         return fValues[index];
82                 }
83
84                 public int getSelection(String value) {
85                         if (value != null) {
86                                 for (int i = 0; i < fValues.length; i++) {
87                                         if (value.equals(fValues[i])) {
88                                                 return i;
89                                         }
90                                 }
91                         }
92                         return fValues.length - 1; // assume the last option is the least
93                                                                                 // severe
94                 }
95         }
96
97         protected Map fWorkingValues;
98
99         protected ArrayList fCheckBoxes;
100
101         protected ArrayList fComboBoxes;
102
103         protected ArrayList fTextBoxes;
104
105         protected HashMap fLabels;
106
107         private SelectionListener fSelectionListener;
108
109         private ModifyListener fTextModifyListener;
110
111         protected IStatusChangeListener fContext;
112
113         protected IJavaProject fProject; // project or null
114
115         protected String[] fAllKeys;
116
117         private Shell fShell;
118
119         public OptionsConfigurationBlock(IStatusChangeListener context,
120                         IJavaProject project, String[] allKeys) {
121                 fContext = context;
122                 fProject = project;
123                 fAllKeys = allKeys;
124
125                 fWorkingValues = getOptions(true);
126                 testIfOptionsComplete(fWorkingValues, allKeys);
127
128                 fCheckBoxes = new ArrayList();
129                 fComboBoxes = new ArrayList();
130                 fTextBoxes = new ArrayList(2);
131                 fLabels = new HashMap();
132         }
133
134         private void testIfOptionsComplete(Map workingValues, String[] allKeys) {
135                 for (int i = 0; i < allKeys.length; i++) {
136                         if (workingValues.get(allKeys[i]) == null) {
137                                 PHPeclipsePlugin
138                                                 .logErrorMessage("preference option missing: " + allKeys[i] + " (" + this.getClass().getName() + ')'); //$NON-NLS-1$//$NON-NLS-2$
139                         }
140                 }
141         }
142
143         protected Map getOptions(boolean inheritJavaCoreOptions) {
144                 if (fProject != null) {
145                         return fProject.getOptions(inheritJavaCoreOptions);
146                 } else {
147                         return JavaCore.getOptions();
148                 }
149         }
150
151         protected Map getDefaultOptions() {
152                 return JavaCore.getDefaultOptions();
153         }
154
155         public final boolean hasProjectSpecificOptions() {
156                 if (fProject != null) {
157                         Map settings = fProject.getOptions(false);
158                         String[] allKeys = fAllKeys;
159                         for (int i = 0; i < allKeys.length; i++) {
160                                 if (settings.get(allKeys[i]) != null) {
161                                         return true;
162                                 }
163                         }
164                 }
165                 return false;
166         }
167
168         protected void setOptions(Map map) {
169                 if (fProject != null) {
170                         Map oldOptions = fProject.getOptions(false);
171                         fProject.setOptions(map);
172                         firePropertyChangeEvents(oldOptions, map);
173                 } else {
174                         JavaCore.setOptions((Hashtable) map);
175                 }
176         }
177
178         /**
179          * Computes the differences between the given old and new options and fires
180          * corresponding property change events on the Java plugin's mockup
181          * preference store.
182          * 
183          * @param oldOptions
184          *            The old options
185          * @param newOptions
186          *            The new options
187          */
188         private void firePropertyChangeEvents(Map oldOptions, Map newOptions) {
189                 oldOptions = new HashMap(oldOptions);
190                 Object source = fProject.getProject();
191                 MockupPreferenceStore store = PHPeclipsePlugin.getDefault()
192                                 .getMockupPreferenceStore();
193                 Iterator iter = newOptions.entrySet().iterator();
194                 while (iter.hasNext()) {
195                         Entry entry = (Entry) iter.next();
196
197                         String name = (String) entry.getKey();
198                         Object oldValue = oldOptions.get(name);
199                         Object newValue = entry.getValue();
200
201                         if ((oldValue != null && !oldValue.equals(newValue))
202                                         || (oldValue == null && newValue != null))
203                                 store.firePropertyChangeEvent(source, name, oldValue, newValue);
204                         oldOptions.remove(name);
205                 }
206
207                 iter = oldOptions.entrySet().iterator();
208                 while (iter.hasNext()) {
209                         Entry entry = (Entry) iter.next();
210                         store.firePropertyChangeEvent(source, (String) entry.getKey(),
211                                         entry.getValue(), null);
212                 }
213         }
214
215         protected Shell getShell() {
216                 return fShell;
217         }
218
219         protected void setShell(Shell shell) {
220                 fShell = shell;
221         }
222
223         protected abstract Control createContents(Composite parent);
224
225         protected Button addCheckBox(Composite parent, String label, String key,
226                         String[] values, int indent) {
227                 ControlData data = new ControlData(key, values);
228
229                 GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
230                 gd.horizontalSpan = 3;
231                 gd.horizontalIndent = indent;
232
233                 Button checkBox = new Button(parent, SWT.CHECK);
234                 checkBox.setText(label);
235                 checkBox.setData(data);
236                 checkBox.setLayoutData(gd);
237                 checkBox.addSelectionListener(getSelectionListener());
238
239                 String currValue = (String) fWorkingValues.get(key);
240                 checkBox.setSelection(data.getSelection(currValue) == 0);
241
242                 fCheckBoxes.add(checkBox);
243
244                 return checkBox;
245         }
246
247         protected Combo addComboBox(Composite parent, String label, String key,
248                         String[] values, String[] valueLabels, int indent) {
249                 ControlData data = new ControlData(key, values);
250
251                 GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
252                 gd.horizontalIndent = indent;
253
254                 Label labelControl = new Label(parent, SWT.LEFT | SWT.WRAP);
255                 labelControl.setText(label);
256                 labelControl.setLayoutData(gd);
257
258                 Combo comboBox = new Combo(parent, SWT.READ_ONLY);
259                 comboBox.setItems(valueLabels);
260                 comboBox.setData(data);
261                 comboBox.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
262                 comboBox.addSelectionListener(getSelectionListener());
263
264                 fLabels.put(comboBox, labelControl);
265
266                 Label placeHolder = new Label(parent, SWT.NONE);
267                 placeHolder.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
268
269                 String currValue = (String) fWorkingValues.get(key);
270                 comboBox.select(data.getSelection(currValue));
271
272                 fComboBoxes.add(comboBox);
273                 return comboBox;
274         }
275
276         protected void addInversedComboBox(Composite parent, String label,
277                         String key, String[] values, String[] valueLabels, int indent) {
278                 ControlData data = new ControlData(key, values);
279
280                 GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
281                 gd.horizontalIndent = indent;
282                 gd.horizontalSpan = 3;
283
284                 Composite composite = new Composite(parent, SWT.NONE);
285                 GridLayout layout = new GridLayout();
286                 layout.marginHeight = 0;
287                 layout.marginWidth = 0;
288                 layout.numColumns = 2;
289                 composite.setLayout(layout);
290                 composite.setLayoutData(gd);
291
292                 Combo comboBox = new Combo(composite, SWT.READ_ONLY);
293                 comboBox.setItems(valueLabels);
294                 comboBox.setData(data);
295                 comboBox.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
296                 comboBox.addSelectionListener(getSelectionListener());
297
298                 Label labelControl = new Label(composite, SWT.LEFT | SWT.WRAP);
299                 labelControl.setText(label);
300                 labelControl.setLayoutData(new GridData());
301
302                 fLabels.put(comboBox, labelControl);
303
304                 String currValue = (String) fWorkingValues.get(key);
305                 comboBox.select(data.getSelection(currValue));
306
307                 fComboBoxes.add(comboBox);
308         }
309
310         protected Text addTextField(Composite parent, String label, String key,
311                         int indent, int widthHint) {
312                 Label labelControl = new Label(parent, SWT.NONE);
313                 labelControl.setText(label);
314                 labelControl.setLayoutData(new GridData());
315
316                 Text textBox = new Text(parent, SWT.BORDER | SWT.SINGLE);
317                 textBox.setData(key);
318                 textBox.setLayoutData(new GridData());
319
320                 fLabels.put(textBox, labelControl);
321
322                 String currValue = (String) fWorkingValues.get(key);
323                 if (currValue != null) {
324                         textBox.setText(currValue);
325                 }
326                 textBox.addModifyListener(getTextModifyListener());
327
328                 GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
329                 if (widthHint != 0) {
330                         data.widthHint = widthHint;
331                 }
332                 data.horizontalIndent = indent;
333                 data.horizontalSpan = 2;
334                 textBox.setLayoutData(data);
335
336                 fTextBoxes.add(textBox);
337                 return textBox;
338         }
339
340         protected SelectionListener getSelectionListener() {
341                 if (fSelectionListener == null) {
342                         fSelectionListener = new SelectionListener() {
343                                 public void widgetDefaultSelected(SelectionEvent e) {
344                                 }
345
346                                 public void widgetSelected(SelectionEvent e) {
347                                         controlChanged(e.widget);
348                                 }
349                         };
350                 }
351                 return fSelectionListener;
352         }
353
354         protected ModifyListener getTextModifyListener() {
355                 if (fTextModifyListener == null) {
356                         fTextModifyListener = new ModifyListener() {
357                                 public void modifyText(ModifyEvent e) {
358                                         textChanged((Text) e.widget);
359                                 }
360                         };
361                 }
362                 return fTextModifyListener;
363         }
364
365         protected void controlChanged(Widget widget) {
366                 ControlData data = (ControlData) widget.getData();
367                 String newValue = null;
368                 if (widget instanceof Button) {
369                         newValue = data.getValue(((Button) widget).getSelection());
370                 } else if (widget instanceof Combo) {
371                         newValue = data.getValue(((Combo) widget).getSelectionIndex());
372                 } else {
373                         return;
374                 }
375                 fWorkingValues.put(data.getKey(), newValue);
376
377                 validateSettings(data.getKey(), newValue);
378         }
379
380         protected void textChanged(Text textControl) {
381                 String key = (String) textControl.getData();
382                 String number = textControl.getText();
383                 fWorkingValues.put(key, number);
384                 validateSettings(key, number);
385         }
386
387         protected boolean checkValue(String key, String value) {
388                 return value.equals(fWorkingValues.get(key));
389         }
390
391         /*
392          * (non-javadoc) Update fields and validate. @param changedKey Key that
393          * changed, or null, if all changed.
394          */
395         protected abstract void validateSettings(String changedKey, String newValue);
396
397         protected String[] getTokens(String text, String separator) {
398                 StringTokenizer tok = new StringTokenizer(text, separator); //$NON-NLS-1$
399                 int nTokens = tok.countTokens();
400                 String[] res = new String[nTokens];
401                 for (int i = 0; i < res.length; i++) {
402                         res[i] = tok.nextToken().trim();
403                 }
404                 return res;
405         }
406
407         public boolean performOk(boolean enabled) {
408                 String[] allKeys = fAllKeys;
409                 Map actualOptions = getOptions(false);
410
411                 // preserve other options
412                 boolean hasChanges = false;
413                 for (int i = 0; i < allKeys.length; i++) {
414                         String key = allKeys[i];
415                         String oldVal = (String) actualOptions.get(key);
416                         String val = null;
417                         if (enabled) {
418                                 val = (String) fWorkingValues.get(key);
419                                 if (val != null && !val.equals(oldVal)) {
420                                         hasChanges = true;
421                                         actualOptions.put(key, val);
422                                 }
423                         } else {
424                                 if (oldVal != null) {
425                                         actualOptions.remove(key);
426                                         hasChanges = true;
427                                 }
428                         }
429                 }
430
431                 if (hasChanges) {
432                         boolean doBuild = false;
433                         String[] strings = getFullBuildDialogStrings(fProject == null);
434                         if (strings != null) {
435                                 MessageDialog dialog = new MessageDialog(getShell(),
436                                                 strings[0], null, strings[1], MessageDialog.QUESTION,
437                                                 new String[] { IDialogConstants.YES_LABEL,
438                                                                 IDialogConstants.NO_LABEL,
439                                                                 IDialogConstants.CANCEL_LABEL }, 2);
440                                 int res = dialog.open();
441                                 if (res == 0) {
442                                         doBuild = true;
443                                 } else if (res != 1) {
444                                         return false; // cancel pressed
445                                 }
446                         }
447                         setOptions(actualOptions);
448                         if (doBuild) {
449                                 boolean res = doFullBuild();
450                                 if (!res) {
451                                         return false;
452                                 }
453                         }
454                 }
455                 return true;
456         }
457
458         protected abstract String[] getFullBuildDialogStrings(
459                         boolean workspaceSettings);
460
461         protected boolean doFullBuild() {
462
463                 Job buildJob = new Job(PreferencesMessages
464                                 .getString("OptionsConfigurationBlock.job.title")) { //$NON-NLS-1$
465                         /*
466                          * (non-Javadoc)
467                          * 
468                          * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
469                          */
470                         protected IStatus run(IProgressMonitor monitor) {
471                                 try {
472                                         if (fProject != null) {
473                                                 monitor
474                                                                 .setTaskName(PreferencesMessages
475                                                                                 .getFormattedString(
476                                                                                                 "OptionsConfigurationBlock.buildproject.taskname", fProject.getElementName())); //$NON-NLS-1$
477                                                 fProject.getProject().build(
478                                                                 IncrementalProjectBuilder.FULL_BUILD,
479                                                                 new SubProgressMonitor(monitor, 1));
480                                                 PHPeclipsePlugin.getWorkspace().build(
481                                                                 IncrementalProjectBuilder.INCREMENTAL_BUILD,
482                                                                 new SubProgressMonitor(monitor, 1));
483                                         } else {
484                                                 monitor
485                                                                 .setTaskName(PreferencesMessages
486                                                                                 .getString("OptionsConfigurationBlock.buildall.taskname")); //$NON-NLS-1$
487                                                 PHPeclipsePlugin.getWorkspace().build(
488                                                                 IncrementalProjectBuilder.FULL_BUILD,
489                                                                 new SubProgressMonitor(monitor, 2));
490                                         }
491                                 } catch (CoreException e) {
492                                         return e.getStatus();
493                                 } catch (OperationCanceledException e) {
494                                         return Status.CANCEL_STATUS;
495                                 } finally {
496                                         monitor.done();
497                                 }
498                                 return Status.OK_STATUS;
499                         }
500
501                         public boolean belongsTo(Object family) {
502                                 return ResourcesPlugin.FAMILY_MANUAL_BUILD == family;
503                         }
504                 };
505
506                 buildJob.setRule(ResourcesPlugin.getWorkspace().getRuleFactory()
507                                 .buildRule());
508                 buildJob.setUser(true);
509                 buildJob.schedule();
510                 return true;
511         }
512
513         public void performDefaults() {
514                 fWorkingValues = getDefaultOptions();
515                 updateControls();
516                 validateSettings(null, null);
517         }
518
519         protected void updateControls() {
520                 // update the UI
521                 for (int i = fCheckBoxes.size() - 1; i >= 0; i--) {
522                         updateCheckBox((Button) fCheckBoxes.get(i));
523                 }
524                 for (int i = fComboBoxes.size() - 1; i >= 0; i--) {
525                         updateCombo((Combo) fComboBoxes.get(i));
526                 }
527                 for (int i = fTextBoxes.size() - 1; i >= 0; i--) {
528                         updateText((Text) fTextBoxes.get(i));
529                 }
530         }
531
532         protected void updateCombo(Combo curr) {
533                 ControlData data = (ControlData) curr.getData();
534
535                 String currValue = (String) fWorkingValues.get(data.getKey());
536                 curr.select(data.getSelection(currValue));
537         }
538
539         protected void updateCheckBox(Button curr) {
540                 ControlData data = (ControlData) curr.getData();
541
542                 String currValue = (String) fWorkingValues.get(data.getKey());
543                 curr.setSelection(data.getSelection(currValue) == 0);
544         }
545
546         protected void updateText(Text curr) {
547                 String key = (String) curr.getData();
548
549                 String currValue = (String) fWorkingValues.get(key);
550                 if (currValue != null) {
551                         curr.setText(currValue);
552                 }
553         }
554
555         protected Button getCheckBox(String key) {
556                 for (int i = fCheckBoxes.size() - 1; i >= 0; i--) {
557                         Button curr = (Button) fCheckBoxes.get(i);
558                         ControlData data = (ControlData) curr.getData();
559                         if (key.equals(data.getKey())) {
560                                 return curr;
561                         }
562                 }
563                 return null;
564         }
565
566         protected Combo getComboBox(String key) {
567                 for (int i = fComboBoxes.size() - 1; i >= 0; i--) {
568                         Combo curr = (Combo) fComboBoxes.get(i);
569                         ControlData data = (ControlData) curr.getData();
570                         if (key.equals(data.getKey())) {
571                                 return curr;
572                         }
573                 }
574                 return null;
575         }
576
577         protected void setComboEnabled(String key, boolean enabled) {
578                 Combo combo = getComboBox(key);
579                 Label label = (Label) fLabels.get(combo);
580                 combo.setEnabled(enabled);
581                 label.setEnabled(enabled);
582         }
583
584 }