Icon click on Qt QTreeWidget - c++

I have a treewidget in my Qt form. It shows a tree of files, showing a icon representing something about them, and their name.
I entered these using treeItem->setIcon(0, *icon), and treeItem->setText(0, text) .
The reason I entered both values to the same column (0), is because otherwise the icons would not stay next to the text, rather always stick to the left, even when the text was indented to the right (because it's a child of another item).
The problem is, now I can't tell if the user clicked on the icon or on the text itself, and I need to deal with these separately.
So, is there anyway to get more info than just the treeitem and column when an object in a treewidget is clicked,
or is there any way to put them on seperate columns and still have the normal behavior icons and text should have?
Thanks.

I don't think there is a straight forward way to get more info, if you are simply using the clicked() or itemClicked() signals. You probably have to create a custom class that inherits QTreeWidget, and reimplement one of the virtual mouse-event functions.
mouseMoveEvent ( QMouseEvent * )
mousePressEvent ( QMouseEvent * )
mouseReleaseEvent ( QMouseEvent * )
This is not something I would recommend, unless you really know what you are doing, and really need to do it.
However, I can't remember seeing a list widget anywhere, where clicking an icon is handled differently from clicking the text in the same column. So if you are looking for "the normal behavior icons and text should have", you probably should look for another solution.

I found the following solution for this problem:
void MyTreeWidget::mousePressEvent( QMouseEvent* aEvent )
{
QTreeWidget::mousePressEvent( aEvent );
const QPoint clickedPosition = aEvent->pos();
const QRect itemRectangle = visualItemRect( itemAt( clickedPosition ) );
const int iconOffset = itemRectangle.height() - iconSize().height();
QRect iconRectangle;
iconRectangle.setTopLeft( itemRectangle.topLeft() + QPoint( iconOffset, iconOffset ) );
iconRectangle.setWidth( iconSize().width() );
iconRectangle.setHeight( iconSize().height() );
if ( iconRectangle.contains( clickedPosition ) )
{
qDebug() << "ICON clicked";
// Emit an icon clicked SIGNAL.
}
}

Related

Closing Popup and setting button label

I'm writing a C++ wxWidgets calculator application. I want to compress trigonometric function buttons into a single one to save on space, using what's basically a split button. If you left click on it, the current option is used. If you right click, a popup menu is opened, which contains all the buttons; when you click on one of them, it is used and the big button changes.
I've been suggested to use wxComboBox and other stuff for this job, but I preferred using wxPopupTransientWindow because this way I can display the buttons in a grid, making everything - in my opinion - neater.
Problem is, when I choose an option from the menu, the main button's ID changes (because when I reopen the menu the previously clicked button is light up as its ID and the big button's ID match), but the label does not. Furthermore, the popup is supposed to close itself when you left click on one of the buttons, but it does not.
This is the code for the custom button in the popup which is supposed to do all that stuff:
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxWindow* mBtn = this->GetParent()->GetParent();
mBtn->SetLabel(this->GetLabel());
mBtn->SetId(this->GetId());
this->GetParent()->Close(true);
}
}
This is the code for the custom button in the main frame which opens up the popup (temporary setup just to test if the whole thing is working):
void ikeButton::rightClick(wxMouseEvent& evt) // CREA PANNELLO ESTENSIONE
{
if (flags & EXPANDABLE)
{
std::vector<expandMenuInfo> buttons;
buttons.push_back(expandMenuInfo(L"sin", 3001));
buttons.push_back(expandMenuInfo(L"cos", 3002));
buttons.push_back(expandMenuInfo(L"tan", 3003));
buttons.push_back(expandMenuInfo(L"arcsin", 3004));
buttons.push_back(expandMenuInfo(L"arccos", 3005));
buttons.push_back(expandMenuInfo(L"arctan", 3006));
wxPoint p = this->GetScreenPosition();
size_t sz = this->GetSize().GetHeight() / 1.15;
expandMenu* menu = new expandMenu(this, buttons, sz, wxPoint(
p.x, p.y + this->GetSize().GetHeight() + 2));
menu->SetPosition(wxPoint(
menu->GetPosition().x - ((menu->GetSize().GetWidth() - this->GetSize().GetWidth()) / 2),
menu->GetPosition().y));
menu->Popup();
}
}
Let me know if I need to show more code.
This is probably a terrible way of doing this, but this is basically the first "serious" application I'm creating using the wxWidgets framework. Any help is appreciated.
when I choose an option from the menu, the main button's ID changes
(because when I reopen the menu the previously clicked button is light
up as its ID and the big button's ID match), but the label does not.
If you're creating the popup menu like in your previous post, you had a popup window with a panel as its child and the buttons were then children of the panel layed out with a sizer.
If that's still the case, this->GetParent() and should be the panel, this->GetParent()->GetParent() should be the popup. So this->GetParent()->GetParent()->GetParent() should be the trig function button (assuming you created the popup with the trig function button as the parent).
So I think the line wxWindow* mBtn = this->GetParent()->GetParent(); should be changed to wxWindow* mBtn = this->GetParent()->GetParent()->GetParent();.
Or slightly shorter wxWindow* mBtn = this->GetGrandParent()->GetParent();;
the popup is supposed to close itself when you left click on one of
the buttons, but it does not.
It looks like wxPopupTransientWindow has a special Dismiss method that is supposed to be used to close it.
So I think the line this->GetParent()->Close(true); should be changed to this->GetGrandParent()->Dismiss(); (Assuming as above that the buttons in the popup are children pf a panel).
Alternately, if you want a solution that will work regardless of the parentage of the controls in the popup window, you could have a utility function to find the popup ancestor which would look something like this:
wxPopupTransientWindow* FindPopup(wxWindow* w)
{
wxPopupTransientWindow* popup = NULL;
while ( w != NULL )
{
popup = wxDynamicCast(w, wxPopupTransientWindow);
if ( popup )
{
break;
}
w = w->GetParent();
}
return popup;
}
This uses the wxDynamicCast function which is slightly different from the c++ dynamic_cast expression. wxDynamicCast uses wxWidgets' RTTI system to check if the given pointer can be converted to the given type.
Then the mouseReleased method could use this utility function something like this:
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxPopupTransientWindow* popup = FindPopup(this);
if (popup ) {
wxWindow* mBtn = popup->GetParent();
mBtn->SetLabel(this->GetLabel());
mBtn->SetId(this->GetId());
mBtn->Refresh();
popup->Dismiss();
}
}
}
I'm not sure why you're setting trig function button to have a new id, but I assume you have a reason.
To make the SetLabel method work in your custom button class, I think the easist thing is to call the SetLabel() method in the button's constructor. This will store the string passed to the constructor in the button's internal label member.
Based on other questions, I think the ikeButton constructor looks something like this:
ikeButton::ikeButton(wxFrame* parent, wxWindowID id, wxString text,...
{
...
this->text = text;
To store the label, you would need to change the line this->text = text; to
SetLabel(text);
And when you draw the button, I think the method you use looks like this:
void ikeButton::render(wxDC& dc)
{
...
dc.DrawText(text, ...);
}
You would need to change, the line dc.DrawText(text, ...); to
dc.DrawText(GetLabel(), ...);
Likewise, any other references to the button's text member should be changed to GetLabel() instead.
Finally, when you set the label in the expandButton::mouseReleased method, it might be a good idea to call button's Refresh() method to force the button to redraw itself. I added that my suggestion for the mouseReleased method above.

Disable auto-selecting items in QListWidget on click+drag

I've spent better half of today trying to resolve seemingly trivial QListWidget behavior customization: when used presses mouse left button and moves mouse cursor, the content of ListWidget is scrolled and selection is moved to another item that happens to appear under mouse cursor. I am alright with scrolling, but I want to avoid selecting all consequtive items because this causes timely operation in my program. Finally I would like to keep list content scrolling on mouse press and move, but select items only by clicking directly on them.
Drag-n-drop is disabled for this list (which is default behavior) and it should be; I've tried to disable it explicitly: no changes whatsoever.
I have read all available docs on Qt related classes like QListWidget, QListWidgetItem, QListView, you name it! Tried to make sense of source code for these widgets; dug up StackOverflow and Google... but sadly no result :(
Here is all relevant code for my QListWidget: single selection, nothing fancy:
QListWidget* categoryListWidget;
...
categoryListWidget = new QListWidget();
categoryListWidget->move(offsetX, offsetY);
categoryListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
categoryListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
categoryListWidget->setFocusPolicy(Qt::NoFocus);
categoryListWidget->setStyleSheet(listQSS);
...
categoryListWidget->clear();
new QListWidgetItem(tr("1 - Sample Category 1"), categoryListWidget);
new QListWidgetItem(tr("2 - Sample Category 2"), categoryListWidget);
new QListWidgetItem(tr("3 - Sample Category 3 with a very long name"), categoryListWidget);
new QListWidgetItem(tr("4 - Sample Category 4"), categoryListWidget);
C++/Qt 5.5 if that's somehow relevant, both Win and Mac platforms share similar behavior.
For the sake of whoever stumbles upon the same question, here is my solution: subclass QListWidget and make child class ignore mouseMove events when leftButton is pressed.
Header:
class QtListWidget: public QListWidget
{ // QListWidget clone that suppresses item selection on mouse click+drag
private:
bool mousePressed;
public:
QtListWidget():QListWidget(), mousePressed(false) {}
protected:
virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseMoveEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
};
Source:
//////////////////////////////////////////////////////////////////////////
void QtListWidget::mousePressEvent(QMouseEvent *event){
// qDebug() << "QtListWidget::mousePressEvent";
if(event->button() == Qt::LeftButton)
mousePressed = true;
QListWidget::mousePressEvent(event);
}
void QtListWidget::mouseMoveEvent(QMouseEvent *event){
// qDebug() << "QtListWidget::mouseMoveEvent";
if(!mousePressed) // disable click+drag
QListWidget::mouseMoveEvent(event);
}
void QtListWidget::mouseReleaseEvent(QMouseEvent *event){
// qDebug() << "QtListWidget::mouseReleaseEvent";
if(event->button() == Qt::LeftButton)
mousePressed = false;
QListWidget::mouseReleaseEvent(event);
}
//////////////////////////////////////////////////////////////////////////
Usage is trivial, for as many List Widgets as you need:
QtListWidget* categoryListWidget;
// all original code above would still work as expected
...
Want it done right? then do it yourself! :)
Your solution killed scrolling for me. I am using QListView. Here's another way:
In the constructor of the QListView's parent:
ui->listView->setSelectionMode(QAbstractItemView::NoSelection);
connect(ui->listView, SIGNAL(clicked(QModelIndex)), this, SLOT(on_listview_clicked(QModelIndex)));
In the connected slot:
on_listview_clicked(const QModelIndex &index)
{
if (index.isValid())
{
ui->listView->selectionModel->select(index, QItemSelectionModel::Toggle | QItemSelectionModel::Rows);
}
}
So, it only selects on a click.

Qt How to Prevent dropping item FROM application TO Windows File Explorer

I'm looking for a clean and cross-platform way to Prevent dropping an item FROM a Qt application TO Windows File Explorer (or other OS equiv.)
The following diagram shows the desired behavior:
I haven't had luck finding examples online or hacking a work-around together, but it seems like it would be a common-enough use-case that there would be a well designed and implemented solution floating around.
What I've tried and do not have working:
Detecting the Drag and Killing It:
detecting the QDragEnterEvent, QDragMoveEvent, QDragLeaveEvent
comparing the answerRect() or pos() of the event to the Geometry of
the Window or Widget to detect if the drag has left the application
This is pretty hacky (and not working at them moment) and I'm hoping you can point me towards a more elegant solution.
(UPDATE - tried changing mimeType, but Windows File Explorer still accepts the drop)
Changing the MIME Type to a custom type:
Pre: the "Widget w/ Drag & Drop" from the diagram above is a QTreeView with a QFileSystemModel model
Sub-classing the QFileSystemModel and overriding the mimeTypes() function like the code below
From the qDebug() output, it looks like the mimeType is correctly being set, but Windows File Explorer still accepts the drop :/
QStringList MyFileSystemModel::mimeTypes() const
{
QStringList customMimeTypes;
customMimeTypes << QString("UnicornsAndRainbows/uri-list");
qDebug() << "customMimeTypes: " << customMimeTypes;
return customMimeTypes;
}
Please let me know when you have a chance.
Thanks! :)
Dmitry Sazonov gave the correct answer. I will explain how I implemented it below. Dmitry, if you want cred, post it as an answer and not a comment so I can accept it as the answer.
What I did wrong on my question update based on Dmitry's suggestion was to override the QFileSystemModel::mimeTypes() when, in fact, I had to modify the QTreeView::mouseMoveEvent() and QTreeView::dropEvent().
//---------------------------------------------------------
void MyTreeView::mouseMoveEvent( QMouseEvent *event )
{
if( !(event->buttons() & Qt::LeftButton) )
{
return; // we only care about left mouse drags at the moment
}
if( (event->pos() - dragStartPosition).manhattanLength() < QApplication::startDragDistance() )
{
return; // a buffer when calculating waht qualifies as a "drag event"
}
QDrag *drag = new QDrag( this );
QMimeData *mimeData = new QMimeData();
QByteArray data;
const QStringList selectedPaths = this->getSelectedPaths(); // custom helper method
foreach( QString path, selectedPaths )
{
data.append( path ).append( ";" ); // using ';' as path deliminator
}
data.chop( 1 );
//--- this sets the custom MIME Type filter
mimeData->setData( CUSTOM_MIMETYPE_STRING, data );
drag->setMimeData( mimeData );
Qt::DropAction dropAction = drag->exec( Qt::CopyAction );
}
//---------------------------------------------------------
void MyTreeView::dropEvent( QDropEvent *event )
{
// ...
QList<QByteArray> paths;
//--- this filters based on our custom MIME Type
paths = event->mimeData()->data( CUSTOM_MIMETYPE_STRING ).split(';');
foreach( QByteArray path, paths )
{
// do something with the file paths
}
}

mousePressEvent called only from certain areas in scene

I have an application which draws lines based on different data from cars. I want my application to be able to select the lines drawn, and then make the corresponding item selected in a list on the left as well. The problem is that the mousePressEvent is only called when I press the mousebutton in the leftmost quarter of the scene. When it is called the curveSelected() function works as well, but I can't figure out why I can't invoke the mousePressEvent from the other areas on the scene.
First of all I have a mousePressEvent.
void DrawingScene:::mousePressEvent ( QGraphicsSceneMouseEvent * event ){
event->ignore();
bool leftbutton = (event->button() == Qt::LeftButton);
if(leftbutton)
{
qDebug() << "leftbutton";
emit leftButtonPress(event->scenePos());
}
QGraphicsScene::mousePressEvent(event);
}
Later connected:
connect(d_scene, SIGNAL(leftButtonPress(QPointF)), this, SLOT(curveSelected(QPointF)));
leftButtonPress is the signal emitted. Then I have the function which selects the item in the list. This method seems to work just fine. The problem exists without this function as well.
void CurveDrawer::curveSelected(QPointF pos){
QMapIterator<QPair<unitID, QString>, carData*> it(dataMap);
while(it.hasNext()){
it.next();
QPainterPath curPath = it.value()->pathItem->path();
if(curPath.contains(pos)){
for (int i = 0; i < list->count(); ++i) {
QListWidgetItem* curItem = list->item(i);
if(curItem == it.value()->listItem){
qDebug() << "curveSelected";
curItem->setSelected(true);
}
}
}
}
}
Anyone experienced something similar, or may see some obvious mistakes in my code?
EDIT:
How can i achieve that the mousePressEvent is called every time I click inside the scene? This is basically what I want it to do. Now it is only called when I click in certain area.
I tried to implement it with void DrawGraphicsView;;mousePressEvent(QMouseEvent *event) now, and the same problem existed there. The event just got invoked from certain areas in the scene.
The strange thing for me is that when a certain place in the scene is in the left of the viewport it is not possible to invoke the mousepressEvent, but when I scroll the same place to the right in the viewport, then it is suddenly possible to invoke the mousepressEvent. Does this make the problem clearer?

Editable QComboBox: synchronize edit text with item text

I've got a QComboBox which I want to be "automatically" editable. That is, every time a user manually changes current item's text, that text should "fall" to the underlying model automatically.
So far, I've reached this via a custom signal handler:
void setupUi() {
...
connect( someComboBox,
SIGNAL(editTextChanged(QString)),
SLOT(comboBoxEditTextChanged(QString)) );
...
}
void comboBoxEditTextChanged( const QString& text ) {
someComboBox->setItemText( someComboBox->currentIndex(), text );
}
So I wonder, is there a possibility to do this with less code? I've tried QComboBox::setInsertPolicy(QComboBox::InsertAtCurrent), but that didn't help.
EDIT: Current method with a custom slot works properly - but I'm asking if there's a method that does not involve any signals/slots.
To set the Text Automatically when USER changes it, we can edit your slot as follows:
void comboBoxEditTextChanged( const QString& text )
{
int index = someComboBox->findText(text);
if(index != -1)
{
someComboBox->setCurrentIndex(index);
}
someComboBox->setItemText( someComboBox->currentIndex(), text );
}
I hope this will resolve your issue
QComboBox can add items manually using
combo->additem("X");
combo->addItem(QString Y);
whereas you can manage the maximum number of items in it. Please go through the following link for details.
a link
So, in your slot,
void comboBoxEditTextChanged( const QString& text )
{
someComboBox->addItem(text);
}