how to set axis interval in candle stick using pyqtgraph with pyqt5? - python-3.x

I am using pyqtgraph to draw a candle stick like the following:
#-------------------
# Create pyqtgraph module
#-------------------
def GUI_DRAW_create():
"""
set default config
"""
pg.setConfigOption('background', 'w') #background: white
pg.setConfigOption('foreground', 'k') #font: black
class TimeAxisItem(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
#values is not my date in timestamp
return [datetime.fromtimestamp(value) for value in values]
## Create a subclass of GraphicsObject.
## The only required methods are paint() and boundingRect()
## (see QGraphicsItem documentation)
class CandlestickItem(pg.GraphicsObject):
def __init__(self, data):
pg.GraphicsObject.__init__(self)
self.data = data ## data must have fields: time, open, close, min, max
self.generatePicture()
def generatePicture(self):
## pre-computing a QPicture object allows paint() to run much more quickly,
## rather than re-drawing the shapes every time.
self.picture = QtGui.QPicture()
p = QtGui.QPainter(self.picture)
p.setPen(pg.mkPen('k'))
w = (self.data[1][0] - self.data[0][0]) / 3.
for (t, open, close, min, max) in self.data:
p.drawLine(QtCore.QPointF(t, min), QtCore.QPointF(t, max))
if open > close:
p.setBrush(pg.mkBrush('r'))
else:
p.setBrush(pg.mkBrush('g'))
p.drawRect(QtCore.QRectF(t-w, open, w*2, close-open))
p.end()
# I try to print out t here, is my date
def paint(self, p, *args):
p.drawPicture(0, 0, self.picture)
def boundingRect(self):
## boundingRect _must_ indicate the entire area that will be drawn on
## or else we will get artifacts and possibly crashing.
## (in this case, QPicture does all the work of computing the bouning rect for us)
return QtCore.QRectF(self.picture.boundingRect())
class GUI_DRAW_new(QMainWindow):
def __init__(self):
super().__init__()
GUI_DRAW_create()
self.setWindowTitle("pyqtgraph example: PlotWidget")
cw = QWidget()
self.setCentralWidget(cw)
main_layout = QHBoxLayout()
cw.setLayout(main_layout)
#variable
self.signalgraph = None
self.data = []
self.vb = None
self.vLine = None
# define plot windows
self.GUI_DRAW_new_graph()
main_layout.addWidget(self.signalgraph)
self.signalgraph.setMouseTracking(True)
self.signalgraph.viewport().installEventFilter(self)
self.show()
def eventFilter(self, source, event):
try:
if (event.type() == QtCore.QEvent.MouseMove and
source is self.signalgraph.viewport()):
pos = event.pos()
print('mouse move: (%d, %d)' % (pos.x(), pos.y()))
if self.signalgraph.sceneBoundingRect().contains(pos):
mousePoint = self.vb.mapSceneToView(pos)
index = int(mousePoint.x())
int(index)
#if index > 0 and index < len(self.data):
#print(self.xdict[index])
# self.label.setHtml("<p style='color:black'>日期:{0}</p>".format(self.data[index]))
# self.label.setPos(mousePoint.x(),mousePoint.y())
self.vLine.setPos(mousePoint.x())
return QtGui.QWidget.eventFilter(self, source, event)
except Exception as e:
traceback.print_exc()
err = sys.exc_info()[1]
PRINT_DEBUG(0,str(err))
def GUI_DRAW_new_graph(self):
try:
self.signalgraph = pg.PlotWidget(name="Signalgraph", axisItems={'bottom': TimeAxisItem(orientation='bottom')})
# sample data
self.data = [ ## fields are (time, open, close, min, max).
(1514995200.0, 102.610001, 105.349998, 102, 105.370003),
(1515081600.0, 105.75, 102.709999, 102.410004, 105.849998),
(1515168000.0, 100.559998, 102.370003, 99.870003, 100.699997),
(1515254400.0, 98.68, 96.449997, 96.43, 100.129997),
(1515340800.0, 98.550003, 96.959999, 96.760002, 99.110001),
(1515427200.0, 102.610001, 105.349998, 102, 105.370003),
(1515513600.0, 105.75, 102.709999, 102.410004, 105.849998),
(1515600000.0, 100.559998, 102.370003, 99.870003, 100.699997),
(1515686400.0, 98.68, 96.449997, 96.43, 100.129997),
(1515772800.0, 98.550003, 96.959999, 96.760002, 99.110001),
]
#if comment this 2 code, can see the string
item = CandlestickItem(self.data)
self.signalgraph.addItem(item)
#trick
s_day = datetime.fromtimestamp(self.data[0][0]).strftime("%Y-%m-%d")
e_day = datetime.fromtimestamp(self.data[len(self.data) - 1][0]).strftime("%Y-%m-%d")
tr=np.arange(s_day, e_day, dtype='datetime64') # tick labels one day
tday0=(tr-tr[0])/(tr[-1]-tr[0]) #Map time to 0.0-1.0 day 2 1.0-2.0 ...
tday1=tday0+1
tnorm=np.concatenate([tday0,tday1])
tr[-1]=tr[0] # End day=start next day
ttick=list()
for i,t in enumerate(np.concatenate([tr,tr])):
tstr=np.datetime64(t).astype(datetime)
ttick.append( (tnorm[i], tstr.strftime("%Y-%m-%d")))
ax=self.signalgraph.getAxis('bottom') #This is the trick
ax.setTicks([ttick])
#cross hair in signalgraph
self.vLine = pg.InfiniteLine(angle=90, movable=False)
self.signalgraph.addItem(self.vLine, ignoreBounds=True)
self.vb = self.signalgraph.plotItem.vb
except Exception as e:
traceback.print_exc()
err = sys.exc_info()[1]
print(0,str(err))
# Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == "__main__":
app = QtGui.QApplication([])
gui = GUI_DRAW_new()
app.exec_()
The result is:
candlestick_graph_center
I want to set the x-axis interval using the date: [2018-01-04, 2018-01-05, 2018-01-06, ....].
Thanks for help, many thanks
Update
I figure out that why all data squeeze together, because of the TextItem.
So that, I rewrite the code.
I try to use tickStrings in AxisItem to convert value to string, when I print out the values in tickStrings, it is not my data value (date in timestamp). Why the values are different? Thanks a lots
Update
If I use setTicks with candle stick, it cannot show string in the graph. Only can show the string without candle stick.
Any idea?

You have to convert into a string with the appropriate format using strftime():
from datetime import datetime
class TimeAxisItem(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
#values is not my date in timestamp
return [datetime.fromtimestamp(value).strftime("%Y-%m-%d") for value in values]
Or:
from datetime import date
class TimeAxisItem(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
#values is not my date in timestamp
return [date.fromtimestamp(value) for value in values]
Or:
from datetime import datetime
class TimeAxisItem(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
#values is not my date in timestamp
return [datetime.fromtimestamp(value).date() for value in values]

Related

Pyqtgraph: Overloaded AxisItem only showing original data

I'm building an application to view real time data based on the examples for scrolling plots. My x-axis should display time as a formated string. The x-values added to the plot are timestamp floats in seconds. Here is a simplified version of my plot window code.
Everything works in real time and I have no problem showing the values i want to plot, but the labels for my x-axis are only the timestamps not the formated strings. I know that the function formatTimestampToString(val) in the AxisItem overload returns a good string value.
import pyqtgraph as pg
class NewLegend(pg.LegendItem):
def __init__(self, size=None, offset=None):
pg.LegendItem.__init__(self, size, offset)
def paint(self, p, *args):
p.setPen(pg.mkPen(0,0,0)) # outline
p.setBrush(pg.mkBrush(255,255,255)) # background
p.drawRect(self.boundingRect())
class DateAxis(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
strings = []
for val in values:
strings.append(formatTimestampToString(val))
return strings
class PlotWindow(QtWidgets.QWidget):
def __init__(self, iof, num):
QtWidgets.QWidget.__init__(self)
self.setWindowTitle('Plot Window ')
self.resize(1000, 800)
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
"""
... other stuff ...
"""
# Externally updated dict with data
self.data = {}
self.curves = {}
self.plotWidget = pg.GraphicsWindow("Graph Window")
axis = DateAxis(orientation='bottom')
self.plot = self.plotWidget.addPlot(axisItem={'bottom': axis})
self.plot.setAutoPan(x=True)
self.legend = NewLegend(size=(100, 60), offset=(70, 30))
self.legend.setParentItem(self.plot.graphicsItem())
def updatePlots(self):
#update data for the different curves
for data_name, curve in self.curves.items():
if curve != 0:
curve.setData(y=self.data[data_name].values[:], x=self.data[data_name].timestamps[:])
def addCurve(self, data_name):
if data_name not in self.curves:
self.curves[data_name] = self.plot.plot(y=self.data[data_name].values[:], x=self.data[data_name].timestamps[:], pen=pg.mkPen(color=self.randomColor(),width=3))
self.legend.addItem(self.curves[data_name], name=data_name)
def removeCurve(self, data_name):
if data_name in self.curves:
self.plot.removeItem(self.curves[data_name])
self.legend.removeItem(data_name)
del self.curves[data_name]
Am i doing something wrong? Is there a better way to overload the AxisItem? I have also tried to overload the AxisItem with a simple numerical formula just to see if it has any effect on my axis.
Another problem i have is with the LegendItem: I can add and subtract labels with no problem, but it only updates the size of the legend box when adding labels. This means that when I add and remove curves/data in my plot the legend grows, but never shrinks down again. I have tried calling the LegendItem.updateSize() function after removing labels, but nothing happens.
I hope you can help! Thanks
So I found the problem. In self.plot = self.plotWidget.addPlot(axisItem={'bottom': axis}) its supposed to say axisItems, not axisItem.

How to use decorator and property to check on class attributes' type and range efficiently?

import math
import random
# === Problem 1
class RectangularRoom(object):
"""
A RectangularRoom represents a rectangular region containing clean or dirty
tiles.
A room has a width and a height and contains (width * height) tiles. Each tile
has some fixed amount of dirt. The tile is considered clean only when the amount
of dirt on this tile is 0.
"""
def __init__(self, width, height, dirt_amount):
"""
Initializes a rectangular room with the specified width, height, and
dirt_amount on each tile.
width: an integer > 0
height: an integer > 0
dirt_amount: an integer >= 0
"""
self.width, self.height, self.dirt_amount = width, height, dirt_amount
tiles = [(w,h) for w in range(width) for h in range(height)]
self.room = {tile:dirt for tile in tiles for dirt in [dirt_amount]}
#raise NotImplementedError
def get_width(self):
return self._width
def set_width(self, value):
if value <= 0 :
raise ValueError("Must be greater than 0")
self._width = value
width = property(get_width,set_width)
def __str__(self):
return str((self.room))
This is what I've done so far with this room object. I am trying to make height, dirt_amount also limited to int and either greater than zero or greater and equal to zero. Is there an easier or more efficient way to code these constraints for the other two attributes?
import types
from functools import wraps
def range_check(func):
code = func.__code__
allargs = list(code.co_varnames[:code.co_argcount])
#wraps
def _decorator(*args, **kargs):
positionals = allargs[:len(args)]
for argname, check in func.__annotations__.items():
if argname in kargs:
if not check(kargs[argname]):
raise TypeError('"%s" check failed' % argname)
elif argname in positionals:
pos = positionals.index(argname)
if not check(args[pos]):
raise TypeError('"%s" check failed' % argname)
else:
# argument defaulted
pass
return func(*args, **kargs)
return _decorator
def range(ratio):
return 0 <= ratio and ratio <= 0.5
class Employee:
def __init__(self, name, age, pay):
self.name = name
self.age = age
self.pay = pay
#range_check
def giveRaise(self, ratio: range):
self.pay *= (1 + ratio)
if __name__ == '__main__':
alice = Employee('alice', 20, 50000)
alice.giveRaise(0.1)
alice.giveRaise(0.6)
This is a sample code to do checking of arguments passed to function.
1. use __annotations__ in python3 to pass check functions in function declaration.
2. use 'co_varnames[co_argcount]' to retrieve argument name list.
3. positional arguments are always placed before keyword arguments.

big raw video file seeking in python gstreamer

I'm working on program, that should display big raw video, seek in it that I'd be able to set from and to times cuts in it, set black borders sizes to hide shaked borders of image. The crucial part of this project is seeking. I've tried 5min file cutoff and when I seek at the start of the video, it's OK but after middle something goes wrong. Since it seems there is no much examples and documentation I'm using this:
self.pipe.seek_simple(
Gst.Format.TIME,
Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
destSeek
)
My actual (non-mature) code:
import sys
import gi
gi.require_version('Gst', '1.0')
gi.require_version('Gtk', '3.0')
gi.require_version('GdkX11', '3.0')
gi.require_version('GstVideo', '1.0')
from gi.repository import GObject, Gst, Gtk, GdkX11, GstVideo, Gdk
GObject.threads_init()
Gst.init(None)
testGrab = "testRAW.mkv"
class VideoDec(Gst.Bin):
def __init__(self):
super().__init__()
# elements
q1 = Gst.ElementFactory.make('queue', None)
videoparse = Gst.ElementFactory.make('videoparse', None)
q2 = Gst.ElementFactory.make('queue', None)
self.add(q1)
self.add(videoparse)
self.add(q2)
videoparse.set_property('width', 720)
videoparse.set_property('height', 576)
videoparse.set_property('format', 4)
# link
q1.link(videoparse)
videoparse.link(q2)
# Add Ghost Pads
self.add_pad(
Gst.GhostPad.new('sink', q1.get_static_pad('sink'))
)
self.add_pad(
Gst.GhostPad.new('src', q2.get_static_pad('src'))
)
class AudioDec(Gst.Bin):
def __init__(self):
super().__init__()
# elements
q1 = Gst.ElementFactory.make('queue', None)
audioparse = Gst.ElementFactory.make('audioparse', None)
q2 = Gst.ElementFactory.make('queue', None)
#sink = Gst.ElementFactory.make('autoaudiosink', None)
self.add(q1)
self.add(audioparse)
self.add(q2)
#self.add(sink)
# link
q1.link(audioparse)
audioparse.link(q2)
#audioparse.link(sink)
# Add Ghost Pads
self.add_pad(
Gst.GhostPad.new('sink', q1.get_static_pad('sink'))
)
self.add_pad(
Gst.GhostPad.new('src', q2.get_static_pad('src'))
)
class Player(object):
def __init__(self):
self.fps = 25
self.window = Gtk.Window()
self.window.connect('destroy', self.quit)
self.window.set_default_size(800, 600)
self.drawingarea = Gtk.DrawingArea()
#hbox
self.hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.window.add(self.hbox)
Gtk.Box.pack_start(self.hbox, self.drawingarea, True, True, 0)
self.setPipeline()
self.setGUI()
self.setShortcuts()
self.playing = False
def setPipeline(self):
self.pipe = Gst.Pipeline.new('player')
# Create bus to get events from GStreamer pipeline
self.bus = self.pipe.get_bus()
self.bus.add_signal_watch()
self.bus.connect('message::eos', self.on_eos)
self.bus.connect('message::error', self.on_error)
# This is needed to make the video output in our DrawingArea:
self.bus.enable_sync_message_emission()
self.bus.connect('sync-message::element', self.on_sync_message)
self.src = Gst.ElementFactory.make('filesrc', None)
self.src.set_property("location", testGrab)
self.dec = Gst.ElementFactory.make('decodebin', None)
self.video = VideoDec()
self.audio = AudioDec()
self.glimagesink = Gst.ElementFactory.make('glimagesink', None)
self.audiosink = Gst.ElementFactory.make('autoaudiosink', None)
self.pipe.add(self.src)
self.pipe.add(self.dec)
self.pipe.add(self.video)
self.pipe.add(self.audio)
self.pipe.add(self.glimagesink)
self.pipe.add(self.audiosink)
#self.pipe.add(self.autovideosink)
# Connect signal handlers
self.dec.connect('pad-added', self.on_pad_added)
# link
self.src.link(self.dec)
self.video.link(self.glimagesink)
self.audio.link(self.audiosink)
def on_pad_added(self, element, pad):
string = pad.query_caps(None).to_string()
print('on_pad_added():', string)
if string.startswith('audio/'):
pad.link(self.audio.get_static_pad('sink'))
elif string.startswith('video/'):
pad.link(self.video.get_static_pad('sink'))
def setGUI(self):
vbox = Gtk.Box(Gtk.Orientation.HORIZONTAL, 0)
vbox.set_margin_top(3)
vbox.set_margin_bottom(3)
Gtk.Box.pack_start(self.hbox, vbox, False, False, 0)
self.playButtonImage = Gtk.Image()
self.playButtonImage.set_from_stock("gtk-media-play", Gtk.IconSize.BUTTON)
self.playButton = Gtk.Button.new()
self.playButton.add(self.playButtonImage)
self.playButton.connect("clicked", self.playToggled)
Gtk.Box.pack_start(vbox, self.playButton, False, False, 0)
self.slider = Gtk.HScale()
self.slider.set_margin_left(6)
self.slider.set_margin_right(6)
self.slider.set_draw_value(False)
self.slider.set_range(0, 100)
self.slider.set_increments(1, 10)
Gtk.Box.pack_start(vbox, self.slider, True, True, 0)
self.label = Gtk.Label(label='0:00')
self.label.set_margin_left(6)
self.label.set_margin_right(6)
Gtk.Box.pack_start(vbox, self.label, False, False, 0)
def setShortcuts(self):
accel = Gtk.AccelGroup()
accel.connect(Gdk.KEY_space, Gdk.ModifierType.CONTROL_MASK, 0, self.playToggled)
accel.connect(Gdk.KEY_Right, Gdk.ModifierType.CONTROL_MASK, 0, self.seekFW0)
accel.connect(Gdk.KEY_Right, Gdk.ModifierType.CONTROL_MASK|Gdk.ModifierType.SHIFT_MASK, 0, self.seekFW10s)
accel.connect(Gdk.KEY_Right, Gdk.ModifierType.SHIFT_MASK, 0, self.seekFW2)
accel.connect(Gdk.KEY_Right, Gdk.ModifierType.MOD1_MASK, 0, self.seekFW10) # alt key
self.window.add_accel_group(accel)
def seekFW0(self, *args):
self.seekTime = 2 * Gst.SECOND // self.fps
self.seekFW()
def seekFW10s(self, *args):
self.seekTime = Gst.SECOND * 10
self.seekFW()
def seekFW2(self, *args):
self.seekTime = Gst.SECOND * 60 * 2
self.seekFW()
def seekFW10(self, *args):
self.seekTime = Gst.SECOND * 60 * 10
self.seekFW()
def seekFW(self, *args):
nanosecs = self.pipe.query_position(Gst.Format.TIME)[1]
destSeek = nanosecs + self.seekTime
self.pipe.seek_simple(
Gst.Format.TIME,
Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
destSeek
)
def play(self):
self.pipe.set_state(Gst.State.PLAYING)
GObject.timeout_add(1000, self.updateSlider)
def stop(self):
self.pipe.set_state(Gst.State.PAUSED)
def playToggled(self, *w):
if(self.playing == False):
self.play()
else:
self.stop()
self.playing=not(self.playing)
self.updateButtons()
def updateSlider(self):
try:
nanosecs = self.pipe.query_position(Gst.Format.TIME)[1]
duration_nanosecs = self.pipe.query_duration(Gst.Format.TIME)[1]
# block seek handler so we don't seek when we set_value()
# self.slider.handler_block_by_func(self.on_slider_change)
duration = float(duration_nanosecs) / Gst.SECOND
position = float(nanosecs) / Gst.SECOND
self.slider.set_range(0, duration)
self.slider.set_value(position)
self.label.set_text ("%d" % (position / 60) + ":%02d" % (position % 60))
#self.slider.handler_unblock_by_func(self.on_slider_change)
except Exception as e:
# pipeline must not be ready and does not know position
print(e)
pass
return True
def updateButtons(self):
if(self.playing == False):
self.playButtonImage.set_from_stock("gtk-media-play", Gtk.IconSize.BUTTON)
else:
self.playButtonImage.set_from_stock("gtk-media-pause", Gtk.IconSize.BUTTON)
def run(self):
self.window.show_all()
# You need to get the XID after window.show_all(). You shouldn't get it
# in the on_sync_message() handler because threading issues will cause
# segfaults there.
self.xid = self.drawingarea.get_property('window').get_xid()
#self.pipeline.set_state(Gst.State.PLAYING)
Gtk.main()
def quit(self, window):
self.pipe.set_state(Gst.State.NULL)
Gtk.main_quit()
def on_sync_message(self, bus, msg):
if msg.get_structure().get_name() == 'prepare-window-handle':
print('prepare-window-handle')
msg.src.set_window_handle(self.xid)
def on_eos(self, bus, msg):
#print('on_eos(): seeking to start of video')
print('on_eos(): pausing video')
self.stop()
#self.pipeline.seek_simple(
# Gst.Format.TIME,
# Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
# 0
#)
#self.playing = False
#self.slider.set_value(0)
#self.label.set_text("0:00")
#self.updateButtons()
def on_error(self, bus, msg):
print('on_error():', msg.parse_error())
p = Player()
p.run()
If somebody could share wisdom or documentation/example links I'd be happy.
I no longer remember quite why I gave up using seek_simple but I do recall that it caused me no end of grief.
Use instead a standard seek:
Gplayer.seek(self.rate, Gst.Format.TIME,
(Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE),
Gst.SeekType.SET, seek_time , Gst.SeekType.NONE, -1)
Gstreamer1.0 seek
Late Edit:
I believe the issue was getting accurate and consistent timestamps, when I was using the scaletempo element. The scaletempo element caters for adjusting the speed at which playback occurs and I was getting inconsistent times from the player if I varied the rate of play. I moved from seek_simple to seek to resolve the issue.
Note: self.rate above is a user defined variable, which for normal playback would be 1.00, dropping below 1.00 for slower playback and above 1.00 for faster playback.

python print contents from wx.ListCtrl

I have a list created as
self.statusListCtrl = wx.ListCtrl(self.panelUpper, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
I add data to this list using
self.statusListCtrl.Append([datetime.datetime.now(),action,result])
When my process is all done I have a nice list showing things that were tried, the result of that attempt and a datetime stamp. Now what I want to do is output that to a text file. my problem is that I cant get the data from my listctrl correctly.
This is how I am trying to iterate through the list.
fileName = 'd:\ssplogs\sspInstaller.log'
FILE = open(fileName,"w")
for itemIdx in range(0,self.statusListCtrl.GetItemCount()):
line = str(self.statusListCtrl.GetItemText(itemIdx) + "\n")
print "line" , line
writeLine = line
FILE.write(writeLine)
FILE.close()
The output I am getting though is only my datetime stamp, which is the first column of my list. How do I get this so I see something like
datetime, action, result
Use this to get Row x Column data:
self.list.GetItem(row, col).GetText()
Working example:
import wx
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.panel = wx.Panel(self)
self.list = wx.ListCtrl(self.panel, style=wx.LC_REPORT)
self.list.InsertColumn(0, "Date")
self.list.InsertColumn(1, "Action")
self.list.InsertColumn(2, "Result")
for a in range(5):
self.list.Append(["Date %d" % a, "Action %d" % a, "Result %d" % a])
self.sizer = wx.BoxSizer()
self.sizer.Add(self.list, proportion=1, flag=wx.EXPAND)
self.panel.SetSizerAndFit(self.sizer)
self.Show()
# Save
for row in range(self.list.GetItemCount()):
print(", ".join([self.list.GetItem(row, col).GetText() for col in range(self.list.GetColumnCount())]))
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()

custom delegate doesn't follow when reordering QTableView

I'm using a custom delegate to display a column of comboBoxes in my QTableView.
In addition to the default selection issue (enter link description here) I have a problem when I reorder the data of my QTableView (per column, or by applying filters). The comboxes stay where they were when the grid was not displayed.
Is there a way I can force a repaint of the delegate ? I to copy the code of the paint method (without the index) but this only caused my program to crash.
Let me know if I'm not clear enough.
Here is the code of my custom delegate :
class ComboBoxDelegate(QtGui.QItemDelegate):
def __init__(self, parent, itemslist):
QtGui.QItemDelegate.__init__(self, parent)
self.itemslist = itemslist
self.parent = parent
def paint(self, painter, option, index):
# Get Item Data
value = index.data(QtCore.Qt.DisplayRole).toInt()[0]
# value = self.itemslist[index.data(QtCore.Qt.DisplayRole).toInt()[0]]
# fill style options with item data
style = QtGui.QApplication.style()
opt = QtGui.QStyleOptionComboBox()
opt.currentText = str(self.itemslist[value])
opt.rect = option.rect
# draw item data as ComboBox
style.drawComplexControl(QtGui.QStyle.CC_ComboBox, opt, painter)
self.parent.openPersistentEditor(index)
def createEditor(self, parent, option, index):
##get the "check" value of the row
# for row in range(self.parent.model.rowCount(self.parent)):
# print row
self.editor = QtGui.QComboBox(parent)
self.editor.addItems(self.itemslist)
self.editor.setCurrentIndex(0)
self.editor.installEventFilter(self)
self.connect(self.editor, QtCore.SIGNAL("currentIndexChanged(int)"), self.editorChanged)
return self.editor
# def setEditorData(self, editor, index):
# value = index.data(QtCore.Qt.DisplayRole).toInt()[0]
# editor.setCurrentIndex(value)
def setEditorData(self, editor, index):
text = self.itemslist[index.data(QtCore.Qt.DisplayRole).toInt()[0]]
pos = self.editor.findText(text)
if pos == -1:
pos = 0
self.editor.setCurrentIndex(pos)
def setModelData(self,editor,model,index):
value = self.editor.currentIndex()
model.setData(index, QtCore.QVariant(value))
def updateEditorGeometry(self, editor, option, index):
self.editor.setGeometry(option.rect)
def editorChanged(self, index):
check = self.editor.itemText(index)
id_seq = self.parent.selectedIndexes[0][0]
update.updateCheckSeq(self.parent.db, id_seq, check)
def updateDelegate(self, indexRow, indewCol):
# index = self.parent.model.createIndex(indexRow, indewCol)
seq_id = self.parent.model.arraydata[indexRow][0]
print seq_id
check = select.getCheck(self.parent.db, seq_id)
check = check[0][0]
print check
if check != '':
pos = self.checkDict[check]
else:
pos = 0
self.editor.setCurrentIndex(pos)
And I call it from my QTableView class :
self.setEditTriggers(QtGui.QAbstractItemView.CurrentChanged)
self.viewport().installEventFilter(self)
self.delegate = ComboBoxDelegate(self, self.checkValues)
self.setItemDelegateForColumn(13,self.delegate)
I call the updateDelegate function when I sort the column (from the model class) :
def sort(self, Ncol, order):
self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))
self.arraydata = sorted(self.arraydata, key=operator.itemgetter(Ncol))
i = 0
for row in self.arraydata:
self.parent.delegate.updateDelegate(i, 13)
i += 1
if order == QtCore.Qt.DescendingOrder:
self.arraydata.reverse()
self.emit(QtCore.SIGNAL("layoutChanged()"))
I needed to call QItemDelegate.paint() method in my custom paint() method. Hope it can help someone.

Resources