def __init__(self, wt_dir, parent=None): self.merge_action = None self.wt = None self.wt_dir = wt_dir QBzrWindow.__init__(self, [gettext("Conflicts")], parent) self.restoreSize("conflicts", (550, 380)) vbox = QtGui.QVBoxLayout(self.centralwidget) self.conflicts_list = QtGui.QTreeWidget(self) self.conflicts_list.setRootIsDecorated(False) self.conflicts_list.setUniformRowHeights(True) self.conflicts_list.setSelectionBehavior( QtGui.QAbstractItemView.SelectRows) self.conflicts_list.setSelectionMode( QtGui.QAbstractItemView.ExtendedSelection) self.conflicts_list.setHeaderLabels([ gettext("File"), gettext("Conflict"), ]) self.connect( self.conflicts_list.selectionModel(), QtCore.SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.update_merge_tool_ui) self.connect(self.conflicts_list, QtCore.SIGNAL("customContextMenuRequested(QPoint)"), self.show_context_menu) self.connect( self.conflicts_list, QtCore.SIGNAL("itemDoubleClicked(QTreeWidgetItem *, int)"), self.launch_merge_tool) self.conflicts_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.conflicts_list.setSortingEnabled(True) self.conflicts_list.sortByColumn(0, QtCore.Qt.AscendingOrder) header = self.conflicts_list.header() header.setStretchLastSection(False) header.setResizeMode(0, QtGui.QHeaderView.Stretch) header.setResizeMode(1, QtGui.QHeaderView.ResizeToContents) vbox.addWidget(self.conflicts_list) hbox = QtGui.QHBoxLayout() self.merge_tools_combo = QtGui.QComboBox(self) self.merge_tools_combo.setEditable(False) self.connect(self.merge_tools_combo, QtCore.SIGNAL("currentIndexChanged(int)"), self.update_merge_tool_ui) self.merge_tool_error = QtGui.QLabel('', self) self.program_launch_button = QtGui.QPushButton(gettext("&Launch..."), self) self.program_launch_button.setEnabled(False) self.connect(self.program_launch_button, QtCore.SIGNAL("clicked()"), self.launch_merge_tool) self.program_label = QtGui.QLabel(gettext("M&erge tool:"), self) self.program_label.setBuddy(self.merge_tools_combo) hbox.addWidget(self.program_label) hbox.addWidget(self.merge_tools_combo) hbox.addWidget(self.merge_tool_error) hbox.addStretch(1) hbox.addWidget(self.program_launch_button) vbox.addLayout(hbox) self.create_context_menu() buttonbox = self.create_button_box(BTN_CLOSE) refresh = StandardButton(BTN_REFRESH) buttonbox.addButton(refresh, QtGui.QDialogButtonBox.ActionRole) self.connect(refresh, QtCore.SIGNAL("clicked()"), self.refresh) autobutton = QtGui.QPushButton(gettext('Auto-resolve'), self.centralwidget) self.connect(autobutton, QtCore.SIGNAL("clicked(bool)"), self.auto_resolve) hbox = QtGui.QHBoxLayout() hbox.addWidget(autobutton) hbox.addWidget(buttonbox) vbox.addLayout(hbox) self.initialize_ui()
class SubProcessWindowBase(object): def __init_internal__(self, title, name="genericsubprocess", args=None, dir=None, default_size=None, ui_mode=True, dialog=True, parent=None, hide_progress=False, immediate=False): self.restoreSize(name, default_size) self._name = name self._default_size = default_size self.args = args self.dir = dir self.ui_mode = ui_mode self.return_code = 1 if dialog: flags = (self.windowFlags() & ~QtCore.Qt.Window) | QtCore.Qt.Dialog self.setWindowFlags(flags) self.process_widget = SubProcessWidget(self.ui_mode, self, hide_progress) self.connect(self.process_widget, QtCore.SIGNAL("finished()"), self.on_finished) self.connect(self.process_widget, QtCore.SIGNAL("failed(QString)"), self.on_failed) self.connect(self.process_widget, QtCore.SIGNAL("error()"), self.on_error) self.connect(self.process_widget, QtCore.SIGNAL("conflicted(QString)"), self.on_conflicted) self.closeButton = StandardButton(BTN_CLOSE) self.okButton = StandardButton(BTN_OK) self.cancelButton = StandardButton(BTN_CANCEL) # ok button gets disabled when we start. QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessStarted(bool)"), self.okButton, QtCore.SLOT("setDisabled(bool)")) # ok button gets hidden when we finish. QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessFinished(bool)"), self.okButton, QtCore.SLOT("setHidden(bool)")) # close button gets shown when we finish. QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessFinished(bool)"), self.closeButton, QtCore.SLOT("setShown(bool)")) # cancel button gets disabled when finished. QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessFinished(bool)"), self.cancelButton, QtCore.SLOT("setDisabled(bool)")) # ok button gets enabled when we fail. QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessFailed(bool)"), self.okButton, QtCore.SLOT("setDisabled(bool)")) # Change the ok button to 'retry' if we fail. QtCore.QObject.connect( self, QtCore.SIGNAL("subprocessFailed(bool)"), lambda failed: self.okButton.setText(gettext('&Retry'))) self.buttonbox = QtGui.QDialogButtonBox(self) self.buttonbox.addButton(self.okButton, QtGui.QDialogButtonBox.AcceptRole) self.buttonbox.addButton(self.closeButton, QtGui.QDialogButtonBox.AcceptRole) self.buttonbox.addButton(self.cancelButton, QtGui.QDialogButtonBox.RejectRole) self.connect(self.buttonbox, QtCore.SIGNAL("accepted()"), self.do_accept) self.connect(self.buttonbox, QtCore.SIGNAL("rejected()"), self.do_reject) self.closeButton.setHidden(True) # but 'close' starts as hidden. self.infowidget = WarningInfoWidget(self) self.infowidget.hide() QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessStarted(bool)"), self.infowidget, QtCore.SLOT("setHidden(bool)")) if immediate: self.do_accept() def make_default_status_box(self): panel = QtGui.QWidget() vbox = QtGui.QVBoxLayout(panel) vbox.setContentsMargins(0, 0, 0, 0) vbox.addWidget(self.infowidget) status_group_box = QtGui.QGroupBox(gettext("Status")) status_layout = QtGui.QVBoxLayout(status_group_box) status_layout.setContentsMargins(0, 0, 0, 0) status_layout.addWidget(self.process_widget) vbox.addWidget(status_group_box) return panel def make_process_panel(self): panel = QtGui.QWidget() vbox = QtGui.QVBoxLayout(panel) vbox.setContentsMargins(0, 0, 0, 0) vbox.addWidget(self.infowidget) vbox.addWidget(self.process_widget) return panel def make_default_layout_widgets(self): """Yields widgets to add to main dialog layout: status and button boxes. Status box has progress bar and console area. Button box has 2 buttons: OK and Cancel (after successfull command execution there will be Close and Cancel). """ yield self.make_default_status_box() yield self.buttonbox def validate(self): """Override this method in your class and do any validation there. Return True if all parameters is OK and subprocess can be started. """ return True def _check_args(self): """Check that self.args is not None and return True. Otherwise show error dialog to the user and return False. """ if self.args is None: raise RuntimeError('Subprocess action "%s" cannot be started\n' 'because self.args is None.' % self._name) return True def do_accept(self): if self.process_widget.finished: self.close() else: try: if not self.validate(): return except: report_exception(type=SUB_LOAD_METHOD, window=self.window()) return self.emit(QtCore.SIGNAL("subprocessStarted(bool)"), True) self.emit(QtCore.SIGNAL("disableUi(bool)"), True) self.do_start() def do_start(self): if self._check_args(): self.process_widget.do_start(self.dir, *self.args) else: self.on_failed('CheckArgsFailed') def do_reject(self): if self.process_widget.is_running(): self.process_widget.abort() else: self.close() def on_finished(self): if hasattr(self, 'setResult'): self.setResult(QtGui.QDialog.Accepted) self.emit(QtCore.SIGNAL("subprocessFinished(bool)"), True) self.emit(QtCore.SIGNAL("disableUi(bool)"), False) self.return_code = 0 if not self.ui_mode and not self.infowidget.isVisible(): self.close() def on_conflicted(self, tree_path): if tree_path: self.action_url = unicode(tree_path) # QString -> unicode self.infowidget.setup_for_conflicted(self.open_conflicts_win, self.open_revert_win) self.infowidget.show() def on_failed(self, error): self.emit(QtCore.SIGNAL("subprocessFailed(bool)"), False) self.emit(QtCore.SIGNAL("disableUi(bool)"), False) if error == 'UncommittedChanges': self.action_url = self.process_widget.error_data['display_url'] self.infowidget.setup_for_uncommitted(self.open_commit_win, self.open_revert_win, self.open_shelve_win) self.infowidget.show() elif error == 'LockContention': self.infowidget.setup_for_locked(self.do_accept) self.infowidget.show() def on_error(self): self.emit(QtCore.SIGNAL("subprocessError(bool)"), False) def setupUi(self, ui): ui.setupUi(self) if self._restore_size: self.resize(self._restore_size) def open_commit_win(self, b): # XXX refactor so that the tree can be opened by the window tree, branch = BzrDir.open_tree_or_branch(self.action_url) commit_window = CommitWindow(tree, None, parent=self) self.windows.append(commit_window) commit_window.show() def open_revert_win(self, b): # XXX refactor so that the tree can be opened by the window tree, branch = BzrDir.open_tree_or_branch(self.action_url) revert_window = RevertWindow(tree, None, parent=self) self.windows.append(revert_window) revert_window.show() def open_shelve_win(self, b): shelve_window = ShelveWindow(directory=self.action_url, parent=self) self.windows.append(shelve_window) shelve_window.show() def open_conflicts_win(self, b): window = ConflictsWindow(self.action_url, parent=self) self.windows.append(window) window.show() QtCore.QObject.connect(window, QtCore.SIGNAL("allResolved(bool)"), self.infowidget, QtCore.SLOT("setHidden(bool)"))
def __init_internal__(self, title, name="genericsubprocess", args=None, dir=None, default_size=None, ui_mode=True, dialog=True, parent=None, hide_progress=False, immediate=False): self.restoreSize(name, default_size) self._name = name self._default_size = default_size self.args = args self.dir = dir self.ui_mode = ui_mode self.return_code = 1 if dialog: flags = (self.windowFlags() & ~QtCore.Qt.Window) | QtCore.Qt.Dialog self.setWindowFlags(flags) self.process_widget = SubProcessWidget(self.ui_mode, self, hide_progress) self.connect(self.process_widget, QtCore.SIGNAL("finished()"), self.on_finished) self.connect(self.process_widget, QtCore.SIGNAL("failed(QString)"), self.on_failed) self.connect(self.process_widget, QtCore.SIGNAL("error()"), self.on_error) self.connect(self.process_widget, QtCore.SIGNAL("conflicted(QString)"), self.on_conflicted) self.closeButton = StandardButton(BTN_CLOSE) self.okButton = StandardButton(BTN_OK) self.cancelButton = StandardButton(BTN_CANCEL) # ok button gets disabled when we start. QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessStarted(bool)"), self.okButton, QtCore.SLOT("setDisabled(bool)")) # ok button gets hidden when we finish. QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessFinished(bool)"), self.okButton, QtCore.SLOT("setHidden(bool)")) # close button gets shown when we finish. QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessFinished(bool)"), self.closeButton, QtCore.SLOT("setShown(bool)")) # cancel button gets disabled when finished. QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessFinished(bool)"), self.cancelButton, QtCore.SLOT("setDisabled(bool)")) # ok button gets enabled when we fail. QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessFailed(bool)"), self.okButton, QtCore.SLOT("setDisabled(bool)")) # Change the ok button to 'retry' if we fail. QtCore.QObject.connect( self, QtCore.SIGNAL("subprocessFailed(bool)"), lambda failed: self.okButton.setText(gettext('&Retry'))) self.buttonbox = QtGui.QDialogButtonBox(self) self.buttonbox.addButton(self.okButton, QtGui.QDialogButtonBox.AcceptRole) self.buttonbox.addButton(self.closeButton, QtGui.QDialogButtonBox.AcceptRole) self.buttonbox.addButton(self.cancelButton, QtGui.QDialogButtonBox.RejectRole) self.connect(self.buttonbox, QtCore.SIGNAL("accepted()"), self.do_accept) self.connect(self.buttonbox, QtCore.SIGNAL("rejected()"), self.do_reject) self.closeButton.setHidden(True) # but 'close' starts as hidden. self.infowidget = WarningInfoWidget(self) self.infowidget.hide() QtCore.QObject.connect(self, QtCore.SIGNAL("subprocessStarted(bool)"), self.infowidget, QtCore.SLOT("setHidden(bool)")) if immediate: self.do_accept()
def __init__(self, branch=None, location=None, revision=None, revision_id=None, revision_spec=None, parent=None): if branch: self.branch = branch self.location = url_for_display(branch.base) else: self.branch = None if location is None: location = osutils.getcwd() self.location = location self.workingtree = None self.revision_id = revision_id self.revision_spec = revision_spec self.revision = revision QBzrWindow.__init__(self, [gettext("Browse"), self.location], parent) self.restoreSize("browse", (780, 580)) vbox = QtGui.QVBoxLayout(self.centralwidget) self.throbber = ThrobberWidget(self) vbox.addWidget(self.throbber) hbox = QtGui.QHBoxLayout() hbox.addWidget(QtGui.QLabel(gettext("Location:"))) self.location_edit = QtGui.QLineEdit() self.location_edit.setReadOnly(True) self.location_edit.setText(self.location) hbox.addWidget(self.location_edit, 7) hbox.addWidget(QtGui.QLabel(gettext("Revision:"))) self.revision_edit = QtGui.QLineEdit() self.connect(self.revision_edit, QtCore.SIGNAL("returnPressed()"), self.reload_tree) hbox.addWidget(self.revision_edit, 1) self.show_button = QtGui.QPushButton(gettext("Show")) self.connect(self.show_button, QtCore.SIGNAL("clicked()"), self.reload_tree) hbox.addWidget(self.show_button, 0) self.filter_menu = TreeFilterMenu(self) self.filter_button = QtGui.QPushButton(gettext("&Filter")) self.filter_button.setMenu(self.filter_menu) hbox.addWidget(self.filter_button, 0) self.connect(self.filter_menu, QtCore.SIGNAL("triggered(int, bool)"), self.filter_triggered) vbox.addLayout(hbox) self.file_tree = TreeWidget(self) self.file_tree.throbber = self.throbber vbox.addWidget(self.file_tree) self.filter_menu.set_filters(self.file_tree.tree_filter_model.filters) buttonbox = self.create_button_box(BTN_CLOSE) self.refresh_button = StandardButton(BTN_REFRESH) buttonbox.addButton(self.refresh_button, QtGui.QDialogButtonBox.ActionRole) self.connect(self.refresh_button, QtCore.SIGNAL("clicked()"), self.file_tree.refresh) self.diffbuttons = DiffButtons(self.centralwidget) self.connect(self.diffbuttons, QtCore.SIGNAL("triggered(QString)"), self.file_tree.show_differences) hbox = QtGui.QHBoxLayout() hbox.addWidget(self.diffbuttons) hbox.addWidget(buttonbox) vbox.addLayout(hbox) self.windows = [] self.file_tree.setFocus() # set focus so keyboard navigation will work from the beginning
def __init__(self, locations=None, branch=None, tree=None, specific_file_ids=None, parent=None, ui_mode=True, no_graph=False, show_trees=False): """Create qlog window. Note: you must use either locations or branch+tree+specific_file_id arguments, but not both. @param locations: list of locations to show log (either list of URL/paths for several branches, or list of filenames from one branch). This list used when branch argument is None. @param branch: branch object to show the log. Could be None, in this case locations list will be used to open branch(es). @param specific_file_ids: file ids from the branch to filter the log. @param parent: parent widget. @param ui_mode: for compatibility with TortoiseBzr. @param no_graph: don't show the graph of revisions (make sense for `bzr qlog FILE` to force plain log a-la `bzr log`). """ self.title = gettext("Log") QBzrWindow.__init__(self, [self.title], parent, ui_mode=ui_mode) self.restoreSize("log", (710, 580)) self.no_graph = no_graph self.show_trees = show_trees if branch: self.branch = branch self.tree = tree self.locations = (branch, ) self.specific_file_ids = specific_file_ids assert locations is None, "can't specify both branch and locations" else: self.branch = None self.locations = locations #if self.locations is None: # self.locations = [u"."] assert specific_file_ids is None, "specific_file_ids is ignored if branch is None" self.branches = None self.replace = {} self.throbber = ThrobberWidget(self) logwidget = QtGui.QWidget() logbox = QtGui.QVBoxLayout(logwidget) logbox.setContentsMargins(0, 0, 0, 0) searchbox = QtGui.QHBoxLayout() self.search_label = QtGui.QLabel(gettext("&Search:")) self.search_edit = QtGui.QLineEdit() self.search_label.setBuddy(self.search_edit) self.connect(self.search_edit, QtCore.SIGNAL("textEdited(QString)"), self.set_search_timer) self.search_timer = QtCore.QTimer(self) self.search_timer.setSingleShot(True) self.connect(self.search_timer, QtCore.SIGNAL("timeout()"), self.update_search) searchbox.addWidget(self.search_label) searchbox.addWidget(self.search_edit) self.searchType = QtGui.QComboBox() self.searchType.addItem(gettext("Messages"), QtCore.QVariant(self.FilterMessageRole)) self.searchType.addItem(gettext("Authors"), QtCore.QVariant(self.FilterAuthorRole)) self.searchType.addItem(gettext("Revision IDs"), QtCore.QVariant(self.FilterIdRole)) self.searchType.addItem(gettext("Revision Numbers"), QtCore.QVariant(self.FilterRevnoRole)) self.searchType.addItem(gettext("Tags"), QtCore.QVariant(self.FilterTagRole)) self.searchType.addItem(gettext("Bugs"), QtCore.QVariant(self.FilterBugRole)) searchbox.addWidget(self.searchType) self.connect(self.searchType, QtCore.SIGNAL("currentIndexChanged(int)"), self.updateSearchType) logbox.addLayout(searchbox) self.log_list = LogList(self.processEvents, self.throbber, self, action_commands=True) logbox.addWidget(self.throbber) logbox.addWidget(self.log_list) self.current_rev = None self.message = QtGui.QTextDocument() self.message_browser = LogListRevisionMessageBrowser( self.log_list, self) self.message_browser.setDocument(self.message) self.file_list_container = FileListContainer(self.log_list, self) self.connect( self.log_list.selectionModel(), QtCore.SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.file_list_container.revision_selection_changed) hsplitter = QtGui.QSplitter(QtCore.Qt.Horizontal) hsplitter.addWidget(self.message_browser) hsplitter.addWidget(self.file_list_container) hsplitter.setStretchFactor(0, 3) hsplitter.setStretchFactor(1, 1) splitter = QtGui.QSplitter(QtCore.Qt.Vertical) splitter.addWidget(logwidget) splitter.addWidget(hsplitter) splitter.setStretchFactor(0, 5) splitter.setStretchFactor(1, 3) buttonbox = self.create_button_box(BTN_CLOSE) self.refresh_button = StandardButton(BTN_REFRESH) buttonbox.addButton(self.refresh_button, QtGui.QDialogButtonBox.ActionRole) self.connect(self.refresh_button, QtCore.SIGNAL("clicked()"), self.refresh) self.diffbuttons = DiffButtons(self.centralwidget) self.diffbuttons.setEnabled(False) self.connect(self.diffbuttons, QtCore.SIGNAL("triggered(QString)"), self.log_list.show_diff_specified_files_ext) vbox = QtGui.QVBoxLayout(self.centralwidget) vbox.addWidget(splitter) hbox = QtGui.QHBoxLayout() hbox.addWidget(self.diffbuttons) hbox.addWidget(buttonbox) vbox.addLayout(hbox) self.windows = [] # set focus on search edit widget self.log_list.setFocus()
class LogWindow(QBzrWindow): FilterIdRole = QtCore.Qt.UserRole + 100 FilterMessageRole = QtCore.Qt.UserRole + 101 FilterAuthorRole = QtCore.Qt.UserRole + 102 FilterRevnoRole = QtCore.Qt.UserRole + 103 FilterSearchRole = QtCore.Qt.UserRole + 104 FilterTagRole = QtCore.Qt.UserRole + 105 FilterBugRole = QtCore.Qt.UserRole + 106 def __init__(self, locations=None, branch=None, tree=None, specific_file_ids=None, parent=None, ui_mode=True, no_graph=False, show_trees=False): """Create qlog window. Note: you must use either locations or branch+tree+specific_file_id arguments, but not both. @param locations: list of locations to show log (either list of URL/paths for several branches, or list of filenames from one branch). This list used when branch argument is None. @param branch: branch object to show the log. Could be None, in this case locations list will be used to open branch(es). @param specific_file_ids: file ids from the branch to filter the log. @param parent: parent widget. @param ui_mode: for compatibility with TortoiseBzr. @param no_graph: don't show the graph of revisions (make sense for `bzr qlog FILE` to force plain log a-la `bzr log`). """ self.title = gettext("Log") QBzrWindow.__init__(self, [self.title], parent, ui_mode=ui_mode) self.restoreSize("log", (710, 580)) self.no_graph = no_graph self.show_trees = show_trees if branch: self.branch = branch self.tree = tree self.locations = (branch, ) self.specific_file_ids = specific_file_ids assert locations is None, "can't specify both branch and locations" else: self.branch = None self.locations = locations #if self.locations is None: # self.locations = [u"."] assert specific_file_ids is None, "specific_file_ids is ignored if branch is None" self.branches = None self.replace = {} self.throbber = ThrobberWidget(self) logwidget = QtGui.QWidget() logbox = QtGui.QVBoxLayout(logwidget) logbox.setContentsMargins(0, 0, 0, 0) searchbox = QtGui.QHBoxLayout() self.search_label = QtGui.QLabel(gettext("&Search:")) self.search_edit = QtGui.QLineEdit() self.search_label.setBuddy(self.search_edit) self.connect(self.search_edit, QtCore.SIGNAL("textEdited(QString)"), self.set_search_timer) self.search_timer = QtCore.QTimer(self) self.search_timer.setSingleShot(True) self.connect(self.search_timer, QtCore.SIGNAL("timeout()"), self.update_search) searchbox.addWidget(self.search_label) searchbox.addWidget(self.search_edit) self.searchType = QtGui.QComboBox() self.searchType.addItem(gettext("Messages"), QtCore.QVariant(self.FilterMessageRole)) self.searchType.addItem(gettext("Authors"), QtCore.QVariant(self.FilterAuthorRole)) self.searchType.addItem(gettext("Revision IDs"), QtCore.QVariant(self.FilterIdRole)) self.searchType.addItem(gettext("Revision Numbers"), QtCore.QVariant(self.FilterRevnoRole)) self.searchType.addItem(gettext("Tags"), QtCore.QVariant(self.FilterTagRole)) self.searchType.addItem(gettext("Bugs"), QtCore.QVariant(self.FilterBugRole)) searchbox.addWidget(self.searchType) self.connect(self.searchType, QtCore.SIGNAL("currentIndexChanged(int)"), self.updateSearchType) logbox.addLayout(searchbox) self.log_list = LogList(self.processEvents, self.throbber, self, action_commands=True) logbox.addWidget(self.throbber) logbox.addWidget(self.log_list) self.current_rev = None self.message = QtGui.QTextDocument() self.message_browser = LogListRevisionMessageBrowser( self.log_list, self) self.message_browser.setDocument(self.message) self.file_list_container = FileListContainer(self.log_list, self) self.connect( self.log_list.selectionModel(), QtCore.SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.file_list_container.revision_selection_changed) hsplitter = QtGui.QSplitter(QtCore.Qt.Horizontal) hsplitter.addWidget(self.message_browser) hsplitter.addWidget(self.file_list_container) hsplitter.setStretchFactor(0, 3) hsplitter.setStretchFactor(1, 1) splitter = QtGui.QSplitter(QtCore.Qt.Vertical) splitter.addWidget(logwidget) splitter.addWidget(hsplitter) splitter.setStretchFactor(0, 5) splitter.setStretchFactor(1, 3) buttonbox = self.create_button_box(BTN_CLOSE) self.refresh_button = StandardButton(BTN_REFRESH) buttonbox.addButton(self.refresh_button, QtGui.QDialogButtonBox.ActionRole) self.connect(self.refresh_button, QtCore.SIGNAL("clicked()"), self.refresh) self.diffbuttons = DiffButtons(self.centralwidget) self.diffbuttons.setEnabled(False) self.connect(self.diffbuttons, QtCore.SIGNAL("triggered(QString)"), self.log_list.show_diff_specified_files_ext) vbox = QtGui.QVBoxLayout(self.centralwidget) vbox.addWidget(splitter) hbox = QtGui.QHBoxLayout() hbox.addWidget(self.diffbuttons) hbox.addWidget(buttonbox) vbox.addLayout(hbox) self.windows = [] # set focus on search edit widget self.log_list.setFocus() @runs_in_loading_queue @ui_current_widget @reports_exception() def load(self): self.refresh_button.setDisabled(True) self.throbber.show() self.processEvents() try: # Set window title. lt = self._locations_for_title(self.locations) if lt: self.set_title((self.title, lt)) branches, primary_bi, file_ids = self.get_branches_and_file_ids() if self.show_trees: gz_cls = logmodel.WithWorkingTreeGraphVizLoader else: gz_cls = logmodel.GraphVizLoader self.log_list.load(branches, primary_bi, file_ids, self.no_graph, gz_cls) self.connect( self.log_list.selectionModel(), QtCore.SIGNAL( "selectionChanged(QItemSelection, QItemSelection)"), self.update_selection) self.load_search_indexes(branches) finally: self.refresh_button.setDisabled(False) self.throbber.hide() def get_branches_and_file_ids(self): if self.branch: if self.tree is None: try: self.tree = self.branch.bzrdir.open_workingtree() except (errors.NoWorkingTree, errors.NotLocalUrl): pass label = self.branch_label(None, self.branch) bi = BranchInfo(label, self.tree, self.branch) return [bi], bi, self.specific_file_ids else: primary_bi = None branches = set() file_ids = [] if self.locations is not None: locations = self.locations else: locations = [u'.'] # Branch names that indicated primary branch. # TODO: Make config option. primary_branch_names = ('trunk', 'bzr.dev') for location in locations: tree, br, repo, fp = \ BzrDir.open_containing_tree_branch_or_repository(location) self.processEvents() if br is None: if fp: raise errors.NotBranchError(fp) repo_branches = repo.find_branches(using=True) if not repo_branches: raise errors.NotBranchError(fp) for br in repo_branches: self.processEvents() try: tree = br.bzrdir.open_workingtree() self.processEvents() except errors.NoWorkingTree: tree = None label = self.branch_label(None, br, location, repo) bi = BranchInfo(label, tree, br) branches.add(bi) if not primary_bi and br.nick in primary_branch_names: primary_bi = bi else: if len(locations) > 1: label = self.branch_label(location, br) else: label = None bi = BranchInfo(label, tree, br) if len(branches) == 0: # The first sepecified branch becomes the primary # branch. primary_bi = bi branches.add(bi) # If no locations were sepecified, don't do fileids # Otherwise it gives you the history for the dir if you are # in a sub dir. if fp != '' and self.locations is None: fp = '' if fp != '': # TODO: Have away to specify a revision to find to file # path in, so that one can show deleted files. if tree is None: tree = br.basis_tree() file_id = tree.path2id(fp) if file_id is None: raise errors.BzrCommandError( "Path does not have any revision history: %s" % location) file_ids.append(file_id) if file_ids and len(branches) > 1: raise errors.BzrCommandError( gettext( 'It is not possible to specify different file paths and ' 'different branches at the same time.')) return tuple(branches), primary_bi, file_ids def load_search_indexes(self, branches): global have_search, search_errors, search_index if have_search is None: have_search = True try: from bzrlib.plugins.search import errors as search_errors from bzrlib.plugins.search import index as search_index except (ImportError, errors.IncompatibleAPI): have_search = False if have_search: indexes_availble = False for bi in branches: try: bi.index = search_index.open_index_branch(bi.branch) indexes_availble = True except (search_errors.NoSearchIndex, errors.IncompatibleAPI): pass if indexes_availble: self.searchType.insertItem( 0, gettext("Messages and File text (indexed)"), QtCore.QVariant(self.FilterSearchRole)) self.searchType.setCurrentIndex(0) self.completer = Compleater(self) self.completer_model = QtGui.QStringListModel(self) self.completer.setModel(self.completer_model) self.search_edit.setCompleter(self.completer) self.connect(self.search_edit, QtCore.SIGNAL("textChanged(QString)"), self.update_search_completer) self.suggestion_letters_loaded = {"": QtCore.QStringList()} self.suggestion_last_first_letter = "" self.connect(self.completer, QtCore.SIGNAL("activated(QString)"), self.set_search_timer) no_usefull_info_in_location_re = re.compile(r'^[.:/\\]*$') def branch_label(self, location, branch, shared_repo_location=None, shared_repo=None): # We should rather use QFontMetrics.elidedText. How do we decide on the # width. def elided_text(text, length=20): if len(text) > length + 3: return text[:length] + '...' return text def elided_path(path): if len(path) > 23: dir, name = split(path) dir = elided_text(dir, 10) name = elided_text(name) return join(dir, name) return path if shared_repo_location and shared_repo and not location: # Once we depend on bzrlib 2.2, this can become .user_url branch_rel = determine_relative_path( shared_repo.bzrdir.root_transport.base, branch.bzrdir.root_transport.base) if shared_repo_location == 'colo:': location = shared_repo_location + branch_rel else: location = join(shared_repo_location, branch_rel) if location is None: return elided_text(branch.nick) has_explicit_nickname = getattr(branch.get_config(), 'has_explicit_nickname', lambda: False)() append_nick = (location.startswith(':') or bool( self.no_usefull_info_in_location_re.match(location)) or has_explicit_nickname) if append_nick: return '%s (%s)' % (elided_path(location), branch.nick) return elided_text(location) def refresh(self): self.file_list_container.drop_delta_cache_with_wt() self.replace = {} self.load() def replace_config(self, branch): if branch.base not in self.replace: config = branch.get_config() replace = config.get_user_option("qlog_replace") if replace: replace = replace.split("\n") replace = [ tuple(replace[2 * i:2 * i + 2]) for i in range(len(replace) // 2) ] self.replace[branch.base] = replace return self.replace[branch.base] def show(self): # we show the bare form as soon as possible. QBzrWindow.show(self) QtCore.QTimer.singleShot(0, self.load) def update_selection(self, selected, deselected): indexes = self.log_list.get_selection_indexes() if not indexes: self.diffbuttons.setEnabled(False) else: self.diffbuttons.setEnabled(True) @ui_current_widget def update_search(self): # TODO in_paths = self.search_in_paths.isChecked() gv = self.log_list.log_model.graph_viz role = self.searchType.itemData( self.searchType.currentIndex()).toInt()[0] search_text = unicode(self.search_edit.text()) if search_text == u"": self.log_list.set_search(None, None) elif role == self.FilterIdRole: self.log_list.set_search(None, None) self.log_list.select_revid(search_text) elif role == self.FilterRevnoRole: self.log_list.set_search(None, None) try: revno = tuple( (int(number) for number in search_text.split('.'))) except ValueError: revno = () # Not sure what to do if there is an error. Nothing for now if revno in gv.revno_rev: rev = gv.revno_rev[revno] index = self.log_list.log_model.index_from_rev(rev) self.log_list.setCurrentIndex(index) else: if role == self.FilterMessageRole: field = "message" elif role == self.FilterAuthorRole: field = "author" elif role == self.FilterSearchRole: field = "index" elif role == self.FilterTagRole: field = 'tag' elif role == self.FilterBugRole: field = 'bug' else: raise Exception("Not done") self.log_list.set_search(search_text, field) self.log_list.scrollTo(self.log_list.currentIndex()) # Scroll to ensure the selection is on screen. @ui_current_widget def update_search_completer(self, text): gv = self.log_list.log_model.graph_viz # We only load the suggestions a letter at a time when needed. term = unicode(text).split(" ")[-1] if term: first_letter = term[0] else: first_letter = "" if first_letter != self.suggestion_last_first_letter: self.suggestion_last_first_letter = first_letter if first_letter not in self.suggestion_letters_loaded: suggestions = set() indexes = [ bi.index for bi in gv.branches if bi.index is not None ] for index in indexes: for s in index.suggest(((first_letter, ), )): #if suggestions.count() % 100 == 0: # QtCore.QCoreApplication.processEvents() suggestions.add(s[0]) suggestions = QtCore.QStringList(list(suggestions)) suggestions.sort() self.suggestion_letters_loaded[first_letter] = suggestions else: suggestions = self.suggestion_letters_loaded[first_letter] self.completer_model.setStringList(suggestions) def updateSearchType(self, index=None): self.update_search() def set_search_timer(self): self.search_timer.start(200) def _locations_for_title(self, locations): if locations is None: return osutils.getcwd() else: from bzrlib.branch import Branch def title_for_location(location): if isinstance(location, basestring): return url_for_display(location) if isinstance(location, Branch): return url_for_display(location.base) return str(location) return ", ".join(title_for_location(l) for l in locations)
def __init__(self, tree, selected_list, dialog=True, parent=None, local=None, message=None, ui_mode=True): super(CommitWindow, self).__init__(gettext("Commit"), name="commit", default_size=(540, 540), ui_mode=ui_mode, dialog=dialog, parent=parent) self.tree = tree self.ci_data = QBzrCommitData(tree=tree) self.ci_data.load() self.is_bound = bool(tree.branch.get_bound_location()) self.has_pending_merges = len(tree.get_parent_ids()) > 1 if self.has_pending_merges and selected_list: raise errors.CannotCommitSelectedFileMerge(selected_list) self.windows = [] self.initial_selected_list = selected_list self.connect(self.process_widget, QtCore.SIGNAL("failed(QString)"), self.on_failed) self.throbber = ThrobberWidget(self) # commit to branch location branch_groupbox = QtGui.QGroupBox(gettext("Branch"), self) branch_layout = QtGui.QGridLayout(branch_groupbox) self.branch_location = QtGui.QLineEdit() self.branch_location.setReadOnly(True) # branch_base = url_for_display(tree.branch.base) master_branch = url_for_display(tree.branch.get_bound_location()) if not master_branch: self.branch_location.setText(branch_base) branch_layout.addWidget(self.branch_location, 0, 0, 1, 2) else: self.local_checkbox = QtGui.QCheckBox(gettext("&Local commit")) self.local_checkbox.setToolTip( gettext("Local commits are not pushed to the master branch " "until a normal commit is performed")) branch_layout.addWidget(self.local_checkbox, 0, 0, 1, 2) branch_layout.addWidget(self.branch_location, 1, 0, 1, 2) branch_layout.addWidget(QtGui.QLabel(gettext('Description:')), 2, 0, QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) self.commit_type_description = QtGui.QLabel() self.commit_type_description.setWordWrap(True) branch_layout.addWidget(self.commit_type_description, 2, 1) branch_layout.setColumnStretch(1, 10) self.connect(self.local_checkbox, QtCore.SIGNAL("stateChanged(int)"), self.update_branch_groupbox) if local: self.local_checkbox.setChecked(True) self.update_branch_groupbox() self.not_uptodate_errors = { 'BoundBranchOutOfDate': gettext( 'Local branch is out of date with master branch.\n' 'To commit to master branch, update the local branch.\n' 'You can also pass select local to commit to continue working disconnected.' ), 'OutOfDateTree': gettext( 'Working tree is out of date. To commit, update the working tree.' ) } self.not_uptodate_info = InfoWidget(branch_groupbox) not_uptodate_layout = QtGui.QHBoxLayout(self.not_uptodate_info) # XXX this is to big. Resize not_uptodate_icon = QtGui.QLabel() not_uptodate_icon.setPixmap(self.style().standardPixmap( QtGui.QStyle.SP_MessageBoxWarning)) not_uptodate_layout.addWidget(not_uptodate_icon) self.not_uptodate_label = QtGui.QLabel('error message goes here') not_uptodate_layout.addWidget(self.not_uptodate_label, 2) update_button = QtGui.QPushButton(gettext('Update')) self.connect(update_button, QtCore.SIGNAL("clicked(bool)"), self.open_update_win) not_uptodate_layout.addWidget(update_button) self.not_uptodate_info.hide() branch_layout.addWidget(self.not_uptodate_info, 3, 0, 1, 2) splitter = QtGui.QSplitter(QtCore.Qt.Vertical, self) message_groupbox = QtGui.QGroupBox(gettext("Message"), splitter) splitter.addWidget(message_groupbox) self.tabWidget = QtGui.QTabWidget() splitter.addWidget(self.tabWidget) splitter.setStretchFactor(0, 1) splitter.setStretchFactor(1, 8) grid = QtGui.QGridLayout(message_groupbox) self.show_nonversioned_checkbox = QtGui.QCheckBox( gettext("Show non-versioned files")) show_nonversioned = get_qbzr_config().get_option_as_bool( self._window_name + "_show_nonversioned") if show_nonversioned: self.show_nonversioned_checkbox.setChecked(QtCore.Qt.Checked) else: self.show_nonversioned_checkbox.setChecked(QtCore.Qt.Unchecked) self.filelist = TreeWidget(self) self.filelist.throbber = self.throbber if show_nonversioned: self.filelist.tree_model.set_select_all_kind('all') else: self.filelist.tree_model.set_select_all_kind('versioned') self.file_words = {} self.connect(self.filelist.tree_model, QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.on_filelist_data_changed) self.selectall_checkbox = SelectAllCheckBox(self.filelist, self) self.selectall_checkbox.setCheckState(QtCore.Qt.Checked) language = get_global_config().get_user_option( 'spellcheck_language') or 'en' spell_checker = SpellChecker(language) # Equivalent for 'bzr commit --message' self.message = TextEdit(spell_checker, message_groupbox, main_window=self) self.message.setToolTip(gettext("Enter the commit message")) self.connect(self.message, QtCore.SIGNAL("messageEntered()"), self.do_accept) self.completer = QtGui.QCompleter() self.completer_model = QtGui.QStringListModel(self.completer) self.completer.setModel(self.completer_model) self.message.setCompleter(self.completer) self.message.setAcceptRichText(False) SpellCheckHighlighter(self.message.document(), spell_checker) grid.addWidget(self.message, 0, 0, 1, 2) # Equivalent for 'bzr commit --fixes' self.bugsCheckBox = QtGui.QCheckBox(gettext("&Fixed bugs:")) self.bugsCheckBox.setToolTip( gettext("Set the IDs of bugs fixed by " "this commit")) self.bugs = QtGui.QLineEdit() self.bugs.setToolTip( gettext("Enter the list of bug IDs in format " "<i>tag:id</i> separated by a space, " "e.g. <i>project:123 project:765</i>")) self.bugs.setEnabled(False) self.connect(self.bugsCheckBox, QtCore.SIGNAL("stateChanged(int)"), self.enableBugs) grid.addWidget(self.bugsCheckBox, 1, 0) grid.addWidget(self.bugs, 1, 1) # Equivalent for 'bzr commit --author' self.authorCheckBox = QtGui.QCheckBox(gettext("&Author:")) self.authorCheckBox.setToolTip( gettext("Set the author of this change," " if it's different from the committer")) self.author = QtGui.QLineEdit() self.author.setToolTip( gettext("Enter the author's name, " "e.g. <i>John Doe <[email protected]></i>")) self.author.setEnabled(False) self.connect(self.authorCheckBox, QtCore.SIGNAL("stateChanged(int)"), self.enableAuthor) grid.addWidget(self.authorCheckBox, 2, 0) grid.addWidget(self.author, 2, 1) # default author from config config = self.tree.branch.get_config() self.default_author = config.username() self.custom_author = '' self.author.setText(self.default_author) # Display the list of changed files files_tab = QtGui.QWidget() self.tabWidget.addTab(files_tab, gettext("Changes")) vbox = QtGui.QVBoxLayout(files_tab) vbox.addWidget(self.filelist) self.connect(self.show_nonversioned_checkbox, QtCore.SIGNAL("toggled(bool)"), self.show_nonversioned) vbox.addWidget(self.show_nonversioned_checkbox) vbox.addWidget(self.selectall_checkbox) # Display a list of pending merges if self.has_pending_merges: self.selectall_checkbox.setCheckState(QtCore.Qt.Checked) self.selectall_checkbox.setEnabled(False) self.pending_merges_list = PendingMergesList( self.processEvents, self.throbber, self) self.tabWidget.addTab(self.pending_merges_list, gettext("Pending Merges")) self.tabWidget.setCurrentWidget(self.pending_merges_list) # Pending-merge widget gets disabled as we are executing. QtCore.QObject.connect(self, QtCore.SIGNAL("disableUi(bool)"), self.pending_merges_list, QtCore.SLOT("setDisabled(bool)")) else: self.pending_merges_list = False self.process_panel = self.make_process_panel() self.tabWidget.addTab(self.process_panel, gettext("Status")) splitter.setStretchFactor(0, 3) vbox = QtGui.QVBoxLayout(self) vbox.addWidget(self.throbber) vbox.addWidget(branch_groupbox) vbox.addWidget(splitter) # Diff button to view changes in files selected to commit self.diffbuttons = DiffButtons(self) self.diffbuttons.setToolTip( gettext("View changes in files selected to commit")) self.connect(self.diffbuttons, QtCore.SIGNAL("triggered(QString)"), self.show_diff_for_checked) self.refresh_button = StandardButton(BTN_REFRESH) self.connect(self.refresh_button, QtCore.SIGNAL("clicked()"), self.refresh) hbox = QtGui.QHBoxLayout() hbox.addWidget(self.diffbuttons) hbox.addWidget(self.refresh_button) hbox.addWidget(self.buttonbox) vbox.addLayout(hbox) # groupbox and tabbox signals handling. for w in (message_groupbox, files_tab): # when operation started we need to disable widgets QtCore.QObject.connect(self, QtCore.SIGNAL("disableUi(bool)"), w, QtCore.SLOT("setDisabled(bool)")) self.restore_commit_data() if message: self.message.setText(message) # Try to be smart: if there is no saved message # then set focus on Edit Area; otherwise on OK button. if unicode(self.message.toPlainText()).strip(): self.buttonbox.setFocus() else: self.message.setFocus()
class CommitWindow(SubProcessDialog): RevisionIdRole = QtCore.Qt.UserRole + 1 ParentIdRole = QtCore.Qt.UserRole + 2 def __init__(self, tree, selected_list, dialog=True, parent=None, local=None, message=None, ui_mode=True): super(CommitWindow, self).__init__(gettext("Commit"), name="commit", default_size=(540, 540), ui_mode=ui_mode, dialog=dialog, parent=parent) self.tree = tree self.ci_data = QBzrCommitData(tree=tree) self.ci_data.load() self.is_bound = bool(tree.branch.get_bound_location()) self.has_pending_merges = len(tree.get_parent_ids()) > 1 if self.has_pending_merges and selected_list: raise errors.CannotCommitSelectedFileMerge(selected_list) self.windows = [] self.initial_selected_list = selected_list self.connect(self.process_widget, QtCore.SIGNAL("failed(QString)"), self.on_failed) self.throbber = ThrobberWidget(self) # commit to branch location branch_groupbox = QtGui.QGroupBox(gettext("Branch"), self) branch_layout = QtGui.QGridLayout(branch_groupbox) self.branch_location = QtGui.QLineEdit() self.branch_location.setReadOnly(True) # branch_base = url_for_display(tree.branch.base) master_branch = url_for_display(tree.branch.get_bound_location()) if not master_branch: self.branch_location.setText(branch_base) branch_layout.addWidget(self.branch_location, 0, 0, 1, 2) else: self.local_checkbox = QtGui.QCheckBox(gettext("&Local commit")) self.local_checkbox.setToolTip( gettext("Local commits are not pushed to the master branch " "until a normal commit is performed")) branch_layout.addWidget(self.local_checkbox, 0, 0, 1, 2) branch_layout.addWidget(self.branch_location, 1, 0, 1, 2) branch_layout.addWidget(QtGui.QLabel(gettext('Description:')), 2, 0, QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) self.commit_type_description = QtGui.QLabel() self.commit_type_description.setWordWrap(True) branch_layout.addWidget(self.commit_type_description, 2, 1) branch_layout.setColumnStretch(1, 10) self.connect(self.local_checkbox, QtCore.SIGNAL("stateChanged(int)"), self.update_branch_groupbox) if local: self.local_checkbox.setChecked(True) self.update_branch_groupbox() self.not_uptodate_errors = { 'BoundBranchOutOfDate': gettext( 'Local branch is out of date with master branch.\n' 'To commit to master branch, update the local branch.\n' 'You can also pass select local to commit to continue working disconnected.' ), 'OutOfDateTree': gettext( 'Working tree is out of date. To commit, update the working tree.' ) } self.not_uptodate_info = InfoWidget(branch_groupbox) not_uptodate_layout = QtGui.QHBoxLayout(self.not_uptodate_info) # XXX this is to big. Resize not_uptodate_icon = QtGui.QLabel() not_uptodate_icon.setPixmap(self.style().standardPixmap( QtGui.QStyle.SP_MessageBoxWarning)) not_uptodate_layout.addWidget(not_uptodate_icon) self.not_uptodate_label = QtGui.QLabel('error message goes here') not_uptodate_layout.addWidget(self.not_uptodate_label, 2) update_button = QtGui.QPushButton(gettext('Update')) self.connect(update_button, QtCore.SIGNAL("clicked(bool)"), self.open_update_win) not_uptodate_layout.addWidget(update_button) self.not_uptodate_info.hide() branch_layout.addWidget(self.not_uptodate_info, 3, 0, 1, 2) splitter = QtGui.QSplitter(QtCore.Qt.Vertical, self) message_groupbox = QtGui.QGroupBox(gettext("Message"), splitter) splitter.addWidget(message_groupbox) self.tabWidget = QtGui.QTabWidget() splitter.addWidget(self.tabWidget) splitter.setStretchFactor(0, 1) splitter.setStretchFactor(1, 8) grid = QtGui.QGridLayout(message_groupbox) self.show_nonversioned_checkbox = QtGui.QCheckBox( gettext("Show non-versioned files")) show_nonversioned = get_qbzr_config().get_option_as_bool( self._window_name + "_show_nonversioned") if show_nonversioned: self.show_nonversioned_checkbox.setChecked(QtCore.Qt.Checked) else: self.show_nonversioned_checkbox.setChecked(QtCore.Qt.Unchecked) self.filelist = TreeWidget(self) self.filelist.throbber = self.throbber if show_nonversioned: self.filelist.tree_model.set_select_all_kind('all') else: self.filelist.tree_model.set_select_all_kind('versioned') self.file_words = {} self.connect(self.filelist.tree_model, QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.on_filelist_data_changed) self.selectall_checkbox = SelectAllCheckBox(self.filelist, self) self.selectall_checkbox.setCheckState(QtCore.Qt.Checked) language = get_global_config().get_user_option( 'spellcheck_language') or 'en' spell_checker = SpellChecker(language) # Equivalent for 'bzr commit --message' self.message = TextEdit(spell_checker, message_groupbox, main_window=self) self.message.setToolTip(gettext("Enter the commit message")) self.connect(self.message, QtCore.SIGNAL("messageEntered()"), self.do_accept) self.completer = QtGui.QCompleter() self.completer_model = QtGui.QStringListModel(self.completer) self.completer.setModel(self.completer_model) self.message.setCompleter(self.completer) self.message.setAcceptRichText(False) SpellCheckHighlighter(self.message.document(), spell_checker) grid.addWidget(self.message, 0, 0, 1, 2) # Equivalent for 'bzr commit --fixes' self.bugsCheckBox = QtGui.QCheckBox(gettext("&Fixed bugs:")) self.bugsCheckBox.setToolTip( gettext("Set the IDs of bugs fixed by " "this commit")) self.bugs = QtGui.QLineEdit() self.bugs.setToolTip( gettext("Enter the list of bug IDs in format " "<i>tag:id</i> separated by a space, " "e.g. <i>project:123 project:765</i>")) self.bugs.setEnabled(False) self.connect(self.bugsCheckBox, QtCore.SIGNAL("stateChanged(int)"), self.enableBugs) grid.addWidget(self.bugsCheckBox, 1, 0) grid.addWidget(self.bugs, 1, 1) # Equivalent for 'bzr commit --author' self.authorCheckBox = QtGui.QCheckBox(gettext("&Author:")) self.authorCheckBox.setToolTip( gettext("Set the author of this change," " if it's different from the committer")) self.author = QtGui.QLineEdit() self.author.setToolTip( gettext("Enter the author's name, " "e.g. <i>John Doe <[email protected]></i>")) self.author.setEnabled(False) self.connect(self.authorCheckBox, QtCore.SIGNAL("stateChanged(int)"), self.enableAuthor) grid.addWidget(self.authorCheckBox, 2, 0) grid.addWidget(self.author, 2, 1) # default author from config config = self.tree.branch.get_config() self.default_author = config.username() self.custom_author = '' self.author.setText(self.default_author) # Display the list of changed files files_tab = QtGui.QWidget() self.tabWidget.addTab(files_tab, gettext("Changes")) vbox = QtGui.QVBoxLayout(files_tab) vbox.addWidget(self.filelist) self.connect(self.show_nonversioned_checkbox, QtCore.SIGNAL("toggled(bool)"), self.show_nonversioned) vbox.addWidget(self.show_nonversioned_checkbox) vbox.addWidget(self.selectall_checkbox) # Display a list of pending merges if self.has_pending_merges: self.selectall_checkbox.setCheckState(QtCore.Qt.Checked) self.selectall_checkbox.setEnabled(False) self.pending_merges_list = PendingMergesList( self.processEvents, self.throbber, self) self.tabWidget.addTab(self.pending_merges_list, gettext("Pending Merges")) self.tabWidget.setCurrentWidget(self.pending_merges_list) # Pending-merge widget gets disabled as we are executing. QtCore.QObject.connect(self, QtCore.SIGNAL("disableUi(bool)"), self.pending_merges_list, QtCore.SLOT("setDisabled(bool)")) else: self.pending_merges_list = False self.process_panel = self.make_process_panel() self.tabWidget.addTab(self.process_panel, gettext("Status")) splitter.setStretchFactor(0, 3) vbox = QtGui.QVBoxLayout(self) vbox.addWidget(self.throbber) vbox.addWidget(branch_groupbox) vbox.addWidget(splitter) # Diff button to view changes in files selected to commit self.diffbuttons = DiffButtons(self) self.diffbuttons.setToolTip( gettext("View changes in files selected to commit")) self.connect(self.diffbuttons, QtCore.SIGNAL("triggered(QString)"), self.show_diff_for_checked) self.refresh_button = StandardButton(BTN_REFRESH) self.connect(self.refresh_button, QtCore.SIGNAL("clicked()"), self.refresh) hbox = QtGui.QHBoxLayout() hbox.addWidget(self.diffbuttons) hbox.addWidget(self.refresh_button) hbox.addWidget(self.buttonbox) vbox.addLayout(hbox) # groupbox and tabbox signals handling. for w in (message_groupbox, files_tab): # when operation started we need to disable widgets QtCore.QObject.connect(self, QtCore.SIGNAL("disableUi(bool)"), w, QtCore.SLOT("setDisabled(bool)")) self.restore_commit_data() if message: self.message.setText(message) # Try to be smart: if there is no saved message # then set focus on Edit Area; otherwise on OK button. if unicode(self.message.toPlainText()).strip(): self.buttonbox.setFocus() else: self.message.setFocus() def show(self): # we show the bare form as soon as possible. SubProcessDialog.show(self) QtCore.QTimer.singleShot(1, self.load) def exec_(self): QtCore.QTimer.singleShot(1, self.load) return SubProcessDialog.exec_(self) @runs_in_loading_queue @ui_current_widget @reports_exception() def load(self, refresh=False): if refresh: self.throbber.show() self.refresh_button.setDisabled(True) try: self.tree.lock_read() try: if self.pending_merges_list: self.pending_merges_list.load_tree(self.tree) # Force the loading of the revisions, before we start # loading the file list. self.pending_merges_list._load_visible_revisions() self.processEvents() self.filelist.tree_model.checkable = not self.pending_merges_list self.is_loading = True # XXX Would be nice if we could only load the files when the # user clicks on the changes tab, but that would mean that # we can't load the words list. if not refresh: fmodel = self.filelist.tree_filter_model want_unversioned = self.show_nonversioned_checkbox.isChecked( ) fmodel.setFilter(fmodel.UNVERSIONED, want_unversioned) if not want_unversioned and self.initial_selected_list: # if there are any paths from the command line that # are not versioned, we want_unversioned. for path in self.initial_selected_list: if not self.tree.path2id(path): want_unversioned = True break self.filelist.set_tree( self.tree, branch=self.tree.branch, changes_mode=True, want_unversioned=want_unversioned, initial_checked_paths=self.initial_selected_list, change_load_filter=lambda c: not c.is_ignored()) else: self.filelist.refresh() self.is_loading = False self.processEvents() self.update_compleater_words() finally: self.tree.unlock() finally: self.throbber.hide() self.refresh_button.setDisabled(False) def refresh(self): self.load(True) def on_filelist_data_changed(self, start_index, end_index): self.update_compleater_words() def update_compleater_words(self): if self.is_loading: return num_files_loaded = 0 words = set() for ref in self.filelist.tree_model.iter_checked(): path = ref.path if path not in self.file_words: file_words = set() if num_files_loaded < MAX_AUTOCOMPLETE_FILES: file_words.add(path) file_words.add(os.path.split(path)[-1]) change = self.filelist.tree_model.inventory_data_by_path[ ref.path].change if change and change.is_renamed(): file_words.add(change.oldpath()) file_words.add(os.path.split(change.oldpath())[-1]) #if num_versioned_files < MAX_AUTOCOMPLETE_FILES: ext = file_extension(path) builder = get_wordlist_builder(ext) if builder is not None: try: abspath = os.path.join(self.tree.basedir, path) file = open(abspath, 'rt') file_words.update(builder.iter_words(file)) self.processEvents() except EnvironmentError: pass self.file_words[path] = file_words num_files_loaded += 1 else: file_words = self.file_words[path] words.update(file_words) words = list(words) words.sort(key=lambda x: x.lower()) self.completer_model.setStringList(words) def enableBugs(self, state): if state == QtCore.Qt.Checked: self.bugs.setEnabled(True) self.bugs.setFocus(QtCore.Qt.OtherFocusReason) else: self.bugs.setEnabled(False) def enableAuthor(self, state): if state == QtCore.Qt.Checked: self.author.setEnabled(True) self.author.setText(self.custom_author) self.author.setFocus(QtCore.Qt.OtherFocusReason) else: self.author.setEnabled(False) self.custom_author = self.author.text() self.author.setText(self.default_author) def restore_commit_data(self): message = self.ci_data['message'] if message: self.message.setText(message) bug = self.ci_data['bugs'] if bug: self.bugs.setText(bug) self.bugs.setEnabled(True) self.bugsCheckBox.setChecked(True) def save_commit_data(self): if (self.tree.branch.get_physical_lock_status() or self.tree.branch.is_locked()): # XXX maybe show this in a GUI MessageBox (information box)??? from bzrlib.trace import warning warning("Cannot save commit data because the branch is locked.") return # collect data ci_data = QBzrCommitData(tree=self.tree) message = unicode(self.message.toPlainText()).strip() if message: ci_data['message'] = message bug_str = '' if self.bugsCheckBox.isChecked(): bug_str = unicode(self.bugs.text()).strip() if bug_str: ci_data['bugs'] = bug_str # save only if data different if not ci_data.compare_data(self.ci_data, all_keys=False): ci_data.save() def wipe_commit_data(self): if (self.tree.branch.get_physical_lock_status() or self.tree.branch.is_locked()): # XXX maybe show this in a GUI MessageBox (information box)??? from bzrlib.trace import warning warning("Cannot wipe commit data because the branch is locked.") return self.ci_data.wipe() def _get_message(self): return unicode(self.message.toPlainText()).strip() def _get_selected_files(self): """Return (has_files_to_commit[bool], files_to_commit[list], files_to_add[list])""" if self.has_pending_merges: return True, [], [] files_to_commit = [] files_to_add = [] for ref in self.filelist.tree_model.iter_checked(): if ref.file_id is None: files_to_add.append(ref.path) files_to_commit.append(ref.path) if not files_to_commit: return False, [], [] else: return True, files_to_commit, files_to_add def validate(self): if not self._get_message(): self.operation_blocked( gettext("You should provide a commit message.")) self.message.setFocus() return False if not self._get_selected_files()[0]: if not self.ask_confirmation( gettext("No changes selected to commit.\n" "Do you want to commit anyway?")): return False return True def do_start(self): args = ["commit"] message = self._get_message() args.extend(['-m', message]) # keep them separated to avoid bug #297606 has_files_to_commit, files_to_commit, files_to_add = self._get_selected_files( ) if not has_files_to_commit: # Possible [rare] problems: # 1. unicode tree root in non-user encoding # may provoke UnicodeEncodeError in subprocess (@win32) # 2. if branch has no commits yet then operation may fail # because of bug #299879 args.extend(['--exclude', self.tree.basedir]) args.append('--unchanged') else: args.extend(files_to_commit) if self.bugsCheckBox.isChecked(): for s in unicode(self.bugs.text()).split(): args.append(("--fixes=%s" % s)) if self.authorCheckBox.isChecked(): args.append(("--author=%s" % unicode(self.author.text()))) if self.is_bound and self.local_checkbox.isChecked(): args.append("--local") dir = self.tree.basedir commands = [] if files_to_add: commands.append((dir, ["add", "--no-recurse"] + files_to_add)) commands.append((dir, args)) self.tabWidget.setCurrentWidget(self.process_panel) self.process_widget.start_multi(commands) def show_nonversioned(self, state): """Show/hide non-versioned files.""" if state and not self.filelist.want_unversioned: state = self.filelist.get_state() self.filelist.set_tree( self.tree, changes_mode=True, want_unversioned=True, change_load_filter=lambda c: not c.is_ignored()) self.filelist.restore_state(state) if state: self.filelist.tree_model.set_select_all_kind('all') else: self.filelist.tree_model.set_select_all_kind('versioned') fmodel = self.filelist.tree_filter_model fmodel.setFilter(fmodel.UNVERSIONED, state) def _save_or_wipe_commit_data(self): if not self.process_widget.is_running(): if self.process_widget.finished: self.wipe_commit_data() else: self.save_commit_data() def closeEvent(self, event): self._save_or_wipe_commit_data() qbzr_config = get_qbzr_config() qbzr_config.set_option(self._window_name + "_show_nonversioned", self.show_nonversioned_checkbox.isChecked()) qbzr_config.save() # do I need this or is .saveSize() enough? return SubProcessDialog.closeEvent(self, event) def reject(self): self._save_or_wipe_commit_data() return SubProcessDialog.reject(self) def update_branch_groupbox(self): if not self.local_checkbox.isChecked(): # commit to master branch selected loc = url_for_display(self.tree.branch.get_bound_location()) desc = gettext("A commit will be made directly to " "the master branch, keeping the local " "and master branches in sync.") else: # local commit selected loc = url_for_display(self.tree.branch.base) desc = gettext("A local commit to the branch will be performed. " "The master branch will not be updated until " "a non-local commit is made.") # update GUI self.branch_location.setText(loc) self.commit_type_description.setText(desc) def show_diff_for_checked(self, ext_diff=None, dialog_action='commit'): """Diff button clicked: show the diff for checked entries. @param ext_diff: selected external diff tool (if any) @param dialog_action: purpose of parent window (main action) """ # XXX make this function universal for both qcommit and qrevert (?) if self.filelist.tree_model.checkable: checked = [] # checked versioned unversioned = [] # checked unversioned (supposed to be added) for ref in self.filelist.tree_model.iter_checked(): if ref.file_id: checked.append(ref.path) else: unversioned.append(ref.path) if checked: arg_provider = InternalWTDiffArgProvider( self.tree.basis_tree().get_revision_id(), self.tree, self.tree.branch, self.tree.branch, specific_files=checked) show_diff(arg_provider, ext_diff=ext_diff, parent_window=self, context=self.filelist.diff_context) else: msg = "No changes selected to " + dialog_action QtGui.QMessageBox.warning(self, "QBzr - " + gettext("Diff"), gettext(msg), QtGui.QMessageBox.Ok) if unversioned: # XXX show infobox with message that not all files shown in diff pass else: arg_provider = InternalWTDiffArgProvider( self.tree.basis_tree().get_revision_id(), self.tree, self.tree.branch, self.tree.branch) show_diff(arg_provider, ext_diff=ext_diff, parent_window=self, context=self.filelist.diff_context) def on_failed(self, error): SubProcessDialog.on_failed(self, error) error = str(error) if error in self.not_uptodate_errors: self.not_uptodate_label.setText(self.not_uptodate_errors[error]) self.not_uptodate_info.show() def open_update_win(self, b): update_window = QBzrUpdateWindow(self.tree) self.windows.append(update_window) update_window.show() QtCore.QObject.connect(update_window, QtCore.SIGNAL("subprocessFinished(bool)"), self.not_uptodate_info, QtCore.SLOT("setHidden(bool)"))