Tooltip message when hovering on cell with mouse in wx.grid wxpython - grid

I have a wx.grid table, I want to set a tooltip when I hover on a cell, I tried Mike Driscoll's recommendation below, it works, but I can't select multiple cells with mouse drag anymore, it allows me to select only 1 cell max, please help:
self.grid_area.GetGridWindow().Bind(wx.EVT_MOTION, self.onMouseOver)
def onMouseOver(self, event):
'''
Method to calculate where the mouse is pointing and
then set the tooltip dynamically.
'''
# Use CalcUnscrolledPosition() to get the mouse position
# within the
# entire grid including what's offscreen
x, y = self.grid_area.CalcUnscrolledPosition(event.GetX(),event.GetY())
coords = self.grid_area.XYToCell(x, y)
# you only need these if you need the value in the cell
row = coords[0]
col = coords[1]
if self.grid_area.GetCellValue(row, col):
if self.grid_area.GetCellValue(row, col) == "ABC":
event.GetEventObject().SetToolTipString("Code is abc")
elif self.grid_area.GetCellValue(row, col) == "XYZ":
event.GetEventObject().SetToolTipString("code is xyz")
else:
event.GetEventObject().SetToolTipString("Unknown code")

OK, I found the solution, I have to skip the event:
def onMouseOver(self, event):
'''
Method to calculate where the mouse is pointing and
then set the tooltip dynamically.
'''
# Use CalcUnscrolledPosition() to get the mouse position
# within the
# entire grid including what's offscreen
x, y = self.grid_area.CalcUnscrolledPosition(event.GetX(),event.GetY())
coords = self.grid_area.XYToCell(x, y)
# you only need these if you need the value in the cell
row = coords[0]
col = coords[1]
if self.grid_area.GetCellValue(row, col):
if self.grid_area.GetCellValue(row, col) == "ABC":
event.GetEventObject().SetToolTipString("Code is abc")
elif self.grid_area.GetCellValue(row, col) == "XYZ":
event.GetEventObject().SetToolTipString("code is xyz")
else:
event.GetEventObject().SetToolTipString("Unknown code")
event.Skip()
Thanks
best regards

# GreenAsJade
Since I cannot comment due to reputation i am asnwering your question here!
why: what was going wrong, how does this fix it?
If you check the difference between your event Hanlder and #alwbtc's event Handler only difference is event.Skip()
Any time the wx.EVT_xx is bind with custom method in code, wxpython override default definition. For that reason event handling ends in onMouseOver. event.Skip() will propagate the event to _core of wxptyhon allowing it to execute default event handlers.
Hope this answers your question!

Related

QPaint crashed in subclassed QStyledItemDelegate

I subclassed a QStyledItemDelegate to change the highlight color of the selections, and if there is a QColor in ForegroundRole and BackgroundRole I will try to blend two colors.
However, when I'm trying to select a row in the tableview, following error message shows up:
TypeError: arguments did not match any overloaded call:
setColor(self, QPalette.ColorGroup, QPalette.ColorRole, Union[QColor, Qt.GlobalColor, QGradient]): argument 1 has unexpected type 'ColorRole'
setColor(self, QPalette.ColorRole, Union[QColor, Qt.GlobalColor, QGradient]): argument 2 has unexpected type 'NoneType'
and terminal shows:
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::setCompositionMode: Painter not active
QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
Here is my implementation:
class StyleDelegateForTable_List(QStyledItemDelegate):
"""
Customize highlight style for ListView & TableView
"""
def __init__(self, parent):
super().__init__(parent)
self.hightColor = QtGui.QColor("#0096ff")
def paint(self, painter, option: QtWidgets.QStyleOptionViewItem, index):
self.initStyleOption(option, index)
if (option.state & QtWidgets.QStyle.StateFlag.State_Selected and
option.state & QtWidgets.QStyle.StateFlag.State_Active):
# get foreground color,
# don't know where to set foreground color though...
fg = self.getColor(index, isBG = False)
# get background color
bg = self.getColor(index, isBG = True)
# set highlight color
option.palette.setColor(QtGui.QPalette.ColorRole.Highlight,
self.mixColors(bg))
QStyledItemDelegate.paint(self, painter, option, index)
def getColor(self, index: QModelIndex, isBG = True) -> QtGui.QColor:
parentWidget = self.parent()
model = parentWidget.model()
dataRole = Qt.ItemDataRole.BackgroundRole if isBG else Qt.ItemDataRole.ForegroundRole
# TableView
if isinstance(parentWidget, QtWidgets.QTableView):
if isinstance(model, QSortFilterProxyModel):
# proxy model
sourceIndex = model.mapToSource(index)
return model.sourceModel().data(sourceIndex, dataRole)
elif isinstance(model, TestDataTableModel):
# abstract table model
return model.data(index, dataRole)
# ListView
if isinstance(parentWidget, QtWidgets.QListView):
if isinstance(model, QSortFilterProxyModel):
# all of listView uses proxyModel
sourceIndex = model.mapToSource(index)
return model.sourceModel().data(sourceIndex, dataRole)
return QtGui.QColor("#000000")
def mixColors(self, src) -> QtGui.QColor:
if isinstance(src, QtGui.QColor):
r = int(src.red()*0.7 + self.hightColor.red()*0.3)
g = int(src.green()*0.7 + self.hightColor.green()*0.3)
b = int(src.blue()*0.7 + self.hightColor.blue()*0.3)
return QtGui.QColor(r, g, b)
else:
# I intended to mix default bg or fg color
# with highlight color but default is None.
# return hightlight color for now
self.hightColor
I tried to set a breakpoint in getColor, but my app just crashed, what did I do wrong here?
Edit:
This question is invalid, as # musicamante points out, mixColors returns None instead of hightlight color if the original Background/Foreground is None.
I will keep this question in case someone needs the same functionality.

