Monospaced detailedText in QMessageBox - qt

I've been using a QMessageBox to display the outcome of a statistical test. It's nice, because I can put a summary result in the informative text, then the full result in the detailed text. The trouble is, the full result is a table, so I'd like it to be monospaced so that it looks right, and QMessageBox doesn't use a monospace font in the detailed text area.
So I'm looking at either subclassing QMessageBox, or subclassing QDialog to make something that looks like a QMessageBox but uses a monospace font in the detailed text area. I'm a bit rusty at the moment, and having a hard time figuring out which is the better option. Could I subclass QMessageBox, just add my own QTextEdit and my own "show detailed text" button, and leave the QMessageBox detailed text area and button hidden? Or is there some easier way to do this?

You can use html text in the fields of a QMessageBox, that would be the easiest way. As a hint, try putting
<pre>Test</pre>
in your QString.
Any other customization of the message box will probably imply a subclass, though.

I did not find better than this:
setStyleSheet("QTextEdit { font-family: monospace; }");
It's a bit hacky because (1) it uses stylesheets, which may conflicts with your way of styling your widget and (2) it relies on the fact that the detailed text is in a QTextEdit and is the only such element, which is not officially ensured by the API. But it works. :D

Here is a working example, based on the answer of Lithy:
import sys
from PySide2.QtCore import *
from PySide2.QtWidgets import *
table_text = '\
Name Flower\n\
------ -------\n\
Violet Yes\n\
Robert No\n\
Daisy Yes\n\
Anna No\n\
'
class Widget(QWidget):
def __init__(self, parent= None):
super(Widget, self).__init__()
warning_text = 'warning_text'
info_text = 'info_text'
pt = 'colour name'
msg = QMessageBox()
msg.setIcon(QMessageBox.Warning)
msg.setText(warning_text)
msg.setInformativeText(info_text)
msg.setDetailedText("{}".format(table_text))
msg.setTextInteractionFlags(Qt.TextSelectableByMouse)
# print all children and their children to find out which widget
# is the one that contains the detailed text
for child in msg.children():
print('child:{}'.format(child))
print(' {}'.format(child.children()))
pname = 'QMessageBox'
cname = 'QTextEdit'
msg.setStyleSheet(
"""{} {} {{ background-color: red; color: black; font-family: Courier; }}""".format(pname, cname))
msg.exec_()
if __name__ == '__main__':
app = QApplication(sys.argv)
Widget()

You can use html text in the detailedText of a QMessageBox using this hack:
QString html_formatted_text;
QMessageBox mb;
mb.setDetailedText(html_formatted_text);
// Find detailed text widget
auto te = mb.findChild<QTextEdit*>();
if (te) {
te->setHtml(mb.detailedText());
}
mb.exec();

Related

Clickable hyperlink in QTextEdit

