View Javadoc
1   /*
2    * Copyright 2013-2023 Medical Information Systems Research Group (https://medical.zcu.cz),
3    * Department of Computer Science and Engineering, University of West Bohemia.
4    * Address: Univerzitni 8, 306 14 Plzen, Czech Republic.
5    *
6    * This file is part of Sparkle project.
7    *
8    * Sparkle is free software: you can redistribute it and/or modify
9    * it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License.
11   *
12   * Sparkle is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with Sparkle. If not, see <http://www.gnu.org/licenses/>.
19   */
20  package cz.zcu.mre.sparkle.gui.query.other;
21  
22  import cz.zcu.mre.sparkle.data.PrefixesStorage;
23  import cz.zcu.mre.sparkle.gui.query.autoComplete.AutoCompleteListHandler;
24  import cz.zcu.mre.sparkle.gui.query.autoComplete.AutoCompletePolicy;
25  import cz.zcu.mre.sparkle.gui.query.autoComplete.AutoCompleteWrapper;
26  import cz.zcu.mre.sparkle.gui.query.helpers.*;
27  import cz.zcu.mre.sparkle.gui.tools.Components;
28  import cz.zcu.mre.sparkle.tools.Changeable;
29  import cz.zcu.mre.sparkle.tools.Definitions;
30  import cz.zcu.mre.sparkle.tools.Utils;
31  import cz.zcu.mre.sparkle.tools.sparqlValidation.SparqlValidationUtils;
32  import javafx.beans.InvalidationListener;
33  import javafx.beans.property.*;
34  import javafx.fxml.FXML;
35  import javafx.scene.control.Label;
36  import javafx.scene.control.TextField;
37  import javafx.scene.layout.HBox;
38  import javafx.scene.layout.Priority;
39  import java.util.*;
40  
41  /**
42   * Kontroler komponenty reprezentující "chytré" editační pole. Kombinuje
43   * funkcionalitu {@link AutoCompleteWrapper} a {@link FieldTypeWrapper}.
44   * Umožňuje nastavit {@link AutoCompleteListHandler} pro každý typ pole zvlášť.
45   *
46   * @author Jan Smucr
47   * @author Klara Hlavacova
48   * @author Petr Vcelak (vcelak@kiv.zcu.cz)
49   */
50  public final class TypedTextField
51          extends HBox
52          implements VariablesGenerator, PrefixesUser, Changeable {
53  
54      @FXML
55      private Label prefixLabel;
56      @FXML
57      private Label suffixLabel;
58      @FXML
59      private TextField textField;
60  
61      private final Map<FieldType, AutoCompleteListHandler> autoCompleteHandlers;
62      private final AutoCompleteWrapper autoComplete;
63      private final ReadOnlyStringWrapper valueProperty = new ReadOnlyStringWrapper();
64      private final FieldTypeWrapper fieldTypeWrapper;
65      private final ObjectProperty<TypedTextField> boundTextFieldProperty = new SimpleObjectProperty<>(null);
66      private final VariableFieldListener variableListener = new VariableFieldListener(this);
67      private final Set<VariablesCollector> variablesCollectors = new HashSet<>();
68      private final Set<InvalidationListener> observers = new HashSet<>();
69  
70      public TypedTextField(final PrefixesStorage prefixesStorage, boolean checkValues, final FieldType initialType,
71              final FieldType... otherOptions) {
72  
73          Components.load(this);
74  
75          fieldTypeWrapper = new FieldTypeWrapper(textField, prefixesStorage, initialType, checkValues, otherOptions);
76  
77          autoComplete = new AutoCompleteWrapper(textField, null);
78          autoCompleteHandlers = new HashMap<>();
79          // autoComplete.setReopenOnAutoComplete(true);
80  
81          init();
82      }
83  
84      public TypedTextField(final PrefixesStorage prefixesStorage, final TypedTextField boundTextField) {
85          Components.load(this);
86  
87          fieldTypeWrapper = boundTextField.fieldTypeWrapper;
88          boundTextFieldProperty.set(boundTextField);
89          autoComplete = null;
90          autoCompleteHandlers = null;
91  
92          init();
93      }
94  
95      /**
96       *
97       */
98      private void init() {
99          setupListeners();
100         setupBinding();
101         onFieldTypeChanged(null, fieldTypeWrapper.getFieldType());
102 
103         HBox.setHgrow(this, Priority.ALWAYS);
104     }
105 
106     /**
107      *
108      */
109     private void setupListeners() {
110         fieldTypeWrapper.fieldTypeProperty()
111                 .addListener((observable, oldValue, newValue) -> onFieldTypeChanged(oldValue, newValue));
112         textField.textProperty().addListener((o) -> updateValue());
113         updateValue();
114     }
115 
116     /**
117      *
118      */
119     private void setupBinding() {
120         Utils.makeInvisibleUnmanaged(prefixLabel, suffixLabel);
121 
122         final ObjectProperty<FieldType> ft = fieldTypeWrapper.fieldTypeProperty();
123         prefixLabel.visibleProperty().bind(
124                 ft.isEqualTo(FieldType.Expression).or(ft.isEqualTo(FieldType.IRI)).or(ft.isEqualTo(FieldType.Variable))
125                         .or(ft.isEqualTo(FieldType.Literal)));
126         suffixLabel.visibleProperty().bind(ft.isEqualTo(FieldType.Expression).or(ft.isEqualTo(FieldType.IRI))
127                 .or(ft.isEqualTo(FieldType.Literal)));
128         textField.editableProperty().bind(
129                 ft.isEqualTo(FieldType.Expression).or(ft.isEqualTo(FieldType.IRI)).or(ft.isEqualTo(FieldType.Variable))
130                         .or(ft.isEqualTo(FieldType.Literal))
131                         .or(ft.isEqualTo(FieldType.PrefixedName)).or(ft.isEqualTo(FieldType.Modifier))
132                         .or(ft.isEqualTo(FieldType.BlankNode)).or(ft.isEqualTo(FieldType.Nil))
133                         .or(ft.isEqualTo(FieldType.Numeric)).or(ft.isEqualTo(FieldType.Boolean)));
134 
135         textField.disableProperty().bind(boundTextFieldProperty.isNotNull());
136 
137         if (autoComplete != null) {
138             autoComplete.autoPopupOffsetProperty().bind(ft.isEqualTo(FieldType.Expression));
139             autoComplete.openOnFocusProperty()
140                     .bind(ft.isEqualTo(FieldType.Variable).or(ft.isEqualTo(FieldType.PrefixedName)));
141         }
142 
143         final TypedTextField boundTextField = boundTextFieldProperty.get();
144         if (boundTextField != null) {
145             textField.textProperty().bindBidirectional(boundTextField.textField.textProperty());
146             textField.promptTextProperty().bindBidirectional(boundTextField.textField.promptTextProperty());
147         }
148     }
149 
150     /**
151      * @param oldType
152      * @param newType
153      */
154     private void onFieldTypeChanged(final FieldType oldType, final FieldType newType) {
155         updateLabels(newType);
156         updateAutoComplete(newType);
157         updateListeners(oldType, newType);
158         updateContent(newType);
159         updateValue();
160     }
161 
162     /**
163      * @param oldType
164      * @param newType
165      */
166     private void updateListeners(final FieldType oldType, final FieldType newType) {
167         if (newType == FieldType.Variable) {
168             textField.textProperty().addListener(variableListener);
169             final String variableName = textField.getText().trim();
170             if (SparqlValidationUtils.isVariableNameValid(variableName)) {
171                 notifyVariableAdded(variableName);
172             }
173         } else if (oldType == FieldType.Variable) {
174             textField.textProperty().removeListener(variableListener);
175             final String variableName = textField.getText().trim();
176             if (SparqlValidationUtils.isVariableNameValid(variableName)) {
177                 notifyVariableRemoved(variableName);
178             }
179         }
180     }
181 
182     /**
183      * @param newType
184      */
185     private void updateContent(final FieldType newType) {
186         if (newType == FieldType.A) {
187             setText(Definitions.RDF_TYPE_SHORTCUT);
188         } else if (newType == FieldType.Undef) {
189             setText(Definitions.RDF_UNDEF);
190         }
191         textField.end();
192     }
193 
194     /**
195      * @param type
196      */
197     private void updateAutoComplete(final FieldType type) {
198         if (autoComplete == null) {
199             return;
200         }
201 
202         updateAutoCompleteHandler(type);
203         updateAutoCompleteProperties(type);
204     }
205 
206     /**
207      * Nastavuje způsob dokončování a reakce na napsání znaků podle typu pole.
208      *
209      * @param type Typ pole.
210      */
211     private void updateAutoCompleteProperties(final FieldType type) {
212         switch (type) {
213             case Expression:
214                 autoComplete.setPolicy(AutoCompletePolicy.FinishWord);
215                 autoComplete.setOpenOnChars('?', ':');
216                 break;
217             case PrefixedName:
218                 autoComplete.setPolicy(AutoCompletePolicy.ReplaceAll);
219                 autoComplete.setOpenOnChars(':');
220                 break;
221             default:
222                 autoComplete.setPolicy(AutoCompletePolicy.ReplaceAll);
223                 autoComplete.clearOpenOnChars();
224                 break;
225         }
226 
227     }
228 
229     /**
230      * @param type
231      */
232     private void updateAutoCompleteHandler(final FieldType type) {
233         final AutoCompleteListHandler handler = autoCompleteHandlers.get(type);
234         autoComplete.setListHandler(handler);
235 
236         if (autoComplete.isPopupVisible()) {
237             if (!(textField.isFocused() && autoComplete.getOpenOnFocus())) {
238                 autoComplete.hidePopup();
239             }
240         } else if ((handler != null) && textField.isFocused() && autoComplete.getOpenOnFocus()) {
241             autoComplete.showPopup();
242         }
243     }
244 
245     /**
246      * @param type
247      */
248     private void updateLabels(final FieldType type) {
249         switch (type) {
250             case Expression:
251                 prefixLabel.setText(Definitions.EXPRESSION_PREFIX);
252                 suffixLabel.setText(Definitions.EXPRESSION_SUFFIX);
253                 break;
254             case IRI:
255                 prefixLabel.setText(Definitions.IRI_PREFIX);
256                 suffixLabel.setText(Definitions.IRI_SUFFIX);
257                 break;
258             case Literal:
259                 prefixLabel.setText(Definitions.LITERAL_PREFIX);
260                 suffixLabel.setText(Definitions.LITERAL_SUFFIX);
261                 break;
262             case Variable:
263                 prefixLabel.setText(Definitions.VARIABLE_PREFIX);
264                 suffixLabel.setText(""); //$NON-NLS-1$
265                 break;
266             default:
267                 prefixLabel.setText(""); //$NON-NLS-1$
268                 suffixLabel.setText(""); //$NON-NLS-1$
269                 break;
270 
271         }
272     }
273 
274     /**
275      * @return Get read only string property
276      */
277     public final ReadOnlyStringProperty valueProperty() {
278         return valueProperty.getReadOnlyProperty();
279     }
280 
281     /**
282      * @return Kompletní výstup pole. Text včetně prefixů a suffixů a případného
283      * escapování.
284      */
285     public final String getValue() {
286         return valueProperty.get();
287     }
288 
289     private void updateValue() {
290         valueProperty.set(prefixLabel.getText()
291                 + (getFieldType() == FieldType.Literal ? escapeQuotes(textField.getText().trim())
292                         : textField.getText().trim()) + suffixLabel.getText());
293         notifyChanged();
294     }
295 
296     /**
297      * @return Text v poli bez dalších úprav.
298      */
299     public final String getText() {
300         return textField.getText();
301     }
302 
303     /**
304      * Nastaví text pole.
305      *
306      * @param text Text pole.
307      */
308     public final void setText(final String text) {
309         textField.setText(text);
310         fieldTypeWrapper.setCheckValue(true);
311     }
312 
313     public final AutoCompleteListHandler getAutoCompleteListHandler(final FieldType fieldType) {
314         return autoCompleteHandlers.get(fieldType);
315     }
316 
317     public final void setAutoCompleteListHandler(final FieldType fieldType, final AutoCompleteListHandler handler) {
318         if (fieldTypeWrapper.getFieldType() == fieldType) {
319             autoComplete.setListHandler(handler);
320         }
321 
322         if (handler == null) {
323             autoCompleteHandlers.remove(fieldType);
324             return;
325         }
326 
327         autoCompleteHandlers.put(fieldType, handler);
328     }
329 
330     public TextField getTextField() {
331         return this.textField;
332     }
333 
334     @Override
335     public final Set<String> getVariables() {
336         final Set<String> result;
337         if ((fieldTypeWrapper.getFieldType() == FieldType.Variable)
338                 && (fieldTypeWrapper.fieldContentIsValidProperty().get())) {
339             result = new HashSet<>(1);
340             result.add(getText());
341         } else if (fieldTypeWrapper.getFieldType() == FieldType.Expression) {
342             result = Utils.extractMultipleVariables(getText());
343         } else {
344             result = Collections.emptySet();
345         }
346         return result;
347     }
348 
349     @Override
350     public final void notifyVariableRemoved(final String variableName) {
351         variablesCollectors.forEach((c) -> c.onVariableRemoved(variableName));
352     }
353 
354     @Override
355     public final void notifyVariableAdded(final String variableName) {
356         variablesCollectors.forEach((c) -> c.onVariableAdded(variableName));
357     }
358 
359     @Override
360     public final void addVariablesCollector(final VariablesCollector collector) {
361         variablesCollectors.add(collector);
362     }
363 
364     @Override
365     public final void removeVariablesCollector(final VariablesCollector collector) {
366         variablesCollectors.remove(collector);
367     }
368 
369     @Override
370     public final void notifyVariableChanged(final String oldVariableName, final String newVariableName) {
371         variablesCollectors.forEach((c) -> c.onVariableChanged(oldVariableName, newVariableName));
372     }
373 
374     @Override
375     public final Set<String> getPrefixesUsed(final boolean appendDelimiter) {
376         Set<String> result = Collections.emptySet();
377         if (null != fieldTypeWrapper.getFieldType()) {
378             switch (fieldTypeWrapper.getFieldType()) {
379                 case PrefixedName:
380                 case Expression:
381                     result = Utils.extractMultiplePrefixes(getText(), appendDelimiter);
382                     break;
383                 default:
384                     result = Collections.emptySet();
385                     break;
386             }
387         }
388         return result;
389     }
390 
391     public final String getPlaceholder() {
392         return textField.getPromptText();
393     }
394 
395     public final void setPlaceholder(final String text) {
396         textField.setPromptText(text);
397     }
398 
399     public final ObjectProperty<FieldType> fieldTypeProperty() {
400         return fieldTypeWrapper.fieldTypeProperty();
401     }
402 
403     public final FieldType getFieldType() {
404         return fieldTypeWrapper.getFieldType();
405     }
406 
407     public final void setFieldType(final FieldType type) {
408         fieldTypeWrapper.setFieldType(type);
409     }
410 
411     public final ReadOnlyBooleanProperty fieldContentIsValidProperty() {
412         return fieldTypeWrapper.fieldContentIsValidProperty();
413     }
414 
415     public final ReadOnlyBooleanProperty textFieldFocusedProperty() {
416         return textField.focusedProperty();
417     }
418 
419     @Override
420     public final void requestFocus() {
421         textField.requestFocus();
422     }
423 
424     public final FieldType[] getAllowedFieldTypes() {
425         return fieldTypeWrapper.getAllowedFieldTypes();
426     }
427 
428     public final boolean isAutoCompleteVisible() {
429         return autoComplete != null && autoComplete.isPopupVisible();
430     }
431 
432     public final boolean isEmpty() {
433         return textField.getText().trim().isEmpty();
434     }
435 
436     @Override
437     public final Set<InvalidationListener> getObservers() {
438         return observers;
439     }
440 
441     private static String escapeQuotes(final String unescapedString) {
442         return unescapedString.replace("\"", "\\\"");
443     }
444 }