Qt6: how to disable selection for empty cells in QTableView? - python-3.9

I'm trying to display some data from a database in a grid view, similar to how a file manager works. I thought of using a QTableView as the grid view since it did what I wanted out of the box. However, as shown with the below given MRE, even if just a single cell has value, you can still select the other empty cells, how can I prevent this? Basically, I want to make it so that only cells with a value can be selected.
MRE:
from PySide6 import QtWidgets as qtw
from PySide6 import QtGui as qtg
from PySide6 import QtCore as qtc
ROW_COUNT = 5
COL_COUNT = 5
class Model(qtc.QAbstractTableModel):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._data = [[None for _ in range(COL_COUNT)] for _ in range(ROW_COUNT)]
def data(self, index: qtc.QModelIndex, role: qtc.Qt.ItemDataRole):
if not index.isValid():
return None
if role == qtc.Qt.ItemDataRole.DisplayRole:
return self._data[index.row()][index.column()]
return None
def setData(self, index: qtc.QModelIndex, value, role: qtc.Qt.ItemDataRole=qtc.Qt.ItemDataRole.DisplayRole):
if not index.isValid():
return False
if role == qtc.Qt.ItemDataRole.DisplayRole:
self._data[index.row()][index.column()] = value
return False
def rowCount(self, _):
return ROW_COUNT
def columnCount(self, _):
return COL_COUNT
app = qtw.QApplication()
view = qtw.QTableView()
view.setModel(Model())
view.setShowGrid(False)
view.verticalHeader().setVisible(False)
view.horizontalHeader().setVisible(False)
view.model().setData(view.model().createIndex(0, 0), "this is a test")
view.show()
app.exec()

You need to override the flags() and ensure that it doesn't return the ItemIsSelectable flag.
class Model(qtc.QAbstractTableModel):
# ...
def flags(self, index):
flags = super().flags(index)
if index.data() is None:
flags &= ~qtc.Qt.ItemIsSelectable
return flags
In your case, you also probably want to avoid the ItemIsEnabled, and since these two flags are the default one, you can just return NoItemFlags
def flags(self, index):
if index.data() is None:
return qtc.Qt.NoItemFlags
return super().flags(index)
If you also need to clear the selection, then you could subclass the view and do it in the mousePressEvent():
class TableView(qtw.QTableView):
def mousePressEvent(self, event):
index = self.indexAt(event.pos())
if index.isValid() and not index.flags() & qtc.Qt.ItemIsSelectable:
self.clearSelection()
else:
super().mousePressEvent(event)

Related

Urwid and Multiprocessing