I want to use QTextEdit (in read-only mode) to show a clickable hyperlink, I used to do
QTextEdit *textEdit = new QTextEdit;
QTextCursor cursor(textEdit->document());
textEdit->setTextCursor(cursor);
cursor->insertHtml("<a href=\"www.google.com\" >Google</a>");
textEdit->show();
this code will show Google as hyperlink but unable to click.
And if I used
QTextEdit *textEdit = new QTextEdit;
QTextCursor cursor(textEdit->document());
textEdit->setTextCursor(cursor);
QTextCharFormat linkFormat = cursor.charFormat();
linkFormat.setAnchor(true);
linkFormat.setAnchorHref("http://www.google.com");
linkFormat.setAnchorName("Google");
cursor.insertText("Google", linkFormat);
then nothing happen. "Google" is just normal text.
Please help me insert clickable hyperlink to QTextEdit.
Using QTextBrowser is simpler (as suggested by another answer). However, if for some reason you want to use QTextEdit, try to change the text interaction flags using setTextInteractionFlags().
I think you have to enable the Qt::LinksAccessibleByMouse flag.
See Qt::TextInteractionFlag and QTextEdit::textInteractionFlags
To have clickable hyperlink in QTextEdit, you can use
QTextCharFormat::setAnchorHref to set the link for some text
QWidget::mousePressEvent to capture mouse press event
QTextEdit::anchorAt to obtain the link
Here's the minimal working PyQt example,
import sys
from PyQt5.Qt import QDesktopServices, QUrl, QApplication, QColor, Qt
from PyQt5.QtWidgets import QTextEdit
class MyWidget(QTextEdit):
def mousePressEvent(self, e):
self.anchor = self.anchorAt(e.pos())
if self.anchor:
QApplication.setOverrideCursor(Qt.PointingHandCursor)
def mouseReleaseEvent(self, e):
if self.anchor:
QDesktopServices.openUrl(QUrl(self.anchor))
QApplication.setOverrideCursor(Qt.ArrowCursor)
self.anchor = None
app = QApplication(sys.argv)
editor = MyWidget()
cursor = editor.textCursor()
fmt = cursor.charFormat()
fmt.setForeground(QColor('blue'))
address = 'http://example.com'
fmt.setAnchor(True)
fmt.setAnchorHref(address)
fmt.setToolTip(address)
cursor.insertText("Hello world again", fmt)
editor.show()
app.exec_()
As far as I've tried, when using QTextEdit + Qt::LinksAccessibleByMouse I'm able to click on links, but no action is taken (i.e., link is not open). The only action possible is to right-click on the link and select Copy Link Location.
As mentioned, one option is using QTextBrowser. In this case you have to set the QTextBrowser::openExternalLinks property too, in order to open the link using the default browser, otherwise it will be open in the text-browser widget.
Another option, given you have a read-only text, is to use a QLabel with rich format, and using the QLabel::linkActivated signal to open the URL
label->setTextFormat(Qt::RichText);
QObject::connect(label, &QLabel::linkActivated, [](const QString & link) {
QDesktopServices::openUrl(link);
});
You can use QTextBrowser instead of QTextEdit if it read only text.
In order for setTextInteractionFlags() to work the openExternalLinks property needs to be set. Since this property is not available on QTextEdit, here is a little hack to enable it.
auto &clist = edit->children();
for each (QObject *pObj in clist)
{
QString cname = pObj->metaObject()->className();
if (cname == "QWidgetTextControl")
pObj->setProperty("openExternalLinks", true);
}
This does not address the mouse cursor, so you will still need to override mouseMoveEvent.

Autodesk Maya model panel resize event