Automatically add a scroll in Multiline when the number of rows is exceeded

Tell me how to automatically connect scrolling in PySimpleGUI Multiline when the number of lines entered exceeds. For example, to enable scrolling when there are more than 5 lines
sg.Multiline(size=(42, 5))
import PySimpleGUI as sg
def begin_window():
layout = [[sg.Multiline(size=(42, 5), autoscroll=True)]]
# Create the Window
window = sg.Window('Title', layout)
# window['-INDEX-'].bind("<Return>", "_Enter")
# Event Loop to process "events" and get the "values" of the inputs
while True:
event, values = window.read()
if event == sg.WIN_CLOSED or event == '-CANCEL-': # if user closes window or clicks cancel
break
window.close()
return
if __name__ == '__main__':
begin_window()
Scroll appears immediately, when the field is empty
The method is to hack the method set for the tk.Scrollbar, and it is only work for the vertical scrollbar for following code.
import PySimpleGUI as sg
def scrollbar_set(self, lo, hi):
if float(lo) <= 0.0 and float(hi) >= 1.0:
self.pack_forget()
elif self.cget("orient") != sg.tk.HORIZONTAL:
self.pack(side=sg.tk.RIGHT, fill=sg.tk.Y)
self.old_set(lo, hi)
sg.tk.Scrollbar.old_set = sg.tk.Scrollbar.set
sg.tk.Scrollbar.set = scrollbar_set
layout = [
[sg.Text('Auto hide scrollbar', size=25)],
[sg.Multiline(size=(20, 5), expand_x=True, expand_y=True, key='-ML-')]]
window = sg.Window('Title', layout, finalize=True)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
window.close()

DFS to get all possible solutions?

I have these Circles:
I want to get the list of all possible solution of maximum non-intersecting circles. This is the illustration of the solution I wanted from node A.
Therefore the possible solutions from node A:
1 = [A,B,C], 2 = [A,B,E], 3 = [A,C,B], 4 = [A,E,B] ..etc
I want to store all of the possibilities into a list, which the will be used for weighting and selecting the best result. However, I'm still trying to create the list of all possibilities.
I've tried to code the structure here, however I still confused about backtracking and recursive. Anyone could help here?
# List of circle
# List of circle
list_of_circle = ['A','B','C','D','E']
# List of all possible solutions
result = []
# List of possible nodes
ways = []
for k in list_of_circle:
if len(list_of_circle)==0:
result.append(ways)
else:
ways.append[k]
list_of_circle.remove(k)
for j in list_of_circle:
if k.intersects(j):
list_of_circle.remove(j)
return result
Here is a possible solution (pseudocode).
def get_max_non_intersect(selected_circles, current_circle_idx, all_circles):
if current_circle_idx == len(all_circles): # final case
return selected_circles
# we recursively get the biggest selection of circles if the current circle is not selected
list_without_current_circle = get_max_non_intersect(selected_circles, current_circle_idx + 1, all_circles)
# now we check if we can add the current circle to the ones selected
current_intersects_selected = false
current_circle = all_circles[current_circle_idx]
for selected_circle in selected_circles:
if intersects(current_circle, selected_circle):
current_intersects_selected = true
break
if current_intersects_selected is true: # we cannot add the current circle
return list_without_current_circle
else: # we can add the current circle
list_with_current_circle = get_max_non_intersect(selected_circles + [current_circle], current_circle_idx + 1, all_circles)
return list_with_current_circle + list_without_current_circle