i try to sequence some actions in urwid
I made a timer which run in background and communicate with the mainprocess
like this:
from multiprocessing import Process, Pipe
import time
import urwid
def show_or_exit(key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
class midiloop(urwid.Frame):
def __init__(self):
self.message = urwid.Text('Press Space', align='center')
self.filler = urwid.Filler(self.message, "middle")
super().__init__(urwid.Frame(self.filler))
def keypress(self, size, key):
if key == " ":
self.seq()
else:
return key
def timer(self,conn):
x = 0
while True:
if (conn.poll() == False):
pass
else:
z = conn.recv()
if (z == "kill"):
return()
conn.send(x)
x+=1
time.sleep(0.05)
def seq(self):
self.parent_conn, self.child_conn = Pipe()
self.p = Process(target=self.timer, args=(self.child_conn,))
self.p.start()
while True:
if (self.parent_conn.poll(None)):
self.y = self.parent_conn.recv()
self.message.set_text(str(self.y))
loop.draw_screen()
if ( self.y > 100 ):
self.parent_conn.send("kill")
self.message.set_text("Press Space")
return()
if __name__ == '__main__':
midiloop = midiloop()
loop = urwid.MainLoop(midiloop, unhandled_input=show_or_exit, handle_mouse=True)
loop.run()
The problem is i'm blocking urwid mainloop with while True:
So anyone can give me a solution to listen for key Q to quit the program before it reachs the end of the loop for example and more generally to interact with urwid and communicate with the subprocess
It seems to be rather complicated to combine multiprocessing and urwid.
Since you're using a timer and your class is called midiloop, I'm going to guess that maybe you want to implement a mini sequencer.
One possible way of implementing that is using an asyncio loop instead of urwid's MainLoop, and schedule events with the loop.call_later() function. I've implemented a simple drum machine with that approach in the past, using urwid for drawing the sequencer, asyncio for scheduling the play events and simpleaudio to play. You can see the code for that here: https://github.com/eliasdorneles/kickit
If you still want to implement communication with multiprocessing, I think your best bet is to use urwid.AsyncioEventLoop and the aiopipe helper for duplex communication.
It's not very minimal I'm afraid. However I did spend a day writing this Urwid frontend that starts, stops and communicates with a subprocess.
import os
import sys
from multiprocessing import Process, Pipe, Event
from collections import deque
import urwid
class suppress_stdout_stderr(object):
"""
Supresses the stdout and stderr by piping them to dev null...
The same place I send bad faith replies to my tweets
"""
def __enter__(self):
self.outnull_file = open(os.devnull, 'w')
self.errnull_file = open(os.devnull, 'w')
self.old_stdout_fileno_undup = sys.stdout.fileno()
self.old_stderr_fileno_undup = sys.stderr.fileno()
self.old_stdout_fileno = os.dup(sys.stdout.fileno())
self.old_stderr_fileno = os.dup(sys.stderr.fileno())
self.old_stdout = sys.stdout
self.old_stderr = sys.stderr
os.dup2(self.outnull_file.fileno(), self.old_stdout_fileno_undup)
os.dup2(self.errnull_file.fileno(), self.old_stderr_fileno_undup)
sys.stdout = self.outnull_file
sys.stderr = self.errnull_file
return self
def __exit__(self, *_):
sys.stdout = self.old_stdout
sys.stderr = self.old_stderr
os.dup2(self.old_stdout_fileno, self.old_stdout_fileno_undup)
os.dup2(self.old_stderr_fileno, self.old_stderr_fileno_undup)
os.close(self.old_stdout_fileno)
os.close(self.old_stderr_fileno)
self.outnull_file.close()
self.errnull_file.close()
def subprocess_main(transmit, stop_process):
with suppress_stdout_stderr():
import time
yup = ['yuuuup', 'yuuuuup', 'yeaup', 'yeoop']
nope = ['noooooooe', 'noooope', 'nope', 'nope']
mesg = 0
i = 0
while True:
i = i % len(yup)
if transmit.poll():
mesg = transmit.recv()
if mesg == 'Yup':
transmit.send(yup[i])
if mesg == 'Nope':
transmit.send(nope[i])
if stop_process.wait(0):
break
i += 1
time.sleep(2)
class SubProcess:
def __init__(self, main):
"""
Handles forking, stopping and communication with a subprocess
:param main: subprocess method to run method signature is
def main(transmit, stop_process):
transmit: is a multiprocess Pipe to send data to parent process
stop_process: is multiprocess Event to set when you want the process to exit
"""
self.main = main
self.recv, self.transmit = None, None
self.stop_process = None
self.proc = None
def fork(self):
"""
Forks and starts the subprocess
"""
self.recv, self.transmit = Pipe(duplex=True)
self.stop_process = Event()
self.proc = Process(target=self.main, args=(self.transmit, self.stop_process))
self.proc.start()
def write_pipe(self, item):
self.recv.send(item)
def read_pipe(self):
"""
Reads data sent by the process into a list and returns it
:return:
"""
item = []
if self.recv is not None:
try:
while self.recv.poll():
item += [self.recv.recv()]
except:
pass
return item
def stop(self):
"""
Sets the event to tell the process to exit.
note: this is co-operative multi-tasking, the process must respect the flag or this won't work!
"""
self.stop_process.set()
self.proc.join()
class UrwidFrontend:
def __init__(self, subprocess_main):
"""
Urwid frontend to control the subprocess and display it's output
"""
self.title = 'Urwid Frontend Demo'
self.choices = 'Start Subprocess|Quit'.split('|')
self.response = None
self.item = deque(maxlen=10)
self.event_loop = urwid.SelectEventLoop()
# start the heartbeat
self.event_loop.alarm(0, self.heartbeat)
self.main = urwid.Padding(self.main_menu(), left=2, right=2)
self.top = urwid.Overlay(self.main, urwid.SolidFill(u'\N{MEDIUM SHADE}'),
align='center', width=('relative', 60),
valign='middle', height=('relative', 60),
min_width=20, min_height=9)
self.loop = urwid.MainLoop(self.top, palette=[('reversed', 'standout', ''), ], event_loop=self.event_loop)
self.subprocess = SubProcess(subprocess_main)
def exit_program(self, button):
raise urwid.ExitMainLoop()
def main_menu(self):
body = [urwid.Text(self.title), urwid.Divider()]
for c in self.choices:
button = urwid.Button(c)
urwid.connect_signal(button, 'click', self.handle_button, c)
body.append(urwid.AttrMap(button, None, focus_map='reversed'))
return urwid.ListBox(urwid.SimpleFocusListWalker(body))
def subproc_menu(self):
self.response = urwid.Text('Waiting ...')
body = [self.response, urwid.Divider()]
choices = ['Yup', 'Nope', 'Stop Subprocess']
for c in choices:
button = urwid.Button(c)
urwid.connect_signal(button, 'click', self.handle_button, c)
body.append(urwid.AttrMap(button, None, focus_map='reversed'))
listbox = urwid.ListBox(urwid.SimpleFocusListWalker(body))
return listbox
def update_subproc_menu(self, text):
self.response.set_text(text)
def handle_button(self, button, choice):
if choice == 'Start Subprocess':
self.main.original_widget = self.subproc_menu()
self.subprocess.fork()
self.item = deque(maxlen=10)
if choice == 'Stop Subprocess':
self.subprocess.stop()
self.main.original_widget = self.main_menu()
if choice == 'Quit':
self.exit_program(button)
if choice == 'Yup':
self.subprocess.write_pipe('Yup')
if choice == 'Nope':
self.subprocess.write_pipe('Nope')
def heartbeat(self):
"""
heartbeat that runs 24 times per second
"""
# read from the process
self.item.append(self.subprocess.read_pipe())
# display it
if self.response is not None:
self.update_subproc_menu(['Subprocess started\n', f'{self.item}\n', ])
self.loop.draw_screen()
# set the next beat
self.event_loop.alarm(1 / 24, self.heartbeat)
def run(self):
self.loop.run()
if __name__ == "__main__":
app = UrwidFrontend(subprocess_main)
app.run()

Drag and drop image in Wxpython

I have created 3 panels in wxpython. One at top. And other 2 panels at bottom in vertical fashion. Panel2 consists of ListControl as list1 and Panel3 consist of ListControl as list2.
I have used wx.ImageList in conjunction with wx.ListCtrl in panel2 and panel3.
I am trying to drag the image from Panel2 to Panel3.
Have used:
self.list1.Bind(wx.EVT_LIST_BEGIN_DRAG, self.OnDragInit, id=self.list1.GetId())
I have used wx.FileDropTarget to define Drop target and wx.BitmapDataObject in the def OnDragInit function.
Piece of code is as follows:
class MyTarget(wx.FileDropTarget):
def __init__(self, object):
wx.FileDropTarget.__init__(self)
self.object = object
def OnDropFiles(self, x, y, filenames):
print(filenames)
return(True)
def OnDragInit(self, event):
text = self.list1.GetBitmap(event.GetIndex())
too = wx.BitmapDataObject(text)
src = wx.DropSource(self.list2)
src.SetData(too)
src.DoDragDrop(True)
Issue: The drop target is not accepting data.
I suspect that you are not setting SetDropTarget.
See here, FileDrTr = MyFileDropTarget(self) and Drop_Place.SetDropTarget(FileDrTr)
import wx
import wx.lib.newevent
drop_event, EVT_DROP_EVENT = wx.lib.newevent.NewEvent()
class MyFileDropTarget(wx.FileDropTarget):
def __init__(self, obj):
wx.FileDropTarget.__init__(self)
self.obj = obj
def OnDropFiles(self, x, y, filename):
#filename is a list of 1 or more files
#here we are restricting it 1 file by only taking the first item of the list
TempTxt = filename[0]
evt = drop_event(data=TempTxt)
wx.PostEvent(self.obj,evt)
return True
class Example(wx.Frame):
def __init__(self, parent, title):
super(Example, self).__init__(parent, title=title)
self.InitUI()
self.Center()
def InitUI(self):
panel = wx.Panel(self)
FileDrTr = MyFileDropTarget(self)
font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)
font.SetPointSize(9)
verBox = wx.BoxSizer(wx.VERTICAL)
horBoxOne = wx.BoxSizer(wx.HORIZONTAL)
TextLabel = wx.StaticText(panel, label = 'Drop file here')
TextLabel.SetFont(font)
horBoxOne.Add(TextLabel, flag=wx.RIGHT, border=10)
Drop_Place = wx.TextCtrl(panel)
Drop_Place.SetDropTarget(FileDrTr)
#Bind the drop event listener
self.Bind(EVT_DROP_EVENT, self.LabelTextUpdate)
horBoxOne.Add(Drop_Place, proportion=1)
verBox.Add(horBoxOne, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10)
verBox.Add((-1, 10))
horBoxTwo = wx.BoxSizer(wx.HORIZONTAL)
self.NewText = wx.StaticText(panel, label = 'Path will be')
horBoxTwo.Add(self.NewText, flag=wx.RIGHT, border=5)
verBox.Add(horBoxTwo, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10)
panel.SetSizer(verBox)
def LabelTextUpdate(self, event):
txt = event.data
self.NewText.SetLabel(txt)
def main():
app = wx.App()
ex = Example(None, title = 'drop and see file path')
ex.Show()
app.MainLoop()
if __name__ == '__main__':
main()
Note the target is the textctrl, dropping a file on the panel will achieve nothing, whilst dropping on the target (the textctrl called Drop_Place) will activate the event.

