source: orange/orange/OrangeWidgets/OWGUIEx.py @ 8042:ffcb93bc9028

Revision 8042:ffcb93bc9028, 21.6 KB checked in by markotoplak, 3 years ago (diff)

Hierarchical clustering: also catch RuntimeError when importing matplotlib (or the documentation could not be built on server).

Line 
1from PyQt4.QtCore import *
2from PyQt4.QtGui import *
3import math, re, string, sys
4from OWGUI import widgetLabel, widgetBox, lineEdit
5
6def lineEditFilter(widget, master, value, *arg, **args):
7    callback = args.get("callback", None)
8    args["callback"] = None         # we will have our own callback handler
9    args["baseClass"] = LineEditFilter
10    le = lineEdit(widget, master, value, *arg, **args)
11    le.__dict__.update(args)
12    le.callback = callback
13    le.focusOutEvent(None)
14    return le
15
16def QListWidget_py_iter(self):
17    for i in range(self.count()):
18        yield self.item(i)
19       
20QListWidget.py_iter = QListWidget_py_iter
21
22class LineEditFilter(QLineEdit):
23    def __init__(self, parent):
24        QLineEdit.__init__(self, parent)
25        QObject.connect(self, SIGNAL("textEdited(const QString &)"), self.textChanged)
26        self.enteredText = ""
27        self.listboxItems = []
28        self.listbox = None
29        self.caseSensitive = 1
30        self.matchAnywhere = 0
31        self.useRE = 0
32        self.emptyText = ""
33        self.textFont = self.font()
34        self.callback = None
35     
36    def setListBox(self, listbox):
37        self.listbox = listbox
38           
39    def focusInEvent(self, ev):
40        self.setText(self.enteredText)
41        self.setStyleSheet("")
42        QLineEdit.focusInEvent(self, ev)
43       
44    def focusOutEvent(self, ev):
45        self.enteredText = self.getText()
46           
47        if self.enteredText == "":
48            self.setText(self.emptyText)
49            self.setStyleSheet("color: rgb(170, 170, 127);")
50        if ev:
51            QLineEdit.focusOutEvent(self, ev)
52           
53    def setText(self, text):
54        if text != self.emptyText:
55            self.enteredText = text
56        if not self.hasFocus() and text == "":
57            text = self.emptyText
58        QLineEdit.setText(self, text)
59       
60    def getText(self):
61        if str(self.text()) == self.emptyText:
62            return ""
63        else: return str(self.text())
64       
65    def setAllListItems(self, items = None):
66        if items is None:
67            items = [self.listbox.item(i) for i in range(self.listbox.count())]
68        if not items:
69            self.listboxItems = []
70            return
71        if type(items[0]) == str:           # if items contain strings
72            self.listboxItems = [(item, QListWidgetItem(item)) for item in items]
73        else:                               # if items contain QListWidgetItems
74            self.listboxItems = [(str(item.text()), QListWidgetItem(item)) for item in items]
75       
76    def textChanged(self):
77        self.updateListBoxItems()
78       
79    def updateListBoxItems(self, callCallback = 1):
80        if self.listbox is None: return
81        last = self.getText()
82       
83        if self.useRE:
84            if self.caseSensitive:
85                pattern = re.compile(last)
86            else:
87                pattern = re.compile(last, re.IGNORECASE)
88           
89        for item in self.listbox.py_iter():
90            text = str(item.text())
91            if not self.caseSensitive:
92                text = text.lower()
93            if self.useRE:
94                try:
95                    test = pattern.match(text)
96                except Exception, ex:
97                    print ex
98                    test = True
99            else:
100                if self.matchAnywhere:
101                    test = last in text
102                else:
103                    test = text.startswith(last)
104            item.setHidden(not test)
105           
106#        tuples = self.listboxItems               
107#        if not self.caseSensitive:
108#            tuples = [(text.lower(), item) for (text, item) in tuples]
109#            last = last.lower()
110#
111#        if self.useRE:
112#            try:
113#                pattern = re.compile(last)
114#                tuples = [(text, QListWidgetItem(item)) for (text, item) in tuples if pattern.match(text)]
115#            except:
116#                tuples = [(t, QListWidgetItem(i)) for (t,i) in self.listboxItems]        # in case we make regular expressions crash we show all items
117#        else:
118#            if self.matchAnywhere:  tuples = [(text, QListWidgetItem(item)) for (text, item) in tuples if last in text]
119#            else:                   tuples = [(text, QListWidgetItem(item)) for (text, item) in tuples if text.startswith(last)]
120#       
121#        self.listbox.clear()
122#        for (t, item) in tuples:
123#            self.listbox.addItem(item)
124       
125        if self.callback and callCallback:
126            self.callback()
127       
128
129
130def lineEditHint(widget, master, value, *arg, **args):
131    callback = args.get("callback", None)
132    args["callback"] = None         # we will have our own callback handler
133    args["baseClass"] = LineEditHint
134    le = lineEdit(widget, master, value, *arg, **args)
135    le.setDelimiters(args.get("delimiters", None))      # what are characters that are possible delimiters between items in the edit box
136    le.setItems(args.get("items", []))          # items that will be suggested for selection
137    le.__dict__.update(args)
138    le.callbackOnComplete = callback                                    # this is called when the user selects one of the items in the list
139    return le
140       
141class LineEditHint(QLineEdit):
142    def __init__(self, parent):
143        QLineEdit.__init__(self, parent)
144        QObject.connect(self, SIGNAL("textEdited(const QString &)"), self.textEdited)
145        self.enteredText = ""
146        self.itemList = []
147        self.useRE = 0
148        self.callbackOnComplete = None
149        self.listUpdateCallback = None
150        self.autoSizeListWidget = 0
151        self.caseSensitive = 1
152        self.matchAnywhere = 0
153        self.nrOfSuggestions = 10
154        #self.setDelimiters(",; ")
155        self.delimiters = None          # by default, we only allow selection of one element
156        self.itemsAsStrings = []        # a list of strings that appear in the list widget
157        self.itemsAsItems = []          # can be a list of QListWidgetItems or a list of strings (the same as self.itemsAsStrings)
158        self.listWidget = QListWidget()
159        self.listWidget.setMouseTracking(1)
160        self.listWidget.installEventFilter(self)
161        self.listWidget.setWindowFlags(Qt.Popup)
162        self.listWidget.setFocusPolicy(Qt.NoFocus)
163        QObject.connect(self.listWidget, SIGNAL("itemClicked (QListWidgetItem *)"), self.doneCompletion)
164       
165        QObject.connect(self, SIGNAL("editingFinished()"), lambda : self.callbackOnComplete() if self.callbackOnComplete else None)
166       
167    def setItems(self, items):
168        if items:
169            self.itemsAsItems = items
170            if type(items[0]) == str:                   self.itemsAsStrings = items
171            elif type(items[0]) == QListWidgetItem:     self.itemsAsStrings = [str(item.text()) for item in items]
172            else:                                       print "SuggestLineEdit error: unsupported type for the items"
173        else:
174            self.itemsAsItems = []
175            self.itemsAsStrings = [] 
176   
177    def setDelimiters(self, delimiters):
178        self.delimiters = delimiters
179        if delimiters:
180            self.translation = string.maketrans(self.delimiters, self.delimiters[0] * len(self.delimiters))
181       
182    def eventFilter(self, object, ev):
183        if getattr(self, "listWidget", None) != object:
184            return 0
185       
186        if ev.type() == QEvent.MouseButtonPress:
187            self.listWidget.hide()
188            return 1
189               
190        consumed = 0
191        if ev.type() == QEvent.KeyPress:
192            consumed = 1
193            if ev.key() in [Qt.Key_Enter, Qt.Key_Return]:
194                self.doneCompletion()
195            elif ev.key() == Qt.Key_Escape:
196                self.listWidget.hide()
197                #self.setFocus()
198            elif ev.key() in [Qt.Key_Up, Qt.Key_Down, Qt.Key_Home, Qt.Key_End, Qt.Key_PageUp, Qt.Key_PageDown]:
199                self.listWidget.setFocus()
200                self.listWidget.event(ev)
201            else:
202                #self.setFocus()
203                self.event(ev)
204        return consumed
205       
206    def doneCompletion(self, *args):
207        if self.listWidget.isVisible():
208            if len(args) == 1:  itemText = str(args[0].text())
209            else:               itemText = str(self.listWidget.currentItem().text())
210            last = self.getLastTextItem()
211            self.setText(str(self.text()).rstrip(last) + itemText)
212            self.listWidget.hide()
213            self.setFocus()
214        if self.callbackOnComplete:
215            QTimer.singleShot(0, self.callbackOnComplete)
216   
217    def textEdited(self):
218        self.updateSuggestedItems()
219        if self.getLastTextItem() == "":        # if we haven't typed anything yet we hide the list widget
220            self.listWidget.hide()
221            self.doneCompletion() 
222   
223    def getLastTextItem(self):
224        text = str(self.text())
225        if len(text) == 0: return ""
226        if not self.delimiters: return str(self.text())     # if no delimiters, return full text
227        if text[-1] in self.delimiters: return ""
228        return text.translate(self.translation).split(self.delimiters[0])[-1]       # last word that we want to help to complete
229   
230    def updateSuggestedItems(self):
231        self.listWidget.setUpdatesEnabled(0)
232        self.listWidget.clear()
233       
234        last = self.getLastTextItem()
235        tuples = zip(self.itemsAsStrings, self.itemsAsItems)
236        if not self.caseSensitive:
237            tuples = [(text.lower(), item) for (text, item) in tuples]
238            last = last.lower()
239           
240        if self.useRE:
241            try:
242                pattern = re.compile(last)
243                tuples = [(text, item) for (text, item) in tuples if pattern.match(text)]
244            except:
245                tuples = zip(self.itemsAsStrings, self.itemsAsItems)        # in case we make regular expressions crash we show all items
246        else:
247            if self.matchAnywhere:
248                tuples = [(text, item) for (text, item) in tuples if last in text]
249                tuples = sorted(tuples, key=lambda t: 0 if t[0].startswith(last) else 1) 
250            else:
251                tuples = [(text, item) for (text, item) in tuples if text.startswith(last)]
252       
253        items = [tup[1] for tup in tuples]
254        if items:
255            if type(items[0]) == str:
256                self.listWidget.addItems(items)
257            else:
258                for item in items:
259                    self.listWidget.addItem(QListWidgetItem(item))
260            self.listWidget.setCurrentRow(0)
261
262            self.listWidget.setUpdatesEnabled(1)
263            width = max(self.width(), self.autoSizeListWidget and self.listWidget.sizeHintForColumn(0)+10)
264            if self.autoSizeListWidget:
265                self.listWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 
266            self.listWidget.resize(width, self.listWidget.sizeHintForRow(0) * (min(self.nrOfSuggestions, len(items)))+5)
267            self.listWidget.move(self.mapToGlobal(QPoint(0, self.height())))
268            self.listWidget.show()
269##            if not self.delimiters and items and not self.matchAnywhere:
270##                self.setText(last + str(items[0].text())[len(last):])
271##                self.setSelection(len(str(self.text())), -(len(str(self.text()))-len(last)))           
272##            self.setFocus()
273        else:
274            self.listWidget.hide()
275            return
276       
277        if self.listUpdateCallback:
278            self.listUpdateCallback()
279       
280       
281class LineEditWFocusOutMk2(QLineEdit):
282    def __init__(self, *args, **kwargs):
283        QLineEdit.__init__(self, *args)
284        self.mouseOverPix = False
285        self.showIcon = False
286        self.leftTextMargin, self.topTextMargin, self.rightTextMargin, self.bottomTextMargin = 1, 1, 1, 1
287        self.placeHolderText = ""
288        self.connect(self, SIGNAL("textEdited(QString)"), self.textEdited)
289        self.connect(self, SIGNAL("editingFinished()"), self.editingFinished)
290        self.setStyleSheet("QLineEdit { padding-right: 20px; border-radius: 4px;}")
291       
292    def textEdited(self, string):
293        self.showIcon = True
294        self.update()
295   
296    def editingFinished(self):
297        self.showIcon = False
298        self.update()
299       
300    def enterPixmap(self):
301        import OWGUI
302        return OWGUI.getEnterIcon().pixmap(self.height(), self.height())
303   
304    def mouseMoveEvent(self, event):
305        self.mouseOverPix = self.iconRect().contains(event.pos())
306        self.setCursor(Qt.ArrowCursor if self.mouseOverPix and self.showIcon else Qt.IBeamCursor)
307        self.update()
308        return QLineEdit.mouseMoveEvent(self, event)
309   
310    def mousePressEvent(self, event):
311        if self.iconRect().contains(event.pos()) and event.buttons() & Qt.LeftButton:
312            self.emit(SIGNAL("editingFinished()"))
313            self.emit(SIGNAL("clicked()"))
314   
315    def styleOption(self):
316        option = QStyleOptionFrameV2()
317        option.initFrom(self)
318        return option
319   
320    def iconRect(self):
321        rect = self.contentsRect()
322        rect.adjust(rect.width() - rect.height() + 2, -1, -1, -1)
323        return rect
324   
325    def paintEvent(self, event):
326       QLineEdit.paintEvent(self, event)
327         
328       try:
329           rect = self.contentsRect()
330           size = self.style().sizeFromContents(QStyle.CT_LineEdit, self.styleOption(), rect.size(), self)
331           rect = self.iconRect()
332           pix = self.enterPixmap()
333           p = QPainter(self)
334           p.setRenderHint(QPainter.Antialiasing)
335           p.setOpacity(1.0 if self.mouseOverPix else 0.7)
336           if self.showIcon:
337               p.drawPixmap(rect, pix, QRect(QPoint(0, 0), pix.size()))
338       except Exception, ex:
339           print ex
340           
341class SpinBoxWFocusOut(QSpinBox):
342    def __init__(self, min, max, step, bi):
343        QSpinBox.__init__(self, bi)
344        self.setRange(min, max)
345        self.setSingleStep(step)
346        self.setLineEdit(LineEditWFocusOutMk2(self))
347        self.connect(self.lineEdit(), SIGNAL("textChanged(QString)"), self.lineEdit().textEdited)
348        self.connect(self, SIGNAL("valueChanged(QString)"), self.lineEdit().textEdited)
349        self.connect(self, SIGNAL("valueChanged(QString)"), self.setValueChanged)
350#        self.connect(self.lineEdit(), SIGNAL("editingFinished()"), self.valueCommited)
351        self.connect(self, SIGNAL("editingFinished()"), self.commitValue)
352        self.connect(self.lineEdit(), SIGNAL("clicked()"), self.commitValue)
353       
354        self.connect(self, SIGNAL("editingFinished()"), self.lineEdit().editingFinished)
355        self.valueChangedFlag = False
356       
357    def setValueChanged(self, text):
358        print "valueChanged", text
359        self.valueChangedFlag = True
360       
361    def commitValue(self):
362        if self.valueChangedFlag:
363            print "valueCommited"
364            self.emit(SIGNAL("valueCommited(int)"), self.value())
365            self.valueChangedFlag = False
366                 
367    def onChange(self, value):
368        pass
369   
370    def onEnter(self):
371        pass
372   
373    def setValue(self, text):
374        QSpinBox.setValue(self, text)
375        self.valueChangedFlag = False
376       
377class DoubleSpinBoxWFocusOut(QDoubleSpinBox):
378    def __init__(self, min, max, step, bi):
379        QDoubleSpinBox.__init__(self, bi)
380        self.setDecimals(math.ceil(-math.log10(step)))
381        self.setRange(min, max)
382        self.setSingleStep(step)
383        self.setLineEdit(LineEditWFocusOutMk2(self))
384        self.connect(self.lineEdit(), SIGNAL("textChanged(QString)"), self.lineEdit().textEdited)
385        self.connect(self, SIGNAL("valueChanged(QString)"), self.lineEdit().textEdited)
386        self.connect(self, SIGNAL("valueChanged(QString)"), self.setValueChanged)
387        self.connect(self, SIGNAL("editingFinished()"), self.commitValue)
388        self.connect(self.lineEdit(), SIGNAL("clicked()"), self.commitValue)
389       
390        self.connect(self, SIGNAL("editingFinished()"), self.lineEdit().editingFinished)
391        self.valueChangedFlag = False
392       
393    def setValueChanged(self, text):
394        print "valueChanged", text
395        self.valueChangedFlag = True
396       
397    def commitValue(self):
398        if self.valueChangedFlag:
399            print "valueCommited", self.value()
400            self.emit(SIGNAL("valueCommited(double)"), self.value())
401            self.valueChangedFlag = False
402                 
403    def onChange(self, value):
404        pass
405   
406    def onEnter(self):
407        pass
408   
409    def setText(self, text):
410        QDoubleSpinBox.setValue(self, text)
411        self.valueChangedFlag = False
412       
413class QLineEditWithActions(QLineEdit):
414    def __init__(self, *args):
415        QLineEdit.__init__(self, *args)
416#        self._leftActions = []
417#        self._rightActions = []
418        self._actions = []
419        self._buttons = []
420        self._buttonLayout = QHBoxLayout(self)
421        self._editArea = QSpacerItem(10, 10, QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
422        self._buttonLayout.addSpacerItem(self._editArea)
423        self.setLayout(self._buttonLayout)
424       
425        self._buttonLayout.setContentsMargins(0, 0, 0, 0)
426
427    def insertAction(self, index, action, *args):
428        self._actions.append(action)
429        button = QToolButton(self)
430        button.setDefaultAction(action)
431        button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
432        button.setCursor(QCursor(Qt.ArrowCursor))
433        button.setStyleSheet("border: none;")
434       
435        self._buttons.append(button)
436        self._insertWidget(index, button, *args)
437       
438    def addAction(self, action, *args):
439        self.insertAction(-1, action, *args)
440       
441    def _insertWidget(self, index, widget, *args):
442        widget.installEventFilter(self)
443        self._buttonLayout.insertWidget(index, widget, *args)
444       
445    def eventFilter(self, obj, event):
446        if obj in self._buttons:
447            if event.type() == QEvent.Resize:
448                if event.size().width() != event.oldSize().width():
449                    QTimer.singleShot(50, self._updateTextMargins)
450        return QLineEdit.eventFilter(self, obj, event)
451               
452    def _updateTextMargins(self):
453        left = 0
454        right = sum(w.width() for  w in self._buttons) + 4
455        if qVersion() >= "4.6":
456            self.setTextMargins(left, 0, right, 0)
457#        elif sys.platform != "darwin": ## On Mac this does not work properly
458#            style = "padding-left: %ipx; padding-right: %ipx; height: %ipx; margin: 0px; border: " % (left, right, self.height())
459#            self.setStyleSheet(style)
460           
461    def setPlaceholderText(self, text):
462        self._placeHolderText = text
463        self.update()
464       
465    def paintEvent(self, event):
466        QLineEdit.paintEvent(self, event)
467        if not self.text() and self._placeHolderText and not self.hasFocus():
468            painter = QPainter(self)
469            rect = self._editArea.geometry()
470            painter.setPen(QPen(self.palette().color(QPalette.Inactive, QPalette.WindowText).light()))
471            painter.drawText(rect, Qt.AlignVCenter, " " + self._placeHolderText)
472       
473if __name__ == "__main__":
474    import sys, random, string, OWGUI
475    a = QApplication(sys.argv)
476    import OWWidget
477    dlg = OWWidget.OWWidget()
478   
479    dlg.filter = ""
480    dlg.listboxValue = ""
481    dlg.resize(300, 200)
482    lineEdit = lineEditFilter(dlg.controlArea, dlg, "filter", "Filter:", useRE = 1, emptyText = "filter...")
483       
484    lineEdit.setListBox(OWGUI.listBox(dlg.controlArea, dlg, "listboxValue"))
485    names = []
486    for i in range(10000):
487        names.append("".join([string.ascii_lowercase[random.randint(0, len(string.ascii_lowercase)-1)] for c in range(10)]))
488    lineEdit.listbox.addItems(names)
489    lineEdit.setAllListItems(names)
490   
491#    dlg.text = ""
492#   
493#    s = lineEditHint(dlg.controlArea, dlg, "text", useRE = 1, items = ["janez", "joza", "danica", "jani", "jok", "jure", "jaz"], delimiters = ",; ")
494   
495   
496##    def getFullWidgetIconName(category, widgetInfo):
497##        import os
498##        iconName = widgetInfo.icon
499##        names = []
500##        name, ext = os.path.splitext(iconName)
501##        for num in [16, 32, 42, 60]:
502##            names.append("%s_%d%s" % (name, num, ext))
503##           
504##        widgetDir = str(category.directory) 
505##        fullPaths = []
506##        dirs = orngEnviron.directoryNames
507##        for paths in [(dirs["picsDir"],), (dirs["widgetDir"],), (dirs["widgetDir"], "icons")]:
508##            for name in names + [iconName]:
509##                fname = os.path.join(*paths + (name,))
510##                if os.path.exists(fname):
511##                    fullPaths.append(fname)
512##            if len(fullPaths) > 1 and fullPaths[-1].endswith(iconName):
513##                fullPaths.pop()     # if we have the new icons we can remove the default icon
514##            if fullPaths != []:
515##                return fullPaths   
516##        return "" 
517##
518##
519##    s = lineEditHint(dlg.controlArea, dlg, "text", useRE = 0, caseSensitive = 0, matchAnywhere = 0)
520##    s.listWidget.setSpacing(2)
521##    s.setStyleSheet(""" QLineEdit { background: #fffff0; border: 1px solid blue} """)
522##    s.listWidget.setStyleSheet(""" QListView { background: #fffff0; } QListView::item {padding: 3px 0px 3px 0px} QListView::item:selected, QListView::item:hover { color: white; background: blue;} """)
523##    import orngRegistry, orngEnviron
524##    cats = orngRegistry.readCategories()
525##    items = []
526##    for cat in cats.values():
527##        for widget in cat.values():
528##            iconNames = getFullWidgetIconName(cat, widget)
529##            icon = QIcon()
530##            for name in iconNames:
531##                icon.addPixmap(QPixmap(name))
532##            item = QListWidgetItem(icon, widget.name)
533##            #item.setSizeHint(QSize(100, 32))
534##            #
535##            items.append(item)
536##    s.setItems(items)
537       
538    dlg.show()
539    a.exec_()
Note: See TracBrowser for help on using the repository browser.