QHeaderView::mousePressEvent (need to figure out the column/index) - qt

Generally I can get this to work no problem when I reimplement QTableView::mousePressEvent( QMouseEvent* ). However, doing it on QHeaderView is not working for me. Code is simple.
void my_header_t::mousePressEvent( QMouseEvent* event )
{
if ( !event ) {
return;
}
if ( event->button() == Qt::RightButton ) {
QPoint point( event->x(), event->y() );
QModelIndex index = indexAt( point );
printf( "%s data %s %d,%d %s (point: %d,%d )\n",
ts().c_str(), index.data().toString().toStdString().c_str(),
index.row(), index.column(), index.isValid() ? "True" : "False",
event->x(), event->y() );
handle_right_click( index.data().toString() );
} else {
QHeaderView::mousePressEvent( event );
}
x() and y() from the QMouseEvent are fine. However, it creates an invalid index, with row() of -1, and column() of -1. Obviously, I'm passing an empty string to handle_right_click() which kicks off a menu. That menu is not going to know which column called it, and the mayhem will further ensue.
I know that clicked( const QModelIndex& ) will just tell me the right index, with the text. However, I need to differentiate between buttons.

QHeaderView provides an alternative function, logicalIndexAt, for determining the index of the header item that you're interested in. Using your code from above:
void my_header_t::mousePressEvent( QMouseEvent* event )
{
if ( !event ) {
return;
}
if ( event->button() == Qt::RightButton ) {
int index = logicalIndexAt( event->pos() );
handle_right_click(model()->headerData(index, Qt::Horizontal).toString());
} else {
QHeaderView::mousePressEvent( event );
}
}
Note that the orientation of the header must be passed to the headerData method (in this case, I've just assumed that it's Qt::Horizontal, but in your case it might be something different).

Related

How to hide action button when QLineEdit is disabled?

I have a class which is inherited from QLineEdit, and I set an icon as an action button for this.
MyEdit::MyEdit( QWidget *p_parent ) : QLineEdit( p_parent )
{
m_buttonAction = addAction( QIcon( "search.png" ), QLineEdit::TrailingPosition );
QAbstractButton *button = qobject_cast<QAbstractButton *>( m_buttonAction->associatedWidgets().last() );
m_buttonAction->setVisible( false );
connect( m_buttonAction, &QAction::triggered, this, &MyEdit::openCompleter );
m_completer = new QCompleter( this );
m_sourceModel = new CompleterSourceModel( m_completer );
m_view = new CompleterView();
m_view->setStyle( &m_style );
m_delegate = new CompleterDelegate( m_view );
m_completer->setPopup( m_view );
m_completer->setModel( m_sourceModel );
m_view->setItemDelegate( m_delegate );
setCompleter( m_completer );
}
void MyEdit::setDataForCompleter( const CompleterData &p_data )
{
m_sourceModel->setCompleterData( p_data );
m_buttonAction->setVisible( p_data.data().size() > 0 );
}
When I import data for completer, the icon is always shown. Now I need to hide this icon in case MyEdit is disabled or as ReadOnly.
I am thinking about override setDisabled and setReadOnly for my class, and in there setVisible for the icon. But these functions are not virtual, so can not be overridden.
I am thinking also about a signal like stateChanged of my class, so I can do it in a slot. But I can not find any signal like that for QLineEdit. Do you have any idea how to do it?
You can handle events QEvent::ReadOnlyChange or QEvent::EnabledChange by overriding the QLineEdit::event method
UPDATE:
Here is an example implementation:
bool MyEdit::event(QEvent *e) override {
const auto type = e->type();
if (type == QEvent::ReadOnlyChange || type == QEvent::EnabledChange) {
m_buttonAction->setVisible(m_sourceModel->rowCount() > 0 ? isEnabled() && isReadOnly() : false);
}
return QLineEdit::event(e);
}

javafx - TableView Nested UI elements looses reference data object on dynamic addition of row

I'm trying to create an API, which can dynamically populate tableview with different Nested UI components in each cell of tableview. I'm able to bind the data set to Model object with the table.
The problem is when I try to dynamically add and try to enable the edit, the object reference is seems to messed up.
FYI:
As you see I've last column with 4 buttons, which is Add, Edit, Delete, Reset features. Once click on Add - it clones the current row, click on edit - it enable the ComboBox of Coulmn Category, click on delete - it deletes current row.
What I face is that upon adding multiple entries, I do get the row added dynamically, but then click on first row edit button - then multiple ComboBox is enabled, which is not intended use. The use case is current row's ComboBox must be only be enabled.
Implementation: I've written custom API which extends TableView<S>.
Following snippet may help:
//column category
final ClumpElement< ConstraintsDataModel, String > categoryElement =
new ClumpElement<>( ClumpType.COMBOBOX, true, getCategoryData() );
categoryElement.setClumpTableCellValue( data -> data.categoryProperty() );
categoryElement.setClumpTableNodeAction( ( control, data ) -> {
final ComboBox< String > comboBox = (ComboBox< String >)control;
comboBox.disableProperty().bind( data.disableProperty() );
} );
clumpTableView.addNewColumn( "Category", categoryElement );
// column Action
final ClumpElement< ConstraintsDataModel, String > buttonsElement =
new ClumpElement<>( ClumpType.GROUP_BUTTONS, 4, "+", "✎", "X", "↻" );
buttonsElement.setClumpTableNodeAction( ( control, data ) -> {
final Button button = (Button)control;
switch( button.getText() ) {
case "+":
final ConstraintsDataModel ref =
clumpTableView.getItems().get( clumpTableView.getItems().size() - 1 );
if( ConstraintsDataModel.isValidModel( ref ) )
clumpTableView.getItems().add( new ConstraintsDataModel( data ) );
else
System.out.println( "ERROR: Finish previous constraints" );
break;
case "✎":
data.setDisableValue( false );
button.setText( "✔" );
break;
case "✔":
data.setDisableValue( true );
button.setText( "✎" );
break;
default:
//NOTHING
break;
}
} );
clumpTableView.addNewColumn( "Action", buttonsElement );
clumpTableView.setItems( getData() );
This is my CustomTableView class:
public < T > void addNewColumn( final String columnName, final ClumpElement< S, T > element ) {
final TableColumn< S, T > column = new TableColumn<>( columnName );
getColumns().add( column );
if( element.getClumpTableCellValue() != null ) {
column.setCellValueFactory( param -> element.getClumpTableCellValue()
.act( param.getValue() ) );
}
clumpCellCall( columnName, element, column );
}
private < T > void clumpCellCall( final String colName, final ClumpElement< S, T > element,
final TableColumn< S, T > column ) {
switch( element.getUiNode() ) {
case COMBOBOX:
if( element.getItems() != null && !element.getItems().isEmpty() ) {
column.setCellFactory( param -> {
final ClumpComboBoxTableCell< S, T > clumpComboBoxTableCell =
new ClumpComboBoxTableCell<>( element.isDisable(), element.getItems() );
clumpComboBoxTableCell.prefWidthProperty().bind( column.widthProperty() );
clumpComboBoxTableCell.selectionListener( element );
return clumpComboBoxTableCell;
} );
}
break;
case GROUP_BUTTONS:
column.setCellFactory( param -> {
final ClumpButtonsTableCell< S, T > clumpButtonsTableCell =
new ClumpButtonsTableCell<>( element.getNoOfElements() );
clumpButtonsTableCell.prefWidthProperty().bind( column.widthProperty() );
IntStream.range( 0, element.getNoOfElements() ).forEach( item -> {
final Button button = clumpButtonsTableCell.getButtons().get( item );
button.setText( element.getNames().get( item ) );
button.setOnAction( event -> {
if( element.getClumpTableNodeAction() != null
&& clumpButtonsTableCell.getIndex() < getItems().size() ) {
element.getClumpTableNodeAction()
.act( button, getItems().get( clumpButtonsTableCell.getIndex() ) );
}
} );
} );
return clumpButtonsTableCell;
} );
break;
default:
column.setCellFactory( params -> {
final TextFieldTableCell< S, T > textFieldTableCell = new TextFieldTableCell<>();
textFieldTableCell.setConverter( new StringConverter< T >() {
#Override
public String toString( final T object ) {
return (String)object;
}
#Override
public T fromString( final String string ) {
return (T)string;
}
} );
return textFieldTableCell;
} );
break;
}
}
In my custom API, which shall invoke a custom TableCell<S,T> which has ComboBox<T> pretty standard implementation as per docs. Here its inside a selection listener, as I found that when the cell renders, only this selection listener is called.
public abstract class AbstractClumpTableCell< S, T > extends TableCell< S, T > {
public AbstractClumpTableCell() {
setContentDisplay( ContentDisplay.GRAPHIC_ONLY );
setAlignment(Pos.CENTER);
}
public abstract void renewItem( T item );
#Override
protected void updateItem( T item, boolean empty ) {
super.updateItem( item, empty );
if( empty ) {
setGraphic( null );
} else {
renewItem( item );
}
}
}
public class ClumpComboBoxTableCell< S, T > extends AbstractClumpTableCell< S, T > {
private final ComboBox< T > comboBox;
#SuppressWarnings( "unchecked" )
public ClumpComboBoxTableCell( final boolean isDisable, final ObservableList< T > item ) {
super();
this.comboBox = new ComboBox<>( item );
this.comboBox.setDisable( isDisable );
this.comboBox.valueProperty().addListener( ( obs, oVal, nVal ) -> {
ObservableValue< T > property = getTableColumn().getCellObservableValue( getIndex() );
if( property instanceof WritableValue ) {
((WritableValue< T >)property).setValue( nVal );
}
} );
}
#Override
public void renewItem( T item ) {
comboBox.setValue( item );
setGraphic( comboBox );
}
public ComboBox< T > getComboBox() {
return comboBox;
}
protected void selectionListener( final ClumpElement< S, T > element ) {
this.comboBox.getSelectionModel().selectedItemProperty().addListener( ( obs, oVal, nVal ) -> {
if( element.getClumpTableNodeAction() != null
&& getIndex() < getTableView().getItems().size() ) {
element.getClumpTableNodeAction().act( this.comboBox,
getTableView().getItems().get( getIndex() ) );
}
} );
}
}
And my Data Model has a SimpleStringProperty that is binded to the column accordingly.
So, How can I bind the Nested UI elements correctly rowise within the TableView<S>? Is my approach right or is there alternatives?
I will make an attempt to answer, but as I said the code is hard for me to follow (especially as it is partial, so some methods I can only assume the purpose of).
The issue, as stated in the comments, is node virtualization in TableView. You can't go around it, and you really don't want to - it is a means to vastly improve performance, as you don't need hundreds or thousands of UI nodes (which are "heavy" and degrade performance), but only enough to fill the displayed portion of the table, thus supporting a much larger dataset.
The problem, as far as I can see, as that you have some property of the row (is it currently editable or not) which you need to be reflected in certain columns. More specifically, you want the combo box's disable property to always reflect the disable property of the row it pertains to, so in updateItem you will have to do something like this:
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(T, empty);
if (empty) {
setGraphic(null);
} else {
renewItem(item);
// since the disable property if given by the row value, not only the column value
// we need to get the row value. The cast is needed due to a design oversight
// in JavaFX 8, which is fixed in newer versions. See https://bugs.openjdk.java.net/browse/JDK-8144088
ConstraintsDataModel data = ((TableRow<ConstraintsDataModel>)getTableRow())
.getItem();
combobox.disableProperty().unbind();
combobox.disableProperty().bind(data.disableProperty());
}
}
This is assuming your row data type is indeed ConstaintDataModel, I couldn't quite follow through.
Another option which may be more elegant is to use the editing property of a row - bind the combo box's disable property to the negation of the editing property of the row, and use startEdit and cancelEdit/commitEdit when you start and end editing. This way you will not have to re-bind the disable property of the combo box, as it will always refer to the correct row.

