Example #1
0
    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)
Example #2
0
    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 = {}
Example #3
0
 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)
Example #4
0
    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)
Example #5
0
    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()
Example #6
0
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)
Example #7
0
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)
Example #8
0
    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()
Example #9
0
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)
Example #10
0
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)
Example #11
0
    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
Example #12
0
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)
Example #13
0
    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 &lt;[email protected]&gt;</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()
Example #14
0
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 &lt;[email protected]&gt;</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)
Example #15
0
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)
Example #16
0
    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()
Example #17
0
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()