ADDITIONAL SYSTEM INFORMATION :
java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
LSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch
Distributor ID: CentOS
Description: CentOS release 6.7 (Final)
Release: 6.7
Codename: Final
A DESCRIPTION OF THE PROBLEM :
On Linux machines column sorting is triggered always after context menu request on table header.
Right clicking on sortable column header results in opening context menu and sorting this column.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Open provided source code with sortable table and installed context menu to table header row.
2. Right click on column name - "Name".
3. Click on table to close popup.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
After 3. The previously clicked column should not be sorted.
ACTUAL -
After 3. The previously clicked column is sorted.
---------- BEGIN SOURCE ----------
package sample;
import java.util.function.BooleanSupplier;
import com.sun.javafx.scene.control.skin.TableColumnHeader;
import com.sun.javafx.scene.control.skin.TableHeaderRow;
import com.sun.javafx.scene.control.skin.TableViewSkin;
import com.sun.javafx.scene.control.skin.TableViewSkinBase;
import com.sun.javafx.scene.control.skin.Utils;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TitledPane;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class PopupTest extends Application
{
public static void main( String[] args )
{
Application.launch( args );
}
@Override
public void start( Stage stage )
{
System.err.println( System.getProperty( "javafx.runtime.version" ) );
Parent content = createContent();
Scene scene = new Scene( content, 800, 600 );
stage.setScene( scene );
stage.show();
}
TableView< ? > createTable()
{
final TableView< String > table = new TableView<>();
final TableColumn< String, String > firstNameCol = new TableColumn<>( "Name" );
firstNameCol.setMinWidth( 100 );
firstNameCol.setCellValueFactory( param -> new SimpleStringProperty( param.getValue() ) );
table.getColumns().add( firstNameCol );
table.getItems().addAll( "Test1", "Test2", "Test3" );
Utils.executeOnceWhenPropertyIsNonNull( table.skinProperty(), skin -> {
if( skin instanceof TableViewSkin )
{
installContextMenu( (TableViewSkin< ? >)skin );
}
} );
return table;
}
Parent createContent()
{
final VBox pane = new VBox();
pane.setPadding( new Insets( 3 ) );
final TableView< ? > noFixTable = createTable();
final TableView< ? > fixedTable = createTable();
installWorkaround( fixedTable );
final TitledPane t1 = new TitledPane( "Standard table", noFixTable );
t1.setCollapsible( false );
final TitledPane t2 = new TitledPane( "Table with workaround", fixedTable );
t2.setCollapsible( false );
pane.getChildren().addAll( t1, t2 );
return pane;
}
private void installWorkaround( final TableView< ? > fixedTable )
{
final PopupTriggerWorkaround aPopupTriggerWorkaround = new PopupTriggerWorkaround( fixedTable );
Utils.executeOnceWhenPropertyIsNonNull( fixedTable.skinProperty(), skin -> {
final TableHeaderRow tableHeaderRow = ((TableViewSkinBase)skin).getTableHeaderRow();
final ObservableList< TableColumnHeader > columnHeaders =
tableHeaderRow.getRootHeader().getColumnHeaders();
columnHeaders.addListener( ( ListChangeListener.Change< ? extends TableColumnHeader > aChange ) -> {
while( aChange.next() )
{
aChange.getRemoved().forEach( this::uninstallWorkaround );
aChange.getAddedSubList().forEach( n -> installWorkaround( n, aPopupTriggerWorkaround ) );
}
} );
} );
}
private void uninstallWorkaround( final Node node )
{
throw new UnsupportedOperationException();
}
private void installWorkaround( final Node node, final PopupTriggerWorkaround aPopupTriggerWorkaround )
{
Utils.executeOnceWhenPropertyIsNonNull( node.onMouseReleasedProperty(), aEventHandler -> {
node.onMouseReleasedProperty().set( event -> {
if( !aPopupTriggerWorkaround.getAsBoolean() )
{
aEventHandler.handle( event );
}
} );
} );
}
private void installContextMenu( final TableViewSkin< ? > skin )
{
skin.getTableHeaderRow().addEventHandler( ContextMenuEvent.CONTEXT_MENU_REQUESTED, ( aEvent ) -> {
final ContextMenu aContextMenu = new ContextMenu( new MenuItem( "a" ), new MenuItem( "b" ) );
aContextMenu.show( skin.getSkinnable().getScene().getWindow(), aEvent.getScreenX(),
aEvent.getScreenY() );;
aEvent.consume();
} );
}
static class PopupTriggerWorkaround implements BooleanSupplier
{
boolean shouldSuppresSorting = false;
public PopupTriggerWorkaround( final Node node )
{
if( com.sun.javafx.PlatformUtil.isLinux() || com.sun.javafx.PlatformUtil.isMac() )
{
node.addEventFilter( MouseEvent.ANY, evt -> {
if( evt.getEventType() == MouseEvent.MOUSE_MOVED
|| evt.getEventType() == MouseEvent.MOUSE_DRAGGED )
{
return;
}
if( evt.getEventType() == MouseEvent.MOUSE_PRESSED )
{
shouldSuppresSorting = evt.isPopupTrigger();
}
else if( evt.getEventType() != MouseEvent.MOUSE_RELEASED )
{
shouldSuppresSorting = false;
}
} );
}
}
@Override
public boolean getAsBoolean()
{
final boolean oldValue = shouldSuppresSorting;
shouldSuppresSorting = false;
return oldValue;
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Workaround is provided in source "Source code for an executable test case" - see "Table with workaround"
FREQUENCY : always
java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
LSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch
Distributor ID: CentOS
Description: CentOS release 6.7 (Final)
Release: 6.7
Codename: Final
A DESCRIPTION OF THE PROBLEM :
On Linux machines column sorting is triggered always after context menu request on table header.
Right clicking on sortable column header results in opening context menu and sorting this column.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Open provided source code with sortable table and installed context menu to table header row.
2. Right click on column name - "Name".
3. Click on table to close popup.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
After 3. The previously clicked column should not be sorted.
ACTUAL -
After 3. The previously clicked column is sorted.
---------- BEGIN SOURCE ----------
package sample;
import java.util.function.BooleanSupplier;
import com.sun.javafx.scene.control.skin.TableColumnHeader;
import com.sun.javafx.scene.control.skin.TableHeaderRow;
import com.sun.javafx.scene.control.skin.TableViewSkin;
import com.sun.javafx.scene.control.skin.TableViewSkinBase;
import com.sun.javafx.scene.control.skin.Utils;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TitledPane;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class PopupTest extends Application
{
public static void main( String[] args )
{
Application.launch( args );
}
@Override
public void start( Stage stage )
{
System.err.println( System.getProperty( "javafx.runtime.version" ) );
Parent content = createContent();
Scene scene = new Scene( content, 800, 600 );
stage.setScene( scene );
stage.show();
}
TableView< ? > createTable()
{
final TableView< String > table = new TableView<>();
final TableColumn< String, String > firstNameCol = new TableColumn<>( "Name" );
firstNameCol.setMinWidth( 100 );
firstNameCol.setCellValueFactory( param -> new SimpleStringProperty( param.getValue() ) );
table.getColumns().add( firstNameCol );
table.getItems().addAll( "Test1", "Test2", "Test3" );
Utils.executeOnceWhenPropertyIsNonNull( table.skinProperty(), skin -> {
if( skin instanceof TableViewSkin )
{
installContextMenu( (TableViewSkin< ? >)skin );
}
} );
return table;
}
Parent createContent()
{
final VBox pane = new VBox();
pane.setPadding( new Insets( 3 ) );
final TableView< ? > noFixTable = createTable();
final TableView< ? > fixedTable = createTable();
installWorkaround( fixedTable );
final TitledPane t1 = new TitledPane( "Standard table", noFixTable );
t1.setCollapsible( false );
final TitledPane t2 = new TitledPane( "Table with workaround", fixedTable );
t2.setCollapsible( false );
pane.getChildren().addAll( t1, t2 );
return pane;
}
private void installWorkaround( final TableView< ? > fixedTable )
{
final PopupTriggerWorkaround aPopupTriggerWorkaround = new PopupTriggerWorkaround( fixedTable );
Utils.executeOnceWhenPropertyIsNonNull( fixedTable.skinProperty(), skin -> {
final TableHeaderRow tableHeaderRow = ((TableViewSkinBase)skin).getTableHeaderRow();
final ObservableList< TableColumnHeader > columnHeaders =
tableHeaderRow.getRootHeader().getColumnHeaders();
columnHeaders.addListener( ( ListChangeListener.Change< ? extends TableColumnHeader > aChange ) -> {
while( aChange.next() )
{
aChange.getRemoved().forEach( this::uninstallWorkaround );
aChange.getAddedSubList().forEach( n -> installWorkaround( n, aPopupTriggerWorkaround ) );
}
} );
} );
}
private void uninstallWorkaround( final Node node )
{
throw new UnsupportedOperationException();
}
private void installWorkaround( final Node node, final PopupTriggerWorkaround aPopupTriggerWorkaround )
{
Utils.executeOnceWhenPropertyIsNonNull( node.onMouseReleasedProperty(), aEventHandler -> {
node.onMouseReleasedProperty().set( event -> {
if( !aPopupTriggerWorkaround.getAsBoolean() )
{
aEventHandler.handle( event );
}
} );
} );
}
private void installContextMenu( final TableViewSkin< ? > skin )
{
skin.getTableHeaderRow().addEventHandler( ContextMenuEvent.CONTEXT_MENU_REQUESTED, ( aEvent ) -> {
final ContextMenu aContextMenu = new ContextMenu( new MenuItem( "a" ), new MenuItem( "b" ) );
aContextMenu.show( skin.getSkinnable().getScene().getWindow(), aEvent.getScreenX(),
aEvent.getScreenY() );;
aEvent.consume();
} );
}
static class PopupTriggerWorkaround implements BooleanSupplier
{
boolean shouldSuppresSorting = false;
public PopupTriggerWorkaround( final Node node )
{
if( com.sun.javafx.PlatformUtil.isLinux() || com.sun.javafx.PlatformUtil.isMac() )
{
node.addEventFilter( MouseEvent.ANY, evt -> {
if( evt.getEventType() == MouseEvent.MOUSE_MOVED
|| evt.getEventType() == MouseEvent.MOUSE_DRAGGED )
{
return;
}
if( evt.getEventType() == MouseEvent.MOUSE_PRESSED )
{
shouldSuppresSorting = evt.isPopupTrigger();
}
else if( evt.getEventType() != MouseEvent.MOUSE_RELEASED )
{
shouldSuppresSorting = false;
}
} );
}
}
@Override
public boolean getAsBoolean()
{
final boolean oldValue = shouldSuppresSorting;
shouldSuppresSorting = false;
return oldValue;
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Workaround is provided in source "Source code for an executable test case" - see "Table with workaround"
FREQUENCY : always
- links to
-
Review(master) openjdk/jfx/1754