コード例 #1
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 = QtGui.QWidget()
        logbox = QtGui.QVBoxLayout(logwidget)
        logbox.setContentsMargins(0, 0, 0, 0)

        searchbox = QtGui.QHBoxLayout()

        self.search_label = QtGui.QLabel(gettext("&Search:"))
        self.search_edit = QtGui.QLineEdit()
        self.search_label.setBuddy(self.search_edit)
        self.connect(self.search_edit, QtCore.SIGNAL("textEdited(QString)"),
                     self.set_search_timer)

        self.search_timer = QtCore.QTimer(self)
        self.search_timer.setSingleShot(True)
        self.connect(self.search_timer, QtCore.SIGNAL("timeout()"),
                     self.update_search)

        searchbox.addWidget(self.search_label)
        searchbox.addWidget(self.search_edit)

        self.searchType = QtGui.QComboBox()

        self.searchType.addItem(gettext("Messages"),
                                QtCore.QVariant(self.FilterMessageRole))
        self.searchType.addItem(gettext("Authors"),
                                QtCore.QVariant(self.FilterAuthorRole))
        self.searchType.addItem(gettext("Revision IDs"),
                                QtCore.QVariant(self.FilterIdRole))
        self.searchType.addItem(gettext("Revision Numbers"),
                                QtCore.QVariant(self.FilterRevnoRole))
        self.searchType.addItem(gettext("Tags"),
                                QtCore.QVariant(self.FilterTagRole))
        self.searchType.addItem(gettext("Bugs"),
                                QtCore.QVariant(self.FilterBugRole))
        searchbox.addWidget(self.searchType)
        self.connect(self.searchType,
                     QtCore.SIGNAL("currentIndexChanged(int)"),
                     self.updateSearchType)

        logbox.addLayout(searchbox)

        self.log_list = LogList(self.processEvents,
                                self.throbber,
                                self,
                                action_commands=True)

        logbox.addWidget(self.throbber)
        logbox.addWidget(self.log_list)

        self.current_rev = None

        self.message = QtGui.QTextDocument()
        self.message_browser = LogListRevisionMessageBrowser(
            self.log_list, self)
        self.message_browser.setDocument(self.message)

        self.file_list_container = FileListContainer(self.log_list, self)
        self.connect(
            self.log_list.selectionModel(),
            QtCore.SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
            self.file_list_container.revision_selection_changed)

        hsplitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
        hsplitter.addWidget(self.message_browser)
        hsplitter.addWidget(self.file_list_container)
        hsplitter.setStretchFactor(0, 3)
        hsplitter.setStretchFactor(1, 1)

        splitter = QtGui.QSplitter(QtCore.Qt.Vertical)
        splitter.addWidget(logwidget)
        splitter.addWidget(hsplitter)
        splitter.setStretchFactor(0, 5)
        splitter.setStretchFactor(1, 3)

        buttonbox = self.create_button_box(BTN_CLOSE)
        self.refresh_button = StandardButton(BTN_REFRESH)
        buttonbox.addButton(self.refresh_button,
                            QtGui.QDialogButtonBox.ActionRole)
        self.connect(self.refresh_button, QtCore.SIGNAL("clicked()"),
                     self.refresh)

        self.diffbuttons = DiffButtons(self.centralwidget)
        self.diffbuttons.setEnabled(False)
        self.connect(self.diffbuttons, QtCore.SIGNAL("triggered(QString)"),
                     self.log_list.show_diff_specified_files_ext)

        vbox = QtGui.QVBoxLayout(self.centralwidget)
        vbox.addWidget(splitter)
        hbox = QtGui.QHBoxLayout()
        hbox.addWidget(self.diffbuttons)
        hbox.addWidget(buttonbox)
        vbox.addLayout(hbox)
        self.windows = []
        # set focus on search edit widget
        self.log_list.setFocus()

    @runs_in_loading_queue
    @ui_current_widget
    @reports_exception()
    def load(self):
        self.refresh_button.setDisabled(True)
        self.throbber.show()
        self.processEvents()
        try:
            # Set window title.
            lt = self._locations_for_title(self.locations)
            if lt:
                self.set_title((self.title, lt))

            branches, primary_bi, file_ids = self.get_branches_and_file_ids()
            if self.show_trees:
                gz_cls = logmodel.WithWorkingTreeGraphVizLoader
            else:
                gz_cls = logmodel.GraphVizLoader

            self.log_list.load(branches, primary_bi, file_ids, self.no_graph,
                               gz_cls)
            self.connect(
                self.log_list.selectionModel(),
                QtCore.SIGNAL(
                    "selectionChanged(QItemSelection, QItemSelection)"),
                self.update_selection)

            self.load_search_indexes(branches)
        finally:
            self.refresh_button.setDisabled(False)
            self.throbber.hide()

    def get_branches_and_file_ids(self):
        if self.branch:
            if self.tree is None:
                try:
                    self.tree = self.branch.bzrdir.open_workingtree()
                except (errors.NoWorkingTree, errors.NotLocalUrl):
                    pass
            label = self.branch_label(None, self.branch)
            bi = BranchInfo(label, self.tree, self.branch)
            return [bi], bi, self.specific_file_ids
        else:
            primary_bi = None
            branches = set()
            file_ids = []
            if self.locations is not None:
                locations = self.locations
            else:
                locations = [u'.']

            # Branch names that indicated primary branch.
            # TODO: Make config option.
            primary_branch_names = ('trunk', 'bzr.dev')

            for location in locations:
                tree, br, repo, fp = \
                    BzrDir.open_containing_tree_branch_or_repository(location)
                self.processEvents()

                if br is None:
                    if fp:
                        raise errors.NotBranchError(fp)

                    repo_branches = repo.find_branches(using=True)
                    if not repo_branches:
                        raise errors.NotBranchError(fp)

                    for br in repo_branches:
                        self.processEvents()
                        try:
                            tree = br.bzrdir.open_workingtree()
                            self.processEvents()
                        except errors.NoWorkingTree:
                            tree = None
                        label = self.branch_label(None, br, location, repo)
                        bi = BranchInfo(label, tree, br)
                        branches.add(bi)
                        if not primary_bi and br.nick in primary_branch_names:
                            primary_bi = bi
                else:
                    if len(locations) > 1:
                        label = self.branch_label(location, br)
                    else:
                        label = None
                    bi = BranchInfo(label, tree, br)
                    if len(branches) == 0:
                        # The first sepecified branch becomes the primary
                        # branch.
                        primary_bi = bi
                    branches.add(bi)

                # If no locations were sepecified, don't do fileids
                # Otherwise it gives you the history for the dir if you are
                # in a sub dir.
                if fp != '' and self.locations is None:
                    fp = ''

                if fp != '':
                    # TODO: Have away to specify a revision to find to file
                    # path in, so that one can show deleted files.
                    if tree is None:
                        tree = br.basis_tree()

                    file_id = tree.path2id(fp)
                    if file_id is None:
                        raise errors.BzrCommandError(
                            "Path does not have any revision history: %s" %
                            location)
                    file_ids.append(file_id)
            if file_ids and len(branches) > 1:
                raise errors.BzrCommandError(
                    gettext(
                        'It is not possible to specify different file paths and '
                        'different branches at the same time.'))
            return tuple(branches), primary_bi, file_ids

    def load_search_indexes(self, branches):
        global have_search, search_errors, search_index
        if have_search is None:
            have_search = True
            try:
                from bzrlib.plugins.search import errors as search_errors
                from bzrlib.plugins.search import index as search_index
            except (ImportError, errors.IncompatibleAPI):
                have_search = False

        if have_search:
            indexes_availble = False
            for bi in branches:
                try:
                    bi.index = search_index.open_index_branch(bi.branch)
                    indexes_availble = True
                except (search_errors.NoSearchIndex, errors.IncompatibleAPI):
                    pass
            if indexes_availble:
                self.searchType.insertItem(
                    0, gettext("Messages and File text (indexed)"),
                    QtCore.QVariant(self.FilterSearchRole))
                self.searchType.setCurrentIndex(0)

                self.completer = Compleater(self)
                self.completer_model = QtGui.QStringListModel(self)
                self.completer.setModel(self.completer_model)
                self.search_edit.setCompleter(self.completer)
                self.connect(self.search_edit,
                             QtCore.SIGNAL("textChanged(QString)"),
                             self.update_search_completer)
                self.suggestion_letters_loaded = {"": QtCore.QStringList()}
                self.suggestion_last_first_letter = ""
                self.connect(self.completer,
                             QtCore.SIGNAL("activated(QString)"),
                             self.set_search_timer)

    no_usefull_info_in_location_re = re.compile(r'^[.:/\\]*$')

    def branch_label(self,
                     location,
                     branch,
                     shared_repo_location=None,
                     shared_repo=None):
        # We should rather use QFontMetrics.elidedText. How do we decide on the
        # width.
        def elided_text(text, length=20):
            if len(text) > length + 3:
                return text[:length] + '...'
            return text

        def elided_path(path):
            if len(path) > 23:
                dir, name = split(path)
                dir = elided_text(dir, 10)
                name = elided_text(name)
                return join(dir, name)
            return path

        if shared_repo_location and shared_repo and not location:
            # Once we depend on bzrlib 2.2, this can become .user_url
            branch_rel = determine_relative_path(
                shared_repo.bzrdir.root_transport.base,
                branch.bzrdir.root_transport.base)
            if shared_repo_location == 'colo:':
                location = shared_repo_location + branch_rel
            else:
                location = join(shared_repo_location, branch_rel)
        if location is None:
            return elided_text(branch.nick)

        has_explicit_nickname = getattr(branch.get_config(),
                                        'has_explicit_nickname',
                                        lambda: False)()
        append_nick = (location.startswith(':') or bool(
            self.no_usefull_info_in_location_re.match(location))
                       or has_explicit_nickname)
        if append_nick:
            return '%s (%s)' % (elided_path(location), branch.nick)

        return elided_text(location)

    def refresh(self):
        self.file_list_container.drop_delta_cache_with_wt()
        self.replace = {}
        self.load()

    def replace_config(self, branch):
        if branch.base not in self.replace:
            config = branch.get_config()
            replace = config.get_user_option("qlog_replace")
            if replace:
                replace = replace.split("\n")
                replace = [
                    tuple(replace[2 * i:2 * i + 2])
                    for i in range(len(replace) // 2)
                ]
            self.replace[branch.base] = replace

        return self.replace[branch.base]

    def show(self):
        # we show the bare form as soon as possible.
        QBzrWindow.show(self)
        QtCore.QTimer.singleShot(0, self.load)

    def update_selection(self, selected, deselected):
        indexes = self.log_list.get_selection_indexes()
        if not indexes:
            self.diffbuttons.setEnabled(False)
        else:
            self.diffbuttons.setEnabled(True)

    @ui_current_widget
    def update_search(self):
        # TODO in_paths = self.search_in_paths.isChecked()
        gv = self.log_list.log_model.graph_viz
        role = self.searchType.itemData(
            self.searchType.currentIndex()).toInt()[0]
        search_text = unicode(self.search_edit.text())
        if search_text == u"":
            self.log_list.set_search(None, None)
        elif role == self.FilterIdRole:
            self.log_list.set_search(None, None)
            self.log_list.select_revid(search_text)
        elif role == self.FilterRevnoRole:
            self.log_list.set_search(None, None)
            try:
                revno = tuple(
                    (int(number) for number in search_text.split('.')))
            except ValueError:
                revno = ()
                # Not sure what to do if there is an error. Nothing for now
            if revno in gv.revno_rev:
                rev = gv.revno_rev[revno]
                index = self.log_list.log_model.index_from_rev(rev)
                self.log_list.setCurrentIndex(index)
        else:
            if role == self.FilterMessageRole:
                field = "message"
            elif role == self.FilterAuthorRole:
                field = "author"
            elif role == self.FilterSearchRole:
                field = "index"
            elif role == self.FilterTagRole:
                field = 'tag'
            elif role == self.FilterBugRole:
                field = 'bug'
            else:
                raise Exception("Not done")

            self.log_list.set_search(search_text, field)

        self.log_list.scrollTo(self.log_list.currentIndex())
        # Scroll to ensure the selection is on screen.

    @ui_current_widget
    def update_search_completer(self, text):
        gv = self.log_list.log_model.graph_viz
        # We only load the suggestions a letter at a time when needed.
        term = unicode(text).split(" ")[-1]
        if term:
            first_letter = term[0]
        else:
            first_letter = ""

        if first_letter != self.suggestion_last_first_letter:
            self.suggestion_last_first_letter = first_letter
            if first_letter not in self.suggestion_letters_loaded:
                suggestions = set()
                indexes = [
                    bi.index for bi in gv.branches if bi.index is not None
                ]
                for index in indexes:
                    for s in index.suggest(((first_letter, ), )):
                        #if suggestions.count() % 100 == 0:
                        #    QtCore.QCoreApplication.processEvents()
                        suggestions.add(s[0])
                suggestions = QtCore.QStringList(list(suggestions))
                suggestions.sort()
                self.suggestion_letters_loaded[first_letter] = suggestions
            else:
                suggestions = self.suggestion_letters_loaded[first_letter]
            self.completer_model.setStringList(suggestions)

    def updateSearchType(self, index=None):
        self.update_search()

    def set_search_timer(self):
        self.search_timer.start(200)

    def _locations_for_title(self, locations):
        if locations is None:
            return osutils.getcwd()
        else:
            from bzrlib.branch import Branch

            def title_for_location(location):
                if isinstance(location, basestring):
                    return url_for_display(location)
                if isinstance(location, Branch):
                    return url_for_display(location.base)
                return str(location)

            return ", ".join(title_for_location(l) for l in locations)
コード例 #2
0
ファイル: commit.py プロジェクト: biji/qbzr
class CommitWindow(SubProcessDialog):

    RevisionIdRole = QtCore.Qt.UserRole + 1
    ParentIdRole = QtCore.Qt.UserRole + 2

    def __init__(self,
                 tree,
                 selected_list,
                 dialog=True,
                 parent=None,
                 local=None,
                 message=None,
                 ui_mode=True):
        super(CommitWindow, self).__init__(gettext("Commit"),
                                           name="commit",
                                           default_size=(540, 540),
                                           ui_mode=ui_mode,
                                           dialog=dialog,
                                           parent=parent)
        self.tree = tree
        self.ci_data = QBzrCommitData(tree=tree)
        self.ci_data.load()

        self.is_bound = bool(tree.branch.get_bound_location())
        self.has_pending_merges = len(tree.get_parent_ids()) > 1

        if self.has_pending_merges and selected_list:
            raise errors.CannotCommitSelectedFileMerge(selected_list)

        self.windows = []
        self.initial_selected_list = selected_list

        self.connect(self.process_widget, QtCore.SIGNAL("failed(QString)"),
                     self.on_failed)

        self.throbber = ThrobberWidget(self)

        # commit to branch location
        branch_groupbox = QtGui.QGroupBox(gettext("Branch"), self)
        branch_layout = QtGui.QGridLayout(branch_groupbox)
        self.branch_location = QtGui.QLineEdit()
        self.branch_location.setReadOnly(True)
        #
        branch_base = url_for_display(tree.branch.base)
        master_branch = url_for_display(tree.branch.get_bound_location())
        if not master_branch:
            self.branch_location.setText(branch_base)
            branch_layout.addWidget(self.branch_location, 0, 0, 1, 2)
        else:
            self.local_checkbox = QtGui.QCheckBox(gettext("&Local commit"))
            self.local_checkbox.setToolTip(
                gettext("Local commits are not pushed to the master branch "
                        "until a normal commit is performed"))
            branch_layout.addWidget(self.local_checkbox, 0, 0, 1, 2)
            branch_layout.addWidget(self.branch_location, 1, 0, 1, 2)
            branch_layout.addWidget(QtGui.QLabel(gettext('Description:')), 2,
                                    0,
                                    QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
            self.commit_type_description = QtGui.QLabel()
            self.commit_type_description.setWordWrap(True)
            branch_layout.addWidget(self.commit_type_description, 2, 1)
            branch_layout.setColumnStretch(1, 10)
            self.connect(self.local_checkbox,
                         QtCore.SIGNAL("stateChanged(int)"),
                         self.update_branch_groupbox)
            if local:
                self.local_checkbox.setChecked(True)
            self.update_branch_groupbox()

        self.not_uptodate_errors = {
            'BoundBranchOutOfDate':
            gettext(
                'Local branch is out of date with master branch.\n'
                'To commit to master branch, update the local branch.\n'
                'You can also pass select local to commit to continue working disconnected.'
            ),
            'OutOfDateTree':
            gettext(
                'Working tree is out of date. To commit, update the working tree.'
            )
        }
        self.not_uptodate_info = InfoWidget(branch_groupbox)
        not_uptodate_layout = QtGui.QHBoxLayout(self.not_uptodate_info)

        # XXX this is to big. Resize
        not_uptodate_icon = QtGui.QLabel()
        not_uptodate_icon.setPixmap(self.style().standardPixmap(
            QtGui.QStyle.SP_MessageBoxWarning))
        not_uptodate_layout.addWidget(not_uptodate_icon)

        self.not_uptodate_label = QtGui.QLabel('error message goes here')
        not_uptodate_layout.addWidget(self.not_uptodate_label, 2)

        update_button = QtGui.QPushButton(gettext('Update'))
        self.connect(update_button, QtCore.SIGNAL("clicked(bool)"),
                     self.open_update_win)

        not_uptodate_layout.addWidget(update_button)

        self.not_uptodate_info.hide()
        branch_layout.addWidget(self.not_uptodate_info, 3, 0, 1, 2)

        splitter = QtGui.QSplitter(QtCore.Qt.Vertical, self)

        message_groupbox = QtGui.QGroupBox(gettext("Message"), splitter)
        splitter.addWidget(message_groupbox)
        self.tabWidget = QtGui.QTabWidget()
        splitter.addWidget(self.tabWidget)
        splitter.setStretchFactor(0, 1)
        splitter.setStretchFactor(1, 8)

        grid = QtGui.QGridLayout(message_groupbox)

        self.show_nonversioned_checkbox = QtGui.QCheckBox(
            gettext("Show non-versioned files"))
        show_nonversioned = get_qbzr_config().get_option_as_bool(
            self._window_name + "_show_nonversioned")
        if show_nonversioned:
            self.show_nonversioned_checkbox.setChecked(QtCore.Qt.Checked)
        else:
            self.show_nonversioned_checkbox.setChecked(QtCore.Qt.Unchecked)

        self.filelist = TreeWidget(self)
        self.filelist.throbber = self.throbber
        if show_nonversioned:
            self.filelist.tree_model.set_select_all_kind('all')
        else:
            self.filelist.tree_model.set_select_all_kind('versioned')

        self.file_words = {}
        self.connect(self.filelist.tree_model,
                     QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
                     self.on_filelist_data_changed)

        self.selectall_checkbox = SelectAllCheckBox(self.filelist, self)
        self.selectall_checkbox.setCheckState(QtCore.Qt.Checked)

        language = get_global_config().get_user_option(
            'spellcheck_language') or 'en'
        spell_checker = SpellChecker(language)

        # Equivalent for 'bzr commit --message'
        self.message = TextEdit(spell_checker,
                                message_groupbox,
                                main_window=self)
        self.message.setToolTip(gettext("Enter the commit message"))
        self.connect(self.message, QtCore.SIGNAL("messageEntered()"),
                     self.do_accept)
        self.completer = QtGui.QCompleter()
        self.completer_model = QtGui.QStringListModel(self.completer)
        self.completer.setModel(self.completer_model)
        self.message.setCompleter(self.completer)
        self.message.setAcceptRichText(False)

        SpellCheckHighlighter(self.message.document(), spell_checker)

        grid.addWidget(self.message, 0, 0, 1, 2)

        # Equivalent for 'bzr commit --fixes'
        self.bugsCheckBox = QtGui.QCheckBox(gettext("&Fixed bugs:"))
        self.bugsCheckBox.setToolTip(
            gettext("Set the IDs of bugs fixed by "
                    "this commit"))
        self.bugs = QtGui.QLineEdit()
        self.bugs.setToolTip(
            gettext("Enter the list of bug IDs in format "
                    "<i>tag:id</i> separated by a space, "
                    "e.g. <i>project:123 project:765</i>"))
        self.bugs.setEnabled(False)
        self.connect(self.bugsCheckBox, QtCore.SIGNAL("stateChanged(int)"),
                     self.enableBugs)
        grid.addWidget(self.bugsCheckBox, 1, 0)
        grid.addWidget(self.bugs, 1, 1)

        # Equivalent for 'bzr commit --author'
        self.authorCheckBox = QtGui.QCheckBox(gettext("&Author:"))
        self.authorCheckBox.setToolTip(
            gettext("Set the author of this change,"
                    " if it's different from the committer"))
        self.author = QtGui.QLineEdit()
        self.author.setToolTip(
            gettext("Enter the author's name, "
                    "e.g. <i>John Doe &lt;[email protected]&gt;</i>"))
        self.author.setEnabled(False)
        self.connect(self.authorCheckBox, QtCore.SIGNAL("stateChanged(int)"),
                     self.enableAuthor)
        grid.addWidget(self.authorCheckBox, 2, 0)
        grid.addWidget(self.author, 2, 1)
        # default author from config
        config = self.tree.branch.get_config()
        self.default_author = config.username()
        self.custom_author = ''
        self.author.setText(self.default_author)

        # Display the list of changed files
        files_tab = QtGui.QWidget()
        self.tabWidget.addTab(files_tab, gettext("Changes"))

        vbox = QtGui.QVBoxLayout(files_tab)
        vbox.addWidget(self.filelist)
        self.connect(self.show_nonversioned_checkbox,
                     QtCore.SIGNAL("toggled(bool)"), self.show_nonversioned)
        vbox.addWidget(self.show_nonversioned_checkbox)

        vbox.addWidget(self.selectall_checkbox)

        # Display a list of pending merges
        if self.has_pending_merges:
            self.selectall_checkbox.setCheckState(QtCore.Qt.Checked)
            self.selectall_checkbox.setEnabled(False)
            self.pending_merges_list = PendingMergesList(
                self.processEvents, self.throbber, self)

            self.tabWidget.addTab(self.pending_merges_list,
                                  gettext("Pending Merges"))
            self.tabWidget.setCurrentWidget(self.pending_merges_list)

            # Pending-merge widget gets disabled as we are executing.
            QtCore.QObject.connect(self, QtCore.SIGNAL("disableUi(bool)"),
                                   self.pending_merges_list,
                                   QtCore.SLOT("setDisabled(bool)"))
        else:
            self.pending_merges_list = False

        self.process_panel = self.make_process_panel()
        self.tabWidget.addTab(self.process_panel, gettext("Status"))

        splitter.setStretchFactor(0, 3)

        vbox = QtGui.QVBoxLayout(self)
        vbox.addWidget(self.throbber)
        vbox.addWidget(branch_groupbox)
        vbox.addWidget(splitter)

        # Diff button to view changes in files selected to commit
        self.diffbuttons = DiffButtons(self)
        self.diffbuttons.setToolTip(
            gettext("View changes in files selected to commit"))
        self.connect(self.diffbuttons, QtCore.SIGNAL("triggered(QString)"),
                     self.show_diff_for_checked)

        self.refresh_button = StandardButton(BTN_REFRESH)
        self.connect(self.refresh_button, QtCore.SIGNAL("clicked()"),
                     self.refresh)

        hbox = QtGui.QHBoxLayout()
        hbox.addWidget(self.diffbuttons)
        hbox.addWidget(self.refresh_button)
        hbox.addWidget(self.buttonbox)
        vbox.addLayout(hbox)

        # groupbox and tabbox signals handling.
        for w in (message_groupbox, files_tab):
            # when operation started we need to disable widgets
            QtCore.QObject.connect(self, QtCore.SIGNAL("disableUi(bool)"), w,
                                   QtCore.SLOT("setDisabled(bool)"))

        self.restore_commit_data()
        if message:
            self.message.setText(message)

        # Try to be smart: if there is no saved message
        # then set focus on Edit Area; otherwise on OK button.
        if unicode(self.message.toPlainText()).strip():
            self.buttonbox.setFocus()
        else:
            self.message.setFocus()

    def show(self):
        # we show the bare form as soon as possible.
        SubProcessDialog.show(self)
        QtCore.QTimer.singleShot(1, self.load)

    def exec_(self):
        QtCore.QTimer.singleShot(1, self.load)
        return SubProcessDialog.exec_(self)

    @runs_in_loading_queue
    @ui_current_widget
    @reports_exception()
    def load(self, refresh=False):
        if refresh:
            self.throbber.show()
        self.refresh_button.setDisabled(True)
        try:
            self.tree.lock_read()
            try:
                if self.pending_merges_list:
                    self.pending_merges_list.load_tree(self.tree)
                    # Force the loading of the revisions, before we start
                    # loading the file list.
                    self.pending_merges_list._load_visible_revisions()
                    self.processEvents()

                self.filelist.tree_model.checkable = not self.pending_merges_list
                self.is_loading = True
                # XXX Would be nice if we could only load the files when the
                # user clicks on the changes tab, but that would mean that
                # we can't load the words list.
                if not refresh:
                    fmodel = self.filelist.tree_filter_model

                    want_unversioned = self.show_nonversioned_checkbox.isChecked(
                    )
                    fmodel.setFilter(fmodel.UNVERSIONED, want_unversioned)
                    if not want_unversioned and self.initial_selected_list:
                        # if there are any paths from the command line that
                        # are not versioned, we want_unversioned.
                        for path in self.initial_selected_list:
                            if not self.tree.path2id(path):
                                want_unversioned = True
                                break

                    self.filelist.set_tree(
                        self.tree,
                        branch=self.tree.branch,
                        changes_mode=True,
                        want_unversioned=want_unversioned,
                        initial_checked_paths=self.initial_selected_list,
                        change_load_filter=lambda c: not c.is_ignored())
                else:
                    self.filelist.refresh()
                self.is_loading = False
                self.processEvents()
                self.update_compleater_words()
            finally:
                self.tree.unlock()
        finally:
            self.throbber.hide()
            self.refresh_button.setDisabled(False)

    def refresh(self):
        self.load(True)

    def on_filelist_data_changed(self, start_index, end_index):
        self.update_compleater_words()

    def update_compleater_words(self):
        if self.is_loading:
            return

        num_files_loaded = 0

        words = set()
        for ref in self.filelist.tree_model.iter_checked():
            path = ref.path
            if path not in self.file_words:
                file_words = set()
                if num_files_loaded < MAX_AUTOCOMPLETE_FILES:
                    file_words.add(path)
                    file_words.add(os.path.split(path)[-1])
                    change = self.filelist.tree_model.inventory_data_by_path[
                        ref.path].change
                    if change and change.is_renamed():
                        file_words.add(change.oldpath())
                        file_words.add(os.path.split(change.oldpath())[-1])
                    #if num_versioned_files < MAX_AUTOCOMPLETE_FILES:
                    ext = file_extension(path)
                    builder = get_wordlist_builder(ext)
                    if builder is not None:
                        try:
                            abspath = os.path.join(self.tree.basedir, path)
                            file = open(abspath, 'rt')
                            file_words.update(builder.iter_words(file))
                            self.processEvents()
                        except EnvironmentError:
                            pass
                    self.file_words[path] = file_words
                    num_files_loaded += 1
            else:
                file_words = self.file_words[path]
            words.update(file_words)
        words = list(words)
        words.sort(key=lambda x: x.lower())
        self.completer_model.setStringList(words)

    def enableBugs(self, state):
        if state == QtCore.Qt.Checked:
            self.bugs.setEnabled(True)
            self.bugs.setFocus(QtCore.Qt.OtherFocusReason)
        else:
            self.bugs.setEnabled(False)

    def enableAuthor(self, state):
        if state == QtCore.Qt.Checked:
            self.author.setEnabled(True)
            self.author.setText(self.custom_author)
            self.author.setFocus(QtCore.Qt.OtherFocusReason)
        else:
            self.author.setEnabled(False)
            self.custom_author = self.author.text()
            self.author.setText(self.default_author)

    def restore_commit_data(self):
        message = self.ci_data['message']
        if message:
            self.message.setText(message)
        bug = self.ci_data['bugs']
        if bug:
            self.bugs.setText(bug)
            self.bugs.setEnabled(True)
            self.bugsCheckBox.setChecked(True)

    def save_commit_data(self):
        if (self.tree.branch.get_physical_lock_status()
                or self.tree.branch.is_locked()):
            # XXX maybe show this in a GUI MessageBox (information box)???
            from bzrlib.trace import warning
            warning("Cannot save commit data because the branch is locked.")
            return
        # collect data
        ci_data = QBzrCommitData(tree=self.tree)
        message = unicode(self.message.toPlainText()).strip()
        if message:
            ci_data['message'] = message
        bug_str = ''
        if self.bugsCheckBox.isChecked():
            bug_str = unicode(self.bugs.text()).strip()
        if bug_str:
            ci_data['bugs'] = bug_str
        # save only if data different
        if not ci_data.compare_data(self.ci_data, all_keys=False):
            ci_data.save()

    def wipe_commit_data(self):
        if (self.tree.branch.get_physical_lock_status()
                or self.tree.branch.is_locked()):
            # XXX maybe show this in a GUI MessageBox (information box)???
            from bzrlib.trace import warning
            warning("Cannot wipe commit data because the branch is locked.")
            return
        self.ci_data.wipe()

    def _get_message(self):
        return unicode(self.message.toPlainText()).strip()

    def _get_selected_files(self):
        """Return (has_files_to_commit[bool], files_to_commit[list], files_to_add[list])"""
        if self.has_pending_merges:
            return True, [], []

        files_to_commit = []
        files_to_add = []
        for ref in self.filelist.tree_model.iter_checked():
            if ref.file_id is None:
                files_to_add.append(ref.path)
            files_to_commit.append(ref.path)

        if not files_to_commit:
            return False, [], []
        else:
            return True, files_to_commit, files_to_add

    def validate(self):
        if not self._get_message():
            self.operation_blocked(
                gettext("You should provide a commit message."))
            self.message.setFocus()
            return False
        if not self._get_selected_files()[0]:
            if not self.ask_confirmation(
                    gettext("No changes selected to commit.\n"
                            "Do you want to commit anyway?")):
                return False
        return True

    def do_start(self):
        args = ["commit"]

        message = self._get_message()
        args.extend(['-m',
                     message])  # keep them separated to avoid bug #297606

        has_files_to_commit, files_to_commit, files_to_add = self._get_selected_files(
        )
        if not has_files_to_commit:
            # Possible [rare] problems:
            # 1. unicode tree root in non-user encoding
            #    may provoke UnicodeEncodeError in subprocess (@win32)
            # 2. if branch has no commits yet then operation may fail
            #    because of bug #299879
            args.extend(['--exclude', self.tree.basedir])
            args.append('--unchanged')
        else:
            args.extend(files_to_commit)

        if self.bugsCheckBox.isChecked():
            for s in unicode(self.bugs.text()).split():
                args.append(("--fixes=%s" % s))

        if self.authorCheckBox.isChecked():
            args.append(("--author=%s" % unicode(self.author.text())))

        if self.is_bound and self.local_checkbox.isChecked():
            args.append("--local")

        dir = self.tree.basedir
        commands = []
        if files_to_add:
            commands.append((dir, ["add", "--no-recurse"] + files_to_add))
        commands.append((dir, args))

        self.tabWidget.setCurrentWidget(self.process_panel)
        self.process_widget.start_multi(commands)

    def show_nonversioned(self, state):
        """Show/hide non-versioned files."""
        if state and not self.filelist.want_unversioned:
            state = self.filelist.get_state()
            self.filelist.set_tree(
                self.tree,
                changes_mode=True,
                want_unversioned=True,
                change_load_filter=lambda c: not c.is_ignored())
            self.filelist.restore_state(state)

        if state:
            self.filelist.tree_model.set_select_all_kind('all')
        else:
            self.filelist.tree_model.set_select_all_kind('versioned')

        fmodel = self.filelist.tree_filter_model
        fmodel.setFilter(fmodel.UNVERSIONED, state)

    def _save_or_wipe_commit_data(self):
        if not self.process_widget.is_running():
            if self.process_widget.finished:
                self.wipe_commit_data()
            else:
                self.save_commit_data()

    def closeEvent(self, event):
        self._save_or_wipe_commit_data()
        qbzr_config = get_qbzr_config()
        qbzr_config.set_option(self._window_name + "_show_nonversioned",
                               self.show_nonversioned_checkbox.isChecked())
        qbzr_config.save()  # do I need this or is .saveSize() enough?
        return SubProcessDialog.closeEvent(self, event)

    def reject(self):
        self._save_or_wipe_commit_data()
        return SubProcessDialog.reject(self)

    def update_branch_groupbox(self):
        if not self.local_checkbox.isChecked():
            # commit to master branch selected
            loc = url_for_display(self.tree.branch.get_bound_location())
            desc = gettext("A commit will be made directly to "
                           "the master branch, keeping the local "
                           "and master branches in sync.")
        else:
            # local commit selected
            loc = url_for_display(self.tree.branch.base)
            desc = gettext("A local commit to the branch will be performed. "
                           "The master branch will not be updated until "
                           "a non-local commit is made.")
        # update GUI
        self.branch_location.setText(loc)
        self.commit_type_description.setText(desc)

    def show_diff_for_checked(self, ext_diff=None, dialog_action='commit'):
        """Diff button clicked: show the diff for checked entries.

        @param  ext_diff:       selected external diff tool (if any)
        @param  dialog_action:  purpose of parent window (main action)
        """
        # XXX make this function universal for both qcommit and qrevert (?)
        if self.filelist.tree_model.checkable:
            checked = []  # checked versioned
            unversioned = []  # checked unversioned (supposed to be added)
            for ref in self.filelist.tree_model.iter_checked():
                if ref.file_id:
                    checked.append(ref.path)
                else:
                    unversioned.append(ref.path)

            if checked:
                arg_provider = InternalWTDiffArgProvider(
                    self.tree.basis_tree().get_revision_id(),
                    self.tree,
                    self.tree.branch,
                    self.tree.branch,
                    specific_files=checked)

                show_diff(arg_provider,
                          ext_diff=ext_diff,
                          parent_window=self,
                          context=self.filelist.diff_context)
            else:
                msg = "No changes selected to " + dialog_action
                QtGui.QMessageBox.warning(self, "QBzr - " + gettext("Diff"),
                                          gettext(msg), QtGui.QMessageBox.Ok)

            if unversioned:
                # XXX show infobox with message that not all files shown in diff
                pass
        else:
            arg_provider = InternalWTDiffArgProvider(
                self.tree.basis_tree().get_revision_id(), self.tree,
                self.tree.branch, self.tree.branch)
            show_diff(arg_provider,
                      ext_diff=ext_diff,
                      parent_window=self,
                      context=self.filelist.diff_context)

    def on_failed(self, error):
        SubProcessDialog.on_failed(self, error)
        error = str(error)
        if error in self.not_uptodate_errors:
            self.not_uptodate_label.setText(self.not_uptodate_errors[error])
            self.not_uptodate_info.show()

    def open_update_win(self, b):
        update_window = QBzrUpdateWindow(self.tree)
        self.windows.append(update_window)
        update_window.show()
        QtCore.QObject.connect(update_window,
                               QtCore.SIGNAL("subprocessFinished(bool)"),
                               self.not_uptodate_info,
                               QtCore.SLOT("setHidden(bool)"))