2016-06-21 4 views
1

ユーザーが手紙を入力し、検索を容易にする特定のデータソース(ここにリスト)に基づいていくつかの提案を得る小さなユーザーインターフェイスを作成する方法を調べたいと思います。この目的のために私はQtのQCompleterクラスを使用しています。QCompleterのポップアップリストのリスト項目を正しくフォーマットするにはどうすればいいですか?

一致する要素では、入力された文字は、次のコードの例のようにHTMLで強調表示されます:Au<b>st</b>ria。 は最終的に私は、合併小さなスタンドアロンモジュールにはいくつかのSOの回答(How to make item view render rich (html) text in Qtを参照)、チュートリアル:行の後

from PySide import QtCore, QtGui 

class HTMLDelegate(QtGui.QStyledItemDelegate): 
    """ From: https://stackoverflow.com/a/5443112/1504082 """ 

    def paint(self, painter, option, index): 
     options = QtGui.QStyleOptionViewItemV4(option) 
     self.initStyleOption(options, index) 
     if options.widget is None: 
      style = QtGui.QApplication.style() 
     else: 
      style = options.widget.style() 

     doc = QtGui.QTextDocument() 
     doc.setHtml(options.text) 
     doc.setTextWidth(option.rect.width()) 

     options.text = "" 
     style.drawControl(QtGui.QStyle.CE_ItemViewItem, options, painter) 

     ctx = QtGui.QAbstractTextDocumentLayout.PaintContext() 

     # Highlighting text if item is selected 
     # if options.state & QtGui.QStyle.State_Selected: 
     #  ctx.palette.setColor(QtGui.QPalette.Text, 
     #       options.palette.color(QtGui.QPalette.Active, 
     #            QtGui.QPalette.HighlightedText)) 

     textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, 
             options) 
     painter.save() 
     painter.translate(textRect.topLeft()) 
     painter.setClipRect(textRect.translated(-textRect.topLeft())) 
     doc.documentLayout().draw(painter, ctx) 
     painter.restore() 

    def sizeHint(self, option, index): 
     options = QtGui.QStyleOptionViewItemV4(option) 
     self.initStyleOption(options, index) 
     doc = QtGui.QTextDocument() 
     doc.setHtml(options.text) 
     doc.setTextWidth(options.rect.width()) 
     return QtCore.QSize(doc.size().width(), doc.size().height()) 


class CustomQCompleter(QtGui.QCompleter): 
    """ Implement "contains" filter mode as the filter mode "contains" is not 
    available in Qt < 5.2 
    From: https://stackoverflow.com/a/7767999/1504082 """ 

    def __init__(self, parent=None): 
     super(CustomQCompleter, self).__init__(parent) 
     self.local_completion_prefix = "" 
     self.source_model = None 
     self.delegate = HTMLDelegate() 

    def setModel(self, model): 
     self.source_model = model 
     super(CustomQCompleter, self).setModel(self.source_model) 

    def updateModel(self): 
     local_completion_prefix = self.local_completion_prefix 

     # see: http://doc.qt.io/qt-4.8/model-view-programming.html#proxy-models 
     class InnerProxyModel(QtGui.QSortFilterProxyModel): 
      def filterAcceptsRow(self, sourceRow, sourceParent): 
       # model index mapping by row, 1d model => column is always 0 
       index = self.sourceModel().index(sourceRow, 0, sourceParent) 
       source_data = self.sourceModel().data(index, QtCore.Qt.DisplayRole) 
       # performs case insensitive matching 
       # return True if item shall stay in th returned filtered data 
       # return False to reject an item 
       return local_completion_prefix.lower() in source_data.lower() 

     proxy_model = InnerProxyModel() 
     proxy_model.setSourceModel(self.source_model) 
     super(CustomQCompleter, self).setModel(proxy_model) 
     # @todo: Why to be set here again? 
     self.popup().setItemDelegate(self.delegate) 

    def splitPath(self, path): 
     self.local_completion_prefix = path 
     self.updateModel() 
     return "" 