QTreeWidget findItems or filter by icon

I'm using QTreeWidget with icons and strings data.
Right now I'm using my own filter function to filter out string via QTreeWidget::findItems function and it's works pretty good.
BUT, how can I filter/findItems by icons ?
The Qt findItems functions can get only strings as input...
Any suggestions ?
Try this:
QTreeWidgetItem* findByIcon( QTreeWidget* aTreeWidget, const QIcon& aIcon, const int aColumn = 0 )
{
QTreeWidgetItemIterator iterator( aTreeWidget );
while ( *iterator )
{
if ( (*iterator)->data( aColumn, Qt::DecorationRole ) == aIcon )
{
return (*iterator);
}
++iterator;
}
return nullptr;
}
This will find the first match only, is it enough ?
If not then here is a function which finds all the items with the given icon:
QList< QTreeWidgetItem* > findByIcon( QTreeWidget* aTreeWidget, const QIcon& aIcon, const int aColumn = 0 )
{
QList< QTreeWidgetItem* > items;
QTreeWidgetItemIterator iterator( aTreeWidget );
while ( *iterator )
{
if ( (*iterator)->data( aColumn, Qt::DecorationRole ) == aIcon )
{
items << (*iterator);
}
++iterator;
}
return items;
}

How to know a floating QWidget position when it is shown