Getting the exact data index from a click on pyqt5graph image (not just pixel value)

I am new to PyqtGraph ( Infact this is my first time)
I have a Qt designer file which I import in my python code. I 6 windows in which I plot a 42x22 (different sizes) as an inmage, these have been promoted the graphicview.
I have a data set which is 6x42x22 and so I use a for loop to plot the 6 images
for n in range(imageStackSize):
self.glayout = pg.GraphicsLayout()
self.vb = self.glayout.addViewBox()
self.vb.setAspectLocked(lock=True, ratio=self.aspect_ratio)
img_temp = image[n, :, :]
and...
``
img = pg.ImageItem(img_temp, lut=self.jet_lut)
if n == 0:
self.ui.Channel1_img.setCentralItem(self.glayout)
self.vb.addItem(img)
elif n == 1:
self.ui.Channel2_img.setCentralItem(self.glayout)
self.vb.addItem(img)
elif n == 2:
self.ui.Channel3_img.setCentralItem(self.glayout)
self.vb.addItem(img)
elif n == 3:
self.ui.Channel4_img.setCentralItem(self.glayout)
self.vb.addItem(img)
elif n == 4:
self.ui.Channel5_img.setCentralItem(self.glayout)
self.vb.addItem(img)
elif n == 5:
self.ui.Channel6_img.setCentralItem(self.glayout)
self.vb.addItem(img)
After this I am trying to click on one of the image (ideally I would like to make it such that I can click any of the six images) to get the (6,x,y) coordinated the first dimension does not matter. In order to achieve this I did
self.ui.Channel1_img.scene().sigMouseClicked.connect(self.onClick)
#self.ui.Channel2_img.scene().sigMouseClicked.connect(self.onClick)
#self.ui.Channel3_img.scene().sigMouseClicked.connect(self.onClick)
#self.ui.Channel4_img.scene().sigMouseClicked.connect(self.onClick)
#self.ui.Channel5_img.scene().sigMouseClicked.connect(self.onClick)
#self.ui.Channel6_img.scene().sigMouseClicked.connect(self.onClick)
#self.ui.PMTVoltage_plt.scene().sigMouseClicked.connect(self.onClick)
def onClick(self, event):
print("clicked")
and then I tried
items = self.ui.Channel1_img.imageItem.mapFromViewToItem(event.pos())
and
items = self.ui.Channel1_img.imageItem.mapFromSceneToView(event.pos())
but the prog just crashes. I read somewhere that the coordinates are in the viewbox, but I cant seem to find the viewbox or vb in the self.ui.Channel1_img
I went through the entire ui variable in debug to look for vb or image Item and could not find it.
infact the only thing I found was,
items = {dict} {<pyqtgraph.graphicsItems.ViewBox.ViewBox.ViewBox
object at 0x000001CF73888CA8>: [(0, 0)]}
<pyqtgraph.graphicsItems.ViewBox.ViewBox.ViewBox object at
0x000001CF73888CA8> (1990508186792) = {list} <class 'list'>: [(0, 0)]
0 = {tuple} <class 'tuple'>: (0, 0)
0 = {int} 0
1 = {int} 0
__len__ = {int} 2
__len__ = {int} 1
__len__ = {int} 1
what am I missing? Any help is appreciated
Ok I figured it out, here is my solution for some who may have same question
I plot the data using it as imageItem
vb = pg.ViewBox()
self.ui.PMTVoltage_plt.useOpenGL()
self.ui.PMTVoltage_plt.setCentralItem(vb)
self.img = pg.ImageItem(pmtImage)
vb.addItem(self.img)
and then in other function I recover the vb using getViewBox() and then use mapFromViewtoItem()
vb = self.ui.PMTVoltage_plt.getViewBox()
items = vb.mapSceneToView(event.scenePos())
pixels = vb.mapFromViewToItem(self.img, items)
print(items)
Hope this helps

