Про offset в методу get у ByteBuffer

Что делает off­set в методе get() у Byte­Buffer?

Даже help не вновит ясности. Вроде как смещение — но при попытке сместить и прочитать начинают сыпаться ошибки переполнения буфера.

Справка написана не особо понятно, но, к счастью, есть исходник реализации. И, заглянув в него, мы узнаём, что это.… индекс, с которого надо начинать запись в массив!!

...
int end = offset + buffer;
for(int i = offset; i < end; i++)
  dst[i] = get();
...

Конечно, с точки зрения проектирования это настоящий кошмар. Потому что:

  1. Даже со справкой не очень понятно, как использовать.
  2. Реализована совершенно редкая фича вместо фичи нужной (“прочитать начиная со смещения” смотрелось бы тут куда уместней).

java: javacTask: source release 8 requires target release 1.8 в IntelliJ IDEA

Эта ошибка появляется внезапно и сразу же доводит до бешенства. Запускаешь компиляцию. а Idea в ответ:

java: javacTask: source release 8 requires target release 1.8 в IntelliJ IDEA

Чтобы поправить, отправляемся в .idea/compiler.xml, и выставляем в разделе byte­code­Tar­getLev­el для этого модуля target=1.8

Разница между isInstance() и instanceof в Java

В чём разница между instance­of из class.isInstance(item) в Java? На самом деле разницы почти нет, просто instance­of требует, чтобы класс, с которым сравнивают, был известен ещё на этапе компиляции.

А вот isIn­stance можно смело вызывать и от экземпляра: item1.getClass().isInstance(item2)

JavaFX Color в CSS

Внешний вид компонент JavaFX настраивается в CSS. Соответственно, должен быть какой-то конвертер стандартного javafx.scene.paint.Color в CSS-friend­ly формат.

Возможно, он и правда есть — но я его не нашёл. К тому же, внутри класс Col­or устроен немного по-другому: насыщенность цвета в свойствах getRed, get­Blue и get­Green задаётся dou­ble-числом от 0.0 до 1.0.

И вот что получилось:

public static String colorToJson(Color color){
   return String.format("#%02X%02X%02X",
                 (int)(color.getRed() * 0xFF),
                 (int)(color.getBlue() * 0xFF),
                 (int)(color.getGreen() * 0xFF));
}

На выходе из COLOR.Red получается правильный #FF0000.

Выглядит симпатично и немного напоминает чистый C :).Правда, прозрачность потерялась.

А если заглянуть в стандарт CSS, то выясняется: уже давно можно задавать цвет как rgba(0, 0, 255, 1.0).

А значит, наш код можно переписать вот так (for­mat­ter нужен, чтобы вместо точки в десятичной дроби не завелась запятая):

public static String colorToJson(Color color){
  StringBuilder sb = new StringBuilder();
  Formatter formatter = new Formatter(sb, Locale.US);
  formatter.format("rgba(%.0f, %.0f, %.0f, %.1f)", color.getRed() * 0xFF, color.getBlue() * 0xFF, color.getGreen() * 0xFF, color.getOpacity());
  return sb.toString();
}

 

Callable в Java, который возвращает void

К сожалению, в Java пока не появилась аналога шаблонных Func<> и Action<> из C#. Приходится обходиться Callable, в котором указывать тип возвращаемого значения — обязательно.

А если нужно просто выполнить функцию, которая возвращает void, то пишут Callable<Void> и (для асинхронных) Future<Void>. В учебниках обычно не упоминают, что для void есть класс-обёртка.

Annotation type expected для @Entity или @Test

Иногда в ответ на @Entity у Hiber­nate или @Test у JUnit компилятор Java заявляет: Anno­ta­tion type expect­ed.

Это означает, что выставили не тот Import

У @Entity — вместо org.hibernate.metamodel.domain.Entry надо  java.persistance.*
У @Test — вместо junit.framework.Test надо org.junit.Test

Всплывающие подсказки в JavaFX

