def __init__(self, acceptable_keys, revision, location, parent=None): """load UI file, add buttons and throbber, run refresh""" QBzrDialog.__init__(self, [gettext("Verify Signatures")], parent) self.restoreSize("verify-signatures", (580, 250)) self.buttonbox = self.create_button_box(BTN_CLOSE) self.ui = Ui_VerifyForm() self.ui.setupUi(self) self.ui.verticalLayout.addWidget(self.buttonbox) self.throbber = ThrobberWidget(self) self.ui.verticalLayout.insertWidget(0, self.throbber) self.acceptable_keys = acceptable_keys self.revision = revision self.location = location QTimer.singleShot(0, self.refresh_view)
def __init__(self, log_list, parent=None): QtWidgets.QWidget.__init__(self, parent) self.log_list = log_list self.throbber = ThrobberWidget(self) self.throbber.hide() self.file_list = QtWidgets.QListWidget() self.file_list.doubleClicked[QtCore.QModelIndex].connect( self.show_diff_files) self.file_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.file_list_context_menu = QtWidgets.QMenu(self) if has_ext_diff(): diff_menu = ExtDiffMenu(self) self.file_list_context_menu.addMenu(diff_menu) diff_menu.triggered['QString'].connect(self.show_diff_files_ext) else: show_diff_action = self.file_list_context_menu.addAction( gettext("Show &differences..."), self.show_diff_files) self.file_list_context_menu.setDefaultAction(show_diff_action) self.file_list_context_menu_annotate = self.file_list_context_menu.addAction( gettext("Annotate"), self.show_file_annotate) self.file_list_context_menu_cat = self.file_list_context_menu.addAction( gettext("View file"), self.show_file_content) self.file_list_context_menu_save_old_file = self.file_list_context_menu.addAction( gettext("Save file on this revision as..."), self.save_old_revision_of_file) self.file_list_context_menu_revert_file = self.file_list_context_menu.addAction( gettext("Revert to this revision"), self.revert_file) self.file_list.customContextMenuRequested[QtCore.QPoint].connect( self.show_file_list_context_menu) vbox = QtWidgets.QVBoxLayout(self) vbox.setContentsMargins(0, 0, 0, 0) vbox.addWidget(self.throbber) vbox.addWidget(self.file_list) self.delta_load_timer = QtCore.QTimer(self) self.delta_load_timer.setSingleShot(True) self.delta_load_timer.timeout.connect(self.load_delta) self.current_revids = None self.tree_cache = {} self.delta_cache = {}
def _test(self, wt): def processEvents(): pass throbber = ThrobberWidget(None) log_model = LogModel(processEvents, throbber) modeltest = ModelTest(log_model, None) bi = BranchInfo('', wt, wt.branch) log_model.load((bi,), bi, None, False, GraphVizLoader)
def __init__(self, filename=None, revision=None, tree=None, file_id=None, encoding=None, parent=None): """Create qcat window.""" self.filename = filename self.revision = revision self.tree = tree if tree: self.branch = getattr(tree, 'branch', None) if self.branch is None: self.branch = FakeBranch() self.file_id = file_id self.encoding = encoding if (not self.filename) and self.tree and self.file_id: self.filename = self.tree.id2path(self.file_id) QBzrWindow.__init__(self, [gettext("View"), self.filename], parent) self.restoreSize("cat", (780, 580)) self.throbber = ThrobberWidget(self) self.buttonbox = self.create_button_box(BTN_CLOSE) self.encoding_selector = self._create_encoding_selector() self.vbox = QtWidgets.QVBoxLayout(self.centralwidget) self.vbox.addWidget(self.throbber) self.vbox.addStretch() hbox = QtWidgets.QHBoxLayout() hbox.addWidget(self.encoding_selector) hbox.addWidget(self.buttonbox) self.vbox.addLayout(hbox)
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 = QtWidgets.QWidget() logbox = QtWidgets.QVBoxLayout(logwidget) logbox.setContentsMargins(0, 0, 0, 0) searchbox = QtWidgets.QHBoxLayout() self.search_label = QtWidgets.QLabel(gettext("&Search:")) self.search_edit = QtWidgets.QLineEdit() self.search_label.setBuddy(self.search_edit) self.search_edit.textEdited['QString'].connect(self.set_search_timer) self.search_timer = QtCore.QTimer(self) self.search_timer.setSingleShot(True) self.search_timer.timeout.connect(self.update_search) searchbox.addWidget(self.search_label) searchbox.addWidget(self.search_edit) self.searchType = QtWidgets.QComboBox() self.searchType.addItem(gettext("Messages"), self.FilterMessageRole) self.searchType.addItem(gettext("Authors"), self.FilterAuthorRole) self.searchType.addItem(gettext("Revision IDs"), self.FilterIdRole) self.searchType.addItem(gettext("Revision Numbers"), self.FilterRevnoRole) self.searchType.addItem(gettext("Tags"), self.FilterTagRole) self.searchType.addItem(gettext("Bugs"), self.FilterBugRole) searchbox.addWidget(self.searchType) self.searchType.currentIndexChanged[int].connect(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.log_list.selectionModel().selectionChanged[ QtCore.QItemSelection, QtCore.QItemSelection].connect( self.file_list_container.revision_selection_changed) hsplitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal) hsplitter.addWidget(self.message_browser) hsplitter.addWidget(self.file_list_container) hsplitter.setStretchFactor(0, 3) hsplitter.setStretchFactor(1, 1) splitter = QtWidgets.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, QtWidgets.QDialogButtonBox.ActionRole) self.refresh_button.clicked.connect(self.refresh) self.diffbuttons = DiffButtons(self.centralwidget) self.diffbuttons.setEnabled(False) self.diffbuttons._triggered['QString'].connect( self.log_list.show_diff_specified_files_ext) vbox = QtWidgets.QVBoxLayout(self.centralwidget) vbox.addWidget(splitter) hbox = QtWidgets.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 = QtWidgets.QWidget() logbox = QtWidgets.QVBoxLayout(logwidget) logbox.setContentsMargins(0, 0, 0, 0) searchbox = QtWidgets.QHBoxLayout() self.search_label = QtWidgets.QLabel(gettext("&Search:")) self.search_edit = QtWidgets.QLineEdit() self.search_label.setBuddy(self.search_edit) self.search_edit.textEdited['QString'].connect(self.set_search_timer) self.search_timer = QtCore.QTimer(self) self.search_timer.setSingleShot(True) self.search_timer.timeout.connect(self.update_search) searchbox.addWidget(self.search_label) searchbox.addWidget(self.search_edit) self.searchType = QtWidgets.QComboBox() self.searchType.addItem(gettext("Messages"), self.FilterMessageRole) self.searchType.addItem(gettext("Authors"), self.FilterAuthorRole) self.searchType.addItem(gettext("Revision IDs"), self.FilterIdRole) self.searchType.addItem(gettext("Revision Numbers"), self.FilterRevnoRole) self.searchType.addItem(gettext("Tags"), self.FilterTagRole) self.searchType.addItem(gettext("Bugs"), self.FilterBugRole) searchbox.addWidget(self.searchType) self.searchType.currentIndexChanged[int].connect(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.log_list.selectionModel().selectionChanged[ QtCore.QItemSelection, QtCore.QItemSelection].connect( self.file_list_container.revision_selection_changed) hsplitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal) hsplitter.addWidget(self.message_browser) hsplitter.addWidget(self.file_list_container) hsplitter.setStretchFactor(0, 3) hsplitter.setStretchFactor(1, 1) splitter = QtWidgets.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, QtWidgets.QDialogButtonBox.ActionRole) self.refresh_button.clicked.connect(self.refresh) self.diffbuttons = DiffButtons(self.centralwidget) self.diffbuttons.setEnabled(False) self.diffbuttons._triggered['QString'].connect( self.log_list.show_diff_specified_files_ext) vbox = QtWidgets.QVBoxLayout(self.centralwidget) vbox.addWidget(splitter) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(self.diffbuttons) hbox.addWidget(buttonbox) vbox.addLayout(hbox) self.windows = [] # set focus on search edit widget self.log_list.setFocus() # RJLRJL removed the loading queue decorator... now it runs like the clappers # ...and actually works. # @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.log_list.selectionModel().selectionChanged[ QtCore.QItemSelection, QtCore.QItemSelection].connect(self.update_selection) self.load_search_indexes(branches) finally: self.refresh_button.setDisabled(False) self.throbber.hide() self.processEvents() def get_branches_and_file_ids(self): if self.branch: if self.tree is None: try: self.tree = self.branch.controldir.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 = ['.'] # Branch names that indicated primary branch. # TODO: Make config option. primary_branch_names = ('trunk', 'bzr.dev') for location in locations: tree, br, repo, fp = ControlDir.open_containing_tree_branch_or_repository( location) self.processEvents() if br is None: if fp: raise errors.NotBranchError(fp) # RJLRJL: bzr-3.1 # * ``Repository.find_branches`` now returns an iterator rather than a # list. (Jelmer Vernooij, #413970) repo_branches = list(repo.find_branches(using=True)) if not repo_branches: raise errors.NotBranchError(fp) for br in repo_branches: self.processEvents() try: tree = br.controldir.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 a way 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 breezy.plugins.search import errors as search_errors from breezy.plugins.search import index as search_index except ImportError: 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)"), self.FilterSearchRole) self.searchType.setCurrentIndex(0) self.completer = Compleater(self) self.completer_model = QStringListModel(self) self.completer.setModel(self.completer_model) self.search_edit.setCompleter(self.completer) self.search_edit.textChanged['QString'].connect( self.update_search_completer) self.suggestion_letters_loaded = {"": QStringList()} self.suggestion_last_first_letter = "" self.completer.activated['QString'].connect( 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 breezy 2.2, this can become .user_url branch_rel = determine_relative_path( shared_repo.controldir.root_transport.base, branch.controldir.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. super().show() # 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 # print('old role', self.searchType.itemData(self.searchType.currentIndex()).toInt()[0], # 'new', self.searchType.itemData(self.searchType.currentIndex()).toInt()) role = self.searchType.itemData(self.searchType.currentIndex()) search_text = str(self.search_edit.text()) if search_text == "": 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 = str(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 = 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 breezy.branch import Branch def title_for_location(location): if isinstance(location, str): 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)
class FileListContainer(QtWidgets.QWidget): def __init__(self, log_list, parent=None): QtWidgets.QWidget.__init__(self, parent) self.log_list = log_list self.throbber = ThrobberWidget(self) self.throbber.hide() self.file_list = QtWidgets.QListWidget() self.file_list.doubleClicked[QtCore.QModelIndex].connect( self.show_diff_files) self.file_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.file_list_context_menu = QtWidgets.QMenu(self) if has_ext_diff(): diff_menu = ExtDiffMenu(self) self.file_list_context_menu.addMenu(diff_menu) diff_menu.triggered['QString'].connect(self.show_diff_files_ext) else: show_diff_action = self.file_list_context_menu.addAction( gettext("Show &differences..."), self.show_diff_files) self.file_list_context_menu.setDefaultAction(show_diff_action) self.file_list_context_menu_annotate = self.file_list_context_menu.addAction( gettext("Annotate"), self.show_file_annotate) self.file_list_context_menu_cat = self.file_list_context_menu.addAction( gettext("View file"), self.show_file_content) self.file_list_context_menu_save_old_file = self.file_list_context_menu.addAction( gettext("Save file on this revision as..."), self.save_old_revision_of_file) self.file_list_context_menu_revert_file = self.file_list_context_menu.addAction( gettext("Revert to this revision"), self.revert_file) self.file_list.customContextMenuRequested[QtCore.QPoint].connect( self.show_file_list_context_menu) vbox = QtWidgets.QVBoxLayout(self) vbox.setContentsMargins(0, 0, 0, 0) vbox.addWidget(self.throbber) vbox.addWidget(self.file_list) self.delta_load_timer = QtCore.QTimer(self) self.delta_load_timer.setSingleShot(True) self.delta_load_timer.timeout.connect(self.load_delta) self.current_revids = None self.tree_cache = {} self.delta_cache = {} def processEvents(self): self.window().processEvents() def revision_selection_changed(self, selected, deselected): revids, count = self.log_list.get_selection_top_and_parent_revids_and_count( ) if revids != self.current_revids: self.file_list.clear() self.current_revids = None self.delta_load_timer.start(200) @runs_in_loading_queue @ui_current_widget def load_delta(self): revids, count = self.log_list.get_selection_top_and_parent_revids_and_count( ) # print('\nload_delta got revids, count', revids, count, self.current_revids) if revids == self.current_revids: return gv = self.log_list.log_model.graph_viz gv_is_wtgv = isinstance(gv, logmodel.WithWorkingTreeGraphVizLoader) if self.log_list.log_model.file_id_filter: specific_file_ids = self.log_list.log_model.file_id_filter.file_ids else: specific_file_ids = [] if not revids or revids == (None, None): return # print('\nself.delta_cache [{0}]\n'.format(self.delta_cache)) if revids not in self.delta_cache: self.throbber.show() try: repos = [ gv.get_revid_branch(revid).repository for revid in revids ] # print('\nTRY worked\n', repos) except GhostRevisionError: delta = None else: if (repos[0].__class__.__name__ == 'SvnRepository' or repos[1].__class__.__name__ == 'SvnRepository'): # Loading trees from a remote svn repo is unusably slow. # See https://bugs.launchpad.net/qbrz/+bug/450225 # If only 1 revision is selected, use a optimized svn method # which actualy gets the server to do the delta, # else, don't do any delta. if count == 1: delta = repos[0].get_revision_delta(revids[0]) else: delta = None else: if (len(repos) == 2 and repos[0].base == repos[1].base): # Both revids are from the same repository. Load together. repos_revids = [(repos[0], revids)] # print('\nBOTH FROM SAME', repos_revids) else: repos_revids = [(repo, [revid]) for revid, repo in zip(revids, repos)] # print('\nBoth different\n'. repos_revids) for repo, repo_revids in repos_revids: repo_revids = [ revid for revid in repo_revids if revid not in self.tree_cache ] if repo_revids: repo.lock_read() self.processEvents() try: for revid in repo_revids: if (revid.startswith(CURRENT_REVISION) and gv_is_wtgv): tree = gv.working_trees[revid] else: tree = repo.revision_tree(revid) self.tree_cache[revid] = tree self.processEvents() finally: repo.unlock() self.processEvents() delta = self.tree_cache[revids[0]].changes_from( self.tree_cache[revids[1]]) # print('\n delta calculated as\n', delta, type(delta)) self.delta_cache[revids] = delta finally: self.throbber.hide() self.processEvents() else: delta = self.delta_cache[revids] new_revids, count = self.log_list.get_selection_top_and_parent_revids_and_count( ) if new_revids != revids: return # Jelmer's commit 7389 states: TreeDelta holds TreeChange objects rather than tuples of various sizes # # It used to be seven lists:-- # # added # (path, id, kind) # removed # (path, id, kind) # renamed # (oldpath, newpath, id, kind, text_modified, meta_modified) # kind_changed # (path, id, old_kind, new_kind) # modified # (path, id, kind, text_modified, meta_modified) # unchanged # (path, id, kind) # unversioned # (path, None, kind) # # Now they are TreeChange objects in the lists added[[, removed[] renamed[], copied[] and modified[] # # self.file_id = file_id # self.path = path # self.changed_content = changed_content # self.versioned = versioned # self.parent_id = parent_id # self.name = name # self.kind = kind # self.executable = executable # if delta: items = [ ] # each item is 6-tuple: (id, path, is_not_specific_file_id, display, color, is_alive) if delta.added: for tree_change in delta.added: items.append((tree_change.file_id, tree_change.path[1], tree_change.file_id not in specific_file_ids, tree_change.path[1], 'blue', True)) if delta.modified: for tree_change in delta.modified: items.append((tree_change.file_id, tree_change.path[0], tree_change.file_id not in specific_file_ids, tree_change.path[0], None, True)) if delta.removed: for tree_change in delta.removed: items.append((tree_change.file_id, tree_change.path[0], tree_change.file_id not in specific_file_ids, tree_change.path[0], 'red', False)) if delta.renamed: for tree_change in delta.renamed: items.append((tree_change.file_id, tree_change.path[0], tree_change.file_id not in specific_file_ids, tree_change.path[1], None, True)) # for (oldpath, newpath, id, kind, text_modified, meta_modified) in delta.renamed: # for tree_change in delta.renamed: # items.append( # (tree_change.file_id, tree_change.path, # tree_change.file_id not in specific_file_ids, # tree_change.path, 'purple', True)) # items.append((id, # newpath, # id not in specific_file_ids, # "%s => %s" % (oldpath, newpath), # "purple", # True)) for (id, path, is_not_specific_file_id, display, color, is_alive) in sorted(items, key=lambda x: (x[2], x[1])): item = QtWidgets.QListWidgetItem(display, self.file_list) item.setData(PathRole, path) item.setData(file_idRole, id) item.setData(AliveRole, is_alive) if color: item.setForeground(QtGui.QColor(color)) if not is_not_specific_file_id: f = item.font() f.setBold(True) item.setFont(f) self.current_revids = revids def drop_delta_cache_with_wt(self): gv = self.log_list.log_model.graph_viz if not isinstance(gv, logmodel.WithWorkingTreeGraphVizLoader): return cache = self.delta_cache keys = [k for k in cache.keys() if k[0].startswith(CURRENT_REVISION)] for key in keys: del (cache[key]) def show_file_list_context_menu(self, pos): (top_revid, old_revid), count = \ self.log_list.get_selection_top_and_parent_revids_and_count() if count == 0: return # XXX - We should also check that the selected file is a file, and # not a dir paths, file_ids, alives = self._get_file_selection_paths_ids_and_alives( ) is_single_file = len(paths) == 1 wt_selected = top_revid.startswith(CURRENT_REVISION) menu_enabled = is_single_file and alives[0] self.file_list_context_menu_annotate.setEnabled(menu_enabled) self.file_list_context_menu_cat.setEnabled(menu_enabled) menu_enabled = is_single_file and alives[0] and not wt_selected self.file_list_context_menu_save_old_file.setEnabled(menu_enabled) self.file_list_context_menu_save_old_file.setVisible(menu_enabled) gv = self.log_list.log_model.graph_viz # It would be nice if there were more than one branch, that we # show a menu so the user can chose which branch actions should take # place in. menu_enabled = (len(gv.branches) == 1 and gv.branches[0].tree is not None and not wt_selected) self.file_list_context_menu_revert_file.setEnabled(menu_enabled) self.file_list_context_menu_revert_file.setVisible(menu_enabled) self.file_list_context_menu.popup( self.file_list.viewport().mapToGlobal(pos)) def get_file_selection_indexes(self, index=None): if index is None: return self.file_list.selectionModel().selectedRows(0) else: return [index] def get_file_selection_paths_and_ids(self, index=None): paths, ids, alives = self._get_file_selection_paths_ids_and_alives( index) return paths, ids def _get_file_selection_paths_ids_and_alives(self, index=None): indexes = self.get_file_selection_indexes(index) # paths = [] ids = [] alives = [] # for index in indexes: item = self.file_list.itemFromIndex(index) paths.append(str(item.data(PathRole))) ids.append(item.data(file_idRole)) alives.append(bool(item.data(AliveRole))) return paths, ids, alives @ui_current_widget def show_diff_files(self, index=None, ext_diff=None): """Show differences of a specific file in a single revision""" paths, ids = self.get_file_selection_paths_and_ids(index) self.log_list.show_diff(specific_files=paths, specific_file_ids=ids, ext_diff=ext_diff) @ui_current_widget def show_diff_files_ext(self, ext_diff=None): """Show differences of a specific file in a single revision""" self.show_diff_files(ext_diff=ext_diff) @runs_in_loading_queue @ui_current_widget def show_file_content(self): """Launch qcat for one selected file.""" paths, file_ids = self.get_file_selection_paths_and_ids() ( top_revid, old_revid ), count = self.log_list.get_selection_top_and_parent_revids_and_count( ) gv = self.log_list.log_model.graph_viz branch = gv.get_revid_branch(top_revid) if top_revid.startswith(CURRENT_REVISION): tree = gv.working_trees[top_revid] else: tree = branch.repository.revision_tree(top_revid) encoding = get_set_encoding(None, branch) window = QBzrCatWindow(filename=paths[0], tree=tree, parent=self, encoding=encoding) window.show() self.window().windows.append(window) @ui_current_widget def save_old_revision_of_file(self): """Saves the selected file in its revision to a directory.""" paths, file_ids = self.get_file_selection_paths_and_ids() ( top_revid, old_revid ), count = self.log_list.get_selection_top_and_parent_revids_and_count( ) branch = self.log_list.log_model.graph_viz.get_revid_branch(top_revid) tree = branch.repository.revision_tree(top_revid) file_id = file_ids[0] path = paths[0] tree.lock_read() try: kind = tree.kind(path) if kind == 'file': file_content_bytes = tree.get_file_text(path) finally: tree.unlock() if kind != 'file': QtWidgets.QMessageBox.information( self, gettext("Not a file"), gettext("Operation is supported for a single file only,\n" "not for a %s." % kind)) return filename = QtWidgets.QFileDialog.getSaveFileName( self, gettext("Save file in this revision as..."))[0] if filename: f = open(str(filename), 'wb') try: f.write(file_content_bytes) finally: f.close() @ui_current_widget def revert_file(self): """Reverts the file to what it was at the selected revision.""" res = QtWidgets.QMessageBox.question( self, gettext("Revert File"), gettext("Are you sure you want to revert this file " "to the state it was at the selected revision?"), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) if res == QtWidgets.QMessageBox.Yes: paths, file_ids = self.get_file_selection_paths_and_ids() ( top_revid, old_revid ), count = self.log_list.get_selection_top_and_parent_revids_and_count( ) gv = self.log_list.log_model.graph_viz assert (len(gv.branches) == 1) branch_info = gv.branches[0] rev_tree = gv.get_revid_repo(top_revid).revision_tree(top_revid) branch_info.tree.revert(paths, old_tree=rev_tree, report_changes=True) @ui_current_widget def show_file_annotate(self): """Show qannotate for selected file.""" paths, file_ids = self.get_file_selection_paths_and_ids() ( top_revid, old_revid ), count = self.log_list.get_selection_top_and_parent_revids_and_count( ) branch_info = self.log_list.log_model.graph_viz.get_revid_branch_info( top_revid) if top_revid.startswith(CURRENT_REVISION): tree = branch_info.tree else: tree = branch_info.branch.repository.revision_tree(top_revid) window = AnnotateWindow(branch_info.branch, branch_info.tree, tree, paths[0], file_ids[0]) window.show() self.window().windows.append(window)
def __init__(self, branch, controldir, location, ui_mode = None): super(QBzrSwitchWindow, self).__init__( gettext("Switch"), name = "switch", default_size = (400, 400), ui_mode = ui_mode, dialog = True, parent = None, hide_progress=False, ) self.branch = branch gbSwitch = QtWidgets.QGroupBox(gettext("Switch checkout"), self) switch_box = QtWidgets.QFormLayout(gbSwitch) branchbase = None boundloc = branch.get_bound_location() if boundloc is not None: label = gettext("Heavyweight checkout:") branchbase = branch.base else: if controldir.root_transport.base != branch.controldir.root_transport.base: label = gettext("Lightweight checkout:") boundloc = branch.controldir.root_transport.base branchbase = controldir.root_transport.base else: raise errors.BzrError("This branch is not checkout.") switch_box.addRow(label, QtWidgets.QLabel(url_for_display(branchbase))) switch_box.addRow(gettext("Checkout of branch:"), QtWidgets.QLabel(url_for_display(boundloc))) self.boundloc = url_for_display(boundloc) throb_hbox = QtWidgets.QHBoxLayout() self.throbber = ThrobberWidget(self) throb_hbox.addWidget(self.throbber) self.throbber.hide() switch_box.addRow(throb_hbox) switch_hbox = QtWidgets.QHBoxLayout() branch_label = QtWidgets.QLabel(gettext("Switch to branch:")) branch_combo = QtWidgets.QComboBox() branch_combo.setEditable(True) self.branch_combo = branch_combo if location is not None: branch_combo.addItem(osutils.abspath(location)) elif boundloc is not None: branch_combo.addItem(url_for_display(boundloc)) browse_button = QtWidgets.QPushButton(gettext("Browse")) browse_button.clicked[bool].connect(self.browse_clicked) switch_hbox.addWidget(branch_label) switch_hbox.addWidget(branch_combo) switch_hbox.addWidget(browse_button) switch_hbox.setStretchFactor(branch_label,0) switch_hbox.setStretchFactor(branch_combo,1) switch_hbox.setStretchFactor(browse_button,0) switch_box.addRow(switch_hbox) create_branch_box = QtWidgets.QCheckBox(gettext("Create Branch before switching")) create_branch_box.setChecked(False) switch_box.addRow(create_branch_box) self.create_branch_box = create_branch_box layout = QtWidgets.QVBoxLayout(self) layout.addWidget(gbSwitch) layout.addWidget(self.make_default_status_box()) layout.addWidget(self.buttonbox) self.branch_combo.setFocus()
class QBzrSwitchWindow(SubProcessDialog): def __init__(self, branch, controldir, location, ui_mode = None): super(QBzrSwitchWindow, self).__init__( gettext("Switch"), name = "switch", default_size = (400, 400), ui_mode = ui_mode, dialog = True, parent = None, hide_progress=False, ) self.branch = branch gbSwitch = QtWidgets.QGroupBox(gettext("Switch checkout"), self) switch_box = QtWidgets.QFormLayout(gbSwitch) branchbase = None boundloc = branch.get_bound_location() if boundloc is not None: label = gettext("Heavyweight checkout:") branchbase = branch.base else: if controldir.root_transport.base != branch.controldir.root_transport.base: label = gettext("Lightweight checkout:") boundloc = branch.controldir.root_transport.base branchbase = controldir.root_transport.base else: raise errors.BzrError("This branch is not checkout.") switch_box.addRow(label, QtWidgets.QLabel(url_for_display(branchbase))) switch_box.addRow(gettext("Checkout of branch:"), QtWidgets.QLabel(url_for_display(boundloc))) self.boundloc = url_for_display(boundloc) throb_hbox = QtWidgets.QHBoxLayout() self.throbber = ThrobberWidget(self) throb_hbox.addWidget(self.throbber) self.throbber.hide() switch_box.addRow(throb_hbox) switch_hbox = QtWidgets.QHBoxLayout() branch_label = QtWidgets.QLabel(gettext("Switch to branch:")) branch_combo = QtWidgets.QComboBox() branch_combo.setEditable(True) self.branch_combo = branch_combo if location is not None: branch_combo.addItem(osutils.abspath(location)) elif boundloc is not None: branch_combo.addItem(url_for_display(boundloc)) browse_button = QtWidgets.QPushButton(gettext("Browse")) browse_button.clicked[bool].connect(self.browse_clicked) switch_hbox.addWidget(branch_label) switch_hbox.addWidget(branch_combo) switch_hbox.addWidget(browse_button) switch_hbox.setStretchFactor(branch_label,0) switch_hbox.setStretchFactor(branch_combo,1) switch_hbox.setStretchFactor(browse_button,0) switch_box.addRow(switch_hbox) create_branch_box = QtWidgets.QCheckBox(gettext("Create Branch before switching")) create_branch_box.setChecked(False) switch_box.addRow(create_branch_box) self.create_branch_box = create_branch_box layout = QtWidgets.QVBoxLayout(self) layout.addWidget(gbSwitch) layout.addWidget(self.make_default_status_box()) layout.addWidget(self.buttonbox) self.branch_combo.setFocus() def show(self): QBzrDialog.show(self) QtCore.QTimer.singleShot(0, self.initial_load) def exec_(self): QtCore.QTimer.singleShot(0, self.initial_load) return QBzrDialog.exec_(self) def _load_branch_names(self): branch_combo = self.branch_combo repo = self.branch.controldir.find_repository() if repo is not None: if getattr(repo, "iter_branches", None): for br in repo.iter_branches(): self.processEvents() branch_combo.addItem(url_for_display(br.base)) @runs_in_loading_queue @ui_current_widget @reports_exception(type=SUB_LOAD_METHOD) def initial_load(self): self.throbber.show() self._load_branch_names() self.throbber.hide() def browse_clicked(self): if os.path.exists(self.boundloc): directory = self.boundloc else: directory = os.getcwd() fileName = QtWidgets.QFileDialog.getExistingDirectory(self, gettext("Select branch location"), directory, ) if fileName: self.branch_combo.insertItem(0,fileName) self.branch_combo.setCurrentIndex(0) def validate(self): location = str(self.branch_combo.currentText()) if not location: self.operation_blocked(gettext("Branch location not specified.")) return False return True def do_start(self): location = str(self.branch_combo.currentText()) if self.create_branch_box.isChecked(): self.process_widget.do_start(None, 'switch', '--create-branch', location) else: self.process_widget.do_start(None, 'switch', location)
class RevertWindow(SubProcessDialog): def __init__(self, tree, selected_list, dialog=True, parent=None, local=None, message=None, ui_mode=True, backup=True): self.tree = tree self.has_pending_merges = len(tree.get_parent_ids())>1 self.initial_selected_list = selected_list SubProcessDialog.__init__(self, gettext("Revert"), name = "revert", default_size = (400, 400), ui_mode = ui_mode, dialog = dialog, parent = parent, hide_progress=True) self.throbber = ThrobberWidget(self) # Display the list of changed files self.file_groupbox = QtWidgets.QGroupBox(gettext("Select changes to revert"), self) self.filelist = TreeWidget(self.file_groupbox) self.filelist.throbber = self.throbber self.filelist.tree_model.set_select_all_kind('versioned') def filter_context_menu(): TreeWidget.filter_context_menu(self.filelist) self.filelist.action_add.setVisible(False) self.filelist.action_revert.setVisible(False) self.filelist.filter_context_menu = filter_context_menu self.selectall_checkbox = SelectAllCheckBox(self.filelist, self.file_groupbox) self.selectall_checkbox.setEnabled(True) self.no_backup_checkbox = QtWidgets.QCheckBox( gettext('Do not save backups of reverted files')) if not backup: self.no_backup_checkbox.setCheckState(QtCore.Qt.Checked) self.no_backup_checkbox.setEnabled(True) filesbox = QtWidgets.QVBoxLayout(self.file_groupbox) filesbox.addWidget(self.filelist) filesbox.addWidget(self.selectall_checkbox) filesbox.addWidget(self.no_backup_checkbox) if self.has_pending_merges: self.file_groupbox.setCheckable(True) self.merges_groupbox = QtWidgets.QGroupBox(gettext("Forget pending merges")) self.merges_groupbox.setCheckable(True) # This keeps track of what the merges_groupbox was before the # select all changes it, so that it can put it back to the state # it was. self.merges_base_checked = True self.pending_merges = PendingMergesList( self.processEvents, self.throbber, self) merges_box = QtWidgets.QVBoxLayout(self.merges_groupbox) merges_box.addWidget(self.pending_merges) self.selectall_checkbox.stateChanged[int].connect(self.selectall_state_changed) self.merges_groupbox.clicked[bool].connect(self.merges_clicked) self.file_groupbox.clicked[bool].connect(self.file_groupbox_clicked) self.filelist.tree_model.dataChanged[QModelIndex, QModelIndex].connect(self.filelist_data_changed) # groupbox gets disabled as we are executing. self.disableUi[bool].connect(self.file_groupbox.setDisabled) self.splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) self.splitter.addWidget(self.file_groupbox) if self.has_pending_merges: self.splitter.addWidget(self.merges_groupbox) self.splitter.addWidget(self.make_default_status_box()) self.splitter.setStretchFactor(0, 10) self.restoreSplitterSizes([150, 150]) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.throbber) layout.addWidget(self.splitter) # Diff button to view changes in files selected to revert self.diffbuttons = DiffButtons(self) self.diffbuttons.setToolTip( gettext("View changes in files selected to revert")) self.diffbuttons.triggered['QString'].connect(self.show_diff_for_checked) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(self.diffbuttons) hbox.addWidget(self.buttonbox) layout.addLayout(hbox) self.throbber.show() def show(self): SubProcessDialog.show(self) QtCore.QTimer.singleShot(1, self.initial_load) @runs_in_loading_queue @ui_current_widget @reports_exception() def initial_load(self): self.filelist.tree_model.checkable = True #fmodel.setFilter(fmodel.UNVERSIONED, False) if self.initial_selected_list is None and not self.has_pending_merges: self.initial_selected_list = [] self.filelist.set_tree(self.tree, changes_mode=True, want_unversioned=False, initial_checked_paths=self.initial_selected_list) self.filelist_checked_base = list( self.filelist.tree_model.iter_checked()) self.selectall_checkbox.update_state() self.processEvents() if self.has_pending_merges: self.pending_merges.load_tree(self.tree) self.processEvents() self.throbber.hide() # The logic for the next 4 methods is like this: # * Either file_groupbox or merges_groupbox or both must be checked, # never neither. # * If merges_groupbox is checked, all files must be checked. If a file is # unchecked, merges_groupbox must be unchecked. # Unless: # * file_groupbox is unchecked - then all files are unchecked. # # We keep a recored of what was checked, so that we we change something, # and then later we go back to a state where that change was not necessary, # we can return to what it was. This is stored in merges_base_checked, and # filelist_checked_base. def selectall_state_changed(self, state): if state == QtCore.Qt.Checked: self.merges_groupbox.setChecked(self.merges_base_checked) elif self.file_groupbox.isChecked(): self.merges_groupbox.setChecked(False) def merges_clicked(self, state): self.merges_base_checked = state if state: if self.file_groupbox.isChecked(): self.selectall_checkbox.clicked(QtCore.Qt.Checked) else: self.selectall_checkbox.clicked(QtCore.Qt.Unchecked) if not state: self.file_groupbox.setChecked(True) self.filelist.tree_model.set_checked_items( self.filelist_checked_base, ignore_no_file_error=True) def file_groupbox_clicked(self, state): if not state: self.merges_groupbox.setChecked(True) self.selectall_checkbox.clicked(QtCore.Qt.Unchecked) if state: if not self.merges_base_checked: self.filelist.tree_model.set_checked_items( self.filelist_checked_base, ignore_no_file_error=True) else: self.selectall_checkbox.clicked(QtCore.Qt.Checked) def filelist_data_changed(self, start, end): if (self.file_groupbox.isChecked() and not self.merges_groupbox.isChecked()): self.filelist_checked_base = list( self.filelist.tree_model.iter_checked()) def _is_revert_pending_merges(self): """Return True if selected to revert pending merges, False if not selected, None if there is no pending merges. """ if not self.has_pending_merges: return None return bool(self.merges_groupbox.isChecked()) def _get_files_to_revert(self): return [ref.path for ref in self.filelist.tree_model.iter_checked( include_unchanged_dirs=False) ] def validate(self): if (self._is_revert_pending_merges() is False and self.selectall_checkbox.checkState() == QtCore.Qt.Checked): if not self.ask_confirmation( gettext("You have selected revert for all changed paths\n" "but keep pending merges.\n\n" "Do you want to continue?") ): return False # It doesn't matter if selectall_checkbox checkbox is activated or not - # we really need to check if there are files selected, because you can # check the 'select all' checkbox if there are no files selectable. if not self._is_revert_pending_merges() and not self._get_files_to_revert(): self.operation_blocked(gettext("You have not selected anything to revert.")) return False return True def do_start(self): """Revert the files.""" args = ["revert"] if (self._is_revert_pending_merges() is None or (self._is_revert_pending_merges() is False and self.file_groupbox.isChecked())): args.extend(self._get_files_to_revert()) if (self._is_revert_pending_merges() is True and not self.file_groupbox.isChecked()): args.append("--forget-merges") if self.no_backup_checkbox.checkState(): args.append("--no-backup") self.process_widget.do_start(self.tree.basedir, *args) def _saveSize(self, config): SubProcessDialog._saveSize(self, config) self._saveSplitterSizes(config, self.splitter) def show_diff_for_checked(self, ext_diff=None, dialog_action='revert'): """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 (?) checked = [ref.path for ref in self.filelist.tree_model.iter_checked()] 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 QtWidgets.QMessageBox.warning(self, "QBrz - " + gettext("Diff"), gettext(msg), QtWidgets.QMessageBox.Ok)
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 = QtWidgets.QVBoxLayout(self.centralwidget) self.throbber = ThrobberWidget(self) vbox.addWidget(self.throbber) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(QtWidgets.QLabel(gettext("Location:"))) self.location_edit = QtWidgets.QLineEdit() self.location_edit.setReadOnly(True) self.location_edit.setText(self.location) hbox.addWidget(self.location_edit, 7) hbox.addWidget(QtWidgets.QLabel(gettext("Revision:"))) self.revision_edit = QtWidgets.QLineEdit() self.revision_edit.returnPressed.connect(self.reload_tree) hbox.addWidget(self.revision_edit, 1) self.show_button = QtWidgets.QPushButton(gettext("Show")) self.show_button.clicked.connect(self.reload_tree) hbox.addWidget(self.show_button, 0) self.filter_menu = TreeFilterMenu(self) self.filter_button = QtWidgets.QPushButton(gettext("&Filter")) self.filter_button.setMenu(self.filter_menu) hbox.addWidget(self.filter_button, 0) self.filter_menu.triggered[int, bool].connect(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, QtWidgets.QDialogButtonBox.ActionRole) self.refresh_button.clicked.connect(self.file_tree.refresh) self.diffbuttons = DiffButtons(self.centralwidget) self.diffbuttons._triggered['QString'].connect( self.file_tree.show_differences) hbox = QtWidgets.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
class BrowseWindow(QBzrWindow): 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 = QtWidgets.QVBoxLayout(self.centralwidget) self.throbber = ThrobberWidget(self) vbox.addWidget(self.throbber) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(QtWidgets.QLabel(gettext("Location:"))) self.location_edit = QtWidgets.QLineEdit() self.location_edit.setReadOnly(True) self.location_edit.setText(self.location) hbox.addWidget(self.location_edit, 7) hbox.addWidget(QtWidgets.QLabel(gettext("Revision:"))) self.revision_edit = QtWidgets.QLineEdit() self.revision_edit.returnPressed.connect(self.reload_tree) hbox.addWidget(self.revision_edit, 1) self.show_button = QtWidgets.QPushButton(gettext("Show")) self.show_button.clicked.connect(self.reload_tree) hbox.addWidget(self.show_button, 0) self.filter_menu = TreeFilterMenu(self) self.filter_button = QtWidgets.QPushButton(gettext("&Filter")) self.filter_button.setMenu(self.filter_menu) hbox.addWidget(self.filter_button, 0) self.filter_menu.triggered[int, bool].connect(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, QtWidgets.QDialogButtonBox.ActionRole) self.refresh_button.clicked.connect(self.file_tree.refresh) self.diffbuttons = DiffButtons(self.centralwidget) self.diffbuttons._triggered['QString'].connect( self.file_tree.show_differences) hbox = QtWidgets.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 show(self): # we show the bare form as soon as possible. # QBzrWindow.show(self) super().show() # QtCore.QTimer.singleShot(1, self.load) self.load() self.file_tree.refresh() # @runs_in_loading_queue @ui_current_widget @reports_exception() def load(self): self.throbber.show() self.processEvents() try: self.revno_map = None if not self.branch: (self.workingtree, self.branch, repo, path) = ControlDir.open_containing_tree_branch_or_repository( self.location) if self.revision is None: if self.revision_id is None: if self.workingtree is not None: self.revision_spec = "wt:" else: revno, self.revision_id = self.branch.last_revision_info( ) self.revision_spec = str(revno) self.set_revision(revision_id=self.revision_id, text=self.revision_spec) else: self.set_revision(self.revision) self.processEvents() finally: self.throbber.hide() self.processEvents() # @runs_in_loading_queue @ui_current_widget def set_revision(self, revspec=None, revision_id=None, text=None): self.throbber.show() try: buttons = (self.filter_button, self.diffbuttons, self.refresh_button) state = self.file_tree.get_state() if text == "wt:": self.tree = self.workingtree self.tree.lock_read() try: self.file_tree.set_tree(self.workingtree, self.branch) self.file_tree.restore_state(state) finally: self.tree.unlock() for button in buttons: button.setEnabled(True) else: branch = self.branch branch.lock_read() self.processEvents() for button in buttons: button.setEnabled(False) fmodel = self.file_tree.tree_filter_model fmodel.setFilter(fmodel.UNCHANGED, True) self.filter_menu.set_filters(fmodel.filters) try: if revision_id is None: text = revspec.spec or '' if revspec.in_branch == revspec.in_history: args = [branch] else: args = [branch, False] revision_id = revspec.in_branch(*args).rev_id self.revision_id = revision_id self.tree = branch.repository.revision_tree(revision_id) self.processEvents() self.file_tree.set_tree(self.tree, self.branch) self.file_tree.restore_state(state) if self.revno_map is None: self.processEvents() # XXX make this operation lazy? how? self.revno_map = self.branch.get_revision_id_to_revno_map( ) self.file_tree.tree_model.set_revno_map(self.revno_map) finally: branch.unlock() self.revision_edit.setText(text) finally: self.throbber.hide() # @ui_current_widget def reload_tree(self): revstr = str(self.revision_edit.text()) if not revstr: if self.workingtree is not None: self.revision_spec = "wt:" revision_id = None else: revno, revision_id = self.branch.last_revision_info() self.revision_spec = str(revno) self.set_revision(revision_id=revision_id, text=self.revision_spec) else: if revstr == "wt:": self.revision_spec = "wt:" revision_id = None self.set_revision(revision_id=revision_id, text=self.revision_spec) else: try: revspec = RevisionSpec.from_string(revstr) except errors.NoSuchRevisionSpec as e: QtWidgets.QMessageBox.warning(self, gettext("Browse"), str(e), QtWidgets.QMessageBox.Ok) return self.set_revision(revspec) def filter_triggered(self, filter, checked): self.file_tree.tree_filter_model.setFilter(filter, checked)
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.process_widget.failed['QString'].connect(self.on_failed) self.throbber = ThrobberWidget(self) # commit to branch location branch_groupbox = QtWidgets.QGroupBox(gettext("Branch"), self) branch_layout = QtWidgets.QGridLayout(branch_groupbox) self.branch_location = QtWidgets.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 = QtWidgets.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(QtWidgets.QLabel(gettext('Description:')), 2, 0, QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) self.commit_type_description = QtWidgets.QLabel() self.commit_type_description.setWordWrap(True) branch_layout.addWidget(self.commit_type_description, 2, 1) branch_layout.setColumnStretch(1, 10) self.local_checkbox.stateChanged[int].connect( 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 = QtWidgets.QHBoxLayout(self.not_uptodate_info) # XXX this is to big. Resize not_uptodate_icon = QtWidgets.QLabel() not_uptodate_icon.setPixmap(self.style().standardPixmap( QtWidgets.QStyle.SP_MessageBoxWarning)) not_uptodate_layout.addWidget(not_uptodate_icon) self.not_uptodate_label = QtWidgets.QLabel('error message goes here') not_uptodate_layout.addWidget(self.not_uptodate_label, 2) update_button = QtWidgets.QPushButton(gettext('Update')) update_button.clicked[bool].connect(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 = QtWidgets.QSplitter(QtCore.Qt.Vertical, self) message_groupbox = QtWidgets.QGroupBox(gettext("Message"), splitter) splitter.addWidget(message_groupbox) self.tabWidget = QtWidgets.QTabWidget() splitter.addWidget(self.tabWidget) splitter.setStretchFactor(0, 1) splitter.setStretchFactor(1, 8) grid = QtWidgets.QGridLayout(message_groupbox) self.show_nonversioned_checkbox = QtWidgets.QCheckBox( gettext("Show non-versioned files")) show_nonversioned = get_qbrz_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_widget = TreeWidget(self) self.filelist_widget.throbber = self.throbber if show_nonversioned: self.filelist_widget.tree_model.set_select_all_kind('all') else: self.filelist_widget.tree_model.set_select_all_kind('versioned') self.file_words = {} self.filelist_widget.tree_model.dataChanged[ QtCore.QModelIndex, QtCore.QModelIndex].connect(self.on_filelist_data_changed) self.selectall_checkbox = SelectAllCheckBox(self.filelist_widget, 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.message.messageEntered.connect(self.do_accept) self.completer = QtWidgets.QCompleter() self.completer_model = QtCore.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 = QtWidgets.QCheckBox(gettext("&Fixed bugs:")) self.bugsCheckBox.setToolTip( gettext("Set the IDs of bugs fixed by this commit")) self.bugs = QtWidgets.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.bugsCheckBox.stateChanged[int].connect(self.enableBugs) grid.addWidget(self.bugsCheckBox, 1, 0) grid.addWidget(self.bugs, 1, 1) # Equivalent for 'bzr commit --author' self.authorCheckBox = QtWidgets.QCheckBox(gettext("&Author:")) self.authorCheckBox.setToolTip( gettext("Set the author of this change," " if it's different from the committer")) self.author = QtWidgets.QLineEdit() self.author.setToolTip( gettext("Enter the author's name, " "e.g. <i>John Doe <[email protected]></i>")) self.author.setEnabled(False) self.authorCheckBox.stateChanged[int].connect(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 = QtWidgets.QWidget() self.tabWidget.addTab(files_tab, gettext("Changes")) vbox = QtWidgets.QVBoxLayout(files_tab) vbox.addWidget(self.filelist_widget) self.show_nonversioned_checkbox.toggled[bool].connect( 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. self.disableUi[bool].connect(self.pending_merges_list.setDisabled) 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 = QtWidgets.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.diffbuttons._triggered['QString'].connect( self.show_diff_for_checked) self.refresh_button = StandardButton(BTN_REFRESH) self.refresh_button.clicked.connect(self.refresh) hbox = QtWidgets.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 self.disableUi[bool].connect(w.setDisabled) 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 str(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.process_widget.failed['QString'].connect(self.on_failed) self.throbber = ThrobberWidget(self) # commit to branch location branch_groupbox = QtWidgets.QGroupBox(gettext("Branch"), self) branch_layout = QtWidgets.QGridLayout(branch_groupbox) self.branch_location = QtWidgets.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 = QtWidgets.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(QtWidgets.QLabel(gettext('Description:')), 2, 0, QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop) self.commit_type_description = QtWidgets.QLabel() self.commit_type_description.setWordWrap(True) branch_layout.addWidget(self.commit_type_description, 2, 1) branch_layout.setColumnStretch(1, 10) self.local_checkbox.stateChanged[int].connect( 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 = QtWidgets.QHBoxLayout(self.not_uptodate_info) # XXX this is to big. Resize not_uptodate_icon = QtWidgets.QLabel() not_uptodate_icon.setPixmap(self.style().standardPixmap( QtWidgets.QStyle.SP_MessageBoxWarning)) not_uptodate_layout.addWidget(not_uptodate_icon) self.not_uptodate_label = QtWidgets.QLabel('error message goes here') not_uptodate_layout.addWidget(self.not_uptodate_label, 2) update_button = QtWidgets.QPushButton(gettext('Update')) update_button.clicked[bool].connect(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 = QtWidgets.QSplitter(QtCore.Qt.Vertical, self) message_groupbox = QtWidgets.QGroupBox(gettext("Message"), splitter) splitter.addWidget(message_groupbox) self.tabWidget = QtWidgets.QTabWidget() splitter.addWidget(self.tabWidget) splitter.setStretchFactor(0, 1) splitter.setStretchFactor(1, 8) grid = QtWidgets.QGridLayout(message_groupbox) self.show_nonversioned_checkbox = QtWidgets.QCheckBox( gettext("Show non-versioned files")) show_nonversioned = get_qbrz_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_widget = TreeWidget(self) self.filelist_widget.throbber = self.throbber if show_nonversioned: self.filelist_widget.tree_model.set_select_all_kind('all') else: self.filelist_widget.tree_model.set_select_all_kind('versioned') self.file_words = {} self.filelist_widget.tree_model.dataChanged[ QtCore.QModelIndex, QtCore.QModelIndex].connect(self.on_filelist_data_changed) self.selectall_checkbox = SelectAllCheckBox(self.filelist_widget, 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.message.messageEntered.connect(self.do_accept) self.completer = QtWidgets.QCompleter() self.completer_model = QtCore.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 = QtWidgets.QCheckBox(gettext("&Fixed bugs:")) self.bugsCheckBox.setToolTip( gettext("Set the IDs of bugs fixed by this commit")) self.bugs = QtWidgets.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.bugsCheckBox.stateChanged[int].connect(self.enableBugs) grid.addWidget(self.bugsCheckBox, 1, 0) grid.addWidget(self.bugs, 1, 1) # Equivalent for 'bzr commit --author' self.authorCheckBox = QtWidgets.QCheckBox(gettext("&Author:")) self.authorCheckBox.setToolTip( gettext("Set the author of this change," " if it's different from the committer")) self.author = QtWidgets.QLineEdit() self.author.setToolTip( gettext("Enter the author's name, " "e.g. <i>John Doe <[email protected]></i>")) self.author.setEnabled(False) self.authorCheckBox.stateChanged[int].connect(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 = QtWidgets.QWidget() self.tabWidget.addTab(files_tab, gettext("Changes")) vbox = QtWidgets.QVBoxLayout(files_tab) vbox.addWidget(self.filelist_widget) self.show_nonversioned_checkbox.toggled[bool].connect( 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. self.disableUi[bool].connect(self.pending_merges_list.setDisabled) 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 = QtWidgets.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.diffbuttons._triggered['QString'].connect( self.show_diff_for_checked) self.refresh_button = StandardButton(BTN_REFRESH) self.refresh_button.clicked.connect(self.refresh) hbox = QtWidgets.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 self.disableUi[bool].connect(w.setDisabled) 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 str(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_widget.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_widget.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_widget.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_widget.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_widget.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_widget.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 breezy.trace import warning warning("Cannot save commit data because the branch is locked.") return # collect data ci_data = QBzrCommitData(tree=self.tree) message = str(self.message.toPlainText()).strip() if message: ci_data['message'] = message bug_str = '' if self.bugsCheckBox.isChecked(): bug_str = str(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 breezy.trace import warning warning("Cannot wipe commit data because the branch is locked.") return self.ci_data.wipe() def _get_message(self): return str(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_widget.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() # AND we need to quote it... 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 str(self.bugs.text()).split(): args.append(("--fixes=%s" % s)) if self.authorCheckBox.isChecked(): args.append(("--author=%s" % str(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_widget.want_unversioned: state = self.filelist_widget.get_state() self.filelist_widget.set_tree( self.tree, changes_mode=True, want_unversioned=True, change_load_filter=lambda c: not c.is_ignored()) self.filelist_widget.restore_state(state) if state: self.filelist_widget.tree_model.set_select_all_kind('all') else: self.filelist_widget.tree_model.set_select_all_kind('versioned') fmodel = self.filelist_widget.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() qbrz_config = get_qbrz_config() qbrz_config.set_option(self._window_name + "_show_nonversioned", self.show_nonversioned_checkbox.isChecked()) qbrz_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_widget.tree_model.checkable: checked = [] # checked versioned unversioned = [] # checked unversioned (supposed to be added) for ref in self.filelist_widget.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_widget.diff_context) else: msg = "No changes selected to " + dialog_action QtWidgets.QMessageBox.warning(self, "QBrz - " + gettext("Diff"), gettext(msg), QtWidgets.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_widget.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() update_window.subprocessFinished[bool].connect( self.not_uptodate_info.setHidden)
class QBzrCatWindow(QBzrWindow): """Show content of versioned file/symlink.""" def __init__(self, filename=None, revision=None, tree=None, file_id=None, encoding=None, parent=None): """Create qcat window.""" self.filename = filename self.revision = revision self.tree = tree if tree: self.branch = getattr(tree, 'branch', None) if self.branch is None: self.branch = FakeBranch() self.file_id = file_id self.encoding = encoding if (not self.filename) and self.tree and self.file_id: self.filename = self.tree.id2path(self.file_id) QBzrWindow.__init__(self, [gettext("View"), self.filename], parent) self.restoreSize("cat", (780, 580)) self.throbber = ThrobberWidget(self) self.buttonbox = self.create_button_box(BTN_CLOSE) self.encoding_selector = self._create_encoding_selector() self.vbox = QtWidgets.QVBoxLayout(self.centralwidget) self.vbox.addWidget(self.throbber) self.vbox.addStretch() hbox = QtWidgets.QHBoxLayout() hbox.addWidget(self.encoding_selector) hbox.addWidget(self.buttonbox) self.vbox.addLayout(hbox) def _create_encoding_selector(self): encoding_selector = EncodingSelector(self.encoding, gettext("Encoding:"), self._on_encoding_changed) # disable encoding selector, # it will be enabled later only for text files encoding_selector.setDisabled(True) return encoding_selector def show(self): # we show the bare form as soon as possible. QBzrWindow.show(self) QtCore.QTimer.singleShot(0, self.load) @runs_in_loading_queue @ui_current_widget @reports_exception() def load(self): self.throbber.show() self.processEvents() try: if not self.tree: branch, relpath = Branch.open_containing(self.filename) self.branch = branch self.encoding = get_set_encoding(self.encoding, branch) self.encoding_selector.encoding = self.encoding if self.revision is None: self.tree = branch.basis_tree() else: revision_id = self.revision[0].in_branch(branch).rev_id self.tree = branch.repository.revision_tree(revision_id) self.file_id = self.tree.path2id(relpath) if not self.file_id: self.file_id = self.tree.path2id(self.filename) if not self.file_id: raise errors.BzrCommandError( "%r is not present in revision %s" % (self.filename, self.tree.get_revision_id())) self.tree.lock_read() try: kind = self.tree.kind(self.filename) if kind == 'file': text = self.tree.get_file_text(self.filename) elif kind == 'symlink': text = self.tree.get_symlink_target(self.filename) else: text = '' finally: self.tree.unlock() self.processEvents() self.text = text self.kind = kind self._create_and_show_browser(self.filename, text, kind) finally: self.throbber.hide() def _create_and_show_browser(self, filename, text, kind): """Create browser object for given file and then attach it to GUI. @param filename: filename used for differentiate between images and simply binary files. @param text: raw file content. @param kind: filesystem kind: file, symlink, directory """ type_, fview = self.detect_content_type(filename, text, kind) # update title title = "View " + type_ self.set_title([gettext(title), filename]) # create and show browser self.browser = fview(filename, text) self.vbox.insertWidget(1, self.browser, 1) # set focus on content self.browser.setFocus() def detect_content_type(self, relpath, text, kind='file'): """Return (file_type, viewer_factory) based on kind, text and relpath. Supported file types: text, image, binary """ if kind == 'file': if not b'\0' in text: return 'text file', self._create_text_view else: ext = file_extension(relpath).lower() image_exts = [ '.' + str(i) for i in QtGui.QImageReader.supportedImageFormats() ] if ext in image_exts: return 'image file', self._create_image_view else: return 'binary file', self._create_hexdump_view else: return kind, self._create_symlink_view def _set_text(self, edit_widget, relpath, text, encoding=None): """Set plain text to widget, as unicode. @param edit_widget: edit widget to view the text. @param relpath: filename (required for syntax highlighting to detect file type). @param text: plain non-unicode text (bytes). @param encoding: text encoding (default: utf-8). """ text = text.decode(encoding or 'utf-8', 'replace') edit_widget.setPlainText(text) highlight_document(edit_widget, relpath) def _create_text_view(self, relpath, text): """Create widget to show text files. @return: created widget with loaded text. """ browser = LineNumberEditerFrame(self) edit = browser.edit edit.setReadOnly(True) edit.document().setDefaultFont(get_monospace_font()) edit.setTabStopWidth(get_tab_width_pixels(self.branch)) self._set_text(edit, relpath, text, self.encoding) self.encoding_selector.setEnabled(True) return browser def _on_encoding_changed(self, encoding): """Event handler for EncodingSelector. It sets file text to browser again with new encoding. """ self.encoding = encoding branch = self.branch if branch is None: branch = Branch.open_containing(self.filename)[0] if branch: get_set_encoding(encoding, branch) self._set_text(self.browser.edit, self.filename, self.text, self.encoding) def _create_simple_text_browser(self): """Create and return simple widget to show text-like content.""" browser = QtWidgets.QPlainTextEdit(self) browser.setReadOnly(True) browser.document().setDefaultFont(get_monospace_font()) return browser def _create_symlink_view(self, relpath, target): """Create widget to show symlink target. @return: created widget with loaded content. """ browser = self._create_simple_text_browser() browser.setPlainText('-> ' + target.decode('utf-8', 'replace')) return browser def _create_hexdump_view(self, relpath, data): """Create widget to show content of binary files. @return: created widget with loaded content. """ browser = self._create_simple_text_browser() browser.setPlainText(hexdump(data)) return browser def _create_image_view(self, relpath, data): """Create widget to show image file. @return: created widget with loaded image. """ self.pixmap = QtGui.QPixmap() self.pixmap.loadFromData(data) self.item = QtWidgets.QGraphicsPixmapItem(self.pixmap) self.scene = QtWidgets.QGraphicsScene(self.item.boundingRect()) self.scene.addItem(self.item) return QtWidgets.QGraphicsView(self.scene)
def __init__(self, tree, selected_list, dialog=True, parent=None, local=None, message=None, ui_mode=True, backup=True): self.tree = tree self.has_pending_merges = len(tree.get_parent_ids())>1 self.initial_selected_list = selected_list SubProcessDialog.__init__(self, gettext("Revert"), name = "revert", default_size = (400, 400), ui_mode = ui_mode, dialog = dialog, parent = parent, hide_progress=True) self.throbber = ThrobberWidget(self) # Display the list of changed files self.file_groupbox = QtWidgets.QGroupBox(gettext("Select changes to revert"), self) self.filelist = TreeWidget(self.file_groupbox) self.filelist.throbber = self.throbber self.filelist.tree_model.set_select_all_kind('versioned') def filter_context_menu(): TreeWidget.filter_context_menu(self.filelist) self.filelist.action_add.setVisible(False) self.filelist.action_revert.setVisible(False) self.filelist.filter_context_menu = filter_context_menu self.selectall_checkbox = SelectAllCheckBox(self.filelist, self.file_groupbox) self.selectall_checkbox.setEnabled(True) self.no_backup_checkbox = QtWidgets.QCheckBox( gettext('Do not save backups of reverted files')) if not backup: self.no_backup_checkbox.setCheckState(QtCore.Qt.Checked) self.no_backup_checkbox.setEnabled(True) filesbox = QtWidgets.QVBoxLayout(self.file_groupbox) filesbox.addWidget(self.filelist) filesbox.addWidget(self.selectall_checkbox) filesbox.addWidget(self.no_backup_checkbox) if self.has_pending_merges: self.file_groupbox.setCheckable(True) self.merges_groupbox = QtWidgets.QGroupBox(gettext("Forget pending merges")) self.merges_groupbox.setCheckable(True) # This keeps track of what the merges_groupbox was before the # select all changes it, so that it can put it back to the state # it was. self.merges_base_checked = True self.pending_merges = PendingMergesList( self.processEvents, self.throbber, self) merges_box = QtWidgets.QVBoxLayout(self.merges_groupbox) merges_box.addWidget(self.pending_merges) self.selectall_checkbox.stateChanged[int].connect(self.selectall_state_changed) self.merges_groupbox.clicked[bool].connect(self.merges_clicked) self.file_groupbox.clicked[bool].connect(self.file_groupbox_clicked) self.filelist.tree_model.dataChanged[QModelIndex, QModelIndex].connect(self.filelist_data_changed) # groupbox gets disabled as we are executing. self.disableUi[bool].connect(self.file_groupbox.setDisabled) self.splitter = QtWidgets.QSplitter(QtCore.Qt.Vertical) self.splitter.addWidget(self.file_groupbox) if self.has_pending_merges: self.splitter.addWidget(self.merges_groupbox) self.splitter.addWidget(self.make_default_status_box()) self.splitter.setStretchFactor(0, 10) self.restoreSplitterSizes([150, 150]) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.throbber) layout.addWidget(self.splitter) # Diff button to view changes in files selected to revert self.diffbuttons = DiffButtons(self) self.diffbuttons.setToolTip( gettext("View changes in files selected to revert")) self.diffbuttons.triggered['QString'].connect(self.show_diff_for_checked) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(self.diffbuttons) hbox.addWidget(self.buttonbox) layout.addLayout(hbox) self.throbber.show()
class QBzrVerifySignaturesWindow(QBzrDialog): """Show the user information on the status of digital signatures for the commits on this branch""" def __init__(self, acceptable_keys, revision, location, parent=None): """load UI file, add buttons and throbber, run refresh""" QBzrDialog.__init__(self, [gettext("Verify Signatures")], parent) self.restoreSize("verify-signatures", (580, 250)) self.buttonbox = self.create_button_box(BTN_CLOSE) self.ui = Ui_VerifyForm() self.ui.setupUi(self) self.ui.verticalLayout.addWidget(self.buttonbox) self.throbber = ThrobberWidget(self) self.ui.verticalLayout.insertWidget(0, self.throbber) self.acceptable_keys = acceptable_keys self.revision = revision self.location = location QTimer.singleShot(0, self.refresh_view) def refresh_view(self): """get the revisions wanted by the user, do the verifications and popular the tree widget with the results""" self.throbber.show() controldir = _mod_controldir.ControlDir.open_containing( self.location)[0] branch = controldir.open_branch() repo = branch.repository branch_config = branch.get_config_stack() gpg_strategy = gpg.GPGStrategy(branch_config) gpg_strategy.set_acceptable_keys(self.acceptable_keys) if branch.name is None: header = branch.user_url else: header = branch.name self.ui.treeWidget.setHeaderLabels([str(header)]) # get our list of revisions revisions = [] if self.revision is not None: if len(self.revision) == 1: revno, rev_id = self.revision[0].in_history(branch) revisions.append(rev_id) elif len(sel.revision) == 2: from_revno, from_revid = self.revision[0].in_history(branch) to_revno, to_revid = self.revision[1].in_history(branch) if to_revid is None: to_revno = branch.revno() if from_revno is None or to_revno is None: raise errors.BzrCommandError( 'Cannot verify a range of non-revision-history revisions' ) for revno in range(from_revno, to_revno + 1): revisions.append(branch.get_rev_id(revno)) else: # all revisions by default including merges graph = repo.get_graph() revisions = [] repo.lock_read() for rev_id, parents in graph.iter_ancestry( [branch.last_revision()]): if _mod_revision.is_null(rev_id): continue if parents is None: # Ignore ghosts continue revisions.append(rev_id) repo.unlock() count, result, all_verifiable = gpg.bulk_verify_signatures( repo, revisions, gpg_strategy, QApplication.processEvents) if all_verifiable: message = QTreeWidgetItem( [gettext("All commits signed with verifiable keys")]) self.ui.treeWidget.addTopLevelItem(message) for verbose_message in gpg.verbose_valid_message(result): QTreeWidgetItem(message, [verbose_message]) else: valid_commit_message = QTreeWidgetItem( [gpg.valid_commits_message(count)]) self.ui.treeWidget.addTopLevelItem(valid_commit_message) for verbose_message in gpg.verbose_valid_message(result): QTreeWidgetItem(valid_commit_message, [verbose_message]) expired_key_message = QTreeWidgetItem( [gpg.expired_commit_message(count)]) self.ui.treeWidget.addTopLevelItem(expired_key_message) for verbose_message in gpg.verbose_expired_key_message( result, repo): QTreeWidgetItem(expired_key_message, [verbose_message]) unknown_key_message = QTreeWidgetItem( [gpg.unknown_key_message(count)]) self.ui.treeWidget.addTopLevelItem(unknown_key_message) for verbose_message in gpg.verbose_missing_key_message(result): QTreeWidgetItem(unknown_key_message, [verbose_message]) commit_not_valid_message = QTreeWidgetItem( [gpg.commit_not_valid_message(count)]) self.ui.treeWidget.addTopLevelItem(commit_not_valid_message) for verbose_message in gpg.verbose_not_valid_message(result, repo): QTreeWidgetItem(commit_not_valid_message, [verbose_message]) commit_not_signed_message = QTreeWidgetItem( [gpg.commit_not_signed_message(count)]) self.ui.treeWidget.addTopLevelItem(commit_not_signed_message) for verbose_message in gpg.verbose_not_signed_message( result, repo): QTreeWidgetItem(commit_not_signed_message, [verbose_message]) self.throbber.hide()