1# Copyright (C) 2005 Jeremy S. Sanders 2# Email: Jeremy Sanders <jeremy@jeremysanders.net> 3# 4# This program is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation; either version 2 of the License, or 7# (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License along 15# with this program; if not, write to the Free Software Foundation, Inc., 16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17############################################################################### 18 19"""Module for holding setting values. 20 21e.g. 22 23s = Int('foo', 5) 24s.get() 25s.set(42) 26s.fromUIText('42') 27""" 28 29from __future__ import division 30import re 31import sys 32 33import numpy as N 34 35from ..compat import cbasestr, cstr, crepr 36from .. import qtall as qt 37from . import controls 38from .settingdb import settingdb, uilocale, ui_floattostring, ui_stringtofloat 39from .reference import ReferenceBase, Reference 40 41from .. import utils 42from .. import datasets 43 44class OnModified(qt.QObject): 45 """onmodified is emitted from an object contained in each setting.""" 46 onModified = qt.pyqtSignal() 47 48class Setting(object): 49 """A class to store a value with a particular type.""" 50 51 # differentiate widgets, settings and setting 52 nodetype = 'setting' 53 54 typename = 'setting' 55 56 # various items in class hierarchy 57 iswidget = False 58 issetting = True 59 issettings = False 60 61 def __init__(self, name, value, descr='', usertext='', 62 formatting=False, hidden=False): 63 """Initialise the values. 64 65 name: setting name 66 value: default value and initial value 67 descr: description of the setting 68 usertext: name of setting for user 69 formatting: whether setting applies to formatting 70 hidden: hide widget from user 71 """ 72 self.readonly = False 73 self.parent = None 74 self.name = name 75 self.descr = descr 76 self.usertext = usertext 77 self.formatting = formatting 78 self.hidden = hidden 79 self.default = value 80 self.onmodified = OnModified() 81 self._val = self._ref = None 82 83 # calls the set function for the val property 84 self.val = value 85 86 def _copyHelper(self, before, after, optional): 87 """Help copy an object. 88 89 before are arguments before val 90 after are arguments after val 91 optinal as optional arguments 92 """ 93 94 val = self._ref if self._ref else self._val 95 96 args = (self.name,) + before + (val,) + after 97 98 opt = optional.copy() 99 opt['descr'] = self.descr 100 opt['usertext'] = self.usertext 101 opt['formatting'] = self.formatting 102 opt['hidden'] = self.hidden 103 104 obj = self.__class__(*args, **opt) 105 obj.readonly = self.readonly 106 obj.default = self.default 107 return obj 108 109 def copy(self): 110 """Make a setting which has its values copied from this one. 111 112 This needs to be overridden if the constructor changes 113 """ 114 return self._copyHelper((), (), {}) 115 116 def get(self): 117 """Get the value.""" 118 119 if self._ref: 120 return self._ref.resolve(self).get() 121 else: 122 return self._val 123 124 def set(self, v): 125 """Set the value.""" 126 127 if isinstance(v, ReferenceBase): 128 self._val = None 129 self._ref = v 130 else: 131 self._val = self.normalize(v) 132 self._ref = None 133 134 self.onmodified.onModified.emit() 135 136 val = property( 137 get, set, None, 138 'Get or modify the value of the setting') 139 140 def isReference(self): 141 """Is this a setting a reference to another object.""" 142 return bool(self._ref) 143 144 def getReference(self): 145 """Return the reference object. Raise ValueError if not a reference""" 146 if self._ref: 147 return self._ref 148 else: 149 raise ValueError("Setting is not a reference") 150 151 def getStylesheetLink(self): 152 """Get text that this setting should default to linked to the 153 stylesheet.""" 154 path = [] 155 obj = self 156 while not obj.parent.iswidget: 157 path.insert(0, obj.name) 158 obj = obj.parent 159 path = ['', 'StyleSheet', obj.parent.typename] + path 160 return '/'.join(path) 161 162 def linkToStylesheet(self): 163 """Make this setting link to stylesheet setting, if possible.""" 164 self.set( Reference(self.getStylesheetLink()) ) 165 166 @property 167 def path(self): 168 """Return full path of setting.""" 169 path = [] 170 obj = self 171 while obj is not None: 172 # logic easier to understand here 173 # do not add settings name for settings of widget 174 if not obj.iswidget and obj.parent.iswidget: 175 pass 176 else: 177 if obj.name == '/': 178 path.insert(0, '') 179 else: 180 path.insert(0, obj.name) 181 obj = obj.parent 182 return '/'.join(path) 183 184 def toUIText(self): 185 """Convert the type to text to show in UI.""" 186 return "" 187 188 def fromUIText(self, text): 189 """Convert text from UI into type for setting. 190 191 Raises utils.InvalidType if cannot convert.""" 192 return None 193 194 def saveText(self, saveall, rootname = ''): 195 """Return text to restore the value of this setting.""" 196 197 if (saveall or not self.isDefault()) and not self.readonly: 198 if self._ref: 199 return "SetToReference('%s%s', %s)\n" % ( 200 rootname, self.name, crepr(self._ref.value)) 201 else: 202 return "Set('%s%s', %s)\n" % ( 203 rootname, self.name, utils.rrepr(self.val)) 204 else: 205 return '' 206 207 def setOnModified(self, fn): 208 """Set the function to be called on modification (passing True).""" 209 self.onmodified.onModified.connect(fn) 210 211 if self._ref: 212 # tell references to notify us if they are modified 213 self._ref.setOnModified(self, fn) 214 215 def removeOnModified(self, fn): 216 """Remove the function from the list of function to be called.""" 217 self.onmodified.onModified.disconnect(fn) 218 219 def newDefault(self, value): 220 """Update the default and the value.""" 221 self.default = value 222 self.val = value 223 224 def isDefault(self): 225 """Is the current value a default? 226 This also returns true if it is linked to the appropriate stylesheet 227 """ 228 229 if self._ref and isinstance(self.default, ReferenceBase): 230 return self._ref.value == self.default.value 231 else: 232 return self._val == self.default 233 234 def isDefaultLink(self): 235 """Is this a link to the default stylesheet value.""" 236 return self._ref and self._ref.value == self.getStylesheetLink() 237 238 def setSilent(self, val): 239 """Set the setting, without propagating modified flags. 240 241 This shouldn't often be used as it defeats the automatic updation. 242 Used for temporary modifications.""" 243 244 self._ref = None 245 self._val = self.normalize(val) 246 247 def normalize(self, val): 248 """Convert external value to normalized form for storing 249 250 Raises a utils.InvalidType if this is not possible.""" 251 return val 252 253 def makeControl(self, *args): 254 """Make a qt control for editing the setting. 255 256 The control emits settingValueChanged() when the setting has 257 changed value.""" 258 259 return None 260 261 def getDocument(self): 262 """Return document.""" 263 p = self.parent 264 while p: 265 if p.iswidget: 266 return p.document 267 p = p.parent 268 return None 269 270 def getWidget(self): 271 """Return associated widget.""" 272 w = self.parent 273 while not w.iswidget: 274 w = w.parent 275 return w 276 277 def safeEvalHelper(self, text): 278 """Evaluate an expression, catching naughtiness.""" 279 try: 280 comp = self.getDocument().evaluate.compileCheckedExpression( 281 text) 282 if comp is None: 283 raise utils.InvalidType 284 return float( eval(comp, self.getDocument().evaluate.context) ) 285 except: 286 raise utils.InvalidType 287 288# forward setting to another setting 289class SettingBackwardCompat(Setting): 290 """Forward setting requests to another setting. 291 292 This is used for backward-compatibility. 293 """ 294 295 typename = 'backward-compat' 296 297 def __init__(self, name, newrelpath, val, translatefn=None, 298 **args): 299 """Point this setting to another. 300 newrelpath is a path relative to this setting's parent 301 """ 302 303 self.translatefn = translatefn 304 args['hidden'] = True 305 Setting.__init__(self, name, val, **args) 306 self.relpath = newrelpath 307 308 def getForward(self): 309 """Get setting this setting forwards to.""" 310 doc = self.getDocument() 311 return doc.resolveSettingPath(self.parent, self.relpath) 312 313 def normalize(self, val): 314 if self.parent is not None: 315 return self.getForward().normalize(val) 316 317 def toUIText(self): 318 return self.getForward().toUIText() 319 320 def fromUIText(self, val): 321 return self.getForward().fromUIText(val) 322 323 def set(self, val): 324 if self.parent is not None and not isinstance(val, ReferenceBase): 325 if self.translatefn: 326 val = self.translatefn(val) 327 self.getForward().set(val) 328 329 def isDefault(self): 330 return self.getForward().isDefault() 331 332 def get(self): 333 return self.getForward().get() 334 335 def copy(self): 336 return self._copyHelper( 337 (self.relpath,), (), {'translatefn': self.translatefn}) 338 339 def makeControl(self, *args): 340 return None 341 342 def saveText(self, saveall, rootname = ''): 343 return '' 344 345 def linkToStylesheet(self): 346 """Do nothing for backward compatibility settings.""" 347 pass 348 349# Store strings 350class Str(Setting): 351 """String setting.""" 352 353 typename = 'str' 354 355 def normalize(self, val): 356 if isinstance(val, cbasestr): 357 return val 358 raise utils.InvalidType 359 360 def toUIText(self): 361 return self.val 362 363 def fromUIText(self, text): 364 return text 365 366 def makeControl(self, *args): 367 return controls.String(self, *args) 368 369class Notes(Str): 370 """String for making notes.""" 371 372 typename = 'str-notes' 373 374 def makeControl(self, *args): 375 return controls.Notes(self, *args) 376 377# Store bools 378class Bool(Setting): 379 """Bool setting.""" 380 381 typename = 'bool' 382 383 def normalize(self, val): 384 if type(val) in (bool, int): 385 return bool(val) 386 raise utils.InvalidType 387 388 def toUIText(self): 389 return 'True' if self.val else 'False' 390 391 def fromUIText(self, text): 392 t = text.strip().lower() 393 if t in ('true', '1', 't', 'y', 'yes'): 394 return True 395 elif t in ('false', '0', 'f', 'n', 'no'): 396 return False 397 else: 398 raise utils.InvalidType 399 400 def makeControl(self, *args): 401 return controls.Bool(self, *args) 402 403# Storing integers 404class Int(Setting): 405 """Integer settings.""" 406 407 typename = 'int' 408 409 def __init__(self, name, value, minval=-1000000, maxval=1000000, 410 **args): 411 """Initialise the values. 412 413 minval is minimum possible value of setting 414 maxval is maximum possible value of setting 415 """ 416 417 self.minval = minval 418 self.maxval = maxval 419 Setting.__init__(self, name, value, **args) 420 421 def copy(self): 422 """Make a setting which has its values copied from this one. 423 424 This needs to be overridden if the constructor changes 425 """ 426 return self._copyHelper((), (), {'minval': self.minval, 427 'maxval': self.maxval}) 428 429 def normalize(self, val): 430 if isinstance(val, int): 431 if val >= self.minval and val <= self.maxval: 432 return val 433 else: 434 raise utils.InvalidType('Out of range allowed') 435 raise utils.InvalidType 436 437 def toUIText(self): 438 return uilocale.toString(self.val) 439 440 def fromUIText(self, text): 441 i, ok = uilocale.toLongLong(text) 442 if not ok: 443 raise ValueError 444 445 if i >= self.minval and i <= self.maxval: 446 return i 447 else: 448 raise utils.InvalidType('Out of range allowed') 449 450 def makeControl(self, *args): 451 return controls.Int(self, *args) 452 453def _finiteRangeFloat(f, minval=-1e300, maxval=1e300): 454 """Return a finite float in range or raise exception otherwise.""" 455 f = float(f) 456 if not N.isfinite(f): 457 raise utils.InvalidType('Finite values only allowed') 458 if f < minval or f > maxval: 459 raise utils.InvalidType('Out of range allowed') 460 return f 461 462# for storing floats 463class Float(Setting): 464 """Float settings.""" 465 466 typename = 'float' 467 468 def __init__(self, name, value, minval=-1e200, maxval=1e200, 469 **args): 470 """Initialise the values. 471 472 minval is minimum possible value of setting 473 maxval is maximum possible value of setting 474 """ 475 476 self.minval = minval 477 self.maxval = maxval 478 Setting.__init__(self, name, value, **args) 479 480 def copy(self): 481 """Make a setting which has its values copied from this one. 482 483 This needs to be overridden if the constructor changes 484 """ 485 return self._copyHelper((), (), {'minval': self.minval, 486 'maxval': self.maxval}) 487 488 def normalize(self, val): 489 if isinstance(val, int) or isinstance(val, float): 490 return _finiteRangeFloat( 491 val, minval=self.minval, maxval=self.maxval) 492 raise utils.InvalidType 493 494 def toUIText(self): 495 return ui_floattostring(self.val) 496 497 def fromUIText(self, text): 498 try: 499 f = ui_stringtofloat(text) 500 except ValueError: 501 # try to evaluate 502 f = self.safeEvalHelper(text) 503 return self.normalize(f) 504 505 def makeControl(self, *args): 506 return controls.Edit(self, *args) 507 508class FloatOrAuto(Float): 509 """Save a float or text auto.""" 510 511 typename = 'float-or-auto' 512 513 def normalize(self, val): 514 if type(val) in (int, float): 515 return _finiteRangeFloat(val, minval=self.minval, maxval=self.maxval) 516 elif isinstance(val, cbasestr) and val.strip().lower() == 'auto': 517 return 'Auto' 518 else: 519 raise utils.InvalidType 520 521 def toUIText(self): 522 if isinstance(self.val, cbasestr) and self.val.lower() == 'auto': 523 return 'Auto' 524 else: 525 return ui_floattostring(self.val) 526 527 def fromUIText(self, text): 528 if text.strip().lower() == 'auto': 529 return 'Auto' 530 else: 531 return Float.fromUIText(self, text) 532 533 def makeControl(self, *args): 534 return controls.Choice(self, True, ['Auto'], *args) 535 536class FloatSlider(Float): 537 """A float with a slider control.""" 538 539 typename = 'float-slider' 540 541 def __init__(self, name, value, step=10, tick=50, scale=1, **args): 542 """Step is the size to step by.""" 543 Float.__init__(self, name, value, **args) 544 self.step = step 545 self.tick = tick 546 self.scale = scale 547 548 def copy(self): 549 return self._copyHelper((), (), { 550 'minval': self.minval, 551 'maxval': self.maxval, 552 'step': self.step, 553 'tick': self.tick, 554 'scale': self.scale, 555 }) 556 557 def makeControl(self, *args): 558 return controls.FloatSlider(self, *args) 559 560class IntOrAuto(Setting): 561 """Save an int or text auto.""" 562 563 typename = 'int-or-auto' 564 565 def normalize(self, val): 566 if isinstance(val, int): 567 return val 568 elif isinstance(val, cbasestr) and val.strip().lower() == 'auto': 569 return 'Auto' 570 else: 571 raise utils.InvalidType 572 573 def toUIText(self): 574 if isinstance(self.val, cbasestr) and self.val.lower() == 'auto': 575 return 'Auto' 576 else: 577 return uilocale.toString(self.val) 578 579 def fromUIText(self, text): 580 if text.strip().lower() == 'auto': 581 return 'Auto' 582 else: 583 i, ok = uilocale.toLongLong(text) 584 if not ok: 585 raise utils.InvalidType 586 return i 587 588 def makeControl(self, *args): 589 return controls.Choice(self, True, ['Auto'], *args) 590 591# these are functions used by the distance setting below. 592# they don't work as class methods 593 594def _distPhys(match, painter, mult): 595 """Convert a physical unit measure in multiples of points.""" 596 return painter.pixperpt * mult * float(match.group(1)) 597 598def _idistval(val, unit): 599 """Convert value to text, dropping zeros and . points on right.""" 600 return ("%.3f" % val).rstrip('0').rstrip('.') + unit 601 602def _distInvPhys(pixdist, painter, mult, unit): 603 """Convert number of pixels into physical distance.""" 604 return _idistval(pixdist / (mult*painter.pixperpt), unit) 605 606def _distPerc(match, painter): 607 """Convert from a percentage of maxdim.""" 608 return painter.maxdim * 0.01 * float(match.group(1)) 609 610def _distInvPerc(pixdist, painter): 611 """Convert pixel distance into percentage.""" 612 return _idistval(pixdist * 100. / painter.maxdim, '%') 613 614def _distFrac(match, painter): 615 """Convert from a fraction a/b of maxdim.""" 616 try: 617 return painter.maxdim * float(match.group(1))/float(match.group(4)) 618 except ZeroDivisionError: 619 return 0. 620 621def _distRatio(match, painter): 622 """Convert from a simple 0.xx ratio of maxdim.""" 623 624 # if it's greater than 1 then assume it's a point measurement 625 if float(match.group(1)) > 1.: 626 return _distPhys(match, painter, 1) 627 628 return painter.maxdim * float(match.group(1)) 629 630# regular expression to match distances 631distre_expr = r'''^ 632 [ ]* # optional whitespace 633 634 (\.?[0-9]+|[0-9]+\.[0-9]*) # a floating point number 635 636 [ ]* # whitespace 637 638 (cm|pt|mm|inch|in|"|%|| # ( unit, no unit, 639 (?P<slash>/) ) # or / ) 640 641 (?(slash)[ ]* # if it was a slash, match any whitespace 642 (\.?[0-9]+|[0-9]+\.[0-9]*)) # and match following fp number 643 644 [ ]* # optional whitespace 645$''' 646 647class Distance(Setting): 648 """A veusz distance measure, e.g. 1pt or 3%.""" 649 650 typename = 'distance' 651 652 # match a distance 653 distre = re.compile(distre_expr, re.VERBOSE) 654 655 # functions to convert from unit values to points 656 unit_func = { 657 'cm': lambda match, painter: 658 _distPhys(match, painter, 720/25.4), 659 'pt': lambda match, painter: 660 _distPhys(match, painter, 1.), 661 'mm': lambda match, painter: 662 _distPhys(match, painter, 72/25.4), 663 'in': lambda match, painter: 664 _distPhys(match, painter, 72.), 665 'inch': lambda match, painter: 666 _distPhys(match, painter, 72.), 667 '"': lambda match, painter: 668 _distPhys(match, painter, 72.), 669 '%': _distPerc, 670 '/': _distFrac, 671 '': _distRatio 672 } 673 674 # inverse functions for converting points to units 675 inv_unit_func = { 676 'cm': lambda match, painter: 677 _distInvPhys(match, painter, 720/25.4, 'cm'), 678 'pt': lambda match, painter: 679 _distInvPhys(match, painter, 1., 'pt'), 680 'mm': lambda match, painter: 681 _distInvPhys(match, painter, 72/25.4, 'mm'), 682 'in': lambda match, painter: 683 _distInvPhys(match, painter, 72., 'in'), 684 'inch': lambda match, painter: 685 _distInvPhys(match, painter, 72., 'in'), 686 '"': lambda match, painter: 687 _distInvPhys(match, painter, 72., 'in'), 688 '%': _distInvPerc, 689 '/': _distInvPerc, 690 '': _distInvPerc 691 } 692 693 @classmethod 694 def isDist(kls, dist): 695 """Is the text a valid distance measure?""" 696 697 return kls.distre.match(dist) is not None 698 699 def normalize(self, val): 700 if self.distre.match(val) is not None: 701 return val 702 else: 703 raise utils.InvalidType 704 705 def toUIText(self): 706 # convert decimal point to display locale 707 return self.val.replace('.', uilocale.decimalPoint()) 708 709 def fromUIText(self, text): 710 # convert decimal point from display locale 711 text = text.replace(uilocale.decimalPoint(), '.') 712 713 if self.isDist(text): 714 return text 715 else: 716 raise utils.InvalidType 717 718 def makeControl(self, *args): 719 return controls.Distance(self, *args) 720 721 @classmethod 722 def convertDistance(kls, painter, dist): 723 '''Convert a distance to plotter units. 724 725 dist: eg 0.1 (fraction), 10% (percentage), 1/10 (fraction), 726 10pt, 1cm, 20mm, 1inch, 1in, 1" (size) 727 painter: painter to get metrics to convert physical sizes 728 ''' 729 730 # match distance against expression 731 m = kls.distre.match(dist) 732 if m is not None: 733 # lookup function to call to do conversion 734 func = kls.unit_func[m.group(2)] 735 return func(m, painter) 736 737 # none of the regexps match 738 raise ValueError( "Cannot convert distance in form '%s'" % 739 dist ) 740 741 def convert(self, painter): 742 """Convert this setting's distance as above""" 743 return self.convertDistance(painter, self.val) 744 745 def convertPts(self, painter): 746 """Get the distance in points.""" 747 return self.convert(painter) / painter.pixperpt 748 749 def convertInverse(self, distpix, painter): 750 """Convert distance in pixels into units of this distance. 751 """ 752 753 m = self.distre.match(self.val) 754 if m is not None: 755 # if it matches convert back 756 inversefn = self.inv_unit_func[m.group(2)] 757 else: 758 # otherwise force unit 759 inversefn = self.inv_unit_func['cm'] 760 761 # do inverse mapping 762 return inversefn(distpix, painter) 763 764class DistancePt(Distance): 765 """For a distance in points.""" 766 767 def makeControl(self, *args): 768 return controls.DistancePt(self, *args) 769 770class DistancePhysical(Distance): 771 """For physical distances (no fractional).""" 772 773 def isDist(self, val): 774 m = self.distre.match(val) 775 if m: 776 # disallow non-physical distances 777 if m.group(2) not in ('/', '', '%'): 778 return True 779 return False 780 781 def makeControl(self, *args): 782 return controls.Distance(self, *args, physical=True) 783 784class DistanceOrAuto(Distance): 785 """A distance or the value Auto""" 786 787 typename = 'distance-or-auto' 788 789 distre = re.compile( distre_expr + r'|^Auto$', re.VERBOSE ) 790 791 def isAuto(self): 792 return self.val == 'Auto' 793 794 def makeControl(self, *args): 795 return controls.Distance(self, allowauto=True, *args) 796 797class Choice(Setting): 798 """One out of a list of strings.""" 799 800 # maybe should be implemented as a dict to speed up checks 801 802 typename = 'choice' 803 804 def __init__(self, name, vallist, val, descriptions=None, 805 uilist=None, **args): 806 """Setting val must be in vallist. 807 descriptions is an optional addon to put a tooltip on each item 808 in the control. 809 uilist is a tuple/list of text to show to the user, instead of vallist 810 """ 811 812 assert type(vallist) in (list, tuple) 813 814 self.vallist = vallist 815 self.descriptions = descriptions 816 self.uilist = uilist 817 818 Setting.__init__(self, name, val, **args) 819 820 def copy(self): 821 """Make a copy of the setting.""" 822 return self._copyHelper( 823 (self.vallist,), (), { 824 'descriptions': self.descriptions, 'uilist': self.uilist}) 825 826 def normalize(self, val): 827 if val in self.vallist: 828 return val 829 else: 830 raise utils.InvalidType 831 832 def toUIText(self): 833 return self.val 834 835 def fromUIText(self, text): 836 if text in self.vallist: 837 return text 838 else: 839 raise utils.InvalidType 840 841 def makeControl(self, *args): 842 return controls.Choice(self, False, self.vallist, 843 descriptions=self.descriptions, 844 uilist=self.uilist, *args) 845 846class ChoiceOrMore(Setting): 847 """One out of a list of strings, or anything else.""" 848 849 # maybe should be implemented as a dict to speed up checks 850 851 typename = 'choice-or-more' 852 853 def __init__(self, name, vallist, val, descriptions=None, **args): 854 """Setting has val must be in vallist. 855 descriptions is an optional addon to put a tooltip on each item 856 in the control 857 """ 858 859 self.vallist = vallist 860 self.descriptions = descriptions 861 Setting.__init__(self, name, val, **args) 862 863 def copy(self): 864 """Make a copy of the setting.""" 865 return self._copyHelper( 866 (self.vallist,), (), { 867 'descriptions': self.descriptions}) 868 869 def normalize(self, val): 870 return val 871 872 def toUIText(self): 873 return self.val 874 875 def fromUIText(self, text): 876 return text 877 878 def makeControl(self, *args): 879 argsv = {'descriptions': self.descriptions} 880 return controls.Choice(self, True, self.vallist, *args, **argsv) 881 882class FloatChoice(ChoiceOrMore): 883 """A numeric value, which can also be chosen from the list of values.""" 884 885 typename = 'float-choice' 886 887 def normalize(self, val): 888 if isinstance(val, int) or isinstance(val, float): 889 return _finiteRangeFloat(val) 890 raise utils.InvalidType 891 892 def toUIText(self): 893 return ui_floattostring(self.val) 894 895 def fromUIText(self, text): 896 try: 897 f = ui_stringtofloat(text) 898 except ValueError: 899 # try to evaluate 900 f = self.safeEvalHelper(text) 901 return self.normalize(f) 902 903 def makeControl(self, *args): 904 argsv = {'descriptions': self.descriptions} 905 strings = [ui_floattostring(x) for x in self.vallist] 906 return controls.Choice(self, True, strings, *args, **argsv) 907 908class FloatDict(Setting): 909 """A dictionary, taking floats as values.""" 910 911 typename = 'float-dict' 912 913 def normalize(self, val): 914 if type(val) != dict: 915 raise utils.InvalidType 916 917 for v in val.values(): 918 if type(v) not in (float, int): 919 raise utils.InvalidType 920 921 # return copy 922 return dict(val) 923 924 def toUIText(self): 925 text = ['%s = %s' % (k, ui_floattostring(self.val[k])) 926 for k in sorted(self.val)] 927 return '\n'.join(text) 928 929 def fromUIText(self, text): 930 """Do conversion from list of a=X\n values.""" 931 932 out = {} 933 # break up into lines 934 for l in text.split('\n'): 935 l = l.strip() 936 if len(l) == 0: 937 continue 938 939 # break up using = 940 p = l.strip().split('=') 941 942 if len(p) != 2: 943 raise utils.InvalidType 944 945 try: 946 v = ui_stringtofloat(p[1]) 947 except ValueError: 948 raise utils.InvalidType 949 950 out[ p[0].strip() ] = v 951 return out 952 953 def makeControl(self, *args): 954 return controls.MultiLine(self, *args) 955 956class FloatList(Setting): 957 """A list of float values.""" 958 959 typename = 'float-list' 960 961 def normalize(self, val): 962 if type(val) not in (list, tuple): 963 raise utils.InvalidType 964 965 # horribly slow test for invalid entries 966 out = [] 967 for i in val: 968 if type(i) not in (float, int): 969 raise utils.InvalidType 970 else: 971 out.append( float(i) ) 972 return out 973 974 def toUIText(self): 975 """Make a string a, b, c.""" 976 # can't use the comma for splitting if used as a decimal point 977 978 join = ', ' 979 if uilocale.decimalPoint() == ',': 980 join = '; ' 981 return join.join( [ui_floattostring(x) for x in self.val] ) 982 983 def fromUIText(self, text): 984 """Convert from a, b, c or a b c.""" 985 986 # don't use commas if it is the decimal separator 987 splitre = r'[\t\n, ]+' 988 if uilocale.decimalPoint() == ',': 989 splitre = r'[\t\n; ]+' 990 991 out = [] 992 for x in re.split(splitre, text.strip()): 993 if x: 994 try: 995 out.append( ui_stringtofloat(x) ) 996 except ValueError: 997 out.append( self.safeEvalHelper(x) ) 998 return out 999 1000 def makeControl(self, *args): 1001 return controls.String(self, *args) 1002 1003class WidgetPath(Str): 1004 """A setting holding a path to a widget. This is checked for validity.""" 1005 1006 typename = 'widget-path' 1007 1008 def __init__(self, name, val, relativetoparent=True, 1009 allowedwidgets = None, 1010 **args): 1011 """Initialise the setting. 1012 1013 The widget is located relative to 1014 parent if relativetoparent is True, otherwise this widget. 1015 1016 If allowedwidgets is not None, only those widgets types in the list are 1017 allowed by this setting. 1018 """ 1019 1020 Str.__init__(self, name, val, **args) 1021 self.relativetoparent = relativetoparent 1022 self.allowedwidgets = allowedwidgets 1023 1024 def copy(self): 1025 """Make a copy of the setting.""" 1026 return self._copyHelper((), (), 1027 {'relativetoparent': self.relativetoparent, 1028 'allowedwidgets': self.allowedwidgets}) 1029 1030 def getReferredWidget(self, val = None): 1031 """Get the widget referred to. We double-check here to make sure 1032 it's the one. 1033 1034 Returns None if setting is blank 1035 utils.InvalidType is raised if there's a problem 1036 """ 1037 1038 # this is a bit of a hack, so we don't have to pass a value 1039 # for the setting (which we need to from normalize) 1040 if val is None: 1041 val = self.val 1042 1043 if val == '': 1044 return None 1045 1046 # find the widget associated with this setting 1047 widget = self 1048 while not widget.iswidget: 1049 widget = widget.parent 1050 1051 # usually makes sense to give paths relative to a parent of a widget 1052 if self.relativetoparent: 1053 widget = widget.parent 1054 1055 # resolve the text to a widget 1056 try: 1057 widget = widget.document.resolveWidgetPath(widget, val) 1058 except ValueError: 1059 raise utils.InvalidType 1060 1061 # check the widget against the list of allowed types if given 1062 if self.allowedwidgets is not None: 1063 allowed = False 1064 for c in self.allowedwidgets: 1065 if isinstance(widget, c): 1066 allowed = True 1067 if not allowed: 1068 raise utils.InvalidType 1069 1070 return widget 1071 1072class Dataset(Str): 1073 """A setting to choose from the possible datasets.""" 1074 1075 typename = 'dataset' 1076 1077 def __init__(self, name, val, dimensions=1, datatype='numeric', 1078 **args): 1079 """ 1080 dimensions is the number of dimensions the dataset needs 1081 """ 1082 1083 self.dimensions = dimensions 1084 self.datatype = datatype 1085 Setting.__init__(self, name, val, **args) 1086 1087 def copy(self): 1088 """Make a setting which has its values copied from this one.""" 1089 return self._copyHelper((), (), 1090 {'dimensions': self.dimensions, 1091 'datatype': self.datatype}) 1092 1093 def makeControl(self, *args): 1094 """Allow user to choose between the datasets.""" 1095 return controls.Dataset(self, self.getDocument(), self.dimensions, 1096 self.datatype, *args) 1097 1098 def getData(self, doc): 1099 """Return a list of datasets entered.""" 1100 d = doc.data.get(self.val) 1101 if ( d is not None and 1102 d.datatype == self.datatype and 1103 (d.dimensions == self.dimensions or self.dimensions == 'all') ): 1104 return d 1105 1106class Strings(Setting): 1107 """A multiple set of strings.""" 1108 1109 typename = 'str-multi' 1110 1111 def normalize(self, val): 1112 """Takes a tuple/list of strings: 1113 ('ds1','ds2'...) 1114 """ 1115 1116 if isinstance(val, cbasestr): 1117 return (val, ) 1118 1119 if type(val) not in (list, tuple): 1120 raise utils.InvalidType 1121 1122 # check each entry in the list is appropriate 1123 for ds in val: 1124 if not isinstance(ds, cbasestr): 1125 raise utils.InvalidType 1126 1127 return tuple(val) 1128 1129 def makeControl(self, *args): 1130 """Allow user to choose between the datasets.""" 1131 return controls.Strings(self, self.getDocument(), *args) 1132 1133class Datasets(Setting): 1134 """A setting to choose one or more of the possible datasets.""" 1135 1136 typename = 'dataset-multi' 1137 1138 def __init__(self, name, val, dimensions=1, datatype='numeric', 1139 **args): 1140 """ 1141 dimensions is the number of dimensions the dataset needs 1142 """ 1143 1144 Setting.__init__(self, name, val, **args) 1145 self.dimensions = dimensions 1146 self.datatype = datatype 1147 1148 def normalize(self, val): 1149 """Takes a tuple/list of strings: 1150 ('ds1','ds2'...) 1151 """ 1152 1153 if isinstance(val, cbasestr): 1154 return (val, ) 1155 1156 if type(val) not in (list, tuple): 1157 raise utils.InvalidType 1158 1159 # check each entry in the list is appropriate 1160 for ds in val: 1161 if not isinstance(ds, cbasestr): 1162 raise utils.InvalidType 1163 1164 return tuple(val) 1165 1166 def copy(self): 1167 """Make a setting which has its values copied from this one.""" 1168 return self._copyHelper((), (), 1169 {'dimensions': self.dimensions, 1170 'datatype': self.datatype}) 1171 1172 def makeControl(self, *args): 1173 """Allow user to choose between the datasets.""" 1174 return controls.Datasets(self, self.getDocument(), self.dimensions, 1175 self.datatype, *args) 1176 1177 def getData(self, doc): 1178 """Return a list of datasets entered.""" 1179 out = [] 1180 for name in self.val: 1181 d = doc.data.get(name) 1182 if ( d is not None and 1183 d.datatype == self.datatype and 1184 d.dimensions == self.dimensions ): 1185 out.append(d) 1186 return out 1187 1188class DatasetExtended(Dataset): 1189 """Choose a dataset, give an expression or specify a list of float 1190 values.""" 1191 1192 typename = 'dataset-extended' 1193 1194 def normalize(self, val): 1195 """Check is a string (dataset name or expression) or a list of 1196 floats (numbers). 1197 """ 1198 1199 if isinstance(val, cbasestr): 1200 return val 1201 elif self.dimensions == 1: 1202 # list of numbers only allowed for 1d datasets 1203 if isinstance(val, float) or isinstance(val, int): 1204 return [val] 1205 else: 1206 try: 1207 return [float(x) for x in val] 1208 except (TypeError, ValueError): 1209 pass 1210 raise utils.InvalidType 1211 1212 def toUIText(self): 1213 if isinstance(self.val, cbasestr): 1214 return self.val 1215 else: 1216 # join based on , or ; depending on decimal point 1217 join = ', ' 1218 if uilocale.decimalPoint() == ',': 1219 join = '; ' 1220 return join.join( [ ui_floattostring(x) 1221 for x in self.val ] ) 1222 1223 def fromUIText(self, text): 1224 """Convert from text.""" 1225 1226 text = text.strip() 1227 1228 if self.dimensions > 1: 1229 return text 1230 1231 # split based on , or ; depending on decimal point 1232 splitre = r'[\t\n, ]+' 1233 if uilocale.decimalPoint() == ',': 1234 splitre = r'[\t\n; ]+' 1235 1236 out = [] 1237 for x in re.split(splitre, text): 1238 if x: 1239 try: 1240 out.append( ui_stringtofloat(x) ) 1241 except ValueError: 1242 # fail conversion, so exit with text 1243 return text 1244 return out 1245 1246 def getFloatArray(self, doc): 1247 """Get a numpy of values or None.""" 1248 if isinstance(self.val, cbasestr): 1249 ds = doc.evaluate.evalDatasetExpression( 1250 self.val, datatype=self.datatype, dimensions=self.dimensions) 1251 if ds: 1252 # get numpy array of values 1253 return N.array(ds.data) 1254 else: 1255 # list of values 1256 return N.array(self.val) 1257 return None 1258 1259 def isDataset(self, doc): 1260 """Is this setting a dataset?""" 1261 return (isinstance(self.val, cbasestr) and 1262 doc.data.get(self.val)) 1263 1264 def isEmpty(self): 1265 """Is this unset?""" 1266 return self.val == [] or self.val == '' 1267 1268 def getData(self, doc): 1269 """Return veusz dataset""" 1270 if isinstance(self.val, cbasestr): 1271 return doc.evaluate.evalDatasetExpression( 1272 self.val, datatype=self.datatype, dimensions=self.dimensions) 1273 else: 1274 return datasets.valsToDataset( 1275 self.val, self.datatype, self.dimensions) 1276 1277class DatasetOrStr(Dataset): 1278 """Choose a dataset or enter a string. 1279 1280 Non string datasets are converted to string arrays using this. 1281 """ 1282 1283 typename = 'dataset-or-str' 1284 1285 def __init__(self, name, val, **args): 1286 Dataset.__init__(self, name, val, datatype='text', **args) 1287 1288 def getData(self, doc, checknull=False): 1289 """Return either a list of strings, a single item list. 1290 If checknull then None is returned if blank 1291 """ 1292 if doc: 1293 ds = doc.data.get(self.val) 1294 if ds and ds.dimensions == 1: 1295 return doc.formatValsWithDatatypeToText( 1296 ds.data, ds.displaytype) 1297 if checknull and not self.val: 1298 return None 1299 else: 1300 return [cstr(self.val)] 1301 1302 def makeControl(self, *args): 1303 return controls.DatasetOrString(self, self.getDocument(), *args) 1304 1305 def copy(self): 1306 """Make a setting which has its values copied from this one.""" 1307 return self._copyHelper((), (), {}) 1308 1309class Color(ChoiceOrMore): 1310 """A color setting.""" 1311 1312 typename = 'color' 1313 1314 def __init__(self, name, value, **args): 1315 """Initialise the color setting with the given name, default 1316 and description.""" 1317 ChoiceOrMore.__init__(self, name, [], value, **args) 1318 1319 def copy(self): 1320 """Make a copy of the setting.""" 1321 return self._copyHelper((), (), {}) 1322 1323 def color(self, painter, dataindex=0): 1324 """Return QColor from color. 1325 1326 painter is a Veusz Painter 1327 dataindex is index for automatically getting colors for subdatasets. 1328 """ 1329 1330 if self.val.lower() == 'auto': 1331 # lookup widget 1332 w = self.parent 1333 while w is not None and not w.iswidget: 1334 w = w.parent 1335 if w is None: 1336 return qt.QColor() 1337 # get automatic color 1338 return painter.docColor(w.autoColor(painter, dataindex=dataindex)) 1339 else: 1340 return painter.docColor(self.val) 1341 1342 def makeControl(self, *args): 1343 return controls.Color(self, *args) 1344 1345class FillStyle(Choice): 1346 """A setting for the different fill styles provided by Qt.""" 1347 1348 typename = 'fill-style' 1349 1350 _fillstyles = [ 1351 'solid', 'horizontal', 'vertical', 'cross', 1352 'forward diagonals', 'backward diagonals', 1353 'diagonal cross', 1354 '94% dense', '88% dense', '63% dense', '50% dense', 1355 '37% dense', '12% dense', '6% dense' 1356 ] 1357 1358 _fillcnvt = { 1359 'solid': qt.Qt.SolidPattern, 1360 'horizontal': qt.Qt.HorPattern, 1361 'vertical': qt.Qt.VerPattern, 1362 'cross': qt.Qt.CrossPattern, 1363 'forward diagonals': qt.Qt.FDiagPattern, 1364 'backward diagonals': qt.Qt.BDiagPattern, 1365 'diagonal cross': qt.Qt.DiagCrossPattern, 1366 '94% dense': qt.Qt.Dense1Pattern, 1367 '88% dense': qt.Qt.Dense2Pattern, 1368 '63% dense': qt.Qt.Dense3Pattern, 1369 '50% dense': qt.Qt.Dense4Pattern, 1370 '37% dense': qt.Qt.Dense5Pattern, 1371 '12% dense': qt.Qt.Dense6Pattern, 1372 '6% dense': qt.Qt.Dense7Pattern, 1373 } 1374 1375 controls.FillStyle._fills = _fillstyles 1376 controls.FillStyle._fillcnvt = _fillcnvt 1377 1378 def __init__(self, name, value, **args): 1379 Choice.__init__(self, name, self._fillstyles, value, **args) 1380 1381 def copy(self): 1382 """Make a copy of the setting.""" 1383 return self._copyHelper((), (), {}) 1384 1385 def qtStyle(self): 1386 """Return Qt ID of fill.""" 1387 return self._fillcnvt[self.val] 1388 1389 def makeControl(self, *args): 1390 return controls.FillStyle(self, *args) 1391 1392class LineStyle(Choice): 1393 """A setting choosing a particular line style.""" 1394 1395 typename = 'line-style' 1396 1397 # list of allowed line styles 1398 _linestyles = [ 1399 'solid', 'dashed', 'dotted', 1400 'dash-dot', 'dash-dot-dot', 'dotted-fine', 1401 'dashed-fine', 'dash-dot-fine', 1402 'dot1', 'dot2', 'dot3', 'dot4', 1403 'dash1', 'dash2', 'dash3', 'dash4', 'dash5', 1404 'dashdot1', 'dashdot2', 'dashdot3' 1405 ] 1406 1407 # convert from line styles to Qt constants and a custom pattern (if any) 1408 _linecnvt = { 1409 'solid': (qt.Qt.SolidLine, None), 1410 'dashed': (qt.Qt.DashLine, None), 1411 'dotted': (qt.Qt.DotLine, None), 1412 'dash-dot': (qt.Qt.DashDotLine, None), 1413 'dash-dot-dot': (qt.Qt.DashDotDotLine, None), 1414 'dotted-fine': (qt.Qt.CustomDashLine, [2, 4]), 1415 'dashed-fine': (qt.Qt.CustomDashLine, [8, 4]), 1416 'dash-dot-fine': (qt.Qt.CustomDashLine, [8, 4, 2, 4]), 1417 'dot1': (qt.Qt.CustomDashLine, [0.1, 2]), 1418 'dot2': (qt.Qt.CustomDashLine, [0.1, 4]), 1419 'dot3': (qt.Qt.CustomDashLine, [0.1, 6]), 1420 'dot4': (qt.Qt.CustomDashLine, [0.1, 8]), 1421 'dash1': (qt.Qt.CustomDashLine, [4, 4]), 1422 'dash2': (qt.Qt.CustomDashLine, [4, 8]), 1423 'dash3': (qt.Qt.CustomDashLine, [8, 8]), 1424 'dash4': (qt.Qt.CustomDashLine, [16, 8]), 1425 'dash5': (qt.Qt.CustomDashLine, [16, 16]), 1426 'dashdot1': (qt.Qt.CustomDashLine, [0.1, 4, 4, 4]), 1427 'dashdot2': (qt.Qt.CustomDashLine, [0.1, 4, 8, 4]), 1428 'dashdot3': (qt.Qt.CustomDashLine, [0.1, 2, 4, 2]), 1429 } 1430 1431 controls.LineStyle._lines = _linestyles 1432 1433 def __init__(self, name, default, **args): 1434 Choice.__init__(self, name, self._linestyles, default, **args) 1435 1436 def copy(self): 1437 """Make a copy of the setting.""" 1438 return self._copyHelper((), (), {}) 1439 1440 def qtStyle(self): 1441 """Get Qt ID of chosen line style.""" 1442 return self._linecnvt[self.val] 1443 1444 def makeControl(self, *args): 1445 return controls.LineStyle(self, *args) 1446 1447class Axis(Str): 1448 """A setting to hold the name of an axis. 1449 1450 direction is 'horizontal', 'vertical' or 'both' 1451 """ 1452 1453 typename = 'axis' 1454 1455 def __init__(self, name, val, direction, **args): 1456 """Initialise using the document, so we can get the axes later. 1457 1458 direction is horizontal or vertical to specify the type of axis to 1459 show 1460 """ 1461 1462 Setting.__init__(self, name, val, **args) 1463 self.direction = direction 1464 1465 def copy(self): 1466 """Make a copy of the setting.""" 1467 return self._copyHelper((), (self.direction,), {}) 1468 1469 def makeControl(self, *args): 1470 """Allows user to choose an axis or enter a name.""" 1471 return controls.Axis(self, self.getDocument(), self.direction, *args) 1472 1473class WidgetChoice(Str): 1474 """Hold the name of a child widget.""" 1475 1476 typename = 'widget-choice' 1477 1478 def __init__(self, name, val, widgettypes={}, **args): 1479 """Choose widgets from (named) type given.""" 1480 Setting.__init__(self, name, val, **args) 1481 self.widgettypes = widgettypes 1482 1483 def copy(self): 1484 """Make a copy of the setting.""" 1485 return self._copyHelper((), (), 1486 {'widgettypes': self.widgettypes}) 1487 1488 def buildWidgetList(self, level, widget, outdict): 1489 """A recursive helper to build up a list of possible widgets. 1490 1491 This iterates over widget's children, and adds widgets as tuples 1492 to outdict using outdict[name] = (widget, level) 1493 1494 Lower level images of the same name outweigh other images further down 1495 the tree 1496 """ 1497 1498 for child in widget.children: 1499 if child.typename in self.widgettypes: 1500 if (child.name not in outdict) or (outdict[child.name][1]>level): 1501 outdict[child.name] = (child, level) 1502 else: 1503 self.buildWidgetList(level+1, child, outdict) 1504 1505 def getWidgetList(self): 1506 """Return a dict of valid widget names and the corresponding objects.""" 1507 1508 # find widget which contains setting 1509 widget = self.parent 1510 while not widget.iswidget and widget is not None: 1511 widget = widget.parent 1512 1513 # get widget's parent 1514 if widget is not None: 1515 widget = widget.parent 1516 1517 # get list of widgets from recursive find 1518 widgets = {} 1519 if widget is not None: 1520 self.buildWidgetList(0, widget, widgets) 1521 1522 # turn (object, level) pairs into object 1523 outdict = {} 1524 for name, val in widgets.items(): 1525 outdict[name] = val[0] 1526 1527 return outdict 1528 1529 def findWidget(self): 1530 """Find the image corresponding to this setting. 1531 1532 Returns Image object if succeeds or None if fails 1533 """ 1534 1535 widgets = self.getWidgetList() 1536 try: 1537 return widgets[self.get()] 1538 except KeyError: 1539 return None 1540 1541 def makeControl(self, *args): 1542 """Allows user to choose an image widget or enter a name.""" 1543 return controls.WidgetChoice(self, self.getDocument(), *args) 1544 1545class Marker(Choice): 1546 """Choose a marker type from one allowable.""" 1547 1548 typename = 'marker' 1549 1550 def __init__(self, name, value, **args): 1551 Choice.__init__(self, name, utils.MarkerCodes, value, **args) 1552 1553 def copy(self): 1554 """Make a copy of the setting.""" 1555 return self._copyHelper((), (), {}) 1556 1557 def makeControl(self, *args): 1558 return controls.Marker(self, *args) 1559 1560class Arrow(Choice): 1561 """Choose an arrow type from one allowable.""" 1562 1563 typename = 'arrow' 1564 1565 def __init__(self, name, value, **args): 1566 Choice.__init__(self, name, utils.ArrowCodes, value, **args) 1567 1568 def copy(self): 1569 """Make a copy of the setting.""" 1570 return self._copyHelper((), (), {}) 1571 1572 def makeControl(self, *args): 1573 return controls.Arrow(self, *args) 1574 1575class LineSet(Setting): 1576 """A setting which corresponds to a set of lines. 1577 """ 1578 1579 typename='line-multi' 1580 1581 def normalize(self, val): 1582 """Takes a tuple/list of tuples: 1583 [('dotted', '1pt', 'color', <trans>, False), ...] 1584 1585 These are style, width, color, and hide or 1586 style, widget, color, transparency, hide 1587 """ 1588 1589 if type(val) not in (list, tuple): 1590 raise utils.InvalidType 1591 1592 # check each entry in the list is appropriate 1593 for line in val: 1594 try: 1595 style, width, color, hide = line 1596 except ValueError: 1597 raise utils.InvalidType 1598 1599 if ( not isinstance(color, cbasestr) or 1600 not Distance.isDist(width) or 1601 style not in LineStyle._linestyles or 1602 type(hide) not in (int, bool) ): 1603 raise utils.InvalidType 1604 1605 return val 1606 1607 def makeControl(self, *args): 1608 """Make specialised lineset control.""" 1609 return controls.LineSet(self, *args) 1610 1611 def makePen(self, painter, row): 1612 """Make a pen for the painter using row. 1613 1614 If row is outside of range, then cycle 1615 """ 1616 1617 if len(self.val) == 0: 1618 return qt.QPen(qt.Qt.NoPen) 1619 else: 1620 row = row % len(self.val) 1621 v = self.val[row] 1622 style, width, color, hide = v 1623 width = Distance.convertDistance(painter, width) 1624 style, dashpattern = LineStyle._linecnvt[style] 1625 col = painter.docColor(color) 1626 pen = qt.QPen(col, width, style) 1627 1628 if dashpattern: 1629 pen.setDashPattern(dashpattern) 1630 1631 if hide: 1632 pen.setStyle(qt.Qt.NoPen) 1633 return pen 1634 1635class FillSet(Setting): 1636 """A setting which corresponds to a set of fills. 1637 1638 This setting keeps an internal array of LineSettings. 1639 """ 1640 1641 typename = 'fill-multi' 1642 1643 def normalize(self, val): 1644 """Takes a tuple/list of tuples: 1645 [('solid', 'color', False), ...] 1646 1647 These are color, fill style, and hide or 1648 color, fill style, and hide 1649 1650 (style, color, hide, 1651 [optional transparency, linewidth, 1652 linestyle, spacing, backcolor, backtrans, backhide]]) 1653 1654 """ 1655 1656 if type(val) not in (list, tuple): 1657 raise utils.InvalidType 1658 1659 # check each entry in the list is appropriate 1660 for fill in val: 1661 try: 1662 style, color, hide = fill[:3] 1663 except ValueError: 1664 raise utils.InvalidType 1665 1666 if ( not isinstance(color, cbasestr) or 1667 style not in utils.extfillstyles or 1668 type(hide) not in (int, bool) or 1669 len(fill) not in (3, 10) ): 1670 raise utils.InvalidType 1671 1672 return val 1673 1674 def makeControl(self, *args): 1675 """Make specialised lineset control.""" 1676 return controls.FillSet(self, *args) 1677 1678 def returnBrushExtended(self, row): 1679 """Return BrushExtended for the row.""" 1680 from . import collections 1681 s = collections.BrushExtended('tempbrush') 1682 s.parent = self 1683 1684 if len(self.val) == 0: 1685 s.hide = True 1686 else: 1687 v = self.val[row % len(self.val)] 1688 s.style = v[0] 1689 s.color = v[1] 1690 s.hide = v[2] 1691 if len(v) == 10: 1692 (s.transparency, s.linewidth, s.linestyle, 1693 s.patternspacing, s.backcolor, 1694 s.backtransparency, s.backhide) = v[3:] 1695 return s 1696 1697class Filename(Str): 1698 """Represents a filename setting.""" 1699 1700 typename = 'filename' 1701 1702 def makeControl(self, *args): 1703 return controls.Filename(self, 'file', *args) 1704 1705 def normalize(self, val): 1706 if sys.platform == 'win32': 1707 val = val.replace('\\', '/') 1708 return val 1709 1710class ImageFilename(Filename): 1711 """Represents an image filename setting.""" 1712 1713 typename = 'filename-image' 1714 1715 def makeControl(self, *args): 1716 return controls.Filename(self, 'image', *args) 1717 1718class FontFamily(Str): 1719 """Represents a font family.""" 1720 1721 typename = 'font-family' 1722 1723 def makeControl(self, *args): 1724 """Make a special font combobox.""" 1725 return controls.FontFamily(self, *args) 1726 1727class ErrorStyle(Choice): 1728 """Error bar style. 1729 The allowed values are below in _errorstyles. 1730 """ 1731 1732 typename = 'errorbar-style' 1733 1734 _errorstyles = ( 1735 'none', 1736 'bar', 'barends', 'box', 'diamond', 'curve', 1737 'barbox', 'bardiamond', 'barcurve', 1738 'boxfill', 'diamondfill', 'curvefill', 1739 'fillvert', 'fillhorz', 1740 'linevert', 'linehorz', 1741 'linevertbar', 'linehorzbar', 1742 'barhi', 'barlo', 1743 'barendshi', 'barendslo', 1744 'linehorzlo', 'linehorzhi', 'linevertlo', 'lineverthi', 1745 ) 1746 1747 controls.ErrorStyle._errorstyles = _errorstyles 1748 1749 def __init__(self, name, value, **args): 1750 Choice.__init__(self, name, self._errorstyles, value, **args) 1751 1752 def copy(self): 1753 """Make a copy of the setting.""" 1754 return self._copyHelper((), (), {}) 1755 1756 def makeControl(self, *args): 1757 return controls.ErrorStyle(self, *args) 1758 1759class AlignHorz(Choice): 1760 """Alignment horizontally.""" 1761 1762 typename = 'align-horz' 1763 1764 def __init__(self, name, value, **args): 1765 Choice.__init__(self, name, ['left', 'centre', 'right'], value, **args) 1766 def copy(self): 1767 """Make a copy of the setting.""" 1768 return self._copyHelper((), (), {}) 1769 1770class AlignVert(Choice): 1771 """Alignment vertically.""" 1772 1773 typename = 'align-vert' 1774 1775 def __init__(self, name, value, **args): 1776 Choice.__init__(self, name, ['top', 'centre', 'bottom'], value, **args) 1777 def copy(self): 1778 """Make a copy of the setting.""" 1779 return self._copyHelper((), (), {}) 1780 1781class AlignHorzWManual(Choice): 1782 """Alignment horizontally.""" 1783 1784 typename = 'align-horz-+manual' 1785 1786 def __init__(self, name, value, **args): 1787 Choice.__init__(self, name, ['left', 'centre', 'right', 'manual'], 1788 value, **args) 1789 def copy(self): 1790 """Make a copy of the setting.""" 1791 return self._copyHelper((), (), {}) 1792 1793class AlignVertWManual(Choice): 1794 """Alignment vertically.""" 1795 1796 typename = 'align-vert-+manual' 1797 1798 def __init__(self, name, value, **args): 1799 Choice.__init__(self, name, ['top', 'centre', 'bottom', 'manual'], 1800 value, **args) 1801 def copy(self): 1802 """Make a copy of the setting.""" 1803 return self._copyHelper((), (), {}) 1804 1805# Bool which shows/hides other settings 1806class BoolSwitch(Bool): 1807 """Bool switching setting.""" 1808 1809 def __init__(self, name, value, settingsfalse=[], settingstrue=[], 1810 **args): 1811 """Enables/disables a set of settings if True or False 1812 settingsfalse and settingstrue are lists of names of settings 1813 which are hidden/shown to user 1814 """ 1815 1816 self.sfalse = settingsfalse 1817 self.strue = settingstrue 1818 Bool.__init__(self, name, value, **args) 1819 1820 def makeControl(self, *args): 1821 return controls.BoolSwitch(self, *args) 1822 1823 def copy(self): 1824 return self._copyHelper((), (), {'settingsfalse': self.sfalse, 1825 'settingstrue': self.strue}) 1826 1827class ChoiceSwitch(Choice): 1828 """Show or hide other settings based on the choice given here.""" 1829 1830 def __init__(self, name, vallist, value, 1831 showfn=lambda x: ((),()), 1832 **args): 1833 """Enables/disables a set of settings depending on showfn(val) 1834 1835 showfn(val) returns (show, hide), where show and hide are 1836 lists of settings to show or hide 1837 """ 1838 1839 self.showfn = showfn 1840 Choice.__init__(self, name, vallist, value, **args) 1841 1842 def makeControl(self, *args): 1843 return controls.ChoiceSwitch(self, False, self.vallist, *args) 1844 1845 def copy(self): 1846 return self._copyHelper( 1847 (self.vallist,), (), 1848 {'showfn': self.showfn}) 1849 1850class FillStyleExtended(ChoiceSwitch): 1851 """A setting for the different fill styles provided by Qt.""" 1852 1853 typename = 'fill-style-ext' 1854 1855 @staticmethod 1856 def _showsetns(val): 1857 """Get list of settings to show or hide""" 1858 hatchsetns = ( 1859 'linewidth', 'linestyle', 'patternspacing', 1860 'backcolor', 'backtransparency', 'backhide') 1861 1862 if val == 'solid' or val.find('dense') >= 0: 1863 return ((), hatchsetns) 1864 else: 1865 return (hatchsetns, ()) 1866 1867 def __init__(self, name, value, **args): 1868 ChoiceSwitch.__init__( 1869 self, name, utils.extfillstyles, value, 1870 showfn=self._showsetns, 1871 **args) 1872 1873 def copy(self): 1874 """Make a copy of the setting.""" 1875 return self._copyHelper((), (), {}) 1876 1877 def makeControl(self, *args): 1878 return controls.FillStyleExtended(self, *args) 1879 1880class RotateInterval(Choice): 1881 '''Rotate a label with intervals given.''' 1882 1883 def __init__(self, name, val, **args): 1884 Choice.__init__(self, name, 1885 ('-180', '-135', '-90', '-45', 1886 '0', '45', '90', '135', '180'), 1887 val, **args) 1888 1889 def normalize(self, val): 1890 """Store rotate angle.""" 1891 # backward compatibility with rotate option 1892 # False: angle 0 1893 # True: angle 90 1894 if val == False: 1895 val = '0' 1896 elif val == True: 1897 val = '90' 1898 return Choice.normalize(self, val) 1899 1900 def copy(self): 1901 """Make a copy of the setting.""" 1902 return self._copyHelper((), (), {}) 1903 1904class Colormap(Str): 1905 """A setting to set the color map used in an image. 1906 This is based on a Str rather than Choice as the list might 1907 change later. 1908 """ 1909 1910 def makeControl(self, *args): 1911 return controls.Colormap(self, self.getDocument(), *args) 1912 1913class AxisBound(FloatOrAuto): 1914 """Axis bound - either numeric, Auto or date.""" 1915 1916 typename = 'axis-bound' 1917 1918 def makeControl(self, *args): 1919 return controls.AxisBound(self, *args) 1920 1921 def toUIText(self): 1922 """Convert to text, taking into account mode of Axis. 1923 Displays datetimes in date format if used 1924 """ 1925 1926 try: 1927 mode = self.parent.mode 1928 except AttributeError: 1929 mode = None 1930 1931 v = self.val 1932 if ( not isinstance(v, cbasestr) and v is not None and 1933 mode == 'datetime' ): 1934 return utils.dateFloatToString(v) 1935 1936 return FloatOrAuto.toUIText(self) 1937 1938 def fromUIText(self, txt): 1939 """Convert from text, allowing datetimes.""" 1940 1941 v = utils.dateStringToDate(txt) 1942 if N.isfinite(v): 1943 return v 1944 else: 1945 return FloatOrAuto.fromUIText(self, txt) 1946