4

So I have a graph tool of sorts using a PyQt5 gui, and giving the user the ability to color nodes and name them. But you can imagine that depending on what color you color the nodes, the text can be illegible. So if there was a way to get a black border on a QFont, then I can have text white and have it show up well over anything. Is this possible given the framework?

I'm also open to any solution that solves the problem of being able to read text over any color as well. Thank you.

Edit:

from PyQt5 import QtGui, QtWidgets


class MyPushButton(QtWidgets.QPushButton):

    def __init__(self, text):
        super(MyPushButton, self).__init__()
        self.setFixedHeight(50)

        self.font = QtGui.QFont()
        self.setupFont()
        self.setFont(self.font)

        self.setStyleSheet('color: white; background-color: yellow')
        self.setText(text)
        self.clicked.connect(self.change_color)

    def change_color(self):
        color = QtWidgets.QColorDialog.getColor()
        if color.isValid():
            self.setStyleSheet('color: white;background-color:' + color.name())

    def setupFont(self):
        self.font.setFamily('Palatino')
        self.font.setPointSize(20)
        # some other font manipulations


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)

    widget = QtWidgets.QWidget()
    layout = QtWidgets.QVBoxLayout()

    push1 = MyPushButton('test text 1')
    layout.addWidget(push1)

    push2 = MyPushButton('test text 2')
    layout.addWidget(push2)

    widget.setLayout(layout)
    widget.show()
    sys.exit(app.exec_())

I want:

enter image description here

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Ned U
  • 401
  • 2
  • 7
  • 15
  • you could provide a [mcve], why do not you check the background color and set a font color according to that case? – eyllanesc Sep 11 '18 at 01:14
  • The way the color changes is based on the color spectrum, not a discrete subset of possible colors, so mapping like white font to dark colors and black font to light colors would be complicated in terms of figuring when to switch, also it would not be uniform with a bunch of different node colors and different font colors on the same graph. – Ned U Sep 11 '18 at 16:00
  • What do you mean by: *So if there was a way to get a black border on QFont* ?, you could show a picture of what you get and want to get in cases where you have the problem. – eyllanesc Sep 11 '18 at 16:03
  • Sure. I edited the sample to show an more relevant use case where you can't see text due to the color background color. So I wanted to get something like this https://i.imgur.com/Z6NP8zV.png – Ned U Sep 11 '18 at 16:21
  • Maybe QFont can not implement what you want, maybe there is another method so it is better to particularize the problem so I ask: what are the nodes? Are the nodes QPushButton or are they another widget ?, is that maybe there is no general solution so you could start a particular solution for a particular widget and it is better to use the node widget since that is your goal. – eyllanesc Sep 11 '18 at 16:27
  • have you tried this...? https://stackoverflow.com/questions/18974194/text-shadow-with-python – Any Moose Sep 11 '18 at 16:33
  • @eyllanesc they are push buttons, but circular ofc. – Ned U Sep 11 '18 at 16:45
  • @AnyMoose using a shadow as a mock border looks kind of promising, if I can get it to be straight on and slightly larger than the text. But my experience with QGraphicsDropShadowEffect is that they act like normal shadows, where they're just a small translation away, so it might or might not be possible as well. Thanks for the suggestion. – Ned U Sep 11 '18 at 16:55
  • @NedU I try to use QGraphicsDropShadowEffect but I see that it is not possible since it applies to the edge of the widget and does not consider the text, I see that there is another possibility: to use QML, for that I ask you what QPushButton signals do you need or what properties? is to emulate the behavior of QPushButton using QML but that can be used with classic widgets. – eyllanesc Sep 11 '18 at 17:07
  • @eyllanesc I hook up a lot of things to mousePressEvent. Right click menus, the left click for selection, that changes the color of the background of the selected node to white, and using the built in the setText rather than having to make a label and set it explicitly. I don't have any experience using QML, but you mean you can achieve the desired result just using overriding QWidgets.QWidget? – Ned U Sep 11 '18 at 17:20
  • @NedU From what I understand you want to access mousePressEvent or at least the right click, left click and set the text, am I right ?. On the other hand I see it difficult to implement what you want using the QtWidgets, my idea is to create all that behavior in QML and expose the properties to python and for that I could use QQuickWidget. – eyllanesc Sep 11 '18 at 17:24
  • @eyllanesc. Correct. I mean if it's not worth the time to do it, it's ok. I had a feeling this wouldn't be feasible anywat. But I appreciate the help. – Ned U Sep 11 '18 at 18:53