class AutoCompleteEdit(QtGui.QLineEdit): 
    """ Basically from: 
    http://doc.qt.io/qt-5/qtwidgets-tools-customcompleter-example.html 
    """ 

    def __init__(self, list_data, separator=' ', addSpaceAfterCompleting=True): 
     super(AutoCompleteEdit, self).__init__() 
     # settings 
     self._separator = separator 
     self._addSpaceAfterCompleting = addSpaceAfterCompleting 
     # completer 
     self._completer = CustomQCompleter(self) 
     self._completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) 
     self._completer.setCompletionMode(QtGui.QCompleter.PopupCompletion) 

     self.model = QtGui.QStringListModel(list_data) 
     self._completer.setModel(self.model) 

     # connect the completer to the line edit 
     self._completer.setWidget(self) 
     # trigger insertion of the selected completion when its activated 
     self.connect(self._completer, 
        QtCore.SIGNAL('activated(QString)'), 
        self._insertCompletion) 

     self._ignored_keys = [QtCore.Qt.Key_Enter, 
           QtCore.Qt.Key_Return, 
           QtCore.Qt.Key_Escape, 
           QtCore.Qt.Key_Tab] 

    def _insertCompletion(self, completion): 
     """ 
     This is the event handler for the QCompleter.activated(QString) signal, 
     it is called when the user selects an item in the completer popup. 
     It will remove the already typed string with the one of the completion. 
     """ 
     stripped_text = self.text()[:-len(self._completer.completionPrefix())] 

     extra_text = completion # [-extra:] 
     if self._addSpaceAfterCompleting: 
      extra_text += ' ' 
     self.setText(stripped_text + extra_text) 

    def textUnderCursor(self): 
     text = self.text() 
     textUnderCursor = '' 
     i = self.cursorPosition() - 1 
     while i >= 0 and text[i] != self._separator: 
      textUnderCursor = text[i] + textUnderCursor 
      i -= 1 
     return textUnderCursor 

    def keyPressEvent(self, event): 
     if self._completer.popup().isVisible(): 
      if event.key() in self._ignored_keys: 
       event.ignore() 
       return 
     super(AutoCompleteEdit, self).keyPressEvent(event) 
     completionPrefix = self.textUnderCursor() 
     if completionPrefix != self._completer.completionPrefix(): 
      self._updateCompleterPopupItems(completionPrefix) 
     if len(event.text()) > 0 and len(completionPrefix) > 0: 
      self._completer.complete() 
     if len(completionPrefix) == 0: 
      self._completer.popup().hide() 

    def _updateCompleterPopupItems(self, completionPrefix): 
     """ 
     Filters the completer's popup items to only show items 
     with the given prefix. 
     """ 
     self._completer.setCompletionPrefix(completionPrefix) 
     # self._completer.popup().setCurrentIndex(
     #  self._completer.completionModel().index(0, 0)) 


if __name__ == '__main__': 
    def demo(): 
     import sys 
     app = QtGui.QApplication(sys.argv) 
     values = ['Germany', 
        'Au<b>st</b>ria', 
        'Switzerland', 
        'Hungary', 
        'The United Kingdom of Great Britain and Northern Ireland'] 
     editor = AutoCompleteEdit(values) 
     window = QtGui.QWidget() 
     hbox = QtGui.QHBoxLayout() 
     hbox.addWidget(editor) 
     window.setLayout(hbox) 
     window.show() 

     sys.exit(app.exec_()) 

    demo() 

私の問題は解答https://stackoverflow.com/a/5443112/1504082でユーザーティモの提案である「ドキュメント.setHtml(options.text) 'の場合は、doc.setTextWidth(option.rect.width())も設定する必要があります。そうしないと、デリゲートは対象の描画領域に対して長いコンテンツを正しくレンダリングしません。たとえば、QListViewで単語をラップしません。

