| 1 | from PyQt4.QtCore import * |
|---|
| 2 | from PyQt4.QtGui import * |
|---|
| 3 | from OWWidget import * |
|---|
| 4 | import OWGUI |
|---|
| 5 | import math |
|---|
| 6 | |
|---|
| 7 | defaultRGBColors = [(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)] |
|---|
| 8 | defaultColorBrewerPalette = {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 | |
|---|
| 10 | colorButtonSize = 25 |
|---|
| 11 | specialColorLabelWidth = 160 |
|---|
| 12 | paletteInterpolationColors = 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 | |
|---|
| 21 | try: |
|---|
| 22 | qRed(-1) |
|---|
| 23 | wantsPositiveColor = False |
|---|
| 24 | except: |
|---|
| 25 | wantsPositiveColor = True |
|---|
| 26 | |
|---|
| 27 | def 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 | |
|---|
| 38 | def positiveColor(color): |
|---|
| 39 | if wantsPositiveColor and color < 0: |
|---|
| 40 | return (-color - 1) ^ 0xFFFFFFFF |
|---|
| 41 | else: |
|---|
| 42 | return color |
|---|
| 43 | |
|---|
| 44 | def signedPalette(palette): |
|---|
| 45 | return [signedColor(color) for color in palette] |
|---|
| 46 | |
|---|
| 47 | |
|---|
| 48 | #A 10X10 single color pixmap |
|---|
| 49 | class 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 |
|---|
| 59 | class 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 | |
|---|
| 331 | class 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 | |
|---|
| 392 | class 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 | |
|---|
| 465 | class 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 | |
|---|
| 484 | class 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 | |
|---|
| 513 | class 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 | index = int(index) |
|---|
| 542 | |
|---|
| 543 | if self.numberOfColors == -1: # is this color for continuous attribute? |
|---|
| 544 | col = QColor() |
|---|
| 545 | col.setHsv(index*self.maxHueVal, brightness or 255, 255) # index must be between 0 and 1 |
|---|
| 546 | return col |
|---|
| 547 | else: |
|---|
| 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 |
|---|
| 586 | class ColorPaletteHSV(ColorPaletteGenerator): |
|---|
| 587 | pass |
|---|
| 588 | |
|---|
| 589 | |
|---|
| 590 | # black and white color palette |
|---|
| 591 | class 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 | |
|---|
| 616 | class 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 | |
|---|
| 635 | class 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 |
|---|
| 689 | def 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 |
|---|
| 706 | def 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 |
|---|
| 721 | def 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 | |
|---|
| 737 | class 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 | |
|---|
| 779 | def 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 | |
|---|
| 784 | class 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 | |
|---|
| 797 | class 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 | |
|---|
| 842 | if __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_() |
|---|