I have a label that sometimes contain a long text with no spaces (path in the computer).
So word-wrap wraps it very weirdly.
Is there a way to make the word-wrap of the label break in the middle of the word or not only at white spaces?
This isn't elegant but does work...
So say header class has Private:
QLabel *thisLabel;
QString *pathName;
QString *pathNameClean;
and of course defining thisLabel some where.
so it would be nice if it was this simple....
thisLabel->setWordWrap(true);
that's fine IF AND ONLY IF the word has break points
(WHICH PATHS SHOULD AVOID)
SO keep your actual path in a separate string if you need it for QFile purposes later.
Then manually define a character per line number, and insert the spaces into the string....
so we'll say 50 chars is a good width...
pathNameClean = new QString(pathName);
int c = pathName->length();
if( c > 50)
{
for(int i = 1; i <= c/50; i++)
{
int n = i * 50;
pathName->insert(n, " ");
}
}
thisLabel->setText(pathName);
Shazam.... simulated WordWrap with no original spaces...
just remember that pathName string is now just for pretty QLabel purposes and that the pathNameClean string is the actual path. Qt programs will crash if you try to open a file with a space injected path.....
(if there's no simple class method it's likely just a few lines of code to do...
and why problem solving is a programmers best tool!)
One way is to use the QTextOption class with a QTextDocument instead of a QLabel. This let you use QTextOption::WrapMode. QTextOption::WrapAtWordBoundaryOrAnywhere should do what you want.
QLabel with other wrap mode
I happen to have this same question in 2021, so I here I will share with you some of the best answers I have found so far.
TextWrapAnywhere QLabel
Subclass QLabel and and implement the paintEvent, where you can set the text alignment to TextWrapAnywhere when you drawItemText.
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QStyleOption, QVBoxLayout, QWidget, QStyle
class SuperQLabel(QLabel):
def __init__(self, *args, **kwargs):
super(SuperQLabel, self).__init__(*args, **kwargs)
self.textalignment = Qt.AlignLeft | Qt.TextWrapAnywhere
self.isTextLabel = True
self.align = None
def paintEvent(self, event):
opt = QStyleOption()
opt.initFrom(self)
painter = QPainter(self)
self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)
self.style().drawItemText(painter, self.rect(),
self.textalignment, self.palette(), True, self.text())
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setFixedSize(100, 200)
self.label = QLabel()
self.label.setWordWrap(True)
self.label.setText("1111111111111111111111111111")
self.slabel = SuperQLabel()
self.slabel.setText("111111111111111111111111111")
self.centralwidget = QWidget()
self.setCentralWidget(self.centralwidget)
self.mainlayout = QVBoxLayout()
self.mainlayout.addWidget(self.label)
self.mainlayout.addWidget(self.slabel)
self.centralwidget.setLayout(self.mainlayout)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Text Wrap at char instead of white space
According to this answer(in op's comment) provided by #ekhumoro, if you are looking for wrapping a line based on comma, you can insert a zero-width-space after the char you want to wrap and use the built in word wrap function.
Here is an example:
from PyQt5.QtCore import QRect, Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QStyleOption, QStylePainter, QVBoxLayout, QWidget, QStyle
class CommaWrapableQLabel(QLabel):
def __init__(self, *args, **kwargs):
super(CommaWrapableQLabel, self).__init__(*args, **kwargs)
def setWordWrapAtAnychar(self, char):
newtext = self.text().replace(char, f"{char}\u200b")
self.setText(newtext)
self.setWordWrap(True)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setFixedSize(100, 200)
self.label = QLabel()
self.label.setWordWrap(True)
self.label.setText(
'Dog,Rabbit,Train,Car,Plane,Cheese,Meat,Door,Window')
self.slabel = CommaWrapableQLabel()
self.slabel.setText(
'Dog,Rabbit,Train,Car,Plane,Cheese,Meat,Door,Window')
self.slabel.setWordWrapAtAnychar(",")
self.centralwidget = QWidget()
self.setCentralWidget(self.centralwidget)
self.mainlayout = QVBoxLayout()
self.mainlayout.addWidget(self.label)
self.mainlayout.addWidget(self.slabel)
self.centralwidget.setLayout(self.mainlayout)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Black magic solution
As said from other answers, you can reimplement paintEvent() for QLabel to pass the Qt::TextWrapAnywhere flag.
However, overriding paintEvent() may cause unexpected side effects since the default implementation of paintEvent() contains a lot of extra features besides just painting the text as is.
Actually the alignment flags passed to QStyle::drawItemText is stored in a private member QLabelPrivate::align. I came up with the idea to force rewrite the value of it with the Qt::TextWrapAnywhere flag set, and it works. This workaround requires a bit of C++ black magic.
class WorkaroundLabel: public QLabel {
Q_OBJECT
public:
WorkaroundLabel(QString text, QWidget* parent): QLabel(text, parent) {
setWordWrap(true);
ushort* p;
if (FindAlignAddr(&p)) {
*p = (*p | Qt::TextWrapAnywhere);
} else {
// workaround failed
}
}
virtual ~WorkaroundLabel() {}
protected:
// "ushort align;" in qtbase/src/widgets/widgets/qlabel_p.h
bool FindAlignAddr(ushort** out) {
Qt::Alignment align = alignment();
void* d_raw = (void*) d_ptr.data();
ushort* p = reinterpret_cast<ushort*>(d_raw);
for (int i = 0; i < 1024; i += 1) {
setAlignment(Qt::AlignLeft);
ushort a = *p;
setAlignment(Qt::AlignRight);
ushort b = *p;
if (a != b) {
*out = p;
setAlignment(align);
return true;
}
p++;
}
setAlignment(align);
return false;
}
};
In 2020, PySide2, it is just:
tmp = QLabel()
tmp.setWordWrap(True)
Related
For some strange reason cannot override a highlighted text colour in QListView. It worked fine (a highlighted text colour is auto-changed to white) until I defined my own widget to represent a row.
Now I can change background colour and some other visual aspects of a selected row, but text colour always remains default black.
Already tried all possible with QSS, QPalette and data()/Qt.ForegroundRole - no trick helps.
Here is a simplified code, which still suffers from the issue on OS X. Unfortunately I had no chance to test on Windows or GNU/Linux.
from PySide.QtCore import *
from PySide.QtGui import *
import sys
view = None
mapp = {}
style = '''
QListView {
show-decoration-selected: 1;
selection-color: white;
selection-background-color: #0068d9;
}
QListView::item:selected:active:hover{
background-color:red; color: white;
}
QListView::item:selected:active:!hover{
background-color: #0068d9; color: white;
}
QListView::item:selected:!active{
background-color:yellow; color: white;
}
QListView::item:!selected:hover{
background-color:green; color: white;
}
'''
class SimpleListModel(QAbstractListModel):
def __init__(self, mlist):
QAbstractListModel.__init__(self)
self._items = mlist
def rowCount(self, parent = QModelIndex()):
return len(self._items)
def index(self, row, column, parent=QModelIndex()):
node = self._items[row]
if not(str(row) in mapp):
index = self.createIndex(row, column)
widget = QLabel(node)
view.setIndexWidget(index, widget)
mapp[str(row)] = index
return index
return mapp[str(row)]
def data(self, index, role = Qt.DisplayRole):
return None
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
class MyMainWindow(QWidget):
def __init__(self):
global view
QWidget.__init__(self, None)
self._model = SimpleListModel(["test", "tes1t", "t3est", "t5est", "t3est"])
vbox = QVBoxLayout()
view = QListView()
view.setModel(self._model)
vbox.addWidget(view)
self.setLayout(vbox)
view.setStyleSheet(style)
first = self._model.index(0, 0)
view.setCurrentIndex(first)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyMainWindow()
w.show()
w.raise_()
app.exec_()
sys.exit()
Not the cleanest or best solution but this is what I came with after some tweaking.
Result:
The blue item is the selected one. The green item is the hovered one.
Code:
from PySide.QtCore import *
from PySide.QtGui import *
import sys
view = None
mapp = {}
style = '''
QListView {
show-decoration-selected: 1;
selection-color: white;
selection-background-color: #0068d9;
}
QListView::item:selected:active:hover{
background-color:red; color: white;
}
QListView::item:selected:active:!hover{
background-color: #0068d9; color: white;
}
QListView::item:selected:!active{
background-color:yellow; color: white;
}
QListView::item:!selected:hover{
background-color:green; color: white;
}
'''
class SimpleListModel(QAbstractListModel):
def __init__(self, mlist):
QAbstractListModel.__init__(self)
self._items = mlist
def rowCount(self, parent = QModelIndex()):
return len(self._items)
def index(self, row, column, parent=QModelIndex()):
node = self._items[row]
if not(str(row) in mapp):
index = self.createIndex(row, column)
widget = QLabel(node)
view.setIndexWidget(index, widget)
mapp[str(row)] = index
return index
return mapp[str(row)]
def data(self, index, role = Qt.DisplayRole):
# The following code shouldn't be put in this function but i'm in a hurry right now...
selectedIndexes = view.selectedIndexes()
# Set all items to black
for i in range(0, self.rowCount()):
currentRowIndex = self.index(i, 0, QModelIndex())
myWidget = view.indexWidget(currentRowIndex)
myWidget.setStyleSheet("color: black")
# Set selected items to white
for i in selectedIndexes:
myWidget = view.indexWidget(i)
myWidget.setStyleSheet("color: white")
return None
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
class MyMainWindow(QWidget):
def __init__(self):
global view
QWidget.__init__(self, None)
self._model = SimpleListModel(["test", "tes1t", "t3est", "t5est", "t3est"])
vbox = QVBoxLayout()
view = QListView()
view.setModel(self._model)
vbox.addWidget(view)
self.setLayout(vbox)
view.setStyleSheet(style)
first = self._model.index(0, 0)
view.setCurrentIndex(first)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyMainWindow()
w.show()
w.raise_()
app.exec_()
sys.exit()
Please let me know if there's something I didn't understand correctly or the code is not clear enough.
First:
Your code is working on GNU/Linux (Ubuntu 14.04 LTS, PyQt5) in the same way.
Since text is written to indexWidget, stylesheet for the text-color must be set for QLabel.
As QLabel does not support the hover pseudostate, it can not be done in the same way as for the items.
To set stylesheet for selected indexWidgets I used signal-slot-mechanism, for hovered indexWidgets I used event filter,
only class MyMainWindow modified:
class MyMainWindow(QWidget):
def __init__(self):
global view
QWidget.__init__(self, None)
self._model = SimpleListModel(["test", "tes1t", "t3est", "t5est", "t3est"])
vbox = QVBoxLayout()
view = QListView()
view.setModel(self._model)
view.setMouseTracking(True) # to catch mouseEvents
view.installEventFilter(self) # for events in ListView
vbox.addWidget(view)
self.setLayout(vbox)
view.setStyleSheet(style)
first = self._model.index(0, 0)
view.setCurrentIndex(first)
view.clicked.connect(self.setIndexStyle) # or any other signal
def setIndexStyle(self, index):
for i in range(0,view.model().rowCount()):
style = 'color: black;'
view.indexWidget(view.model().index(i,0)).setStyleSheet(style)
for i in view.selectedIndexes(): # works for multiseletion too
style = 'color: white;'
view.indexWidget(i).setStyleSheet(style)
def eventFilter(self,obj,event):
if event.type() == QEvent.HoverMove and isinstance(obj,QListView):
i = view.indexAt(event.pos()) # index at mouse.pos()
self.setIndexStyle(i) # selected indexWidgets still have white text
style = 'color: white;'
try: # if no item on mouse.pos()
view.indexWidget(i).setStyleSheet(style)
except AttributeError:
pass
return False
return QWidget.eventFilter(self,obj,event)
I tried:
self.installEventFilter(self)
and:
desktop= QApplication.desktop()
desktop.installEventFilter(self)
With:
def eventFilter(self, source, event):
if event.type() == QEvent.MouseMove:
print(event.pos())
return QMainWindow.eventFilter(self, source, event)
In QMainWindow object but nothing conclusive.
Do you have any idea?
Mouse events are initially handled by the window-manager, which then passes them on to whatever window is in that region of the screen. So if there are no Qt windows in that region, you won't get any events (including mouse events).
However, it is still possible to track the cursor position via polling:
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
cursorMove = QtCore.pyqtSignal(object)
def __init__(self):
super(Window, self).__init__()
self.cursorMove.connect(self.handleCursorMove)
self.timer = QtCore.QTimer(self)
self.timer.setInterval(50)
self.timer.timeout.connect(self.pollCursor)
self.timer.start()
self.cursor = None
def pollCursor(self):
pos = QtGui.QCursor.pos()
if pos != self.cursor:
self.cursor = pos
self.cursorMove.emit(pos)
def handleCursorMove(self, pos):
print(pos)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 500, 200, 200)
window.show()
sys.exit(app.exec_())
For a QFileDialog, is it possible to have either files or directories selectable, the choice being given to user on the same UI (like the way a user selects different filetypes amongst filters and the filelist updates accordingly)?
I have done some research and with some help from IRC ppl I found an easier solution. Basically adding a widget (checkbox, a suitable for this one) and connecting it to the file dialog does the work.
(It's actually someone else's answer which I have improved & furnished. Thanks to him ;). Posting answer here just for reference if someone else stumbles here).
from sys import argv
from PySide import QtGui, QtCore
class MyDialog(QtGui.QFileDialog):
def __init__(self, parent=None):
super (MyDialog, self).__init__()
self.init_ui()
def init_ui(self):
cb = QtGui.QCheckBox('Select directory')
cb.stateChanged.connect(self.toggle_files_folders)
self.layout().addWidget(cb)
def toggle_files_folders(self, state):
if state == QtCore.Qt.Checked:
self.setFileMode(self.Directory)
self.setOption(self.ShowDirsOnly, True)
else:
self.setFileMode(self.AnyFile)
self.setOption(self.ShowDirsOnly, False)
self.setNameFilter('All files (*)')
def main():
app = QtGui.QApplication(argv)
dialog = MyDialog()
dialog.show()
raise SystemExit(app.exec_())
if __name__ == '__main__':
main()
Yes, it is. Here is one way:
In the header, declare your QFileDialog pointer:
class buggy : public QWidget
{
Q_OBJECT
public:
buggy(QWidget *parent = 0);
QFileDialog *d;
public slots:
void createDialog();
void changeDialog();
};
In your implementation, set the QFileDialog::DontUseNativeDialog option (you must do this on Mac OS, but I haven't tested it elsewhere), then override the appropriate window flags to get your dialog to display as you like it.
Finally, add a button (or check box) that calls a function to change the file mode of your QFileDialog:
buggy::buggy(QWidget *){
//Ignore this, as its just for example implementation
this->setGeometry(0,0,200,100);
QPushButton *button = new QPushButton(this);
button->setGeometry(0,0,100,50);
button->setText("Dialog");
connect( button, SIGNAL(clicked()),this,SLOT(createDialog()));
//Stop ignoring and initialize this variable
d=NULL;
}
void buggy::createDialog(void){
d = new QFileDialog(this);
d->setOption(QFileDialog::DontUseNativeDialog);
d->overrideWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint | Qt::MacWindowToolBarButtonHint);
QPushButton *b = new QPushButton(d);
connect(b,SIGNAL(clicked()),this,SLOT(changeDialog()));
b->setText("Dir Only");
switch(d->exec()){
default:
break;
}
delete(d);
d=NULL;
}
//FUNCTION:changeDialog(), called from the QFileDialog to switch the fileMode.
void buggy::changeDialog(){
if(d != NULL){
QPushButton *pb = (QPushButton*)d->childAt(5,5);
if(pb->text().contains("Dir Only")){
pb->setText("File Only");
d->setFileMode(QFileDialog::Directory);
}else{
pb->setText("Dir Only");
d->setFileMode(QFileDialog::ExistingFile);
}
}
}
I have dynamically created buttons(QtoolButton) in gridLayout in pyQT.
How can I get the name of the button clicked in the layout?
I can't know the name before hand.
Is there is any trigger to accomplish the task?
Thanks in advance.
You can call self.sender() in a function connected to your button event to get the object that triggered the event. From there you can call the object's objectName() method to get the name.
Here's a quick example - the widget has 10 buttons and clicking on a button will update the label's text to show the button name.
import sys
from PyQt4.QtGui import QApplication, QWidget, QToolButton, QLabel, QVBoxLayout, QHBoxLayout
class Widget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.button_layout = QHBoxLayout()
self.widget_layout = QVBoxLayout()
for button_number in xrange(1, 11):
button = QToolButton()
button.setText(str(button_number))
button.setObjectName('Button%d' % button_number)
button.released.connect(self.button_released)
self.button_layout.addWidget(button)
self.status_label = QLabel('No button clicked')
self.widget_layout.addItem(self.button_layout)
self.widget_layout.addWidget(self.status_label)
self.setLayout(self.widget_layout)
def button_released(self):
sending_button = self.sender()
self.status_label.setText('%s Clicked!' % str(sending_button.objectName()))
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())
I think you have to implement your iconlabel class derived from QToolButton:
Like This:
class IconLabel : public QToolButton
{
Q_OBJECT
public:
explicit IconLabel(QWidget *parent = 0);
bool event (QEvent* e );
QString name;
signals:
void clicked_signal(QString);
};
bool IconLabel::event (QEvent* e ) {
if ( e->type() == QEvent::Paint) {
return QToolButton::event(e);
}
if(e->type() == QEvent::MouseButtonPress)
{
emit clicked_signal(name);
return true;
}
return true;
}
connect(iconlabel, SIGNAL(clicked_signal(QString)), this, SLOT(getClickedButtonName(QString)));
I need to process some data AFTER a drop event has occured on a widget.
i.e a user selects some items in list A and drops them in list B. My program needs to compare the 2 lists after B has added the items selected from A.
Any ideas?
Here's a PyQt script that demonstrates two ways to trap drop events:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
widget = QtGui.QWidget(self)
self.setCentralWidget(widget)
layout = QtGui.QVBoxLayout(widget)
self.listA = ListWidget(self)
self.listB = QtGui.QListWidget(self)
self.listB.viewport().installEventFilter(self)
for widget in (self.listA, self.listB):
widget.setAcceptDrops(True)
widget.setDragEnabled(True)
for item in 'One Two Three Four Five Six'.split():
widget.addItem(item)
layout.addWidget(widget)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.Drop and
source is self.listB.viewport()):
self.listB.dropEvent(event)
if event.isAccepted():
print 'eventFilter', self.listB.count()
return True
return QtGui.QMainWindow.eventFilter(self, source, event)
class ListWidget(QtGui.QListWidget):
def __init__(self, parent):
QtGui.QListWidget.__init__(self, parent)
def dropEvent(self, event):
QtGui.QListWidget.dropEvent(self, event)
if event.isAccepted():
print 'dropEvent', self.count()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
The event you're listening for is QDropEvent. To do some work on receipt of the event you'll need to either:
Reimplement QWidget::dropEvent on the target widget or
Use a separate controller widget as an event filter for the drop target (reimplement QObject::eventFilter on the controller and then install it on the target [installEventFilter]).
EDIT: Going into more depth:
Reimplementing dropEvent
Header:
cDropTarget : public QWidget
{
Q_OBJECT
public:
cDropTarget(QWidget *Parent = 0);
protected:
virtual void dropEvent(QDropEvent *event);
}
Implementation:
cDropTarget::cDropTarget(QWidget *Parent)
: QWidget(Parent)
{
setAcceptDrops(true);
}
void cDropTarget::dropEvent(QDropEvent *event)
{
//check that you want to process the drop event
//i.e. its mimeData()->hasFormat(that you can interpret)
//extract mimeData() from the drop event
//do something with it
}
Using a separate event filter
header:
cDropEventFilter : public QObject
{
...
protected:
virtual bool eventFilter(QObject *watched, QEvent *event);
...
}
Implementation:
bool cDropEventFilter::eventFilter(QObject *watched, QEvent *event)
{
if(event->type() == QEvent::DropEnter)
{
QDropEvent *DropEvent = static_cast<QDropEvent*>(event);
if (DropEvent->mimeData()->hasFormat(MIME_TYPE))
{
DropEvent->acceptProposedAction();
return true;
}
}
else
{
return QObject::eventFilter(watched, event);
}
//Handle all events not matching mimeData format MIME_TYPE
event->ignore();
return true;
}
Got it working!
My code for anyone interested:
class MyList(QListWidget):
def __init__(self , parent = None):
super(MyList, self).__init__(parent)
self.setAlternatingRowColors(True)
self.setDragDropMode( QAbstractItemView.InternalMove )
def dropEvent( self , event ):
# get index to insert at
insertPos = event.pos()
fromList = event.source()
insertRow = fromList.row( fromList.itemAt( insertPos ) )
lowestRow = insertRow
for item in fromList.selectedItems():
name = item.text()
sip.delete( item )
listItem = QListWidgetItem( name )
listItem.setTextAlignment( Qt.AlignHCenter )
self.insertItem( insertRow , listItem )
insertRow += 1
self.emit( SIGNAL("stuffDropped") , lowestRow )
event.accept()