PDA

View Full Version : [Python + Qt] Auto-completamento (parziale) nelle text-box


WarDuck
21-02-2011, 13:57
Salve, sto realizzando una interfaccia grafica per la tesi, e mi sono imbattuto in un problema che spero mi possiate aiutare a risolvere.

Ho bisogno di realizzare l'auto-completamento in alcune textbox, ho provato impostando un QCompleter, però questo agisce su tutta la textbox mentre a me interessa agire in maniera "parziale".

Mi spiego meglio, mettiamo che ho 2 campi: a e b. A questi possono essere associate delle proprietà, ad esempio "text" e "value".

L'utente può digitare una serie di cose nella textbox, ma quando digita uno dei due campi seguito dal punto, vorrei che apparisse l'elenco delle proprietà, in maniera del tutto analoga a quanto fa un IDE.

Ad esempio:
- scrivo if seguito da uno spazio
- scrivo a.
- mi appare l'elenco con "text" e "value" da cui posso scegliere.
- nella casella mi appare a.text o a.value a seconda della scelta.

Sul parsing non c'è alcun problema so bene come farlo, vorrei un consiglio invece proprio sulla parte grafica della faccenda.

Ora ho una mezza idea di come farlo manualmente, magari facendo apparire una sorta di QListWidget in corrispondenza del cursore, ma volevo sapere se c'era qualcuno che si era già imbattuto in un problema simile ed eventualmente sapere come l'ha risolto.

Grazie.

WarDuck
21-02-2011, 18:47
Ho (parzialmente) risolto usando un QMenu, così:


def __init__(...):
...
self._tokens = set(["seq", "src", "dst", "dur", "msg"])

complete = ["text", "value"]
self.actions = dict()

self._completer = QMenu()

self._lastFocused = None

for property in complete:
action = self.actions[property] = QAction(property, None)
self.connect(action, SIGNAL("triggered()"), self._complete)
self._completer.addAction(action)

...
self.connect(self.line, SIGNAL("textEdited(QString)"), self._modified)
self.pos = 0

def _complete(self):

action = self.sender().text()
current = self._lastFocused

current.setText(current.text() + action + " ")

def _modified(self, string):

self._lastFocused = self.sender()

cur = self._lastFocused.cursorPosition()

if cur == 0:
self.pos = cur
return

if string[-1] == ' ':
self.pos = cur
elif string[-1] == '.':
token = str(string[self.pos:cur-1])
if token in self._tokens:
self._completer.popup(self.mapToGlobal(self._lastFocused.pos()))


Il tutto è nella funzione _modified.

Quando viene cambiato il testo nella textbox viene invocata _modified e vengono fatti gli opportuni controlli.

Non è elegantissimo ma funziona, sicuramente si può migliorare la parte in cui viene derivato il token.

Ora l'unico problema che ho è che il menù di completamento mi appare fuori posto, perché a quanto ho capito la funzione pos() mi restituisce la posizione del parent e non del widget stesso.

WarDuck
22-02-2011, 21:43
Up again, ecco il Widget bello che pronto semmai vi servisse :D:


from PyQt4.QtCore import *
from PyQt4.QtGui import *

import sys

class AutoLineEdit(QWidget):

def __init__(self, tokens, completers, parent=None):
super(AutoLineEdit, self).__init__(parent)

self._tokens = tokens

self._completeMenu = QMenu()
self._actionList = []

for property in completers:
action = QAction(property, None)
self._actionList.append(action)
self.connect(action, SIGNAL("triggered()"), self._complete)
self._completeMenu.addAction(action)

layout = QVBoxLayout()
self.lineEdit = QLineEdit()
layout.addWidget(self.lineEdit)

self.connect(self.lineEdit, SIGNAL("textEdited(QString)"), self._modified)

self._pos = 0

self.setLayout(layout)

def _complete(self):
action = self.sender().text()

self.lineEdit.setText(self.lineEdit.text() + action + " ")

def _modified(self, string):

cur = self.lineEdit.cursorPosition()

if cur == 0:
self._pos = cur
return

if string[-1] == ' ':
self._pos = cur
elif string[-1] == '.':
token = str(string[self._pos:cur-1])
if token in self._tokens:
self._completeMenu.popup(self.mapToGlobal(self.lineEdit.pos()))


Così si risolve anche il problema di dove appare il popup ;).

*andre*
23-02-2011, 15:59
ciao!
ottimo lavoro davvero, potresti aggiungere un piccolo codice per testarlo? :D

WarDuck
23-02-2011, 17:44
Ciao!

Per l'esecuzione basta aggiungere dopo la classe questo codice:


if __name__ == "__main__":

tokens = set(["seq", "src", "dst", "dur", "msg"])
completers = ["text", "value"]

app = QApplication(sys.argv)
gui = AutoLineEdit(tokens, completers)
gui.show()
app.exec_()


ed eseguire direttamente il file con Python (ho usato la 2.7).

In pratica vengono riconosciute tutte le sequenze nella lista dei tokens, e viene fatto apparire un popup contenente i completers.

Sicuramente si può migliorare perché attualmente le proprietà (quelli che io ho chiamato "completers") sono fisse ed uguali per tutti i tokens, mentre in realtà volendo fare una cosa un po' più completa andrebbe generalizzato e reso più flessibile.

Poi forse per fare una cosa un po' più decente dovrei ereditare direttamente da QLineEdit piuttosto che da QWidget, così erediterei anche tutti i metodi di QLineEdit.

Altra cosa sicuramente da migliorare è il fatto che quando compare il popup il focus va su di esso quindi non puoi più scrivere nulla, oltre al fatto che comunque compare all'inizio della textbox e non in corrispondenza del cursore :).

Che poi non ho neanche provato a cercare in giro se c'era già qualcosa di fatto...

*andre*
23-02-2011, 22:12
Ciao!

Per l'esecuzione basta aggiungere dopo la classe questo codice:


if __name__ == "__main__":

tokens = set(["seq", "src", "dst", "dur", "msg"])
completers = ["text", "value"]

app = QApplication(sys.argv)
gui = AutoLineEdit(tokens, completers)
gui.show()
app.exec_()


ed eseguire direttamente il file con Python (ho usato la 2.7).

In pratica vengono riconosciute tutte le sequenze nella lista dei tokens, e viene fatto apparire un popup contenente i completers.

Sicuramente si può migliorare perché attualmente le proprietà (quelli che io ho chiamato "completers") sono fisse ed uguali per tutti i tokens, mentre in realtà volendo fare una cosa un po' più completa andrebbe generalizzato e reso più flessibile.

Poi forse per fare una cosa un po' più decente dovrei ereditare direttamente da QLineEdit piuttosto che da QWidget, così erediterei anche tutti i metodi di QLineEdit.

Altra cosa sicuramente da migliorare è il fatto che quando compare il popup il focus va su di esso quindi non puoi più scrivere nulla, oltre al fatto che comunque compare all'inizio della textbox e non in corrispondenza del cursore :).

Che poi non ho neanche provato a cercare in giro se c'era già qualcosa di fatto...

buon widget ;)

premetto che sto studiandomi pyqt, però la prima cosa che mi viene in mente è calcolare il punto dove sta il cursore in coordinate e spostare il menu li.. che sarebbe il punto in basso a sx del line edit + quanto misura il testo inserito (c'è un font metrics da qualche parte sicuramente)