def __init__(self, process, command_string, parent=None): config_key = "layout/window/process" super(ProcessDialog, self).__init__(parent) StoreSizeMixin.__init__(self, app.config, config_key) self.setWindowTitle("Running: %s" % command_string) self.proc = process self.ended = False self.output_ended = False self.capture_output = True self.buffer = [] self.bar = QtGui.QProgressBar() self.bar.setRange(0, 0) self.edit = StreamableTextEdit() close_btn = QtGui.QPushButton("Close") btn_pane = create_pane([None, close_btn], True) create_pane([self.bar, self.edit, btn_pane], False, parent_widget=self) self.lock = Lock() self.stdout_thread = Thread(target=self._read_output, args=(self.proc.stdout, )) self.stderr_thread = Thread(target=self._read_output, args=(self.proc.stderr, )) self.timer = QtCore.QTimer() self.timer.setInterval(100) self.timer.timeout.connect(self._update) self.timer.start() self.stdout_thread.start() self.stderr_thread.start() close_btn.clicked.connect(self.close)
class Writer(QtCore.QObject): graph_written = QtCore.Signal(str, str) def __init__(self, graph_str, filepath, prune_to=None): super(Writer, self).__init__() self.graph_str = graph_str self.filepath = filepath self.prune_to = prune_to self.process = None def cancel(self): if self.process: self.process.terminate() def write_graph(self): filepath = "" error_msg = "" try: self.process = Process(target=_save_graph, args=(self.graph_str, self.filepath, self.prune_to)) self.process.start() self.process.join() if self.process.exitcode == 0: filepath = self.filepath except Exception as e: error_msg = str(e) self.graph_written.emit(filepath, error_msg)
class Writer(QtCore.QObject): graph_written = QtCore.Signal(str, str) def __init__(self, graph_str, filepath, prune_to=None): super(Writer, self).__init__() self.graph_str = graph_str self.filepath = filepath self.prune_to = prune_to self.process = None def cancel(self): if self.process: self.process.terminate() def write_graph(self): if self.prune_to: graph_str = prune_graph(self.graph_str, self.prune_to) else: graph_str = self.graph_str error_msg = '' try: save_graph(graph_str, self.filepath) except Exception as e: error_msg = str(e) self.graph_written.emit(self.filepath, error_msg)
def _start_resolve(self): max_fails = self._get_max_fails() if max_fails is None: return self.setWindowTitle("Resolving...") self.resolve_btn.hide() self.cancel_btn.show() self._set_progress(None) self.started = True verbosity = 0 show_package_loads = True timestamp = None if self.advanced: verbosity = app.config.get("resolve/verbosity") show_package_loads = app.config.get("resolve/show_package_loads") timestamp = self.timestamp_widget.datetime() if timestamp is not None: timestamp = timestamp.toTime_t() self.resolver = ResolveThread(self.context_model, verbosity=verbosity, max_fails=max_fails, timestamp=timestamp, show_package_loads=show_package_loads, buf=self.edit) self.resolver.finished.connect(self._resolve_finished) self.thread = QtCore.QThread() self.resolver.moveToThread(self.thread) self.thread.started.connect(self.resolver.run) self.thread.start()
class BrowsePackageWidget(QtGui.QWidget, ContextViewMixin): """A widget for browsing rez packages. """ packageSelected = QtCore.Signal() def __init__(self, context_model=None, parent=None, lock_package=False, package_selectable_callback=None): super(BrowsePackageWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) self.edit = PackageLineEdit(context_model, family_only=True) if lock_package: self.edit.hide() self.versions_table = PackageVersionsTable(context_model, callback=package_selectable_callback) self.package_tab = PackageTabWidget(versions_tab=False) splitter = ConfiguredSplitter(app.config, "layout/splitter/browse_package") splitter.setOrientation(QtCore.Qt.Vertical) splitter.addWidget(self.versions_table) splitter.addWidget(self.package_tab) if not splitter.apply_saved_layout(): splitter.setStretchFactor(0, 2) splitter.setStretchFactor(1, 1) layout = QtGui.QVBoxLayout() layout.addWidget(self.edit) layout.addWidget(splitter) self.setLayout(layout) self.edit.focusOutViaKeyPress.connect(self._set_package_name) self.versions_table.itemSelectionChanged.connect(self._set_package) def set_package_text(self, txt): try: req = Requirement(str(txt)) package_name = req.name version_range = req.range except: package_name = str(txt) version_range = None self.edit.setText(package_name) self._set_package_name(package_name) if version_range is not None: self.versions_table.select_version(version_range) def current_package(self): return self.versions_table.current_package() def _set_package_name(self, package_name): self.versions_table.set_package_name(package_name) self.versions_table.setFocus() def _set_package(self): package = self.versions_table.current_package() self.package_tab.set_package(package) self.packageSelected.emit()
def __init__(self, pivot_widget, width=240, height=160, parent=None): super(TimeSelecterPopup, self).__init__(parent) self.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Raised) self.setWindowFlags(QtCore.Qt.Popup) self.seconds = None self.label = QtGui.QLabel("") canvas_frame = QtGui.QFrame() canvas_frame.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Sunken) canvas = Canvas(width, height) layout = QtGui.QVBoxLayout() layout.setSpacing(2) layout.setContentsMargins(2, 2, 2, 2) layout.addWidget(canvas) canvas_frame.setLayout(layout) create_pane([self.label, canvas_frame], False, compact=True, parent_widget=self) self.adjustSize() pt = pivot_widget.rect().topLeft() global_pt = pivot_widget.mapToGlobal(pt) self.move(global_pt - QtCore.QPoint(0, self.height())) canvas.secondsHover.connect(self._secondsHover) canvas.secondsClicked.connect(self._secondsClicked)
def __init__(self, context_model=None, parent=None): super(ContextResolveTimeLabel, self).__init__(parent) ContextViewMixin.__init__(self, context_model) self.timer = QtCore.QTimer(self) self.timer.setInterval(60 * 1000) self.timer.timeout.connect(self.refresh) self.refresh()
class LoadPackagesThread(QtCore.QObject): """Load packages in a separate thread. Packages are loaded in decreasing version order. """ progress = QtCore.Signal(int, int) finished = QtCore.Signal(object) def __init__(self, package_paths, package_name, range_=None, package_attributes=None, callback=None): super(LoadPackagesThread, self).__init__() self.stopped = False self.package_paths = package_paths self.package_name = package_name self.range_ = range_ self.package_attributes = package_attributes self.callback = callback def stop(self): self.stopped = True def run(self): it = iter_packages(name=self.package_name, paths=self.package_paths, range_=self.range_) packages = sorted(it, key=lambda x: x.version, reverse=True) num_packages = len(packages) self.progress.emit(0, num_packages) for i, package in enumerate(packages): if self.stopped: return if self.callback and not self.callback(package): break for attr in self.package_attributes: getattr(package, attr) # cause load and/or data validation self.progress.emit(i + 1, num_packages) if not self.stopped: self.finished.emit(packages)
def __init__(self, parent=None): super(ProcessTrackerThread, self).__init__(parent) self.processes = {} self.proc_list = [] self.pending_procs = [] self.lock = Lock() self.timer = QtCore.QTimer() self.timer.timeout.connect(self._update)
class ResolveThread(QtCore.QObject): finished = QtCore.Signal() def __init__(self, context_model, verbosity=0, max_fails=-1, timestamp=None, show_package_loads=True, buf=None): super(ResolveThread, self).__init__() self.context_model = context_model self.context = None self.verbosity = verbosity self.max_fails = max_fails self.timestamp = timestamp self.show_package_loads = show_package_loads self.buf = buf self.context = None self.stopped = False self.abort_reason = None self.error_message = None def run(self): package_load_callback = (self._package_load_callback if self.show_package_loads else None) try: self.context = self.context_model.resolve_context( verbosity=self.verbosity, max_fails=self.max_fails, timestamp=self.timestamp, buf=self.buf, callback=self._callback, package_load_callback=package_load_callback) except RezError as e: self.error_message = str(e) if not self.stopped: self.finished.emit() def stop(self): self.stopped = True self.abort_reason = "Cancelled by user." def success(self): return bool(self.context and self.context.success) def _callback(self, solver_state): if self.buf and self.verbosity == 0: print >> self.buf, "solve step %d..." % solver_state.num_solves return (not self.stopped), self.abort_reason def _package_load_callback(self, package): if self.buf: print >> self.buf, "loading %s..." % str(package)
class FindPopup(QtGui.QFrame): find = QtCore.Signal(str) def __init__(self, pivot_widget, pivot_position=None, words=None, initial_word=None, close_on_find=True, parent=None): super(FindPopup, self).__init__(parent) self.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Raised) self.setWindowFlags(QtCore.Qt.Popup) self.close_on_find = close_on_find self.edit = QtGui.QLineEdit() self.btn = QtGui.QPushButton("Find") create_pane([self.edit, self.btn], True, compact=True, compact_spacing=0, parent_widget=self) self.edit.setFocus() if initial_word: self.edit.setText(initial_word) self.edit.selectAll() self.completer = None if words: self.completer = QtGui.QCompleter(self) self.completer.setCompletionMode(QtGui.QCompleter.PopupCompletion) self.completions = QtGui.QStringListModel(words, self.completer) self.completer.setModel(self.completions) self.edit.setCompleter(self.completer) pt = getattr(pivot_widget.rect(), pivot_position)() global_pt = pivot_widget.mapToGlobal(pt) self.move(global_pt) self.btn.clicked.connect(self._find) self.edit.returnPressed.connect(self._find) find_shortcut = QtGui.QShortcut(QtGui.QKeySequence("Ctrl+F"), self) find_shortcut.activated.connect(self._find_again) def _find(self): word = self.edit.text() self.find.emit(word) if self.close_on_find: self.close() def _find_again(self): self.edit.selectAll()
def load_packages(self, package_paths, package_name, range_=None, package_attributes=None, callback=None): self.stop_loading_packages() self.bar.setRange(0, 0) self.worker = LoadPackagesThread(package_paths=package_paths, package_name=package_name, range_=range_, package_attributes=package_attributes, callback=callback) id_ = id(self.worker) self.worker.progress.connect(partial(self._progress, id_)) self.worker.finished.connect(partial(self._packagesLoaded, id_)) thread = QtCore.QThread() self.worker.moveToThread(thread) thread.started.connect(self.worker.run) threads = [(thread, self.worker)] for th, worker in self.threads: if th.isRunning(): threads.append((th, worker)) self.threads = threads if self.swap_delay == 0: self.loading_widget.show() if self.main_widget is not None: self.main_widget.hide() else: self.timer = QtCore.QTimer() self.timer.setSingleShot(True) self.timer.setInterval(self.swap_delay) fn = partial(self._swap_to_loader, id_) self.timer.timeout.connect(fn) self.timer.start() thread.start()
class IconButton(QtGui.QLabel): clicked = QtCore.Signal(int) def __init__(self, icon_name, tooltip=None, parent=None): super(IconButton, self).__init__(parent) icon = get_icon(icon_name) self.setPixmap(icon) self.setCursor(QtCore.Qt.PointingHandCursor) if tooltip: self.setToolTip(tooltip) def mousePressEvent(self, event): self.clicked.emit(event.button())
class _TreeNode(QtGui.QLabel): clicked = QtCore.Signal() def __init__(self, item, txt, parent=None): super(_TreeNode, self).__init__(txt, parent) self.item = item self.setCursor(QtCore.Qt.PointingHandCursor) def mouseReleaseEvent(self, event): super(_TreeNode, self).mouseReleaseEvent(event) self.clicked.emit() if event.button() == QtCore.Qt.LeftButton: self.item.setExpanded(not self.item.isExpanded())
class StreamableTextEdit(SearchableTextEdit): """A QTextEdit that also acts like a write-only file object. The object is threadsafe and can be written to from any thread. """ written = QtCore.Signal() def __init__(self, parent=None): super(StreamableTextEdit, self).__init__(parent) self.setReadOnly(True) self.buffer = [] self.lock = threading.Lock() self.written.connect(self._consume) # -- file-like methods def isatty(self): return False def write(self, txt): emit = False try: self.lock.acquire() emit = not bool(self.buffer) self.buffer.append(str(txt)) finally: self.lock.release() if emit: self.written.emit() def _consume(self): try: self.lock.acquire() buffer_ = self.buffer self.buffer = [] finally: self.lock.release() if buffer_: txt = ''.join(buffer_) self._write(txt) def _write(self, txt): self.moveCursor(QtGui.QTextCursor.End) self.insertPlainText(txt) self.moveCursor(QtGui.QTextCursor.End)
def _currentCellChanged(self, currentRow, currentColumn, previousRow, previousColumn): widget = self.cellWidget(currentRow, currentColumn) if self._widget_is_selectable(widget): self._current_variant = widget.variant else: self._current_variant = None self.setCurrentIndex(QtCore.QModelIndex()) # update other variants, this causes them to show/hide the depends icon if previousColumn != currentColumn: for _, widget in self._iter_column_widgets(previousColumn, VariantCellWidget): widget.set_reference_sibling(None) for _, widget in self._iter_column_widgets(currentColumn, VariantCellWidget): widget.set_reference_sibling(self._current_variant) # new selection is failing to cause a paint update sometimes?? This # seems to help but does not 100% fix the problem. self.update(self.model().index(previousRow, previousColumn)) self.update(self.model().index(currentRow, currentColumn)) self.variantSelected.emit(self._current_variant)
class HelpEntryWidget(QtGui.QWidget): clicked = QtCore.Signal() def __init__(self, help_, index, parent=None): super(HelpEntryWidget, self).__init__(parent) self.help_ = help_ self.index = index icon = get_icon_widget("help") label = self.help_.sections[self.index][0] label_widget = QtGui.QLabel(label) self.setCursor(QtCore.Qt.PointingHandCursor) create_pane([icon, label_widget, None], True, compact=True, parent_widget=self) def mouseReleaseEvent(self, event): super(HelpEntryWidget, self).mouseReleaseEvent(event) self.clicked.emit() if event.button() == QtCore.Qt.LeftButton: self.help_.open(self.index)
class ContextTableWidget(QtGui.QTableWidget, ContextViewMixin): default_row_count = 10 double_arrow = u"\u27FA" short_double_arrow = u"\u21D4" variantSelected = QtCore.Signal(object) def __init__(self, context_model=None, parent=None): """Create a context table.""" super(ContextTableWidget, self).__init__(self.default_row_count, 2, parent) ContextViewMixin.__init__(self, context_model) self.diff_mode = False self.diff_context_model = None self.diff_from_source = False self._show_effective_request = False self._current_variant = None self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) hh = self.horizontalHeader() hh.setDefaultSectionSize(12 * self.fontMetrics().height()) vh = self.verticalHeader() vh.setResizeMode(QtGui.QHeaderView.ResizeToContents) vh.setVisible(False) self.delegate = CellDelegate(self) self.setItemDelegate(self.delegate) self.setShowGrid(False) self.currentCellChanged.connect(self._currentCellChanged) self.itemSelectionChanged.connect(self._itemSelectionChanged) self.refresh() def selectionCommand(self, index, event=None): row = index.row() column = index.column() widget = self.cellWidget(row, column) if self._widget_is_selectable(widget): return QtGui.QItemSelectionModel.ClearAndSelect else: return QtGui.QItemSelectionModel.Clear def current_variant(self): """Returns the currently selected variant, if any.""" return self._current_variant def show_effective_request(self, b): if b != self._show_effective_request: self._show_effective_request = b self._update_request_column(0, self.context_model) if self.diff_mode: self._update_request_column(4, self.diff_context_model) def get_request(self): """Get the current request list. Returns: List of strings. """ return self._get_request(0) def enter_diff_mode(self, context_model=None): """Enter diff mode. Args: context_model (`ContextModel`): Context to diff against. If None, a copy of the current context is used. """ assert not self.diff_mode self.diff_mode = True if context_model is None: self.diff_from_source = True self.diff_context_model = self.context_model.copy() else: self.diff_from_source = False self.diff_context_model = context_model self.clear() self.setColumnCount(5) self.refresh() def leave_diff_mode(self): """Leave diff mode.""" assert self.diff_mode self.diff_mode = False self.diff_context_model = None self.diff_from_source = False self.setColumnCount(2) self.refresh() def revert_to_diff(self): assert self.diff_mode source_context = self.diff_context_model.context() self.context_model.set_context(source_context) def revert_to_disk(self): filepath = self.context_model.filepath() assert filepath disk_context = app.load_context(filepath) self.context_model.set_context(disk_context) def get_title(self): """Returns a string suitable for titling a window containing this table.""" def _title(context_model): context = context_model.context() if context is None: return "new context*" title = os.path.basename(context.load_path) if context.load_path \ else "new context" if context_model.is_modified(): title += '*' return title if self.diff_mode: diff_title = _title(self.diff_context_model) if self.diff_from_source: diff_title += "'" return "%s %s %s" % (_title( self.context_model), self.short_double_arrow, diff_title) else: return _title(self.context_model) # Stops focus loss when a widget inside the table is selected. In an MDI app # this can cause the current subwindow to lose focus. def clear(self): self.setFocus() super(ContextTableWidget, self).clear() def select_variant(self, name): for row, widget in self._iter_column_widgets(1, VariantCellWidget): if widget.variant.name == str(name): self.setCurrentIndex(self.model().index(row, 1)) return def refresh(self): self._contextChanged(ContextModel.CONTEXT_CHANGED) def _contextChanged(self, flags=0): update_request_columns = {} # apply request and variant widgets to columns if flags & ContextModel.CONTEXT_CHANGED: self.clear() if self.diff_mode: hh = self.horizontalHeader() hh.setResizeMode(2, QtGui.QHeaderView.Fixed) self.setColumnWidth(2, 50) if self.context(): if self.diff_mode: self._apply_request(self.diff_context_model, 4) self._apply_resolve(self.diff_context_model, 3, 4, hide_locks=True, read_only=True) self._apply_request(self.context_model, 0) self._apply_resolve(self.context_model, 1, 3, reference_column_is_variants=True) self._update_comparison_column(2) update_request_columns[4] = self.diff_context_model else: self._apply_request(self.context_model, 0) self._apply_resolve(self.context_model, 1, 0) else: self._set_package_cell(0, 0) update_request_columns[0] = self.context_model if flags & ContextModel.LOCKS_CHANGED and self._show_effective_request: update_request_columns[0] = self.context_model for column, context_model in update_request_columns.iteritems(): self._update_request_column(column, context_model) # set column headers if self.diff_mode: headers = [["current request", False], ["current resolve", False], [self.double_arrow, False], ["reference resolve", True], ["reference request", True]] else: headers = [["request", False], ["resolve", False]] if self.context_model.is_stale(): headers[0][0] += '*' headers[1][0] += " (stale)" headers[1][1] = True for column, (label, italic) in enumerate(headers): item = QtGui.QTableWidgetItem(label) update_font(item, italic=italic) self.setHorizontalHeaderItem(column, item) self.update() def _update_request_column(self, column, context_model): # remove effective request cells for row, widget in self._iter_column_widgets( column, EffectivePackageCellWidget): self.removeCellWidget(row, column) # update effective request cells if self._show_effective_request: # get row following package select widgets last_row = -1 for row, widget in self._iter_column_widgets( column, PackageSelectWidget): last_row = row row = last_row + 1 for request_str in context_model.implicit_packages: self._set_effective_package_cell(row, column, request_str, "implicit") row += 1 d = context_model.get_lock_requests() for lock, requests in d.iteritems(): for request in requests: request_str = str(request) self._set_effective_package_cell(row, column, request_str, lock.name) row += 1 self._trim_trailing_rows() def _widget_is_selectable(self, widget): return (widget and widget.isEnabled() and isinstance(widget, VariantCellWidget) and not widget.read_only) def _currentCellChanged(self, currentRow, currentColumn, previousRow, previousColumn): widget = self.cellWidget(currentRow, currentColumn) if self._widget_is_selectable(widget): self._current_variant = widget.variant else: self._current_variant = None self.setCurrentIndex(QtCore.QModelIndex()) # update other variants, this causes them to show/hide the depends icon if previousColumn != currentColumn: for _, widget in self._iter_column_widgets(previousColumn, VariantCellWidget): widget.set_reference_sibling(None) for _, widget in self._iter_column_widgets(currentColumn, VariantCellWidget): widget.set_reference_sibling(self._current_variant) # new selection is failing to cause a paint update sometimes?? This # seems to help but does not 100% fix the problem. self.update(self.model().index(previousRow, previousColumn)) self.update(self.model().index(currentRow, currentColumn)) self.variantSelected.emit(self._current_variant) # this is only here to clear the current index, which leaves an annoying # visual cue even though the cell is not selected def _itemSelectionChanged(self): if not self.selectedIndexes(): self.setCurrentIndex(QtCore.QModelIndex()) def _iter_column_widgets(self, column, types=None): types = types or QtGui.QWidget for row in range(self.rowCount()): widget = self.cellWidget(row, column) if widget and isinstance(widget, types): yield row, widget def _get_request(self, column): request_strs = [] for _, edit in self._iter_column_widgets(column, PackageSelectWidget): txt = str(edit.text()).strip() if txt: request_strs.append(txt) return request_strs def _apply_request(self, context_model, column): context = context_model.context() requests = context.requested_packages() num_requests = len(requests) for i, request in enumerate(requests): self._set_package_cell(i, column, request) self._set_package_cell(num_requests, column) def _apply_resolve(self, context_model, column, reference_column, hide_locks=False, read_only=False, reference_column_is_variants=False): context = context_model.context() resolved = context.resolved_packages[:] consumed_rows = set() # match variants up with matching request/variant in source column for row, widget in self._iter_column_widgets( reference_column, (PackageSelectWidget, VariantCellWidget)): request_str = str(widget.text()) if not request_str: continue package_name = Requirement(request_str).name matches = [x for x in resolved if x.name == package_name] if matches: variant = matches[0] resolved = [x for x in resolved if x.name != package_name] reference_variant = None if reference_column_is_variants and isinstance( widget, VariantCellWidget): reference_variant = widget.variant self._set_variant_cell(row, column, context_model, variant, reference_variant=reference_variant, hide_locks=hide_locks, read_only=read_only) consumed_rows.add(row) # append variants that don't match reference requests/variants if reference_column_is_variants: hide_locks = True row = 0 while resolved: variant = resolved[0] resolved = resolved[1:] while row in consumed_rows: row += 1 self._set_variant_cell(row, column, context_model, variant, hide_locks=hide_locks, read_only=read_only) row += 1 def _update_comparison_column(self, column): #no_color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Base) for row in range(self.rowCount()): left = self.cellWidget(row, column - 1) right = self.cellWidget(row, column + 1) left_variant = left.variant if left else None right_variant = right.variant if right else None if left_variant or right_variant: widget = CompareCell(self.context_model, left_variant, right_variant) self.setCellWidget(row, column, widget) def _set_package_cell(self, row, column, request=None): if row >= self.rowCount(): self.setRowCount(row + 1) if request is None: # don't overwrite existing package request widget = self.cellWidget(row, column) if widget and isinstance(widget, PackageSelectWidget): return None txt = str(request) if request else "" read_only = (column != 0) edit = PackageSelectWidget(self.context_model, read_only=read_only) edit.setText(txt) self.setCellWidget(row, column, edit) edit.textChanged.connect(partial(self._packageTextChanged, row, column)) edit.focusOut.connect(partial(self._packageFocusOut, row, column)) edit.focusOutViaKeyPress.connect( partial(self._packageFocusOutViaKeyPress, row, column)) return edit def _set_effective_package_cell(self, row, column, request, lock_type): if row >= self.rowCount(): self.setRowCount(row + 1) cell = EffectivePackageCellWidget(request, lock_type) self.setCellWidget(row, column, cell) def _set_variant_cell(self, row, column, context_model, variant, reference_variant=None, hide_locks=False, read_only=False): if row >= self.rowCount(): self.setRowCount(row + 1) widget = VariantCellWidget(context_model, variant, reference_variant=reference_variant, hide_locks=hide_locks, read_only=read_only) self.setCellWidget(row, column, widget) widget._set_stale(column != 1) def _set_cell_text(self, row, column, txt): if row >= self.rowCount(): self.setRowCount(row + 1) if self.cellWidget(row, column): self.removeCellWidget(row, column) item = QtGui.QTableWidgetItem(txt) self.setItem(row, column, item) def _packageTextChanged(self, row, column, txt): if txt: if self._set_package_cell(row + 1, column): self._update_request_column(column, self.context_model) def _packageFocusOutViaKeyPress(self, row, column, txt): if txt: self._set_current_cell(row + 1, column) else: widget = self.cellWidget(row + 1, column) if widget and isinstance(widget, PackageSelectWidget): self._delete_cell(row, column) new_request = self.get_request() self.context_model.set_request(new_request) self._update_request_column(column, self.context_model) def _packageFocusOut(self, row, column, txt): if txt: self._set_package_cell(row + 1, column) else: widget = self.cellWidget(row + 1, column) if widget and isinstance(widget, PackageSelectWidget): self._delete_cell(row, column) new_request = self.get_request() self.context_model.set_request(new_request) self._update_request_column(column, self.context_model) def _delete_cell(self, row, column): for i in range(row, self.rowCount()): edit = self.cellWidget(i, column) if edit and isinstance(edit, PackageSelectWidget): next_edit = self.cellWidget(i + 1, column) if next_edit and isinstance(next_edit, PackageSelectWidget): next_edit.clone_into(edit) else: self.removeCellWidget(i, column) def _trim_trailing_rows(self): n = 0 for i in reversed(range(self.default_row_count, self.rowCount())): row_clear = not any( self.cellWidget(i, x) for x in range(self.columnCount())) if row_clear: n += 1 else: break if n: row, column = self.currentRow(), self.currentColumn() self.setRowCount(self.rowCount() - n) self._set_current_cell(row, column) def _set_current_cell(self, row, column): self.setCurrentCell(row, column) edit = self.cellWidget(row, column) if edit: edit.setFocus()
def paint(self, painter, option, index): row = index.row() column = index.column() table = self.parent() cmp_widget = table.cellWidget(row, 2) stale = table.context_model.is_stale() rect = option.rect oldbrush = painter.brush() oldpen = painter.pen() pal = table.palette() def _setpen(to_stale): pen = self.stale_pen if stale and to_stale else self.pen painter.setPen(pen) # determine cell bg color and paint it selected_cells = set( (x.row(), x.column()) for x in table.selectedIndexes()) bg_color = None if (row, column) in selected_cells: bg_color = self.highlight_brush elif cmp_widget and \ ((cmp_widget.left() and column == 1) or (cmp_widget.right() and column == 3)): bg_color = cmp_widget.color else: bg_color = pal.color(QtGui.QPalette.Base) painter.fillRect(rect, bg_color) # draw grid lines r = (rect.topRight(), rect.bottomRight()) b = (rect.bottomLeft(), rect.bottomRight() - QtCore.QPoint(1, 0)) _setpen(column < 2) if column == 0: painter.drawLine(*r) _setpen(False) painter.drawLine(*b) elif column == 1: if not cmp_widget or not cmp_widget.left(): painter.drawLine(*r) if row == table.rowCount() - 1: painter.drawLine(*b) else: if stale and row == 0: painter.drawLine(rect.topLeft(), rect.topRight()) _setpen(False) painter.drawLine(*b) elif column == 2: # draw the curvy bits in the comparison column draw_right_edge = True def _draw_path(): painter.setRenderHints(QtGui.QPainter.Antialiasing, True) painter.drawPath(self.path) painter.resetTransform() painter.setRenderHints(QtGui.QPainter.Antialiasing, False) if cmp_widget: if cmp_widget.left(): painter.translate(rect.topLeft() - QtCore.QPoint(1, 0.5)) painter.scale(rect.width() / 2.5, rect.height()) _setpen(True) if stale: pen = QtGui.QPen(self.stale_color) pen.setCosmetic(True) pen.setWidthF(1.5) painter.setPen(pen) if (row, 1) in selected_cells: painter.setBrush(self.highlight_brush) elif cmp_widget.color: painter.setBrush(QtGui.QBrush(cmp_widget.color)) _draw_path() _setpen(False) if cmp_widget.right(): painter.translate(rect.topRight() - QtCore.QPoint(-1, 0.5)) painter.scale(-rect.width() / 2.5, rect.height()) if (row, 3) in selected_cells: painter.setBrush(self.highlight_brush) elif cmp_widget.color: painter.setBrush(QtGui.QBrush(cmp_widget.color)) _draw_path() draw_right_edge = False if draw_right_edge: painter.drawLine(*r) else: painter.drawLine(*r) painter.drawLine(*b) painter.setPen(oldpen) painter.setBrush(oldbrush) if cmp_widget and column in (1, 3): index = table.model().index(row, 2) table.update(index)
class PackageLineEdit(QtGui.QLineEdit, ContextViewMixin): focusOutViaKeyPress = QtCore.Signal(str) focusOut = QtCore.Signal(str) focusIn = QtCore.Signal() def __init__(self, context_model=None, parent=None, family_only=False, read_only=False): super(PackageLineEdit, self).__init__(parent) ContextViewMixin.__init__(self, context_model) self.read_only = read_only self.family_only = family_only self.default_style = None pal = self.palette() self.normal_font = self.font() self.placeholder_font = self.font() self.placeholder_font.setItalic(True) self.normal_text_color = pal.color(QtGui.QPalette.Text) self.placeholder_text_color = pal.color(QtGui.QPalette.Disabled, QtGui.QPalette.Text) if not self.read_only: self.setPlaceholderText("enter package") self._update_font() self.completer = QtGui.QCompleter(self) self.completer.setCompletionMode(QtGui.QCompleter.PopupCompletion) self.completions = QtGui.QStringListModel(self.completer) self.completer.setModel(self.completions) self.setCompleter(self.completer) self.textEdited.connect(self._textEdited) self.textChanged.connect(self._textChanged) def mouseReleaseEvent(self, event): if not self.hasSelectedText(): self.completer.complete() def event(self, event): # keyPressEvent does not capture tab if event.type() == QtCore.QEvent.KeyPress \ and event.key() in (QtCore.Qt.Key_Tab, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): self._update_status() self.focusOutViaKeyPress.emit(self.text()) return True return super(PackageLineEdit, self).event(event) def focusInEvent(self, event): self._update_font() self.focusIn.emit() return super(PackageLineEdit, self).focusInEvent(event) def focusOutEvent(self, event): self._update_status() self._update_font() self.focusOut.emit(self.text()) return super(PackageLineEdit, self).focusOutEvent(event) def clone_into(self, other): other.family_only = self.family_only other.default_style = self.default_style other.setText(self.text()) other.setStyleSheet(self.styleSheet()) completions = self.completions.stringList() other.completions.setStringList(completions) other.completer.setCompletionPrefix(self.text()) def _textChanged(self, txt): self._update_font() def _update_font(self): if self.read_only: return elif self.text(): font = self.normal_font color = self.normal_text_color else: font = self.placeholder_font color = self.placeholder_text_color self.setFont(font) pal = self.palette() pal.setColor(QtGui.QPalette.Active, QtGui.QPalette.Text, color) pal.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Text, color) self.setPalette(pal) def _contextChanged(self, flags=0): if flags & ContextModel.PACKAGES_PATH_CHANGED: self._update_status() @property def _paths(self): return self.context_model.packages_path def _textEdited(self, txt): words = get_completions(str(txt), paths=self._paths, family_only=self.family_only) self.completions.setStringList(list(reversed(list(words)))) def _set_style(self, style=None): if style is None: if self.default_style is not None: self.setStyleSheet(self.default_style) else: if self.default_style is None: self.default_style = self.styleSheet() self.setStyleSheet(style) def _update_status(self): def _ok(): self._set_style() self.setToolTip("") def _err(msg, color="red"): self._set_style("QLineEdit { border : 2px solid %s;}" % color) self.setToolTip(msg) txt = str(self.text()) if not txt: _ok() return try: req = Requirement(str(txt)) except Exception as e: _err(str(e)) return _ok() if not req.conflict: try: it = iter_packages(name=req.name, range_=req.range, paths=self._paths) pkg = sorted(it, key=lambda x: x.version)[-1] except Exception: _err("cannot find package: %r" % txt, "orange") return if pkg.description: self.setToolTip(pkg.description)
class VariantVersionsWidget(PackageLoadingWidget, ContextViewMixin): closeWindow = QtCore.Signal() def __init__(self, context_model=None, reference_variant=None, in_window=False, parent=None): """ Args: reference_variant (`Variant`): Used to show the difference between two variants. in_window (bool): If True, the 'view changelogs' option turns into a checkbox, dropping the 'View in window' option. """ super(VariantVersionsWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) self.in_window = in_window self.variant = None self.reference_variant = reference_variant self.pending_changelog_packages = None self.label = QtGui.QLabel() self.changelog_edit = ChangelogEdit() self.table = VariantVersionsTable(self.context_model, reference_variant=reference_variant) self.tab = QtGui.QTabWidget() self.tab.addTab(self.table, "list view") self.tab.addTab(self.changelog_edit, "changelogs") self.tab.currentChanged.connect(self._tabIndexChanged) buttons = [None] if self.in_window: close_btn = QtGui.QPushButton("Close") buttons.append(close_btn) close_btn.clicked.connect(self._close_window) else: browse_versions_btn = QtGui.QPushButton("Browse Versions...") browse_versions_btn.clicked.connect(self._browseVersions) buttons.append(browse_versions_btn) window_btn = QtGui.QPushButton("View In Window...") window_btn.clicked.connect(self._view_changelogs_window) buttons.append(window_btn) btn_pane = create_pane(buttons, True, compact=not self.in_window) pane = create_pane([self.label, self.tab, btn_pane], False, compact=True) self.set_main_widget(pane) self.set_loader_swap_delay(300) self.clear() def clear(self): self.label.setText("no package selected") self.table.clear() self.pending_changelog_packages = None self.setEnabled(False) def refresh(self): variant = self.variant self.variant = None self.set_variant(variant) def set_variant(self, variant): self.tab.setCurrentIndex(0) self.stop_loading_packages() self.clear() self.variant = variant if self.variant is None: return package_paths = self.context_model.packages_path if self.variant.wrapped.location not in package_paths: txt = "not on the package search path" self.label.setText(txt) return self.setEnabled(True) range_ = None if self.reference_variant and self.reference_variant.name == variant.name: versions = sorted( [variant.version, self.reference_variant.version]) range_ = VersionRange.as_span(*versions) self.load_packages(package_paths=package_paths, package_name=variant.name, range_=range_, package_attributes=("timestamp", )) def set_packages(self, packages): self.table._set_variant(self.variant, packages) self._update_label() self._update_changelogs(packages) self.setEnabled(True) def _update_label(self): diff_num = self.table.get_reference_difference() if diff_num is None: # normal mode if self.table.version_index == 0: if self.table.num_versions == 1: txt = "the only package" else: txt = "the latest package" else: nth = positional_number_string(self.table.version_index + 1) txt = "the %s latest package" % nth if self.table.num_versions > 1: txt += " of %d packages" % self.table.num_versions txt = "%s is %s" % (self.variant.qualified_package_name, txt) else: # reference mode - showing difference between two versions adj = "ahead" if diff_num > 0 else "behind" diff_num = abs(diff_num) unit = "version" if diff_num == 1 else "versions" txt = "Package is %d %s %s" % (diff_num, unit, adj) self.label.setText(txt) def _update_changelogs(self, packages): # don't actually update until tab is selected - changelogs may get big, # we don't want to block up the gui thread unless necessary self.pending_changelog_packages = packages if self.tab.currentIndex() == 1: self._apply_changelogs() def _tabIndexChanged(self, index): if index == 1: self._apply_changelogs() def _apply_changelogs(self): if self.pending_changelog_packages: self.changelog_edit.set_packages(self.pending_changelog_packages) self.pending_changelog_packages = None def _changelogStateChanged(self, state): self._view_changelogs(state == QtCore.Qt.Checked) self.refresh() def _view_or_hide_changelogs(self): enable = (not self.table.view_changelog) self._view_changelogs(enable) self.refresh() def _view_changelogs_window(self): from rezgui.dialogs.VariantVersionsDialog import VariantVersionsDialog dlg = VariantVersionsDialog(self.context_model, self.variant, parent=self) dlg.exec_() def _browseVersions(self): from rezgui.dialogs.BrowsePackageDialog import BrowsePackageDialog dlg = BrowsePackageDialog( context_model=self.context_model, package_text=self.variant.qualified_package_name, close_only=True, lock_package=True, parent=self.parentWidget()) dlg.setWindowTitle("Versions - %s" % self.variant.name) dlg.exec_() def _close_window(self): self.closeWindow.emit()
def sizeHint(self): width = self.config.get(self.config_key + "/width") height = self.config.get(self.config_key + "/height") return QtCore.QSize(width, height)
class ToolWidget(QtGui.QWidget): clicked = QtCore.Signal() def __init__(self, context, tool_name, process_tracker=None, parent=None): super(ToolWidget, self).__init__(parent) self.context = context self.tool_name = tool_name self.process_tracker = process_tracker tool_icon = get_icon_widget("spanner") self.label = QtGui.QLabel(tool_name) self.instances_label = QtGui.QLabel("") self.instances_label.setEnabled(False) update_font(self.instances_label, italic=True) if self.context: self.setCursor(QtCore.Qt.PointingHandCursor) if self.process_tracker: entries = self.get_processes() self.set_instance_count(len(entries)) layout = QtGui.QHBoxLayout() layout.setSpacing(2) layout.setContentsMargins(2, 2, 2, 2) layout.addWidget(tool_icon) layout.addWidget(self.label, 1) layout.addWidget(self.instances_label) self.setLayout(layout) def get_processes(self): if not self.process_tracker: return [] return self.process_tracker.running_instances(self.context, self.tool_name) def mouseReleaseEvent(self, event): super(ToolWidget, self).mouseReleaseEvent(event) if not self.context: return menu = QtGui.QMenu(self) add_menu_action(menu, "Run", self._launch_tool) fn = partial(self._launch_tool, terminal=True) add_menu_action(menu, "Run In Terminal", fn) fn = partial(self._launch_tool, moniter=True) add_menu_action(menu, "Run And Moniter", fn) entries = self.get_processes() if entries: menu.addSeparator() add_menu_action(menu, "Running Processes...", self._list_processes) menu.addSeparator() add_menu_action(menu, "Cancel") menu.exec_(self.mapToGlobal(event.pos())) self.clicked.emit() def _launch_tool(self, terminal=False, moniter=False): buf = subprocess.PIPE if moniter else None proc = app.execute_shell(context=self.context, command=self.tool_name, terminal=terminal, stdout=buf, stderr=buf) if self.process_tracker: self.process_tracker.add_instance(self.context, self.tool_name, proc) if moniter: dlg = ProcessDialog(proc, self.tool_name) dlg.exec_() def _list_processes(self): entries = self.get_processes() now = int(time.time()) items = [] for proc, start_time in entries: age = now - start_time items.append((age, proc.pid)) if items: items = sorted(items) lines = [] for age, pid in items: t_str = readable_time_duration(age) line = "Process #%d has been running for %s" % (pid, t_str) lines.append(line) txt = "\n".join(lines) else: txt = "There are no running processes." QtGui.QMessageBox.information(self, "Processes", txt) def set_instance_count(self, nprocs): if nprocs: txt = "%d instances running..." % nprocs else: txt = "" self.instances_label.setText(txt)
class ProcessTrackerThread(QtCore.QThread): instanceCountChanged = QtCore.Signal(int, str, int) def __init__(self, parent=None): super(ProcessTrackerThread, self).__init__(parent) self.processes = {} self.proc_list = [] self.pending_procs = [] self.lock = Lock() self.timer = QtCore.QTimer() self.timer.timeout.connect(self._update) def run(self): self.timer.start() self.exec_() def running_instances(self, context, process_name): """Get a list of running instances. Args: context (`ResolvedContext`): Context the process is running in. process_name (str): Name of the process. Returns: List of (`subprocess.Popen`, start-time) 2-tuples, where start_time is the epoch time the process was added. """ handle = (id(context), process_name) it = self.processes.get(handle, {}).itervalues() entries = [x for x in it if x[0].poll() is None] return entries def add_instance(self, context, process_name, process): try: self.lock.acquire() entry = (id(context), process_name, process, int(time.time())) self.pending_procs.append(entry) finally: self.lock.release() def _update(self): # add pending instances if self.pending_procs: try: self.lock.acquire() pending_procs = self.pending_procs self.pending_procs = [] finally: self.lock.release() for (context_id, process_name, process, start_time) in pending_procs: handle = (context_id, process_name) procs = self.processes.get(handle) value = (process, start_time) if procs is None: self.processes[handle] = {process.pid: value} nprocs = 1 else: if process.pid not in procs: procs[process.pid] = value nprocs = len(procs) self.instanceCountChanged.emit(context_id, process_name, nprocs) # rebuild proc list to iterate over if self.processes and not self.proc_list: for (context_id, process_name), d in self.processes.iteritems(): for proc, _ in d.itervalues(): entry = (context_id, process_name, proc) self.proc_list.append(entry) # poll a proc if self.proc_list: context_id, process_name, proc = self.proc_list.pop() if proc.poll() is not None: nprocs = self._remove_proc(context_id, process_name, proc.pid) self.instanceCountChanged.emit(context_id, process_name, nprocs) def _remove_proc(self, context_id, process_name, pid): handle = (context_id, process_name) procs = self.processes.get(handle) if procs is None: return 0 if pid in procs: del procs[pid] nprocs = len(procs) if not procs: del self.processes[handle] return nprocs
def __init__(self, context_model, parent=None, advanced=False): config_key = ("layout/window/advanced_resolve" if advanced else "layout/window/resolve") super(ResolveDialog, self).__init__(parent) StoreSizeMixin.__init__(self, app.config, config_key) self.setWindowTitle("Resolve") self.setContentsMargins(0, 0, 0, 0) self.context_model = context_model self.advanced = advanced self.resolver = None self.thread = None self.started = False self._finished = False self.busy_cursor = QtGui.QCursor(QtCore.Qt.WaitCursor) self.edit = StreamableTextEdit() self.edit.setStyleSheet("font: 9pt 'Courier'") self.bar = QtGui.QProgressBar() self.bar.setRange(0, 10) self.save_context_btn = QtGui.QPushButton("Save Context As...") self.graph_btn = QtGui.QPushButton("View Graph...") self.ok_btn = QtGui.QPushButton("Ok") self.start_again_btn = QtGui.QPushButton("Start Again") self.cancel_btn = QtGui.QPushButton("Cancel") self.resolve_btn = QtGui.QPushButton("Resolve") self.ok_btn.hide() self.graph_btn.hide() self.start_again_btn.hide() self.save_context_btn.hide() btn_pane = create_pane([None, self.save_context_btn, self.graph_btn, self.start_again_btn, self.ok_btn, self.cancel_btn, self.resolve_btn], not self.advanced) layout = QtGui.QVBoxLayout() layout.addWidget(self.bar) layout.addWidget(self.edit, 1) self.resolve_group = None self.max_fails_combo = None self.verbosity_combo = None self.show_package_loads_checkbox = None # this is solely to execute _start_resolve() as soon as the dialog opens self.timer = QtCore.QTimer() self.timer.setInterval(1) self.timer.setSingleShot(True) self.timer.timeout.connect(self._on_dialog_open) if self.advanced: self.resolve_group = QtGui.QGroupBox("resolve settings") label = QtGui.QLabel("maximum fails:") self.max_fails_combo = QtGui.QComboBox() self.max_fails_combo.setEditable(True) self.max_fails_combo.addItem("-") self.max_fails_combo.addItem("1") self.max_fails_combo.addItem("2") self.max_fails_combo.addItem("3") app.config.attach(self.max_fails_combo, "resolve/max_fails") max_fails_pane = create_pane([None, label, self.max_fails_combo], True) label = QtGui.QLabel("verbosity:") self.verbosity_combo = QtGui.QComboBox() self.verbosity_combo.addItem("0") self.verbosity_combo.addItem("1") self.verbosity_combo.addItem("2") app.config.attach(self.verbosity_combo, "resolve/verbosity") verbosity_pane = create_pane([None, label, self.verbosity_combo], True) self.show_package_loads_checkbox = QtGui.QCheckBox("show package loads") self.show_package_loads_checkbox.setLayoutDirection(QtCore.Qt.RightToLeft) app.config.attach(self.show_package_loads_checkbox, "resolve/show_package_loads") show_loads_pane = create_pane([None, self.show_package_loads_checkbox], True) self.timestamp_widget = TimestampWidget(self.context_model) context = self.context_model.context() if context and context.requested_timestamp: self.timestamp_widget.set_time(context.requested_timestamp) left_pane = create_pane([self.timestamp_widget, None], False, compact=True) right_pane = create_pane([max_fails_pane, verbosity_pane, show_loads_pane, None], False, compact=True) create_pane([left_pane, right_pane], True, parent_widget=self.resolve_group) pane = create_pane([self.resolve_group, None, btn_pane], True) self.cancel_btn.hide() layout.addWidget(pane) else: self.resolve_btn.hide() layout.addWidget(btn_pane) self.setLayout(layout) self.cancel_btn.clicked.connect(self._cancel_resolve) self.resolve_btn.clicked.connect(self._start_resolve) self.graph_btn.clicked.connect(self._view_graph) self.save_context_btn.clicked.connect(self._save_context) self.start_again_btn.clicked.connect(self._reset) self.ok_btn.clicked.connect(self.close)
def _itemSelectionChanged(self): if not self.selectedIndexes(): self.setCurrentIndex(QtCore.QModelIndex())
class ContextModel(QtCore.QObject): """A model of a `ResolvedContext` object. Note that this is NOT a QAbstractItemModel subclass! A context does not lend itself to this data structure unfortunately. This model not only represents a context, but also contains the settings needed to create a new context, or re-resolve an existing context. """ dataChanged = QtCore.Signal(int) # dataChanged flags REQUEST_CHANGED = 1 PACKAGES_PATH_CHANGED = 2 LOCKS_CHANGED = 4 CONTEXT_CHANGED = 8 LOADPATH_CHANGED = 16 PACKAGE_FILTER_CHANGED = 32 def __init__(self, context=None, parent=None): super(ContextModel, self).__init__(parent) self._context = None self._stale = True self._modified = True self._dependency_graph = None self._dependency_lookup = None self.request = [] self.packages_path = config.packages_path self.implicit_packages = config.implicit_packages self.package_filter = config.package_filter self.default_patch_lock = PatchLock.no_lock self.patch_locks = {} if context: self._set_context(context) self._modified = False def copy(self): """Returns a copy of the context.""" other = ContextModel(self._context, self.parent()) other._stale = self._stale other._modified = self._modified other.request = self.request[:] other.packages_path = self.packages_path other.implicit_packages = self.implicit_packages other.package_filter = self.package_filter other.default_patch_lock = self.default_patch_lock other.patch_locks = copy.deepcopy(self.patch_locks) return other def is_stale(self): """Returns True if the context is stale. If the context is stale, this means there are pending changes. To update the model, you should call resolve_context(). """ return self._stale def is_modified(self): """Returns True if the context has been changed since save/load, False otherwise. If the context has never been saved, True is always returned. """ return self._modified def package_depends_on(self, name_a, name_b): """Returns dependency information about two packages: 0: A does not depend, directly or indirectly, on B; 1: A depends indirectly on B; 2: A depends directly on B. """ assert self._context if self._dependency_lookup is None: self._dependency_graph = self._context.get_dependency_graph() self._dependency_lookup = accessibility(self._dependency_graph) downstream = self._dependency_lookup.get(name_a, []) accessible = (name_b in downstream) if accessible: neighbours = self._dependency_graph.neighbors(name_a) return 2 if name_b in neighbours else 1 else: return 0 def context(self): """Return the current context, if any.""" return self._context def filepath(self): """Return the path the current context was saved/loaded to, if any.""" return self._context.load_path if self._context else None def get_patch_lock(self, package_name): """Return the patch lock associated with the package, or None.""" return self.patch_locks.get(package_name) def get_lock_requests(self): """Take the current context, and the current patch locks, and determine the effective requests that will be added to the main request. Returns: A dict of (PatchLock, [Requirement]) tuples. Each requirement will be a weak package reference. If there is no current context, an empty dict will be returned. """ d = defaultdict(list) if self._context: for variant in self._context.resolved_packages: name = variant.name version = variant.version lock = self.patch_locks.get(name) if lock is None: lock = self.default_patch_lock request = get_lock_request(name, version, lock) if request is not None: d[lock].append(request) return d def set_request(self, request_strings): self._attr_changed("request", request_strings, self.REQUEST_CHANGED) def set_packages_path(self, packages_path): self._attr_changed("packages_path", packages_path, self.PACKAGES_PATH_CHANGED) def set_package_filter(self, package_filter): self._attr_changed("package_filter", package_filter, self.PACKAGE_FILTER_CHANGED) def save(self, filepath): assert self._context assert not self._stale self._context.save(filepath) self._context.set_load_path(filepath) self._modified = False self.dataChanged.emit(self.LOADPATH_CHANGED) def set_default_patch_lock(self, lock): self._attr_changed("default_patch_lock", lock, self.LOCKS_CHANGED) def set_patch_lock(self, package_name, lock): existing_lock = self.patch_locks.get(package_name) if lock != existing_lock: self.patch_locks[package_name] = lock self._changed(self.LOCKS_CHANGED) def remove_patch_lock(self, package_name): if package_name in self.patch_locks: del self.patch_locks[package_name] self._changed(self.LOCKS_CHANGED) def remove_all_patch_locks(self): if self.patch_locks: self.patch_locks = {} self._changed(self.LOCKS_CHANGED) def resolve_context(self, verbosity=0, max_fails=-1, timestamp=None, callback=None, buf=None, package_load_callback=None): """Update the current context by performing a re-resolve. The newly resolved context is only applied if it is a successful solve. Returns: `ResolvedContext` object, which may be a successful or failed solve. """ package_filter = PackageFilterList.from_pod(self.package_filter) context = ResolvedContext( self.request, package_paths=self.packages_path, package_filter=package_filter, verbosity=verbosity, max_fails=max_fails, timestamp=timestamp, buf=buf, callback=callback, package_load_callback=package_load_callback) if context.success: if self._context and self._context.load_path: context.set_load_path(self._context.load_path) self._set_context(context) self._modified = True return context def can_revert(self): """Return True if the model is revertable, False otherwise.""" return bool(self._stale and self._context) def revert(self): """Discard any pending changes.""" if self.can_revert(): self._set_context(self._context) def set_context(self, context): """Replace the current context with another.""" self._set_context(context, emit=False) self._modified = (not context.load_path) self.dataChanged.emit(self.CONTEXT_CHANGED | self.REQUEST_CHANGED | self.PACKAGES_PATH_CHANGED | self.LOCKS_CHANGED | self.LOADPATH_CHANGED | self.PACKAGE_FILTER_CHANGED) def _set_context(self, context, emit=True): self._context = context self._stale = False self._dependency_lookup = None self.request = [str(x) for x in context.requested_packages()] self.packages_path = context.package_paths self.implicit_packages = context.implicit_packages[:] self.package_filter = context.package_filter.to_pod() self.default_patch_lock = context.default_patch_lock self.patch_locks = copy.deepcopy(context.patch_locks) if emit: self.dataChanged.emit(self.CONTEXT_CHANGED | self.REQUEST_CHANGED | self.PACKAGES_PATH_CHANGED | self.LOCKS_CHANGED) def _changed(self, flags): self._stale = True self._modified = True self.dataChanged.emit(flags) def _attr_changed(self, attr, value, flags): if getattr(self, attr) == value: return setattr(self, attr, value) self._changed(flags)
class ContextManagerWidget(QtGui.QWidget, ContextViewMixin): diffModeChanged = QtCore.Signal() def __init__(self, context_model=None, parent=None): super(ContextManagerWidget, self).__init__(parent) ContextViewMixin.__init__(self, context_model) # widgets self.popup = None self.context_table = ContextTableWidget(self.context_model) self.show_effective_request_checkbox = QtGui.QCheckBox( "show effective request") resolve_time_label = ContextResolveTimeLabel(self.context_model) self.time_lock_tbtn = QtGui.QToolButton() icon = get_icon("time_lock", as_qicon=True) self.time_lock_tbtn.setIcon(icon) self.find_tbtn = QtGui.QToolButton() self.find_tbtn.setToolTip("find resolved package") icon = get_icon("find", as_qicon=True) self.find_tbtn.setIcon(icon) self.shell_tbtn = QtGui.QToolButton() self.shell_tbtn.setToolTip("open shell") icon = get_icon("terminal", as_qicon=True) self.shell_tbtn.setIcon(icon) self.diff_tbtn = QtGui.QToolButton() self.diff_tbtn.setToolTip("enter diff mode") self.diff_tbtn.setPopupMode(QtGui.QToolButton.MenuButtonPopup) self.diff_menu = QtGui.QMenu() self.diff_action = add_menu_action(self.diff_menu, "Diff Against Current", self._diff_with_last_resolve, "diff") self.diff_to_disk_action = add_menu_action(self.diff_menu, "Diff Against Disk", self._diff_with_disk, "diff_to_disk") self.diff_to_other_action = add_menu_action(self.diff_menu, "Diff Against Other...", self._diff_with_other, "diff_to_other") self.diff_tbtn.setMenu(self.diff_menu) self.diff_tbtn.setDefaultAction(self.diff_action) self.undiff_tbtn = QtGui.QToolButton() self.undiff_tbtn.setToolTip("leave diff mode") icon = get_icon("diff", as_qicon=True) self.undiff_tbtn.setIcon(icon) self.undiff_tbtn.setCheckable(True) self.lock_tbtn = QtGui.QToolButton() self.lock_tbtn.setToolTip("locking") icon = get_icon("no_lock", as_qicon=True) self.lock_tbtn.setIcon(icon) self.lock_tbtn.setPopupMode(QtGui.QToolButton.InstantPopup) menu = QtGui.QMenu() for lock_type in PatchLock: fn = partial(self._set_lock_type, lock_type) add_menu_action(menu, lock_type.description, fn, lock_type.name) menu.addSeparator() add_menu_action(menu, "Remove Explicit Locks", self._removeExplicitLocks) self.lock_tbtn.setMenu(menu) self.revert_tbtn = QtGui.QToolButton() self.revert_tbtn.setToolTip("revert") icon = get_icon("revert", as_qicon=True) self.revert_tbtn.setIcon(icon) self.revert_tbtn.setPopupMode(QtGui.QToolButton.InstantPopup) self.revert_menu = QtGui.QMenu() self.revert_action = add_menu_action(self.revert_menu, "Revert To Last Resolve...", self._revert_to_last_resolve, "revert") self.revert_diff_action = add_menu_action(self.revert_menu, "Revert To Reference...", self._revert_to_diff, "revert_to_diff") self.revert_disk_action = add_menu_action(self.revert_menu, "Revert To Disk...", self._revert_to_disk, "revert_to_disk") self.revert_tbtn.setMenu(self.revert_menu) resolve_tbtn = QtGui.QToolButton() resolve_tbtn.setPopupMode(QtGui.QToolButton.MenuButtonPopup) menu = QtGui.QMenu() default_resolve_action = add_menu_action(menu, "Resolve", self._resolve, "resolve") add_menu_action(menu, "Advanced Resolve...", partial(self._resolve, advanced=True), "advanced_resolve") resolve_tbtn.setDefaultAction(default_resolve_action) resolve_tbtn.setMenu(menu) toolbar = QtGui.QToolBar() toolbar.addWidget(resolve_time_label) self.time_lock_tbtn_action = toolbar.addWidget(self.time_lock_tbtn) toolbar.addSeparator() toolbar.addWidget(self.find_tbtn) toolbar.addWidget(self.shell_tbtn) self.diff_tbtn_action = toolbar.addWidget(self.diff_tbtn) self.undiff_tbtn_action = toolbar.addWidget(self.undiff_tbtn) toolbar.addWidget(self.lock_tbtn) toolbar.addWidget(self.revert_tbtn) toolbar.addWidget(resolve_tbtn) self.time_lock_tbtn_action.setVisible(False) self.undiff_tbtn_action.setVisible(False) self.time_lock_tbtn.setCursor(QtCore.Qt.PointingHandCursor) self.find_tbtn.setCursor(QtCore.Qt.PointingHandCursor) self.shell_tbtn.setCursor(QtCore.Qt.PointingHandCursor) self.diff_tbtn.setCursor(QtCore.Qt.PointingHandCursor) self.lock_tbtn.setCursor(QtCore.Qt.PointingHandCursor) self.revert_tbtn.setCursor(QtCore.Qt.PointingHandCursor) resolve_tbtn.setCursor(QtCore.Qt.PointingHandCursor) btn_pane = create_pane( [self.show_effective_request_checkbox, None, toolbar], True, compact=True, compact_spacing=0) context_pane = create_pane([btn_pane, self.context_table], False, compact=True, compact_spacing=0) self.package_tab = PackageTabWidget(self.context_model, versions_tab=True) context_splitter = ConfiguredSplitter(app.config, "layout/splitter/main") context_splitter.setOrientation(QtCore.Qt.Vertical) context_splitter.addWidget(context_pane) context_splitter.addWidget(self.package_tab) if not context_splitter.apply_saved_layout(): context_splitter.setStretchFactor(0, 2) context_splitter.setStretchFactor(1, 1) self.settings = ContextSettingsWidget(self.context_model) self.tools_list = ContextToolsWidget(self.context_model) self.resolve_details = ContextDetailsWidget(self.context_model) self.tab = QtGui.QTabWidget() icon = get_icon("context", as_qicon=True) self.tab.addTab(context_splitter, icon, "context") icon = get_icon("context_settings", as_qicon=True) self.tab.addTab(self.settings, icon, "settings") icon = get_icon("tools", as_qicon=True) self.tab.addTab(self.tools_list, icon, "tools") icon = get_icon("info", as_qicon=True) self.tab.addTab(self.resolve_details, icon, "resolve details") self.tab.setTabEnabled(2, False) self.tab.setTabEnabled(3, False) # layout layout = QtGui.QVBoxLayout() layout.setSpacing(0) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.tab) self.setLayout(layout) # shortcuts find_shortcut = QtGui.QShortcut(QtGui.QKeySequence("Ctrl+F"), self) find_shortcut.activated.connect(self._search) # widget signals self.context_table.variantSelected.connect(self._variantSelected) self.find_tbtn.clicked.connect(self._search_variant) self.shell_tbtn.clicked.connect(self._open_shell) self.undiff_tbtn.clicked.connect(self._leave_diff_mode) self.time_lock_tbtn.clicked.connect(self._timelockClicked) self.tools_list.toolsChanged.connect(self._updateToolsCount) self.diff_menu.aboutToShow.connect(self._aboutToShowDiffMenu) self.revert_menu.aboutToShow.connect(self._aboutToShowRevertMenu) self.show_effective_request_checkbox.stateChanged.connect( self._effectiveRequestStateChanged) self.refresh() self._updateToolsCount() def sizeHint(self): return QtCore.QSize(800, 500) def get_title(self): """Returns a string suitable for titling a window containing this widget.""" return self.context_table.get_title() def refresh(self): self._contextChanged(ContextModel.CONTEXT_CHANGED) def _resolve(self, advanced=False): dlg = ResolveDialog(self.context_model, parent=self, advanced=advanced) dlg.resolve() # this updates the model on successful solve def _changes_prompt(self): ret = QtGui.QMessageBox.warning( self, "The context has been modified.", "Your changes will be lost. Are you sure?", QtGui.QMessageBox.Ok, QtGui.QMessageBox.Cancel) return (ret == QtGui.QMessageBox.Ok) def _revert_to_last_resolve(self): assert self.context_model.can_revert() if self._changes_prompt(): self.context_model.revert() def _revert_to_diff(self): if self._changes_prompt(): self.context_table.revert_to_diff() def _revert_to_disk(self): if self._changes_prompt(): self.context_table.revert_to_disk() def _open_shell(self): assert self.context() app.execute_shell(context=self.context(), terminal=True) def _leave_diff_mode(self): self.context_table.leave_diff_mode() self._change_diff_mode(False) def _diff_with_last_resolve(self): self.context_table.enter_diff_mode() self._change_diff_mode(True) def _diff_with_disk(self): filepath = self.context_model.filepath() self._diff_with_file(filepath) def _diff_with_other(self): filepath = QtGui.QFileDialog.getOpenFileName( self, "Open Context", filter="Context files (*.rxt)") if filepath: self._diff_with_file(str(filepath)) def _diff_with_file(self, filepath): assert filepath disk_context = app.load_context(filepath) model = ContextModel(disk_context) self.context_table.enter_diff_mode(model) self._change_diff_mode(True) def _change_diff_mode(self, enabled): self.undiff_tbtn.setChecked(enabled) self.diff_tbtn_action.setVisible(not enabled) self.undiff_tbtn_action.setVisible(enabled) self.diffModeChanged.emit() def _aboutToShowDiffMenu(self): stale = self.context_model.is_stale() self.diff_action.setEnabled(not stale) self.diff_to_other_action.setEnabled(not stale) self.diff_to_disk_action.setEnabled( bool(self.context_model.filepath()) and not stale) def _aboutToShowRevertMenu(self): model = self.context_model self.revert_action.setEnabled(model.can_revert()) self.revert_disk_action.setEnabled( bool(model.filepath()) and model.is_modified()) self.revert_diff_action.setEnabled( self.context_table.diff_mode and self.context_table.diff_from_source and not model.is_stale()) def _contextChanged(self, flags=0): stale = self.context_model.is_stale() context = self.context() is_context = bool(context) self.diff_action.setEnabled(not stale) self.diff_tbtn.setEnabled(not stale) self.undiff_tbtn.setEnabled(not stale) self.shell_tbtn.setEnabled(not stale) self.lock_tbtn.setEnabled(is_context) self.find_tbtn.setEnabled(is_context) self.tab.setTabEnabled(2, is_context) self.tab.setTabEnabled(3, is_context) tab_text = "context*" if stale else "context" self.tab.setTabText(0, tab_text) context_changed = (flags & ContextModel.CONTEXT_CHANGED) if context_changed: if is_context and context.requested_timestamp: t = time.localtime(context.requested_timestamp) t_str = time.strftime("%a %b %d %H:%M:%S %Y", t) txt = "packages released after %s were ignored" % t_str self.time_lock_tbtn.setToolTip(txt) self.time_lock_tbtn_action.setVisible(True) else: self.time_lock_tbtn_action.setVisible(False) settings_modified = ((flags & ContextModel.PACKAGES_PATH_CHANGED) and not context_changed) label = "settings*" if settings_modified else "settings" self.tab.setTabText(1, label) if flags & (ContextModel.LOCKS_CHANGED | ContextModel.CONTEXT_CHANGED): lock_type = self.context_model.default_patch_lock icon = get_icon(lock_type.name, as_qicon=True) self.lock_tbtn.setIcon(icon) def _variantSelected(self, variant): self.package_tab.set_variant(variant) def _effectiveRequestStateChanged(self, state): self.context_table.show_effective_request(state == QtCore.Qt.Checked) def _timelockClicked(self): title = "The resolve is timelocked" body = str(self.time_lock_tbtn.toolTip()).capitalize() secs = int(time.time()) - self.context().requested_timestamp t_str = readable_time_duration(secs) body += "\n(%s ago)" % t_str QtGui.QMessageBox.information(self, title, body) def _set_lock_type(self, lock_type): self.context_model.set_default_patch_lock(lock_type) def _updateToolsCount(self): label = "tools (%d)" % self.tools_list.num_tools() self.tab.setTabText(2, label) def _removeExplicitLocks(self): self.context_model.remove_all_patch_locks() def _search(self): tab_index = self.tab.currentIndex() if tab_index == 0: self._search_variant() elif tab_index == 3: self.resolve_details.search() def _search_variant(self): context = self.context() if not context: return words = [x.name for x in context.resolved_packages] self.popup = FindPopup(self.find_tbtn, "bottomLeft", words, parent=self) self.popup.find.connect(self.context_table.select_variant) self.popup.show()
def _clear_selection(self): self.clearSelection() self.setCurrentIndex(QtCore.QModelIndex())
def sizeHint(self): return QtCore.QSize(800, 500)