Накопилась куча материала (кое-что годится на полноценную статью, а что-то просто может забиться).

Буду понемногу выкладывать, чтобы не хранить на бумаге.
Итак, всплывающие подсказки в JavaFx.
В давние времена, когда приложения оставались десктопными, окошечки под Win­dows было удобней всего рисовать с помощью библиотеки VCL, той самой, которая жила внутри Del­phi. Конечно, кто-то писал на MFC в тогда ещё доисторической Visu­al Stu­dio, кто-то был вынужден поддерживать проекты на OWL, а отдельные маньяки — даже на голом WinAPI, но их мы трогать не будем. И ещё не будем трогать тех, кто писал на VCL для Unix-ов (помните Kylix? А он был!)
Итак, внутри Del­phi жил VCL. Если вы писали на C++Builder, он там тоже был. И дополнял C++ своей уникальной String, а также списками, которые начинались с 1.
И почти у всех визуальных компонент были свойства ShowHint и Hint. В Hint писался текст подсказки, а ShowHint мог её отключить. А более прокачанные даже знали, что можно сделать расширенный вариант подсказки. Если написать “Нажми меня|Кнопка просит, чтобы вы её нажали”, то левая часть всплывёт, а правая будет передана сообщением, которое можно перехватить и вывести, например, в Sta­tus Bar.
Но пришла новая эра, VCL ушёл в историю, а у нас теперь, к примеру, кроссплатформенный JavaFX. И никакого свойства Hint у его компонентов нет. И sta­tus bar-а среди компонентов тоже нет. Такие дела.
Что же делать?
Для подсказок всплывающих есть невидимый для SceneB­uilder компонент Tooltip, который отвечает за всплывающие подсказки. Если его создать, а потом привязать через set­Tooltip, то при наведении курсора мы и правда увидим подсказку (на чёрном фоне, но так надо).
Но при этом свойство Tooltip (и соответствующие методы) есть только у наследников класса javafx.scene.control.Control. А все панели и прочие области наследуются от javafx.scene.layout.Region. И никаких подсказок на них всплывать не может.
К тому же, разумеется, мы по прежнему не приблизились к Sta­tus­Bar-у. Придётся писать всё своими руками.
Сначала класс привязки, который хранит ссылку на элемент управления и текст, который к нему относится:
ATooltipHintItem.java
import javafx.scene.Node;

/**
* Базовый класс для элемента подсказки. Связывает вместе компонент и
* текст подсказки.
*
* Cre­at­ed by a.teut on 18.03.15.
*/
pub­lic abstract class ATooltipHintItem<N> {
pri­vate N attachedNode;
pro­tect­ed void setAttachedNode(N node) {
attachedNode = node;
}
pub­lic N getAt­tachedNode() {
return attachedNode;
}

pri­vate String sta­tus­BarHint;
pro­tect­ed void setStatusBarHint(String hint){
sta­tus­BarHint = hint;
}
pub­lic String get­Sta­tus­BarHint(){
return sta­tus­BarHint;
}

pri­vate ITooltipHint­Con­troller tooltipHint­Con­troller;
pub­lic ITooltipHint­Con­troller get­TooltipHint­Con­troller() {
return tooltipHint­Con­troller;
}

pub­lic void show­Sta­tus­BarHint(){
tooltipHintController.setStatusBarText(statusBarHint);
}

pub­lic ATooltipHintItem(N attachedNode, ITooltipHint­Con­troller tooltipHint­Con­troller, String sta­tus­BarHint) {
this.attachedNode = attachedNode;
this.tooltipHintController = tooltipHint­Con­troller;

if(statusBarHint != null && sta­tus­BarHint != “”){
init­Sta­tus­Bar();
this.setStatusBarHint(statusBarHint);
}
}

pri­vate void init­Sta­tus­Bar() {
getAttachedNode().setOnMouseEntered(observableValue -> {
this.showStatusBarHint();
});

getAttachedNode().setOnMouseExited(observableValue -> {
getTooltipHintController().setDefaultStatusBarText();
});
}
}