pyqt pass focus to delegate editor

My QStyledItemDelegate uses a QDialog (Qt.Popup) to display a QLineEdit and a QListWidget. I'm failing to have the focus set properly on the QLineEdit. The focus seems to remain on the QTreeView from which the editing began.
I've tried,
-setFocus() and setFocusPolicy(Qt.StrongFocus) on the QDialog editor and the QLineEdit within
-activateWindow() on the editor (although the pop is already on top)
my QDialog and QLineEdit subclasses:
class ValidatedLineEdit(QLineEdit):
def __init__(self, list_widget, parent=None):
QLineEdit.__init__(self, parent)
self._listW = list_widget
self._parent = parent
self.setFocusPolicy(Qt.StrongFocus)
self.setFocus()
self.activateWindow()
validator = QRegExpValidator(QRegExp("[\w\*]*"), self)
self.setValidator(validator)
def keyReleaseEvent(self, event):
'''
#param event QKeyEvent
'''
self._listW.clear()
text = self.text()
reg_exp = QRegExp(text.replace('*', '.*'))
self._listW.addItems(QStringList([a for a in self._parent._elementList if reg_exp.exactMatch(a)]))
return super(ValidatedLineEdit, self).keyReleaseEvent(event)
class ValidatedFilterListDialog(QDialog):
def __init__(self, element_list=[], window_flag=Qt.Popup, parent=None):
QDialog.__init__(self, parent, window_flag)
self._elementList = element_list
layout = QVBoxLayout(self)
self._listW = QListWidget(self)
self._lineE = ValidatedLineEdit(self._listW, parent=self)
self._lineE.setFocus()
self._listW.addItems(QStringList(sorted(self._elementList)))
layout.addWidget(self._lineE)
layout.addWidget(self._listW)
#property
def value(self):
return self._lineE.text()
#property
def line_edit(self):
return self._lineE
#property
def list_widget(self):
return self._listW
My delegate class,
class AddValidatedFilterDelegate(QStyledItemDelegate):
'''
A custom validator for the include/exclude items.
'''
def __init__(self, parent=None):
QStyledItemDelegate.__init__(self, parent)
self._parent = parent
def createEditor(self, parent, option, index):
'''
#param parent QWidget
#param option QStyleOptionViewItem
#param index QModelIndex
'''
if not index.isValid():
return 0
if index.column() == 0: #only on the cells in the first column
editor = generic.ValidatedFilterListDialog(element_list=sorted(index.model()._parent._elementList), parent=parent)
editor.setFocusPolicy(Qt.StrongFocus)
editor.activateWindow()
editor.line_edit.setFocus()
return editor
return super(AddValidatedFilterDelegate, self).createEditor(parent, option, index)
def sizeHint(self, option, index):
text = index.model().data(index)
document = QTextDocument()
document.setDefaultFont(option.font)
document.setHtml(text)
return QSize(document.idealWidth() + 5,
option.fontMetrics.height())
def setEditorData(self, editor, index):
value = index.model().data(index, Qt.DisplayRole)
editor.line_edit.setText(value)
editor.line_edit.setFocus()
def setModelData(self, editor, model, index):
model.setData(index, QVariant(editor.value))
def updateEditorGeometry(self, editor, option, index):
top_left = self._parent.mapToGlobal(self._parent.rect().topLeft())
x = top_left.x()
y = top_left.y() + editor.rect().y()
w = self._parent.width()
editor.setGeometry(x, y, w, 400)
Anything stick out??

