Qt Designer - clickable area to generate map - python

Using Python 2.7.3 and Qt Designer 4.8.2: I'm new to Qt, how may I create a simple grid area that is clickable to generate a map? The image below illustrates what I intend.
In essence my main issue is the grid area, I'm unable to see anything like 'off the shelf' within Qt.

The nearest equivalent would seem to be a QTableWidget.
Here is a crude demo that should give you a start in the right direction:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self, rows, columns):
QtGui.QWidget.__init__(self)
self.table = QtGui.QTableWidget(rows, columns, self)
self.table.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
header = self.table.horizontalHeader()
header.setResizeMode(QtGui.QHeaderView.Fixed)
header.setDefaultSectionSize(25)
header.hide()
header = self.table.verticalHeader()
header.setResizeMode(QtGui.QHeaderView.Fixed)
header.setDefaultSectionSize(25)
for row in range(rows):
item = QtGui.QTableWidgetItem('0x00')
self.table.setVerticalHeaderItem(row, item)
for column in range(columns):
item = QtGui.QTableWidgetItem()
item.setBackground(QtCore.Qt.white)
self.table.setItem(row, column, item)
self.table.itemPressed.connect(self.handleItemPressed)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.table)
def handleItemPressed(self, item):
if item.background().color() == QtCore.Qt.black:
item.setBackground(QtCore.Qt.white)
else:
item.setBackground(QtCore.Qt.black)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window(4, 8)
window.resize(300, 150)
window.show()
sys.exit(app.exec_())

One (clunky?) solution would be to draw your map image using a label widget with a pixmap set. You can achieve the click-ability by listening for mousePressEvent on that widget, upon which you can get a QMouseEvent object that contains mouse x, y position (both global and relative to the clicked widget). This can then be used to determine where on the image was clicked.

Related

Change the Icon of the Selected Item of the QToolBox in PyQT