これは、コンプリータのポップアップで長いテキストを切り取らないようにするためです。しかし、私は次の出力を得ます: Where does this vertical margin come from?

この追加の垂直マージンはどこから来ますか?

私はこのビットを調査し、私は HTMLDelegatesizeHint方法は時々属性 (0, 0, 0, 0)を持つ矩形が含まれてい optionsパラメータで呼び出されていることがわかります。そして、 doc.setTextWidth(options.rect.width())の呼び出しの後、最終的に表示動作が変わります。しかし、私は最終的に誰がこのパラメータでそれを呼び出すか、そしてこれをいかに適切に修正できるかを知ることができませんでした。

これはどこから来て、どのように私はこれをうまく解決できるのか誰かが説明できますか?

答えて

0

最後に私はhttps://stackoverflow.com/a/8036666/1504082のアイデアを使ってそれを実現する別の方法を見つけました。私がまだ理解していないこのすべてのカスタム図面のものがなければ、私にとってずっと前向きです:)

from PySide import QtCore, QtGui 


class TaskDelegate(QtGui.QItemDelegate): 
    # based on https://stackoverflow.com/a/8036666/1504082 
    # http://doc.qt.nokia.com/4.7/qitemdelegate.html#drawDisplay 
    # http://doc.qt.nokia.com/4.7/qwidget.html#render 
    margin_x = 5 
    margin_y = 3 

    def drawDisplay(self, painter, option, rect, text): 
     label = self.make_label(option, text) 
     # calculate render anchor point 
     point = rect.topLeft() 
     point.setX(point.x() + self.margin_x) 
     point.setY(point.y() + self.margin_y) 

     label.render(painter, point, renderFlags=QtGui.QWidget.DrawChildren) 

    def sizeHint(self, option, index): 
     # get text using model and index 
     text = index.model().data(index) 
     label = self.make_label(option, text) 
     return QtCore.QSize(label.width(), label.height() + self.margin_y) 

    def make_label(self, option, text): 
     label = QtGui.QLabel(text) 

     if option.state & QtGui.QStyle.State_Selected: 
      p = option.palette 
      p.setColor(QtGui.QPalette.WindowText, 
         p.color(QtGui.QPalette.Active, 
           QtGui.QPalette.HighlightedText) 
         ) 

      label.setPalette(p) 

     label.setStyleSheet("border: 1px dotted black") 

     # adjust width according to widget's target width 
     label.setMinimumWidth(self.target_width - (2 * self.margin_x)) 
     label.setMaximumWidth(self.target_width - self.margin_x) 
     label.setWordWrap(True) 
     label.adjustSize() 
     return label 


class CustomQCompleter(QtGui.QCompleter): 
    """ Implement "contains" filter mode as the filter mode "contains" is not 
    available in Qt < 5.2 
    From: https://stackoverflow.com/a/7767999/1504082 """ 

    def __init__(self, parent=None): 
     super(CustomQCompleter, self).__init__(parent) 
     self.local_completion_prefix = "" 
     self.source_model = None 
     self.delegate = TaskDelegate() 
     # widget not set yet 
     # self.delegate.target_width = self.widget().width() 

    def setModel(self, model): 
     self.source_model = model 
     super(CustomQCompleter, self).setModel(self.source_model) 

    def updateModel(self): 
     local_completion_prefix = self.local_completion_prefix 

     # see: http://doc.qt.io/qt-4.8/model-view-programming.html#proxy-models 
     class InnerProxyModel(QtGui.QSortFilterProxyModel): 
      def filterAcceptsRow(self, sourceRow, sourceParent): 
       # model index mapping by row, 1d model => column is always 0 
       index = self.sourceModel().index(sourceRow, 0, sourceParent) 
       source_data = self.sourceModel().data(index, QtCore.Qt.DisplayRole) 
       # performs case insensitive matching 
       # return True if item shall stay in th returned filtered data 
       # return False to reject an item 
       return local_completion_prefix.lower() in source_data.lower() 

     proxy_model = InnerProxyModel() 
     proxy_model.setSourceModel(self.source_model) 
     super(CustomQCompleter, self).setModel(proxy_model) 
     # @todo: Why to be set here again? 
     # -> rescale popup list items to widget width 
     self.delegate.target_width = self.widget().width() 
     self.popup().setItemDelegate(self.delegate) 

    def splitPath(self, path): 
     self.local_completion_prefix = path 
     self.updateModel() 
     return "" 