1 Answers1

2

One possible solution is to use QML to create that effect, and for you to use it with classic widgets you should use QQuickWidget as shown below:

main.qml

import QtQuick 2.9
import QtQuick.Controls 2.4

Button {
    id: control
    property color color: "white"
    property color _color:  down ? Qt.lighter(color, 1.1):  color
    property color light_color: Qt.lighter(control._color, 1.1)
    property color dark_color: Qt.darker(control._color, 1.1)
    contentItem: Text {
        text: control.text
        font: control.font
        color: "white"
        style: Text.Outline
        styleColor: "black"
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
    }

    background: Rectangle {
        opacity: enabled ? 1 : 0.3
        border.width: 0.7
        border.color: "gray"
        radius: 2
        gradient: Gradient {
            GradientStop { position: 0 ; color: control.pressed ? control._color : control.light_color }
            GradientStop { position: 1 ; color: control.pressed ? control.dark_color : control._color }
        }
    }
    onClicked: obj.clicked()
}

main.py

from PyQt5 import QtCore, QtGui, QtWidgets, QtQuickWidgets

class ButtonQMl(QtQuickWidgets.QQuickWidget):
    clicked = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super(ButtonQMl, self).__init__(parent)
        self.setSource(QtCore.QUrl.fromLocalFile("main.qml"))
        self.setResizeMode(QtQuickWidgets.QQuickWidget.SizeRootObjectToView)
        self.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
        self.engine().rootContext().setContextProperty("obj", self)
        self.setFixedHeight(50)

    def text(self):
        return self.rootObject().property("text") if self.rootObject() else  ""

    def setText(self, text):
        if self.rootObject() and self.text() != text:
            self.rootObject().setProperty("text", text)

    def color(self):
        return self.rootObject().property("color") if self.rootObject() else  ""

    def setColor(self, color):
        color = QtGui.QColor(color)
        if self.rootObject() and self.color() != color:
            self.rootObject().setProperty("color", color)

    def font(self):
        return self.rootObject().property("font") if self.rootObject() else  ""

    def setFont(self, font):
        if self.rootObject() and self.font() != font:
            self.rootObject().setProperty("font", font)


class MyButton(ButtonQMl):
    def __init__(self, parent=None):
        super(MyButton, self).__init__(parent)
        self.clicked.connect(self.change_color)
        self.setupFont()

    def change_color(self):
        color = QtWidgets.QColorDialog.getColor()
        if color.isValid():
            self.setColor(color)

    def setupFont(self):
        fnt = self.font()
        fnt.setFamily('Palatino')
        fnt.setPointSize(20)
        self.setFont(fnt)

    def mousePressEvent(self, event):
        print("event: ", event)
        super(MyButton, self).mousePressEvent(event)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)

    w = QtWidgets.QWidget()
    lay = QtWidgets.QVBoxLayout(w)
    btn1 = MyButton()
    btn1.setText("Hello World");
    btn1.setColor(QtCore.Qt.yellow)

    btn2 = QtWidgets.QPushButton("Click me")
    btn2.setFixedHeight(50)
    btn2.setStyleSheet("color: white; background-color: yellow")

    lay.addWidget(btn1)
    lay.addWidget(btn2)

    w.show()
    sys.exit(app.exec_())

enter image description here

eyllanesc
  • 235,170
  • 19
  • 170
  • 241