Can I make a QToolButton perform QLabel.setLayout(someLayout)? -- setting row height isn't working

My only GUI experience is with java.swing. I'm using PySide to update which of two QGridLayouts are set to a QLabel depending on a button press, but the actual update isn't happening. Making a call to self.update() after lines 123 and 130 didn't work. Should I be using a repaint event or something?
def displaySimulator(self):
if self.sim_vis == True: pass
else:
self.sim_vis = True
self.graph_vis = False
self.options.setLayout(self.simulator_settings)
def displayGraphing(self):
if self.graph_vis == True: pass
else:
self.graph_vis = True
self.sim_vis = False
self.options.setLayout(self.graphing_settings)
Here's the full code.
----------------------------------------------------------------------------------
Also, the toolbar label at the top is way too big...
self.layout.setRowMinimumHeight(0,20)
self.layout.setColumnMinimumWidth(0,250)
self.layout.setColumnMinimumWidth(1,1000)
setColumnMinimumWidth() works fine but setRowMinimumHeight() doesn't seem to work at all. I'm still a little confused about how the size of QWidgets are affected by layouts, containers, and subwidgets...perhaps I need to adjust the size of the QLabel, toolbar?
Try using a QWidget instead of a QLabel for self.options. Does that change anything?
For the toolbar spacing, do addStretch() after you add the last widget to the layout, but note that if you use a layout in a toolbar, you lose the built-in capabilities for it to autosize and hide buttons in a "... more options ..." type thing.
Since you name your object a toolbar I would actually make it a QToolbar. I would like to point out some styling points, because you said you were new.
import * is always bad for many reasons. You should know what you are importing and from where.
from PySide import QtGui, QtCore
Since you have a main GUI that you are running I would make that a QMainWindow. I would then add a menu bar and move some of your functionality to a file menu or edit menu.
This is just my style and how I like to do things
import sys
from PySide import QtGui, QtCore
class SimulatorWindow(QtGui.QMainWindow):
"""Application for running a simulator and displaying the resutls."""
def __init__(self):
super(SimulatorWindow, self).__init__()
self.setWindowTitle("Simulator") # Main window
# Properties
self.main_menu = None
self.simulator = None
self.export_action = None
self.settings_action = None
self.exit_action = None
self.initUI()
self.initMenu()
self.resize(600, 400)
# end Constructor
# Builds Simulator GUI
def initUI(self):
self.simulator = Simulator()
self.setCentralWidget(self.simulator)
self.addToolBar(self.simulator.toolbar)
# end initUI
def initMenu(self):
"""Initialize the menu bar."""
menubar = self.menuBar()
# ===== File Menu =====
self.file_menu = menubar.addMenu('&File')
# Export action
iexport = QtGui.QIcon()
self.export_action = QtGui.QAction(iexport, "Export", self)
self.export_action.triggered.connect(self.export)
self.file_menu.addAction(self.export_action)
# Separator
self.file_menu.addSeparator()
# Simulator Settings Dialog action
isettings = QtGui.QIcon()
self.settings_action = QtGui.QAction(isettings, "Settings", self)
self.settings_action.triggered.connect(self.simulator.dialog.show)
self.file_menu.addAction(self.settings_action)
# Exit action
iexit = QtGui.QIcon('exit.png')
self.exit_action = QtGui.QAction(iexit, '&Exit', self)
self.exit_action.triggered.connect(self.close)
self.file_menu.addAction(self.exit_action)
# ===== Edit Menu =====
self.edit_menu = menubar.addMenu('&Edit')
self.edit_menu.addAction(self.simulator.run_action)
# end initMenu
def export(self):
"""Export the simulation file."""
pass
# end export
# end class SimulatorWindow
class Simulator(QtGui.QWidget):
"""Simulator display."""
def __init__(self):
super().__init__()
#Properties
self.toolbar = None
self.dialog = None
self.run_action = None
# Layout
self.main_layout = QtGui.QGridLayout()
self.setLayout(self.main_layout)
# Graph
stuff = QtGui.QLabel("<font color=red size=300>Graphing stuff</font>")
stuff.setStyleSheet("QLabel {background-color: rgb(50,50,50); font-size:250;}")
stuff.setAlignment(QtCore.Qt.AlignCenter)
graph = QtGui.QLabel("<font color=green size=250>Graph here</font>")
graph.setStyleSheet("QLabel {background-color: rgb(0,0,0); font-size:250;}")
graph.setAlignment(QtCore.Qt.AlignCenter)
graph.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred)
self.main_layout.addWidget(stuff)
self.main_layout.addWidget(graph)
self.initToolbar()
self.initSettingsDialog()
# end Constructor
def initToolbar(self):
self.toolbar = QtGui.QToolBar()
irun = QtGui.QIcon()
self.run_action = QtGui.QAction(irun, "Run", self)
self.run_action.triggered.connect(self.run)
self.toolbar.addAction(self.run_action)
# end initToolbar
def initSettingsDialog(self):
"""Initialize the Settings dialog."""
self.dialog = QtGui.QDialog(self)
self.dialog.setWindowFlags(QtCore.Qt.Dialog | QtCore.Qt.WindowSystemMenuHint)
layout = QtGui.QGridLayout()
self.dialog.setLayout(layout)
# Initialize the Widgets
title = QtGui.QLabel("<font size=6>Simulator Settings</font>")
num_sim = QtGui.QLineEdit("Number of simulations")
num_trials = QtGui.QLineEdit("Number of trials (per learning phase)")
network = QtGui.QComboBox()
subject = QtGui.QComboBox()
# Set the layout
layout.addWidget(title, 0, 0, 1, 2, QtCore.Qt.AlignCenter)
layout.addWidget(num_sim, 1, 0)
layout.addWidget(num_trials, 1, 1)
layout.addWidget(network, 2, 0)
layout.addWidget(subject, 2, 1)
# end initSettingsDialog
def run(self):
"""Run the simulation."""
pass
# end run
# end class Simulator
def main():
"""Run the application."""
app = QtGui.QApplication(sys.argv)
window = SimulatorWindow()
window.show()
return app.exec_()
# end main
if __name__ == "__main__":
sys.exit(main())

