class ValidatedDialog(QDialog): """ A dialog for creating a validated new value. Performs validation of name against a provided. Can be used to select from the list or for creating a new value that is not on the list. """ INVALID_COLOR = QColor(255, 235, 235) def __init__( self, title="Title", description="Description", unique_names=None, choose_from_list=False, ): QDialog.__init__(self) self.setModal(True) self.setWindowTitle(title) # self.setMinimumWidth(250) # self.setMinimumHeight(150) if unique_names is None: unique_names = [] self.unique_names = unique_names self.choose_from_list = choose_from_list self.layout = QFormLayout() self.layout.setSizeConstraint(QLayout.SetFixedSize) label = QLabel(description) label.setAlignment(Qt.AlignHCenter) self.layout.addRow(self.createSpace(5)) self.layout.addRow(label) self.layout.addRow(self.createSpace(10)) buttons = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.ok_button = buttons.button(QDialogButtonBox.Ok) self.ok_button.setEnabled(False) if choose_from_list: self.param_name_combo = QComboBox() self.param_name.currentIndexChanged.connect(self.validateChoice) for item in unique_names: self.param_name_combo.addItem(item) self.layout.addRow("Job:", self.param_name_combo) else: self.param_name = QLineEdit(self) self.param_name.setFocus() self.param_name.textChanged.connect(self.validateName) self.validColor = self.param_name.palette().color( self.param_name.backgroundRole()) self.layout.addRow("Name:", self.param_name) self.layout.addRow(self.createSpace(10)) self.layout.addRow(buttons) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) self.setLayout(self.layout) def notValid(self, msg): """Called when the name is not valid.""" self.ok_button.setEnabled(False) palette = self.param_name.palette() palette.setColor(self.param_name.backgroundRole(), self.INVALID_COLOR) self.param_name.setToolTip(msg) self.param_name.setPalette(palette) def valid(self): """Called when the name is valid.""" self.ok_button.setEnabled(True) palette = self.param_name.palette() palette.setColor(self.param_name.backgroundRole(), self.validColor) self.param_name.setToolTip("") self.param_name.setPalette(palette) def validateName(self, value): """Called to perform validation of a name. For specific needs override this function and call valid() and notValid(msg).""" value = str(value) if value == "": self.notValid("Can not be empty!") elif not value.find(" ") == -1: self.notValid("No spaces allowed!") elif value in self.unique_names: self.notValid("Name must be unique!") else: self.valid() def validateChoice(self, choice): """Only called when using selection mode.""" self.ok_button.setEnabled(not choice == "") def getName(self): """Return the new name chosen by the user""" if self.choose_from_list: return str(self.param_name_combo.currentText()) else: return str(self.param_name.text()) def showAndTell(self): """Shows the dialog and returns the result""" if self.exec_(): return str(self.getName()).strip() return "" def createSpace(self, size=5): """Creates a widget that can be used as spacing on a panel.""" qw = QWidget() qw.setMinimumSize(QSize(size, size)) return qw
class PathChooser(QWidget): """ PathChooser: shows, enables choosing of and validates paths. The data structure expected and sent to the models getValue and setValue is a string. """ PATH_DOES_NOT_EXIST_MSG = "The specified path does not exist." FILE_IS_NOT_EXECUTABLE_MSG = "The specified file is not an executable." PATH_IS_NOT_A_FILE_MSG = "The specified path must be a file." PATH_IS_NOT_ABSOLUTE_MSG = "The specified path must be an absolute path." PATH_IS_NOT_A_DIRECTORY_MSG = "The specified path must be a directory." REQUIRED_FIELD_MSG = "A path is required." # UNDEFINED = 0 # REQUIRED = 1 # FILE = 2 # DIRECTORY = 4 # MUST_EXIST = 8 # EXECUTABLE = 16 def __init__(self, model, help_link=""): """ :type model: ert_gui.ertwidgets.models.path_model.PathModel :param help_link: str """ QWidget.__init__(self) addHelpToWidget(self, help_link) self._validation_support = ValidationSupport(self) self._editing = True layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self._path_line = QLineEdit() self._path_line.setMinimumWidth(250) layout.addWidget(self._path_line) dialog_button = QToolButton(self) dialog_button.setIcon(resourceIcon("ide/small/folder")) dialog_button.setIconSize(QSize(16, 16)) dialog_button.clicked.connect(self.selectPath) layout.addWidget(dialog_button) self.valid_color = self._path_line.palette().color( self._path_line.backgroundRole()) self._path_line.setText(os.getcwd()) self._editing = False self._model = model self._model.valueChanged.connect(self.getPathFromModel) self._path_line.editingFinished.connect(self.validatePath) self._path_line.editingFinished.connect(self.contentsChanged) self._path_line.textChanged.connect(self.validatePath) self.setLayout(layout) self.getPathFromModel() def isPathValid(self, path): """@rtype: tuple of (bool, str)""" path = path.strip() path_exists = os.path.exists(path) is_file = os.path.isfile(path) is_directory = os.path.isdir(path) is_executable = os.access(path, os.X_OK) is_absolute = os.path.isabs(path) valid = True message = "" if path == "": if self._model.pathIsRequired(): valid = False message = PathChooser.REQUIRED_FIELD_MSG elif not path_exists: if self._model.pathMustExist(): valid = False message = PathChooser.PATH_DOES_NOT_EXIST_MSG # todo: check if new (non-existing) file has directory or file format? elif path_exists: if self._model.pathMustBeExecutable( ) and is_file and not is_executable: valid = False message = PathChooser.FILE_IS_NOT_EXECUTABLE_MSG elif self._model.pathMustBeADirectory() and not is_directory: valid = False message = PathChooser.PATH_IS_NOT_A_DIRECTORY_MSG elif self._model.pathMustBeAbsolute() and not is_absolute: valid = False message = PathChooser.PATH_IS_NOT_ABSOLUTE_MSG elif self._model.pathMustBeAFile() and not is_file: valid = False message = PathChooser.PATH_IS_NOT_A_FILE_MSG return valid, message def validatePath(self): """Called whenever the path is modified""" palette = self._path_line.palette() valid, message = self.isPathValid(self.getPath()) validity_type = ValidationSupport.WARNING if not valid: color = ValidationSupport.ERROR_COLOR else: color = self.valid_color self._validation_support.setValidationMessage(message, validity_type) self._path_line.setToolTip(message) palette.setColor(self._path_line.backgroundRole(), color) self._path_line.setPalette(palette) def getPath(self): """Returns the path""" return os.path.expanduser(str(self._path_line.text()).strip()) def pathExists(self): """Returns True if the entered path exists""" return os.path.exists(self.getPath()) def selectPath(self): """Pops up the 'select a file/directory' dialog""" # todo: This probably needs some reworking to work properly with different scenarios... (file + dir) self._editing = True current_directory = self.getPath() # if not os.path.exists(currentDirectory): # currentDirectory = "~" if self._model.pathMustBeAFile(): current_directory = QFileDialog.getOpenFileName( self, "Select a file path", current_directory) else: current_directory = QFileDialog.getExistingDirectory( self, "Select a directory", current_directory) if not current_directory == "": if not self._model.pathMustBeAbsolute(): cwd = os.getcwd() match = re.match(cwd + "/(.*)", current_directory) if match: current_directory = match.group(1) self._path_line.setText(current_directory) self._model.setPath(self.getPath()) self._editing = False def contentsChanged(self): """Called whenever the path is changed.""" path_is_valid, message = self.isPathValid(self.getPath()) if not self._editing and path_is_valid: self._model.setPath(self.getPath()) def getPathFromModel(self): """Retrieves data from the model and inserts it into the edit line""" self._editing = True path = self._model.getPath() if path is None: path = "" self._path_line.setText("%s" % path) self._editing = False def getValidationSupport(self): return self._validation_support def isValid(self): return self._validation_support.isValid()
class ReactionBox(QGraphicsItem): """Handle to the line edits on the map""" def __init__(self, parent: MapView, r_id: str, name): QGraphicsItem.__init__(self) self.map = parent self.id = r_id self.name = name self.item = QLineEdit() self.item.setMaximumWidth(80) r = self.map.appdata.project.cobra_py_model.reactions.get_by_id(r_id) text = "Id: " + r.id + "\nName: " + r.name \ + "\nEquation: " + r.build_reaction_string()\ + "\nLowerbound: " + str(r.lower_bound) \ + "\nUpper bound: " + str(r.upper_bound) \ + "\nObjective coefficient: " + str(r.objective_coefficient) self.item.setToolTip(text) self.proxy = self.map.scene.addWidget(self.item) self.proxy.show() palette = self.item.palette() palette.setColor(QPalette.Base, self.map.appdata.default_color) role = self.item.foregroundRole() palette.setColor(role, Qt.black) self.item.setPalette(palette) self.setCursor(Qt.OpenHandCursor) self.setAcceptedMouseButtons(Qt.LeftButton) self.item.textEdited.connect(self.value_changed) self.item.returnPressed.connect(self.returnPressed) self.item.setContextMenuPolicy(Qt.CustomContextMenu) self.item.customContextMenuRequested.connect(self.on_context_menu) # create context menu self.pop_menu = QMenu(parent) maximize_action = QAction('maximize flux for this reaction', parent) self.pop_menu.addAction(maximize_action) maximize_action.triggered.connect(self.emit_maximize_action) minimize_action = QAction('minimize flux for this reaction', parent) self.pop_menu.addAction(minimize_action) minimize_action.triggered.connect(self.emit_minimize_action) switch_action = QAction('switch to reaction mask', parent) self.pop_menu.addAction(switch_action) switch_action.triggered.connect(self.switch_to_reaction_mask) remove_action = QAction('remove from map', parent) self.pop_menu.addAction(remove_action) remove_action.triggered.connect(self.remove) self.pop_menu.addSeparator() def returnPressed(self): if validate_value(self.item.text()): self.map.value_changed(self.id, self.item.text()) # TODO: actually I want to repaint # self.map.update() def value_changed(self): test = self.item.text().replace(" ", "") if test == "": self.map.value_changed(self.id, test) self.set_color(self.map.appdata.default_color) elif validate_value(self.item.text()): self.map.value_changed(self.id, self.item.text()) if self.id in self.map.appdata.project.scen_values.keys(): self.set_color(self.map.appdata.scen_color) else: self.set_color(self.map.appdata.comp_color) else: self.set_color(Qt.magenta) # TODO: actually I want to repaint # self.map.update() def set_val_and_color(self, value: Tuple[float, float]): self.set_value(value) self.recolor() def set_value(self, value: Tuple[float, float]): (vl, vu) = value if isclose(vl, vu, abs_tol=self.map.appdata.abs_tol): self.item.setText(str(round(vl, self.map.appdata.rounding))) else: self.item.setText( str((round(vl, self.map.appdata.rounding), round(vu, self.map.appdata.rounding)))) self.item.setCursorPosition(0) def recolor(self): value = self.item.text() test = value.replace(" ", "") if test == "": self.set_color(self.map.appdata.default_color) elif validate_value(value): if self.id in self.map.appdata.project.scen_values.keys(): value = self.map.appdata.project.scen_values[self.id] self.set_color(self.map.appdata.scen_color) else: value = self.map.appdata.project.comp_values[self.id] (vl, vu) = value if math.isclose(vl, vu, abs_tol=self.map.appdata.abs_tol): if self.map.appdata.modes_coloring: if vl == 0: self.set_color(Qt.red) else: self.set_color(Qt.green) else: self.set_color(self.map.appdata.comp_color) else: if math.isclose(vl, 0.0, abs_tol=self.map.appdata.abs_tol): self.set_color(self.map.appdata.special_color_1) elif math.isclose(vu, 0.0, abs_tol=self.map.appdata.abs_tol): self.set_color(self.map.appdata.special_color_1) elif vl <= 0 and vu >= 0: self.set_color(self.map.appdata.special_color_1) else: self.set_color(self.map.appdata.special_color_2) else: self.set_color(Qt.magenta) def set_color(self, color: QColor): palette = self.item.palette() palette.setColor(QPalette.Base, color) role = self.item.foregroundRole() palette.setColor(role, Qt.black) self.item.setPalette(palette) def boundingRect(self): return QRectF(-15, -15, 20, 20) def paint(self, painter: QPainter, _option, _widget: QWidget): # set color depending on wether the value belongs to the scenario if self.id in self.map.appdata.project.scen_values.keys(): painter.setPen(Qt.magenta) painter.setBrush(Qt.magenta) else: painter.setPen(Qt.darkGray) painter.drawRect(-15, -15, 20, 20) painter.setPen(Qt.darkGray) painter.drawLine(-5, 0, -5, -10) painter.drawLine(0, -5, -10, -5) def mousePressEvent(self, _event: QGraphicsSceneMouseEvent): pass def mouseReleaseEvent(self, _event: QGraphicsSceneMouseEvent): pass def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): drag = QDrag(event.widget()) mime = QMimeData() mime.setText(str(self.id)) drag.setMimeData(mime) # self.setCursor(Qt.ClosedHandCursor) drag.exec_() # self.setCursor(Qt.OpenHandCursor) def setPos(self, x, y): self.proxy.setPos(x, y) super().setPos(x, y) def on_context_menu(self, point): # show context menu self.pop_menu.exec_(self.item.mapToGlobal(point)) def remove(self): self.map.remove_box(self.id) self.map.drag = False def switch_to_reaction_mask(self): self.map.switchToReactionMask.emit(self.id) self.map.drag = False def emit_maximize_action(self): self.map.maximizeReaction.emit(self.id) self.map.drag = False def emit_minimize_action(self): self.map.minimizeReaction.emit(self.id) self.map.drag = False