Python3: How to dynamically resize button text in tkinter/ttk?

I want to know how to arrange for the text on a ttk widget (a label or button, say) to resize automatically.
Changing the size of the text is easy, it is just a matter of changing the font in the style. However, hooking it into changes in the size of the window is a little more tricky. Looking on the web I found some hints, but there was nowhere a complete answer was posted.
So, here below is a complete working example posted as an answer to my own question. I hope someone finds it useful. If anyone has further improvements to suggest, I will be delighted to see them!
The example below shows two techniques, one activated by re-sizing the window (see the resize() method, bound to the <Configure> event), and the other by directly changing the size of the font (see the mutate() method).
Other code necessary to get resizing working is the grid configuration code in the __init__() method.
When running the example, there is some interaction between the two methods, but I think in a 'real' situation one technique would be sufficient, so that issue won't arise.
from tkinter import *
from tkinter.ttk import *
class ButtonApp(Frame):
"""Container for the buttons."""
def __init__(self, master=None):
"""Initialize the frame and its children."""
super().__init__(master)
self.createWidgets()
# configure the frame's resize behaviour
master.columnconfigure(0, weight=1)
master.rowconfigure(0, weight=1)
self.grid(sticky=(N,S,E,W))
# configure resize behaviour for the frame's children
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# bind to window resize events
self.bind('<Configure>', self.resize)
def createWidgets(self):
"""Make the widgets."""
# this button mutates
self.mutantButton = Button(self, text='Press Me',
style='10.TButton')
self.mutantButton.grid(column=0, row=0, sticky=(N,S,E,W))
self.mutantButton['command'] = self.mutate
# an ordinary quit button for comparison
self.quitButton = Button(self, text='Quit', style='TButton')
self.quitButton.grid(column=0, row=1, sticky=(N,S,E,W))
self.quitButton['command'] = self.quit
def mutate(self):
"""Rotate through the styles by hitting the button."""
style = int(self.mutantButton['style'].split('.')[0])
newStyle = style + 5
if newStyle > 50: newStyle = 10
print('Choosing font '+str(newStyle))
self.mutantButton['style'] = fontStyle[newStyle]
# resize the frame
# get the current geometries
currentGeometry = self._root().geometry()
w, h, x, y = self.parseGeometry(currentGeometry)
reqWidth = self.mutantButton.winfo_reqwidth()
reqHeight = self.mutantButton.winfo_reqheight()
# note assume height of quit button is constant at 20.
w = max([w, reqWidth])
h = 20 + reqHeight
self._root().geometry('%dx%d+%d+%d' % (w, h, x, y))
def parseGeometry(self, geometry):
"""Geometry parser.
Returns the geometry as a (w, h, x, y) tuple."""
# get w
xsplit = geometry.split('x')
w = int(xsplit[0])
rest = xsplit[1]
# get h, x, y
plussplit = rest.split('+')
h = int(plussplit[0])
x = int(plussplit[1])
y = int(plussplit[2])
return w, h, x, y
def resize(self, event):
"""Method bound to the <Configure> event for resizing."""
# get geometry info from the root window.
wm, hm = self._root().winfo_width(), self._root().winfo_height()
# choose a font height to match
# note subtract 30 for the button we are NOT scaling.
# note we assume optimal font height is 1/2 widget height.
fontHeight = (hm - 20) // 2
print('Resizing to font '+str(fontHeight))
# calculate the best font to use (use int rounding)
bestStyle = fontStyle[10] # use min size as the fallback
if fontHeight < 10: pass # the min size
elif fontHeight >= 50: # the max size
bestStyle = fontStyle[50]
else: # everything in between
bestFitFont = (fontHeight // 5) * 5
bestStyle = fontStyle[bestFitFont]
# set the style on the button
self.mutantButton['style'] = bestStyle
root = Tk()
root.title('Alice in Pythonland')
# make a dictionary of sized font styles in the range of interest.
fontStyle = {}
for font in range(10, 51, 5):
styleName = str(font)+'.TButton'
fontName = ' '.join(['helvetica', str(font), 'bold'])
fontStyle[font] = styleName
Style().configure(styleName, font=fontName)
# run the app
app = ButtonApp(master=root)
app.mainloop()
root.destroy()

Resources