I'm writing a simple tool menu for Maya, and I'd like to stick it to the border of model panel (perspective).
from PySide import QtCore, QtGui
from maya import OpenMayaUI as omui
from shiboken import wrapInstance
class TestWidget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent = self.getMayaWindow())
self.setWindowFlags(QtCore.Qt.Tool | QtCore.Qt.FramelessWindowHint)
self.setFixedSize(100, 100)
panelPtr = omui.MQtUtil.findControl('modelPanel4')
panel = wrapInstance(long(panelPtr), QtGui.QWidget)
position = panel.mapToGlobal(panel.pos())
self.move(position.x(), position.y() + panel.geometry().height() / 2 - self.geometry().height() / 2)
mainLayout = QtGui.QVBoxLayout(self)
button = QtGui.QPushButton('CLOSE')
button.setFixedSize(80, 80)
button.clicked.connect(self.deleteLater)
mainLayout.addWidget(button)
def getMayaWindow(self):
omui.MQtUtil.mainWindow()
ptr = omui.MQtUtil.mainWindow()
return wrapInstance(long(ptr), QtGui.QWidget)
w = TestWidget()
w.show()
The main widget is positioned exactly where I want when it is created (horizontally on the left side of model panel, vertically - in the middle of model panel).
I need to reposition it accordingly when the model panel is resized, but model panel does not emit resized() signal. I'd appreciate any advise.
I've been trying many things to get this working yesterday. I did some additionnal researches today and came to this topic: cgsociety: Creating a floating button inside the viewport
In case of broken link, this is one of the answer:
You can use geometry but there are some issues with triggering
commands based on selection and the undo queue. If you want to go that
route, I would suggest looking into zooHud and zooTriggers (Part of
the zooToolbox)
If you are wanting actual GUI control parented to the viewport, mel
only offers hudslider, hudbutton, and headsUpMessage.
You can also use PyQt and parent in your own custom widgets/layouts or
whatever you want using something like this:
import maya.OpenMayaUI as apiUI import sip
from PyQt4 import QtGui
view = apiUI.M3dView()
apiUI.M3dView.getM3dViewFromModelPanel('modelPanel4', view)
viewWidget = sip.wrapinstance(long(view.widget()), QtCore.QObject)
global myBtn
myBtn = QtGui.QPushButton(viewWidget)
myBtn.setText('testing!')
myBtn.move(100, 100) #Relative to top-left corner of viewport myBtn.show()
You can do anything a full qt widget can do with that, so it's
extremely flexible. but it would require having PyQt installed, which
can be a barrier depending on your tools distribution.
I did a mix of this answer and your code:
from PySide import QtCore, QtGui
from maya import OpenMayaUI as omui
from shiboken import wrapInstance
class CustomQWidget(QtGui.QWidget):
def __init__(self, *args, **kwargs):
QtGui.QWidget.__init__(self, *args, **kwargs)
mainLayout = QtGui.QVBoxLayout(self)
closeButton = QtGui.QPushButton('CLOSE')
closeButton.setFixedSize(80, 40)
closeButton.clicked.connect(self.deleteLater)
helloButton = QtGui.QPushButton('HELLO')
helloButton.setFixedSize(80, 40)
helloButton.clicked.connect(self.printHello)
#Trying to fix glitchy background / Doesn't work, why?
#Is it because layouts don't have background?
p = self.palette()
p.setColor(self.backgroundRole(), QtCore.Qt.red)
self.setPalette(p)
self.setAttribute(QtCore.Qt.WA_StyledBackground, True)
##############################
mainLayout.addWidget(closeButton)
mainLayout.addWidget(helloButton)
def printHello(self):
print "Hello"
view = omui.M3dView()
omui.M3dView.getM3dViewFromModelPanel('modelPanel4', view) #Given the name of a model panel,
#get the M3dView used by that panel. If this fails, then a panel with the given name could not be located.
viewWidget = wrapInstance(long(view.widget()), QtGui.QWidget)
position = viewWidget.mapToGlobal(viewWidget.pos())
w = CustomQWidget(viewWidget)
w.move(0, viewWidget.geometry().height() / 2 - 100 / 2) #Relative to middle-left corner of viewport
w.show()
One of the issue I have it that the background of the widget is glitched:
If anyone knows why and how to fix it, I'll edit my answer with pleasure.
Else, when running this script from Maya's script editor, the widget follows the panel when it is resized.
I did fix such a problem, but not using Python/PyQt.
The problem itself is, that your Qt Widget is there. I have not found a way to make it not paint its background.
My solution was different: I derived from a Qt Layout, pushed all my widgets into that layout and used MQtUtil to get the QWidget of that modelPanel's modelEditor to attach the "real Qt layout" to it.
Heavy caveat that may make Python not suited: Maya doesn't expect "non-Maya" Layouts to be bound to "real-Maya" Widgets like modelEditors. So you need to listen to QEvents and find out when to destroy your layout, so Maya doesn't crash trying.
set autofillbackground True to fix your background painting issue

How to change font of a QMessageBox in Qt?

I was trying to build a simple application with a QComboBox and a QPushButton. The idea is to populate the QComboBox with a list of all available fonts in the system. When the user selects a font and presses the QPushButton then a QMessageBox appears with the font selected. Now how to do it?
The solution is using the setFont() method of the QMessageBox
QMessageBox *msg = new QMessageBox(QMessageBox::Information, "Message with font",
"This message is in font: " + ui->comboBox->currentText(),
QMessageBox::Ok | QMessageBox::Cancel, this);
QFont font = QFont(ui->comboBox->currentText());
msg->setFont(font);
msg->exec();
Where combobox is QComboBox used.
You can use basic HTML markups when setting the text to your message box label. The markup supported by QLabel includes <font>.This method also allows more versatile formatting.
As before suggested you could use styles in your Html blocks (in my example add style to the paragraphs):
QMessageBox.about(
self,
"About",
"<font>"
"<p style='font-family: Terminal'>An simple app.</p>"
"<p style='font-family: Georgia, 'Times New Roman'>- PyQt</p>"
"<p>- Qt Designer</p>"
"<p>- Python3</p>",
)
Results in:
QMessageBox