I'm trying to move a floating QWidget (Qt::window flag ), in a position depending of it's initial position, wich is determined by the window manager.
I can't find a clean way to do it. The first move event is always at position 0,0; and during show event, position is also 0,0.
So the only way for now i've found is something like that :
this->firstMoved = false;
this->myFirstMoved = false;
...
void TabWidget::moveEvent( QMoveEvent* event )
{
if( this->firstMoved )
{
if(! this->myFirstMoved )
{
this->myMoveMethod();
this->myFirstMoved = true;
}
}
else
{
this->firstMoved = true;
}
Superclass::moveEvent( event );
}
not as clean as i would have expected. I'm using X11 by the way.

JavaFX 8.0: How to change the focus traversal key?

The regular focus traversal keys are TAB for moving focus forward and SHIFT+TAB for moving focus backward. And now I want to use ENTER key instead of TAB. Are there any idea for this?
Qinn
This is how I have done it:
rootNode.addEventFilter( KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>()
{
Robot eventRobot = new Robot();
#Override
public void handle( KeyEvent KV )
{
switch ( KV.getCode() )
{
case ENTER :
{
if ( ! (KV.getTarget() instanceof TextArea) )
{
eventRobot.keyPress( java.awt.event.KeyEvent.VK_TAB );
eventRobot.keyRelease( java.awt.event.KeyEvent.VK_TAB );
KV.consume();
}
break;
}
case TAB :
{
if ( ! (KV.getTarget() instanceof TextArea) )
{
KV.consume();
}
break;
}
}
}
}
I Think it would be better off if you use two classes:
KeyCombination for a more precise key checking along with
KeyboardShortcutsHandler to move focus.
So there's no need to use a Robot to send tab key
final KeyCombination ENTER = new KeyCodeCombination(KeyCode.ENTER);
rootNode.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent KV) -> {
EventTarget tgt = KV.getTarget();
if (ENTER.match(KV)) {
KeyboardShortcutsHandler ksh = new KeyboardShortcutsHandler();
ksh.traverse((Node) tgt, Direction.NEXT);
KV.consume();
} else if ( ! (KV.getTarget() instanceof TextArea) ) {
KV.consume();
}
});

Resources