Following is the PyQT code,
from PyQt5.QtWidgets import *
import sys
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QGridLayout()
self.setLayout(layout)
# Add toolbar and items
toolbox = QToolBox()
layout.addWidget(toolbox, 0, 0)
label = QLabel()
toolbox.addItem(label, "Students")
label = QLabel()
toolbox.addItem(label, "Teachers")
label = QLabel()
toolbox.addItem(label, "Directors")
app = QApplication(sys.argv)
screen = Window()
screen.show()
sys.exit(app.exec_())
What I want is whenever an Item in that ToolBox is selected, its icon should change from "arrow-straight" to "arrow-down" to represent that this item is currently opened and others are closed. Now, if another item is clicked, then the first item's arrow should again be changed back to arrow-straight and the item that is clicked gets its arrow changed now.
How can I accomplish this in PyQT? Be it from the designer or from the Code Logic.
EDIT: For example, look at this designer below,
Since the "Registration Details" is selected, so I want its arrow to be replaced with another Icon (Say "arrow down" icon). And once I select some other item in the toolbox (like View Clashes), then Registration Details' arrow should be replaced with the old arrow and View Clashes arrow should get changed to another Icon.
The code for this in Pyuic file is this,
icon = QIcon()
icon.addFile(u":/icons/icons/arrow-right.svg", QSize(), QIcon.Normal, QIcon.Off)
self.toolBox.addItem(self.page_2, icon, u"Registration Details")
You can set a default icon when adding items, and then connect the currentChanged signal in order to set the other one.
If you create a basic list with both icons, setting the proper icon is even simpler, as you only need to cycle through all items and set the icon based on the index match.
class Test(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.arrowIcons = []
for direction in ('right', 'down'):
self.arrowIcons.append(QtGui.QIcon(
':/icons/icons/arrow-{}.svg'.format(direction)))
layout = QtWidgets.QVBoxLayout(self)
self.toolBox = QtWidgets.QToolBox()
layout.addWidget(self.toolBox)
self.toolBox.currentChanged.connect(self.updateIcons)
for i in range(5):
self.toolBox.addItem(
QtWidgets.QLabel(),
self.arrowIcons[0],
'Item {}'.format(i + 1))
def updateIcons(self, index):
for i in range(self.toolBox.count()):
self.toolBox.setItemIcon(i, self.arrowIcons[index == i])

Issue with positioning of one widget with respect to the position of another

I’m trying to make a GUI and I have a trouble with widget positioning. I would like to tie position of one widget (A) to the position of another (B) that is added in GridLayout. So, main idea: A.pos() = B.pos() + DELTA.
In detail:
Let us have such QWidget with QGridLayout containing four Qlabels:
import sys
from PySide2.QtWidgets import*
from PySide2.QtGui import*
from PySide2.QtCore import*
if __name__ == '__main__':
app = QApplication(sys.argv)
w = QWidget()
w.resize(500, 300)
lab1 = QLabel("label1")
lab2 = QLabel("label2")
lab3 = QLabel("label3")
lab4 = QLabel("label4")
lay = QGridLayout()
lay.addWidget(lab1,0,0)
lay.addWidget(lab2,0,1)
lay.addWidget(lab3,1,0)
lay.addWidget(lab4,1,1)
w.setLayout(lay)
w.show()
sys.exit(app.exec_())
Screen of GUI is here
So, I would like to create QLineEdit that will be placed right above lab4 and trying to realize something like:
le = QLineEdit(w)
le.setGeometry(QRect(lab4.pos().x(),lab4.pos().y()+10,20,20))
But if make print(lab4.pos().x(), lab4.pos().y()) it will print (0,0)... and my GUI will look like this
But I want it to look like this
Please, help me set my widget correctly. As you have noticed, I write using PyQT, but if you answer me using C++ QT I would be grateful too!
If, as I suspect, the intention is to place a QLineEdit over the top-left corner of one of the grid elements then you can take advantage of the fact that...
QGridLayout allows multiple items to be added to the same cell.
It also allows you to add a QLayout to a cell.
Firstly, create a class that inherits from QGridLayout and really does nothing more than add a QLineEdit to cell(0, 0) and a stretch to the right and below the QLineEdit...
class line_edit_overlay(QGridLayout):
def __init__(self):
super(line_edit_overlay, self).__init__()
self.line_edit = QLineEdit()
self.addWidget(self.line_edit, 0, 0);
self.setRowStretch(1, 1);
self.setColumnStretch(1, 1);
Now your code would be something like...
import sys
from PySide2.QtWidgets import*
from PySide2.QtGui import*
from PySide2.QtCore import*
if __name__ == '__main__':
app = QApplication(sys.argv)
w = QWidget()
w.resize(500, 300)
lab1 = QLabel("label1")
lab2 = QLabel("label2")
lab3 = QLabel("label3")
lab4 = QLabel("label4")
lay = QGridLayout()
lay.addWidget(lab1,0,0)
lay.addWidget(lab2,0,1)
lay.addWidget(lab3,1,0)
lay.addWidget(lab4,1,1)
w.setLayout(lay)
# Added the following. Note the calls to lay.setRowStretch(...)
# and lay.setColumnStretch(...) which seem to be required to
# maintain even grid sizing.
for row in range(0, lay.rowCount()):
lay.setRowStretch(row, 1)
for col in range(0, lay.columnCount()):
lay.setColumnStretch(col, 1)
lay.addLayout(line_edit_overlay(), 1, 1)
w.show()
sys.exit(app.exec_())
The above results in the following...

Qt resize layout during widget property animation

I have an existing application that I am polishing off and I want to add some animation to a few of the widgets. Animating widgets with QPropertyAnimation outside of layouts is easy and fun, however when they are in a layout I am having various difficulties. The current one giving me a headache is that when I animate the size of a widget, the layout does not adjust to it's new size.
So lets say I have a QVBoxLayout with three widgets: a label which should expand to all available space, a treeview, and a button. When I click the button I want the tree to collapse and the label to take over it's space. Below is this example in code, and as you can see while the tree animates it's size nothing happens, and then when I hide it at the end of the animation the label pops to fill the now vacant space. So it seems that during the animation the layout does not "know" the tree is resizing. What I would like to happen is that AS the tree shrinks, the label expands to fill it.
Could this could be done not by absolute sizing of the label, but by calling a resize on the layout or something like that? I ask because I want to animate several widgets across my application and I want to find the best way to do this without having to make too many widgets interdependent upon each other.
Example code:
import sys
from PyQt4 import QtGui, QtCore
class AnimatedWidgets(QtGui.QWidget):
def __init__(self):
super(AnimatedWidgets, self).__init__()
layout1 = QtGui.QVBoxLayout()
self.setLayout(layout1)
expanding_label = QtGui.QLabel("Expanding label!")
expanding_label.setStyleSheet("border: 1px solid red")
layout1.addWidget(expanding_label)
self.file_model = QtGui.QFileSystemModel(self)
sefl.file_model.setRootPath("C:/")
self.browse_tree = QtGui.QTreeView()
self.browse_tree.setModel(self.file_model)
layout1.addWidget(self.browse_tree)
shrink_tree_btn = QtGui.QPushButton("Shrink the tree")
shrink_tree_btn.clicked.connect(self.shrink_tree)
layout1.addWidget(shrink_tree_btn)
#--
self.tree_size_anim = QtCore.QPropertyAnimation(self.browse_tree, "size")
self.tree_size_anim.setDuration(1000)
self.tree_size_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
self.tree_pos_anim = QtCore.QPropertyAnimation(self.browse_tree, "pos")
self.tree_pos_anim.setDuration(1000)
self.tree_pos_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
self.tree_anim_out = QtCore.QParallelAnimationGroup()
self.tree_anim_out.addAnimation(self.tree_size_anim)
self.tree_anim_out.addAnimation(self.tree_pos_anim)
def shrink_tree(self):
self.tree_size_anim.setStartValue(self.browse_tree.size())
self.tree_size_anim.setEndValue(QtCore.QSize(self.browse_tree.width(), 0))
tree_rect = self.browse_tree.geometry()
self.tree_pos_anim.setStartValue(tree_rect.topLeft())
self.tree_pos_anim.setEndValue(QtCore.QPoint(tree_rect.left(), tree_rect.bottom()))
self.tree_anim_out.start()
self.tree_anim_out.finished.connect(self.browse_tree.hide)
def main():
app = QtGui.QApplication(sys.argv)
ex = AnimatedWidgets()
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
The layouts handle the geometry() of the widgets so that when wanting to change the pos property these are interfacing with their handles so it is very common that you get that type of behavior, a better option is to use a QVariantAnimation to establish a fixed height:
import sys
from PyQt4 import QtGui, QtCore
class AnimatedWidgets(QtGui.QWidget):
def __init__(self):
super(AnimatedWidgets, self).__init__()
layout1 = QtGui.QVBoxLayout(self)
expanding_label = QtGui.QLabel("Expanding label!")
expanding_label.setStyleSheet("border: 1px solid red")
layout1.addWidget(expanding_label)
self.file_model = QtGui.QFileSystemModel(self)
self.file_model.setRootPath(QtCore.QDir.rootPath())
self.browse_tree = QtGui.QTreeView()
self.browse_tree.setModel(self.file_model)
layout1.addWidget(self.browse_tree)
shrink_tree_btn = QtGui.QPushButton("Shrink the tree")
shrink_tree_btn.clicked.connect(self.shrink_tree)
layout1.addWidget(shrink_tree_btn)
#--
self.tree_anim = QtCore.QVariantAnimation(self)
self.tree_anim.setDuration(1000)
self.tree_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
def shrink_tree(self):
self.tree_anim.setStartValue(self.browse_tree.height())
self.tree_anim.setEndValue(0)
self.tree_anim.valueChanged.connect(self.on_valueChanged)
self.tree_anim.start()
def on_valueChanged(self, val):
h, isValid = val.toInt()
if isValid:
self.browse_tree.setFixedHeight(h)
def main():
app = QtGui.QApplication(sys.argv)
ex = AnimatedWidgets()
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

How to catch mouse over event of QTableWidget item in pyqt?

what I want to do is to change the color of a QTableWidget item, when I hover with the mouse over the item of my QTableWidget.
Firstly, the table widget needs to have mouse-tracking switched on to get the hover events.
Secondly, we need to find some signals that tell us when the mouse enters and leaves the table cells, so that the background colours can be changed at the right times.
The QTableWidget class has the cellEntered / itemEntered signals, but there is nothing for when the mouse leaves a cell. So, we will need to create some custom signals to do that.
The TableWidget class in the demo script below sets up the necessary cellExited / itemExited signals, and then shows how everything can be hooked up to change the item background when hovering with the mouse:
from PyQt4 import QtGui, QtCore
class TableWidget(QtGui.QTableWidget):
cellExited = QtCore.pyqtSignal(int, int)
itemExited = QtCore.pyqtSignal(QtGui.QTableWidgetItem)
def __init__(self, rows, columns, parent=None):
QtGui.QTableWidget.__init__(self, rows, columns, parent)
self._last_index = QtCore.QPersistentModelIndex()
self.viewport().installEventFilter(self)
def eventFilter(self, widget, event):
if widget is self.viewport():
index = self._last_index
if event.type() == QtCore.QEvent.MouseMove:
index = self.indexAt(event.pos())
elif event.type() == QtCore.QEvent.Leave:
index = QtCore.QModelIndex()
if index != self._last_index:
row = self._last_index.row()
column = self._last_index.column()
item = self.item(row, column)
if item is not None:
self.itemExited.emit(item)
self.cellExited.emit(row, column)
self._last_index = QtCore.QPersistentModelIndex(index)
return QtGui.QTableWidget.eventFilter(self, widget, event)
class Window(QtGui.QWidget):
def __init__(self, rows, columns):
QtGui.QWidget.__init__(self)
self.table = TableWidget(rows, columns, self)
for column in range(columns):
for row in range(rows):
item = QtGui.QTableWidgetItem('Text%d' % row)
self.table.setItem(row, column, item)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.table)
self.table.setMouseTracking(True)
self.table.itemEntered.connect(self.handleItemEntered)
self.table.itemExited.connect(self.handleItemExited)
def handleItemEntered(self, item):
item.setBackground(QtGui.QColor('moccasin'))
def handleItemExited(self, item):
item.setBackground(QtGui.QTableWidgetItem().background())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window(6, 3)
window.setGeometry(500, 300, 350, 250)
window.show()
sys.exit(app.exec_())
You can achieve your goal pretty easily using the proper signals as proved by the following simple code:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class TableViewer(QMainWindow):
def __init__(self, parent=None):
super(TableViewer, self).__init__(parent)
self.table = QTableWidget(3, 3)
for row in range (0,3):
for column in range(0,3):
item = QTableWidgetItem("This is cell {} {}".format(row+1, column+1))
self.table.setItem(row, column, item)
self.setCentralWidget(self.table)
self.table.setMouseTracking(True)
self.current_hover = [0, 0]
self.table.cellEntered.connect(self.cellHover)
def cellHover(self, row, column):
item = self.table.item(row, column)
old_item = self.table.item(self.current_hover[0], self.current_hover[1])
if self.current_hover != [row,column]:
old_item.setBackground(QBrush(QColor('white')))
item.setBackground(QBrush(QColor('yellow')))
self.current_hover = [row, column]
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
tv = TableViewer()
tv.show()
sys.exit(app.exec_())
You may be interested in other signals too, especially itemEntered. However, if you want total control over the editing and display of items then using delegates (via the QTableWidget.setItemDelegate method) is strongly recommended.
UPDATE:
sorry, I had forgotten the second part of the problem i.e. what happens when the mouse exits a cell. Even then the problem can be solved easily without using events. See the updated code, please.
There are no events based on QTableWidgetItem, but you can do this:
reimplement the mouseMoveEvent() of QTableWidget, you can get the mouse position;
use itemAt() method to get the item under your mouse cursor;
customize your item;
This may simalute what you want.
I know this is old but wanted to update a couple of parts to it as I came across this page looking for a similar solution. This has a couple of parts to it, one is similar to the above but avoids the NoneType error if the cell is empty. Additionally, it will change the color of the highlighted cell, but also update a tooltip for the cell to display the contents of the cell in a tooltip. Nice if you have cells with runoffs 123...
Sure it could be cleaned up a bit, but works for PyQt5. Cheers!
def cellHover(self, row, column):
item = self.My_Table1.item(row, column)
old_item = self.My_Table1.item(self.current_hover[0], self.current_hover[1])
if item is not None:
if self.current_hover != [row,column]:
text = item.text()
if text is not None:
self.My_Table1.setToolTip(text)
item.setBackground(QBrush(QColor('#bbd9f7')))
old_item.setBackground(QBrush(QColor('white')))
self.current_hover = [row, column]