Теперь реализация для Region:

TooltipHintRegionItem.java

import javafx.scene.layout.Region;

/**
* Реализация для компонент, унаследованных от Region. Всплывающие подсказки
* прицепить нельзя.
*
* Cre­at­ed by a.teut on 18.03.15.
*/
pub­lic final class TooltipHin­tRe­gion­Item extends ATooltipHintItem<Region>{
pub­lic TooltipHintRegionItem(Region attachedNode, ITooltipHint­Con­troller tooltipHint­Con­troller, String sta­tus­BarHint) {
super(attachedNode, tooltipHint­Con­troller, sta­tus­BarHint);
}
}

А у Con­trol могут быть Tooltip-ы:

TooltipHintRegionItem.java

import javafx.scene.control.Control;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

/**
* Реализация для компонент, унаследованных от Con­trol. Можно делать и всплывающие подсказки.
*
* Cre­at­ed by a.teut on 18.03.15.
*/

/**
* Реализация для компонент, унаследованных от Con­trol. Можно делать и всплывающие подсказки.
*
* Cre­at­ed by a.teut on 18.03.15.
*/
pub­lic final class TooltipHint­Con­tro­lItem extends ATooltipHintItem<Control> {
pri­vate Tooltip tooltip;
pub­lic Tooltip get­Tooltip() {
return tooltip;
}

pri­vate String tooltipHint;
pub­lic TooltipHint­Con­tro­lItem setTooltipHint(String hint){
tooltipHint = hint;
if(tooltip == null) {
init­Tooltip();
}
tooltip.setText(hint);
return this;
}
pub­lic String get­TooltipHint(){
return tooltipHint;
}

pri­vate Image tooltip­Im­age;
pub­lic TooltipHint­Con­tro­lItem setTooltipImage(Image image){
tooltip­Im­age = image;
tooltip.setGraphic((image != null) ? new ImageView(image) : null);
return this;
}
pub­lic Image get­Tooltip­Im­age(){
return tooltip­Im­age;
}

pub­lic TooltipHintControlItem(Control attachedNode, ITooltipHint­Con­troller tooltipHint­Con­troller, String sta­tus­BarHint, String tooltipHint, Image image­Hint) {
super(attachedNode, tooltipHint­Con­troller, sta­tus­BarHint);
if(tooltipHint != null && tooltipHint != “”){
init­Tooltip();
}
setTooltipHint(tooltipHint);

if(imageHint == null) {
setTooltipImage(imageHint);
}
}

pub­lic TooltipHintControlItem(Control attachedNode, ITooltipHint­Con­troller tooltipHint­Con­troller, String sta­tus­BarHint, String tooltipHint) {
this(attachedNode, tooltipHint­Con­troller, sta­tus­BarHint, tooltipHint, null);
}

pub­lic TooltipHintControlItem(Control attachedNode, ITooltipHint­Con­troller tooltipHint­Con­troller, String sta­tus­BarHint) {
this(attachedNode, tooltipHint­Con­troller, sta­tus­BarHint, null, null);
}

pri­vate void init­Tooltip() {
tooltip = new Tooltip();
getAttachedNode().setTooltip(tooltip);
}
pub­lic Image get­Tooltip­Im­age(){
return tooltip­Im­age;
}

pub­lic TooltipHintControlItem(Control attachedNode, ITooltipHint­Con­troller tooltipHint­Con­troller, String sta­tus­BarHint, String tooltipHint) {
super(attachedNode, tooltipHint­Con­troller, sta­tus­BarHint);
if(tooltipHint != null && tooltipHint != “”){
init­Tooltip();
}
setTooltipHint(tooltipHint);
}

pub­lic TooltipHintControlItem(Control attachedNode, ITooltipHint­Con­troller tooltipHint­Con­troller, String sta­tus­BarHint) {
this(attachedNode, tooltipHint­Con­troller, sta­tus­BarHint, null);
}

pri­vate void init­Tooltip() {
tooltip = new Tooltip();
getAttachedNode().setTooltip(tooltip);
}
}