class AutoCompleteEdit(QtGui.QLineEdit): 
    """ Basically from: 
    http://doc.qt.io/qt-5/qtwidgets-tools-customcompleter-example.html 
    """ 

    def __init__(self, list_data, separator=' ', addSpaceAfterCompleting=True): 
     super(AutoCompleteEdit, self).__init__() 
     # settings 
     self._separator = separator 
     self._addSpaceAfterCompleting = addSpaceAfterCompleting 
     # completer 
     self._completer = CustomQCompleter(self) 
     self._completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) 
     self._completer.setCompletionMode(QtGui.QCompleter.PopupCompletion) 

     self.model = QtGui.QStringListModel(list_data) 
     self._completer.setModel(self.model) 

     # connect the completer to the line edit 
     self._completer.setWidget(self) 
     # trigger insertion of the selected completion when its activated 
     self.connect(self._completer, 
        QtCore.SIGNAL('activated(QString)'), 
        self._insertCompletion) 

     self._ignored_keys = [QtCore.Qt.Key_Enter, 
           QtCore.Qt.Key_Return, 
           QtCore.Qt.Key_Escape, 
           QtCore.Qt.Key_Tab] 

    def _insertCompletion(self, completion): 
     """ 
     This is the event handler for the QCompleter.activated(QString) signal, 
     it is called when the user selects an item in the completer popup. 
     It will remove the already typed string with the one of the completion. 
     """ 
     stripped_text = self.text()[:-len(self._completer.completionPrefix())] 

     extra_text = completion # [-extra:] 
     if self._addSpaceAfterCompleting: 
      extra_text += ' ' 
     self.setText(stripped_text + extra_text) 

    def textUnderCursor(self): 
     text = self.text() 
     textUnderCursor = '' 
     i = self.cursorPosition() - 1 
     while i >= 0 and text[i] != self._separator: 
      textUnderCursor = text[i] + textUnderCursor 
      i -= 1 
     return textUnderCursor 

    def keyPressEvent(self, event): 
     if self._completer.popup().isVisible(): 
      if event.key() in self._ignored_keys: 
       event.ignore() 
       return 
     super(AutoCompleteEdit, self).keyPressEvent(event) 
     completionPrefix = self.textUnderCursor() 
     if completionPrefix != self._completer.completionPrefix(): 
      self._updateCompleterPopupItems(completionPrefix) 
     if len(event.text()) > 0 and len(completionPrefix) > 0: 
      self._completer.complete() 
     if len(completionPrefix) == 0: 
      self._completer.popup().hide() 

    def _updateCompleterPopupItems(self, completionPrefix): 
     """ 
     Filters the completer's popup items to only show items 
     with the given prefix. 
     """ 
     self._completer.setCompletionPrefix(completionPrefix) 
     # self._completer.popup().setCurrentIndex(
     #  self._completer.completionModel().index(0, 0)) 


if __name__ == '__main__': 
    def demo(): 
     import sys 
     app = QtGui.QApplication(sys.argv) 
     values = ['Germany', 
        'Au<b>st</b>ria', 
        'Switzerland', 
        'Hungary', 
        'The United Kingdom of Great Britain and Northern Ireland', 
        'USA'] 
     editor = AutoCompleteEdit(values) 
     window = QtGui.QWidget() 
     hbox = QtGui.QHBoxLayout() 
     hbox.addWidget(editor) 
     window.setLayout(hbox) 
     window.show() 

     sys.exit(app.exec_()) 

    demo() 
関連する問題