PyQt4 - add a text edit area animation example

I have realized a python simple application, without any animation on it.
Now I want to add a simple animation, triggered by a signal (a button click for example), which on trigger enlarges the width of the windows and shows a new text area with some text in it.
Honestly, I am quite new to python/pyqt4, and I do not know much about the animation framework.
I tried to add this to my class code, for example in a method called clicking on the about menu :) :
self.anim = QPropertyAnimation(self, "size")
self.anim.setDuration(2500)
self.anim.setStartValue(QSize(self.width(), self.height()))
self.anim.setEndValue(QSize(self.width()+100, self.height()))
self.anim.start()
and this enlarge my window as I want.
Unfortunately I have no idea how to insert a new text area, avoiding the widgets already present to fill the new space (actually, when the window enlarge, the widgets use
all the spaces, thus enlarging themselves)
Could someone help me knowing how to add the text area appearance animation?
Any help is appreciated...really...
One way to achieve this is to animate the maximumWidth property on both the window and the text-edit.
The main difficulty is doing it in a way that plays nicely with standard layouts whilst also allowing resizing of the window. Avoiding flicker during the animation is also quite tricky.
The following demo is almost there (the animation is slightly jerky at the beginning and end):
from PyQt4 import QtGui, QtCore
class Window(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self)
self._offset = 200
self._closed = False
self._maxwidth = self.maximumWidth()
self.widget = QtGui.QWidget(self)
self.listbox = QtGui.QListWidget(self.widget)
self.button = QtGui.QPushButton('Slide', self.widget)
self.button.clicked.connect(self.handleButton)
self.editor = QtGui.QTextEdit(self)
self.editor.setMaximumWidth(self._offset)
vbox = QtGui.QVBoxLayout(self.widget)
vbox.setContentsMargins(0, 0, 0, 0)
vbox.addWidget(self.listbox)
vbox.addWidget(self.button)
layout = QtGui.QHBoxLayout(self)
layout.addWidget(self.widget)
layout.addWidget(self.editor)
layout.setSizeConstraint(QtGui.QLayout.SetMinAndMaxSize)
self.animator = QtCore.QParallelAnimationGroup(self)
for item in (self, self.editor):
animation = QtCore.QPropertyAnimation(item, 'maximumWidth')
animation.setDuration(800)
animation.setEasingCurve(QtCore.QEasingCurve.OutCubic)
self.animator.addAnimation(animation)
self.animator.finished.connect(self.handleFinished)
def handleButton(self):
for index in range(self.animator.animationCount()):
animation = self.animator.animationAt(index)
width = animation.targetObject().width()
animation.setStartValue(width)
if self._closed:
self.editor.show()
animation.setEndValue(width + self._offset)
else:
animation.setEndValue(width - self._offset)
self._closed = not self._closed
self.widget.setMinimumSize(self.widget.size())
self.layout().setSizeConstraint(QtGui.QLayout.SetFixedSize)
self.animator.start()
def handleFinished(self):
if self._closed:
self.editor.hide()
self.layout().setSizeConstraint(QtGui.QLayout.SetMinAndMaxSize)
self.widget.setMinimumSize(0, 0)
self.setMaximumWidth(self._maxwidth)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.move(500, 300)
window.show()
sys.exit(app.exec_())

Categories

Resources