View Javadoc

1   /*
2    * Created on Mar 18, 2005. Copyright 1999-2006 Faculty of Mathematics, Physics
3    * and Informatics, Comenius University, Bratislava. This file is protected by
4    * the Mozilla Public License version 1.1 (the "License"); you may not use this
5    * file except in compliance with the License. You may obtain a copy of the
6    * License at http://euromath2.sourceforge.net/license.html Unless required by
7    * applicable law or agreed to in writing, software distributed under the
8    * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
9    * OF ANY KIND, either express or implied. See the License for the specific
10   * language governing permissions and limitations under the License.
11   */
12  package sk.uniba.euromath.editor.widgets;
13  import java.util.ArrayList;
14  import java.util.List;
15  import java.util.Set;
16  import javax.xml.namespace.QName;
17  import org.apache.commons.lang.StringUtils;
18  import org.eclipse.swt.SWT;
19  import org.eclipse.swt.events.DisposeEvent;
20  import org.eclipse.swt.events.DisposeListener;
21  import org.eclipse.swt.events.ModifyEvent;
22  import org.eclipse.swt.events.ModifyListener;
23  import org.eclipse.swt.events.SelectionAdapter;
24  import org.eclipse.swt.events.SelectionEvent;
25  import org.eclipse.swt.layout.GridData;
26  import org.eclipse.swt.layout.GridLayout;
27  import org.eclipse.swt.layout.RowLayout;
28  import org.eclipse.swt.widgets.Button;
29  import org.eclipse.swt.widgets.Combo;
30  import org.eclipse.swt.widgets.Composite;
31  import org.eclipse.swt.widgets.Label;
32  import org.eclipse.swt.widgets.Text;
33  import sk.uniba.euromath.document.NamespaceManager;
34  import sk.uniba.euromath.document.XMLAccess;
35  import sk.uniba.euromath.document.schema.ElementLoc;
36  import sk.uniba.euromath.document.schema.InsertList;
37  import sk.uniba.euromath.document.schema.NewElementRule;
38  import sk.uniba.euromath.document.schema.plug.IValueRule;
39  import sk.uniba.euromath.editor.lang.Messages;
40  import sk.uniba.euromath.editor.widgets.namelist.DisplayableNameItemTypeEnum;
41  import sk.uniba.euromath.editor.widgets.namelist.DisplayableNameListImpl;
42  /***
43   * Allows the user to choose between multiple insertlists, and to choose the
44   * name of each element. These user settings are returned. Alternatively, user
45   * may choose to enter some text if it is permitted by the rule.
46   * @author Martin Vysny
47   */
48  public class InsertListChooser extends AbstractUserInputWidget {
49  	/***
50  	 * Here all controls will be placed.
51  	 */
52  	protected final Composite composite;
53  	/***
54  	 * The XML Access instance.
55  	 */
56  	protected final XMLAccess xmlAccess;
57  	/***
58  	 * Insertlists.
59  	 */
60  	protected final List<InsertList> insertLists;
61  	/***
62  	 * User may choose to enter text contents of an element instead.
63  	 */
64  	protected final IValueRule textRule;
65  	/***
66  	 * Current namespace manager.
67  	 */
68  	protected final NamespaceManager nsManager;
69  	/***
70  	 * Creates an instance of the window.
71  	 * @param parent where to place controls.
72  	 * @param xmlAccess the XML Access instance.
73  	 * @param insertLists the list of choosable insertlists.
74  	 * @param nsManager the map of namespace&gt;prefix mapping. It will not get
75  	 * modified. If <code>null</code> then manager from <code>xmlAccess</code>
76  	 * will be used.
77  	 * @param parentName the displayable qname of the parent. It is only
78  	 * displayed in a window as a text - it is not used in other way.
79  	 * @param textRule if not <code>null</code> then it is possible to choose
80  	 * a text value also. This value must comply this rule.
81  	 */
82  	public InsertListChooser(Composite parent, XMLAccess xmlAccess,
83  			List<InsertList> insertLists, NamespaceManager nsManager,
84  			String parentName, IValueRule textRule) {
85  		super();
86  		this.xmlAccess = xmlAccess;
87  		this.insertLists = (insertLists == null) ? new ArrayList<InsertList>()
88  				: insertLists;
89  		this.textRule = textRule;
90  		this.nsManager = (nsManager == null) ? xmlAccess.getNsManager()
91  				: nsManager;
92  		// create controls
93  		composite = new Composite(parent, SWT.NONE);
94  		final RowLayout shellLayout = new RowLayout();
95  		shellLayout.type = SWT.VERTICAL;
96  		shellLayout.fill = true;
97  		composite.setLayout(shellLayout);
98  		new Label(composite, SWT.NONE).setText(Messages.getString(
99  				"SELECT_ELEMENT_SEQ", parentName)); //$NON-NLS-1$
100 		// build a component displaying list of insertlists
101 		final Composite panel = new Composite(composite, SWT.NONE);
102 		final GridLayout panelLayout = new GridLayout(2, false);
103 		panel.setLayout(panelLayout);
104 		// create components for text insertion
105 		if (textRule != null) {
106 			radioText = new Button(panel, SWT.RADIO);
107 			if (insertLists.size() == 0) {
108 				// there are no insertlists; select the text radio button
109 				radioText.setSelection(true);
110 			}
111 			radioText.addDisposeListener(new DisposeListener() {
112 				/*
113 				 * (non-Javadoc)
114 				 * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
115 				 */
116 				public void widgetDisposed(DisposeEvent e) {
117 					if (((Button) e.widget).getSelection())
118 						_selected = -2;
119 				}
120 			});
121 			radioText.addSelectionListener(new SelectionAdapter() {
122 				/*
123 				 * (non-Javadoc)
124 				 * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
125 				 */
126 				@Override
127 				public void widgetSelected(SelectionEvent e) {
128 					fireDataModified();
129 				}
130 			});
131 			final Composite tPanel = new Composite(panel, SWT.NONE);
132 			tPanel.setLayout(new GridLayout(2, false));
133 			new Label(tPanel, SWT.NONE).setText(Messages
134 					.getString("VALUE_MARK")); //$NON-NLS-1$
135 			textText = new Text(tPanel, SWT.SINGLE | SWT.BORDER);
136 			textText
137 					.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
138 			textText.addModifyListener(new ModifyListener() {
139 				/*
140 				 * (non-Javadoc)
141 				 * @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent)
142 				 */
143 				public void modifyText(ModifyEvent e) {
144 					fireDataModified();
145 				}
146 			});
147 			textText.addDisposeListener(new DisposeListener() {
148 				/*
149 				 * (non-Javadoc)
150 				 * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
151 				 */
152 				public void widgetDisposed(DisposeEvent e) {
153 					// store its value to the _value property
154 					_text = ((Text) e.widget).getText();
155 				}
156 			});
157 			new Label(tPanel, SWT.NONE).setText(Messages
158 					.getString("VALUE_TYPE_MARK")); //$NON-NLS-1$
159 			new Label(tPanel, SWT.NONE).setText(textRule.getDatatypeName());
160 		} else {
161 			radioText = null;
162 			textText = null;
163 		}
164 		// fill the component with the list of insertlists
165 		ilControls = new ArrayList<List<IlItemData>>(insertLists.size());
166 		radioButtons = new ArrayList<Button>(insertLists.size());
167 		for (int i = 0; i < insertLists.size(); i++) {
168 			final Button radio = new Button(panel, SWT.RADIO);
169 			if (i == 0)
170 				radio.setSelection(true);
171 			radioButtons.add(radio);
172 			radio.setData(i);
173 			radio.addSelectionListener(new SelectionAdapter() {
174 				/*
175 				 * (non-Javadoc)
176 				 * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
177 				 */
178 				@Override
179 				public void widgetSelected(SelectionEvent e) {
180 					fireDataModified();
181 				}
182 			});
183 			radio.addDisposeListener(new DisposeListener() {
184 				/*
185 				 * (non-Javadoc)
186 				 * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
187 				 */
188 				public void widgetDisposed(DisposeEvent e) {
189 					if (((Button) e.widget).getSelection())
190 						_selected = ((Integer) e.widget.getData()).intValue();
191 				}
192 			});
193 			// this panel shall contain the combos
194 			final Composite ilPanel = new Composite(panel, SWT.NONE);
195 			ilPanel.setLayout(new RowLayout());
196 			final List<IlItemData> combos = new ArrayList<IlItemData>(
197 					insertLists.get(i).size());
198 			InsertList insertList = insertLists.get(i);
199 			ilControls.add(combos);
200 			for (final ElementLoc loc : insertList) {
201 				final Combo combo = new Combo(ilPanel, SWT.READ_ONLY);
202 				final DisplayableNameListImpl<NewElementRule> dnl = new DisplayableNameListImpl<NewElementRule>(
203 						loc.nameList, xmlAccess, nsManager, true);
204 				combo.setItems(dnl.getStrings());
205 				combo.select(0);
206 				final IlItemData id = new IlItemData(combo, dnl);
207 				combo.setData(id);
208 				combo.addSelectionListener(csInstance);
209 				combos.add(id);
210 			}
211 		}
212 	}
213 	/***
214 	 * Controls (comboboxes) containing chosen qnames for each insertlist. Each
215 	 * list is a list of <code>IlItemData</code> instances, one instance
216 	 * depicts one <code>ElementLoc</code>.
217 	 */
218 	protected final List<List<IlItemData>> ilControls;
219 	/***
220 	 * List of radio buttons. These radio buttons serves for choosing the
221 	 * insertlist.
222 	 */
223 	protected final List<Button> radioButtons;
224 	/***
225 	 * This control is valid (non- <code>null</code>) when the user can enter
226 	 * some text as an alternative to the insertlists.
227 	 */
228 	protected final Text textText;
229 	/***
230 	 * This controls is valid (non- <code>null</code>) when the user can
231 	 * enter some text as an alternative to the insertlists.
232 	 */
233 	protected final Button radioText;
234 	/***
235 	 * Returns the index of the radio button, that is checked (selected). Radio
236 	 * buttons must not be disposed.
237 	 * @return the index of selected radio button, -1 if none is selected and -2
238 	 * if the text is selected.
239 	 */
240 	protected int getSelectedFromControl() {
241 		int i = 0;
242 		for (final Button radio : radioButtons) {
243 			if (radio.getSelection())
244 				return i;
245 			i++;
246 		}
247 		if ((textRule != null) && (radioText.getSelection()))
248 			return -2;
249 		return -1;
250 	}
251 	/***
252 	 * Returns the index of the radio button, that is checked (selected). Does
253 	 * not check for errors.
254 	 * @return the index of selected radio button, -1 if none is selected and -2
255 	 * if the text is selected.
256 	 */
257 	protected int getSelectedInternal() {
258 		final boolean disposed = (radioText != null) ? radioText.isDisposed()
259 				: radioButtons.get(0).isDisposed();
260 		if (disposed)
261 			return _selected;
262 		return getSelectedFromControl();
263 	}
264 	/***
265 	 * Returns the index of the insert list, that is checked (selected). Sets
266 	 * error if -1 is returned.
267 	 * @return the index of selected radio button, -1 if none is selected and -2
268 	 * if the text is selected.
269 	 */
270 	public int getSelected() {
271 		lastMessages = null;
272 		final int result = getSelectedInternal();
273 		if (result == -1) {
274 			lastMessages = MessageLevelEnum.ERROR.setMessage(lastMessages,
275 					Messages.getString("SELECT_INSERTLIST")); //$NON-NLS-1$
276 		}
277 		return result;
278 	}
279 	/***
280 	 * Returns new textual value of element. Sets error if text value is
281 	 * invalid.
282 	 * @return new text value, never <code>null</code>.
283 	 */
284 	public String getText() {
285 		if (getSelected() != -2)
286 			throw new IllegalStateException("Text must be selected"); //$NON-NLS-1$
287 		final String text = textText.isDisposed() ? _text : textText.getText();
288 		if (!textRule.acceptsValue(text)) {
289 			String errorMsg = textRule.getErrorMessage(text);
290 			if (!StringUtils.isEmpty(text))
291 				errorMsg = " " + errorMsg; //$NON-NLS-1$
292 			errorMsg = Messages.getString("ERROR_INVALID_VALUE") + errorMsg; //$NON-NLS-1$
293 			lastMessages = MessageLevelEnum.ERROR.setMessage(lastMessages,
294 					errorMsg);
295 		}
296 		return text;
297 	}
298 	/***
299 	 * Returns selected insertlist as a list of qnames. Calls
300 	 * <code>getInsertList()</code>.
301 	 * @return list of qnames, never <code>null</code>. May contain
302 	 * <code>null</code>s if appropriate qnames were not selected.
303 	 * @throws IllegalStateException if insertlist is not selected.
304 	 */
305 	public List<QName> getInsertListNames() {
306 		int i = getSelected();
307 		final InsertList il = getInsertList();
308 		final List<QName> result = new ArrayList<QName>(il.size());
309 		for (final IlItemData item : ilControls.get(i)) {
310 			int selected = item.getSelectionIndex();
311 			if (selected < 0) {
312 				result.add(null);
313 				lastMessages = MessageLevelEnum.ERROR.setMessage(lastMessages,
314 						Messages.getString("ERROR_MISSING_ELEMENT_NAME")); //$NON-NLS-1$
315 				continue;
316 			}
317 			final String ns = item.comboItems.getDomNamespaceUri(selected);
318 			final String prefix = nsManager.getBestPrefix(ns);
319 			result.add(item.comboItems.getDomQName(selected, prefix));
320 		}
321 		return result;
322 	}
323 	/***
324 	 * Returns selected insertlist as a list of qnames.
325 	 * @return list of qnames, never <code>null</code>.
326 	 * @throws IllegalStateException if insertlist is not selected.
327 	 */
328 	public InsertList getInsertList() {
329 		int i = getSelected();
330 		if (i < 0)
331 			throw new IllegalStateException("Insertlist must be selected"); //$NON-NLS-1$
332 		InsertList il = insertLists.get(i);
333 		return il;
334 	}
335 	/***
336 	 * Stores the index of the insertlist radio button, that is checked
337 	 * (selected). -1 if none is selected and -2 if the text is selected. Valid
338 	 * after all radio buttons are disposed.
339 	 */
340 	protected int _selected = -1;
341 	/***
342 	 * Stores the text value that new element may contain. Valid after
343 	 * <code>textText</code> is disposed.
344 	 */
345 	protected String _text = null;
346 	/***
347 	 * Contains information on insertlist's comboboxes. Caches value when the
348 	 * combobox gets disposed.
349 	 * @author Martin Vysny
350 	 */
351 	protected class IlItemData {
352 		/***
353 		 * Combobox reference.
354 		 */
355 		protected final Combo combo;
356 		/***
357 		 * Combobox items.
358 		 */
359 		protected final DisplayableNameListImpl<NewElementRule> comboItems;
360 		/***
361 		 * Constructor.
362 		 * @param combo reference to combobox
363 		 * @param comboItems items displayed in the combobox.
364 		 */
365 		protected IlItemData(Combo combo,
366 				DisplayableNameListImpl<NewElementRule> comboItems) {
367 			super();
368 			this.combo = combo;
369 			this.comboItems = comboItems;
370 			combo.addDisposeListener(new DisposeListener() {
371 				public void widgetDisposed(DisposeEvent e) {
372 					_selectedIndex = ((Combo) e.widget).getSelectionIndex();
373 				}
374 			});
375 		}
376 		/***
377 		 * Returns index of selected name list item.
378 		 * @return index to <code>comboItems</code> list.
379 		 */
380 		public int getSelectionIndex() {
381 			if (combo.isDisposed())
382 				return _selectedIndex;
383 			return combo.getSelectionIndex();
384 		}
385 		/***
386 		 * Here the index is stored when combobox is disposed.
387 		 */
388 		protected int _selectedIndex;
389 	}
390 	/***
391 	 * Event handler for each combobox.
392 	 * @author Martin Vysny
393 	 */
394 	protected class ComboSelected extends SelectionAdapter {
395 		/*
396 		 * (non-Javadoc)
397 		 * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
398 		 */
399 		@Override
400 		public void widgetSelected(SelectionEvent e) {
401 			final Combo sender = (Combo) e.widget;
402 			final IlItemData id = (IlItemData) sender.getData();
403 			final int sel = sender.getSelectionIndex();
404 			if (sel < 0) {
405 				fireDataModified();
406 				return;
407 			}
408 			final DisplayableNameItemTypeEnum type = id.comboItems.getType(sel);
409 			if (type != DisplayableNameItemTypeEnum.ITEM_NAMESPACE) {
410 				fireDataModified();
411 				return;
412 			}
413 			// a namespace was selected for which a schema is not yet loaded.
414 			final String ns = id.comboItems.getNamespace(sel);
415 			if (!Tools.loadSchema(composite.getShell(), xmlAccess.getSchema()
416 					.getRefs(), ns)) {
417 				// we couldn't load schema for this namespace. Select null item
418 				sender.select(-1);
419 				// data-modified event is already fired by previous command, so
420 				// just return.
421 				return;
422 			}
423 			// Recreate all namelists, update all combos
424 			update();
425 			fireDataModified();
426 		}
427 	}
428 	/***
429 	 * The event handler instance.
430 	 */
431 	private final ComboSelected csInstance = new ComboSelected();
432 	/***
433 	 * Updates the contents of all combos. Run when new schema is loaded.
434 	 */
435 	private void update() {
436 		suppressModifyEvent();
437 		// update all combos
438 		for (final List<IlItemData> list : ilControls) {
439 			for (final IlItemData id : list) {
440 				int item = id.combo.getSelectionIndex();
441 				if ((item >= 0)
442 						&& (item < id.comboItems.getLength())
443 						&& (id.comboItems.getType(item) == DisplayableNameItemTypeEnum.ITEM_LOCAL)) {
444 					// ok, combobox is scrolled on local name, leave it be
445 				} else {
446 					// combobox points onto item that is likely to be moved,
447 					// reset it
448 					item = -1;
449 				}
450 				id.comboItems.refresh();
451 				id.combo.setItems(id.comboItems.getStrings());
452 				id.combo.select(item);
453 			}
454 		}
455 		allowModifyEvent();
456 	}
457 	/*
458 	 * (non-Javadoc)
459 	 * @see sk.uniba.euromath.editor.widgets.IUserInputWidget#getComposite()
460 	 */
461 	public Composite getComposite() {
462 		return composite;
463 	}
464 	/***
465 	 * Last messages.
466 	 */
467 	protected ValidityMessages lastMessages = null;
468 	/*
469 	 * (non-Javadoc)
470 	 * @see sk.uniba.euromath.editor.widgets.IUserInputWidget#getLastError()
471 	 */
472 	public ValidityMessages getMessages() {
473 		return lastMessages;
474 	}
475 	/***
476 	 * Updates error messages.
477 	 */
478 	protected void updateMessages() {
479 		final int i = getSelected();
480 		if (i == -1)
481 			return;
482 		if (i == -2) {
483 			getText();
484 			return;
485 		}
486 		getInsertListNames();
487 	}
488 	/***
489 	 * Retrieves all namespaces of currently selected attributes. Function must
490 	 * not be used when no insertlist is selected.
491 	 * @return all namespaces of the attributes. If there are errors on the
492 	 * page, the function may return incomplete set.
493 	 */
494 	public Set<String> getAllNamespaces() {
495 		final List<QName> qnames = getInsertListNames();
496 		return NewPrefixesQuery.getNamespaces(qnames);
497 	}
498 	/***
499 	 * The state of the chooser.
500 	 * @author Martin Vysny
501 	 */
502 	public class State {
503 		/***
504 		 * Creates new snapshot of chooser state.
505 		 */
506 		public State() {
507 			super();
508 			final int sel = getSelected();
509 			textSelected = (sel == -2);
510 			insertListSelected = (sel >= 0);
511 			text = textSelected ? getText() : null;
512 			insertList = insertListSelected ? getInsertList() : null;
513 			insertListNames = insertListSelected ? getInsertListNames() : null;
514 		}
515 		/***
516 		 * If <code>true</code> then user wishes to insert a text. Cannot be
517 		 * <code>true</code> if {@link #insertListSelected} is
518 		 * <code>true</code>.
519 		 */
520 		public final boolean textSelected;
521 		/***
522 		 * If <code>true</code> then user wishes to insert an insert list.
523 		 * Cannot be <code>true</code> if {@link #textSelected} is
524 		 * <code>true</code>.
525 		 */
526 		public final boolean insertListSelected;
527 		/***
528 		 * New textual value of element. Sets error if text value is invalid.
529 		 * <code>null</code> if {@link #textSelected} is <code>false</code>.
530 		 */
531 		public final String text;
532 		/***
533 		 * Selected insertlist as a list of qnames. <code>null</code> if
534 		 * {@link #insertListSelected} is <code>false</code>.
535 		 */
536 		public final List<QName> insertListNames;
537 		/***
538 		 * Selected insert list. <code>null</code> if
539 		 * {@link #insertListSelected} is <code>false</code>.
540 		 */
541 		public final InsertList insertList;
542 	}
543 	/*
544 	 * (non-Javadoc)
545 	 * @see sk.uniba.euromath.editor.widgets.IUserInputWidget#getState()
546 	 */
547 	public Object getState() {
548 		return new State();
549 	}
550 	/*
551 	 * (non-Javadoc)
552 	 * @see sk.uniba.euromath.editor.widgets.IUserInputWidget#getStateClass()
553 	 */
554 	public Class< ? > getStateClass() {
555 		return State.class;
556 	}
557 	/*
558 	 * (non-Javadoc)
559 	 * @see sk.uniba.euromath.editor.widgets.IUserInputWidget#setState(java.lang.Object)
560 	 */
561 	public void setState(Object state) {
562 		throw new UnsupportedOperationException();
563 	}
564 }