Теперь распишем интерфейс, который их вызывает:
ITooltipHintController.java

/**
 * Функции контроллера, которые вызывают сами элементы привязок.
 *
 * Created by a.teut on 18.03.15.
 */
public interface ITooltipHintController {
    void setStatusBarText(String text);
    String getStatusBarText();
    void setDefaultStatusBarText();
}

И, наконец, контроллер:

TooltipHintController.java

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Labeled;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;

import java.util.ArrayList;
import java.util.Iterator;

/**
* Контроллер подсказок. Хранит привязки подсказок к компонентом и, если нужно,
* всплывающие подсказки.
*
* Как правило, один на всё приложение.
*
* Cre­at­ed by a.teut on 18.03.15.
*/
pub­lic final class TooltipHint­Con­troller imple­ments ITooltipHint­Con­troller {
pri­vate final String Default­Sta­tus­Bar­Text = “”;
pri­vate final Labeled sta­tus­Bar­Con­trol;
pri­vate final ObservableList<ATooltipHintItem> tooltipHin­tItems;

pri­vate boolean isSta­tus­Bar­Locked = false;
pub­lic boolean getIs­Sta­tus­Bar­Locked() {
return isSta­tus­Bar­Locked;
}
pub­lic void setIsStatusBarLocked(boolean isSta­tus­Bar­Locked) {
this.isStatusBarLocked = isSta­tus­Bar­Locked;
}

pub­lic Labeled get­Sta­tus­Bar­Con­trol() {
return this.statusBarControl;
}

pub­lic void setStatusBarTextForce(String text) {
statusBarControl.setText(text);
}
@Override
pub­lic void setStatusBarText(String text) {
if(!isStatusBarLocked){
setStatusBarTextForce(text);
}
}
@Override
pub­lic String get­Sta­tus­Bar­Text() {
return statusBarControl.getText();
}
@Override
pub­lic void set­De­fault­Sta­tus­Bar­Text(){
setStatusBarTextForce(DefaultStatusBarText);
}

//тут есть дублирование кода, но пока ничего серьёзного
pub­lic void addTooltipHint(Region region, String sta­tus­BarHint){
// Tooltip нас не интересует — у регионов в JavaFX не бывает всплывающих подсказок
ATooltipHin­tItem tooltipHin­tItem = findTooltipHint(region);
if(tooltipHintItem == null) {
tooltipHin­tItem = new TooltipHintRegionItem(region, this, sta­tus­BarHint);
tooltipHintItems.add(tooltipHintItem);
} else {
TooltipHint­Con­tro­lItem tooltipHint­Con­tro­lItem = (TooltipHintControlItem)tooltipHintItem;
if(statusBarHint != null && tooltipHintControlItem.getStatusBarHint() == null)
tooltipHintControlItem.setStatusBarHint(statusBarHint);
}
}

pub­lic void addTooltipHint(Control con­trol, String sta­tus­BarHint){
addTooltipHint(control, sta­tus­BarHint, null, null);
}

pub­lic void addTooltipHint(Control con­trol, String sta­tus­BarHint, String tooltipHint){
addTooltipHint(control, sta­tus­BarHint, tooltipHint, null);
}

pub­lic void addTooltipHint(Control con­trol, String sta­tus­BarHint, String tooltipHint, Image image){
ATooltipHin­tItem tooltipHin­tItem = findTooltipHint(control);
if(tooltipHintItem == null) {
tooltipHin­tItem = new TooltipHintControlItem(control, this, sta­tus­BarHint, tooltipHint, image);
tooltipHintItems.add(tooltipHintItem);
} else {
TooltipHint­Con­tro­lItem tooltipHint­Con­tro­lItem = (TooltipHintControlItem)tooltipHintItem;
if(statusBarHint != null && tooltipHintControlItem.getStatusBarHint() == null)
tooltipHintControlItem.setStatusBarHint(statusBarHint);

if(tooltipHint != null && tooltipHintControlItem.getTooltipHint() == null)
tooltipHintControlItem.setTooltipHint(tooltipHint);

if(image != null && tooltipHintControlItem.getTooltipImage() == null)
tooltipHintControlItem.setTooltipImage(image);
}
}

pub­lic void removeTooltipHint(Node con­trol){
ATooltipHin­tItem tooltipHin­tItem = null;
Iterator<ATooltipHintItem> iter­a­tor­TooltipHin­tItems = tooltipHintItems.iterator();
while(iteratorTooltipHintItems.hasNext()){
tooltipHin­tItem = iteratorTooltipHintItems.next();
if(tooltipHintItem.getAttachedNode() == con­trol){
tooltipHintItems.remove(tooltipHintItem);
break;
}
}
}

pub­lic ATooltipHin­tItem findTooltipHint(Node con­trol){
for(ATooltipHintItem tooltipHin­tItem : tooltipHin­tItems)
if(tooltipHintItem.getAttachedNode() == con­trol)
return tooltipHin­tItem;
return null;
}

/**
* При создании нужно привязать контроллер к компоненту, который будет
* показывать подсказки.
*
* @param sta­tus­Bar­Con­trol Компонент для подсказок
*/

pub­lic TooltipHintController(Labeled sta­tus­Bar­Con­trol){
if(statusBarControl == null) {
throw new NullPointerException(“Unable to cre­ate TooltipHint­Con­troller. sta­tus­Bar­Con­trol can’t be null.”);
}

this.statusBarControl = sta­tus­Bar­Con­trol;
tooltipHin­tItems = FXCollections.observableList(new ArrayList<>());
}

pri­vate sta­t­ic TooltipHint­Con­troller main­In­stance;
pub­lic sta­t­ic TooltipHint­Con­troller get­Main­In­stance() {
if(mainInstance == null){
throw new NullPointerException(“Main instance of TooltipHint­Con­troller isn’t ini­tialised.”);
}

return main­In­stance;
}
pub­lic sta­t­ic void setMainInstance(TooltipHintController tooltipHint­Con­troller) {
main­In­stance = tooltipHint­Con­troller;
}
}

