Qt. QSplitter. Handle double click when cursor is under splitter - c++

I need to handle double click for QSplitter when cursor is under splitter.
I redefined mouseDoubleClickEvent. But this does not work for this case.
When I do doulble click when cursor is under splitter (ready to move splitter) the method is not calling.

You can use an event filter to filter all events going to the handle of Qsplitter :
bool MyClass::eventFilter(QObject * obj, QEvent * event)
{
if(event->type()==QEvent::MouseButtonDblClick)
{
...
}
return false;
}
Also don't forget to install event filter in the constructor of your class :
MyClass::MyClass(QWidget *parent):QWidget(parent)
{
...
ui->splitter->handle(1)->installEventFilter(this);
...
}

I needed the same in order to be able to evenly space the widgets in the splitter when the user double clicks on the handle (this was my use case). Overriding QSplitter.mouseDoubleClickEvent() does not work because it seems that the handle consumes the double click event itself so it is not propagated to the parent QSplitter. The solution proposed in the accepted answer using eventFilter is quite good but is has the disadvantage that it is not 'dynamic', i.e. the event filter is not installed when the user adds new widgets to the splitter during runtime. So we need to find a way to install the event filter dynamically. There are two options to achieve this:
Override QSplitter.addWidget() and QSplitter.insertWidget():
# inside class MySplitter
def addWidget(self, widget):
super(MySplitter, self).addWidget(widget) # call the base class
self.handle(self.count() - 1).installEventFilter(self)
def insertWidget(self, index, widget):
super(MySplitter, self).insertWidget(index, widget) # call the base class
self.handle(index).installEventFilter(self)
but this is a bit problematic when the user adds widgets by not using these two methods but by setting parent to the child widget, though this is discouraged by the docs - see: http://doc.qt.io/qt-5/qsplitter.html#childEvent
Intercept the childEvent(), which feels a bit hacky but is error proof:
# inside class MySplitter
def childEvent(self, event):
if event.added():
# Note that we cannot test isinstance(event.child(), QSplitterHandle)
# because at this moment the child is of type QWidget,
# it is not fully constructed yet.
# So we assume (hacky but it works) that the splitter
# handle is added always as the second child after
# the true child widget.
if len(self.children()) % 2 == 0:
event.child().installEventFilter(self)
super(MySplitter, self).childEvent(event) # call the base class
I am using b) and it works for me quite well. This has the advantage that you do not need to subclass (I do, however, for teh sake of simplicity), you can install another event filter to intercept the childEvent and install the event filter from outside.
Sorry my code is in PyQt, but I think it is idiomatic enough and easily translated to C++.

Related

PyQt5 QComboBox list items changing postion

