-
Notifications
You must be signed in to change notification settings - Fork 104
/
Copy pathTestFile.dat
917 lines (792 loc) · 34.9 KB
/
TestFile.dat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javafx.scene.control;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import com.sun.javafx.scene.control.skin.Utils;
import com.sun.javafx.scene.control.skin.resources.ControlResources;
import javafx.beans.DefaultProperty;
import javafx.beans.InvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.WritableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.css.CssMetaData;
import javafx.css.StyleOrigin;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.css.StyleableStringProperty;
import javafx.event.ActionEvent;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import com.sun.javafx.css.StyleManager;
import com.sun.javafx.css.converters.StringConverter;
/**
* DialogPane should be considered to be the root node displayed within a
* {@link Dialog} instance. In this role, the DialogPane is responsible for the
* placement of {@link #headerProperty() headers}, {@link #graphicProperty() graphics},
* {@link #contentProperty() content}, and {@link #getButtonTypes() buttons}.
* The default implementation of DialogPane (that is, the DialogPane class itself)
* handles the layout via the normal {@link #layoutChildren()} method. This
* method may be overridden by subclasses wishing to handle the layout in an
* alternative fashion).
*
* <p>In addition to the {@link #headerProperty() header} and
* {@link #contentProperty() content} properties, there exists
* {@link #headerTextProperty() header text} and
* {@link #contentTextProperty() content text} properties. The way the *Text
* properties work is that they are a lower precedence compared to the Node
* properties, but they are far more convenient for developers in the common case,
* as it is likely the case that a developer more often than not simply wants to
* set a string value into the header or content areas of the DialogPane.
*
* <p>It is important to understand the implications of setting non-null values
* in the {@link #headerProperty() header} and {@link #headerTextProperty() headerText}
* properties. The key points are as follows:
*
* <ol>
* <li>The {@code header} property takes precedence over the {@code headerText}
* property, so if both are set to non-null values, {@code header} will be
* used and {@code headerText} will be ignored.</li>
* <li>If {@code headerText} is set to a non-null value, and a
* {@link #graphicProperty() graphic} has also been set, the default position
* for the graphic shifts from being located to the left of the content area
* to being to the right of the header text.</li>
* <li>If {@code header} is set to a non-null value, and a
* {@link #graphicProperty() graphic} has also been set, the graphic is
* removed from its default position (to the left of the content area),
* and <strong>is not</strong> placed to the right of the custom header
* node. If the graphic is desired, it should be manually added in to the
* layout of the custom header node manually.</li>
* </ol>
*
* <p>DialogPane operates on the concept of {@link ButtonType}. A ButtonType is
* a descriptor of a single button that should be represented visually in the
* DialogPane. Developers who create a DialogPane therefore must specify the
* button types that they want to display, and this is done via the
* {@link #getButtonTypes()} method, which returns a modifiable
* {@link ObservableList}, which users can add to and remove from as desired.
*
* <p>The {@link ButtonType} class defines a number of pre-defined button types,
* such as {@link ButtonType#OK} and {@link ButtonType#CANCEL}. Many users of the
* JavaFX dialogs API will find that these pre-defined button types meet their
* needs, particularly due to their built-in support for
* {@link ButtonData#isDefaultButton() default} and
* {@link ButtonData#isCancelButton() cancel} buttons, as well as the benefit of
* the strings being translated into all languages which JavaFX is translated to.
* For users that want to define their own {@link ButtonType} (most commonly to
* define a button with custom text), they may do so via the constructors available
* on the {@link ButtonType} class.
*
* <p>Developers will quickly find that the amount of configurability offered
* via the {@link ButtonType} class is minimal. This is intentional, but does not
* mean that developers can not modify the buttons created by the {@link ButtonType}
* that have been specified. To do this, developers simply call the
* {@link #lookupButton(ButtonType)} method with the ButtonType (assuming it has
* already been set in the {@link #getButtonTypes()} list. The returned Node is
* typically of type {@link Button}, but this depends on if the
* {@link #createButton(ButtonType)} method has been overridden.
*
* <p>The DialogPane class offers a few methods that can be overridden by
* subclasses, to more easily enable custom functionality. These methods include
* the following:
*
* <ul>
* <li>{@link #createButton(ButtonType)}
* <li>{@link #createDetailsButton()}
* <li>{@link #createButtonBar()}
* </ul>
*
* <p>These methods are documented, so please take note of the expectations
* placed on any developer who wishes to override these methods with their own
* functionality.
*
* @see Dialog
* @since JavaFX 8u40
*/
@DefaultProperty("buttonTypes")
public class DialogPane extends Pane {
/**************************************************************************
*
* Static fields
*
**************************************************************************/
/**
* Creates a Label node that works well within a Dialog.
* @param text The text to display
*/
static Label createContentLabel(String text) {
Label label = new Label(text);
label.setMaxWidth(Double.MAX_VALUE);
label.setMaxHeight(Double.MAX_VALUE);
label.getStyleClass().add("content");
label.setWrapText(true);
label.setPrefWidth(360);
return label;
}
/**************************************************************************
*
* Private fields
*
**************************************************************************/
private final GridPane headerTextPanel;
private final Label contentLabel;
private final StackPane graphicContainer;
private final Node buttonBar;
private final ObservableList<ButtonType> buttons = FXCollections.observableArrayList();
private final Map<ButtonType, Node> buttonNodes = new WeakHashMap<>();
private Node detailsButton;
// this is not a property - we have a package-scope setDialog method that
// sets this field. It is set by Dialog if the DialogPane is set inside a Dialog.
private Dialog<?> dialog;
/**************************************************************************
*
* Constructors
*
**************************************************************************/
/**
* Creates a new DialogPane instance with a style class of 'dialog-pane'.
*/
public DialogPane() {
getStyleClass().add("dialog-pane");
headerTextPanel = new GridPane();
getChildren().add(headerTextPanel);
graphicContainer = new StackPane();
contentLabel = createContentLabel("");
getChildren().add(contentLabel);
buttonBar = createButtonBar();
if (buttonBar != null) {
getChildren().add(buttonBar);
}
buttons.addListener((ListChangeListener<ButtonType>) c -> {
while (c.next()) {
if (c.wasRemoved()) {
for (ButtonType cmd : c.getRemoved()) {
buttonNodes.remove(cmd);
}
}
if (c.wasAdded()) {
for (ButtonType cmd : c.getAddedSubList()) {
if (! buttonNodes.containsKey(cmd)) {
buttonNodes.put(cmd, createButton(cmd));
}
}
}
}
});
}
/**************************************************************************
*
* Properties
*
**************************************************************************/
// --- graphic
private final ObjectProperty<Node> graphicProperty = new StyleableObjectProperty<Node>() {
// The graphic is styleable by css, but it is the
// imageUrlProperty that handles the style value.
@Override public CssMetaData getCssMetaData() {
return StyleableProperties.GRAPHIC;
}
@Override public Object getBean() {
return DialogPane.this;
}
@Override public String getName() {
return "graphic";
}
WeakReference<Node> graphicRef = new WeakReference<>(null);
protected void invalidated() {
Node oldGraphic = graphicRef.get();
if (oldGraphic != null) {
getChildren().remove(oldGraphic);
}
Node newGraphic = getGraphic();
graphicRef = new WeakReference<>(newGraphic);
updateHeaderArea();
}
};
/**
* The dialog graphic, presented either in the header, if one is showing, or
* to the left of the {@link #contentProperty() content}.
*
* @return An ObjectProperty wrapping the current graphic.
*/
public final ObjectProperty<Node> graphicProperty() {
return graphicProperty;
}
public final Node getGraphic() {
return graphicProperty.get();
}
/**
* Sets the dialog graphic, which will be displayed either in the header, if
* one is showing, or to the left of the {@link #contentProperty() content}.
*
* @param graphic
* The new dialog graphic, or null if no graphic should be shown.
*/
public final void setGraphic(Node graphic) {
this.graphicProperty.set(graphic);
}
// --- imageUrl (this is NOT public API, except via CSS)
// Note that this code is a copy/paste from Labeled
private StyleableStringProperty imageUrl = null;
/**
* The imageUrl property is set from CSS and then the graphic property is
* set from the invalidated method. This ensures that the same image isn't
* reloaded.
*/
private StyleableStringProperty imageUrlProperty() {
if (imageUrl == null) {
imageUrl = new StyleableStringProperty() {
//
// If imageUrlProperty is invalidated, this is the origin of the style that
// triggered the invalidation. This is used in the invalidated() method where the
// value of super.getStyleOrigin() is not valid until after the call to set(v) returns,
// by which time invalidated will have been called.
// This value is initialized to USER in case someone calls set on the imageUrlProperty, which
// is possible:
// CssMetaData metaData = ((StyleableProperty)dialogPane.graphicProperty()).getCssMetaData();
// StyleableProperty prop = metaData.getStyleableProperty(dialogPane);
// prop.set(someUrl);
//
// TODO: Note that prop != dialogPane, which violates the contract between StyleableProperty and CssMetaData.
//
StyleOrigin origin = StyleOrigin.USER;
@Override
public void applyStyle(StyleOrigin origin, String v) {
this.origin = origin;
// Don't want applyStyle to throw an exception which would leave this.origin set to the wrong value
if (graphicProperty == null || graphicProperty.isBound() == false) super.applyStyle(origin, v);
// Origin is only valid for this invocation of applyStyle, so reset it to USER in case someone calls set.
this.origin = StyleOrigin.USER;
}
@Override
protected void invalidated() {
// need to call super.get() here since get() is overridden to return the graphicProperty's value
final String url = super.get();
if (url == null) {
((StyleableProperty<Node>)(WritableValue<Node>)graphicProperty()).applyStyle(origin, null);
} else {
// RT-34466 - if graphic's url is the same as this property's value, then don't overwrite.
final Node graphicNode = DialogPane.this.getGraphic();
if (graphicNode instanceof ImageView) {
final ImageView imageView = (ImageView)graphicNode;
final Image image = imageView.getImage();
if (image != null) {
final String imageViewUrl = image.impl_getUrl();
if (url.equals(imageViewUrl)) return;
}
}
final Image img = StyleManager.getInstance().getCachedImage(url);
if (img != null) {
//
// Note that it is tempting to try to re-use existing ImageView simply by setting
// the image on the current ImageView, if there is one. This would effectively change
// the image, but not the ImageView which means that no graphicProperty listeners would
// be notified. This is probably not what we want.
//
//
// Have to call applyStyle on graphicProperty so that the graphicProperty's
// origin matches the imageUrlProperty's origin.
//
((StyleableProperty<Node>)(WritableValue<Node>)graphicProperty()).applyStyle(origin, new ImageView(img));
}
}
}
@Override
public String get() {
//
// The value of the imageUrlProperty is that of the graphicProperty.
// Return the value in a way that doesn't expand the graphicProperty.
//
final Node graphic = getGraphic();
if (graphic instanceof ImageView) {
final Image image = ((ImageView)graphic).getImage();
if (image != null) {
return image.impl_getUrl();
}
}
return null;
}
@Override
public StyleOrigin getStyleOrigin() {
//
// The origin of the imageUrlProperty is that of the graphicProperty.
// Return the origin in a way that doesn't expand the graphicProperty.
//
return graphicProperty != null ? ((StyleableProperty<Node>)(WritableValue<Node>)graphicProperty).getStyleOrigin() : null;
}
@Override
public Object getBean() {
return DialogPane.this;
}
@Override
public String getName() {
return "imageUrl";
}
@Override
public CssMetaData<DialogPane,String> getCssMetaData() {
return StyleableProperties.GRAPHIC;
}
};
}
return imageUrl;
}
// --- header
private final ObjectProperty<Node> header = new SimpleObjectProperty<Node>(null) {
WeakReference<Node> headerRef = new WeakReference<>(null);
@Override protected void invalidated() {
Node oldHeader = headerRef.get();
if (oldHeader != null) {
getChildren().remove(oldHeader);
}
Node newHeader = getHeader();
headerRef = new WeakReference<>(newHeader);
updateHeaderArea();
}
};
/**
* Node which acts as the dialog pane header.
*
* @return the header of the dialog pane.
*/
public final Node getHeader() {
return header.get();
}
/**
* Assigns the dialog pane header. Any Node can be used.
*
* @param header The new header of the DialogPane.
*/
public final void setHeader(Node header) {
this.header.setValue(header);
}
/**
* Property representing the header area of the dialog pane. Note that if this
* header is set to a non-null value, that it will take up the entire top
* area of the DialogPane. It will also result in the DialogPane switching its
* layout to the 'header' layout - as outlined in the {@link DialogPane} class
* javadoc.
*/
public final ObjectProperty<Node> headerProperty() {
return header;
}
// --- header text
private final StringProperty headerText = new SimpleStringProperty(this, "headerText") {
@Override protected void invalidated() {
updateHeaderArea();
requestLayout();
}
};
/**
* Sets the string to show in the dialog header area. Note that the header text
* is lower precedence than the {@link #headerProperty() header node}, meaning
* that if both the header node and the headerText properties are set, the
* header text will not be displayed in a default DialogPane instance.
*
* <p>When headerText is set to a non-null value, this will result in the
* DialogPane switching its layout to the 'header' layout - as outlined in
* the {@link DialogPane} class javadoc.</p>
*/
public final void setHeaderText(String headerText) {
this.headerText.set(headerText);
}
/**
* Returns the currently-set header text for this DialogPane.
*/
public final String getHeaderText() {
return headerText.get();
}
/**
* A property representing the header text for the dialog pane. The header text
* is lower precedence than the {@link #headerProperty() header node}, meaning
* that if both the header node and the headerText properties are set, the
* header text will not be displayed in a default DialogPane instance.
*
* <p>When headerText is set to a non-null value, this will result in the
* DialogPane switching its layout to the 'header' layout - as outlined in
* the {@link DialogPane} class javadoc.</p>
*/
public final StringProperty headerTextProperty() {
return headerText;
}
// --- content
private final ObjectProperty<Node> content = new SimpleObjectProperty<Node>(null) {
WeakReference<Node> contentRef = new WeakReference<>(null);
@Override protected void invalidated() {
Node oldContent = contentRef.get();
if (oldContent != null) {
getChildren().remove(oldContent);
}
Node newContent = getContent();
contentRef = new WeakReference<>(newContent);
updateContentArea();
}
};
/**
* Returns the dialog content as a Node (even if it was set as a String
* using {@link #setContentText(String)} - this was simply transformed into a
* {@link Node} (most probably a {@link Label}).
*
* @return dialog's content
*/
public final Node getContent() {
return content.get();
}
/**
* Assign dialog content. Any Node can be used
*
* @param content
* dialog's content
*/
public final void setContent(Node content) {
this.content.setValue(content);
}
/**
* Property representing the content area of the dialog.
*/
public final ObjectProperty<Node> contentProperty() {
return content;
}
// --- content text
private final StringProperty contentText = new SimpleStringProperty(this, "contentText") {
@Override protected void invalidated() {
updateContentArea();
requestLayout();
}
};
/**
* Sets the string to show in the dialog content area. Note that the content text
* is lower precedence than the {@link #contentProperty() content node}, meaning
* that if both the content node and the contentText properties are set, the
* content text will not be displayed in a default DialogPane instance.
*/
public final void setContentText(String contentText) {
this.contentText.set(contentText);
}
/**
* Returns the currently-set content text for this DialogPane.
*/
public final String getContentText() {
return contentText.get();
}
/**
* A property representing the content text for the dialog pane. The content text
* is lower precedence than the {@link #contentProperty() content node}, meaning
* that if both the content node and the contentText properties are set, the
* content text will not be displayed in a default DialogPane instance.
*/
public final StringProperty contentTextProperty() {
return contentText;
}
// --- expandable content
private final ObjectProperty<Node> expandableContentProperty = new SimpleObjectProperty<Node>(null) {
WeakReference<Node> expandableContentRef = new WeakReference<>(null);
@Override protected void invalidated() {
Node oldExpandableContent = expandableContentRef.get();
if (oldExpandableContent != null) {
getChildren().remove(oldExpandableContent);
}
Node newExpandableContent = getExpandableContent();
expandableContentRef = new WeakReference<Node>(newExpandableContent);
if (newExpandableContent != null) {
newExpandableContent.setVisible(isExpanded());
newExpandableContent.setManaged(isExpanded());
if (!newExpandableContent.getStyleClass().contains("expandable-content")) { //$NON-NLS-1$
newExpandableContent.getStyleClass().add("expandable-content"); //$NON-NLS-1$
}
getChildren().add(newExpandableContent);
}
}
};
/**
* A property that represents the dialog expandable content area. Any Node
* can be placed in this area, but it will only be shown when the user
* clicks the 'Show Details' expandable button. This button will be added
* automatically when the expandable content property is non-null.
*/
public final ObjectProperty<Node> expandableContentProperty() {
return expandableContentProperty;
}
/**
* Returns the dialog expandable content node, if one is set, or null
* otherwise.
*/
public final Node getExpandableContent() {
return expandableContentProperty.get();
}
/**
* Sets the dialog expandable content node, or null if no expandable content
* needs to be shown.
*/
public final void setExpandableContent(Node content) {
this.expandableContentProperty.set(content);
}
// --- expanded
private final BooleanProperty expandedProperty = new SimpleBooleanProperty(this, "expanded", false) {
protected void invalidated() {
final Node expandableContent = getExpandableContent();
if (expandableContent != null) {
expandableContent.setVisible(isExpanded());
}
requestLayout();
}
};
/**
* Represents whether the dialogPane is expanded.
*/
public final BooleanProperty expandedProperty() {
return expandedProperty;
}
/**
* Returns whether or not the dialogPane is expanded.
*
* @return true if dialogPane is expanded.
*/
public final boolean isExpanded() {
return expandedProperty().get();
}
/**
* Sets whether the dialogPane is expanded. This only makes sense when there
* is {@link #expandableContentProperty() expandable content} to show.
*
* @param value true if dialogPane should be expanded.
*/
public final void setExpanded(boolean value) {
expandedProperty().set(value);
}
/**************************************************************************
*
* Public API
*
**************************************************************************/
// --- button types
/**
* Observable list of button types used for the dialog button bar area
* (created via the {@link #createButtonBar()} method). Modifying the contents
* of this list will immediately change the buttons displayed to the user
* within the dialog pane.
*
* @return The {@link ObservableList} of {@link ButtonType button types}
* available to the user.
*/
public final ObservableList<ButtonType> getButtonTypes() {
return buttons;
}
/**
* This method provides a way in which developers may retrieve the actual
* Node for a given {@link ButtonType} (assuming it is part of the
* {@link #getButtonTypes() button types} list).
*
* @param buttonType The {@link ButtonType} for which a Node representation is requested.
* @return The Node used to represent the button type, as created by
* {@link #createButton(ButtonType)}, and only if the button type
* is part of the {@link #getButtonTypes() button types} list, otherwise null.
*/
public final Node lookupButton(ButtonType buttonType) {
return buttonNodes.get(buttonType);
}
/**
* This method can be overridden by subclasses to provide the button bar.
* Note that by overriding this method, the developer must take on multiple
* responsibilities:
*
* <ol>
* <li>The developer must immediately iterate through all
* {@link #getButtonTypes() button types} and call
* {@link #createButton(ButtonType)} for each of them in turn.
* <li>The developer must add a listener to the
* {@link #getButtonTypes() button types} list, and when this list changes
* update the button bar as appropriate.
* <li>Similarly, the developer must watch for changes to the
* {@link #expandableContentProperty() expandable content} property,
* adding and removing the details button (created via
* {@link #createDetailsButton()} method).
* </ol>
*
* <p>The default implementation of this method creates and returns a new
* {@link ButtonBar} instance.
*/
protected Node createButtonBar() {
ButtonBar buttonBar = new ButtonBar();
buttonBar.setMaxWidth(Double.MAX_VALUE);
updateButtons(buttonBar);
getButtonTypes().addListener((ListChangeListener<? super ButtonType>) c -> updateButtons(buttonBar));
expandableContentProperty().addListener(o -> updateButtons(buttonBar));
return buttonBar;
}
/**
* This method can be overridden by subclasses to create a custom button that
* will subsequently inserted into the DialogPane button area (created via
* the {@link #createButtonBar()} method, but mostly commonly it is an instance
* of {@link ButtonBar}.
*
* @param buttonType The {@link ButtonType} to create a button from.
* @return A JavaFX {@link Node} that represents the given {@link ButtonType},
* most commonly an instance of {@link Button}.
*/
protected Node createButton(ButtonType buttonType) {
final Button button = new Button(buttonType.getText());
final ButtonData buttonData = buttonType.getButtonData();
ButtonBar.setButtonData(button, buttonData);
button.setDefaultButton(buttonType != null && buttonData.isDefaultButton());
button.setCancelButton(buttonType != null && buttonData.isCancelButton());
button.addEventHandler(ActionEvent.ACTION, ae -> {
if (ae.isConsumed()) return;
if (dialog != null) {
dialog.impl_setResultAndClose(buttonType, true);
}
});
return button;
}
/**
* This method can be overridden by subclasses to create a custom details button.
*
* <p>To override this method you must do two things:
* <ol>
* <li>The button will need to have its own code set to handle mouse / keyboard
* interaction and to toggle the state of the
* {@link #expandedProperty() expanded} property.
* <li>If your button changes its visuals based on whether the dialog pane
* is expanded or collapsed, you should add a listener to the
* {@link #expandedProperty() expanded} property, so that you may update
* the button visuals.
* </ol>
*/
protected Node createDetailsButton() {
final Hyperlink detailsButton = new Hyperlink();
final String moreText = ControlResources.getString("Dialog.detail.button.more"); //$NON-NLS-1$
final String lessText = ControlResources.getString("Dialog.detail.button.less"); //$NON-NLS-1$
InvalidationListener expandedListener = o -> {
final boolean isExpanded = isExpanded();
detailsButton.setText(isExpanded ? lessText : moreText);
detailsButton.getStyleClass().setAll("details-button", (isExpanded ? "less" : "more")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
};
// we call the listener immediately to ensure the state is correct at start up
expandedListener.invalidated(null);
expandedProperty().addListener(expandedListener);
detailsButton.setOnAction(ae -> setExpanded(!isExpanded()));
return detailsButton;
}
private double oldHeight = -1;
/** {@inheritDoc} */
@Override protected void layoutChildren() {
final boolean hasHeader = hasHeader();
// snapped insets code commented out to resolve RT-39738
final double w = Math.max(minWidth(-1), getWidth());// - (snappedLeftInset() + snappedRightInset());
final double minHeight = minHeight(w);
final double prefHeight = prefHeight(w);
final double maxHeight = maxHeight(w);
final double currentHeight = getHeight();
final double dialogHeight = dialog == null ? 0 : dialog.dialog.getSceneHeight();
double h;
if (prefHeight > currentHeight && prefHeight > minHeight && (prefHeight <= dialogHeight || dialogHeight == 0)) {
h = prefHeight;
resize(w, h);
} else {
boolean isDialogGrowing = currentHeight > oldHeight;
if (isDialogGrowing) {
double _h = currentHeight < prefHeight ?
Math.min(prefHeight, currentHeight) : Math.max(prefHeight, dialogHeight);
h = Utils.boundedSize(_h, minHeight, maxHeight);
} else {
h = Utils.boundedSize(Math.min(currentHeight, dialogHeight), minHeight, maxHeight);
}
resize(w, h);
}
h -= (snappedTopInset() + snappedBottomInset());
oldHeight = h;
final double leftPadding = snappedLeftInset();
final double topPadding = snappedTopInset();
final double rightPadding = snappedRightInset();
final double bottomPadding = snappedBottomInset();
// create the nodes up front so we can work out sizing
final Node header = getActualHeader();
final Node content = getActualContent();
final Node graphic = getActualGraphic();
final Node expandableContent = getExpandableContent();
final double graphicPrefWidth = hasHeader || graphic == null ? 0 : graphic.prefWidth(-1);
final double headerPrefHeight = hasHeader ? header.prefHeight(w) : 0;
final double buttonBarPrefHeight = buttonBar == null ? 0 : buttonBar.prefHeight(w);
final double graphicPrefHeight = hasHeader || graphic == null ? 0 : graphic.prefHeight(-1);
final double expandableContentPrefHeight;
final double contentAreaHeight;
final double contentAndGraphicHeight;
final double availableContentWidth = w - graphicPrefWidth - leftPadding - rightPadding;
if (isExpanded()) {
// precedence goes to content and then expandable content
contentAreaHeight = isExpanded() ? content.prefHeight(availableContentWidth) : 0;
contentAndGraphicHeight = hasHeader ? contentAreaHeight : Math.max(graphicPrefHeight, contentAreaHeight);
expandableContentPrefHeight = h - (headerPrefHeight + contentAndGraphicHeight + buttonBarPrefHeight);
} else {
// content gets the lowest precedence
expandableContentPrefHeight = isExpanded() ? expandableContent.prefHeight(w) : 0;
contentAreaHeight = h - (headerPrefHeight + expandableContentPrefHeight + buttonBarPrefHeight);
contentAndGraphicHeight = hasHeader ? contentAreaHeight : Math.max(graphicPrefHeight, contentAreaHeight);
}
double x = leftPadding;
double y = topPadding;
if (! hasHeader) {
if (graphic != null) {
graphic.resizeRelocate(x, y, graphicPrefWidth, graphicPrefHeight);
x += graphicPrefWidth;
}
} else {
header.resizeRelocate(x, y, w - (leftPadding + rightPadding), headerPrefHeight);
y += headerPrefHeight;
}
content.resizeRelocate(x, y, availableContentWidth, contentAreaHeight);
y += hasHeader ? contentAreaHeight : contentAndGraphicHeight;
if (expandableContent != null) {
expandableContent.resizeRelocate(leftPadding, y, w - rightPadding, expandableContentPrefHeight);
y += expandableContentPrefHeight;
}
if (buttonBar != null) {
buttonBar.resizeRelocate(leftPadding,
y,
w - (leftPadding + rightPadding),
buttonBarPrefHeight);
}
}
/** {@inheritDoc}