Чтобы заработало, надо инициализировать контроллер тем самым элементом, в который надо отображать подсказки:

TooltipHintController.setMainInstance(new TooltipHintController(labelStatusBar));

И привязываем к компонентам:

TooltipHintController.getMainInstance().addTooltipHint(buttonStart, "Нажми меня", "Нажми эту кнопку");
TooltipHintController.getMainInstance().addTooltipHint(paneButtons, "Здесь нажимают");

FontFamily и FontName

Забытое искусство подсказки

Давным-давно, когда люди ещё делали домашние странички, интернет был по шипучему модему, а Nap­ster казался опасной провокацией коммунистов, простенькие оконные приложения под Win­dows очень часто писали на библиотеке VCL. Одни пользовались Del­phi (но признавались только домашним), другие смело запускали её из C++ Builder (и удивлялись ещё одному String, а также спискам, которые начинаются с 1). А кто-то ухитрялся писать на нём под unix-ы (вы помните Kylix? А он был!)

В VCL почти у всех визуальных компонент были свойства ShowHint и Hint. Если быть точным, они были у всех компонент оконного типа (кнопок, выпадающих списков и прочих панелек).

В строковой Hint писался текст подсказки, а булевый ShowHint мог её отключить. А более прокачанные даже знали, что можно сделать расширенный вариант подсказки. Если написать в HintНажми меня|Кнопка просит, чтобы вы её нажали, то левая часть всплывёт, а правая будет передана в событие. Это событие перехватывали и показывали полученный текст в строке состояния.