PySide crash on exit (using QCompleter)

I have reproduced the "Custom Completer Example" from the Qt documentation using PySide
(Python 2.7.3, PySide 1.1.2, Qt 4.8.1).
I have an issue where a win32 exception is thrown on exit (or on Mac OS X a access violation exception).
On the Mac I can see a stack trace and the issue occurs during garbage collection, where references to QObjects are apparently not consistent, such that things go bad.
I can see this crash with the following self-contained script, only if a completer insertion was accepted. I.e. type the first few letters, then accept the completion.
On the other hand, if I have seen the completion list popup, but not accepted the completion, no crash occurs on exit.
################################################################################
# Completer.py
#
# A PySide port of the Qt 4.8 "Custom Completer Example"
# http://qt-project.org/doc/qt-4.8/tools-customcompleter.html
#
################################################################################
from PySide.QtCore import *
from PySide.QtGui import *
class TextEdit(QPlainTextEdit):
def __init__(self, parent=None):
super(TextEdit, self).__init__(parent)
self.c = None
def completer(self):
return self.c
def setCompleter(self, completer):
if self.c:
QObject.disconnect(self.c, 0, self, 0)
self.c = completer
if not self.c:
return
self.c.setWidget(self)
self.c.setCompletionMode(QCompleter.PopupCompletion)
self.c.setCaseSensitivity(Qt.CaseInsensitive)
self.c.activated.connect(self.insertCompletion)
def insertCompletion(self, completion):
if self.c.widget() is not self:
return
tc = self.textCursor()
extra = len(completion) - len(self.c.completionPrefix())
tc.movePosition(QTextCursor.Left)
tc.movePosition(QTextCursor.EndOfWord)
tc.insertText(completion[-extra:])
self.setTextCursor(tc)
def textUnderCursor(self):
tc = self.textCursor()
tc.select(QTextCursor.WordUnderCursor)
return tc.selectedText()
def focusInEvent(self, event):
if self.c:
self.c.setWidget(self)
super(TextEdit, self).focusInEvent(event)
def keyPressEvent(self, e):
if self.c and self.c.popup().isVisible():
if e.key() in (Qt.Key_Enter,
Qt.Key_Return,
Qt.Key_Escape,
Qt.Key_Tab,
Qt.Key_Backtab):
e.ignore()
return
# Check for the shortcut combination Ctrl+E
isShortcut = (e.modifiers() & Qt.ControlModifier) and e.key() == Qt.Key_E
# Do not process the shortcut when we have a completion
if not self.c or not isShortcut:
super(TextEdit, self).keyPressEvent(e)
noText = not e.text()
ctrlOrShift = e.modifiers() & (Qt.ControlModifier | Qt.ShiftModifier)
if not self.c or (ctrlOrShift and noText):
return
eow = "~!##$%^&*()_+{}|:\"<>?,./;'[]\\-=" # End of word
hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift
completionPrefix = self.textUnderCursor()
if not isShortcut and \
(hasModifier or noText or len(completionPrefix) < 1 or e.text()[-1:] in eow):
self.c.popup().hide()
return
if completionPrefix != self.c.completionPrefix():
self.c.setCompletionPrefix(completionPrefix)
self.c.popup().setCurrentIndex( self.c.completionModel().index(0,0) )
cr = self.cursorRect()
cr.setWidth(self.c.popup().sizeHintForColumn(0) + \
self.c.popup().verticalScrollBar().sizeHint().width())
self.c.complete(cr)
class Completer(QMainWindow):
words = ("one",
"two",
"three",
"four")
def __init__(self, parent=None):
super(Completer, self).__init__(parent)
self.setWindowTitle("Completer")
self.textEdit = TextEdit()
self.completer = QCompleter(self)
self.completer.setModelSorting(QCompleter.CaseInsensitivelySortedModel)
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
self.completer.setWrapAround(False)
self.completer.setModel(QStringListModel(Completer.words, self.completer))
self.textEdit.setCompleter(self.completer)
self.setCentralWidget(self.textEdit)
self.resize(500, 300)
self.setWindowTitle("Completer")
if __name__ == '__main__':
import sys
from PySide.QtGui import QApplication
app = QApplication(sys.argv)
window = Completer()
window.show()
sys.exit(app.exec_())

Resources