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;
21  
22  import cz.zcu.mre.sparkle.Messages;
23  import cz.zcu.mre.sparkle.data.DataAgent;
24  import cz.zcu.mre.sparkle.data.Storage;
25  import cz.zcu.mre.sparkle.gui.dialogs.other.ConfirmationDialog;
26  import cz.zcu.mre.sparkle.gui.dialogs.other.ErrorDialog;
27  import cz.zcu.mre.sparkle.gui.dialogs.other.ProgressDialog;
28  import cz.zcu.mre.sparkle.gui.dialogs.other.YesNoCancelDialog;
29  import cz.zcu.mre.sparkle.gui.dialogs.other.YesNoCancelDialog.Result;
30  import cz.zcu.mre.sparkle.gui.evaluation.EvaluationTab;
31  import cz.zcu.mre.sparkle.gui.evaluation.EvaluatorTab;
32  import cz.zcu.mre.sparkle.gui.mainForm.MainForm;
33  import cz.zcu.mre.sparkle.gui.query.queryTypes.insert.InsertQueryFormPane;
34  import cz.zcu.mre.sparkle.gui.query.queryTypes.insert.InsertQueryTab;
35  import cz.zcu.mre.sparkle.gui.query.other.CloseableTab;
36  import cz.zcu.mre.sparkle.gui.query.other.ExporterTab;
37  import cz.zcu.mre.sparkle.gui.query.other.SaverTab;
38  import cz.zcu.mre.sparkle.gui.query.queryTypes.ask.AskQueryFormPane;
39  import cz.zcu.mre.sparkle.gui.query.queryTypes.ask.AskQueryTab;
40  import cz.zcu.mre.sparkle.gui.query.queryTypes.construct.ConstructQueryFormPane;
41  import cz.zcu.mre.sparkle.gui.query.queryTypes.construct.ConstructQueryTab;
42  import cz.zcu.mre.sparkle.gui.query.queryTypes.delete.DeleteInsertQueryFormPane;
43  import cz.zcu.mre.sparkle.gui.query.queryTypes.delete.DeleteInsertQueryTab;
44  import cz.zcu.mre.sparkle.gui.query.queryTypes.delete.DeleteQueryFormPane;
45  import cz.zcu.mre.sparkle.gui.query.queryTypes.delete.DeleteQueryTab;
46  import cz.zcu.mre.sparkle.gui.query.queryTypes.describe.DescribeQueryFormPane;
47  import cz.zcu.mre.sparkle.gui.query.queryTypes.describe.DescribeQueryTab;
48  import cz.zcu.mre.sparkle.gui.query.queryTypes.select.SelectQueryFormPane;
49  import cz.zcu.mre.sparkle.gui.query.queryTypes.select.SelectQueryTab;
50  import cz.zcu.mre.sparkle.gui.query.queryTypes.subselect.SubSelectPane;
51  import cz.zcu.mre.sparkle.gui.text_editor.TextEditor;
52  import cz.zcu.mre.sparkle.gui.tools.ReferenceKeeper;
53  import cz.zcu.mre.sparkle.tools.IO;
54  import cz.zcu.mre.sparkle.tools.Utils;
55  import javafx.beans.Observable;
56  import javafx.beans.property.*;
57  import javafx.beans.value.ObservableValue;
58  import javafx.concurrent.Task;
59  import javafx.event.Event;
60  import javafx.scene.control.ColorPicker;
61  import javafx.scene.control.Tab;
62  import javafx.scene.paint.Color;
63  import javafx.stage.Window;
64  import org.apache.jena.query.Query;
65  import java.io.File;
66  import java.util.concurrent.ExecutionException;
67  import static cz.zcu.mre.sparkle.tools.Definitions.QUERY_FILE_EXTENSION_DELIMITER;
68  import org.slf4j.Logger;
69  import org.slf4j.LoggerFactory;
70  
71  /**
72   * Kontroler komponenty reprezentující záložku obsahující jádro dotazu odděděné
73   * od {@link QueryFormPane}.
74   *
75   * @param <QueryResultType> Typ výsledku dotazu.
76   *
77   * @author Jan Smucr
78   * @author Klara Hlavacova
79   * @author Petr Vcelak (vcelak@kiv.zcu.cz)
80   */
81  public abstract class QueryTab<QueryResultType>
82          extends Tab
83          implements EvaluatorTab, SaverTab, CloseableTab, ExporterTab {
84  
85      private static final Logger LOG = LoggerFactory.getLogger(QueryTab.class);
86  
87      private static final String FORM_TAB_NAME = Messages.getString("FORM");
88      private static final String EDITOR_TAB_NAME = Messages.getString("EDITOR");
89  
90      private final String defaultFileName = getDefaultFileName();
91  
92      private QueryFormPane<QueryResultType> queryFormPane;
93      private final QueryTabPane<QueryResultType> queryTabPane;
94  
95      private final ReadOnlyObjectWrapper<File> fileProperty = new ReadOnlyObjectWrapper<>();
96      private final ReadOnlyStringWrapper titleProperty = new ReadOnlyStringWrapper();
97  
98      private final ReadOnlyBooleanWrapper formChangedProperty = new ReadOnlyBooleanWrapper(false);
99      private final ReferenceKeeper keeperForm = new ReferenceKeeper();
100 
101     private final ReadOnlyBooleanWrapper editorChangedProperty = new ReadOnlyBooleanWrapper(false);
102     private final ReferenceKeeper keeperEditor = new ReferenceKeeper();
103 
104     private final ColorPicker colorPicker = new ColorPicker(Utils.DEFAULT_COLOR);
105 
106     private final ReadOnlyBooleanWrapper queryValidProperty = new ReadOnlyBooleanWrapper(true);
107 
108     protected QueryTab(final QueryFormPane<QueryResultType> queryFormPane) {
109         this.queryFormPane = queryFormPane;
110 
111         //vlozeni zalozky s formularem, zalozka s editorem je definovana ve .fxml sablone
112         queryTabPane = new QueryTabPane<>(this.queryFormPane);
113 
114         setContent(queryTabPane);
115 
116         titleProperty.addListener((observable) -> updateText());
117         fileProperty.addListener((observable) -> updateTitle());
118 
119         //detekce zmen ve formulari
120         setFormChangeListener();
121         setEditorChangeListener();
122         colorPicker.valueProperty().addListener((observable, oldValue, newValue) -> formChangedProperty.set(true));
123 
124         updateTitle();
125         setOnCloseRequest(this::onCloseRequest);
126 
127         initializeEditor();
128     }
129 
130     /**
131      * Inicializace editoru.
132      */
133     private void initializeEditor() {
134         Window window = queryFormPane.getMainForm().getMainTabPane().getScene().getWindow();
135         queryTabPane.initStorage(this, window);
136     }
137 
138     /**
139      * Nastavi posluchace zmen formulare.
140      */
141     private void setEditorChangeListener() {
142         editorChangedProperty.addListener((observable) -> updateText());
143 
144         ((TextEditor) queryTabPane.getQueryTextArea())
145                 .textProperty()
146                 .addListener(keeperEditor.toWeak((obs, oldText, newText) -> {
147                     validateQuery(newText);
148                     editorChangedProperty.set(true);
149 
150                 }));
151     }
152 
153     /**
154      * Nastavi posluchace zmen editoru.
155      */
156     private void setFormChangeListener() {
157         formChangedProperty.addListener((observable) -> updateText());
158 
159         this.queryFormPane.addListener(
160                 keeperForm.toWeak((final Observable observable) -> {
161                     formChangedProperty.set(true);
162                     validateQuery(queryFormPane.getQuery());
163                 }));
164 
165     }
166 
167     /**
168      * Přidá k titulku záložky hvězdičku, pokud byl dotaz uvnitř změněn.
169      */
170     private void updateText() {
171         setText(formChangedProperty.get() ? "*" + titleProperty.get() : titleProperty.get()); //$NON-NLS-1$
172         queryTabPane.getFormTab()
173                 .setText(formChangedProperty.get() ? "*" + FORM_TAB_NAME : FORM_TAB_NAME); //$NON-NLS-1$
174         queryTabPane.getEditorTab()
175                 .setText(editorChangedProperty.get() ? "*" + EDITOR_TAB_NAME : EDITOR_TAB_NAME); //$NON-NLS-1$
176 
177         formChangedProperty.set(formChangedProperty.get() || editorChangedProperty.get());
178     }
179 
180     /**
181      * Prelozi dotaz a pokud je chybny nastavi flag.
182      *
183      * @param queryInEditor dotaz v editoru
184      */
185     private void validateQuery(String queryInEditor) {
186         try {
187             if (queryTabPane.getEditorTab().isSelected()) {
188                 queryTabPane.getQuery(queryInEditor);
189             } else {
190                 queryTabPane.getQuery(queryFormPane.getQuery());
191             }
192 
193             queryValidProperty.set(true);
194         } catch (Exception e) {
195 
196             queryValidProperty.set(false);
197         }
198     }
199 
200     /**
201      * It sets default SUBSELECT name according to a main query.
202      *
203      * @param mainQuery Main query tab.
204      * @param subSelectQueryPane Hyperlink of SUBSELECT.
205      * @param loadedQueryTabName A dynamic created name of {@link QueryTab}
206      * (after loading query from file).
207      */
208     public void setSubSelectName(QueryTab mainQuery, SubSelectPane subSelectQueryPane, String loadedQueryTabName) {
209         // It sets SUBSELECT name
210         formChangedProperty.set(false);
211         if (loadedQueryTabName != null && !loadedQueryTabName.isEmpty()) {
212             this.titleProperty.set(loadedQueryTabName);
213         } else {
214             this.titleProperty.set(mainQuery.titleProperty().get() + this.titleProperty.get()
215                     + subSelectQueryPane.getSubSelectOrder());
216         }
217         setText(titleProperty.get());
218 
219         // Change listener on tab name changes
220         mainQuery.titleProperty()
221                 .addListener((ObservableValue<? extends String> ov, String oldString, String newString) -> {
222                     String suffix = titleProperty.get().substring(titleProperty.get().indexOf(
223                             Messages.getString("SUBSELECT_QUERY_DEFAULT_FILE_NAME"))); //$NON-NLS-1$
224                     if (newString.contains(QUERY_FILE_EXTENSION_DELIMITER)) {
225                         titleProperty
226                                 .set(newString.subSequence(0, newString.lastIndexOf(QUERY_FILE_EXTENSION_DELIMITER))
227                                         + suffix);
228                     } else {
229                         titleProperty.set(newString + suffix);
230                     }
231                 });
232     }
233 
234     /**
235      * varti FLaG pro zjisteni zmeny ve formulari.
236      *
237      * @return FLaG pro zjisteni zmeny ve formulari.
238      */
239     public ReadOnlyBooleanWrapper getFormChangedProperty() {
240         return this.formChangedProperty;
241     }
242 
243     /**
244      * It creates binding between a main query and new added SUBSELECT. The
245      * binding reports changes of a new added SUBSELECT to a main query.
246      *
247      * @param changedPropertySelect {@link ReadOnlyBooleanWrapper} of SUBSELECT.
248      */
249     public void setCheckingSubSelect(ReadOnlyBooleanWrapper changedPropertySelect) {
250         changedPropertySelect
251                 .addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
252                     if (newValue) {
253                         this.formChangedProperty.set(true);
254                     }
255                 });
256     }
257 
258     /**
259      * It creates binding between a new added SUBSELECT and a main query. The
260      * binding reports a saving of changes to a new added SUBSELECT.
261      *
262      * @param changedPropertySelect {@link ReadOnlyBooleanWrapper} of a main
263      * query.
264      */
265     public void setCheckingMainQueryPart(ReadOnlyBooleanWrapper changedPropertySelect) {
266         changedPropertySelect
267                 .addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
268                     if (!newValue) {
269                         this.formChangedProperty.set(newValue);
270                     }
271                 });
272     }
273 
274     /**
275      * Vyhodnoceni dotazu.
276      *
277      * @param dataAgent propojeni aplikace a uloziste.
278      */
279     @Override
280     public final void evaluate(DataAgent dataAgent) {
281 
282         if (queryTabPane.getEditorTab().isSelected()) {
283             queryTabPane.getSelectionModel().select(queryTabPane.getFormTab());
284             queryTabPane.getSelectionModel().select(queryTabPane.getEditorTab());
285 
286             queryTabPane.getEvaluationTab()
287                     .evaluate(queryTabPane.getQueryTextArea().getText(), dataAgent);
288         } else {
289             queryTabPane.getEvaluationTab()
290                     .evaluate(queryFormPane.getQuery(), dataAgent);
291         }
292     }
293 
294     /**
295      * Zastavi vyhodnoceni, pokud to uloziste podporuje.
296      */
297     @Override
298     public void stopEvaluating() {
299         if (queryTabPane.getEvaluationTab() != null) {
300             queryTabPane.getEvaluationTab().stopEvaluating();
301         }
302     }
303 
304     /**
305      * vrati flag udavajici jestli se dotaz vyhodnocuje.
306      *
307      * @return jestli se dotaz vyhodnocuje.
308      */
309     @Override
310     public ReadOnlyBooleanProperty evaluatingProperty() {
311         return queryTabPane.getEvaluatingProperty().getReadOnlyProperty();
312     }
313 
314     /**
315      * Ulozi dotaz v textove forme do souboru.
316      */
317     @Override
318     public void export() {
319         QueryTabPane.updateFormAndReturnToTab(this);
320 
321         String query = ""; //$NON-NLS-1$
322         try {
323             // Pokus o přeparsování dotazu. V případě úspěchu bude pěkně zformátován.
324             final Query q;
325             q = DataAgent.createQuery(query = queryFormPane.getQuery());
326             query = q.serialize();
327         } catch (final Exception e) {
328             if (!ConfirmationDialog.open(getWindow(), Messages.getString("EXPORT"), Messages.getString(
329                     "QUERY_CONTAINS_ERRORS_EXPORT_ANYWAY"))) {
330                 return;
331             }
332             // V případě neúspěchu uložíme dotaz tak, jak byl vygenerován. Bude vypadat ošklivě, ale když si to uživatel
333             // přeje...
334         }
335 
336         assert (!query.isEmpty());
337 
338         final File file = IO.saveExportedQueryFile(getWindow(), Messages.getString("EXPORT"),
339                 getFileNameWithoutExtension()); //$NON-NLS-1$
340         if (file == null) {
341             return;
342         }
343 
344         final String finalQuery = query;
345         try {
346             ProgressDialog.performTask(getWindow(), new Task<Void>() {
347                 @Override
348                 protected Void call() throws Exception {
349                     updateMessage(Messages.getString("SAVING_IN_PROGRESS")); //$NON-NLS-1$
350                     IO.saveString(file, finalQuery);
351                     return null;
352                 }
353             }, false);
354         } catch (final InterruptedException | ExecutionException excp) {
355             LOG.error("Exception: ", excp);//$NON-NLS-2$
356             ErrorDialog.open(getWindow(), Messages.getString("ERROR"),
357                     Messages.getString("FILE_COULD_NOT_BE_SAVED"),
358                     excp); //$NON-NLS-1$
359         }
360     }
361 
362     /**
363      * @return Název souboru s dotazem bez přípony.
364      */
365     private String getFileNameWithoutExtension() {
366         final File file = fileProperty.get();
367         String name;
368         if (file == null) {
369             name = defaultFileName;
370         } else {
371             name = file.getName();
372         }
373         final int lastDotIndex = name.lastIndexOf('.');
374         if (lastDotIndex == -1) {
375             return name;
376         }
377         return name.substring(0, lastDotIndex);
378     }
379 
380     /**
381      * Ulozi dotaz v .rqf formatu
382      *
383      * @return true (OK) x false
384      */
385     @Override
386     public final boolean save() {
387         final File f = fileProperty.get();
388         if (f == null || f.getName().contains(".txt")) {//$NON-NLS-1$
389             return saveAs();
390         }
391         return saveAs(f);
392     }
393 
394     /**
395      * @return Aktuální okno.
396      */
397     private Window getWindow() {
398         return getTabPane().getScene().getWindow();
399     }
400 
401     /**
402      * Ulozi dotaz v .rqf formatu do zadaneho souboru
403      *
404      * @return true (OK) x false
405      */
406     @Override
407     public final boolean saveAs() {
408         QueryTabPane.updateFormAndReturnToTab(this);
409 
410         final File oldFile = fileProperty.get();
411         final File f = IO.saveQueryFile(getWindow(), Messages.getString("SAVE_QUERY_AS"),
412                 oldFile == null ? new File(defaultFileName) : oldFile); //$NON-NLS-1$
413         return f != null && saveAs(f);
414     }
415 
416     /**
417      * Uloží dotaz do předaného souboru a v případě potřeby nastaví adekvátně
418      * titulek.
419      *
420      * @param file Výstupní soubor.
421      *
422      * @return <code>true</code>, pokud se uložení podařilo.
423      */
424     private boolean saveAs(final File file) {
425         try {
426             ProgressDialog.performTask(getWindow(), new Task<Void>() {
427                 @Override
428                 protected Void call() throws Exception {
429                     updateMessage(Messages.getString("SAVING_IN_PROGRESS")); //$NON-NLS-1$
430                     queryFormPane.save(file);
431                     return null;
432                 }
433             }, false);
434 
435             fileProperty.set(file);
436             formChangedProperty.set(false);
437             editorChangedProperty.set(false);
438 
439             return true;
440         } catch (final InterruptedException | ExecutionException excp) {
441             LOG.error("Exception: ", excp);//$NON-NLS-1$
442             ErrorDialog.open(getWindow(), Messages.getString("ERROR"),
443                     Messages.getString("FILE_COULD_NOT_BE_SAVED"),
444                     excp); //$NON-NLS-1$
445         }
446         return false;
447     }
448 
449     /**
450      * Otevře dotaz uložený v XML souboru.
451      *
452      * @param mainForm Hlavní okno programu.
453      * @param file Vstupní XML soubor.
454      * @param ownerWindow Rodičovské okno vznikající záložky.
455      * @param dataAgent Připojení k úložišti.
456      * @param localStorage Lokální mapování prefixů; Lokální seznam ostatních
457      * pojmů; Lokální seznam datových typů; Lokální seznam funkcí.
458      *
459      * @return Nově vzniklá záložka s otevřeným dotazem.
460      */
461     public final static QueryTab<?> open(final MainForm mainForm, final File file, final Window ownerWindow,
462             final DataAgent dataAgent, final Storage localStorage) {
463 
464         mainForm.setLoading(true);
465 
466         try {
467             return ProgressDialog.performTask(ownerWindow, new Task<QueryTab<?>>() {
468                 @Override
469                 protected QueryTab<?> call() throws Exception {
470                     updateMessage(Messages.getString("OPENING_IN_PROGRESS")); //$NON-NLS-1$
471 
472                     final QueryFormPane<?> pane = Utils.runAndWait(() -> QueryFormPane
473                             .load(mainForm, dataAgent, localStorage, file));
474 
475                     QueryTab<?> tab;
476                     if (pane instanceof SelectQueryFormPane) {
477                         tab = new SelectQueryTab((SelectQueryFormPane) pane);
478                     } else if (pane instanceof AskQueryFormPane) {
479                         tab = new AskQueryTab((AskQueryFormPane) pane);
480                     } else if (pane instanceof ConstructQueryFormPane) {
481                         tab = new ConstructQueryTab((ConstructQueryFormPane) pane);
482                     } else if (pane instanceof DescribeQueryFormPane) {
483                         tab = new DescribeQueryTab((DescribeQueryFormPane) pane);
484                     } else if (pane instanceof DeleteQueryFormPane) {
485                         tab = new DeleteQueryTab((DeleteQueryFormPane) pane);
486                     } else if (pane instanceof InsertQueryFormPane) {
487                         tab = new InsertQueryTab((InsertQueryFormPane) pane);
488                     } else if (pane instanceof DeleteInsertQueryFormPane) {
489                         tab = new DeleteInsertQueryTab((DeleteInsertQueryFormPane) pane);
490                     } else {
491                         return null;
492                     }
493                     tab.fileProperty.set(file);
494 
495                     return tab;
496                 }
497             });
498         } catch (InterruptedException | ExecutionException e) {
499 
500             //LOG.log(Level.SEVERE, "Exception: ", e);
501             ErrorDialog.open(ownerWindow, Messages.getString("ERROR"),
502                     Messages.getString("FILE_COULD_NOT_BE_OPENED"),
503                     e);
504 
505             mainForm.setLoading(false);
506             return null;
507         }
508     }
509 
510     public final QueryFormPane<QueryResultType> getQueryFormPane() {
511         return queryFormPane;
512     }
513 
514     public void setBackgroundColor(Color color) {
515         this.colorPicker.setValue(color);
516     }
517 
518     public Color getBackgroundColor() {
519         return this.colorPicker.getValue();
520     }
521 
522     public ColorPicker getColorPicker() {
523         return this.colorPicker;
524     }
525 
526     public final String getTitle() {
527         return titleProperty.get();
528     }
529 
530     public final ReadOnlyStringProperty titleProperty() {
531         return titleProperty.getReadOnlyProperty();
532     }
533 
534     /**
535      * Nastaví do titulku záložky buďto výchozí název souboru nebo název
536      * souboru, do kterého byl dotaz uložen.
537      */
538     private void updateTitle() {
539         final File file = fileProperty.get();
540         titleProperty.set(file == null ? defaultFileName : file.getName());
541     }
542 
543     /**
544      * Akce pri zavreni zalozky.
545      *
546      * @param event
547      */
548     private void onCloseRequest(final Event event) {
549         if (!requestClose()) {
550             // Zabránění zavření záložky
551             event.consume();
552         }
553     }
554 
555     /**
556      * Vyvola dotaz na ulozeni pri zavreni zalozky.
557      *
558      * @return true (OK) x false
559      */
560     @Override
561     public final boolean requestClose() {
562         if (!formChangedProperty.get()) {
563             return true;
564         }
565         boolean closed = ((QueryTabPane) getQueryTabPane()).setClosed();
566         LOG.debug("Closed QueryTabPane {}, closed={}", ((QueryTabPane) getQueryTabPane()), closed);
567 
568         // Hack: Toto zavře všechna plovoucí okna, jejichž viditelnost je závislá na zaměření nějaké komponenty.
569         getTabPane().requestFocus();
570 
571         final Result result
572                 = YesNoCancelDialog.open(getWindow(),
573                         Messages.getString("CONFIRMATION"),
574                         String.format(Messages.getString("FILE_X_MODIFIED_SAVE"),
575                                 titleProperty.get()));
576         switch (result) {
577             case Cancel:
578                 return false;
579             case Yes:
580                 return save();
581             case No:
582                 return true;
583         }
584 
585         return true;
586     }
587 
588     /**
589      * vrati flag udavajici zmenu v dotazu (form, editor).
590      *
591      * @return flag udavajici zmenu v dotazu (form, editor)
592      */
593     @Override
594     public final ReadOnlyBooleanProperty hasChangesProperty() {
595         return formChangedProperty.getReadOnlyProperty();
596     }
597 
598     /**
599      * flag udavajici zmenu souboru.
600      *
601      * @return flag udavajici zmenu souboru
602      */
603     @Override
604     public ReadOnlyObjectProperty<File> fileProperty() {
605         return fileProperty.getReadOnlyProperty();
606     }
607 
608     /**
609      * Vytvoří a vrátí záložku pro vyhodnocení dotazu.
610      *
611      * @param dataAgent
612      * @return Nová záložka pro vyhodnocení dotazu.
613      */
614     protected abstract EvaluationTab<QueryResultType> createEvaluationTab(final DataAgent dataAgent);
615 
616     /**
617      * @return Výchozí název souboru pro konkrétní typ dotazu.
618      */
619     protected abstract String getDefaultFileName();
620 
621     public QueryTabPane<QueryResultType> getQueryTabPane() {
622         return queryTabPane;
623     }
624 
625     public boolean isEditorChangedProperty() {
626         return editorChangedProperty.get();
627     }
628 
629     public ReadOnlyBooleanWrapper getEditorChangedPropertyProperty() {
630         return editorChangedProperty;
631     }
632 
633     public void setQueryFormPane(QueryFormPane<QueryResultType> queryFormPane) {
634         this.queryFormPane = queryFormPane;
635     }
636 
637     public boolean isQueryValidProperty() {
638         return queryValidProperty.get();
639     }
640 
641     public ReadOnlyBooleanWrapper queryValidPropertyProperty() {
642         return queryValidProperty;
643     }
644 
645     public void setQueryValidProperty(boolean queryValidProperty) {
646         this.queryValidProperty.set(queryValidProperty);
647     }
648 
649 }