Настолько простые и удобные подсказки были предметом величайшей зависти тех, кто сидел на MFC (никаких Win­Forms в тогдашней Visu­al Stu­dio ещё не было, не говоря о WPF), поддерживал OWL или штурмовал чистый WinAPI с Петцольдом наперевес. Кто знает, может быть кто-то из них и приложил свою мозолистую от кодинга руку к тому, что уникальная по своей удобности технология подсказок оказалась полностью утрачена в JavaFX.

Что у нас есть?

В JavaFX за подсказки отвечает компонент Tooltip. По всей видимости, это тайный компонент, потому что, к примеру, SceneB­uilder его знать не знает.

Если мы создадим новый Tooltip, а потом привяжем через set­Tooltip, то при наведении курсора на компонент, к которому привязывали, мы и правда увидим подсказку. На чёрном фоне (так надо). И с поддержкой картинок (спасибо).

Но JavaFX не позволит нам расслабиться: свойство Tooltip (и соответствующие методы) есть только у наследников класса javafx.scene.control.Control. А все панели и прочие области наследуются от javafx.scene.layout.Region. И никаких подсказок на них всплывать, получается, не может. Видимо, раз в приложении есть панель, то пользователю должно быть и так ясно, что там находится.

А может, разработчиков в школе слишком часто заставляли решать задачу «двумя разными способами»? Что до поддержки подсказок в панели статуса, то их нет даже близко.

А ТЗ требовало панель состояния и подсказки в ней в том числе и на панелях. И я справился своими силами.

Показываем и подсказываем

Я не стал пытаться заставить Tooltip всплывать над компонентами «неправильного» типа. Я просто хотел получить простой стандартный интерфейс, чтобы привязывать к компонентам подсказки в строке состояния, И, по возможности, делать всплывающие подсказки.

Причём интерфейс должен быть настолько простым, чтобы я мог поручить непосредственное привязывание подсказок студенту-стажёру, который работает на полставки.

Как и положено рабочему прототипу, это решение выглядит почти тривиальным. Поэтому я исключил все комментарии, которые бы только растянули статью. Но у него есть маленькое, но преимущество: приспособить его к своему проекту намного быстрее и проще, чем писать с нуля.

Особенно если вы такой же лентай, как и я.

Подсказываем

Саму подсказку мы будем хранить в двух классах, унаследованных от общего предка. Сначала создадим абстрактный класс подсказки.

Буква A перед названием пошла из старых учебников C++, по которым я учился программировать. Я не против, если вы привыкли по-другому (например, с постфиксом Base и т. п.). Я люблю начальную A, потому что она короткая.

И, как вы наверное догадались, любовь к шаблонам и генетикам у меня тоже из C++:

import javafx.scene.Node;

public abstract class ATooltipHintItem {
    private N attachedNode;
    protected void setAttachedNode(N node) {
        attachedNode = node;
    }
    public N getAttachedNode() {
        return attachedNode;
    }

    private String statusBarHint;
    protected void setStatusBarHint(String hint){
        statusBarHint = hint;
    }
    public String getStatusBarHint(){
        return statusBarHint;
    }

    private ITooltipHintController tooltipHintController;
    public ITooltipHintController getTooltipHintController() {
        return tooltipHintController;
    }

    public void showStatusBarHint(){
        tooltipHintController.setStatusBarText(statusBarHint);
    }

    public ATooltipHintItem(N attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) {
        this.attachedNode = attachedNode;
        this.tooltipHintController = tooltipHintController;

        if(statusBarHint != null && !statusBarHint.equals("")){
            initStatusBar();
            this.setStatusBarHint(statusBarHint);
        }
    }

    private void initStatusBar() {
        getAttachedNode().setOnMouseEntered(observableValue -> {
            this.showStatusBarHint();
        });

        getAttachedNode().setOnMouseExited(observableValue -> {
            getTooltipHintController().setDefaultStatusBarText();
        });
    }
}

А вот для Con­trol:

import javafx.scene.control.Control;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

public final class TooltipHintControlItem extends ATooltipHintItem {
    private Tooltip tooltip;
    public Tooltip getTooltip() {
        return tooltip;
    }

    private String tooltipHint;
    public TooltipHintControlItem setTooltipHint(String hint){
        tooltipHint = hint;
        if(hint == null || hint.trim().length() <= 0)
            return this;
        if(tooltip == null) {
            initTooltip();
        }
        tooltip.setText(hint);
        return this;
    }
    public String getTooltipHint(){
        return tooltipHint;
    }

    private Image tooltipImage;
    public TooltipHintControlItem setTooltipImage(Image image){
        tooltipImage = image;
        if(tooltip != null) tooltip.setGraphic((image != null) ? new ImageView(image) : null);
        return this;
    }
    public Image getTooltipImage(){
        return tooltipImage;
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint, String tooltipHint, Image imageHint) {
        super(attachedNode, tooltipHintController, statusBarHint);
        if(tooltipHint != null && !tooltipHint.isEmpty()){
            initTooltip();
        }
        setTooltipHint(tooltipHint);

        if(imageHint == null) {
            setTooltipImage(imageHint);
        }
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint, String tooltipHint) {
        this(attachedNode, tooltipHintController, statusBarHint, tooltipHint, null);
    }

    public TooltipHintControlItem(Control attachedNode, ITooltipHintController tooltipHintController, String statusBarHint) {
        this(attachedNode, tooltipHintController, statusBarHint, null, null);
    }

    private void initTooltip() {
        tooltip = new Tooltip();
        getAttachedNode().setTooltip(tooltip);
    }
}

Контролируем

Теперь берёмся за Con­troller. А контроллер у нас начинается с интерфейса. Java без интерфейса — это как хакер без ноутбука.

public interface ITooltipHintController {
    void setStatusBarText(String text);
    String getStatusBarText();
    void setDefaultStatusBarText();
}

А вот и сам контроллер:

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Labeled;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;

import java.util.ArrayList;
import java.util.Iterator;

public final class TooltipHintController implements ITooltipHintController {
    private final String DefaultStatusBarText = "";
    private final Labeled statusBarControl;
    private final ObservableList tooltipHintItems;

    private boolean isStatusBarLocked = false;
    public boolean getIsStatusBarLocked() {
        return isStatusBarLocked;
    }
    public void setIsStatusBarLocked(boolean isStatusBarLocked) {
        this.isStatusBarLocked = isStatusBarLocked;
    }

    public Labeled getStatusBarControl() {
        return this.statusBarControl;
    }

    public void setStatusBarTextForce(String text) {
        if(statusBarControl == null) {
            return;
        }
        statusBarControl.setText(text);
    }
    @Override
    public void setStatusBarText(String text) {
        if(!isStatusBarLocked){
            setStatusBarTextForce(text);
        }
    }
    @Override
    public String getStatusBarText() {
        return (statusBarControl != null) ? statusBarControl.getText() : "";
    }
    @Override
    public void setDefaultStatusBarText(){
        setStatusBarTextForce(DefaultStatusBarText);
    }

    //тут есть дублирование кода, но пока ничего серьёзного
    public void addTooltipHint(Region region, String statusBarHint){
        // Tooltip нас не интересует - у регионов в JavaFX не бывает всплывающих подсказок
        ATooltipHintItem tooltipHintItem = findTooltipHint(region);
        if(tooltipHintItem == null) {
            tooltipHintItem = new TooltipHintRegionItem(region, this, statusBarHint);
            tooltipHintItems.add(tooltipHintItem);
        } else {
            TooltipHintControlItem tooltipHintControlItem = (TooltipHintControlItem)tooltipHintItem;
            if(statusBarHint != null && tooltipHintControlItem.getStatusBarHint() == null)
                tooltipHintControlItem.setStatusBarHint(statusBarHint);
        }
    }