How to set stylesheet for a single item in the Qlistview?

I have a QListView containing QStandardItems . How to set stylesheet for a single item in the Qlistview based on the QModelIndex acquired?
If you use QListWidget instead of QListView, you can call QListWidget::setItemWidget() and you can customize how individual items look by applying a stylesheet to the items that you add. You need to make sure that your item widget class inherits from QWidget and you can apply styles to the widget using QSS like so in your constructor:
setStyleSheet("WidgetItem:pressed { background-color: #444444; }");
Here's a reference to QSS: http://qt-project.org/doc/qt-4.8/stylesheet-examples.html
I still see the Widget-classes documented in Qt Documentation for Qt 5.7 -
Qt Widgets Widgets Classes.
Reference: http://doc.qt.io/qt-5/widget-classes.html
There seems to be no way to set a stylesheet in the Model-View conecpt. But what exist is the FontRole. If you want to make an entry bold or italic or change the size then the FontRole can do that. If you want to change the color then you have to find some other solution.
Here is an example to make certain entries bold in python:
def data(self, index, role=QtCore.Qt.DisplayRole):
# use parent class to get unaltered result
res = super().data(index, role=role)
if role == QtCore.Qt.FontRole:
# modify FontRole
if index.row() = 3:
# row number 3 should be bold
if res is None:
# parent class didn't return a QFont so make one
res = QtGui.QFont()
# set bold attribute in font
res.setBold(True)
return res
The above data() method sets the 4th row bold (rows are counted from 0). I leave translating to C++ to the reader.

How can I change the QStyle properties in PyQt4?

I'd like to change the QStyle::PM_TabBarTabHSpace property for a PyQt application. I read the Qt document for QStyle, but I'm not sure how to set this correctly in PyQt.
Non-working code:
style = QStyleFactory.create('Cleanlooks')
style.PM_TabBarTabHSpace = 5 # 5 pixels?
app.setStyle(style)
This code runs, but it doesn't change the padding on the tabbar tabs. I tried using stylesheets to change the tabbar padding, but that ruins the graphics drawing, so that none of the default look-feel stuff gets drawn (I don't want to reimplement all the ui drawing).
I think I might need to use QProxyStyle, but I can't find any examples of how to use this in PyQt4. Edit: It seems that PyQt doesn't have QProxyStyle, as from PyQt4.QtGui import QProxyStyle fails.
Can someone please post an example of changing the value of PM_TabBarTabHSpace? Thanks.
Edit Here is a skeleton code. Changing the PM_TabBarTabHSpace value doesn't do anything. :(
from PyQt4.QtGui import (QApplication, QTabWidget, QWidget,
QStyle, QStyleFactory)
def myPixelMetric(self, option=None, widget=None):
if option == QStyle.PM_TabBarTabHSpace:
return 200 # pixels
else:
return QStyle.pixelMetric(option, widget)
style = QStyleFactory.create('Windows')
style.pixelMetric = myPixelMetric
app = QApplication('test -style Cleanlooks'.split())
# Override style
app.setStyle(style)
tab = QTabWidget()
tab.addTab(QWidget(), 'one')
tab.addTab(QWidget(), 'two')
tab.show()
app.exec_()
QStyle.pixelMetric(...) is built-in class method. You can not set via function pointing. Because, it is in C code. You can test it with adding
def myPixelMetric(self, option=None, widget=None):
print 'Debug, i am calling'
...
in your myPixelmetric function. You need to subclass Style object to achieve this. Here is an example:
class MyStyle(QCommonStyle):
def pixelMetric(self, QStyle_PixelMetric, QStyleOption_option=None, QWidget_widget=None):
if QStyle_PixelMetric == QStyle.PM_TabBarTabHSpace:
return 200
else:
return QCommonStyle.pixelMetric(self, QStyle_PixelMetric, QStyleOption_option, QWidget_widget)
app = QApplication('test -style Cleanlooks'.split())
app.setStyle(MyStyle())
This code snippet will work, but it is ugly. I prefer using stylesheets over manipulating Style.

Resources