I am facing some issue with the display style of Qcombobox items. Currently am hardcoding the data to be shown in the combobox.
here is the code :
self.Dummy = QComboBox(self)
self.Dummy.setGeometry(200,600, 350, 50)
self.Dummy.setStyleSheet("QComboBox {background-color: white;border-style: outset;" border-width: 2px;border-radius: 5px;border-color: #448aff; font: 12px; min-width: 10em; padding: 3px;}")
self.Dummy.addItems(["-Select-", "2", "3","4","5","6","7","8","9","0","11",])
The issue is that the dropdown "list" postion keeps changing after each selection. Here is the image of the issue am facing.
Below is my combobox
The list contains items <-Select->,2,3,4,5,6,7,8,9,0,11 , where <-Select-> will be the first element shown.
Now when I click the box, the box list "down" the elements and suppose I selected '2'. Then, if I try to select another item, the list will be dropped in a "downwards" direction. see below
Now, say if selected the last element from the items, '11'. Now if I try to select a new item by clicking on the box, the list will be popped "up" instead of down. see below
What should be done to fix it ? I don't think its an issue with stylesheet, without it also, this issue is happening. The reason I need this to be fixed is that when the list is popping up, its covering the label above it
What you see is a behavior that is OS and style dependent.
To avoid it, the best way is to subclass QComboBox and overwrite showPopup(), then we call the base class implementation (which is responsible of showing, resizing and positioning the popup view) and move it if necessary.
class Combo(QtWidgets.QComboBox):
def showPopup(self):
super().showPopup()
# find the widget that contains the list; note that this is *not* the view
# that QComboBox.view() returns, but what is used to show it.
popup = self.view().window()
rect = popup.geometry()
if not rect.contains(self.mapToGlobal(self.rect().center())):
# the popup is not over the combo, there's no need to move it
return
# move the popup at the bottom left of the combo
rect.moveTopLeft(self.mapToGlobal(self.rect().bottomLeft()))
# ensure that the popup is always inside the edges of the screen
# we use the center of the popup as a reference, since with multiple
# screens the combo might be between two screens, but that position
# could also be completely outside the screen, so the cursor position
# is used as a fallback to decide on what screen we'll show it
done = False
for i, pos in enumerate((rect.center(), QtGui.QCursor.pos())):
for screen in QtWidgets.QApplication.screens():
if pos in screen.geometry():
screen = screen.geometry()
if rect.x() < screen.x():
rect.moveLeft(screen.x())
elif rect.right() > screen.right():
rect.moveRight(screen.right())
if rect.y() < screen.y():
rect.moveTop(screen.y())
elif rect.bottom() > screen.bottom():
# if the popup goes below the screen, move its bottom
# *over* the combo, so that the its current selected
# item will always be visible
rect.moveBottom(self.mapToGlobal(QtCore.QPoint()).y())
done = True
break
if done:
break
popup.move(rect.topLeft())
This can also be done without subclassing (for example if you have many combos, you created the UI from Designer and don't want to use promoted widgets), but you'll have to remember to change all referencies to the combo.
class MyWindow(QtWidgets.QWidget):
def __init__(self):
# ...
self.combo = QtWidgets.QComboBox()
self.combo.showPopup = self.showPopupAndCheck
def showPopupAndCheck(self):
QtWidgets.QComboBox.showPopup(self.combo)
popup = self.view().window()
rect = popup.geometry()
if not rect.contains(self.combo.mapToGlobal(self.combo.rect().center())):
# the popup is not over the combo, there's no need to move it
return
# change everything from self to self.combo
Alternatively, if you want to keep this behavior consistent through all your program without always using the subclass, you can use some sort of monkey patching hack.
The advantage is that any QComboBox you create (even when loading UI files or creating a combo at runtime) will always use the new behavior.
Important: this MUST be at the very beginning of the main file of your program, possibly just after the import section.
from PyQt5 import QtCore, QtGui, QtWidgets
def customShowPopup(self):
# we can't use super(), because we're not in the class definition, but
# calling the class method with "self" as first argument is practically the
# same thing; note the underscore!
QtWidgets.QComboBox._showPopup(self)
popup = self.view().window()
# ... go on, exactly as above
# create a new reference to the showPopup method, which is the one we've used
# in the function above
QtWidgets.QComboBox._showPopup = QtWidgets.QComboBox.showPopup
# overwrite the original reference with the new function
QtWidgets.QComboBox.showPopup = customShowPopup

Pyqt get qlistwidget item when widget inside itemwidget is clicked

this is the current application looks like this:
It has a Qlistwidget listWidget_links where each item has own itemwidget set (combobox, checkbox, button, ...) Now I came upon a problem that neither google or I can solve.
If the user presses the create button or any other item's itemwidget how do I let a method know the item the widget was pressed changed inside.
item_widget.comboBox_type.currentIndexChanged.connect(self.itemupdate_linktype)
item_widget.checkBox_hide.stateChanged.connect(self.itemupdate_hidden)
cbox = self.sender() # gives the widget that released the signal
cbox.parent() #I discovered by a lucky try, returns NodeLinkItemWidgetUI.Ui_Form object which is a item's itemwidget
Here is how items get created to in order to understand the program structure better:
def createNewLink(self, nodename, nodeclass):
item = QtWidgets.QListWidgetItem(self.listWidget_links)
item_widget = NodeLinkItemWidgetUI.Ui_Form(nodename, nodeclass)
item.nodename = nodename
item.nodeclass = nodeclass
item.setSizeHint(QtCore.QSize(130, 160))
self.listWidget_links.addItem(item)
self.listWidget_links.setItemWidget(item, item_widget)
Edit: solved setting the variable item_widget.item = item seems to work, but is there a more elegant way?

Inline renaming of a tab in QTabBar/QTabWidget

I really want to have a QTabWidget that can be renamed by double-click on tab caption.
I have googled and found this solution, but it's more like an outline for a developer who knows his way through Qt and subclassing it's widgets.
I'm kind of stuck how to implement all that. I have used an example given further down that thread (with IndependentLineEdit and so on) and it worked, but it's not what i wanted.
I do not want to have any kind of InputDialog. I do not want to have any floating widget or something.
What i basically want is to use QTabWidget (subclass), that behaves the similar way as spreadsheet tabs do in modern office suites - like, tab label is substituted with seamless line edit, which resets tab label on Enter or leaves it intact on Esc.
I have not been able to find such a solution so far. I do understand that what I actually need is very close to this:
provide a temporary QLineEdit at QTabBar::tabRect() filled with QTabBar::tabText()
but i do not understand how to do that. Moreover, since QTabBar is kind of bare tabbar i would also prefer to have in enclosed into QTabWidget (subclass).
The following is an implementation of such behavior in Python in PyQt4. It should be easy to convert that to C++. Please note that Qt5 has a nice signal tabBarDoubleClicked which is missing in Qt4. This piece of code creates such signal as well.
from PyQt4 import QtGui
from PyQt4.QtCore import pyqtSignal, pyqtSlot
class QTabBar(QtGui.QTabBar):
"""QTabBar with double click signal and tab rename behavior."""
def __init__(self, parent=None):
super().__init__(parent)
tabDoubleClicked = pyqtSignal(int)
def mouseDoubleClickEvent(self, event):
tab_index = self.tabAt(event.pos())
self.tabDoubleClicked.emit(tab_index)
self.start_rename(tab_index)
def start_rename(self, tab_index):
self.__edited_tab = tab_index
rect = self.tabRect(tab_index)
top_margin = 3
left_margin = 6
self.__edit = QtGui.QLineEdit(self)
self.__edit.show()
self.__edit.move(rect.left() + left_margin, rect.top() + top_margin)
self.__edit.resize(rect.width() - 2 * left_margin, rect.height() - 2 * top_margin)
self.__edit.setText(self.tabText(tab_index))
self.__edit.selectAll()
self.__edit.setFocus()
self.__edit.editingFinished.connect(self.finish_rename)
#pyqtSlot()
def finish_rename(self):
self.setTabText(self.__edited_tab, self.__edit.text())
self.__edit.deleteLater()
class QTabWidget(QtGui.QTabWidget):
"""QTabWidget with double click on tab signal and tab rename behavior."""
def __init__(self, parent):
super().__init__(parent)
self.setTabBar(QTabBar())
self.tabBar().tabDoubleClicked.connect(self.tabBarDoubleClicked)
tabBarDoubleClicked = pyqtSignal(int)
Please note that there is a rather hacky solution to aligning the qlineedit within the tab header. It is achieved by fixing the margin to a certain value. I'm sure it is possible to look that value up in the style if necessary.

QDialog on accept return custom class object

I'm using qt-creator to build a little QT application.
I have a main window where I have some controls like a "new contact" button.
Pressing the button a QDialog is shown, it contains 3 line edits: name, mobile and email.
The dialog is shown through the Signal/Slot system. It works fine but I want to create a Contact object when OK is clicked and I want to give back that Contact to my main window in order to put it in a QList created in the main window code.
The approach is:
QMainWindow -> new contact -> QDialog is shown
QDialog -> ok -> QMainWindow
Should I pass the QList from the main window to the QDialog as argument or there is a best way?
Should I pass the QList from the main window to the QDialog as argument or there is a best way?
In my opinion, best would be a custom QDialog subclass with three QLabels and 3 QLineEdits.
The labels would get the following type of values:
Label 1: name
Label 2: mobileNumber
Label 3: email
You would use then QLabels and QLineEdits to display them with the input coming from the user.
Then, as your program probably already does, just handle the "accept" event respectively. You could use the following method to retrieve the text entered by the end user:
text : QString
This property holds the line edit's text.
Setting this property clears the selection, clears the undo/redo history, moves the cursor to the end of the line and resets the modified property to false. The text is not validated when inserted with setText().
The text is truncated to maxLength() length.
By default, this property contains an empty string.
Then, in the handler of the accepted signal, you could call three accessor methods, like:
QString name() const { return nameLineEdit->text(); }
QString mobileNumber() const { return mobileNumberLineEdit->text(); }
QString email() const { return emailLineEdit->text(); }
You could also store that in a dedicated structure depending on you build up your data representation, so the structure would be something like this:
struct Contact {
QString name;
QString mobileNumber;
QString email;
};
and then you would have the accessor for that as follows
Contact contact() const;
Make a subclass of QDialog. Call Yourclass::exec() to show the dialog (exec is a function in QDialog), then afterwards Yourclass::contactDetails() to get them. contactDetails is a perfectly ordinary member function that you have to write.

how to use ribbon category to switch view?

I want to use two ribbon category buttons to switch between two different views, but I found it can't add event handler to the button.
Is there any hint to solve this problem? Better if there is some sample, actually I'm new to MFC.
You could try hooking into this event?
AFX_WM_ON_CHANGE_RIBBON_CATEGORY
An option I have found successful was to subclass CMFCRibbonBar and override PreTranslateMessage and check for mouse clicks. Below are the steps I took which have thus far worked well.
Subclass CMFCRibbon - in my example I created CCustomRibbonBar
Override PreTranslateMessage, and add an int value to keep track of the tab
Create a custom windows message that your applications MainForm handles -WM_ACTIVE_RIBBON_TAB_CHANGED in my example
Inside of PreTranslateMessage check for Left Mouse Up event
In the event of a left mouse button up, let the Ribbon finish handling the message and then query the Active Category.
Post the active category to MainForm (or other form)
In your MainForm, handle the category and take into account that with most events the category will not have changed.
Then in my override I check for the mouse up event and retrieve the Active category
Inside Class Declaration
virtual BOOL PreTranslateMessage(MSG* pMsg);
int m_LastActiveCategory;
Inside Class Definition
BOOL CCustomRibbonBar::PreTranslateMessage(MSG* pMsg)
{
//If command was finishing a click
if(pMsg->message == WM_LBUTTONUP && pMsg->wParam == 0)
{
//Allow ribbon to handle itself first
BOOL result = CMFCRibbonBar::PreTranslateMessage(pMsg);
//Get new active tab
int activeTab = GetCategoryIndex(GetActiveCategory());
//If tab has changed, forward message
if(activeTab != m_LastActiveCategory)
{
//forward message to parent
::SendMessage(GetParentFrame()->GetSafeHwnd(),WM_ACTIVE_RIBBON_TAB_CHANGED,activeTab,0);
m_LastActiveCategory = activeTab;
}
return result;
}
//Otherwise handle like normal
return CMFCRibbonBar::PreTranslateMessage(pMsg);
}