    public void addTooltipHint(Control control, String statusBarHint){
        addTooltipHint(control, statusBarHint, null, null);
    }

    public void addTooltipHint(Control control, String statusBarHint, String tooltipHint){
        addTooltipHint(control, statusBarHint, tooltipHint, null);
    }

    public void addTooltipHint(Control control, String statusBarHint, String tooltipHint, Image image){
        ATooltipHintItem tooltipHintItem = findTooltipHint(control);
        if(tooltipHintItem == null) {
            tooltipHintItem = new TooltipHintControlItem(control, this, statusBarHint, tooltipHint, image);
            tooltipHintItems.add(tooltipHintItem);
        } else {
            TooltipHintControlItem tooltipHintControlItem = (TooltipHintControlItem)tooltipHintItem;
            if(statusBarHint != null && tooltipHintControlItem.getStatusBarHint() == null)
                tooltipHintControlItem.setStatusBarHint(statusBarHint);

            if(tooltipHint != null && tooltipHintControlItem.getTooltipHint() == null)
                tooltipHintControlItem.setTooltipHint(tooltipHint);

            if(image != null && tooltipHintControlItem.getTooltipImage() == null)
                tooltipHintControlItem.setTooltipImage(image);
        }
    }

    public void removeTooltipHint(Node control){
        ATooltipHintItem tooltipHintItem = null;
        Iterator iteratorTooltipHintItems = tooltipHintItems.iterator();
        while(iteratorTooltipHintItems.hasNext()){
            tooltipHintItem = iteratorTooltipHintItems.next();
            if(tooltipHintItem.getAttachedNode() == control){
                tooltipHintItems.remove(tooltipHintItem);
                break;
            }
        }
    }

    public ATooltipHintItem findTooltipHint(Node control){
        for(ATooltipHintItem tooltipHintItem : tooltipHintItems)
            if(tooltipHintItem.getAttachedNode() == control)
                return tooltipHintItem;
        return null;
    }

    /**
     * При создании нужно привязать контроллер к компоненту, который будет
     * показывать подсказки.
     *
     * @param statusBarControl Компонент для подсказок
     */

    public TooltipHintController(Labeled statusBarControl){
        this.statusBarControl = statusBarControl;
        tooltipHintItems = FXCollections.observableList(new ArrayList<>());
    }

    public TooltipHintController(){
        this(null);
    }

    private static TooltipHintController mainInstance;
    public static TooltipHintController getMainInstance() {
        if(mainInstance == null){
            mainInstance = new TooltipHintController();
        }

        return mainInstance;
    }
    public static void setMainInstance(TooltipHintController tooltipHintController) {
        mainInstance = tooltipHintController;
    }
}

Дальше можно смело писать, даже не задумываясь, кто от кого унаследован:

TooltipHintController.getMainInstance().addTooltipHint(buttonStart, "Нажми меня", "Нажми эту кнопку");
TooltipHintController.getMainInstance().addTooltipHint(paneButtons, "Здесь нажимают");

А если у нас есть label­Sta­tus­Bar в качестве строки состояния, то мы можем использовать и его:

TooltipHintController.setMainInstance(new TooltipHintController(labelStatusBar));

Конечно, эту реализацию стоит доработать — ведь чисто теоретически в приложении может быть и больше одной строки состояния. Если вам такие известны (разумеется, современные, на JavaFx и активно используемые) — дайте знать.

Заключение

К сожалению, этот набор из 4 классов слишком объёмен, чтобы свестись к одному документу на paste­bin. Но и слишком мал и несамостоятелен, чтобы стать гордым maven-пакетом и занять почётное место в известном репозитории.

Но я всё равно надеюсь, что он найдёт своего пользователя (точнее, своего разработчика). А может, он попадётся на глаза разработчикам из Ora­cle и убедит их сделать подсказки удобней.

Есть одобренные самой Ora­cle библиотеки расширений — возможно. стоит попытаться пристроить этот интерфейс в одну из них?