1# -*- coding: utf-8 -*- 2 3#------------------------------------------------------------------------------- 4 5# This file is part of Code_Saturne, a general-purpose CFD tool. 6# 7# Copyright (C) 1998-2021 EDF S.A. 8# 9# This program is free software; you can redistribute it and/or modify it under 10# the terms of the GNU General Public License as published by the Free Software 11# Foundation; either version 2 of the License, or (at your option) any later 12# version. 13# 14# This program is distributed in the hope that it will be useful, but WITHOUT 15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 16# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 17# details. 18# 19# You should have received a copy of the GNU General Public License along with 20# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin 21# Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 23#------------------------------------------------------------------------------- 24 25""" 26This module defines basic classes used for the Pages construction. 27 28This module defines the following classes: 29- ComboModel 30- IntValidator 31- DoubleValidator 32- RegExpValidator 33""" 34 35#------------------------------------------------------------------------------- 36# Library modules import 37#------------------------------------------------------------------------------- 38 39import sys 40import os 41import logging 42import locale 43 44Py2 = sys.version[0] == '2' 45Py3 = sys.version[0] == '3' 46 47 48#------------------------------------------------------------------------------- 49# Third-party modules 50#------------------------------------------------------------------------------- 51 52from code_saturne.Base.QtCore import * 53from code_saturne.Base.QtGui import * 54from code_saturne.Base.QtWidgets import * 55 56#------------------------------------------------------------------------------- 57# Application modules import 58#------------------------------------------------------------------------------- 59 60from code_saturne.model.Common import LABEL_LENGTH_MAX, GuiParam 61 62#------------------------------------------------------------------------------- 63# log config 64#------------------------------------------------------------------------------- 65 66logging.basicConfig() 67log = logging.getLogger("QtPage") 68log.setLevel(GuiParam.DEBUG) 69 70#------------------------------------------------------------------------------- 71# Compatibility PyQt API #1 and #2 and PySide 72#------------------------------------------------------------------------------- 73 74#============================================================================== 75# Data types 76#============================================================================== 77if Py2: 78 TEXT_TYPES = (str, unicode) 79else: 80 TEXT_TYPES = (str,) 81 82#============================================================================== 83# Classes used to handle ComboBox subsections (groups) 84#============================================================================== 85 86# Qt Role used to differentiate whether a ComboBox entry is a group child 87# or not 88GroupRole = Qt.UserRole 89 90 91class GroupDelegate(QStyledItemDelegate): 92 """ 93 Class used to create a group delegate which applies a tab to a combobox 94 entry if it is a group child. Only applied to the visual style! 95 """ 96 def initStyleOption(self, option, index): 97 super(GroupDelegate, self).initStyleOption(option, index) 98 if not index.data(GroupRole): 99 option.text = " " + option.text 100 101class GroupItem(): 102 """ 103 GroupItem class. 104 Handles the subsections in a ComboModel. 105 """ 106 107 def __init__(self, name, index): 108 """ 109 Init method. Inputs are: 110 name : String with the name of the group 111 index : Position of the group name in the combobox list of elements 112 """ 113 self._name = name 114 self._index = index 115 116 self._children = [] 117 self._number_of_children = 0 118 119 120 def addChild(self, name): 121 """ 122 Method to add a child with the given name. 123 Returns the index of the child to be used if the 'insertRow' method. 124 """ 125 self._children.append(name) 126 self._number_of_children += 1 127 128 child_index = self._index + self._number_of_children 129 130 return child_index 131 132 def getIndex(self): 133 """ 134 Method which returns the index of the group 135 """ 136 return self._index 137 138 def getChildIndex(self, childName): 139 """ 140 Method to retrieve the index of a given child 141 """ 142 if childName not in self._children: 143 msg = "%s is not part of group %s\n" % (childName, self._name) 144 raise Exception(msg) 145 146 child_index = self._children.index(childName) + self._index + 1 147 return child_index 148 149 150 def getNumberOfChildren(self): 151 """ 152 Method which returns the number of children in the group 153 """ 154 return self._number_of_children 155 156 157 def incrementIndex(self): 158 159 self._index += 1 160 161 def generateItem(self): 162 163 item = QStandardItem(str(self._name)) 164 item.setData(True, GroupRole) 165 166 font = item.font() 167 font.setBold(True) 168 font.setItalic(True) 169 item.setFont(font) 170 item.setFlags(item.flags() & ~Qt.ItemIsSelectable) 171 172 return item 173#============================================================================== 174# Strings 175#============================================================================== 176 177def is_text_string(obj): 178 """Return True if `obj` is a text string, 179 False if it is anything else, 180 like binary data (Python 3) or 181 QString (Python 2, PyQt API #1)""" 182 if Py2: 183 return isinstance(obj, basestring) 184 else: 185 return isinstance(obj, str) 186 187def to_text_string(obj, encoding=None): 188 """Convert `obj` to (unicode) text string""" 189 if Py2: 190 if encoding is None: 191 return unicode(obj) 192 else: 193 return unicode(obj, encoding) 194 else: 195 if encoding is None: 196 return str(obj) 197 elif isinstance(obj, str): 198 # In case this function is not used properly, this could happen 199 return obj 200 else: 201 return str(obj, encoding) 202 203#============================================================================== 204# QVariant conversion utilities 205# 206# Note: to_qvariant was removed recently; from_qvariant may be removed in 207# many_places and the "to_text_string" variants could be replaced 208# by "to_str"; where the value is already known to be of the correct 209# type, or None handled in the associated code, 210# this could even be ignored 211#============================================================================== 212 213import collections 214 215if os.environ.get('QT_API', 'pyqt') == 'pyqt': 216 217 def from_qvariant(qobj=None, convfunc=None): 218 """Convert QVariant object to Python object 219 This is a transitional function from PyQt API #1 (QVariant exists) 220 to PyQt API #2 and Pyside (QVariant does not exist)""" 221 if (qobj != None): 222 if convfunc in TEXT_TYPES or convfunc is to_text_string: 223 return str(qobj) 224 elif convfunc is int: 225 return int(qobj) 226 elif convfunc is float: 227 try: 228 return float(qobj) 229 except Exception: 230 return locale.atof(qobj) 231 else: 232 return qobj 233 else: 234 return qobj 235 236else: 237 238 def from_qvariant(qobj=None, pytype=None): 239 """Convert QVariant object to Python object 240 This is a transitional function from PyQt API #1 (QVariant exist) 241 to PyQt API #2 and Pyside (QVariant does not exist)""" 242 return qobj 243 244def qbytearray_to_str(qba): 245 """Convert QByteArray object to str in a way compatible with Python 2/3""" 246 return str(bytes(qba.toHex().data()).decode()) 247 248 249def to_str(s): 250 """Convert argument to string, using an empty string for None""" 251 if s is None: 252 return '' 253 return str(s) 254 255#============================================================================== 256# Wrappers around QFileDialog static methods 257#============================================================================== 258 259def getexistingdirectory(parent=None, caption='', basedir='', 260 options=QFileDialog.ShowDirsOnly): 261 """Wrapper around QtGui.QFileDialog.getExistingDirectory static method 262 Compatible with PyQt >=v4.4 (API #1 and #2) and PySide >=v1.0""" 263 # Calling QFileDialog static method 264 if sys.platform == "win32": 265 # On Windows platforms: redirect standard outputs 266 _temp1, _temp2 = sys.stdout, sys.stderr 267 sys.stdout, sys.stderr = None, None 268 try: 269 result = QFileDialog.getExistingDirectory(parent, caption, basedir, 270 options) 271 finally: 272 if sys.platform == "win32": 273 # On Windows platforms: restore standard outputs 274 sys.stdout, sys.stderr = _temp1, _temp2 275 if not is_text_string(result): 276 # PyQt API #1 277 result = to_text_string(result) 278 return result 279 280 281def _qfiledialog_wrapper(attr, parent=None, caption='', basedir='', 282 filters='', selectedfilter='', options=None): 283 if options is None: 284 options = QFileDialog.Options(0) 285 286 try: 287 from code_saturne.Base.QtCore import QString 288 except ImportError: 289 QString = None # analysis:ignore 290 291 tuple_returned = True 292 try: 293 func = getattr(QFileDialog, attr+'AndFilter') 294 except AttributeError: 295 func = getattr(QFileDialog, attr) 296 if QString is not None: 297 selectedfilter = QString() 298 tuple_returned = False 299 300 if sys.platform == "win32": 301 # On Windows platforms: redirect standard outputs 302 _temp1, _temp2 = sys.stdout, sys.stderr 303 sys.stdout, sys.stderr = None, None 304 try: 305 result = func(parent, caption, basedir, 306 filters, selectedfilter, options) 307 except TypeError: 308 result = func(parent, caption, basedir, filters, options) 309 finally: 310 if sys.platform == "win32": 311 # On Windows platforms: restore standard outputs 312 sys.stdout, sys.stderr = _temp1, _temp2 313 314 # Processing output 315 if tuple_returned: 316 output, selectedfilter = result 317 else: 318 output = result 319 if QString is not None: 320 # PyQt API #1: conversions needed from QString/QStringList 321 selectedfilter = to_text_string(selectedfilter) 322 if isinstance(output, QString): 323 # Single filename 324 output = to_text_string(output) 325 else: 326 # List of filenames 327 output = [to_text_string(fname) for fname in output] 328 329 # Always returns the tuple (output, selectedfilter) 330 return output, selectedfilter 331 332 333def getopenfilename(parent=None, 334 caption='', 335 basedir='', 336 filters='', 337 selectedfilter='', 338 options=None): 339 return _qfiledialog_wrapper('getOpenFileName', 340 parent=parent, 341 caption=caption, 342 basedir=basedir, 343 filters=filters, 344 selectedfilter=selectedfilter, 345 options=options) 346 347 348def getopenfilenames(parent=None, 349 caption='', 350 basedir='', 351 filters='', 352 selectedfilter='', 353 options=None): 354 return _qfiledialog_wrapper('getOpenFileNames', 355 parent=parent, 356 caption=caption, 357 basedir=basedir, 358 filters=filters, 359 selectedfilter=selectedfilter, 360 options=options) 361 362 363def getsavefilename(parent=None, 364 caption='', 365 basedir='', 366 filters='', 367 selectedfilter='', 368 options=None): 369 return _qfiledialog_wrapper('getSaveFileName', 370 parent=parent, 371 caption=caption, 372 basedir=basedir, 373 filters=filters, 374 selectedfilter=selectedfilter, 375 options=options) 376 377 378#------------------------------------------------------------------------------- 379# QComboBox model 380#------------------------------------------------------------------------------- 381 382class ComboModel: 383 """ 384 Class to build a model (QStandardItemModel) used with a QComboBox. 385 386 Main attributes of class are: 387 388 combo: QComboBox passed as arguments. It uses the model 389 model: QStandardItemModel which contains items 390 391 dicoV2M: correspondance between strings in Qt view and strings in parameters 392 dicoM2V: correspondance between strings in parameters and strings in Qt view 393 394 items: tuple which contains all model strings (usefull to get its index in the model) 395 """ 396 def __init__(self, combo, rows=0, columns=0): 397 """ 398 Initialization 399 """ 400 self.combo = combo 401 402 self.rows = rows 403 self.columns = columns 404 self.last = 0 405 406 self.model = QStandardItemModel() 407 self.model.clear() 408 self.model.setRowCount(rows) 409 self.model.setColumnCount(columns) 410 411 self.dicoV2M = {} 412 self.dicoM2V = {} 413 414 self.item_groups = {} 415 416 self.items = [] 417 self.combo.setModel(self.model) 418 self.combo.setItemDelegate(GroupDelegate(self.combo)) 419 420 421 def addItemGroup(self, group_name): 422 423 if group_name in self.item_groups.keys(): 424 return 425 426 index = self.last 427 self.item_groups[group_name] = GroupItem(group_name, index) 428 429 item = self.item_groups[group_name].generateItem() 430 self.model.setItem(index, item) 431 432 self.items.append(group_name) 433 434 self.last += 1 435 436 def addItemList(self, list_of_views_and_models, warn=False, groupName=None): 437 """ 438 Insert of a list of items in the model. 439 list_of_views_and_models is a list of 2-element list 440 e.g. [ ["View1", "Model1"], ["View2", "Model2"] ] 441 """ 442 for view, model in list_of_views_and_models: 443 self.addItem(view, model, warn, groupName) 444 445 def hasItem(self, index=None, str_view="", str_model=""): 446 if index is not None: 447 return index < self.last 448 449 elif str_model: 450 return (str_model in self.items) 451 452 elif str_view: 453 return (str_view in self.dicoV2M.keys()) 454 455 def addItem(self, str_view, str_model="", warn=False, groupName=None): 456 """ 457 Insert an item in the model. 458 459 str_view: string to be displayed in the view. 460 For example, 'Eulerian/Lagrangian Multi-phase Treatment' 461 462 str_model: correponding string used in the model. 463 For example, 'lagrangian' 464 465 warn: If True, entry is marked with a color. 466 """ 467 if self.hasItem(str_view=str_view): 468 return 469 470 item = QStandardItem(str(str_view)) 471 item.setData(True, GroupRole) 472 473 if groupName in self.item_groups.keys(): 474 # Insert the child after the group name in the ComboModel 475 item.setData(False, GroupRole) 476 index = self.item_groups[groupName].addChild(str(str_view)) 477 self.model.setItem(index, item) 478 479 # In case groups were created before, update the index of 480 # the following groups in the list 481 for key in self.item_groups.keys(): 482 gpe = self.item_groups[key] 483 if gpe.getIndex() >= index: 484 gpe.incrementIndex() 485 486 idx = gpe.getIndex() 487 it = gpe.generateItem() 488 self.model.setItem(idx, it) 489 490 else: 491 index = self.last 492 self.model.setItem(index, item) 493 494 if warn: 495 self.combo.setItemData(index, 496 QColor(Qt.red), 497 Qt.TextColorRole) 498 499 self.last += 1 500 501 if not str_model: str_model = str_view 502 503 self.items.append(str_model) 504 505 self.dicoM2V[str_model] = str_view 506 self.dicoV2M[str_view] = str_model 507 508 509 def modifyItem(self, old_str_view, new_str_view, new_str_model=""): 510 """ 511 Modify string names. 512 """ 513 if old_str_view in self.items: 514 index = self.items.index(old_str_view) 515 self.items[index] = new_str_view 516 517 old_str_model = dicoV2M[old_str_view] 518 if new_str_model == "": 519 new_str_model = old_str_model 520 521 del self.dicoM2V[str_model] 522 del self.dicoV2M[str_view] 523 524 self.dicoM2V[new_str_model] = new_str_view 525 self.dicoV2M[new_str_view] = new_str_model 526 527 def flushItems(self): 528 """ 529 Remove all items 530 """ 531 while self.last > 0: 532 self.delItem(0) 533 534 def delItem(self, index=None, str_model="", str_view=""): 535 """ 536 Remove the item specified with its index or a string. 537 """ 538 if index is not None: 539 self.__deleteItem(index) 540 541 elif str_model: 542 index = self.items.index(str_model) 543 self.__deleteItem(index) 544 545 elif str_view: 546 str_model = self.dicoV2M[str_view] 547 index = self.items.index(str_model) 548 self.__deleteItem(index) 549 self.combo.removeItem(index) 550 self.last = self.last - 1 551 552 553 def __deleteItem(self, index): 554 """ 555 Delete the item specified with its index 556 """ 557 str_model = self.items[index] 558 str_view = self.dicoM2V[str_model] 559 del self.items[index] 560 del self.dicoV2M[str_view] 561 del self.dicoM2V[str_model] 562 563 564 def __disableItem(self, index): 565 """ 566 Disable the item specified with its index 567 """ 568 self.model.item(index).setEnabled(False) 569 570 571 def __enableItem(self, index): 572 """ 573 Enable the item specified with its index 574 """ 575 self.model.item(index).setEnabled(True) 576 577 578 def disableItem(self, index=None, str_model="", str_view=""): 579 """ 580 Disable the item specified with its index or a string. 581 """ 582 if index is not None: 583 self.__disableItem(index) 584 585 elif str_model: 586 index = self.items.index(str_model) 587 self.__disableItem(index) 588 589 elif str_view: 590 str_model = self.dicoV2M[str_view] 591 index = self.items.index(str_model) 592 self.__disableItem(index) 593 594 595 def enableItem(self, index=None, str_model="", str_view=""): 596 """ 597 Enable the item specified with its index or a string. 598 """ 599 if index is not None: 600 self.__enableItem(index) 601 602 elif str_model: 603 index = self.items.index(str_model) 604 self.__enableItem(index) 605 606 elif str_view: 607 str_model = self.dicoV2M[str_view] 608 index = self.items.index(str_model) 609 self.__enableItem(index) 610 611 612 def setItem(self, index=None, str_model="", str_view=""): 613 """ 614 Set item as current. 615 """ 616 if index is not None: 617 self.combo.setCurrentIndex(index) 618 619 elif str_model: 620 try: 621 index = self.items.index(str_model) 622 self.combo.setCurrentIndex(index) 623 except Exception: 624 self._displayWarning(str_model) 625 # Throw signals to ensure XML is updated (not very elegant) 626 self.combo.activated[str].emit(self.dicoM2V[self.items[0]]) 627 self.combo.currentTextChanged[str].emit(self.dicoM2V[self.items[0]]) 628 self.combo.currentIndexChanged[int].emit(0) 629 index = 0 630 631 elif str_view: 632 try: 633 str_model = self.dicoV2M[str_view] 634 index = self.items.index(str_model) 635 self.combo.setCurrentIndex(index) 636 except Exception: 637 self._displayWarning(str_model) 638 index = 0 639 640 def _displayWarning(self, str_model): 641 if self.combo.accessibleName() != "": 642 name = self.combo.accessibleName() 643 else: 644 name = self.combo.objectName() 645 title = "Warning in " + name 646 msg = str_model + " is not in list: " + str(self.items) + "\n" 647 msg += "Value reset to " + str(self.items[0]) + " but should be checked.\n" 648 QMessageBox.warning(self.combo, title, msg) 649 650 def enableItem(self, index=None, str_model="", str_view=""): 651 """ 652 Enable the item specified with its index or a string. 653 """ 654 if index is not None: 655 self.__enableItem(index) 656 657 elif str_model: 658 index = self.items.index(str_model) 659 self.__enableItem(index) 660 661 elif str_view: 662 str_model = self.dicoV2M[str_view] 663 index = self.items.index(str_model) 664 self.__enableItem(index) 665 666 667 def hide(self): 668 """ 669 Hide combobox. 670 """ 671 self.combo.hide() 672 673 674 def show(self): 675 """ 676 Show combobox. 677 """ 678 self.combo.show() 679 680 681 def getItems(self): 682 """ 683 Get the tuple of items. 684 """ 685 return self.items 686 687#------------------------------------------------------------------------------- 688# Validators for editors 689#------------------------------------------------------------------------------- 690 691vmax = 2147483647 692vmin = -vmax 693 694class IntValidator(QIntValidator): 695 """ 696 Validator for integer data. 697 """ 698 def __init__(self, parent, min=vmin, max=vmax): 699 """ 700 Initialization for validator 701 """ 702 QIntValidator.__init__(self, parent) 703 self.parent = parent 704 self.state = QValidator.Invalid 705 self.__min = min 706 self.__max = max 707 708 if type(min) != int or type(max) != int: 709 raise ValueError("The given parameters are not integers (warning: long are not allowed).") 710 self.setBottom(min) 711 self.setTop(max) 712 713 self.exclusiveMin = False 714 self.exclusiveMax = False 715 self.exclusiveValues = [] 716 717 self.default = 0 718 self.fix = False 719 720 msg = "" 721 if min > vmin and max == vmax: 722 msg = self.tr("The integer value must be greater than or equal to %i" % min) 723 elif min == vmin and max < vmax: 724 msg = self.tr("The integer value must be lower than or equal to %i" % max) 725 elif min > vmin and max < vmax: 726 msg = self.tr("The integer value must be between %i and %i" % (min, max)) 727 728 self.parent.setStatusTip(msg) 729 730 731 def setExclusiveMin(self, b=True): 732 if type(b) != bool: 733 raise ValueError("The given parameter is not a boolean.") 734 self.exclusiveMin = b 735 736 msg = "" 737 if self.__min > vmin and self.__max == vmax: 738 msg = self.tr("The integer value must be greater than %i" % self.__min) 739 elif self.__min == vmin and self.__max < vmax: 740 msg = self.tr("The integer value must be lower than or equal to %i" % self.__max) 741 elif self.__min > vmin and self.__max < vmax: 742 msg = self.tr("The integer value must be greater %i and lower than or equal to %i" % (self.__min, self.__max)) 743 744 self.parent.setStatusTip(msg) 745 746 747 def setExclusiveMax(self, b=True): 748 if type(b) != bool: 749 raise ValueError("The given parameter is not a boolean.") 750 self.exclusiveMax = b 751 752 msg = "" 753 if self.__min > vmin and self.__max == vmax: 754 msg = self.tr("The integer value must be greater than or equal to %i" % self.__min) 755 elif self.__min == vmin and self.__max < vmax: 756 msg = self.tr("The integer value must be lower than %i" % self.__max) 757 elif self.__min > vmin and self.__max < vmax: 758 msg = self.tr("The integer value must be greater than or equal to %i and lower than %i" % (self.__min, self.__max)) 759 760 self.parent.setStatusTip(msg) 761 762 763 def setExclusiveValues(self, l): 764 if type(l) != list and type(l) != tuple: 765 raise ValueError("The given parameter is not a list or a tuple.") 766 self.exclusiveValues = l 767 768 msg = "" 769 for v in l: 770 if self.__min > vmin or self.__max < vmax: 771 msg = self.tr("All integers value must be greater than %i and lower than %i" % (self.__min, self.__max)) 772 773 self.parent.setStatusTip(msg) 774 775 776 def setFixup(self, v): 777 if type(v) != int: 778 raise ValueError("The given parameter is not an integer.") 779 self.default = v 780 self.fix = True 781 782 783 def fixup(self, stri): 784 if self.fix: 785 if not stri: 786 stri = str(self.default) 787 788 789 def validate(self, stri, pos): 790 """ 791 Validation method. 792 793 QValidator.Invalid 0 The string is clearly invalid. 794 QValidator.Intermediate 1 The string is a plausible intermediate value during editing. 795 QValidator.Acceptable 2 The string is acceptable as a final result; i.e. it is valid. 796 """ 797 state = QIntValidator.validate(self, stri, pos)[0] 798 799 try: 800 x = from_qvariant(stri, int) 801 valid = True 802 pass 803 except (TypeError, ValueError): 804 x = 0 805 valid = False 806 807 if state == QValidator.Acceptable: 808 if self.exclusiveMin and x == self.bottom(): 809 state = QValidator.Intermediate 810 elif self.exclusiveMax and x == self.top(): 811 state = QValidator.Intermediate 812 elif x in self.exclusiveValues: 813 state = QValidator.Intermediate 814 815 palette = self.parent.palette() 816 817 if not valid or state == QValidator.Intermediate: 818 palette.setColor(QPalette.Text, QColor("red")) 819 self.parent.setPalette(palette) 820 else: 821 palette.setColor(QPalette.Text, QColor("black")) 822 self.parent.setPalette(palette) 823 824 self.state = state 825 826 return (state, stri, pos) 827 828 829 def tr(self, text): 830 """ 831 """ 832 return text 833 834 835class DoubleValidator(QDoubleValidator): 836 """ 837 Validator for real data. 838 """ 839 def __init__(self, parent, min=-1.e99, max=1.e99): 840 """ 841 Initialization for validator 842 """ 843 QDoubleValidator.__init__(self, parent) 844 self.setLocale(QLocale(QLocale.C, QLocale.AnyCountry)) 845 self.parent = parent 846 self.state = QValidator.Invalid 847 self.__min = min 848 self.__max = max 849 850 self.setNotation(self.ScientificNotation) 851 852 if type(min) != float or type(max) != float: 853 raise ValueError("The given parameters are not floats.") 854 self.setBottom(min) 855 self.setTop(max) 856 857 self.exclusiveMin = False 858 self.exclusiveMax = False 859 860 self.default = 0.0 861 self.fix = False 862 863 msg = "" 864 if min > -1.e99 and max == 1.e99: 865 msg = self.tr("The float value must be greater than %.1f" % min) 866 elif min == -1.e99 and max < 1.e99: 867 msg = self.tr("The float value must be lower than %.1f" % max) 868 elif min > -1.e99 and max < 1.e99: 869 msg = self.tr("The float value must be between than %.1f and %.1f" % (min, max)) 870 871 self.parent.setStatusTip(str(msg)) 872 873 874 def setExclusiveMin(self, b=True): 875 if type(b) != bool: 876 raise ValueError("The given parameter is not a boolean.") 877 self.exclusiveMin = b 878 879 msg = "" 880 if self.__min > -1.e99 and self.__max == 1.e99: 881 msg = self.tr("The float value must be greater than %.1f" % self.__min) 882 elif self.__min == -1.e99 and self.__max < 1.e99: 883 msg = self.tr("The float value must be lower than or equal to %.1f" % self.__max) 884 elif self.__min > -1.e99 and self.__max < 1.e99: 885 msg = self.tr("The float value must be greater than %.1f and lower than or equal to %.1f" % (self.__min, self.__max)) 886 887 self.parent.setStatusTip(str(msg)) 888 889 890 def setExclusiveMax(self, b=True): 891 if type(b) != bool: 892 raise ValueError("The given parameter is not a boolean.") 893 self.exclusiveMax = b 894 895 msg = "" 896 if self.__min > -1.e99 and self.__max == 1.e99: 897 msg = self.tr("The float value must be greater than or equal to %.1f" % self.__min) 898 elif self.__min == -1.e99 and self.__max < 1.e99: 899 msg = self.tr("The float value must be lower than %.1f" % self.__max) 900 elif self.__min > -1.e99 and self.__max < 1.e99: 901 msg = self.tr("The float value must be greater than or equal to %.1f and lower than %.1f" % (self.__min, self.__max)) 902 903 self.parent.setStatusTip(str(msg)) 904 905 906 def setFixup(self, v): 907 if type(v) != float: 908 raise ValueError("The given parameter is not a float.") 909 self.default = v 910 self.fix = True 911 912 913 def fixup(self, stri): 914 if self.fix: 915 if not stri: 916 stri = str(self.default) 917 918 919 def validate(self, stri, pos): 920 """ 921 Validation method. 922 923 QValidator.Invalid 0 The string is clearly invalid. 924 QValidator.Intermediate 1 The string is a plausible intermediate value during editing. 925 QValidator.Acceptable 2 The string is acceptable as a final result; i.e. it is valid. 926 """ 927 state = QDoubleValidator.validate(self, stri, pos)[0] 928 929 if state == QValidator.Acceptable: 930 try: 931 x = from_qvariant(stri, float) 932 except Exception: # may be type error or localization issue 933 x = 0.0 934 state = QValidator.Intermediate 935 936 if state == QValidator.Acceptable: 937 if self.exclusiveMin and x == self.bottom(): 938 state = QValidator.Intermediate 939 elif self.exclusiveMax and x == self.top(): 940 state = QValidator.Intermediate 941 942 palette = self.parent.palette() 943 944 if state != QValidator.Acceptable: 945 palette.setColor(QPalette.Text, QColor("red")) 946 self.parent.setPalette(palette) 947 else: 948 palette.setColor(QPalette.Text, QColor("black")) 949 self.parent.setPalette(palette) 950 951 self.state = state 952 953 return (state, stri, pos) 954 955 956 def tr(self, text): 957 """ 958 """ 959 return text 960 961 962class RegExpValidator(QRegExpValidator): 963 """ 964 Validator for regular expression. 965 """ 966 def __init__(self, parent, rx, forbidden_labels=None): 967 """ 968 Initialization for validator 969 """ 970 QRegExpValidator.__init__(self, parent) 971 self.parent = parent 972 self.state = QRegExpValidator.Invalid 973 self.forbidden = forbidden_labels 974 975 self.__validator = QRegExpValidator(rx, parent) 976 977 if "{1," + str(LABEL_LENGTH_MAX) + "}" in rx.pattern(): 978 msg = self.tr("The maximum length of the label is %i characters" % LABEL_LENGTH_MAX) 979 self.parent.setStatusTip(str(msg)) 980 981 982 def validate(self, stri, pos): 983 """ 984 Validation method. 985 986 QValidator.Invalid 0 The string is clearly invalid. 987 QValidator.Intermediate 1 The string is a plausible intermediate value during editing. 988 QValidator.Acceptable 2 The string is acceptable as a final result; i.e. it is valid. 989 """ 990 state = self.__validator.validate(stri, pos)[0] 991 992 if self.forbidden: 993 if stri in self.forbidden: 994 state = QValidator.Intermediate 995 996 palette = self.parent.palette() 997 998 if state == QValidator.Intermediate: 999 palette.setColor(QPalette.Text, QColor("red")) 1000 self.parent.setPalette(palette) 1001 else: 1002 palette.setColor(QPalette.Text, QColor("black")) 1003 self.parent.setPalette(palette) 1004 1005 self.state = state 1006 1007 return (state, stri, pos) 1008 1009 1010 def tr(self, text): 1011 """ 1012 """ 1013 return text 1014 1015#------------------------------------------------------------------------------- 1016# SpinBox progressing by multiplication and division 1017#------------------------------------------------------------------------------- 1018 1019class RankSpinBoxWidget(QSpinBox): 1020 """ 1021 Special Spin box for rank stepping. 1022 """ 1023 def __init__(self, parent): 1024 """ 1025 Constructor 1026 """ 1027 QSpinBox.__init__(self, parent) 1028 1029 def stepBy(self, steps): 1030 v = self.value() 1031 if steps > 0: 1032 self.setValue(v*2) 1033 elif steps < 0 and v > 1: 1034 self.setValue(v/2) 1035 1036 def stepEnabled(self): 1037 v = self.value() 1038 if v < 2: 1039 return QAbstractSpinBox.StepUpEnabled 1040 else: 1041 return QAbstractSpinBox.StepUpEnabled | QAbstractSpinBox.StepDownEnabled 1042 1043#------------------------------------------------------------------------------- 1044# SpinBox progressing by multiplication and division for Buffer size 1045#------------------------------------------------------------------------------- 1046 1047class BufferSpinBoxWidget(QSpinBox): 1048 """ 1049 Special Spin box for buffer size. 1050 """ 1051 def __init__(self, parent): 1052 """ 1053 Constructor 1054 """ 1055 QSpinBox.__init__(self, parent) 1056 self.basesize = 1024*1024 1057 1058 def stepBy(self, steps): 1059 v = self.value() 1060 if steps > 0: 1061 if v > 0: 1062 self.setValue(v*2) 1063 else: 1064 self.setValue(self.basesize) 1065 elif steps < 0 and v > 0: 1066 self.setValue(v/2) 1067 1068 def textFromValue(self, v): 1069 """ 1070 Define text to be shown. 1071 This text uses a local suffix (not that of the QSpinBox), 1072 as the suffix and value shown are dynamically related. 1073 """ 1074 tv = v 1075 suffix = '' 1076 if v >= 1073741824 and v % 1073741824 == 0: 1077 tv = v / 1073741824 1078 suffix = ' GiB' 1079 elif v >= 1048576 and v % 1048576 == 0: 1080 tv = v / 1048576 1081 suffix = ' MiB' 1082 elif v >= 1024 and v % 1024 == 0: 1083 tv = v / 1024 1084 suffix = ' KiB' 1085 elif v > 0: 1086 tv = v 1087 suffix = ' B' 1088 else: 1089 tv = 0 1090 suffix = '' 1091 return QSpinBox.textFromValue(self, tv) + suffix 1092 1093 def stepEnabled(self): 1094 v = self.value() 1095 if v < 1: 1096 return QAbstractSpinBox.StepUpEnabled 1097 else: 1098 return QAbstractSpinBox.StepUpEnabled | QAbstractSpinBox.StepDownEnabled 1099 1100#------------------------------------------------------------------------------- 1101# Delegates to use 1102#------------------------------------------------------------------------------- 1103 1104class LabelDelegate(QItemDelegate): 1105 """ 1106 Delegate for lines 1107 """ 1108 1109 def __init__(self, parent=None, xml_model=None, 1110 forbidden_labels=None, 1111 accepted_regex=None, 1112 auto_completion=[]): 1113 super(LabelDelegate, self).__init__(parent) 1114 1115 self.parent = parent 1116 self.mdl = xml_model 1117 1118 self._forbidden_labels = forbidden_labels 1119 1120 # Regex 1121 rx = accepted_regex 1122 if rx == None: 1123 rx = "[ -~]*" 1124# "[\-_A-Za-z0-9]{1," + str(LABEL_LENGTH_MAX) + "}" 1125 self.regExp = QRegExp(rx) 1126 1127 # Auto completion 1128 self._comp_list = auto_completion 1129 1130 def createEditor(self, parent, option, index): 1131 1132 editor = QLineEdit(parent) 1133 1134 v = RegExpValidator(editor, self.regExp, self._forbidden_labels) 1135 editor.setValidator(v) 1136 1137 # Autocompletion if necessary: 1138 if len(self._comp_list) > 0: 1139 completer = QCompleter() 1140 editor.setCompleter(completer) 1141 mc = QStringListModel() 1142 completer.setModel(mc) 1143 mc.setStringList(self._comp_list) 1144 1145 # return editor 1146 return editor 1147 1148 def setEditorData(self, editor, index): 1149 1150 editor.setAutoFillBackground(True) 1151 1152 v = from_qvariant(index.model().data(index, Qt.DisplayRole), 1153 to_text_string) 1154 self.p_value = str(v) 1155 1156 editor.setText(v) 1157 1158 def setModelData(self, editor, model, index): 1159 1160 if not editor.isModified(): 1161 return 1162 1163 if editor.validator().state == QValidator.Acceptable: 1164 p_value = str(editor.text()) 1165 model.setData(index, p_value, Qt.DisplayRole) 1166 1167class FloatDelegate(QItemDelegate): 1168 1169 def __init__(self, parent=None, xml_model=None, 1170 minVal=-1.e99, maxVal=+1.e99): 1171 1172 super(FloatDelegate, self).__init__(parent) 1173 1174 self.parent = parent 1175 self.mdl = xml_model 1176 1177 self._min = minVal 1178 if type(self._min) != float: 1179 self._min = -1.e99 1180 1181 self._max = maxVal 1182 if type(self._max) != float: 1183 self._max = +1.e99 1184 1185 def createEditor(self, parent, option, index): 1186 1187 editor = QLineEdit(parent) 1188 1189 validator = DoubleValidator(editor, 1190 min=self._min, 1191 max=self._max) 1192 1193 editor.setValidator(validator) 1194 1195 return editor 1196 1197 def setEditorData(self, editor, index): 1198 1199 editor.setAutoFillBackground(True) 1200 1201 value = from_qvariant(index.model().data(index, Qt.DisplayRole), 1202 to_text_string) 1203 editor.setText(value) 1204 1205 def setModelData(self, editor, model, index): 1206 1207 if editor.validator().state == QValidator.Acceptable: 1208 value = from_qvariant(editor.text(), float) 1209 selectionModel = self.parent.selectionModel() 1210 1211 for idx in selectionModel.selectedIndexes(): 1212 if idx.column() == index.column(): 1213 model.setData(idx, value, Qt.DisplayRole) 1214 1215class IntegerDelegate(QItemDelegate): 1216 1217 def __init__(self, parent=None, xml_model=None, 1218 minVal=-10000000000, maxVal=+10000000000): 1219 1220 super(IntegerDelegate, self).__init__(parent) 1221 1222 self.parent = parent 1223 self.mdl = xml_model 1224 1225 self._min = minVal 1226 if type(self._min) != int: 1227 self._min = -10000000000 1228 self._max = maxVal 1229 if type(self._max) != int: 1230 self._max = +10000000000 1231 1232 1233 def createEditor(self, parent, option, index): 1234 1235 editor = QLineEdit(parent) 1236 1237 validator = IntValidator(editor, 1238 min=self._min, 1239 max=self._max) 1240 1241 editor.setValidator(validator) 1242 1243 return editor 1244 1245 def setEditorData(self, editor, index): 1246 1247 editor.setAutoFillBackground(True) 1248 1249 value = from_qvariant(index.model().data(index, Qt.DisplayRole), 1250 to_text_string) 1251 editor.setText(value) 1252 1253 def setModelData(self, editor, model, index): 1254 1255 value = from_qvariant(editor.text(), int) 1256 1257 if editor.validator().state == QValidator.Acceptable: 1258 selectionModel = self.parent.selectionModel() 1259 for idx in selectionModel.selectedIndexes(): 1260 if idx.column() == index.column(): 1261 model.setData(idx, value, Qt.DisplayRole) 1262 1263 1264class ComboDelegate(QItemDelegate): 1265 1266 def __init__(self, parent=None, xml_model=None, 1267 opts_list=[], opts_state=None): 1268 1269 super(ComboDelegate, self).__init__(parent) 1270 1271 self.parent = parent 1272 self.mdl = xml_model 1273 1274 if opts_state: 1275 if type(opts_state) != list: 1276 raise Exception("Wrong type for opts_state") 1277 if len(opts_state) != len(opts_list): 1278 raise Exception("Wrong length of opts_state") 1279 else: 1280 # To simplify the code which follows, we ensure that 1281 # opts_state is a list with the correct length 1282 opts_state = ["on"]*len(opts_list) 1283 1284 self.opts_list = [] 1285 for i, opt in enumerate(opts_list): 1286 1287 if opts_state[i] not in ["on", "na", "off"]: 1288 msg="Wrong state for opts %s : %s" % (opt,opts_state[i]) 1289 raise Exception(msg) 1290 1291 ac = True 1292 av = True 1293 if opts_state[i] == "na": 1294 av = False 1295 elif opts_state[i] == "off": 1296 ac = False 1297 av = False 1298 1299 self.opts_list.append({'name':opt, 1300 'active':ac, 1301 'available':av}) 1302 1303 1304 def createEditor(self, parent, option, index): 1305 1306 editor = QComboBox(parent) 1307 for opt in self.opts_list: 1308 name = opt['name'] 1309 isActive = opt['active'] 1310 isAvail = opt['available'] 1311 1312 editor.addItem(name) 1313 idx = editor.findText(name) 1314 editor.model().item(idx).setEnabled(isActive) 1315 if not isAvail: 1316 editor.setItemData(idx, QColor(Qt.red), Qt.TextColorRole) 1317 1318 editor.installEventFilter(self) 1319 1320 return editor 1321 1322 1323 def setEditorData(self, comboBox, index): 1324 1325 string = from_qvariant(index.model().data(index, Qt.DisplayRole), 1326 to_text_string) 1327 comboBox.setEditText(string) 1328 1329 def setModelData(self, comboBox, model, index): 1330 1331 value = comboBox.currentText() 1332 1333 selectionModel = self.parent.selectionModel() 1334 1335 for idx in selectionModel.selectedIndexes(): 1336 if idx.column() == index.column(): 1337 model.setData(idx, value, Qt.DisplayRole) 1338 1339 1340class BasicTableModel(QAbstractTableModel): 1341 1342 def __init__(self, parent=QModelIndex(), xml_model=None, data=[[]], headers=[], default_row=[]): 1343 super(BasicTableModel, self).__init__(parent) 1344 self.parent = parent 1345 self.xml_model = xml_model 1346 self.data_table = data 1347 self.headers = headers 1348 self.default_row = default_row 1349 1350 def rowCount(self, parent=QModelIndex()): 1351 return len(self.data_table) 1352 1353 def columnCount(self, parent=QModelIndex()): 1354 return len(self.headers) 1355 1356 def data(self, index, role=Qt.DisplayRole): 1357 if not index.isValid(): 1358 return QVariant() 1359 elif role != Qt.DisplayRole: 1360 return QVariant() 1361 return self.data_table[index.row()][index.column()] 1362 1363 def headerData(self, section, orientation, role=Qt.DisplayRole): 1364 if role == Qt.DisplayRole and orientation == Qt.Horizontal: 1365 return self.headers[section] 1366 return None 1367 1368 def flags(self, index): 1369 if not index.isValid(): 1370 return Qt.NoItemFlags 1371 else: 1372 return Qt.ItemIsEnabled | Qt.ItemIsSelectable 1373 1374 def setData(self, index, value, role=Qt.EditRole): 1375 if (role == Qt.EditRole) and (index.isValid()): 1376 self.data_table[index.row()][index.column()] = value 1377 self.dataChanged.emit(index, index) 1378 return True 1379 return QAbstractTableModel.setData(index, value, role) 1380 1381 def insertRows(self, position, rows=1, index=QModelIndex()): 1382 self.beginInsertRows(QModelIndex(), position, position + rows - 1) 1383 for row in range(rows): 1384 self.data_table.insert(position + row, self.default_row) 1385 self.endInsertRows() 1386 return True 1387 1388 def removeRows(self, position, rows=1, index=QModelIndex()): 1389 self.beginRemoveRows(QModelIndex(), position, position + rows - 1) 1390 del self.data_table[position:position + rows] 1391 self.endRemoveRows() 1392 return True 1393 1394# ------------------------------------------------------------------------------- 1395# End of QtPage 1396# ------------------------------------------------------------------------------- 1397