source: orange/orange/OrangeWidgets/OWColorPalette.py @ 8241:d8e47aa467bd

Revision 8241:d8e47aa467bd, 39.0 KB checked in by Noughmad <Noughmad@…>, 3 years ago (diff)

Finish the basic plot example, and add some fixes to support it.

Line 
1from PyQt4.QtCore import *
2from PyQt4.QtGui import *
3from OWWidget import *
4import OWGUI
5import math
6
7defaultRGBColors = [(0, 0, 255), (255, 0, 0), (0, 255, 0), (255, 128, 0), (255, 255, 0), (255, 0, 255), (0, 255, 255), (128, 0, 255), (0, 128, 255), (255, 223, 128), (127, 111, 64), (92, 46, 0), (0, 84, 0), (192, 192, 0), (0, 127, 127), (128, 0, 0), (127, 0, 127)]
8defaultColorBrewerPalette = {3: [(127, 201, 127), (190, 174, 212), (253, 192, 134)], 4: [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153)], 5: [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153), (56, 108, 176)], 6: [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153), (56, 108, 176), (240, 2, 127)], 7: [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153), (56, 108, 176), (240, 2, 127), (191, 91, 23)], 8: [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153), (56, 108, 176), (240, 2, 127), (191, 91, 23), (102, 102, 102)]} 
9
10colorButtonSize = 25
11specialColorLabelWidth = 160
12paletteInterpolationColors = 250
13
14# On Mac OS X there are problems with QRgb and whether it is long or int and even whether
15# it is positive or negative number (there is corelation between those)
16# Color can be stored in 32 bit unsigned int but Python does not have unsigned int explicitly
17# So Python on Mac sometimes uses long where it should use int (when the highest bit is set and
18# it sees the number as positive - so it cannot be stored as positive number in 31 bits) and sometimes
19# it needs unsigned number and so uses long and does not want a signed int
20
21try:
22    qRed(-1)
23    wantsPositiveColor = False
24except:
25    wantsPositiveColor = True
26
27def signedColor(long):
28    if type(long) == int:
29        return long
30   
31    long &= 0xFFFFFFFF
32   
33    if long & 0x80000000:
34        return int(-((long ^ 0xFFFFFFFF) + 1))
35    else:
36        return int(long)
37
38def positiveColor(color):
39    if wantsPositiveColor and color < 0:
40        return (-color - 1) ^ 0xFFFFFFFF
41    else:
42        return color
43
44def signedPalette(palette):
45    return [signedColor(color) for color in palette]
46
47
48#A 10X10 single color pixmap
49class ColorPixmap (QIcon):
50    def __init__(self,color=QColor(Qt.white), size = 12):
51        "Creates a single-color pixmap"
52        p = QPixmap(size,size)
53        p.fill(color)
54        self.color = color
55        QIcon.__init__(self, p)
56
57
58# a widget that can be used to select the colors to be used
59class ColorPaletteDlg(OWBaseWidget):
60    def __init__(self,parent, caption = "Color Palette", callback = None, modal  = TRUE):
61        OWBaseWidget.__init__(self, None, None, caption, modal = modal)
62        self.setLayout(QVBoxLayout(self))
63        self.layout().setMargin(4)
64
65        self.callback = callback
66        self.contPaletteNames = []
67        self.exContPaletteNames = []
68        self.discPaletteNames = []
69        self.colorButtonNames = []
70        self.colorSchemas = []
71        self.selectedSchemaIndex = 0
72
73        self.mainArea = OWGUI.widgetBox(self, spacing = 4)
74        self.layout().addWidget(self.mainArea)
75        self.schemaCombo = OWGUI.comboBox(self.mainArea, self, "selectedSchemaIndex", box = "Saved Profiles", callback = self.paletteSelected)
76
77        self.hbox = OWGUI.widgetBox(self, orientation = "horizontal")
78        self.okButton = OWGUI.button(self.hbox, self, "OK", self.acceptChanges)
79        self.cancelButton = OWGUI.button(self.hbox, self, "Cancel", self.reject)
80        self.setMinimumWidth(230)
81        self.resize(350, 200)
82
83    def acceptChanges(self):
84        state = self.getCurrentState()
85        oldState = self.colorSchemas[self.selectedSchemaIndex][1]
86        if state == oldState:
87            QDialog.accept(self)
88        else:
89            # if we changed the deafult schema, we must save it under a new name
90            if self.colorSchemas[self.selectedSchemaIndex][0] == "Default":
91                if QMessageBox.information(self, 'Question', 'The color schema has changed. Do you want to save changes?','Yes','No', '', 0,1):
92                    QDialog.reject(self)
93                else:
94                    self.selectedSchemaIndex = self.schemaCombo.count()-1
95                    self.paletteSelected()
96                    QDialog.accept(self)
97            # simply save the new users schema
98            else:
99                self.colorSchemas[self.selectedSchemaIndex] = [self.colorSchemas[self.selectedSchemaIndex][0], state]
100                QDialog.accept(self)
101
102    def createBox(self, boxName, boxCaption = None):
103        box = OWGUI.widgetBox(self.mainArea, boxCaption)
104        box.setAlignment(Qt.AlignLeft)
105        return box
106
107    def createColorButton(self, box, buttonName, buttonCaption, initialColor = Qt.black):
108        self.__dict__["butt" + buttonName] = ColorButton(self, box, buttonCaption)
109        self.__dict__["butt" + buttonName].setColor(QColor(initialColor))
110        self.colorButtonNames.append(buttonName)
111
112
113    def createContinuousPalette(self, paletteName, boxCaption, passThroughBlack = 0, initialColor1 = QColor(Qt.white), initialColor2 = Qt.black):
114        buttBox = OWGUI.widgetBox(self.mainArea, boxCaption) 
115        box = OWGUI.widgetBox(buttBox, orientation = "horizontal")
116       
117        self.__dict__["cont"+paletteName+"Left"]  = ColorButton(self, box, color = QColor(initialColor1))
118        self.__dict__["cont"+paletteName+"View"] = PaletteView(box)
119        self.__dict__["cont"+paletteName+"Right"] = ColorButton(self, box, color = QColor(initialColor2))
120
121        self.__dict__["cont"+paletteName+"passThroughBlack"] = passThroughBlack
122        self.__dict__["cont"+paletteName+"passThroughBlackCheckbox"] = OWGUI.checkBox(buttBox, self, "cont"+paletteName+"passThroughBlack", "Pass through black", callback = self.colorSchemaChange)
123        self.contPaletteNames.append(paletteName)
124
125    def createExtendedContinuousPalette(self, paletteName, boxCaption, passThroughColors = 0, initialColor1 = QColor(Qt.white), initialColor2 = Qt.black, extendedPassThroughColors = ((Qt.red, 1), (Qt.black, 1), (Qt.green, 1))):
126        buttBox = OWGUI.widgetBox(self.mainArea, boxCaption) 
127        box = OWGUI.widgetBox(buttBox, orientation = "horizontal")
128       
129        self.__dict__["exCont"+paletteName+"Left"]  = ColorButton(self, box, color = QColor(initialColor1))
130        self.__dict__["exCont"+paletteName+"View"] = PaletteView(box)
131        self.__dict__["exCont"+paletteName+"Right"] = ColorButton(self, box, color = QColor(initialColor2))
132
133        self.__dict__["exCont"+paletteName+"passThroughColors"] = passThroughColors
134        self.__dict__["exCont"+paletteName+"passThroughColorsCheckbox"] = OWGUI.checkBox(buttBox, self, "exCont"+paletteName+"passThroughColors", "Use pass-through colors", callback = self.colorSchemaChange)
135
136        box = OWGUI.widgetBox(buttBox, "Pass-through colors", orientation = "horizontal")
137        for i, (color, check) in enumerate(extendedPassThroughColors):
138            self.__dict__["exCont"+paletteName+"passThroughColor"+str(i)] = check
139            self.__dict__["exCont"+paletteName+"passThroughColor"+str(i)+"Checkbox"] = cb = OWGUI.checkBox(box, self, "exCont"+paletteName+"passThroughColor"+str(i), "", tooltip="Use color", callback = self.colorSchemaChange)
140            self.__dict__["exCont"+paletteName+"color"+str(i)] = ColorButton(self, box, color = QColor(color))
141            if i < len(extendedPassThroughColors) - 1:
142                OWGUI.rubber(box)
143        self.__dict__["exCont"+paletteName+"colorCount"] = len(extendedPassThroughColors)
144        self.exContPaletteNames.append(paletteName)           
145       
146
147    # #####################################################
148    # DISCRETE COLOR PALETTE
149    # #####################################################
150    def createDiscretePalette(self, paletteName, boxCaption, rgbColors = defaultRGBColors):
151        vbox = OWGUI.widgetBox(self.mainArea, boxCaption, orientation = 'vertical')
152        self.__dict__["disc"+paletteName+"View"] = PaletteView(vbox)
153        self.__dict__["disc"+paletteName+"View"].rgbColors = rgbColors
154       
155        hbox = OWGUI.widgetBox(vbox, orientation = 'horizontal')
156        self.__dict__["disc"+paletteName+"EditButt"] = OWGUI.button(hbox, self, "Edit palette", self.editPalette, tooltip = "Edit the order and colors of the palette", debuggingEnabled = 0, toggleButton = 1)
157        self.__dict__["disc"+paletteName+"LoadButt"] = OWGUI.button(hbox, self, "Load palette", self.loadPalette, tooltip = "Load a predefined color palette", debuggingEnabled = 0, toggleButton = 1)
158        self.discPaletteNames.append(paletteName)
159       
160
161    def editPalette(self):
162        for paletteName in self.discPaletteNames:
163            if self.__dict__["disc"+paletteName+"EditButt"].isChecked():
164                colors = self.__dict__["disc"+paletteName+"View"].rgbColors
165                if type(colors) == dict:
166                    colors = colors[max(colors.keys())]
167                dlg = PaletteEditor(self, colors)
168                if dlg.exec_() and colors != dlg.getRgbColors():
169                    self.__dict__["disc"+paletteName+"View"].setDiscPalette(dlg.getRgbColors())
170                self.__dict__["disc"+paletteName+"EditButt"].setChecked(0)
171                return
172               
173    def loadPalette(self):
174        for paletteName in self.discPaletteNames:
175            if self.__dict__["disc"+paletteName+"LoadButt"].isChecked():
176                self.__dict__["disc"+paletteName+"LoadButt"].setChecked(0)
177                dlg = ColorPalleteListing()
178                if dlg.exec_() == QDialog.Accepted:
179                    for butt in dlg.buttons:
180                        if butt.isChecked():
181                            self.__dict__["disc"+paletteName+"View"].setDiscPalette(butt.rgbColors)
182                            return 
183
184
185    # #####################################################
186
187    def getCurrentSchemeIndex(self):
188        return self.selectedSchemaIndex
189
190    def getColor(self, buttonName):
191        return self.__dict__["butt"+buttonName].getColor()
192
193    def getContinuousPalette(self, paletteName):
194        c1 = self.__dict__["cont"+paletteName+"Left"].getColor()
195        c2 = self.__dict__["cont"+paletteName+"Right"].getColor()
196        b = self.__dict__["cont"+paletteName+"passThroughBlack"]
197        return ContinuousPaletteGenerator(c1, c2, b)
198
199    def getExtendedContinuousPalette(self, paletteName):
200        c1 = self.__dict__["exCont"+paletteName+"Left"].getColor()
201        c2 = self.__dict__["exCont"+paletteName+"Right"].getColor()
202        colors = self.__dict__["exCont"+paletteName+"passThroughColors"]
203        if colors:
204            colors = [self.__dict__["exCont"+paletteName+"color"+str(i)].getColor()
205                      for i in range(self.__dict__["exCont"+paletteName+"colorCount"])
206                      if self.__dict__["exCont"+paletteName+"passThroughColor"+str(i)]]
207        return ExtendedContinuousPaletteGenerator(c1, c2, colors or [])
208
209    def getDiscretePalette(self, paletteName):
210        return ColorPaletteGenerator(rgbColors = self.__dict__["disc"+paletteName+"View"].rgbColors)
211
212    def getColorSchemas(self):
213        return self.colorSchemas
214
215    def getCurrentState(self):
216        l1 = [(name, self.qRgbFromQColor(self.__dict__["butt"+name].getColor())) for name in self.colorButtonNames]
217        l2 = [(name, (self.qRgbFromQColor(self.__dict__["cont"+name+"Left"].getColor()), self.qRgbFromQColor(self.__dict__["cont"+name+"Right"].getColor()), self.__dict__["cont"+name+"passThroughBlack"])) for name in self.contPaletteNames]
218        l3 = [(name, self.__dict__["disc"+name+"View"].rgbColors) for name in self.discPaletteNames]
219        l4 = [(name, (self.qRgbFromQColor(self.__dict__["exCont"+name+"Left"].getColor()), self.qRgbFromQColor(self.__dict__["exCont"+name+"Right"].getColor()), self.__dict__["exCont"+name+"passThroughColors"],
220                      [(self.qRgbFromQColor(self.__dict__["exCont"+name+"color"+str(i)].getColor()), self.__dict__["exCont"+name+"passThroughColor"+str(i)])
221                       for i in range(self.__dict__["exCont"+name+"colorCount"])]))
222                       for name in self.exContPaletteNames]
223        return [l1, l2, l3, l4]
224
225
226    def setColorSchemas(self, schemas = None, selectedSchemaIndex = 0):
227        self.schemaCombo.clear()
228
229        if not schemas or type(schemas) != list:
230            schemas = [("Default", self.getCurrentState()) ]
231
232        self.colorSchemas = schemas
233        self.schemaCombo.addItems([s[0] for s in schemas])
234        self.schemaCombo.addItem("Save current palette as...")
235        self.selectedSchemaIndex = selectedSchemaIndex
236        self.paletteSelected()
237
238    def setCurrentState(self, state):
239        if len(state) > 3:
240            [buttons, contPalettes, discPalettes, exContPalettes] = state
241        else:
242            [buttons, contPalettes, discPalettes] = state
243            exContPalettes = []
244        for (name, but) in buttons:
245            self.__dict__["butt"+name].setColor(self.rgbToQColor(but))
246        for (name, (l,r,chk)) in contPalettes:
247            self.__dict__["cont"+name+"Left"].setColor(self.rgbToQColor(l))
248            self.__dict__["cont"+name+"Right"].setColor(self.rgbToQColor(r))
249            self.__dict__["cont"+name+"passThroughBlack"] = chk
250            self.__dict__["cont"+name+"passThroughBlackCheckbox"].setChecked(chk)
251            self.__dict__["cont"+name+"View"].setContPalette(self.rgbToQColor(l), self.rgbToQColor(r), chk)
252
253        for (name, rgbColors) in discPalettes:
254            self.__dict__["disc"+name+"View"].setDiscPalette(rgbColors)
255
256        for name, (l, r, chk, colors) in exContPalettes:
257            self.__dict__["exCont"+name+"Left"].setColor(self.rgbToQColor(l))
258            self.__dict__["exCont"+name+"Right"].setColor(self.rgbToQColor(r))
259
260            self.__dict__["exCont"+name+"passThroughColors"] = chk
261            self.__dict__["exCont"+name+"passThroughColorsCheckbox"].setChecked(chk)
262
263            colorsList = []
264            for i, (color, check) in enumerate(colors):
265                self.__dict__["exCont"+name+"passThroughColor"+str(i)] = check
266                self.__dict__["exCont"+name+"passThroughColor"+str(i)+"Checkbox"].setChecked(check)
267                self.__dict__["exCont"+name+"color"+str(i)].setColor(self.rgbToQColor(color))
268                if check and chk:
269                    colorsList.append(self.rgbToQColor(color))
270            self.__dict__["exCont"+name+"colorCount"] = self.__dict__.get("exCont"+name+"colorCount", len(colors))
271            self.__dict__["exCont"+name+"View"].setExContPalette(self.rgbToQColor(l), self.rgbToQColor(r), colorsList)
272
273    def paletteSelected(self):
274        if not self.schemaCombo.count(): return
275
276        # if we selected "Save current palette as..." option then add another option to the list
277        if self.selectedSchemaIndex == self.schemaCombo.count()-1:
278            message = "Please enter a name for the current color settings.\nPressing 'Cancel' will cancel your changes and close the dialog."
279            ok = 0
280            while not ok:
281                text, ok = QInputDialog.getText(self, "Name Your Color Settings", message)
282                if (ok):
283                    newName = str(text)
284                    oldNames = [str(self.schemaCombo.itemText(i)).lower() for i in range(self.schemaCombo.count()-1)]
285                    if newName.lower() == "default":
286                        ok = FALSE
287                        message = "The 'Default' settings cannot be changed. Please enter a different name:"
288                    elif newName.lower() in oldNames:
289                        index = oldNames.index(newName.lower())
290                        self.colorSchemas.pop(index)
291
292                    if ok:
293                        self.colorSchemas.insert(0, (newName, self.getCurrentState()))
294                        self.schemaCombo.insertItem(0, newName)
295                        #self.schemaCombo.setCurrentIndex(0)
296                        self.selectedSchemaIndex = 0
297                else:
298                    ok = 1
299                    state = self.getCurrentState()  # if we pressed cancel we have to select a different item than the "Save current palette as..."
300                    self.selectedSchemaIndex = 0    # this will change the color buttons, so we have to restore the colors
301                    self.setCurrentState(state)
302        else:
303            schema = self.colorSchemas[self.selectedSchemaIndex][1]
304            self.setCurrentState(schema)
305            if self.callback: self.callback()
306
307
308    def rgbToQColor(self, rgb):
309        # we could also use QColor(positiveColor(rgb), 0xFFFFFFFF) but there is probably a reason
310        # why this was not used before so I am leaving it as it is
311        return QColor(qRed(positiveColor(rgb)), qGreen(positiveColor(rgb)), qBlue(positiveColor(rgb))) # on Mac color cannot be negative number in this case so we convert it manually
312
313    def qRgbFromQColor(self, qcolor):
314        return qRgb(qcolor.red(), qcolor.green(), qcolor.blue())
315
316    def createPalette(self, color1, color2, passThroughBlack, colorNumber = paletteInterpolationColors):
317        if passThroughBlack:
318            palette = [qRgb(color1.red() - color1.red()*i*2./colorNumber, color1.green() - color1.green()*i*2./colorNumber, color1.blue() - color1.blue()*i*2./colorNumber) for i in range(colorNumber/2)]
319            palette += [qRgb(color2.red()*i*2./colorNumber, color2.green()*i*2./colorNumber, color2.blue()*i*2./colorNumber) for i in range(colorNumber - (colorNumber/2))]
320        else:
321            palette = [qRgb(color1.red() + (color2.red()-color1.red())*i/colorNumber, color1.green() + (color2.green()-color1.green())*i/colorNumber, color1.blue() + (color2.blue()-color1.blue())*i/colorNumber) for i in range(colorNumber)]
322        return palette
323
324    # this function is called if one of the color buttons was pressed or there was any other change of the color palette
325    def colorSchemaChange(self):
326        self.setCurrentState(self.getCurrentState())
327        self.emit(SIGNAL("shemaChanged"))
328        if self.callback: self.callback()
329
330
331class ColorPalleteListing(OWBaseWidget):
332    def __init__(self):
333        OWBaseWidget.__init__(self, None, None, "Color Palette List", modal = 1)
334        self.setLayout(QVBoxLayout(self))
335        self.layout().setMargin(0)
336        sa = QScrollArea()
337        sa.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
338        sa.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
339        self.layout().addWidget(sa)
340       
341        space = QWidget(self)
342        space.setLayout(QVBoxLayout())
343        sa.setWidget(space)
344        sa.setWidgetResizable(1)        # this is crucial in order for the scrollarea to work - otherwise the content is not visible!!!
345       
346        self.buttons = []
347        self.setMinimumWidth(400)
348       
349        from ColorBrewerColorSchemes import colorSchemes
350           
351        box = OWGUI.widgetBox(space, "Information", addSpace = True, orientation="vertical")
352        OWGUI.widgetLabel(box, '<p align="center">This dialog shows a list of predefined color palettes <br>from colorbrewer.org that can be used in Orange.<br>You can select a palette by clicking on it.</p>')
353       
354        box = OWGUI.widgetBox(space, "Default Palette", addSpace = True, orientation="vertical")
355        butt = OWGUI.button(box, self, "", self.buttClicked, tooltip = "Default color palette", toggleButton = 1)
356        butt.rgbColors = defaultRGBColors
357        butt.setIcon(QIcon(createDiscPalettePixmap(butt.iconSize().width(), butt.iconSize().height(), defaultRGBColors)))
358        self.buttons.append(butt)
359       
360        for type in ["Qualitative", "Spectral", "Diverging", "Sequential", "Pastels"]:
361            colorGroup = colorSchemes.get(type.lower(), {})
362            if colorGroup != {}:
363                box = OWGUI.widgetBox(space, type + " Palettes", addSpace = True, orientation="vertical")
364                keys = colorGroup.keys()
365                keys.sort()
366                for key in keys:
367                    butt = OWGUI.button(box, self, "", self.buttClicked, tooltip = key, toggleButton = 1)
368                    butt.rgbColors = colorGroup[key]
369                    self.buttons.append(butt)
370                   
371        box = OWGUI.widgetBox(space, 1, orientation = "horizontal")
372        #OWGUI.button(box, self, "OK", self.accept)
373        OWGUI.button(box, self, "Cancel", self.reject)
374       
375        self.resize(300, 400)
376
377    def showEvent(self, ev):
378        self.resizeEvent(ev)
379
380    def resizeEvent(self, ev):
381        for butt in self.buttons:
382            butt.setFixedHeight(40)
383            butt.setFlat(1)
384            #butt.setStyleSheet("QPushButton:hover { color: white; }")
385            butt.setIconSize(butt.size() - QSize(20, 14))
386            butt.setIcon(QIcon(createDiscPalettePixmap(butt.iconSize().width(), butt.iconSize().height(), butt.rgbColors)))
387       
388    def buttClicked(self):
389        self.accept()
390   
391
392class PaletteEditor(OWBaseWidget):
393    def __init__(self, parent, rgbColors):
394        OWBaseWidget.__init__(self, None, None, "Palette Editor", modal = 1)
395        self.setLayout(QVBoxLayout(self))
396        self.layout().setMargin(4)
397       
398        hbox = OWGUI.widgetBox(self, "Information" , orientation = 'horizontal')
399        OWGUI.widgetLabel(hbox, '<p align="center">You can reorder colors in the list using the<br>buttons on the right or by dragging and dropping the items.<br>To change a specific color double click the item in the list.</p>')
400       
401        hbox = OWGUI.widgetBox(self, 1, orientation = 'horizontal')
402        self.discListbox = OWGUI.listBox(hbox, self, enableDragDrop = 1)
403       
404        vbox = OWGUI.widgetBox(hbox, orientation = 'vertical')
405        buttonUPAttr   = OWGUI.button(vbox, self, "", callback = self.moveAttrUP, tooltip="Move selected colors up")
406        buttonDOWNAttr = OWGUI.button(vbox, self, "", callback = self.moveAttrDOWN, tooltip="Move selected colors down")
407        buttonUPAttr.setIcon(QIcon(os.path.join(self.widgetDir, "icons/Dlg_up3.png")))
408        buttonUPAttr.setSizePolicy(QSizePolicy(QSizePolicy.Fixed , QSizePolicy.Expanding))
409        buttonUPAttr.setMaximumWidth(30)
410        buttonDOWNAttr.setIcon(QIcon(os.path.join(self.widgetDir, "icons/Dlg_down3.png")))
411        buttonDOWNAttr.setSizePolicy(QSizePolicy(QSizePolicy.Fixed , QSizePolicy.Expanding))
412        buttonDOWNAttr.setMaximumWidth(30)
413        self.connect(self.discListbox, SIGNAL("itemDoubleClicked ( QListWidgetItem *)"), self.changeDiscreteColor)
414       
415        box = OWGUI.widgetBox(self, 1, orientation = "horizontal")
416        OWGUI.button(box, self, "OK", self.accept)
417        OWGUI.button(box, self, "Cancel", self.reject)
418               
419        self.discListbox.setIconSize(QSize(25, 25))
420        for ind, (r,g,b) in enumerate(rgbColors):
421            item = QListWidgetItem(ColorPixmap(QColor(r,g,b), 25), "Color %d" % (ind+1))
422            item.rgbColor = (r,g,b)
423            self.discListbox.addItem(item)
424           
425        self.resize(300, 300)
426                 
427                 
428    def changeDiscreteColor(self, item):
429        r,g,b = item.rgbColor
430        color = QColorDialog.getColor(QColor(r,g,b), self)
431        if color.isValid():
432            item.setIcon(ColorPixmap(color, 25))
433            item.rgbColor = (color.red(), color.green(), color.blue())
434
435
436    # move selected attribute in "Attribute Order" list one place up
437    def moveAttrUP(self):
438        if len(self.discListbox.selectedIndexes()) == 0: return
439        ind = self.discListbox.selectedIndexes()[0].row()
440        if ind == 0: return
441        iconI = self.discListbox.item(ind-1).icon()
442        iconII = self.discListbox.item(ind).icon()
443        self.discListbox.item(ind-1).setIcon(iconII)
444        self.discListbox.item(ind).setIcon(iconI)
445        self.discListbox.item(ind-1).rgbColor, self.discListbox.item(ind).rgbColor = self.discListbox.item(ind).rgbColor, self.discListbox.item(ind-1).rgbColor
446        self.discListbox.setCurrentRow(ind-1)
447       
448
449    # move selected attribute in "Attribute Order" list one place down
450    def moveAttrDOWN(self):
451        if len(self.discListbox.selectedIndexes()) == 0: return
452        ind = self.discListbox.selectedIndexes()[0].row()
453        if ind == self.discListbox.count()-1: return
454        iconI = self.discListbox.item(ind+1).icon()
455        iconII = self.discListbox.item(ind).icon()
456        self.discListbox.item(ind+1).setIcon(iconII)
457        self.discListbox.item(ind).setIcon(iconI)
458        self.discListbox.item(ind).rgbColor, self.discListbox.item(ind+1).rgbColor = self.discListbox.item(ind+1).rgbColor, self.discListbox.item(ind).rgbColor
459        self.discListbox.setCurrentRow(ind+1)
460       
461    def getRgbColors(self):
462        return [self.discListbox.item(i).rgbColor for i in range(self.discListbox.count())]
463       
464
465class ContinuousPaletteGenerator:
466    def __init__(self, color1, color2, passThroughBlack):
467        self.c1Red, self.c1Green, self.c1Blue = color1.red(), color1.green(), color1.blue()
468        self.c2Red, self.c2Green, self.c2Blue = color2.red(), color2.green(), color2.blue()
469        self.passThroughBlack = passThroughBlack
470
471    def getRGB(self, val):
472        if self.passThroughBlack:
473            if val < 0.5:
474                return (self.c1Red - self.c1Red*val*2, self.c1Green - self.c1Green*val*2, self.c1Blue - self.c1Blue*val*2)
475            else:
476                return (self.c2Red*(val-0.5)*2., self.c2Green*(val-0.5)*2., self.c2Blue*(val-0.5)*2.)
477        else:
478            return (self.c1Red + (self.c2Red-self.c1Red)*val, self.c1Green + (self.c2Green-self.c1Green)*val, self.c1Blue + (self.c2Blue-self.c1Blue)*val)
479
480    # val must be between 0 and 1
481    def __getitem__(self, val):
482        return QColor(*self.getRGB(val))
483
484class ExtendedContinuousPaletteGenerator:
485    def __init__(self, color1, color2, passThroughColors):
486        self.colors = [color1] + passThroughColors + [color2]
487        self.gammaFunc = lambda x, gamma:((math.exp(gamma*math.log(2*x-1)) if x > 0.5 else -math.exp(gamma*math.log(-2*x+1)) if x!=0.5 else 0.0)+1)/2.0
488
489    def getRGB(self, val, gamma=1.0):
490        index = int(val * (len(self.colors) - 1))
491        if index == len(self.colors) - 1:
492            return (self.colors[-1].red(), self.colors[-1].green(), self.colors[-1].blue())
493        else:
494            red1, green1, blue1 = self.colors[index].red(), self.colors[index].green(), self.colors[index].blue()
495            red2, green2, blue2 = self.colors[index + 1].red(), self.colors[index + 1].green(), self.colors[index + 1].blue()
496            x = val * (len(self.colors) - 1) - index
497            if gamma != 1.0:
498                x = self.gammaFunc(x, gamma)
499            return [(c2 - c1) * x + c1 for c1, c2 in [(red1, red2), (green1, green2), (blue1, blue2)]]
500##        if self.passThroughBlack:
501##            if val < 0.5:
502##                return (self.c1Red - self.c1Red*val*2, self.c1Green - self.c1Green*val*2, self.c1Blue - self.c1Blue*val*2)
503##            else:
504##                return (self.c2Red*(val-0.5)*2., self.c2Green*(val-0.5)*2., self.c2Blue*(val-0.5)*2.)
505##        else:
506##            return (self.c1Red + (self.c2Red-self.c1Red)*val, self.c1Green + (self.c2Green-self.c1Green)*val, self.c1Blue + (self.c2Blue-self.c1Blue)*val)
507
508    # val must be between 0 and 1
509    def __getitem__(self, val):
510        return QColor(*self.getRGB(val))
511
512
513class ColorPaletteGenerator:
514    maxHueVal = 260
515
516    def __init__(self, numberOfColors = 0, rgbColors = defaultRGBColors):
517        self.numberOfColors = -1
518        self.rgbColors = rgbColors
519        if type(rgbColors) == dict:
520            self.rgbColorsDict = rgbColors
521            self.setNumberOfColors(max(rgbColors.keys()))
522        else:
523            self.setNumberOfColors(numberOfColors)
524       
525    # set the number of colors in the palette
526    def setNumberOfColors(self, numberOfColors):
527        if numberOfColors == self.numberOfColors:
528            return
529       
530        self.numberOfColors = numberOfColors
531       
532        if hasattr(self, "rgbColorsDict") and self.rgbColorsDict.has_key(max(3, numberOfColors)):
533            self.rgbColors = self.rgbColorsDict[max(3, numberOfColors)][:numberOfColors]
534
535        self.rgbQColors = [QColor(*color) for color in self.rgbColors]
536       
537
538    def __getitem__(self, index, brightness = None):
539        if type(index) == tuple:
540            index, brightness = index
541       
542        if self.numberOfColors == -1:     # is this color for continuous attribute?
543            col = QColor()
544            col.setHsv(index*self.maxHueVal, brightness or 255, 255)     # index must be between 0 and 1
545            return col
546        else:
547            index = int(index)
548            if index < len(self.rgbColors):
549                if brightness == None:
550                    return self.rgbQColors[index]
551                else:
552                    color = QColor(*self.rgbColors[index])
553                    h,s,v,a = color.getHsv()
554                    color.setHsv(h, int(brightness), v, a)
555                    return color
556            else:
557                col = QColor()
558                col.setHsv(index*self.maxHueVal, brightness or 255, 255)
559                return col
560
561    def getRGB(self, index, brightness = None):
562        index = int(index)
563        if self.numberOfColors == -1:     # is this color for continuous attribute?
564            col = QColor()
565            col.setHsv(index*self.maxHueVal, brightness or 255, 255)     # index must be between 0 and 1
566            return (col.red(), col.green(), col.blue())
567        else:
568            if index < len(self.rgbColors):
569                if brightness == None:
570                    return self.rgbColors[index]
571                else:
572                    col = QColor(*self.rgbColors[index])
573                    h,s,v,a = col.getHsv()
574                    col.setHsv(h, int(brightness), v, a)
575                    return (col.red(), col.green(), col.blue())
576            else:
577                col = QColor()
578                col.setHsv(index*self.maxHueVal, brightness or 255, 255)
579                return (col.red(), col.green(), col.blue())
580
581    # get QColor instance for given index
582    def getColor(self, index, brightness = None):
583        return self.__getitem__(index, brightness)
584
585# only for backward compatibility
586class ColorPaletteHSV(ColorPaletteGenerator):
587    pass
588
589
590# black and white color palette
591class ColorPaletteBW:
592    def __init__(self, numberOfColors = -1, brightest = 50, darkest = 255):
593        self.numberOfColors = numberOfColors
594        self.brightest = brightest
595        self.darkest = darkest
596        self.hueValues = []
597
598        if numberOfColors == -1: return  # used for coloring continuous variables
599        else:
600            self.values = [int(brightest + (darkest-brightest)*x/float(numberOfColors-1)) for x in range(numberOfColors)]
601
602    def __getitem__(self, index):
603        if self.numberOfColors == -1:                # is this color for continuous attribute?
604            val = int(self.brightest + (self.darkest-self.brightest)*index)
605            return QColor(val, val, val)
606        else:                   
607            index = int(index)                       # get color for discrete attribute
608            return QColor(self.values[index], self.values[index], self.values[index])   # index must be between 0 and self.numberofColors
609
610    # get QColor instance for given index
611    def getColor(self, index):
612        return self.__getitem__(index)
613
614
615
616class ColorSchema:
617    def __init__(self, name, palette, additionalColors, passThroughBlack):
618        self.name = name
619        self.palette = palette
620        self.additionalColors = additionalColors
621        self.passThroughBlack = passThroughBlack
622
623    def getName(self):
624        return self.name
625
626    def getPalette(self):
627        return self.palette
628
629    def getAdditionalColors(self):
630        return self.additionalColors
631
632    def getPassThroughBlack(self):
633        return self.passThroughBlack
634
635class PaletteView(QGraphicsView):
636    def __init__(self, parent = None):
637        self.canvas = QGraphicsScene(0, 0, 1000, colorButtonSize)
638        QGraphicsView.__init__(self, self.canvas, parent)
639        self.ensureVisible(0,0,1,1)
640       
641        self.color1 = None
642        self.color2 = None
643        self.rgbColors = []
644        self.passThroughColors = None
645
646        #self.setFrameStyle(QFrame.NoFrame)
647        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
648        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
649
650        self.setFixedHeight(colorButtonSize)
651        self.setMinimumWidth(colorButtonSize)
652       
653        if parent and parent.layout() is not None:
654            parent.layout().addWidget(self)
655   
656    def resizeEvent(self, ev):
657        self.updateImage()
658       
659    def setDiscPalette(self, rgbColors):
660        self.rgbColors = rgbColors
661        self.updateImage()
662   
663    def setContPalette(self, color1, color2, passThroughBlack):
664        self.color1 = color1
665        self.color2 = color2
666        self.passThroughBlack = passThroughBlack
667        self.updateImage()
668
669    def setExContPalette(self, color1, color2, passThroughColors):
670        self.color1 = color1
671        self.color2 = color2
672        self.passThroughColors = passThroughColors
673        self.updateImage()
674       
675    def updateImage(self):
676        for item in self.scene().items():
677            item.hide()
678        if self.color1 == None: 
679            img = createDiscPalettePixmap(self.width(), self.height(), self.rgbColors)
680        elif self.passThroughColors == None:
681            img = createContPalettePixmap(self.width(), self.height(), self.color1, self.color2, self.passThroughBlack)
682        else:
683            img = createExContPalettePixmap(self.width(), self.height(), self.color1, self.color2, self.passThroughColors)
684        self.scene().addPixmap(img)
685        self.scene().update()
686
687
688# create a pixmap with color going from color1 to color2
689def createContPalettePixmap(width, height, color1, color2, passThroughBlack):
690    p = QPainter()
691    img = QPixmap(width, height)
692    p.begin(img)
693   
694    #p.eraseRect(0, 0, w, h)
695    p.setPen(QPen(Qt.NoPen))
696    g = QLinearGradient(0, 0, width, height)
697    g.setColorAt(0, color1)
698    g.setColorAt(1, color2)
699    if passThroughBlack:
700        g.setColorAt(0.5, Qt.black)
701    p.fillRect(img.rect(), QBrush(g))
702    return img
703       
704   
705# create a pixmap with a discrete palette
706def createDiscPalettePixmap(width, height, palette):
707    p = QPainter()
708    img = QPixmap(width, height)
709    p.begin(img)
710    p.setPen(QPen(Qt.NoPen))
711    if type(palette) == dict:       # if palette is the dict with different
712        palette = palette[max(palette.keys())]
713    if len(palette) == 0: return img
714    rectWidth = width / float(len(palette))
715    for i, col in enumerate(palette):
716        p.setBrush(QBrush(QColor(*col)))
717        p.drawRect(QRectF(i*rectWidth, 0, (i+1)*rectWidth, height))
718    return img
719
720# create a pixmap withcolor going from color1 to color2 passing through all intermidiate colors in passThroughColors
721def createExContPalettePixmap(width, height, color1, color2, passThroughColors):
722    p = QPainter()
723    img = QPixmap(width, height)
724    p.begin(img)
725   
726    #p.eraseRect(0, 0, w, h)
727    p.setPen(QPen(Qt.NoPen))
728    g = QLinearGradient(0, 0, width, height)
729    g.setColorAt(0, color1)
730    g.setColorAt(1, color2)
731    for i, color in enumerate(passThroughColors):
732        g.setColorAt(float(i + 1) / (len(passThroughColors) + 1), color)
733    p.fillRect(img.rect(), QBrush(g))
734    return img
735       
736
737class ColorButton(QWidget):
738    def __init__(self, master = None, parent = None, label = None, color = None):
739        QWidget.__init__(self, master)
740
741        self.parent = parent
742        self.master = master
743
744        if self.parent and self.parent.layout() is not None:
745            self.parent.layout().addWidget(self)
746
747        self.setLayout(QHBoxLayout())
748        self.layout().setMargin(0)
749        self.icon = QFrame(self)
750        self.icon.setFixedSize(colorButtonSize, colorButtonSize)
751        self.icon.setAutoFillBackground(1)
752        self.icon.setFrameStyle (QFrame.StyledPanel+ QFrame.Sunken)
753        self.layout().addWidget(self.icon)
754
755        if label != None:
756            self.label = OWGUI.widgetLabel(self, label)
757            self.layout().addWidget(self.label)
758
759        if color != None:
760            self.setColor(color)
761
762
763    def setColor(self, color):
764        self.color = color
765        palette = QPalette()
766        palette.setBrush(QPalette.Background, color)
767        self.icon.setPalette(palette)
768
769    def getColor(self):
770        return self.color
771
772    def mousePressEvent(self, ev):
773        color = QColorDialog.getColor(self.color)
774        if color.isValid():
775            self.setColor(color)
776            if self.master and hasattr(self.master, "colorSchemaChange"):
777                self.master.colorSchemaChange()
778
779def rgbToQColor(rgb):
780    # we could also use QColor(positiveColor(rgb), 0xFFFFFFFF) but there is probably a reason
781    # why this was not used before so I am leaving it as it is
782    return QColor(qRed(positiveColor(rgb)), qGreen(positiveColor(rgb)), qBlue(positiveColor(rgb)))
783
784class PaletteItemDelegate(QItemDelegate):
785    def __init__(self, selector, *args):
786        QItemDelegate.__init__(self, *args)
787        self.selector = selector
788       
789    def paint(self, painter, option, index):
790        img = self.selector.paletteImg[index.row()]
791        painter.drawPixmap(option.rect.x(), option.rect.y(), img)
792
793    def sizeHint(self, option, index):       
794        img = self.selector.paletteImg[index.row()]
795        return img.size()
796   
797class PaletteSelectorComboBox(QComboBox):
798    def __init__(self, *args):
799        QComboBox.__init__(self, *args)
800        self.paletteImg = []
801        self.cachedPalettes = []
802##        self.setItemDelegate(PaletteItemDelegate(self, self))
803        size = self.sizeHint()
804        size = QSize(size.width()*2/3, size.height()*2/3)
805        self.setIconSize(size)
806
807    def setPalettes(self, name, paletteDlg):
808        self.clear()
809        self.cachedPalettes = []
810        shemas = paletteDlg.getColorSchemas()
811        if name in paletteDlg.discPaletteNames:
812            pass
813        if name in paletteDlg.contPaletteNames:
814            pass
815        if name in paletteDlg.exContPaletteNames:
816            palettes = []
817            paletteIndex = paletteDlg.exContPaletteNames.index(name)
818            for schemaName, state in shemas:
819                butt, disc, cont, exCont = state
820                name, (c1, c2, chk, colors) = exCont[paletteIndex]
821                palettes.append((schemaName, ((rgbToQColor(c1), rgbToQColor(c2), [rgbToQColor(color) for color, check in colors if check and chk]))))
822            self.setContinuousPalettes(palettes)
823
824    def setDiscretePalettes(self, palettes):
825        self.clear()
826        paletteImg = []
827        self.cachedPalettes = []
828        for name, colors in palettes:
829            self.addItem(name)
830            self.paletteImg.append(createDiscPalettePixmap(200, 20, colors))
831            self.cachedPalettes.append(ColorPaletteGenerator(rgbColors = colors))
832
833    def setContinuousPalettes(self, palettes):
834        self.clear()
835        paletteImg = []
836        self.cachedPalettes = []
837        for name, (c1, c2, colors) in palettes:
838            icon = QIcon(createExContPalettePixmap(self.iconSize().width(), self.iconSize().height(), c1, c2, colors))
839            self.addItem(icon, name)
840           
841
842if __name__== "__main__":
843    a = QApplication(sys.argv)
844
845    c = ColorPaletteDlg(None, modal = FALSE)
846    c.createContinuousPalette("continuousPalette", "Continuous Palette")
847    c.createDiscretePalette("discPalette", "Discrete Palette")
848    box = c.createBox("otherColors", "Colors")
849    c.createColorButton(box, "Canvas", "Canvas")
850    c.createColorButton(box, "Grid", "Grid")
851    c.setColorSchemas()
852    c.show()
853    a.exec_()
Note: See TracBrowser for help on using the repository browser.